diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py | 114 |
1 files changed, 103 insertions, 11 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py b/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py index 749157e..90643e9 100644 --- a/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py +++ b/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py @@ -43,7 +43,9 @@ from sim.msgq_attach import ( # noqa: E402 RingGeometry, assemble_report, classify_ring_access, + image_tasks, missing_semantic_after_handoff, + missing_semantic_after_second_task, ring_geometry_from_host, run_attach, ) @@ -236,6 +238,69 @@ class ReportTests(unittest.TestCase): max_cycles=MAX_RUN_CYCLES + 1) with self.assertRaises(MsgqAttachError): run_attach(image="/nonexistent/gsp-image.bin", max_cycles=True) + with self.assertRaises(MsgqAttachError): + run_attach(image="/nonexistent/gsp-image.bin", + second_task_budget=-1) + with self.assertRaises(MsgqAttachError): + run_attach(image="/nonexistent/gsp-image.bin", + second_task_budget=MAX_RUN_CYCLES + 1) + + +class TaskTableTests(unittest.TestCase): + """Data-driven task discovery from the real image section table.""" + + @unittest.skipUnless(os.path.isfile(DEFAULT_IMAGE), "real GSP firmware absent") + def test_two_tasks_are_discovered_with_entries_and_stacks(self): + doc = image_tasks(DEFAULT_IMAGE) + self.assertEqual(doc["format"], "greeneye-image-tasks/1") + names = [task["name"] for task in doc["tasks"]] + self.assertEqual(names, ["init", "rm"]) + for task in doc["tasks"]: + self.assertGreaterEqual(task["entry"], task["text_base"]) + self.assertLess(task["entry"], + task["text_base"] + task["text_size"]) + self.assertGreater(task["stack_top"], task["stack_base"]) + self.assertGreater(task["text_size"], 0) + rm = doc["tasks"][1] + self.assertGreater(rm["text_size"], doc["tasks"][0]["text_size"]) + + def test_absent_image_yields_an_empty_table(self): + doc = image_tasks("/nonexistent/image.bin") + self.assertEqual(doc, {"format": "greeneye-image-tasks/1", "tasks": []}) + + def test_second_task_missing_semantic_is_precise(self): + doc = image_tasks(DEFAULT_IMAGE) if os.path.isfile(DEFAULT_IMAGE) \ + else {"format": "greeneye-image-tasks/1", + "tasks": [{"name": "init"}, {"name": "rm"}]} + missing = missing_semantic_after_second_task( + tasks_doc=doc, + schedule=[{"task": "init", "reason": "port-wait-blocks"}, + {"task": "rm", "reason": "scheduled"}], + frontier={"task": "rm", "reason": "second-task-slice-budget"}, + polls=[]) + self.assertEqual(missing["stage"], "second-task-frontier") + self.assertEqual(missing["polls"], 0) + self.assertEqual(missing["second_task"], "rm") + self.assertGreaterEqual(len(missing["missing"]), 3) + self.assertTrue(missing["next_step"]) + + def test_report_carries_tasks_and_schedule_additively(self): + report = assemble_report( + image=_image(), profile=_profile(), boot=_boot(True), attach={}, + doorbell={"requested": True}, polls=[], divergence=None, + missing_mmio={"count": 0, "first": None}, + determinism={"instructions": 1}, + tasks={"format": "greeneye-image-tasks/1", + "tasks": [{"name": "init"}, {"name": "rm"}]}, + schedule=[{"task": "rm", "reason": "scheduled"}], + ) + self.assertEqual([task["name"] for task in report["tasks"]["tasks"]], + ["init", "rm"]) + self.assertEqual(report["schedule"][0]["task"], "rm") + # the schema tag and the original keys are untouched + self.assertEqual(report["format"], REPORT_FORMAT) + self.assertIn("first_poll", report) + self.assertIn("missing_semantic", report) class PortModelReportTests(unittest.TestCase): @@ -285,12 +350,12 @@ class ImageTests(unittest.TestCase): """Bounded real-firmware run: deterministic, honest outcome only. Limitation: the budget is deliberately small (600k instructions; boot-to-B3 - needs ~474k). If the firmware needs more, the report is expected to take - the honest ``not-reached`` path with a non-empty divergence reason -- this - test accepts either outcome and asserts only determinism, never success. - With the default port model the run advances past the host wake to the - external-interrupt dispatch and reports the exact unmodelled semantic - (``task-state-query``); a fabricated poll is never accepted. + needs ~474k). The scheduled second task (the image-discovered RM task) + completes its boot port handshake inside the bounded slice and then runs + its own start-up past the cap, so the report is expected to take the + honest ``wait-reached-no-poll`` frontier path -- this test accepts any of + the allowed outcomes and asserts determinism, never success. A fabricated + poll is never accepted. """ def test_bounded_run_is_deterministic_and_honest(self): @@ -306,6 +371,12 @@ class ImageTests(unittest.TestCase): self.assertGreater(report["image"]["size"], 0) self.assertEqual(len(report["image"]["sha256"]), 64) self.assertTrue(report["port_model"]["enabled"]) + self.assertEqual(report["port_model"]["format"], + "greeneye-msgq-port-network/1") + self.assertEqual([task["name"] for task in report["tasks"]["tasks"]], + ["init", "rm"]) + self.assertTrue(report["schedule"]) + self.assertIn("rm", [step.get("task") for step in report["schedule"]]) if not report["boot"]["message_loop_wait_reached"]: self.assertIsNotNone(report["divergence"]) self.assertTrue(report["divergence"]["reason"]) @@ -313,17 +384,22 @@ class ImageTests(unittest.TestCase): self.assertTrue(report["first_poll"]["in_ring"]) self.assertTrue(report["first_poll"]["kind"] != "other") divergence = report["divergence"] - if divergence and divergence.get("kind") == "task-state-query": - # The port model delivered the doorbell wake (extintr shuttle - # 10) and stopped at the libos trap/report path; the missing - # semantic is named, and no poll is claimed. + if divergence and divergence.get("kind") == "second-task-frontier": + # The doorbell wake was consumed by the init task and the + # discovered RM task completed the boot handshake before its + # start-up ran past the bounded slice; the exact frontier is + # named and no poll is claimed. self.assertIsNone(report["first_poll"]) self.assertIsNotNone(report["missing_semantic"]) self.assertEqual(report["missing_semantic"]["stage"], - "after-extintr-handoff") + "second-task-frontier") completed = [wait.get("completed") for wait in report["port_model"].get("waits", [])] self.assertIn(10, completed) + payloads = report["port_model"].get("cross_task_payloads", []) + self.assertTrue(any(item.get("from") == "rm" + and item.get("to") == "init" + for item in payloads)) self.assertEqual(first["boot"]["message_loop_wait_reached"], second["boot"]["message_loop_wait_reached"]) self.assertEqual(first["boot"]["instructions_executed"], @@ -334,9 +410,25 @@ class ImageTests(unittest.TestCase): self.assertEqual(first["continuation"], second["continuation"]) self.assertEqual(first["port_model"], second["port_model"]) self.assertEqual(first["missing_semantic"], second["missing_semantic"]) + self.assertEqual(first["tasks"], second["tasks"]) + self.assertEqual(first["schedule"], second["schedule"]) self.assertEqual(first["determinism"]["report_sha256"], second["determinism"]["report_sha256"]) + def test_single_task_mode_keeps_the_handoff_classification(self): + report = run_attach(image=DEFAULT_IMAGE, max_cycles=SMALL_BUDGET, + doorbell=True, second_task=False) + self.assertIn(report["outcome"], + ("not-reached", "wait-reached-no-poll", + "wait-reached-poll-classified")) + self.assertEqual(report["port_model"]["format"], "greeneye-msgq-port/1") + self.assertFalse(report["schedule"]) + divergence = report["divergence"] + if divergence and divergence.get("kind") == "task-state-query": + self.assertIsNone(report["first_poll"]) + self.assertEqual(report["missing_semantic"]["stage"], + "after-extintr-handoff") + if __name__ == "__main__": unittest.main() |
