src/testset.jl:310 declares _FILE_AST as a plain Dict, written via get! from _file_ast (:313), which is called inside Test.record (ext/PinaxTestExt.jl:141).
The module header claims task-locality. That covers the container -- the captured tree -- but not this cache, which is global and unlocked.
A suite using Threads.@spawned testsets is a supported Test pattern. Concurrent get! on an unlocked Dict can corrupt it or crash.
Why this one is worse than an ordinary race
It happens only when the report is enabled. The machinery whose selling point is that it observes the suite without affecting it becomes the thing that breaks the suite -- and only in the configuration a user turns on to get a report.
Fix
Guard _FILE_AST with a lock, or make it task-local like the container it lives beside.
src/testset.jl:310declares_FILE_ASTas a plainDict, written viaget!from_file_ast(:313), which is called insideTest.record(ext/PinaxTestExt.jl:141).The module header claims task-locality. That covers the container -- the captured tree -- but not this cache, which is global and unlocked.
A suite using
Threads.@spawned testsets is a supportedTestpattern. Concurrentget!on an unlockedDictcan corrupt it or crash.Why this one is worse than an ordinary race
It happens only when the report is enabled. The machinery whose selling point is that it observes the suite without affecting it becomes the thing that breaks the suite -- and only in the configuration a user turns on to get a report.
Fix
Guard
_FILE_ASTwith a lock, or make it task-local like the container it lives beside.