From 110d58bea91c87bf1d7a3ea9b9d05bebe5d1d80a Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 11 Oct 2026 10:06:39 +0300 Subject: [PATCH] CI: split the Windows integration tests over two runners; retry flaky GPU tests nextest --partition splits the integration tests in two, so Windows runs three legs: on the Windows runner (EPYC 7763, 2 runs) the integration halves took 9.8 and 10.8 min against 13.8 for one, leaving the unit leg (~12.8) as the longest. A ci nextest profile retries the GPU tests: on WARP, under load, one hit wgpu's poll timeout once in ~40 runs while measuring. A test that passes on retry is reported as FLAKY. Co-Authored-By: Claude Opus 5.5 --- .config/nextest.toml | 7 +++++++ .github/workflows/ci.yml | 32 ++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 2585577ec1..bb2a3881b2 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -13,3 +13,10 @@ gpu = { max-threads = 4 } [[profile.default.overrides]] filter = 'package(photocraft-gpu) | (package(photocraft-ui-egui) & (kind(test) | test(/canvas|gpu|mip|type_tool/)))' test-group = 'gpu' + +# CI (`--profile ci`): the GPU tests run on WARP, the software adapter, on the Windows runner, and +# under load one occasionally hits that same timeout (1 in ~40 runs while measuring #2933). Retry +# them; nextest reports a test that passes on retry as FLAKY, so it stays visible. +[[profile.ci.overrides]] +filter = 'package(photocraft-gpu) | (package(photocraft-ui-egui) & (kind(test) | test(/canvas|gpu|mip|type_tool/)))' +retries = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa3299cca0..337f75ef60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,10 +56,12 @@ jobs: run: | # No macOS on PRs or main: the org's 5 macOS slots are kept for release builds, and # release.yml builds and tests macOS (a failing release is fixed and rebuilt). - # Windows tests run as two legs in parallel (unit tests, integration tests): together they - # took ~28 min on the Windows runner, each leg takes ~18. + # Windows tests run as three legs in parallel: the unit tests, and the integration tests + # split in two (nextest --partition). As one job they took ~28 min on the Windows runner; + # the legs take ~13, ~10 and ~11. linux='{"os":"ubuntu-latest","part":"all"}' - all="[$linux,{\"os\":\"windows-latest\",\"part\":\"unit\"},{\"os\":\"windows-latest\",\"part\":\"integration\"}]" + win() { printf '{"os":"windows-latest","part":"%s"}' "$1"; } + all="[$linux,$(win unit),$(win integration-1),$(win integration-2)]" if [ "$EVENT" != pull_request ] || [ "$FULL" = true ]; then echo "legs=$all" >> "$GITHUB_OUTPUT" echo "web=true" >> "$GITHUB_OUTPUT" @@ -114,11 +116,11 @@ jobs: # Only main saves caches; PRs restore main's. Every PR used to save its own set (up to # 3.8 GB), usable by that PR only, so the repo's 10 GB cache budget kept evicting main's. # A failing test still saves: the cache holds compiled dependencies, not test results. - # The two Windows legs share one key (same dependencies); the integration leg saves it, + # The Windows legs share one key (same dependencies); the integration-2 leg saves it, # because it also runs Clippy and so leaves Clippy's dependency metadata in it. - uses: Swatinem/rust-cache@v2 with: - save-if: ${{ github.ref == 'refs/heads/main' && matrix.part != 'unit' }} + save-if: ${{ github.ref == 'refs/heads/main' && (matrix.part == 'all' || matrix.part == 'integration-2') }} cache-on-failure: true # sccache caches the workspace crates' compiled libraries, which rust-cache leaves out: @@ -156,31 +158,33 @@ jobs: # never run (Clippy's `--all-targets` still checks they compile). nextest skips doctests, so # they get their own step. # - # On Windows the unit leg runs `--lib --bins` and the integration leg `--test '*'` (every - # integration-test target); the feature selection stays the same, so both legs share the - # dependency cache. + # On Windows the unit leg runs `--lib --bins` and the integration legs `--test '*'` (every + # integration-test target), half of the tests each; the feature selection stays the same, so + # all legs share the dependency cache. The `ci` profile retries the GPU tests + # (.config/nextest.toml). - name: Test env: PART: ${{ matrix.part }} run: | case "$PART" in unit) targets=(--lib --bins) ;; - integration) targets=(--test '*') ;; + integration-1) targets=(--test '*' --partition count:1/2) ;; + integration-2) targets=(--test '*' --partition count:2/2) ;; *) targets=(--lib --bins --tests) ;; esac - cargo nextest run --workspace --locked --features heif "${targets[@]}" --no-fail-fast + cargo nextest run --profile ci --workspace --locked --features heif "${targets[@]}" --no-fail-fast shell: bash - name: Doctests - if: matrix.part != 'integration' + if: matrix.part == 'all' || matrix.part == 'unit' run: cargo test --workspace --locked --features heif --doc - name: Clippy - if: matrix.part != 'unit' + if: matrix.part == 'all' || matrix.part == 'integration-2' run: cargo clippy --workspace --all-targets --locked --features heif -- -D warnings - name: Dependency layering - if: matrix.part != 'unit' + if: matrix.part == 'all' || matrix.part == 'integration-2' run: cargo xtask layers # docs/scorecard.md must match its sources (checklists, budgets, baseline, corpus floors, @@ -246,7 +250,7 @@ jobs: env: CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts CRAFT_FONTS_REQUIRED: "1" - run: cargo nextest run --workspace --locked --features heif --lib --bins --tests --no-fail-fast + run: cargo nextest run --profile ci --workspace --locked --features heif --lib --bins --tests --no-fail-fast corpus: # Real-file corpus tests (cargo feature `corpus`), on every PR and on main. The corpora are