109 lines
4.6 KiB
Python
109 lines
4.6 KiB
Python
#!/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__])
|