From 5f9cf8c7f56207eb3de77e883bedb709096ea6ab Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 6 Nov 2026 20:14:14 -0500 Subject: [PATCH] add infra for e2e testing --- flake.lock | 194 ++++++++++++++++++++++++++++++++++++++-- flake.nix | 2 + linux/nix/build_env.nix | 30 +++++++ linux/nix/default.nix | 23 +++++ linux/nix/devshell.nix | 43 +++------ linux/nix/e2e.sh | 77 ++++++++++++++++ pubspec.lock | 37 +++++++- pubspec.yaml | 6 ++ 8 files changed, 371 insertions(+), 41 deletions(-) create mode 100644 linux/nix/build_env.nix create mode 100755 linux/nix/e2e.sh diff --git a/flake.lock b/flake.lock index db5f957..6508ef8 100644 --- a/flake.lock +++ b/flake.lock @@ -1,9 +1,121 @@ { "nodes": { + "advisory-db": { + "flake": false, + "locked": { + "lastModified": 1789329924, + "narHash": "sha256-mykGMFabQ7yP6G373M3VHkrac9PJPKJstrhQz2D/Rdc=", + "owner": "rustsec", + "repo": "advisory-db", + "rev": "455fd4bac659b5f1fca3810661c2d8b3c25dad05", + "type": "github" + }, + "original": { + "owner": "rustsec", + "repo": "advisory-db", + "type": "github" + } + }, + "continuwuity": { + "inputs": { + "advisory-db": "advisory-db", + "crane": "crane", + "fenix": "fenix", + "flake-compat": "flake-compat", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1794004794, + "narHash": "sha256-fNmKraRLE963JuV3K4giays4KcOVCGpjvFuEG2E0hmE=", + "ref": "refs/heads/quad/fix/latest-event-in-thread-aggregation", + "rev": "35813f52748c31b0b10cc72e69dabef6a838c46a", + "revCount": 8067, + "type": "git", + "url": "file:///home/quadradical/Documents/Code/continuwuity" + }, + "original": { + "type": "git", + "url": "file:///home/quadradical/Documents/Code/continuwuity" + } + }, + "crane": { + "locked": { + "lastModified": 1788465171, + "narHash": "sha256-Y1/TTVXjYXGF068IThQH9fPSZ0SIE74PABlUxnWTUH0=", + "owner": "ipetkov", + "repo": "crane", + "rev": "eb35abda9f232cc6610b1d1e3200d15c49b7ac54", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "continuwuity", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1789298866, + "narHash": "sha256-6eaV5+MdWy1tAlN4HAFr64pTnYc9jiKGXBsu18cyVjE=", + "owner": "nix-community", + "repo": "fenix", + "rev": "1fc6f36a23fa16e3740987957266f6e4b915c3cd", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "edolstra", + "ref": "master", + "repo": "flake-compat", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" }, + "locked": { + "lastModified": 1788450739, + "narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, "locked": { "lastModified": 1793576659, "narHash": "sha256-V1h85lUaJBQoHFrB2vpXZOE0Ju1MYSnIpsehoVZYlDo=", @@ -39,7 +151,7 @@ "nix2flatpak": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs_2" }, "locked": { "lastModified": 1791751882, @@ -57,11 +169,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1773389992, - "narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=", + "lastModified": 1789149629, + "narHash": "sha256-H6GwaZzZf+4npqv0tph94w9tZddSjFjmQrVsW0z78uk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c06b4ae3d6599a672a6210b7021d699c351eebda", + "rev": "eaad089433ca2bb662274377d33df3d0e51ef28b", "type": "github" }, "original": { @@ -72,6 +184,21 @@ } }, "nixpkgs-lib": { + "locked": { + "lastModified": 1788057806, + "narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_2": { "locked": { "lastModified": 1793504054, "narHash": "sha256-ThkfL3D/FhCiZFc9+3BtxwuMmpuK+/bGtXCSo1+yVew=", @@ -87,6 +214,22 @@ } }, "nixpkgs_2": { + "locked": { + "lastModified": 1773389992, + "narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c06b4ae3d6599a672a6210b7021d699c351eebda", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1793540411, "narHash": "sha256-fiPJ88izTVucjeo2EQ3jk4aWmLg03I1D4vxnX2QjEgE=", @@ -104,9 +247,27 @@ }, "root": { "inputs": { - "flake-parts": "flake-parts", + "continuwuity": "continuwuity", + "flake-parts": "flake-parts_2", "nix2flatpak": "nix2flatpak", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_3" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1789128286, + "narHash": "sha256-KRxxgHiKIBp7R2n41IJuE5K4IUiLOpg47EEi3AxuSp8=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "6aeeb8cf02741e5da07a1615a05faa506cecb21a", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" } }, "systems": { @@ -123,6 +284,27 @@ "repo": "default", "type": "github" } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "continuwuity", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1786901030, + "narHash": "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "27b3b12a8e6375f28ebe122f07d230ca5459bbfa", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index e4cae8a..2074aa6 100644 --- a/flake.nix +++ b/flake.nix @@ -6,6 +6,8 @@ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; nix2flatpak.url = "github:neobrain/nix2flatpak"; + # TEMPORARY: the e2e tests against a local Continuwuity checkout + continuwuity.url = "git+file:///home/quadradical/Documents/Code/continuwuity"; }; outputs = diff --git a/linux/nix/build_env.nix b/linux/nix/build_env.nix new file mode 100644 index 0000000..cac5fbd --- /dev/null +++ b/linux/nix/build_env.nix @@ -0,0 +1,30 @@ +{ pkgs, lib }: +# What building Nexus needs, shared by the dev shell and the e2e tests +{ + packages = + with pkgs; + [ + stdenv.cc + go + git + + libGL + (flutter.override { + extraPkgConfigPackages = [ + mpv-unwrapped + libass + ]; + }) + ] + ++ lib.optionals pkgs.stdenv.isLinux [ + wayland + # nativeapi includes XInput2.h, which needs these, but xi.pc only lists them privately + libxext + libxfixes + (callPackage ./pkg/hook_pkg_config.nix { }) + ]; + + env = lib.optionalAttrs pkgs.stdenv.isLinux { + LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; + }; +} diff --git a/linux/nix/default.nix b/linux/nix/default.nix index 3c0a301..3d2f7b2 100644 --- a/linux/nix/default.nix +++ b/linux/nix/default.nix @@ -54,6 +54,29 @@ }; devShells.default = pkgs.callPackage ./devshell.nix { }; + + apps.e2e = + let + buildEnv = import ./build_env.nix { inherit pkgs lib; }; + in + { + type = "app"; + program = lib.getExe ( + pkgs.writeShellApplication { + name = "nexus-e2e"; + runtimeInputs = + buildEnv.packages + ++ (with pkgs; [ + # TEMPORARY: matrix-continuwuity, once the local fixes are released + inputs.continuwuity.packages.${system}.default + curl + dbus + ]); + runtimeEnv = buildEnv.env; + text = builtins.readFile ./e2e.sh; + } + ); + }; }; flake.nixosModules.default = import ./module.nix self; diff --git a/linux/nix/devshell.nix b/linux/nix/devshell.nix index f4bf1c6..cc2223e 100644 --- a/linux/nix/devshell.nix +++ b/linux/nix/devshell.nix @@ -20,41 +20,20 @@ let includeNDK = true; ndkVersions = [ "28.2.13676358" ]; }; + buildEnv = import ./build_env.nix { inherit pkgs lib; }; in pkgs.mkShell { - packages = - with pkgs; - [ - go - git + packages = buildEnv.packages ++ [ android.platform-tools ]; - libGL - (flutter.override { - extraPkgConfigPackages = [ - mpv-unwrapped - libass - ]; - }) - android.platform-tools - ] - ++ lib.optionals pkgs.stdenv.isLinux [ - wayland - # nativeapi includes XInput2.h, which needs these, but xi.pc only lists them privately - libxext - libxfixes - (callPackage ./pkg/hook_pkg_config.nix { }) - ]; + env = + rec { + LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ]; - env = rec { - LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ]; + ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk"; + ANDROID_SDK_ROOT = ANDROID_HOME; + JAVA_HOME = pkgs.jdk21; - ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk"; - ANDROID_SDK_ROOT = ANDROID_HOME; - JAVA_HOME = pkgs.jdk21; - - GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2"; - } - // lib.optionalAttrs pkgs.stdenv.isLinux { - LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; - }; + GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2"; + } + // buildEnv.env; } diff --git a/linux/nix/e2e.sh b/linux/nix/e2e.sh new file mode 100755 index 0000000..e57e812 --- /dev/null +++ b/linux/nix/e2e.sh @@ -0,0 +1,77 @@ +# shellcheck shell=bash + +files=() +flutter_args=() +for arg in "$@"; do + if [[ "$arg" == *.dart ]]; then files+=("$arg"); else flutter_args+=("$arg"); fi +done +if [[ ${#files[@]} -eq 0 ]]; then files=(integration_test/*_test.dart); fi + +homeserver="http://localhost:8448" + +if curl -s -o /dev/null "$homeserver"; then + echo "Something is already using port 8448, like another e2e run." >&2 + exit 1 +fi + +work="$(mktemp -d)" +# shellcheck disable=SC2329 # Run by the trap +cleanup() { + [[ -n "${server_pid:-}" ]] && kill "$server_pid" 2>/dev/null && wait "$server_pid" 2>/dev/null + rm -rf "$work" +} +trap cleanup EXIT + +mkdir -p "$work/db" +cat > "$work/continuwuity.toml" < "$work/continuwuity.log" 2>&1 & +server_pid=$! + +all_can_log_in() { + for user in "${users[@]}"; do + curl -sf -XPOST "$homeserver/_matrix/client/v3/login" \ + -d "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"$user\"},\"password\":\"${user}pass\"}" \ + > /dev/null || return 1 + done +} + +# Wait until login succeeds, otherwise you might try to log in before the account is created +attempts=60 +until all_can_log_in; do + kill -0 "$server_pid" 2>/dev/null || { cat "$work/continuwuity.log"; exit 1; } + (( --attempts )) || { echo "Test users weren't created" >&2; exit 1; } + sleep 0.5 +done + +GOCACHE="$(go env GOCACHE)" +export GOCACHE + +status=0 +for file in "${files[@]}"; do + name="$(basename "$file" .dart)" + mkdir -p "$work/$name/data" "$work/$name/cache" + XDG_DATA_HOME="$work/$name/data" XDG_CACHE_HOME="$work/$name/cache" \ + dbus-run-session -- \ + flutter test "$file" -d linux \ + --dart-define="NEXUS_E2E_HOMESERVER=$homeserver" "${flutter_args[@]}" || + status=$? +done +exit "$status" diff --git a/pubspec.lock b/pubspec.lock index 9ea2664..c4ccd9b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -58,7 +58,7 @@ packages: source: hosted version: "1.0.4" app_links_platform_interface: - dependency: transitive + dependency: "direct dev" description: name: app_links_platform_interface sha256: "7546f09a6e93f4a2df2fe2bd40a5c6c64310ac461b036d82b43033be7a59f809" @@ -471,6 +471,11 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.1" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_file_dialog: dependency: "direct main" description: @@ -581,7 +586,7 @@ packages: source: hosted version: "2.3.2" flutter_test: - dependency: transitive + dependency: "direct dev" description: flutter source: sdk version: "0.0.0" @@ -623,6 +628,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" geoclue: dependency: transitive description: @@ -839,6 +849,11 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -1552,6 +1567,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" synchronized: dependency: transitive description: @@ -1715,7 +1738,7 @@ packages: source: hosted version: "3.2.6" url_launcher_platform_interface: - dependency: transitive + dependency: "direct dev" description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" @@ -1843,6 +1866,14 @@ packages: url: "https://github.com/DavidClaudeAI/webcrypto.dart" source: git version: "0.6.2-wip" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "28b82ec894fed45dd71c23ba62d1af973ed97dd59a4f5790a4d38b0b13e5657e" + url: "https://pub.dev" + source: hosted + version: "3.2.0" webpush_encryption: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a1dbdf5..145a55c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,6 +122,12 @@ dependencies: latlong2: 0.10.1 geolocator: 14.1.2 dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + app_links_platform_interface: 2.0.4 + url_launcher_platform_interface: 2.3.2 build_runner: 2.17.1 flutter_lints: 6.0.0 freezed: 4.0.3