From 619f0e0090ef04d380efe3c3fefce9763d35e770 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Fri, 1 May 2026 19:07:02 +0200 Subject: [PATCH 1/5] Fix support for 1.13 In 1.13 the Test stdlib internals changed to use ScopedValues for testsets, which required changing a bit of the ReTest internals. Also fixed some world age warnings. --- InlineTest/src/InlineTest.jl | 10 ++- Project.toml | 9 ++- src/ReTest.jl | 18 +++-- src/hijack.jl | 7 +- src/testset.jl | 124 ++++++++++++++++------------------ src/utils.jl | 8 ++- test/FakePackage/Project.toml | 4 ++ test/Hijack/Project.toml | 3 + test/runtests.jl | 61 +++++++++++------ 9 files changed, 142 insertions(+), 102 deletions(-) diff --git a/InlineTest/src/InlineTest.jl b/InlineTest/src/InlineTest.jl index 993fe21..a53441f 100644 --- a/InlineTest/src/InlineTest.jl +++ b/InlineTest/src/InlineTest.jl @@ -32,7 +32,11 @@ const INLINE_TEST = Symbol("##InlineTest-01b48f5c342f65df7fcd07f28f0d2cacbb09f0a const TESTED_MODULES = Union{Module,Nothing}[] const TESTSET_MACROS = Symbol[] -get_tests(m::Module) = getfield(m, INLINE_TEST).tests +function get_tests(m::Module) + invokelatest() do + getfield(m, INLINE_TEST).tests + end +end function register(m::Module, macros::Vector{Symbol}) push!(TESTED_MODULES, m) @@ -111,7 +115,9 @@ function get_inline_mod!(mod)::Module @eval mod module $INLINE_TEST const tests = (tests=[], news=[], map=Dict{Union{String,Expr},Int}()) const TESTSET_MACROS = Symbol[] - __init__() = $register($mod, TESTSET_MACROS) + # use invokelatest so TESTSET_MACROS (defined in the same world) + # is visible to __init__ under Julia 1.13+ binding semantics + __init__() = $register($mod, invokelatest(getglobal, @__MODULE__, :TESTSET_MACROS)) end end end diff --git a/Project.toml b/Project.toml index a51b311..c3460a2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ReTest" uuid = "e0db7c4e-2690-44b9-bad6-7687da720f89" -authors = ["Rafael Fourquet "] version = "0.3.4" +authors = ["Rafael Fourquet "] [deps] Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" @@ -12,11 +12,14 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +[sources] +InlineTest = {path = "InlineTest"} + [compat] InlineTest = "=0.2.0" -Revise = "3.1" PrecompileTools = "1.2.1" -julia = "1.10" +Revise = "3.1" +julia = "1.13" [extras] Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/src/ReTest.jl b/src/ReTest.jl index 8e8de1f..f324c61 100644 --- a/src/ReTest.jl +++ b/src/ReTest.jl @@ -16,6 +16,8 @@ export Test, detect_ambiguities, detect_unbound_args, GenericString, GenericSet, GenericDict, GenericArray, GenericOrder +using Base.ScopedValues: @with + using Test: Test, @test, @test_throws, @test_broken, @test_skip, @test_warn, @test_nowarn, @@ -1151,7 +1153,13 @@ function retest(@nospecialize(args::ArgType...); resp = remotecall_fetch(wrkr, mod, ts, pat, chan ) do mod, ts, pat, chan mts = make_ts(ts, pat, format.stats, chan) - Core.eval(mod, mts) + # Run in a fresh dynamic scope so a surrounding + # Test.@testset (whose CURRENT_TESTSET is now a + # ScopedValue on Julia 1.13+) doesn't make our + # top-level testset look nested. + @with(Test.CURRENT_TESTSET => Test.FallbackTestSet(), + Test.TESTSET_DEPTH => 0, + Core.eval(mod, mts)) end if resp isa Vector ntests += length(resp) @@ -1276,8 +1284,8 @@ function process_args(@nospecialize(args); stestmod = Symbol(mod, :Tests) testmods = get(loaded_testmodules, mod, nothing) - if testmods === nothing && isdefined(Main, stestmod) - testmod = getfield(Main, stestmod) + if testmods === nothing && invokelatest(isdefined, Main, stestmod) + testmod = invokelatest(getglobal, Main, stestmod) # TODO: test this branch if testmod isa Module testmods = [testmod] @@ -1405,7 +1413,7 @@ function process_args(@nospecialize(args); # remove modules which don't have tests, which can happen when a parent module without # tests is passed to retest in order to run tests in its submodules - filter!(m -> isdefined(m, INLINE_TEST), modules) + filter!(m -> invokelatest(isdefined, m, INLINE_TEST), modules) # Remove the precompilation module if we're not precompiling if ccall(:jl_generating_output, Cint, ()) == 0 @@ -1460,7 +1468,7 @@ function update_TESTED_MODULES!(double_check::Bool=false) for sub in recsubmodules(mod) # new version: just check the assumption nameof(sub) == INLINE_TEST && continue - if isdefined(sub, INLINE_TEST) + if invokelatest(isdefined, sub, INLINE_TEST) @assert sub in TESTED_MODULES end # old effective version: diff --git a/src/hijack.jl b/src/hijack.jl index d4b9fe1..6ab212f 100644 --- a/src/hijack.jl +++ b/src/hijack.jl @@ -215,6 +215,7 @@ function hijack(path::AbstractString, modname=nothing; parentmodule::Module=Main # do first, to error early if necessary Revise = get_revise(revise) + include = setinclude(include, testset) if modname === nothing modname = replace(splitext(basename(path))[1], ['-', '.'] => '_') @@ -222,7 +223,7 @@ function hijack(path::AbstractString, modname=nothing; parentmodule::Module=Main modname = Symbol(modname) newmod = @eval parentmodule module $modname end - populate_mod!(newmod, path; lazy=lazy, include=setinclude(include, testset), + populate_mod!(newmod, path; lazy=lazy, include=include, include_functions=include_functions, Revise=Revise) newmod @@ -511,9 +512,9 @@ function hijack_base(tests, modname=nothing; parentmodule::Module=Main, lazy=fal # e.g. `tuple`, collision betwen the tuple function and test/tuple.jl comp = Symbol(comp, :_) end - if isdefined(mod, comp) && ith != length(components) || + if invokelatest(isdefined, mod, comp) && ith != length(components) || modname !== nothing && ith == 1 # module already freshly created - mod = getfield(mod, comp) + mod = invokelatest(getglobal, mod, comp) else # we always re-eval leaf-modules mod = @eval mod module $comp end diff --git a/src/testset.jl b/src/testset.jl index c4d3fb6..5582e0b 100644 --- a/src/testset.jl +++ b/src/testset.jl @@ -2,7 +2,8 @@ module Testset using Test: AbstractTestSet, Broken, DefaultTestSet, Error, Fail, Pass, Test, TestSetException, get_testset, get_testset_depth, - parse_testset_args, pop_testset, push_testset + parse_testset_args +using Base.ScopedValues: @with import Test: finish, record @@ -461,12 +462,11 @@ default_rng() = isdefined(Random, :default_rng) ? Random.default_rng() : Random.GLOBAL_RNG -function make_retestset(mod, desc, id, verbose, marks, remove_last=false, iter=1) - _testsets = get(task_local_storage(), :__BASETESTNEXT__, Test.AbstractTestSet[]) - @assert !(remove_last && isempty(_testsets)) - testsets = @view _testsets[1:end-remove_last] +function make_retestset(mod, desc, id, verbose, marks, iter=1) + parent_ts = get_testset() + parent = parent_ts isa ReTestSet ? parent_ts : nothing ReTestSet(mod, desc, id; verbose=verbose, marks=marks, iter=iter, - parent = isempty(testsets) ? nothing : testsets[end]) + parent=parent) end # HACK: we re-use the same macro name `@testset` for actual execution (like in `Test`) @@ -514,38 +514,36 @@ function testset_beginend(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, d if nworkers() == 1 && get_testset_depth() == 0 && $(chan.preview) !== nothing put!($(chan.preview), ($id, $desc)) end - push_testset(ts) # we reproduce the logic of guardseed, but this function # cannot be used as it changes slightly the semantic of @testset, # by wrapping the body in a function local default_rng_orig = copy(default_rng()) - @static if VERSION >= v"1.11" - local tls_seed_orig = copy(Random.get_tls_seed()) - end + local tls_seed_orig = copy(Random.get_tls_seed()) try - # RNG is re-seeded with the desired seed for the test - if ReTest.test_seed[] !== false - Random.seed!(ReTest.test_seed[]) - end - let - ts.timed = @stats $stats $(esc(tests)) - end - catch err - err isa InterruptException && rethrow() - # something in the test block threw an error. Count that as an - # error in this test set - record(ts, Error(:nontest_error, Expr(:tuple), err, - current_exceptions(), $(QuoteNode(source)))) + @with(Test.CURRENT_TESTSET => ts, + Test.TESTSET_DEPTH => get_testset_depth() + 1, + try + # RNG is re-seeded with the desired seed for the test + if ReTest.test_seed[] !== false + Random.seed!(ReTest.test_seed[]) + end + let + ts.timed = @stats $stats $(esc(tests)) + end + catch err + err isa InterruptException && rethrow() + # something in the test block threw an error. Count that as an + # error in this test set + record(ts, Error(:nontest_error, Expr(:tuple), err, + current_exceptions(), $(QuoteNode(source)))) + end) finally copy!(default_rng(), default_rng_orig) - @static if VERSION >= v"1.11" - copy!(Random.get_tls_seed(), tls_seed_orig) - end + copy!(Random.get_tls_seed(), tls_seed_orig) setresult!($marks, ts.subject, !anyfailed(ts)) - pop_testset() ret = finish(ts, $chan) end ret @@ -569,42 +567,48 @@ function testset_forloop(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, desc = esc(desc) blk = quote iter += 1 - local ts0 = make_retestset($mod, $desc, $id, $(options.transient_verbose), - $marks, !first_iteration, iter) + local ts = make_retestset($mod, $desc, $id, $(options.transient_verbose), + $marks, iter) - if !$isfinal || matches($pat, ts0.subject, ts0) - # Trick to handle `break` and `continue` in the test code before - # they can be handled properly by `finally` lowering. + if !$isfinal || matches($pat, ts.subject, ts) if !first_iteration - pop_testset() - push!(arr, finish(ts, $chan)) - if ts.exception !== nothing - # ts.exception might be set in finish(...) above - # In this case, we currently don't want to continue with subsequent - # iterations, as is done in Test. - # See also https://github.com/JuliaLang/julia/pull/41715 - break - end # it's 1000 times faster to copy from tmprng rather than calling Random.seed! copy!(default_rng(), tmprng) end - ts = ts0 if nworkers() == 1 && get_testset_depth() == 0 && $(chan.preview) !== nothing put!($(chan.preview), ($id, ts.description)) end - push_testset(ts) first_iteration = false + pending_ts = ts try - let - ts.timed = @stats $stats $(esc(tests)) + @with(Test.CURRENT_TESTSET => ts, + Test.TESTSET_DEPTH => get_testset_depth() + 1, + try + let + ts.timed = @stats $stats $(esc(tests)) + end + setresult!($marks, ts.subject, !anyfailed(ts)) + catch err + err isa InterruptException && rethrow() + # Something in the test block threw an error. Count that as an + # error in this test set + record(ts, Error(:nontest_error, Expr(:tuple), err, current_exceptions(), $(QuoteNode(source)))) + setresult!($marks, ts.subject, false) + end) + push!(arr, finish(ts, $chan)) + pending_ts = nothing + if ts.exception !== nothing + # ts.exception might be set in finish(...) above + # In this case, we currently don't want to continue with subsequent + # iterations, as is done in Test. + # See also https://github.com/JuliaLang/julia/pull/41715 + break + end + finally + if pending_ts !== nothing + # body exited via return/break/continue; finalize before unwinding + push!(arr, finish(pending_ts, $chan)) end - setresult!($marks, ts.subject, !anyfailed(ts)) - catch err - err isa InterruptException && rethrow() - # Something in the test block threw an error. Count that as an - # error in this test set - record(ts, Error(:nontest_error, Expr(:tuple), err, current_exceptions(), $(QuoteNode(source)))) - setresult!($marks, ts.subject, false) end end end @@ -613,12 +617,10 @@ function testset_forloop(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, local arr = Vector{Any}() local first_iteration = true local iter = 0 - local ts + local pending_ts = nothing local default_rng_orig = copy(default_rng()) - @static if VERSION >= v"1.11" - local tls_seed_orig = copy(Random.get_tls_seed()) - end + local tls_seed_orig = copy(Random.get_tls_seed()) local tmprng = copy(default_rng()) if ReTest.test_seed[] !== false @@ -629,16 +631,8 @@ function testset_forloop(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, $(Expr(:for, Expr(:block, [esc(v) for v in loops]...), blk)) end finally - # Handle `return` in test body - if !first_iteration && ts.exception === nothing - pop_testset() - push!(arr, finish(ts, $chan)) - end - copy!(default_rng(), default_rng_orig) - @static if VERSION >= v"1.11" - copy!(Random.get_tls_seed(), tls_seed_orig) - end + copy!(Random.get_tls_seed(), tls_seed_orig) end arr end diff --git a/src/utils.jl b/src/utils.jl index 3f15bb6..93d9db0 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -3,10 +3,12 @@ Maybe{T} = Union{T,Nothing} issubmodule(m::Module, s) = s isa Module && parentmodule(s) == m && m != s function submodules(m::Module) + # use invokelatest so freshly-loaded submodules are visible regardless + # of the caller's world age (Julia 1.13+ binding semantics) nms = filter!(names(m, all=true)) do y - Base.isdefined(m, y) && !Base.isdeprecated(m, y) + invokelatest(isdefined, m, y) && !Base.isdeprecated(m, y) end - symbols = Core.eval.(Ref(m), nms) + symbols = [invokelatest(getglobal, m, y) for y in nms] filter!(x -> issubmodule(m, x), symbols) end @@ -38,7 +40,7 @@ end function is_replaced(mod::Module) par = parentmodule(mod) while par != mod - getfield(par, nameof(mod)) != mod && return true + invokelatest(getglobal, par, nameof(mod)) != mod && return true mod = par par = parentmodule(par) end diff --git a/test/FakePackage/Project.toml b/test/FakePackage/Project.toml index 9eb11d2..5543512 100644 --- a/test/FakePackage/Project.toml +++ b/test/FakePackage/Project.toml @@ -5,3 +5,7 @@ version = "0.1.0" [deps] InlineTest = "bd334432-b1e7-49c7-a2dc-dd9149e4ebd6" ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89" + +[sources] +InlineTest = {path = "../../InlineTest"} +ReTest = {path = "../.."} diff --git a/test/Hijack/Project.toml b/test/Hijack/Project.toml index cbb55f0..295e56d 100644 --- a/test/Hijack/Project.toml +++ b/test/Hijack/Project.toml @@ -5,3 +5,6 @@ version = "0.1.0" [deps] ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[sources] +ReTest = {path = "../.."} diff --git a/test/runtests.jl b/test/runtests.jl index 4110cb3..b606202 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1680,11 +1680,16 @@ end using Hijack @test !haskey(ReTest.loaded_testmodules, Hijack) - @test first.(process_args((Hijack,), load=true).modules) == - [ - HijackLoadTrueTests, - HijackLoadTrueTests2 - ] + # call load=true first so the next stmt sees the freshly-loaded + # modules at its own (later) compilation world (Julia 1.13+) + process_args((Hijack,), load=true) + invokelatest() do + @test first.(process_args((Hijack,), load=true).modules) == + [ + HijackLoadTrueTests, + HijackLoadTrueTests2 + ] + end Hijack_testmodules = ReTest.loaded_testmodules[Hijack] @test Hijack_testmodules == [HijackLoadTrueTests, HijackLoadTrueTests2] @test first.(process_args((Hijack,), load=true).modules) == @@ -1695,7 +1700,9 @@ end ReTest.hijack(Hijack, testset=false) # testset: just check that this method # also accepts this kw (TODO: test it!) - retest(HijackTests) + invokelatest() do + retest(HijackTests) + end @test Hijack.RUN == [1, 5, 4] empty!(Hijack.RUN) @@ -1711,15 +1718,19 @@ end Test.@testset "load(revise=true)" begin # big hack, this should belong to the FakePackage chapter, but we can't load # Revise then, because we @test_throws above for Revise not loaded - @test ReTest.load(Hijack, "../../FakePackage/test/FakePackageTests2.jl", - parentmodule=Load, revise=true) == - Load.AlternateFakePackageTests - @test first.(process_args((Load.AlternateFakePackageTests,)).modules) == - [Load.AlternateFakePackageTests] + m = ReTest.load(Hijack, "../../FakePackage/test/FakePackageTests2.jl", + parentmodule=Load, revise=true) + invokelatest() do + @test m == Load.AlternateFakePackageTests + @test first.(process_args((Load.AlternateFakePackageTests,)).modules) == + [Load.AlternateFakePackageTests] + end # here, Load.HijackTests gets defined - @test ReTest.load(Hijack, "load_revise.jl", parentmodule=Load) == # revise=true - Load.HijackTests + m = ReTest.load(Hijack, "load_revise.jl", parentmodule=Load) # revise=true + invokelatest() do + @test m == Load.HijackTests + end @test Load.load_revise_function() == 1 @test Load.HijackTests.load_revise_function() == 1 @test first.(process_args((Load.HijackTests,)).modules) == [Load.HijackTests] @@ -1737,16 +1748,20 @@ end end ReTest.hijack(Hijack, :HijackTests2) # revise=true by default - retest(HijackTests2) + invokelatest() do + retest(HijackTests2) + end @test Hijack.RUN == [1, 5, 4] empty!(Hijack.RUN) # Submodules ReTest.hijack("Hijack/test/submodules_tests.jl", :SubMod1, revise=true) - retest(SubMod1) - @test SubMod1.RUN == [1]; empty!(SubMod1.RUN) - @test SubMod1.SubModule.RUN == [1]; empty!(SubMod1.SubModule.RUN) - @test SubMod1.SubModule.Sub.RUN == [1]; empty!(SubMod1.SubModule.Sub.RUN) + Base.invokelatest() do + retest(SubMod1) + @test SubMod1.RUN == [1]; empty!(SubMod1.RUN) + @test SubMod1.SubModule.RUN == [1]; empty!(SubMod1.SubModule.RUN) + @test SubMod1.SubModule.Sub.RUN == [1]; empty!(SubMod1.SubModule.Sub.RUN) + end ## UPDATING ###### @@ -1860,7 +1875,9 @@ end # test include=:outline empty!(Hijack.RUN) ReTest.hijack("./Hijack/test/testset.jl", :HijackTestset, include=:outline) - retest(HijackTestset) + invokelatest() do + retest(HijackTestset) + end @test Hijack.RUN == [1, 2, 3] # test include=:static @@ -1869,7 +1886,8 @@ end @test_throws ErrorException ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:notvalid) ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:static, include_functions=[:include, :custom_include_function]) - check(HijackInclude, dry=true, verbose=9, [], output=""" + invokelatest() do + check(HijackInclude, dry=true, verbose=9, [], output=""" 1| include_static 2| include_static_included1 1 3| nested include_static_included1 @@ -1878,7 +1896,8 @@ end 3| nested include_static_included1 4| include_static_included2 """) - retest(HijackInclude) + retest(HijackInclude) + end @test Hijack.RUN == [1, 2, 3, 2, 3] end end From 0e315b2f4cefbe240fd5de09383c991e9895e3d9 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Fri, 1 May 2026 19:27:49 +0200 Subject: [PATCH 2/5] Delete old compatibility code --- src/ReTest.jl | 9 +- src/hijack.jl | 56 +---- src/patterns.jl | 12 +- src/testset.jl | 33 +-- test/FakePackage/test/runtests.jl | 8 +- test/runtests.jl | 404 ++++++++++++++---------------- test/test_patterns.jl | 16 +- 7 files changed, 219 insertions(+), 319 deletions(-) diff --git a/src/ReTest.jl b/src/ReTest.jl index f324c61..4d96bdb 100644 --- a/src/ReTest.jl +++ b/src/ReTest.jl @@ -890,8 +890,7 @@ function retest(@nospecialize(args::ArgType...); printlock = ReentrantLock() previewchan = - if spin && stdout isa Base.TTY && (nthreads() > 1 && VERSION >= v"1.3" || - nprocs() > 1) + if spin && stdout isa Base.TTY && (nthreads() > 1 || nprocs() > 1) RemoteChannel(() -> Channel{Maybe{Tuple{Int64,String}}}(Inf)) # needs to be "remote" in the case nprocs() == 2, as then nworkers() == 1, # which means the one remote worker will put descriptions on previewchan @@ -899,10 +898,6 @@ function retest(@nospecialize(args::ArgType...); # the order in which they complete, and then the previewer will # not show the descriptions, just the spinning wheel) - # on VERSION < v"1.3" : we can't call `thread_pin` (see below), and in this - # case previewing doesn't work well, as the worker and previewer tasks - # can end up in the same thread, and the previewer is not responsive - # channel size: if nworkers() == 1, then 2 would suffice (one for # the "compilation step", one for @testset execution step, and then # the printer would empty the channel; but for two workers and more, @@ -1182,7 +1177,7 @@ function retest(@nospecialize(args::ArgType...); end # worker = @task begin ... try - if previewchan !== nothing && nthreads() > 1 && VERSION >= v"1.3" + if previewchan !== nothing && nthreads() > 1 # we try to keep thread #1 free of heavy work, so that the previewer stays # responsive tid = rand(2:nthreads()) diff --git a/src/hijack.jl b/src/hijack.jl index 6ab212f..6cb3a7e 100644 --- a/src/hijack.jl +++ b/src/hijack.jl @@ -5,8 +5,7 @@ Include file `testpath` into `parentmodule`. If `revise` is `true`, `Revise`, which must be loaded beforehand in your Julia session, is used to track all recursively included files (in particular testsets). The `revise` keyword -defaults to `true` when `Revise` is loaded and `VERSION >= v"1.5"`, and to -`false` otherwise. +defaults to `true` when `Revise` is loaded, and to `false` otherwise. The point of using this function is when `revise` is `true` and in particular when files are included recursively. @@ -15,15 +14,10 @@ and if there are no recursively included files, this should be equivalent to `Revise.includet(testpath)`, provided `parentmodule == Main` and all `@testset`s defined in `testpath` are in a module defining `__revise_mode__ = :eval`. - -!!! compat "Julia 1.5" - This function requires at least Julia 1.5 when `revise` is `true`. """ function load(testpath::AbstractString; parentmodule::Module=Main, revise::Maybe{Bool}=nothing) - revise === true && VERSION < v"1.5" && - error("the `revise` keyword requires at least Julia 1.5") Revise = get_revise(revise) if Revise === nothing @@ -53,10 +47,7 @@ If `revise` is `true`, `Revise`, which must be loaded beforehand in your Julia session, is used to track the test files (in particular testsets). Note that this might be brittle, and it's recommended instead to load your test module via `using ModTests`. The `revise` keyword defaults to `true` when `Revise` is -loaded and `VERSION >= v"1.5"`, and to `false` otherwise. - -!!! compat "Julia 1.5" - This function requires at least Julia 1.5 when `revise` is `true`. +loaded, and to `false` otherwise. """ function load(packagemod::Module, testfile::Maybe{AbstractString}=nothing; parentmodule::Module=Main, revise::Maybe{Bool}=nothing, @@ -200,8 +191,6 @@ be loaded beforehand in your Julia session. Note that this might be brittle and not work in all cases. `revise` defaults to `true` when `Revise` is loaded, and to `false` otherwise. -!!! compat "Julia 1.5" - This function requires at least Julia 1.5. """ function hijack end @@ -270,8 +259,6 @@ function populate_mod!(mod::Module, path; lazy, Revise, include::Maybe{Symbol}=n end function revise_track(Revise, files) - # uniquemod serves in v1.5 to make hijack w/ revise still work in many cases, - # when there aren't nested submodules/includes for (filepath, mod) in files if isfile(filepath) # some files might not exist when they are conditionally # included @@ -333,11 +320,7 @@ function substitute_retest!(ex, lazy, include_::Maybe{Symbol}, files=nothing; include($substitute!, $newfile) $files[newfile] = $(root_module[]) end) - if VERSION >= v"1.6" - # v1.5 doesn't play well with @__MODULE__, the let expression - # simply... vanishes; so we have to use root_module instead - push!(ex2.args[2].args, :(@assert $(root_module[]) == @__MODULE__)) - end + push!(ex2.args[2].args, :(@assert $(root_module[]) == @__MODULE__)) # TODO: add `copy!(::Expr, ::Expr)` to Base ex.head = ex2.head copy!(ex.args, ex2.args) @@ -457,9 +440,6 @@ and not enclosed within `BaseTests` or `StdLibTests`. The `lazy` and `revise` keywords have the same meaning as in [`ReTest.hijack`](@ref). Depending on the value of `lazy`, some test files are skipped when they are known to fail. - -!!! compat "Julia 1.5" - This function requires at least Julia 1.5. """ function hijack_base(tests, modname=nothing; parentmodule::Module=Main, lazy=false, base=:BaseTests, stdlib=:StdLibTests, revise::Maybe{Bool}=nothing) @@ -529,7 +509,7 @@ function hijack_base(tests, modname=nothing; parentmodule::Module=Main, lazy=fal end get_revise(revise) = - if revise === true || revise === nothing && VERSION >= v"1.5" + if revise === true || revise === nothing Revise = get(Base.loaded_modules, revise_pkgid(), nothing) Revise === nothing && revise === true && error("Revise is not loaded") @@ -574,34 +554,6 @@ function test_path(test) end end -if VERSION < v"1.8.0-DEV.34" - 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" - ] -end - const BLACKLIST = [ # failing at load time (in hijack_base) "backtrace", "misc", "threads", "cmdlineargs","boundscheck", diff --git a/src/patterns.jl b/src/patterns.jl index 29b8afa..9bc1100 100644 --- a/src/patterns.jl +++ b/src/patterns.jl @@ -182,10 +182,8 @@ function make_pattern(str::AbstractString) rx = if isempty(str) r"" # in order to know to match unconditionally - elseif VERSION >= v"1.3" - r""i * str else - Regex(str, "i") + r""i * str end neg ? not(rx) : rx end @@ -462,14 +460,8 @@ julia> retest(Fail, reachable("a"), verbose=9, dry=true, static=true) 1| a ``` -!!! compat "Julia 1.3" - This function requires at least Julia 1.3. """ -function reachable end - -if VERSION >= v"1.3" - reachable(x) = Reachable(make_pattern(x)) -end +reachable(x) = Reachable(make_pattern(x)) """ depth(d::Integer) diff --git a/src/testset.jl b/src/testset.jl index 5582e0b..a0de8bd 100644 --- a/src/testset.jl +++ b/src/testset.jl @@ -48,11 +48,6 @@ function scrub_exc_stack(stack) return Any[ (x[1], scrub_backtrace(x[2])) for x in stack ] end -# Compat for catch_stack(), which was deprecated in 1.7 -@static if VERSION < v"1.7" - current_exceptions() = Base.catch_stack() -end - mutable struct Format stats::Bool desc_align::Int @@ -231,8 +226,7 @@ function print_test_results(ts::ReTestSet, fmt::Format; end if fmt.stats # copied from Julia/test/runtests.jl - compile_header = VERSION >= v"1.6-" ? " Compile /" : "" - printstyled("| Time /$compile_header GC | Alloc ΔRSS |", color=:white) + printstyled("| Time / Compile / GC | Alloc ΔRSS |", color=:white) end println() end @@ -421,15 +415,13 @@ function print_counts(ts::ReTestSet, fmt::Format, depth, align, time_str = hide_zero(@sprintf("%6.2f", timed.time), "s") printstyled("| ", time_str, " ", color=:white) - if VERSION >= v"1.6-" - compile_str = all(==(' '), time_str) ? - ' '^6 : # print percentages only if time itself is shown! - # (also, this can result in weird things, like "30663.3%", - # if e.g. there was no @test in the @testset) - hide_zero(@sprintf("%5.1f", timed.compile_time / 10^7 / timed.time), "%") - # can be >= 100% !? - printstyled(compile_str, " ", color=:white) - end + compile_str = all(==(' '), time_str) ? + ' '^6 : # print percentages only if time itself is shown! + # (also, this can result in weird things, like "30663.3%", + # if e.g. there was no @test in the @testset) + hide_zero(@sprintf("%5.1f", timed.compile_time / 10^7 / timed.time), "%") + # can be >= 100% !? + printstyled(compile_str, " ", color=:white) gc_str = all(==(' '), time_str) ? ' '^5 : hide_zero(@sprintf("%4.1f", 100 * timed.gctime / timed.time), "%") @@ -677,13 +669,6 @@ macro stats(yes, ex) end end -@static if VERSION >= v"1.9" - # In 1.9 this function was changed to return a tuple of (compile_time, recompilation_time) - cumulative_compile_time_ns() = sum(Base.cumulative_compile_time_ns()) -else - cumulative_compile_time_ns() = isdefined(Base, :cumulative_compile_time_ns) ? - Base.cumulative_compile_time_ns() : - UInt(0) -end +cumulative_compile_time_ns() = sum(Base.cumulative_compile_time_ns()) end # module diff --git a/test/FakePackage/test/runtests.jl b/test/FakePackage/test/runtests.jl index bf4a5df..2128326 100644 --- a/test/FakePackage/test/runtests.jl +++ b/test/FakePackage/test/runtests.jl @@ -57,15 +57,13 @@ RUN = [] @testset "include parent" begin include("included.jl") include(joinpath(@__DIR__, "included.jl")) - if VERSION >= v"1.5" - include(identity, "included.jl") - include(identity, joinpath(@__DIR__, "included.jl")) - end + include(identity, "included.jl") + include(identity, joinpath(@__DIR__, "included.jl")) end end # Included ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ retest(Included) -@test Included.RUN == (VERSION >= v"1.5" ? [0, 0, 0, 0] : [0, 0]) +@test Included.RUN == [0, 0, 0, 0] ReTest.Test.@testset "retest: load" begin @test process_args(()).modules == [ diff --git a/test/runtests.jl b/test/runtests.jl index b606202..2f65ca7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -168,18 +168,16 @@ end # N check(N, 1, "i j l1", strict=false) # reachable - if VERSION >= v"1.3" - check(N, reachable(1), " i j k l1 m") - check(N, reachable(1), dry=true, id=false, verbose=3, "", output = """ + check(N, reachable(1), " i j k l1 m") + check(N, reachable(1), dry=true, id=false, verbose=3, "", output = """ i j k l1 m """) - check(N, reachable("l1"), " i l1 m") - check(N, not(reachable("l1")), " i j k") - end + check(N, reachable("l1"), " i l1 m") + check(N, not(reachable("l1")), " i j k") end # * P ........................................................................ @@ -210,10 +208,8 @@ end # P check(P, "B", "a b b|c"; verbose=0, runtests=true) # idem, case-insensitive check(P, r"B", "") # not case-insensitive - if VERSION >= v"1.3" - check(P, "b|c", "a b|c") # "b" is not matched - check(P, "B|C", "a b|c") # idem, case-insensitive - end + check(P, "b|c", "a b|c") # "b" is not matched + check(P, "B|C", "a b|c") # idem, case-insensitive check(P, "d&e", "D&E") check(P, "d&E", "D&E") @@ -244,9 +240,7 @@ end check(Depth, depth(2), [1, 2, 4]) check(Depth, depth(3), [1, 2, 3]) check(Depth, depth.(2:3), [1, 2, 3, 4]) - if VERSION >= v"1.3" - check(Depth, reachable(depth(2)), [1, 2, 3, 4]) - end + check(Depth, reachable(depth(2)), [1, 2, 3, 4]) end @@ -529,19 +523,17 @@ loops 1 sub final """) - if VERSION >= v"1.3" - check(Loops1, reachable("loops 1"), verbose=9, [9, 1, 0, -1, 2, 0, -1]) - check(Loops1, reachable("loops 1"), verbose=9, dry=true, id=false, [], output=raw""" + check(Loops1, reachable("loops 1"), verbose=9, [9, 1, 0, -1, 2, 0, -1]) + check(Loops1, reachable("loops 1"), verbose=9, dry=true, id=false, [], output=raw""" loops 1 "local$(i)" (repeated) sub final """) - check(Loops1, reachable("loops 1"), verbose=9, dry=true, id=false, static=true, [], - output="loops 1") - check(Loops1, reachable(interpolated), verbose=9, dry=true, id=false, [], - output="loops 1") - end + check(Loops1, reachable("loops 1"), verbose=9, dry=true, id=false, static=true, [], + output="loops 1") + check(Loops1, reachable(interpolated), verbose=9, dry=true, id=false, [], + output="loops 1") end # same as Loops1, but the iterator can be statically computed, only descriptions can't @@ -868,8 +860,7 @@ end end @chapter MiscSeed begin - rands = VERSION < v"1.7-" ? [0x24ae, 0x837e] : - [0x12c8, 0x0093] + rands = [0x12c8, 0x0093] MiscSeed.runtests(verbose=0, seed=1) @test MiscSeed.RAND1 === MiscSeed.RAND2 === rands[1] MiscSeed.runtests(verbose=0, seed=2) @@ -1179,9 +1170,8 @@ x hx check(Marks, "a", "-l", -4, :a2, dry=true, verbose=9, id=false, marks=true, [], output="a a1 a2 a3 a4 a5 a6 ✔") check(Marks, "a", "-l", -4, :a2, dry=false, verbose=9, id=false, marks=true, ["a"]) - if VERSION >= v"1.3" - check(Marks, "-l", -4, not(reachable(:a1)), dry=true, verbose=9, id=false, - marks=true, [], output=""" + check(Marks, "-l", -4, not(reachable(:a1)), dry=true, verbose=9, id=false, + marks=true, [], output=""" x hx y1 hx ylabel z1 hx hz1 ylabel @@ -1194,8 +1184,8 @@ x hx z3 hx z4 hx """) - check(Marks, "-l", -4, reachable("x"), not(:ylabel), dry=true, verbose=9, id=false, - marks=true, [], output=""" + check(Marks, "-l", -4, reachable("x"), not(:ylabel), dry=true, verbose=9, id=false, + marks=true, [], output=""" x hx y1 hx ylabel y2 hx @@ -1204,7 +1194,6 @@ x hx z3 hx z4 hx """) - end check(Marks, -4, [r"y1$", "z3"], dry=true, marks=true, id=false, tag=not(:ylabel), verbose=9, [], output=""" x hx @@ -1672,112 +1661,106 @@ end Pkg.develop(PackageSpec(path="..")) # ReTest Pkg.test("Hijack") - if VERSION < v"1.5" - using Revise - @test ReTest.get_revise(nothing) === nothing - - else - - using Hijack - @test !haskey(ReTest.loaded_testmodules, Hijack) - # call load=true first so the next stmt sees the freshly-loaded - # modules at its own (later) compilation world (Julia 1.13+) - process_args((Hijack,), load=true) - invokelatest() do - @test first.(process_args((Hijack,), load=true).modules) == - [ - HijackLoadTrueTests, - HijackLoadTrueTests2 - ] - end - Hijack_testmodules = ReTest.loaded_testmodules[Hijack] - @test Hijack_testmodules == [HijackLoadTrueTests, HijackLoadTrueTests2] + using Hijack + @test !haskey(ReTest.loaded_testmodules, Hijack) + # call load=true first so the next stmt sees the freshly-loaded + # modules at its own (later) compilation world (Julia 1.13+) + process_args((Hijack,), load=true) + invokelatest() do @test first.(process_args((Hijack,), load=true).modules) == [ HijackLoadTrueTests, HijackLoadTrueTests2 - ] == Hijack_testmodules # to be sure no module was overwritten - - ReTest.hijack(Hijack, testset=false) # testset: just check that this method - # also accepts this kw (TODO: test it!) - invokelatest() do - retest(HijackTests) - end - @test Hijack.RUN == [1, 5, 4] - empty!(Hijack.RUN) - - @test_throws ErrorException ReTest.hijack(Hijack, :HijackTests2, revise=true) - # Revise not loaded || VERSION < v"1.5" - @test_throws ErrorException ReTest.load(Hijack, "load_revise.jl", - revise=true, parentmodule=Load) + ] + end + Hijack_testmodules = ReTest.loaded_testmodules[Hijack] + @test Hijack_testmodules == [HijackLoadTrueTests, HijackLoadTrueTests2] + @test first.(process_args((Hijack,), load=true).modules) == + [ + HijackLoadTrueTests, + HijackLoadTrueTests2 + ] == Hijack_testmodules # to be sure no module was overwritten + + ReTest.hijack(Hijack, testset=false) # testset: just check that this method + # also accepts this kw (TODO: test it!) + invokelatest() do + retest(HijackTests) + end + @test Hijack.RUN == [1, 5, 4] + empty!(Hijack.RUN) + @test_throws ErrorException ReTest.hijack(Hijack, :HijackTests2, revise=true) + # Revise not loaded + @test_throws ErrorException ReTest.load(Hijack, "load_revise.jl", + revise=true, parentmodule=Load) - using Revise ############################### - @test nameof(ReTest.get_revise(nothing)) == :Revise - Test.@testset "load(revise=true)" begin - # big hack, this should belong to the FakePackage chapter, but we can't load - # Revise then, because we @test_throws above for Revise not loaded - m = ReTest.load(Hijack, "../../FakePackage/test/FakePackageTests2.jl", - parentmodule=Load, revise=true) - invokelatest() do - @test m == Load.AlternateFakePackageTests - @test first.(process_args((Load.AlternateFakePackageTests,)).modules) == - [Load.AlternateFakePackageTests] - end + using Revise ############################### + @test nameof(ReTest.get_revise(nothing)) == :Revise - # here, Load.HijackTests gets defined - m = ReTest.load(Hijack, "load_revise.jl", parentmodule=Load) # revise=true - invokelatest() do - @test m == Load.HijackTests - end - @test Load.load_revise_function() == 1 - @test Load.HijackTests.load_revise_function() == 1 - @test first.(process_args((Load.HijackTests,)).modules) == [Load.HijackTests] - Load.HijackTests.check(Load.HijackTests, [1]) - - HL = ReTest.load(Hijack, "HijackTestsLoad123.jl", parentmodule=Load) - @test HL == [Load.HijackTestsLoad1, Load.HijackTestsLoad2] - @test HL[1].f() == 1 - @test HL[2].f() == 1 - @test Load.HijackTestsLoad3.f() == 1 - - lpf = ReTest.load("Hijack/test/load_path.jl", parentmodule=Load2) - @test Load2.load_path_function() == 1 - @test lpf == Load2.load_path_function + Test.@testset "load(revise=true)" begin + # big hack, this should belong to the FakePackage chapter, but we can't load + # Revise then, because we @test_throws above for Revise not loaded + m = ReTest.load(Hijack, "../../FakePackage/test/FakePackageTests2.jl", + parentmodule=Load, revise=true) + invokelatest() do + @test m == Load.AlternateFakePackageTests + @test first.(process_args((Load.AlternateFakePackageTests,)).modules) == + [Load.AlternateFakePackageTests] end - ReTest.hijack(Hijack, :HijackTests2) # revise=true by default + # here, Load.HijackTests gets defined + m = ReTest.load(Hijack, "load_revise.jl", parentmodule=Load) # revise=true invokelatest() do - retest(HijackTests2) + @test m == Load.HijackTests end - @test Hijack.RUN == [1, 5, 4] - empty!(Hijack.RUN) + @test Load.load_revise_function() == 1 + @test Load.HijackTests.load_revise_function() == 1 + @test first.(process_args((Load.HijackTests,)).modules) == [Load.HijackTests] + Load.HijackTests.check(Load.HijackTests, [1]) + + HL = ReTest.load(Hijack, "HijackTestsLoad123.jl", parentmodule=Load) + @test HL == [Load.HijackTestsLoad1, Load.HijackTestsLoad2] + @test HL[1].f() == 1 + @test HL[2].f() == 1 + @test Load.HijackTestsLoad3.f() == 1 + + lpf = ReTest.load("Hijack/test/load_path.jl", parentmodule=Load2) + @test Load2.load_path_function() == 1 + @test lpf == Load2.load_path_function + end - # Submodules - ReTest.hijack("Hijack/test/submodules_tests.jl", :SubMod1, revise=true) - Base.invokelatest() do - retest(SubMod1) - @test SubMod1.RUN == [1]; empty!(SubMod1.RUN) - @test SubMod1.SubModule.RUN == [1]; empty!(SubMod1.SubModule.RUN) - @test SubMod1.SubModule.Sub.RUN == [1]; empty!(SubMod1.SubModule.Sub.RUN) - end + ReTest.hijack(Hijack, :HijackTests2) # revise=true by default + invokelatest() do + retest(HijackTests2) + end + @test Hijack.RUN == [1, 5, 4] + empty!(Hijack.RUN) + + # Submodules + ReTest.hijack("Hijack/test/submodules_tests.jl", :SubMod1, revise=true) + Base.invokelatest() do + retest(SubMod1) + @test SubMod1.RUN == [1]; empty!(SubMod1.RUN) + @test SubMod1.SubModule.RUN == [1]; empty!(SubMod1.SubModule.RUN) + @test SubMod1.SubModule.Sub.RUN == [1]; empty!(SubMod1.SubModule.Sub.RUN) + end - ## UPDATING ###### + ## UPDATING ###### - orig(file) = let s = splitext(file) - join([s[1], ".orig", s[2]]) - end - function update_file!(f, file) - cp(file, orig(file), force=true) - write(file, f(chomp(read(file, String)))) - end - restore_file!(file) = mv(orig(file), file, force=true) + orig(file) = let s = splitext(file) + join([s[1], ".orig", s[2]]) + end + function update_file!(f, file) + cp(file, orig(file), force=true) + write(file, f(chomp(read(file, String)))) + end + restore_file!(file) = mv(orig(file), file, force=true) - # EDIT FILE 1 - sub_file = "./Hijack/test/subdir/sub.jl" - update_file!(sub_file) do _ - """ + # EDIT FILE 1 + sub_file = "./Hijack/test/subdir/sub.jl" + update_file!(sub_file) do _ + """ @test true @testset "sub" begin @@ -1786,108 +1769,108 @@ end push!(Hijack.RUN, 2) end """ - end + end - # EDIT FILE 2 - load_revise = "./Hijack/test/load_revise.jl" - update_file!(load_revise) do content - content = replace(content, - "load_revise_function() = 1" => "load_revise_function() = 2") - lines = split(content, r"\n|\r\n") # cf. https://github.com/JuliaLang/julia/pull/20390 - @assert endswith(lines[14], "@test true") - @assert endswith(lines[15], "trace(1)") - insert!(lines, 16, "@test true") - insert!(lines, 17, "trace(2)") - join(lines, '\n') - end + # EDIT FILE 2 + load_revise = "./Hijack/test/load_revise.jl" + update_file!(load_revise) do content + content = replace(content, + "load_revise_function() = 1" => "load_revise_function() = 2") + lines = split(content, r"\n|\r\n") # cf. https://github.com/JuliaLang/julia/pull/20390 + @assert endswith(lines[14], "@test true") + @assert endswith(lines[15], "trace(1)") + insert!(lines, 16, "@test true") + insert!(lines, 17, "trace(2)") + join(lines, '\n') + end - # EDIT FILES 3 & 4 & 5 - mod_revise = "Hijack/test/submodules_tests.jl" - submod_revise = "Hijack/test/submodule.jl" - subsubmod_revise = "Hijack/test/subsubmodule.jl" - for sub in (mod_revise, submod_revise, subsubmod_revise) - update_file!(sub) do content - replace(content, "push!(RUN, 1)" => "push!(RUN, 2)") - end + # EDIT FILES 3 & 4 & 5 + mod_revise = "Hijack/test/submodules_tests.jl" + submod_revise = "Hijack/test/submodule.jl" + subsubmod_revise = "Hijack/test/subsubmodule.jl" + for sub in (mod_revise, submod_revise, subsubmod_revise) + update_file!(sub) do content + replace(content, "push!(RUN, 1)" => "push!(RUN, 2)") end + end - # EDIT FILE 6 - load_hijack123 = "Hijack/test/HijackTestsLoad123.jl" - update_file!(load_hijack123) do content - replace(content, "f() = 1" => "f() = 2") - end + # EDIT FILE 6 + load_hijack123 = "Hijack/test/HijackTestsLoad123.jl" + update_file!(load_hijack123) do content + replace(content, "f() = 1" => "f() = 2") + end - # EDIT FILE 7 - load_path_file = "Hijack/test/load_path.jl" - update_file!(load_path_file) do content - replace(content, "load_path_function() = 1" => "load_path_function() = 2") - end + # EDIT FILE 7 + load_path_file = "Hijack/test/load_path.jl" + update_file!(load_path_file) do content + replace(content, "load_path_function() = 1" => "load_path_function() = 2") + end - Revise.revise() - try - Test.@testset "revise works" begin - retest(HijackTests2) - @test Hijack.RUN == [2, 5, 4] - Load.HijackTests.check(Load.HijackTests, [1, 2]) - @test Load.load_revise_function() == 2 - @test Load.HijackTests.load_revise_function() == 2 - retest(SubMod1) - @test SubMod1.RUN == [2]; empty!(SubMod1.RUN) - @test SubMod1.SubModule.RUN == [2]; empty!(SubMod1.SubModule.RUN) - @test SubMod1.SubModule.Sub.RUN == [2]; empty!(SubMod1.SubModule.Sub.RUN) - - @test Load.HijackTestsLoad1.f() == 2 - @test Load.HijackTestsLoad2.f() == 2 - @test Load.HijackTestsLoad3.f() == 2 - @test Load2.load_path_function() == 2 - end - finally - restore_file!(sub_file) - restore_file!(load_revise) - restore_file!(mod_revise) - restore_file!(submod_revise) - restore_file!(subsubmod_revise) - restore_file!(load_hijack123) - restore_file!(load_path_file) + Revise.revise() + try + Test.@testset "revise works" begin + retest(HijackTests2) + @test Hijack.RUN == [2, 5, 4] + Load.HijackTests.check(Load.HijackTests, [1, 2]) + @test Load.load_revise_function() == 2 + @test Load.HijackTests.load_revise_function() == 2 + retest(SubMod1) + @test SubMod1.RUN == [2]; empty!(SubMod1.RUN) + @test SubMod1.SubModule.RUN == [2]; empty!(SubMod1.SubModule.RUN) + @test SubMod1.SubModule.Sub.RUN == [2]; empty!(SubMod1.SubModule.Sub.RUN) + + @test Load.HijackTestsLoad1.f() == 2 + @test Load.HijackTestsLoad2.f() == 2 + @test Load.HijackTestsLoad3.f() == 2 + @test Load2.load_path_function() == 2 end + finally + restore_file!(sub_file) + restore_file!(load_revise) + restore_file!(mod_revise) + restore_file!(submod_revise) + restore_file!(subsubmod_revise) + restore_file!(load_hijack123) + restore_file!(load_path_file) + end - # These two tests currently just spin forever - if false - @warn "Skipping some hijack tests because they cause Revise to get into an infinite loop" - - # test lazy=true - empty!(Hijack.RUN) - ReTest.hijack("./Hijack/test/lazy.jl", :HijackLazy, lazy=true) - retest(HijackLazy) - @test Hijack.RUN == [1, 3] - - # test lazy=:brutal - empty!(Hijack.RUN) - ReTest.hijack("./Hijack/test/lazy.jl", :HijackBrutal, lazy=:brutal) - retest(HijackBrutal) - @test Hijack.RUN == [3] - end + # These two tests currently just spin forever + if false + @warn "Skipping some hijack tests because they cause Revise to get into an infinite loop" - # test lazy=:wrong + # test lazy=true empty!(Hijack.RUN) - @test_throws ArgumentError ReTest.hijack("./Hijack/test/lazy.jl", :HijackWrong, lazy=:wrong) + ReTest.hijack("./Hijack/test/lazy.jl", :HijackLazy, lazy=true) + retest(HijackLazy) + @test Hijack.RUN == [1, 3] - # test include=:outline + # test lazy=:brutal empty!(Hijack.RUN) - ReTest.hijack("./Hijack/test/testset.jl", :HijackTestset, include=:outline) - invokelatest() do - retest(HijackTestset) - end - @test Hijack.RUN == [1, 2, 3] + ReTest.hijack("./Hijack/test/lazy.jl", :HijackBrutal, lazy=:brutal) + retest(HijackBrutal) + @test Hijack.RUN == [3] + end - # test include=:static - empty!(Hijack.RUN) - @test_throws ErrorException ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:static, testset=true) - @test_throws ErrorException ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:notvalid) - ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:static, - include_functions=[:include, :custom_include_function]) - invokelatest() do - check(HijackInclude, dry=true, verbose=9, [], output=""" + # test lazy=:wrong + empty!(Hijack.RUN) + @test_throws ArgumentError ReTest.hijack("./Hijack/test/lazy.jl", :HijackWrong, lazy=:wrong) + + # test include=:outline + empty!(Hijack.RUN) + ReTest.hijack("./Hijack/test/testset.jl", :HijackTestset, include=:outline) + invokelatest() do + retest(HijackTestset) + end + @test Hijack.RUN == [1, 2, 3] + + # test include=:static + empty!(Hijack.RUN) + @test_throws ErrorException ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:static, testset=true) + @test_throws ErrorException ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:notvalid) + ReTest.hijack("./Hijack/test/include_static.jl", :HijackInclude, include=:static, + include_functions=[:include, :custom_include_function]) + invokelatest() do + check(HijackInclude, dry=true, verbose=9, [], output=""" 1| include_static 2| include_static_included1 1 3| nested include_static_included1 @@ -1896,8 +1879,7 @@ end 3| nested include_static_included1 4| include_static_included2 """) - retest(HijackInclude) - end - @test Hijack.RUN == [1, 2, 3, 2, 3] + retest(HijackInclude) end + @test Hijack.RUN == [1, 2, 3, 2, 3] end diff --git a/test/test_patterns.jl b/test/test_patterns.jl index dc7cc65..aea01f3 100644 --- a/test/test_patterns.jl +++ b/test/test_patterns.jl @@ -16,8 +16,7 @@ end ReTest.tsdepth(::MockTestset) = 1 const basic_patterns = [and(), or(), not(0), interpolated, 0, r"", :label, - depth(2), pass, fail, iter(1)] -VERSION >= v"1.3" && push!(basic_patterns, reachable(1)) + depth(2), pass, fail, iter(1), reachable(1)] @testset "patterns: ==" begin for a = basic_patterns, b = basic_patterns @@ -46,12 +45,10 @@ VERSION >= v"1.3" && push!(basic_patterns, reachable(1)) @test not(a) == not(deepcopy(a)) @test not(a) != not(b) @test not(not(a)) == not(deepcopy(not(a))) - if VERSION >= v"1.3" - @test reachable(a) == reachable(a) - @test reachable(a) == reachable(deepcopy(a)) - @test reachable(a) != reachable(b) - @test reachable(reachable(a)) == reachable(deepcopy(reachable(a))) - end + @test reachable(a) == reachable(a) + @test reachable(a) == reachable(deepcopy(a)) + @test reachable(a) != reachable(b) + @test reachable(reachable(a)) == reachable(deepcopy(reachable(a))) end end end @@ -67,8 +64,7 @@ end end @testset "patterns: not" begin - pats = [or(1, 3), and(1, r"a"), not(1), interpolated, depth(3)] - VERSION >= v"1.3" && push!(pats, reachable("c")) + pats = [or(1, 3), and(1, r"a"), not(1), interpolated, depth(3), reachable("c")] for p ∈ pats @test -p == not(p) end From ca82c566037f293a19cb29f0c4fbbaec76b77958 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Fri, 1 May 2026 20:05:10 +0200 Subject: [PATCH 3/5] Improve compilation latency Partly by doing type erasure, partly by adding another retest() call to the precompilation workload. --- src/ReTest.jl | 19 +++++++++++++------ src/precompile.jl | 38 ++++++++++++++++++++++++++------------ src/utils.jl | 13 +++++++++---- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/ReTest.jl b/src/ReTest.jl index 4d96bdb..e331e89 100644 --- a/src/ReTest.jl +++ b/src/ReTest.jl @@ -181,11 +181,17 @@ function replace_ts(source, mod, x::Expr, parent; static_include::Bool, x, false end else @label default - body_br = map(z -> replace_ts(source, mod, z, parent; static_include=static_include, - include_functions=include_functions), - x.args) - filter!(x -> first(x) !== invalid, body_br) - Expr(x.head, first.(body_br)...), any(last.(body_br)) + new_args = Any[] + hasbroken = false + for z in x.args + nz, br = replace_ts(source, mod, z, parent; + static_include=static_include, + include_functions=include_functions) + nz === invalid && continue + push!(new_args, nz) + hasbroken |= br + end + Expr(x.head, new_args...), hasbroken end end @@ -778,8 +784,9 @@ function retest(@nospecialize(args::ArgType...); root = Testset.ReTestSet(Main, "Overall", overall=true) maxidw = Ref{Int}(0) # visual width for showing IDs (Ref for mutability in hack below) - tests_descs_hasbrokens = fetchtests.(modules, verbose, module_header, Ref(maxidw); + tests_descs_hasbrokens = [fetchtests(m, verbose, module_header, maxidw; strict=strict, dup=dup, static=static) + for m in modules] isempty(tests_descs_hasbrokens) && throw(ArgumentError("no modules using ReTest could be found")) diff --git a/src/precompile.jl b/src/precompile.jl index 9c47a1a..22a064c 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -20,27 +20,41 @@ end # _ReTestPrecompileTestsModule stderr_pipe = Pipe() stdout_pipe = Pipe() + function run_workload(f) + redirect_stdio(; stderr=stderr_pipe, stdout=stdout_pipe) do + try + f() + catch ex + if !(ex isa Test.TestSetException) + rethrow() + end + end + end + end + @compile_workload begin try - redirect_stdio(; stderr=stderr_pipe, stdout=stdout_pipe) do - retest(_ReTestPrecompile, _ReTestPrecompileTests; recursive=false, stats=true, spin=true) + run_workload() do + retest(_ReTestPrecompile, _ReTestPrecompileTests; + recursive=false, stats=true, spin=true) + end + run_workload() do + retest(_ReTestPrecompileTests, "precomp"; recursive=false) end - catch ex + catch close(stderr_pipe.in) close(stdout_pipe.in) - if !(ex isa Test.TestSetException) - stdout_str = read(stdout_pipe, String) - stderr_str = read(stderr_pipe, String) + stdout_str = read(stdout_pipe, String) + stderr_str = read(stderr_pipe, String) - @error "Precompilation failed, this is the captured stdout ($(length(stdout_str)) chars):" - println(stdout_str) + @error "Precompilation failed, this is the captured stdout ($(length(stdout_str)) chars):" + println(stdout_str) - @error "And this is the captured stderr ($(length(stderr_str)) chars):" - println(stderr_str) + @error "And this is the captured stderr ($(length(stderr_str)) chars):" + println(stderr_str) - rethrow() - end + rethrow() finally empty!(ReTest.TESTED_MODULES) close(stderr_pipe) diff --git a/src/utils.jl b/src/utils.jl index 93d9db0..658a267 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -5,11 +5,16 @@ issubmodule(m::Module, s) = s isa Module && parentmodule(s) == m && m != s function submodules(m::Module) # use invokelatest so freshly-loaded submodules are visible regardless # of the caller's world age (Julia 1.13+ binding semantics) - nms = filter!(names(m, all=true)) do y - invokelatest(isdefined, m, y) && !Base.isdeprecated(m, y) + result = Module[] + for y in names(m, all=true) + if invokelatest(isdefined, m, y) && !Base.isdeprecated(m, y) + v = invokelatest(getglobal, m, y) + if issubmodule(m, v) + push!(result, v) + end + end end - symbols = [invokelatest(getglobal, m, y) for y in nms] - filter!(x -> issubmodule(m, x), symbols) + result end # list of recursive submodules of m, including m itself From bbef0395978c3ac9a015966400edc194deb6db0b Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Fri, 1 May 2026 20:52:14 +0200 Subject: [PATCH 4/5] Always normalize hijacked paths --- src/hijack.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hijack.jl b/src/hijack.jl index 6cb3a7e..5c2fb97 100644 --- a/src/hijack.jl +++ b/src/hijack.jl @@ -19,6 +19,7 @@ function load(testpath::AbstractString; parentmodule::Module=Main, revise::Maybe{Bool}=nothing) Revise = get_revise(revise) + testpath = normpath(abspath(testpath)) if Revise === nothing Base.include(parentmodule, testpath) @@ -240,6 +241,7 @@ function populate_mod!(mod::Module, path; lazy, Revise, include::Maybe{Symbol}=n lazy ∈ (true, false, :brutal) || throw(ArgumentError("the `lazy` keyword must be `true`, `false` or `:brutal`")) + path = normpath(abspath(path)) files = Revise === nothing ? nothing : Dict(path => mod) substitute!(x) = substitute_retest!(x, lazy, include, files; include_functions=include_functions) From dfa64a01c318cea2df6c8d68f44a66869beadc35 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Fri, 1 May 2026 20:10:03 +0200 Subject: [PATCH 5/5] Bump version --- .github/workflows/CI.yml | 8 ++++---- .github/workflows/Documentation.yml | 2 ++ Project.toml | 2 +- README.md | 3 +++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2361a69..335dbde 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,11 +19,11 @@ jobs: fail-fast: false matrix: include: - - { os: ubuntu-latest, version: '1.10', arch: x64} - { os: ubuntu-latest, version: 'nightly', arch: x64} - - { os: ubuntu-latest, version: '1', arch: x86 } - - { os: windows-latest, version: '1', arch: x64} - - { os: macOS-latest, version: '1', arch: aarch64} + - { os: ubuntu-latest, version: '1.13.0-rc1', arch: x64 } + - { os: ubuntu-latest, version: '1.13.0-rc1', arch: x86 } + - { os: windows-latest, version: '1.13.0-rc1', arch: x64} + - { os: macOS-latest, version: '1.13.0-rc1', arch: aarch64} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 1606434..736b0dc 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -13,6 +13,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest + with: + version: '1.13.0-rc1' - uses: julia-actions/cache@v2 - name: Install dependencies run: | diff --git a/Project.toml b/Project.toml index c3460a2..1203427 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ReTest" uuid = "e0db7c4e-2690-44b9-bad6-7687da720f89" -version = "0.3.4" +version = "0.4.0" authors = ["Rafael Fourquet "] [deps] diff --git a/README.md b/README.md index 3f0be60..5c6db76 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ [![](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaTesting.github.io/ReTest.jl/stable) [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaTesting.github.io/ReTest.jl/dev) +> [!NOTE] +> For compatibility reasons, ReTest v0.4 requires at least Julia 1.13. + `ReTest` is a testing framework for Julia allowing: 1. Defining tests in source files, whose execution is deferred and triggered