Skip to content

allow Base and stdlibs in hijack via hijack_base #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
113 changes: 93 additions & 20 deletions src/hijack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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
Expand All @@ -89,41 +100,63 @@ 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
if test ∈ ["show"] && !isdefined(Main, :Distributed)
@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
Expand Down Expand Up @@ -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
12 changes: 12 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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