From 1403855e9eaff667fc3704a9421f1d5502754f0a Mon Sep 17 00:00:00 2001 From: David Anthoff Date: Wed, 19 Aug 2026 11:47:16 -0700 Subject: [PATCH] Precompile the test environment inside the serialized window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The controller nominates one test process to activate the environment while the others wait, and releases them when it reports back. The point is to build the test environment's precompile caches exactly once, because Julia has no cache file locking before 1.10: two processes precompiling the same package race, and on Windows the loser cannot replace a `.ji` the winner holds open, so it dies with "Cannot write cache file". From Julia 1.9 on that works, because `TestEnv.activate` finishes with `Pkg._auto_precompile` on the sandbox environment. The older TestEnv variants stop at `Pkg.activate`, and `Pkg.instantiate` did not precompile before Julia 1.6 either — so on Julia 1.0 to 1.8 activation built nothing, every peer was released, and each one then evaluated `using ` at the same time. Whoever won decided whether the run passed. Precompiling here puts that work back inside the window the gate already serializes. `Pkg.API.precompile()` rather than `Pkg.precompile()`: the latter does not exist before Julia 1.4, where the name resolves to `Base.precompile` through the implicit `using Base` and throws a MethodError. Verified with a fixture whose only test item has `default_imports=false`, so nothing in the item loads the package: on Julia 1.0 the package and its dependency have no cache file after a run today, and both do with this change. --- .../TestItemServer/src/TestItemServer.jl | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/testprocess/TestItemServer/src/TestItemServer.jl b/testprocess/TestItemServer/src/TestItemServer.jl index 5b6c69e..447976d 100644 --- a/testprocess/TestItemServer/src/TestItemServer.jl +++ b/testprocess/TestItemServer/src/TestItemServer.jl @@ -1089,6 +1089,34 @@ function activate_env_request(params::TestItemServerProtocol.ActivateEnvParams, TestEnv.activate(params.packageName) end + # The controller serializes this request: one test process is nominated to activate + # while every other one waits, and they are released when it reports back. The point + # of that is to build the test environment's precompile caches exactly once, because + # Julia has no cache file locking before 1.10 — two processes precompiling the same + # package race, and on Windows the loser cannot replace a `.ji` the winner holds + # open, so it dies with "Cannot write cache file". + # + # From Julia 1.9 on, `TestEnv.activate` finishes with `Pkg._auto_precompile` on the + # sandbox environment, so the caches really are built inside that window. The older + # variants stop at `Pkg.activate`, and `Pkg.instantiate` did not precompile before + # Julia 1.6 either — so nothing was compiled until the first `using` inside a test + # item, which happens in every test process at once, after the gate has let them all + # go. Precompiling here puts that work back inside the serialized window. + @static if VERSION < v"1.9" + try + # `Pkg.precompile` does not exist before Julia 1.4 — it only became a + # forwarder to `Pkg.API.precompile` then, and before that the name resolved + # to `Base.precompile` through the implicit `using Base` and threw a + # MethodError. `Pkg.API.precompile()` has a zero-argument method in every + # version this branch runs on. + Pkg.API.precompile() + catch err + # Not worth failing activation over: whatever is wrong will come back with a + # better message when a test item loads the package. + @debug "Precompiling the test environment failed" exception = (err, catch_backtrace()) + end + end + return TestItemServerProtocol.ActivateEnvResult( status = "success", error = missing