What is the fastest possible way to run a Julia test suite (and also get test coverage)?
Normally I call
julia --startup-file=no --code-coverage=user --project=. -e 'using Pkg; Pkg.test(coverage=true)'
julia --startup-file=no coverage.jl
where coverage.jl is:
using Coverage
# process '*.cov' files
coverage = process_folder() # defaults to src/; alternatively, supply the folder name as argument
push!(coverage, process_folder("ext")...)
LCOV.writefile("lcov.info", coverage)
# process '*.info' files
coverage = merge_coverage_counts(
coverage,
filter!(
let prefixes = (joinpath(pwd(), "src", ""), joinpath(pwd(), "ext", ""))
c -> any(p -> startswith(c.filename, p), prefixes)
end,
LCOV.readfolder("test"),
),
)
# Get total coverage for all Julia files
covered_lines, total_lines = get_summary(coverage)
@show covered_lines, total_lines
However, this takes 4 minutes in total to run in its entirety (for the package I’m currently working on[1]), so tends to slow me down when I am trying to iterate on my test suite – specifically with the goal of improving code coverage.
The other workflow I have tried is to use Revise, and call my test suite from the REPL:
julia> @eval module $(gensym())
include("test/runtests.jl")
end
where I put it in a module to avoid issues with test-specific structs changing.
This tends to be faster, but (1) I’m not sure how to get coverage out of this, and (2) it still feels like the tests themselves are not “julia speed” [see more notes below].
Any tips for speeding up this part of the development process? What is the fastest possible way I can compute code coverage for my test suite? Alternatively, is there a way I can dynamically generate code coverage without restarting Julia?
Regarding (2) – perhaps I need to wrap more things in functions, or perhaps the @test
and @testset
macros should be improved in some way (?).
For example, if you expand the `@testset` macro, there is a lot of dynamic dispatch going on:
julia> @macroexpand @testset "blah" begin
x = 1
@test x == 2
end
quote
#= REPL[9]:2 =#
begin
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1559 =#
Test._check_testset(if Test.get_testset_depth() == 0
Test.DefaultTestSet
else
Test.typeof(Test.get_testset())
end, $(QuoteNode(:(get_testset_depth() == 0))))
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1560 =#
local var"#13375#ret"
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1561 =#
local var"#13371#ts" = if if Test.get_testset_depth() == 0
Test.DefaultTestSet
else
Test.typeof(Test.get_testset())
end === Test.DefaultTestSet && true
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1562 =#
(if Test.get_testset_depth() == 0
Test.DefaultTestSet
else
Test.typeof(Test.get_testset())
end)("blah"; source = Symbol("REPL[9]"), Test.Dict{Test.Symbol, Test.Any}()...)
else
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1564 =#
(if Test.get_testset_depth() == 0
Test.DefaultTestSet
else
Test.typeof(Test.get_testset())
end)("blah"; Test.Dict{Test.Symbol, Test.Any}()...)
end
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1566 =#
Test.push_testset(var"#13371#ts")
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1570 =#
local var"#13372#RNG" = Test.default_rng()
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1571 =#
local var"#13373#oldrng" = Test.copy(var"#13372#RNG")
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1572 =#
local var"#13374#oldseed" = (Test.Random).GLOBAL_SEED
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1573 =#
try
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1575 =#
(Test.Random).seed!((Test.Random).GLOBAL_SEED)
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1576 =#
let
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1577 =#
begin
#= REPL[9]:2 =#
x = 1
#= REPL[9]:3 =#
begin
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:508 =#
if false
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:509 =#
Test.record(Test.get_testset(), Test.Broken(:skipped, $(QuoteNode(:(x == 2)))))
else
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:511 =#
let var"#13378#_do" = if false
Test.do_broken_test
else
Test.do_test
end
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:512 =#
var"#13378#_do"(begin
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:668 =#
try
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:669 =#
Test.eval_test(Test.Expr(:comparison, x, ==, 2), Test.Expr(:comparison, :x, :(==), $(QuoteNode(2))), $(QuoteNode(:(#= REPL[9]:3 =#))), $(QuoteNode(false)))
catch var"#13380#_e"
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:671 =#
var"#13380#_e" isa Test.InterruptException && Test.rethrow()
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:672 =#
Test.Threw(var"#13380#_e", (Test.Base).current_exceptions(), $(QuoteNode(:(#= REPL[9]:3 =#))))
end
end, $(QuoteNode(:(x == 2))))
end
end
end
end
end
catch var"#13377#err"
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1580 =#
var"#13377#err" isa Test.InterruptException && Test.rethrow()
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1583 =#
Test.trigger_test_failure_break(var"#13377#err")
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1584 =#
if var"#13377#err" isa Test.FailFastError
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1585 =#
if Test.get_testset_depth() > 1
Test.rethrow()
else
Test.failfast_print()
end
else
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1587 =#
Test.record(var"#13371#ts", Test.Error(:nontest_error, Test.Expr(:tuple), var"#13377#err", (Test.Base).current_exceptions(), $(QuoteNode(:(#= REPL[9]:1 =#)))))
end
finally
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1590 =#
Test.copy!(var"#13372#RNG", var"#13373#oldrng")
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1591 =#
(Test.Random).set_global_seed!(var"#13374#oldseed")
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1592 =#
Test.pop_testset()
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1593 =#
var"#13375#ret" = Test.finish(var"#13371#ts")
end
#= /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/usr/share/julia/stdlib/v1.10/Test/src/Test.jl:1595 =#
var"#13375#ret"
end
end
I wonder if the macro could instead statically converting the @test
s into function calls or something, instead of eval
’ing them at runtime. Most of the recent changes to Test.jl have been bug fixes and documentation rather than performance improvement: (see commits, apart from maybe #42518) so perhaps it could be worth refactoring the code a bit.
If wanting a specific example of my use-case, here’s the test suite I am working on at the moment: DynamicExpressions.jl/test at master · SymbolicML/DynamicExpressions.jl · GitHub. ↩︎