Why cache tests?
If you're working on a big package with a test suite that takes a long time to run, having an option to only run tests that call functions (directly or indirectly) where code has changed could potentially bring lengthy calls to test() down to only a few seconds.
How would this work?
You'd need to check whether any functions that are directly or indirectly called during a test have changed since the last time it was run. I can think of two possible implementations:
Method 1: Use code parsing
You would need to parse each function called during the test to get a dependency tree, then inspect this for changes. This opens a can of worms because extracting function calls from R code is hard. E.g. how do you generalise the detection of the function foo in lapply(foo, 1:10)?
Method 2: Modify functions before running test_that()
This would work something like this:
-
When test_that() gets run, every function in the package is modified so that it records its (original) body whenever it gets called:
# 1. `foo()` is defined somewhere in myPackage
foo <- function(x) x + 1
# 2. At the start of `test_that()`, a new environment is set up to record the definitions of any
# functions which are called during the test
called_funs <- new.env()
# 3. Something like this happens for each function in myPackage - the function is modified so
# that, when called, it saves its (original) body to the `called_funs` environment
body(foo) <- as.call(c(
as.symbol("{"),
eval(bquote(expression(
called_funs$foo <- .(deparse(body(foo)))
))),
body(foo)
))
-
At the end of the test, called_funs will contain the bodies of all the functions in myPackage which have been called, directly or indirectly, during the test. (NB, you'd also want to record function arguments but that's omitted for the sake of brevity).
-
This list of functions definitions can then be checked for changes each time the test gets called, e.g. using snapshotting. If the snapshot passes (and the code for the test itself hasn't changed), the full test doesn't need to be run.
Possible issues
I can see two potential issues here:
-
Modifying the definition for foo as outlined above breaks something. I can't think of any reason why this would happen, but there's a lot of weird R code out there. This could be handled by making the caching opt-in.
-
Functions don't change superficially, but some dependency, e.g. some C++ code, does. Again, I think this could be handled by making the caching opt-in, or maybe opt-out for specific tests.
NB, this issue was prompted by a discussion on Twitter with @moodymudskipper.
As ever, thanks for the hard work on this amazing package.
Why cache tests?
If you're working on a big package with a test suite that takes a long time to run, having an option to only run tests that call functions (directly or indirectly) where code has changed could potentially bring lengthy calls to
test()down to only a few seconds.How would this work?
You'd need to check whether any functions that are directly or indirectly called during a test have changed since the last time it was run. I can think of two possible implementations:
Method 1: Use code parsing
You would need to parse each function called during the test to get a dependency tree, then inspect this for changes. This opens a can of worms because extracting function calls from R code is hard. E.g. how do you generalise the detection of the function
fooinlapply(foo, 1:10)?Method 2: Modify functions before running
test_that()This would work something like this:
When
test_that()gets run, every function in the package is modified so that it records its (original) body whenever it gets called:At the end of the test,
called_funswill contain the bodies of all the functions in myPackage which have been called, directly or indirectly, during the test. (NB, you'd also want to record function arguments but that's omitted for the sake of brevity).This list of functions definitions can then be checked for changes each time the test gets called, e.g. using snapshotting. If the snapshot passes (and the code for the test itself hasn't changed), the full test doesn't need to be run.
Possible issues
I can see two potential issues here:
Modifying the definition for
fooas outlined above breaks something. I can't think of any reason why this would happen, but there's a lot of weird R code out there. This could be handled by making the caching opt-in.Functions don't change superficially, but some dependency, e.g. some C++ code, does. Again, I think this could be handled by making the caching opt-in, or maybe opt-out for specific tests.
NB, this issue was prompted by a discussion on Twitter with @moodymudskipper.
As ever, thanks for the hard work on this amazing package.