Question: How can I get testthat to run in an environment that loads my package, rather than inherits from my package?
Background: The testthat package runs tests "in an environment that inherits from the package's namespace environment" [see the docs for test_check]. This means it doesn't make sure I've done my exports correctly, and that's bitten me several times.
For example, I have the following code in my package:
##' The foo() method
##' #param x object
##' #export
foo <- function(x)
UseMethod('foo')
##' #rdname foo
foo.data.frame <- function(x) {
message("foo data.frame")
}
##' #rdname foo
foo.default <- function(x) {
message("foo default")
}
And the following in my tests:
x <- 5:13
foo(x)
That tests just fine. But if a user installs the package, they'll get this error:
Error in UseMethod("foo") :
no applicable method for 'foo' applied to an object of class "c('integer', 'numeric')"
The solution is to put #exports declarations for the two methods, but it's a bummer that the tests didn't catch that.
I would much prefer to run all my tests from the point of view of a user, because I tend to screw up my exporting sometimes. Perhaps an option could be added to testthat:::run_tests that selects which behavior is desired?
Use test_dir. I don't use test_check for this exact reason. In "/tests/run-tests.R" (file name doesn't matter, it just needs to be in that directory and end in ".R") write:
library(testthat)
library(<my package>) # insert actual package name here
test_dir('testthat') # assuming your tests are in "tests/testthat"
Then, in order to run your tests:
setwd("<pkg dir>/tests")
source("run-tests.R")
Or from the command line:
cd <pkg-dir>/tests
Rscript run-tests.R
Or do R CMD build and R CMD check to run the tests that way.
The setwd is not strictly necessary if your tests don't care about working directory. However, if they do doing so will replicate the working directory set by R CMD check.
Related
I have developed a local package with 150+ functions and would like to build a dictionary of input/output formats (mostly nested tibbles) over all functions.
Since I have testthat tests written, I would like to decorate every function of the package with:
write_in_out_formats_decorator <- function(f){
wrapper <- function(...) {
res <- f(...)
# Write arguments and results formats in some files
return(res)
}
return(wrapper)
}
Then I would run my tests and retrieve all the format files.
My problem is that I cannot figure out how to decorate my functions inside a testthat context.
I tried the package tinsel : https://github.com/nteetor/tinsel
but the source_decoratees function does not seem to do the trick since testthat runs tests using the functions from package namespace and source functions are not taken into account.
So I thought about something in a setup.R before tests run:
for (funcname in package_funcnames) {
func <- get(funcname, envir = asNamespace(my_package))
# decorate my function here
decorated_func <- func ...
assignInNamespace(funcname, decorated_func, ns = my_package)
}
I have tried several things like creating a helper namespace to store my original functions and have:
decorated_func <- write_in_out_formats_decorator(helper_ns::func)
No success so far... I feel a bit lost.
Would someone have an idea of how this can be done?
I am trying to create an R package that uses functions from another package (gamlss.tr).
The function I need from the dependency is gamlss.dist::TF (gamlss.dist is loaded alongside gamlss.tr), but it is referenced in my code as simply TF within a call to gamlss.tr::gen.trun.
When I load gamlss.tr manually with library(), this works. However, when I rely on the functions of the dependency automatically being imported by my package through #import, I get an "object not found" error as soon as TF is accessed.
My attempt to be more explicit and reference the function I need as gamlss.dist::TF resulted in a different error ("unexpected '::'").
Any tips on how to use this function in my package would be much appreciated!
The code below reproduces the problem if incorporated into a clean R package (as done in this .zip), built and loaded with document("/path/to/package"):
#' #import gamlss gamlss.tr gamlss.dist
NULL
#' Use GAMLSS
#'
#' Generate a truncated distribution and use it.
#' #export
use_gamlss <- function() {
print("gen.trun():")
gamlss.tr::gen.trun(par=0,family=TF)
#Error in inherits(object, "gamlss.family") : object 'TF' not found
#gamlss.tr::gen.trun(par=0,family=gamlss.dist::TF)
#Error in parse(text = fname) : <text>:1:1: unexpected '::'
y = rTFtr(1000,mu=10,sigma=5, nu=5)
print("trun():")
truncated_dist = gamlss.tr::trun(par=0,family=TF, local=TRUE)
model = gamlss(y~1, family=truncated_dist)
print(model)
}
use_gamlss() will only start working once a user calls library(gamlss.tr).
This is due to bad design of gamlss.tr in particular the trun.x functions (they take character vectors instead of family objects / they evaluate everything in the function environment instead of the calling environment).
To work around this, you have to make sure that gamlss.distr is in the search path of the execution environment of gamlss.tr functions (This is why ## import-ing it in your package does not help: it would need to be #' #import-ed in gamlss.tr).
This can be achieved by adding it to Depends: of your package.
If you want to avoid that attaching your package also attaches gamlss.distr, you could also add the following at the top of use_gamlss:
nsname <-"gamlss.dist"
attname <- paste0("package:", nsname)
if (!(attname %in% search())) {
attachNamespace(nsname)
on.exit(detach(attname, character.only = TRUE))
}
This would temporarily attach gamlss.dist if it is not attached already.
You can read more on namespaces in R in Hadley Wickham's "Advanced R"
I am trying to build a package that extends another package. However at its most basic level I am doing something wrong. I build a simple example that presents the same issue:
I have two packages, packageA and packageB. packageA has a single R file in the R folder that reads:
local.env.A <- new.env()
setVal <- function()
{
local.env.A$test <- 1
}
getVal <- function()
{
if(!exists("test", envir = local.env.A)) stop("test does not exist")
return(local.env.A$test)
}
For packageB I have the following single R file in the R folder:
# refers to package A
setVal()
getValinA <- function()
{
return(getVal())
}
I want both packageA and packageB to be available for end users, therefore I set packageB to depend on packageA (in the description file). When packageB is loaded, e.g. by means of library(packageB) I expect it to run setVal() and thus set the test value. However, if I next try to get the value that was set by means of getValinA(), it throws me the stop:
> library(packageB)
Loading required package: PackageA
> getValinA()
Error in getVal() : test does not exist
I am pretty sure it is related to environments, but I am not sure how. Please help!
With thanks to #Roland. The answer was very simple. I was under the impression (assumptions assumptions assumptions!) that when you perform library(packageB) it would load all the actions within it, in my case perform the setVal() function. This is however not the case. If you wish this function to be performed you need to place this within the function .onLoad:
.onLoad <- function(libname, pkgname)
{
setVal()
}
By convention you place this .onload function in an R file called zzz.R. Reason being that if you do not specifically collate your R scripts it will load alphabetically, and it makes sense to perform your actions when at least all the functions in your package are loaded.
I am working on two separate, but related packages.
Among a bunch of other functions flowerR contains the following generic function show_match:
show_match <- function(x, ...) {
UseMethod("show_match")
}
#' #export
#' #rdname show_match
show_match.flowerClass <- function(x, ...) {
# do a bunch of stuff and generate some output
}
Package flowerR is installed locally on my machine, in "C:/my_R_packages", it loads fine into R (library(flowerR) and works fine by itself.
In package treeR I want to build on this generic method for a second class:
#' #export
#' #rdname show_match
show_match.treeClass <- function(x, ...) {
# do a bunch of stuff and generate some output
}
In order for R to be able to find the generic function, I included
Depends: flowerR
in the treeR DESCRIPTION file. However, when I check treeR (using devtools::check), I receive the error: Error : package 'flowerR' required by 'treeR' could not be found. This boggles me, because R has no problem attaching flowerR when using library(flowerR).
How can I make this work?
BTW, what does work fine is:
library(flowerR)
class(x) # [1] "treeData"
show_match(x) # works fine, with flowerR loaded, the method in treeR runs perfeclty
so, when flowerR is loaded into R interactively, all works well. Now, the question is how to make this happen when flowerR is not attached by hand.
I am developing a package which has the function forecast.myclass. I want that function to work nicely with forecast package. I.e. when forecast package is loaded the code forecast(object) should call forecast.myclass from my package.
Since I need only generic definition of forecast from the package forecast, and I do not use any other function from the package forecast I am reluctant to include it in the Depends. So I define the generic in my package in the following way:
##'
##' #export
forecast <- function(object,...) UseMethod("forecast")
##' #rdname forecast.midas_r
##' #method forecast midas_r
##' #export
forecast.midas_r <- function(object,newdata=NULL,method=c("static","dynamic"),insample=get_estimation_sample(object),...) {
Now everything works as expected when package forecast is not loaded. But when I load package forecast, then forecast.midas_r is not called, when doing forecast(object) where object is of class midas_r. How should I solve this problem?
I'm not sure there's an easy solution to this. As others have said it's probably easiest to use Depends to get around this, rather than redefining a generic method.
Here's a simple example which works for me. It's largely the same as your solution, but declaring #export means you won't need to manually update the NAMESPACE file.
##' #name mean
##' #export mean.newClass
##'
##' #method mean newClass
##'
##' #title mean for \code{newClass} object
##' #param x A \code{newClass} object
##' #param ... Additional arguments
##'
mean.newClass <- function(x, ...){
stopifnot(class(x)=="newClass")
return(42)
}
Then package.skeleton("newPkg"). Put file mean.R with the above contents in the directory /R of the package.
Ensure you're in the directory 1 level below, then
roxygenize("newPkg", roxygen.dir="newPkg", copy.package=F, unlink.target=F)
Now
library(devtools)
dev_mode(on=TRUE) ### don't want to have to uninstall the package later
install_local("newPkg")
library(newPkg)
x <- c(1,2)
class(x) <- "newClass"
stopifnot(mean(x)==42)
stopifnot(mean(unclass(x))==1.5)
I realize mean is a function in base but I have tested this for modifying generic functions elsewhere to give them a new method, so it should extend to your more general case also.
The problem here is that your definition of the forecast generic is masking the definition from the forecast package, and your method is associated with your generic rather than the forecast package generic; this is just a complicated instance of two packages defining functions of the same name. The solution is to bite the bullet and Depend: on forecast, or when at the command line and both your package and forecast are attached fully resolve the function mypackage::forecast(), or Import: forecast but not make the forecast generic available to the end user except by having them require(forecast) (this might be appropriate if forecast functionality were somehow peripheral to your package, e.g., plotting in 3D when plotting in 2D was sufficient).
For what it's worth, an S4 method in PkgB defined and exported on an imported S4 generic from PkgA implicitly exposes the S4 generic to the user, so the generic is available even if Imports: PkgA is specified in the DESCRIPTION file of PkgB.
One possible solution is to forcefully export forecast.midas_r. This means manually updating NAMESPACE with export(forecast.midas_r) everytime after check("yourpackagename").
What this does is make forecast.midas_r visible for package forecast. If forecast.midas_r is not exported, and exists only in the namespace of the package, then when package forecast is loaded, the generic forecast is overwritten with the identical function from package forecast. So when forecast is invoked on unknown object, R looks up for corresponding methods in package forecast and in general workspace. Since forecast.midas_r is a private method R does not find it and produces an error.
This is not a perfect solution, since you need to manually update NAMESPACE, but it is a solution nevertheless.
This github issue was very helpful for me.
Following the patterns of {sf}:
Don't export your functions in your namespace
Copy the register_s3_method() function definition in a new R script or the same that has your methods
copy the scaffolding for registering all methods and repeat the body for all of the methods you may have in your package
register_all_s3_methods = function() {
register_s3_method("pkg", "function", "class")
}
In a new R script (preferably zzz.R), have an .onLoad() hook containing
.onLoad = function(libname, pkgname) {
register_all_s3_methods()
}
Redocument your package
devtools::load_all()