134 lines
6.0 KiB
Python
134 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Failure-injection tests for Phase-1.0AI mapping transactions."""
|
|
|
|
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 phase10ag_bounded_elf import LoadSegment, PF_R, PF_W, PF_X # noqa: E402
|
|
from phase10ai_mapping_model import * # noqa: E402,F403
|
|
|
|
|
|
def plan() -> MappingPlan:
|
|
return MappingPlan((
|
|
LoadSegment(PF_R | PF_X, 0x4000, 0, 0x1800, 0x1800, 0x4000),
|
|
LoadSegment(PF_R, 0x8000, 0x8000, 0x1000, 0x1000, 0x4000),
|
|
LoadSegment(PF_R | PF_W, 0xc000, 0xc000, 0x800, 0x1800, 0x4000),
|
|
), 7)
|
|
|
|
|
|
def happy(mapping: MappingPlan | None = None) -> list[FakeMappingEvent]:
|
|
mapping = mapping or plan()
|
|
events = [FakeMappingEvent(RESERVE_CHILD, OK, value=mapping.region_size),
|
|
FakeMappingEvent(CREATE_MIRROR, OK, value=mapping.region_size)]
|
|
for index, segment in enumerate(mapping.segments):
|
|
if segment.file_size:
|
|
events.append(FakeMappingEvent(COPY_FILE_BYTES, OK, index,
|
|
segment.file_size))
|
|
if segment.memory_size > segment.file_size:
|
|
events.append(FakeMappingEvent(ZERO_BSS, OK, index,
|
|
segment.memory_size - segment.file_size))
|
|
events.append(FakeMappingEvent(APPLY_RELATIVE, OK,
|
|
value=mapping.relative_relocations))
|
|
events.append(FakeMappingEvent(COPY_MIRROR_TO_CHILD, OK,
|
|
value=mapping.region_size))
|
|
for index, segment in enumerate(mapping.segments):
|
|
events.append(FakeMappingEvent(SET_FINAL_PROTECTION, OK, index,
|
|
segment.flags))
|
|
events += [FakeMappingEvent(SYNC_IMAGE, OK, value=mapping.region_size),
|
|
FakeMappingEvent(RELEASE_MIRROR, OK)]
|
|
return events
|
|
|
|
|
|
class MappingModelTests(unittest.TestCase):
|
|
def test_success(self) -> None:
|
|
mapping = plan()
|
|
result = run_mapping(mapping, FakeMappingFacade(tuple(happy(mapping))))
|
|
self.assertTrue(result.success and result.child_region_retained)
|
|
self.assertTrue(result.mirror_released)
|
|
self.assertFalse(result.child_region_unmapped or result.target_mapping_performed)
|
|
self.assertEqual(result.zeroed_bss_bytes, 0x1000)
|
|
|
|
def test_reserve_failure_has_no_cleanup(self) -> None:
|
|
mapping = plan()
|
|
events = (FakeMappingEvent(RESERVE_CHILD, ERROR, value=mapping.region_size),)
|
|
result = run_mapping(mapping, FakeMappingFacade(events))
|
|
self.assertFalse(result.success or result.child_region_unmapped)
|
|
|
|
def test_mirror_creation_failure_unmaps_child(self) -> None:
|
|
mapping = plan()
|
|
events = (FakeMappingEvent(RESERVE_CHILD, OK, value=mapping.region_size),
|
|
FakeMappingEvent(CREATE_MIRROR, ERROR, value=mapping.region_size),
|
|
FakeMappingEvent(UNMAP_CHILD, OK, value=mapping.region_size))
|
|
result = run_mapping(mapping, FakeMappingFacade(events))
|
|
self.assertFalse(result.success)
|
|
self.assertTrue(result.child_region_unmapped)
|
|
|
|
def test_every_post_mirror_failure_releases_and_unmaps(self) -> None:
|
|
mapping = plan()
|
|
baseline = happy(mapping)
|
|
for index in range(2, len(baseline)):
|
|
failed = baseline[:index + 1]
|
|
event = failed[index]
|
|
failed[index] = FakeMappingEvent(event.operation, ERROR,
|
|
event.segment_index, event.value)
|
|
failed += [FakeMappingEvent(RELEASE_MIRROR, OK),
|
|
FakeMappingEvent(UNMAP_CHILD, OK, value=mapping.region_size)]
|
|
with self.subTest(operation=event.operation, index=index):
|
|
result = run_mapping(mapping, FakeMappingFacade(tuple(failed)))
|
|
self.assertFalse(result.success)
|
|
self.assertTrue(result.mirror_released)
|
|
self.assertTrue(result.child_region_unmapped)
|
|
|
|
def test_cleanup_failure_is_hard_error(self) -> None:
|
|
mapping = plan()
|
|
events = happy(mapping)[:3]
|
|
event = events[-1]
|
|
events[-1] = FakeMappingEvent(event.operation, ERROR,
|
|
event.segment_index, event.value)
|
|
events.append(FakeMappingEvent(RELEASE_MIRROR, ERROR))
|
|
with self.assertRaises(MappingModelError):
|
|
run_mapping(mapping, FakeMappingFacade(tuple(events)))
|
|
|
|
def test_tick_budget_is_enforced_and_rolled_back(self) -> None:
|
|
mapping = plan()
|
|
events = happy(mapping)[:2]
|
|
first = events[0]
|
|
events[0] = FakeMappingEvent(first.operation, OK, value=first.value,
|
|
ticks=128)
|
|
events += [FakeMappingEvent(UNMAP_CHILD, OK, value=mapping.region_size)]
|
|
result = run_mapping(mapping, FakeMappingFacade(tuple(events)))
|
|
self.assertFalse(result.success)
|
|
self.assertTrue(result.child_region_unmapped)
|
|
|
|
def test_plan_rejects_wx_and_page_protection_overlap(self) -> None:
|
|
with self.assertRaises(MappingModelError):
|
|
MappingPlan((LoadSegment(PF_R | PF_W | PF_X, 0, 0, 1, 1, 0x4000),), 1)
|
|
with self.assertRaises(MappingModelError):
|
|
MappingPlan((LoadSegment(PF_R | PF_X, 0, 0, 1, 0x3000, 0x4000),
|
|
LoadSegment(PF_R | PF_W, 0x3000, 0x3000, 1, 1, 0x4000)), 1)
|
|
|
|
def test_wrong_binding_and_unused_events_are_rejected(self) -> None:
|
|
mapping = plan()
|
|
events = happy(mapping)
|
|
events[2] = FakeMappingEvent(COPY_FILE_BYTES, OK, 0, 1)
|
|
with self.assertRaises(MappingModelError):
|
|
run_mapping(mapping, FakeMappingFacade(tuple(events)))
|
|
with self.assertRaises(MappingModelError):
|
|
run_mapping(mapping, FakeMappingFacade(tuple(
|
|
happy(mapping) + [FakeMappingEvent(RELEASE_MIRROR, OK)])))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(argv=[__file__])
|