Skip to content

Trait-based test lanes: filterable inner loop, and PR CI on a stock runner - #156

Merged
zhongkaifu merged 2 commits into
zhongkaifu:mainfrom
craig-b:chore/test-traits
Aug 18, 2026
Merged

Trait-based test lanes: filterable inner loop, and PR CI on a stock runner#156
zhongkaifu merged 2 commits into
zhongkaifu:mainfrom
craig-b:chore/test-traits

Conversation

@craig-b

@craig-b craig-b commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Implements #153 — plain xUnit traits that make the existing test suite filterable into lanes, plus a minimal PR workflow that runs the environment-independent lane on a stock GitHub-hosted runner.

What's in the diff

  1. Trait tagging (43 files, additive only — no test moves, no behavior change): [Trait] attributes on the existing classes, method-level for the three mixed classes. Membership was determined from each class's actual gate (availability early-returns, model-dir env lookups), not from names — e.g. ConfigFileArgsTests mentions .gguf but is pure-unit and stays untagged.

    Trait Meaning Tests
    Category=Bench benchmark harnesses (11 classes) 39
    Requires=Cuda needs a CUDA device 164
    Requires=Mlx needs the MLX backend 94
    Requires=Models needs real GGUF weights 89

    Untagged = self-contained correctness tests that run anywhere. The taxonomy and lane filters are documented in InferenceWeb.Tests/TestAssemblyConfig.cs.

  2. Lane documentation: a "Test lanes" section in DEVELOPMENT.md (and the zh-cn twin) with the three filter invocations — inner loop, full correctness, bench-only.

  3. PR CI (.github/workflows/pr-unit-tests.yml): runs the inner-loop filter on ubuntu-latest for every PR — no GPU, no model downloads, no secrets — so PRs get a deterministic test signal even when the self-hosted CUDA runner is offline. The managed build already triggers the CPU-only native GGML build by itself; Vulkan is pinned off since tests default to the CPU backend. The engine-comparison benchmark workflow (test-matrix.yml) is untouched.

Verified on a Linux box without CUDA/MLX/model weights, rebased on current main:

Invocation Result
unfiltered dotnet test unchanged — 1,474 passed in ~3m29s
--filter "Category!=Bench" 1,435 passed, ~8 s
inner-loop filter (CI lane) 1,110 passed, ~5 s

One thing this PR deliberately does not do: CUDA/MLX/model-gated tests still silently pass when their prerequisite is missing. Turning those into visible xUnit Skipped counts needs custom fact attributes; happy to follow up separately if you want that.

The suite mixes four populations in one assembly: self-contained
correctness tests, benchmarks, CUDA/MLX-gated tests, and tests needing
real GGUF weights — the gated ones silently pass when their
prerequisite is absent, and the benchmarks flake under full-suite load.
Class-level (method-level for the three mixed classes) traits make each
population selectable:

  Category=Bench    39 benchmark tests across 11 classes
  Requires=Cuda     164 tests needing a CUDA device
  Requires=Mlx      94 tests needing the MLX backend
  Requires=Models   89 tests needing real weights (TS_TEST_MODEL_DIR etc.)

Membership was determined from each class's actual gate (availability
early-returns, model-dir env lookups), not names: e.g.
ConfigFileArgsTests and ServerOptionsBuilderTests mention .gguf but are
pure-unit and stay untagged; SafetensorsReaderTests, WanDirectOpsTests,
and Gemma4PromptRenderReproTests are mixed and tagged per method.
ToolFunctionParserBenchmark asserts correctness rather than timing, but
its measurement iterations are pure wall clock for the inner loop and
its numbers only mean something on a quiet box, so it rides the Bench
lane with the other harnesses.

Verified on a machine without CUDA/MLX/model weights: unfiltered run
unchanged (1474 passed, 3m29s); Category!=Bench = 1435 passed in 8s
(the benches carry ~95% of the suite's wall clock); the inner-loop lane
(no bench/models/cuda/mlx) = 1110 passed in 5s. Lane filters documented
in TestAssemblyConfig.cs.
DEVELOPMENT.md (and the zh-cn twin) gets a Test lanes section under
Unit tests showing the three filter invocations: the inner loop
(environment-independent correctness), full correctness (everything but
benchmarks), and the bench lane to run deliberately on quiet hardware.

pr-unit-tests.yml runs the inner-loop lane on ubuntu-latest for every
PR: no GPU, no model downloads, no secrets — a deterministic test
signal that doesn't depend on the self-hosted CUDA runner being online.
The managed build triggers the CPU-only native GGML build itself;
Vulkan is pinned off since tests default to the CPU backend. The
engine-comparison benchmark workflow is untouched.
@zhongkaifu
zhongkaifu merged commit 14d964c into zhongkaifu:main Aug 18, 2026
1 of 3 checks passed
@zhongkaifu

Copy link
Copy Markdown
Owner

@craig-b Thanks for this update. It really makes the entire test process be better managed. Merged it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants