test_that("cnd_entrace() entraces conditions properly", {
with_cnd_entrace <- function(signaller, catcher, arg, classes = "error") {
f <- function() g()
g <- function() h()
h <- function() signaller(arg)
handlers <- rep_named(classes, alist(function(cnd) {
cnd <- cnd_entrace(cnd)
cnd_signal(cnd)
}))
env_bind_lazy(current_env(), do = catcher(withCallingHandlers(f(), !!!handlers)))
do
}
expect_cnd_trace <- function(signaller,
catcher,
arg,
native = NULL,
classes = "error",
abort = FALSE) {
err <- with_cnd_entrace(signaller, catcher, arg, classes = classes)
trace <- err$trace
n <- trace_length(err$trace)
if (is_null(trace)) {
abort("Expected trace, got NULL.")
}
if (abort) {
calls <- trace$call[seq2(n - 3, n)]
expect_true(all(
is_call(calls[[1]], "f"),
is_call(calls[[2]], "g"),
is_call(calls[[3]], "h"),
is_call(calls[[4]], "signaller")
))
} else if (is_null(native)) {
calls <- trace$call[seq2(n - 2, n)]
expect_true(all(
is_call(calls[[1]], "f"),
is_call(calls[[2]], "g"),
is_call(calls[[3]], "h")
))
} else {
calls <- trace$call[seq2(n - 4, n)]
expect_true(all(
is_call(calls[[1]], "f"),
is_call(calls[[2]], "g"),
is_call(calls[[3]], "h"),
is_call(calls[[4]], "signaller"),
is_call(calls[[5]], native)
))
}
}
local_options(
rlang_trace_top_env = current_env()
)
with_cnd_entrace(base::message, catch_message, "")
with_cnd_entrace(base::message, catch_error, "", classes = "message")
expect_cnd_trace(base::stop, catch_error, "")
expect_cnd_trace(base::stop, catch_error, cnd("error"))
expect_cnd_trace(function(msg) errorcall(NULL, msg), catch_error, "", "errorcall")
expect_cnd_trace(abort, catch_error, "", abort = TRUE)
expect_cnd_trace(base::warning, catch_warning, "", classes = "warning")
expect_cnd_trace(base::warning, catch_warning, cnd("warning"), classes = "warning")
expect_cnd_trace(function(msg) warningcall(NULL, msg), catch_warning, "", "warningcall", classes = "warning")
expect_cnd_trace(warn, catch_warning, "", classes = "warning")
expect_cnd_trace(base::message, catch_message, "", classes = "message")
expect_cnd_trace(base::message, catch_message, cnd("message"), classes = "message")
expect_cnd_trace(inform, catch_message, "", classes = "message")
expect_cnd_trace(base::signalCondition, catch_cnd, cnd("foo"), classes = "condition")
})
test_that("signal context is detected", {
get_signal_info <- function(cnd) {
nframe <- sys.nframe() - 1
out <- signal_context_info(nframe)
info <- list(out[[1]], sys.call(out[[2]]))
invokeRestart("out", info)
}
signal_info <- function(class, signaller, arg) {
f <- function() signaller(arg)
hnd <- set_names(list(get_signal_info), class)
inject(
withRestarts(
out = identity,
withCallingHandlers(!!!hnd, f())
)
)
}
expect_equal(signal_info("error", base::stop, ""), list("stop_message", quote(f())))
expect_equal(signal_info("error", base::stop, cnd("error")), list("stop_condition", quote(f())))
expect_equal(signal_info("error", function(msg) errorcall(NULL, msg), ""), list("stop_native", quote(errorcall(NULL, msg))))
# No longer works since we switched to signalCondition approach
# expect_equal(signal_info(abort, "")[[1]], "stop_rlang")
expect_equal(signal_info("warning", base::warning, ""), list("warning_message", quote(f())))
expect_equal(signal_info("warning", base::warning, cnd("warning")), list("warning_condition", quote(f())))
expect_equal(signal_info("warning", function(msg) warningcall(NULL, msg), ""), list("warning_native", quote(warningcall(NULL, msg))))
expect_equal(signal_info("warning", warn, "")[[1]], "warning_rlang")
expect_equal(signal_info("message", base::message, ""), list("message", quote(f())))
expect_equal(signal_info("message", base::message, cnd("message")), list("message", quote(f())))
expect_equal(signal_info("message", inform, "")[[1]], "message_rlang")
expect_equal(signal_info("condition", base::signalCondition, cnd("foo")), list("condition", quote(f())))
# Warnings won't be promoted if `condition` is handled. We need to
# handle `error` instead.
signal_info_error <- function(signaller, arg) {
f <- function() signaller(arg)
withRestarts(
out = identity,
withCallingHandlers(error = get_signal_info, f())
)
}
expr <- quote(with_options(warn = 2, signal_info_error(base::warning, "")))
expect_equal(eval_top(expr), list("warning_promoted", quote(f())))
})
test_that("cnd_entrace() skips capture context", {
capture <- function(expr) {
env <- environment()
withCallingHandlers(
expr,
error = function(err) {
err <- cnd_entrace(err)
return_from(env, err)
}
)
}
foo <- function() bar()
bar <- function() stop("foobar")
local_options(rlang_trace_top_env = current_env())
err <- capture(foo())
last <- err$trace$call[[4]]
expect_match(deparse(last), "bar")
})
test_that("rlang and base errors are properly entraced", {
skip_if_stale_backtrace()
base <- run_script(test_path("fixtures", "error-entrace.R"))
rlang <- run_script(
test_path("fixtures", "error-entrace.R"),
envvars = "rlang_error_kind=rlang"
)
expect_snapshot({
cat_line(base)
cat_line(rlang)
})
})
test_that("entrace() preserves exit status in non-interactive sessions (#1052, rstudio/bookdown#920)", {
# Probably because of <https://github.com/wch/r-source/commit/3055aa86>
skip_if(getRversion() < "3.3")
# This also tests for empty backtraces
out <- Rscript(shQuote(c("--vanilla", "-e", 'options(error = rlang::entrace); stop("An error")')))
expect_false(out$status == 0L)
code <- '{
options(error = rlang::entrace)
f <- function() g()
g <- function() h()
h <- function() stop("An error")
f()
}'
out <- Rscript(shQuote(c("--vanilla", "-e", code)))
expect_false(out$status == 0L)
})
test_that("entrace() doesn't embed backtraces twice", {
skip_if_stale_backtrace()
code <- "withCallingHandlers(error = rlang::entrace, rlang::abort('foo'))"
out <- Rscript(shQuote(c("--vanilla", "-e", code)))$out
expect_equal(sum(grepl("^Backtrace", out)), 1)
})
test_that("`options(error = entrace)` strips error prefix", {
code <- '
{
options(error = rlang::entrace)
f <- function() g()
g <- function() h()
h <- function() 1 + ""
f()
last_error()
}'
out <- Rscript(shQuote(c("--vanilla", "-e", code)))
expect_false(out$status == 0L)
})
test_that("can supply handler environment as `bottom`", {
local_options(
rlang_trace_format_srcrefs = FALSE,
rlang_trace_top_env = current_env()
)
f <- function() g()
g <- function() h()
h <- function() identity(1 + "")
err <- catch_cnd(
withCallingHandlers(
error = function(...) rlang::entrace(..., bottom = environment()),
f()
),
"error"
)
expect_snapshot(print(err))
})
test_that("can set `entrace()` as a global handler", {
skip_if_not_installed("base", "4.0.0")
expect_snapshot_output(run('{
suppressMessages(testthat::local_reproducible_output())
rlang::global_entrace()
f <- function() g()
g <- function() h()
h <- function() 1 + ""
f()
}'))
# Indirected case for developers of rlang
expect_snapshot_output(run('{
suppressMessages(testthat::local_reproducible_output())
globalCallingHandlers(error = function(...) rlang::entrace(..., bottom = environment()))
f <- function() g()
g <- function() h()
h <- function() 1 + ""
f()
}'))
expect_snapshot_output(run('{
suppressMessages(testthat::local_reproducible_output())
rlang::global_entrace()
f <- function() { warning("foo"); message("FOO"); g() }
g <- function() { warning("bar", immediate. = TRUE); h() }
h <- function() message("baz")
f()
writeLines("> rlang::last_warnings()")
print(rlang::last_warnings())
writeLines("\\n> rlang::last_warnings(2)")
print(rlang::last_warnings(2))
writeLines("\\n> summary(rlang::last_messages())")
summary(rlang::last_messages())
writeLines("\\n> summary(rlang::last_messages(1))")
summary(rlang::last_messages(1))
}'))
})
test_that("can set `entrace()` as a global handler (older R)", {
skip_if(getRversion() >= "4.0", )
expect_snapshot_output(run('{
suppressMessages(testthat::local_reproducible_output())
rlang::global_entrace()
f <- function() g()
g <- function() h()
h <- function() 1 + ""
f()
}'))
})
test_that("errors are saved by `entrace()`", {
out <- tryCatch(
withCallingHandlers(
abort("foo"),
error = entrace
),
error = identity
)
# Remove internal data stored by `last_error()`
err <- last_error()
err$rlang <- NULL
out$rlang <- NULL
expect_equal(err, out)
})
test_that("only the first n warnings are entraced (#1473)", {
suppressWarnings({
local_options(
"rlang:::cnd_frame" = current_env(),
"rlang:::max_entracing" = 3L
)
f <- function() g()
g <- function() h()
h <- function() warning("foo")
try_fetch(
warning = function(cnd) { entrace(cnd); zap() },
for (i in 1:5) f()
)
expect_equal(
map_lgl(last_warnings(), function(x) is_null(x$trace)),
c(FALSE, FALSE, FALSE, TRUE, TRUE)
)
})
})
test_that("warnings are resignalled", {
expect_no_warning(
cnd <- catch_cnd(withCallingHandlers(
warning = entrace,
warning("foo")
))
)
expect_s3_class(cnd, "rlang_warning")
expect_true(!is_null(cnd$trace))
})
test_that("can call `global_entrace()` in knitted documents", {
local_options(
rlang_backtrace_on_error_report = peek_option("rlang_backtrace_on_error_report"),
rlang_backtrace_on_warning_report = peek_option("rlang_backtrace_on_warning_report")
)
skip_if_not_installed("knitr")
skip_if_not_installed("rmarkdown")
skip_if(!rmarkdown::pandoc_available())
entrace_lines <- render_md("test-entrace.Rmd", env = current_env())
expect_snapshot({
cat_line(entrace_lines)
})
})
test_that("can't set backtrace-on-warning to reminder", {
local_options(rlang_backtrace_on_warning_report = "reminder")
expect_snapshot({
peek_backtrace_on_warning_report()
})
expect_equal(
peek_option("rlang_backtrace_on_warning_report"),
"none"
)
})
test_that("warnings converted to errors are not resignalled by `global_entrace()`", {
skip_if_not_installed("base", "3.6.0")
local_options(warn = 2)
out <- withCallingHandlers(
warning = entrace,
tryCatch(error = function(...) "ok", warning("foo"))
)
expect_equal(out, "ok")
})
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.