Use CircleCI for R Projects

October 18, 2017 by Hiroaki Yutani

Why CircleCI? Yes, I know using Travis CI is this easy, thanks to devtools package:

devtools::use_travis()

Travis CI is OK most of the time. Still, CircleCI has some advantages:

  • arbitrary Docker images
  • cool test summaries

Arbitrary Docker images

Compared to Travis CI, CircleCI allows the users to use any docker images. For example, my WIP package which provides DBI-compatible interface to Redash uses these images:

     docker:
       - image: rocker/tidyverse:latest

       - image: redis:3.0-alpine

       - image: postgres:9.5.6-alpine

       - image: redash/redash:latest
         command: [server]
         environment:
           PYTHONUNBUFFERED: 0
           REDASH_LOG_LEVEL: "INFO"
           REDASH_REDIS_URL: "redis://localhost:6379/0"
           REDASH_DATABASE_URL: "postgresql://postgres@localhost/postgres"
           REDASH_COOKIE_SECRET: veryverysecret
           REDASH_WEB_WORKERS: 4

       - image: redash/redash:latest
         command: [scheduler]
         environment:
           PYTHONUNBUFFERED: 0
           REDASH_LOG_LEVEL: "INFO"
           REDASH_REDIS_URL: "redis://localhost:6379/0"
           REDASH_DATABASE_URL: "postgresql://postgres@localhost/postgres"
           QUEUES: "queries,scheduled_queries,celery"
           WORKERS_COUNT: 2

(https://github.com/yutannihilation/Redashr/blob/71b525872c0c7b7f7d7cf8f3eaee8ad4d869b89e/.circleci/config.yml#L82-L110)

While Redis and PostgresSQL services are available in Travis CI, users have to compile Redash, which takes some time. It tends to fail again and again due to lack of commands or library needed for compiling. Sigh.

Though Travis can cache the setup once it succeeds, it is good if we can save time to setup testing environment by using existing Docker images.

Test summaries

CircleCI displays the test summary in this pretty way:

(https://circleci.com/gh/yutannihilation/testthatJunitRporterTest/25)

This is cool as I don’t need to dig raw outputs anymore. This is possible by JunitReporter, which will be introduced the next version (v2.0.0?) of testthat package.

All I had to do was two steps.

First, pass JunitReporter instance with the location of the result XML file to test_check() in tests/testthat.R:

library(testthat)
library(mypackage)

test_check("mypackage",
           reporter = JunitReporter$new(file = "junit_result.xml"))

Second, specify store_test_result on config.yml for CircleCI:

    - store_test_results:
        path: /path/to/tests/
        when: always

So simple!

Sample YAML file

The bellow is the CircleCI setting I tried to do roughly the same as what Travis CI does. If you have some suggestions, please let me know!

defaults: &steps
  steps:
    - checkout

    ## setup -------------------------------

    - run:
        name: Set environmental variables
        command: |
          Rscript --vanilla \
            -e 'dsc <- read.dcf("DESCRIPTION")' \
            -e 'cat(sprintf("export PKG_TARBALL=%s_%s.tar.gz\n", dsc[,"Package"], dsc[,"Version"]))' \
            -e 'cat(sprintf("export RCHECK_DIR=%s.Rcheck\n", dsc[,"Package"]))' \
            >> ${BASH_ENV}

    ## install dependencies ------------------

    - run:
        name: Install devtools and dependencies
        command: |
          Rscript \
            -e 'if (!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools")' \
            -e 'devtools::install_deps(dependencies = TRUE)'

    ## build and test -----------------

    - run:
        name: Build package
        command: R CMD build .

    - run:
        name: Check package
        command: R CMD check "${PKG_TARBALL}" --as-cran --no-manual
    - run:
        name: Check failures
        command: |
          Rscript -e "message(devtools::check_failures(path = '${RCHECK_DIR}'))"
          # warnings are errors
          # - run: if grep -q -R "WARNING" "${RCHECK_DIR}/00check.log"; then exit 1; fi

    ## store artifacts -----------------

    - run:
        command: mv ${RCHECK_DIR} /tmp/Rcheck
        when: always
    - store_test_results:
        path: /tmp/Rcheck/tests/
        when: always
    - store_artifacts:
        path: /tmp/Rcheck
        when: always

version: 2
jobs:
  "r-release":
     docker:
       - image: rocker/tidyverse:latest
     <<: *steps

  "r-devel":
     docker:
       - image: rocker/tidyverse:devel
     <<: *steps

workflows:
  version: 2
  build_and_test:
    jobs:
      - "r-release"
      - "r-devel"