#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Exhaustive ownership tests for Phase-1.0AK.""" from __future__ import annotations import argparse from pathlib import Path import sys import unittest PARSER = argparse.ArgumentParser() PARSER.add_argument("--root", type=Path, required=True) ROOT = PARSER.parse_args().root sys.path.insert(0, str(ROOT / "tools")) from phase10ak_hybrid_composition import * # noqa: E402,F403 def happy(count: int = 2) -> list[FakeEvent]: events = [FakeEvent(CREATE_CHILD, OK), FakeEvent(RESERVE_REGION, OK), FakeEvent(CREATE_MIRROR, OK)] for segment in range(count): events += [FakeEvent(CREATE_JIT_MASTER, OK, segment), FakeEvent(MAP_EXECUTABLE, OK, segment), FakeEvent(CREATE_JIT_ALIAS, OK, segment), FakeEvent(MAP_HOST_ALIAS, OK, segment), FakeEvent(MAP_REMOTE_ALIAS, OK, segment), FakeEvent(COPY_ALIAS, OK, segment), FakeEvent(UNMAP_REMOTE_ALIAS, OK, segment), FakeEvent(UNMAP_HOST_ALIAS, OK, segment), FakeEvent(CLOSE_JIT_ALIAS, OK, segment), FakeEvent(CLOSE_JIT_MASTER, OK, segment)] return events + [FakeEvent(FINALIZE_IMAGE, OK), FakeEvent(RELEASE_MIRROR, OK)] def cleanup_for(prefix: list[FakeEvent]) -> list[FakeEvent]: acquired = {name: set() for name in ("masters", "aliases", "host", "remote")} child = region = mirror = False for event in prefix: if event.result != OK: break if event.operation == CREATE_CHILD: child = True elif event.operation == RESERVE_REGION: region = True elif event.operation == CREATE_MIRROR: mirror = True elif event.operation == CREATE_JIT_MASTER: acquired["masters"].add(event.segment) elif event.operation == CREATE_JIT_ALIAS: acquired["aliases"].add(event.segment) elif event.operation == MAP_HOST_ALIAS: acquired["host"].add(event.segment) elif event.operation == MAP_REMOTE_ALIAS: acquired["remote"].add(event.segment) elif event.operation == UNMAP_REMOTE_ALIAS: acquired["remote"].remove(event.segment) elif event.operation == UNMAP_HOST_ALIAS: acquired["host"].remove(event.segment) elif event.operation == CLOSE_JIT_ALIAS: acquired["aliases"].remove(event.segment) elif event.operation == CLOSE_JIT_MASTER: acquired["masters"].remove(event.segment) elif event.operation == RELEASE_MIRROR: mirror = False result: list[FakeEvent] = [] for key, operation in (("remote", UNMAP_REMOTE_ALIAS), ("host", UNMAP_HOST_ALIAS), ("aliases", CLOSE_JIT_ALIAS), ("masters", CLOSE_JIT_MASTER)): result += [FakeEvent(operation, OK, item) for item in sorted(acquired[key], reverse=True)] if mirror: result.append(FakeEvent(RELEASE_MIRROR, OK)) if region: result.append(FakeEvent(UNMAP_REGION, OK)) if child: result.append(FakeEvent(KILL_AND_REAP_CHILD, OK)) return result class HybridCompositionTests(unittest.TestCase): def test_success_releases_every_temporary_resource(self) -> None: outcome = run_composition(CompositionPlan(2), FakeFacade(tuple(happy()))) self.assertTrue(outcome.success and outcome.child_alive and outcome.image_retained) self.assertEqual(outcome.resources_open, 0) self.assertFalse(outcome.target_action_performed or outcome.firmware_behavior_proven) def test_every_forward_failure_terminates_fail_closed(self) -> None: baseline = happy() for index, original in enumerate(baseline): failed = baseline[:index] + [FakeEvent(original.operation, ERROR, original.segment)] failed += cleanup_for(failed) with self.subTest(index=index, operation=original.operation): outcome = run_composition(CompositionPlan(2), FakeFacade(tuple(failed))) self.assertFalse(outcome.success or outcome.child_alive) self.assertEqual(outcome.fail_closed_termination, index > 0) self.assertEqual(outcome.resources_open, 0) def test_cleanup_failure_is_contained_by_child_termination(self) -> None: failed = happy()[:9] original = failed[-1] failed[-1] = FakeEvent(original.operation, ERROR, original.segment) cleanup = cleanup_for(failed) cleanup[0] = FakeEvent(cleanup[0].operation, ERROR, cleanup[0].segment) outcome = run_composition(CompositionPlan(2), FakeFacade(tuple(failed + cleanup))) self.assertEqual(outcome.classification, "OFFLINE_FAIL_CLOSED_AFTER_CLEANUP_FAILURE") self.assertFalse(outcome.child_alive) def test_failed_termination_is_a_hard_error(self) -> None: events = [FakeEvent(CREATE_CHILD, OK), FakeEvent(RESERVE_REGION, ERROR), FakeEvent(KILL_AND_REAP_CHILD, ERROR)] with self.assertRaises(CompositionError): run_composition(CompositionPlan(1), FakeFacade(tuple(events))) def test_bounds_wrong_order_deadline_and_unused_events(self) -> None: for value in (0, 9, True): with self.subTest(value=value), self.assertRaises(CompositionError): CompositionPlan(value) events = happy(1) events[0] = FakeEvent(CREATE_CHILD, OK, ticks=256) events = events[:2] + [FakeEvent(KILL_AND_REAP_CHILD, OK)] outcome = run_composition(CompositionPlan(1), FakeFacade(tuple(events))) self.assertFalse(outcome.success) with self.assertRaises(CompositionError): run_composition(CompositionPlan(1), FakeFacade(tuple( happy(1) + [FakeEvent(KILL_AND_REAP_CHILD, OK)]))) if __name__ == "__main__": unittest.main(argv=[__file__])