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
64 changes: 49 additions & 15 deletions src/SciMLTesting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -531,27 +531,43 @@ function with_clean_persistent_tasks_sources(f; verbose::Bool = false)
end
end

# Aqua runs its ambiguity detector in a clean Julia process. That child receives the
# caller's QA environment, where Aqua is intentionally not a direct dependency, so
# also expose SciMLTesting's project while Aqua builds its child command.
# Aqua runs its ambiguity detector in a clean Julia process. Preserve a caller's QA
# environment when it declares Aqua directly; otherwise give the child an isolated
# environment whose manifest resolves SciMLTesting's installed Aqua dependency.
function _with_aqua_dependency_load_path(f)
package_root = pkgdir(@__MODULE__)
package_root === nothing && return f()
project_file = joinpath(package_root, "Project.toml")
isfile(project_file) || return f()
_active_project_declares_dependency(:Aqua) && return f()

aqua_root = pkgdir(Aqua)
aqua_root === nothing && return f()
original_load_path = copy(LOAD_PATH)
if !(project_file in LOAD_PATH)
pushfirst!(LOAD_PATH, project_file)
end
try
return f()
finally
empty!(LOAD_PATH)
append!(LOAD_PATH, original_load_path)
original_project = Base.active_project()
return mktempdir() do environment
try
Pkg.activate(environment; io = devnull)
Pkg.develop(path = aqua_root; io = devnull)
finally
original_project === nothing ? Pkg.activate(; io = devnull) :
Pkg.activate(original_project; io = devnull)
end

pushfirst!(LOAD_PATH, environment)
try
return f()
finally
empty!(LOAD_PATH)
append!(LOAD_PATH, original_load_path)
end
end
end

function _active_project_declares_dependency(name::Symbol)
project_file = Base.active_project()
(project_file === nothing || !isfile(project_file)) && return false
project = TOML.parsefile(project_file)
deps = get(project, "deps", nothing)
return deps isa AbstractDict && haskey(deps, string(name))
end

# For every dep of the active environment whose installed Project.toml declares a
# `[sources]` table with at least one *broken* path entry (a `path =` whose resolved
# location does not exist), back up the file (bytes + mode) and rewrite it with only
Expand Down Expand Up @@ -915,8 +931,22 @@ function _generated_enum_modules(pkg::Module)
return Tuple(modules)
end

const BASE_BROADCAST_EXTENSION_HOOKS = (
:Broadcasted, :broadcastable, :dotview, :materialize!,
)

function _explicit_imports_kwargs(pkg::Module, name::Symbol, ei_kwargs)
kwargs = NamedTuple(get(ei_kwargs, name, (;)))
if name === :all_qualified_accesses_are_public
# Julia's broadcast-extension protocol has no public spelling: packages must
# dispatch on these Base hooks to support dotted assignment and broadcasting.
# Keep that language-level exception here, rather than requiring every package
# to carry the same per-repository ExplicitImports ignore list.
kwargs = merge(
kwargs,
(; ignore = (get(kwargs, :ignore, ())..., BASE_BROADCAST_EXTENSION_HOOKS...)),
)
end
name in (:no_implicit_imports, :no_stale_explicit_imports) || return kwargs
generated_enums = _generated_enum_modules(pkg)
isempty(generated_enums) && return kwargs
Expand All @@ -939,6 +969,10 @@ so this helper stays usable with any compatible module. `ei_kwargs`
is a `NamedTuple` keyed by each check's short name (the part after `check_`); its
value is that check's keyword arguments, so callers curate per-check ignore-lists,
e.g. `ei_kwargs = (; all_qualified_accesses_are_public = (; ignore = (:foo,)))`.
SciMLTesting itself ignores Base's `Broadcasted`, `broadcastable`, `dotview`, and
`materialize!` for the public-access check: they are required Julia broadcast
extension hooks but Base does not declare them public. This global exception does not
cover dependency-owned names.
EnumX-generated child modules are automatically excluded from the two source-analysis
checks: they contain macro-generated enum bindings but no package-authored imports to
analyze.
Expand Down
36 changes: 23 additions & 13 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ module FakeExplicitImports
function check_all_qualified_accesses_are_public(pkg; ignore = (), kwargs...)
PUBLIC_CALLED[] = true
@test pkg === SciMLTesting
@test ignore == (:internal_thing,)
@test ignore == (
:internal_thing, :Broadcasted, :broadcastable, :dotview, :materialize!,
)
return _result(:check_all_qualified_accesses_are_public)
end
end
Expand Down Expand Up @@ -466,26 +468,34 @@ end

@testset "Aqua ambiguity subprocess resolves SciMLTesting dependencies" begin
original_load_path = copy(LOAD_PATH)
qa_project = joinpath(mktempdir(), "Project.toml")
write(qa_project, "[deps]\n")
original_project = Base.active_project()
child_can_load_aqua() = success(
pipeline(
`$(Base.julia_cmd()) --startup-file=no -e $("$(Base.load_path_setup_code())\nusing Aqua")`;
stdout = devnull, stderr = devnull,
),
)

try
empty!(LOAD_PATH)
append!(LOAD_PATH, (qa_project, "@stdlib"))
@test !child_can_load_aqua()
mktempdir() do qa_environment
try
Pkg.activate(qa_environment; io = devnull)
qa_project = joinpath(qa_environment, "Project.toml")
empty!(LOAD_PATH)
append!(LOAD_PATH, (qa_environment, "@stdlib"))
@test Base.active_project() == qa_project
@test !child_can_load_aqua()

SciMLTesting._with_aqua_dependency_load_path() do
@test child_can_load_aqua()
SciMLTesting._with_aqua_dependency_load_path() do
@test Base.active_project() == qa_project
@test child_can_load_aqua()
end
@test Base.active_project() == qa_project
finally
original_project === nothing ? Pkg.activate(; io = devnull) :
Pkg.activate(original_project; io = devnull)
empty!(LOAD_PATH)
append!(LOAD_PATH, original_load_path)
end
finally
empty!(LOAD_PATH)
append!(LOAD_PATH, original_load_path)
end
end

Expand Down Expand Up @@ -746,7 +756,7 @@ end
@test kwargs.allow_unanalyzable == (ApiFixture, EnumFixture.Generated)
@test SciMLTesting._explicit_imports_kwargs(
EnumFixture, :all_qualified_accesses_are_public, (;)
) == NamedTuple()
) == (; ignore = SciMLTesting.BASE_BROADCAST_EXTENSION_HOOKS)
end

@testset "run_qa enable-flag defaulting" begin
Expand Down