diff options
Diffstat (limited to 'nixos/tests/turbovnc-headless-server.nix')
-rw-r--r-- | nixos/tests/turbovnc-headless-server.nix | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/nixos/tests/turbovnc-headless-server.nix b/nixos/tests/turbovnc-headless-server.nix new file mode 100644 index 0000000000000..35da9a53d2db8 --- /dev/null +++ b/nixos/tests/turbovnc-headless-server.nix @@ -0,0 +1,171 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "turbovnc-headless-server"; + meta = { + maintainers = with lib.maintainers; [ nh2 ]; + }; + + machine = { pkgs, ... }: { + + environment.systemPackages = with pkgs; [ + glxinfo + procps # for `pkill`, `pidof` in the test + scrot # for screenshotting Xorg + turbovnc + ]; + + programs.turbovnc.ensureHeadlessSoftwareOpenGL = true; + + networking.firewall = { + # Reject instead of drop, for failures instead of hangs. + rejectPackets = true; + allowedTCPPorts = [ + 5900 # VNC :0, for seeing what's going on in the server + ]; + }; + + # So that we can ssh into the VM, see e.g. + # http://blog.patapon.info/nixos-local-vm/#accessing-the-vm-with-ssh + services.openssh.enable = true; + services.openssh.permitRootLogin = "yes"; + users.extraUsers.root.password = ""; + users.mutableUsers = false; + }; + + testScript = '' + def wait_until_terminated_or_succeeds( + termination_check_shell_command, + success_check_shell_command, + get_detail_message_fn, + retries=60, + retry_sleep=0.5, + ): + def check_success(): + command_exit_code, _output = machine.execute(success_check_shell_command) + return command_exit_code == 0 + + for _ in range(retries): + exit_check_exit_code, _output = machine.execute(termination_check_shell_command) + is_terminated = exit_check_exit_code != 0 + if is_terminated: + if check_success(): + return + else: + details = get_detail_message_fn() + raise Exception( + f"termination check ({termination_check_shell_command}) triggered without command succeeding ({success_check_shell_command}); details: {details}" + ) + else: + if check_success(): + return + time.sleep(retry_sleep) + + if not check_success(): + details = get_detail_message_fn() + raise Exception( + f"action timed out ({success_check_shell_command}); details: {details}" + ) + + + # Below we use the pattern: + # (cmd | tee stdout.log) 3>&1 1>&2 2>&3 | tee stderr.log + # to capture both stderr and stdout while also teeing them, see: + # https://unix.stackexchange.com/questions/6430/how-to-redirect-stderr-and-stdout-to-different-files-and-also-display-in-termina/6431#6431 + + + # Starts headless VNC server, backgrounding it. + def start_xvnc(): + xvnc_command = " ".join( + [ + "Xvnc", + ":0", + "-iglx", + "-auth /root/.Xauthority", + "-geometry 1240x900", + "-depth 24", + "-rfbwait 5000", + "-deferupdate 1", + "-verbose", + "-securitytypes none", + # We don't enforce localhost listening such that we + # can connect from outside the VM using + # env QEMU_NET_OPTS=hostfwd=tcp::5900-:5900 $(nix-build nixos/tests/turbovnc-headless-server.nix -A driver)/bin/nixos-test-driver + # for testing purposes, and so that we can in the future + # add another test case that connects the TurboVNC client. + # "-localhost", + ] + ) + machine.execute( + # Note trailing & for backgrounding. + f"({xvnc_command} | tee /tmp/Xvnc.stdout) 3>&1 1>&2 2>&3 | tee /tmp/Xvnc.stderr &", + ) + + + # Waits until the server log message that tells us that GLX is ready + # (requires `-verbose` above), avoiding screenshoting racing below. + def wait_until_xvnc_glx_ready(): + machine.wait_until_succeeds("test -f /tmp/Xvnc.stderr") + wait_until_terminated_or_succeeds( + termination_check_shell_command="pidof Xvnc", + success_check_shell_command="grep 'GLX: Initialized DRISWRAST' /tmp/Xvnc.stderr", + get_detail_message_fn=lambda: "Contents of /tmp/Xvnc.stderr:\n" + + machine.succeed("cat /tmp/Xvnc.stderr"), + ) + + + # Checks that we detect glxgears failing when + # `LIBGL_DRIVERS_PATH=/nonexistent` is set + # (in which case software rendering should not work). + def test_glxgears_failing_with_bad_driver_path(): + machine.execute( + # Note trailing & for backgrounding. + "(env DISPLAY=:0 LIBGL_DRIVERS_PATH=/nonexistent glxgears -info | tee /tmp/glxgears-should-fail.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears-should-fail.stderr &" + ) + machine.wait_until_succeeds("test -f /tmp/glxgears-should-fail.stderr") + wait_until_terminated_or_succeeds( + termination_check_shell_command="pidof glxgears", + success_check_shell_command="grep 'libGL error: failed to load driver: swrast' /tmp/glxgears-should-fail.stderr", + get_detail_message_fn=lambda: "Contents of /tmp/glxgears-should-fail.stderr:\n" + + machine.succeed("cat /tmp/glxgears-should-fail.stderr"), + ) + machine.wait_until_fails("pidof glxgears") + + + # Starts glxgears, backgrounding it. Waits until it prints the `GL_RENDERER`. + # Does not quit glxgears. + def test_glxgears_prints_renderer(): + machine.execute( + # Note trailing & for backgrounding. + "(env DISPLAY=:0 glxgears -info | tee /tmp/glxgears.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears.stderr &" + ) + machine.wait_until_succeeds("test -f /tmp/glxgears.stderr") + wait_until_terminated_or_succeeds( + termination_check_shell_command="pidof glxgears", + success_check_shell_command="grep 'GL_RENDERER' /tmp/glxgears.stdout", + get_detail_message_fn=lambda: "Contents of /tmp/glxgears.stderr:\n" + + machine.succeed("cat /tmp/glxgears.stderr"), + ) + + + with subtest("Start Xvnc"): + start_xvnc() + wait_until_xvnc_glx_ready() + + with subtest("Ensure bad driver path makes glxgears fail"): + test_glxgears_failing_with_bad_driver_path() + + with subtest("Run 3D application (glxgears)"): + test_glxgears_prints_renderer() + + # Take screenshot; should display the glxgears. + machine.succeed("scrot --display :0 /tmp/glxgears.png") + + # Copy files down. + machine.copy_from_vm("/tmp/glxgears.png") + machine.copy_from_vm("/tmp/glxgears.stdout") + machine.copy_from_vm("/tmp/glxgears-should-fail.stdout") + machine.copy_from_vm("/tmp/glxgears-should-fail.stderr") + machine.copy_from_vm("/tmp/Xvnc.stdout") + machine.copy_from_vm("/tmp/Xvnc.stderr") + ''; + +}) |