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')
+10
View File
@@ -24,6 +24,16 @@ adds the following real guest operations:
fallback bytes, and firmware BootOrder. Install Btrfs into `/dev/vda3` while
retaining `/dev/vda1`, explicitly register a HydraOS entry, then verify those
invariants before the harness deliberately sets BootNext to choose that entry.
Subsequent live boots also use verified one-shot BootNext selection. The same
SATA DVD drive stays attached throughout, empty during installed boots; no ISO
is available to those boots. This keeps OVMF's device entries stable without
resetting firmware variables or changing BootOrder. Every deliberate BootNext
write is read back and checked against the unchanged BootOrder. If OVMF prunes
HydraOS's create-only entry after consuming BootNext, the harness recreates only
that known ESP/loader entry before the next selection, and verifies that both
registration and selection preserve the original order. Live test consoles
reduce kernel console verbosity so asynchronous mount messages cannot corrupt
framed JSON; the messages remain in the guest kernel log.
2. Boot without ISO, verify the packaged Fastfetch art and doctor, install version
1.0 of a locally created EAPI 8 Portage package, and create a real qcow2 plus
profile/data sentinels under HOME. The fixture has no sources or dependencies;
+24
View File
@@ -63,6 +63,30 @@ class SmokeTests(unittest.TestCase):
self.assertNotIn('media=cdrom', ' '.join(argv))
self.assertNotIn('-cdrom', argv)
def test_persistent_optical_drive_keeps_topology_with_no_installed_medium(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
iso = self.root / 'trusted.iso'
iso.write_bytes(b'ISO')
optical = []
for medium in (iso, None):
argv = self.mod.qemu_argv('qemu', self.root, disk, self.code,
self.variables, medium, 2048, keep_cdrom_drive=True)
drives = [argv[i + 1] for i, value in enumerate(argv[:-1]) if value == '-drive']
cdrom = [dict(field.split('=', 1) for field in drive.split(','))
for drive in drives if 'media=cdrom' in drive]
self.assertEqual(len(cdrom), 1)
self.assertEqual(cdrom[0]['if'], 'ide')
self.assertEqual(cdrom[0]['index'], '0')
self.assertEqual(cdrom[0]['id'], 'acceptance-cdrom')
self.assertEqual(cdrom[0]['readonly'], 'on')
optical.append(cdrom[0])
self.assertEqual(optical[0].pop('file'), str(iso))
self.assertEqual(optical[0].pop('format'), 'raw')
self.assertNotIn('file', optical[1], 'Installed boot must have no ISO medium')
self.assertNotIn('format', optical[1], 'An empty drive must not request a raw block backend')
self.assertEqual(optical[0], optical[1])
def test_hardlinked_disk_is_refused(self):
disk = self.root / 'disk.qcow2'
self.mod.reserve_disk(disk)
+84
View File
@@ -9,6 +9,38 @@ import urllib.error
import urllib.request
class BootConsole:
"""Captured efibootmgr format with observable guest NVRAM mutations."""
def __init__(self, entries=None, changed_order=False, drop_next=False, changed_create_order=False):
self.order = '0000,0001,0002,0003'
self.next = None
self.changed_order, self.drop_next = changed_order, drop_next
self.changed_create_order = changed_create_order
self.entries = entries if entries is not None else (
'Boot0000* UiApp\tFvVol(example)/FvFile(example)\n'
'Boot0001* UEFI QEMU DVD-ROM QM00001 \tPciRoot(0x0)/Pci(0x1f,0x2)/Sata(0,65535,0){auto_created_boot_option}\n'
'Boot0002* UEFI Misc Device\tPciRoot(0x0)/Pci(0x3,0x0)\n'
'Boot0003* EFI Internal Shell\tFvVol(example)/FvFile(example)\n'
'Boot0004* HydraOS\tHD(1,GPT,5d24ab30-9c80-4f5d-8594-f9f08fdf31fb,0x800,0x100000)/\\EFI\\HydraOS\\grubx64.efi\n')
def command(self, command, timeout):
if command == 'sudo -n blkid -s PARTUUID -o value /dev/vda1':
return b'5d24ab30-9c80-4f5d-8594-f9f08fdf31fb\n'
if command == "sudo -n efibootmgr --create-only --disk /dev/vda --part 1 --label HydraOS --loader '\\EFI\\HydraOS\\grubx64.efi'":
self.entries += ('Boot0004* HydraOS\tHD(1,GPT,5d24ab30-9c80-4f5d-8594-f9f08fdf31fb,0x800,0x100000)/\\EFI\\HydraOS\\grubx64.efi\n')
if self.changed_create_order:
self.order = '0004,0000,0001,0002,0003'
elif command.startswith('sudo -n efibootmgr --bootnext '):
if not self.drop_next:
self.next = command.split()[-1]
if self.changed_order:
self.order = '0001,0000,0002,0003'
elif command != 'sudo -n efibootmgr':
raise AssertionError('Unexpected firmware mutation: ' + command)
return ('BootCurrent: 0004\nTimeout: 1 seconds\nBootOrder: ' + self.order + '\n' +
('BootNext: ' + self.next + '\n' if self.next else '') + self.entries).encode()
SCRIPT = Path(__file__).resolve().parents[1] / 'dev/laptop-smoke.py'
@@ -80,6 +112,58 @@ class LaptopSmokeTests(unittest.TestCase):
with self.assertRaises(self.mod.SmokeError):
self.mod.partition_installer_command('bad;token')
def test_live_boot_is_selected_once_without_changing_bootorder(self):
console = BootConsole()
self.mod.select_live(console)
self.assertEqual(console.next, '0001')
self.assertEqual(console.order, '0000,0001,0002,0003')
def test_live_boot_refuses_missing_ambiguous_or_inactive_dvd_entry(self):
for entries in ('Boot0001* Unrelated DVD\tPciRoot(0x0)\n',
'Boot0001* UEFI QEMU DVD-ROM QM00001 Other\tPciRoot(0x0)\n',
'Boot0001 UEFI QEMU DVD-ROM QM00001 \tPciRoot(0x0)\n',
'Boot0001* UEFI QEMU DVD-ROM QM00001 \tPciRoot(0x0)\n'
'Boot0002* UEFI QEMU DVD-ROM QM00001 \tPciRoot(0x0)\n'):
console = BootConsole(entries=entries)
with self.subTest(entries=entries), self.assertRaises(self.mod.SmokeError):
self.mod.select_live(console)
self.assertIsNone(console.next)
def test_boot_selection_verifies_effective_bootnext_and_unchanged_order(self):
for selector in ('select_live', 'select_installed'):
for fault in ('changed_order', 'drop_next'):
with self.subTest(selector=selector, fault=fault), self.assertRaises(self.mod.SmokeError):
getattr(self.mod, selector)(BootConsole(**{fault: True}))
def test_installed_boot_recreates_pruned_owned_entry_without_changing_order(self):
console = BootConsole()
console.entries = console.entries[:console.entries.index('Boot0004')]
with self.assertRaises(self.mod.SmokeError):
self.mod.select_installed(console)
self.assertIsNone(console.next, 'First selection must prove installer registration')
self.mod.select_installed(console, recreate=True)
self.assertEqual(console.next, '0004')
self.assertEqual(console.order, '0000,0001,0002,0003')
self.assertIn('\\EFI\\HydraOS\\grubx64.efi', console.entries)
def test_recreated_entry_cannot_change_bootorder(self):
console = BootConsole(changed_create_order=True)
console.entries = console.entries[:console.entries.index('Boot0004')]
with self.assertRaises(self.mod.SmokeError):
self.mod.select_installed(console, recreate=True)
self.assertIsNone(console.next)
def test_boot_selection_rejects_correct_label_with_foreign_device_or_loader(self):
for selector, before, after in (
('select_live', 'Sata(0,65535,0)', 'Sata(2,65535,0)'),
('select_installed', '5d24ab30-9c80-4f5d-8594-f9f08fdf31fb', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'),
('select_installed', '\\EFI\\HydraOS\\grubx64.efi', '\\EFI\\ForeignOS\\grubx64.efi')):
console = BootConsole()
console.entries = console.entries.replace(before, after)
with self.subTest(selector=selector, wrong=after), self.assertRaises(self.mod.SmokeError):
getattr(self.mod, selector)(console)
self.assertIsNone(console.next)
def test_base_state_compares_kernel_bytes_without_including_mounted_esp(self):
# Offline /boot/efi is an empty mountpoint; on disk-only boot it contains
# the mounted ESP. That mount must not produce a false base mismatch.