test(backend): cover agent orchestration + sandbox lifecycle (#115) - #155
test(backend): cover agent orchestration + sandbox lifecycle (#115)#155lucastononro wants to merge 2 commits into
Conversation
Adds focused unit tests for the two most critical, near-untested modules in the agent runtime, with the Modal boundary faked throughout: - test_sandbox.py: run_code's success/timeout/exception paths against a faked modal.Sandbox (create/stdout/stderr/wait) — asserts span attributes, metric/log-event dispatch routing, and usage recording per path. - test_runner.py: run_agent's reachable orchestration state machine (spawn -> run -> complete/error/cancel) and its interplay with the real task registry (register_task/abort_agent/is_agent_running/ cleanup_session), using real asyncio Tasks for the registry tests so cancellation semantics are genuine. _drive_provider is faked — its internals are already covered by test_drive_provider.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
- Add autouse fixture clearing module-level task-registry state (_running_tasks, _silent_aborts, _session_task_locks) so a mid-flight test failure can't leak entries across tests. - Explicitly await the stale task's cancellation in the swap test instead of relying on the event loop having delivered CancelledError during new_task's run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
Greptile review findings addressed (befc75f)Fixed (2/2):
Dismissed: none. Validation: full backend suite |
|
Merged into integration branch |
Summary
backend/tests/test_sandbox.py— unit tests forrun_code's success/timeout/exception paths, withmodal.Sandboxfaked end-to-end (create/stdout/stderr/wait).backend/tests/test_runner.py— unit tests forrun_agent's reachable orchestration state machine (spawn → run → complete/error/cancel) and its interplay with the real task registry (register_task/abort_agent/is_agent_running/cleanup_session) inservices/agent/tasks.py.Changes
test_sandbox.pyfakesmodal.Sandbox(a_FakeSandbox/_FakeStreampair mimicking the real async stdout/stderr streams) andsandbox_span(a recording fake context manager), then exercisesservices/sandbox.py::run_codefor real:stdout/stderr/returncode, span attributes (sandbox.code_chars,sandbox.returncode,sandbox.elapsed_s, noerror), cleanrecord_sandbox_usage(is_error=False).parse_stdout_linerouting — a JSON metrics line goes topersist_and_publish, plain text doesn't; nostage→ no dispatch at all.run_codemust not raise, but must flagrecord_sandbox_usage(is_error=True)and tag the span witherror=True.error=True, exception is re-raised, usage is never recorded.modal.Sandbox.createunchanged.test_runner.pyfakes only_drive_provider(the LLM/provider boundary — its internals are already covered in depth by the existingtest_drive_provider.py) and drivesrun_agentfor real against a real in-memory SQLite DB:{stage}_runningthen{stage}_done,publish_artifacts/post_stage_hookcalled, text returned.TimeoutError→ contained (no raise),agent_timeoutevent +timed_outterminal state, post-run hooks skipped.Exception→agent_errorevent +failedstate, re-raised (unlike Timeout/CancelledError).CancelledError→ contained, gated by the_silent_abortsset (abort_agent(..., silent=True)'s "quiet swap" path fromrouters/sessions.py), one-shot consumed.asyncio.Tasks (not mocks) wrappingrun_agent, wired through the realregister_task/abort_agent/is_agent_runningto verify the task-registry bookkeeping end-to-end for both the complete and the abort cycle, plus a stale-task-swap test for the "followup message while still running" case.Test plan
311 passed, 8 skipped(was296 passed, 8 skippedbefore this PR; the 15 new tests account for the delta, no regressions).test_sandbox.py, 8 tests intest_runner.py, all passing and re-run multiple times for scheduling-order stability (asyncio interleaving is inherently order-sensitive; verified deterministic).ruff check/ruff format --checkclean on both new files.is_error=(sb.returncode != 0)tois_error=False(i.e. stop tagging non-zero exits as errors) inservices/sandbox.py::run_code→test_run_code_nonzero_returncode_marks_error_but_does_not_raisefails immediately (usage_kwargs["is_error"]assertion). Similarly, removing theraiseinrun_agent's genericexcept Exceptionbranch →test_run_agent_exception_marks_failed_and_reraises'spytest.raises(RuntimeError, ...)fails.Closes #115
🤖 Generated with Claude Code
Greptile Summary
This PR adds 15 new unit tests (7 in
test_sandbox.py, 8 intest_runner.py) covering the two previously near-untested critical paths in the agent runtime: Modal sandbox lifecycle and the agent orchestration state machine.test_sandbox.pyintroduces_FakeSandbox/_FakeStream/_FakeSpanCtxfakes and exercisesrun_codeend-to-end across success, non-zero returncode, streaming dispatch, sandbox creation failure, and mid-stream stdout failure paths — asserting on span attributes, usage recording flags, and re-raise semantics.test_runner.pyfakes only_drive_providerand drivesrun_agentagainst a real in-memory SQLite DB; includes realasyncio.Taskregistry tests (spawn/complete, spawn/abort, stale-task swap) and addresses both concerns raised in the prior review round — an autouse fixture now explicitly clears module-level registry state, and the stale-task cancellation is awaited withasyncio.wait_forrather than relying on event-loop scheduling order.Confidence Score: 5/5
Test-only PR; no production code is modified. All new test logic is internally consistent with the source under test.
Both new test files exercise real code paths with well-constructed fakes. The
_FakeStreamcorrectly usesasyncio.sleep(0)to reflect real Modal stream interleaving so the stderr drain task gets scheduled. The_drive_providerpatch works on the correct module namespace. The autouse_clear_task_registryfixture and the explicitasyncio.wait_foron stale-task cancellation both address the issues flagged in the prior review. No production code changes, and the test assertions map directly to the behaviour enforced by the source.No files require special attention.
Important Files Changed
run_code's sandbox lifecycle; fakesmodal.Sandbox.create, stdout/stderr streams, andsandbox_span. Covers success, streaming/dispatch, non-zero returncode, create failure, and mid-stream stdout failure paths with correct assertions on span attributes, usage recording, and re-raise semantics.run_agent's orchestration state machine and task registry; only fakes_drive_providerand post-run hooks, driving everything else (DB, event publishing, task registry) for real. Includes autouse registry-cleanup and real-asyncio-Task tests for spawn/complete/abort/swap cycles.Reviews (2): Last reviewed commit: "fix(review): address Greptile findings o..." | Re-trigger Greptile