Skip to content

Commit 8b14a04

Browse files
committed
Put testset exceptions in the test record.
This makes it easier to differentiate between failures and crashes.
1 parent 4ce98c9 commit 8b14a04

File tree

2 files changed

+73
-40
lines changed

2 files changed

+73
-40
lines changed

src/ParallelTestRunner.jl

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ end
6868
abstract type AbstractTestRecord end
6969

7070
struct TestRecord <: AbstractTestRecord
71-
test::Any
72-
output::String
71+
value::Any # AbstractTestSet or TestSetException
72+
output::String # captured stdout/stderr
73+
74+
# stats
7375
time::Float64
7476
bytes::UInt64
7577
gctime::Float64
@@ -165,7 +167,7 @@ function print_test_finished(record::TestRecord, wrkr, test, ctx::TestIOContext)
165167
end
166168
end
167169

168-
function print_test_errorred(::Type{TestRecord}, wrkr, test, ctx::TestIOContext, test_time)
170+
function print_test_failed(record::TestRecord, wrkr, test, ctx::TestIOContext)
169171
lock(ctx.lock)
170172
try
171173
printstyled(ctx.stderr, test, color = :red)
@@ -174,7 +176,7 @@ function print_test_errorred(::Type{TestRecord}, wrkr, test, ctx::TestIOContext,
174176
lpad("($wrkr)", ctx.name_align - textwidth(test) + 1, " "), " |"
175177
, color = :red
176178
)
177-
time_str = @sprintf("%7.2f", test_time)
179+
time_str = @sprintf("%7.2f", record.time)
178180
printstyled(ctx.stderr, lpad(time_str, ctx.elapsed_align + 1, " "), "", color = :red)
179181

180182
failed_str = "failed at $(now())\n"
@@ -183,6 +185,24 @@ function print_test_errorred(::Type{TestRecord}, wrkr, test, ctx::TestIOContext,
183185
failed_str = lpad(failed_str, fail_align, " ")
184186
printstyled(ctx.stderr, failed_str, color = :red)
185187

188+
# TODO: print other stats?
189+
190+
flush(ctx.stderr)
191+
finally
192+
unlock(ctx.lock)
193+
end
194+
end
195+
196+
function print_test_crashed(::Type{TestRecord}, wrkr, test, ctx::TestIOContext)
197+
lock(ctx.lock)
198+
try
199+
printstyled(ctx.stderr, test, color = :red)
200+
printstyled(
201+
ctx.stderr,
202+
lpad("($wrkr)", ctx.name_align - textwidth(test) + 1, " "), " |",
203+
" "^ctx.elapsed_align, " crashed at $(now())\n", color = :red
204+
)
205+
186206
flush(ctx.stderr)
187207
finally
188208
unlock(ctx.lock)
@@ -209,8 +229,15 @@ function runtest(::Type{TestRecord}, f, name, init_code, color)
209229

210230
mktemp() do path, io
211231
stats = redirect_stdio(stdout=io, stderr=io) do
212-
@timed @testset $name begin
213-
$f
232+
@timed try
233+
@testset $name begin
234+
$f
235+
end
236+
catch err
237+
isa(err, Test.TestSetException) || rethrow()
238+
239+
# return the error to package it into a TestRecord
240+
err
214241
end
215242
end
216243
close(io)
@@ -385,7 +412,7 @@ Run Julia tests in parallel across multiple worker processes.
385412
386413
- `mod`: The module calling runtests
387414
- `ARGS`: Command line arguments array, typically from `Base.ARGS`. When you run the tests
388-
with `Pkg.test`, this can be changed with the `test_args` keyword argument.
415+
with `Pkg.value`, this can be changed with the `test_args` keyword argument.
389416
390417
Several keyword arguments are also supported:
391418
@@ -665,7 +692,7 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
665692
# Message types for the printer channel
666693
# (:started, test_name, worker_id)
667694
# (:finished, test_name, worker_id, record)
668-
# (:errored, test_name, worker_id, test_time)
695+
# (:crashed, test_name, worker_id, test_time)
669696
printer_channel = Channel{Tuple}(100)
670697

671698
printer_task = @async begin
@@ -692,13 +719,17 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
692719
test_name, wrkr, record = msg[2], msg[3], msg[4]
693720

694721
clear_status()
695-
print_test_finished(record, wrkr, test_name, io_ctx)
722+
if record.value isa Exception
723+
print_test_failed(record, wrkr, test_name, io_ctx)
724+
else
725+
print_test_finished(record, wrkr, test_name, io_ctx)
726+
end
696727

697-
elseif msg_type == :errored
698-
test_name, wrkr, test_time = msg[2], msg[3], msg[4]
728+
elseif msg_type == :crashed
729+
test_name, wrkr = msg[2], msg[3]
699730

700731
clear_status()
701-
print_test_errorred(RecordType, wrkr, test_name, io_ctx, test_time)
732+
print_test_crashed(RecordType, wrkr, test_name, io_ctx)
702733
end
703734
end
704735

@@ -766,16 +797,15 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
766797
break
767798
end
768799

769-
# return any other exception as the result
770-
# XXX: also put this in a test record?
771800
ex
772801
end
773802
test_t1 = time()
774803
push!(results, (test, result, test_t0, test_t1))
775804

776805
# act on the results
777806
if result isa AbstractTestRecord
778-
put!(printer_channel, (:finished, test, wrkr, result::RecordType))
807+
@assert result isa RecordType
808+
put!(printer_channel, (:finished, test, wrkr, result))
779809

780810
if memory_usage(result) > max_worker_rss
781811
# the worker has reached the max-rss limit, recycle it
@@ -784,13 +814,12 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
784814
end
785815
else
786816
@assert result isa Exception
787-
put!(printer_channel, (:errored, test, wrkr, test_t1-test_t0))
817+
put!(printer_channel, (:crashed, test, wrkr))
788818
if do_quickfail
789819
stop_work()
790820
end
791821

792-
# the worker encountered some failure, recycle it
793-
# so future tests get a fresh environment
822+
# the worker encountered some serious failure, recycle it
794823
p = recycle_worker(p)
795824
end
796825

@@ -903,32 +932,33 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
903932
push!(completed_tests, testname)
904933

905934
# decode or fake a testset
906-
if isa(result, AbstractTestRecord)
907-
testset = result.test
908-
historical_durations[testname] = stop - start
909-
else
910-
testset = create_testset(testname; start, stop)
911-
if isa(result, RemoteException) &&
912-
isa(result.captured.ex, Test.TestSetException)
913-
for i in 1:result.captured.ex.pass
935+
if result isa AbstractTestRecord
936+
if result.value isa Test.AbstractTestSet
937+
testset = result.value
938+
historical_durations[testname] = stop - start
939+
else
940+
# TODO: improve the Test stdlib to keep track of the exact failure
941+
# instead of flattening into an exception without provenance
942+
@assert result.value isa Test.TestSetException
943+
testset = create_testset(testname; start, stop)
944+
for i in 1:result.value.pass
914945
Test.record(testset, Test.Pass(:test, nothing, nothing, nothing, LineNumberNode(@__LINE__, @__FILE__)))
915946
end
916-
for i in 1:result.captured.ex.broken
947+
for i in 1:result.value.broken
917948
Test.record(testset, Test.Broken(:test, nothing))
918949
end
919-
for t in result.captured.ex.errors_and_fails
950+
for t in result.value.errors_and_fails
920951
Test.record(testset, t)
921952
end
922-
else
923-
if !isa(result, Exception)
924-
result = ErrorException(string("Unknown result type : ", typeof(result)))
925-
end
926-
# If this test raised an exception that is not a remote testset exception,
927-
# i.e. not a RemoteException capturing a TestSetException that means
928-
# the test runner itself had some problem, so we may have hit a segfault,
929-
# deserialization errors or something similar. Record this testset as Errored.
930-
Test.record(testset, Test.Error(:nontest_error, testname, nothing, Base.ExceptionStack(NamedTuple[(;exception = result, backtrace = [])]), LineNumberNode(1)))
931953
end
954+
else
955+
# If this test raised an exception that is not a remote testset
956+
# exception, that means the test runner itself had some problem, so we
957+
# may have hit a segfault, deserialization errors or something similar.
958+
# Record this testset as Errored.
959+
@assert result isa Exception
960+
testset = create_testset(testname; start, stop)
961+
Test.record(testset, Test.Error(:nontest_error, testname, nothing, Base.ExceptionStack(NamedTuple[(;exception = result, backtrace = [])]), LineNumberNode(1)))
932962
end
933963

934964
with_testset(testset) do

test/runtests.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,24 @@ end
117117

118118
@testset "crashing test" begin
119119
custom_tests = Dict(
120-
"crash" => quote
120+
"abort" => quote
121121
abort() = ccall(:abort, Nothing, ())
122122
abort()
123123
end
124124
)
125125

126-
println("NOTE: The next test is expected to crash a worker process, which may print some output to the terminal.")
126+
print("""NOTE: The next test is expected to crash a worker process,
127+
which may print some output to the terminal.
128+
""")
127129
io = IOBuffer()
128130
@test_throws Test.FallbackTestSetException("Test run finished with errors") begin
129131
runtests(ParallelTestRunner, ["--verbose"]; custom_tests, stdout=io, stderr=io)
130132
end
131133
println()
132134

133135
str = String(take!(io))
134-
@test contains(str, r"crash .+ started at")
136+
@test contains(str, r"abort .+ started at")
137+
@test contains(str, r"abort .+ crashed at")
135138
@test contains(str, "FAILURE")
136139
@test contains(str, "Error During Test")
137140
@test contains(str, "ProcessExitedException")

0 commit comments

Comments
 (0)