172 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
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")
 | 
						|
  '';
 | 
						|
 | 
						|
})
 |