causeError <- function(full) {
  A <- function() {
    stop("foo")
  }

  B <- function() {
    A()
  }

  C <- reactive({
    B()
  })

  res <- try({
      captureStackTraces({
        isolate({
          renderTable({
            C()
          }, server = FALSE)()
        })
      })
    },
    silent = TRUE)
  cond <- attr(res, "condition", exact = TRUE)

  suppressMessages(df <- extractStackTrace(conditionStackTrace(cond), full = full))
  df$loc <- cleanLocs(df$loc)
  # Compensate for this test being called from different call sites;
  # whack the top n frames off using the `num` frame column
  df <- df[df$num >= sys.nframe(), ]
  df$num <- df$num - sys.nframe()
  df
}

test_that("integration tests", {
  if (shiny_otel_tracer()$is_enabled()) {
    announce_snapshot_file(name = "stacks.md")

    skip("Skipping stack trace tests when OpenTelemetry is already enabled")
  }

  # The expected call stack can be changed by other packages (namely, promises).
  # 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
  # to release a version of promises that will not break this test and cause
  # problems on CRAN.
  skip_on_cran()

  df_integration_slim <- causeError(full = FALSE)
  # dumpTests(df_integration_slim)

  expect_snapshot(df_integration_slim)

  df_integration_full <- causeError(full = TRUE)

  expect_snapshot(df_integration_full)
  # dumpTests(df_integration_full)
})

test_that("shiny.error", {
  caught <- NULL
  op <- options(shiny.error = function() { caught <<- TRUE })
  on.exit(options(op))

  # Regular errors should be intercepted by shiny.error
  try(shiny:::shinyCallingHandlers(stop("boom")), silent = TRUE)
  expect_true(caught)

  caught <- NULL

  # Validation errors shouldn't be intercepted by shiny.error

  try(shiny:::shinyCallingHandlers(validate(need(NULL, FALSE))), silent = TRUE)
  expect_null(caught)

  er <- eventReactive(NULL, { "Hello" })
  try(shiny:::shinyCallingHandlers(isolate(er())), silent = TRUE)
  expect_null(caught)
  try(shiny:::shinyCallingHandlers(isolate(er())), silent = TRUE)
  expect_null(caught)
})

test_that("chained silent errors aren't intercepted (tidyverse/dplyr#5552)", {
  withr::local_options(
    shiny.error = function() caught <<- TRUE
  )

  f <- function() {
    withCallingHandlers(
      validate(need(NULL, FALSE)),
      error = function(cnd) {
        rlang::abort("Child error.", parent = cnd)
      }
    )
  }
  caught <- NULL
  try(shiny:::shinyCallingHandlers(f()), silent = TRUE)
  expect_null(caught)

  caught <- NULL
  try(hybrid_chain(f()), silent = TRUE)
  expect_null(caught)
})

test_that("validation error logging", {
  caught <- NULL

  # Given an error-throwing exception expr, execute it
  # using withLogErrors, and superassign the warning that
  # results (the error log is emitted using warning())
  # into the parent variable `caught`
  captureErrorLog <- function(expr) {
    tryCatch(
      tryCatch(
        shiny::withLogErrors(expr),
        warning = function(cond) {
          caught <<- cond
        }
      ),
      error = function(e) {
      }
    )
  }

  captureErrorLog(validate("boom"))
  expect_null(caught)

  caught <- NULL
  captureErrorLog(stop("boom"))
  expect_true(!is.null(caught))
})

test_that("observeEvent is not overly stripped (#4162)", {
  caught <- NULL
  ..stacktraceoff..(
    ..stacktracefloor..({
      observeEvent(1, {
        tryCatch(
          captureStackTraces(stop("boom")),
          error = function(cond) {
            caught <<- cond
          }
        )
      })
      flushReact()
    })
  )
  st_str <- capture.output(printStackTrace(caught), type = "message")
  expect_match(st_str, "observeEvent\\(1\\)", all = FALSE)

  # Now same thing, but deep stack trace version

  A__ <- function() {
    promises::then(promises::promise_resolve(TRUE), ~{
      stop("boom")
    })
  }

  B__ <- function() {
    promises::then(promises::promise_resolve(TRUE), ~{
      A__()
    })
  }

  caught <- NULL
  ..stacktraceoff..(
    ..stacktracefloor..({
      observeEvent(1, {
        captureStackTraces(promises::catch(B__(), ~{
          caught <<- .
        }))
      })
      flushReact()
      wait_for_it()
    })
  )
  st_str <- capture.output(printStackTrace(caught), type = "message")
  # cat(st_str, sep = "\n")
  expect_match(st_str, "A__", all = FALSE)
  expect_match(st_str, "B__", all = FALSE)
})

test_that("renderPlot stack trace fences hide internal rendering pipeline (#4357)", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  userFunc <- function() {
    stop("test error in renderPlot")
  }

  df <- captureFilteredRenderTrace(renderPlot({ userFunc() }))

  expect_true("userFunc" %in% df$call)

  # Internal rendering pipeline frames should NOT appear in the filtered
  # stack trace. These are Shiny internals between the stack trace fences
  # that currently leak through due to missing fences.
  internal_render_frames <- c(
    "drawPlot",
    "drawReactive",
    "renderFunc",
    "startPNG"
  )

  leaked <- df$call[df$call %in% internal_render_frames]
  expect_length(leaked, 0)
})

test_that("renderPrint stack trace fences hide internal rendering pipeline (#4357)", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  userFunc <- function() {
    stop("test error in renderPrint")
  }

  df <- captureFilteredRenderTrace(renderPrint({ userFunc() }))

  expect_true("userFunc" %in% df$call)

  internal_render_frames <- c("renderFunc")
  leaked <- df$call[df$call %in% internal_render_frames]
  expect_length(leaked, 0)
})

test_that("renderText stack trace fences hide internal rendering pipeline (#4357)", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  userFunc <- function() {
    stop("test error in renderText")
  }

  df <- captureFilteredRenderTrace(renderText({ userFunc() }), needs_session = FALSE)

  expect_true("userFunc" %in% df$call)

  internal_render_frames <- c("renderFunc")
  leaked <- df$call[df$call %in% internal_render_frames]
  expect_length(leaked, 0)
})

test_that("renderUI stack trace fences hide internal rendering pipeline (#4357)", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  userFunc <- function() {
    stop("test error in renderUI")
  }

  df <- captureFilteredRenderTrace(renderUI({ userFunc() }), needs_session = FALSE)

  expect_true("userFunc" %in% df$call)

  internal_render_frames <- c("renderFunc")
  leaked <- df$call[df$call %in% internal_render_frames]
  expect_length(leaked, 0)
})

test_that("renderTable stack trace fences hide internal rendering pipeline (#4357)", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  userFunc <- function() {
    stop("test error in renderTable")
  }

  df <- captureFilteredRenderTrace(
    renderTable({ userFunc() }, server = FALSE),
    needs_session = FALSE
  )

  expect_true("userFunc" %in% df$call)

  internal_render_frames <- c("renderFunc")
  leaked <- df$call[df$call %in% internal_render_frames]
  expect_length(leaked, 0)
})

test_that("renderImage stack trace fences hide internal rendering pipeline (#4357)", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  userFunc <- function() {
    stop("test error in renderImage")
  }

  df <- captureFilteredRenderTrace(
    renderImage({ userFunc() }, deleteFile = FALSE),
    needs_session = FALSE
  )

  expect_true("userFunc" %in% df$call)

  internal_render_frames <- c("renderFunc")
  leaked <- df$call[df$call %in% internal_render_frames]
  expect_length(leaked, 0)
})

test_that("legacyRenderDataTable stack trace fences hide internal rendering pipeline (#4357)", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  userFunc <- function() {
    stop("test error in renderDataTable")
  }

  df <- captureFilteredRenderTrace(
    legacyRenderDataTable({ userFunc() })
  )

  expect_true("userFunc" %in% df$call)

  internal_render_frames <- c("renderFunc")
  leaked <- df$call[df$call %in% internal_render_frames]
  expect_length(leaked, 0)
})

test_that("markRenderFunction preserves user frames outside reactive domain", {
  skip_on_cran()

  skip_if_shiny_otel_tracer_is_enabled()

  # htmlwidgets-style: exprToFunction + markRenderFunction, no ..stacktraceon..
  renderWidgetLike <- function(expr, env = parent.frame(), quoted = FALSE) {
    if (!quoted) expr <- substitute(expr)
    func <- exprToFunction(expr, env, TRUE)
    renderFunc <- function() { func() }
    markRenderFunction(textOutput, renderFunc)
  }

  userFunc <- function() stop("boom")
  render_fn <- renderWidgetLike({ userFunc() })

  res <- try(captureStackTraces({ render_fn() }), silent = TRUE)
  cond <- attr(res, "condition", exact = TRUE)
  df <- extractStackTrace(conditionStackTrace(cond), full = FALSE)

  expect_true("userFunc" %in% df$call)
})

