Examples and use-cases

Rich FitzJohn

2018-11-08

Using a database in testing

This example requires the redux package, which is available on CRAN and can be installed with

install.packages("redux")
docker <- stevedore::docker_client()

redis <- docker$container$run("redis", name = "redis", ports = "6379",
                              detach = TRUE, rm = TRUE)

We now have a Redis server running on 32775

redis$ports()
##   container_port protocol host_ip host_port
## 1           6379      tcp 0.0.0.0     32775

Make a connection to the Redis server:

con <- redux::hiredis(port = redis$ports()$host_port)

and we can communicate with the Redis server:

con$PING()
## [Redis: PONG]

Because this is a brand new container we can write data without worrying about clobbering data that already exists:

con$SET("key", "hello redis")
## [Redis: OK]
con$GET("key")
## [1] "hello redis"

And we can get a fresh copy of Redis by simply starting a new copy of redis

redis$kill()
## NULL
redis <- docker$container$run("redis", name = "redis", ports = "6379",
                              detach = TRUE, rm = TRUE)
con <- redux::hiredis(port = redis$ports()$host_port)
con$KEYS("*")
## list()
redis$kill()
## NULL

The same approach works for other database that might be large or awkward to install, such as Postgres or MySQL.

Testing shiny apps

(this section is not actually run because doing anything with it requires looking in a web browser)

The rocker project has a shiny container that provides an easy to use version of the shiny server. If we have a directory app that contains a shiny application we can map this with:

volumes <- sprintf("%s:%s", normalizePath("app"), "/srv/shiny-server/")

(see the shiny docker image documentation for the destination path.

We can start this with

shiny <- docker$container$run("rocker/shiny", name = "shiny", ports = "3838",
                              volumes = volumes,
                              detach = TRUE, rm = TRUE)

In an interactive session, you can visit the shiny server:

browseURL(sprintf("http://localhost:%s", shiny$ports()$host_port))

Package testing

You can build an image that contains all the bits required to test your package and then run tests using that image. This ensures that you’re running in a totally clean environment (and that you really know what all the dependencies of your package are)

For this we have a Dockerfile, which contains

FROM r-base

RUN apt-get update && \
        apt-get install -yy \
                git \
                libcurl4-openssl-dev \
                libssl-dev \
                qpdf

RUN install2.r --error \
        curl \
        knitr \
        openssl \
        rcmdcheck \
        rmarkdown \
        rcorpora \
        testthat \
        uuid

COPY tester.sh /usr/local/bin
RUN chmod +x /usr/local/bin/tester.sh

ENTRYPOINT ["/usr/local/bin/tester.sh"]

And the script tester.sh (copied into the docker image above) which contains:

lang_output(readLines("tester/tester.sh"), "shell")
## ```shell
## #!/usr/bin/env bash
## set -e
##
## if [[ "$#" -ne 1 ]]; then
##     echo "Expected one argument"
##     exit 1
## fi
## SRC=$1
##
## if echo $SRC | grep -q "^https://"; then
##     DEST=$(mktemp -d)
##     git clone $SRC $DEST
##     SRC=$DEST
## fi
##
## if [ ! -f "$SRC/DESCRIPTION" ]; then
##     echo "Did not find a DESCRIPTION" file
##     exit 1
## fi
##
## PACKAGE=$(grep '^Package:' "$SRC/DESCRIPTION" | \
##                  sed -E 's/^Package:[[:space:]]+//')
## R CMD build $SRC
## PACKAGE_TGZ=$(ls -1tr ${PACKAGE}*gz | tail -n1)
##
## export _R_CHECK_CRAN_INCOMING_=FALSE
## Rscript -e "rcmdcheck::rcmdcheck('$PACKAGE_TGZ', args = c('--as-cran', '--no-manual'))"
## ```
img <- docker$image$build("tester", tag = "richfitz/tester")
## Step 1/6 : FROM r-base
##  ---> e4a158184cc5
## Step 2/6 : RUN apt-get update &&         apt-get install -yy                 git                 libcurl4-openssl-dev                 libssl-dev                 qpdf
##  ---> Using cache
##  ---> ef239ce55ffe
## Step 3/6 : RUN install2.r --error         curl         knitr         openssl         rcmdcheck         rmarkdown         rcorpora         testthat         uuid
##  ---> Using cache
##  ---> 412ea6a1c8a4
## Step 4/6 : COPY tester.sh /usr/local/bin
##  ---> Using cache
##  ---> 079439ae1bae
## Step 5/6 : RUN chmod +x /usr/local/bin/tester.sh
##  ---> Using cache
##  ---> 9d56dca452f9
## Step 6/6 : ENTRYPOINT ["/usr/local/bin/tester.sh"]
##  ---> Using cache
##  ---> 68e1b48c1b2c
## Successfully built 68e1b48c1b2c
## Successfully tagged richfitz/tester:latest

With this image we can then test packages off github:

invisible(docker$container$run(img, "https://github.com/richfitz/ids",
                               rm = TRUE, stream = stdout()))
## E> Cloning into '/tmp/tmp.3r6OVHu5N6'...
## O> * checking for file ‘/tmp/tmp.3r6OVHu5N6/DESCRIPTION’ ... OK
## O> * preparing ‘ids’:
## O> * checking DESCRIPTION meta-information ... OK
## O> * installing the package to build vignettes
## O> * creating vignettes ... OK
## O> * checking for LF line-endings in source and make files and shell scripts
## O> * checking for empty or unneeded directories
## O> * looking to see if a ‘data/datalist’ file should be added
## O> * building ‘ids_1.0.1.tar.gz’
## E>
## O> ── R CMD check ─────────────────────────────────────────────────────────────────
## O>   
─  using log directory ‘/tmp/Rtmp9phSht/file3f69a6c616/ids.Rcheck’
## O> ─  using R version 3.5.1 (2018-07-02)
## O> 
  
─  using platform: x86_64-pc-linux-gnu (64-bit)
## O> ─  using session charset: UTF-8
## O> 
  
─  using options ‘--no-manual --as-cran’
## O> 
  
✔  checking for file ‘ids/DESCRIPTION’
## O> ─  this is package ‘ids’ version ‘1.0.1’
## O>    checking package namespace information ...
  
✔  checking package namespace information
## O>    checking package dependencies ...
  
✔  checking package dependencies (1.2s)
## O>    checking if this is a source package ...
  
✔  checking if this is a source package
## O> ✔  checking if there is a namespace
## O> 
  
✔  checking for .dll and .exe files
## O> ✔  checking for hidden files and directories
## O>    checking for portable file names ...
  
✔  checking for portable file names
## O> ✔  checking for sufficient/correct file permissions
## O> ✔  checking serialization versions
## O>    checking whether package ‘ids’ can be installed ...
  
✔  checking whether package ‘ids’ can be installed (854ms)
## O> 
  
✔  checking installed package size
## O> 
  
   checking package directory ...
  
✔  checking package directory
## O> ✔  checking ‘build’ directory
## O>    checking DESCRIPTION meta-information ...
  
✔  checking DESCRIPTION meta-information
## O> 
  
✔  checking top-level files
## O> ✔  checking for left-over files
## O>    checking index information ...
  
✔  checking index information
## O> 
  
   checking package subdirectories ...
  
✔  checking package subdirectories
## O> 
  
   checking R files for non-ASCII characters ...
  
✔  checking R files for non-ASCII characters
## O> 
  
   checking R files for syntax errors ...
  
✔  checking R files for syntax errors
## O> 
  
   checking whether the package can be loaded ...
  
✔  checking whether the package can be loaded
## O> 
  
   checking whether the package can be loaded with stated dependencies ...
  
✔  checking whether the package can be loaded with stated dependencies
## O> 
  
   checking whether the package can be unloaded cleanly ...
  
✔  checking whether the package can be unloaded cleanly
## O> 
  
   checking whether the namespace can be loaded with stated dependencies ...
  
✔  checking whether the namespace can be loaded with stated dependencies
## O> 
  
   checking whether the namespace can be unloaded cleanly ...
  
✔  checking whether the namespace can be unloaded cleanly
## O> 
  
   checking loading without being on the library search path ...
  
✔  checking loading without being on the library search path
## O> 
  
   checking dependencies in R code ...
  
✔  checking dependencies in R code
## O> 
  
   checking S3 generic/method consistency ...
  
✔  checking S3 generic/method consistency (416ms)
## O> 
  
   checking replacement functions ...
  
✔  checking replacement functions
## O> 
  
   checking foreign function calls ...
  
✔  checking foreign function calls
## O> 
  
   checking R code for possible problems ...
  
✔  checking R code for possible problems (2s)
## O> 
  
   checking Rd files ...
  
✔  checking Rd files
## O> 
  
   checking Rd metadata ...
  
✔  checking Rd metadata
## O> 
  
   checking Rd line widths ...
  
✔  checking Rd line widths
## O> 
  
   checking Rd cross-references ...
  
✔  checking Rd cross-references
## O> 
  
   checking for missing documentation entries ...
  
✔  checking for missing documentation entries
## O> 
  
   checking for code/documentation mismatches ...
  
✔  checking for code/documentation mismatches (347ms)
## O> 
  
   checking Rd \usage sections ...
  
✔  checking Rd \usage sections (541ms)
## O> 
  
   checking Rd contents ...
  
✔  checking Rd contents
## O> 
  
   checking for unstated dependencies in examples ...
  
✔  checking for unstated dependencies in examples
## O> 
  
   checking R/sysdata.rda ...
  
✔  checking R/sysdata.rda
## O> 
  
   checking installed files from ‘inst/doc’ ...
  
✔  checking installed files from ‘inst/doc’
## O> 
  
   checking files in ‘vignettes’ ...
  
   checking files in ‘vignettes’ ... 
  
✔  checking files in ‘vignettes’
## O>    checking examples ...
  
   checking examples ... 
  
✔  checking examples (732ms)
## O> 
  
   checking for unstated dependencies in ‘tests’ ...
  
✔  checking for unstated dependencies in ‘tests’
## O> 
  
   checking tests ...
  
─  checking tests ...
## O> 
  
   Running ‘testthat.R’
  

  
✔  Running ‘testthat.R’
## O> 
  
   checking for unstated dependencies in vignettes ...
  
✔  checking for unstated dependencies in vignettes
## O> 
  
✔  checking package vignettes in ‘inst/doc’
## O> 
  
   checking re-building of vignette outputs ...
  
✔  checking re-building of vignette outputs (1.1s)
## O>
## O>
## O> 
  

── R CMD check results ────────────────────────────────────────── ids 1.0.1 ────
## O> Duration: 12.4s
## O>
## O> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔

(I have cheated here and put all of the dependencies of ids into the docker image via the Dockerfile).

The same approach would work by mounting the package source directory into the container and passing the path (within the container) to $run().