Skip to content

Commit 1c18196

Browse files
authored
Migrate test infrastructure to Pkg workspaces (#262)
1 parent e912dd9 commit 1c18196

9 files changed

Lines changed: 230 additions & 40 deletions

File tree

.github/dependabot.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ updates:
55
schedule:
66
interval: "weekly"
77
- package-ecosystem: "julia"
8-
directory: "/"
8+
directories: # workspace members each need their own entry
9+
- "/"
10+
- "/test"
11+
- "/docs"
912
schedule:
1013
interval: "weekly"
1114
groups: # uncomment to group all julia package updates into a single PR

Project.toml

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ uuid = "6c742aac-3347-4629-af66-fc926824e5e4"
33
version = "0.6.9"
44
authors = ["Jutho Haegeman <jutho.haegeman@ugent.be>, Lukas Devos, Katharine Hyatt and contributors"]
55

6+
[workspace]
7+
projects = ["test", "docs"]
8+
69
[deps]
710
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
811
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
@@ -27,41 +30,12 @@ MatrixAlgebraKitMooncakeExt = "Mooncake"
2730

2831
[compat]
2932
AMDGPU = "2"
30-
Aqua = "0.6, 0.7, 0.8"
3133
CUDA = "6"
3234
ChainRulesCore = "1"
33-
ChainRulesTestUtils = "1"
3435
Enzyme = "0.13.148"
35-
EnzymeTestUtils = "0.2.5"
3636
GenericLinearAlgebra = "0.3.19, 0.4"
3737
GenericSchur = "0.5.6"
3838
LinearAlgebra = "1"
3939
PrecompileTools = "1"
4040
Mooncake = "0.5.27"
41-
ParallelTestRunner = "2"
42-
Random = "1"
43-
SafeTestsets = "0.1"
44-
StableRNGs = "1"
45-
Test = "1"
46-
TestExtras = "0.3.2"
47-
Zygote = "0.7"
4841
julia = "1.10"
49-
50-
[extras]
51-
AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e"
52-
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
53-
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
54-
ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a"
55-
Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
56-
EnzymeTestUtils = "12d8515a-0907-448a-8884-5fe00fdf1c5a"
57-
Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6"
58-
ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc"
59-
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
60-
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
61-
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
62-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
63-
TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a"
64-
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
65-
66-
[targets]
67-
test = ["Aqua", "SafeTestsets", "Test", "TestExtras", "ChainRulesCore", "ChainRulesTestUtils", "Random", "StableRNGs", "Zygote", "CUDA", "AMDGPU", "GenericLinearAlgebra", "GenericSchur", "Mooncake", "Enzyme", "EnzymeTestUtils", "ParallelTestRunner"]

docs/Project.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
name = "MatrixAlgebraKitDocs"
2+
13
[deps]
24
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
35
MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4"
6+
7+
[sources]
8+
MatrixAlgebraKit = {path = ".."}
9+
10+
[compat]
11+
Documenter = "1"

docs/make.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
if Base.active_project() != joinpath(@__DIR__, "Project.toml")
33
using Pkg
44
Pkg.activate(@__DIR__)
5-
Pkg.develop(PackageSpec(; path = joinpath(@__DIR__, "..")))
65
Pkg.resolve()
76
Pkg.instantiate()
87
end

test/Project.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name = "MatrixAlgebraKitTests"
2+
3+
[deps]
4+
AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e"
5+
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
6+
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
7+
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
8+
ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a"
9+
Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
10+
EnzymeTestUtils = "12d8515a-0907-448a-8884-5fe00fdf1c5a"
11+
GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a"
12+
GenericSchur = "c145ed77-6b09-5dd9-b285-bf645a82121e"
13+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
14+
MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4"
15+
Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6"
16+
ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc"
17+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
18+
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
19+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
20+
TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a"
21+
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
22+
23+
[sources]
24+
MatrixAlgebraKit = {path = ".."}
25+
26+
[compat]
27+
Aqua = "0.6, 0.7, 0.8"
28+
ChainRulesTestUtils = "1"
29+
EnzymeTestUtils = "0.2.5"
30+
ParallelTestRunner = "2"
31+
Random = "1"
32+
StableRNGs = "1"
33+
Test = "1"
34+
TestExtras = "0.3.2"
35+
Zygote = "0.7"

test/README.md

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# MatrixAlgebraKit.jl test suite
2+
3+
Tests are driven by [ParallelTestRunner.jl](https://github.com/JuliaTesting/ParallelTestRunner.jl).
4+
Every `.jl` file under `test/` is auto-discovered and executed in its own worker process, so test
5+
files share no state and must be self-contained.
6+
7+
The test environment is a [Pkg workspace](https://pkgdocs.julialang.org/dev/workspaces/) member:
8+
`test/Project.toml` declares the test dependencies and resolves the parent package through
9+
`[sources]`, while the whole workspace shares a single `Manifest.toml` at the repository root.
10+
11+
## Running tests
12+
13+
```julia
14+
# Standard — works on all supported Julia versions
15+
using Pkg; Pkg.test()
16+
17+
# With arguments (see "Selecting tests" below)
18+
using Pkg; Pkg.test("MatrixAlgebraKit"; test_args = ["decompositions", "--fast"])
19+
```
20+
21+
```bash
22+
# Direct invocation — requires Julia 1.12+ (workspace support)
23+
julia --project=test test/runtests.jl
24+
```
25+
26+
The direct form is the convenient one for local iteration: it reuses the already-instantiated
27+
workspace environment instead of building a fresh temporary one on every run.
28+
29+
## Selecting tests
30+
31+
Each discovered test is keyed by its path relative to `test/`, with the `.jl` extension stripped —
32+
for example `decompositions/svd`, `common/truncate`, `mooncake/qr`. Positional arguments are matched
33+
against those keys with `startswith`, so a directory name selects a whole group and a longer path
34+
selects a single file:
35+
36+
```bash
37+
julia --project=test test/runtests.jl --list # print all discovered test keys
38+
39+
julia --project=test test/runtests.jl decompositions # one group
40+
julia --project=test test/runtests.jl decompositions/svd # one file
41+
julia --project=test test/runtests.jl mooncake enzyme # union of several selectors
42+
```
43+
44+
Available flags:
45+
46+
| Flag | Effect |
47+
|------|--------|
48+
| `--list` | List all discovered tests and exit |
49+
| `--fast` | Reduced test matrix (see below) |
50+
| `--jobs=N` | Use `N` worker processes (default: derived from CPU count and available memory) |
51+
| `--verbose` | More detailed progress output |
52+
| `--quickfail` | Abort the whole run as soon as one test errors |
53+
| `--help` | Usage information |
54+
55+
Note that passing an explicit selector **disables** the automatic OS- and CI-based filtering in
56+
`runtests.jl``filter_tests!` returns `false` once positional arguments are present, so you get
57+
exactly the tests you asked for.
58+
59+
### Fast mode (`--fast`)
60+
61+
Sets a `fast_tests = true` constant in every test sandbox. Files branch on
62+
`@isdefined(fast_tests) && fast_tests` to shrink their element-type matrix, typically from
63+
`(Float32, Float64, ComplexF32, ComplexF64)` plus generic floats down to `(Float64, ComplexF64)` and
64+
one generic float. CI uses this for draft pull requests.
65+
66+
## Test groups
67+
68+
The top-level directories under `test/` are the test groups, and they are exactly the matrix jobs of
69+
the CI workflow — `.github/workflows/Tests.yml` delegates to
70+
`QuantumKitHub/QuantumKitHubActions/.github/workflows/TestGroups.yml`, which discovers groups by
71+
listing those directories. Adding a new directory therefore adds a new CI job automatically; a test
72+
file placed directly in `test/` would belong to no group and never run in CI.
73+
74+
| Group | Contents |
75+
|-------|----------|
76+
| `common` | Algorithm selection and defaults, truncation strategies, projections, matrix exponential, Aqua code-quality checks |
77+
| `decompositions` | `qr`, `lq`, `svd`, `eig`, `eigh`, `gen_eig`, `schur`, `polar`, `orthnull` on CPU and GPU array types |
78+
| `chainrules` | ChainRulesCore rules, exercised through ChainRulesTestUtils and Zygote |
79+
| `mooncake` | Mooncake AD rules |
80+
| `enzyme` | Enzyme AD rules, exercised through EnzymeTestUtils |
81+
| `genericlinearalgebra` | `MatrixAlgebraKitGenericLinearAlgebraExt` |
82+
| `genericschur` | `MatrixAlgebraKitGenericSchurExt` |
83+
84+
Two directories are *not* groups: `testsuite/` holds the shared implementation described below and
85+
is excluded both from discovery (in `runtests.jl`) and from CI (`exclude: '["testsuite"]'`).
86+
87+
## `testsuite/` — the shared test suite
88+
89+
`test/testsuite/TestSuite.jl` defines the `TestSuite` module, a backend-parametric suite modelled on
90+
GPUArrays.jl and intended to be reusable by packages that build on MatrixAlgebraKit. It contains the
91+
actual assertions; the files under the group directories are thin drivers that pick element types
92+
and array types and call into it.
93+
94+
Because it is excluded from discovery, it is loaded explicitly by each test file that needs it:
95+
96+
```julia
97+
@isdefined(TestSuite) || include("../testsuite/TestSuite.jl")
98+
using .TestSuite
99+
```
100+
101+
The suite exposes `test_*` entry points, one per operation, taking a *matrix type* and a size:
102+
103+
```julia
104+
TestSuite.test_qr(Matrix{Float64}, (54, 37))
105+
TestSuite.test_qr(CuMatrix{ComplexF64}, (54, 37); test_pivoted = false, test_blocksize = false)
106+
TestSuite.test_qr(Diagonal{Float64, CuVector{Float64}}, 54)
107+
TestSuite.test_qr_algs(Matrix{Float64}, (54, 37), (Householder(; positive = true),))
108+
```
109+
110+
The AD entry points (`test_chainrules`, `test_mooncake_*`, `test_enzyme_*`) instead take a scalar
111+
element type and a size, plus `atol`/`rtol` keywords:
112+
113+
```julia
114+
TestSuite.test_chainrules(ComplexF64, (19, 17); atol = tol, rtol = tol)
115+
```
116+
117+
Supporting infrastructure in the module:
118+
119+
- `instantiate_matrix(AT, size)` — builds a random matrix of the requested type, with methods for
120+
`Array`, `CuArray`, `ROCArray`, `Diagonal`, and `Diagonal` wrapping GPU vectors. This is the single
121+
hook a new backend needs to implement.
122+
- `rng` / `seed_rng!(seed)` — a shared `StableRNG(123)`. Drivers call `TestSuite.seed_rng!(123)`
123+
before each parameter combination so results are reproducible independent of test ordering.
124+
- `precision(T)` — the default tolerance, `sqrt(eps(real(T)))`.
125+
- Predicates used throughout the assertions: `isleftnull`, `isrightnull`, `isleftcomplete`,
126+
`isrightcomplete`, `has_positive_diagonal`.
127+
- `instantiate_unitary`, `instantiate_rank_deficient_matrix` — inputs with prescribed structure.
128+
129+
`test/linearmap.jl` is a further helper, defining a `LinearMap` wrapper that is deliberately *not* an
130+
`AbstractMatrix`, used to check the generic code paths. It is excluded from discovery and included
131+
directly where needed.
132+
133+
## Hardware and platform gating
134+
135+
GPU tests are guarded at runtime rather than by dependency availability: CUDA and AMDGPU are
136+
unconditional dependencies of the test environment, and the tests themselves are wrapped in
137+
`CUDA.functional()` / `AMDGPU.functional()` blocks. Running the suite on a machine without a GPU
138+
therefore skips them silently.
139+
140+
`runtests.jl` applies two further filters, both only when no explicit selector was given:
141+
142+
- **`BUILDKITE=true`** (the GPU CI runners) drops the files that carry no GPU coverage —
143+
`common/algorithms`, `common/codequality`, `common/truncate`, `decompositions/gen_eig`, and the
144+
`chainrules` group — so GPU runners spend their time on GPU code paths.
145+
- **macOS CI** drops the `mooncake` and `chainrules` groups; **macOS and Windows CI** drop the
146+
`enzyme` group. These reflect AD backends that are unsupported or unreliable on those platforms.
147+
148+
## Adding a test
149+
150+
Create a `.jl` file inside one of the group directories. It is picked up automatically, runs in a
151+
fresh worker process, and must therefore load everything it needs itself:
152+
153+
```julia
154+
using MatrixAlgebraKit
155+
using Test
156+
using CUDA, AMDGPU
157+
158+
@isdefined(TestSuite) || include("../testsuite/TestSuite.jl")
159+
using .TestSuite
160+
161+
if @isdefined(fast_tests) && fast_tests
162+
eltypes = (Float64, ComplexF64)
163+
else
164+
eltypes = (Float32, Float64, ComplexF32, ComplexF64)
165+
end
166+
167+
for T in eltypes
168+
TestSuite.seed_rng!(123)
169+
TestSuite.test_myop(Matrix{T}, (54, 37))
170+
CUDA.functional() && TestSuite.test_myop(CuMatrix{T}, (54, 37))
171+
end
172+
```
173+
174+
Add assertions that should be shared with downstream packages to `test/testsuite/` and call them
175+
from the driver, rather than writing them inline.

test/runtests.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ testsuite = find_tests(@__DIR__)
88
filter!(!(startswith("testsuite") first), testsuite)
99

1010
# remove utils
11-
delete!(testsuite, "utilities")
1211
delete!(testsuite, "linearmap")
1312

1413
# Parse arguments
@@ -21,11 +20,11 @@ if filter_tests!(testsuite, args)
2120
# don't run all tests on GPU, only the GPU specific ones
2221
is_buildkite = get(ENV, "BUILDKITE", "false") == "true"
2322
if is_buildkite
24-
delete!(testsuite, "algorithms")
25-
delete!(testsuite, "truncate")
26-
delete!(testsuite, "gen_eig")
27-
delete!(testsuite, "chainrules")
28-
delete!(testsuite, "codequality")
23+
delete!(testsuite, "common/algorithms")
24+
delete!(testsuite, "common/codequality")
25+
delete!(testsuite, "common/truncate")
26+
delete!(testsuite, "decompositions/gen_eig")
27+
filter!(p -> !startswith(first(p), "chainrules/"), testsuite)
2928
else
3029
is_apple_ci = Sys.isapple() && get(ENV, "CI", "false") == "true"
3130
is_windows_ci = Sys.iswindows() && get(ENV, "CI", "false") == "true"

test/utilities.jl

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)