@@ -12,6 +12,16 @@ using Serialization
1212import Test
1313import Random
1414import IOCapture
15+ import Test: DefaultTestSet
16+
17+ function anynonpass (ts:: Test.AbstractTestSet )
18+ @static if VERSION >= v " 1.13.0-DEV.1037"
19+ return Test. anynonpass (ts)
20+ else
21+ Test. get_test_counts (ts)
22+ return ts. anynonpass
23+ end
24+ end
1525
1626# Always set the max rss so that if tests add large global variables (which they do) we don't make the GC's life too hard
1727if Sys. WORD_SIZE == 64
6878abstract type AbstractTestRecord end
6979
7080struct TestRecord <: AbstractTestRecord
71- value:: Any # AbstractTestSet or TestSetException
81+ value:: DefaultTestSet
7282 output:: String # captured stdout/stderr
7383
7484 # stats
@@ -82,6 +92,10 @@ function memory_usage(rec::TestRecord)
8292 return rec. rss
8393end
8494
95+ function Base. getindex (rec:: TestRecord )
96+ return rec. value
97+ end
98+
8599
86100#
87101# overridable I/O context for pretty-printing
@@ -213,13 +227,46 @@ end
213227#
214228# entry point
215229#
230+ """
231+ WorkerTestSet
232+
233+ A test set wrapper used internally by worker processes.
234+ `Base.DefaultTestSet` detects when it is the top-most and throws
235+ a `TestSetException` containing very little information. By inserting this
236+ wrapper as the top-most test set, we can capture the full results.
237+ """
238+ mutable struct WorkerTestSet <: Test.AbstractTestSet
239+ const name:: String
240+ wrapped_ts:: Test.DefaultTestSet
241+ function WorkerTestSet (name:: AbstractString )
242+ new (name)
243+ end
244+ end
245+
246+ function Test. record (ts:: WorkerTestSet , res)
247+ @assert res isa Test. DefaultTestSet
248+ @assert ! isdefined (ts, :wrapped_ts )
249+ ts. wrapped_ts = res
250+ return nothing
251+ end
252+
253+ function Test. finish (ts:: WorkerTestSet )
254+ # This testset is just a placeholder so it must be the top-most
255+ @assert Test. get_testset_depth () == 0
256+ @assert isdefined (ts, :wrapped_ts )
257+ # Return the wrapped_ts so that we don't need to handle WorkerTestSet anywhere else
258+ return ts. wrapped_ts
259+ end
216260
217261function runtest (:: Type{TestRecord} , f, name, init_code, color)
218262 function inner ()
219263 # generate a temporary module to execute the tests in
220264 mod = @eval (Main, module $ (gensym (name)) end )
221265 @eval (mod, import ParallelTestRunner: Test, Random)
222266 @eval (mod, using . Test, . Random)
267+ # Both bindings must be imported since `@testset` can't handle fully-qualified names when VERSION < v"1.11.0-DEV.1518".
268+ @eval (mod, import ParallelTestRunner: WorkerTestSet)
269+ @eval (mod, import Test: DefaultTestSet)
223270
224271 Core. eval (mod, init_code)
225272
@@ -229,15 +276,13 @@ function runtest(::Type{TestRecord}, f, name, init_code, color)
229276
230277 mktemp () do path, io
231278 stats = redirect_stdio (stdout = io, stderr = io) do
232- @timed try
233- @testset $ name begin
279+ # @testset CustomTestRecord switches the all lower-level testset to our custom testset,
280+ # so we need to have two layers here such that the user-defined testsets are using `DefaultTestSet`.
281+ # This also guarantees our invariant about `WorkerTestSet` containing a single `DefaultTestSet`.
282+ @timed @testset WorkerTestSet " placeholder" begin
283+ @testset DefaultTestSet $ name begin
234284 $ f
235285 end
236- catch err
237- isa (err, Test. TestSetException) || rethrow ()
238-
239- # return the error to package it into a TestRecord
240- err
241286 end
242287 end
243288 close (io)
@@ -719,7 +764,7 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
719764 test_name, wrkr, record = msg[2 ], msg[3 ], msg[4 ]
720765
721766 clear_status ()
722- if record. value isa Exception
767+ if anynonpass ( record[])
723768 print_test_failed (record, wrkr, test_name, io_ctx)
724769 else
725770 print_test_finished (record, wrkr, test_name, io_ctx)
@@ -814,6 +859,7 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
814859 Malt. stop (wrkr)
815860 end
816861 else
862+ # One of Malt.TerminatedWorkerException, Malt.RemoteException, or ErrorException
817863 @assert result isa Exception
818864 put! (printer_channel, (:crashed , test, worker_id (wrkr)))
819865 if do_quickfail
@@ -932,31 +978,14 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
932978 for (testname, result, start, stop) in results
933979 push! (completed_tests, testname)
934980
935- # decode or fake a testset
936981 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
946- Test. record (testset, Test. Pass (:test , nothing , nothing , nothing , LineNumberNode (@__LINE__ , @__FILE__ )))
947- end
948- for i in 1 : result. value. broken
949- Test. record (testset, Test. Broken (:test , nothing ))
950- end
951- for t in result. value. errors_and_fails
952- Test. record (testset, t)
953- end
954- end
982+ testset = result[]:: DefaultTestSet
983+ historical_durations[testname] = stop - start
955984 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.
985+ # If this test raised an exception that means the test runner itself had some problem,
986+ # so we may have hit a segfault, deserialization errors or something similar.
959987 # Record this testset as Errored.
988+ # One of Malt.TerminatedWorkerException, Malt.RemoteException, or ErrorException
960989 @assert result isa Exception
961990 testset = create_testset (testname; start, stop)
962991 Test. record (testset, Test. Error (:nontest_error , testname, nothing , Base. ExceptionStack (NamedTuple[(;exception = result, backtrace = [])]), LineNumberNode (1 )))
@@ -1003,8 +1032,7 @@ function runtests(mod::Module, ARGS; test_filter = Returns(true), RecordType = T
10031032 end
10041033 print (io_ctx. stdout , c. output)
10051034 end
1006- if (VERSION >= v " 1.13.0-DEV.1037" && ! Test. anynonpass (o_ts)) ||
1007- (VERSION < v " 1.13.0-DEV.1037" && ! o_ts. anynonpass)
1035+ if ! anynonpass (o_ts)
10081036 println (io_ctx. stdout , " \0 33[32;1mSUCCESS\0 33[0m" )
10091037 else
10101038 println (io_ctx. stderr , " \0 33[31;1mFAILURE\0 33[0m\n " )
0 commit comments