nixos/tests: Test that Remote SSH can patch Node

+157 -3
+1
nixos/tests/all-tests.nix
··· 812 victoriametrics = handleTest ./victoriametrics.nix {}; 813 vikunja = handleTest ./vikunja.nix {}; 814 virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {}; 815 vscodium = discoverTests (import ./vscodium.nix); 816 vsftpd = handleTest ./vsftpd.nix {}; 817 warzone2100 = handleTest ./warzone2100.nix {};
··· 812 victoriametrics = handleTest ./victoriametrics.nix {}; 813 vikunja = handleTest ./vikunja.nix {}; 814 virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {}; 815 + vscode-remote-ssh = handleTestOn ["x86_64-linux"] ./vscode-remote-ssh.nix {}; 816 vscodium = discoverTests (import ./vscodium.nix); 817 vsftpd = handleTest ./vsftpd.nix {}; 818 warzone2100 = handleTest ./warzone2100.nix {};
+145
nixos/tests/vscode-remote-ssh.nix
···
··· 1 + import ./make-test-python.nix ({ lib, ... }: let 2 + pkgs = (import ../..) { 3 + config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ 4 + "vscode" "vscode-with-extensions" "vscode-extension-ms-vscode-remote-remote-ssh" 5 + ]; 6 + }; 7 + 8 + inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; 9 + 10 + # Every VS Code server build corresponds to a specific commit of VS Code, so we 11 + # want this to match the commit of VS Code in Nixpkgs. 12 + # e.g. git rev-parse 1.77.0 13 + rev = "7f329fe6c66b0f86ae1574c2911b681ad5a45d63"; 14 + shortRev = builtins.substring 0 8 rev; 15 + 16 + # Our tests run without networking so the remote-ssh extension will always fail to 17 + # download the VSCode server so we can copy it onto the server ourselves. 18 + vscode-server = pkgs.srcOnly { 19 + name = "vscode-server-${shortRev}"; 20 + src = pkgs.fetchurl { 21 + name = "vscode-server-${shortRev}.tar.gz"; 22 + url = "https://update.code.visualstudio.com/commit:${rev}/server-linux-x64/stable"; 23 + sha256 = "11g234lwl3jn5q3637n9sxz5ghhzqvq137lk42vl2nbb57hgyqgq"; 24 + }; 25 + }; 26 + in { 27 + name = "vscode-remote-ssh"; 28 + meta.maintainers = with lib.maintainers; [ Enzime ]; 29 + 30 + nodes = let 31 + serverAddress = "192.168.0.2"; 32 + clientAddress = "192.168.0.1"; 33 + in { 34 + server = { ... }: { 35 + networking.interfaces.eth1.ipv4.addresses = [ { address = serverAddress; prefixLength = 24; } ]; 36 + services.openssh.enable = true; 37 + users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 38 + virtualisation.additionalPaths = [ pkgs.nodejs-14_x pkgs.nodejs-16_x ]; 39 + }; 40 + client = { ... }: { 41 + imports = [ ./common/x11.nix ./common/user-account.nix ]; 42 + networking.interfaces.eth1.ipv4.addresses = [ { address = clientAddress; prefixLength = 24; } ]; 43 + networking.hosts.${serverAddress} = [ "server" ]; 44 + test-support.displayManager.auto.user = "alice"; 45 + environment.systemPackages = [ 46 + (pkgs.vscode-with-extensions.override { 47 + vscodeExtensions = [ 48 + pkgs.vscode-extensions.ms-vscode-remote.remote-ssh 49 + ]; 50 + }) 51 + ]; 52 + }; 53 + }; 54 + 55 + enableOCR = true; 56 + 57 + testScript = let 58 + jq = "${pkgs.jq}/bin/jq"; 59 + 60 + ssh-config = builtins.toFile "ssh.conf" '' 61 + UserKnownHostsFile=/dev/null 62 + StrictHostKeyChecking=no 63 + ''; 64 + 65 + vscode-config = builtins.toFile "settings.json" '' 66 + { 67 + "window.zoomLevel": 1, 68 + "security.workspace.trust.startupPrompt": "always" 69 + } 70 + ''; 71 + in '' 72 + def connect_with_remote_ssh(screenshot, should_succeed): 73 + print(f"connect_with_remote_ssh({screenshot=}, {should_succeed=})") 74 + 75 + if server.execute("test -d ~/.vscode-server")[0] == 0: 76 + server.succeed("rm -r ~/.vscode-server") 77 + 78 + server.succeed("mkdir -p ~/.vscode-server/bin") 79 + server.succeed("cp -r ${vscode-server} ~/.vscode-server/bin/${rev}") 80 + 81 + client.succeed("sudo -u alice code --remote=ssh-remote+root@server /root") 82 + client.wait_for_window("Visual Studio Code") 83 + 84 + client.wait_for_text("Do you trust the authors" if should_succeed else "Disconnected from SSH") 85 + client.screenshot(screenshot) 86 + 87 + if should_succeed: 88 + # Press the Don't Trust button 89 + client.send_key("tab") 90 + client.send_key("tab") 91 + client.send_key("tab") 92 + client.send_key("\n") 93 + else: 94 + # Close the error dialog 95 + client.send_key("esc") 96 + 97 + client.send_key("ctrl-q") 98 + client.wait_until_fails("pidof code") 99 + 100 + 101 + start_all() 102 + server.wait_for_open_port(22) 103 + 104 + VSCODE_COMMIT = server.execute("${jq} -r .commit ${pkgs.vscode}/lib/vscode/resources/app/product.json")[1].rstrip() 105 + SERVER_COMMIT = server.execute("${jq} -r .commit ${vscode-server}/product.json")[1].rstrip() 106 + 107 + print(f"{VSCODE_COMMIT=} {SERVER_COMMIT=}") 108 + assert VSCODE_COMMIT == SERVER_COMMIT, "VSCODE_COMMIT and SERVER_COMMIT do not match" 109 + 110 + client.wait_until_succeeds("ping -c1 server") 111 + client.succeed("sudo -u alice mkdir ~alice/.ssh") 112 + client.succeed("sudo -u alice install -Dm 600 ${snakeOilPrivateKey} ~alice/.ssh/id_ecdsa") 113 + client.succeed("sudo -u alice install ${ssh-config} ~alice/.ssh/config") 114 + client.succeed("sudo -u alice install -Dm 644 ${vscode-config} ~alice/.config/Code/User/settings.json") 115 + 116 + client.wait_for_x() 117 + client.wait_for_file("~alice/.Xauthority") 118 + client.succeed("xauth merge ~alice/.Xauthority") 119 + # Move the mouse out of the way 120 + client.succeed("${pkgs.xdotool}/bin/xdotool mousemove 0 0") 121 + 122 + with subtest("fails to connect when Node is broken"): 123 + server.fail("node -v") 124 + connect_with_remote_ssh(screenshot="no_node_installed", should_succeed=False) 125 + server.succeed("test -e ~/.vscode-server/bin/*/node") 126 + server.fail("~/.vscode-server/bin/*/node -v") 127 + 128 + with subtest("fails to connect when server has the wrong Node installed"): 129 + server.succeed("nix-env -i ${pkgs.nodejs-14_x}") 130 + connect_with_remote_ssh(screenshot="wrong_node_installed", should_succeed=False) 131 + server.fail("~/.vscode-server/bin/*/node -v") 132 + 133 + with subtest("connects when server has the correct Node installed"): 134 + server.succeed("nix-env -i ${pkgs.nodejs-16_x}") 135 + connect_with_remote_ssh(screenshot="correct_node_installed", should_succeed=True) 136 + server.succeed("~/.vscode-server/bin/*/node -v") 137 + server.succeed("kill $(pgrep -f [v]scode-server)") 138 + server.succeed("nix-env -e nodejs") 139 + 140 + with subtest("connects when server can build Node from Nixpkgs"): 141 + server.succeed("mkdir -p /nix/var/nix/profiles/per-user/root/channels") 142 + server.succeed("ln -s ${pkgs.path} /nix/var/nix/profiles/per-user/root/channels/nixos") 143 + connect_with_remote_ssh(screenshot="build_node_with_nix", should_succeed=True) 144 + ''; 145 + })
+3
pkgs/applications/editors/vscode/extensions/ms-vscode-remote.remote-ssh/default.nix
··· 1 { lib 2 , vscode-utils 3 , useLocalExtensions ? false 4 }: ··· 86 substituteInPlace "out/extension.js" \ 87 --replace '# Start the server\n' '${patch}' 88 ''; 89 90 meta = { 91 description = "Use any remote machine with a SSH server as your development environment.";
··· 1 { lib 2 + , nixosTests 3 , vscode-utils 4 , useLocalExtensions ? false 5 }: ··· 87 substituteInPlace "out/extension.js" \ 88 --replace '# Start the server\n' '${patch}' 89 ''; 90 + 91 + passthru.tests = { inherit (nixosTests) vscode-remote-ssh; }; 92 93 meta = { 94 description = "Use any remote machine with a SSH server as your development environment.";
+7 -1
pkgs/applications/editors/vscode/vscode.nix
··· 1 - { stdenv, lib, callPackage, fetchurl 2 , isInsiders ? false 3 , commandLineArgs ? "" 4 , useVSCodeRipgrep ? stdenv.isDarwin ··· 47 tests = {}; 48 49 sourceRoot = ""; 50 51 updateScript = ./update-vscode.sh; 52
··· 1 + { stdenv 2 + , lib 3 + , callPackage 4 + , fetchurl 5 + , nixosTests 6 , isInsiders ? false 7 , commandLineArgs ? "" 8 , useVSCodeRipgrep ? stdenv.isDarwin ··· 51 tests = {}; 52 53 sourceRoot = ""; 54 + 55 + tests = { inherit (nixosTests) vscode-remote-ssh; }; 56 57 updateScript = ./update-vscode.sh; 58
+1 -2
pkgs/build-support/src-only/default.nix
··· 1 { stdenv }: 2 # srcOnly is a utility builder that only fetches and unpacks the given `src`, 3 - # maybe pathings it in the process with the optional `patches` and 4 - # `buildInputs` attributes. 5 # 6 # It can be invoked directly, or be used to wrap an existing derivation. Eg: 7 #
··· 1 { stdenv }: 2 # srcOnly is a utility builder that only fetches and unpacks the given `src`, 3 + # and optionally patching with `patches` or adding build inputs. 4 # 5 # It can be invoked directly, or be used to wrap an existing derivation. Eg: 6 #