Skip to content

Commit 7d6010a

Browse files
ChrisRackauckas-ClaudeChrisRackauckasclaude
authored
Use SciMLTesting v1.2 folder-based run_tests (#57)
Convert test/runtests.jl to the SciMLTesting v1.2 folder-discovery model: `using SciMLTesting; run_tests()`. The inline Core test block is extracted to a self-contained top-level test/core_tests.jl (Core group). QA stays in test/qa/ with its own Project.toml. Behavior-preserving: GROUP=Core, GROUP=QA, and GROUP=All run exactly the same tests as before. Adds SciMLTesting + SafeTestsets to the root test deps and the QA sub-env; drops the now-unused Pkg test extra (the v1.2 harness owns all Pkg ops). test/test_groups.toml is unchanged. Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 76c260e commit 7d6010a

4 files changed

Lines changed: 96 additions & 103 deletions

File tree

Project.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ authors = ["SciML"]
99
ComponentArrays = "0.15"
1010
Random = "1.10"
1111
SafeTestsets = "0.1"
12+
SciMLTesting = "1"
1213
Test = "1.10"
1314
julia = "1.10"
1415

1516
[extras]
1617
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
17-
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
1818
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1919
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
20+
SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283"
2021
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2122

2223
[targets]
23-
test = ["Test", "SafeTestsets", "ComponentArrays", "Random", "Pkg"]
24+
test = ["Test", "SafeTestsets", "SciMLTesting", "ComponentArrays", "Random"]

test/core_tests.jl

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using FunctionProperties
2+
using ComponentArrays, Random
3+
using Test
4+
5+
@test hasbranching(1, 2) do x, y
6+
(x < 0 ? -x : x) + exp(y)
7+
end
8+
9+
@test !hasbranching(1, 2) do x, y
10+
ifelse(x < 0, -x, x) + exp(y)
11+
end
12+
13+
# Test overloading via is_leaf
14+
15+
f_branch() = true ? 1 : 0
16+
@test FunctionProperties.hasbranching(f_branch)
17+
FunctionProperties.is_leaf(::typeof(f_branch)) = true
18+
@test !FunctionProperties.hasbranching(f_branch)
19+
20+
# Test simple mutating functions
21+
function f(dx, x)
22+
return @inbounds dx[1] = x[1]
23+
end
24+
x = zeros(1)
25+
dx = zeros(1)
26+
@test !FunctionProperties.hasbranching(f, dx, x)
27+
28+
# Test broadcast
29+
function f(x)
30+
return cos.(x .+ x .* x)
31+
end
32+
x = [1.0]
33+
@test !FunctionProperties.hasbranching(f, x)
34+
35+
# Neural networks
36+
#
37+
# The relevant scenario is a neural-network-shaped ODE right-hand side (SciML/SciMLSensitivity.jl#997):
38+
# `hasbranching` must report it as branch-free so a tracing AD like ReverseDiff can compile a tape.
39+
# The forward pass is expressed here as explicit affine transforms plus broadcast activations, which
40+
# is the value flow `hasbranching` actually inspects. We deliberately do not trace a real Lux layer:
41+
# modern Lux layer dispatch routes through device-detection / type-introspection helpers that contain
42+
# genuine (but value-independent, compile-time) `GotoIfNot` branches, which this syntactic IR scan
43+
# cannot distinguish from value-dependent branches (SciML/FunctionProperties.jl#46).
44+
rng = Random.default_rng()
45+
W = randn(rng, Float32, 1, 1)
46+
b = randn(rng, Float32, 1)
47+
p = ComponentArray(; weight = W, bias = b)
48+
t = [0.0]
49+
50+
function f(x, ps)
51+
return ps.weight * x
52+
end
53+
@test !FunctionProperties.hasbranching(f, t, p)
54+
55+
function f(x, ps)
56+
return x .+ x
57+
end
58+
@test !FunctionProperties.hasbranching(f, t, p)
59+
60+
# Affine transform followed by a broadcast activation (the original `apply_activation` intent).
61+
function f2(x, ps)
62+
return identity.(ps.weight * x .+ vec(ps.bias))
63+
end
64+
@test !FunctionProperties.hasbranching(f2, t, p)
65+
66+
# A multi-layer perceptron forward pass built from broadcast `tanh` activations.
67+
rng = Random.default_rng()
68+
tspan = (0.0f0, 8.0f0)
69+
W1 = randn(rng, Float32, 32, 2)
70+
b1 = randn(rng, Float32, 32)
71+
W2 = randn(rng, Float32, 32, 32)
72+
b2 = randn(rng, Float32, 32)
73+
W3 = randn(rng, Float32, 1, 32)
74+
b3 = randn(rng, Float32, 1)
75+
p = ComponentArray(; W1, b1, W2, b2, W3, b3)
76+
θ, ax = getdata(p), getaxes(p)
77+
78+
ann(x, p) = p.W3 * tanh.(p.W2 * tanh.(p.W1 * x .+ p.b1) .+ p.b2) .+ p.b3
79+
80+
function dxdt_(dx, x, p, t)
81+
x1, x2 = x
82+
dx[1] = x[2] + first(ann(x, p))
83+
return dx[2] = first(ann([t, t], p))
84+
end
85+
x0 = [-4.0f0, 0.0f0]
86+
ts = Float32.(collect(0.0:0.01:tspan[2]))
87+
@test !FunctionProperties.hasbranching(dxdt_, copy(x0), x0, p, tspan[1])

test/qa/Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
33
FunctionProperties = "f62d2435-5019-4c03-9749-2d4c77af0cbc"
44
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
5-
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
5+
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
6+
SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283"
67
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
78

89
[sources]
@@ -11,5 +12,7 @@ FunctionProperties = {path = "../.."}
1112
[compat]
1213
Aqua = "0.8"
1314
JET = "0.9,0.10,0.11"
15+
SafeTestsets = "0.0.1, 0.1"
16+
SciMLTesting = "1"
1417
Test = "1"
1518
julia = "1.10"

test/runtests.jl

Lines changed: 2 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,2 @@
1-
using Test
2-
using FunctionProperties
3-
using ComponentArrays, Random
4-
5-
const GROUP = get(ENV, "GROUP", "All")
6-
7-
if GROUP == "QA"
8-
using Pkg
9-
Pkg.activate(joinpath(@__DIR__, "qa"))
10-
Pkg.instantiate()
11-
include(joinpath(@__DIR__, "qa", "qa.jl"))
12-
end
13-
14-
if GROUP in ("All", "Core")
15-
16-
@test hasbranching(1, 2) do x, y
17-
(x < 0 ? -x : x) + exp(y)
18-
end
19-
20-
@test !hasbranching(1, 2) do x, y
21-
ifelse(x < 0, -x, x) + exp(y)
22-
end
23-
24-
# Test overloading via is_leaf
25-
26-
f_branch() = true ? 1 : 0
27-
@test FunctionProperties.hasbranching(f_branch)
28-
FunctionProperties.is_leaf(::typeof(f_branch)) = true
29-
@test !FunctionProperties.hasbranching(f_branch)
30-
31-
# Test simple mutating functions
32-
function f(dx, x)
33-
return @inbounds dx[1] = x[1]
34-
end
35-
x = zeros(1)
36-
dx = zeros(1)
37-
@test !FunctionProperties.hasbranching(f, dx, x)
38-
39-
# Test broadcast
40-
function f(x)
41-
return cos.(x .+ x .* x)
42-
end
43-
x = [1.0]
44-
@test !FunctionProperties.hasbranching(f, x)
45-
46-
# Neural networks
47-
#
48-
# The relevant scenario is a neural-network-shaped ODE right-hand side (SciML/SciMLSensitivity.jl#997):
49-
# `hasbranching` must report it as branch-free so a tracing AD like ReverseDiff can compile a tape.
50-
# The forward pass is expressed here as explicit affine transforms plus broadcast activations, which
51-
# is the value flow `hasbranching` actually inspects. We deliberately do not trace a real Lux layer:
52-
# modern Lux layer dispatch routes through device-detection / type-introspection helpers that contain
53-
# genuine (but value-independent, compile-time) `GotoIfNot` branches, which this syntactic IR scan
54-
# cannot distinguish from value-dependent branches (SciML/FunctionProperties.jl#46).
55-
rng = Random.default_rng()
56-
W = randn(rng, Float32, 1, 1)
57-
b = randn(rng, Float32, 1)
58-
p = ComponentArray(; weight = W, bias = b)
59-
t = [0.0]
60-
61-
function f(x, ps)
62-
return ps.weight * x
63-
end
64-
@test !FunctionProperties.hasbranching(f, t, p)
65-
66-
function f(x, ps)
67-
return x .+ x
68-
end
69-
@test !FunctionProperties.hasbranching(f, t, p)
70-
71-
# Affine transform followed by a broadcast activation (the original `apply_activation` intent).
72-
function f2(x, ps)
73-
return identity.(ps.weight * x .+ vec(ps.bias))
74-
end
75-
@test !FunctionProperties.hasbranching(f2, t, p)
76-
77-
# A multi-layer perceptron forward pass built from broadcast `tanh` activations.
78-
rng = Random.default_rng()
79-
tspan = (0.0f0, 8.0f0)
80-
W1 = randn(rng, Float32, 32, 2)
81-
b1 = randn(rng, Float32, 32)
82-
W2 = randn(rng, Float32, 32, 32)
83-
b2 = randn(rng, Float32, 32)
84-
W3 = randn(rng, Float32, 1, 32)
85-
b3 = randn(rng, Float32, 1)
86-
p = ComponentArray(; W1, b1, W2, b2, W3, b3)
87-
θ, ax = getdata(p), getaxes(p)
88-
89-
ann(x, p) = p.W3 * tanh.(p.W2 * tanh.(p.W1 * x .+ p.b1) .+ p.b2) .+ p.b3
90-
91-
function dxdt_(dx, x, p, t)
92-
x1, x2 = x
93-
dx[1] = x[2] + first(ann(x, p))
94-
return dx[2] = first(ann([t, t], p))
95-
end
96-
x0 = [-4.0f0, 0.0f0]
97-
ts = Float32.(collect(0.0:0.01:tspan[2]))
98-
@test !FunctionProperties.hasbranching(dxdt_, copy(x0), x0, p, tspan[1])
99-
100-
end
1+
using SciMLTesting
2+
run_tests()

0 commit comments

Comments
 (0)