Debugging within a namespace

Viewed 145

I frequently need to debug a function in one of my R packages, and find it convenient to add test code and print statements throughout the code. Then the function is a method inside a package, running tests from the console will use the package-stored old version of the function and not the new test version. I often resort to something like cat *.r > /tmp/package.r and then source('/tmp/package.r') to override all functions, which allows the test function to be prioritized. But this doesn't work when I have .Fortran or similar calls within the package.

Is there an elegant way to debug with function overrides within the correct version of a local package?

2 Answers

Regardless of your IDE, you can reload your package under development with devtools:

devtools::load_all("path/to/your/package/directory")

This should load it to your R session (RStudio has buttons and keyboard shortcuts for this too)

This is an extension of my comment above. As said in the comment checkout this guide for more detail.

To inspect calls dynamically during calls, editing functions and adding code can be done using the trace(func, edit = TRUE) method. However it is not the recommended methodology for doing so in any programming language. It is however recommended to instead perform debugging. Luckily debugging in R is simpler than many other languages. Some of the most noticeable methods for debugging in R is obtained using

  • debug(func) or debugonce(func)
  • trace
  • browser

I'll ignore trace's main usage, and only pass over it briefly in conjunction with browser.

Take this small example code

x <- 3
f <- function(x){
   x <- x ** 2
   x
}

Now if we wanted to "go into" this function and inspect what happens we can use the debug method simply by doing

debug(f) # alt: debugonce(f)
f(x)

and the following shows in the console

debugging in: f(x)
debug at #1: {
    x <- x^2
    x
}

We see 2 things: The original call in line 1, the function debugged including a function-line-number (#1) with the body in the function (potentially truncated). In addition the command has changed to Browse[n] (where n is a number), indicating we are in the debugger.

At this point nothing has run so in the "environment" tab in Rstudio we can see x | x. Note that x is still a promise (its value is delayed until used or changed). If we execute x we get the value [1] 3 and we see the environment change to x | 3 (it has been forced, and is no longer a promise).

Inside the debugger we can use the following commands to "walk through" the function

  1. n to move to the "next line"
  2. s to move to the "next line", or if the current call is a function, "step" into the function (debug this function)
  3. f to move forward until the next break (with the added utility that if you are in a loop you stop at "loop end".
  4. c to move until next break point or end of function (without breaking at end of loops).
  5. Q to exit immediately

It you click n for example you will see

debug at #2: x <- x^2

printed in the console. This indicates the line that is executed next. Notice the value of x in the environment and run n again, notice the value changed from x | 3 to x | 9 and

debug at #3: x

is printed. This being the last line pressing n again will exit the function and print

exiting from: f(x)
[1] 9

Once you're done debugging you can run undebug(f) to remove the breakpoint and stop the debugger from activating.

This is a simple function, easy to debug, but the idea for more complex functions are the same. If you are in a long loop you can use f to skip to the end of the loop, similar to pressing n a bunch of times. Note that if you hit an error at any point it will exit automatically when the error occurs and you'll have to walk back to the point again or alternatively use browser.

In the case where you have a function like

f2 <- function(x){
  x <- x + 2
  f(x)
}

you can further step into the nested function call f(x) by using the s command while the line is printing

debug at #3: f(x)

or by using debug(f2) and debug(f) in conjunction. Both will give the same result (try it out!).

Now in many cases you might hit a bug or debug many lines (potentially thousands). In this case, you might have some "idea" where you want to start, and this might not be the start of the function. In this case you can use browser(). This basically sets a breakpoint. Whenever browser() is hit, it will stop and start the debugger (similar to debug(f) and calling f(x) but at a specific point). Try for example

f3 <- function(x){
  x1 <- f(x)
  browser()
  x2 <- f2(x)
  c(x1, x2)
}
f3(x)

and you'll notice see

Called from: f3(x)

printed (if you have run undebug(f2) and undebug(f) first).

Lets say it is not your function but a function within a namespace, well then we can even add the breakpoint ourself at run-time. Try for example calling

trace(f3, edit = TRUE)

and you will see an editing window pop up. Simply add browser() at the desired spot and click save. This edits the function within the namespace. It will be reverted once R is closed or alternatively you can remove it with another call to trace(f3, edit = TRUE).

Related