@@ -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+
378593end
0 commit comments