mirror of
https://github.com/brunopostle/ifcurl.git
synced 2026-07-12 10:18:14 +00:00
All ifcopenshell calls (from_string, filter_elements, geom.iterator) now run inside a short-lived child process via run_sandboxed(). A SIGSEGV or SIGABRT in the child returns HTTP 422 instead of killing the service worker. Configurable timeout (IFCURL_SANDBOX_TIMEOUT, default 120s) and optional resource limits (IFCURL_SANDBOX_MEMORY_MB, IFCURL_SANDBOX_CPU_SECS). The T3 GUID cache still works: the sandboxed pipeline returns resolved GUIDs on selector miss so the parent can populate the cache; on hit the parent passes cached GUIDs and the child converts them to step IDs. Tests use a sync_sandbox fixture that runs run_sandboxed synchronously so mock patches remain visible; four dedicated sandbox tests cover normal return, exception propagation, segfault detection, and timeout. Closes ifcurl-vm5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 lines
1.1 KiB
Python
50 lines
1.1 KiB
Python
"""Tests for ifcurl.sandbox — subprocess isolation for ifcopenshell calls."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ctypes
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from ifcurl.sandbox import (
|
|
SandboxCrashError,
|
|
SandboxTimeoutError,
|
|
run_sandboxed,
|
|
)
|
|
|
|
|
|
def _return_value(x):
|
|
return x
|
|
|
|
|
|
def _raise_value_error(msg):
|
|
raise ValueError(msg)
|
|
|
|
|
|
def _segfault():
|
|
ctypes.string_at(0) # dereference NULL → SIGSEGV
|
|
|
|
|
|
def _sleep_forever():
|
|
import time
|
|
time.sleep(9999)
|
|
|
|
|
|
class TestRunSandboxed:
|
|
def test_returns_value(self):
|
|
assert run_sandboxed(_return_value, 42) == 42
|
|
|
|
def test_propagates_exception(self):
|
|
with pytest.raises(ValueError, match="bad input"):
|
|
run_sandboxed(_raise_value_error, "bad input")
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="SIGSEGV test requires Unix")
|
|
def test_crash_raises_sandbox_crash_error(self):
|
|
with pytest.raises(SandboxCrashError):
|
|
run_sandboxed(_segfault)
|
|
|
|
def test_timeout_raises_sandbox_timeout_error(self):
|
|
with pytest.raises(SandboxTimeoutError):
|
|
run_sandboxed(_sleep_forever, timeout=1)
|