6

I'm working on an R package and I need some help writing R test functions that are meant to check whether the correct warning is being thrown on C-side code and then caught on the R side. Let me give you some background on what I'm working on exactly:

  1. Most of what I'm writing is done on the C side. In addition, I have an if-statement type macro in C that allows the coder to pass a warning to R in the form of a string. The basic premise is that if(statement_true) pass_warning_to_R("Warning string to pass"). What I'd like to do is test whether these warnings are being thrown when I expect/need them to be by writing an R file that uses tryCatch blocks.
  2. So far I've written something similar to this:

    counter <- 0
    tryCatch({
    
    function_im_testing()
    
    }, warning = function(war) {
         # Check if warning is as expected and if so increment counter
         if(toString(war)=="The warning I'm expecting/testing for"){
            print(toString(war))
            counter <- counter + 1
         }
    
    }, error = function(err) {
          print(toString(err))
    
    }, finally = {
    print("Leaving tryCatch")
    })
    
    # Stop if the 3 warnings we expected aren't present
    stopifnot(counter == 3)
    

This is the method I'm using and, so far, I haven't even been able to get the if statement to execute by trying to get toString(war) and "Warning I'm expecting/testing for" to be the same thing. This, in addition with the fact that this method is pretty sloppy and unreliable, leads me to believe that there's a better way. So, is there a better approach to doing this?

Spacedman
  • 92,590
  • 12
  • 140
  • 224
Decave
  • 127
  • 7
  • just print the warning (before that `if`) to see what you should actually expect to see there and that'll help you understand why your match is not working – eddi Apr 19 '13 at 20:28
  • 3
    It's a somewhat different approach, but have you considered the `testthat` package? It has an `expect_warning` function which can take a string parameter to match the warning against. I imagine other testing suites for R have a similar functionality. – Brian Diggs Apr 19 '13 at 20:29
  • @eddi I've tried that and literally copied and pasted the output into the second part of the conditional to no avail. – Decave Apr 19 '13 at 20:29
  • @BrianDiggs I haven't tried that but I'll check it out, thanks. – Decave Apr 19 '13 at 20:30
  • @user2263969 - here's an example: `tryCatch({warning("A", call. = FALSE)}, warning = function(war) {print(gettext(war)); if (gettext(war) == "simpleWarning: A\n") {print("got it")}})`. I'm pretty sure there is no way for this method to not work :) – eddi Apr 19 '13 at 20:33
  • I hadn't tried using gettext -- will that return a different string than toString? – Decave Apr 19 '13 at 20:36
  • Also, thanks to both of you. – Decave Apr 19 '13 at 20:37
  • I don't know if that would make a difference for you (it certainly doesn't for my example), I used `gettext` because it was mentioned in the `warning` documentation. – eddi Apr 19 '13 at 20:38

3 Answers3

4

Usually with warnings you'd like to allow evaluation to continue; tryCatch is used to stop evaluation. So instead use withCallingHandlers with a handler for warnings that does what you want, and then invokes the 'muffleWarning' restart. The message of an error / warning can be extracted with conditionMessage

counter <- 0L
withCallingHandlers({
    function_im_testing()
}, warning = function(w) {
    if (conditionMessage(w) == "The warning I'm expecting/testing for")
        counter <<- counter + 1L
    invokeRestart("muffleWarning")
})

Since you're writing your own package, it makes sense to create warnings that can be identified in a more robust way, e.g., the following returns a condition that can be used in warning, but that has a class 'bad_input' that can be used in withCallingHandlers.

bad_input <- function(...) {
    w <- simpleWarning(...)
    class(w) <- c("bad_input", class(w))
    w
}

To be used like warning(bad_input("your input is bad")) and producing output with

fun <- function() {
    warning("oops")
    warning(bad_input("your input is bad"))
    "DONE"
}        

like

>     fun()
[1] "DONE"
Warning messages:
1: In fun() : oops
2: your input is bad 
>     counter <- 0L
>     withCallingHandlers(fun(), bad_input = function(w) {
+         counter <<- counter + 1L
+         invokeRestart("muffleWarning")
+     })
[1] "DONE"
Warning message:
In fun() : oops
> counter
[1] 1
Martin Morgan
  • 45,935
  • 7
  • 84
  • 112
4

Apart from actually capturing the warning, you need to be aware that warning messages are translated:

library(devtools)
with_envvar(c(LANG = "en"), log(-1))
# In log(-1) : NaNs produced
with_envvar(c(LANG = "de"), log(-1))
# In log(-1) : NaNs wurden erzeugt
with_envvar(c(LANG = "fr"), log(-1))
# In log(-1) : production de NaN
with_envvar(c(LANG = "ko"), log(-1))
# In log(-1) : NaN이 생성되었습니다

So if you're doing this inside a test, make sure you set the LANG environmental variable to ensure that the message doesn't vary according to what computer it's run on.

hadley
  • 102,019
  • 32
  • 183
  • 245
0

Check out testthat::expect_warnings()

yenlow
  • 11
  • 2