stopifnot() vs. assertError() - r

I wonder what the differences between stopifnot() and assertError() are:
assertError() is not found by default (You'll have to load the "tools" package first), but stopifnot() is.
More significantly, assertError() always throws an error message, even if I pass arguments like TRUE or FALSE, while stopifnot() does the obvious and expected thing.
Reading the manual page did not help. What is the correct use instead of assertError(length(x) != 7)? If x is undefined, the statement produces no error, but as soon as it is defined, it is producing errors, independent of the length of x (7 or not).

The main difference is where they should be used.
stopIfnot aim at stopping an execution if some conditions are not met during the run where assertError aim at testing your code.
assertError expect it's parameter to raise an error, this is what happens when x is not defined, there's an error
> length(x) != 7
Error: object 'x' not found
When you pass this expression to assertError, it raise an error and assertError return the conditions met (the error in itself). This allow you to test the failure cases of your code.
So assertError is mostly used in tests cases in a Test Driven Development pattern (TDD), when your code/function is supposed to raise an error for some specific parameters and ensure when you update your function later you're not breaking it.
Example usage of stopifnot and assertError:
mydiv <- function(a,b) {
stopifnot(b>0)
a/b
}
And now lets make a test to ensure this will raise an error if we pass "b" as 0:
tryCatch(
assertError(mydiv(3,0)),
error = function(e) { print("Warning, mydiv accept to divide by 0") }
)
Running this code produce no output, desired behavior.
Now if we comment the stopifnot in mydiv like this:
mydiv <- function(a,b) {
#stopifnot(abs(b)>0)
a/b
}
And testing again the tryCatch block, we get this output:
[1] "Warning, mydiv accept to divide by 0"
This is a small example of testing a function really throw an error as expected.
The tryCatch block is just to showcase with a different message, I hope this give more light on the subject.

Related

Check if a quosure contains a string

I have a function that used to accept unquoted input back when I was first learning about NSE and now I hate it in this use case. I want to force the function to only take a string, but provide a clear error message on what happened back to the user.
My problem is testing for what type of input is being passed along. I can use rlang::quo_is_symbol(rlang::enquo(str_x)), but this will fail when chaining together multiple functions. Here is my cut at a reprex.
fun1 <- function(str_x) {
depr_str_x(str_x)
paste("this is x", str_x)
}
depr_str_x <- function(str_x) {
if (rlang::quo_is_symbol(rlang::enquo(str_x))) {
stop("this is a clear error message to the user")
}
invisible(NULL)
}
So when I run this, I get what I want.
depr_str_x("test") # nothing (which I want)
depr_str_x(test) # my custom error (which I want)
But when I run this, I get the error both times. How do I get it to recognize that "test" being passed through is a string? I have also tried rlang::is_scalar_character() but that fails for the symbol case. I was hoping there was a quick and easy way to do this without forcing evaluation and seeing if it fails or something.
fun1("test") # my custom error (which I don't want)
fun1(test) # my custom error (which I want)
Further, this should work when the function gets used inside of other functions. This is where I keep running into problems with my approaches.
fun2 <- function(str_x) fun1(str_x)
fun2("test") # my custom error (which I don't want)
fun2(test) # my custom error (which I want)
I have it working (but am not sure this won't have bugs creeping up) by crudely trapping the error object 'x' not found. I am leaving the question open and welcome better solutions.
depr_str_x <- function(str_x) {
is_valid <- tryCatch(rlang::is_scalar_character(str_x),
error = function(e) {
if (grepl("not found", e, fixed = TRUE)) {
return(FALSE)
} else {
stop(e)
}
})
if (!is_valid)
stop("this is a clear error message to the user")
}
NSE arguments and arguments that may or may not be NSE require quasiquotation to be properly passed from one function to the next. The most straightforward way is to use {{:
fun1 <- function(str_x) {
depr_str_x({{str_x}}) # <-- Note the {{ operator
paste("this is x", str_x)
}
fun1("test") # [1] "this is x test"
fun1(test) # Error in depr_str_x({ : this is a clear error message to the user
The same holds for fun2:
fun2 <- function(str_x) fun1({{str_x}}) # <---- Ditto on {{
fun2("test") # [1] "this is x test"
fun2(test) # Error in depr_str_x({ : this is a clear error message to the user
EDIT: Something to keep in mind is that by allowing arguments that may or may not be NSE, you are creating ambiguity. Consider,
test <- "This is a string"
fun2(test) # What should happen here?
My general advice for your situation would be to drop NSE support altogether and document the change. NSE input will then fail automatically, but with an error that says "Variable test not found", which should be enough for most users to figure what the issue is.

Capture and handle multiple errors in sequence

I have an expression that spits out multiple errors:
stop("first")
stop("second")
stop("third")
print("success")
Is there a way to make this expression run all the way to the end (and, if possible, store all of the errors)?
The problem with tryCatch is that it stops execution on the first handled condition (so this will print "There was an error" exactly once).
tryCatch({
stop("first")
stop("second")
stop("third")
print("success")
}, error = function(e) print("There was an error"))
I read that withCallingHandlers will capture conditions and continue execution, so I tried something like this...
withCallingHandlers(
{...},
error = function(e) print("There was an error")
)
...but, although this does print the message, it also fails after the first error, which, as far as I can tell, is because the default restart just runs the current line again.
I think what I need to do is write a custom restart that simply skips the current expression and jumps to the next one, but I'm at a bit of a loss about how to go about that.
(Background: Basically, I'm trying to replicate what happens inside testthat::test_that, which is that even though each failing expectation throws an error, all expectations are run before quitting).

How to target exception-handling to specific exceptions?

Does R provide any support for targetting exception handling to only specific exceptions?
In Python, for example, one can narrow down exception-handling to specific exception types; e.g.:
try:
return frobozz[i]
except IndexError:
return DEFAULT
In this example, the exception handling will kick in only if i is an integer such that i >= len(frobozz) or i < -len(frobozz), but will not catch the exception resulting from, e.g., the case where i is the string "0" (which would be a TypeError, rather than an IndexError).
Wellllll...yes and no, and mostly no.
Every Python exception is wrapped in a particular error class which derives from Error, and Python modules are supposed to raise the "right" kinds of errors. For instance, an index out of range error should throw IndexError. The base language knows about these errors and so you can catch the appropriate error type in your except... clause.
R doesn't do that. Errors are untyped; there's no essential difference between an index out of bounds error and any other error.
That said, you can cheat under certain, very limited, circumstances.
> y <- tryCatch(x[[2]], error = function(e) e)
> y
<simpleError in x[[2]]: subscript out of bounds>
> y$message
[1] "subscript out of bounds"
The key here is the use of the tryCatch function and the error clause. The error clause in a tryCatch is a function of one variable which can perform arbitrary operations on e, which is an object of type 'simpleError' and contains an item named "message". You can parse message and handle interesting cases separately
> y <- tryCatch(x[[2]],
error = function(e) {
if ('subscript out of bounds' == e$message) return(NA) else stop(e))
})
> y
[1] NA
This only works if you can actually detect the error string you want to look for, and that isn't guaranteed. (Then again, it also isn't guaranteed in Python, so things aren't really much different from one another.)
Final question, though: why in Heaven's name are you doing this? What are you really trying to do?

Handling multiple warning (ggplot2) warnings with testthat while testing a Markdown script [duplicate]

I want to test that a function generates multiple warnings (4 or more), when the order of the warnings can vary. My best attempt to do this is based on look-ahead RegExp matching. Simplifying to just 2 warnings, I know my RegExp work on a single string output, because both of the following are true:
grepl("(?s)(?=.*2)(?=.*1)", "* warn 1.\n* warn 2.", perl=TRUE)
grepl("(?s)(?=.*2)(?=.*1)", "* warn 2.\n* warn 1.", perl=TRUE)
However, this does not work when testing multiple warnings with testhat::expect_warning
# The function generating warnings:
foo <- function() { warning("warn 1."); warning("warn 2.") }
foo()
Warning messages:
1: In foo() : warn 1.
2: In foo() : warn 2.
# Testing it
expect_warning( foo(), "(?s)(?=.*1)(?=.*2)", perl=TRUE)
Error: foo() does not match '(?s)(?=.*1)(?=.*2)'. Actual values:
* warn 1.
* warn 2.
I suspect this is because the internals of expect_warning are doing something like testing the given RegExp against each warning separately--why an expect_warning( ... all=TRUE ) parameter might be meaningful.
Unfortunately I can't use this with a RegExp like "1 | 2"; that succeeds if only one warning is given.
I also want to avoid running the function multiple times and testing a different warning each time. There is a large amount of setup and teardown code needed to test the real function. It interacts heavily with the file system, and since it is the file-system warnings I am testing for, I can't mock that. Moreover, I want to test for the warnings in multiple situations, each of which requires different setup and teardown code, so this quickly bloats my tests.
Any suggestion on how to test for multiple warnings simply and all at once?
To capture the warnings and analyze them "by hand", you can also use testthat::capture_warnings:
# The function generating warnings:
foo <- function() { warning("warn 1."); warning("warn 2.") }
w <- capture_warnings(foo())
expect_match(w, ".*1", all = FALSE)
expect_match(w, ".*2", all = FALSE)
expect_match(w, ".*3", all = FALSE)
(The last line raises an error.)
So I have cobbled together a solution that involves using my own warning catch loop and checking the warnings as strings. Not an "all at once" solution, but it is at least compact given a complex function call. My code as it would apply to the example function foo() is the following:
gotWarnings= character(0)
withCallingHandlers({
got <- foo()
}, warning= function(e) {
# Push warning onto vector in parent frame.
gotWarnings <<- c(gotWarnings, conditionMessage(e))
invokeRestart("muffleWarning")
})
# Ensure no unexpected warnings,
expect_equal(length(gotWarnings), 2)
# Test that each warning I want is there
expect_true( any( grepl( "warn 1\\.", gotWarnings )))
expect_true( any( grepl( "warn 2\\.", gotWarnings )))
Figuring out how to continue processing after catching a warning was the hard part; tryCatch exits after catching the first warning. Here and here helped me work this out. Perhaps this could be converted into an expect_all_warnings test that takes a vector of RegEx to match, maybe with all= TRUE meaning no warning can go unmatched. I'll hold off accepting this as an answer in case somebody posts a better one, or at least one that packages a solution like this nicely.
You can maybe use the [12] like:
expect_warning( foo(),'(?s)(?=.*[12])' , all=T, perl=TRUE)

Debugging unexpected errors in R -- how can I find where the error occurred?

Sometimes R throws me errors such as
Error in if (ncol(x) != 2) { : argument is of length zero
with no additional information, when I've written no such code. Is there a general way for finding which function in which package causes an error?
Since most packages come compressed, it isn't trivial to grep /usr/lib/R/library.
You can use traceback() to locate where the last error occurred. Usually it will point you to a call you make in your function. Then I typically put browser() at that point, run the function again and see what is going wrong.
For example, here are two functions:
f2 <- function(x)
{
if (x==1) "foo"
}
f <- function(x)
{
f2(x)
}
Note that f2() assumes an argument of length 1. We can misuse f:
> f(NULL)
Error in if (x == 1) "foo" : argument is of length zero
Now we can use traceback() to locate what went wrong:
> traceback()
2: f2(x) at #3
1: f(NULL)
The number means how deep we are in the nested functions. So we see that f calls f2 and that gives an error at line 3. Pretty clear. We could reassign f with browser placed just before the f2 call now to check it's input. browser() simply allows you to stop executing a function and look around in its environment. Similar to debug and debugonce except that you don't have to execute every line up until the point you know something goes wrong.
Just to add to what #SachaEpskamp has already suggested, setting options(error=recover) and options(show.error.locations=TRUE) can be extremely helpful when debugging unfamiliar code. The first causes R to launch a debugging session on error, giving you the option to invoke the browser at any point in the call stack up to that error. The second option will tell R to include the source line number in the error.

Resources