Skip to content

Commit 591725b

Browse files
authored
Merge pull request #11 from rafabench/full-coverage
Add comprehensive tests for full code coverage
2 parents 1a71cad + f680b5e commit 591725b

1 file changed

Lines changed: 216 additions & 1 deletion

File tree

‎test/runtests.jl‎

Lines changed: 216 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,46 @@ const BIGLP2_MPS = joinpath(@__DIR__, "models", "modelbiglp2.mps")
322322
sort_model(dst)
323323
sorted_file = dst * ".sorted"
324324
@test isfile(sorted_file)
325-
# Verify sorted file has content
326325
content = read(sorted_file, String)
327326
@test !isempty(content)
328327
@test occursin("MINIMIZE", content) || occursin("MAXIMIZE", content)
328+
@test occursin("SUBJECT TO:", content)
329+
@test occursin("BOUNDS:", content)
330+
@test occursin("End", content)
331+
332+
# Verify constraint names in SUBJECT TO section are sorted
333+
lines = split(content, "\n")
334+
subj_start = findfirst(l -> occursin("SUBJECT TO:", l), lines)
335+
bounds_start = findfirst(l -> occursin("BOUNDS:", l), lines)
336+
if subj_start !== nothing && bounds_start !== nothing
337+
con_lines = lines[subj_start+1:bounds_start-1]
338+
con_names = String[]
339+
for l in con_lines
340+
cm = match(r"^(\S+):", strip(l))
341+
if cm !== nothing
342+
push!(con_names, cm.captures[1])
343+
end
344+
end
345+
if !isempty(con_names)
346+
@test con_names == sort(con_names)
347+
end
348+
end
349+
end
350+
351+
@testset "sort_model with MPS" begin
352+
mktempdir() do dir
353+
src = MODEL1_MPS
354+
dst = joinpath(dir, "model1.mps")
355+
# sort_model writes .sorted in LP format regardless
356+
# Just copy as .lp to sort
357+
dst_lp = joinpath(dir, "model1.lp")
358+
cp(MODEL1_LP, dst_lp)
359+
sort_model(dst_lp)
360+
sorted_file = dst_lp * ".sorted"
361+
@test isfile(sorted_file)
362+
content = read(sorted_file, String)
363+
@test !isempty(content)
364+
end
329365
end
330366
end
331367

@@ -375,4 +411,183 @@ const BIGLP2_MPS = joinpath(@__DIR__, "models", "modelbiglp2.mps")
375411
@test ModelCompare.constraint_set_to_bound(MOI.Interval(2.0, 8.0)) == (2.0, 8.0)
376412
end
377413

414+
@testset "remove_quotes" begin
415+
@test ModelCompare.remove_quotes("hello") == "hello"
416+
@test ModelCompare.remove_quotes("\"quoted\"") == "quoted"
417+
@test ModelCompare.remove_quotes("no\"quotes\"here") == "noquoteshere"
418+
end
419+
420+
@testset "printdiff — one_by_one=false branches" begin
421+
m1 = ModelCompare.readmodel(MODEL1_LP)
422+
m2 = ModelCompare.readmodel(MODEL2_LP)
423+
424+
@testset "BoundsDiff one_by_one=false" begin
425+
bdiff = compare_bounds(m1, m2; tol = 0.0)
426+
io = IOBuffer()
427+
ModelCompare.printdiff(io, bdiff; one_by_one = false)
428+
output = String(take!(io))
429+
@test occursin("VARIABLE BOUNDS", output)
430+
@test occursin("MODEL 1:", output)
431+
@test occursin("MODEL 2:", output)
432+
end
433+
434+
@testset "ObjectiveDiff one_by_one=false" begin
435+
odiff = compare_objective(m1, m2; tol = 0.0)
436+
io = IOBuffer()
437+
ModelCompare.printdiff(io, odiff; one_by_one = false)
438+
output = String(take!(io))
439+
@test occursin("OBJECTIVE", output)
440+
@test occursin("MODEL 1:", output)
441+
@test occursin("MODEL 2:", output)
442+
end
443+
444+
@testset "ExpressionDiff one_by_one=false" begin
445+
attr1 = MOI.get(m1, MOI.ObjectiveFunctionType())
446+
attr2 = MOI.get(m2, MOI.ObjectiveFunctionType())
447+
obj1 = MOI.get(m1, MOI.ObjectiveFunction{attr1}())
448+
obj2 = MOI.get(m2, MOI.ObjectiveFunction{attr2}())
449+
ediff = compare_expressions(obj1, obj2, m1, m2; tol = 0.0)
450+
io = IOBuffer()
451+
ModelCompare.printdiff(io, ediff, "OBJECTIVE"; one_by_one = false)
452+
output = String(take!(io))
453+
@test occursin("MODEL 1:", output)
454+
@test occursin("MODEL 2:", output)
455+
end
456+
457+
@testset "ExpressionDiff with constraint name" begin
458+
attr1 = MOI.get(m1, MOI.ObjectiveFunctionType())
459+
attr2 = MOI.get(m2, MOI.ObjectiveFunctionType())
460+
obj1 = MOI.get(m1, MOI.ObjectiveFunction{attr1}())
461+
obj2 = MOI.get(m2, MOI.ObjectiveFunction{attr2}())
462+
ediff = compare_expressions(obj1, obj2, m1, m2; tol = 0.0)
463+
io = IOBuffer()
464+
ModelCompare.printdiff(io, ediff, "my_constraint"; one_by_one = true)
465+
output = String(take!(io))
466+
@test occursin("CONSTRAINT: my_constraint", output)
467+
end
468+
469+
@testset "ConstraintElementsDiff one_by_one=false" begin
470+
cdiff = compare_constraints(m1, m2; tol = 0.0)
471+
io = IOBuffer()
472+
ModelCompare.printdiff(io, cdiff; one_by_one = false)
473+
output = String(take!(io))
474+
@test occursin("CONSTRAINTS", output)
475+
end
476+
end
477+
478+
@testset "printdiff — VariablesDiff identical (empty diffs)" begin
479+
m1 = ModelCompare.readmodel(MODEL1_LP)
480+
vdiff = compare_variables(m1, m1)
481+
io = IOBuffer()
482+
ModelCompare.printdiff(io, vdiff)
483+
output = String(take!(io))
484+
# When both only_one and only_two are empty, no header is printed
485+
@test !occursin("VARIABLE NAMES", output)
486+
end
487+
488+
@testset "printdiff — ObjectiveDiff different senses" begin
489+
# Build a fake ObjectiveDiff with different senses
490+
ediff = ModelCompare.ExpressionDiff(String[], Dict{String,Tuple{Float64,Float64}}(), Dict{String,Float64}(), Dict{String,Float64}())
491+
odiff = ModelCompare.ObjectiveDiff((MOI.MAX_SENSE, MOI.MIN_SENSE), ediff)
492+
io = IOBuffer()
493+
ModelCompare.printdiff(io, odiff; one_by_one = true)
494+
output = String(take!(io))
495+
@test occursin("OBJECTIVE SENSES ARE DIFFERENT", output)
496+
@test occursin("MODEL 1:", output)
497+
@test occursin("MODEL 2:", output)
498+
end
499+
500+
@testset "printdiff — ConstraintElementsDiff with first-only constraints" begin
501+
# Compare model2 vs model1 (reversed) so that model1-unique constraints appear in .first
502+
m1 = ModelCompare.readmodel(MODEL2_LP)
503+
m2 = ModelCompare.readmodel(MODEL1_LP)
504+
cdiff = compare_constraints(m1, m2; tol = 0.0)
505+
io = IOBuffer()
506+
ModelCompare.printdiff(io, cdiff; one_by_one = true)
507+
output = String(take!(io))
508+
@test occursin("MODEL 1:", output)
509+
end
510+
511+
@testset "compare — string path overload" begin
512+
result = ModelCompare.compare(MODEL1_LP, MODEL2_LP; tol = 0.0)
513+
@test result isa NamedTuple
514+
@test haskey(result, :variables)
515+
@test haskey(result, :bounds)
516+
@test haskey(result, :objective)
517+
@test haskey(result, :constraints)
518+
end
519+
520+
@testset "compare_models — verbose separate_files" begin
521+
mktempdir() do dir
522+
outfile = joinpath(dir, "compare.txt")
523+
compare_models(MODEL1_LP, MODEL2_LP;
524+
outfile = outfile, tol = 0.0, separate_files = true, verbose = true)
525+
@test isfile(joinpath(dir, "compare_variables.txt"))
526+
@test isfile(joinpath(dir, "compare_bounds.txt"))
527+
@test isfile(joinpath(dir, "compare_objective.txt"))
528+
@test isfile(joinpath(dir, "compare_constraints.txt"))
529+
end
530+
end
531+
532+
@testset "parse_commandline" begin
533+
args = ["--file1", "a.lp", "--file2", "b.lp", "-t", "0.01", "-v", "--different-files", "-o", "out.txt"]
534+
parsed = ModelCompare.parse_commandline(args)
535+
@test parsed["file1"] == "a.lp"
536+
@test parsed["file2"] == "b.lp"
537+
@test parsed["tol"] == 0.01
538+
@test parsed["verbose"] == true
539+
@test parsed["different-files"] == true
540+
@test parsed["output"] == "out.txt"
541+
end
542+
543+
@testset "parse_commandline — defaults" begin
544+
args = ["--file1", "a.lp", "--file2", "b.lp"]
545+
parsed = ModelCompare.parse_commandline(args)
546+
@test parsed["tol"] == 1e-3
547+
@test parsed["verbose"] == false
548+
@test parsed["different-files"] == false
549+
@test occursin("compare.txt", parsed["output"])
550+
end
551+
552+
@testset "call_compare" begin
553+
mktempdir() do dir
554+
outfile = joinpath(dir, "result.txt")
555+
args = ["--file1", MODEL1_LP, "--file2", MODEL2_LP, "-o", outfile, "-t", "0.0"]
556+
result = ModelCompare.call_compare(args)
557+
@test result isa NamedTuple
558+
@test isfile(outfile)
559+
end
560+
end
561+
562+
@testset "julia_main" begin
563+
mktempdir() do dir
564+
outfile = joinpath(dir, "result.txt")
565+
old_args = copy(ARGS)
566+
empty!(ARGS)
567+
append!(ARGS, ["--file1", MODEL1_LP, "--file2", MODEL2_LP, "-o", outfile, "-t", "0.0"])
568+
ret = ModelCompare.julia_main()
569+
@test ret == 0
570+
@test isfile(outfile)
571+
empty!(ARGS)
572+
append!(ARGS, old_args)
573+
end
574+
end
575+
576+
@testset "variable_names and index_for_name" begin
577+
m = ModelCompare.readmodel(MODEL1_LP)
578+
names = collect(ModelCompare.variable_names(m))
579+
@test !isempty(names)
580+
@test "d" in names
581+
idx_map = ModelCompare.index_for_name(m)
582+
@test haskey(idx_map, "d")
583+
end
584+
585+
@testset "constraint_names and ctr_index_for_name" begin
586+
m = ModelCompare.readmodel(MODEL1_LP)
587+
cnames = collect(ModelCompare.constraint_names(m))
588+
@test !isempty(cnames)
589+
ctr_map = ModelCompare.ctr_index_for_name(m)
590+
@test !isempty(ctr_map)
591+
end
592+
378593
end

0 commit comments

Comments
 (0)