diff --git a/docs/src/index.md b/docs/src/index.md index 0c89a8f..647726a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -226,8 +226,9 @@ uses `Test`, without modifications: `retest`. - if you have a test file `"testfile.jl"`, try `ReTest.hijack("testfile.jl")` (this will define a fresh module like above). -- `Base` and standard libraries tests can be loaded via the [`ReTest.hijack_base`](@ref) - function. +- `Base` and standard library modules can also be passed to `ReTest.hijack` + (corresponding tests are loaded via the lower level [`ReTest.hijack_base`](@ref) + function). ```@docs ReTest.hijack diff --git a/src/hijack.jl b/src/hijack.jl index 654dc56..11dfc14 100644 --- a/src/hijack.jl +++ b/src/hijack.jl @@ -19,6 +19,10 @@ The current procedure is as follows: 2. apply recursively these two rules for all `include`d files, provided the `include` statement is at the toplevel, and on the content of all modules. + +When `source` is `Base` or a standard library module, a slightly different +mechanism is used to find test files (which can contain e.g. non-toplevel +`include`s), i.e. `ReTest.hijack_base` is used underneath. """ function hijack end @@ -41,11 +45,22 @@ function hijack(path::AbstractString, modname=nothing; parentmodule::Module=Main end function hijack(packagemod::Module, modname=nothing; parentmodule::Module=Main) - path = joinpath(dirname(dirname(pathof(packagemod))), "test", "runtests.jl") + packagepath = pathof(packagemod) + packagepath === nothing && packagemod !== Base && + throw(ArgumentError("$packagemod is not a package")) + if modname === nothing modname = Symbol(packagemod, :Tests) end - hijack(path, modname, parentmodule=parentmodule) + + if packagemod === Base + hijack_base(ChooseTests.BASETESTS, base=modname) + elseif startswith(packagepath, Sys.STDLIB) # packagemod is an STDLIB + hijack_base(string(packagemod), modname, parentmodule=parentmodule) + else + path = joinpath(dirname(dirname(packagepath)), "test", "runtests.jl") + hijack(path, modname, parentmodule=parentmodule) + end end function substitue_retest!(ex) @@ -70,12 +85,8 @@ function substitue_retest!(ex) ex end -const BASETESTS_BLACKLIST = ["backtrace", "misc", "threads", "cmdlineargs","boundscheck", - "SharedArrays", "Test"] -# SharedArrays: problem with workers (should be sortable out) - """ - hijack_base(tests; parentmodule::Module=Main) + hijack_base(tests, [modname]; parentmodule::Module=Main) Similar to `ReTest.hijack`, but specifically for `Base` and stdlib tests. `tests` speficies which test files should be loaded, in the exact same format @@ -89,16 +100,35 @@ then `sometest_` is used instead). Tests corresponding to a standard library `Lib` are loaded in the `StdLibTests.Lib_` module. When there are "testgroups", submodules are created accordingly. + +If `modname` is specified (experimental), this will be the name of the module +in which testsets are defined; passing `modname` is allowed only when all +the loaded tests would otherwise be defined in the same second top-most +module, the one under `BaseTests` or `StdLibTests` (e.g. `somedir` if any, +or `sometest` otherwise, or `Lib_`). This unique module is then named `modname`, +and not enclosed withing `BaseTests` or `StdLibTests`. """ -function hijack_base(tests; parentmodule::Module=Main) +function hijack_base(tests, modname=nothing; parentmodule::Module=Main, + base=:BaseTests, stdlib=:StdLibTests) if isa(tests, AbstractString) tests = split(tests) end tests, = ChooseTests.call_choosetests(tests) # TODO: handle other return values? + allcomponents = (components -> Symbol.(components)).(split.(tests, '/')) + + if modname !== nothing + modname = Symbol(modname) + allequal(components[1] for components in allcomponents) || + throw(ArgumentError("can't specify `modname` for multiple test roots")) + # if modname was passed, it seems nicer to have it fresh, otherwise it might + # contain left-overs from previous incantations (and potentially lead to + # a series of "WARNING: replacing module submodule_i") + @eval parentmodule module $modname end + end - for test in tests - if test ∈ BASETESTS_BLACKLIST + for (test, components) in zip(tests, allcomponents) + if test ∈ ChooseTests.BLACKLIST @warn "skipping \"$test\" test (incompatible with ReTest)" continue end @@ -106,24 +136,27 @@ function hijack_base(tests; parentmodule::Module=Main) @eval Main using Distributed end - components = Symbol.(split(test, '/')) - if string(components[1]) in ChooseTests.STDLIBS - # it's an stdlib Lib, make toplevel modules StdLibTests/Lib_ - components[1] = Symbol(components[1], :_) - pushfirst!(components, :StdLibTests) + if modname === nothing + if string(components[1]) in ChooseTests.STDLIBS + # it's an stdlib Lib, make toplevel modules StdLibTests/Lib_ + components[1] = Symbol(components[1], :_) + pushfirst!(components, Symbol(stdlib)) + else + # it's a Base test, use BaseTests as toplevel module + pushfirst!(components, Symbol(base)) + end else - # it's a Base test, use BaseTests as toplevel module - pushfirst!(components, :BaseTests) + components[1] = modname end mod = parentmodule for (ith, comp) in enumerate(components) - if isdefined(Base, comp) + if isdefined(Base, comp) || comp ∈ ChooseTests.EPONYM_TESTS # e.g. `tuple`, collision betwen the tuple function and test/tuple.jl comp = Symbol(comp, :_) end - - if isdefined(mod, comp) && ith != length(components) + if isdefined(mod, comp) && ith != length(components) || + modname !== nothing && ith == 1 # module already freshly created mod = getfield(mod, comp) else # we always re-eval leaf-modules @@ -169,4 +202,44 @@ function test_path(test) end end +const TESTNAMES = [ + "subarray", "core", "compiler", "worlds", + "keywordargs", "numbers", "subtype", + "char", "strings", "triplequote", "unicode", "intrinsics", + "dict", "hashing", "iobuffer", "staged", "offsetarray", + "arrayops", "tuple", "reduce", "reducedim", "abstractarray", + "intfuncs", "simdloop", "vecelement", "rational", + "bitarray", "copy", "math", "fastmath", "functional", "iterators", + "operators", "ordering", "path", "ccall", "parse", "loading", "gmp", + "sorting", "spawn", "backtrace", "exceptions", + "file", "read", "version", "namedtuple", + "mpfr", "broadcast", "complex", + "floatapprox", "stdlib", "reflection", "regex", "float16", + "combinatorics", "sysinfo", "env", "rounding", "ranges", "mod2pi", + "euler", "show", "client", + "errorshow", "sets", "goto", "llvmcall", "llvmcall2", "ryu", + "some", "meta", "stacktraces", "docs", + "misc", "threads", "stress", "binaryplatforms", "atexit", + "enums", "cmdlineargs", "int", "interpreter", + "checked", "bitset", "floatfuncs", "precompile", + "boundscheck", "error", "ambiguous", "cartesian", "osutils", + "channels", "iostream", "secretbuffer", "specificity", + "reinterpretarray", "syntax", "corelogging", "missing", "asyncmap", + "smallarrayshrink", "opaque_closure" +] + +const BASETESTS = filter(x -> x != "stdlib", TESTNAMES) + +const BLACKLIST = [ + # failing at load time (in hijack_base) + "backtrace", "misc", "threads", "cmdlineargs","boundscheck", + "SharedArrays", # problem with workers (should be sortable out) + "Test", + # failing at runtime (in retest) + "precompile", # no toplevel testset, just one with interpolated subject +] + +# file names which define a global variable of the same name as the file +const EPONYM_TESTS = [:worlds, :file] + end # ChooseTests diff --git a/src/utils.jl b/src/utils.jl index 73db919..d8d8528 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -16,3 +16,15 @@ function recsubmodules(m::Module) pushfirst!(recs, m) recs end + +function allequal(xs) + local val + for x in xs + if !@isdefined(val) + val = x + else + isequal(val, x) || return false + end + end + true +end