fix(test): select persistent UEFI test media without changing boot order
This commit is contained in:
+14
-4
@@ -635,7 +635,7 @@ def qemu_path(path):
|
|||||||
return str(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)
|
scratch = Path(scratch)
|
||||||
owned_file(disk, scratch)
|
owned_file(disk, scratch)
|
||||||
owned_file(variables, 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 iso is not None:
|
||||||
if not Path(iso).is_file():
|
if not Path(iso).is_file():
|
||||||
raise SmokeError('Trusted ISO must exist as a regular file')
|
raise SmokeError('Trusted ISO must exist as a regular file')
|
||||||
argv += ['-drive', f'file={qemu_path(iso)},media=cdrom,format=raw,readonly=on',
|
if keep_cdrom_drive:
|
||||||
'-boot', 'order=d,strict=on']
|
# 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:
|
else:
|
||||||
argv += ['-boot', 'order=c,strict=on']
|
argv += ['-boot', 'order=c,strict=on']
|
||||||
return argv
|
return argv
|
||||||
@@ -816,7 +825,8 @@ class VM:
|
|||||||
(scratch / name).unlink(missing_ok=True)
|
(scratch / name).unlink(missing_ok=True)
|
||||||
self.stderr = open(scratch / f'{phase}-qemu.log', 'ab', buffering=0)
|
self.stderr = open(scratch / f'{phase}-qemu.log', 'ab', buffering=0)
|
||||||
self.process = subprocess.Popen(qemu_argv(args.qemu, scratch, disk,
|
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)
|
stdin=subprocess.DEVNULL, stdout=self.stderr, stderr=self.stderr)
|
||||||
self.console = None
|
self.console = None
|
||||||
self.qmp = None
|
self.qmp = None
|
||||||
|
|||||||
+53
-11
@@ -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))
|
return json.loads(console.command("sudo -n python3 - <<'HYDRA_LAPTOP_PROBE'\n" + source + '\nHYDRA_LAPTOP_PROBE', timeout))
|
||||||
|
|
||||||
|
|
||||||
def select_installed(console):
|
def select_boot_entry(console, label, expected_path, create=None):
|
||||||
# This is a deliberate harness action AFTER proving installer preserved order.
|
# Harness-only one-shot selection: never rewrite BootOrder or firmware VARS.
|
||||||
output = console.command('sudo -n efibootmgr', 30).decode()
|
output = console.command('sudo -n efibootmgr', 30).decode()
|
||||||
entries = re.findall(r'^Boot([0-9A-Fa-f]{4})\*?\s+HydraOS\s', output, re.M)
|
before = re.findall(r'^BootOrder:\s*([^\r\n]+)', output, re.M)
|
||||||
if len(entries) != 1:
|
if len(before) != 1:
|
||||||
raise SmokeError('Expected one registered HydraOS entry: ' + output)
|
raise SmokeError('Expected one observable UEFI BootOrder: ' + output)
|
||||||
console.command('sudo -n efibootmgr --bootnext ' + entries[0], 30)
|
pattern = r'^Boot([0-9A-Fa-f]{4})(\*?)[ \t]+' + re.escape(label) + r' *\t([^\r\n]+)'
|
||||||
return entries[0]
|
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):
|
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 not path.is_file(): parser.error('Missing regular input: ' + str(path))
|
||||||
if args.memory < 2048 or min(args.boot_timeout, args.install_timeout) <= 0:
|
if args.memory < 2048 or min(args.boot_timeout, args.install_timeout) <= 0:
|
||||||
parser.error('Memory >=2048 MiB and positive timeouts required')
|
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))
|
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))
|
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')
|
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
|
console = vm.console
|
||||||
if live:
|
if live:
|
||||||
console.expect(rb'hydra-live[^\r\n]*[#$] ', args.boot_timeout)
|
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:
|
else:
|
||||||
console.expect(rb'login: ', args.boot_timeout); console.send('hydratest\n')
|
console.expect(rb'login: ', args.boot_timeout); console.send('hydratest\n')
|
||||||
console.expect(rb'Password: ', 30); console.send(password + '\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)))
|
record('packaged_ascii_branding', json.loads(iso_smoke.installed_terminal_ux_probe(console)))
|
||||||
iso_smoke.installed_admin(console, password)
|
iso_smoke.installed_admin(console, password)
|
||||||
baseline = probe(console, 'baseline', context, 720); record('real_portage_baseline_doctor_vmdata', baseline)
|
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')
|
console = boot(True, 'live-checkpoint')
|
||||||
base_before = probe(console, 'base-state', context); record('base_before_checkpoint', base_before)
|
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()
|
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))
|
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)
|
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)))
|
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)
|
select_installed(console, recreate=True); shutdown(console)
|
||||||
console = boot(False, 'installed-upgrade'); record('real_portage_upgrade_current_home', probe(console, 'upgrade', context, 720)); 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')
|
console = boot(True, 'live-restore')
|
||||||
record('deliberate_kernel_efi_damage', probe(console, 'damage', context))
|
record('deliberate_kernel_efi_damage', probe(console, 'damage', context))
|
||||||
record('restore_sigkill_real_btrfs', probe(console, 'restore-interrupt', context, 300))
|
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')
|
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)
|
preserved_after = probe(console, 'preserved', context); verify_preserved(before, preserved_after)
|
||||||
record('restored_base_foreign_invariants', {'base': base_after, 'preserved': 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)
|
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 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')
|
if restored['vm_data'] != baseline['vm_data']: raise SmokeError('VM disk/profile bytes changed')
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ adds the following real guest operations:
|
|||||||
fallback bytes, and firmware BootOrder. Install Btrfs into `/dev/vda3` while
|
fallback bytes, and firmware BootOrder. Install Btrfs into `/dev/vda3` while
|
||||||
retaining `/dev/vda1`, explicitly register a HydraOS entry, then verify those
|
retaining `/dev/vda1`, explicitly register a HydraOS entry, then verify those
|
||||||
invariants before the harness deliberately sets BootNext to choose that entry.
|
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
|
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
|
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;
|
profile/data sentinels under HOME. The fixture has no sources or dependencies;
|
||||||
|
|||||||
@@ -63,6 +63,30 @@ class SmokeTests(unittest.TestCase):
|
|||||||
self.assertNotIn('media=cdrom', ' '.join(argv))
|
self.assertNotIn('media=cdrom', ' '.join(argv))
|
||||||
self.assertNotIn('-cdrom', 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):
|
def test_hardlinked_disk_is_refused(self):
|
||||||
disk = self.root / 'disk.qcow2'
|
disk = self.root / 'disk.qcow2'
|
||||||
self.mod.reserve_disk(disk)
|
self.mod.reserve_disk(disk)
|
||||||
|
|||||||
@@ -9,6 +9,38 @@ import urllib.error
|
|||||||
import urllib.request
|
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'
|
SCRIPT = Path(__file__).resolve().parents[1] / 'dev/laptop-smoke.py'
|
||||||
|
|
||||||
|
|
||||||
@@ -80,6 +112,58 @@ class LaptopSmokeTests(unittest.TestCase):
|
|||||||
with self.assertRaises(self.mod.SmokeError):
|
with self.assertRaises(self.mod.SmokeError):
|
||||||
self.mod.partition_installer_command('bad;token')
|
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):
|
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
|
# 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.
|
# the mounted ESP. That mount must not produce a false base mismatch.
|
||||||
|
|||||||
Reference in New Issue
Block a user