Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions test/test_testserver_precompile.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@testitem "The test process environment precompiles cleanly" begin
# `TestItemServer` assembles the vendored packages by hand, and an override that
# *redefines* one of their methods rather than adding a more specific one is a method
# overwrite — which Julia rejects outright while a package precompiles.
#
# Nothing else in this suite notices directly: precompilation is warn-only on the path a
# test process takes, so the package still loads. What it costs is a full source reload
# of the vendored stack in every test process (tens of seconds each) and a precompile
# banner on its stderr, which then lands in whatever a test item captures. Those
# second-order effects are how it last surfaced, in assertions about process output.
# Assert the cache is actually built instead.
env_dir = normpath(joinpath(@__DIR__, "..", "testprocess", "environments"))
versioned = joinpath(env_dir, "v$(VERSION.major).$(VERSION.minor)")
project = isdir(versioned) ? versioned : joinpath(env_dir, "fallback")

# `Base.compilecache` rather than `Pkg.precompile`: it rebuilds unconditionally, so a
# cache some other test item already wrote cannot short-circuit the check, and it reports
# a failure as a `Core.PrecompilableError` return value — `Pkg.precompile` reports one as
# a `?` in its progress list and still exits 0.
code = """
result = Base.compilecache(Base.identify_package("TestItemServer"))
result isa Tuple || exit(1)
"""
julia = joinpath(Sys.BINDIR, Base.julia_exename())
cmd = `$julia --startup-file=no --history-file=no --project=$project -e $code`

io = IOBuffer()
process = run(pipeline(ignorestatus(cmd), stdout=io, stderr=io))
output = String(take!(io))

ok = success(process) &&
!occursin("Method overwriting is not permitted", output) &&
!occursin("overwritten at", output)
if !ok
println("── precompiling TestItemServer in $project ──")
println(output)
end

@test success(process)
@test !occursin("Method overwriting is not permitted", output)
@test !occursin("overwritten at", output)
end
18 changes: 18 additions & 0 deletions testprocess/TestItemServer/src/pkg_imports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ module Revise
using ...LoweredCodeUtils: next_or_nothing!, callee_matches

include("../../../packages/Revise/src/packagedef.jl")

# Revise seeds the cache source hash with a `UInt64`, while the seed `hash` takes is a
# `UInt` — 32 bits wide on a 32 bit platform. Its package callback dies there with
# `MethodError: no method matching hash(::UInt64, ::UInt64)`, and every test item that
# loads a package in that process reports the failed callback as its own error.
#
# `packages/` holds git subtrees that are never edited by hand, so the method is added
# here instead, where this module is assembled. It is deliberately *more specific* than
# the vendored `cache_src_id(inc)` rather than a redefinition of it: redefining is
# method overwriting, which Julia rejects outright while this package precompiles. Both
# call sites pass a `Base.CacheHeaderIncludes`, so this one wins dispatch. `inc.hash` is
# a `UInt32`, so widening it to `UInt` is exact. Guarded like the definition it shadows
# (https://github.com/JuliaLang/julia/pull/49866); older versions hash `inc.mtime` and
# are unaffected. Drop this once the fix is in a released Revise.
@static if Sys.WORD_SIZE == 32 && VERSION >= v"1.11.0-DEV.683" &&
isdefined(Base, :CacheHeaderIncludes)
cache_src_id(inc::Base.CacheHeaderIncludes) = hash(inc.fsize, UInt(inc.hash))
end
elseif VERSION >= v"1.6.0"
using ..OrderedCollections
using ..LoweredCodeUtils
Expand Down
Loading