118 lines
5.0 KiB
Python
118 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Failure injection for Phase-1.0AT descriptor ownership."""
|
|
|
|
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 phase10aq_worker_result_record import * # noqa: E402,F403
|
|
from phase10at_fd_deadline_model import * # noqa: E402,F403
|
|
|
|
|
|
PRECOMMIT = WorkerPrecommit(b"a" * 16, b"b" * 16, 101, 202, 7, 4096)
|
|
RAW = encode_record(PRECOMMIT, STATUS_SUCCESS, 4096)
|
|
|
|
|
|
def success_events() -> list[FakeFdEvent]:
|
|
return [FakeFdEvent(CREATE_PIPE, OK),
|
|
FakeFdEvent(SET_PARENT_READ_NONBLOCK, OK),
|
|
FakeFdEvent(SPAWN_RFFDG, OK),
|
|
FakeFdEvent(PARENT_CLOSE_WRITE, OK),
|
|
FakeFdEvent(CHILD_CLOSE_READ, OK),
|
|
FakeFdEvent(CHILD_CLOSE_WRITE, OK),
|
|
FakeFdEvent(REAP_CHILD, OK),
|
|
FakeFdEvent(PARENT_CLOSE_READ, OK)]
|
|
|
|
|
|
class FdDeadlineTests(unittest.TestCase):
|
|
def test_success_with_partial_reads_eintr_and_would_block(self) -> None:
|
|
reads = (FakeRead(EINTR), FakeRead(WOULD_BLOCK),
|
|
FakeRead(DATA, RAW[:40]), FakeRead(EINTR),
|
|
FakeRead(DATA, RAW[40:]))
|
|
result = run_fd_transaction(
|
|
PRECOMMIT, FakeFdFacade(tuple(success_events())), reads)
|
|
self.assertTrue(result.success and result.rffdg_used)
|
|
self.assertEqual(result.eintr_count, 2)
|
|
self.assertEqual(result.parent_fds_open + result.child_fds_open, 0)
|
|
self.assertFalse(result.worker_alive or result.live_fd_present)
|
|
|
|
def test_every_setup_failure_closes_only_acquired_resources(self) -> None:
|
|
expected_cleanup = {
|
|
0: [],
|
|
1: [PARENT_CLOSE_WRITE, PARENT_CLOSE_READ],
|
|
2: [PARENT_CLOSE_WRITE, PARENT_CLOSE_READ],
|
|
3: [TERMINATE_CHILD, CHILD_CLOSE_READ, CHILD_CLOSE_WRITE,
|
|
REAP_CHILD, PARENT_CLOSE_WRITE, PARENT_CLOSE_READ],
|
|
4: [TERMINATE_CHILD, CHILD_CLOSE_READ, CHILD_CLOSE_WRITE,
|
|
REAP_CHILD, PARENT_CLOSE_READ],
|
|
}
|
|
baseline = success_events()[:5]
|
|
for index, event in enumerate(baseline):
|
|
prefix = baseline[:index] + [FakeFdEvent(event.operation, ERROR)]
|
|
prefix += [FakeFdEvent(operation, OK)
|
|
for operation in expected_cleanup[index]]
|
|
with self.subTest(index=index):
|
|
result = run_fd_transaction(
|
|
PRECOMMIT, FakeFdFacade(tuple(prefix)),
|
|
(FakeRead(DATA, RAW),))
|
|
self.assertFalse(result.success)
|
|
self.assertEqual(result.parent_fds_open + result.child_fds_open, 0)
|
|
|
|
def test_deadline_eof_overflow_and_bad_record_contain_worker(self) -> None:
|
|
scripts = (
|
|
(FakeRead(DATA, RAW[:10], ticks=252), FakeRead(DATA, RAW[10:])),
|
|
(FakeRead(DATA, RAW[:10]), FakeRead(EOF)),
|
|
(FakeRead(DATA, RAW[:100]), FakeRead(DATA, RAW[100:] + b"x")),
|
|
(FakeRead(DATA, RAW), FakeRead(WOULD_BLOCK)),
|
|
(FakeRead(DATA, bytes(bytearray(RAW[:1]) + RAW[1:])),),
|
|
)
|
|
# Make the final script genuinely invalid.
|
|
damaged = bytearray(RAW)
|
|
damaged[0] ^= 1
|
|
scripts = scripts[:-1] + ((FakeRead(DATA, bytes(damaged)),),)
|
|
cleanup = [FakeFdEvent(TERMINATE_CHILD, OK),
|
|
FakeFdEvent(CHILD_CLOSE_WRITE, OK),
|
|
FakeFdEvent(REAP_CHILD, OK),
|
|
FakeFdEvent(PARENT_CLOSE_READ, OK)]
|
|
for reads in scripts:
|
|
events = success_events()[:5] + cleanup
|
|
with self.subTest(reads=reads):
|
|
result = run_fd_transaction(
|
|
PRECOMMIT, FakeFdFacade(tuple(events)), reads)
|
|
self.assertFalse(result.success or result.worker_alive)
|
|
self.assertTrue(result.containment_required)
|
|
|
|
def test_cleanup_failure_and_unused_operations_are_hard_errors(self) -> None:
|
|
events = [FakeFdEvent(CREATE_PIPE, OK),
|
|
FakeFdEvent(SET_PARENT_READ_NONBLOCK, ERROR),
|
|
FakeFdEvent(PARENT_CLOSE_WRITE, ERROR)]
|
|
with self.assertRaises(FdModelError):
|
|
run_fd_transaction(PRECOMMIT, FakeFdFacade(tuple(events)),
|
|
(FakeRead(DATA, RAW),))
|
|
with self.assertRaises(FdModelError):
|
|
run_fd_transaction(
|
|
PRECOMMIT,
|
|
FakeFdFacade(tuple(success_events() +
|
|
[FakeFdEvent(PARENT_CLOSE_READ, OK)])),
|
|
(FakeRead(DATA, RAW),))
|
|
|
|
def test_invalid_read_and_boundary_are_rejected(self) -> None:
|
|
with self.assertRaises(FdModelError):
|
|
FakeRead(DATA, b"")
|
|
with self.assertRaises(FdModelError):
|
|
run_fd_transaction(PRECOMMIT, FakeFdFacade(tuple(success_events())), ())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(argv=[__file__])
|