This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
#!/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__])
|
||||
Reference in New Issue
Block a user