Code
<- function(x, y) {
myFun <- sum(x, y)
out return(out)
}
myFun(runif(20), "A")
Ceres Barros
November 15, 2024
See Barebones R script for the code shown in this chapter
The flexibility and collaborative model development that SpaDES promotes can come with the cost of having module code that may not fit all desired applications out-of-the-box or, like any other piece of code, that may have errors.
It is a good idea to learn the basics of debugging, especially when using open-source, interpreted code languages like R (in opposition to compiled code languages like C++).
It is also a (very) good idea to learn how to develop a good reproducible example (reprex; see How to make a reprex) when debugging attempts have failed or when we cannot fix the issue ourselves (e.g., bugs in R packages need to be fixed by package maintainers).
browser()
browser()
calls are very useful when you have access to the source code. When inserted inside a function, they will interrupt code execution at that point and allow the user to “enter” the function’s environment in debugging mode – i.e. they will have access to all the objects the function has access to internally.
Let’s define a simple function and then use it improperly.
Because we have the source code, we can:
# > myFun(runif(20), "A")
# Called from: myFun(runif(20), "A")
# Browse[1]> x
# [1] 0.48059327 0.12201652 0.39367787 0.91989186 0.04872701 0.85632846 0.05945062 0.87683559 0.58599446 0.10403352 0.49429023
# [12] 0.69785397 0.19622413 0.05559181 0.20329131 0.14909383 0.61400844 0.73638292 0.21185129 0.72534305
# Browse[1]> y
# [1] "A"
From the above we would quickly realise we were trying to add a numeric vector with a character vector, which obviously doesn’t work.
browser()
with a SpaDES moduleGo back to the module My_linear_model created in Chapter 4 and insert a browser()
in the init
event, save the module and run again.
If you are using RStudio, it probably opened the module .R script (if not try right-clicking the RStudio window and selecting “Reload”), showing a highlighted browser()
line. The R console shows:
# No packages to install/update
# Jun09 00:03:51 simInit Resetting .Random.seed of session because sim$._randomSeed is not NULL. To get a different seed, run: sim$._randomSeed <- NULL to clear it.
# Jun09 00:03:51 simInit Using setDTthreads(1). To change: 'options(spades.DTthreads = X)'.
# Jun09 00:03:51 chckpn:init total elpsd: 21 secs | 0 checkpoint init 0
# Jun09 00:03:51 save :init total elpsd: 21 secs | 0 save init 0
# Jun09 00:03:51 prgrss:init total elpsd: 21 secs | 0 progress init 0
# Jun09 00:03:51 load :init total elpsd: 21 secs | 0 load init 0
# Jun09 00:03:51 My_lnr:init total elpsd: 21 secs | 0 My_linear_model init 1
# Called from: get(moduleCall, envir = fnEnv)(sim, cur[["eventTime"]], cur[["eventType"]])
Use ls()
to see what objects are in the function environment, then execute code line-by-line with ENTER, F10 or the “Next” button.
debug()
and debugonce()
If we don’t have access to the function code (or don’t want to insert a browser()
) we can use debug()
and debugonce()
. The effect will be similar to having a browser()
in the first line of a function’s definition.
Here’s an example:
undebug(<function_name>)
will de-activate debugging for that function.
debugonce()
and debug()
with a SpaDES moduleThe process would be similar in a module, with the difference that the debug()
/debugonce()
call would either happen before running the module with spades()
, OR from within the module in debugging mode.
If debugging module functions, they might not be easily available from the .GlobalEnv
since they “live” inside the simList
.
The easiest way to debug module functions is to
browser()
in that functionOR
Insert a browser()
in the module, before the function is called
Call debugonce("<function_name>")
/debug("<function_name>")
Proceed to executing the function
Let’s try it:
Exit browser()
mode (e.g., enter Q
in the R console)
Remove the browser()
from My_linear_model
Run debugonce("lm")
.
Run the simInit()
+ spades()
lines again to re-source module code and run the module OR run restartSpades()
which will re-parse the module code and resume the workflow from the top of the event that was interrupted (the init
).
ls()
show now?Exit debugging mode again
Re-run restartSpades()
Now go through steps 1-6 again, but replace debugonce("lm")
with debug("lm")
in step 3. What happened in step 6. this time?
If debugging functions that are S4 objects, you may need to be aware of which method needs to be debugged before calling debug
or debugOnce
.
Try showMethods("show")
to see all the methods implemented.
restartSpades()
Probably one of our BFFs (best-friend functions) as SpaDES developers, it will allow resuming a workflow whose execution was interrupted by an error or the user from the top of the interrupted event, but will first re-parse module code.
This means that we can insert a browser()
somewhere in the event code, then restartSpades()
and debug the event.
Module testing can happen at several levels:
Assertions – tests/checks embedded in module code.
Unit tests of module functions - individual functions are tested independently of the module.
Solo-module testing - the module is tested alone with default and non-default input/parameter values.
Integration tests - the module is tested in a workflow with other modules, using alone with default and non-default input/parameter values.
At a minimum, a developer should put in place assertions. These are small checks and tests inserted in the module code that issue meaningful warnings/error messages to users when they fail. Here’s an example of an assertion:
Unit tests require “pulling out” the functions in the module and, potentially, testing them in separate testing workflows.
Integration tests are implicitly done when modules are put together for particular projects, but this will only cover a specific set of input/parameter values and conditions. Therefore, it is ideal to also do solo-module testing and integration tests that capture a range of module setup conditions.
This is time-consuming work, but does pay off in the long-run especially if tests are repeated on a regular basis. For this reason, SpaDES.core::newModule()
creates a tests/
folder in the module folder as a reminder to the developer that they should eventually develop tests for their modules.
show()
. Here’s a tip: start with showMethods("show")
.?showMethods()
– useful to find out what methods of a function you may want to activate debugging for
An example of debugging a more complex SpaDES workflow in Section 14.6
myFun <- function(x, y) {
out <- sum(x, y)
return(out)
}
myFun(runif(20), "A")
myFun <- function(x, y) {
browser()
out <- sum(x, y)
return(out)
}
myFun(runif(20), "A")
out <- simInit(modules = "My_linear_model", paths = list(modulePath = modulePath))
out <- spades(out)
debugonce("time")
time(out) ## then press ENTER to execute each line of code one-by-one
myFunction <- function(x, ...) {
if (!inherits(x, c("numeric", "integer"))) {
stop("x should be a numeric/integer vector")
}
mean(x, ...)
}
myFunction(LETTERS[1:10])
# Error in myFunction(LETTERS[1:10]) : x should be a numeric/integer vector