From c1d9ad51db4dc9758f6f52b26cdd11971346fae6 Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Tue, 20 Oct 2026 23:12:06 +0900 Subject: [PATCH] =?UTF-8?q?P28-01-COLLECTOR:=20=E7=8F=BEHEAD=E3=81=AE?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E5=9F=BA=E6=BA=96=EF=BC=9Awindowed=20x1/x3?= =?UTF-8?q?=E3=83=BBOperations=20ON=20collector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../p28-01/collector/check_p28_01_report.py | 92 ++++++++- .../p28-01/collector/collect_p28_01.py | 62 +++++- .../p28-01/collector/measure_p28_01.gd | 6 +- .../p28-01/collector/test_collect_p28_01.py | 179 +++++++++++++++++- 4 files changed, 318 insertions(+), 21 deletions(-) diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py index 886a3ff..e5fbab1 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py @@ -87,6 +87,19 @@ def check_budget_scope(value, report_paths=(), path=(), raw=False): def check_admission(admission): check_budget_scope(admission) + if "planned_argv" in admission: + from collect_p28_01 import argv_for, parse_argv + planned = admission["planned_argv"] + need(len(planned) == 3 + len(admission["phases"]), "internal phase plan length") + engine = planned[0][0] + need("--path" in planned[0], "import stage missing") + stage = Path(planned[0][planned[0].index("--path") + 1]) + wrapper = Path(planned[2][-1]) + need(wrapper.name == "guarded.gd", "wrapper plan target") + equal(planned, [argv_for(engine, stage, wrapper, True), + parse_argv(engine, stage, wrapper.parent / "measure_p28_01.gd"), + parse_argv(engine, stage, wrapper)] + + [argv_for(engine, stage, wrapper) for _ in admission["phases"]], "execution argv plan") need(admission.get("budget_verdict_scope") == VERDICT_SCOPE, "admission verdict scope") reused = admission["reused_r02"] def no_verdict_fields(value): @@ -442,6 +455,75 @@ def check_report(report): need(frames and all(f["registered"]["guests"] == 200 for f in frames), "200 guest claim") +def check_parse_process(row, directory): + from collect_p28_01 import PARSE_NA, diagnostics, sha + equal(row["parse_na"], PARSE_NA, "parse-only N/A reasons") + need(not any(k in row for k in ("report", "guard", "audio_close", "runtime_profile")), + "parse claims runtime evidence") + need(row["passed"] is True, "parse failed") + need(row["source_preserved"] is True and row["player_preserved"] is True, "parse preservation") + need(set(row["raw"]) == {"stdout.log", "stderr.log", "engine-native.log"}, "parse raw missing") + text = "" + for name, pin in row["raw"].items(): + path = directory / row["phase"] / name + need(pin == {"sha256": sha(path), "bytes": path.stat().st_size}, "parse raw pin") + text += path.read_text(errors="replace") + "\n" + need(not diagnostics(text) and row["diagnostics"] == [], "parse diagnostics") + need("P28_01_PERF_REPORT=" not in text and "P28_01_AUDIO_CLOSE=" not in text, + "runtime markers during parse") + process, capture = row["process"], row["capture"] + need(process["exit_code"] == 0 and process["helper_exit_code"] == 0 and + process["timed_out"] is False and process["error_lines"] == [] and + process["owned_popen_ended"] is True and process["old_helper_engine_ended"] is True and + process["start_receipt_written"] is True and process["end_receipt_written"] is True and + process["lifecycle_error"] is None and process["external_process_or_group_signals"] == 0, + "parse process failure") + need(capture["passed"] is True and capture["all_captured_ended"] is True and + capture["actual_captured_pids"] == [process["pid"]] and + not capture["outer_cleanup_errors"] and capture["foreign_process_or_group_signals"] == 0 and + all(c["engine_ended"] is True and not c["outer_cleanup_errors"] and + not c["cleanup"].get("errors") for c in capture["cleanup"]), "parse ownership") + equal(json.loads((directory / row["phase"] / "process.json").read_text()), process, + "parse process binding") + for name, pin in row["owned_files"].items(): + path = directory / row["phase"] / name + need(pin == {"sha256": sha(path), "bytes": path.stat().st_size}, "parse owned pin") + from collect_p28_01 import OWNED_FILES + need(set(row["owned_files"]) == set(OWNED_FILES), "parse ownership evidence missing") + saved_capture = json.loads((directory / row["phase"] / "outer-owned-finally.json").read_text()) + equal(saved_capture, {k: v for k, v in capture.items() + if k not in ("passed", "outer_receipt_written", "outer_receipt_error")}, + "parse capture binding") + + +def check_parse(row, directory, source): + from collect_p28_01 import parse_argv, sha, digest + check_parse_process(row, directory) + target = directory / ("measure_p28_01.gd" if row["phase"] == "parse-base" else "guarded.gd") + command = parse_argv(row["argv"][0], directory / "stage", target) + command += ["--log-file", str(directory / row["phase"] / "engine-native.log"), "--verbose"] + equal(row["argv"], command, "parse argv") + equal(row["process"]["argv"], command, "parse owned argv") + equal(row["script_pins"], {str(directory / n): sha(directory / n) + for n in ("measure_p28_01.gd", "guarded.gd", "profile_guard.gd")}, + "parse script pins") + startup = row["startup_project"] + need(not (directory / (row["phase"] + "-guard.json")).exists(), "Guard ran during parse") + path = directory / (row["phase"] + "-startup-project.godot") + need(startup["path"] == str(path) and startup["sha256"] == sha(path) and + startup["fresh_before"] is True, "parse startup evidence") + from collect_p28_01 import load_tool, EXTERNAL_IMPORTS + standard = load_tool(EXTERNAL_IMPORTS[0]) + public = (directory / "stage/project.godot").read_bytes() + need(path.read_bytes() == standard.private_project(public, startup["name"]) and + startup["profile"] == str(standard.profile_path(startup["name"])) and + startup["restored_sha256"] == hashlib.sha256(public).hexdigest(), "parse private project") + stage_inventory = standard.inventory(directory / "stage", stage=True) + need(row["stage_before_sha256"] == row["stage_after_sha256"] == + digest(stage_inventory), "parse stage preservation") + need(all(stage_inventory.get(k) == v for k, v in source.items()), "parse product preservation") + + def check_receipt(receipt, directory): from collect_p28_01 import OWNED_FILES, projection, extrapolation, guard_gate check_budget_scope(receipt, tuple(("phases", i, "report") @@ -458,7 +540,10 @@ def check_receipt(receipt, directory): "execution probe pin/check") check_probe(text) rows = receipt["phases"] - need([r["phase"] for r in rows] == ["import", *PHASES], "phase order") + from collect_p28_01 import PARSE_PHASES + need([r["phase"] for r in rows] == ["import", *PARSE_PHASES, *PHASES], "phase order") + parse_profiles = [r["startup_project"]["profile"] for r in rows if r["phase"] in PARSE_PHASES] + need(len(set(parse_profiles)) == len(parse_profiles), "parse profiles reused") for row in rows: equal(row["phase_stop"], {"limit_seconds": 1800, "source": CAP_SOURCE}, "phase cap origin") forecast = None @@ -486,6 +571,11 @@ def check_receipt(receipt, directory): need(hashlib.sha256(data).hexdigest() == pin["sha256"] and len(data) == pin["bytes"], "raw SHA") if row["phase"] == "import": continue + if row["phase"] in PARSE_PHASES: + check_parse(row, directory, receipt["source_before"]) + index = 1 + PARSE_PHASES.index(row["phase"]) + equal(row["argv"][:-3], receipt["admission"]["planned_argv"][index], "parse plan binding") + continue guard_gate(row["guard"], Path(row["runtime_profile"]), row["runtime_name"]) stdout = (directory / row["phase"] / "stdout.log").read_text() printed = [normalize_report(json.loads(x[len(PREFIX):])) for x in stdout.splitlines() if x.startswith(PREFIX)] diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py index 61d5623..56fa984 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py @@ -24,6 +24,9 @@ PHASES = (("R0", "R", 1, 0, 10), ("P1", "R", 1, 30, 120), ("P2", "R", 3, 30, 120), ("O0", "O", 1, 0, 10), ("P3", "O", 1, 30, 120), ("P4", "O", 3, 30, 120)) EXTERNAL_IMPORTS = ("tools/run_all_tests.py", "tools/regression/owned_outer.py") +PARSE_PHASES = ("parse-base", "parse-wrapper") +PARSE_NA = {key: {"value": None, "reason": "--check-only does not run Runtime or Guard"} + for key in ("PERF_REPORT", "AUDIO_CLOSE", "Guard.activate", "runtime_profile")} def require(value, message): @@ -98,6 +101,38 @@ def argv_for(engine, stage, wrapper, importing=False): return [str(engine), "--path", str(stage), "--script", str(wrapper)] +def parse_argv(engine, stage, target): + return [str(engine), "--headless", "--path", str(stage), "--check-only", "--script", str(target)] + + +def execution_plan(engine, stage, wrapper): + return [argv_for(engine, stage, wrapper, True), + parse_argv(engine, stage, wrapper.parent / "measure_p28_01.gd"), + parse_argv(engine, stage, wrapper)] + [argv_for(engine, stage, wrapper) for _ in PHASES] + + +def run_private_engine(standard, outer, command, folder, environment, stage, public, name): + """Keep launch settings outside the owned folder and restore even on failure.""" + project = stage / "project.godot" + private = standard.private_project(public, name) + startup = standard.profile_path(name) + require(not startup.exists(), "startup profile not fresh") + evidence = folder.parent / (folder.name + "-startup-project.godot") + evidence.write_bytes(private) + try: + project.write_bytes(private) + require(project.read_bytes() == private, "private startup project differs") + row = run_engine(outer, command, folder, environment) + require(project.read_bytes() == private, "startup project changed during execution") + finally: + project.write_bytes(public) + row["startup_project"] = {"path": str(evidence), "sha256": sha(evidence), + "name": name, "profile": str(startup), "fresh_before": True, + "restored_sha256": sha(project)} + require(project.read_bytes() == public, "project restoration failed") + return row + + def checked_probe(path): text = path.read_text() check_probe(text) @@ -162,7 +197,8 @@ def bound_report(folder, row): def phase_schedule(rows): """Caller appends each executed result; exceptions abort the consuming loop.""" - for index, phase in enumerate((("import", "", 0, 0, 0),) + PHASES): + for index, phase in enumerate((("import", "", 0, 0, 0),) + + tuple((label, "", 0, 0, 0) for label in PARSE_PHASES) + PHASES): label, source, _, _, _ = phase forecast = None if label in ("P1", "P2", "P3", "P4"): @@ -282,8 +318,7 @@ def main(): require(actual == blob, "dirty source: " + name) stage = args.output.resolve() / "stage" wrapper = args.output.resolve() / "guarded.gd" - planned = [argv_for(args.engine, stage, wrapper, True)] + [ - argv_for(args.engine, stage, wrapper) for _ in PHASES] + planned = execution_plan(args.engine, stage, wrapper) admission = {"commit": commit, "source_map": before, "source_map_sha256": digest(before), "readonly": pins, "engine_sha256": ENGINE_SHA, "engine_version": VERSION, "fixture_sha256": FIXTURE_SHA, "planned_argv": planned, "phases": PHASES, @@ -351,20 +386,31 @@ def main(): "THE_TREE_P28_WARMUP": str(warmup), "THE_TREE_P28_MINUTES": str(minutes), "THE_TREE_P28_FIXTURE": str(args.fixture.resolve()), "THE_TREE_P28_FIXTURE_SHA": FIXTURE_SHA, "THE_TREE_P28_META": json.dumps(meta)} - (stage / "project.godot").write_bytes(standard.private_project(public, name)) - try: - row = run_engine(outer, planned[index], out / label, env) - finally: - (stage / "project.godot").write_bytes(public) + row = run_private_engine(standard, outer, planned[index], out / label, env, + stage, public, name) row["phase"] = label row["projection"] = forecast row["probe_check"] = probe_check + if label in PARSE_PHASES: + row.update(parse_na=PARSE_NA, script_pins=script_pins, + stage_before_sha256=digest(expected), + stage_after_sha256=digest(standard.inventory(stage, stage=True)), + source_preserved=source_map(standard, ROOT) == before, + player_preserved=standard.default_player(ROOT) == player) rows.append(row) + if label in PARSE_PHASES: + require(row["stage_before_sha256"] == row["stage_after_sha256"] and + row["source_preserved"] and row["player_preserved"] and + all(sha(Path(p)) == pin for p, pin in script_pins.items()), + "parse preservation failed") require(row["passed"], "native phase failed; raw retained: " + label) after = standard.inventory(stage, stage=True) if label == "import": stage_gate(expected, after, importing=True) expected = after + elif label in PARSE_PHASES: + require(after == expected, "stage changed during parse") + row["stage_after_sha256"] = digest(after) else: require(after == expected, "stage changed during measurement") observation = json.loads(guard_path.read_text()) diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd index cb7c0cc..05d2f4b 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd @@ -478,9 +478,9 @@ func sample(observer_started_us: int, delta: float) -> void: var stall_count: Variant = field(clock.performance_budget.get("_sample_counts"), "main_thread_stall") var kernel_stalls: Array = [] if stall_count != null and last_stall_count != null and stall_raw is Array: - var delta: int = stall_count - last_stall_count - if delta > 0 and delta <= stall_raw.size(): - kernel_stalls = stall_raw.slice(stall_raw.size() - delta) + var stall_delta: int = stall_count - last_stall_count + if stall_delta > 0 and stall_delta <= stall_raw.size(): + kernel_stalls = stall_raw.slice(stall_raw.size() - stall_delta) last_stall_count = stall_count var causes: Array = [] for event: Dictionary in save_events: diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py index d59c9ee..dfb6378 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py @@ -462,7 +462,10 @@ class ReportTests(unittest.TestCase): phase["probe_check"] = receipt["probe_check"].copy() phase["owned_files"] = {name: {"sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw)} for name in collector.OWNED_FILES} - with patch.object(checker, "PHASES", {"O0": ("O", 1, 0, 10)}), \ + # This fixture isolates the original runtime receipt checks. The actual + # parse prefix and its evidence are covered by test_l2_owned_parse_gate. + with patch.object(collector, "PARSE_PHASES", ()), \ + patch.object(checker, "PHASES", {"O0": ("O", 1, 0, 10)}), \ patch.object(Path, "read_bytes", return_value=raw), \ patch.object(Path, "read_text", autospec=True, side_effect=lambda p: (probe_text if p.name == "measure_p28_01.gd" else @@ -686,6 +689,164 @@ class ReportTests(unittest.TestCase): self.assertFalse(errors) self.assertTrue(all(r["engine_ended"] for r in cleanup)) + def test_l2_owned_parse_gate(self): + """Real owned Python children; no engine and no fabricated Runtime markers.""" + standard = collector.load_tool(collector.EXTERNAL_IMPORTS[0]) + outer = collector.load_tool(collector.EXTERNAL_IMPORTS[1]) + child = ( + "import os,sys,json,time,pathlib\n" + "mode=sys.argv[1]\n" + "identity={'pid':os.getpid(),'ppid':os.getppid(),'pgid':os.getpgrp()," + "'args':' '.join([sys.executable]+sys.argv)}\n" + "base=pathlib.Path(sys.argv[0]).parent\n" + "tmp=base/'identity.tmp'; tmp.write_text(json.dumps(identity)); tmp.replace(base/'identity')\n" + "stage=pathlib.Path(sys.argv[sys.argv.index('--path')+1])\n" + "assert 'config/use_custom_user_dir=true' in (stage/'project.godot').read_text()\n" + "native=pathlib.Path(sys.argv[sys.argv.index('--log-file')+1])\n" + "native.write_text('ERROR: native parse failure\\n' if mode=='native' else 'native raw\\n')\n" + "print('Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org',flush=True)\n" + "print('SCRIPT ERROR: Parse Error: synthetic' if mode=='stdout' else 'parse stdout',flush=True)\n" + "print('Parse Error: synthetic' if mode=='stderr' else 'parse stderr',file=sys.stderr,flush=True)\n" + "time.sleep(.15)\n" + "sys.exit(3 if mode=='exit' else 0)\n") + for failure_phase, mode in ((None, "ok"), ("parse-base", "stdout"), + ("parse-base", "stderr"), ("parse-base", "native"), + ("parse-base", "exit"), ("parse-wrapper", "stdout")): + with self.subTest(failure_phase=failure_phase, mode=mode), tempfile.TemporaryDirectory() as temp: + base = Path(temp) + stage = base / "stage" + stage.mkdir() + public = b'[application]\n\nconfig/name="The Tree"\n' + (stage / "project.godot").write_bytes(public) + script = base / "child.py" + script.write_text(child) + pins = {} + for name in ("measure_p28_01.gd", "profile_guard.gd", "guarded.gd"): + (base / name).write_text("# Synthetic parse input; never executed by Godot.\n") + pins[str(base / name)] = collector.sha(base / name) + source = standard.inventory(stage, stage=True) + def diagnostic(command, folder, timeout, env, **kwargs): + def probe(pid): + deadline = time.monotonic() + 2 + while time.monotonic() < deadline: + if (base / "identity").exists(): + identity = json.loads((base / "identity").read_text()) + if identity["pid"] == pid: + return identity + time.sleep(.005) + raise RuntimeError("child identity unavailable") + return outer.native.run_owned_diagnostic(command, folder, timeout, env, + probe_fn=probe, **kwargs) + def owned(command, folder, limit, env, **kwargs): + self.assertEqual(limit, 1800) + return outer.run_phase(command, folder, 5, env, diagnostic_fn=diagnostic, **kwargs) + rows, launched = [], [] + try: + with patch.object(collector, "live_engines", return_value=[]): + for index, phase, _ in collector.phase_schedule(rows): + label = phase[0] + if label == "import": + rows.append({"phase": label}) + continue # This engine-free fixture begins after successful import. + launched.append(label) + if label not in collector.PARSE_PHASES: + break # Record admission of R0 without starting any Runtime. + command = [sys.executable, str(script), mode if label == failure_phase else "ok", + "--headless", "--path", str(stage), "--check-only", "--script", "probe"] + row = collector.run_private_engine( + standard, SimpleNamespace(run_phase=owned), command, base / label, {}, + stage, public, "TheTree-PCK-tests-" + base.name + "-" + label) + row.update(phase=label, parse_na=copy.deepcopy(collector.PARSE_NA), + source_preserved=True, player_preserved=True, script_pins=pins, + stage_before_sha256=collector.digest(source), + stage_after_sha256=collector.digest(standard.inventory(stage, stage=True))) + rows.append(row) + self.assertEqual((stage / "project.godot").read_bytes(), public) + self.assertEqual(row["startup_project"]["sha256"], + collector.sha(Path(row["startup_project"]["path"]))) + self.assertTrue(row["capture"]["all_captured_ended"]) + self.assertFalse(row["capture"]["outer_cleanup_errors"]) + self.assertEqual(row["capture"]["actual_captured_pids"], [row["process"]["pid"]]) + self.assertTrue((base / (label + "-collector-receipt.json")).is_file()) + if label == failure_phase: + self.assertFalse(row["passed"]) + # Claimed success cannot override actual diagnostics/nonzero exit. + broken = copy.deepcopy(row) + broken["passed"] = True + with self.assertRaises(ValueError): + checker.check_parse_process(broken, base) + else: + checker.check_parse_process(row, base) + # Only argv generation is substituted for the Python fake child; + # exact Godot argv is asserted separately in the static plan test. + with patch.object(collector, "parse_argv", return_value=row["argv"][:-3]): + checker.check_parse(row, base, source) + for mutation in ( + lambda r: r["startup_project"].update(name="wrong"), + lambda r: r.update(stage_after_sha256="f" * 64), + lambda r: r["script_pins"].update({str(base / "guarded.gd"): "f" * 64}), + ): + broken = copy.deepcopy(row) + mutation(broken) + with self.assertRaises(ValueError): + checker.check_parse(broken, base, source) + for mutation in ( + lambda r: r["parse_na"]["AUDIO_CLOSE"].update(reason=""), + lambda r: r.update(report={"pid": r["process"]["pid"]}), + lambda r: r["capture"].update(actual_captured_pids=[-1]), + lambda r: r.update(player_preserved=False), + lambda r: r["owned_files"].pop("ended.json"), + ): + broken = copy.deepcopy(row) + mutation(broken) + with self.assertRaises(ValueError): + checker.check_parse_process(broken, base) + collector.require(row["passed"], "parse failed") + except ValueError: + self.assertIsNotNone(failure_phase) + finally: + cleanup, errors = outer.finish_captured(outer.OWNED) + self.assertFalse(errors) + self.assertTrue(all(r["engine_ended"] for r in cleanup)) + self.assertEqual(launched, ["parse-base"] if failure_phase == "parse-base" else + ["parse-base", "parse-wrapper"] if failure_phase else + ["parse-base", "parse-wrapper", "R0"]) + self.assertFalse(any((base / p).exists() for p in ("R0", "P1", "P2", "O0", "P3", "P4"))) + + def test_l2_plan_and_restoration(self): + plan = collector.execution_plan("engine", Path("/stage"), Path("/out/guarded.gd")) + self.assertEqual(len(plan), 9) + self.assertEqual(plan[1], ["engine", "--headless", "--path", "/stage", "--check-only", + "--script", "/out/measure_p28_01.gd"]) + self.assertEqual(plan[2][-1], "/out/guarded.gd") + self.assertEqual(collector.PHASES[0], ("R0", "R", 1, 0, 10)) + admission = synthetic_admission() + admission.update(planned_argv=plan, phases=collector.PHASES) + checker.check_admission(admission) + for mutate in ( + lambda a: a["planned_argv"].pop(1), + lambda a: a["planned_argv"][1].remove("--check-only"), + lambda a: a["planned_argv"][1].__setitem__(-1, "/out/guarded.gd"), + lambda a: a["planned_argv"][3].append("--headless"), + ): + broken = copy.deepcopy(admission) + mutate(broken) + with self.assertRaises(ValueError): + checker.check_admission(broken) + for command in plan[3:]: + self.assertNotIn("--headless", command) + self.assertNotIn("--check-only", command) + standard = collector.load_tool(collector.EXTERNAL_IMPORTS[0]) + with tempfile.TemporaryDirectory() as temp: + base = Path(temp) + public = b'[application]\n\nconfig/name="The Tree"\n' + (base / "project.godot").write_bytes(public) + with patch.object(collector, "run_engine", side_effect=RuntimeError("owned failure")), \ + self.assertRaises(RuntimeError): + collector.run_private_engine(standard, None, [], base / "parse-base", {}, base, + public, "TheTree-PCK-tests-" + base.name) + self.assertEqual((base / "project.godot").read_bytes(), public) + def test_j3_guard_and_whole_inventory(self): guard = synthetic_guard() collector.guard_gate(guard, Path("private"), "runtime") @@ -735,18 +896,18 @@ class ReportTests(unittest.TestCase): pass return rows, launched rows, launched = execute() - self.assertEqual(launched, ["import", "R0", "P1", "P2", "O0", "P3", "P4"]) - self.assertEqual(rows[1]["report"]["stop_reason"], "simulation_overloaded") - self.assertEqual(execute(failure=True)[1], ["import", "R0"]) + self.assertEqual(launched, ["import", "parse-base", "parse-wrapper", "R0", "P1", "P2", "O0", "P3", "P4"]) + self.assertEqual(rows[3]["report"]["stop_reason"], "simulation_overloaded") + self.assertEqual(execute(failure=True)[1], ["import", "parse-base", "parse-wrapper", "R0"]) rows, launched = execute(duration=20) - self.assertEqual(launched, ["import", "R0", "O0", "P3", "P4"]) + self.assertEqual(launched, ["import", "parse-base", "parse-wrapper", "R0", "O0", "P3", "P4"]) self.assertEqual([r["phase"] for r in rows if r.get("engines_started") == 0], ["P1", "P2"]) rows, launched = execute(minutes=0) - self.assertEqual(len(launched), 7) - self.assertIsNone(rows[1]["extrapolation"]["seconds_per_phase"]) - self.assertEqual(rows[1]["extrapolation"]["reason"], "no_minutes_observed") - self.assertIsNone(rows[2]["projection"]["projected_total_seconds"]) + self.assertEqual(len(launched), 9) + self.assertIsNone(rows[3]["extrapolation"]["seconds_per_phase"]) + self.assertEqual(rows[3]["extrapolation"]["reason"], "no_minutes_observed") + self.assertIsNone(rows[4]["projection"]["projected_total_seconds"]) shortfall = synthetic_report() shortfall.update(stop_reason="input_shortfall", frames=[], actual_minutes=0) checker.check_report(shortfall)