Files
FreeCADWasmDev/tools/test_scheduler_step_timing.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

132 lines
8.6 KiB
Python

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