Skip to content

Commit 596b3e4

Browse files
kubajalhadley
andauthored
Support selection of nested subtests (#2077)
Fixes #2077 Co-authored-by: Hadley Wickham <[email protected]>
1 parent 0cea43e commit 596b3e4

File tree

4 files changed

+82
-55
lines changed

4 files changed

+82
-55
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# testthat (development version)
22

3+
* 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).
34
* New `snapshot_reject()` rejects all modified snapshots by deleting the `.new` variants (#1923).
45
* 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).
56
* Power `expect_mapequal()` with `waldo::compare(list_as_map = TRUE)` (#1521).

R/source.R

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
#' @param path Path to files.
66
#' @param pattern Regular expression used to filter files.
77
#' @param env Environment in which to evaluate code.
8-
#' @param desc If not-`NULL`, will run only test with this `desc`ription.
8+
#' @param desc A character vector used to filter tests. This is used to
9+
#' (recursively) filter the content of the file, so that only the non-test
10+
#' code up to and including the match test is run.
911
#' @param chdir Change working directory to `dirname(path)`?
1012
#' @param wrap Automatically wrap all code within [test_that()]? This ensures
1113
#' that all expectations are reported, even if outside a test block.
@@ -26,6 +28,7 @@ source_file <- function(
2628
if (!is.environment(env)) {
2729
stop_input_type(env, "an environment", call = error_call)
2830
}
31+
check_character(desc, allow_null = TRUE)
2932

3033
lines <- brio::read_lines(path)
3134
srcfile <- srcfilecopy(
@@ -73,50 +76,40 @@ source_file <- function(
7376
}
7477
}
7578

76-
filter_desc <- function(exprs, desc = NULL, error_call = caller_env()) {
77-
if (is.null(desc)) {
79+
filter_desc <- function(exprs, descs, error_call = caller_env()) {
80+
if (length(descs) == 0) {
7881
return(exprs)
7982
}
83+
desc <- descs[[1]]
8084

81-
found <- FALSE
82-
include <- rep(FALSE, length(exprs))
85+
subtest_idx <- which(unname(map_lgl(exprs, is_subtest)))
8386

84-
for (i in seq_along(exprs)) {
85-
expr <- exprs[[i]]
86-
87-
if (!is_call(expr, c("test_that", "describe"), n = 2)) {
88-
if (!found) {
89-
include[[i]] <- TRUE
90-
}
91-
} else {
92-
if (!is_string(expr[[2]])) {
93-
next
94-
}
95-
96-
test_desc <- as.character(expr[[2]])
97-
if (test_desc != desc) {
98-
next
99-
}
100-
101-
if (found) {
102-
cli::cli_abort(
103-
"Found multiple tests with specified description.",
104-
call = error_call
105-
)
106-
}
107-
include[[i]] <- TRUE
108-
found <- TRUE
109-
}
110-
}
111-
112-
if (!found) {
87+
matching_idx <- keep(subtest_idx, \(idx) exprs[[idx]][[2]] == desc)
88+
if (length(matching_idx) == 0) {
11389
cli::cli_abort(
114-
"Failed to find test with specified description.",
90+
"Failed to find test with description {.str {desc}}.",
91+
call = error_call
92+
)
93+
} else if (length(matching_idx) > 1) {
94+
cli::cli_abort(
95+
"Found multiple tests with description {.str {desc}}.",
11596
call = error_call
11697
)
11798
}
11899

119-
exprs[include]
100+
# Want all code up to and including the matching test, except for subtests
101+
keep_idx <- setdiff(seq2(1, matching_idx), setdiff(subtest_idx, matching_idx))
102+
# Recursively inspect the components of the subtest
103+
exprs[[matching_idx]][[3]] <- filter_desc(
104+
exprs[[matching_idx]][[3]],
105+
descs[-1],
106+
error_call = error_call
107+
)
108+
exprs[keep_idx]
109+
}
110+
111+
is_subtest <- function(expr) {
112+
is_call(expr, c("test_that", "describe", "it"), n = 2) && is_string(expr[[2]])
120113
}
121114

122115
#' @rdname source_file

tests/testthat/_snaps/source.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,6 @@
2626
Error:
2727
! `env` must be an environment, not the string "x".
2828

29-
# can find only matching test
30-
31-
Code
32-
filter_desc(code, "baz")
33-
Condition
34-
Error:
35-
! Failed to find test with specified description.
36-
3729
# preserve srcrefs
3830

3931
Code
@@ -43,11 +35,16 @@
4335
# this is a comment
4436
}))
4537

46-
# errors if duplicate labels
38+
# errors if zero or duplicate labels
4739

4840
Code
4941
filter_desc(code, "baz")
5042
Condition
5143
Error:
52-
! Found multiple tests with specified description.
44+
! Found multiple tests with description "baz".
45+
Code
46+
filter_desc(code, "missing")
47+
Condition
48+
Error:
49+
! Failed to find test with description "missing".
5350

tests/testthat/test-source.R

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,54 @@ test_that("checks its inputs", {
8282
})
8383
})
8484

85+
# filter_desc -------------------------------------------------------------
8586

86-
# filter_label -------------------------------------------------------------
87+
test_that("works with all tests types", {
88+
code <- exprs(
89+
test_that("foo", {}),
90+
describe("bar", {}),
91+
it("baz", {})
92+
)
93+
expect_equal(filter_desc(code, "foo"), code[1])
94+
expect_equal(filter_desc(code, "bar"), code[2])
95+
expect_equal(filter_desc(code, "baz"), code[3])
96+
})
8797

88-
test_that("can find only matching test", {
98+
test_that("only returns code before subtest", {
8999
code <- exprs(
90100
f(),
91-
test_that("foo", {}),
101+
describe("foo", {}),
92102
g(),
93-
describe("bar", {}),
94103
h()
95104
)
96105
expect_equal(filter_desc(code, "foo"), code[c(1, 2)])
97-
expect_equal(filter_desc(code, "bar"), code[c(1, 3, 4)])
98-
expect_snapshot(filter_desc(code, "baz"), error = TRUE)
106+
})
107+
108+
test_that("can select recursively", {
109+
code <- exprs(
110+
x <- 1,
111+
describe("a", {
112+
y <- 1
113+
describe("b", {
114+
z <- 1
115+
})
116+
y <- 2
117+
}),
118+
x <- 2
119+
)
120+
121+
expect_equal(
122+
filter_desc(code, c("a", "b")),
123+
exprs(
124+
x <- 1,
125+
describe("a", {
126+
y <- 1
127+
describe("b", {
128+
z <- 1
129+
})
130+
})
131+
)
132+
)
99133
})
100134

101135
test_that("preserve srcrefs", {
@@ -110,16 +144,18 @@ test_that("preserve srcrefs", {
110144
expect_snapshot(filter_desc(code, "foo"))
111145
})
112146

113-
114-
test_that("errors if duplicate labels", {
147+
test_that("errors if zero or duplicate labels", {
115148
code <- exprs(
116149
f(),
117150
test_that("baz", {}),
118151
test_that("baz", {}),
119152
g()
120153
)
121154

122-
expect_snapshot(filter_desc(code, "baz"), error = TRUE)
155+
expect_snapshot(error = TRUE, {
156+
filter_desc(code, "baz")
157+
filter_desc(code, "missing")
158+
})
123159
})
124160

125161
test_that("source_dir()", {

0 commit comments

Comments
 (0)