mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-09 15:08:04 -05:00
232 lines
5.9 KiB
R
232 lines
5.9 KiB
R
skip_on_cran()
|
|
skip_if_not_installed("otelsdk")
|
|
|
|
create_mock_session <- function() {
|
|
session <- MockShinySession$new()
|
|
session$token <- "test-session-token"
|
|
session
|
|
}
|
|
|
|
expect_session_warning <- function(session, warning) {
|
|
testthat::expect_warning(
|
|
capture.output(
|
|
type = "message",
|
|
{
|
|
session$flushReact()
|
|
}
|
|
),
|
|
warning
|
|
)
|
|
}
|
|
|
|
exception_trace_events <- function(traces) {
|
|
unlist(lapply(traces, function(trace) {
|
|
if (is.null(trace$events)) return(list())
|
|
events <- Filter(function(event) {
|
|
!is.null(event$attributes) &&
|
|
!is.null(event$attributes[["exception.message"]])
|
|
}, trace$events)
|
|
events
|
|
}), recursive = FALSE)
|
|
}
|
|
|
|
test_server_with_otel_error <- function(session, server, expr, sanitize = FALSE, args = list()) {
|
|
stopifnot(inherits(session, "MockShinySession"))
|
|
stopifnot(is.function(server))
|
|
|
|
traces <- with_shiny_otel_record({ 42 })$traces
|
|
expect_length(traces, 0)
|
|
|
|
withr::with_options(
|
|
list(
|
|
shiny.otel.bind = "all",
|
|
shiny.otel.sanitize.errors = sanitize
|
|
),
|
|
{
|
|
info <- with_shiny_otel_record({
|
|
# rlang quosure magic to capture and pass through `expr`
|
|
testServer(server, {{ expr }}, args = args, session = session)
|
|
})
|
|
}
|
|
)
|
|
|
|
info$traces
|
|
}
|
|
|
|
|
|
test_that("mark_otel_exception_as_seen() returns modified condition", {
|
|
cnd <- simpleError("test error")
|
|
result <- mark_otel_exception_as_seen(cnd)
|
|
|
|
expect_true(inherits(result, "error"))
|
|
expect_true(inherits(result, "condition"))
|
|
expect_equal(conditionMessage(result), "test error")
|
|
expect_true(isTRUE(result$.shiny_otel_exception))
|
|
})
|
|
|
|
test_that("mark_otel_exception_as_seen() marks error as seen", {
|
|
cnd <- simpleError("test error")
|
|
expect_false(has_seen_otel_exception(cnd))
|
|
|
|
cnd <- mark_otel_exception_as_seen(cnd)
|
|
expect_true(has_seen_otel_exception(cnd))
|
|
})
|
|
|
|
|
|
test_that("set_otel_exception_status() records sanitized errors by default", {
|
|
server <- function(input, output, session) {
|
|
r1 <- reactive(label = "r1", {
|
|
stop("test error in r1")
|
|
})
|
|
|
|
r2 <- reactive(label = "r2", {
|
|
r1()
|
|
})
|
|
|
|
observe(label = "obs", {
|
|
r2()
|
|
})
|
|
}
|
|
|
|
session <- create_mock_session()
|
|
traces <- test_server_with_otel_error(
|
|
sanitize = NULL,
|
|
session,
|
|
server,
|
|
{
|
|
# Expect an error to be thrown as warning
|
|
expect_session_warning(session, "test error in r1")
|
|
}
|
|
)
|
|
|
|
# IDK why, I don't have time to debug
|
|
skip_if(length(traces) > 3, "Too many traces collected; otelsdk traces are polluted. Run in single test file only: testthat::test_file(testthat::test_path('test-otel-error.R'))`")
|
|
|
|
# Find traces with exception events (should only be one)
|
|
exception_events <- exception_trace_events(traces)
|
|
|
|
# Exception should be recorded only once at the original point of failure
|
|
expect_equal(length(exception_events), 1)
|
|
expect_match(
|
|
exception_events[[1]]$attributes[["exception.message"]],
|
|
"Check your logs or contact the app author for clarification."
|
|
)
|
|
})
|
|
|
|
test_that("set_otel_exception_status() records exception only once in reactive context", {
|
|
server <- function(input, output, session) {
|
|
r1 <- reactive(label = "r1", {
|
|
stop("test error in r1")
|
|
})
|
|
|
|
r2 <- reactive(label = "r2", {
|
|
r1()
|
|
})
|
|
|
|
observe(label = "obs", {
|
|
r2()
|
|
})
|
|
}
|
|
|
|
session <- create_mock_session()
|
|
traces <- test_server_with_otel_error(session, server, {
|
|
# Expect an error to be thrown as warning
|
|
expect_session_warning(session, "test error in r1")
|
|
})
|
|
|
|
# Find traces with error status
|
|
for (trace in traces) {
|
|
expect_equal(trace$status, "error")
|
|
}
|
|
|
|
# Find traces with exception events (should only be one)
|
|
exception_events <- exception_trace_events(traces)
|
|
|
|
# Exception should be recorded only once at the original point of failure
|
|
expect_equal(length(exception_events), 1)
|
|
expect_match(
|
|
exception_events[[1]]$attributes[["exception.message"]],
|
|
"test error in r1"
|
|
)
|
|
})
|
|
|
|
test_that("set_otel_exception_status() records exception for multiple independent errors", {
|
|
server <- function(input, output, session) {
|
|
r1 <- reactive(label = "r1", {
|
|
stop("error in r1")
|
|
})
|
|
|
|
r2 <- reactive(label = "r2", {
|
|
stop("error in r2")
|
|
})
|
|
|
|
observe(label = "obs1", {
|
|
r1()
|
|
})
|
|
|
|
observe(label = "obs2", {
|
|
r2()
|
|
})
|
|
}
|
|
|
|
session <- create_mock_session()
|
|
traces <- test_server_with_otel_error(session, server, {
|
|
# Both observers should error
|
|
expect_session_warning(session, "error in r1")
|
|
})
|
|
|
|
# Find traces with exception events
|
|
exception_events <- exception_trace_events(traces)
|
|
|
|
# Each unique error should be recorded once
|
|
expect_gte(length(exception_events), 1)
|
|
})
|
|
|
|
test_that("set_otel_exception_status() does not record shiny.custom.error", {
|
|
server <- function(input, output, session) {
|
|
r <- reactive(label = "r", {
|
|
cnd <- simpleError("custom error")
|
|
class(cnd) <- c("shiny.custom.error", class(cnd))
|
|
stop(cnd)
|
|
})
|
|
|
|
observe(label = "obs", {
|
|
r()
|
|
})
|
|
}
|
|
|
|
session <- create_mock_session()
|
|
traces <- test_server_with_otel_error(session, server, {
|
|
expect_session_warning(session, "custom error")
|
|
})
|
|
|
|
# Find traces with error status (should be none for custom errors)
|
|
for (trace in traces) {
|
|
expect_true(trace$status != "error")
|
|
}
|
|
})
|
|
|
|
test_that("set_otel_exception_status() does not record shiny.silent.error", {
|
|
server <- function(input, output, session) {
|
|
r <- reactive(label = "r", {
|
|
cnd <- simpleError("silent error")
|
|
class(cnd) <- c("shiny.silent.error", class(cnd))
|
|
stop(cnd)
|
|
})
|
|
|
|
observe(label = "obs", {
|
|
r()
|
|
})
|
|
}
|
|
|
|
session <- create_mock_session()
|
|
traces <- test_server_with_otel_error(session, server, {
|
|
expect_no_error(session$flushReact())
|
|
})
|
|
|
|
# Find traces with error status (should be none for silent errors)
|
|
for (trace in traces) {
|
|
expect_true(trace$status != "error")
|
|
}
|
|
})
|