Skip to content

Commit ff8793f

Browse files
authored
Put testset exceptions in the test record. (#44)
This makes it easier to differentiate between failures and crashes.
1 parent 3df36d4 commit ff8793f

File tree

2 files changed

+75
-41
lines changed

2 files changed

+75
-41
lines changed

src/ParallelTestRunner.jl

Lines changed: 69 additions & 38 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)
@@ -665,13 +692,13 @@ 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
672699
last_status_update = Ref(time())
673700
try
674-
while isopen(printer_channel)
701+
while isopen(printer_channel) || isready(printer_channel)
675702
got_message = false
676703
while isready(printer_channel)
677704
# Try to get a message from the channel (with timeout)
@@ -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

@@ -707,7 +738,8 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
707738
update_status()
708739
last_status_update[] = time()
709740
end
710-
sleep(0.1)
741+
742+
isopen(printer_channel) && sleep(0.1)
711743
end
712744
catch ex
713745
if isa(ex, InterruptException)
@@ -766,16 +798,15 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
766798
break
767799
end
768800

769-
# return any other exception as the result
770-
# XXX: also put this in a test record?
771801
ex
772802
end
773803
test_t1 = time()
774804
push!(results, (test, result, test_t0, test_t1))
775805

776806
# act on the results
777807
if result isa AbstractTestRecord
778-
put!(printer_channel, (:finished, test, wrkr, result::RecordType))
808+
@assert result isa RecordType
809+
put!(printer_channel, (:finished, test, wrkr, result))
779810

780811
if memory_usage(result) > max_worker_rss
781812
# the worker has reached the max-rss limit, recycle it
@@ -784,13 +815,12 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
784815
end
785816
else
786817
@assert result isa Exception
787-
put!(printer_channel, (:errored, test, wrkr, test_t1-test_t0))
818+
put!(printer_channel, (:crashed, test, wrkr))
788819
if do_quickfail
789820
stop_work()
790821
end
791822

792-
# the worker encountered some failure, recycle it
793-
# so future tests get a fresh environment
823+
# the worker encountered some serious failure, recycle it
794824
p = recycle_worker(p)
795825
end
796826

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

905935
# 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
936+
if result isa AbstractTestRecord
937+
if result.value isa Test.AbstractTestSet
938+
testset = result.value
939+
historical_durations[testname] = stop - start
940+
else
941+
# TODO: improve the Test stdlib to keep track of the exact failure
942+
# instead of flattening into an exception without provenance
943+
@assert result.value isa Test.TestSetException
944+
testset = create_testset(testname; start, stop)
945+
for i in 1:result.value.pass
914946
Test.record(testset, Test.Pass(:test, nothing, nothing, nothing, LineNumberNode(@__LINE__, @__FILE__)))
915947
end
916-
for i in 1:result.captured.ex.broken
948+
for i in 1:result.value.broken
917949
Test.record(testset, Test.Broken(:test, nothing))
918950
end
919-
for t in result.captured.ex.errors_and_fails
951+
for t in result.value.errors_and_fails
920952
Test.record(testset, t)
921953
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)))
931954
end
955+
else
956+
# If this test raised an exception that is not a remote testset
957+
# exception, that means the test runner itself had some problem, so we
958+
# may have hit a segfault, deserialization errors or something similar.
959+
# Record this testset as Errored.
960+
@assert result isa Exception
961+
testset = create_testset(testname; start, stop)
962+
Test.record(testset, Test.Error(:nontest_error, testname, nothing, Base.ExceptionStack(NamedTuple[(;exception = result, backtrace = [])]), LineNumberNode(1)))
932963
end
933964

934965
with_testset(testset) do

test/runtests.jl

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

122122
@testset "crashing test" begin
123123
custom_tests = Dict(
124-
"crash" => quote
124+
"abort" => quote
125125
abort() = ccall(:abort, Nothing, ())
126126
abort()
127127
end
128128
)
129129

130-
println("NOTE: The next test is expected to crash a worker process, which may print some output to the terminal.")
130+
print("""NOTE: The next test is expected to crash a worker process,
131+
which may print some output to the terminal.
132+
""")
131133
io = IOBuffer()
132134
@test_throws Test.FallbackTestSetException("Test run finished with errors") begin
133135
runtests(ParallelTestRunner, ["--verbose"]; custom_tests, stdout=io, stderr=io)
134136
end
135137
println()
136138

137139
str = String(take!(io))
138-
@test contains(str, r"crash .+ started at")
140+
@test contains(str, r"abort .+ started at")
141+
@test contains(str, r"abort .+ crashed at")
139142
@test contains(str, "FAILURE")
140143
@test contains(str, "Error During Test")
141144
@test contains(str, "ProcessExitedException")

0 commit comments

Comments
 (0)