Skip to content

Commit c36e880

Browse files
committed
Replaced filter_desc with dynamic label checking
1 parent c45ce93 commit c36e880

File tree

7 files changed

+99
-229
lines changed

7 files changed

+99
-229
lines changed

R/describe.R

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,21 @@
5858
#' })
5959
#' })
6060
describe <- function(description, code) {
61-
local_description_push(description)
61+
for (desc in eval(description)) {
62+
local_description_push(desc)
6263

63-
code <- substitute(code)
64-
test_code(code, parent.frame())
64+
code <- substitute(code)
65+
test_code(code, parent.frame())
66+
}
6567
}
6668

6769
#' @export
6870
#' @rdname describe
6971
it <- function(description, code = NULL) {
70-
local_description_push(description)
72+
for (desc in eval(description)) {
73+
local_description_push(desc)
7174

72-
code <- substitute(code)
73-
test_code(code, parent.frame())
75+
code <- substitute(code)
76+
test_code(code, parent.frame())
77+
}
7478
}

R/source.R

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ source_file <- function(
2424
shuffle = FALSE,
2525
error_call = caller_env()
2626
) {
27+
old_description <- the$description
28+
the$description <- character()
29+
withr::defer(the$description <- old_description)
30+
if (!is.null(desc)) {
31+
the$selected_description <- desc
32+
}
2733
check_string(path, call = error_call)
2834
if (!file.exists(path)) {
2935
cli::cli_abort("{.arg path} does not exist.", call = error_call)
@@ -49,7 +55,6 @@ source_file <- function(
4955
if (shuffle) {
5056
exprs <- sample(exprs)
5157
}
52-
exprs <- filter_desc(exprs, desc, error_call = error_call)
5358

5459
n <- length(exprs)
5560
if (n == 0L) {
@@ -82,38 +87,6 @@ source_file <- function(
8287
}
8388
}
8489

85-
filter_desc <- function(exprs, descs, error_call = caller_env()) {
86-
if (length(descs) == 0) {
87-
return(exprs)
88-
}
89-
desc <- descs[[1]]
90-
91-
subtest_idx <- which(unname(map_lgl(exprs, is_subtest)))
92-
93-
matching_idx <- keep(subtest_idx, \(idx) exprs[[idx]][[2]] == desc)
94-
if (length(matching_idx) == 0) {
95-
cli::cli_abort(
96-
"Failed to find test with description {.str {desc}}.",
97-
call = error_call
98-
)
99-
} else if (length(matching_idx) > 1) {
100-
cli::cli_abort(
101-
"Found multiple tests with description {.str {desc}}.",
102-
call = error_call
103-
)
104-
}
105-
106-
# Want all code up to and including the matching test, except for subtests
107-
keep_idx <- setdiff(seq2(1, matching_idx), setdiff(subtest_idx, matching_idx))
108-
# Recursively inspect the components of the subtest
109-
exprs[[matching_idx]][[3]] <- filter_desc(
110-
exprs[[matching_idx]][[3]],
111-
descs[-1],
112-
error_call = error_call
113-
)
114-
exprs[keep_idx]
115-
}
116-
11790
is_subtest <- function(expr) {
11891
is_call(expr, c("test_that", "describe", "it"), n = 2) && is_string(expr[[2]])
11992
}

R/test-that.R

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,41 @@
3333
#' expect_equal(sin(pi / 4), 1)
3434
#' })
3535
#' }
36-
test_that <- function(desc, code) {
37-
local_description_push(desc)
36+
test_that <- function(description, code) {
37+
for (desc in eval(description)) {
38+
local_description_push(desc)
3839

39-
code <- substitute(code)
40-
test_code(code, parent.frame())
40+
code <- substitute(code)
41+
test_code(code, parent.frame())
42+
}
43+
}
44+
45+
vgrepl <- function(x, pattern, ...) {
46+
if (length(pattern) != length(x)) stop("pattern and x must have the same length")
47+
mapply(function(p, s) grepl(p, s, ...), pattern, x, USE.NAMES = FALSE)
48+
}
49+
50+
# This utility function checks if the left vector of labels is a prefix of the
51+
# right vector of labels.
52+
is_prefix <- function(a, b) {
53+
if (length(a) == 0 || length(b) == 0) {
54+
TRUE
55+
} else if (length(a) <= length(b)) {
56+
all(vgrepl(a, head(b, length(a))))
57+
} else {
58+
all(vgrepl(b, head(a, length(b))))
59+
}
4160
}
4261

4362
# Access error fields with `[[` rather than `$` because the
4463
# `$.Throwable` from the rJava package throws with unknown fields
4564
test_code <- function(code, env, reporter = NULL, skip_on_empty = TRUE) {
65+
66+
# Check if the selected descriptions match the current stack
67+
check <- !is_prefix(the$description, the$selected_description)
68+
if (check) {
69+
return()
70+
}
4671
# Must initialise interactive reporter before local_test_context()
4772
reporter <- get_reporter() %||% local_interactive_reporter()
4873
local_test_context()

R/testthat-package.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ NULL
2020

2121
the <- new.env(parent = emptyenv())
2222
the$description <- character()
23+
the$selected_description <- character()
2324
the$top_level_test <- TRUE
2425
the$test_expectations <- 0
2526
the$in_check_reporter <- FALSE

tests/testthat/_snaps/source.md

Lines changed: 0 additions & 58 deletions
This file was deleted.

tests/testthat/test-source.R

Lines changed: 11 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -82,134 +82,6 @@ test_that("checks its inputs", {
8282
})
8383
})
8484

85-
# filter_desc -------------------------------------------------------------
86-
87-
test_that("works with all subtest 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-
})
97-
98-
test_that("only returns non-subtest code before subtest", {
99-
code <- exprs(
100-
f(),
101-
test_that("bar", {}),
102-
describe("foo", {}),
103-
g(),
104-
h()
105-
)
106-
expect_equal(filter_desc(code, "foo"), code[c(1, 3)])
107-
})
108-
109-
test_that("can select recursively", {
110-
code <- exprs(
111-
x <- 1,
112-
describe("a", {
113-
y <- 1
114-
describe("b", {
115-
z <- 1
116-
})
117-
y <- 2
118-
}),
119-
x <- 2
120-
)
121-
122-
expect_equal(
123-
filter_desc(code, c("a", "b")),
124-
exprs(
125-
x <- 1,
126-
describe("a", {
127-
y <- 1
128-
describe("b", {
129-
z <- 1
130-
})
131-
})
132-
)
133-
)
134-
})
135-
136-
test_that("works on code like the describe() example", {
137-
code <- exprs(
138-
describe("math library", {
139-
x1 <- 1
140-
x2 <- 1
141-
describe("addition()", {
142-
it("can add two numbers", {
143-
expect_equal(x1 + x2, addition(x1, x2))
144-
})
145-
})
146-
describe("division()", {
147-
x1 <- 10
148-
x2 <- 2
149-
it("can divide two numbers", {
150-
expect_equal(x1 / x2, division(x1, x2))
151-
})
152-
it("can handle division by 0") #not yet implemented
153-
})
154-
})
155-
)
156-
157-
expect_equal(
158-
filter_desc(
159-
code,
160-
c("math library", "division()", "can divide two numbers")
161-
),
162-
exprs(
163-
describe("math library", {
164-
x1 <- 1
165-
x2 <- 1
166-
describe("division()", {
167-
x1 <- 10
168-
x2 <- 2
169-
it("can divide two numbers", {
170-
expect_equal(x1 / x2, division(x1, x2))
171-
})
172-
})
173-
})
174-
)
175-
)
176-
177-
# what happens for an unimplemented specification?
178-
expect_snapshot(
179-
error = TRUE,
180-
filter_desc(
181-
code,
182-
c("math library", "division()", "can handle division by 0")
183-
)
184-
)
185-
})
186-
187-
test_that("preserve srcrefs", {
188-
code <- parse(
189-
keep.source = TRUE,
190-
text = '
191-
test_that("foo", {
192-
# this is a comment
193-
})
194-
'
195-
)
196-
expect_snapshot(filter_desc(code, "foo"))
197-
})
198-
199-
test_that("errors if zero or duplicate labels", {
200-
code <- exprs(
201-
f(),
202-
test_that("baz", {}),
203-
test_that("baz", {}),
204-
g()
205-
)
206-
207-
expect_snapshot(error = TRUE, {
208-
filter_desc(code, "baz")
209-
filter_desc(code, "missing")
210-
})
211-
})
212-
21385
test_that("source_dir()", {
21486
res <- source_dir("test_dir", pattern = "hello", chdir = TRUE, wrap = FALSE)
21587
expect_equal(res[[1]](), "Hello World")
@@ -233,3 +105,14 @@ test_that("source_dir()", {
233105
)
234106
expect_equal(res[[1]](), "Hello World")
235107
})
108+
109+
test_that("source_file selects correct descriptions", {
110+
reporter <- CheckReporter$new()
111+
with_reporter(reporter, {
112+
source_file("/home/kubajal/development/testthat/tests/testthat/test_dir/test-desc.R", desc = c("math library", "addition|division", "works for 4 and (1|7)"), wrap = FALSE)
113+
})
114+
expect_equal(reporter$problems$size(), 1)
115+
expect_equal(reporter$skips$size(), 1)
116+
expect_equal(reporter$n_ok, 3)
117+
})
118+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
addition <- function(a, b) { a + b }
3+
division <- function(a, b) {
4+
if (b == 1) {
5+
100
6+
} else if (b != 0) {
7+
a / b
8+
} else {
9+
NULL
10+
}
11+
}
12+
13+
describe("math library", {
14+
x1 <- 1
15+
x2 <- 1
16+
describe("addition()", {
17+
for (i in seq(1, 10)) {
18+
for (j in seq(1, 10)) {
19+
it(paste0("works for ", i, " and ", j), {
20+
expect_equal(addition(i, j), i + j)
21+
})
22+
}
23+
}
24+
})
25+
describe("division()", {
26+
x1 <- 10
27+
x2 <- 2
28+
for (i in seq(1, 10)) {
29+
for (j in seq(1, 10)) {
30+
if (i == 4 && j == 7) {
31+
skip()
32+
}
33+
it(paste0("works for ", i, " and ", j), {
34+
expect_equal(division(i, j), i / j)
35+
})
36+
}
37+
}
38+
it("can handle division by 0", {
39+
expect_null(division(x1, 0))
40+
})
41+
})
42+
})

0 commit comments

Comments
 (0)