Skip to content

Commit 5ad80db

Browse files
giordanoclaude
andauthored
Add comprehensive tests for previously uncovered functionality (#118)
* Add comprehensive tests for previously uncovered functionality Cover parse_args, filter_tests!, extract_flag!, find_tests edge cases, get_max_worker_rss, test_exe, non-verbose mode, positional filtering, addworkers, multi-job parallelism, worker RSS recycling, mixed pass/fail, and empty test suite. Co-authored-by: Claude <noreply@anthropic.com> Made-with: Cursor --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ee40af5 commit 5ad80db

1 file changed

Lines changed: 279 additions & 0 deletions

File tree

test/runtests.jl

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,283 @@ end
496496
@test all(!Base.process_running, procs)
497497
end
498498

499+
# ── Unit tests for internal helpers ──────────────────────────────────────────
500+
501+
@testset "extract_flag!" begin
502+
args = ["--verbose", "--jobs=4", "test1"]
503+
result = ParallelTestRunner.extract_flag!(args, "--verbose")
504+
@test result === Some(nothing)
505+
@test args == ["--jobs=4", "test1"]
506+
507+
args = ["--verbose", "--jobs=4", "test1"]
508+
result = ParallelTestRunner.extract_flag!(args, "--jobs"; typ=Int)
509+
@test something(result) == 4
510+
@test args == ["--verbose", "test1"]
511+
512+
args = ["--verbose", "test1"]
513+
result = ParallelTestRunner.extract_flag!(args, "--jobs")
514+
@test result === nothing
515+
@test args == ["--verbose", "test1"]
516+
517+
args = ["--format=json"]
518+
result = ParallelTestRunner.extract_flag!(args, "--format")
519+
@test something(result) == "json"
520+
@test isempty(args)
521+
end
522+
523+
@testset "parse_args" begin
524+
@testset "individual flags" begin
525+
args = parse_args(["--verbose"])
526+
@test args.verbose !== nothing
527+
@test args.jobs === nothing
528+
@test args.quickfail === nothing
529+
@test args.list === nothing
530+
@test isempty(args.positionals)
531+
532+
args = parse_args(["--jobs=4"])
533+
@test something(args.jobs) == 4
534+
@test args.verbose === nothing
535+
536+
args = parse_args(["--quickfail"])
537+
@test args.quickfail !== nothing
538+
@test args.verbose === nothing
539+
540+
args = parse_args(["--list"])
541+
@test args.list !== nothing
542+
end
543+
544+
@testset "combined flags" begin
545+
args = parse_args(["--verbose", "--quickfail", "--jobs=2"])
546+
@test args.verbose !== nothing
547+
@test args.quickfail !== nothing
548+
@test something(args.jobs) == 2
549+
end
550+
551+
@testset "positional arguments" begin
552+
args = parse_args(["--verbose", "basic", "subdir"])
553+
@test args.verbose !== nothing
554+
@test args.positionals == ["basic", "subdir"]
555+
556+
args = parse_args(["test1", "test2"])
557+
@test args.positionals == ["test1", "test2"]
558+
end
559+
560+
@testset "custom arguments" begin
561+
args = parse_args(["--gpu", "--nocuda"]; custom=["gpu", "nocuda", "other"])
562+
@test args.custom["gpu"] !== nothing
563+
@test args.custom["nocuda"] !== nothing
564+
@test args.custom["other"] === nothing
565+
end
566+
567+
@testset "unknown flags" begin
568+
@test_throws ErrorException parse_args(["--unknown-flag"])
569+
@test_throws ErrorException parse_args(["--verbose", "--bogus"])
570+
end
571+
572+
@testset "no arguments" begin
573+
args = parse_args(String[])
574+
@test args.jobs === nothing
575+
@test args.verbose === nothing
576+
@test args.quickfail === nothing
577+
@test args.list === nothing
578+
@test isempty(args.positionals)
579+
@test isempty(args.custom)
580+
end
581+
end
582+
583+
@testset "filter_tests!" begin
584+
@testset "empty positionals preserves all tests" begin
585+
testsuite = Dict("a" => :(), "b" => :(), "c" => :())
586+
args = parse_args(String[])
587+
@test filter_tests!(testsuite, args) == true
588+
@test length(testsuite) == 3
589+
end
590+
591+
@testset "startswith matching" begin
592+
testsuite = Dict("basic" => :(), "advanced" => :(), "basic_extra" => :())
593+
args = parse_args(["basic"])
594+
@test filter_tests!(testsuite, args) == false
595+
@test haskey(testsuite, "basic")
596+
@test haskey(testsuite, "basic_extra")
597+
@test !haskey(testsuite, "advanced")
598+
end
599+
600+
@testset "multiple positional filters" begin
601+
testsuite = Dict("unit/a" => :(), "unit/b" => :(), "integration/c" => :(), "perf/d" => :())
602+
args = parse_args(["unit", "integration"])
603+
@test filter_tests!(testsuite, args) == false
604+
@test haskey(testsuite, "unit/a")
605+
@test haskey(testsuite, "unit/b")
606+
@test haskey(testsuite, "integration/c")
607+
@test !haskey(testsuite, "perf/d")
608+
end
609+
610+
@testset "no matches yields empty suite" begin
611+
testsuite = Dict("a" => :(), "b" => :())
612+
args = parse_args(["nonexistent"])
613+
@test filter_tests!(testsuite, args) == false
614+
@test isempty(testsuite)
615+
end
616+
end
617+
618+
@testset "find_tests edge cases" begin
619+
@testset "empty directory" begin
620+
mktempdir() do dir
621+
@test isempty(find_tests(dir))
622+
end
623+
end
624+
625+
@testset "only runtests.jl" begin
626+
mktempdir() do dir
627+
write(joinpath(dir, "runtests.jl"), "@test true")
628+
@test isempty(find_tests(dir))
629+
end
630+
end
631+
632+
@testset "nested subdirectories" begin
633+
mktempdir() do dir
634+
mkpath(joinpath(dir, "a", "b"))
635+
write(joinpath(dir, "test1.jl"), "@test true")
636+
write(joinpath(dir, "a", "test2.jl"), "@test true")
637+
write(joinpath(dir, "a", "b", "test3.jl"), "@test true")
638+
ts = find_tests(dir)
639+
@test length(ts) == 3
640+
@test haskey(ts, "test1")
641+
@test haskey(ts, "a/test2")
642+
@test haskey(ts, "a/b/test3")
643+
end
644+
end
645+
646+
@testset "non-.jl files ignored" begin
647+
mktempdir() do dir
648+
write(joinpath(dir, "test.jl"), "@test true")
649+
write(joinpath(dir, "readme.md"), "# Readme")
650+
write(joinpath(dir, "data.csv"), "1,2,3")
651+
ts = find_tests(dir)
652+
@test length(ts) == 1
653+
@test haskey(ts, "test")
654+
end
655+
end
656+
end
657+
658+
@testset "get_max_worker_rss" begin
659+
rss = withenv("JULIA_TEST_MAXRSS_MB" => nothing) do
660+
ParallelTestRunner.get_max_worker_rss()
661+
end
662+
@test rss > 0
663+
664+
rss = withenv("JULIA_TEST_MAXRSS_MB" => "1024") do
665+
ParallelTestRunner.get_max_worker_rss()
666+
end
667+
@test rss == 1024 * 2^20
668+
end
669+
670+
@testset "test_exe" begin
671+
exe = ParallelTestRunner.test_exe(false)
672+
@test any(contains("--color=no"), exe.exec)
673+
@test any(contains("--project="), exe.exec)
674+
675+
exe = ParallelTestRunner.test_exe(true)
676+
@test any(contains("--color=yes"), exe.exec)
677+
end
678+
679+
# ── Integration tests ────────────────────────────────────────────────────────
680+
681+
@testset "non-verbose mode" begin
682+
testsuite = Dict("quiet" => quote @test true end)
683+
io = IOBuffer()
684+
runtests(ParallelTestRunner, String[]; testsuite, stdout=io, stderr=io)
685+
str = String(take!(io))
686+
@test !contains(str, "started at")
687+
@test !contains(str, "Available memory:")
688+
@test contains(str, "SUCCESS")
689+
end
690+
691+
@testset "positional filter end-to-end" begin
692+
testsuite = Dict(
693+
"unit/math" => :( @test 1 + 1 == 2 ),
694+
"unit/string" => :( @test "a" * "b" == "ab" ),
695+
"integration/api" => :( @test true ),
696+
)
697+
io = IOBuffer()
698+
runtests(ParallelTestRunner, ["unit"]; testsuite, stdout=io, stderr=io)
699+
str = String(take!(io))
700+
@test contains(str, "Running 2 tests")
701+
@test contains(str, "SUCCESS")
702+
end
703+
704+
@testset "addworkers" begin
705+
workers = addworkers(2)
706+
@test length(workers) == 2
707+
@test all(w -> w isa ParallelTestRunner.PTRWorker, workers)
708+
@test all(w -> Base.process_running(w.w.proc), workers)
709+
for w in workers
710+
ParallelTestRunner.Malt.stop(w)
711+
end
712+
sleep(0.5)
713+
@test all(w -> !Base.process_running(w.w.proc), workers)
714+
end
715+
716+
@testset "multiple tests multiple jobs" begin
717+
testsuite = Dict(
718+
"m1" => :( @test 1 + 1 == 2 ),
719+
"m2" => :( @test 2 + 2 == 4 ),
720+
"m3" => :( @test 3 + 3 == 6 ),
721+
"m4" => :( @test 4 + 4 == 8 ),
722+
)
723+
io = IOBuffer()
724+
runtests(ParallelTestRunner, ["--jobs=2"]; testsuite, stdout=io, stderr=io)
725+
str = String(take!(io))
726+
@test contains(str, "Running 4 tests using 2 parallel jobs")
727+
@test contains(str, "SUCCESS")
728+
end
729+
730+
@testset "worker RSS recycling" begin
731+
testsuite = Dict(
732+
"alloc1" => :( @test true ),
733+
"alloc2" => :( @test true ),
734+
"alloc3" => :( @test true ),
735+
"alloc4" => :( @test true ),
736+
)
737+
io = IOBuffer()
738+
old_id_counter = ParallelTestRunner.ID_COUNTER[]
739+
runtests(ParallelTestRunner, ["--jobs=1"]; testsuite, stdout=io, stderr=io, max_worker_rss=0)
740+
str = String(take!(io))
741+
@test contains(str, "SUCCESS")
742+
@test ParallelTestRunner.ID_COUNTER[] == old_id_counter + length(testsuite)
743+
end
744+
745+
@testset "mixed pass and fail" begin
746+
testsuite = Dict(
747+
"passes" => quote
748+
@test true
749+
@test 1 + 1 == 2
750+
end,
751+
"also_passes" => quote
752+
@test true
753+
end,
754+
"fails" => quote
755+
@test false
756+
end,
757+
)
758+
io = IOBuffer()
759+
@test_throws Test.FallbackTestSetException begin
760+
runtests(ParallelTestRunner, String[]; testsuite, stdout=io, stderr=io)
761+
end
762+
str = String(take!(io))
763+
@test contains(str, "FAILURE")
764+
@test contains(str, "passes")
765+
@test contains(str, "also_passes")
766+
@test contains(str, "fails")
767+
end
768+
769+
@testset "empty test suite" begin
770+
testsuite = Dict{String,Expr}()
771+
io = IOBuffer()
772+
runtests(ParallelTestRunner, String[]; testsuite, stdout=io, stderr=io)
773+
str = String(take!(io))
774+
@test contains(str, "Running 0 tests")
775+
@test contains(str, "SUCCESS")
776+
end
777+
499778
end

0 commit comments

Comments
 (0)