diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cb87844..6a6b3afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.8.4] - 2023-12-01 + +### Added + +- `test_persistent_tasks` now accepts an optional `expr` to run in the precompile package. ([#255](https://github.com/JuliaTesting/Aqua.jl/pull/255)) + + The `expr` option lets you test whether your precompile script leaves any dangling Tasks + or Timers, which would make it unsafe to use as a dependency for downstream packages. + ## [0.8.3] - 2023-11-29 diff --git a/Project.toml b/Project.toml index 9a95e3d5..29cf94df 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Aqua" uuid = "4c88cf16-eb10-579e-8560-4a9242c79595" authors = ["Takafumi Arakaki and contributors"] -version = "0.8.3" +version = "0.8.4" [deps] Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" diff --git a/docs/src/persistent_tasks.md b/docs/src/persistent_tasks.md index aef2dcaa..49371c19 100644 --- a/docs/src/persistent_tasks.md +++ b/docs/src/persistent_tasks.md @@ -32,6 +32,27 @@ fails to precompile: `using PkgA` runs `PkgA.__init__()`, which leaves the `Timer` `Task` running, and that causes precompilation of `PkgB` to hang. +## Example with `expr` + +You can test that an expression using your package finishes without leaving any persistent +tasks by passing a quoted expression: + +```julia +Aqua.test_persistent_tasks(MyPackage, quote + # Code to run after loading MyPackage + server = MyPackage.start_server() + MyPackage.stop_server!(server) +end) +``` + +Here is an example test with a dummy expr which will obviously fail, because it's explicitly +spawning a Task that never dies. +```@repl +Aqua.test_persistent_tasks(Aqua, quote + Threads.@spawn while true sleep(0.5) end +end +``` + ## How the test works This test works by launching a Julia process that tries to precompile a diff --git a/src/persistent_tasks.jl b/src/persistent_tasks.jl index 437fbdf7..a44994e7 100644 --- a/src/persistent_tasks.jl +++ b/src/persistent_tasks.jl @@ -5,6 +5,10 @@ Test whether loading `package` creates persistent `Task`s which may block precompilation of dependent packages. See also [`Aqua.find_persistent_tasks_deps`](@ref). +If you provide an optional `expr`, this tests whether loading `package` and running `expr` +creates persistent `Task`s. For example, you might start and shutdown a web server, and +this will test that there aren't any persistent `Task`s. + On Julia version 1.9 and before, this test always succeeds. # Arguments @@ -16,6 +20,7 @@ On Julia version 1.9 and before, this test always succeeds. - `tmax::Real = 5`: the maximum time (in seconds) to wait after loading the package before forcibly shutting down the precompilation process (triggering a test failure). +- `expr::Expr = quote end`: An expression to run in the precompile package. """ function test_persistent_tasks(package::PkgId; broken::Bool = false, kwargs...) if broken @@ -29,10 +34,10 @@ function test_persistent_tasks(package::Module; kwargs...) test_persistent_tasks(PkgId(package); kwargs...) end -function has_persistent_tasks(package::PkgId; tmax = 10) +function has_persistent_tasks(package::PkgId; expr::Expr = quote end, tmax = 10) root_project_path, found = root_project_toml(package) found || error("Unable to locate Project.toml") - return !precompile_wrapper(root_project_path, tmax) + return !precompile_wrapper(root_project_path, tmax, expr) end """ @@ -60,7 +65,7 @@ function find_persistent_tasks_deps(package::Module; kwargs...) find_persistent_tasks_deps(PkgId(package); kwargs...) end -function precompile_wrapper(project, tmax) +function precompile_wrapper(project, tmax, expr) @static if VERSION < v"1.10.0-" return true end @@ -84,6 +89,7 @@ function precompile_wrapper(project, tmax) """ module $wrappername using $pkgname +$expr # Signal Aqua from the precompilation process that we've finished loading the package open("$(escape_string(statusfile))", "w") do io println(io, "done") @@ -110,6 +116,8 @@ end end success = !process_running(proc) if !success + # SIGKILL to prevent julia from printing the SIG 15 handler, which can + # misleadingly look like it's caused by an issue in the user's program. kill(proc, Base.SIGKILL) end return success diff --git a/test/test_persistent_tasks.jl b/test/test_persistent_tasks.jl index 82554089..1339f159 100644 --- a/test/test_persistent_tasks.jl +++ b/test/test_persistent_tasks.jl @@ -29,4 +29,20 @@ end filter!(str -> !occursin("PersistentTasks", str), LOAD_PATH) end +@testset "test_persistent_tasks(expr)" begin + if Base.VERSION >= v"1.10-" + @test !Aqua.has_persistent_tasks( + getid("TransientTask"), + expr = quote + fetch(Threads.@spawn nothing) + end, + ) + @test Aqua.has_persistent_tasks(getid("TransientTask"), expr = quote + Threads.@spawn while true + sleep(0.5) + end + end) + end +end + end