Skip to content

Hacky prototype for running test failures first #212

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
22 changes: 21 additions & 1 deletion src/ReTestItems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ else
const errmon = identity
end

# Set of testitems identified by (file, name) tuples storing whether the testitem
# has failed (-1) or passed (1) or hasn't yet been seen (0).
# Used by fails_first to sort failures before unseen before passes.
const GLOBAL_TEST_STATUS = Dict{Tuple{String, String},Int}()
reset_test_status!() = (empty!(GLOBAL_TEST_STATUS); nothing)
_status_when_last_seen(ti) = get(GLOBAL_TEST_STATUS, (ti.file, ti.name), 0)
_store_failure!(ti) = GLOBAL_TEST_STATUS[(ti.file, ti.name)] = -1
_store_pass!(ti) = GLOBAL_TEST_STATUS[(ti.file, ti.name)] = 1

# We use the Test.jl stdlib `failfast` mechanism to implement `testitem_failfast`, but that
# feature was only added in Julia v1.9, so we define these shims so our code can be
# compatible with earlier Julia versions, with `testitem_failfast` just having no effect.
Expand Down Expand Up @@ -257,6 +266,7 @@ end
timeout_profile_wait::Int
memory_threshold::Float64
gc_between_testitems::Bool
fails_first::Bool
end


Expand All @@ -281,6 +291,7 @@ function runtests(
gc_between_testitems::Bool=parse(Bool, get(ENV, "RETESTITEMS_GC_BETWEEN_TESTITEMS", string(nworkers > 1))),
failfast::Bool=parse(Bool, get(ENV, "RETESTITEMS_FAILFAST", "false")),
testitem_failfast::Bool=parse(Bool, get(ENV, "RETESTITEMS_TESTITEM_FAILFAST", string(failfast))),
fails_first::Bool=parse(Bool, get(ENV, "RETESTITEMS_FAILS_FIRST", "false")),
)
nworker_threads = _validated_nworker_threads(nworker_threads)
paths′ = _validated_paths(paths, validate_paths)
Expand All @@ -291,6 +302,7 @@ function runtests(
testitem_timeout > 0 || throw(ArgumentError("`testitem_timeout` must be a positive number, got $(repr(testitem_timeout))"))
timeout_profile_wait >= 0 || throw(ArgumentError("`timeout_profile_wait` must be a non-negative number, got $(repr(timeout_profile_wait))"))
test_end_expr.head === :block || throw(ArgumentError("`test_end_expr` must be a `:block` expression, got a `$(repr(test_end_expr.head))` expression"))
!fails_first || nworkers == 0 || throw(ArgumentError("`fails_first` is only supported with `nworkers=0`"))
# If we were given paths but none were valid, then nothing to run.
!isempty(paths) && isempty(paths′) && return nothing
ti_filter = TestItemFilter(shouldrun, tags, name)
Expand All @@ -301,7 +313,7 @@ function runtests(
(timeout_profile_wait > 0 && Sys.iswindows()) && @warn "CPU profiles on timeout is not supported on Windows, ignoring `timeout_profile_wait`"
mkpath(RETESTITEMS_TEMP_FOLDER[]) # ensure our folder wasn't removed
save_current_stdio()
cfg = _Config(; nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, testitem_failfast, failfast, retries, logs, report, verbose_results, timeout_profile_wait, memory_threshold, gc_between_testitems)
cfg = _Config(; nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, testitem_failfast, failfast, retries, logs, report, verbose_results, timeout_profile_wait, memory_threshold, gc_between_testitems, fails_first)
debuglvl = Int(debug)
if debuglvl > 0
withdebug(debuglvl) do
Expand Down Expand Up @@ -387,6 +399,9 @@ function _runtests_in_current_env(
ctx = TestContext(proj_name, ntestitems)
# we use a single TestSetupModules
ctx.setups_evaled = TestSetupModules()
if cfg.fails_first && !isempty(GLOBAL_TEST_STATUS)
sort!(testitems.testitems; by=_status_when_last_seen)
end
for (i, testitem) in enumerate(testitems.testitems)
testitem.workerid[] = Libc.getpid()
testitem.eval_number[] = i
Expand All @@ -411,6 +426,11 @@ function _runtests_in_current_env(
break
end
end
if is_non_pass
_store_failure!(testitem)
else
_store_pass!(testitem)
end
if cfg.failfast && is_non_pass
cancel!(testitems)
print_failfast_cancellation(testitem)
Expand Down
Loading