Simple GitLab CI/CD pipeline for testing R package commits
18 March, 2022
Introduction
tl;dr example syntax here.
You do test your code, right? With devtools::test()
? Or maybe R CMD check
if you're feeling frisky?
We can use GitLab's free shared runners to test our packages for us when we make commits. We should be doing this ourselves anyway (see above), but if we forget, this can be a good stopgap. The utility of these pipelines increases if we're doing more advanced things, with a build–test–deploy workflow, so things could get really exciting if you want them to. This post will just look at the basic syntax for getting started with a GitLab pipeline for testing your R packages.
.gitlab-ci.yml
That's the name of the file. You write YAML to describe your pipeline. Create that file in the root path of your package. The easiest way to test these is via GitLab's browser interface, using the pipeline editor.
The first step is to set the Docker image. Using prebuilt images will speed up our pipeline tremendously. You could use the r-base Docker image, but for our purposes, using the rocker/tidyverse image will make things even easier because it comes with devtools installed. (devtools is the R package that everyone (?) uses to build and test R packages. It has a metric shittonne of dependencies, so installing it ourselves from source can literally take ten minutes and you could get into some fun dependency shenanigans. Let's just let rocker sort that out for us.) Anyway, so... this is the first line:
image: rocker/tidyverse
Next, we set the stages. This is where we define what stages of CI/CD we're going to use. We're basically just going to test and install our package; we could split the install and test stages out, but for simplicity let's just keep them together. So we set one stage:
stages:
- test
Do note the two-space indentation. Coming from R where spaces mean literally nothing, YAML is suuuuper strict. At least the pipeline editor will let you know if the syntax is valid or not!
Finally, we define the actual test job. Again, this is pretty straightforward. We could just run devtools::test()
here, but for the sake of completeness let's also run R CMD check
. Note that the script
part is a shell script, so for the CMD check we run it normally as if from the terminal, but for the devtools check, we use the R -e ...
syntax to call R from the shell. I also like to specify --no-manual
because I probably don't (and you might not) have a manual for your R package, so this just removes one potential error.
test-job:
stage: test
script:
# run the R CMD check on the package (this path)
- R CMD check ./ --no-manual
# run the devtools test
- R -e 'devtools::test()'
# install the package
- R -e 'devtools::install()'
And that's pretty much it. When you commit the change in the pipeline editor, scroll up and you'll see that it's checking the pipeline status.
After a few seconds, it'll say that the pipeline is running. On the far right, click "View pipeline".
That'll bring up your pipeline, with all your separate stages available to view. Click on the "test-job"—it may have passed already—and it'll bring up the shell output of the actual test.
So you'll see it installing rocker/tidyverse, running the CMD check, running the devtools test... and hopefully terminating with "Job succeeded" in nice friendly green text. Woo.
Conclusion
We can also split the test and install processes into distinct steps. That might look something like this (in its entirety):
# use the tidyverse rocker image, as it contains devtools preinstalled
image: rocker/tidyverse
# define stages of runner. at the moment,
# just test (no build or deploy).
stages:
- test
- build
# define test job
test-job:
stage: test
script:
# run the R CMD check on the package (this path)
- R CMD check ./ --no-manual
# run the devtools test
- R -e 'devtools::test()'
build-job:
stage: build
script:
# install
- R -e 'devtools::install()'
The world is your oyster, or something.
There's also an implicit assumption here that the pipeline will fail if the devtools::test()
check fails. Fortunately, this is the case. If you have one failing test, the pipeline will fail. So. Happy testing.