I want to test a function that can throw an error but I also want to ensure that a variable value is correct (to verify the expected last state before the error - so I want to test for unwanted side effects). Is it possible to do this?
Simplified expression to be tested
x <- 1 # could also be made "global" by "<<-"
stop("damn, an error occured")
How can I do something like
testthat("double check",
expect_error_and_TRUE( x == 1, {
x <- 1
stop("damn, an error occured")
}))
I could not find a way to "stack" (pipe) the expect functions?
If a function returns a value it doesn't throws an error and otherwise round. You can test for a specific string in expect_error if you wish, so you could set in the error the value of x.
{
x <- 1
stop("damn, an error occured")
}
## Error: damn, an error occured
x
## [1] 1
rm(x)
f <- function(){
x <- 1
stop("damn, an error occured")
}
f() == 1
## Error in f() : damn, an error occured
expect_error(f(), "damn, an error occured")
f <- function(){
x <- 1
stop("damn, an error occured, x is ", x)
}
expect_error(f(), "x is 1")
I would advise against testing code outside a function, as it depends on the environment, where it is run, so if you run it in the testing file it might not be the same as in the "main" part of your code
Here is a better example now to test for an unwanted side effect - and a simple solution:
library(testthat)
counter <- 1
test_that("error occurs but does not change the previous value", {
expect_error(if (log("a") == 42) counter <- counter + 1)
expect_equal(counter, 1)
})
Related
I have a function that may throw an error. When an error is thrown, I'd like to show the error message, as if the error actually occurred, and further return an object invisibly.
I looked at this thread, which uses withCallingHandlers and logs the error message somewhere. This comes close, but I do not want to log the message as text and then print a text message, the function should show the error message as if it would have exited on error.
The functions workflow looks like the following:
foo <- function(x){
y <- x + 1
if(y == 2) {
stop("oops")
# also return y invisibly when error is thrown,
}
z <- y + 1
z
}
Based on input x an intermediate y is calculated. y is used for the error check. If an error occurs y should be returned invisibly and an normal error message should be thrown. Otherwise z is calculated an returned.
foo(1) should return error message and y invisibly.
I thought about using on.exit but in this case, always y is return invisibly.
Any help is appreciated.
Add: Maybe what I have in mind is not possible. In this case, would it be possible to show a logged error message in a way that comes close to a real error message?
Add2: I thought about issuing a warning, but in my actual use case a warning would be misleading, since the function does not produce desired result z, but just some intermediate result y and I want to return y so that the user can inspect it further, and reason about why it wasn't processed correctly by foo. Thinking about it, other must have encountered the same problem, there should be some kind of solution.
Add3: Maybe it is possible to use on.exit together with a flag which is triggered, so that on.exit will return y invisibly in case of an error and do nothing otherwise.
func <- function(x) { a <- simpleError("quux"); attr(a,"abc") <- 7; stop(a); }
func()
# Error: quux
So far so good, we see an error. If we catch this and look at the contents of the error message we can see the attribute tucked inside:
dput(tryCatch(func(), error=function(e) e))
# structure(list(message = "quux", call = NULL), class = c("simpleError",
# "error", "condition"), abc = 7)
and even extract it easily
dput(tryCatch(func(), error=function(e) attr(e,"abc")))
# 7
Below I post my final solution to my question. Basically it is based on r2evans answer combined with user20650's comment.
As r2evans showed we can use simpleError to create an error and also attach an object in the attributes. The big plus of this approach is that it returns a real error that we can program with. If we would only show the error message and then return a vector invisibly, tryCatch wouldn't recognize it as an error. The downside is that simpleError doesn't look like a normal error to an interactive user when printed in the console. It will show something like <simpleError: x must not be 1>. However, the message is not printed in red like normal errors.
Here user20650's comment shows a nice way out. We can first print the message with message(conditionMessage(e)) and then we can return the simpleError invisibly.
foo <- function(x) {
y <- x + 1
if(y == 2) {
foo_internal <- function(val) {
a <- simpleError("x must not be 1")
attr(a,".data") <- y
stop(a)
}
return(
tryCatch(foo_internal(y),
error = function(e) {
message(conditionMessage(e))
invisible(e)
})
)
}
z <- y + 1
z
}
# this is a special function to inspect the object attached to `simpleError`
inspect <- function(x = NULL) {
if(is.null(x)) {
x <- .Last.value
}
attr(x,".data")
}
# returns error message
foo(1)
#> x must not be 1
# we can get y's value with a special inspect function
inspect()
#> [1] 2
# foo(1) returns a "real" error
class(foo(1))
#> x must not be 1
#> [1] "simpleError" "error" "condition"
Created on 2021-03-13 by the reprex package (v0.3.0)
I have a regression model (lm or glm or lmer ...) and I do fitmodel <- lm(inputs) where inputs changes inside a loop (the formula and the data). Then, if the model function does not produce any warning I want to keep fitmodel, but if I get a warning I want to update the model and I want the warning not printed, so I do fitmodel <- lm(inputs) inside tryCatch. So, if it produces a warning, inside warning = function(w){f(fitmodel)}, f(fitmodel) would be something like
fitmodel <- update(fitmodel, something suitable to do on the model)
In fact, this assignation would be inside an if-else structure in such a way that depending on the warning if(w$message satisfies something) I would adapt the suitable to do on the model inside update.
The problem is that I get Error in ... object 'fitmodel' not found. If I use withCallingHandlers with invokeRestarts, it just finishes the computation of the model with the warning without update it. If I add again fitmodel <- lm(inputs) inside something suitable to do on the model, I get the warning printed; now I think I could try suppresswarnings(fitmodel <- lm(inputs)), but yet I think it is not an elegant solution, since I have to add 2 times the line fitmodel <- lm(inputs), making 2 times all the computation (inside expr and inside warning).
Summarising, what I would like but fails is:
tryCatch(expr = {fitmodel <- lm(inputs)},
warning = function(w) {if (w$message satisfies something) {
fitmodel <- update(fitmodel, something suitable to do on the model)
} else if (w$message satisfies something2){
fitmodel <- update(fitmodel, something2 suitable to do on the model)
}
}
)
What can I do?
The loop part of the question is because I thought it like follows (maybe is another question, but for the moment I leave it here): it can happen that after the update I get another warning, so I would do something like while(get a warning on update){update}; in some way, this update inside warning should be understood also as expr. Is something like this possible?
Thank you very much!
Generic version of the question with minimal example:
Let's say I have a tryCatch(expr = {result <- operations}, warning = function(w){f(...)} and if I get a warning in expr (produced in fact in operations) I want to do something with result, so I would do warning = function(w){f(result)}, but then I get Error in ... object 'result' not found.
A minimal example:
y <- "a"
tryCatch(expr = {x <- as.numeric(y)},
warning = function(w) {print(x)})
Error in ... object 'x' not found
I tried using withCallingHandlers instead of tryCatch without success, and also using invokeRestart but it does the expression part, not what I want to do when I get a warning.
Could you help me?
Thank you!
The problem, fundamentally, is that the handler is called before the assignment happens. And even if that weren’t the case, the handler runs in a different scope than the tryCatch expression, so the handler can’t access the names in the other scope.
We need to separate the handling from the value transformation.
For errors (but not warnings), base R provides the function try, which wraps tryCatch to achieve this effect. However, using try is discouraged, because its return type is unsound.1 As mentioned in the answer by ekoam, ‘purrr’ provides soundly typed functional wrappers (e.g. safely) to achieve a similar effect.
However, we can also build our own, which might be a better fit in this situation:
with_warning = function (expr) {
self = environment()
warning = NULL
result = withCallingHandlers(expr, warning = function (w) {
self$warning = w
tryInvokeRestart('muffleWarning')
})
list(result = result, warning = warning)
}
This gives us a wrapper that distinguishes between the result value and a warning. We can now use it to implement your requirement:
fitmodel = with(with_warning(lm(inputs)), {
if (! is.null(warning)) {
if (conditionMessage(warning) satisfies something) {
update(result, something suitable to do on the model)
} else {
update(result, something2 suitable to do on the model)
}
} else {
result
}
})
1 What this means is that try’s return type doesn’t distinguish between an error and a non-error value of type try-error. This is a real situation that can occur, for example, when nesting multiple try calls.
It seems that you are looking for a functional wrapper that captures both the returned value and side effects of a function call. I think purrr::quietly is a perfect candidate for this kind of task. Consider something like this
quietly <- purrr::quietly
foo <- function(x) {
if (x < 3)
warning(x, " is less than 3")
if (x < 4)
warning(x, " is less than 4")
x
}
update_foo <- function(x, y) {
x <- x + y
foo(x)
}
keep_doing <- function(inputs) {
out <- quietly(foo)(inputs)
repeat {
if (length(out$warnings) < 1L)
return(out$result)
cat(paste0(out$warnings, collapse = ", "), "\n")
# This is for you to see the process. You can delete this line.
if (grepl("less than 3", out$warnings[[1L]])) {
out <- quietly(update_foo)(out$result, 1.5)
} else if (grepl("less than 4", out$warnings[[1L]])) {
out <- quietly(update_foo)(out$result, 1)
}
}
}
Output
> keep_doing(1)
1 is less than 3, 1 is less than 4
2.5 is less than 3, 2.5 is less than 4
[1] 4
> keep_doing(3)
3 is less than 4
[1] 4
Are you looking for something like the following? If it is run with y <- "123", the "OK" message will be printed.
y <- "a"
#y <- "123"
x <- tryCatch(as.numeric(y),
warning = function(w) w
)
if(inherits(x, "warning")){
message(x$message)
} else{
message(paste("OK:", x))
}
It's easier to test several argument values with the code above rewritten as a function.
testWarning <- function(x){
out <- tryCatch(as.numeric(x),
warning = function(w) w
)
if(inherits(out, "warning")){
message(out$message)
} else{
message(paste("OK:", out))
}
invisible(out)
}
testWarning("a")
#NAs introduced by coercion
testWarning("123")
#OK: 123
Maybe you could assign x again in the handling condition?
tryCatch(
warning = function(cnd) {
x <- suppressWarnings(as.numeric(y))
print(x)},
expr = {x <- as.numeric(y)}
)
#> [1] NA
Perhaps not the most elegant answer, but solves your toy example.
Don't put the assignment in the tryCatch call, put it outside. For example,
y <- "a"
x <- tryCatch(expr = {as.numeric(y)},
warning = function(w) {y})
This assigns y to x, but you could put anything in the warning body, and the result will be assigned to x.
Your "what I would like" example is more complicated, because you want access to the expr value, but it hasn't been assigned anywhere at the time the warning is generated. I think you'll have to recalculate it:
fitmodel <- tryCatch(expr = {lm(inputs)},
warning = function(w) {if (w$message satisfies something) {
update(lm(inputs), something suitable to do on the model)
} else if (w$message satisfies something2){
update(lm(inputs), something2 suitable to do on the model)
}
}
)
Edited to add:
To allow the evaluation to proceed to completion before processing the warning, you can't use tryCatch. The evaluate package has a function (also called evaluate) that can do this. For example,
y <- "a"
res <- evaluate::evaluate(quote(x <- as.numeric(y)))
for (i in seq_along(res)) {
if (inherits(res[[i]], "warning") &&
conditionMessage(res[[i]]) == gettext("NAs introduced by coercion",
domain = "R"))
x <- y
}
Some notes: the res list will contain lots of different things, including messages, warnings, errors, etc. My code only looks at the warnings. I used conditionMessage to extract the warning message, but
it will be translated to the local language, so you should use gettext to translate the English version of the message for comparison.
I keep getting this error message when I run my code, and I'm not sure what I need to do to fix it.
My code is as follows:
gwmh<-function(target,N,x,sigmasq){
p<-add.var()
samples<-c(x,p)
for(i in 2:N){
prop<-rnorm(1,0,sqrt(sigmasq))
if(runif(1)<min(1,(target(x+abs(prop)*p))/target(x))){
x<-x+prop
samples<-rbind(samples,c(x,p))} else{
p<--p
samples<-rbind(samples,c(x,p))
}
}
samples[(1:N)] ##delete after testing
}
The error says:
Error in if (runif(1) < min(1, (target(x + abs(prop) * p))/target(x))) { :
missing value where TRUE/FALSE needed
(add.var is a function i created to generate p in {-1,1} randomly)
I tested my comment and feel more comfortable offering it as an answer.
Try the following
if(TRUE){print("Test")}
# prints "Test"
if(FALSE){print("Test")}
# prints nothing
if(NA){print("Test")}
# throws your error
So within this expression:
runif(1)<min(1,(target(x+abs(prop)*p))/target(x))
the result is neither TRUE or FALSE but NAand seeing as runif()should not throw any missings it has to be in the rhs of the comparison.
Assuming that target, x, and sigmasq are all values from a df and not functions there is probably a missing value there. If this is the case and also intended you have to add an exception for catching and handling these missings that might look like this:
# test being your test expression
if(
if(is.na(test) {Do Stuff for missing values}
else {original IF statement}
You need to add na.rm=TRUE to the min function to account for possible NA in x.
Your function will fail if x contains NA.
target <- function(x) dnorm(x, 0, 1)
add.var <- function() runif(1, -1, 1)
gwmh <- function(target,N,x,sigmasq){
p <- add.var()
samples<-c(x,p)
for(i in 2:N){
prop<-rnorm(1,0,sqrt(sigmasq))
if(runif(1) < min(1, (target(x+abs(prop)*p))/target(x), na.rm=TRUE)){ # <- Here
x<-x+prop
samples<-rbind(samples,c(x,p))} else{
p<--p
samples<-rbind(samples,c(x,p))
}
}
samples[(1:N)] ##delete after testing
}
Now try:
gwmh(target, N=2, x=c(1,2,NA,3), sigmasq=2)
# [1] 1.00 3.14
Given the following R knitr document:
\documentclass{article}
\begin{document}
<<data>>=
opts_chunk$set(comment = NA) # omits "##" at beginning of error message
x <- data.frame(x1 = 1:10)
y <- data.frame()
#
<<output_x>>=
if (nrow(x) == 0) stop("x is an empty data frame.") else summary(x)
#
<<output_y>>=
if (nrow(y) == 0) stop("y is an empty data frame.") else summary(y)
#
\end{document}
As expected, the last chunk returns an error with the custom message. The compiled PDF looks a little different:
Error: y is an empty data frame.
I want this text to just be
y is an empty data frame.
Without the Error: part or the red color. Can I achieve this? How?
Edit: I was able to make it in the mock data through the following workaround:
<<output_y>>=
if (nrow(y) == 0) cat("y is an empty data frame.") else summary(y)
#
However, that doesn't work with my real data, because I need the function to be stopped at that point.
Although I do not understand why an error should not be called Error, you are free to customize the output hook error to remove Error: from the message:
library(knitr)
knit_hooks$set(error = function(x, options) {
knitr:::escape_latex(sub('^Error: ', '', x))
})
You could do something like this. options("show.error.messages" = FALSE) turns off error messages, so you could temporarily employ that once the if statement is entered and use on.exit to reset it.
This way, stop stops the function, Error: is avoided, and the desired message is printed in red.
> f <- function(x) {
if(x > 5) {
g <- getOption("show.error.messages")
options(show.error.messages = FALSE)
on.exit(options(show.error.messages = g))
message("x is greater than 5.")
stop()
}
x
}
> f(2)
# [1] 2
> f(7)
# x is greater than 5.
Note: I'm not exactly sure how safe this is and I'm not a big supporter of changing options settings inside functions.
Here is my code:
test <- function(y){
irisname <- c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species")
if(y %in% irisname){
print(y)
} else{
test <- function(...) stop("dummy error")
test(y)
}
}
> test("ds")
Error in test(y) : dummy error
In the result: "Error in test(y) : dummy error ", I need "ds" in test("ds"), not test(y).
How can I do that?
This almost does it (there's an extra colon ...), by using call.=FALSE to suppress the information about the call and hacking it into the error message.
update: added quotation marks to error #1; explained a bit more about why this problem is hard.
I don't know the structure of your code, but you are making life considerably harder for yourself by passing objects farther down into the structure. It would be a lot easier to call stop() directly from within your first level, or to use the information carried in y directly within your error message.
test <- function(y,stop=FALSE){
irisname <- c("Sepal.Length","Sepal.Width",
"Petal.Length","Petal.Width","Species")
if (stop) stop(sprintf("premature stop: var %s",y))
if(y %in% irisname){
print(y)
} else{
test <- function(...) {
stop(sprintf("in test(\"%s\"): dummy error",...),
call.=FALSE)
}
test(y)
}
}
test("junk")
## Error: in test("junk"): dummy error
test("junk",stop=TRUE)
## Error in test("junk", stop = TRUE) : premature stop: var junk
Getting rid of the spurious first colon in the output of test("junk") will be considerably harder, because the Error: string is hard-coded within R. Your best bet is probably, somehow, to print your own custom error message and then stop silently, or recreate the behaviour of stop() without generating the message (see ?condition: e.g. return(invisible(simpleError("foo")))). However, you're going to have to jump through a lot of hoops to do this, and it will be hard to ensure that you get exactly the same behaviour that you would have with stop() (e.g. will the error message have been saved in the error-message buffer?)
What you want to do is probably possible by mucking around with R internals enough, but in my opinion so hard that it would be better to rethink the problem ...
Good luck.
You could check the argument right at the start of the function. match.arg might come in handy, or you could print custom message and return NA.
two updates below
> test <- function(y)
{
if(!(y %in% names(iris))){
message(sprintf('test("%s") is an error. "%s" not found in string', y, y))
return(NA) ## stop all executions and exit the function
}
return(y) ## ... continue
}
> test("Sepal.Length")
# [1] "Sepal.Length"
> test("ds")
# test("ds") is an error. "ds" not found in string
# [1] NA
Add/Edit : Is there a reason why you're nesting a function when the function goes to else? I removed it, and now get the following. It seems all you are doing is checking an argument, and end-users (and RAM) want to know immediately if they enter an incorrect default arguments. Otherwise, you're calling up unnecessary jobs and using memory when you don't need to.
test <- function(y){
irisname <- c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species")
if(y %in% irisname){
print(y)
} else{
stop("dummy error")
}
}
> test("ds")
# Error in test("ds") : dummy error
> test("Sepal.Length")
# [1] "Sepal.Length"
You could also use pmatch, rather than match.arg, since match.arg prints a default error.
> test2 <- function(x)
{
y <- pmatch(x, names(iris))
if(is.na(y)) stop('dummy error')
names(iris)[y]
}
> test2("ds")
# Error in test2("ds") : dummy error
> test2("Sepal.Length")
# [1] "Sepal.Length"