saveTo <- function(to) function(cond) assign(to, cond, envir = globalenv()) isCond <- function(x, class, msg) { inherits(x, "condition") && inherits(x, class) && identical(x$message, msg) } ## Handler stack starts empty handlers <- withVisible(globalCallingHandlers()) stopifnot( identical(handlers$value, list()), isTRUE(handlers$visible) ) ## Handlers must be functions err <- tryCatch(globalCallingHandlers(foo = identity, bar = NA), error = identity) stopifnot(identical(err$message, "condition handlers must be functions")) ## Can register and inspect handler globalCallingHandlers(error = saveTo(".Last.error")) handlers <- withVisible(globalCallingHandlers()) stopifnot( length(handlers$value) == 1, names(handlers$value) == "error", handlers$visible ) ## Handlers are invoked based on class .Last.error <- NULL signalCondition(simpleCondition("foo")) stopifnot(is.null(.Last.error)) signalCondition(simpleError("foo")) stopifnot(isCond(.Last.error, "error", "foo")) ## Can register multiple handlers globalCallingHandlers( condition = saveTo(".Last.condition"), warning = saveTo(".Last.warning") ) handlers <- globalCallingHandlers() stopifnot(length(handlers) == 3, all(names(handlers) == c("condition", "warning", "error"))) ## Multiple handlers are invoked .Last.error <- NULL signalCondition(simpleWarning("bar")) stopifnot( is.null(.Last.error), isCond(.Last.condition, "warning", "bar"), isCond(.Last.warning, "warning", "bar") ) signalCondition(simpleError("baz")) stopifnot( isCond(.Last.error, "error", "baz"), isCond(.Last.condition, "error", "baz"), isCond(.Last.warning, "warning", "bar") ) ## Handlers are not invoked if error is caught .Last.error <- NULL try(stop("baz"), TRUE) stopifnot(is.null(.Last.error)) ## Can remove handlers handlers <- globalCallingHandlers() old <- withVisible(globalCallingHandlers(NULL)) stopifnot( identical(old$value, handlers), !old$visible, identical(globalCallingHandlers(), list()) ) signalCondition(simpleError("foo")) stopifnot(is.null(.Last.error)) ## Can pass list of handlers foobars <- list(foo = function() "foo", bar = function() "bar") globalCallingHandlers(foobars) stopifnot(identical(globalCallingHandlers(), foobars)) globalCallingHandlers(NULL) ## Local handlers are not returned handlers <- withCallingHandlers(foo = function(...) NULL, globalCallingHandlers()) stopifnot(identical(handlers, list())) globalCallingHandlers(foobars) handlers <- withCallingHandlers(foo = function(...) NULL, globalCallingHandlers()) stopifnot(identical(handlers, foobars)) globalCallingHandlers(NULL) ## Registering a handler again moves it to the top of the stack globalCallingHandlers( warning = function(...) "foo", error = function(...) "foo", condition = function(...) "foo", error = function(...) "bar" ) globalCallingHandlers( error = function(...) "foo" ) bumped <- list( error = function(...) "foo", warning = function(...) "foo", condition = function(...) "foo", error = function(...) "bar" ) stopifnot(identical(globalCallingHandlers(), bumped)) ## Fails in R 4.0 globalCallingHandlers( warning = function(...) "foo", error = function(...) "foo" ) bumped <- list( warning = function(...) "foo", error = function(...) "foo", condition = function(...) "foo", error = function(...) "bar" ) stopifnot(identical(globalCallingHandlers(), bumped)) globalCallingHandlers(NULL) ## Attributes and closure environments are detected in the duplicate ## handlers check hnd1 <- function(...) "foo" hnd2 <- structure(function(...) "foo", bar = TRUE) hnd3 <- local(function(...) "foo") expectedList <- list( error = hnd1, error = hnd2, error = hnd3 ) globalCallingHandlers(error = hnd1, error = hnd2, error = hnd3) stopifnot(identical(globalCallingHandlers(), expectedList)) globalCallingHandlers(NULL) ## and removeSource() now retains attributes: stopifnot( identical(attributes(removeSource(hnd2)), list(bar = TRUE)) ) ## Source references do not cause handlers to be treated as distinct withSource <- function(src, envir = parent.frame(), file = NULL) { if (is.null(file)) { file <- tempfile("sourced", fileext = ".R") } on.exit(unlink(file)) writeLines(src, file) source(file, local = envir, keep.source = TRUE) } withSource("hnd1 <- structure(function(...) { NULL })") withSource("hnd2 <- structure(function(...) { NULL })") globalCallingHandlers(NULL) globalCallingHandlers(error = hnd1) globalCallingHandlers(error = hnd2) # message "pushing duplicate .." stopifnot(identical(globalCallingHandlers(), list(error = hnd2))) globalCallingHandlers(NULL)