fix(test): select persistent UEFI test media without changing boot order

This commit is contained in:
Codex
2026-10-13 22:49:33 +03:00
parent 3c15890823
commit 6058a0660e
5 changed files with 185 additions and 15 deletions
+14 -4
View File
@@ -635,7 +635,7 @@ def qemu_path(path):
return str(path)
def qemu_argv(qemu, scratch, disk, code, variables, iso, memory):
def qemu_argv(qemu, scratch, disk, code, variables, iso, memory, *, keep_cdrom_drive=False):
scratch = Path(scratch)
owned_file(disk, scratch)
owned_file(variables, scratch)
@@ -650,8 +650,17 @@ def qemu_argv(qemu, scratch, disk, code, variables, iso, memory):
if iso is not None:
if not Path(iso).is_file():
raise SmokeError('Trusted ISO must exist as a regular file')
argv += ['-drive', f'file={qemu_path(iso)},media=cdrom,format=raw,readonly=on',
'-boot', 'order=d,strict=on']
if keep_cdrom_drive:
# Keep one SATA0 device even without media so OVMF retains its DVD boot
# entry across ISO-free boots. No file= means there is no optical medium.
optical = 'if=ide,index=0,id=acceptance-cdrom,media=cdrom,readonly=on'
if iso is not None:
optical += ',format=raw,file=' + qemu_path(iso)
argv += ['-drive', optical]
elif iso is not None:
argv += ['-drive', f'file={qemu_path(iso)},media=cdrom,format=raw,readonly=on']
if iso is not None:
argv += ['-boot', 'order=d,strict=on']
else:
argv += ['-boot', 'order=c,strict=on']
return argv
@@ -816,7 +825,8 @@ class VM:
(scratch / name).unlink(missing_ok=True)
self.stderr = open(scratch / f'{phase}-qemu.log', 'ab', buffering=0)
self.process = subprocess.Popen(qemu_argv(args.qemu, scratch, disk,
args.ovmf_code, scratch / 'OVMF_VARS.fd', iso, args.memory),
args.ovmf_code, scratch / 'OVMF_VARS.fd', iso, args.memory,
keep_cdrom_drive=getattr(args, 'keep_cdrom_drive', False)),
stdin=subprocess.DEVNULL, stdout=self.stderr, stderr=self.stderr)
self.console = None
self.qmp = None
+53 -11
View File
@@ -357,14 +357,50 @@ def probe(console, mode, context, timeout=180):
return json.loads(console.command("sudo -n python3 - <<'HYDRA_LAPTOP_PROBE'\n" + source + '\nHYDRA_LAPTOP_PROBE', timeout))
def select_installed(console):
# This is a deliberate harness action AFTER proving installer preserved order.
def select_boot_entry(console, label, expected_path, create=None):
# Harness-only one-shot selection: never rewrite BootOrder or firmware VARS.
output = console.command('sudo -n efibootmgr', 30).decode()
entries = re.findall(r'^Boot([0-9A-Fa-f]{4})\*?\s+HydraOS\s', output, re.M)
if len(entries) != 1:
raise SmokeError('Expected one registered HydraOS entry: ' + output)
console.command('sudo -n efibootmgr --bootnext ' + entries[0], 30)
return entries[0]
before = re.findall(r'^BootOrder:\s*([^\r\n]+)', output, re.M)
if len(before) != 1:
raise SmokeError('Expected one observable UEFI BootOrder: ' + output)
pattern = r'^Boot([0-9A-Fa-f]{4})(\*?)[ \t]+' + re.escape(label) + r' *\t([^\r\n]+)'
entries = re.findall(pattern, output, re.M)
if not entries and create:
# OVMF may prune create-only entries that are outside BootOrder after
# consuming BootNext. Recreate only our known loader; never reorder it.
console.command(create, 30)
output = console.command('sudo -n efibootmgr', 30).decode()
if re.findall(r'^BootOrder:\s*([^\r\n]+)', output, re.M) != before:
raise SmokeError('Create-only registration changed UEFI BootOrder')
entries = re.findall(pattern, output, re.M)
if len(entries) != 1 or entries[0][1] != '*':
raise SmokeError('Expected one active ' + label + ' entry: ' + output)
entry, _, path = entries[0]
if not re.fullmatch(expected_path, path, re.I):
raise SmokeError('Unexpected ' + label + ' boot device or loader: ' + path)
console.command('sudo -n efibootmgr --bootnext ' + entry, 30)
after = console.command('sudo -n efibootmgr', 30).decode()
if re.findall(r'^BootOrder:\s*([^\r\n]+)', after, re.M) != before:
raise SmokeError('BootNext selection changed UEFI BootOrder')
if re.findall(r'^BootNext:\s*([0-9A-Fa-f]{4})\s*$', after, re.M) != [entry]:
raise SmokeError('UEFI did not retain the requested BootNext')
return entry
def select_installed(console, *, recreate=False):
# Deliberate action AFTER proving the installer preserved the original order.
esp = console.command('sudo -n blkid -s PARTUUID -o value /dev/vda1', 30).decode().strip()
if not re.fullmatch(r'[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}', esp):
raise SmokeError('Expected GPT identity of the disposable ESP')
path = re.escape('HD(1,GPT,' + esp + ',0x800,0x100000)/\\EFI\\HydraOS\\grubx64.efi')
create = ("sudo -n efibootmgr --create-only --disk /dev/vda --part 1 "
"--label HydraOS --loader '\\EFI\\HydraOS\\grubx64.efi'")
return select_boot_entry(console, 'HydraOS', path, create if recreate else None)
def select_live(console):
path = re.escape('PciRoot(0x0)/Pci(0x1f,0x2)/Sata(0,65535,0)') + r'(?:\{auto_created_boot_option\})?'
return select_boot_entry(console, 'UEFI QEMU DVD-ROM QM00001', path)
def main(argv=None):
@@ -373,6 +409,7 @@ def main(argv=None):
if not path.is_file(): parser.error('Missing regular input: ' + str(path))
if args.memory < 2048 or min(args.boot_timeout, args.install_timeout) <= 0:
parser.error('Memory >=2048 MiB and positive timeouts required')
args.keep_cdrom_drive = True
args.iso, args.ovmf_code, args.ovmf_vars = (p.resolve() for p in (args.iso, args.ovmf_code, args.ovmf_vars))
scratch = Path(tempfile.mkdtemp(prefix='hydra-laptop-smoke-', dir=args.scratch_parent))
if len(str(scratch / 'serial.sock').encode()) >= 104: parser.error('Use shorter scratch parent such as /tmp')
@@ -393,6 +430,9 @@ def main(argv=None):
console = vm.console
if live:
console.expect(rb'hydra-live[^\r\n]*[#$] ', args.boot_timeout)
# Keep asynchronous mount diagnostics out of framed JSON on ttyS0;
# they remain available in the guest's dmesg/journal buffers.
console.command('sudo -n dmesg --console-level 3', 30)
else:
console.expect(rb'login: ', args.boot_timeout); console.send('hydratest\n')
console.expect(rb'Password: ', 30); console.send(password + '\n')
@@ -431,7 +471,8 @@ def main(argv=None):
record('packaged_ascii_branding', json.loads(iso_smoke.installed_terminal_ux_probe(console)))
iso_smoke.installed_admin(console, password)
baseline = probe(console, 'baseline', context, 720); record('real_portage_baseline_doctor_vmdata', baseline)
record('signed_runtime_update_recovery', probe(console, 'runtime', context, 300)); shutdown(console)
record('signed_runtime_update_recovery', probe(console, 'runtime', context, 300))
record('harness_checkpoint_bootnext', select_live(console)); shutdown(console)
console = boot(True, 'live-checkpoint')
base_before = probe(console, 'base-state', context); record('base_before_checkpoint', base_before)
context['root_uuid'] = console.command('sudo -n blkid -s UUID -o value /dev/vda3').decode().strip()
@@ -442,8 +483,9 @@ def main(argv=None):
cp = json.loads(console.command(command + ' --execute --confirm-root-uuid ' + shlex.quote(context['root_uuid']), 300))
context['checkpoint'] = cp['id']; record('real_offline_checkpoint', cp)
record('checkpoint_list', json.loads(console.command('sudo -n hydra-system list --root-uuid ' + shlex.quote(context['root_uuid']), 180)))
select_installed(console); shutdown(console)
console = boot(False, 'installed-upgrade'); record('real_portage_upgrade_current_home', probe(console, 'upgrade', context, 720)); shutdown(console)
select_installed(console, recreate=True); shutdown(console)
console = boot(False, 'installed-upgrade'); record('real_portage_upgrade_current_home', probe(console, 'upgrade', context, 720))
record('harness_restore_bootnext', select_live(console)); shutdown(console)
console = boot(True, 'live-restore')
record('deliberate_kernel_efi_damage', probe(console, 'damage', context))
record('restore_sigkill_real_btrfs', probe(console, 'restore-interrupt', context, 300))
@@ -455,7 +497,7 @@ def main(argv=None):
if base_after != base_before: raise SmokeError('Offline restore did not restore exact base/Portage/kernel/ownedEFI hashes')
preserved_after = probe(console, 'preserved', context); verify_preserved(before, preserved_after)
record('restored_base_foreign_invariants', {'base': base_after, 'preserved': preserved_after})
select_installed(console); shutdown(console)
select_installed(console, recreate=True); shutdown(console)
console = boot(False, 'installed-restored'); restored = probe(console, 'restored', context)
if any(restored[k] != v for k, v in base_before.items()): raise SmokeError('Disk-only boot did not retain restored base')
if restored['vm_data'] != baseline['vm_data']: raise SmokeError('VM disk/profile bytes changed')