From 6042ed5cfea869cfc1cb899fdc17c176f624a0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 15 Apr 2025 17:40:07 +0200 Subject: [PATCH 1/4] Add testset to Bridges.runtests --- src/Bridges/Bridges.jl | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index 1491540ec7..04a67114e3 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -161,7 +161,7 @@ function _test_structural_identical( # To check that the constraints, we need to first cache all of the # constraints in `a`. constraints = Dict{Any,Any}() - for (F, S) in MOI.get(a, MOI.ListOfConstraintTypesPresent()) + Test.@testset "$F-in-$S" for (F, S) in MOI.get(a, MOI.ListOfConstraintTypesPresent()) Test.@test MOI.supports_constraint(a, F, S) constraints[(F, S)] = map(MOI.get(a, MOI.ListOfConstraintIndices{F,S}())) do ci @@ -175,11 +175,11 @@ function _test_structural_identical( b_constraint_types = MOI.get(b, MOI.ListOfConstraintTypesPresent()) # There may be constraint types reported in `a` that are not in `b`, but # have zero constraints in `a`. - for (F, S) in keys(constraints) + Test.@testset "$F-in-$S" for (F, S) in keys(constraints) attr = MOI.NumberOfConstraints{F,S}() Test.@test (F, S) in b_constraint_types || MOI.get(a, attr) == 0 end - for (F, S) in b_constraint_types + Test.@testset "$F-in-$S" for (F, S) in b_constraint_types Test.@test haskey(constraints, (F, S)) # Check that the same number of constraints are present attr = MOI.NumberOfConstraints{F,S}() @@ -207,7 +207,7 @@ function _test_structural_identical( a_attrs = MOI.get(a, MOI.ListOfModelAttributesSet()) b_attrs = MOI.get(b, MOI.ListOfModelAttributesSet()) Test.@test length(a_attrs) == length(b_attrs) - for attr in b_attrs + Test.@testset "$attr" for attr in b_attrs Test.@test attr in a_attrs if attr == MOI.ObjectiveSense() # map_indices isn't defined for `OptimizationSense` @@ -293,11 +293,19 @@ function runtests( # Load a non-bridged input model, and check that getters are the same. test = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) input_fn(test) - _test_structural_identical(test, model; cannot_unbridge = cannot_unbridge) + Test.@testset "Outer model" begin + _test_structural_identical( + test, + model; + cannot_unbridge = cannot_unbridge, + ) + end # Load a bridged target model, and check that getters are the same. target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) output_fn(target) - _test_structural_identical(target, inner) + Test.@testset "Inner model" begin + _test_structural_identical(target, inner) + end # Test VariablePrimalStart attr = MOI.VariablePrimalStart() bridge_supported = all(values(Variable.bridges(model))) do bridge From 6f58fcb9de96abf071b60be2f9aea036a6cbc41b Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 16 Apr 2025 09:33:42 +1200 Subject: [PATCH 2/4] Update --- docs/src/submodules/Bridges/implementation.md | 8 +- src/Bridges/Bridges.jl | 151 +++++++++++------- 2 files changed, 95 insertions(+), 64 deletions(-) diff --git a/docs/src/submodules/Bridges/implementation.md b/docs/src/submodules/Bridges/implementation.md index 3782edd7a4..5d7734e089 100644 --- a/docs/src/submodules/Bridges/implementation.md +++ b/docs/src/submodules/Bridges/implementation.md @@ -66,7 +66,7 @@ arguments: the type of the bridge, the input model as a string, and the output model as a string. Here is an example: -```jldoctest +```jldoctest; filter="[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.GreaterToLessBridge, """ @@ -78,6 +78,8 @@ julia> MOI.Bridges.runtests( -1.0 * x <= -1.0 """, ) +Test Summary: | Pass Total Time +Bridges.runtests | 29 29 0.0s ``` There are a number of other useful keyword arguments. @@ -98,7 +100,7 @@ There are a number of other useful keyword arguments. Here is an example: -```jldoctest +```jldoctest; filter="[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.GreaterToLessBridge, """ @@ -120,4 +122,6 @@ Subject to: ScalarAffineFunction{Int64}-in-LessThan{Int64} (0) - (1) x <= (-1) +Test Summary: | Pass Total Time +Bridges.runtests | 29 29 0.0s ``` diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index 04a67114e3..20990018c7 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -153,15 +153,21 @@ function _test_structural_identical( # the variables are added in the same order to both models. a_x = MOI.get(a, MOI.ListOfVariableIndices()) b_x = MOI.get(b, MOI.ListOfVariableIndices()) - attr = MOI.NumberOfVariables() - Test.@test MOI.get(a, attr) == MOI.get(b, attr) - Test.@test length(a_x) == length(b_x) + Test.@testset "Test NumberOfVariables" begin + Test.@test MOI.get(a, MOI.NumberOfVariables()) == + MOI.get(b, MOI.NumberOfVariables()) + end + Test.@testset "Test length ListOfVariableIndices" begin + Test.@test length(a_x) == length(b_x) + end # A dictionary that maps things from `b`-space to `a`-space. x_map = Dict(bx => a_x[i] for (i, bx) in enumerate(b_x)) # To check that the constraints, we need to first cache all of the # constraints in `a`. constraints = Dict{Any,Any}() - Test.@testset "$F-in-$S" for (F, S) in MOI.get(a, MOI.ListOfConstraintTypesPresent()) + a_constraint_types = MOI.get(a, MOI.ListOfConstraintTypesPresent()) + b_constraint_types = MOI.get(b, MOI.ListOfConstraintTypesPresent()) + Test.@testset "get $F and $S" for (F, S) in a_constraint_types Test.@test MOI.supports_constraint(a, F, S) constraints[(F, S)] = map(MOI.get(a, MOI.ListOfConstraintIndices{F,S}())) do ci @@ -170,20 +176,16 @@ function _test_structural_identical( MOI.get(a, MOI.ConstraintSet(), ci), ) end - end - # Now compare the constraints in `b` with the cache in `constraints`. - b_constraint_types = MOI.get(b, MOI.ListOfConstraintTypesPresent()) - # There may be constraint types reported in `a` that are not in `b`, but - # have zero constraints in `a`. - Test.@testset "$F-in-$S" for (F, S) in keys(constraints) - attr = MOI.NumberOfConstraints{F,S}() - Test.@test (F, S) in b_constraint_types || MOI.get(a, attr) == 0 + # There may be constraint types reported in `a` that are not in `b`, but + # have zero constraints in `a`. + Test.@test (F, S) in b_constraint_types || + MOI.get(a, MOI.NumberOfConstraints{F,S}()) == 0 end Test.@testset "$F-in-$S" for (F, S) in b_constraint_types Test.@test haskey(constraints, (F, S)) # Check that the same number of constraints are present - attr = MOI.NumberOfConstraints{F,S}() - Test.@test MOI.get(a, attr) == MOI.get(b, attr) + Test.@test MOI.get(a, MOI.NumberOfConstraints{F,S}()) == + MOI.get(b, MOI.NumberOfConstraints{F,S}()) # Check that supports_constraint is implemented Test.@test MOI.supports_constraint(b, F, S) # Check that each function in `b` matches a function in `a` @@ -206,7 +208,9 @@ function _test_structural_identical( # Test model attributes are set, like ObjectiveSense and ObjectiveFunction. a_attrs = MOI.get(a, MOI.ListOfModelAttributesSet()) b_attrs = MOI.get(b, MOI.ListOfModelAttributesSet()) - Test.@test length(a_attrs) == length(b_attrs) + Test.@testset "Test length ListOfModelAttributesSet" begin + Test.@test length(a_attrs) == length(b_attrs) + end Test.@testset "$attr" for attr in b_attrs Test.@test attr in a_attrs if attr == MOI.ObjectiveSense() @@ -259,7 +263,7 @@ and [`MOI.ConstraintPrimalStart`](@ref) to throw [`MOI.GetAttributeNotAllowed`]( ## Example -```jldoctest; setup=:(import MathOptInterface as MOI) +```jldoctest; setup=:(import MathOptInterface as MOI), filter="[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.ZeroOneBridge, model -> MOI.add_constrained_variable(model, MOI.ZeroOne()), @@ -268,9 +272,18 @@ julia> MOI.Bridges.runtests( MOI.add_constraint(model, 1.0 * x, MOI.Interval(0.0, 1.0)) end, ) +Test Summary: | Pass Total Time +Bridges.runtests | 32 32 0.8s ``` """ -function runtests( +function runtests(args...; kwargs...) + Test.@testset "Bridges.runtests" begin + _runtests(args...; kwargs...) + end + return +end + +function _runtests( Bridge::Type{<:AbstractBridge}, input_fn::Function, output_fn::Function; @@ -290,49 +303,52 @@ function runtests( if print_inner_model print(inner) end - # Load a non-bridged input model, and check that getters are the same. - test = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) - input_fn(test) - Test.@testset "Outer model" begin + Test.@testset "Test outer bridged model appears like the input" begin + test = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) + input_fn(test) _test_structural_identical( test, model; cannot_unbridge = cannot_unbridge, ) end - # Load a bridged target model, and check that getters are the same. - target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) - output_fn(target) - Test.@testset "Inner model" begin + Test.@testset "Test inner bridged model appears like the target" begin + target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) + output_fn(target) _test_structural_identical(target, inner) end - # Test VariablePrimalStart - attr = MOI.VariablePrimalStart() - bridge_supported = all(values(Variable.bridges(model))) do bridge - return MOI.supports(model, attr, typeof(bridge)) - end - if MOI.supports(model, attr, MOI.VariableIndex) && bridge_supported - x = MOI.get(model, MOI.ListOfVariableIndices()) - MOI.set(model, attr, x, fill(nothing, length(x))) - Test.@test all(isnothing, MOI.get(model, attr, x)) - primal_start = fill(variable_start, length(x)) - MOI.set(model, attr, x, primal_start) - if !isempty(x) - # ≈ does not work if x is empty because the return of get is Any[] - Test.@test MOI.get(model, attr, x) ≈ primal_start + Test.@testset "Test MOI.VariablePrimalStart" begin + attr = MOI.VariablePrimalStart() + bridge_supported = all(values(Variable.bridges(model))) do bridge + return MOI.supports(model, attr, typeof(bridge)) end - end - # Test ConstraintPrimalStart and ConstraintDualStart - for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) - for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) - set = try - MOI.get(model, MOI.ConstraintSet(), ci) - catch err - _runtests_error_handler(err, cannot_unbridge) - continue + if MOI.supports(model, attr, MOI.VariableIndex) && bridge_supported + x = MOI.get(model, MOI.ListOfVariableIndices()) + MOI.set(model, attr, x, fill(nothing, length(x))) + Test.@test all(isnothing, MOI.get(model, attr, x)) + primal_start = fill(variable_start, length(x)) + MOI.set(model, attr, x, primal_start) + if !isempty(x) + # ≈ does not work if x is empty because the return of get is Any[] + Test.@test MOI.get(model, attr, x) ≈ primal_start end - for attr in (MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()) - if MOI.supports(model, attr, MOI.ConstraintIndex{F,S}) + end + end + Test.@testset "Test ConstraintPrimalStart and ConstraintDualStart" begin + list_of_constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent()) + Test.@testset "$F-in-$S" for (F, S) in list_of_constraints + for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + set = try + MOI.get(model, MOI.ConstraintSet(), ci) + catch err + _runtests_error_handler(err, cannot_unbridge) + continue + end + attrs = (MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()) + Test.@testset "$attr" for attr in attrs + if !MOI.supports(model, attr, MOI.ConstraintIndex{F,S}) + continue + end MOI.set(model, attr, ci, nothing) Test.@test MOI.get(model, attr, ci) === nothing start = _fake_start(constraint_start, set) @@ -340,8 +356,8 @@ function runtests( returned_start = try MOI.get(model, attr, ci) catch err - # For a Constraint bridge for which the map is not invertible, the constraint primal cannot - # be inverted + # For a Constraint bridge for which the map is not + # invertible, the constraint primal cannot be inverted _runtests_error_handler( err, Bridge <: MOI.Bridges.Constraint.AbstractBridge && @@ -354,17 +370,26 @@ function runtests( end end end - # Test other bridge functions - for b in values(Constraint.bridges(model)) - _general_bridge_tests(something(b)) - end - for b in values(Objective.bridges(model)) - _general_bridge_tests(something(b)) + Test.@testset "Test general bridge tests" begin + Test.@testset "Constraint" begin + for b in values(Constraint.bridges(model)) + _general_bridge_tests(something(b)) + end + end + Test.@testset "Objective" begin + for b in values(Objective.bridges(model)) + _general_bridge_tests(something(b)) + end + end + Test.@testset "Variable" begin + for b in values(Variable.bridges(model)) + _general_bridge_tests(something(b)) + end + end end - for b in values(Variable.bridges(model)) - _general_bridge_tests(something(b)) + Test.@testset "Test delete" begin + _test_delete(Bridge, model, inner) end - _test_delete(Bridge, model, inner) return end @@ -385,7 +410,7 @@ Run a series of tests that check the correctness of `Bridge`. ## Example -```jldoctest; setup=:(import MathOptInterface as MOI) +```jldoctest; setup=:(import MathOptInterface as MOI), filter="[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.ZeroOneBridge, \"\"\" @@ -398,6 +423,8 @@ julia> MOI.Bridges.runtests( 1.0 * x in Interval(0.0, 1.0) \"\"\", ) +Test Summary: | Pass Total Time +Bridges.runtests | 32 32 0.0s ``` """ function runtests( From 48f36dcbbfd07f58b64aacc351b9064d9d0ec8fb Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 16 Apr 2025 09:51:02 +1200 Subject: [PATCH 3/4] update --- docs/src/submodules/Bridges/implementation.md | 4 ++-- src/Bridges/Bridges.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/submodules/Bridges/implementation.md b/docs/src/submodules/Bridges/implementation.md index 5d7734e089..f0ef9d7141 100644 --- a/docs/src/submodules/Bridges/implementation.md +++ b/docs/src/submodules/Bridges/implementation.md @@ -66,7 +66,7 @@ arguments: the type of the bridge, the input model as a string, and the output model as a string. Here is an example: -```jldoctest; filter="[0-9.]+s" +```jldoctest; filter=r"[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.GreaterToLessBridge, """ @@ -100,7 +100,7 @@ There are a number of other useful keyword arguments. Here is an example: -```jldoctest; filter="[0-9.]+s" +```jldoctest; filter=r"[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.GreaterToLessBridge, """ diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index 20990018c7..ae0b51ad11 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -263,7 +263,7 @@ and [`MOI.ConstraintPrimalStart`](@ref) to throw [`MOI.GetAttributeNotAllowed`]( ## Example -```jldoctest; setup=:(import MathOptInterface as MOI), filter="[0-9.]+s" +```jldoctest; setup=:(import MathOptInterface as MOI), filter=r"[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.ZeroOneBridge, model -> MOI.add_constrained_variable(model, MOI.ZeroOne()), @@ -410,7 +410,7 @@ Run a series of tests that check the correctness of `Bridge`. ## Example -```jldoctest; setup=:(import MathOptInterface as MOI), filter="[0-9.]+s" +```jldoctest; setup=:(import MathOptInterface as MOI), filter=r"[0-9.]+s" julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.ZeroOneBridge, \"\"\" From 2b5f64163bad33dd928e9d048967c564e97415fb Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 16 Apr 2025 10:37:25 +1200 Subject: [PATCH 4/4] Update --- src/Bridges/Bridges.jl | 43 +++++++++++++++++++++++++----------------- src/Test/Test.jl | 2 +- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index ae0b51ad11..9e742c8517 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -4,6 +4,15 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. +# !!! info "COV_EXCL_LINE" +# +# The Julia coverage check is not perfect, particularly when it comes to +# macros that produce code that is not executed. To work around +# false-negatives, some lines in this file are excluded from coverage with +# `# COV_EXCL_LINE`. (In most of the excluded cases, the default is for the +# tests to pass, so the failure case of the testset macro is not executed, +# and so no code is executed that can be tied back to the excluded lines. + module Bridges import MathOptInterface as MOI @@ -153,11 +162,11 @@ function _test_structural_identical( # the variables are added in the same order to both models. a_x = MOI.get(a, MOI.ListOfVariableIndices()) b_x = MOI.get(b, MOI.ListOfVariableIndices()) - Test.@testset "Test NumberOfVariables" begin + Test.@testset "Test NumberOfVariables" begin # COV_EXCL_LINE Test.@test MOI.get(a, MOI.NumberOfVariables()) == MOI.get(b, MOI.NumberOfVariables()) end - Test.@testset "Test length ListOfVariableIndices" begin + Test.@testset "Test length ListOfVariableIndices" begin # COV_EXCL_LINE Test.@test length(a_x) == length(b_x) end # A dictionary that maps things from `b`-space to `a`-space. @@ -180,7 +189,7 @@ function _test_structural_identical( # have zero constraints in `a`. Test.@test (F, S) in b_constraint_types || MOI.get(a, MOI.NumberOfConstraints{F,S}()) == 0 - end + end # COV_EXCL_LINE Test.@testset "$F-in-$S" for (F, S) in b_constraint_types Test.@test haskey(constraints, (F, S)) # Check that the same number of constraints are present @@ -204,11 +213,11 @@ function _test_structural_identical( return s_b == s && isapprox(f, f_b) && typeof(f) == typeof(f_b) end end - end + end # COV_EXCL_LINE # Test model attributes are set, like ObjectiveSense and ObjectiveFunction. a_attrs = MOI.get(a, MOI.ListOfModelAttributesSet()) b_attrs = MOI.get(b, MOI.ListOfModelAttributesSet()) - Test.@testset "Test length ListOfModelAttributesSet" begin + Test.@testset "Test length ListOfModelAttributesSet" begin # COV_EXCL_LINE Test.@test length(a_attrs) == length(b_attrs) end Test.@testset "$attr" for attr in b_attrs @@ -220,7 +229,7 @@ function _test_structural_identical( attr_b = MOI.Utilities.map_indices(x_map, MOI.get(b, attr)) Test.@test isapprox(MOI.get(a, attr), attr_b) end - end + end # COV_EXCL_LINE return end @@ -303,7 +312,7 @@ function _runtests( if print_inner_model print(inner) end - Test.@testset "Test outer bridged model appears like the input" begin + Test.@testset "Test outer bridged model appears like the input" begin # COV_EXCL_LINE test = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) input_fn(test) _test_structural_identical( @@ -312,12 +321,12 @@ function _runtests( cannot_unbridge = cannot_unbridge, ) end - Test.@testset "Test inner bridged model appears like the target" begin + Test.@testset "Test inner bridged model appears like the target" begin # COV_EXCL_LINE target = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{eltype}()) output_fn(target) _test_structural_identical(target, inner) end - Test.@testset "Test MOI.VariablePrimalStart" begin + Test.@testset "Test MOI.VariablePrimalStart" begin # COV_EXCL_LINE attr = MOI.VariablePrimalStart() bridge_supported = all(values(Variable.bridges(model))) do bridge return MOI.supports(model, attr, typeof(bridge)) @@ -334,7 +343,7 @@ function _runtests( end end end - Test.@testset "Test ConstraintPrimalStart and ConstraintDualStart" begin + Test.@testset "Test ConstraintPrimalStart and ConstraintDualStart" begin # COV_EXCL_LINE list_of_constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent()) Test.@testset "$F-in-$S" for (F, S) in list_of_constraints for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) @@ -366,28 +375,28 @@ function _runtests( continue end Test.@test returned_start ≈ start - end + end # COV_EXCL_LINE end - end + end # COV_EXCL_LINE end - Test.@testset "Test general bridge tests" begin - Test.@testset "Constraint" begin + Test.@testset "Test general bridge tests" begin # COV_EXCL_LINE + Test.@testset "Constraint" begin # COV_EXCL_LINE for b in values(Constraint.bridges(model)) _general_bridge_tests(something(b)) end end - Test.@testset "Objective" begin + Test.@testset "Objective" begin # COV_EXCL_LINE for b in values(Objective.bridges(model)) _general_bridge_tests(something(b)) end end - Test.@testset "Variable" begin + Test.@testset "Variable" begin # COV_EXCL_LINE for b in values(Variable.bridges(model)) _general_bridge_tests(something(b)) end end end - Test.@testset "Test delete" begin + Test.@testset "Test delete" begin # COV_EXCL_LINE _test_delete(Bridge, model, inner) end return diff --git a/src/Test/Test.jl b/src/Test/Test.jl index fdfcacd6e0..00f0f9be0b 100644 --- a/src/Test/Test.jl +++ b/src/Test/Test.jl @@ -263,7 +263,7 @@ function runtests( end continue end - @testset "$(name)" begin + @testset "$(name)" begin # COV_EXCL_LINE c = copy(config) tear_down = setup_test(test_function, model, c) # Make sure to empty the model before every test.