Skip to content

Commit 330d4c2

Browse files
committed
test(solve): cleanup and improve tests for Option B
- test_orchestration: use full mocks for contract tests, separate integration tests - test_explicit: remove redundant complete coverage test (already in canonical) - test_dispatch: implement full strategy contract on mocks + mock registry for partial completion testing - test_canonical: restore detailed reporting with improved formatting - print_utils: use Unicode box drawing and bold headers for test tables - All 701 tests passing
1 parent ca254e2 commit 330d4c2

5 files changed

Lines changed: 201 additions & 153 deletions

File tree

test/helpers/print_utils.jl

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -107,30 +107,62 @@ function print_test_header(show_memory::Bool = false)
107107

108108
# Table header (aligned with data columns)
109109
print(" ") # Space for the ✓/✗ symbol (2 characters)
110-
print(" | ")
111-
print(rpad("Type", 4))
112-
print(" | ")
113-
print(rpad("Problem", 8))
114-
print(" | ")
115-
print(rpad("Disc", 8))
116-
print(" | ")
117-
print(rpad("Modeler", 12))
118-
print(" | ")
119-
print(rpad("Solver", 6))
120-
print(" | ")
121-
print(lpad("Time", 12))
122-
print(" | ")
123-
print(lpad("Iters", 5))
124-
print(" | ")
125-
print(lpad("Objective", 14))
126-
print(" | ")
127-
print(lpad("Reference", 14))
128-
print(" | ")
129-
print(lpad("Error", 7))
110+
print(" ")
111+
printstyled(rpad("Type", 4); bold=true)
112+
print(" ")
113+
printstyled(rpad("Problem", 8); bold=true)
114+
print(" ")
115+
printstyled(rpad("Disc", 8); bold=true)
116+
print(" ")
117+
printstyled(rpad("Modeler", 12); bold=true)
118+
print(" ")
119+
printstyled(rpad("Solver", 6); bold=true)
120+
print(" ")
121+
printstyled(lpad("Time", 12); bold=true)
122+
print(" ")
123+
printstyled(lpad("Iters", 5); bold=true)
124+
print(" ")
125+
printstyled(lpad("Objective", 14); bold=true)
126+
print(" ")
127+
printstyled(lpad("Reference", 14); bold=true)
128+
print(" ")
129+
printstyled(lpad("Error", 7); bold=true)
130130

131131
if show_memory
132-
print(" | ")
133-
print(lpad("Memory", 10))
132+
print("")
133+
printstyled(lpad("Memory", 10); bold=true)
134+
end
135+
136+
println()
137+
138+
# Separator line
139+
print("───")
140+
print("─┼─")
141+
print("────")
142+
print("─┼─")
143+
print("────────")
144+
print("─┼─")
145+
print("────────")
146+
print("─┼─")
147+
print("────────────")
148+
print("─┼─")
149+
print("──────")
150+
print("─┼─")
151+
print("────────────")
152+
print("─┼─")
153+
print("─────")
154+
print("─┼─")
155+
print("──────────────")
156+
print("─┼─")
157+
print("──────────────")
158+
print("─┼─")
159+
160+
if show_memory
161+
print("───────")
162+
print("─┼─")
163+
print("──────────")
164+
else
165+
print("────────")
134166
end
135167

136168
println()
@@ -208,37 +240,37 @@ function print_test_line(
208240
printstyled(""; color=:red, bold=true)
209241
end
210242

211-
print(" | ")
243+
print(" ")
212244

213245
# Type column: CPU or GPU
214246
printstyled(rpad(test_type, 4); color=:magenta)
215-
print(" | ")
247+
print(" ")
216248

217249
# Fixed columns with rpad/lpad (like CTBenchmarks)
218250
# Problem: 8 characters
219251
printstyled(rpad(problem, 8); color=:cyan, bold=true)
220-
print(" | ")
252+
print(" ")
221253

222254
# Discretizer: 8 characters
223255
printstyled(rpad(discretizer, 8); color=:blue)
224-
print(" | ")
256+
print(" ")
225257

226258
# Modeler: 12 characters
227259
printstyled(rpad(modeler, 12); color=:magenta)
228-
print(" | ")
260+
print(" ")
229261

230262
# Solver: 6 characters
231263
printstyled(rpad(solver, 6); color=:yellow)
232-
print(" | ")
264+
print(" ")
233265

234266
# Time: right-aligned, 12 characters
235267
print(lpad(prettytime(time), 12))
236-
print(" | ")
268+
print(" ")
237269

238270
# Iterations: right-aligned, 5 characters
239271
iter_str = iterations === nothing ? "N/A" : string(iterations)
240272
print(lpad(iter_str, 5))
241-
print(" | ")
273+
print(" ")
242274

243275
# Objective: scientific format with phantom sign for alignment
244276
# Add space instead of '-' for positive values
@@ -247,15 +279,15 @@ function print_test_line(
247279
obj_str = " " * obj_str # Phantom sign
248280
end
249281
print(lpad(obj_str, 14))
250-
print(" | ")
282+
print(" ")
251283

252284
# Reference: scientific format with phantom sign
253285
ref_str = @sprintf("%.6e", ref_obj)
254286
if ref_obj >= 0
255287
ref_str = " " * ref_str # Phantom sign
256288
end
257289
print(lpad(ref_str, 14))
258-
print(" | ")
290+
print(" ")
259291

260292
# Error: scientific notation with 2 decimal places
261293
err_str = @sprintf("%.2e", rel_error / 100) # Convert to fraction then scientific format
@@ -264,7 +296,7 @@ function print_test_line(
264296

265297
# Memory: optional, right-aligned, 10 characters
266298
if show_memory
267-
print(" | ")
299+
print(" ")
268300
if memory_bytes !== nothing
269301
mem_str = prettymemory(memory_bytes)
270302
else

test/suite/solve/test_canonical.jl

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,7 @@ function test_canonical()
9494
obj = success ? OptimalControl.objective(solve_result) : 0.0
9595

9696
# Extract iterations using CTModels function
97-
iters::Union{Nothing, Int} = try
98-
OptimalControl.iterations(solve_result)
99-
catch
100-
nothing
101-
end
97+
iters = OptimalControl.iterations(solve_result)
10298

10399
# Display table line (SRP - responsibility delegated)
104100
if VERBOSE
@@ -162,11 +158,7 @@ function test_canonical()
162158
obj = success ? OptimalControl.objective(solve_result) : 0.0
163159

164160
# Extract iterations using CTModels function
165-
iters::Union{Nothing, Int} = try
166-
OptimalControl.iterations(solve_result)
167-
catch
168-
nothing
169-
end
161+
iters = OptimalControl.iterations(solve_result)
170162

171163
# Display table line (SRP - responsibility delegated)
172164
if VERBOSE

test/suite/solve/test_dispatch.jl

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,44 @@ struct MockSolution <: CTModels.AbstractSolution end
2222
struct MockDiscretizer <: CTDirect.AbstractDiscretizer
2323
options::CTSolvers.StrategyOptions
2424
end
25+
CTSolvers.Strategies.id(::Type{<:MockDiscretizer}) = :collocation
26+
CTSolvers.Strategies.metadata(::Type{<:MockDiscretizer}) = CTSolvers.Strategies.StrategyMetadata()
27+
CTSolvers.Strategies.options(d::MockDiscretizer) = d.options
28+
function MockDiscretizer(; mode::Symbol=:strict, kwargs...)
29+
opts = CTSolvers.Strategies.build_strategy_options(MockDiscretizer; mode=mode, kwargs...)
30+
return MockDiscretizer(opts)
31+
end
32+
2533
struct MockModeler <: CTSolvers.AbstractNLPModeler
2634
options::CTSolvers.StrategyOptions
2735
end
36+
CTSolvers.Strategies.id(::Type{<:MockModeler}) = :adnlp
37+
CTSolvers.Strategies.metadata(::Type{<:MockModeler}) = CTSolvers.Strategies.StrategyMetadata()
38+
CTSolvers.Strategies.options(m::MockModeler) = m.options
39+
function MockModeler(; mode::Symbol=:strict, kwargs...)
40+
opts = CTSolvers.Strategies.build_strategy_options(MockModeler; mode=mode, kwargs...)
41+
return MockModeler(opts)
42+
end
43+
2844
struct MockSolver <: CTSolvers.AbstractNLPSolver
2945
options::CTSolvers.StrategyOptions
3046
end
47+
CTSolvers.Strategies.id(::Type{<:MockSolver}) = :ipopt
48+
CTSolvers.Strategies.metadata(::Type{<:MockSolver}) = CTSolvers.Strategies.StrategyMetadata()
49+
CTSolvers.Strategies.options(s::MockSolver) = s.options
50+
function MockSolver(; mode::Symbol=:strict, kwargs...)
51+
opts = CTSolvers.Strategies.build_strategy_options(MockSolver; mode=mode, kwargs...)
52+
return MockSolver(opts)
53+
end
54+
55+
# Mock registry: maps mock types so _complete_components builds mocks, not real solvers
56+
function mock_strategy_registry()::CTSolvers.StrategyRegistry
57+
return CTSolvers.create_registry(
58+
CTDirect.AbstractDiscretizer => (MockDiscretizer,),
59+
CTSolvers.AbstractNLPModeler => (MockModeler,),
60+
CTSolvers.AbstractNLPSolver => (MockSolver,)
61+
)
62+
end
3163

3264
# Override Layer 3 solve for mocks — returns MockSolution immediately
3365
function CommonSolve.solve(
@@ -46,7 +78,7 @@ function test_solve_dispatch()
4678
disc = MockDiscretizer(CTSolvers.StrategyOptions())
4779
mod = MockModeler(CTSolvers.StrategyOptions())
4880
sol = MockSolver(CTSolvers.StrategyOptions())
49-
registry = OptimalControl.get_strategy_registry()
81+
registry = mock_strategy_registry()
5082

5183
# ====================================================================
5284
# CONTRACT TESTS - solve_explicit: complete components (mock Layer 3)
@@ -63,6 +95,17 @@ function test_solve_dispatch()
6395
Test.@test result isa MockSolution
6496
end
6597

98+
Test.@testset "solve_explicit - partial components (mock registry completes)" begin
99+
result = OptimalControl.solve_explicit(
100+
ocp;
101+
initial_guess=init,
102+
display=false,
103+
registry=registry,
104+
discretizer=disc, modeler=nothing, solver=nothing
105+
)
106+
Test.@test result isa MockSolution
107+
end
108+
66109
# ====================================================================
67110
# CONTRACT TESTS - solve_descriptive: stub raises NotImplemented
68111
# ====================================================================

test/suite/solve/test_explicit.jl

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -131,69 +131,6 @@ function test_explicit()
131131
end
132132
end
133133

134-
Test.@testset "Complete method coverage" begin
135-
# Test that all methods() are covered by integration tests
136-
# Track which methods we've tested
137-
available = Set(OptimalControl.methods())
138-
tested = Set{Tuple{Symbol, Symbol, Symbol}}()
139-
140-
# Define all strategy combinations to test
141-
discretizers = [
142-
("Collocation/midpoint", OptimalControl.Collocation(grid_size=20, scheme=:midpoint)),
143-
]
144-
145-
modelers = [
146-
("ADNLP", OptimalControl.ADNLP()),
147-
("Exa", OptimalControl.Exa()),
148-
]
149-
150-
solvers = [
151-
("Ipopt", OptimalControl.Ipopt(print_level=0, max_iter=0)),
152-
("MadNLP", OptimalControl.MadNLP(print_level=MadNLP.ERROR, max_iter=0)),
153-
("MadNCL", OptimalControl.MadNCL(print_level=MadNLP.ERROR, max_iter=0)),
154-
]
155-
156-
# Use only one problem to test all method combinations
157-
pb = TestProblems.Beam()
158-
init = OptimalControl.build_initial_guess(pb.ocp, pb.init)
159-
160-
# Test all combinations
161-
for (dname, disc) in discretizers
162-
for (mname, mod) in modelers
163-
for (sname, sol) in solvers
164-
# Build method using R3 helpers
165-
partial = OptimalControl._build_partial_description(disc, mod, sol)
166-
complete = OptimalControl._complete_description(partial)
167-
168-
# Check that this method is available and not already tested
169-
Test.@test complete in available
170-
Test.@test complete tested
171-
172-
# Mark as tested
173-
push!(tested, complete)
174-
175-
# Test the actual solve - just verify it returns a solution
176-
result = OptimalControl.solve_explicit(
177-
pb.ocp;
178-
initial_guess=init,
179-
discretizer=disc,
180-
modeler=mod,
181-
solver=sol,
182-
display=false,
183-
registry=registry
184-
)
185-
Test.@test result isa CTModels.AbstractSolution
186-
end
187-
end
188-
end
189-
190-
# Verify all methods have been tested (modulo Knitro which requires license)
191-
knitro_methods = Set([m for m in available if m[3] == :knitro])
192-
non_knitro_available = setdiff(available, knitro_methods)
193-
Test.@test tested == non_knitro_available
194-
Test.@test length(tested) == length(non_knitro_available)
195-
Test.@test length(tested) + length(knitro_methods) == length(OptimalControl.methods())
196-
end
197134
end
198135
end
199136
end

0 commit comments

Comments
 (0)