Skip to contents

In this vignette we will use VerR to compare two different version of an R package. We will use the dummyPkg package, which is a dummy package that was created for this purpose. The package has two versions, 0.1.1 and 0.1.2, and we will compare them using VerR. This package is available through GitHub, via leopoldguyot/dummyPkg.

1. Setup your environments

Create a first environment

To start using VerR, we need to create a new environment. To do so, we will use the envCreate function. This function will create a new environment in the current working directory, and will populate it with the specified packages. Note that a new environment is just a new directory within .envs/ that contains a renv project.

To specify the packages that we want to install in the environment, we have multiple options:

  • pkg: Install the latest version of the package from CRAN.
  • pkg@version: Install a specific version of the package from CRAN.
  • username/repo: Install the package from GitHub.
    You can also specify the commit with username/repo@commitId.
  • bioc::pkg: Install the package from Bioconductor.

Note that to install a specific version of a Bioconductor package, it is recommended to install it via GitHub.

In our case we will use the GitHub option to install the dummyPkg package using his sleep1 branch.

library(VerR)

envCreate("sleep1Env", "leopoldguyot/dummyPkg@sleep1")
#> - The project is out-of-sync -- use `renv::status()` for details.
#> # Downloading packages -------------------------------------------------------
#> - Downloading dummyPkg from GitHub ...          OK [1 Kb in 0.41s]
#> Successfully downloaded 1 package in 0.47 seconds.
#> 
#> The following package(s) will be installed:
#> - dummyPkg [leopoldguyot/dummyPkg@sleep1]
#> These packages will be installed into "~/work/VerR/VerR/vignettes/.envs/sleep1Env/renv/library/linux-ubuntu-noble/R-4.5/x86_64-pc-linux-gnu".
#> 
#> # Installing packages --------------------------------------------------------
#> - Installing dummyPkg ...                       OK [built from source and cached in 1.0s]
#> Successfully installed 1 package in 1 second.
#> Installed packages in environment: /home/runner/work/VerR/VerR/vignettes/.envs/sleep1Env
#> - The project is out-of-sync -- use `renv::status()` for details.
#> The following package(s) will be updated in the lockfile:
#> 
#> # GitHub ---------------------------------------------------------------------
#> - dummyPkg   [* -> leopoldguyot/dummyPkg@sleep1]
#> 
#> # RSPM -----------------------------------------------------------------------
#> - renv       [* -> 1.1.5]
#> 
#> The version of R recorded in the lockfile will be updated:
#> - R          [* -> 4.5.1]
#> 
#> - Lockfile written to "~/work/VerR/VerR/vignettes/.envs/sleep1Env/renv.lock".
#> Updated lockfile in environment: .envs/sleep1Env

Once the environment is created, we can verify that it was correctly initialized. To do so, we can use the envList function. It will list all the environments present in the .envs/ directory.

envList()
#> [1] "sleep1Env"

To obtain more details about this environment you can also use the envInfo function which will display extra information, like the installed packages in the environment.

envInfo("sleep1Env", pkgInfo = TRUE)
#> Environment Name: sleep1Env
#> Path: .envs/sleep1Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#>   - dummyPkg (Version: 0.1.1, Source: GitHub)
#>   - renv (Version: 1.1.5, Source: Repository)

Extract the environment’s lockFile

To save your environment configuration you can export his lockFile to a target location, this will allow to restore this environment later. See ?renv::snapshot for more information on lockFiles. To export this lockFile you can use the lockFileExport function. By default it will export the lockFiles of all available environments and store these files within the exportedLockFiles/ directory inside your current working directory. You can also choose to select specific environments and target paths for the lockFiles.

lockFileExport()
#> Exported lockfile from sleep1Env to exportedLockFiles/sleep1Env_lockFile.lock

The lockFile is now available in the exportedLockFiles/ directory:

list.files("exportedLockFiles")
#> [1] "sleep1Env_lockFile.lock"

Create an environment from a lockFile

We will now use the exported lockFile to create a new environment. This environment will therefore be a clone of the sleep1Env environment. To do so, we will again use the envCreate function, but this time with the lockFile argument.

envCreate("sleep2Env", lockFile = "exportedLockFiles/sleep1Env_lockFile.lock")
#> - None of the packages recorded in the lockfile are currently installed.
#> The following package(s) will be updated:
#> 
#> # GitHub ---------------------------------------------------------------------
#> - dummyPkg   [* -> leopoldguyot/dummyPkg@sleep1]
#> 
#> # Installing packages --------------------------------------------------------
#> - Installing dummyPkg ...                       OK [linked from cache]
#> Restored environment from lockfile: exportedLockFiles/sleep1Env_lockFile.lock
#> - The lockfile is already up to date.
#> Updated lockfile in environment: .envs/sleep2Env

Again we can check, that the environment was correctly created.

envInfo("sleep2Env", pkgInfo = TRUE)
#> Environment Name: sleep2Env
#> Path: .envs/sleep2Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#>   - dummyPkg (Version: 0.1.1, Source: GitHub)
#>   - renv (Version: 1.1.5, Source: Repository)

Update your environment

Currently we have two environments, sleep1Env and sleep2Env, that contain the same version of the dummyPkg package. We will now update the sleep2Env environment to use another version of the dummyPkg package. To do so, we will use the envInstallPackage function.

envInstallPackage(
    package = "leopoldguyot/dummyPkg@sleep2",
    envName = "sleep2Env"
)
#> # Downloading packages -------------------------------------------------------
#> - Downloading dummyPkg from GitHub ...          OK [1 Kb in 0.38s]
#> Successfully downloaded 1 package in 0.44 seconds.
#> 
#> The following package(s) will be installed:
#> - dummyPkg [leopoldguyot/dummyPkg@sleep2]
#> These packages will be installed into "~/work/VerR/VerR/vignettes/.envs/sleep2Env/renv/library/linux-ubuntu-noble/R-4.5/x86_64-pc-linux-gnu".
#> 
#> # Installing packages --------------------------------------------------------
#> - Installing dummyPkg ...                       OK [built from source and cached in 1.0s]
#> Successfully installed 1 package in 1 second.
#> - The project is out-of-sync -- use `renv::status()` for details.
#> The following package(s) will be updated in the lockfile:
#> 
#> # GitHub ---------------------------------------------------------------------
#> - dummyPkg   [ver: 0.1.1 -> 0.1.2; ref: sleep1 -> sleep2; sha: 2916e807 -> 08efd32f]
#> 
#> - Lockfile written to "~/work/VerR/VerR/vignettes/.envs/sleep2Env/renv.lock".
#> Updated lockfile in environment: .envs/sleep2Env

To be sure that the package was correctly updated, we can check the installed packages for both environments.

envInfo(pkgInfo = TRUE)
#> Environment Name: sleep1Env
#> Path: .envs/sleep1Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#>   - dummyPkg (Version: 0.1.1, Source: GitHub)
#>   - renv (Version: 1.1.5, Source: Repository)
#> Environment Name: sleep2Env
#> Path: .envs/sleep2Env
#> Size: 2.98 MB
#> Lockfile: Present
#> Packages Installed: 2
#> Packages:
#>   - dummyPkg (Version: 0.1.2, Source: GitHub)
#>   - renv (Version: 1.1.5, Source: Repository)

As expected, the sleep2Env environment now contains the dummyPkg package version 0.1.2 while the sleep1Env environment stills contain the version 0.1.1.

2. Run expression within the environments

Now that we have two environments with different versions of the dummyPkg package, we can compare them using the runInEnv function. This function allows us to run an expression in a specific environment.

We will compare the sleepy function, which is a simple function that prints a message and sleeps for a certain amount of time. In the sleep1Env environment, the function sleeps for 1 second, while in the sleep2Env environment, it sleeps for 2 seconds.

# version 0.1.1, x = 1
# version 0.1.2, x = 2
sleepy <- function() {
    message("Sleeping for x seconds")
    Sys.sleep(x)
}
runInEnv(
    expr = {
        library(dummyPkg)
        sleepy()
        return(packageVersion("dummyPkg"))
    },
    envName = envList()
)
#> Running expression in environment: sleep1Env
#> Warning in normalizePath(result_file, winslash = "/"):
#> path[1]="/tmp/Rtmp74nUvJ/result.rds": No such file or directory
#> Warning in normalizePath(result_file, winslash = "/"):
#> path[1]="/tmp/Rtmp74nUvJ/result.rds": No such file or directory
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Running expression in environment: sleep2Env 
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> $sleep1Env
#> [1] '0.1.1'
#> 
#> $sleep2Env
#> [1] '0.1.2'

The first thing to note is that any text output generated during the evaluation of the expression is displayed in the console. In this case, for the sleep1Env environment, the function prints Sleeping for 1 seconds, while for the sleep2Env environment, it prints Sleeping for 2 seconds.

The runInEnv function when called on multiple environments returns a named list, where the names correspond to the environment names, and the values are the return values of the evaluated expression. As expected, the sleep1Env environment returns the version 0.1.1, while the sleep2Env environment returns the version 0.1.2.

Run expression in parallel

noPar <- system.time(runInEnv(
    expr = {
        library(dummyPkg)
        sleepy()
        return(packageVersion("dummyPkg"))
    },
    envName = envList(),
    parallel = FALSE
))
#> Running expression in environment: sleep1Env 
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Running expression in environment: sleep2Env 
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds

par <- system.time(runInEnv(
    expr = {
        library(dummyPkg)
        sleepy()
        return(packageVersion("dummyPkg"))
    },
    envName = envList(),
    parallel = TRUE
))

noPar
#>    user  system elapsed 
#>   0.928   0.777   4.551
par
#>    user  system elapsed 
#>   0.008   0.006   4.415

4. Manipulate file through the environments

We will now explore how to send files to the environments, and also how to remove them. For this example we will use an R script that contains code to measure execution time of the sleepy function. Here is the code contained in the script:

library("dummyPkg")

executionTimes <- replicate(3, {
    startTime <- Sys.time()
    sleepy()
    endTime <- Sys.time()
    as.numeric(difftime(endTime, startTime, units = "secs"))
})

executionTimes

We would want to run this script in both environments to compare the execution times. First, we need to copy this script in each environment. We use the envCopyTo function to copy the script to the environments.

scriptPath <- system.file("scripts",
    "sleepyBenchmark.R",
    package = "VerR"
)
envCopyTo(
    sourcePath = scriptPath,
    envName = envList(),
    targetPath = "scripts/sleepyBenchmark.R"
)
#> Copied /home/runner/work/_temp/Library/VerR/scripts/sleepyBenchmark.R to .envs/sleep1Env/scripts/sleepyBenchmark.R
#> Copied /home/runner/work/_temp/Library/VerR/scripts/sleepyBenchmark.R to .envs/sleep2Env/scripts/sleepyBenchmark.R

To verify that the copy was successful, we can use the fileInfo parameter from the envInfo function. This will display a file tree of each environment:

envInfo(fileInfo = TRUE)
#> Environment Name: sleep1Env
#> Path: .envs/sleep1Env
#> Size: 2.98 MB
#> Lockfile: Present
#> File Tree:
#> .envs/sleep1Env
#> └── scripts
#>     └── sleepyBenchmark.R
#> Environment Name: sleep2Env
#> Path: .envs/sleep2Env
#> Size: 2.98 MB
#> Lockfile: Present
#> File Tree:
#> .envs/sleep2Env
#> └── scripts
#>     └── sleepyBenchmark.R

5. Run a script in the environments

We will now run the script in both environments using the runInEnv function. Using source() to run the script will return the last value of the script. In this case, it will return the execution times of the sleepy function.

result <- runInEnv(
    expr = {
        result <- source("scripts/sleepyBenchmark.R")
        result$value
    }
)
#> Running expression in environment: sleep1Env 
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Running expression in environment: sleep2Env 
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds

Lets now check the results:

result
#> $sleep1Env
#> [1] 1.001483 1.001204 1.001260
#> 
#> $sleep2Env
#> [1] 2.002466 2.002234 2.002316

lapply(result, mean)
#> $sleep1Env
#> [1] 1.001316
#> 
#> $sleep2Env
#> [1] 2.002339

As expected, the average execution time for the sleep1Env environment is approximately 1 second, while for the sleep2Env environment, it is approximately 2 seconds. This confirms that the sleepy function behaves as intended in both versions of the dummyPkg package.

6. Benchmark in environments

VerR provides an easy way to benchmark the execution time of an expression in multiple environments. The benchInEnv function takes an expression and measures the execution time of this expression in each environment. The setup parameter allow to evaluate R code before the evaluation of the benchmarked expression, this is particularly useful to load packages or to retrieve data. You can also provide the rep argument to specify the number of replications to perform. The function will return a named list with the execution times for each environment.

res <- benchInEnv(
    expr = {
        sleepy()
    },
    envName = envList(),
    rep = 3,
    setup = {
        library(dummyPkg)
    }
)
#> 
#> Benchmarking expression in environment: sleep1Env 
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> Sleeping for 1 seconds
#> 
#> Benchmarking expression in environment: sleep2Env 
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds
#> Sleeping for 2 seconds

boxplot(time ~ envName, data = res)

Cleaning

envDelete(force = TRUE)
#> Cleared environment: sleep1Env
#> Cleared environment: sleep2Env
#> Removed '.envs' directory as no environments are left.
unlink("exportedLockFiles",
    recursive = TRUE,
    force = TRUE
)