diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 495d6e1..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: build -on: [pull_request, push] -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v19 - with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} - - run: nix build - - run: nix flake check diff --git a/README.md b/README.md index 2aff0ee..4fbaae7 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,27 @@ cargo build ## Install ```bash cargo install -``` \ No newline at end of file +``` + +## Test + +##### Gnome Graphical Integration Test + +- `nix build .#gnome-graphical-test` + + This test uses Nix to reproducibly execute a QEMU virtual machine which + spawns a full Gnome desktop. It runs `monado-service`, `stardust-xr-server` + `flatland` underneath of Gnome and then attaches `weston-cliptest` to the + `flatland` process running underneath of `stardust-xr-server`, the result is + a screenshot in PNG format that should look like expected. If any process in + this test produces an exit code above 0, the test will fail, graphical bugs + should be visible in the screenshot. An example of the result is below. + + ###### Result + + ![image](https://github.com/StardustXR/server/assets/26458780/e21cd039-2528-4568-b20a-ce4abfab6d9b) + +##### Everything + +`nix flake check` will build every test underneath of the `checks` attribute in the `flake.nix` + diff --git a/flake.lock b/flake.lock index 342e0a5..e1fd9de 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1681971746, - "narHash": "sha256-RAFvwcAds5WeCSUYONE+YzR0uKVuLc6BIeN4WFix4f0=", + "lastModified": 1683786056, + "narHash": "sha256-Wrz/X9D0t8akhvEGj5a93xgpxI3vAcdPGcwn6tKHooc=", "owner": "nix-community", "repo": "fenix", - "rev": "3a0b59a2ea946a533c62ac417596835779087f0e", + "rev": "5816c7bbcc385d2e65877631497df3f7d66b354a", "type": "github" }, "original": { @@ -21,13 +21,276 @@ "type": "github" } }, + "fenix_2": { + "inputs": { + "nixpkgs": [ + "flatland", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src_2" + }, + "locked": { + "lastModified": 1678775037, + "narHash": "sha256-chx0tWnXKpcayPkPY3Qh+2hNwptvX8XE3o+fYZ+GNzg=", + "owner": "nix-community", + "repo": "fenix", + "rev": "ee59e1c769657b1e27e608f8b981fa8f6b715583", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1678379998, + "narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c13d60b89adea3dc20704c045ec4d50dd964d447", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "hercules-ci-effects", + "hercules-ci-agent", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1678379998, + "narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c13d60b89adea3dc20704c045ec4d50dd964d447", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flatland": { + "inputs": { + "fenix": "fenix_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1683766358, + "narHash": "sha256-wX1Lpj95kkHUZAloB1fGs+ixaRycaOJq4F77+HvaJCQ=", + "owner": "StardustXR", + "repo": "flatland", + "rev": "24613a496841bdf38e5f136608d5295860a75fce", + "type": "github" + }, + "original": { + "owner": "StardustXR", + "repo": "flatland", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "hercules-ci-effects", + "hercules-ci-agent", + "pre-commit-hooks-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "haskell-flake": { + "locked": { + "lastModified": 1678138103, + "narHash": "sha256-D0lao82bV3t2gEFjHiU6RN233t+1MnkQV+bq8MEu2ic=", + "owner": "hercules-ci", + "repo": "haskell-flake", + "rev": "1e1660e6dd00838ba73bc7952e6e73be67da18d1", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "ref": "0.1-extraLibraries", + "repo": "haskell-flake", + "type": "github" + } + }, + "hercules-ci-agent": { + "inputs": { + "flake-parts": "flake-parts_2", + "haskell-flake": "haskell-flake", + "nix-darwin": "nix-darwin", + "nixpkgs": "nixpkgs_2", + "pre-commit-hooks-nix": "pre-commit-hooks-nix" + }, + "locked": { + "lastModified": 1678446614, + "narHash": "sha256-Z6Gsba5ahn/N0QlF0vJfIEfnZgCs4qr1IZtXAqjbE7s=", + "owner": "hercules-ci", + "repo": "hercules-ci-agent", + "rev": "0b90d1a87c117a5861785cb85833dd1c9df0b6ef", + "type": "github" + }, + "original": { + "id": "hercules-ci-agent", + "type": "indirect" + } + }, + "hercules-ci-effects": { + "inputs": { + "flake-parts": "flake-parts", + "hercules-ci-agent": "hercules-ci-agent", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1681898675, + "narHash": "sha256-nIJ7CAdiHv4i1no/VgDoeTJLzbLYwu5+/Ycoyzn0S78=", + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "rev": "15ff4f63e5f28070391a5b09a82f6d5c6cc5c9d0", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "type": "github" + } + }, + "nix-darwin": { + "inputs": { + "nixpkgs": [ + "hercules-ci-effects", + "hercules-ci-agent", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1673295039, + "narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=", + "owner": "LnL7", + "repo": "nix-darwin", + "rev": "87b9d090ad39b25b2400029c64825fc2a8868943", + "type": "github" + }, + "original": { + "owner": "LnL7", + "repo": "nix-darwin", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1682879489, - "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=", + "lastModified": 1678703398, + "narHash": "sha256-Y1mW3dBsoWLHpYm+UIHb5VZ7rx024NNHaF16oZBx++o=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0", + "rev": "67f26c1cfc5d5783628231e776a81c1ade623e0b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1678375444, + "narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1673800717, + "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1678293141, + "narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d", "type": "github" }, "original": { @@ -37,20 +300,96 @@ "type": "github" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1678891326, + "narHash": "sha256-cjgrjKx7y+hO9I8O2b6QvBaTt9w7Xhk/5hsnJYTUb2I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1544ef240132d4357d9a39a40c8e6afd1678b052", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1683408522, + "narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks-nix": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "hercules-ci-effects", + "hercules-ci-agent", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1678376203, + "narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "1a20b9708962096ec2481eeb2ddca29ed747770a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { "fenix": "fenix", - "nixpkgs": "nixpkgs" + "flatland": "flatland", + "hercules-ci-effects": "hercules-ci-effects", + "nixpkgs": "nixpkgs_4" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1681913140, - "narHash": "sha256-+m+eXpFa5c8nQ4hAZyshIjovKzhMfd5Eo6jQDk6eeig=", + "lastModified": 1683653808, + "narHash": "sha256-GiKwJySG/YCPIKwz9wSm9fJa5e4CU3GvTYAKZzjBhFo=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "2400b36a2ed40f68a26473f69ac208ba10d98af9", + "rev": "b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "rust-analyzer-src_2": { + "flake": false, + "locked": { + "lastModified": 1678695923, + "narHash": "sha256-rDhiiU8P6sf6mgj5IKgCuTRN9uYeqWr6xl4XLkKnMWg=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "95497533524537b1cc7a2870ce94b0b14503be8b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 43438b5..1f650f0 100644 --- a/flake.nix +++ b/flake.nix @@ -1,11 +1,22 @@ { + nixConfig = { + extra-substituters = [ "https://stardustxr.cachix.org" ]; + extra-trusted-public-keys = [ "stardustxr.cachix.org-1:mWSn8Ap2RLsIWT/8gsj+VfbJB6xoOkPaZpbjO+r9HBo=" ]; + }; + # 22.11 does not include PR #218472, hence we use the unstable version inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; + # Since we do not have a monorepo, we have to fetch Flatland in order to use + # it to create VM Tests + inputs.flatland.url = "github:StardustXR/flatland"; + inputs.fenix.url = github:nix-community/fenix; inputs.fenix.inputs.nixpkgs.follows = "nixpkgs"; - outputs = { self, nixpkgs, fenix }: + inputs.hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects"; + + outputs = { self, nixpkgs, fenix, hercules-ci-effects, flatland, ... }: let name = "server"; pkgs = system: import nixpkgs { @@ -27,7 +38,19 @@ rustc = toolchain; }).buildRustPackage rec { pname = "stardust-xr-${name}"; - src = ./.; + src = builtins.path { + name = "stardust-xr-source"; + path = toString ./.; + filter = path: type: + nixpkgs.lib.all + (n: builtins.baseNameOf path != n) + [ + "flake.nix" + "flake.lock" + "nix" + "README.md" + ]; + }; # ---- START package specific settings ---- version = "0.10.2"; @@ -70,7 +93,41 @@ packages."x86_64-linux".default = package (pkgs "x86_64-linux"); packages."aarch64-linux".default = package (pkgs "aarch64-linux"); + packages."x86_64-linux".gnome-graphical-test = self.checks.x86_64-linux.gnome-graphical-test; + packages."aarch64-linux".gnome-graphical-test = self.checks.aarch64-linux.gnome-graphical-test; + + checks."x86_64-linux".gnome-graphical-test = (pkgs "x86_64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "x86_64-linux"); inherit self; }); + checks."aarch64-linux".gnome-graphical-test = (pkgs "aarch64-linux").nixosTest (import ./nix/gnome-graphical-test.nix { pkgs = (pkgs "aarch64-linux"); inherit self; }); + devShells."x86_64-linux".default = shell (pkgs "x86_64-linux"); devShells."aarch64-linux".default = shell (pkgs "aarch64-linux"); + + herculesCI.ciSystems = [ "x86_64-linux" ]; + + effects = let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + hci-effects = hercules-ci-effects.lib.withPkgs pkgs; + in { branch, rev, ... }: { + gnome-graphical-test = hci-effects.mkEffect { + secretsMap."stardustxrDiscord" = "stardustxrDiscord"; + secretsMap."stardustxrIpfs" = "stardustxrIpfs"; + effectScript = '' + readSecretString stardustxrDiscord .webhook > .webhook + readSecretString stardustxrIpfs .basicauth > .basicauth + set -x + export RESPONSE=$(curl -H @.basicauth -F file=@${self.packages."x86_64-linux".gnome-graphical-test}/screen.png https://ipfs-api.stardustxr.org/api/v0/add) + export CID=$(echo "$RESPONSE" | ${pkgs.jq}/bin/jq -r .Hash) + set +x + export ADDRESS="https://ipfs.stardustxr.org/ipfs/$CID" + ${pkgs.discord-sh}/bin/discord.sh \ + --description "\`stardustxr/server\` has been modified, here's how it renders \`weston-cliptest\` on \`flatland\` via \`monado-service\` inside of the \`gnome-graphical-test\`" \ + --field "Branch;${branch}" \ + --field "Commit ID;${rev}" \ + --field "Flatland Revision;${flatland.rev}" \ + --field "Reproducer;\`nix build github:stardustxr/server/${rev}#gnome-graphical-test\`" \ + --image "$ADDRESS" + ''; + }; + }; }; } diff --git a/nix/gnome-graphical-test.nix b/nix/gnome-graphical-test.nix new file mode 100644 index 0000000..95b7235 --- /dev/null +++ b/nix/gnome-graphical-test.nix @@ -0,0 +1,155 @@ +{ pkgs, lib ? pkgs.lib, self, ... }: +# Some code is copy-pasted from https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/gnome.nix +# TODO: make this less boiler-platey and make a function like mkGnomeTest that does all this and upstream it to nixpkgs +{ + name = "stardust-xr-server-gnome-vmtest"; + meta = with lib; { + maintainers = [ maintainers.matthewcroughan ]; + }; + nodes.machine = { ... }: { + imports = [ "${pkgs.path}/nixos/tests/common/user-account.nix" ]; + virtualisation.qemu.options = [ + "-device virtio-gpu-pci" + ]; + environment.systemPackages = [ pkgs.monado ]; + services.xserver = { + enable = true; + desktopManager.gnome = { + enable = true; + debug = true; + # Set a nice desktop background that is pleasing to the eyes :3 + extraGSettingsOverrides = '' + [org.gnome.desktop.background] + picture-uri='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg' + picture-uri-dark='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg' + ''; + }; + displayManager = { + gdm = { + enable = true; + debug = true; + }; + autoLogin = { + enable = true; + user = "alice"; + }; + }; + }; + systemd.user.services = { + "monado" = { + after = [ "graphical-session.target" "default.target" "org.gnome.Shell@wayland.service" ]; + environment = { + XRT_COMPOSITOR_FORCE_WAYLAND = "1"; + WAYLAND_DISPLAY = "wayland-0"; + }; + serviceConfig = { + ExecStartPre = [ + "${pkgs.writeShellScript "sleep" '' + sleep 3 + ''}" + ]; + ExecStart = let + # stdin disappears in NixOS test driver ( machine.succeed() ), requiring us to specify < /dev/ttyS0 to fake stdin + exec-monado-service = pkgs.writeShellScript "exec-monado-service" "${pkgs.monado}/bin/monado-service < /dev/ttyS0"; + in [ + "${exec-monado-service}" + ]; + }; + }; + "stardust-xr-server" = { + after = [ "monado.service" ]; + serviceConfig = { + Type = "notify"; + NotifyAccess = "all"; + ExecStartPre = [ + "${pkgs.writeShellScript "sleep" '' + sleep 3 + ''}" + ]; + ExecStart = let + notifyReady = pkgs.writeShellScript "notifyReady" "systemd-notify --ready"; + exec-stardust-xr-server = pkgs.writeShellScript "exec-stardust-xr-server" "${self.packages.${pkgs.hostPlatform.system}.default}/bin/stardust-xr-server -e ${notifyReady}"; + in [ + "${exec-stardust-xr-server}" + ]; + }; + }; + "weston-cliptest" = { + after = [ "flatland.service" ]; + environment.WAYLAND_DISPLAY = "wayland-1"; + serviceConfig = { + ExecStart = [ + "${pkgs.weston}/bin/weston-cliptest" + ]; + }; + }; + "flatland" = { + after = [ "stardust-xr-server.service" ]; + serviceConfig = { + ExecStart = [ + "${self.inputs.flatland.packages.${pkgs.hostPlatform.system}.default}/bin/flatland" + ]; + }; + }; + "org.gnome.Shell@wayland" = { + wants = [ "monado.service" "stardust-xr-server.service" "flatland.service" "weston-cliptest.service" ]; + serviceConfig = { + ExecStart = [ + # Clear the list before overriding it. + "" + # Eval API is now internal so Shell needs to run in unsafe mode. + # TODO: improve test driver so that it supports openqa-like manipulation + # that would allow us to drop this mess. + "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode" + ]; + }; + }; + }; + }; + + testScript = { nodes, ... }: let + # Keep line widths somewhat managable + user = nodes.machine.config.users.users.alice; + uid = toString user.uid; + bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus"; + gdbus = "${bus} gdbus"; + su = command: "su ${user.name} -c '${command}'"; + + # Call javascript in gnome shell, returns a tuple (success, output), where + # `success` is true if the dbus call was successful and output is what the + # javascript evaluates to. + eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval"; + + # False when startup is done + startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp"; + in '' + with subtest("Login to GNOME with GDM"): + # wait for gdm to start + machine.wait_for_unit("display-manager.service") + # wait for the wayland server + machine.wait_for_file("/run/user/${uid}/wayland-0") + # wait for alice to be logged in + machine.wait_for_unit("default.target", "${user.name}") + # check that logging in has given the user ownership of devices + assert "alice" in machine.succeed("getfacl -p /dev/snd/timer") + + with subtest("Wait for GNOME Shell"): + # correct output should be (true, 'false') + machine.wait_until_succeeds( + "${startingUp} | grep -q 'true,..false'" + ) + + # To allow monado-service to use < /dev/ttyS0 + machine.succeed("chown alice /dev/ttyS0") + + with subtest("Open Monado and StardustXR"): + # Close the Activities view so that Shell can correctly track the focused window. + machine.send_key("esc") + machine.wait_for_unit("monado.service", "${user.name}") + machine.wait_for_unit("stardust-xr-server.service", "${user.name}") + machine.wait_for_unit("flatland.service", "${user.name}") + machine.wait_for_unit("weston-cliptest.service", "${user.name}") + machine.sleep(3) + machine.screenshot("screen") + ''; +}