91 lines
5.8 KiB
Python
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()
|