test+docs(sandbox): add FR3 invariant regression tests + tech-stack note
This commit is contained in:
@@ -86,6 +86,12 @@
|
||||
- **Thread-Local Context Isolation:** Utilizes `threading.local()` for managing per-thread AI client context (e.g., source tier tagging), ensuring thread safety during concurrent multi-agent execution.
|
||||
- **Asynchronous Tool Execution Engine:** Refactored MCP tool dispatch and AI client loops to use `asyncio.gather` and `asyncio.to_thread`, enabling parallel execution of independent tool calls within a single AI turn to reduce latency.
|
||||
|
||||
## pyproject.toml pytest addopts (added 2026-06-19, per test_sandbox_hardening_20260619)
|
||||
|
||||
`[tool.pytest.ini_options].addopts = "--basetemp=tests/artifacts/_pytest_tmp"`.
|
||||
|
||||
**Rationale:** Per `conductor/code_styleguides/workspace_paths.md`, ALL test infrastructure paths must live under `./tests/`. pytest's `tmp_path` and `tmp_path_factory` fixtures default to `%TEMP%\pytest-of-<user>\` on Windows. This `addopts` redirects them under `./tests/` so the FR1 runtime guard's allowlist (also `./tests/`) is a single rule.
|
||||
|
||||
## Architectural Patterns
|
||||
|
||||
- **Centralized Registry Management:** Consolidation of critical application constants (e.g., `PROVIDERS`, `AGENT_TOOL_NAMES`) into `src/models.py` as a single source of truth, eliminating redundant list definitions across the UI and Controller.
|
||||
|
||||
@@ -191,4 +191,58 @@ def test_sloppy_py_parses_config_flag() -> None:
|
||||
if isinstance(func, ast.Name) and func.id == "set_config_override":
|
||||
found_set_override_call = True
|
||||
assert found_config_arg, "sloppy.py must have a --config argparse argument"
|
||||
assert found_set_override_call, "sloppy.py must call paths.set_config_override(args.config)"
|
||||
assert found_set_override_call, "sloppy.py must call paths.set_config_override(args.config)"
|
||||
|
||||
|
||||
def test_pyproject_toml_basetemp_is_under_tests() -> None:
|
||||
"""pyproject.toml contains --basetemp=tests/artifacts/_pytest_tmp."""
|
||||
pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
||||
text = pyproject.read_text(encoding="utf-8")
|
||||
assert "--basetemp=tests/artifacts/_pytest_tmp" in text, (
|
||||
"pyproject.toml must set addopts = '--basetemp=tests/artifacts/_pytest_tmp' "
|
||||
"so the FR1 runtime guard's allowlist can be a single rule."
|
||||
)
|
||||
|
||||
|
||||
def test_isolate_workspace_does_not_use_tmp_path_factory_for_infra() -> None:
|
||||
"""isolate_workspace fixture does not use tmp_path_factory.mktemp."""
|
||||
import ast
|
||||
conftest = Path(__file__).resolve().parent / "conftest.py"
|
||||
tree = ast.parse(conftest.read_text(encoding="utf-8"))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.FunctionDef) and node.name == "isolate_workspace":
|
||||
body = node.body
|
||||
if body and isinstance(body[0], ast.Expr) and isinstance(body[0].value, ast.Constant):
|
||||
body = body[1:]
|
||||
body_src = "\n".join(ast.unparse(stmt) for stmt in body)
|
||||
assert "tmp_path_factory.mktemp" not in body_src, (
|
||||
"isolate_workspace must not use tmp_path_factory.mktemp; "
|
||||
"use _ISOLATION_WORKSPACE under tests/artifacts/ instead."
|
||||
)
|
||||
assert "_ISOLATION_WORKSPACE" in body_src, (
|
||||
"isolate_workspace should reference _ISOLATION_WORKSPACE"
|
||||
)
|
||||
return
|
||||
raise AssertionError("isolate_workspace fixture not found in conftest.py")
|
||||
|
||||
|
||||
def test_appcontroller_init_does_not_load_config() -> None:
|
||||
"""AppController.__init__ must not call init_state() or load_config() —
|
||||
fixtures apply before App.__init__; loading config in AppController.__init__
|
||||
would race against the autouse isolate_workspace."""
|
||||
import ast
|
||||
app_controller = Path(__file__).resolve().parent.parent / "src" / "app_controller.py"
|
||||
tree = ast.parse(app_controller.read_text(encoding="utf-8"))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.FunctionDef) and node.name == "__init__":
|
||||
src = ast.unparse(node)
|
||||
assert "init_state()" not in src, (
|
||||
"AppController.__init__ must not call init_state() "
|
||||
"(this would trigger config reads before fixtures apply)"
|
||||
)
|
||||
assert "load_config()" not in src, (
|
||||
"AppController.__init__ must not call load_config() "
|
||||
"(this would trigger config reads before fixtures apply)"
|
||||
)
|
||||
return
|
||||
raise AssertionError("AppController.__init__ not found")
|
||||
Reference in New Issue
Block a user