Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# testthat (development version)

* Test filtering now works with `it()`, and the `desc` argument can take a character vector in order to recursively filter subtests (i.e. `it()` nested inside of `describe()`) (#2118).
* New `SlowReporter` makes it easier to find the slowest tests in your package. The easiest way to run it is with `devtools::test(reporter = "slow")` (#1466).
* Power `expect_mapequal()` with `waldo::compare(list_as_map = TRUE)` (#1521).
* On CRAN, `test_that()` now automatically skips if a package is not installed (#1585). Practically, this means that you no longer need to check that suggested packages are installed. (We don't do this in the tidyverse because we think it has limited payoff, but other styles advise differently.)
Expand Down
66 changes: 31 additions & 35 deletions R/source.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#' @param path Path to files.
#' @param pattern Regular expression used to filter files.
#' @param env Environment in which to evaluate code.
#' @param desc If not-`NULL`, will run only test with this `desc`ription.
#' @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 match 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.
Expand All @@ -26,6 +28,7 @@ source_file <- function(
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(
Expand Down Expand Up @@ -73,47 +76,40 @@ source_file <- function(
}
}

filter_desc <- function(exprs, desc = NULL, error_call = caller_env()) {
if (is.null(desc)) {
filter_desc <- function(exprs, descs, error_call = caller_env()) {
if (length(descs) == 0) {
return(exprs)
}
desc <- descs[[1]]

found <- FALSE
include <- rep(FALSE, length(exprs))
subtest_idx <- which(unname(map_lgl(exprs, is_subtest)))

for (i in seq_along(exprs)) {
expr <- exprs[[i]]

if (!is_call(expr, c("test_that", "describe"), n = 2)) {
if (!found) {
include[[i]] <- TRUE
}
} else {
if (!is_string(expr[[2]])) {
next
}

test_desc <- as.character(expr[[2]])
if (test_desc != desc) {
next
}

if (found) {
abort(
"Found multiple tests with specified description",
call = error_call
)
}
include[[i]] <- TRUE
found <- TRUE
}
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
)
}

if (!found) {
abort("Failed to find test with specified description", 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]
}

exprs[include]
is_subtest <- function(expr) {
is_call(expr, c("test_that", "describe", "it"), n = 2) && is_string(expr[[2]])
}

#' @rdname source_file
Expand Down
17 changes: 7 additions & 10 deletions tests/testthat/_snaps/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@
Error:
! `env` must be an environment, not the string "x".

# can find only matching test

Code
filter_desc(code, "baz")
Condition
Error:
! Failed to find test with specified description

# preserve srcrefs

Code
Expand All @@ -43,11 +35,16 @@
# this is a comment
}))

# errors if duplicate labels
# errors if zero or duplicate labels

Code
filter_desc(code, "baz")
Condition
Error:
! Found multiple tests with specified description
! Found multiple tests with description "baz".
Code
filter_desc(code, "missing")
Condition
Error:
! Failed to find test with description "missing".

54 changes: 45 additions & 9 deletions tests/testthat/test-source.R
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,54 @@ test_that("checks its inputs", {
})
})

# filter_desc -------------------------------------------------------------

# filter_label -------------------------------------------------------------
test_that("works with all tests types", {
code <- exprs(
test_that("foo", {}),
describe("bar", {}),
it("baz", {})
)
expect_equal(filter_desc(code, "foo"), code[1])
expect_equal(filter_desc(code, "bar"), code[2])
expect_equal(filter_desc(code, "baz"), code[3])
})

test_that("can find only matching test", {
test_that("only returns code before subtest", {
code <- exprs(
f(),
test_that("foo", {}),
describe("foo", {}),
g(),
describe("bar", {}),
h()
)
expect_equal(filter_desc(code, "foo"), code[c(1, 2)])
expect_equal(filter_desc(code, "bar"), code[c(1, 3, 4)])
expect_snapshot(filter_desc(code, "baz"), error = TRUE)
})

test_that("can select recursively", {
code <- exprs(
x <- 1,
describe("a", {
y <- 1
describe("b", {
z <- 1
})
y <- 2
}),
x <- 2
)

expect_equal(
filter_desc(code, c("a", "b")),
exprs(
x <- 1,
describe("a", {
y <- 1
describe("b", {
z <- 1
})
})
)
)
})

test_that("preserve srcrefs", {
Expand All @@ -110,16 +144,18 @@ test_that("preserve srcrefs", {
expect_snapshot(filter_desc(code, "foo"))
})


test_that("errors if duplicate labels", {
test_that("errors if zero or duplicate labels", {
code <- exprs(
f(),
test_that("baz", {}),
test_that("baz", {}),
g()
)

expect_snapshot(filter_desc(code, "baz"), error = TRUE)
expect_snapshot(error = TRUE, {
filter_desc(code, "baz")
filter_desc(code, "missing")
})
})

test_that("source_dir()", {
Expand Down
Loading