@@ -496,4 +496,283 @@ end
496496 @test all (! Base. process_running, procs)
497497end
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+
499778end
0 commit comments