diff --git a/dev/laptop-smoke.py b/dev/laptop-smoke.py index da5ae8f..d8c997c 100755 --- a/dev/laptop-smoke.py +++ b/dev/laptop-smoke.py @@ -138,10 +138,10 @@ def tree(path): return {str(p.relative_to(path)): sha(p) for p in sorted(path.rglob('*')) if p.is_file()} @contextlib.contextmanager -def mounted(device, options=''): +def mounted(device, filesystem, options=''): import tempfile with tempfile.TemporaryDirectory(prefix='hydra-acceptance-') as directory: - args = ['mount'] + (['-o', options] if options else []) + [device, directory] + args = ['mount', '-t', filesystem] + (['-o', options] if options else []) + [device, directory] run(*args) try: yield Path(directory) finally: run('umount', directory) @@ -149,7 +149,7 @@ def mounted(device, options=''): def preserved(): table = json.loads(run('sfdisk', '--json', '/dev/vda'))['partitiontable'] # No filesystem UUIDs: selected root UUID must change on formatting. - with mounted('/dev/vda1', 'ro') as esp: + with mounted('/dev/vda1', 'vfat', 'ro') as esp: foreign = {k: v for k, v in tree(esp / 'EFI').items() if not k.startswith('HydraOS/')} order = re.search(r'^BootOrder:\s*(.*)$', run('efibootmgr'), re.M) assert order, 'UEFI BootOrder was not observable' @@ -213,7 +213,7 @@ run('udevadm', 'settle') run('mkfs.vfat', '-F', '32', '/dev/vda1') with open('/dev/vda2', 'r+b', buffering=0) as foreign: foreign.write((context['sentinel'] * 1024).encode()); os.fsync(foreign.fileno()) -with mounted('/dev/vda1') as esp: +with mounted('/dev/vda1', 'vfat') as esp: for name in ('EFI/ForeignOS/loader.efi', 'EFI/BOOT/BOOTX64.EFI'): path = esp / name; path.parent.mkdir(parents=True, exist_ok=True) path.write_text(context['sentinel'] + ':' + name) @@ -242,16 +242,16 @@ with Path('/var/lib/portage/world').open('a') as world: world.write('# acceptanc result = {'fixture_emerge': output, 'home': Path('/home/hydratest/acceptance-home').read_text()} ''', 'base-state': r''' -with mounted('/dev/vda3', 'ro,subvol=@root') as root: - with mounted('/dev/vda1', 'ro') as esp: +with mounted('/dev/vda3', 'btrfs', 'ro,subvol=@root') as root: + with mounted('/dev/vda1', 'vfat', 'ro') as esp: result = base_state(root, esp) ''', 'damage': r''' -with mounted('/dev/vda3', 'subvol=@root') as root: +with mounted('/dev/vda3', 'btrfs', 'subvol=@root') as root: kernels = list((root / 'boot').glob('vmlinuz*')) assert kernels, 'No actual installed kernel to damage' for kernel in kernels: kernel.write_bytes(b'DELIBERATE ACCEPTANCE KERNEL DAMAGE\n') -with mounted('/dev/vda1') as esp: +with mounted('/dev/vda1', 'vfat') as esp: loader = esp / 'EFI/HydraOS/grubx64.efi' assert loader.is_file(); loader.write_bytes(b'DELIBERATE ACCEPTANCE EFI DAMAGE\n') run('sync') @@ -263,7 +263,7 @@ assert marker['root_subvolume'] == '@root' assert Path('/home/hydratest/acceptance-home').read_text() == 'current-' + context['sentinel'] assert not Path('/var/db/pkg/app-misc/hydra-acceptance-2.0').exists() assert Path('/usr/share/hydra/base-fixture.txt').read_text().strip() == '1.0' -with mounted('/dev/vda1', 'ro') as esp: result = base_state(Path('/'), esp) +with mounted('/dev/vda1', 'vfat', 'ro') as esp: result = base_state(Path('/'), esp) result['home'] = Path('/home/hydratest/acceptance-home').read_text() result['vm_data'] = tree(Path('/home/hydratest/.local/share/hydra/acceptance-guest')) result['root_mount'] = run('findmnt', '-n', '-o', 'SOURCE,OPTIONS', '/') @@ -339,7 +339,7 @@ recovery.RecoverySession.save_journal = pause raise SystemExit(recovery.main(ARGUMENTS)) """.replace('ARGUMENTS', repr(['restore', '--root-uuid', context['root_uuid'], '--checkpoint', context['checkpoint'], '--execute', '--confirm-root-uuid', context['root_uuid']])) stop_child(source) -with mounted('/dev/vda3', 'ro,subvolid=5') as top: +with mounted('/dev/vda3', 'btrfs', 'ro,subvolid=5') as top: journal = json.loads((top / '@snapshots/restore.json').read_text()) assert journal['phase'] == 'root-retained' assert not (top / '@root').exists() and (top / journal['failed_root']).exists() diff --git a/tests/test_laptop_smoke.py b/tests/test_laptop_smoke.py index ee73586..e78875a 100644 --- a/tests/test_laptop_smoke.py +++ b/tests/test_laptop_smoke.py @@ -117,3 +117,27 @@ class LaptopSmokeTests(unittest.TestCase): self.assertNotIn('--offline', constants, 'emerge has no --offline option') self.assertIn('--ignore-default-opts', constants) self.assertIn('--nodeps', constants) + + def test_guest_mount_explicitly_selects_known_filesystem(self): + namespace = {} + exec(self.mod.GUEST_COMMON, namespace) + calls = [] + namespace['run'] = lambda *args: calls.append(args) + for device, filesystem, options in (('/dev/vda1', 'vfat', 'ro'), + ('/dev/vda2', 'ext4', 'ro'), + ('/dev/vda3', 'btrfs', 'ro,subvol=@root')): + with self.subTest(device=device): + with namespace['mounted'](device, filesystem, options) as directory: + self.assertEqual(calls[-1], ('mount', '-t', filesystem, '-o', options, device, str(directory))) + self.assertEqual(calls[-1], ('umount', str(directory))) + + def test_every_guest_mount_call_names_the_partition_filesystem(self): + import ast + expected = {'/dev/vda1': 'vfat', '/dev/vda2': 'ext4', '/dev/vda3': 'btrfs'} + sources = [self.mod.GUEST_COMMON, *self.mod.GUEST_BODIES.values()] + for source in sources: + for node in ast.walk(ast.parse(source)): + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'mounted': + with self.subTest(call=ast.unparse(node)): + self.assertGreaterEqual(len(node.args), 2) + self.assertEqual(ast.literal_eval(node.args[1]), expected[ast.literal_eval(node.args[0])])