Files
FreeCADWasmDev/tools/test_scheduler_maintenance.py
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

175 lines
9.7 KiB
Python

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()