at 25.11-pre 7.4 kB view raw
1import ./make-test-python.nix ( 2 { pkgs, lib, ... }: 3 { 4 name = "turbovnc-headless-server"; 5 meta = { 6 maintainers = with lib.maintainers; [ nh2 ]; 7 }; 8 9 nodes.machine = 10 { pkgs, ... }: 11 { 12 13 environment.systemPackages = with pkgs; [ 14 mesa-demos 15 procps # for `pkill`, `pidof` in the test 16 scrot # for screenshotting Xorg 17 turbovnc 18 ]; 19 20 programs.turbovnc.ensureHeadlessSoftwareOpenGL = true; 21 22 networking.firewall = { 23 # Reject instead of drop, for failures instead of hangs. 24 rejectPackets = true; 25 allowedTCPPorts = [ 26 5900 # VNC :0, for seeing what's going on in the server 27 ]; 28 }; 29 30 # So that we can ssh into the VM, see e.g. 31 # https://nixos.org/manual/nixos/stable/#sec-nixos-test-port-forwarding 32 services.openssh.enable = true; 33 users.mutableUsers = false; 34 # `test-instrumentation.nix` already sets an empty root password. 35 # The following have to all be set to allow an empty SSH login password. 36 services.openssh.settings.PermitRootLogin = "yes"; 37 services.openssh.settings.PermitEmptyPasswords = "yes"; 38 security.pam.services.sshd.allowNullPassword = true; # the default `UsePam yes` makes this necessary 39 }; 40 41 testScript = '' 42 def wait_until_terminated_or_succeeds( 43 termination_check_shell_command, 44 success_check_shell_command, 45 get_detail_message_fn, 46 retries=60, 47 retry_sleep=0.5, 48 ): 49 def check_success(): 50 command_exit_code, _output = machine.execute(success_check_shell_command) 51 return command_exit_code == 0 52 53 for _ in range(retries): 54 exit_check_exit_code, _output = machine.execute(termination_check_shell_command) 55 is_terminated = exit_check_exit_code != 0 56 if is_terminated: 57 if check_success(): 58 return 59 else: 60 details = get_detail_message_fn() 61 raise Exception( 62 f"termination check ({termination_check_shell_command}) triggered without command succeeding ({success_check_shell_command}); details: {details}" 63 ) 64 else: 65 if check_success(): 66 return 67 import time 68 time.sleep(retry_sleep) 69 70 if not check_success(): 71 details = get_detail_message_fn() 72 raise Exception( 73 f"action timed out ({success_check_shell_command}); details: {details}" 74 ) 75 76 77 # Below we use the pattern: 78 # (cmd | tee stdout.log) 3>&1 1>&2 2>&3 | tee stderr.log 79 # to capture both stderr and stdout while also teeing them, see: 80 # https://unix.stackexchange.com/questions/6430/how-to-redirect-stderr-and-stdout-to-different-files-and-also-display-in-termina/6431#6431 81 82 83 # Starts headless VNC server, backgrounding it. 84 def start_xvnc(): 85 xvnc_command = " ".join( 86 [ 87 "Xvnc", 88 ":0", 89 "-iglx", 90 "-auth /root/.Xauthority", 91 "-geometry 1240x900", 92 "-depth 24", 93 "-rfbwait 5000", 94 "-deferupdate 1", 95 "-verbose", 96 "-securitytypes none", 97 # We don't enforce localhost listening such that we 98 # can connect from outside the VM using 99 # env QEMU_NET_OPTS=hostfwd=tcp::5900-:5900 $(nix-build nixos/tests/turbovnc-headless-server.nix -A driver)/bin/nixos-test-driver 100 # for testing purposes, and so that we can in the future 101 # add another test case that connects the TurboVNC client. 102 # "-localhost", 103 ] 104 ) 105 machine.execute( 106 # Note trailing & for backgrounding. 107 f"({xvnc_command} | tee /tmp/Xvnc.stdout) 3>&1 1>&2 2>&3 | tee /tmp/Xvnc.stderr >&2 &", 108 ) 109 110 111 # Waits until the server log message that tells us that GLX is ready 112 # (requires `-verbose` above), avoiding screenshoting racing below. 113 def wait_until_xvnc_glx_ready(): 114 machine.wait_until_succeeds("test -f /tmp/Xvnc.stderr") 115 wait_until_terminated_or_succeeds( 116 termination_check_shell_command="pidof Xvnc", 117 success_check_shell_command="grep 'GLX: Initialized DRISWRAST' /tmp/Xvnc.stderr", 118 get_detail_message_fn=lambda: "Contents of /tmp/Xvnc.stderr:\n" 119 + machine.succeed("cat /tmp/Xvnc.stderr"), 120 ) 121 122 123 # Checks that we detect glxgears failing when 124 # `LIBGL_DRIVERS_PATH=/nonexistent` is set 125 # (in which case software rendering should not work). 126 def test_glxgears_failing_with_bad_driver_path(): 127 machine.execute( 128 # Note trailing & for backgrounding. 129 "(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 >&2 &" 130 ) 131 machine.wait_until_succeeds("test -f /tmp/glxgears-should-fail.stderr") 132 wait_until_terminated_or_succeeds( 133 termination_check_shell_command="pidof glxgears", 134 success_check_shell_command="grep 'MESA-LOADER: failed to open swrast' /tmp/glxgears-should-fail.stderr", 135 get_detail_message_fn=lambda: "Contents of /tmp/glxgears-should-fail.stderr:\n" 136 + machine.succeed("cat /tmp/glxgears-should-fail.stderr"), 137 ) 138 machine.wait_until_fails("pidof glxgears") 139 140 141 # Starts glxgears, backgrounding it. Waits until it prints the `GL_RENDERER`. 142 # Does not quit glxgears. 143 def test_glxgears_prints_renderer(): 144 machine.execute( 145 # Note trailing & for backgrounding. 146 "(env DISPLAY=:0 glxgears -info | tee /tmp/glxgears.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears.stderr >&2 &" 147 ) 148 machine.wait_until_succeeds("test -f /tmp/glxgears.stderr") 149 wait_until_terminated_or_succeeds( 150 termination_check_shell_command="pidof glxgears", 151 success_check_shell_command="grep 'GL_RENDERER' /tmp/glxgears.stdout", 152 get_detail_message_fn=lambda: "Contents of /tmp/glxgears.stderr:\n" 153 + machine.succeed("cat /tmp/glxgears.stderr"), 154 ) 155 156 157 with subtest("Start Xvnc"): 158 start_xvnc() 159 wait_until_xvnc_glx_ready() 160 161 with subtest("Ensure bad driver path makes glxgears fail"): 162 test_glxgears_failing_with_bad_driver_path() 163 164 with subtest("Run 3D application (glxgears)"): 165 test_glxgears_prints_renderer() 166 167 # Take screenshot; should display the glxgears. 168 machine.succeed("scrot --display :0 /tmp/glxgears.png") 169 170 # Copy files down. 171 machine.copy_from_vm("/tmp/glxgears.png") 172 machine.copy_from_vm("/tmp/glxgears.stdout") 173 machine.copy_from_vm("/tmp/glxgears-should-fail.stdout") 174 machine.copy_from_vm("/tmp/glxgears-should-fail.stderr") 175 machine.copy_from_vm("/tmp/Xvnc.stdout") 176 machine.copy_from_vm("/tmp/Xvnc.stderr") 177 ''; 178 179 } 180)