Skip to content

Commit 3c19dec

Browse files
authored
[Bridges] add testset to Bridges.runtests (#2726)
1 parent 3538b7f commit 3c19dec

File tree

3 files changed

+119
-71
lines changed

3 files changed

+119
-71
lines changed

docs/src/submodules/Bridges/implementation.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ arguments: the type of the bridge, the input model as a string, and the output
6666
model as a string.
6767

6868
Here is an example:
69-
```jldoctest
69+
```jldoctest; filter=r"[0-9.]+s"
7070
julia> MOI.Bridges.runtests(
7171
MOI.Bridges.Constraint.GreaterToLessBridge,
7272
"""
@@ -78,6 +78,8 @@ julia> MOI.Bridges.runtests(
7878
-1.0 * x <= -1.0
7979
""",
8080
)
81+
Test Summary: | Pass Total Time
82+
Bridges.runtests | 29 29 0.0s
8183
```
8284

8385
There are a number of other useful keyword arguments.
@@ -98,7 +100,7 @@ There are a number of other useful keyword arguments.
98100

99101
Here is an example:
100102

101-
```jldoctest
103+
```jldoctest; filter=r"[0-9.]+s"
102104
julia> MOI.Bridges.runtests(
103105
MOI.Bridges.Constraint.GreaterToLessBridge,
104106
"""
@@ -120,4 +122,6 @@ Subject to:
120122
121123
ScalarAffineFunction{Int64}-in-LessThan{Int64}
122124
(0) - (1) x <= (-1)
125+
Test Summary: | Pass Total Time
126+
Bridges.runtests | 29 29 0.0s
123127
```

src/Bridges/Bridges.jl

Lines changed: 112 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
# Use of this source code is governed by an MIT-style license that can be found
55
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
66

7+
# !!! info "COV_EXCL_LINE"
8+
#
9+
# The Julia coverage check is not perfect, particularly when it comes to
10+
# macros that produce code that is not executed. To work around
11+
# false-negatives, some lines in this file are excluded from coverage with
12+
# `# COV_EXCL_LINE`. (In most of the excluded cases, the default is for the
13+
# tests to pass, so the failure case of the testset macro is not executed,
14+
# and so no code is executed that can be tied back to the excluded lines.
15+
716
module Bridges
817

918
import MathOptInterface as MOI
@@ -153,15 +162,21 @@ function _test_structural_identical(
153162
# the variables are added in the same order to both models.
154163
a_x = MOI.get(a, MOI.ListOfVariableIndices())
155164
b_x = MOI.get(b, MOI.ListOfVariableIndices())
156-
attr = MOI.NumberOfVariables()
157-
Test.@test MOI.get(a, attr) == MOI.get(b, attr)
158-
Test.@test length(a_x) == length(b_x)
165+
Test.@testset "Test NumberOfVariables" begin # COV_EXCL_LINE
166+
Test.@test MOI.get(a, MOI.NumberOfVariables()) ==
167+
MOI.get(b, MOI.NumberOfVariables())
168+
end
169+
Test.@testset "Test length ListOfVariableIndices" begin # COV_EXCL_LINE
170+
Test.@test length(a_x) == length(b_x)
171+
end
159172
# A dictionary that maps things from `b`-space to `a`-space.
160173
x_map = Dict(bx => a_x[i] for (i, bx) in enumerate(b_x))
161174
# To check that the constraints, we need to first cache all of the
162175
# constraints in `a`.
163176
constraints = Dict{Any,Any}()
164-
for (F, S) in MOI.get(a, MOI.ListOfConstraintTypesPresent())
177+
a_constraint_types = MOI.get(a, MOI.ListOfConstraintTypesPresent())
178+
b_constraint_types = MOI.get(b, MOI.ListOfConstraintTypesPresent())
179+
Test.@testset "get $F and $S" for (F, S) in a_constraint_types
165180
Test.@test MOI.supports_constraint(a, F, S)
166181
constraints[(F, S)] =
167182
map(MOI.get(a, MOI.ListOfConstraintIndices{F,S}())) do ci
@@ -170,20 +185,16 @@ function _test_structural_identical(
170185
MOI.get(a, MOI.ConstraintSet(), ci),
171186
)
172187
end
173-
end
174-
# Now compare the constraints in `b` with the cache in `constraints`.
175-
b_constraint_types = MOI.get(b, MOI.ListOfConstraintTypesPresent())
176-
# There may be constraint types reported in `a` that are not in `b`, but
177-
# have zero constraints in `a`.
178-
for (F, S) in keys(constraints)
179-
attr = MOI.NumberOfConstraints{F,S}()
180-
Test.@test (F, S) in b_constraint_types || MOI.get(a, attr) == 0
181-
end
182-
for (F, S) in b_constraint_types
188+
# There may be constraint types reported in `a` that are not in `b`, but
189+
# have zero constraints in `a`.
190+
Test.@test (F, S) in b_constraint_types ||
191+
MOI.get(a, MOI.NumberOfConstraints{F,S}()) == 0
192+
end # COV_EXCL_LINE
193+
Test.@testset "$F-in-$S" for (F, S) in b_constraint_types
183194
Test.@test haskey(constraints, (F, S))
184195
# Check that the same number of constraints are present
185-
attr = MOI.NumberOfConstraints{F,S}()
186-
Test.@test MOI.get(a, attr) == MOI.get(b, attr)
196+
Test.@test MOI.get(a, MOI.NumberOfConstraints{F,S}()) ==
197+
MOI.get(b, MOI.NumberOfConstraints{F,S}())
187198
# Check that supports_constraint is implemented
188199
Test.@test MOI.supports_constraint(b, F, S)
189200
# Check that each function in `b` matches a function in `a`
@@ -202,12 +213,14 @@ function _test_structural_identical(
202213
return s_b == s && isapprox(f, f_b) && typeof(f) == typeof(f_b)
203214
end
204215
end
205-
end
216+
end # COV_EXCL_LINE
206217
# Test model attributes are set, like ObjectiveSense and ObjectiveFunction.
207218
a_attrs = MOI.get(a, MOI.ListOfModelAttributesSet())
208219
b_attrs = MOI.get(b, MOI.ListOfModelAttributesSet())
209-
Test.@test length(a_attrs) == length(b_attrs)
210-
for attr in b_attrs
220+
Test.@testset "Test length ListOfModelAttributesSet" begin # COV_EXCL_LINE
221+
Test.@test length(a_attrs) == length(b_attrs)
222+
end
223+
Test.@testset "$attr" for attr in b_attrs
211224
Test.@test attr in a_attrs
212225
if attr == MOI.ObjectiveSense()
213226
# map_indices isn't defined for `OptimizationSense`
@@ -216,7 +229,7 @@ function _test_structural_identical(
216229
attr_b = MOI.Utilities.map_indices(x_map, MOI.get(b, attr))
217230
Test.@test isapprox(MOI.get(a, attr), attr_b)
218231
end
219-
end
232+
end # COV_EXCL_LINE
220233
return
221234
end
222235

@@ -259,7 +272,7 @@ and [`MOI.ConstraintPrimalStart`](@ref) to throw [`MOI.GetAttributeNotAllowed`](
259272
260273
## Example
261274
262-
```jldoctest; setup=:(import MathOptInterface as MOI)
275+
```jldoctest; setup=:(import MathOptInterface as MOI), filter=r"[0-9.]+s"
263276
julia> MOI.Bridges.runtests(
264277
MOI.Bridges.Constraint.ZeroOneBridge,
265278
model -> MOI.add_constrained_variable(model, MOI.ZeroOne()),
@@ -268,9 +281,18 @@ julia> MOI.Bridges.runtests(
268281
MOI.add_constraint(model, 1.0 * x, MOI.Interval(0.0, 1.0))
269282
end,
270283
)
284+
Test Summary: | Pass Total Time
285+
Bridges.runtests | 32 32 0.8s
271286
```
272287
"""
273-
function runtests(
288+
function runtests(args...; kwargs...)
289+
Test.@testset "Bridges.runtests" begin
290+
_runtests(args...; kwargs...)
291+
end
292+
return
293+
end
294+
295+
function _runtests(
274296
Bridge::Type{<:AbstractBridge},
275297
input_fn::Function,
276298
output_fn::Function;
@@ -290,50 +312,61 @@ function runtests(
290312
if print_inner_model
291313
print(inner)
292314
end
293-
# Load a non-bridged input model, and check that getters are the same.
294-
test = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}())
295-
input_fn(test)
296-
_test_structural_identical(test, model; cannot_unbridge = cannot_unbridge)
297-
# Load a bridged target model, and check that getters are the same.
298-
target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}())
299-
output_fn(target)
300-
_test_structural_identical(target, inner)
301-
# Test VariablePrimalStart
302-
attr = MOI.VariablePrimalStart()
303-
bridge_supported = all(values(Variable.bridges(model))) do bridge
304-
return MOI.supports(model, attr, typeof(bridge))
315+
Test.@testset "Test outer bridged model appears like the input" begin # COV_EXCL_LINE
316+
test = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}())
317+
input_fn(test)
318+
_test_structural_identical(
319+
test,
320+
model;
321+
cannot_unbridge = cannot_unbridge,
322+
)
305323
end
306-
if MOI.supports(model, attr, MOI.VariableIndex) && bridge_supported
307-
x = MOI.get(model, MOI.ListOfVariableIndices())
308-
MOI.set(model, attr, x, fill(nothing, length(x)))
309-
Test.@test all(isnothing, MOI.get(model, attr, x))
310-
primal_start = fill(variable_start, length(x))
311-
MOI.set(model, attr, x, primal_start)
312-
if !isempty(x)
313-
# ≈ does not work if x is empty because the return of get is Any[]
314-
Test.@test MOI.get(model, attr, x) primal_start
315-
end
324+
Test.@testset "Test inner bridged model appears like the target" begin # COV_EXCL_LINE
325+
target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}())
326+
output_fn(target)
327+
_test_structural_identical(target, inner)
316328
end
317-
# Test ConstraintPrimalStart and ConstraintDualStart
318-
for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
319-
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
320-
set = try
321-
MOI.get(model, MOI.ConstraintSet(), ci)
322-
catch err
323-
_runtests_error_handler(err, cannot_unbridge)
324-
continue
329+
Test.@testset "Test MOI.VariablePrimalStart" begin # COV_EXCL_LINE
330+
attr = MOI.VariablePrimalStart()
331+
bridge_supported = all(values(Variable.bridges(model))) do bridge
332+
return MOI.supports(model, attr, typeof(bridge))
333+
end
334+
if MOI.supports(model, attr, MOI.VariableIndex) && bridge_supported
335+
x = MOI.get(model, MOI.ListOfVariableIndices())
336+
MOI.set(model, attr, x, fill(nothing, length(x)))
337+
Test.@test all(isnothing, MOI.get(model, attr, x))
338+
primal_start = fill(variable_start, length(x))
339+
MOI.set(model, attr, x, primal_start)
340+
if !isempty(x)
341+
# ≈ does not work if x is empty because the return of get is Any[]
342+
Test.@test MOI.get(model, attr, x) primal_start
325343
end
326-
for attr in (MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart())
327-
if MOI.supports(model, attr, MOI.ConstraintIndex{F,S})
344+
end
345+
end
346+
Test.@testset "Test ConstraintPrimalStart and ConstraintDualStart" begin # COV_EXCL_LINE
347+
list_of_constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent())
348+
Test.@testset "$F-in-$S" for (F, S) in list_of_constraints
349+
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
350+
set = try
351+
MOI.get(model, MOI.ConstraintSet(), ci)
352+
catch err
353+
_runtests_error_handler(err, cannot_unbridge)
354+
continue
355+
end
356+
attrs = (MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart())
357+
Test.@testset "$attr" for attr in attrs
358+
if !MOI.supports(model, attr, MOI.ConstraintIndex{F,S})
359+
continue
360+
end
328361
MOI.set(model, attr, ci, nothing)
329362
Test.@test MOI.get(model, attr, ci) === nothing
330363
start = _fake_start(constraint_start, set)
331364
MOI.set(model, attr, ci, start)
332365
returned_start = try
333366
MOI.get(model, attr, ci)
334367
catch err
335-
# For a Constraint bridge for which the map is not invertible, the constraint primal cannot
336-
# be inverted
368+
# For a Constraint bridge for which the map is not
369+
# invertible, the constraint primal cannot be inverted
337370
_runtests_error_handler(
338371
err,
339372
Bridge <: MOI.Bridges.Constraint.AbstractBridge &&
@@ -342,21 +375,30 @@ function runtests(
342375
continue
343376
end
344377
Test.@test returned_start start
345-
end
378+
end # COV_EXCL_LINE
346379
end
347-
end
348-
end
349-
# Test other bridge functions
350-
for b in values(Constraint.bridges(model))
351-
_general_bridge_tests(something(b))
380+
end # COV_EXCL_LINE
352381
end
353-
for b in values(Objective.bridges(model))
354-
_general_bridge_tests(something(b))
382+
Test.@testset "Test general bridge tests" begin # COV_EXCL_LINE
383+
Test.@testset "Constraint" begin # COV_EXCL_LINE
384+
for b in values(Constraint.bridges(model))
385+
_general_bridge_tests(something(b))
386+
end
387+
end
388+
Test.@testset "Objective" begin # COV_EXCL_LINE
389+
for b in values(Objective.bridges(model))
390+
_general_bridge_tests(something(b))
391+
end
392+
end
393+
Test.@testset "Variable" begin # COV_EXCL_LINE
394+
for b in values(Variable.bridges(model))
395+
_general_bridge_tests(something(b))
396+
end
397+
end
355398
end
356-
for b in values(Variable.bridges(model))
357-
_general_bridge_tests(something(b))
399+
Test.@testset "Test delete" begin # COV_EXCL_LINE
400+
_test_delete(Bridge, model, inner)
358401
end
359-
_test_delete(Bridge, model, inner)
360402
return
361403
end
362404

@@ -377,7 +419,7 @@ Run a series of tests that check the correctness of `Bridge`.
377419
378420
## Example
379421
380-
```jldoctest; setup=:(import MathOptInterface as MOI)
422+
```jldoctest; setup=:(import MathOptInterface as MOI), filter=r"[0-9.]+s"
381423
julia> MOI.Bridges.runtests(
382424
MOI.Bridges.Constraint.ZeroOneBridge,
383425
\"\"\"
@@ -390,6 +432,8 @@ julia> MOI.Bridges.runtests(
390432
1.0 * x in Interval(0.0, 1.0)
391433
\"\"\",
392434
)
435+
Test Summary: | Pass Total Time
436+
Bridges.runtests | 32 32 0.0s
393437
```
394438
"""
395439
function runtests(

src/Test/Test.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ function runtests(
263263
end
264264
continue
265265
end
266-
@testset "$(name)" begin
266+
@testset "$(name)" begin # COV_EXCL_LINE
267267
c = copy(config)
268268
tear_down = setup_test(test_function, model, c)
269269
# Make sure to empty the model before every test.

0 commit comments

Comments
 (0)