11module ParallelTestRunner
22
3- export runtests, addworkers, addworker
3+ export runtests, addworkers, addworker, find_tests
44
55using Malt
66using Dates
@@ -445,9 +445,51 @@ function addworker(; env=Vector{Pair{String, String}}())
445445end
446446
447447"""
448- runtests(mod::Module, ARGS; RecordType = TestRecord,
449- test_filter = Returns(true),
450- custom_tests = Dict(),
448+ find_tests(dir::String) -> Dict{String, Expr}
449+
450+ Discover test files in a directory and return a test suite dictionary.
451+
452+ Walks through `dir` and finds all `.jl` files (excluding `runtests.jl`), returning a
453+ dictionary mapping test names to expression that include each test file.
454+ """
455+ function find_tests (dir:: String )
456+ tests = Dict {String, Expr} ()
457+ for (rootpath, dirs, files) in walkdir (dir)
458+ # find Julia files
459+ filter! (files) do file
460+ endswith (file, " .jl" ) && file != = " runtests.jl"
461+ end
462+ isempty (files) && continue
463+
464+ # strip extension
465+ files = map (files) do file
466+ file[1 : (end - 3 )]
467+ end
468+
469+ # prepend subdir
470+ subdir = relpath (rootpath, dir)
471+ if subdir != " ."
472+ files = map (files) do file
473+ joinpath (subdir, file)
474+ end
475+ end
476+
477+ # unify path separators
478+ files = map (files) do file
479+ replace (file, path_separator => ' /' )
480+ end
481+
482+ for file in files
483+ path = joinpath (rootpath, file * " .jl" )
484+ tests[file] = :(include ($ path))
485+ end
486+ end
487+ return tests
488+ end
489+
490+ """
491+ runtests(mod::Module, ARGS; testsuite::Dict{String,Expr}=find_tests(pwd()),
492+ RecordType = TestRecord,
451493 init_code = :(),
452494 test_worker = Returns(nothing),
453495 stdout = Base.stdout,
@@ -463,9 +505,9 @@ Run Julia tests in parallel across multiple worker processes.
463505
464506Several keyword arguments are also supported:
465507
508+ - `testsuite`: Dictionary mapping test names to expressions to execute (default: `find_tests(pwd())`).
509+ By default, automatically discovers all `.jl` files in the test directory.
466510- `RecordType`: Type of test record to use for tracking test results (default: `TestRecord`)
467- - `test_filter`: Optional function to filter which tests to run (default: run all tests)
468- - `custom_tests`: Optional dictionary of custom tests, mapping test names to expressions.
469511- `init_code`: Code use to initialize each test's sandbox module (e.g., import auxiliary
470512 packages, define constants, etc).
471513- `test_worker`: Optional function that takes a test name and returns a specific worker.
@@ -494,14 +536,24 @@ Several keyword arguments are also supported:
494536## Examples
495537
496538```julia
497- # Run all tests with default settings
539+ # Run all tests with default settings (auto-discovers .jl files)
498540runtests(MyModule, ARGS)
499541
500542# Run only tests matching "integration"
501543runtests(MyModule, ["integration"])
502544
503- # Run with custom filter function
504- runtests(MyModule, ARGS; test_filter = test -> occursin("unit", test))
545+ # Customize the test suite
546+ testsuite = find_tests(pwd())
547+ delete!(testsuite, "slow_test") # Remove a specific test
548+ runtests(MyModule, ARGS; testsuite)
549+
550+ # Define a custom test suite manually
551+ testsuite = Dict(
552+ "custom" => quote
553+ @test 1 + 1 == 2
554+ end
555+ )
556+ runtests(MyModule, ARGS; testsuite)
505557
506558# Use custom test record type
507559runtests(MyModule, ARGS; RecordType = MyCustomTestRecord)
@@ -512,9 +564,9 @@ runtests(MyModule, ARGS; RecordType = MyCustomTestRecord)
512564Workers are automatically recycled when they exceed memory limits to prevent out-of-memory
513565issues during long test runs. The memory limit is set based on system architecture.
514566"""
515- function runtests (mod:: Module , ARGS ; test_filter = Returns ( true ), RecordType = TestRecord ,
516- custom_tests :: Dict{String, Expr} = Dict {String, Expr} (), init_code = :( ),
517- test_worker = Returns ( nothing ), stdout = Base. stdout , stderr = Base. stderr )
567+ function runtests (mod:: Module , ARGS ; testsuite :: Dict{String,Expr} = find_tests ( pwd ()) ,
568+ RecordType = TestRecord, init_code = : (), test_worker = Returns ( nothing ),
569+ stdout = Base. stdout , stderr = Base. stderr )
518570 #
519571 # set-up
520572 #
@@ -545,51 +597,8 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
545597 error (" Unknown test options `$(join (optlike_args, " " )) ` (try `--help` for usage instructions)" )
546598 end
547599
548- WORKDIR = pwd ()
549-
550- # choose tests
551- tests = []
552- test_runners = Dict ()
553- # # custom tests by the user
554- for (name, runner) in custom_tests
555- push! (tests, name)
556- test_runners[name] = runner
557- end
558- # # files in the test folder
559- for (rootpath, dirs, files) in walkdir (WORKDIR)
560- # find Julia files
561- filter! (files) do file
562- endswith (file, " .jl" ) && file != = " runtests.jl"
563- end
564- isempty (files) && continue
565-
566- # strip extension
567- files = map (files) do file
568- file[1 : (end - 3 )]
569- end
570-
571- # prepend subdir
572- subdir = relpath (rootpath, WORKDIR)
573- if subdir != " ."
574- files = map (files) do file
575- joinpath (subdir, file)
576- end
577- end
578-
579- # unify path separators
580- files = map (files) do file
581- replace (file, path_separator => ' /' )
582- end
583-
584- append! (tests, files)
585- for file in files
586- test_runners[file] = quote
587- include ($ (joinpath (WORKDIR, file * " .jl" )))
588- end
589- end
590- end
591- # # finalize
592- unique! (tests)
600+ # determine test order
601+ tests = collect (keys (testsuite))
593602 Random. shuffle! (tests)
594603 historical_durations = load_test_history (mod)
595604 sort! (tests, by = x -> - get (historical_durations, x, Inf ))
@@ -603,11 +612,8 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
603612 exit (0 )
604613 end
605614
606- # filter tests
607- if isempty (ARGS )
608- filter! (test_filter, tests)
609- else
610- # let the user filter
615+ # filter tests based on command-line arguments
616+ if ! isempty (ARGS )
611617 filter! (tests) do test
612618 any (arg -> startswith (test, arg), ARGS )
613619 end
@@ -834,8 +840,8 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
834840 put! (printer_channel, (:started , test, worker_id (wrkr)))
835841 result = try
836842 Malt. remote_eval_wait (Main, wrkr, :(import ParallelTestRunner))
837- Malt. remote_call_fetch (invokelatest, wrkr, runtest, RecordType, test_runners[test], test,
838- init_code, io_ctx. color)
843+ Malt. remote_call_fetch (invokelatest, wrkr, runtest, RecordType,
844+ testsuite[test], test, init_code, io_ctx. color)
839845 catch ex
840846 if isa (ex, InterruptException)
841847 # the worker got interrupted, signal other tasks to stop
0 commit comments