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