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

91 lines
5.8 KiB
Python

import contextlib
import io
import json
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 prepare_feature_capability_batch as planner
from scheduler_progress import ineffective_feature_seconds
class StallHistoryTests(unittest.TestCase):
def batch(self):
return {"status": "ready", "strategy": "feature-first-deferred-validation", "batchId": "b",
"generatedAt": "2020-01-01T00:00:00+00:00",
"selected": {"module": "TechDraw", "family": "desktop-command-command", "recordCount": 12},
"records": [[f"TechDraw:{n}"] for n in range(12)]}
def event(self, cycle, seconds=90, **kwargs):
return {"cycle": cycle, "batchId": "b", "tier": "feature-workflow", "outcome": "no_progress",
"seconds": seconds, "progressedIds": [], **kwargs}
def test_counts_effective_work_across_module_handoffs_and_ignores_provider_wait(self):
history = [self.event(1), self.event(2, batchId="other", seconds=400),
self.event(3, outcome="provider_wait", seconds=130), self.event(4)]
self.assertEqual(ineffective_feature_seconds(self.batch(), history), 180)
self.assertEqual(ineffective_feature_seconds(self.batch(), history + [history[-1]]), 180)
def test_partial_progress_in_a_smaller_batch_resets_the_window(self):
history = [self.event(1, seconds=300), self.event(2, batchId="smaller", outcome="progress", progressedIds=["TechDraw:0"]), self.event(3, seconds=30)]
self.assertEqual(ineffective_feature_seconds(self.batch(), history), 30)
def test_regeneration_does_not_hide_history_or_carry_a_limit_to_another_batch(self):
with tempfile.TemporaryDirectory() as directory, patch.object(planner, "PROGRESS", Path(directory) / "progress.json"):
planner.PROGRESS.write_text(json.dumps({"history": [self.event(1), self.event(2)]}))
batch = self.batch()
batch["generatedAt"] = "2999-01-01T00:00:00+00:00"
self.assertEqual(planner.stalled_batch_limit(batch, {"batchId": "other"}), 4)
batch["batchId"] = "new-work"
self.assertIsNone(planner.stalled_batch_limit(batch, {"batchId": "other"}))
def test_provider_only_history_and_real_progress_do_not_shrink_old_manifest(self):
with tempfile.TemporaryDirectory() as directory, patch.object(planner, "PROGRESS", Path(directory) / "progress.json"):
pipeline = {"batchId": "b", "status": "implementation_incomplete", "staticReadiness": {"ready": 0}}
planner.PROGRESS.write_text(json.dumps({"history": [self.event(1, seconds=500, outcome="provider_wait")]}))
self.assertIsNone(planner.stalled_batch_limit(self.batch(), pipeline))
history = [self.event(1, seconds=500), self.event(2, outcome="progress", progressedIds=["TechDraw:0"])]
planner.PROGRESS.write_text(json.dumps({"history": history}))
self.assertIsNone(planner.stalled_batch_limit(self.batch(), pipeline))
def test_ready_native_work_remains_on_its_build_path(self):
with tempfile.TemporaryDirectory() as directory, patch.object(planner, "PROGRESS", Path(directory) / "progress.json"):
planner.PROGRESS.write_text(json.dumps({"history": [self.event(1, seconds=500)]}))
for pipeline in [{"batchId": "b", "status": "failed", "failedStage": "build"},
{"batchId": "b", "status": "implementation_incomplete", "staticReadiness": {"ready": 1}}]:
self.assertIsNone(planner.stalled_batch_limit(self.batch(), pipeline))
def test_planner_applies_limit_to_candidate_after_other_module_and_keeps_queue(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
paths = {name: root / f"{name}.json" for name in ("REGISTRY", "ISSUES", "STATE", "OUTPUT", "PIPELINE", "PROGRESS")}
records = [{"id": f"TechDraw:{n}", "module": "TechDraw", "command": str(n),
"sourcePath": "src/Mod/TechDraw/Gui/Command.cpp", "implementationStatus": "implementation_required"} for n in range(12)]
identity = planner.feature_batch_id("TechDraw", "desktop-command-command", records)
paths["REGISTRY"].write_text(json.dumps({"records": records}))
paths["ISSUES"].write_text('{"issues":[]}')
paths["STATE"].write_text('{"active_task":"task"}')
paths["OUTPUT"].write_text('{"batchId":"other"}')
paths["PIPELINE"].write_text('{"batchId":"other"}')
history = [self.event(1, batchId=identity), self.event(2, batchId=identity)]
paths["PROGRESS"].write_text(json.dumps({"history": history}))
with patch.multiple(planner, **paths), patch.object(planner, "capability_source_fingerprint", return_value="stable"), \
patch.object(planner, "implementation_ready_ids", return_value=set()), patch.object(planner, "cooling_ids", return_value=set()), \
contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(planner.main(), 0)
batch = json.loads(paths["OUTPUT"].read_text())
self.assertEqual(batch["selected"]["recordCount"], 4)
self.assertEqual(batch["queue"][0]["unresolved"], 12)
self.assertEqual(batch["batchSizing"]["effectiveNoProgressSeconds"], 180)
history.append(self.event(3, batchId=batch["batchId"], outcome="progress", progressedIds=["TechDraw:0"]))
paths["PROGRESS"].write_text(json.dumps({"history": history}))
self.assertEqual(planner.main(), 0)
self.assertEqual(json.loads(paths["OUTPUT"].read_text())["selected"]["recordCount"], 12)
if __name__ == "__main__":
unittest.main()