Files
Acelogic 1510d8c6ce Snapintosh public release
An EL2 monitor/hypervisor and toolchain that boots macOS 27 on the ASUS A16
(Snapdragon X2 Elite), with an Adreno Metal plug-in and IOGPU kext, and
air2ir3, an AIR to Mesa ir3 shader compiler.

MIT, with GPL-2.0 components listed in LICENSE and THIRD_PARTY_NOTICES.md.
No Apple, Qualcomm, ASUS or Microsoft binaries are included.
2026-10-21 21:26:36 -04:00

1365 lines
76 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Host tests for tools/cycle.py. No A16 access, ever.
Offline equivalence tests read the real workspace and write nothing: the rendered
observers against the per-generation wrappers of generations 61-66, the gen66
candidate manifest against tools/run-ca-memo-candidate.py, and `storage` against
tools/storage-gen66-memo.py.
Record tests run in copied workspaces under a temporary directory: the real records of
generations 61-66 are copied (never modified in place), read-only inputs are symbolic
links, and the only q1n1 hardware entry points present (q1n1proxy.py, a16gpu.py) are
fakes that play the A16's side from a state file. Stopped-state steps are played back
from each generation's real outputs and exit codes; the historical exit-6 sequencer
records are the oracle for which steps must run, in which order.
Run: python3 tools/test-cycle.py [-k NAME]
"""
import sys
sys.dont_write_bytecode = True
import argparse
import json
import os
import shutil
import struct
import subprocess
import tempfile
import time
import traceback
from pathlib import Path
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent
REAL = ROOT.parent
sys.path.insert(0, str(HERE))
from cycle_lib import (capture, common, evidence, launch, observers, preserve, retire, storage, # noqa: E402
substitute, unqualified)
from cycle_lib.common import Refused, Workspace, read, sha, sha_bytes # noqa: E402
GEN = {
61: 'installed-metal-ca-network-3600s-20260928',
63: 'installed-metal-ca-composite-2400s-20260928',
64: 'installed-metal-ca-quiet-memo-1800s-20260928',
65: 'installed-metal-ca-quiet-bisect-1800s-20260928',
66: 'installed-metal-ca-memo-2400s-20260928',
70: 'installed-metal-integrated-2400s-20260928',
71: 'installed-metal-integrated-guestes-2400s-20260928',
76: 'installed-metal-native-tables-1800s-20260929',
77: 'installed-metal-native-2a-1800s-20260929',
78: 'installed-metal-native-p3-1800s-20260929',
}
WRAPPERS = { # generation: (collector, ring, native startup) wrappers written by hand
61: ('collect-ca-network-20260928.py', 'capture-ca-network-ring-20260928.py', 'capture-native-startup-gen61-20260928.py'),
63: ('collect-ca-composite-20260928.py', 'capture-ca-composite-ring-20260928.py', 'capture-native-startup-gen63-20260928.py'),
64: ('collect-ca-quiet-memo-20260928.py', 'capture-ca-quiet-memo-ring-20260928.py', 'capture-native-startup-gen64-20260928.py'),
65: ('collect-ca-quiet-bisect-20260928.py', 'capture-ca-quiet-bisect-ring-20260928.py', 'capture-native-startup-gen65-20260928.py'),
66: ('collect-ca-memo-20260928.py', 'capture-ca-memo-ring-20260928.py', 'capture-native-startup-gen66-20260928.py'),
}
GEN66_MANIFEST = ROOT / 'candidates/gen66-memo.json'
HARDWARE_TOOLS = ('q1n1proxy.py', 'udplink.py', 'a16gpu.py', 'a16ctl.py')
BASE_DISPATCH = "exec(compile(source, str(base), 'exec'), dict(__file__=__file__, __name__='__main__'))"
FAKE_PROXY = r'''"""Fake q1n1proxy for tools/test-cycle.py: the A16 side is a JSON state file."""
import json
import struct
from pathlib import Path
STATE = Path(__file__).resolve().parents[2] / 'fake-a16.json'
STAGE_MAGIC = 0x3147545331314E51
class ProxyError(RuntimeError):
pass
def _load():
return json.loads(STATE.read_text())
def _save(state):
STATE.write_text(json.dumps(state, indent=1))
def _log(event):
state = _load()
state['log'].append(event)
_save(state)
class Link:
def close(self):
_log('close')
class Proxy:
def __init__(self):
self.link = Link()
def bootinfo(self):
return dict(_load()['info'])
def serror_state(self):
return dict(_load()['serror'])
def live_battery(self):
state = _load()
return state['battery_status'], dict(state['battery'])
def readmem(self, address, size, live=False):
for base, data in _load()['memory']:
data = bytes.fromhex(data)
if base <= address and address + size <= base + len(data):
return data[address - base:address - base + size]
raise ProxyError(f'unmapped read {address:#x}+{size}')
@staticmethod
def stage_configure(data, logo=None, xhci=False):
return data
@staticmethod
def stage_header(data):
magic, link = struct.unpack('<QQ', data[8:24])
if magic != STAGE_MAGIC:
raise ProxyError('not a stage image')
return link
def pick_stage(self, path):
info = self.bootinfo()
name = Path(path).name
sibling = name[:-8] + '.bin' if name.endswith('-alt.bin') else name[:-4] + '-alt.bin'
for candidate in (Path(path), Path(path).with_name(sibling)):
data = candidate.read_bytes()
link = self.stage_header(data)
if link != info['image_base'] and info['stage_base'] <= link < info['stage_base'] + info['stage_size']:
return candidate, data, link
raise ProxyError('no free stage slot')
def chainload(self, data, base=None):
state = _load()
base = base or self.stage_header(data)
before = state['info']['stage_generation']
if base == state['info']['image_base']:
raise ProxyError('that stage is linked for the address it would overwrite')
state['info']['stage_generation'] += 1
state['info']['image_base'] = base
state['log'].append(f'chainload {base:#x}')
_save(state)
return {'base': base, 'bytes': len(data), 'generation_before': before}
def connect(device=None, serial=None, interface=None, debug=False, wait=0.0):
state = _load()
if state.get('offline'):
raise ProxyError('no responding q1n1 endpoint')
state['log'].append(f'connect {device}')
_save(state)
return Proxy()
'''
FAKE_A16GPU = r'''"""Fake a16gpu for tools/test-cycle.py: quiesce writes the record the state file asks for."""
import json
import sys
from pathlib import Path
STATE = Path(__file__).resolve().parents[2] / 'fake-a16.json'
state = json.loads(STATE.read_text())
args = sys.argv[1:]
out = Path(args[args.index('--out') + 1])
command = [a for a in args if not a.startswith('-') and a != args[args.index('--out') + 1] and a != args[args.index('--device') + 1]]
state['log'].append('a16gpu ' + ' '.join(command))
STATE.write_text(json.dumps(state, indent=1))
record = dict(tool='a16gpu', command=command[0], stopped=None, quiesce=state.get('quiesce', 'graceful'),
serror_guard=dict(present=True, after=dict(before=0, during=0, unconsumed=0)),
gmu_steps=['oob_cleared', 'gbif_halted', 'prepare_slumber', 'gmu_reset'])
with out.open('x') as stream:
json.dump(record, stream)
print('quiesce', record['quiesce'])
'''
# ----------------------------------------------------------------------------- fixture
def link(target, source):
target.symlink_to(source)
def mirror_dir(target, source, copy_prefixes=(), cutoff=None, skip=()):
"""target becomes a real directory whose entries are links to source's entries,
except entries named with a tested prefix: files are copied (if older than
cutoff), directories left out, so every write stays under target."""
target.mkdir(parents=True, exist_ok=True)
for entry in source.iterdir():
if entry.name in skip or entry.name in ('.git', '__pycache__'):
continue
if any(entry.name.startswith(p) for p in copy_prefixes):
if entry.is_file() and not entry.is_symlink() and (cutoff is None or entry.stat().st_mtime < cutoff):
shutil.copy2(entry, target / entry.name)
continue
link(target / entry.name, entry)
def materialize(path):
"""Make every symlinked directory between the workspace and path a real directory
of links, so one file under it can be replaced without touching the original."""
parts = []
probe = path.parent
while probe.is_symlink() or not probe.exists() or probe.resolve() != probe:
parts.append(probe)
probe = probe.parent
for directory in reversed(parts):
if directory.is_symlink():
source = directory.resolve()
directory.unlink()
mirror_dir(directory, source)
class Scratch:
"""A copied workspace: tmp/Snapintosh, tmp/<runtime roots>, tmp/q1n1 with fakes."""
def __init__(self, tmp, generations, cutoff=None, restore_pins=True):
self.tmp = Path(tmp).resolve()
self.prefixes = [GEN[g] for g in generations]
runtimes = {Path(read(ROOT / 'build' / f'{p}-launch.json')['runtime_root']).name for p in self.prefixes}
snap = self.tmp / 'Snapintosh'
mirror_dir(snap, ROOT, skip=('build', 'tools', 'candidates'))
mirror_dir(snap / 'build', ROOT / 'build', self.prefixes, cutoff)
mirror_dir(snap / 'tools', ROOT / 'tools', skip=('cycle.py', 'cycle_lib'))
(snap / 'candidates').mkdir()
for name in runtimes:
mirror_dir(self.tmp / name, REAL / name, skip=('build', 'downloads'))
(self.tmp / name / 'downloads').symlink_to(os.readlink(REAL / name / 'downloads'))
mirror_dir(self.tmp / name / 'build', REAL / name / 'build', self.prefixes, cutoff)
for entry in REAL.iterdir():
if entry.name.startswith('Snapintosh-') and entry.name not in runtimes and entry.is_dir():
link(self.tmp / entry.name, entry)
mirror_dir(self.tmp / 'q1n1', REAL / 'q1n1', skip=('tools',))
mirror_dir(self.tmp / 'q1n1/tools', REAL / 'q1n1/tools', skip=HARDWARE_TOOLS)
(self.tmp / 'q1n1/tools/q1n1proxy.py').write_text(FAKE_PROXY)
(self.tmp / 'q1n1/tools/a16gpu.py').write_text(FAKE_A16GPU)
self.ws = Workspace(snap, origin=REAL)
self.ws.processes = lambda: {}
self.ws.settle_seconds = 0
if restore_pins:
self.restore_drifted_pins()
def restore_drifted_pins(self):
"""Put back launch-time content of source pins changed since (from the real
preservation's copies), the way a conductor would before re-collecting."""
for prefix in self.prefixes:
launch_record = read(ROOT / 'build' / f'{prefix}-launch.json')
monitor = Path(launch_record['monitor_path'])
runtime_build = self.ws.local(launch_record['runtime_root']) / 'build'
for suffix in ('.bin', '.elf'):
built = runtime_build / ('xnuboot' + suffix)
if sha(built) != sha(monitor.with_suffix(suffix)):
built.unlink()
shutil.copy2(monitor.with_suffix(suffix), built) # what the launcher copied in at boot
drift = [n for n, d in launch_record['source_sha256'].items() if not common.matches(n, d)]
if not drift:
continue
saved = next(read(p) for p in (ROOT / 'build').glob('*-preserved-*/preservation.json')
if read(p).get('prefix') == prefix)
for name in drift:
target = self.ws.local(name)
materialize(target)
target.unlink()
shutil.copy2(saved['runtime_sources'][name]['copy'], target)
def a16(self, generation, base, battery=(0, True, False, 100), service=None, run=None, quiesce='graceful'):
"""Put the fake A16 at a generation; optionally hold a stopped guest whose
monitor work locator and service words match the run's records."""
info = dict(current_el=2, stage_generation=generation, image_base=base, stage_base=0xb0000000,
stage_size=0x1000000, heap_base=0xbf0000000, heap_size=0x1000000, memory_map=0xc10000000,
memory_map_size=48, memory_map_stride=48)
memory = []
if service is not None:
work_module = common.load_module('test_monitor_work', run.runtime / 'probes/monitor_work.py')
entry = struct.pack('<IIQQQQ', 7, 0, 0x800000000, 0, 0x400000, 0) + bytes(8)
memory.append((info['memory_map'], entry.hex()))
free = work_module.free_spans(entry, 48, info)
locator = work_module.encode_locator(0xb00000000, work_module.WORK_SIZE, 0x8c0000000, 0xac0000000, info, free)
memory.append((info['heap_base'] + work_module.LOCATOR_OFFSET, locator.hex()))
layout, _ = common.context_layout(run.runtime)
memory.append((0xb00000000 + layout.offset('context', 'services'), struct.pack('<8Q', *service).hex()))
status, plausible, ac, percent = battery
state = dict(info=info, memory=memory, serror=dict(before=0, during=0, unconsumed=0),
battery_status=status, battery=dict(plausible=plausible, ac=ac, percent=percent,
text=f'{percent}% {"AC" if ac else "battery"}'),
quiesce=quiesce, log=[])
(self.tmp / 'fake-a16.json').write_text(json.dumps(state))
def a16_state(self):
return read(self.tmp / 'fake-a16.json')
class Playback:
"""Runs a capture step by restoring what that step produced in the real run
(its output files and exit code), without executing anything."""
RUNTIME_OUTPUTS = {'tools/audit-secondary-runtime.py': ('audit', ['-audit.json']),
'tools/capture-secondary-stop.py': ('cpu-stop', None),
'tools/capture-service-profile.py': ('service-profile', ['-service-profile.json']),
'tools/capture-console-tail.py': ('console-tail', ['-console-tail.json', '-console-tail.bin',
'-console-tail.txt'])}
def __init__(self, scratch, prefix, exits, primary_log=None, stand_in=None):
self.s, self.prefix, self.exits, self.primary_log = scratch, prefix, exits, primary_log
self.stand_in = stand_in or {} # step: real file from another run, when this run has none yet
self.ran = []
def outputs(self, command):
"""(step name, [(source file, destination)])."""
s, p = self.s, self.prefix
if 'substituted' in command: # cycle.py substituted ... -- HELPER SCRIPT ARGS: the step itself
command = [command[0], *command[command.index('--') + 1:]]
run_root = s.ws.local(read(ROOT / 'build' / f'{p}-launch.json')['runtime_root']) / 'build'
real = lambda suffix: ROOT / 'build' / f'{p}{suffix}'
if 'observe' in command:
kind = command[command.index('observe') + 1]
if kind == 'ring':
return 'ring', [(real(x), s.ws.build / f'{p}{x}') for x in ('-gpu-ring.json', '-gpu-ring.bin', '-gpu-fence.bin')]
if kind == 'collector':
produced = [x for x in (ROOT / 'build').glob(p + '-*') if x.stat().st_mtime > read(real('-launch.json'))['finished'] + 5
and not x.name.startswith(f'{p}-sequence') and 'pre-reset' not in x.name]
return 'primary', [(x, s.ws.build / x.name) for x in produced]
attempt = command[command.index('--attempt') + 1]
stem = 'native-startup-state' if kind == 'native' else 'crash-retention'
source = sorted((ROOT / 'build').glob(f'{p}-{stem}-*-binding.json'))[-1]
old = source.name[len(p) + len(stem) + 2:-len('-binding.json')]
return ('native-startup' if kind == 'native' else 'crash-retention',
[(source, s.ws.build / f'{p}-{stem}-{attempt}-binding.json'),
(source.with_name(f'{p}-{stem}-{old}.json'), s.ws.build / f'{p}-{stem}-{attempt}.json')])
if 'tools/capture-panel.py' in command:
return 'panel', [(real('-panel.png'), run_root / f'{p}-panel.png')]
if 'tools/auxkc-verdict.py' in command:
return 'verdict', []
script = command[2]
if script == 'tools/capture-kernel-waits.py':
suffix = command[command.index('--output-suffix') + 1] if '--output-suffix' in command else 'kernel-waits'
return suffix, [(real(f'-{suffix}.json'), run_root / f'{p}-{suffix}.json')]
name, suffixes = self.RUNTIME_OUTPUTS[script]
if name == 'cpu-stop':
cpu = command[command.index('--cpu') + 1]
suffixes, name = [f'-cpu{cpu}-stop.json'], f'cpu{cpu}-stop'
return name, [(real(x), run_root / f'{p}{x}') for x in suffixes]
def __call__(self, command, cwd, env, log):
name, files = self.outputs(command)
code = self.exits.get(name, 0)
self.ran.append(dict(name=name, command=command, env={k: env[k] for k in ('SNAP_STOPPED_EXIT', 'SNAP_DEVICE')}))
with log.open('x') as stream:
if name == 'primary':
stream.write(self.primary_log.read_text())
else:
stream.write(f'played back {name} exit {code}\n')
if code == 0:
for source, dest in files:
source = self.stand_in.get(name, source)
if source.is_file() and not dest.exists():
shutil.copy2(source, dest)
return code
def historical_steps(prefix):
"""(name, required, command) of the steps a hand-written exit-6 sequencer ran itself."""
record = read(ROOT / 'build' / f'{prefix}-exit6-capture-v1.json')
return [(s['name'], s['required'], s['command'], s['exit_code']) for s in record['steps'] if not s.get('prior')]
def cycle_commands_parse(commands, prefix):
"""Every cycle.py command a plan writes (and the ring command inside the rendered
collector) is one cycle.py's own parser accepts, naming this run."""
import runpy
parse = runpy.run_path(str(HERE / 'cycle.py'), run_name='cycle_parse')['parse']
for command in [c[2:] for c in commands if len(c) > 2 and c[1].endswith('tools/cycle.py')]:
args = parse(command)
assert args.command == 'observe' and args.launch == prefix, command
assert args.rest and args.rest[0] in ('--prefix', '--attempt'), command
def expect_refusal(function, text):
try:
function()
except Refused as refusal:
assert text in str(refusal), f'refused for another reason: {refusal}'
return str(refusal)
raise AssertionError(f'expected a refusal containing {text!r}')
# ----------------------------------------------------------------------------- offline equivalence
def run_record(generation):
"""The attributes observers.Params reads, straight from a launch record."""
prefix = GEN[generation]
record = read(ROOT / 'build' / f'{prefix}-launch.json')
return type('LaunchView', (), dict(prefix=prefix, generation=generation, image_base=common.image_base(generation),
runtime=Path(record['runtime_root']), launch=record))()
def wrapper_source(path, collector=False):
"""The program a per-generation wrapper would run, computed by the wrapper itself
in its offline mode (collectors: --syntax-only; others: dispatch replaced)."""
namespace = dict(__file__=str(path), __name__='wrapper_under_test')
text = path.read_text()
saved = sys.argv
if collector:
sys.argv = [str(path), '--syntax-only']
else:
assert text.count(BASE_DISPATCH) == 1, path
text = text.replace(BASE_DISPATCH, 'GENERATED_SOURCE = source')
try:
from contextlib import redirect_stdout
import io
with redirect_stdout(io.StringIO()):
exec(compile(text, str(path), 'exec'), namespace)
finally:
sys.argv = saved
return namespace['source'] if collector else namespace['GENERATED_SOURCE']
def ring_command_in_collector(ws, p):
"""The ring command the rendered standard collector would run, taken from the
collect-metal-gui program it builds (captured in the collector's --check mode)."""
import ast
import builtins
import io
from contextlib import redirect_stdout
gui, base = str(observers.pinned(ws, 'gui_collector')), observers.pinned(ws, 'collector')
seen, original, saved = [], builtins.compile, sys.argv
def hook(source, filename, *args, **kwargs):
if filename == gui:
seen.append(source)
return original(source, filename, *args, **kwargs)
builtins.compile, sys.argv = hook, [str(base), '--check']
try:
with redirect_stdout(io.StringIO()):
exec(original(observers.render_collector(ws, p), str(base), 'exec'), dict(__file__=str(base), __name__='__main__'))
finally:
builtins.compile, sys.argv = original, saved
calls = [n for n in ast.walk(ast.parse(seen[0])) if isinstance(n, ast.Call) and ast.unparse(n.func) == 'subprocess.run'
and isinstance(n.args[0], ast.List) and "'observe'" in ast.unparse(n.args[0])]
assert len(calls) == 1
return eval(compile(ast.Expression(calls[0].args[0]), 'ring-command', 'eval'),
dict(sys=sys, os=os, ROOT=ws.root, PREFIX=p.prefix))
def test_rendered_observers_equal_generation_wrappers():
"""Collector, ring and native observers rendered from records equal the hand-written
wrappers of generations 61, 63, 64, 65 and 66 (collector: except its one ring entry)."""
ws = Workspace(ROOT)
display = launch.Manifest(ws, GEN66_MANIFEST).observers['display']
for generation, (collector, ring, native) in WRAPPERS.items():
p = observers.Params(run_record(generation), display)
theirs = wrapper_source(ROOT / 'build' / collector, collector=True)
ring_entry = observers.RING_ENTRY.replace('capture-panel-mode-ring-20260928.py', ring)
assert theirs.count(ring_entry) == 1, generation
mine = observers.render_collector(ws, p)
assert mine.count(observers.CYCLE_RING_ENTRY) == 1, generation
assert mine.replace(observers.CYCLE_RING_ENTRY, '') == theirs.replace(ring_entry, ''), f'collector {generation}'
assert observers.render_ring(ws, p) == wrapper_source(ROOT / 'build' / ring), f'ring {generation}'
cycle_commands_parse([ring_command_in_collector(ws, p)], p.prefix)
assert observers.render_native(ws, p) == wrapper_source(ROOT / 'build' / native), f'native {generation}'
def test_gen66_manifest_reproduces_run_ca_memo_candidate():
"""Acceptance: the gen66 manifest passes run-sks-transport-candidate.py the same
arguments and environment as tools/run-ca-memo-candidate.py (and as the real gen66
launch record), through a byte-identical transformed launcher."""
command = [sys.executable, str(HERE / 'cycle.py'), 'launch', '--candidate', str(GEN66_MANIFEST), '--check-inputs']
compare = subprocess.run(command + ['--compare-adapter', str(HERE / 'run-ca-memo-candidate.py')],
capture_output=True, text=True, cwd=ROOT)
report = json.loads(compare.stdout)
assert compare.returncode == 0 and report['status'] == 'EQUIVALENT', report
assert report['launch_record_arguments_identical'] and report['launch_record_environment_identical']
assert report['inputs_digest_mismatch'] == []
mine = subprocess.run(command, capture_output=True, text=True, cwd=ROOT)
theirs = subprocess.run([sys.executable, str(HERE / 'run-ca-memo-candidate.py'), '--check-inputs'],
capture_output=True, text=True, cwd=ROOT)
assert mine.returncode == theirs.returncode == 0
assert mine.stdout == theirs.stdout, 'check-inputs output differs'
return dict(only_here=len(report['inputs_only_here']), only_in_adapter=len(report['inputs_only_in_adapter']))
def test_storage_reproduces_gen66_adapter():
"""`storage` derives the approved-header chain, package, preceding persistence and
seed from records and renders the same programs as tools/storage-gen66-memo.py."""
ws = Workspace(ROOT)
adapter = HERE / 'storage-gen66-memo.py'
for mode in ('persistence', 'snapshot'):
namespace = dict(__file__=str(adapter), __name__='adapter_under_test')
saved = sys.argv
sys.argv = [str(adapter), mode, '--check']
try:
from contextlib import redirect_stdout
import io
with redirect_stdout(io.StringIO()):
exec(compile(adapter.read_text(), str(adapter), 'exec'), namespace)
finally:
sys.argv = saved
out = ROOT / 'build/ca-memo-deploy-20260928/snapshot-v1' if mode == 'snapshot' else None
source, _, facts, chain = storage.render(ws, GEN[66], mode, out)
assert source == namespace['source'], mode
assert chain['baseline'] == namespace['baseline'], mode
# ----------------------------------------------------------------------------- capture
def test_capture_continues_gen65_like_its_completion_sequencer():
"""Gen65 stopped (exit 6) after the standard collector ran audit and CPU1 stop and was
refused at the service profile, and the native observers completed. Capture must run
exactly what build/capture-exit6-gen65-20260928.py ran: console tail, panel, ring,
then kernel waits, mounts, verdict - and nothing already done."""
prefix = GEN[65]
started = read(ROOT / 'build' / f'{prefix}-exit6-capture-v1.json')['started']
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [65], cutoff=started)
history = historical_steps(prefix)
manifest = write_manifest(s, 65)
plan = capture.plan(s.ws, prefix, manifest_path=manifest)
assert {st['name']: st['state'] for st in plan['report']['steps']}['service-profile'] == 'refused'
runner = Playback(s, prefix, {n: c for n, _, _, c in history})
result = capture.execute(s.ws, plan, runner)
assert [r['name'] for r in runner.ran] == [n for n, _, _, _ in history], runner.ran
by_name = {r['name']: r['command'] for r in runner.ran}
for name, _, command, _ in history:
if 'run-stopped-cdc-read' in ' '.join(command):
assert by_name[name][2:] == command[2:], name
assert all(r['env']['SNAP_STOPPED_EXIT'] == '6' for r in runner.ran)
assert result['status'] == 'STOPPED_STATE_CAPTURED', result
assert capture.plan(s.ws, prefix, manifest_path=manifest)['steps'] == []
def test_capture_runs_gen64_early_boot_set_after_failed_collector():
"""Gen64 (exit 6): the standard collector's audit failed and both native observers
failed for lack of an audit. Capture must rerun all of them in the early-boot order
build/capture-exit6-gen64-20260928.py used, required steps first."""
prefix = GEN[64]
started = read(ROOT / 'build' / f'{prefix}-exit6-capture-v1.json')['started']
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [64], cutoff=started)
history = historical_steps(prefix)
plan = capture.plan(s.ws, prefix, manifest_path=write_manifest(s, 64))
assert plan['report']['set'] == 'early-boot'
assert plan['copies'] == [], 'the failed standard collector already copied its inputs'
runner = Playback(s, prefix, {n: c for n, _, _, c in history})
result = capture.execute(s.ws, plan, runner)
assert [(r['name']) for r in runner.ran] == [n for n, _, _, _ in history], runner.ran
cycle_commands_parse([r['command'] for r in runner.ran], prefix)
assert [st['required'] for st in read(Path(result['record']))['steps']] == [r for _, r, _, _ in history]
assert result['status'] == 'STOPPED_STATE_CAPTURED', result
def test_capture_exit9_runs_the_standard_collector_then_native_observers():
"""Gen63 (exit 9, nothing collected yet): the whole standard collector runs as one
step with SNAP_STOPPED_EXIT=9, then native startup and crash retention, all required."""
prefix = GEN[63]
finished = read(ROOT / 'build' / f'{prefix}-launch.json')['finished']
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [63], cutoff=finished + 5)
plan = capture.plan(s.ws, prefix, manifest_path=write_manifest(s, 63))
assert [(st['name'], st['required']) for st in plan['steps']] == \
[('primary', True), ('native-startup', True), ('crash-retention', True)]
assert plan['copies'] == [], 'the standard collector does its own preflight'
runner = Playback(s, prefix, {}, primary_log=ROOT / 'build' / f'{prefix}-sequence-primary-v1.log')
result = capture.execute(s.ws, plan, runner)
assert [r['name'] for r in runner.ran] == ['primary', 'native-startup', 'crash-retention']
cycle_commands_parse([r['command'] for r in runner.ran], prefix)
assert runner.ran[0]['env']['SNAP_STOPPED_EXIT'] == '9'
assert result['status'] == 'STOPPED_STATE_CAPTURED', result
def test_exit12_needs_its_early_stop_record():
"""An early stop (exit 12) is a normal stopped guest, but only with the launcher's
PREFIX-early-stop.json; capture records it."""
prefix = GEN[66]
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [66])
path = s.ws.prefixed(prefix, '-launch.json')
record = read(path)
record['exit_code'] = 12
path.write_text(json.dumps(record))
expect_refusal(lambda: evidence.Run(s.ws, prefix), 'early stop')
stop = s.ws.prefixed(prefix, '-early-stop.json')
stop.write_text(json.dumps(dict(outcome='stopped', source='guest', reason=1)))
run = evidence.Run(s.ws, prefix)
assert run.summary()['early_stop']['sha256'] == sha(stop) and run.summary()['stop'] == 'early stop'
# ----------------------------------------------------------------------------- preserve / retire
def gen66_before_preservation(tmp):
cutoff = (ROOT / 'build' / f'{GEN[66]}-pre-reset-verification.json').stat().st_mtime
return Scratch(tmp, [66], cutoff=cutoff)
def test_gen66_exit7_preserve_then_retire_with_graceful_quiesce():
"""The standard collectors completed after a monitor-refusal stop (exit 7): preserve
binds them, retire verifies the parked stop, quiesces the GPU the run initialised,
chainloads 0xb0800000 -> 0xb0000000 and confirms generation 67 idle - the same end
state as the real gen66 retirement. Both outputs refuse reuse."""
prefix = GEN[66]
real = read(ROOT / 'build' / f'{prefix}-retirement-v1.json')
with tempfile.TemporaryDirectory() as tmp:
s = gen66_before_preservation(tmp)
done = preserve.preserve(s.ws, prefix)
saved = read(done['output'])
assert saved['exit_code'] == 7 and saved['capture']['set'] == 'standard'
assert all(v['state'] == 'complete' for v in saved['capture']['steps'].values())
programs = {Path(p).name for p in saved['diagnosis']}
assert {'collect-ca-memo-20260928.py', 'capture-ca-memo-ring-20260928.py',
'capture-native-startup-gen66-20260928.py', 'capture-crash-retention-gen66-20260928.py'} <= programs
assert 'GEN66-RESULT-20260928.md' in {Path(p).name for p in saved['documents']}
expect_refusal(lambda: preserve.preserve(s.ws, prefix), 'reused output')
ready = retire.retire(s.ws, prefix, done['sha256'], check=True)
assert ready['status'] == 'READY' and ready['target_base'] == 0xb0000000
run = evidence.Run(s.ws, prefix)
s.a16(66, 0xb0800000, service=saved['service'], run=run)
result = retire.retire(s.ws, prefix, done['sha256'])
record = read(result['output'])
log = s.a16_state()['log']
assert log.count('a16gpu quiesce') == 1 and 'chainload 0xb0000000' in log
assert record['status'] == real['status'] == 'GENERATION67_IDLE'
assert record['after']['image_base'] == real['after']['image_base'] == 0xb0000000
assert Path(record['chosen_stage']['path']).name == Path(real['chosen_stage']['path']).name
assert record['quiesce_sha256'] == sha(s.ws.prefixed(prefix, '-quiesce-v1.json'))
expect_refusal(lambda: retire.retire(s.ws, prefix, done['sha256'], check=True), 'reused output')
def test_idle_generation_inherits_the_graceful_quiesce():
"""Gen62 never initialised the GPU: retiring it must not run a16gpu (a quiesce of a
GMU held in reset never reports graceful) and must bind gen61's graceful quiesce -
the same bindings the hand-written retire-gen62-composite.py recorded."""
prefix = GEN[61]
real = read(ROOT / 'build/ca-composite-deploy-gen62-20260928/retirement-gen62-v1.json')
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [61])
prior = s.ws.prefixed(prefix, '-retirement-v1.json')
idle = dict(after_retirement=str(prior), retirement_sha256=sha(prior))
label = 'ca-composite-deploy-gen62-20260928'
apply_record = s.ws.build / label / 'apply-v1.json'
plan = retire.retire(s.ws, label, check=True, idle=idle, binds=[apply_record])
assert plan['kind'] == 'idle' and plan['gpu_writes'] is False
s.a16(62, 0xb0800000)
record = read(retire.retire(s.ws, label, idle=idle, binds=[apply_record])['output'])
log = s.a16_state()['log']
assert not any(e.startswith('a16gpu') for e in log) and 'chainload 0xb0000000' in log
assert record['status'] == real['status'] == 'GENERATION63_IDLE'
assert record['prior_retirement_sha256'] == real['prior_retirement_sha256']
assert record['inherited_quiesce_sha256'] == real['inherited_quiesce_sha256']
assert record['bound_sha256'][str(apply_record)] == real['apply_sha256']
def test_refusals():
"""Each refusal happens before any hardware write (no quiesce, no chainload)."""
prefix = GEN[66]
rows = []
with tempfile.TemporaryDirectory() as tmp:
s = gen66_before_preservation(tmp)
done = preserve.preserve(s.ws, prefix)
saved = read(done['output'])
run = evidence.Run(s.ws, prefix)
def untouched():
log = s.a16_state()['log']
assert not any(e.startswith(('a16gpu', 'chainload')) for e in log), log
s.a16(65, 0xb0800000, service=saved['service'], run=run)
rows.append(('wrong generation', expect_refusal(lambda: retire.retire(s.ws, prefix, done['sha256']),
'expected 66')))
untouched()
failed = s.ws.prefixed(prefix, '-retirement-v1.json')
assert read(failed)['status'] == 'FAILED'
for leftover in (failed, s.ws.prefixed(prefix, '-retirement-v1-tool')):
shutil.rmtree(leftover) if leftover.is_dir() else leftover.unlink()
s.a16(66, 0xb0000000, service=saved['service'], run=run)
rows.append(('image base not the alternating slot',
expect_refusal(lambda: retire.retire(s.ws, prefix, done['sha256']), 'expected 66/EL2/0xb0800000')))
untouched()
for leftover in (failed, s.ws.prefixed(prefix, '-retirement-v1-tool')):
shutil.rmtree(leftover) if leftover.is_dir() else leftover.unlink()
s.ws.processes = lambda: {4242: (0.0, 1, 'python3 tools/run-ca-next-candidate.py --boot')}
rows.append(('another hardware client', expect_refusal(lambda: retire.retire(s.ws, prefix, done['sha256']),
'another hardware client')))
s.ws.processes = lambda: {}
untouched()
artifact = s.ws.prefixed(prefix, '-console-tail.json')
original = artifact.read_bytes()
artifact.write_bytes(original + b' ')
rows.append(('changed input hash', expect_refusal(lambda: retire.retire(s.ws, prefix, done['sha256'], check=True),
f'{prefix}-console-tail.json changed')))
artifact.write_bytes(original)
rows.append(('wrong preservation hash', expect_refusal(
lambda: retire.retire(s.ws, prefix, '0' * 64, check=True), 'preservation hash differs')))
link_path = s.ws.local(run.launch['runtime_root']) / 'downloads'
link_path.unlink()
rows.append(('missing downloads link', expect_refusal(lambda: preserve.validate(s.ws, prefix),
'downloads is missing or not a symbolic link')))
link_path.symlink_to('../Snapintosh/downloads')
return sorted(name for name, _ in rows)
# ----------------------------------------------------------------------------- launch admission
def write_manifest(scratch, run_generation, **changes):
"""A manifest in the scratch workspace for an existing run (capture needs its display
binding) or, with changes, for the next candidate."""
data = read(GEN66_MANIFEST)
previous = read(ROOT / 'build' / f'{GEN[run_generation]}-launch.json')['previous_stopped_prefix']
data.update(prefix=GEN[run_generation], name=f'gen{run_generation}-test',
predecessor=dict(data['predecessor'], prefix=previous))
data.update(changes)
path = scratch.ws.root / 'candidates' / f'{data["prefix"]}.json'
path.write_text(json.dumps(data, indent=2))
return path
def test_launch_admission_battery_generation_and_stage_pair():
"""The admission a candidate passes before any GPU write: the predecessor's cycle
retirement and graceful quiesce, the live generation, and the battery policy
(50% without AC by default since 2026-10-19, --battery-minimum between the floor of 50 and 100)."""
prefix = GEN[66]
with tempfile.TemporaryDirectory() as tmp:
s = gen66_before_preservation(tmp)
done = preserve.preserve(s.ws, prefix)
run = evidence.Run(s.ws, prefix)
s.a16(66, 0xb0800000, service=read(done['output'])['service'], run=run)
retire.retire(s.ws, prefix, done['sha256'])
predecessor = dict(prefix=prefix, retirement=s.ws.relative(s.ws.prefixed(prefix, '-retirement-v1.json')),
preservation=s.ws.relative(Path(done['output'])))
manifest = launch.Manifest(s.ws, write_manifest(s, 66, prefix='installed-metal-ca-next-2400s-20260928',
generation=67, predecessor=predecessor))
scope = dict(deployment_binding={})
s.a16(67, 0xb0000000, battery=(0, True, False, 45))
expect_refusal(lambda: launch.admit(s.ws, manifest, scope, common.battery_minimum(None)), 'battery below 50%')
s.a16(67, 0xb0000000, battery=(0, True, False, 72))
expect_refusal(lambda: launch.admit(s.ws, manifest, scope, common.battery_minimum(80)), 'battery below 80%')
info, battery = launch.admit(s.ws, manifest, scope, common.battery_minimum(70))
assert info['stage_generation'] == 67 and battery['required_minimum_without_ac'] == 70
assert scope['deployment_binding']['preservation_sha256'] == done['sha256']
expect_refusal(lambda: common.battery_minimum(40), 'outside 50..100')
s.a16(66, 0xb0800000)
expect_refusal(lambda: launch.admit(s.ws, manifest, scope, 50), 'expected 67')
early = dict(read(manifest.path), stages={
'q1n1-early-stop/build/uefi/q1n1-stage.bin': '31682994ea7df1858fab7247a82453818d63a0df1a0e118f3809880733775301',
'q1n1-early-stop/build/uefi/q1n1-stage-alt.bin': 'e5de280843597c0318b688450cbc82a6a0bbe6c409dc616cc260c1d4b6c6d405'})
manifest.path.write_text(json.dumps(early))
expect_refusal(lambda: launch.admit(s.ws, launch.Manifest(s.ws, manifest.path), scope, 50), 'stage pair')
GEN70_MANIFEST = ROOT / 'candidates/gen70-integrated.json'
def validate_candidate(data):
"""launch.validate() and the offline admission for a manifest dict, in process,
against the real workspace. validate() imports guestprofile and its siblings
from the runtime root; they are dropped before and after so no other root's
copies are reused."""
runtime_modules = ('guestprofile', 'trustcache', 'auxkc', 'nvram', 'adreno', 'appledt')
saved = list(sys.path)
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / 'candidate.json'
path.write_text(json.dumps(data))
for name in runtime_modules:
sys.modules.pop(name, None)
try:
ws = Workspace(ROOT)
manifest = launch.Manifest(ws, path)
scope = {}
candidate, old, arguments, settings = launch.validate(ws, manifest, scope)
idle = launch.admit_records(ws, manifest, scope)
return dict(old=old, binding=scope['deployment_binding'], arguments=arguments, settings=settings,
candidate=candidate, idle=idle)
finally:
sys.path[:] = saved
for name in runtime_modules:
sys.modules.pop(name, None)
def test_gen70_explicit_auxkc_and_ssd_installed_after_baseline():
"""A candidate whose AuxKC and installed SSD differ from its baseline launch's names
both explicitly (`auxkc`, `ssd.after_baseline`); the launch record then carries
what this boot loads and expects. Each explicit fact is checked: dropping or
bending one refuses instead of falling back to the baseline's values."""
if not GEN70_MANIFEST.is_file():
return dict(skipped='no candidates/gen70-integrated.json')
good = read(GEN70_MANIFEST)
result = validate_candidate(good)
ws = Workspace(ROOT)
old, binding = result['old'], result['binding']
package = read(ws.path(good['ssd']['package']))
assert result['idle'] == 70
assert (old['auxkc'], old['auxkc_sha256'], old['kext_sha256']) == \
(str(ws.path(good['auxkc']['path'])), good['auxkc']['sha256'], good['kext_sha256'])
assert old['expected_window_header_sha256'] == package['header_after_sha256'] != binding['baseline_window_header_sha256']
assert old['ssd_apply_record'] == str(ws.path(good['ssd']['apply_record']))
assert old['storage_snapshot_generation'] == 69
assert (binding['collection_sha256'], binding['kext_sha256'], binding['network_qualification_sha256']) == \
(good['auxkc']['sha256'], good['auxkc']['kext_sha256'], good['auxkc']['qualification_sha256'])
assert binding['staged_collection_sha256'] != binding['collection_sha256']
assert result['settings']['SNAP_EARLY_STOP'] == 'guest,host'
def refused(change, text):
data = json.loads(json.dumps(good))
change(data)
expect_refusal(lambda: validate_candidate(data), text)
def drop_auxkc(d):
del d['auxkc']
refused(drop_auxkc, 'AuxKC differs')
refused(lambda d: d['auxkc'].update(sha256='0' * 64), 'is missing or not')
network = 'Snapintosh-adreno-buffer-textures/build/ca-network-qualification-20260928-v2.json'
refused(lambda d: d['auxkc'].update(qualification=network, qualification_sha256=sha(ws.path(network))),
'name another collection')
refused(lambda d: d['arguments'].pop('--auxkc'), 'argument AuxKC')
def drop_after(d):
del d['ssd']['after_baseline']
refused(drop_after, 'SSD package differs')
earlier = 'Snapintosh-shaders/build/shader-cache-deploy-20260928/local-stage-v1/stage.json'
refused(lambda d: d['ssd']['after_baseline'].update(local_stage=earlier), 'named snapshot and stage')
refused(lambda d: d['ssd'].update(apply_generation=70), 'SSD apply generation')
# Admission: the idle generation must have been reached after that apply and
# its retirement must bind the apply record.
from types import SimpleNamespace
manifest = launch.Manifest(ws, GEN70_MANIFEST)
other = 'Snapintosh/build/ca-composite-deploy-gen62-20260928/apply-v1.json'
for ssd, text in ((dict(manifest.ssd, apply_generation=70), 'not before the idle generation'),
(dict(manifest.ssd, apply_record=other), 'does not bind the SSD apply record')):
bent = SimpleNamespace(**dict(vars(manifest), ssd=ssd))
expect_refusal(lambda: launch.admit_records(ws, bent, dict(deployment_binding={})), text)
return dict(refusals=9)
GEN71_MANIFEST = ROOT / 'candidates/gen71-integrated-regular.json'
def before_retirement(prefix):
"""Copy cutoff that leaves out a real retirement of this prefix (and anything after it)."""
real = ROOT / 'build' / f'{prefix}-retirement-v1.json'
return read(real)['started'] if real.is_file() else None
def test_loader_failure_before_the_guest_is_retired_with_a_graceful_quiesce():
"""Generation70 prepared the GPU, then its loader refused native Image4 (exit 2)
before the upload: no guest ran. --loader-failure binds the launch record, boot log,
GPU preparation and SSD apply records, quiesces the GPU gracefully, chainloads the
regular pair and confirms generation 71 idle; the gen71 candidate's admission
accepts that retirement (stage pair and SSD apply binding included)."""
prefix = GEN[70]
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [70], cutoff=before_retirement(prefix))
expect_refusal(lambda: retire.retire(s.ws, prefix, check=True), 'not a stopped guest')
ready = retire.retire(s.ws, prefix, check=True, pre_guest=True)
assert ready['kind'] == 'loader-failure' and ready['exit_code'] == 2
assert ready['loader_verdict'] == 'native Image4 refused: resident stage differs from its ELF'
assert ready['no_guest']['verdicts'] == ['== LOADER FAILURE (exit 2) ==']
s.a16(70, 0xb0800000)
record = read(retire.retire(s.ws, prefix, pre_guest=True)['output'])
log = s.a16_state()['log']
assert log.count('a16gpu quiesce') == 1 and 'chainload 0xb0000000' in log
assert record['status'] == 'GENERATION71_IDLE' and record['after']['image_base'] == 0xb0000000
assert record['chosen_stage']['sha256'] == common.PINS['stages']['q1n1/build/uefi/q1n1-stage.bin']
assert record['quiesce_sha256'] == sha(s.ws.prefixed(prefix, '-quiesce-v1.json'))
bound = {Path(k).name: v for k, v in record['bound_sha256'].items()}
for suffix in ('-launch.json', '-boot.log', '-prepare.log', '-adopt.json'):
assert bound[prefix + suffix] == sha(ROOT / 'build' / (prefix + suffix)), suffix
assert bound[f'{prefix}-fence.json'] == sha(REAL / 'q1n1/build/a16-gpu' / f'{prefix}-fence.json')
assert bound['apply-v1.json'] == read(ROOT / 'build' / f'{prefix}-launch.json')['ssd_apply_record_sha256']
manifest = launch.Manifest(s.ws, GEN71_MANIFEST)
assert launch.admit_records(s.ws, manifest, dict(deployment_binding={})) == 71
def test_loader_failure_refusals():
"""A loader-failure retirement refuses a boot log that reached the upload, a record
written at the monitor call, an exit that is not a loader failure, a wrong live
generation (before any GPU write) and a quiesce that is not graceful (before the
chainload)."""
prefix = GEN[70]
rows = []
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [70], cutoff=before_retirement(prefix))
attempt = lambda: retire.retire(s.ws, prefix, check=True, pre_guest=True)
boot_log = s.ws.prefixed(prefix, '-boot.log')
original = boot_log.read_text()
boot_log.write_text(original.replace('\n== LOADER FAILURE', '\n uploaded in 11.2s\n== LOADER FAILURE'))
rows.append(('boot log reached the upload', expect_refusal(attempt, 'shows the upload or the guest run')))
boot_log.write_text(original)
entered = s.ws.prefixed(prefix, '-result.json')
entered.write_text('{}')
rows.append(('record written at the monitor call', expect_refusal(attempt, 'reached the monitor call')))
entered.unlink()
path = s.ws.prefixed(prefix, '-launch.json')
text = path.read_text()
path.write_text(json.dumps(dict(json.loads(text), exit_code=9)))
rows.append(('not a loader exit', expect_refusal(attempt, 'not a pre-guest loader exit')))
path.write_text(text)
out = retire.outputs(s.ws, prefix)
def clear():
for leftover in out.values():
shutil.rmtree(leftover) if leftover.is_dir() else leftover.unlink(missing_ok=True)
s.a16(71, 0xb0000000)
rows.append(('wrong generation', expect_refusal(lambda: retire.retire(s.ws, prefix, pre_guest=True),
'expected 70/0xb0800000')))
assert not any(e.startswith(('a16gpu', 'chainload')) for e in s.a16_state()['log'])
assert read(out['record'])['status'] == 'FAILED'
clear()
s.a16(70, 0xb0800000, quiesce='graceful path failed (Q1): forcing off')
rows.append(('quiesce not graceful', expect_refusal(lambda: retire.retire(s.ws, prefix, pre_guest=True),
'not graceful')))
log = s.a16_state()['log']
assert log.count('a16gpu quiesce') == 1 and not any(e.startswith('chainload') for e in log)
clear()
return sorted(name for name, _ in rows)
def test_gen71_redecode_completes_service_profile_and_step_runs_only_metal_probe():
"""Gen71 (exit 9): the standard collector's service profile failed in the runtime
root's old decoder, a newer committed decoder re-read it (redecode binding), and the
early-boot set captured the rest but not metal-probe. The verified redecode completes
the service profile and names its decoder; `--step metal-probe` runs that step and
nothing else; preserve records the decoder; retire reaches generation72."""
prefix = GEN[71]
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [71], cutoff=before_retirement(prefix))
run = evidence.Run(s.ws, prefix)
states = {st['name']: st for st in evidence.assess(run)['steps']}
binding = read(REAL / 'Snapintosh-gen70/build' / f'{prefix}-service-profile-redecode-v1-binding.json')
assert states['service-profile']['state'] == 'complete'
assert states['service-profile']['redecode']['decoder_commit'] == binding['decoder_commit']
assert [n for n, st in states.items() if st['state'] != 'complete'] == ['metal-probe']
expect_refusal(lambda: preserve.validate(s.ws, prefix), 'metal-probe:missing')
for done in ('audit', 'service-profile', 'console-tail'):
expect_refusal(lambda done=done: capture.plan(s.ws, prefix, only=done), f'{done} is complete')
expect_refusal(lambda: capture.plan(s.ws, prefix, only='no-such-step'), 'is not a step of the standard set')
plan = capture.plan(s.ws, prefix, only='metal-probe')
assert [st['name'] for st in plan['steps']] == ['metal-probe']
runner = Playback(s, prefix, {}, stand_in={'metal-probe': ROOT / 'build' / f'{GEN[66]}-metal-probe.json'})
result = capture.execute(s.ws, plan, runner)
assert [r['name'] for r in runner.ran] == ['metal-probe'] and runner.ran[0]['env']['SNAP_STOPPED_EXIT'] == '9'
assert result['status'] == 'STOPPED_STATE_CAPTURED', result
assert s.ws.prefixed(prefix, '-service-profile-redecode-v1-binding.json').is_file(), 'copied back'
done = preserve.preserve(s.ws, prefix)
saved = read(done['output'])
assert saved['capture']['steps']['service-profile']['redecode']['decoder_sha256'] == binding['decoder_sha256']
assert [Path(p).name for p in saved['decoders']] == ['kernel_ops_perf.py']
assert f'{prefix}-service-profile-redecode-v1.json' in {Path(p).name for p in saved['artifacts']}
s.a16(71, 0xb0000000, service=saved['service'], run=run)
record = read(retire.retire(s.ws, prefix, done['sha256'])['output'])
assert record['status'] == 'GENERATION72_IDLE' and record['after']['image_base'] == 0xb0800000
assert s.a16_state()['log'].count('a16gpu quiesce') == 1
def test_redecode_binding_refusals():
"""A redecode completes the service profile only when its binding names this run's
audit, its output is unchanged, it used the standard service-profile arguments and
its decoder is exactly the committed version it names."""
prefix = GEN[71]
rows = []
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [71])
path = s.ws.local(REAL / 'Snapintosh-gen70/build' / f'{prefix}-service-profile-redecode-v1-binding.json')
original = path.read_text()
attempt = lambda: evidence.assess(evidence.Run(s.ws, prefix))
for name, change, text in (
('another audit', lambda b: b.update(audit_sha256='0' * 64), 'bound to another audit'),
('other arguments', lambda b: b['arguments'].remove('--user-tree-memo'),
'did not use the standard service-profile arguments'),
('decoder not at its commit', lambda b: b.update(decoder_commit='d2e4731'), 'not the committed')):
bent = json.loads(original)
change(bent)
path.write_text(json.dumps(bent))
rows.append((name, expect_refusal(attempt, text)))
path.write_text(original)
output = path.with_name(f'{prefix}-service-profile-redecode-v1.json')
kept = output.read_bytes()
output.write_bytes(kept + b' ')
rows.append(('output changed', expect_refusal(attempt, 'changed or missing')))
output.write_bytes(kept)
assert {st['name']: st['state'] for st in attempt()['steps']}['service-profile'] == 'complete'
return sorted(name for name, _ in rows)
def test_reader_substitution_interposer():
"""install() serves the committed text for exactly one runtime file: run_path runs it
compiled as that file, read_text returns it; read_bytes (hash pins) and every other
file stay the originals."""
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
(tmp / 'tools').mkdir()
target, other = tmp / 'tools/reader.py', tmp / 'tools/other.py'
target.write_text("MARK = 'old'\n")
other.write_text("MARK = 'other'\n")
uses = substitute.install(target, "MARK = 'new'\nSEEN = __file__\n")
try:
import runpy
ran = runpy.run_path(str(target), run_name='__main__')
assert ran['MARK'] == 'new' and ran['SEEN'] == str(target.resolve()) and ran['__name__'] == '__main__'
assert runpy.run_path(str(other))['MARK'] == 'other'
assert target.read_text() == "MARK = 'new'\nSEEN = __file__\n" and other.read_text() == "MARK = 'other'\n"
assert target.read_bytes() == b"MARK = 'old'\n"
assert (uses['run_path'], uses['read_text']) == (1, 1)
finally:
uses['restore']()
assert target.read_text() == "MARK = 'old'\n"
def test_gen76_reader_substitution():
"""Gen76 (exit 12): the runtime root's capture-kernel-waits.py refused the native
tables' variant copies. With --replace, the three collector steps and the two
observers that run that reader run the committed fix; nothing else changes. The
capture record binds the substitution; assess and preserve re-verify it."""
prefix = GEN[76]
fixed = REAL / 'Snapintosh-native-tables-2b/tools/capture-kernel-waits.py'
commit = subprocess.run(['git', '-C', str(fixed.parent), 'log', '-1', '--format=%H', '--',
'capture-kernel-waits.py'], capture_output=True, text=True, check=True).stdout.strip()
rows = []
with tempfile.TemporaryDirectory() as tmp:
# The records as they were before the real --replace capture (attempt 4).
s = Scratch(tmp, [76], cutoff=before_capture(prefix, 4))
spec = f'tools/capture-kernel-waits.py={fixed}@{commit}'
plan = capture.plan(s.ws, prefix, replace=spec)
substituted = sorted(st['name'] for st in plan['steps'] if st.get('substituted'))
assert substituted == ['crash-retention', 'kernel-waits', 'metal-probe', 'mounts', 'native-startup'], substituted
b = plan['substitution']
assert b['replaced_sha256'] == sha(s.ws.local(read(ROOT / 'build' / f'{prefix}-launch.json')['runtime_root'])
/ 'tools/capture-kernel-waits.py') and b['commit'] == commit
described = capture.describe(plan)
kernel = next(st for st in described['steps'] if st['name'] == 'kernel-waits')
assert kernel['command'][2:4] == ['substituted', '--launch'] and '--replace' in kernel['command']
native = next(st for st in described['steps'] if st['name'] == 'native-startup')
assert native['command'][native['command'].index('--replace') + 1] == spec
# Refusals before anything runs.
for name, bad, text in (
('not allowed', spec.replace('capture-kernel-waits.py=', 'capture-console-tail.py=', 1), 'may not be replaced'),
('other commit', spec.replace(commit, '357b01e'), 'not the committed'),
('malformed', spec.replace('@', '#'), 'PATH=FILE@COMMIT'),
('the runtime file', f'tools/capture-kernel-waits.py={plan["run"].runtime}/tools/capture-kernel-waits.py@{commit}',
'runtime root')):
rows.append((name, expect_refusal(lambda bad=bad: capture.plan(s.ws, prefix, replace=bad), text)))
# One substituted step, played back: the record carries the binding.
only = capture.plan(s.ws, prefix, only='kernel-waits', replace=spec)
runner = Playback(s, prefix, {}, stand_in={'kernel-waits': ROOT / 'build' / f'{GEN[71]}-kernel-waits.json'})
result = capture.execute(s.ws, only, runner)
record = read(result['record'])
assert record['substitution'] == b and record['steps'][0]['substituted'] == 'tools/capture-kernel-waits.py'
assert runner.ran[0]['command'][2] == 'substituted'
report = evidence.assess(evidence.Run(s.ws, prefix))
assert report['substitutions'] == [b]
assert {st['name']: st['state'] for st in report['steps']}['kernel-waits'] == 'complete'
run = evidence.Run(s.ws, prefix)
assert [Path(p) for p in preserve.groups(s.ws, run, report)['readers']] == [s.ws.local(b['file'])]
# The binding is re-verified: a changed runtime file or record refuses.
runtime_file = run.runtime / 'tools/capture-kernel-waits.py'
materialize(runtime_file)
kept = runtime_file.read_bytes()
runtime_file.unlink()
runtime_file.write_bytes(kept + b'\n')
rows.append(('runtime file changed', expect_refusal(lambda: evidence.assess(evidence.Run(s.ws, prefix)),
'runtime root\'s file changed')))
runtime_file.write_bytes(kept)
path = Path(result['record'])
original = path.read_text()
for name, change, text in (('other audit', lambda r: r['substitution'].update(audit_sha256='0' * 64), 'another audit'),
('other file hash', lambda r: r['substitution'].update(file_sha256='0' * 64), 'changed since')):
bent = json.loads(original)
change(bent)
path.write_text(json.dumps(bent))
rows.append((name, expect_refusal(lambda: evidence.assess(evidence.Run(s.ws, prefix)), text)))
path.write_text(original)
assert evidence.assess(evidence.Run(s.ws, prefix))['substitutions'] == [b]
return sorted(name for name, _ in rows)
def before_capture(prefix, attempt):
"""The start of a run's cycle capture attempt `attempt` (a Scratch cutoff that keeps
the records as they were before it), or None while that attempt does not exist."""
record = ROOT / 'build' / f'{prefix}-cycle-capture-v{attempt}.json'
return read(record)['started'] if record.is_file() else None
def launched_manifest(prefix):
"""The launched bytes of a run's candidate manifest, found in git by the launch record's hash."""
cycle = read(ROOT / 'build' / f'{prefix}-launch.json')['compiler_deployment']['cycle']
for commit, digest in substitute._history(Path(cycle['manifest']), 'HEAD'):
if digest == cycle['manifest_sha256']:
return subprocess.run(['git', '-C', str(ROOT), 'show', f'{commit}:{Path(cycle["manifest"]).relative_to(ROOT)}'],
capture_output=True, check=True).stdout
raise AssertionError('launched manifest not in git history')
def observe_offline(ws, prefix, kind, manifest):
"""One rendered observer run in-process through its whole chain in --syntax-only
mode: every pin check and compile, then a return before any hardware access."""
import io
from contextlib import redirect_stdout
args = argparse.Namespace(kind=kind, launch=prefix, candidate=str(manifest), replace=None,
rest=['--syntax-only', '--attempt', 'offline'])
saved, out = (list(sys.argv), list(sys.path)), io.StringIO()
try:
with redirect_stdout(out):
observers.observe(ws, args)
finally:
sys.argv[:], sys.path[:] = saved
return out.getvalue()
def test_gen77_accepted_reader():
"""Gen77 (exit 12): its runtime root's capture-kernel-waits.py is the committed fix,
which the native observers' chain pins at its older version, so native-startup
refused before any read. Offline, the chain refuses the same way; with the launched
manifest amended by observers.readers, both observers pass every pin check and
compile. The acceptance needs the declared hash, that commit's exact file and descent
from the pinned version; capture records bind it, assess and preserve re-verify it."""
prefix = GEN[77]
runtime = REAL / Path(read(ROOT / 'build' / f'{prefix}-launch.json')['runtime_root']).name
reader = 'tools/capture-kernel-waits.py'
commit = subprocess.run(['git', '-C', str(runtime), 'log', '-1', '--format=%H', '--', reader],
capture_output=True, text=True, check=True).stdout.strip()
declared = {reader: dict(sha256=sha(runtime / reader), commit=commit)}
rows = []
with tempfile.TemporaryDirectory() as tmp:
# The records as they were after the first capture (attempt 1) refused.
s = Scratch(tmp, [77], cutoff=before_capture(prefix, 2))
git = lambda *a: subprocess.run(['git', '-C', str(s.ws.root / 'candidates'), '-c', 'user.name=t',
'-c', 'user.email=t@t', *a], capture_output=True, check=True)
path = s.ws.root / 'candidates/gen77-native-2a.json'
path.write_bytes(launched_manifest(prefix))
git('init', '-q')
git('add', path.name)
git('commit', '-q', '-m', 'launched')
# Without a declaration: nothing is accepted and the chain refuses as gen77 did.
plan = capture.plan(s.ws, prefix, manifest_path=path)
assert plan['readers'] == [] and [st['name'] for st in plan['steps']] == ['native-startup', 'crash-retention']
plain = observers.rendered(s.ws, plan['params'])
try:
observe_offline(s.ws, prefix, 'native', path)
except AssertionError as error:
assert str(error) == reader, error
else:
raise AssertionError('the chain accepted an unpinned reader')
# The launched manifest amended by exactly observers.readers.
launched = json.loads(path.read_text())
amended = json.loads(path.read_text())
amended['observers']['readers'] = declared
path.write_text(json.dumps(amended, indent=2) + '\n')
plan = capture.plan(s.ws, prefix, manifest_path=path)
b, = plan['readers']
assert (b['path'], b['sha256'], b['commit'], b['declared_by']) == (reader, declared[reader]['sha256'], commit,
'amended manifest')
assert b['pinned_sha256'] == observers.READER_PINS[reader][2] and b['changed_by'] == [commit]
assert b['amendment']['launched_manifest_sha256'] == sha_bytes(launched_manifest(prefix))
assert sha(runtime / reader) != b['pinned_sha256'] and json.loads(json.dumps(capture.describe(plan)))['readers'] == [b]
rendered = observers.rendered(s.ws, plan['params'])
# The crash program runs the rendered native text (NATIVE_SOURCE), hashed as 'native'.
assert {k for k in rendered if rendered[k] != plain[k]} == {'native'}, rendered
for kind in ('native', 'crash'):
text = observe_offline(s.ws, prefix, kind, path)
assert 'Generation77 native startup observer compiles; no hardware access' in text, (kind, text)
# Refusals before anything runs.
good = path.read_text()
def amend(change):
data = json.loads(good)
change(data)
path.write_text(json.dumps(data))
return lambda: capture.plan(s.ws, prefix, manifest_path=path)
other = subprocess.run(['git', '-C', str(runtime), 'log', '-1', '--format=%H', f'{commit}~1', '--', reader],
capture_output=True, text=True, check=True).stdout.strip()
for name, change, text in (
('other hash', lambda d: d['observers']['readers'][reader].update(sha256='0' * 64), 'not the declared'),
('other commit', lambda d: d['observers']['readers'][reader].update(commit=other), 'commit'),
('no commit', lambda d: d['observers']['readers'][reader].update(commit='0' * 40), 'not in the runtime root'),
('extra key', lambda d: d['observers']['readers'][reader].update(about='x'), 'exactly sha256 and commit'),
('unpinned path', lambda d: d['observers']['readers'].update({'tools/capture-console-tail.py': {}}),
'pin no reader'),
('more than readers', lambda d: d.update(about='amended'), 'changed beyond observers.readers')):
rows.append((name, expect_refusal(amend(change), text)))
path.write_text(good)
pins = observers.READER_PINS[reader]
observers.READER_PINS[reader] = (*pins[:2], '0' * 64)
try:
rows.append(('no descent', expect_refusal(lambda: capture.plan(s.ws, prefix, manifest_path=path),
'does not descend from the pinned version')))
finally:
observers.READER_PINS[reader] = pins
(s.ws.root / 'candidates/.git').rename(s.ws.root / 'candidates/.git-off')
rows.append(('launched version unknown', expect_refusal(lambda: capture.plan(s.ws, prefix, manifest_path=path),
'not in its git history')))
(s.ws.root / 'candidates/.git-off').rename(s.ws.root / 'candidates/.git')
# One observer step run (by a stand-in runner): the record binds the acceptance.
only = capture.plan(s.ws, prefix, manifest_path=path, only='native-startup')
ran = []
def runner(command, cwd, env, log):
ran.append(command)
log.write_text('stand-in\n')
return 0
result = capture.execute(s.ws, only, runner)
record = read(result['record'])
assert record['readers'] == [b] and record['observers'] == rendered
assert ran[0][ran[0].index('--candidate') + 1] == str(path)
run = evidence.Run(s.ws, prefix)
report = evidence.assess(run)
assert report['readers'] == [b]
assert preserve.groups(s.ws, run, report)['readers'] == [s.ws.local(b['runtime_root']) / reader, path]
# Re-verified on every read.
path.write_text(good.replace('"readers"', '"readers" ', 1))
rows.append(('manifest changed', expect_refusal(lambda: evidence.assess(evidence.Run(s.ws, prefix)),
'changed since')))
path.write_text(good)
record_path = Path(result['record'])
kept = record_path.read_text()
bent = json.loads(kept)
bent['readers'][0]['pinned_commit'] = commit
record_path.write_text(json.dumps(bent))
rows.append(('record bent', expect_refusal(lambda: evidence.assess(evidence.Run(s.ws, prefix)),
'no longer holds')))
record_path.write_text(kept)
assert evidence.assess(evidence.Run(s.ws, prefix))['readers'] == [b]
assert launched['observers'] == read(ROOT / 'build' / f'{prefix}-launch.json')['compiler_deployment']['cycle']['observers']
return sorted(name for name, _ in rows)
def test_gen78_unqualified_stop_preserved_and_retired():
"""Gen78 (exit 7): the monitor refused SWITCH_ROOT and the runtime root's own audit
then refused the stop ('native shared counters disagree'), correctly. capture
--unqualified records that refusal, the boot log's terminal event, the evidence
(a diagnosis in build/ and the committed tool that wrote it) and a read-only witness
of the parked stop; preserve --unqualified keeps qualified false with the witness's
service words; retire verifies the live stop against them and reaches generation 79.
Each binding is re-verified, and the run can never be a baseline."""
prefix = GEN[78]
tool = REAL / 'Snapintosh-native-tables-3b/tools/diagnose-native-shared-stop.py'
commit = subprocess.run(['git', '-C', str(tool.parent), 'log', '-1', '--format=%H', '--', tool.name],
capture_output=True, text=True, check=True).stdout.strip()
service = [0, 3, common.PARKED_SERVICE_WORD, 0, 0, 0, 0, 0]
rows = []
with tempfile.TemporaryDirectory() as tmp:
s = Scratch(tmp, [78], cutoff=before_capture(prefix, 2))
diagnosis = s.ws.prefixed(prefix, '-native-shared-diagnosis-v1.json')
diagnosis.write_text(json.dumps(dict(header=dict(live=512, errors=1))))
evidence_specs = [str(diagnosis), f'{tool}@{commit}']
# Nothing to record without evidence; the standard capture still cannot complete.
rows.append(('no evidence', expect_refusal(lambda: unqualified.record(s.ws, prefix, []), '--evidence')))
plan = unqualified.record(s.ws, prefix, evidence_specs, check=True)
assert plan['status'] == 'UNQUALIFIED_PLAN' and plan['refusal']['refusal'] == \
'ValueError: native shared counters disagree' and plan['refusal']['exit_code'] == 1
assert plan['terminal']['last_event'].startswith('mapping references rejected pc 0xfffffe000c22b6d4')
assert plan['terminal']['verdict'] == '== UNSUPPORTED OPERATION (exit 7) =='
run = evidence.Run(s.ws, prefix)
s.a16(79, 0xb0000000, service=service, run=run)
rows.append(('witness at another generation', expect_refusal(
lambda: unqualified.record(s.ws, prefix, evidence_specs), 'expected 78/EL2/0xb0800000')))
for stale in s.ws.build.glob(f'{prefix}-cycle-capture-v*.json'):
if read(stale).get('kind') == 'unqualified':
stale.unlink() # the refused witness left a FAILED record; start again
s.a16(78, 0xb0800000, service=service, run=run)
done = unqualified.record(s.ws, prefix, evidence_specs)
rec = read(done['record'])
assert rec['status'] == unqualified.STATUS and rec['qualified'] is False
assert rec['witness']['service'] == service and [e.get('commit') for e in rec['evidence']] == [None, commit]
rows.append(('recorded twice', expect_refusal(lambda: unqualified.record(s.ws, prefix, evidence_specs),
'already has a recorded unqualified stop')))
# A candidate naming this run as its baseline is refused.
data = json.loads(launched_manifest(GEN[77]))
launch78 = read(s.ws.prefixed(prefix, '-launch.json'))
data.update(prefix='installed-metal-after-unqualified-test', baseline_launch=dict(
data['baseline_launch'], prefix=prefix, sha256=sha(s.ws.prefixed(prefix, '-launch.json')), generation=78,
exit_code=7, runtime_root='Snapintosh-native-tables-3', context_bytes=launch78['context_bytes'],
context_fields=launch78['context_fields'], monitor_sha256=launch78['monitor_sha256']))
candidate = s.ws.root / 'candidates/after-unqualified.json'
candidate.write_text(json.dumps(data))
rows.append(('baseline', expect_refusal(lambda: launch.validate(s.ws, launch.Manifest(s.ws, candidate), {}),
'can never be a baseline')))
# The standard preservation still refuses; the unqualified one preserves.
expect_refusal(lambda: preserve.preserve(s.ws, prefix), 'capture incomplete')
kept = diagnosis.read_bytes()
diagnosis.write_bytes(kept + b' ')
rows.append(('evidence changed', expect_refusal(lambda: preserve.preserve(s.ws, prefix, unqualified=True),
'changed since')))
diagnosis.write_bytes(kept)
saved_done = preserve.preserve(s.ws, prefix, unqualified=True)
saved = read(saved_done['output'])
assert saved['qualified'] is False and saved['status'] == 'GEN78_UNQUALIFIED_STOP_PRESERVED'
assert saved['service'] == service and saved['audit_sha256'] is None
assert saved['unqualified']['record_sha256'] == sha(done['record'])
assert any(Path(k).resolve() == tool.resolve() for k in saved['diagnosis']), sorted(saved['diagnosis'])[-3:]
assert any(Path(k).resolve() == diagnosis.resolve() for k in saved['artifacts'])
# Retirement verifies the live stop against the witness and reaches generation 79.
ready = retire.retire(s.ws, prefix, saved_done['sha256'], check=True)
assert ready['status'] == 'READY' and ready['qualified'] is False and ready['target_base'] == 0xb0000000
s.a16(78, 0xb0800000, service=[0, 4, common.PARKED_SERVICE_WORD, 0, 0, 0, 0, 0], run=run)
rows.append(('live stop differs', expect_refusal(lambda: retire.retire(s.ws, prefix, saved_done['sha256']),
'live service words differ')))
for stale in s.ws.build.glob(f'{prefix}-retirement*'):
shutil.rmtree(stale) if stale.is_dir() else stale.unlink()
s.a16(78, 0xb0800000, service=service, run=run)
result = retire.retire(s.ws, prefix, saved_done['sha256'])
record = read(result['output'])
assert record['status'] == 'GENERATION79_IDLE' and record['qualified'] is False
assert 'chainload 0xb0000000' in s.a16_state()['log']
return sorted(name for name, _ in rows)
TESTS = [test_rendered_observers_equal_generation_wrappers, test_gen66_manifest_reproduces_run_ca_memo_candidate,
test_storage_reproduces_gen66_adapter, test_capture_continues_gen65_like_its_completion_sequencer,
test_capture_runs_gen64_early_boot_set_after_failed_collector,
test_capture_exit9_runs_the_standard_collector_then_native_observers, test_exit12_needs_its_early_stop_record,
test_gen66_exit7_preserve_then_retire_with_graceful_quiesce, test_idle_generation_inherits_the_graceful_quiesce,
test_refusals, test_launch_admission_battery_generation_and_stage_pair,
test_gen70_explicit_auxkc_and_ssd_installed_after_baseline,
test_loader_failure_before_the_guest_is_retired_with_a_graceful_quiesce, test_loader_failure_refusals,
test_gen71_redecode_completes_service_profile_and_step_runs_only_metal_probe, test_redecode_binding_refusals,
test_reader_substitution_interposer, test_gen76_reader_substitution, test_gen77_accepted_reader,
test_gen78_unqualified_stop_preserved_and_retired]
def fingerprint():
"""(size, mtime) of every real file the tests copy from: the tested generations'
records in build/ and in their runtime roots."""
out = {}
for prefix in GEN.values():
runtime = Path(read(ROOT / 'build' / f'{prefix}-launch.json')['runtime_root']) / 'build'
for directory in (ROOT / 'build', runtime):
for p in directory.glob(prefix + '*'):
if p.is_file():
st = p.stat()
out[str(p)] = (st.st_size, st.st_mtime_ns)
return out
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument('-k', help='run tests whose name contains this')
args = ap.parse_args()
before = fingerprint()
failures = 0
for test in TESTS:
if args.k and args.k not in test.__name__:
continue
started = time.time()
try:
detail = test()
print(f'PASS {test.__name__} ({time.time() - started:.1f} s)' + (f' {json.dumps(detail)}' if detail else ''),
flush=True)
except (Exception, SystemExit):
failures += 1
print(f'FAIL {test.__name__}', flush=True)
traceback.print_exc()
if fingerprint() != before:
failures += 1
print('FAIL a real record of a tested generation changed during the tests', flush=True)
print(f'{len(TESTS) - failures if not args.k else "selected"} passed, {failures} failed', flush=True)
return 1 if failures else 0
if __name__ == '__main__':
raise SystemExit(main())