Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 3 additions & 1 deletion src/TestEnv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ elseif VERSION < v"1.9-"
include("julia-1.8/TestEnv.jl")
elseif VERSION < v"1.11-"
include("julia-1.9/TestEnv.jl")
else
elseif VERSION < v"1.13-"
include("julia-1.11/TestEnv.jl")
else
include("julia-1.13/TestEnv.jl")
end

end
13 changes: 13 additions & 0 deletions src/julia-1.13/TestEnv.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Pkg
using Pkg: PackageSpec
using Pkg.Types: Context, ensure_resolved, is_project_uuid, write_env, is_stdlib
using Pkg.Types: Types, projectfile_path, manifestfile_path
using Pkg.Operations: manifest_info, manifest_resolve!, project_deps_resolve!
using Pkg.Operations: manifest_rel_path, project_resolve!
using Pkg.Operations: sandbox, source_path, sandbox_preserve, abspath!
using Pkg.Operations: gen_target_project, isfixed


include("common.jl")
include("activate_do.jl")
include("activate_set.jl")
21 changes: 21 additions & 0 deletions src/julia-1.13/activate_do.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
TestEnv.activate(f, [pkg]; allow_reresolve=true)

Activate the test enviroment of `pkg` (defaults to current enviroment), and run `f()`,
then deactivate the enviroment.
This is not useful for many people: Julia is not really designed to have the enviroment
being changed while you are executing code.
However, this *is* useful for anyone doing something like making a alternative to
`Pkg.test()`.
Indeed this is basically extracted from what `Pkg.test()` does.
"""
function activate(f, pkg::AbstractString=current_pkg_name(); allow_reresolve=true)
ctx, pkgspec = ctx_and_pkgspec(pkg)

test_project_override = maybe_gen_project_override!(ctx, pkgspec)
path = pkgspec.path::String
return sandbox(ctx, pkgspec, joinpath(path, "test"), test_project_override; allow_reresolve) do
flush(stdout)
f()
end
end
89 changes: 89 additions & 0 deletions src/julia-1.13/activate_set.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@

# Originally from Pkg.Operations.sandbox

"""
TestEnv.activate([pkg]; allow_reresolve=true)

Activate the test enviroment of `pkg` (defaults to current enviroment).
"""
function activate(pkg::AbstractString=current_pkg_name(); allow_reresolve=true)
ctx, pkgspec = ctx_and_pkgspec(pkg)
# This needs to be first as `gen_target_project` fixes `pkgspec.path` if it is nothing
sandbox_project_override = maybe_gen_project_override!(ctx, pkgspec)

sandbox_path = joinpath(pkgspec.path::String, "test")
sandbox_project = projectfile_path(sandbox_path)

tmp = mktempdir()
tmp_project = projectfile_path(tmp)
tmp_manifest = manifestfile_path(tmp)

# Copy env info over to temp env
if sandbox_project_override !== nothing
Types.write_project(sandbox_project_override, tmp_project)
elseif isfile(sandbox_project)
cp(sandbox_project, tmp_project)
chmod(tmp_project, 0o600)
end
# create merged manifest
# - copy over active subgraph
# - abspath! to maintain location of all deved nodes
working_manifest = abspath!(ctx.env, sandbox_preserve(ctx.env, pkgspec, tmp_project))

# - copy over fixed subgraphs from test subgraph
# really only need to copy over "special" nodes
sandbox_env = Types.EnvCache(projectfile_path(sandbox_path))
sandbox_manifest = abspath!(sandbox_env, sandbox_env.manifest)
for (name, uuid) in sandbox_env.project.deps
entry = get(sandbox_manifest, uuid, nothing)
if entry !== nothing && isfixed(entry)
# Signature changed when workspaces were introduced to Pkg in v1.12 (see Pkg.jl#3841)
subgraph = Pkg.Operations.prune_manifest(sandbox_manifest, VERSION < v"1.12.0-" ? [uuid] : Set([uuid]))
for (uuid, entry) in subgraph
if haskey(working_manifest, uuid)
Pkg.Operations.pkgerror("can not merge projects")
end
working_manifest[uuid] = entry
end
end
end

Types.write_manifest(working_manifest, tmp_manifest)

Base.ACTIVE_PROJECT[] = tmp_project

temp_ctx = Context()
temp_ctx.env.project.deps[pkgspec.name] = pkgspec.uuid

# A hack to get [sources] with relative paths working. We just dive into the project
# context and replace all the relative '{path = ".."}' instances with the corresponding
# absolute paths. `pkgspec.path` is assumed to be relative to the Project.toml file
# that we are activating and has the relative paths.
for source in values(temp_ctx.env.project.sources)
isa(source, Dict) || continue
haskey(source, "path") || continue
base_path = test_dir_has_project_file(temp_ctx, pkgspec) ? joinpath(pkgspec.path, "test") : pkgspec.path
if !isabspath(source["path"])
source["path"] = joinpath(base_path, source["path"])
end
end

try
Pkg.resolve(temp_ctx; io=devnull)
@debug "Using _parent_ dep graph"
catch err# TODO
allow_reresolve || rethrow()
@debug err
@warn "Could not use exact versions of packages in manifest, re-resolving"
temp_ctx.env.manifest.deps = Dict(
uuid => entry for
(uuid, entry) in temp_ctx.env.manifest.deps if isfixed(entry)
)
Pkg.resolve(temp_ctx; io=devnull)
@debug "Using _clean_ dep graph"
end

write_env(temp_ctx.env; update_undo=false)

return Base.active_project()
end
88 changes: 88 additions & 0 deletions src/julia-1.13/common.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
struct TestEnvError <: Exception
msg::AbstractString
end

function Base.showerror(io::IO, ex::TestEnvError, bt; backtrace=true)
printstyled(io, ex.msg, color=Base.error_color())
end

function current_pkg_name()
ctx = Context()
ctx.env.pkg === nothing && throw(TestEnvError("trying to activate test environment of an unnamed project"))
return ctx.env.pkg.name
end

"""
ctx, pkgspec = ctx_and_pkgspec(pkg::AbstractString)

For a given package name `pkg`, instantiate a `Context` for it, and return that `Context`,
and it's `PackageSpec`.
"""
function ctx_and_pkgspec(pkg::AbstractString)
pkgspec = deepcopy(PackageSpec(pkg))
ctx = Context()
isinstalled!(ctx, pkgspec) || throw(TestEnvError("$pkg not installed 👻"))
Pkg.instantiate(ctx)
return ctx, pkgspec
end

"""
isinstalled!(ctx::Context, pkgspec::Pkg.Types.PackageSpec)

Checks if the package is installed by using `ensure_resolved` from `Pkg/src/Types.jl`.
This function fails if the package is not installed, but here we wrap it in a
try-catch as we may want to test another package after the one that isn't installed.
"""
function isinstalled!(ctx::Context, pkgspec::Pkg.Types.PackageSpec)
project_resolve!(ctx.env, [pkgspec])
project_deps_resolve!(ctx.env, [pkgspec])
manifest_resolve!(ctx.env.manifest, [pkgspec])

try
ensure_resolved(ctx, ctx.env.manifest, [pkgspec])
catch err
err isa MethodError && rethrow()
return false
end
return true
end


function test_dir_has_project_file(ctx, pkgspec)
test_dir = get_test_dir(ctx, pkgspec)
test_dir === nothing && return false
return isfile(joinpath(test_dir, "Project.toml"))
end

"""
get_test_dir(ctx::Context, pkgspec::Pkg.Types.PackageSpec)

Gets the testfile path of the package. Code for each Julia version mirrors that found
in `Pkg/src/Operations.jl`.
"""
function get_test_dir(ctx::Context, pkgspec::Pkg.Types.PackageSpec)
if is_project_uuid(ctx.env, pkgspec.uuid)
pkgspec.path = dirname(ctx.env.project_file)
pkgspec.version = ctx.env.pkg.version
else
is_stdlib(pkgspec.uuid::Base.UUID) && return
entry = manifest_info(ctx.env.manifest, pkgspec.uuid)
pkgspec.version = entry.version
pkgspec.tree_hash = entry.tree_hash
pkgspec.repo = entry.repo
pkgspec.path = entry.path
pkgspec.pinned = entry.pinned
pkgspec.path = Pkg.safe_realpath(manifest_rel_path(ctx.env, source_path(ctx.env.project_file, pkgspec)::String))
end
pkgfilepath = source_path(ctx.env.project_file, pkgspec)::String
return joinpath(pkgfilepath, "test")
end


function maybe_gen_project_override!(ctx, pkgspec)
if !test_dir_has_project_file(ctx, pkgspec)
gen_target_project(ctx, pkgspec, pkgspec.path::String, "test")
else
nothing
end
end
Loading