This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
#!/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__])
|
||||
Reference in New Issue
Block a user