import contextlib from datetime import datetime, timezone import io import json import os from pathlib import Path import sys import tempfile import unittest from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parent)) import analyze_scheduler_timings as timing import run_freecad_web_scheduler as scheduler import scheduler_maintenance as maintenance class StepTimingTests(unittest.TestCase): def event(self, seconds, event_type, item=None): return {"schedulerObservedAt": datetime.fromtimestamp(seconds, timezone.utc).isoformat(), "type": event_type, **({"item": item} if item else {})} def test_waiting_is_not_reported_as_command_execution(self): with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "cycle-000001.log" command = {"id": "one", "type": "command_execution", "command": "cat source.py"} events = [self.event(0, "scheduler.cycle.started"), self.event(65, "item.started", command), self.event(65.01, "item.completed", {**command, "exit_code": 0}), self.event(131, "scheduler.cycle.completed")] path.write_text("\n".join(json.dumps(e) for e in events)) result = timing.detailed_steps(path) self.assertAlmostEqual(result["toolUnionSeconds"], 0.01) self.assertAlmostEqual(result["betweenEventSeconds"], 130.99) self.assertEqual(result["commands"][0]["waitBeforeSeconds"], 65) self.assertEqual(result["observedSeconds"], 131) def test_overlapping_commands_are_not_double_counted(self): with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "cycle.log" a = {"id": "a", "type": "command_execution", "command": "first"} b = {"id": "b", "type": "command_execution", "command": "second"} events = [self.event(0, "scheduler.cycle.started"), self.event(1, "item.started", a), self.event(2, "item.started", b), self.event(5, "item.completed", {**a, "exit_code": 0}), self.event(7, "item.completed", {**b, "exit_code": 1}), self.event(8, "scheduler.cycle.completed")] path.write_text("\n".join(json.dumps(e) for e in events)) result = timing.detailed_steps(path) self.assertEqual(result["toolUnionSeconds"], 6) self.assertEqual(result["betweenEventSeconds"], 2) self.assertEqual([r["runSeconds"] for r in result["commands"]], [4, 5]) self.assertEqual(result["unfinishedCommands"], 0) class ExplorationTests(unittest.TestCase): def test_budget_ignores_active_tools_edits_and_real_verification(self): reads = [{"phase": "source/read", "command": "cat tools/draft-business-cases.mjs"}] self.assertTrue(scheduler.exploration_expired(181, False, 0, reads, 180)) self.assertFalse(scheduler.exploration_expired(179, False, 0, reads, 180)) self.assertFalse(scheduler.exploration_expired(300, True, 0, reads, 180)) self.assertFalse(scheduler.exploration_expired(300, False, 1, reads, 180)) self.assertFalse(scheduler.exploration_expired(300, False, 0, [{"phase": "other", "command": "node tools/draft-business-cases.mjs"}], 180)) def test_runtime_business_retains_completed_reads_after_bounded_stop(self): self.assertTrue(scheduler.retain_turn_context(business_turn=True, feature_turn=True, pipeline_eligible=False, agent_successful=False, session_clean=False, retained_business_context=True)) def test_cumulative_exploration_stops_even_when_each_read_resets_idle(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) item = {"id": "read", "type": "command_execution", "command": "cat source.py"} events = [{"type": "turn.started"}, {"type": "item.started", "item": item}, {"type": "item.completed", "item": {**item, "exit_code": 0}}] script = "import sys,time\nsys.stdin.read()\n" + "\n".join(f"print({json.dumps(e)!r},flush=True)" for e in events) + "\ntime.sleep(20)\n" metadata = {} with patch.object(scheduler, "ROOT", root), patch.object(scheduler, "build_environment", return_value=dict(os.environ)), \ patch.object(scheduler, "load_failure_state", return_value={"failures": {}}), patch.object(scheduler, "update_timing_summary"), \ contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): result = scheduler.run_codex_turn([sys.executable, "-c", script], "fixture", 10, 1, "test", root / "cycle.log", lambda **v: None, turn_metadata=metadata, exploration_limit_seconds=0.2) self.assertTrue(result[4]) self.assertFalse(result[1]) self.assertTrue(metadata["explorationBudgetExceeded"]) self.assertEqual(metadata["commandsCompleted"], 1) def test_rejected_exploration_does_not_discard_following_edit(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) item = {"id": "read", "type": "command_execution", "command": "git status --short"} events = [{"type": "turn.started"}, {"type": "item.started", "item": item}, {"type": "item.completed", "item": {**item, "exit_code": 0}}, {"type": "item.completed", "item": {"type": "file_change", "changes": [{"path": "source.py", "kind": "update"}]}}, {"type": "turn.completed"}] script = "import sys\nsys.stdin.read()\n" + "\n".join(f"print({json.dumps(e)!r},flush=True)" for e in events) metadata = {} with patch.object(scheduler, "ROOT", root), patch.object(scheduler, "build_environment", return_value=dict(os.environ)), \ patch.object(scheduler, "load_failure_state", return_value={"failures": {}}), patch.object(scheduler, "update_timing_summary"), \ patch.object(scheduler, "record_unbuilt_edit"), patch.object(scheduler, "forbidden_implementation_command", return_value=True), \ contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): result = scheduler.run_codex_turn([sys.executable, "-c", script], "fixture", 10, 1, "test", root / "cycle.log", lambda **v: None, turn_metadata=metadata) self.assertEqual(result[0], 0) self.assertTrue(metadata["turnCompleted"]) self.assertEqual(metadata["fileChanges"], 1) self.assertNotIn("forbiddenImplementationCommand", metadata) class PreparedWorkTests(unittest.TestCase): def test_periodic_work_uses_isolated_batch_and_existing_source(self): with tempfile.TemporaryDirectory() as directory, patch.object(maintenance, "ROOT", Path(directory)): root = Path(directory) current = root / "docs/current" current.mkdir(parents=True) registry = current / "feature-capability-registry.json" registry.write_text(json.dumps({"records": [{"id": "Draft:format", "command": "format", "module": "Draft", "worker": "draft.worker.ts"}]})) global_batch = current / "module-batch-manifest.json" global_batch.write_text('{"batchId":"business-original"}') context = {"runtimeIssues": [{"capabilityId": "Draft:format"}], "batch": {"batchId": "other"}} def prepare(command, **kwargs): batch = json.loads(Path(command[command.index("--batch") + 1]).read_text()) output = Path(command[command.index("--output") + 1]) self.assertEqual(batch["selected"]["module"], "Draft") output.write_text(json.dumps({"module": "Draft", "supportFiles": [{"path": "web/src/draft.worker.ts", "sha256": "hash"}], "editableExcerpts": [{"path": "web/src/draft.worker.ts", "excerpt": "dispatchDraftFormat(request)"}]})) return type("Result", (), {"returncode": 0})() with patch.object(maintenance.subprocess, "run", side_effect=prepare): work = maintenance.prepare_work(context, current, "optimization-test") self.assertEqual(global_batch.read_text(), '{"batchId":"business-original"}') self.assertEqual(work["module"], "Draft") self.assertIn("dispatchDraftFormat", maintenance.prompt_for(current / "context.json", current / "report.md", work)) self.assertNotEqual(root / work["sourceBatch"], global_batch) if __name__ == "__main__": unittest.main()