import contextlib 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 scheduler_maintenance as maintenance import run_freecad_web_scheduler as scheduler import watch_freecad_web_scheduler as watchdog class ScheduleTests(unittest.TestCase): def setUp(self): self.directory = tempfile.TemporaryDirectory() self.addCleanup(self.directory.cleanup) self.root = Path(self.directory.name) self.path = self.root / "docs/current/scheduler-maintenance.json" self.enterContext(patch.object(maintenance, "ROOT", self.root)) self.enterContext(patch.object(maintenance, "STATE_PATH", self.path)) def test_disabled_schedule_never_runs(self): self.assertIsNone(maintenance.claim_due(now=10000)) maintenance.configure(False, now=1000) self.assertIsNone(maintenance.claim_due(now=10000)) def test_thirty_minute_schedule_is_idempotent_and_claims_only_once(self): first = maintenance.configure(True, now=1000) self.assertEqual(first["nextRunAt"], 2800) self.assertEqual(maintenance.configure(True, now=1100)["nextRunAt"], 2800) self.assertIsNone(maintenance.claim_due(now=2799)) claimed = maintenance.claim_due(now=2800) self.assertEqual(claimed["nextRunAt"], 4600) self.assertEqual(claimed["runCount"], 1) self.assertIsNone(maintenance.claim_due(now=5000)) def test_missed_intervals_coalesce_without_catchup_loop(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=8300) self.assertEqual(claimed["nextRunAt"], 10000) self.assertEqual(claimed["skippedIntervals"], 3) self.assertEqual(claimed["runCount"], 1) def test_restart_keeps_next_slot_and_marks_interrupted_run(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=2800) claimed["schedulerPid"] = 999999999 maintenance.write(self.path, claimed) self.assertIsNone(maintenance.claim_due(now=3000)) state = maintenance.read(self.path) self.assertEqual(state["lastOutcome"], "interrupted") self.assertEqual(state["nextRunAt"], 4600) self.assertEqual(maintenance.claim_due(now=4600)["runCount"], 2) def test_surviving_child_blocks_duplicate_after_restart(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=2800) claimed["schedulerPid"] = 999999999 maintenance.write(self.path, claimed) with patch.object(maintenance, "active_child", return_value=123): self.assertIsNone(maintenance.claim_due(now=5000)) def test_result_records_real_command_outcomes_and_preserves_disabled_schedule(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=2800) def runner(prompt, log, metadata): self.assertIn("functional module business and functional routes", prompt) self.assertIn("Scoped builds and meaningful verification are allowed", prompt) self.assertIn("Do not start, stop, kill or restart", prompt) metadata.update(turnCompleted=True, fileChanges=1, commandsCompleted=2, commandsFailed=1, commandResults=[{"command": "focused check", "exitCode": 1}], lastAgentMessage="Fix needs follow-up") maintenance.configure(False, now=2850) return 0, False, False, False, False result = maintenance.execute(claimed, runner, self.root / "cycle.log", now=lambda: 2900, context={"observed": True}) self.assertEqual(result["commandsFailed"], 1) self.assertTrue((self.root / result["reportPath"]).is_file()) self.assertEqual(result["reportSource"], "scheduler") self.assertEqual(result["status"], "incomplete") state = maintenance.read(self.path) self.assertEqual(state["status"], "disabled") self.assertEqual(state["nextRunAt"], 4600) self.assertTrue((self.root / state["lastResultPath"]).is_file()) self.assertIsNone(maintenance.claim_due(now=5000)) def test_provider_failure_and_long_run_do_not_create_immediate_retry_loop(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=2800) result = maintenance.execute(claimed, lambda *args: (1, False, True, False, False), self.root / "cycle.log", now=lambda: 8200, context={}) self.assertEqual(result["status"], "provider_wait") self.assertTrue(result["providerFailed"]) self.assertFalse(result["modelResponded"]) state = maintenance.read(self.path) self.assertEqual(state["nextRunAt"], 10000) self.assertIsNone(maintenance.claim_due(now=8201)) def test_empty_clean_exit_is_not_a_completed_optimization(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=2800) result = maintenance.execute(claimed, lambda *args: (0, False, False, False, False), self.root / "cycle.log", now=lambda: 2900, context={}) self.assertEqual(result["status"], "incomplete") self.assertEqual(maintenance.read(self.path)["nextRunAt"], 4600) def test_completed_inspection_has_a_report_and_returns_to_fixed_schedule(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=2800) def runner(prompt, log, metadata): metadata.update(turnCompleted=True, commandsCompleted=1, commandsFailed=0) report = self.root / "docs/current/periodic-optimization" / f"{claimed['runId']}.md" report.write_text("Inspected current bottlenecks; no actionable regression found.\n") return 0, False, False, False, False result = maintenance.execute(claimed, runner, self.root / "cycle.log", now=lambda: 2900, context={}) self.assertEqual(result["status"], "completed") self.assertTrue(result["modelResponded"]) self.assertFalse(result["providerFailed"]) self.assertEqual(maintenance.read(self.path)["nextRunAt"], 4600) self.assertIsNone(maintenance.claim_due(now=4599)) self.assertEqual(maintenance.claim_due(now=4600)["runCount"], 2) def test_unexpected_runner_failure_is_reported_and_rescheduled(self): maintenance.configure(True, now=1000) claimed = maintenance.claim_due(now=2800) def fail(*args): raise OSError("executable unavailable") result = maintenance.execute(claimed, fail, self.root / "cycle.log", now=lambda: 2900, context={}) self.assertEqual(result["status"], "failed") self.assertFalse(result["providerFailed"]) self.assertIn("executable unavailable", result["error"]) self.assertEqual(maintenance.read(self.path)["status"], "scheduled") class ExecutionTests(unittest.TestCase): def test_optimization_keeps_verification_after_edit_and_does_not_dirty_business_batch(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) events = [{"type": "turn.started"}, {"type": "item.completed", "item": {"type": "file_change", "changes": [{"path": "repair.py", "kind": "update"}]}}] command = {"id": "check", "type": "command_execution", "command": "cmake --build fixture"} tail = [{"type": "item.started", "item": command}, {"type": "item.completed", "item": {**command, "exit_code": 0}}, {"type": "item.completed", "item": {"type": "agent_message", "text": "Checked repair"}}, {"type": "turn.completed"}] script = "import sys,time\nsys.stdin.read()\n" + "\n".join(f"print({json.dumps(event)!r}, flush=True)" for event in events) script += "\ntime.sleep(1.5)\n" + "\n".join(f"print({json.dumps(event)!r}, flush=True)" for event in tail) metadata = {} with patch.object(scheduler, "ROOT", root), patch.object(scheduler, "POST_EDIT_FINALIZE_SECONDS", 0.05), \ 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", side_effect=AssertionError("must not alter business receipt")), \ 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], "test optimization", 10, 1, "UNATTENDED-OPTIMIZATION", root / "cycle.log", lambda **values: None, turn_metadata=metadata, optimization=True) self.assertEqual(result, (0, False, False, False, False)) self.assertTrue(metadata["turnCompleted"]) self.assertEqual(metadata["commandsCompleted"], 1) self.assertEqual(metadata["lastAgentMessage"], "Checked repair") self.assertEqual(metadata["commandResults"][0]["exitCode"], 0) def test_watchdog_does_not_apply_business_cooldown_to_optimization(self): with patch.object(watchdog, "scheduler_pid_alive", return_value=True), \ patch.object(watchdog, "stalled_launch", side_effect=AssertionError("wrong scope")): self.assertEqual(watchdog.request_stall_repair(42, {"pid": 42, "executionMode": "periodic-optimization"}, {}, {}), {}) if __name__ == "__main__": unittest.main()