diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..700707c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..008ce90 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,48 @@ +name: CI +on: + push: + branches: + - main + tags: ['*'] + pull_request: + workflow_dispatch: +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created + actions: write + contents: read + strategy: + fail-fast: false + matrix: + version: + - '1.10' + - 'pre' + os: + - ubuntu-latest +# - mac-os-latest + - windows-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v5 + with: + files: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..cba9134 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,16 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..0cd3114 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,31 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f84bed --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.jl.*.cov +*.jl.cov +*.jl.mem +/Manifest.toml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ebec6a6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 John Lapeyre and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..c23eafe --- /dev/null +++ b/Project.toml @@ -0,0 +1,24 @@ +name = "StackEnvs" +uuid = "6dbe6948-1a74-428b-a0c7-b6033448b207" +authors = ["John Lapeyre and contributors"] +version = "0.1.0" + +[deps] +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" + +[compat] +Aqua = ">= 0" +TOML = "1.0.3" +JET = ">= 0" +Test = ">= 0" +Pkg = ">= 0" +julia = "1.8" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test", "Aqua", "JET"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ed34a6 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# StackEnvs + +[![Build Status](https://github.com/jlapeyre/StackEnvs.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/jlapeyre/StackEnvs.jl/actions/workflows/CI.yml?query=branch%3Amain) +[![Coverage](https://codecov.io/gh/jlapeyre/StackEnvs.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/jlapeyre/StackEnvs.jl) +[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) + +`StackEnvs` provides tools for minimal management of a shared environment that is meant to be used from your "stacked environment" but never to be active itself. + +This is useful if you are developing a package and want to use some other packages without polluting any environment. + +It is meant to be used in a script that you `include` when, for example, developing a particular package, and want some other particular +packages to be available. + +```julia +using StackEnvs +env = StackEnv("my_env", ["PackA", "PackB"]) +ensure_in_stack(env) +``` + +or more simply, + +```julia +using StackEnvs +ensure_in_stack("my_env", ["PackA", "PackB"]) +``` + +After including the script, you can immediately `using PackA`. + +> [!WARNING] +> `StackedEnvs` pays no attention to version numbers, uuids. It can't remove any packages. It is useful + for some simple, straightforward, use cases. + +> [!WARNING] +> The test suite adds and deletes an environment to your `~/.julia/environments` directory. Something could potentially go wrong causing it + to delete your entire home directory. I've taken some steps to minimize this risk. There is nothing like `rm -r *` in the test. Rather, the + two `toml` files are deleted, then the empty directory is deleted. If any of this should fail, the test suite will fail. + +> [!WARNING] +> `StackedEnvs` uses some non-API functions in `Base`. But the way these are used is simple and should be fixable. + +### Example + +```julia-repl +julia> using StackEnvs + +julia> env = StackEnv("my_env", ["StatsBase"]) +StackEnv("my_env", [:StatsBase]) + +julia> env_exists(env) +false + +julia> is_in_stack(env) +false + +julia> ensure_in_stack(env) + Activating new project at `~/.julia/environments/my_env` + Resolving package versions... + +julia> is_in_stack(env) +true + +julia> env_exists(env) +true + +julia> using StatsBase + +julia> push!(env, "ILog2") +2-element Vector{Symbol}: + :StatsBase + :ILog2 + +julia> ensure_in_stack(env) + Activating project at `~/.julia/environments/my_env` + Resolving package versions... + Updating `~/.julia/environments/my_env/Project.toml` + [2cd5bd5f] + ILog2 v2.0.0 + Updating `~/.julia/environments/my_env/Manifest.toml` + [2cd5bd5f] + ILog2 v2.0.0 + Activating project at `~/github/jlapeyre/StackEnvs` +StackEnv("my_env", [:StatsBase, :ILog2]) + +julia> ensure_in_stack(env) # Nothing to do. Returns in 30 micro seconds +StackEnv("my_env", [:StatsBase, :ILog2]) +``` + + diff --git a/src/StackEnvs.jl b/src/StackEnvs.jl new file mode 100644 index 0000000..c50c6ed --- /dev/null +++ b/src/StackEnvs.jl @@ -0,0 +1,382 @@ +""" + module StackEnvs + +Failites for managing an environment that is not active, but in the stack. + +The module defines [`StackEnv`](@ref) which represents an environment meant to used +in the enironment stack rather than as an active environment. + +There are some functions missing. For example, for adding and removing packages from the +`StackEnv`. This can be done manually, but is would be a bit fiddle. These functions might +be added later. +""" +module StackEnvs + +using Pkg: Pkg +using TOML: TOML + +export StackEnv, + ensure_in_stack, + is_in_stack, + env_exists, + create_env, + delete_from_stack!, + activate_env, + update_env, + read_env + +const StrOrSym = Union{AbstractString, Symbol} + +""" + struct StackEnv + +Data for defining a shared environment to be used in the environment stack. + +When working with a particular package, you might frequently use certain other packages that +are not in the dependencies of the particular one. `StackEnv` is meant to help manage these +other packages. You can do this by hand as well. But it's sometimes not easy to remember +what you want to do and exactly how to do it. + +`StackEnv` helps you create a shared environment and make sure it is in your stack of environments +so that its packages are visibile when it is not the active environment. You are *not* meant +to do work with the environment in `StackEnv` activated. + +The most important function for creating an `StackEnv` and making it visible is [`ensure_in_stack`](@ref). + +# Fields +- `name::String`: the name of the extra environment +- `packages::Vector{Symbol}`: A list of packages to use to initialize the environment. + +See [`update_env`](@ref) [`create_env`](@ref), [`ensure_in_stack`](@ref), [`env_exists`](@ref), +[`activate_env`](@ref), [`is_in_stack`](@ref), [`delete_from_stack!`](@ref). + +# Examples +```jldoctest +julia> StackEnv("an_extra_env", [:Example]) +StackEnv("an_extra_env", [:Example]) + +julia> StackEnv("@an_extra_env", [:Example]) +StackEnv("an_extra_env", [:Example]) +``` +""" +struct StackEnv + name::String + packages::Vector{Symbol} + + function StackEnv(name::AbstractString, packages::AbstractVector) + return new(_no_at_name(name), [Symbol(p) for p in packages]) + end +end + +""" + StackEnv(env_name::AbstractString) + +Initialize an `StackEnv` with an empty list of packages. + +# Examples +```jldoctest +julia> StackEnv("an_extra_env") +StackEnv("an_extra_env", Symbol[]) + +julia> StackEnv("@an_extra_env") +StackEnv("an_extra_env", Symbol[]) +``` +""" +StackEnv(env_name::AbstractString) = StackEnv(env_name, Symbol[]) + +# Make sure `name` starts with "@". +# Prepend an "@" only if it is missing. +function _at_name(name::StrOrSym)::String + sname = string(name) + startswith(sname, "@") && return sname + return string("@", name) +end + +# Make sure `name` does *not* start with "@" +# Remove "@" if present. +function _no_at_name(name::StrOrSym)::String + sname = string(name) + isempty(sname) && return "" + startswith(sname, "@") && return string(@view sname[2:end]) + return string(sname) +end + +_at_name(env::StackEnv)::String = _at_name(env.name) +_no_at_name(env::StackEnv)::String = _no_at_name(env.name) + +function Base.push!(env::StackEnv, package::StrOrSym) + push!(env.packages, Symbol(package)) +end + +""" + ensure_in_stack(env_name::AbstractString, env_packages::AbstractVector)::StackEnv + +Create `env = StackEnv(env_name, env_packages)`, run `ensure_in_stack(env)` and return `env`. + +Elements of `env_packages` should be `AbstractString`s or `Symbol`s. + +See [`StackEnv`](@ref). + +# Examples +```julia-repl +julia> ensure_in_stack("my_extra_env", [:PackageA, :PackageB]); +``` +""" +function ensure_in_stack(env_name::AbstractString, env_packages::AbstractVector{<:StrOrSym}) + env = StackEnv(env_name, [Symbol(p) for p in env_packages]) + ensure_in_stack(env) + return env +end + +""" + ensure_in_stack(env::StackEnv)::StackEnv + +Ensure that a shared environment `env.name` with `env.packages` is in your stack. + +Recall that the environment stack is `Base.LOAD_PATH`. + +If `env.name` does not name a shared environment, create it and add `env.packages`. +Furthermore, if `env.name` is not in the stack, add it to the stack. + +After this runs, the packages `env.packages` should be available in whatever project +is active. + +`ensure_in_stack` compares `env.packages` to the list of packages in the `Project.toml` for the environment +and adds any missing packages that you may have added since the environment was first created. + +Things that are not supported: +* removing packages from the environment for any reason. +* Specifying or checking versions, uuids, etc. + +The function [`update_env`](@ref) will unconditionally add all packages in `env.packages` +to the environment, which may perform an upgrade (I'm not sure). + +See [`StackEnv`](@ref). +""" +function ensure_in_stack(env::StackEnv)::StackEnv + env_exists(env) || create_env(env) + atenv = _at_name(env) # This must already be the case! + add_missing(env) + maybepushenv!(atenv) + return env +end + +function add_missing(env::StackEnv) + env_exists(env) || return nothing + existing_packages = collect(keys(read_env(env))) + missing_packs = String[] + for pack in env.packages + spack = string(pack) + spack in existing_packages || push!(missing_packs, spack) + end + isempty(missing_packs) && return + _add_packages(env.name, missing_packs) +end + +""" + maybepushenv!(env::AbstractString) + +Add `env` to `LOAD_PATH`, the list of stacked environments and return `true`. +If `env` is already in `LOAD_PATH`, do nothing and return `false`. +""" +function maybepushenv!(env::AbstractString) + in(env, LOAD_PATH) && return false + push!(LOAD_PATH, env) + return true +end + +""" + is_in_stack(env_name::AbstractString)::Bool + +Return `true` if the shared environment `env_name` is in the environment stack. + +If `env_name` does not start with `'@'`, it is prepended. The stack is `Base.LOAD_PATH`. +""" +function is_in_stack(env_name::AbstractString)::Bool + return _at_name(env_name) in Base.LOAD_PATH +end + +""" + is_in_stack(env::StackEnv)::Bool + +Return `true` if the shared environment `env.name` is in the environment stack. + +See [`StackEnv`](@ref). +""" +is_in_stack(env::StackEnv)::Bool = is_in_stack(env.name) + +""" + env_exists(env::StackEnv)::Bool + +Return `true` if the shared environment in `env` already exists. + +See [`StackEnv`](@ref). +""" +env_exists(env::StackEnv)::Bool = env_exists(env.name) + +""" + env_exists(env_name::AbstractString)::Bool + +Return `true` if the shared environment `env_name` already exists. + +`env_name` may begin with "@" or not. + +This environment might be activated via `Pkg.activate(env_name)` +If done from the `pkg` repl, the name must start with `'@'`. +""" +function env_exists(env_name::AbstractString)::Bool + return _no_at_name(env_name) in readdir(Pkg.envdir()) +end + +""" + create_env(env::StackEnv) + +Create a shared environment named `env.name` with packages `env.packages`. + +If the shared environment `env.name` already exists, an error is thrown. + +See [`StackEnv`](@ref). +""" +function create_env(env::StackEnv) + env_exists(env) && + throw(ErrorException(lazy"Environment \"$(env.name)\" already exists")) + return update_env(env) +end + +""" + update_env(env::StackEnv) + +Add all packages in `env.packages` to the shared environment `env.name`. + +If the shared environment `env.name` does not exist, it is created. + +This is the same as [`create_env`](@ref) except no check is made that the environment +does not already exist. Any packages that have already been added to the environment +will be added again, which should be little more than a no-op. + +# Examples + +Say I want to add `CondaPkg.jl` to the packages in `env::StackEnv`. + +```julia-repl +julia> env.packages +2-element Vector{Symbol}: + :PythonCall + :StatsBase + +julia> push!(env, :CondaPkg) +3-element Vector{Symbol}: + :PythonCall + :StatsBase + :CondaPkg + +julia> update_env(env) + Activating project at `~/.julia/environments/qkruntime_extra` + ... + Updating `~/.julia/environments/qkruntime_extra/Project.toml` + [992eb4ea] + CondaPkg v0.2.24 +``` +""" +function update_env(env::StackEnv) + _add_packages(env.name, env.packages) +end + +# Add `packages` to shared environment `name`. +function _add_packages(name::AbstractString, packages::AbstractVector{<:StrOrSym}) + current_project = Base.active_project() + try + activate_env(name) + for pkg in packages + Pkg.add(string(pkg)) + end + catch + rethrow() + finally + Pkg.activate(current_project) + end +end + +""" + update_env(env_name::AbstractString, env_packages::AbstractVector)::StackEnv + +Create `env = StackEnv(env_name, env_packages)` and create or update the environment `env_name`. + +The shared environment `env_name` will be created if it does not exist. +Possible existing packages in the the environment are not removed. If +packages in `env_packages` are already in the environment, they are added again. + +Elements of `env_packages` should be `AbstractString`s or `Symbol`s. + +`update_env` is the same as `create_env(env_name, env_packages)` except that the +latter will throw an error if the environment already exists. + +See [`StackEnv`](@ref). +""" +function update_env(env_name::AbstractString, env_packages::AbstractVector) + env = StackEnv(env_name, [Symbol(p) for p in env_packages]) + update_env(env) + return env +end + +""" + delete_from_stack!(env::StackEnv) + +Delete environment `env.name` if and wherever it occurs in the stack `Base.LOAD_PATH`. + +See [`StackEnv`](@ref). +""" +function delete_from_stack!(env::StackEnv) + return delete_from_stack!(env.name) +end + +""" + delete_from_stack!(env_name::Union{AbstractString, Symbol}) + +Delete environment `env_name` if and wherever it occurs in the stack `Base.LOAD_PATH`. + +See [`StackEnv`](@ref). +""" +function delete_from_stack!(env_name::StrOrSym) + stack = Base.LOAD_PATH + atname = _at_name(env_name) + inds = findall(==(atname), stack) + if !isnothing(inds) && !isempty(inds) + return deleteat!(stack, inds...) + end +end + +""" + activate_env(env::StackEnv) + +Activate the shared environment `env.name`. + +You might want to do this to add or remove packages from the environment. +But it is not necessary to activate it to use it. + +See [`StackEnv`](@ref), [`create_env`](@ref), [`ensure_in_stack`](@ref), +[`env_exists`](@ref). +""" +function activate_env(env::StackEnv) + return activate_env(env.name) +end + +function activate_env(env_name::StrOrSym) + return Pkg.activate(_no_at_name(env_name); shared=true) +end + +""" + read_env(env_name::StrOrSym) + +Read the `Project.toml` in environment `env_name`. + +A `Dict` mapping package names to uuids is returned. +""" +function read_env(env_name::StrOrSym) + env_name = string(env_name) + proj_file = joinpath(Pkg.envdir(), env_name, "Project.toml") + return TOML.parsefile(proj_file)["deps"] +end + +read_env(env::StackEnv) = read_env(env.name) + +end # module StackEnvs diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..ed485a1 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,113 @@ +using StackEnvs +using Test +import Pkg + +# Remove the environment from filesystem if it exists +function remove_from_filesystem(env_name) + env_path = joinpath(Pkg.envdir(), env_name) + isdir(env_path) || throw(ErrorException(lazy"Environment $env_name does not exist")) + if isdir(env_path) + rm(joinpath(env_path, "Project.toml")) + rm(joinpath(env_path, "Manifest.toml")) + rm(env_path) + end +end + + +@testset "StackEnvs.jl" begin + if !isdir(Pkg.envdir()) + mkdir(Pkg.envdir()) + end + + env_name = "stackenv" * string(rand(UInt), base=16) + packages = [:Example] + env = StackEnv(env_name, packages) + @test StackEnvs._at_name(env) == "@" * env_name + @test StackEnvs._no_at_name(env) == env_name + + @test !env_exists(env_name) + @test !env_exists(env) + @test !is_in_stack(env_name) + @test !is_in_stack(env) + + # There are two equivalent methods for ensure_in_stack + for func in (() -> ensure_in_stack(env), () -> ensure_in_stack(env_name, packages)) + try + env = func() + @test env_exists(env_name) + @test env_exists(env) + @test is_in_stack(env_name) + @test is_in_stack(env) + + proj_file_content = read_env(env) + @test collect(keys(proj_file_content)) == ["Example"] + + push!(env.packages, :ZChop) + ensure_in_stack(env) + proj_file_content = read_env(env) + @test sort!(collect(keys(proj_file_content))) == ["Example", "ZChop"] + + # Should do approximately nothing. Also not error + ensure_in_stack(env) + @test env_exists(env) + @test is_in_stack(env) + proj_file_content = read_env(env) + @test sort!(collect(keys(proj_file_content))) == ["Example", "ZChop"] + + # We don't have a precise test. This has added packages again. + update_env(env) + @test env_exists(env) + @test is_in_stack(env) + update_env(env.name, env.packages) + @test env_exists(env) + @test is_in_stack(env) + + catch + rethrow() + finally + remove_from_filesystem(env_name) + end + + @test !env_exists(env_name) + @test !env_exists(env) + @test is_in_stack(env_name) + @test is_in_stack(env) + + delete_from_stack!(env) + @test !is_in_stack(env_name) + @test !is_in_stack(env) + + + # Does not error if it is already gone + delete_from_stack!(env) + end + + current_project = Base.active_project() + try + ensure_in_stack(env) + activate_env(env) + @test Base.active_project() == joinpath(Pkg.envdir(), env_name, "Project.toml") + catch + rethrow() + finally + Pkg.activate(current_project) + remove_from_filesystem(env_name) + end + + env2 = StackEnv("newenv") + @test env2.name == "newenv" + @test isempty(env2.packages) + + for str in ("dog", "@dog") + @test StackEnvs._at_name(str) == "@dog" + @test StackEnvs._at_name(Symbol(str)) == "@dog" + @test StackEnvs._no_at_name(str) == "dog" + @test StackEnvs._no_at_name(Symbol(str)) == "dog" + end +end + +# JET seems to be making some false positives. +# Anyway, there are hundreds of complaints about Pkg that are beyond my control. +# include("test_jet.jl") + +include("test_aqua.jl") diff --git a/test/test_aqua.jl b/test/test_aqua.jl new file mode 100644 index 0000000..6966975 --- /dev/null +++ b/test/test_aqua.jl @@ -0,0 +1,30 @@ +using StackEnvs +using Aqua: Aqua + +@testset "aqua test ambiguities" begin + Aqua.test_ambiguities([StackEnvs, Core, Base]) +end + +@testset "aqua unbound_args" begin + Aqua.test_unbound_args(StackEnvs) +end + +@testset "aqua undefined exports" begin + Aqua.test_undefined_exports(StackEnvs) +end + +@testset "aqua piracies" begin + Aqua.test_piracies(StackEnvs) +end + +@testset "aqua project extras" begin + Aqua.test_project_extras(StackEnvs) +end + +@testset "aqua stale deps" begin + Aqua.test_stale_deps(StackEnvs) +end + +@testset "aqua deps compat" begin + Aqua.test_deps_compat(StackEnvs) +end diff --git a/test/test_jet.jl b/test/test_jet.jl new file mode 100644 index 0000000..3f749d2 --- /dev/null +++ b/test/test_jet.jl @@ -0,0 +1,32 @@ +# Borrowed from QuantumOpticsBase +using Test +using StackEnvs +using JET + +using JET: ReportPass, BasicPass, InferenceErrorReport, UncaughtExceptionReport + +# Custom report pass that ignores `UncaughtExceptionReport` +# Too coarse currently, but it serves to ignore the various +# "may throw" messages for runtime errors we raise on purpose +# (mostly on malformed user input) +struct MayThrowIsOk <: ReportPass end + +# ignores `UncaughtExceptionReport` analyzed by `JETAnalyzer` +(::MayThrowIsOk)(::Type{UncaughtExceptionReport}, @nospecialize(_...)) = return + +# forward to `BasicPass` for everything else +function (::MayThrowIsOk)(report_type::Type{<:InferenceErrorReport}, @nospecialize(args...)) + BasicPass()(report_type, args...) +end + +@testset "jet" begin +# if get(ENV,"STACK_ENVS_JET_TEST","")=="true" + if true + rep = report_package( + "StackEnvs"; + report_pass=MayThrowIsOk(), # TODO have something more fine grained than a generic "do not care about thrown errors" + ) + @show rep + @test length(JET.get_reports(rep)) == 0 + end +end # testset