From b3dee6506f90a0389001ccb3ecfc24888814a4dc Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sun, 12 Jul 2026 07:19:55 -0400 Subject: [PATCH] Folder discovery: skip CI-matrix-only `group` alias sections A test_groups.toml section with a `group` key is a matrix-only alias: it runs the named group's body under a different matrix cell (e.g. a 32-bit lane `["Core 32-bit"] group = "Core"` that CI runs with `arch = "x86"`) and has no test folder of its own. Previously folder-discovery mode treated every declared section as a group needing a `test//` folder, so such a section broke `GROUP=All` / `Everything` / a bare `Pkg.test()` with "folder is missing". Skip alias sections under All/Everything (the target group already runs), and when one is selected by its own name run the target group instead of demanding a folder. Document the `group` key in `read_test_groups` and add a folder-mode test. Minor bump 2.1.0 -> 2.2.0 (new recognized test_groups.toml key). Verified: with `["Core 32-bit"] group = "Core"` declared, GROUP=All runs Core+Light without throwing, and GROUP="Core 32-bit" runs the Core body. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01EcQkauMQ4KSytnTpJpXUZu --- Project.toml | 2 +- src/SciMLTesting.jl | 27 ++++++++++++++++++++++- test/runtests.jl | 52 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index f7d508d..6c03982 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SciMLTesting" uuid = "09d9d899-5365-40a9-917a-5f67fddea283" authors = ["SciML"] -version = "2.1.0" +version = "2.2.0" [deps] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/src/SciMLTesting.jl b/src/SciMLTesting.jl index f6915a3..b728265 100644 --- a/src/SciMLTesting.jl +++ b/src/SciMLTesting.jl @@ -109,6 +109,12 @@ options. Recognized per-group options: explicitly to `false` for documentation/clarity). Set `in_all = false` on any heavy or environment-specific group to curate `"All"`. The reserved `"Everything"` group ignores `in_all` and runs every declared group (including `"QA"`). + * `group` (`String`) — marks the section as a CI-matrix-only *alias* for the named + group: it runs that group's body under a different matrix cell (e.g. a 32-bit lane + `["Core 32-bit"] group = "Core"` that the CI matrix runs with `arch = "x86"`) and + has no test folder of its own. Folder-discovery mode skips such a section under + `"All"`/`"Everything"` (the target group already runs) and, if it is selected by + its own name, runs the target group rather than demanding a `test/
/` folder. The returned dict preserves declaration order (insertion-ordered), and group names are returned verbatim (SciML group names are capitalized: `Core`, `QA`, `Interface`, @@ -155,6 +161,16 @@ end _group_in_all(name::AbstractString, opts::AbstractDict) = name != "QA" && get(opts, "in_all", true) == true +# A test_groups.toml section with a `group` key pointing at another group is a +# CI-matrix-only alias: it runs that group's body under a different arch/os cell +# (e.g. a 32-bit lane `["Core 32-bit"] group = "Core" arch = "x86"`) and has no +# test folder of its own. The CI matrix dispatches GROUP=, so such a +# section is never selected by its own name in practice; folder discovery skips +# it under All/Everything (the target group already runs) and, if it is selected +# directly, redirects to the target rather than demanding a `test/
/` folder. +_group_alias_target(name::AbstractString, opts::AbstractDict) = + (g = get(opts, "group", nothing); (g isa AbstractString && g != name) ? g : nothing) + # Resolve a group name to its folder under `test_dir`, trying an exact match first # then a case-insensitive match (so group "Interface" finds test/Interface or # test/interface, and "QA" finds test/qa or test/QA). Returns the absolute folder @@ -1284,6 +1300,7 @@ function _run_folder_group( # top-level Core run above and not re-run as a folder. for name in sort!(collect(keys(group_table))) name in ("Core", "QA") && continue + _group_alias_target(name, group_table[name]) === nothing || continue _group_in_all(name, group_table[name]) || continue _run_group_folder(test_dir, name, default_parent) end @@ -1295,6 +1312,7 @@ function _run_folder_group( _run_core_folder(test_dir) for name in sort!(collect(keys(group_table))) name == "Core" && continue + _group_alias_target(name, group_table[name]) === nothing || continue _run_group_folder(test_dir, name, default_parent) end return nothing @@ -1307,7 +1325,14 @@ function _run_folder_group( _run_group_folder(test_dir, "QA", default_parent) return nothing elseif haskey(group_table, group) - _run_group_folder(test_dir, group, default_parent) + # A matrix-only alias section selected by its own name runs the target + # group's body rather than demanding a `test/
/` folder. + target = _group_alias_target(group, group_table[group]) + if target !== nothing + _run_folder_group(target, test_dir, group_table, default_parent) + else + _run_group_folder(test_dir, group, default_parent) + end return nothing else # An unknown group in folder mode is an error rather than a silent Core diff --git a/test/runtests.jl b/test/runtests.jl index 88a2f30..0b71133 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1763,6 +1763,58 @@ end @test ran("core") && ran("Light") && ran("Heavy") && ran("QA") end + @testset "run_tests folder mode: matrix-only `group` alias section demands no folder" begin + # A section with a `group` key (e.g. a 32-bit CI lane `["Core 32-bit"] + # group = "Core"`) is a matrix-only alias for another group's body. It has + # no test folder, so All/Everything must SKIP it (not throw "folder missing"), + # and selecting it by name runs the target group. + tdir = mktempdir() + write( + joinpath(tdir, "test_groups.toml"), + """ + ["Core 32-bit"] + group = "Core" + arch = "x86" + [Light] + """, + ) + markerfile(rel, name) = "write(joinpath(@__DIR__, \"$(rel)ran_$(name)\"), \"1\")\n" + write( + joinpath(tdir, "core.jl"), + "@testset \"core\" begin @test true end\n" * markerfile("", "core"), + ) + ld = joinpath(tdir, "Light"); mkpath(ld) + write( + joinpath(ld, "l.jl"), + "@testset \"l\" begin @test true end\n" * markerfile("../", "Light"), + ) + ran(n) = isfile(joinpath(tdir, "ran_$(n)")) + clear() = for n in ("core", "Light") + isfile(joinpath(tdir, "ran_$(n)")) && rm(joinpath(tdir, "ran_$(n)")) + end + + # All: the alias section is skipped — no `test/Core 32-bit/` folder is + # demanded (previously this threw ArgumentError), Core + Light still run. + withenv("GROUP" => "All") do + run_tests(; test_dir = tdir) + end + @test ran("core") && ran("Light") + + # Everything: same skip, no throw. + clear() + withenv("GROUP" => "Everything") do + run_tests(; test_dir = tdir) + end + @test ran("core") && ran("Light") + + # Selecting the alias by name runs the target group's body (Core), not Light. + clear() + withenv("GROUP" => "Core 32-bit") do + run_tests(; test_dir = tdir) + end + @test ran("core") && !ran("Light") + end + @testset "run_tests folder mode: non-group subfolder (shared/) is ignored" begin # A subfolder that is NOT a declared group (test/shared/) is never discovered. # It is where shared include/fixture files live. Selecting any group must not