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