#!/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