Last active
June 21, 2023 06:33
-
-
Save torfjelde/62c1281d5fc486d3a404e5de6cf285d4 to your computer and use it in GitHub Desktop.
Script for watching and running tests or certain files (using Revise.jl to avoid full re-compilation). Useful if you're taking a test-driven development. Requries the following packages to installed in the global environment: - `ArgParse` - `Revise` - `TestEnv`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env julia | |
using ArgParse | |
# Scenarios: | |
# 1. Project is specified, but no files => watch project + run `runtests.jl`. | |
# 2. Project is specified, and files => watch project + run files. | |
# Command line arguments. | |
s = ArgParseSettings() | |
@add_arg_table s begin | |
"files" | |
help = "files to both watch and run upon changes" | |
nargs = '*' | |
action = :store_arg | |
"--project" | |
help = "path to project" | |
"--setup" | |
help = "setup script to run before running tests" | |
"--test" | |
help = "run the `runtests.jl` file in the project upon changes" | |
action = :store_true | |
"--use-testenv" | |
help = "make use of `TestEnv.activate()`" | |
action = :store_true | |
"--not-all" | |
help = "only run upon changes to the target module" | |
action = :store_true | |
"--show-errors" | |
help = "show errors if encountered" | |
action = :store_true | |
"--verbose" | |
help = "show verbose output" | |
action = :store_true | |
end | |
# Parse command line arguments | |
parsed_args = parse_args(ARGS, s) | |
files = parsed_args["files"] | |
project = parsed_args["project"] | |
setup = parsed_args["setup"] | |
istest = parsed_args["test"] | |
notall = parsed_args["not-all"] | |
usetestenv = parsed_args["use-testenv"] | |
verbose = parsed_args["verbose"] | |
if isempty(files) && !isnothing(project) | |
@warn "Project specified, but no files; running as if `--test` was specified." | |
istest = true | |
end | |
# Activate the module at path. | |
using Pkg | |
if !isnothing(project) | |
Pkg.activate(project) | |
end | |
# Load `Revise`. | |
using Revise | |
function revise_watch_and_run(files_to_run, args...; show_errors=false, verbose=false, kwargs...) | |
# Revise might encounter an error on the files it's watching, in which case | |
# we need to re-trigger `Revise.entr`. BUT to avoid this happening repeatedly, | |
# we set `postpone=true` in the `Revise.entr` call above. This postpones the first | |
# trigger of the provided `f` until an actual change (which should hopefully be fixing | |
# the error that caused Revise to fail). | |
revise_errored = false | |
while true | |
try | |
Revise.entr(args...; postpone=revise_errored, kwargs...) do | |
try | |
verbose && @info "Detected change; running!" | |
for f in files_to_run | |
verbose && @info "Including" abspath(f) | |
include(abspath(f)) | |
end | |
catch e | |
if show_errors | |
showerror(stderr, e, catch_backtrace()) | |
end | |
end | |
# Reset `revise_errored` | |
revise_errored = false | |
end | |
catch e | |
showerror(stderr, e, catch_backtrace()) | |
# Set `revise_errored` to true, so that the next time `Revise.entr` is called, | |
# we have `postpone=true` and don't trigger revision until a change is detected. | |
revise_errored = true | |
end | |
end | |
end | |
files_to_watch = [] | |
files_to_run = [] | |
modules_to_watch = [] | |
mod_sym = nothing | |
# Activate the test environment for the package. | |
if usetestenv | |
@assert !isnothing(project) "Must specify a project to use `TestEnv`." | |
@info "Activating test environment." | |
using TestEnv | |
# NOTE: Have to get this before `TestEnv.activate`. | |
mod_sym = Symbol(TestEnv.current_pkg_name()) | |
TestEnv.activate() | |
end | |
if istest | |
@assert !isnothing(project) "Project must be specified if `--test` is specified." | |
@info "Loading module in $(project)" | |
# Get the current package name. | |
if isnothing(mod_sym) | |
using TestEnv | |
mod_sym = Symbol(TestEnv.current_pkg_name()) | |
end | |
# Load the module and assign it to `mod` so we can reference it. | |
@eval begin | |
using $mod_sym | |
mod = $mod_sym | |
end | |
# Obtain the path for the `runtests.jl` file. | |
testpath = joinpath(dirname(pathof(mod)), "..", "test", "runtests.jl") | |
# Watch the module and run the tests. | |
push!(files_to_watch, testpath) | |
push!(files_to_run, testpath) | |
push!(modules_to_watch, mod) | |
end | |
if !isempty(files) | |
# Watch both module and the specified files, and run the specified files upon changes. | |
append!(files_to_watch, files) | |
append!(files_to_run, files) | |
end | |
# Watch and run! | |
isempty(files_to_run) && error("Either `--test` or `files` must be specified.") | |
# Run the setup script if specified. | |
if !isnothing(setup) | |
@info "Including setup script ($setup); to NOT watch modules imported in this too, make sure `--not-all` is specified." | |
include(abspath(setup)) | |
end | |
if isempty(modules_to_watch) | |
revise_watch_and_run( | |
files_to_run, files_to_watch; | |
show_errors=parsed_args["show-errors"], | |
verbose=verbose, | |
all=!notall, | |
) | |
else | |
revise_watch_and_run( | |
files_to_run, files_to_watch, modules_to_watch; | |
show_errors=parsed_args["show-errors"], | |
verbose=verbose, | |
all=!notall, | |
) | |
end | |
Note that this requires having both https://timholy.github.io/Revise.jl/stable/ and https://github.com/JuliaTesting/TestEnv.jl in our global Julia env.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Typical use-case for me looks something like: