Skip to content

Commit 29a3042

Browse files
authored
Better handling for user-defined error conditions in debug mode (#116)
* rename pruned_errors to prune_errors * rename pruned to prune * 🔨 handle stop errors * 🚨 add test for stop errors
1 parent 0845184 commit 29a3042

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

R/utils.R

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -743,9 +743,20 @@ getStackTrace <- function(expr, debug = FALSE, prune_errors = TRUE) {
743743
error = function(e) {
744744
if (is.null(attr(e, "stack.trace", exact = TRUE))) {
745745
calls <- sys.calls()
746+
reverseStack <- rev(calls)
746747
attr(e, "stack.trace") <- calls
747-
errorCall <- e$call[[1]]
748-
748+
749+
if (!is.null(e$call[[1]]))
750+
errorCall <- e$call[[1]]
751+
else {
752+
# attempt to capture the error or warning if thrown by
753+
# simpleError or simpleWarning (which may arise for user-defined errors)
754+
#
755+
# the first matching call in the reversed stack will always be
756+
# getStackTrace, so we select the second match instead
757+
errorCall <- reverseStack[grepl(x=reverseStack, "simpleError|simpleWarning")][[2]]
758+
}
759+
749760
functionsAsList <- lapply(calls, function(completeCall) {
750761
currentCall <- completeCall[[1]]
751762

@@ -760,8 +771,6 @@ getStackTrace <- function(expr, debug = FALSE, prune_errors = TRUE) {
760771

761772
})
762773

763-
reverseStack <- rev(calls)
764-
765774
if (prune_errors) {
766775
# this line should match the last occurrence of the function
767776
# which raised the error within the call stack; prune here
@@ -779,14 +788,18 @@ getStackTrace <- function(expr, debug = FALSE, prune_errors = TRUE) {
779788
# to stop at the correct position.
780789
if (is.function(currentCall[[1]])) {
781790
identical(deparse(errorCall), deparse(currentCall[[1]]))
782-
} else {
791+
} else if (currentCall[[1]] == "stop") {
792+
# handle case where function developer deliberately invokes a stop
793+
# condition and halts function execution
794+
identical(deparse(errorCall), deparse(currentCall))
795+
}
796+
else {
783797
FALSE
784798
}
785799

786800
}
787801
)
788802
)
789-
790803
# the position to stop at is one less than the difference
791804
# between the total number of calls and the index of the
792805
# call throwing the error
@@ -797,12 +810,14 @@ getStackTrace <- function(expr, debug = FALSE, prune_errors = TRUE) {
797810
functionsAsList <- removeHandlers(functionsAsList)
798811
}
799812

813+
# use deparse in case the call throwing the error is a symbol,
814+
# since this cannot be "printed" without deparsing the call
800815
warning(call. = FALSE, immediate. = TRUE, sprintf("Execution error in %s: %s",
801-
functionsAsList[[length(functionsAsList)]],
816+
deparse(functionsAsList[[length(functionsAsList)]]),
802817
conditionMessage(e)))
803818

804819
stack_message <- stackTraceToHTML(functionsAsList,
805-
functionsAsList[[length(functionsAsList)]],
820+
deparse(functionsAsList[[length(functionsAsList)]]),
806821
conditionMessage(e))
807822

808823
assign("stack_message", value=stack_message,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from selenium.webdriver.support.select import Select
2+
3+
app = """
4+
library(dash)
5+
library(dashHtmlComponents)
6+
library(dashCoreComponents)
7+
8+
app <- Dash$new()
9+
10+
app$layout(
11+
htmlDiv(
12+
list(
13+
dccDropdown(options = list(
14+
list(label = "Red", value = "#FF0000"),
15+
list(label = "Throw error", value = "error")
16+
),
17+
id = "input-choice",
18+
value = "error"),
19+
htmlDiv(id="div-choice")
20+
)
21+
)
22+
)
23+
24+
app$callback(output(id = 'div-choice', property = 'children'),
25+
list(input(id = 'input-choice', property = 'value')),
26+
function(choice) {
27+
if (choice == "error") {
28+
stop(simpleError("Throwing an error by request"))
29+
}
30+
if (!is.null(unlist(choice))) {
31+
return(sprintf("Choice was %s", choice))
32+
} else {
33+
return(sprintf("Make a choice"))
34+
}
35+
})
36+
37+
app$run_server(debug=TRUE)
38+
"""
39+
40+
41+
def test_rshs001_handle_stop(dashr):
42+
dashr.start_server(app)
43+
dashr.wait_for_text_to_equal(
44+
".dash-fe-error__title",
45+
"Callback error updating div-choice.children"
46+
)

0 commit comments

Comments
 (0)