Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor LazyTestResult away #237

Merged
merged 8 commits into from
Nov 24, 2023
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
3 changes: 3 additions & 0 deletions src/Aqua.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ using Base: PkgId, UUID
using Pkg: Pkg, TOML, PackageSpec
using Test

@static if VERSION < v"1.1.0-DEV.472"
using Compat: isnothing
end
@static if VERSION < v"1.3.0-DEV.349"
using Compat: findfirst
end
Expand Down
22 changes: 0 additions & 22 deletions src/ambiguities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,6 @@ test_ambiguities(packages; kwargs...) = _test_ambiguities(aspkgids(packages); kw

const ExcludeSpec = Pair{Base.PkgId,String}

aspkgids(pkg::Union{Module,PkgId}) = aspkgids([pkg])
aspkgids(packages) = mapfoldl(aspkgid, push!, packages, init = PkgId[])

aspkgid(pkg::PkgId) = pkg
function aspkgid(m::Module)
if !ispackage(m)
error("Non-package (non-toplevel) module is not supported. Got: $m")
end
return PkgId(m)
end
function aspkgid(name::Symbol)
# Maybe `Base.depwarn()`
return Base.identify_package(String(name))::PkgId
end

ispackage(m::Module) =
if m in (Base, Core)
true
else
parentmodule(m) == m
end

strnameof(x) = string(x)
strnameof(x::Type) = string(nameof(x))

Expand Down
10 changes: 4 additions & 6 deletions src/deps_compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ function test_julia_compat(pkg::PkgId; broken::Bool = false)
end

function has_julia_compat(pkg::PkgId)
result = root_project_or_failed_lazytest(pkg)
result isa LazyTestResult && error("Unable to locate Project.toml")
root_project_path = result
root_project_path, found = root_project_toml(pkg)
found || error("Unable to locate Project.toml")
prj = TOML.parsefile(root_project_path)
return has_julia_compat(prj)
end
Expand All @@ -93,9 +92,8 @@ function has_julia_compat(prj::Dict{String,Any})
end

function find_missing_deps_compat(pkg::PkgId, deps_type::String = "deps"; kwargs...)
result = root_project_or_failed_lazytest(pkg)
result isa LazyTestResult && error("Unable to locate Project.toml")
root_project_path = result
root_project_path, found = root_project_toml(pkg)
found || error("Unable to locate Project.toml")
missing_compat =
find_missing_deps_compat(TOML.parsefile(root_project_path), deps_type; kwargs...)

Expand Down
14 changes: 7 additions & 7 deletions src/persistent_tasks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ function test_persistent_tasks(package::Module; kwargs...)
end

function has_persistent_tasks(package::PkgId; tmax = 10)
result = root_project_or_failed_lazytest(package)
result isa LazyTestResult && error("Unable to locate Project.toml")
return !precompile_wrapper(result, tmax)
root_project_path, found = root_project_toml(package)
found || error("Unable to locate Project.toml")
return !precompile_wrapper(root_project_path, tmax)
end

"""
Expand All @@ -102,9 +102,9 @@ These are likely the ones blocking precompilation of your package.
Any additional kwargs (e.g., `tmax`) are passed to [`Aqua.test_persistent_tasks`](@ref).
"""
function find_persistent_tasks_deps(package::PkgId; kwargs...)
result = root_project_or_failed_lazytest(package)
result isa LazyTestResult && error("Unable to locate Project.toml")
prj = TOML.parsefile(result)
root_project_path, found = root_project_toml(package)
found || error("Unable to locate Project.toml")
prj = TOML.parsefile(root_project_path)
deps = get(prj, "deps", Dict{String,Any}())
filter!(deps) do (name, uuid)
id = PkgId(UUID(uuid), name)
Expand All @@ -121,7 +121,7 @@ function precompile_wrapper(project, tmax)
if VERSION < v"1.10.0-"
return true
end
prev_project = Base.active_project()
prev_project = Base.active_project()::String
isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(false)
try
pkgdir = dirname(project)
Expand Down
128 changes: 55 additions & 73 deletions src/project_extras.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,103 +7,85 @@
Julia < 1.2 while recording test-only dependency compatibility in
`test/Project.toml`.
"""
function test_project_extras(packages)
@testset "$(result.label)" for result in analyze_project_extras(packages)
@debug result.label result
@test result ⊜ true
end
function test_project_extras(pkg::PkgId; kwargs...)
msgs = analyze_project_extras(pkg; kwargs...)
@test isempty(msgs)
end

function project_toml_path(dir)
candidates = joinpath.(dir, ["Project.toml", "JuliaProject.toml"])
i = findfirst(isfile, candidates)
i === nothing && return candidates[1], false
return candidates[i], true
# Remove with next breaking version
function test_project_extras(packages::Vector{<:Union{Module,PkgId}}; kwargs...)
@testset "$pkg" for pkg in packages
test_project_extras(pkg; kwargs...)
end

Check warning on line 19 in src/project_extras.jl

View check run for this annotation

Codecov / codecov/patch

src/project_extras.jl#L16-L19

Added lines #L16 - L19 were not covered by tests
end

analyze_project_extras(packages) = map(_analyze_project_extras, aspkgids(packages))
function test_project_extras(mod::Module; kwargs...)
test_project_extras(aspkgid(mod); kwargs...)
end

is_julia12_or_later(compat::AbstractString) = is_julia12_or_later(semver_spec(compat))
is_julia12_or_later(compat::VersionSpec) = isempty(compat ∩ semver_spec("1.0 - 1.1"))

function _analyze_project_extras(pkg::PkgId)
label = string(pkg)
function analyze_project_extras(pkg::PkgId)
root_project_path, found = root_project_toml(pkg)
found || error("Unable to locate Project.toml")

result = root_project_or_failed_lazytest(pkg)
result isa LazyTestResult && return result
root_project_path = result

package_loc = Base.locate_package(pkg)
package_loc === nothing &&
return LazyTestResult(label, "Base.locate_package failed.", false)
pkgpath = dirname(dirname(package_loc))
test_project_path, found = project_toml_path(joinpath(pkgpath, "test"))
if !found
return LazyTestResult(label, "test/Project.toml file does not exist.", true)
end
test_project_path, found =
project_toml_path(joinpath(dirname(root_project_path), "test"))
found || return String[] # having no test/Project.toml is fine
root_project = TOML.parsefile(root_project_path)
test_project = TOML.parsefile(test_project_path)

# Ignore root project's extras if only supporting julia 1.2 or later.
# See: # https://julialang.github.io/Pkg.jl/v1/creating-packages/#Test-specific-dependencies-in-Julia-1.2-and-above-1
julia_version = get(get(root_project, "compat", Dict()), "julia", "1")
if is_julia12_or_later(julia_version)
return LazyTestResult(
label,
string(
"Supporting only post-1.2 `julia` ($julia_version); ",
"ignoring root project.",
),
true,
)
end
julia_version = get(get(root_project, "compat", Dict{String,Any}()), "julia", nothing)
isnothing(julia_version) && return String["Could not find `julia` compat."]
is_julia12_or_later(julia_version) && return String[]

# `extras_deps`: test-only dependencies according to /Project.toml
all_extras_deps = get(root_project, "extras", Dict())
target = Set{String}(get(get(root_project, "targets", Dict()), "test", []))
extras_deps = setdiff(
Set{Pair{String,String}}(p for p in all_extras_deps if first(p) in target),
Set{Pair{String,String}}(get(root_project, "deps", [])),
# `extras_test_deps`: test-only dependencies according to Project.toml
deps = [PkgId(UUID(v), k) for (k, v) in get(root_project, "deps", Dict{String,Any}())]
target =
Set{String}(get(get(root_project, "targets", Dict{String,Any}()), "test", String[]))
extras_test_deps = setdiff(
[
PkgId(UUID(v), k) for
(k, v) in get(root_project, "extras", Dict{String,Any}()) if k in target
],
deps,
)

# `test_deps`: test-only dependencies according to /test/Project.toml:
# `test_deps`: test-only dependencies according to test/Project.toml:
test_deps = setdiff(
Set{Pair{String,String}}(get(test_project, "deps", [])),
Set{Pair{String,String}}(get(root_project, "deps", [])),
[root_project["name"] => root_project["uuid"]],
[PkgId(UUID(v), k) for (k, v) in get(test_project, "deps", Dict{String,Any}())],
deps,
[PkgId(UUID(root_project["uuid"]), root_project["name"])],
)

not_in_extras = setdiff(test_deps, extras_deps)
not_in_test = setdiff(extras_deps, test_deps)
not_in_extras = setdiff(test_deps, extras_test_deps)
not_in_test = setdiff(extras_test_deps, test_deps)
if isempty(not_in_extras) && isempty(not_in_test)
return LazyTestResult(
label,
"""
Root and test projects are consistent.
Root project: $root_project_path
Test project: $test_project_path
""",
true,
)
return String[]
else
msg = sprint() do io
println(
io,
"Root and test projects should be consistent for projects supporting Julia <= 1.1.",
)
if !isempty(not_in_extras)
println(io, "Test dependencies not in root project ($root_project_path):")
for (name, uuid) in sort!(collect(not_in_extras))
println(io, " $name = \"$uuid\"")
end
msgs = String[]
push!(
msgs,
"Root and test projects should be consistent for projects supporting Julia <= 1.1.",
)
if !isempty(not_in_extras)
msg = "Test dependencies not in root project ($root_project_path):"
for pkgs in sort!(collect(not_in_extras); by = (pkg -> pkg.name))
msg *= "\n\t$pkgs"
end
if !isempty(not_in_test)
println(io, "Dependencies not in test project ($test_project_path):")
for (name, uuid) in sort!(collect(not_in_test))
println(io, " $name = \"$uuid\"")
end
push!(msgs, msg)
end
if !isempty(not_in_test)
msg = "Dependencies not in test project ($test_project_path):"
for pkgs in sort!(collect(not_in_test); by = (pkg -> pkg.name))
msg *= "\n\t$pkgs"
end
push!(msgs, msg)
end
return LazyTestResult(label, msg, false)

return msgs
end
end
68 changes: 20 additions & 48 deletions src/stale_deps.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,29 @@
# Keyword Arguments
- `ignore::Vector{Symbol}`: names of dependent packages to be ignored.
"""
function test_stale_deps(packages; kwargs...)
@testset "$(result.label)" for result in analyze_stale_deps(packages, kwargs...)
@debug result.label result
@test result ⊜ true
end
function test_stale_deps(pkg::PkgId; kwargs...)
stale_deps = find_stale_deps(pkg; kwargs...)
@test isempty(stale_deps)
end

analyze_stale_deps(packages, kwargs...) =
[_analyze_stale_deps_1(pkg; kwargs...) for pkg in aspkgids(packages)]
function test_stale_deps(mod::Module; kwargs...)
test_stale_deps(aspkgid(mod); kwargs...)
end

function _analyze_stale_deps_1(pkg::PkgId; ignore::AbstractVector{Symbol} = Symbol[])
label = "$pkg"
# Remove in next breaking release
function test_stale_deps(packages::Vector{<:Union{Module,PkgId}}; kwargs...)
@testset "$pkg" for pkg in packages
test_stale_deps(pkg; kwargs...)
end

Check warning on line 42 in src/stale_deps.jl

View check run for this annotation

Codecov / codecov/patch

src/stale_deps.jl#L39-L42

Added lines #L39 - L42 were not covered by tests
end

result = root_project_or_failed_lazytest(pkg)
result isa LazyTestResult && return result
root_project_path = result
function find_stale_deps(pkg::PkgId; ignore::AbstractVector{Symbol} = Symbol[])
root_project_path, found = root_project_toml(pkg)
found || error("Unable to locate Project.toml")

@debug "Parsing `$root_project_path`"
prj = TOML.parsefile(root_project_path)
raw_deps = get(prj, "deps", nothing)
if raw_deps === nothing
return LazyTestResult(label, "No `deps` table in `$root_project_path`", true)
end
deps = [PkgId(UUID(v), k) for (k, v) in raw_deps]

raw_weakdeps = get(prj, "weakdeps", nothing)
weakdeps =
isnothing(raw_weakdeps) ? PkgId[] : [PkgId(UUID(v), k) for (k, v) in raw_weakdeps]
deps = [PkgId(UUID(v), k) for (k, v) in get(prj, "deps", Dict{String,Any}())]
weakdeps = [PkgId(UUID(v), k) for (k, v) in get(prj, "weakdeps", Dict{String,Any}())]

marker = "_START_MARKER_"
code = """
Expand All @@ -66,14 +61,12 @@
"""
cmd = Base.julia_cmd()
output = read(`$cmd --startup-file=no --color=no -e $code`, String)
@debug("Checked modules loaded in a separate process.", cmd, Text(code), Text(output))
pos = findfirst(marker, output)
@assert !isnothing(pos)
output = output[pos.stop+1:end]
loaded_uuids = map(UUID, eachline(IOBuffer(output)))

return _analyze_stale_deps_2(;
pkg = pkg,
return find_stale_deps_2(;
deps = deps,
weakdeps = weakdeps,
loaded_uuids = loaded_uuids,
Expand All @@ -82,14 +75,12 @@
end

# Side-effect -free part of stale dependency analysis.
function _analyze_stale_deps_2(;
pkg::PkgId,
function find_stale_deps_2(;
deps::AbstractVector{PkgId},
weakdeps::AbstractVector{PkgId},
loaded_uuids::AbstractVector{UUID},
ignore::AbstractVector{Symbol},
)
label = "$pkg"
deps_uuids = [p.uuid for p in deps]
pkgid_from_uuid = Dict(p.uuid => p for p in deps)

Expand All @@ -98,24 +89,5 @@
stale_pkgs = setdiff(stale_pkgs, weakdeps)
stale_pkgs = [p for p in stale_pkgs if !(Symbol(p.name) in ignore)]

if isempty(stale_pkgs)
return LazyTestResult(
label,
"""
All packages in `deps` are loaded via `using $(pkg.name)`.
""",
true,
)
end

stale_msg = join(("* $p" for p in stale_pkgs), "\n")
msglines = [
"Some package(s) in `deps` of $pkg are not loaded during via" *
" `using $(pkg.name)`.",
stale_msg,
"",
"To ignore from stale dependency detection, pass the package name to" *
" `ignore` keyword argument of `Aqua.test_stale_deps`",
]
return LazyTestResult(label, join(msglines, "\n"), false)
return stale_pkgs
end
Loading
Loading