From d16c21b63f6ff1c22491bb3961b51cf64ebece89 Mon Sep 17 00:00:00 2001 From: Thang Nguyen Date: Sun, 23 Aug 2026 15:26:50 +0700 Subject: [PATCH] ci: split the suite into parallel jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One sequential job took ~9m45s, and the two heaviest steps do not depend on each other at all: the yourssh_rdp cargo tests (165s) and the app suite (176s). Nothing under app/test loads the native bridge — verified by hiding assets/native and running the RDP/VNC model and workspace tests, which still pass — so the app work never needed to queue behind Rust. Split into an `app` job and a `bridge` matrix job per crate. Each crate keeps its cargo tests, build.sh and Dart tests together: both cargo invocations use --release, so the library build reuses the test build's compilation, and splitting them would compile the IronRDP / vnc-rs tree twice. fail-fast is off so a broken RDP crate does not hide a broken VNC crate, and rust-cache is keyed per crate workspace. Wall clock becomes the longest job (~5 min, the RDP crate) instead of the sum. It costs two extra Flutter/toolchain setups in compute — cheaper waiting, more machine time. master's branch protection requires a status check literally named "test", so the split keeps a job with that name: an aggregate gate that needs both others and fails unless both succeeded. It runs with `always()` — otherwise a failure upstream would skip it and leave the required check pending forever rather than red. --- .github/workflows/pr-test.yml | 80 +++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index d5623a68..ea421155 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -16,7 +16,9 @@ concurrency: cancel-in-progress: true jobs: - test: + # The app suite needs no Rust at all: nothing under app/test loads the native + # bridge, so this runs beside the crate jobs instead of behind them. + app: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -25,28 +27,6 @@ jobs: with: channel: stable - - uses: dtolnay/rust-toolchain@stable - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - packages/yourssh_rdp/rust - packages/yourssh_vnc/rust - - # --release so the test build and build.sh's `cargo build --release` - # share one compiled profile instead of compiling the IronRDP tree twice. - - name: Rust tests (yourssh_rdp) - run: cargo test --release --manifest-path packages/yourssh_rdp/rust/Cargo.toml - - - name: Rust tests (yourssh_vnc) - run: cargo test --release --manifest-path packages/yourssh_vnc/rust/Cargo.toml - - - name: Build RDP native library for Dart tests - run: bash packages/yourssh_rdp/build.sh - - - name: Build VNC native library for Dart tests - run: bash packages/yourssh_vnc/build.sh - - name: Install Linux build dependencies run: | sudo apt-get update @@ -68,10 +48,54 @@ jobs: working-directory: app run: flutter test - - name: Dart tests (yourssh_rdp) - working-directory: packages/yourssh_rdp - run: flutter test + # One job per Rust crate. The cargo tests, build.sh and the Dart bridge tests + # stay together on purpose: both cargo invocations use --release, so the + # library build reuses the test build's compilation. Splitting them across jobs + # would compile the IronRDP / vnc-rs tree twice. + bridge: + name: bridge (${{ matrix.crate }}) + runs-on: ubuntu-latest + strategy: + # A broken RDP crate should not hide a broken VNC crate. + fail-fast: false + matrix: + crate: [yourssh_rdp, yourssh_vnc] + steps: + - uses: actions/checkout@v7 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: packages/${{ matrix.crate }}/rust - - name: Dart tests (yourssh_vnc) - working-directory: packages/yourssh_vnc + - name: Rust tests + run: cargo test --release --manifest-path packages/${{ matrix.crate }}/rust/Cargo.toml + + - name: Build native library for Dart tests + run: bash packages/${{ matrix.crate }}/build.sh + + - name: Dart tests + working-directory: packages/${{ matrix.crate }} run: flutter test + + # Aggregate gate. master's branch protection requires a check literally named + # "test", so that name has to survive the split — this job carries it, and it + # is red unless every job above is green. `always()` matters: without it the + # job would be skipped when something fails, leaving the required check pending + # forever instead of failing. + test: + name: test + needs: [app, bridge] + if: always() + runs-on: ubuntu-latest + steps: + - name: Check job results + run: | + echo "app=${{ needs.app.result }} bridge=${{ needs.bridge.result }}" + [ "${{ needs.app.result }}" = "success" ] || exit 1 + [ "${{ needs.bridge.result }}" = "success" ] || exit 1