tests/testthat/test-cnd-signal.R

test_that("cnd_signal() creates muffle restarts", {
  withCallingHandlers(cnd_signal(cnd("foo")),
    foo = function(c) {
      expect_true(rst_exists("rlang_muffle"))
    }
  )
})

test_that("signallers support character vectors as `message` parameter", {
  expect_message(inform(c("foo", "*" = "bar")), "foo\n* bar", fixed = TRUE)
  expect_warning(warn(c("foo", "*" = "bar")), "foo\n* bar", fixed = TRUE)
  expect_error(abort(c("foo", "*" = "bar")), "foo\n* bar", fixed = TRUE)
  expect_condition(signal(c("foo", "*" = "bar"), "quux"), "quux", regex = "foo\n\\* bar")
})

test_that("cnd_signal() and signal() returns NULL invisibly", {
  expect_identical(withVisible(cnd_signal(cnd("foo"))), withVisible(invisible(NULL)))
  expect_identical(withVisible(signal("", "foo")), withVisible(invisible(NULL)))
})

test_that("signal() accepts character vectors of classes (#195)", {
  expect <- function(cnd) {
    expect_identical(class(cnd), c("foo", "bar", "condition"))
  }
  withCallingHandlers(signal("", c("foo", "bar")), foo = expect)
})

test_that("can pass condition metadata", {
  msg <- expect_message(inform("type", foo = "bar"))
  expect_identical(msg$foo, "bar")

  wng <- expect_warning2(warn("type", foo = "bar"))
  expect_identical(wng$foo, "bar")

  err <- expect_error(abort("type", foo = "bar"))
  expect_identical(err$foo, "bar")
})

test_that("can signal and catch interrupts", {
  expect_s3_class(catch_cnd(interrupt()), "interrupt")
})

test_that("can signal interrupts with cnd_signal()", {
  intr <- catch_cnd(interrupt())
  tryCatch(cnd_signal(intr),
    condition = function(cnd) expect_s3_class(cnd, "interrupt")
  )
})

test_that("conditions have correct subclasses", {
  expect_true(inherits_all(expect_condition(signal("", "foo")), c("foo", "condition", "condition")))
  expect_true(inherits_all(expect_message(inform("", "foo")), c("foo", "message", "condition")))
  expect_true(inherits_all(expect_warning2(warn("", "foo")), c("foo", "warning", "condition")))
  expect_true(inherits_all(expect_error(abort("", "foo")), c("foo", "rlang_error", "error", "condition")))
})

test_that("cnd_signal() creates a backtrace if needed", {
  local_options(
    rlang_trace_top_env = current_env(),
    rlang_trace_format_srcrefs = FALSE
  )

  err <- error_cnd("rlang_error_foobar", trace = NULL)
  f <- function() g()
  g <- function() h()
  h <- function() cnd_signal(err)

  err <- catch_cnd(f())
  expect_snapshot(print(err))
})

test_that("`inform()` does not append newlines to message", {
  expect_equal(
    expect_message(inform("foo"))$message,
    "foo"
  )
  expect_equal(
    conditionMessage(expect_message(inform("foo"))),
    "foo"
  )
})

test_that("condition signallers can be called without arguments", {
  # For pragmatic reasons we don't require a class because we now use
  # `inform()` in places where `cat()` would be more appropriate
  expect_message(inform(), "", fixed = TRUE)
  expect_warning(warn(class = "foo"), "", fixed = TRUE)
  expect_error(abort(class = "foo"), "", fixed = TRUE, class = "foo")
})

test_that("`inform()` returns invisibly", {
  expect_message(expect_invisible(inform("foo")))
})

test_that("warn() respects frequency", {
  local_options(rlib_warning_verbosity = "default")

  expect_warning(
    warn("foo", .frequency = "always", .frequency_id = "warn_always"),
    "^foo$"
  )
  expect_warning(
    warn("foo", .frequency = "always", .frequency_id = "warn_always"),
    "^foo$"
  )

  expect_warning(
    warn("foo", .frequency = "once", .frequency_id = "warn_once"),
    "^foo\n.*warning is displayed once per session"
  )
  expect_no_warning(
    warn("foo", .frequency = "once", .frequency_id = "warn_once")
  )

  expect_warning(
    warn("foo", .frequency = "regularly", .frequency_id = "warn_regularly"),
    "foo\n.*warning is displayed once every 8 hours"
  )
  expect_no_warning(
    warn("foo", .frequency = "regularly", .frequency_id = "warn_regularly")
  )
})

test_that("inform() respects frequency", {
  local_options(rlib_message_verbosity = "default")

  expect_message(
    inform("foo", .frequency = "always", .frequency_id = "inform_always"),
    "^foo$"
  )
  expect_message(
    inform("foo", .frequency = "always", .frequency_id = "inform_always"),
    "^foo$"
  )

  expect_message(
    inform("foo", .frequency = "once", .frequency_id = "inform_once"),
    "^foo.*message is displayed once per session"
  )
  expect_no_message(
    inform("foo", .frequency = "once", .frequency_id = "inform_once")
  )

  expect_message(
    inform("foo", .frequency = "regularly", .frequency_id = "inform_regularly"),
    "foo\n.*message is displayed once every 8 hours"
  )
  expect_no_message(
    inform("foo", .frequency = "regularly", .frequency_id = "inform_regularly")
  )
})

test_that("warn() and inform() use different periodicity environments", {
  local_options(
    rlib_message_verbosity = "default",
    rlib_warning_verbosity = "default"
  )

  expect_message(
    inform("foo", .frequency = "once", .frequency_id = "warn_inform_different_envs"),
    "foo"
  )
  expect_warning(
    warn("foo", .frequency = "once", .frequency_id = "warn_inform_different_envs"),
    "foo"
  )
})

test_that("periodic messages can be forced", {
  local_options(rlib_warning_verbosity = "verbose")
  expect_warning(
    warn("foo", .frequency = "once", .frequency_id = "warn_forced"),
    "foo"
  )
  expect_warning(
    warn("foo", .frequency = "once", .frequency_id = "warn_forced"),
    "foo"
  )
})

test_that("messages can be silenced", {
  local_options(
    rlib_message_verbosity = "quiet",
    rlib_warning_verbosity = "quiet"
  )
  expect_message(inform("foo"), NA)
  expect_warning(warn("foo"), NA)
})

test_that("`.frequency_id` is mandatory", {
  expect_error(warn("foo", .frequency = "once"), "frequency_id")
})

test_that("cnd_signal() is a no-op with `NULL`", {
  expect_null(catch_cnd(cnd_signal(NULL)))
})

test_that("`inform()` behaves consistently in interactive and non-interactive sessions (#1037)", {
  # Default behaviour
  out1 <- Rscript(shQuote(c("--vanilla", "-e", "rlang::inform('foo')")))
  out2 <- Rscript(shQuote(c("--vanilla", "-e", "rlang::with_interactive(rlang::inform('foo'))")))
  expect_equal(out1$out, "foo")
  expect_equal(out1$out, out2$out)

  # Sinked behaviour
  out1 <- Rscript(shQuote(c("--vanilla", "-e", "capture.output(rlang::inform('foo'))")))
  out2 <- Rscript(shQuote(c("--vanilla", "-e", "rlang::with_interactive(capture.output(rlang::inform('foo')))")))
  expect_equal(out1$out, c("foo", "character(0)"))
  expect_equal(out1$out, out2$out)
})

test_that("`inform()` and `warn()` with recurrent footer handle newlines correctly", {
  expect_snapshot({
    inform("foo", .frequency = "regularly", .frequency_id = as.character(runif(1)))
    inform("bar", .frequency = "regularly", .frequency_id = as.character(runif(1)))

    warn("foo", .frequency = "regularly", .frequency_id = as.character(runif(1)))
    warn("bar", .frequency = "regularly", .frequency_id = as.character(runif(1)))
  })
})

test_that("`warning.length` is increased (#1211)", {
  code <- 'rlang::with_interactive(rlang::abort(paste0(strrep("_", 1000), "foo")))'
  out <- Rscript(shQuote(c("--vanilla", "-e", code)))
  expect_true(any(grepl("foo", out$out)))

  code <- 'rlang::with_interactive(rlang::warn(paste0(strrep("_", 1000), "foo")))'
  out <- Rscript(shQuote(c("--vanilla", "-e", code)))
  expect_true(any(grepl("foo", out$out)))

  # Messages are not controlled by `warning.length`
  code <- 'rlang::inform(paste0(strrep("_", 1000), "foo"))'
  out <- Rscript(shQuote(c("--vanilla", "-e", code)))
  expect_true(any(grepl("foo", out$out)))
})

test_that("interrupt() doesn't fail when interrupts are suspended (#1224)", {
  skip_if_not_installed("base", "3.5.0")

  out <- FALSE

  tryCatch(
    interrupt = identity,
    {
      suspendInterrupts({
        tryCatch(
          interrupt = function(x) stop("interrupt!"),
          interrupt()
        )
        out <- TRUE
      })
      # Make sure suspended interrupt is processed
      interrupt()
    }
  )

  expect_true(out)
})

test_that("`frequency` has good error messages", {
  expect_snapshot({
    (expect_error(inform("foo", .frequency = "once", .frequency_id = NULL)))
    (expect_error(warn("foo", .frequency = "once", .frequency_id = 1L)))
  })
})

test_that("can pass `use_cli_format` as condition field", {
  signal_lazy_bullets <- function(catcher, signaller) {
    catch_error(abort(
      c("Header.", i = "Bullet."),
      use_cli_format = TRUE
    ))
  }
  expect_lazy_bullets <- function(cnd) {
    expect_equal(cnd$message, set_names("Header.", ""))
    expect_equal(cnd$body, c(i = "Bullet."))
    expect_true(cnd$use_cli_format)
  }

  expect_lazy_bullets(signal_lazy_bullets(catch_error, abort))
  expect_lazy_bullets(signal_lazy_bullets(catch_warning, warn))
  expect_lazy_bullets(signal_lazy_bullets(catch_message, inform))
})

test_that("signal functions check inputs", {
  expect_snapshot({
    (expect_error(abort(error_cnd("foo"))))
    (expect_error(inform(error_cnd("foo"))))
    (expect_error(warn(class = error_cnd("foo"))))
    (expect_error(abort("foo", call = base::call)))
  })
})

test_that("cnd_signal() sets call", {
  f <- function() {
    cnd_signal(error_cnd(message = "foo", call = current_env()))
  }
  cnd <- catch_cnd(f())
  expect_equal(cnd$call, quote(f()))
})

test_that("can reset verbosity", {
  on.exit(reset_warning_verbosity("test_reset_verbosity"))

  expect_warning(
    warn("foo", .frequency = "once", .frequency_id = "test_reset_verbosity")
  )
  expect_no_warning(
    warn("foo", .frequency = "once", .frequency_id = "test_reset_verbosity")
  )

  reset_warning_verbosity("test_reset_verbosity")

  expect_warning(
    warn("foo", .frequency = "once", .frequency_id = "test_reset_verbosity")
  )
})

test_that("downgraded conditions are not inherited (#1573)", {
  cnd <- catch_cnd(warn("", parent = error_cnd()))
  expect_false(cnd$rlang$inherit)

  cnd <- catch_cnd(inform("", parent = error_cnd()))
  expect_false(cnd$rlang$inherit)

  cnd <- catch_cnd(inform("", parent = warning_cnd()))
  expect_false(cnd$rlang$inherit)

  cnd <- catch_cnd(warn("", parent = error_cnd(), .inherit = TRUE))
  expect_true(cnd$rlang$inherit)

  cnd <- catch_cnd(inform("", parent = error_cnd(), .inherit = TRUE))
  expect_true(cnd$rlang$inherit)

  cnd <- catch_cnd(inform("", parent = warning_cnd(), .inherit = TRUE))
  expect_true(cnd$rlang$inherit)
})


# Lifecycle ----------------------------------------------------------

test_that("error_cnd() still accepts `.subclass`", {
  # <deprecatedWarning>
  skip_if(getRversion() < "3.6.0")

  local_options(
    lifecycle_disable_warnings = FALSE,
    force_subclass_deprecation = TRUE
  )
  expect_snapshot({
    expect_equal(
      error_cnd(.subclass = "foo"),
      error_cnd("foo")
    )
    expect_error(abort("foo", .subclass = "bar"), class = "bar")
  })
})
hadley/rlang documentation built on April 24, 2024, 1:05 a.m.