This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure-injection tests for Phase-1.0AM bounded copy/restore."""
|
||||
|
||||
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 phase10am_bounded_copy_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
def prefix() -> list[FakeCopyEvent]:
|
||||
return [FakeCopyEvent(BACKUP_AUTHID, OK), FakeCopyEvent(BACKUP_CAPS, OK),
|
||||
FakeCopyEvent(SET_PRIV_AUTHID, OK), FakeCopyEvent(SET_PRIV_CAPS, OK)]
|
||||
|
||||
|
||||
class BoundedCopyTests(unittest.TestCase):
|
||||
def test_exact_multichunk_success_restores_both_fields(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 4, MORE),
|
||||
FakeCopyEvent(COPY_CHUNK, OK, 6, COMPLETE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK)]
|
||||
result = run_copy(CopyPlan(100, 200, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(result.copied, 10)
|
||||
self.assertTrue(result.child_alive)
|
||||
self.assertFalse(result.target_copy_performed)
|
||||
self.assertTrue(result.service_available)
|
||||
|
||||
def test_partial_error_is_reported_and_child_is_reaped(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 4, MORE),
|
||||
FakeCopyEvent(COPY_CHUNK, ERROR, 0, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertEqual(result.classification, "OFFLINE_PARTIAL_COPY_CONTAINED")
|
||||
self.assertEqual(result.copied, 4)
|
||||
self.assertFalse(result.child_alive)
|
||||
|
||||
def test_zero_and_oversized_progress_never_complete(self) -> None:
|
||||
for progress in (0, 11):
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, progress, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK)]
|
||||
with self.subTest(progress=progress):
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(result.copied, 0)
|
||||
|
||||
def test_both_restores_are_attempted_and_failure_contains_service(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, ERROR, 0, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, ERROR),
|
||||
FakeCopyEvent(RESTORE_AUTHID, ERROR),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, OK),
|
||||
FakeCopyEvent(TERMINATE_SERVICE, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertEqual(result.restore_failure_bits, 3)
|
||||
self.assertFalse(result.child_alive or result.service_available)
|
||||
|
||||
def test_caps_set_failure_still_restores_authid(self) -> None:
|
||||
events = prefix()[:3] + [FakeCopyEvent(SET_PRIV_CAPS, ERROR),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertEqual(result.classification,
|
||||
"OFFLINE_COPY_REJECTED_BEFORE_MUTATION")
|
||||
self.assertTrue(result.child_alive and result.service_available)
|
||||
|
||||
def test_restore_or_child_termination_failure_is_hard(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 1, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, ERROR)]
|
||||
with self.assertRaises(CopyModelError):
|
||||
run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
|
||||
def test_bounds_deadline_status_and_arithmetic(self) -> None:
|
||||
for args in ((0, 0, 0), (MAX_U64, 0, 2), (0, MAX_U64, 2)):
|
||||
with self.subTest(args=args), self.assertRaises(CopyModelError):
|
||||
CopyPlan(*args)
|
||||
events = prefix()
|
||||
events[0] = FakeCopyEvent(BACKUP_AUTHID, OK, ticks=MAX_TICKS)
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events[:2])))
|
||||
self.assertFalse(result.success)
|
||||
incomplete = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 9, COMPLETE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(incomplete)))
|
||||
self.assertFalse(result.success)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
Reference in New Issue
Block a user