@@ -1208,17 +1208,27 @@ are any `Fail`s or `Error`s, an exception will be thrown only at the end,
1208
1208
along with a summary of the test results.
1209
1209
"""
1210
1210
mutable struct DefaultTestSet <: AbstractTestSet
1211
- description:: String
1212
- results:: Vector{Any}
1213
- n_passed:: Int
1214
- anynonpass:: Bool
1215
- verbose:: Bool
1216
- showtiming:: Bool
1217
- time_start:: Float64
1218
- time_end:: Union{Float64,Nothing}
1219
- failfast:: Bool
1220
- file:: Union{String,Nothing}
1211
+ const description:: String
1212
+ const verbose:: Bool
1213
+ const showtiming:: Bool
1214
+ const failfast:: Bool
1215
+ const file:: Union{String,Nothing}
1216
+ const time_start:: Float64
1217
+
1218
+ # Warning: Not thread-safe
1221
1219
rng:: Union{Nothing,AbstractRNG}
1220
+
1221
+ @atomic n_passed:: Int
1222
+ @atomic time_end:: Float64
1223
+
1224
+ # Memoized test result state over `results` - Computed only once the test set is finished
1225
+ # 0x0: Unknown
1226
+ # 0x1: All passed
1227
+ # 0x2: Some failed
1228
+ @atomic anynonpass:: UInt8
1229
+
1230
+ results_lock:: ReentrantLock
1231
+ results:: Vector{Any}
1222
1232
end
1223
1233
function DefaultTestSet (desc:: AbstractString ; verbose:: Bool = false , showtiming:: Bool = true , failfast:: Union{Nothing,Bool} = nothing , source = nothing , rng = nothing )
1224
1234
if isnothing (failfast)
@@ -1230,7 +1240,9 @@ function DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming:
1230
1240
failfast = false
1231
1241
end
1232
1242
end
1233
- return DefaultTestSet (String (desc):: String , [], 0 , false , verbose, showtiming, time (), nothing , failfast, extract_file (source), rng)
1243
+ return DefaultTestSet (String (desc):: String ,
1244
+ verbose, showtiming, failfast, extract_file (source),
1245
+ time (), rng, 0 , 0. , 0x00 , ReentrantLock (), Any[])
1234
1246
end
1235
1247
extract_file (source:: LineNumberNode ) = extract_file (source. file)
1236
1248
extract_file (file:: Symbol ) = string (file)
@@ -1239,15 +1251,15 @@ extract_file(::Nothing) = nothing
1239
1251
struct FailFastError <: Exception end
1240
1252
1241
1253
# For a broken result, simply store the result
1242
- record (ts:: DefaultTestSet , t:: Broken ) = (push! (ts. results, t); t)
1254
+ record (ts:: DefaultTestSet , t:: Broken ) = (( @lock ts . results_lock push! (ts. results, t) ); t)
1243
1255
# For a passed result, do not store the result since it uses a lot of memory, unless
1244
1256
# `record_passes()` is true. i.e. set env var `JULIA_TEST_RECORD_PASSES=true` before running any testsets
1245
1257
function record (ts:: DefaultTestSet , t:: Pass )
1246
- ts. n_passed += 1
1258
+ @atomic :monotonic ts. n_passed += 1
1247
1259
if record_passes ()
1248
1260
# throw away the captured data so it can be GC-ed
1249
1261
t_nodata = Pass (t. test_type, t. orig_expr, nothing , t. value, t. source, t. message_only)
1250
- push! (ts. results, t_nodata)
1262
+ @lock ts . results_lock push! (ts. results, t_nodata)
1251
1263
return t_nodata
1252
1264
end
1253
1265
return t
@@ -1268,7 +1280,7 @@ function record(ts::DefaultTestSet, t::Union{Fail, Error}; print_result::Bool=TE
1268
1280
println ()
1269
1281
end
1270
1282
end
1271
- push! (ts. results, t)
1283
+ @lock ts . results_lock push! (ts. results, t)
1272
1284
(FAIL_FAST[] || ts. failfast) && throw (FailFastError ())
1273
1285
return t
1274
1286
end
@@ -1297,7 +1309,7 @@ results(ts::DefaultTestSet) = ts.results
1297
1309
# When a DefaultTestSet finishes, it records itself to its parent
1298
1310
# testset, if there is one. This allows for recursive printing of
1299
1311
# the results at the end of the tests
1300
- record (ts:: DefaultTestSet , t:: AbstractTestSet ) = push! (ts. results, t)
1312
+ record (ts:: DefaultTestSet , t:: AbstractTestSet ) = @lock ts . results_lock push! (ts. results, t)
1301
1313
1302
1314
@specialize
1303
1315
@@ -1402,7 +1414,9 @@ const TESTSET_PRINT_ENABLE = Ref(true)
1402
1414
# Called at the end of a @testset, behaviour depends on whether
1403
1415
# this is a child of another testset, or the "root" testset
1404
1416
function finish (ts:: DefaultTestSet ; print_results:: Bool = TESTSET_PRINT_ENABLE[])
1405
- ts. time_end = time ()
1417
+ if (@atomicswap ts. time_end = time ()) != = 0.
1418
+ error (" Test set was finished more than once" )
1419
+ end
1406
1420
# If we are a nested test set, do not print a full summary
1407
1421
# now - let the parent test set do the printing
1408
1422
if get_testset_depth () != 0
@@ -1433,24 +1447,6 @@ function finish(ts::DefaultTestSet; print_results::Bool=TESTSET_PRINT_ENABLE[])
1433
1447
return ts
1434
1448
end
1435
1449
1436
- # Recursive function that finds the column that the result counts
1437
- # can begin at by taking into account the width of the descriptions
1438
- # and the amount of indentation. If a test set had no failures, and
1439
- # no failures in child test sets, there is no need to include those
1440
- # in calculating the alignment
1441
- function get_alignment (ts:: DefaultTestSet , depth:: Int )
1442
- # The minimum width at this depth is
1443
- ts_width = 2 * depth + length (ts. description)
1444
- # If not verbose and all passing, no need to look at children
1445
- ! ts. verbose && ! ts. anynonpass && return ts_width
1446
- # Return the maximum of this width and the minimum width
1447
- # for all children (if they exist)
1448
- isempty (ts. results) && return ts_width
1449
- child_widths = map (t-> get_alignment (t, depth+ 1 ), ts. results)
1450
- return max (ts_width, maximum (child_widths))
1451
- end
1452
- get_alignment (ts, depth:: Int ) = 0
1453
-
1454
1450
# Recursive function that fetches backtraces for any and all errors
1455
1451
# or failures the testset and its children encountered
1456
1452
function filter_errors (ts:: DefaultTestSet )
@@ -1536,7 +1532,7 @@ function get_test_counts(ts::DefaultTestSet)
1536
1532
passes, fails, errors, broken = ts. n_passed, 0 , 0 , 0
1537
1533
# cumulative results
1538
1534
c_passes, c_fails, c_errors, c_broken = 0 , 0 , 0 , 0
1539
- for t in ts. results
1535
+ @lock ts . results_lock for t in ts. results
1540
1536
isa (t, Fail) && (fails += 1 )
1541
1537
isa (t, Error) && (errors += 1 )
1542
1538
isa (t, Broken) && (broken += 1 )
@@ -1549,10 +1545,37 @@ function get_test_counts(ts::DefaultTestSet)
1549
1545
end
1550
1546
end
1551
1547
duration = format_duration (ts)
1552
- ts. anynonpass = (fails + errors + c_fails + c_errors > 0 )
1553
- return TestCounts (true , passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration)
1548
+ tc = TestCounts (true , passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration)
1549
+ # Memoize for printing convenience
1550
+ @atomic :monotonic ts. anynonpass = (anynonpass (tc) ? 0x02 : 0x01 )
1551
+ return tc
1552
+ end
1553
+ anynonpass (tc:: TestCounts ) = (tc. fails + tc. errors + tc. cumulative_fails + tc. cumulative_errors > 0 )
1554
+ function anynonpass (ts:: DefaultTestSet )
1555
+ if (@atomic :monotonic ts. anynonpass) == 0x00
1556
+ get_test_counts (ts) # fills in the anynonpass field
1557
+ end
1558
+ return (@atomic :monotonic ts. anynonpass) != 0x01
1554
1559
end
1555
1560
1561
+ # Recursive function that finds the column that the result counts
1562
+ # can begin at by taking into account the width of the descriptions
1563
+ # and the amount of indentation. If a test set had no failures, and
1564
+ # no failures in child test sets, there is no need to include those
1565
+ # in calculating the alignment
1566
+ function get_alignment (ts:: DefaultTestSet , depth:: Int )
1567
+ # The minimum width at this depth is
1568
+ ts_width = 2 * depth + length (ts. description)
1569
+ # If not verbose and all passing, no need to look at children
1570
+ ! ts. verbose && ! anynonpass (ts) && return ts_width
1571
+ # Return the maximum of this width and the minimum width
1572
+ # for all children (if they exist)
1573
+ isempty (ts. results) && return ts_width
1574
+ child_widths = map (t-> get_alignment (t, depth+ 1 ), ts. results)
1575
+ return max (ts_width, maximum (child_widths))
1576
+ end
1577
+ get_alignment (ts, depth:: Int ) = 0
1578
+
1556
1579
"""
1557
1580
format_duration(::AbstractTestSet)
1558
1581
@@ -1564,7 +1587,7 @@ format_duration(::AbstractTestSet) = "?s"
1564
1587
1565
1588
function format_duration (ts:: DefaultTestSet )
1566
1589
(; time_start, time_end) = ts
1567
- isnothing ( time_end) && return " "
1590
+ time_end === 0. && return " "
1568
1591
1569
1592
dur_s = time_end - time_start
1570
1593
if dur_s < 60
0 commit comments