This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure-injection tests for the Phase-1.0AF lifecycle model."""
|
||||
|
||||
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 phase10af_bigapp_lifecycle_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
def happy() -> list[FakeEvent]:
|
||||
return [FakeEvent(CHECK_NO_BIGAPP, NONE), FakeEvent(ATTACH_PARENT, OK),
|
||||
FakeEvent(ARM_FORK, OK), FakeEvent(CONTINUE_PARENT, OK),
|
||||
FakeEvent(LAUNCH_FIXED_TITLE, OK),
|
||||
FakeEvent(AWAIT_UNIQUE_CHILD, CHILD, child_id=4242),
|
||||
FakeEvent(DETACH_PARENT, OK), FakeEvent(ARM_EXEC, OK),
|
||||
FakeEvent(CONTINUE_CHILD, OK), FakeEvent(AWAIT_EXEC, OK),
|
||||
FakeEvent(REPLACE_EXACT_PAYLOAD, OK),
|
||||
FakeEvent(RESTORE_MUTATIONS, OK), FakeEvent(DETACH_CHILD, OK),
|
||||
FakeEvent(EMIT_RESULT, OK)]
|
||||
|
||||
|
||||
class LifecycleTests(unittest.TestCase):
|
||||
def test_success(self) -> None:
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(happy())))
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(result.child_id, 4242)
|
||||
self.assertTrue(result.parent_detached and result.child_detached)
|
||||
self.assertTrue(result.mutations_restored)
|
||||
self.assertFalse(result.child_terminated or result.existing_bigapp_killed)
|
||||
|
||||
def test_existing_bigapp_fails_without_kill(self) -> None:
|
||||
result = run_lifecycle(FakeLifecycleFacade((FakeEvent(CHECK_NO_BIGAPP, EXISTS),)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertFalse(result.existing_bigapp_killed)
|
||||
self.assertEqual(result.child_id, 0)
|
||||
|
||||
def test_parent_stage_failures_detach(self) -> None:
|
||||
for index in range(2, 5):
|
||||
events = happy()[:index + 1]
|
||||
events[index] = FakeEvent(events[index].operation, ERROR)
|
||||
events = events[:index + 1] + [FakeEvent(DETACH_PARENT, OK)]
|
||||
with self.subTest(index=index):
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertTrue(result.parent_detached)
|
||||
|
||||
def test_child_stage_failures_terminate_only_child(self) -> None:
|
||||
for index in range(7, 10):
|
||||
events = happy()[:index + 1]
|
||||
events[index] = FakeEvent(events[index].operation, ERROR)
|
||||
events = events[:index + 1] + [FakeEvent(TERMINATE_CHILD, OK)]
|
||||
with self.subTest(index=index):
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(result.child_id, 4242)
|
||||
self.assertTrue(result.child_terminated)
|
||||
|
||||
def test_replace_failure_restores_then_terminates(self) -> None:
|
||||
events = happy()[:11]
|
||||
events[10] = FakeEvent(REPLACE_EXACT_PAYLOAD, ERROR)
|
||||
events += [FakeEvent(RESTORE_MUTATIONS, OK), FakeEvent(TERMINATE_CHILD, OK)]
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertTrue(result.mutations_restored and result.child_terminated)
|
||||
self.assertLess(result.trace.index("RESTORE_MUTATIONS:OK"),
|
||||
result.trace.index("TERMINATE_CHILD:OK"))
|
||||
|
||||
def test_timeout_is_failure(self) -> None:
|
||||
events = happy()[:6]
|
||||
events[5] = FakeEvent(AWAIT_UNIQUE_CHILD, TIMEOUT)
|
||||
events += [FakeEvent(DETACH_PARENT, OK)]
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
|
||||
def test_main_tick_budget(self) -> None:
|
||||
events = happy()
|
||||
events[0] = FakeEvent(CHECK_NO_BIGAPP, NONE, ticks=64)
|
||||
events = events[:2] + [FakeEvent(DETACH_PARENT, OK)]
|
||||
with self.assertRaises(LifecycleModelError):
|
||||
run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
|
||||
def test_cleanup_failure_is_hard_error(self) -> None:
|
||||
events = happy()[:3]
|
||||
events[2] = FakeEvent(ARM_FORK, ERROR)
|
||||
events += [FakeEvent(DETACH_PARENT, ERROR)]
|
||||
with self.assertRaises(LifecycleModelError):
|
||||
run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
|
||||
def test_unused_or_wrong_order_is_rejected(self) -> None:
|
||||
result = run_lifecycle(FakeLifecycleFacade((FakeEvent(ATTACH_PARENT, OK),)))
|
||||
self.assertFalse(result.success)
|
||||
with self.assertRaises(LifecycleModelError):
|
||||
run_lifecycle(FakeLifecycleFacade(tuple(happy() + [FakeEvent(EMIT_RESULT, OK)])))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
Reference in New Issue
Block a user