Skip to content

Commit ca74c2a

Browse files
committed
Add backtraces to errors
1 parent 4dfc105 commit ca74c2a

File tree

6 files changed

+60
-10
lines changed

6 files changed

+60
-10
lines changed

R/expectation.R

+30-6
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@
99
#' for backward compatibility only and new expectations should not use it.
1010
#' @param srcref Location of the failure. Should only needed to be explicitly
1111
#' supplied when you need to forward a srcref captured elsewhere.
12+
#' @param env The environment of the `expect_` function. You should
13+
#' only pass this when you're calling `expect()` from an internal
14+
#' helper. This environment is passed to [rlang::trace_back()] as
15+
#' `bottom` argument, in order to remove the uninformative testthat
16+
#' context from backtraces.
1217
#' @return An expectation object. Signals the expectation condition
1318
#' with a `continue_test` restart.
1419
#' @export
15-
expect <- function(ok, failure_message, info = NULL, srcref = NULL) {
20+
expect <- function(ok, failure_message, info = NULL, srcref = NULL,
21+
env = NULL) {
22+
env <- env %||% caller_env()
23+
1624
type <- if (ok) "success" else "failure"
1725

1826
# Preserve existing API which appear to be used in package test code
@@ -29,7 +37,7 @@ expect <- function(ok, failure_message, info = NULL, srcref = NULL) {
2937
}
3038
}
3139

32-
exp <- expectation(type, message, srcref = srcref)
40+
exp <- expectation(type, message, srcref = srcref, env = env)
3341

3442
withRestarts(
3543
if (ok) signalCondition(exp) else stop(exp),
@@ -49,15 +57,23 @@ expect <- function(ok, failure_message, info = NULL, srcref = NULL) {
4957
#' "skip", "warning".
5058
#' @param message Message describing test failure
5159
#' @param srcref Optional `srcref` giving location of test.
60+
#' @inheritParams expect
5261
#' @keywords internal
5362
#' @export
54-
expectation <- function(type, message, srcref = NULL) {
63+
expectation <- function(type, message, srcref = NULL, env = NULL) {
5564
type <- match.arg(type, c("success", "failure", "error", "skip", "warning"))
5665

66+
if (type %in% c("failure", "error")) {
67+
trace <- trace_back(test_data$trace_top, env)
68+
} else {
69+
trace <- NULL
70+
}
71+
5772
structure(
5873
list(
5974
message = message,
60-
srcref = srcref
75+
srcref = srcref,
76+
trace = trace
6177
),
6278
class = c(
6379
paste0("expectation_", type),
@@ -86,14 +102,22 @@ format.expectation_success <- function(x, ...) {
86102

87103
#' @export
88104
format.expectation_error <- function(x, ...) {
89-
paste(c(x$message, create_traceback(x$call)), collapse = "\n")
105+
format_with_trace(x)
90106
}
91107

92108
#' @export
93109
format.expectation <- function(x, ...) {
94110
x$message
95111
}
96112

113+
format_with_trace <- function(exp) {
114+
paste_line(
115+
paste0("Message: ", crayon::italic(exp$message)),
116+
"Backtrace:",
117+
!!!format(exp$trace, simplify = "branch")
118+
)
119+
}
120+
97121
# as.expectation ----------------------------------------------------------
98122

99123
as.expectation <- function(x, ...) UseMethod("as.expectation", x)
@@ -120,7 +144,7 @@ as.expectation.error <- function(x, ..., srcref = NULL) {
120144
# Remove trailing newline to be consistent with other conditons
121145
msg <- gsub("\n$", "", msg)
122146

123-
expectation("error", msg, srcref)
147+
expectation("error", msg, srcref, env = x$trace_bottom)
124148
}
125149

126150
#' @export

R/test-that.R

+10
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ test_that <- function(desc, code) {
3131
test_code(desc, code, env = parent.frame())
3232
}
3333

34+
test_data <- env(emptyenv())
35+
test_data$trace_top <- NULL
36+
3437
test_code <- function(test, code, env = test_env(), skip_on_empty = TRUE) {
3538
if (!is.null(test)) {
3639
get_reporter()$start_test(context = get_reporter()$.context, test = test)
@@ -90,6 +93,11 @@ test_code <- function(test, code, env = test_env(), skip_on_empty = TRUE) {
9093
# tryCatch etc).
9194
e$expectation_calls <- frame_calls(11, 2)
9295

96+
# FIXME: Export from rlang
97+
nframe <- sys.nframe() - 1
98+
info <- rlang:::signal_context_info(nframe)
99+
e$trace_bottom <- sys.frame(info[[2]])
100+
93101
test_error <<- e
94102

95103
# Error will be handled by handle_fatal() if this fails; need to do it here
@@ -152,6 +160,8 @@ test_code <- function(test, code, env = test_env(), skip_on_empty = TRUE) {
152160
}
153161

154162
test_env <- new.env(parent = env)
163+
test_data$trace_top <- test_env
164+
155165
tryCatch(
156166
withCallingHandlers(
157167
{

R/utils.R

+4
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,7 @@ context_name <- function(filename) {
9393

9494
filename
9595
}
96+
97+
paste_line <- function(...) {
98+
paste(chr(...), collapse = "\n")
99+
}

man/expect.Rd

+7-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/expectation.Rd

+7-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/quasi_label.Rd

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)