Nothing
#' Source a file, directory of files, or various important subsets
#'
#' These are used by [test_dir()] and friends
#'
#' @param path Path to files.
#' @param pattern Regular expression used to filter files.
#' @param env Environment in which to evaluate code.
#' @param desc A character vector used to filter tests. This is used to
#' (recursively) filter the content of the file, so that only the non-test
#' code up to and including the matching test is run.
#' @param chdir Change working directory to `dirname(path)`?
#' @param wrap Automatically wrap all code within [test_that()]? This ensures
#' that all expectations are reported, even if outside a test block.
#' @param shuffle If `TRUE`, randomly reorder the top-level expressions
#' in the file.
#' @export
#' @keywords internal
source_file <- function(
path,
env = test_env(),
chdir = TRUE,
desc = NULL,
wrap = TRUE,
shuffle = FALSE,
error_call = caller_env()
) {
check_string(path, call = error_call)
if (!file.exists(path)) {
cli::cli_abort("{.arg path} does not exist.", call = error_call)
}
if (!is.environment(env)) {
stop_input_type(env, "an environment", call = error_call)
}
check_character(desc, allow_null = TRUE)
lines <- brio::read_lines(path)
srcfile <- srcfilecopy(
path,
lines,
file.info(path)[1, "mtime"],
isFile = TRUE
)
## We need to parse from a connection, because parse() has a bug,
## and converts the input to the native encoding, if the text arg is used
con <- textConnection(lines, encoding = "UTF-8")
withr::defer(try(close(con), silent = TRUE))
exprs <- parse(con, n = -1, srcfile = srcfile, encoding = "UTF-8")
if (shuffle) {
exprs <- sample(exprs)
}
exprs <- filter_desc(exprs, desc, error_call = error_call)
n <- length(exprs)
if (n == 0L) {
return(invisible())
}
if (chdir) {
old_dir <- setwd(dirname(path))
withr::defer(setwd(old_dir))
}
withr::local_options(testthat_topenv = env, testthat_path = path)
if (wrap) {
invisible(test_code(
code = exprs,
env = env,
reporter = get_reporter() %||% StopReporter$new()
))
} else {
withCallingHandlers(
invisible(eval(exprs, env)),
error = function(err) {
cli::cli_abort(
"Failed to evaluate {.path {path}}.",
parent = err,
call = error_call
)
}
)
}
}
filter_desc <- function(exprs, descs, error_call = caller_env()) {
if (length(descs) == 0) {
return(exprs)
}
desc <- descs[[1]]
subtest_idx <- which(unname(map_lgl(exprs, is_subtest)))
matching_idx <- keep(subtest_idx, \(idx) exprs[[idx]][[2]] == desc)
if (length(matching_idx) == 0) {
cli::cli_abort(
"Failed to find test with description {.str {desc}}.",
call = error_call
)
} else if (length(matching_idx) > 1) {
cli::cli_abort(
"Found multiple tests with description {.str {desc}}.",
call = error_call
)
}
# Want all code up to and including the matching test, except for subtests
keep_idx <- setdiff(seq2(1, matching_idx), setdiff(subtest_idx, matching_idx))
# Recursively inspect the components of the subtest
exprs[[matching_idx]][[3]] <- filter_desc(
exprs[[matching_idx]][[3]],
descs[-1],
error_call = error_call
)
exprs[keep_idx]
}
is_subtest <- function(expr) {
is_call(expr, c("test_that", "describe", "it"), n = 2) &&
is_string(expr[[2]]) &&
is_call(expr[[3]], "{")
}
#' @rdname source_file
#' @export
source_dir <- function(
path,
pattern = "\\.[rR]$",
env = test_env(),
chdir = TRUE,
wrap = TRUE,
shuffle = FALSE
) {
files <- sort(dir(path, pattern, full.names = TRUE))
error_call <- current_env()
lapply(files, function(path) {
source_file(
path,
env = env,
chdir = chdir,
wrap = wrap,
shuffle = shuffle,
error_call = error_call
)
})
}
#' @rdname source_file
#' @export
source_test_helpers <- function(path = "tests/testthat", env = test_env()) {
source_dir(path, "^helper.*\\.[rR]$", env = env, wrap = FALSE)
}
#' @rdname source_file
#' @export
source_test_setup <- function(path = "tests/testthat", env = test_env()) {
source_dir(path, "^setup.*\\.[rR]$", env = env, wrap = FALSE)
}
#' @rdname source_file
#' @export
source_test_teardown <- function(path = "tests/testthat", env = test_env()) {
source_dir(path, "^teardown.*\\.[rR]$", env = env, wrap = FALSE)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.