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
17 changes: 16 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
name = "ReTest"
uuid = "e0db7c4e-2690-44b9-bad6-7687da720f89"
version = "0.4.1"
version = "0.4.2"
authors = ["Rafael Fourquet <fourquet.rafael@gmail.com>"]

[deps]
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
InlineTest = "bd334432-b1e7-49c7-a2dc-dd9149e4ebd6"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReplMaker = "b873ce64-0db9-51f5-a568-4457d8e49576"
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestEnv = "1e6cf692-eddd-4d53-88a5-2d735e33781b"

[weakdeps]
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[sources]
InlineTest = {path = "InlineTest"}

[extensions]
ReTestREPLExt = "REPL"

[compat]
InlineTest = "=0.2.0"
PrecompileTools = "1.2.1"
Preferences = "1.6"
REPL = "1"
ReplMaker = "0.2.8"
Revise = "3.1"
TOML = "1"
TestEnv = "1.102"
julia = "1.13"

[extras]
Expand Down
8 changes: 4 additions & 4 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
InlineTest = "bd334432-b1e7-49c7-a2dc-dd9149e4ebd6"
ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89"

[sources]
InlineTest = {path = "../InlineTest"}
ReTest = {path = ".."}

[compat]
Documenter = "1"

[sources]
ReTest = { path = ".." }
InlineTest = { path = "../InlineTest" }
105 changes: 78 additions & 27 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,40 +125,52 @@ end
end # module
```

We can now load tests either via `using MyPackageTests`, if `LOAD_PATH` is configured
appropriately, or via `include`, and run whichever tests we want:
We can now load tests either via `using MyPackageTests;
MyPackageTests.runtests()` as above, or with the ReTest REPL mode. When `ReTest`
is loaded in an interactive REPL, pressing `}` at the start of an empty `julia>`
line enters the `retest>` mode. Here are some example commands:

| Input | Equivalent call |
|:-----------------------|:-----------------------------------|
| `run` (`r`) | `retest()` |
| `run foo bar` | `retest("foo", "bar")` |
| `run foo -slow` | `retest("foo", "-slow")` |
| `run "two words"` | `retest("two words")` |
| `run 3 -4` | `retest(3, -4)` |
| `run :label` | `retest(:label)` |
| `run foo _dv2` | `retest("foo", :_dv2)` |
| `run foo verbose=2` | `retest("foo"; verbose=2)` |
| `run tag=[:a,:b]` | `retest(; tag=[:a, :b])` |
| `dry-run foo` (`dr`) | `retest("foo"; dry=true)` |
| `run-failed` (`rf`) | `retest(fail)` |
| `load` | (see below) |
| `set verbose 2` | (see [Preferences](@ref)) |
| `unset verbose` | (see [Preferences](@ref)) |
| `help` (`?`) | (prints a summary of the commands) |

For `run`, every argument which isn't a number, a `:label`, a `_`-prefixed
shorthand or a `key=value` keyword is passed as a pattern to
[`retest`](@ref). The value of a keyword is evaluated as Julia code, unless it
is quoted, in which case it is passed as a string.

The `load` command activates the test environment of the active project with
[TestEnv.jl](https://github.com/JuliaTesting/TestEnv.jl) and loads its tests
from `test/<Package>Tests.jl` with `load()`. The package itself doesn't
have to be loaded beforehand, as the test file can load it once the test
environment is active. For example, in the project of a
package `Foo`:
```julia
julia> include("test/MyPackageTests.jl");
julia> using ReTest # assumes ReTest.jl is installed in your base environment

julia> using ReTest # to use the `retest` function
retest> load # activates Foo's test environment and loads `FooTests`

julia> retest(dry=true, verbose=2) # just list tests, showing nested ones
MyPackage
1| greet
retest> dr # display all testsets (dry-run)

Main.MyPackageTests
1| more greet
2| concatenation
3| stuff
retest> r stats=true # run all tests with stats

julia> retest("greet", verbose=2) # run only tests related to `greet()`
Pass
MyPackage:
greet | 1

Main.MyPackageTests:
more greet | 1
concatenation | 1

Overall | 2

julia> MyPackageTests.runtests(3) # run only testset with ID 3 in MyPackageTests
Pass
3| stuff | 1
retest> rf # run last failing tests (run-failed)
```

Here it is for basic usage!


## API

Expand Down Expand Up @@ -364,6 +376,45 @@ instead of `@test true`, it could be useful to wrap it in its own testset, so th
it can be filtered out.


## Preferences
The default value of the `stats`, `verbose`, `id`, `marks`, and `spin` keywords
of [`retest`](@ref) can be set persistently for a project with
[`Preferences.jl`](https://github.com/JuliaPackaging/Preferences.jl), either by
manually editing a preferences file or by using the `set` and `unset` commands
of the REPL mode. `set` prints the current preferences when called
without arguments:
```julia
retest> set verbose inf

retest> set spin false

retest> unset spin # remove the `spin` preference

retest> set
stats = false
verbose = Inf
id = nothing
marks = true
spin = true
```

This writes the values in the `LocalPreferences.toml` file of the active project,
where they can also be edited by hand:
```toml
[ReTest]
verbose = inf
spin = false
```

These preferences are read each time `retest` is called so changing one takes
effect immediately. These particular preferences also will not trigger
recompilation of `ReTest`. They are overridden by the keywords explicitly passed
to `retest`.

The preferences file the REPL mode uses will be in the `test/` directory when
that contains a project file, or the package root directory otherwise.


## Running tests in parallel with `Distributed`

Currently, the tests are automatically run in parallel whenever there are
Expand Down
113 changes: 113 additions & 0 deletions ext/ReTestREPLExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
module ReTestREPLExt

import REPL
import TOML
using Base.CoreLogging: CoreLogging
using ReplMaker: initrepl, FunctionCompletionProvider
using ReTest: ReTest
using TestEnv: TestEnv


function load_tests()
project = Base.active_project()
name = get(TOML.parsefile(project), "name", nothing)
if isnothing(name)
error("the active project ($project) is not a package")
end
# must be computed before activating, which changes the active project
testdir = joinpath(dirname(project), "test")
testpath = joinpath(testdir, "$(name)Tests.jl")
if !isfile(testpath)
error("no test file found at $testpath")
end

@info "Switching to test environment..."
TestEnv.activate()
try
@info "Switched to $(Base.active_project())"
keep_preferences(project, testdir)
ReTest.load(testpath)
@info "Loaded $testpath"
catch
# leave the session as it was, so `load` can be tried again
Base.set_active_project(project)
undo_keep_preferences()
rethrow()
end
end

# The LOAD_PATH entry added by `keep_preferences`, if any
const pushed_env = Ref{Union{Nothing, String}}(nothing)

# Preferences set in the temporary test environment would be lost with it, so write
# them to the project `Pkg.test` reads them from, and put it on the load path to read
# them back (the active temporary project doesn't inherit from it)
function keep_preferences(project, testdir)
testproject = nothing
for name in Base.project_names
file = joinpath(testdir, name)
if isfile(file)
testproject = file
break
end
end
if !isnothing(testproject)
project = testproject
end
ReTest.preferences_project = project
env = dirname(project)
if !(env in Base.LOAD_PATH)
push!(Base.LOAD_PATH, env)
pushed_env[] = env
end
end

function undo_keep_preferences()
ReTest.preferences_project = nothing
env = pushed_env[]
if !isnothing(env)
filter!(!=(env), Base.LOAD_PATH)
pushed_env[] = nothing
end
end

# ReplMaker warns that `}` is already bound by the REPL's bracket insertion, which it
# preserves anyway when not at the start of the line: drop that message, keep the rest
struct DropKeyWarning{L<:CoreLogging.AbstractLogger} <: CoreLogging.AbstractLogger
parent::L
end

CoreLogging.shouldlog(l::DropKeyWarning, args...) = CoreLogging.shouldlog(l.parent, args...)
CoreLogging.min_enabled_level(l::DropKeyWarning) = CoreLogging.min_enabled_level(l.parent)
CoreLogging.catch_exceptions(l::DropKeyWarning) = CoreLogging.catch_exceptions(l.parent)

function CoreLogging.handle_message(l::DropKeyWarning, level, message, args...; kwargs...)
if level == CoreLogging.Warn && startswith(string(message), "REPL key ")
return nothing
end
CoreLogging.handle_message(l.parent, level, message, args...; kwargs...)
end

function init_repl_mode(repl)
CoreLogging.with_logger(DropKeyWarning(CoreLogging.current_logger())) do
initrepl(ReTest.repl_parse;
repl,
prompt_text = "retest> ",
prompt_color = :magenta,
start_key = '}',
mode_name = :retest,
completion_provider = FunctionCompletionProvider(ReTest.repl_completions),
startup_text = false)
end
end

function __init__()
ReTest.repl_load_tests_hook = load_tests
if isdefined(Base, :active_repl) && Base.active_repl isa REPL.LineEditREPL
init_repl_mode(Base.active_repl)
elseif isinteractive()
Base.atreplinit(init_repl_mode)
end
end

end
Loading
Loading