diff --git a/NEWS.md b/NEWS.md index 8e6be6d72..1a7b33964 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,6 +52,8 @@ * Updated jQuery UI from 1.13.2 to 1.14.1. (#4175) +* Shiny better truncates stack traces when errors occur (@olivroy #4186). + ## Bug fixes * The Shiny Client Console (enabled with `shiny::devmode()`) no longer displays duplicate warning or error message. (#4177) diff --git a/R/conditions.R b/R/conditions.R index 2b3ae0161..509def2c8 100644 --- a/R/conditions.R +++ b/R/conditions.R @@ -527,7 +527,7 @@ dropTrivialFrames <- function(callnames) { # the calls--they don't add any helpful information. But only remove # the last *contiguous* block of them, and then, only if they are the # last thing in the calls list. - hideable <- callnames %in% c(".handleSimpleError", "h", "base$wrapOnFulfilled") + hideable <- callnames %in% c(".handleSimpleError", "h", "base$wrapOnFulfilled", "signal_abort", "handlers[[1L]]", "signalCondition", "rlang::abort", "abort", "cli::cli_abort", "cli_abort") # What's the last that *didn't* match stop/.handleSimpleError/h? lastGoodCall <- max(which(!hideable)) toRemove <- length(callnames) - lastGoodCall diff --git a/tests/testthat/_snaps/stacks.md b/tests/testthat/_snaps/stacks.md index 46741f909..723f389c6 100644 --- a/tests/testthat/_snaps/stacks.md +++ b/tests/testthat/_snaps/stacks.md @@ -22,7 +22,7 @@ num call loc 1 67 h 2 66 .handleSimpleError - 3 65 stop + 3 65 f 4 64 A [test-stacks.R#3] 5 63 B [test-stacks.R#7] 6 62 [test-stacks.R#11] @@ -88,3 +88,100 @@ 66 2 tryCatch 67 1 try +--- + + Code + df + Output + num call loc + 1 69 + 2 68 signalCondition + 3 67 signal_abort + 4 66 rlang::abort + 5 65 f + 6 64 A [test-stacks.R#3] + 7 63 B [test-stacks.R#7] + 8 62 [test-stacks.R#11] + 9 42 C + 10 41 renderTable [test-stacks.R#18] + 11 40 func + 12 39 force + 13 38 withVisible + 14 37 withCallingHandlers + +--- + + Code + df + Output + num call loc + 1 69 + 2 68 signalCondition + 3 67 signal_abort + 4 66 rlang::abort + 5 65 f + 6 64 A [test-stacks.R#3] + 7 63 B [test-stacks.R#7] + 8 62 [test-stacks.R#11] + 9 61 ..stacktraceon.. + 10 60 .func + 11 59 withVisible + 12 58 withCallingHandlers + 13 57 contextFunc + 14 56 env$runWith + 15 55 withCallingHandlers + 16 54 domain$wrapSync + 17 53 promises::with_promise_domain + 18 52 captureStackTraces + 19 51 force + 20 50 domain$wrapSync + 21 49 promises::with_promise_domain + 22 48 withReactiveDomain + 23 47 domain$wrapSync + 24 46 promises::with_promise_domain + 25 45 ctx$run + 26 44 self$.updateValue + 27 43 ..stacktraceoff.. + 28 42 C + 29 41 renderTable [test-stacks.R#18] + 30 40 func + 31 39 force + 32 38 withVisible + 33 37 withCallingHandlers + 34 36 domain$wrapSync + 35 35 promises::with_promise_domain + 36 34 captureStackTraces + 37 33 doTryCatch + 38 32 tryCatchOne + 39 31 tryCatchList + 40 30 tryCatch + 41 29 do + 42 28 hybrid_chain + 43 27 renderFunc + 44 26 renderTable({ C() }, server = FALSE) + 45 25 ..stacktraceon.. [test-stacks.R#17] + 46 24 contextFunc + 47 23 env$runWith + 48 22 withCallingHandlers + 49 21 domain$wrapSync + 50 20 promises::with_promise_domain + 51 19 captureStackTraces + 52 18 force + 53 17 domain$wrapSync + 54 16 promises::with_promise_domain + 55 15 withReactiveDomain + 56 14 domain$wrapSync + 57 13 promises::with_promise_domain + 58 12 ctx$run + 59 11 ..stacktraceoff.. + 60 10 isolate + 61 9 withCallingHandlers [test-stacks.R#16] + 62 8 domain$wrapSync + 63 7 promises::with_promise_domain + 64 6 captureStackTraces + 65 5 doTryCatch [test-stacks.R#15] + 66 4 tryCatchOne + 67 3 tryCatchList + 68 2 tryCatch + 69 1 try + diff --git a/tests/testthat/test-stacks.R b/tests/testthat/test-stacks.R index b34048217..209116156 100644 --- a/tests/testthat/test-stacks.R +++ b/tests/testthat/test-stacks.R @@ -1,6 +1,6 @@ -causeError <- function(full) { +causeError <- function(full, f = stop) { A <- function() { - stop("foo") + f("foo") } B <- function() { @@ -63,7 +63,7 @@ extractStackTrace <- function(calls, # the calls--they don't add any helpful information. But only remove # the last *contiguous* block of them, and then, only if they are the # last thing in the calls list. - hideable <- callnames %in% c("stop", ".handleSimpleError", "h") + hideable <- callnames %in% c("stop", ".handleSimpleError", "h", "base$wrapOnFulfilled", "signal_abort", "handlers[[1L]]", "signalCondition", "abort", "rlang::abort", "cli::cli_abort", "cli_abort", "f") # What's the last that *didn't* match stop/.handleSimpleError/h? lastGoodCall <- max(which(!hideable)) toRemove <- length(calls) - lastGoodCall @@ -122,20 +122,33 @@ test_that("integration tests", { # If promises changes its internals, it can break this test on CRAN. Because # CRAN package releases are generally not synchronized (that is, promises and # shiny can't be updated at the same time, unless there is manual intervention - # from CRAN maintaineres), these specific test expectations make it impossible + # from CRAN maintainers), these specific test expectations make it impossible # to release a version of promises that will not break this test and cause # problems on CRAN. skip_on_cran() - df <- causeError(full = FALSE) + df <- causeError(full = FALSE, f = stop) # dumpTests(df) expect_snapshot(df) - df <- causeError(full = TRUE) + df <- causeError(full = TRUE, f = stop) expect_snapshot(df) # dumpTests(df) + + # Error with cli + df <- causeError(full = FALSE, f = cli::cli_abort) + # dumpTests(df) + + expect_snapshot(df) + + df <- causeError(full = TRUE, f = cli::cli_abort) + + expect_snapshot(df) + # dumpTests(df) + + }) test_that("shiny.error", {