Skip to content

Commit 2da71bf

Browse files
committed
Merged origin/main into comparison-display
2 parents d305501 + 551f101 commit 2da71bf

File tree

15 files changed

+134
-126
lines changed

15 files changed

+134
-126
lines changed

.claude/settings.local.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
],
1010
"deny": []
1111
}
12-
}
12+
}

NEWS.md

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

33
* `expect_lt()`, `expect_gt()`, and friends have a refined display that is more likely to display the correct number of digits and shows you the actual values compared.
4+
* `describe()`, `it()`, and `test_that()` now have a shared stack of descriptions so that if you nest any inside of each other, any resulting failures will show you the full path.
5+
* `describe()` now correctly scopes `skip()` (#2007).
46
* `ParallelProgressReporter` now respect `max_failures` (#1162).
57
* The last snapshot is no longer lost if the snapshot file is missing the final newline (#2092). It's easy to accidentally remove this because there are two trailing new lines in snapshot files and many editors will automatically remove if you touch the file.
68
* New `expect_r6_class()` (#2030).

R/describe.R

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -56,43 +56,17 @@
5656
#' it("can handle division by 0") #not yet implemented
5757
#' })
5858
#' })
59-
6059
describe <- function(description, code) {
61-
check_string(description, allow_empty = FALSE)
62-
describe_description <- description
63-
64-
# prepares a new environment for each it-block
65-
describe_environment <- new.env(parent = parent.frame())
66-
describe_environment$it <- function(description, code = NULL) {
67-
check_string(description, allow_empty = FALSE)
68-
code <- substitute(code)
69-
70-
description <- paste0(describe_description, ": ", description)
71-
describe_it(description, code, describe_environment)
72-
}
73-
74-
eval(substitute(code), describe_environment)
75-
invisible()
76-
}
77-
78-
describe_it <- function(description, code, env = parent.frame()) {
79-
reporter <- get_reporter() %||% local_interactive_reporter()
80-
local_test_context()
60+
local_description_push(description)
8161

82-
test_code(
83-
description,
84-
code,
85-
env = env,
86-
reporter = reporter,
87-
skip_on_empty = FALSE
88-
)
62+
test_code(code, parent.frame(), skip_on_empty = FALSE)
8963
}
9064

9165
#' @export
9266
#' @rdname describe
9367
it <- function(description, code = NULL) {
94-
check_string(description, allow_empty = FALSE)
68+
local_description_push(description)
9569

9670
code <- substitute(code)
97-
describe_it(description, code, env = parent.frame())
71+
test_code(code, env = parent.frame(), skip_on_empty = FALSE)
9872
}

R/reporter-silent.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,11 @@ SilentReporter <- R6::R6Class(
2828
}
2929
)
3030
)
31+
32+
# Useful for testing test_that() and friends which otherwise swallow
33+
# all expectations by design
34+
capture_expectations <- function(code) {
35+
reporter <- SilentReporter$new()
36+
with_reporter(reporter, code)
37+
reporter$expectations()
38+
}

R/reporter-zzz.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ get_reporter <- function() {
3636
#' @rdname reporter-accessors
3737
#' @export
3838
with_reporter <- function(reporter, code, start_end_reporter = TRUE) {
39+
# Ensure we don't propagate the local description to the new reporter
40+
local_description_set()
3941
reporter <- find_reporter(reporter)
4042

4143
old <- set_reporter(reporter)

R/snapshot-reporter.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ SnapshotReporter <- R6::R6Class(
3535
},
3636

3737
start_test = function(context, test) {
38-
if (is.character(test)) {
39-
self$test <- gsub("\n", "", test)
38+
if (is.null(test)) {
39+
return()
4040
}
41+
42+
self$test <- paste0(gsub("\n", "", test), collapse = " / ")
4143
},
4244

4345
# Called by expectation

R/source.R

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ source_file <- function(
5555
withr::local_options(testthat_topenv = env, testthat_path = path)
5656
if (wrap) {
5757
invisible(test_code(
58-
test = NULL,
5958
code = exprs,
6059
env = env,
6160
reporter = get_reporter() %||% StopReporter$new()

R/test-example.R

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,23 @@ test_examples_installed <- function(package = testing_package()) {
4747
#' @export
4848
#' @rdname test_examples
4949
test_rd <- function(rd, title = attr(rd, "Rdfile")) {
50-
test_example(rd, title)
50+
test_example(rd, title %||% "example")
5151
}
5252

5353
#' @export
5454
#' @rdname test_examples
5555
test_example <- function(path, title = path) {
56+
local_description_push(title)
57+
5658
ex_path <- withr::local_tempfile(pattern = "test_example-", fileext = ".R")
5759
tools::Rd2ex(path, ex_path)
5860
if (!file.exists(ex_path)) {
5961
return(invisible(FALSE))
6062
}
6163

62-
env <- new.env(parent = globalenv())
63-
6464
ok <- test_code(
65-
test = title,
6665
code = parse(ex_path, encoding = "UTF-8"),
67-
env = env,
66+
env = globalenv(),
6867
reporter = get_reporter() %||% StopReporter$new(),
6968
skip_on_empty = FALSE
7069
)

R/test-that.R

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
#' })
3535
#' }
3636
test_that <- function(desc, code) {
37-
check_string(desc)
37+
local_description_push(desc)
3838

3939
code <- substitute(code)
4040
if (edition_get() >= 3) {
@@ -46,23 +46,19 @@ test_that <- function(desc, code) {
4646
}
4747
}
4848

49-
# Must initialise interactive reporter before local_test_context()
50-
reporter <- get_reporter() %||% local_interactive_reporter()
51-
local_test_context()
52-
53-
test_code(
54-
desc,
55-
code,
56-
env = parent.frame(),
57-
reporter = reporter
58-
)
49+
test_code(code, env = parent.frame())
5950
}
6051

6152
# Access error fields with `[[` rather than `$` because the
6253
# `$.Throwable` from the rJava package throws with unknown fields
63-
test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
54+
test_code <- function(code, env, reporter = NULL, skip_on_empty = TRUE) {
55+
# Must initialise interactive reporter before local_test_context()
56+
reporter <- get_reporter() %||% local_interactive_reporter()
57+
local_test_context()
58+
6459
frame <- caller_env()
6560

61+
test <- test_description()
6662
if (!is.null(test)) {
6763
reporter$start_test(context = reporter$.context, test = test)
6864
withr::defer(reporter$end_test(context = reporter$.context, test = test))
@@ -89,11 +85,6 @@ test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
8985
reporter$add_result(context = reporter$.context, test = test, result = e)
9086
}
9187

92-
# Any error will be assigned to this variable first
93-
# In case of stack overflow, no further processing (not even a call to
94-
# signalCondition() ) might be possible
95-
test_error <- NULL
96-
9788
expressions_opt <- getOption("expressions")
9889
expressions_opt_new <- min(expressions_opt + 500L, 500000L)
9990

@@ -104,39 +95,21 @@ test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
10495

10596
handle_error <- function(e) {
10697
handled <<- TRUE
107-
# First thing: Collect test error
108-
test_error <<- e
10998

11099
# Increase option(expressions) to handle errors here if possible, even in
111-
# case of a stack overflow. This is important for the DebugReporter.
112-
# Call options() manually, avoid withr overhead.
113-
options(expressions = expressions_opt_new)
114-
withr::defer(options(expressions = expressions_opt))
100+
# case of a stack overflow. This is important for the DebugReporter.
101+
local_options(expressions = expressions_opt_new)
115102

116103
# Add structured backtrace to the expectation
117104
if (can_entrace(e)) {
118105
e <- cnd_entrace(e)
119106
}
120107

121-
test_error <<- e
122-
123-
# Error will be handled by handle_fatal() if this fails; need to do it here
124-
# to be able to debug with the DebugReporter
125108
register_expectation(e, 2)
126-
127-
e[["handled"]] <- TRUE
128-
test_error <<- e
109+
invokeRestart("end_test")
129110
}
130111
handle_fatal <- function(e) {
131112
handled <<- TRUE
132-
# Error caught in handle_error() has precedence
133-
if (!is.null(test_error)) {
134-
e <- test_error
135-
if (isTRUE(e[["handled"]])) {
136-
return()
137-
}
138-
}
139-
140113
register_expectation(e, 0)
141114
}
142115
handle_expectation <- function(e) {
@@ -162,7 +135,6 @@ test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
162135
}
163136

164137
register_expectation(e, 5)
165-
166138
tryInvokeRestart("muffleWarning")
167139
}
168140
handle_message <- function(e) {
@@ -175,7 +147,7 @@ test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
175147

176148
debug_end <- if (inherits(e, "skip_empty")) -1 else 2
177149
register_expectation(e, debug_end)
178-
signalCondition(e)
150+
invokeRestart("end_test")
179151
}
180152

181153
test_env <- new.env(parent = env)
@@ -185,24 +157,25 @@ test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
185157
withr::local_options(testthat_topenv = test_env)
186158

187159
before <- inspect_state()
188-
tryCatch(
189-
withCallingHandlers(
190-
{
191-
eval(code, test_env)
192-
if (!handled && !is.null(test)) {
193-
skip_empty()
194-
}
195-
},
196-
expectation = handle_expectation,
197-
skip = handle_skip,
198-
warning = handle_warning,
199-
message = handle_message,
200-
error = handle_error
160+
withRestarts(
161+
tryCatch(
162+
withCallingHandlers(
163+
{
164+
eval(code, test_env)
165+
if (!handled && !is.null(test)) {
166+
skip_empty()
167+
}
168+
},
169+
expectation = handle_expectation,
170+
skip = handle_skip,
171+
warning = handle_warning,
172+
message = handle_message,
173+
error = handle_error
174+
),
175+
# some errors may need handling here, e.g., stack overflow
176+
error = handle_fatal
201177
),
202-
# some errors may need handling here, e.g., stack overflow
203-
error = handle_fatal,
204-
# skip silently terminate code
205-
skip = function(e) {}
178+
end_test = function() {}
206179
)
207180
after <- inspect_state()
208181

@@ -215,3 +188,30 @@ test_code <- function(test, code, env, reporter, skip_on_empty = TRUE) {
215188

216189
invisible(ok)
217190
}
191+
192+
193+
# Maintain a stack of descriptions
194+
local_description_push <- function(description, frame = caller_env()) {
195+
check_string(description, call = frame)
196+
local_description_set(c(the$description, description), frame = frame)
197+
}
198+
local_description_set <- function(
199+
description = character(),
200+
frame = caller_env()
201+
) {
202+
check_character(description, call = frame)
203+
204+
old <- the$description
205+
the$description <- description
206+
withr::defer(the$description <- old, frame)
207+
208+
invisible(old)
209+
}
210+
211+
test_description <- function() {
212+
if (length(the$description) == 0) {
213+
NULL
214+
} else {
215+
paste(the$description, collapse = " / ")
216+
}
217+
}

R/testthat-package.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
NULL
2020

2121
the <- new.env(parent = emptyenv())
22-
22+
the$description <- character()
2323

2424
# The following block is used by usethis to automatically manage
2525
# roxygen namespace tags. Modify with care!

0 commit comments

Comments
 (0)