| """ |
| Network-free tests for the executor seam (ADR-0007 Phase 0, |
| `src/managers/execution/base.py`). |
| |
| Covers executor SELECTION via the EXECUTOR env var (in_process | sandbox + |
| unknown), that the default factory returns an in-process executor conforming to |
| the `Executor` protocol, that it round-trips a trivial `execute(...)`, that the |
| `sandbox` value returns a `SandboxedExecutor` (ADR-0007 Phase 1 — was a |
| NotImplementedError stub in Phase 0), and that an unknown value raises ValueError. |
| |
| The factory dispatch / error paths need no heavy deps. The round-trip touches |
| `PythonExecutor.reset_environment`, which imports pandas/numpy — guarded with |
| `importorskip` so the dispatch/error assertions still run where those are absent. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import importlib.util |
| import sys |
| import types |
| from pathlib import Path |
|
|
| import pytest |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| _EXEC_DIR = Path(__file__).parent.parent / "src" / "managers" / "execution" |
| _ALIAS = "_seam_mgr" |
|
|
|
|
| def _load_execution_package(): |
| for name, path in ((_ALIAS, []), (f"{_ALIAS}.execution", [str(_EXEC_DIR)])): |
| if name not in sys.modules: |
| pkg = types.ModuleType(name) |
| pkg.__path__ = path |
| sys.modules[name] = pkg |
| |
| |
| |
| return ( |
| importlib.import_module(f"{_ALIAS}.execution.base"), |
| importlib.import_module(f"{_ALIAS}.execution.python_executor"), |
| ) |
|
|
|
|
| _base, _py = _load_execution_package() |
| Executor = _base.Executor |
| get_executor = _base.get_executor |
| PythonExecutor = _py.PythonExecutor |
|
|
|
|
| |
| |
| |
| def test_executor_protocol_surface(): |
| """The Executor protocol captures exactly the current PythonExecutor surface.""" |
| for method in ("reset_environment", "send_functions", "send_variables", "execute", "__call__"): |
| assert hasattr(Executor, method), f"Executor missing {method}" |
|
|
|
|
| def test_python_executor_conforms_structurally(): |
| """PythonExecutor conforms to the runtime_checkable Executor protocol.""" |
| assert issubclass(PythonExecutor, Executor) |
|
|
|
|
| def test_seam_is_reexported_from_init_files(): |
| """Executor / get_executor are wired into the package __init__ exports. |
| |
| Asserted by reading the __init__ source (not importing it) so the check |
| stays free of the heavy `managers` import chain (langchain_core, rich, …). |
| """ |
| exec_init = (_EXEC_DIR / "__init__.py").read_text() |
| assert "get_executor" in exec_init and "Executor" in exec_init |
|
|
| managers_init = (_EXEC_DIR.parent / "__init__.py").read_text() |
| assert "get_executor" in managers_init and "Executor" in managers_init |
|
|
|
|
| |
| |
| |
| def test_default_is_in_process(monkeypatch): |
| """No EXECUTOR set → in_process → a PythonExecutor conforming to Executor.""" |
| monkeypatch.delenv("EXECUTOR", raising=False) |
| ex = get_executor() |
| assert isinstance(ex, PythonExecutor) |
| assert isinstance(ex, Executor) |
|
|
|
|
| def test_explicit_in_process_kind(): |
| """Explicit kind='in_process' returns a PythonExecutor regardless of env.""" |
| ex = get_executor("in_process") |
| assert isinstance(ex, PythonExecutor) |
|
|
|
|
| def test_env_selects_in_process(monkeypatch): |
| """EXECUTOR=in_process (case/space-insensitive) selects the in-process executor.""" |
| monkeypatch.setenv("EXECUTOR", " In_Process ") |
| assert isinstance(get_executor(), PythonExecutor) |
|
|
|
|
| def test_sandbox_selects_sandboxed_executor(monkeypatch): |
| """EXECUTOR=sandbox now returns a SandboxedExecutor (ADR-0007 Phase 1 landed). |
| |
| Was a NotImplementedError stub in Phase 0. With the subprocess launcher (so no |
| Docker is needed) the factory constructs the real client — verified by class |
| name so this seam test stays free of the sandbox package's own imports. No |
| kernel is started here (construction is lazy; start happens on first call). |
| """ |
| monkeypatch.setenv("EXECUTOR", "sandbox") |
| monkeypatch.setenv("SANDBOX_LAUNCHER", "subprocess") |
| ex = get_executor() |
| assert type(ex).__name__ == "SandboxedExecutor" |
| assert isinstance(ex, Executor) |
| |
| ex2 = get_executor("sandbox") |
| assert type(ex2).__name__ == "SandboxedExecutor" |
|
|
|
|
| def test_unknown_value_raises_value_error(monkeypatch): |
| """An unknown EXECUTOR value fails loud with ValueError.""" |
| monkeypatch.setenv("EXECUTOR", "wasm") |
| with pytest.raises(ValueError) as exc: |
| get_executor() |
| assert "wasm" in str(exc.value) |
|
|
|
|
| |
| |
| |
| def test_in_process_round_trip(): |
| """The default executor executes code and returns captured stdout.""" |
| pytest.importorskip("pandas") |
| pytest.importorskip("numpy") |
| ex = get_executor("in_process") |
| assert ex.execute("print(1 + 1)") == "2" |
| |
| assert ex("print('hi')") == "hi" |
| |
| assert ex.execute("x = 5") == "Code executed successfully" |
|
|
|
|
| def test_in_process_namespace_persists(): |
| """State persists across calls (Jupyter-kernel semantics).""" |
| pytest.importorskip("pandas") |
| pytest.importorskip("numpy") |
| ex = get_executor("in_process") |
| ex.send_variables({"seed": 41}) |
| assert ex.execute("print(seed + 1)") == "42" |
|
|