diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix index d07272360dde..45d31b6f2ab0 100644 --- a/nixos/tests/virtualbox.nix +++ b/nixos/tests/virtualbox.nix @@ -15,7 +15,7 @@ assert use64bitGuest -> useKvmNestedVirt; -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; let @@ -99,7 +99,7 @@ let rotated = map (i: "${logfile}.${toString i}") (range 1 9); all = concatMapStringsSep " " (f: "\"${f}\"") ([logfile] ++ rotated); logcmd = "tail -F ${all} 2> /dev/null | logger -t \"${tag}\""; - in optionalString debug "$machine->execute(ru '${logcmd} & disown');"; + in if debug then "machine.execute(ru('${logcmd} & disown'))" else "pass"; testVM = vmName: vmScript: let cfg = (import ../lib/eval-config.nix { @@ -207,96 +207,105 @@ let }; testSubs = '' - my ${"$" + name}_sharepath = '${sharePath}'; - sub checkRunning_${name} { - my $cmd = 'VBoxManage list runningvms | grep -q "^\"${name}\""'; - my ($status, $out) = $machine->execute(ru $cmd); - return $status == 0; - } - sub cleanup_${name} { - $machine->execute(ru "VBoxManage controlvm ${name} poweroff") - if checkRunning_${name}; - $machine->succeed("rm -rf ${sharePath}"); - $machine->succeed("mkdir -p ${sharePath}"); - $machine->succeed("chown alice.users ${sharePath}"); - } + ${name}_sharepath = "${sharePath}" - sub createVM_${name} { - vbm("createvm --name ${name} ${createFlags}"); - vbm("modifyvm ${name} ${vmFlags}"); - vbm("setextradata ${name} VBoxInternal/PDM/HaltOnReset 1"); - vbm("storagectl ${name} ${controllerFlags}"); - vbm("storageattach ${name} ${diskFlags}"); - vbm("sharedfolder add ${name} ${sharedFlags}"); - vbm("sharedfolder add ${name} ${nixstoreFlags}"); - cleanup_${name}; - ${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"} - } + def check_running_${name}(): + cmd = "VBoxManage list runningvms | grep -q '^\"${name}\"'" + (status, _) = machine.execute(ru(cmd)) + return status == 0 - sub destroyVM_${name} { - cleanup_${name}; - vbm("unregistervm ${name} --delete"); - } - sub waitForVMBoot_${name} { - $machine->execute(ru( - 'set -e; i=0; '. - 'while ! test -e ${sharePath}/boot-done; do '. - 'sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; '. - 'VBoxManage list runningvms | grep -q "^\"${name}\""; '. - 'done' - )); - } + def cleanup_${name}(): + if check_running_${name}(): + machine.execute(ru("VBoxManage controlvm ${name} poweroff")) + machine.succeed("rm -rf ${sharePath}") + machine.succeed("mkdir -p ${sharePath}") + machine.succeed("chown alice.users ${sharePath}") - sub waitForIP_${name} ($) { - my $property = "/VirtualBox/GuestInfo/Net/$_[0]/V4/IP"; - my $getip = "VBoxManage guestproperty get ${name} $property | ". - "sed -n -e 's/^Value: //p'"; - my $ip = $machine->succeed(ru( - 'for i in $(seq 1000); do '. - 'if ipaddr="$('.$getip.')" && [ -n "$ipaddr" ]; then '. - 'echo "$ipaddr"; exit 0; '. - 'fi; '. - 'sleep 1; '. - 'done; '. - 'echo "Could not get IPv4 address for ${name}!" >&2; '. - 'exit 1' - )); - chomp $ip; - return $ip; - } - sub waitForStartup_${name} { - for (my $i = 0; $i <= 120; $i += 10) { - $machine->sleep(10); - return if checkRunning_${name}; - eval { $_[0]->() } if defined $_[0]; - } - die "VirtualBox VM didn't start up within 2 minutes"; - } + def create_vm_${name}(): + # fmt: off + vbm(f"createvm --name ${name} ${createFlags}") + vbm(f"modifyvm ${name} ${vmFlags}") + vbm(f"setextradata ${name} VBoxInternal/PDM/HaltOnReset 1") + vbm(f"storagectl ${name} ${controllerFlags}") + vbm(f"storageattach ${name} ${diskFlags}") + vbm(f"sharedfolder add ${name} ${sharedFlags}") + vbm(f"sharedfolder add ${name} ${nixstoreFlags}") + cleanup_${name}() - sub waitForShutdown_${name} { - for (my $i = 0; $i <= 120; $i += 10) { - $machine->sleep(10); - return unless checkRunning_${name}; - } - die "VirtualBox VM didn't shut down within 2 minutes"; - } + ${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"} + # fmt: on - sub shutdownVM_${name} { - $machine->succeed(ru "touch ${sharePath}/shutdown"); - $machine->execute( - 'set -e; i=0; '. - 'while test -e ${sharePath}/shutdown '. - ' -o -e ${sharePath}/boot-done; do '. - 'sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; '. - 'done' - ); - waitForShutdown_${name}; - } + + def destroy_vm_${name}(): + cleanup_${name}() + vbm("unregistervm ${name} --delete") + + + def wait_for_vm_boot_${name}(): + machine.execute( + ru( + "set -e; i=0; " + "while ! test -e ${sharePath}/boot-done; do " + "sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; " + "VBoxManage list runningvms | grep -q '^\"${name}\"'; " + "done" + ) + ) + + + def wait_for_ip_${name}(interface): + property = f"/VirtualBox/GuestInfo/Net/{interface}/V4/IP" + # fmt: off + getip = f"VBoxManage guestproperty get ${name} {property} | sed -n -e 's/^Value: //p'" + # fmt: on + + ip = machine.succeed( + ru( + "for i in $(seq 1000); do " + f'if ipaddr="$({getip})" && [ -n "$ipaddr" ]; then ' + 'echo "$ipaddr"; exit 0; ' + "fi; " + "sleep 1; " + "done; " + "echo 'Could not get IPv4 address for ${name}!' >&2; " + "exit 1" + ) + ).strip() + return ip + + + def wait_for_startup_${name}(nudge=lambda: None): + for _ in range(0, 130, 10): + machine.sleep(10) + if check_running_${name}(): + return + nudge() + raise Exception("VirtualBox VM didn't start up within 2 minutes") + + + def wait_for_shutdown_${name}(): + for _ in range(0, 130, 10): + machine.sleep(10) + if not check_running_${name}(): + return + raise Exception("VirtualBox VM didn't shut down within 2 minutes") + + + def shutdown_vm_${name}(): + machine.succeed(ru("touch ${sharePath}/shutdown")) + machine.execute( + "set -e; i=0; " + "while test -e ${sharePath}/shutdown " + " -o -e ${sharePath}/boot-done; do " + "sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; " + "done" + ) + wait_for_shutdown_${name}() ''; }; @@ -367,26 +376,31 @@ let }; testScript = '' - sub ru ($) { - my $esc = $_[0] =~ s/'/'\\${"'"}'/gr; - return "su - alice -c '$esc'"; - } - - sub vbm { - $machine->succeed(ru("VBoxManage ".$_[0])); - }; - - sub removeUUIDs { - return join("\n", grep { $_ !~ /^UUID:/ } split(/\n/, $_[0]))."\n"; - } - + from shlex import quote ${concatStrings (mapAttrsToList (_: getAttr "testSubs") vms)} - $machine->waitForX; + def ru(cmd: str) -> str: + return f"su - alice -c {quote(cmd)}" + + def vbm(cmd: str) -> str: + return machine.succeed(ru(f"VBoxManage {cmd}")) + + + def remove_uuids(output: str) -> str: + return "\n".join( + [line for line in (output or "").splitlines() if not line.startswith("UUID:")] + ) + + + machine.wait_for_x() + + # fmt: off ${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"} + # fmt: on ${testScript} + # (keep black happy) ''; meta = with pkgs.stdenv.lib.maintainers; { @@ -396,133 +410,129 @@ let unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) { enable-extension-pack = '' - createVM_testExtensionPack; - vbm("startvm testExtensionPack"); - waitForStartup_testExtensionPack; - $machine->screenshot("cli_started"); - waitForVMBoot_testExtensionPack; - $machine->screenshot("cli_booted"); + create_vm_testExtensionPack() + vbm("startvm testExtensionPack") + wait_for_startup_testExtensionPack() + machine.screenshot("cli_started") + wait_for_vm_boot_testExtensionPack() + machine.screenshot("cli_booted") - $machine->nest("Checking for privilege escalation", sub { - $machine->fail("test -e '/root/VirtualBox VMs'"); - $machine->fail("test -e '/root/.config/VirtualBox'"); - $machine->succeed("test -e '/home/alice/VirtualBox VMs'"); - }); + with machine.nested("Checking for privilege escalation"): + machine.fail("test -e '/root/VirtualBox VMs'") + machine.fail("test -e '/root/.config/VirtualBox'") + machine.succeed("test -e '/home/alice/VirtualBox VMs'") - shutdownVM_testExtensionPack; - destroyVM_testExtensionPack; + shutdown_vm_testExtensionPack() + destroy_vm_testExtensionPack() ''; }; in mapAttrs (mkVBoxTest false vboxVMs) { simple-gui = '' - createVM_simple; - $machine->succeed(ru "VirtualBox &"); - $machine->waitUntilSucceeds( - ru "xprop -name 'Oracle VM VirtualBox Manager'" - ); - $machine->sleep(5); - $machine->screenshot("gui_manager_started"); # Home to select Tools, down to move to the VM, enter to start it. - $machine->sendKeys("home"); - $machine->sendKeys("down"); - $machine->sendKeys("ret"); - $machine->screenshot("gui_manager_sent_startup"); - waitForStartup_simple (sub { - $machine->sendKeys("home"); - $machine->sendKeys("down"); - $machine->sendKeys("ret"); - }); - $machine->screenshot("gui_started"); - waitForVMBoot_simple; - $machine->screenshot("gui_booted"); - shutdownVM_simple; - $machine->sleep(5); - $machine->screenshot("gui_stopped"); - $machine->sendKeys("ctrl-q"); - $machine->sleep(5); - $machine->screenshot("gui_manager_stopped"); - destroyVM_simple; + def send_vm_startup(): + machine.send_key("home") + machine.send_key("down") + machine.send_key("ret") + + + create_vm_simple() + machine.succeed(ru("VirtualBox &")) + machine.wait_until_succeeds(ru("xprop -name 'Oracle VM VirtualBox Manager'")) + machine.sleep(5) + machine.screenshot("gui_manager_started") + send_vm_startup() + machine.screenshot("gui_manager_sent_startup") + wait_for_startup_simple(send_vm_startup) + machine.screenshot("gui_started") + wait_for_vm_boot_simple() + machine.screenshot("gui_booted") + shutdown_vm_simple() + machine.sleep(5) + machine.screenshot("gui_stopped") + machine.send_key("ctrl-q") + machine.sleep(5) + machine.screenshot("gui_manager_stopped") + destroy_vm_simple() ''; simple-cli = '' - createVM_simple; - vbm("startvm simple"); - waitForStartup_simple; - $machine->screenshot("cli_started"); - waitForVMBoot_simple; - $machine->screenshot("cli_booted"); + create_vm_simple() + vbm("startvm simple") + wait_for_startup_simple() + machine.screenshot("cli_started") + wait_for_vm_boot_simple() + machine.screenshot("cli_booted") - $machine->nest("Checking for privilege escalation", sub { - $machine->fail("test -e '/root/VirtualBox VMs'"); - $machine->fail("test -e '/root/.config/VirtualBox'"); - $machine->succeed("test -e '/home/alice/VirtualBox VMs'"); - }); + with machine.nested("Checking for privilege escalation"): + machine.fail("test -e '/root/VirtualBox VMs'") + machine.fail("test -e '/root/.config/VirtualBox'") + machine.succeed("test -e '/home/alice/VirtualBox VMs'") - shutdownVM_simple; - destroyVM_simple; + shutdown_vm_simple() + destroy_vm_simple() ''; headless = '' - createVM_headless; - $machine->succeed(ru("VBoxHeadless --startvm headless & disown %1")); - waitForStartup_headless; - waitForVMBoot_headless; - shutdownVM_headless; - destroyVM_headless; + create_vm_headless() + machine.succeed(ru("VBoxHeadless --startvm headless & disown %1")) + wait_for_startup_headless() + wait_for_vm_boot_headless() + shutdown_vm_headless() + destroy_vm_headless() ''; host-usb-permissions = '' - my $userUSB = removeUUIDs vbm("list usbhost"); - print STDERR $userUSB; - my $rootUSB = removeUUIDs $machine->succeed("VBoxManage list usbhost"); - print STDERR $rootUSB; + user_usb = remove_uuids(vbm("list usbhost")) + print(user_usb, file=sys.stderr) + root_usb = remove_uuids(machine.succeed("VBoxManage list usbhost")) + print(root_usb, file=sys.stderr) - die "USB host devices differ for root and normal user" - if $userUSB ne $rootUSB; - die "No USB host devices found" if $userUSB =~ //; + if user_usb != root_usb: + raise Exception("USB host devices differ for root and normal user") + if "" in user_usb: + raise Exception("No USB host devices found") ''; systemd-detect-virt = '' - createVM_detectvirt; - vbm("startvm detectvirt"); - waitForStartup_detectvirt; - waitForVMBoot_detectvirt; - shutdownVM_detectvirt; - my $result = $machine->succeed("cat '$detectvirt_sharepath/result'"); - chomp $result; - destroyVM_detectvirt; - die "systemd-detect-virt returned \"$result\" instead of \"oracle\"" - if $result ne "oracle"; + create_vm_detectvirt() + vbm("startvm detectvirt") + wait_for_startup_detectvirt() + wait_for_vm_boot_detectvirt() + shutdown_vm_detectvirt() + result = machine.succeed(f"cat '{detectvirt_sharepath}/result'").strip() + destroy_vm_detectvirt() + if result != "oracle": + raise Exception(f'systemd-detect-virt returned "{result}" instead of "oracle"') ''; net-hostonlyif = '' - createVM_test1; - createVM_test2; + create_vm_test1() + create_vm_test2() - vbm("startvm test1"); - waitForStartup_test1; - waitForVMBoot_test1; + vbm("startvm test1") + wait_for_startup_test1() + wait_for_vm_boot_test1() - vbm("startvm test2"); - waitForStartup_test2; - waitForVMBoot_test2; + vbm("startvm test2") + wait_for_startup_test2() + wait_for_vm_boot_test2() - $machine->screenshot("net_booted"); + machine.screenshot("net_booted") - my $test1IP = waitForIP_test1 1; - my $test2IP = waitForIP_test2 1; + test1_ip = wait_for_ip_test1(1) + test2_ip = wait_for_ip_test2(1) - $machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234"); - $machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234"); + machine.succeed(f"echo '{test2_ip}' | nc -N '{test1_ip}' 1234") + machine.succeed(f"echo '{test1_ip}' | nc -N '{test2_ip}' 1234") - $machine->waitUntilSucceeds("nc -N '$test1IP' 5678 < /dev/null >&2"); - $machine->waitUntilSucceeds("nc -N '$test2IP' 5678 < /dev/null >&2"); + machine.wait_until_succeeds(f"nc -N '{test1_ip}' 5678 < /dev/null >&2") + machine.wait_until_succeeds(f"nc -N '{test2_ip}' 5678 < /dev/null >&2") - shutdownVM_test1; - shutdownVM_test2; + shutdown_vm_test1() + shutdown_vm_test2() - destroyVM_test1; - destroyVM_test2; + destroy_vm_test1() + destroy_vm_test2() ''; } // (if enableUnfree then unfreeTests else {})