Skip to content

Tests don't run in CI on macOS, Windows or Linux, and the CPU/GPU equivalence tests never run in CI (gap 9) #1050

Open

Description

@THEtheChad

Summary

cargo test runs in CI only on FreeBSD. Nothing runs the tests on macOS, Windows or Linux x64, the three platforms people install LightCraft on, so platform-specific breakage reaches main and stays there until someone builds on that platform by hand. The CPU/GPU equivalence tests, the guard that keeps the GPU twin matching the CPU reference, never run in CI on any platform: they skip when there is no GPU adapter, and the code refuses the software adapters CI runners have.

This is gap 9 in docs/gaps.md (beta blocker, 6–12 h). This issue proposes a fix that stays inside the current design and adds no new runner types.

Evidence

What a test run on the three platforms finds today

I ran the workflow below on my fork, on main at 76b5377. All runners were standard GitHub-hosted ones with a cold cache.

main alone main + #990, #997, #1003, #1007, #1018
Linux x86_64, cargo xtask ci fails at clippy (clippy::chunks_exact_to_as_chunks, rustc 1.99, #989 item 4) all 8 steps pass (39 min cold)
macOS arm64, clippy + test clippy as above; lightcraft does not compile (#989 item 1) pass (clippy 3 min, tests 19 min)
Windows x86_64, clippy + test clippy as above 2 new Windows-only failures, below
GPU tests on a software adapter / Metal lavapipe: 16/16 equivalence tests pass lavapipe, WARP and Metal all pass

Two Windows failures that nobody had reported:

  1. clippy, Windows only: method descriptor_copy is never used at crates/catalog/src/lock.rs:140 (-D dead-code). The method is #[cfg(test)], and its only caller is a #[cfg(unix)] test (crates/catalog/src/tests_lock.rs:114-119).
  2. editors::tests::desktop_entries_give_their_program (apps/lightcraft/src/editors.rs:222) fails on Windows: left: None, right: Some("/home/simon/Apps/photocraft-1.2-linux-x86_64.AppImage"). The .desktop parser presumably checks for an absolute path, and /home/... is not absolute on Windows. That run stopped at the first failing test binary, so there may be more. The workflow now uses --no-fail-fast on macOS and Windows so every failure shows.

These two are not fixed here. They belong in their own PRs, the same way the #989 items were split.

The equivalence suite on software adapters

With the opt-in described below, all 134 compared renders (16 tests) stay inside the documented bounds (mean |Δ| < 0.5 LSB, max ≤ 3 LSB). The results match the Apple M4 Pro numbers in docs/gpu-pipeline.md, and no tolerance was changed:

adapter renders max |Δ| worst mean |Δ| equivalence time
lavapipe (Mesa, LLVM 20), local 134 1 LSB (110 renders), 0 (24) 0.0003 19 s
lavapipe, ubuntu-latest 134 1 LSB 0.0003 50 s
WARP (DX12, FXC), windows-latest 134 1 LSB (109), 0 (25) 0.0003 446 s
Metal (paravirtual GPU), macos-latest 16 tests pass (not printed) 29 s

One test assumed more than a WebGPU-sized device. windows_of_a_big_photo_share_one_uploaded_source failed on lavapipe (left: 0, right: 1 uploads). lavapipe's max_storage_buffer_binding_size is 128 MiB, which is the WebGPU default. The test's 24 MP source is 288 MB, so by design (render.rs:619) geometry ran on the CPU and nothing was uploaded. This is not a drift between CPU and GPU. Its sibling, a_window_does_not_keep_the_whole_source_on_the_device, passed on lavapipe without checking anything, for the same reason.

Proposed fix (within the current design)

  1. Test-only opt-in for software adapters. Add LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1, read in Gpu::new. The app never sets it, and the default behaviour does not change: software adapters are still refused. This follows LIGHTCRAFT_GPU_LIMITS, which also exists to reproduce an adapter. A unit test covers the decision (refuse_adapter).
  2. Size the two big-source tests to fit a 128 MiB binding. Use 3200 × 3200 (117 MiB): above the 96 MiB RETAIN_SOURCE_BYTES, so the source is still shared, and within the WebGPU default, so it is uploaded on every adapter. On a real GPU they test the same code path. The first test still catches the Missing a native full resolution preview of pictures #323 regression: a 117 MiB source kept in the stages is more than its 100 MiB bound.
  3. .github/workflows/ci.yml, standard runners only, on every pull request and every push to main, with concurrency cancel-in-progress:
    • Linux x86_64 (ubuntu-latest): cargo xtask ci, the same gate contributors run locally, so new xtask steps are checked automatically. Then the GPU test targets with lavapipe (mesa-vulkan-drivers).
    • macOS arm64 (macos-latest) and Windows x86_64 (windows-latest): clippy and cargo test --workspace --no-fail-fast. These are the steps whose result depends on the platform: cfg(target_os) code and file-system behaviour. fmt, parity, layers, assets and wasm give the same answer everywhere, so they run once, on Linux. Then the GPU test targets on Metal and WARP.
    • The GPU step names the test targets with --workspace (--test equivalence --test fallback ...) so it reuses the workspace build, and fails if anything still prints skipped: no GPU adapter. Without that check, a runner-image change could silently turn the guard off again.
    • Swatinem/rust-cache (the pin release.yml already uses) caches dependencies only, and saves only from main (save-if). Pull requests restore from it, so the 10 GB repository cache holds one entry per job.
    • It uses only the actions and pins already in release.yml (checkout, dtolnay/rust-toolchain, rust-cache). No new third-party actions.
    • xtask already sizes build jobs and test threads by available RAM (ci_jobs). The macOS runner (3 cores, 7 GB) passed with the defaults.
  4. Docs: gap 9 and the CI mentions in ROADMAP.md / docs/target-app-parity.md, plus the new variable in docs/gpu-pipeline.md.

Cost: standard GitHub-hosted runners are free for public repositories. Measured cold-cache durations: Linux 41 min, Windows 28 min, macOS 23 min, all in parallel. Warm runs should be shorter, because dependencies are restored from main's cache.

For the maintainers to decide (not in the PR)

Test plan

  • cargo test -p lightcraft-gpu, default environment: the GPU tests skip as before, and software_adapters_need_the_opt_in passes.
  • LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1 VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json cargo test -p lightcraft-gpu -- --nocapture: all 16 equivalence tests and the fallback, memory and shutdown tests run on llvmpipe and pass.
  • The workflow, run on a fork: the Linux, macOS and Windows jobs and their GPU steps. Results are in the table above.
  • actionlint reports nothing for the workflow file.

Activity

  1. added 5 commits that reference this issue on Oct 11, 2026
  2. THEtheChad commented on Oct 11, 2026

    @THEtheChad
    ContributorAuthor

    Update: the full Windows picture, and where each failure is being fixed

    The first Windows run stopped at the first failing test binary. The workflow now runs cargo test --workspace --no-fail-fast on macOS and Windows. A second fork run (main at 76b5377 + the workflow + #990, #997, #1003, #1007, #1018) shows everything that fails there:

    job result
    Linux x86_64, cargo xtask ci + GPU tests on lavapipe pass (all 8 steps; 16/16 equivalence tests)
    macOS arm64, clippy + tests + GPU tests on Metal pass
    Windows x86_64, clippy fails: dead_code on LibraryLock::descriptor_copy (crates/catalog/src/lock.rs:140)
    Windows x86_64, tests 22 failures in 2 binaries (below)
    Windows x86_64, GPU tests on WARP pass (134 renders, max 1 LSB, mean ≤ 0.0003)

    Windows test failures, all on main before this change (the PR touches no engine or app code):

    #1052 (OneSlips, open) already fixes all of these: the clippy lint, the editors test, the case-insensitive rename / save bugs (#1006) and the separator fixtures. So nothing new needs filing. I confirmed this with a third fork run that also had #1052 merged on top (run 38174552427). All three jobs are green:

    job clippy tests GPU step
    Linux x86_64 cargo xtask ci: all 8 steps, 39 min (in xtask ci) lavapipe: 134 renders, max 1 LSB, mean ≤ 0.0003, suite 46 s; step 2 min
    macOS arm64 pass, 4 min pass, 26 min Metal (paravirtual): 134 renders, max 2 LSB in one case (point colour range overlay, 0.0002% of pixels over 1), the rest ≤ 1, mean ≤ 0.0003, suite 29 s
    Windows x86_64 pass, 5 min pass, 33 min WARP: 134 renders, max 1 LSB, mean ≤ 0.0003, suite 436 s (111 s in an earlier run)

    cargo xtask ci on the same merge also passes all 8 steps locally (Linux, rustc 1.99). #1052 and #997 both change fill_box in crates/pipeline/src/spots.rs, and #1052 and #1007 change the same assertion in tests_repair_saves.rs, so whichever lands second needs a small rebase.

    Change to the workflow since the issue

    The Linux GPU step now runs cargo test -p lightcraft-gpu --tests. Its earlier --workspace --test … form rebuilt about 190 crates after cargo xtask ci (7 min); this form takes about 2 min. macOS and Windows keep the --workspace form, which reuses the test step's build (2–8 s of building).

    Prior art since the issue

    #1083 and #1084 (a nightly macos.yml) were opened and then withdrawn by their author in favour of the open #989 PRs and #901's nightly direction. Per-PR or nightly remains the maintainers' call (see "For the maintainers to decide" in the issue body). The PR keeps the platform jobs per-PR and explains how to move them.

  3. added 5 commits that reference this issue on Oct 11, 2026
  4. francoisgn commented on Oct 16, 2026

    @francoisgn
    Contributor

    Sorry, I missed this issue and the existing CI PRs (#1148, #1159, #941) before opening #2005, which duplicates them. I'll close #2005 in favour of whichever one you pick.

    What's still useful from it: #2004 fixes 3 tests that fail on windows-latest (path separators in the folder-drop and export tests, and a missing wait in the offline test). Any of these CI PRs will hit those failures on Windows.
    Green run on all 3 OS for reference: https://github.com/francoisgn/lightcraft/actions/runs/38873633880

  5. added a commit that references this issue on Oct 26, 2026
    b7e99b3
  6. 6 remaining items

  7. added 13 commits that reference this issue on Oct 27, 2026
  8. AlexChambers commented on Nov 9, 2026

    @AlexChambers
    Contributor

    One more gap to fold in here. The Linux gate runs the tests now, but it has no corpus. This run on main reports corpus: 0 of 462 pinned raws, 0 images, 0 PngSuite files, so every corpus-backed test skips and passes. That includes the per-model checks behind the verified-models count in ROADMAP.md, which CI therefore never exercises.

    A nightly job that runs cargo xtask corpus --download before the tests would cover it without adding time to pull requests.

  9. THEtheChad commented on Nov 11, 2026

    @THEtheChad
    ContributorAuthor

    Agreed. I ran it locally to see what the gap is hiding. With the full pinned corpus (LIGHTCRAFT_CORPUS set, all 460 raws plus the D750 set), lightcraft-raw, -codecs and -engine on main (8ef35987d) had 12 failures, all green in CI because their tests skip without the corpus:

    So corpus-only breakage is already landing within days.

    Size, which decides how a nightly can work:

    • The pinned corpus is 14.1 GB in 462 files, median 24.5 MB. It's essentially one file per model or format variant, so no small subset still covers every verified model.
    • Files up to 30 MB: 307 files, 5.6 GB. Up to 60 MB: 421 files, 10.1 GB.
    • The Actions cache is 10 GB per repository by default and is shared with the cargo caches, so the full corpus can't be cached there.
    • Downloading 14 GB from raw.pixls.us every night isn't fair to a community-run site, so a nightly needs a cache or a mirror either way.
    • Run time is modest: those three crates take 9.5 min in the dev profile on a 32-core machine.

    Options, roughly in order:

    1. A self-hosted runner that keeps the corpus on disk, for a scheduled job only. Full coverage, nothing downloaded after the first run. An infrastructure and security call for maintainers.
    2. A cached subset nightly (e.g. the ≤30 MB files, 5.6 GB) plus the full set weekly or on demand. Partial nightly coverage, and the cache competes with the cargo caches.
    3. A pinned mirror like storytold/photocraft-corpus, which takes the load off raw.pixls.us but still downloads 14 GB per run.

    Whichever is chosen, the job would call the shared gate (it already has command and free-disk-space), and any corpus caching support belongs in craft-common-actions, per the CI rule. I can open that issue there once there's a preferred option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions