Skip to content

Commit

Permalink
run snapshot tests inside docker
Browse files Browse the repository at this point in the history
  • Loading branch information
hillalex committed Oct 6, 2024
1 parent 46ed54f commit e5c95f0
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 9 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/test-snapshots.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Snapshot testing

on:
pull_request:
branches:
- main

env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
snapshot:
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4

- name: Run tests
run: ./tests/snapshots/test-snapshots
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ RoxygenNote: 7.3.1
Imports:
data.table,
forcats,
fs,
instantiate,
logger,
mosaic,
Expand Down
2 changes: 2 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ summarise_draws <- function(dt_in, column_name, by = by) {

build_covariate_lookup_table <- function(data, design_matrix, all_formula_vars) {

p_name <- NULL

if (length(all_formula_vars) == 0) {
return(NULL)
}
Expand Down
23 changes: 18 additions & 5 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,32 @@
# find the executable. But pkgload::load_all does not simulate behaviour of
# src/install.libs.R so here we compile the stan models and move
# them into a place where the pkgload system.file shim can find them

if (pkgload::is_loading()) {

Check warning on line 9 in R/zzz.R

View check run for this annotation

Codecov / codecov/patch

R/zzz.R#L9

Added line #L9 was not covered by tests
# When installation is simulated using load_all, the system.file shim looks
# for files in the local source directory, so here we create a temporary
# 'bin/stan' directory in the source directory so that calls to

# User may not have cmdstan installed yet
if (is.null(cmdstanr::cmdstan_version(error_on_NA = FALSE))) {
packageStartupMessage("Installing cmdstan")
cmdstanr::install_cmdstan()

Check warning on line 14 in R/zzz.R

View check run for this annotation

Codecov / codecov/patch

R/zzz.R#L12-L14

Added lines #L12 - L14 were not covered by tests
} else {
packageStartupMessage(paste("Found cmdstan at path",
cmdstanr::cmdstan_path()))

Check warning on line 17 in R/zzz.R

View check run for this annotation

Codecov / codecov/patch

R/zzz.R#L16-L17

Added lines #L16 - L17 were not covered by tests
}
# When epikinetics installation is simulated using load_all, the system.file
# shim looks for files in the local source directory so here we create a
# temporary 'bin/stan' directory in the source directory so that calls to
# system.file("bin/stan/model.stan", "epikinetics") will resolve correctly
if (nchar(libname) == 1) {
libname <- ""

Check warning on line 24 in R/zzz.R

View check run for this annotation

Codecov / codecov/patch

R/zzz.R#L23-L24

Added lines #L23 - L24 were not covered by tests
}
bin <- file.path(libname, pkgname, "bin")
if (!dir.exists(bin)) {
packageStartupMessage("Creating local bin directory")
dir.create(bin, recursive = TRUE, showWarnings = FALSE)
dir.create(bin, recursive = TRUE, showWarnings = TRUE)

Check warning on line 29 in R/zzz.R

View check run for this annotation

Codecov / codecov/patch

R/zzz.R#L26-L29

Added lines #L26 - L29 were not covered by tests
}
packageStartupMessage("Copying stan files")
bin_stan <- file.path(libname, pkgname, "bin", "stan")
source_path <- file.path(libname, pkgname, "src", "stan")

Check warning on line 33 in R/zzz.R

View check run for this annotation

Codecov / codecov/patch

R/zzz.R#L31-L33

Added lines #L31 - L33 were not covered by tests

fs::dir_copy(path = source_path, new_path = bin, overwrite = TRUE)
instantiate::stan_package_compile(
models = instantiate::stan_package_model_files(path = bin_stan),
Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ an explanation of the input format.

# Installing

This package uses `cmdstanr`, which isn't available on cran, so you will first have to install it as follows:
To interface with [cmdstan](https://mc-stan.org/users/interfaces/cmdstan), this package uses `cmdstanr`, which isn't available on cran, so you will first have to install it as follows:

```
install.packages('cmdstanr', repos = c('https://stan-dev.r-universe.dev', getOption('repos')))
Expand All @@ -32,6 +32,23 @@ You can then install `epikinetics` from GitHub:
remotes::install_github("seroanalytics/epikinetics")
```

## Troubleshooting installation

If you don't already have [cmdstan](https://mc-stan.org/users/interfaces/cmdstan) installed, the `epikinetics` installer will attempt
to install it, which can take a few minutes. If you see errors as part of installation, it is
probably a good idea to try and install `cmdstan` first, for easier debugging. You can
do this using the `cmdstanr` package as follows:

```{r}
cmdstanr::install_cmdstan()
```

Verify the installation is working with

```{r}
cmdstanr::cmdstan_version()
```

# Running in Docker

Alternatively, you can run `epikinetics` via a Docker image, mounting a working directory which contains your input data files:
Expand All @@ -52,6 +69,28 @@ so for this we use an `onLoad` hook which checks whether the package is being lo
and if so, copies compiled models into a local `bin` directory where the `system.file` shim
can access them.

Note that if you are running `devtools::load_all` or `devtool::test` and you don't yet
have `cmdstan` installed, this will trigger an installation of `cmdstan` which can take
a few minutes.

## Testing

Most tests are run with

```{r}
devtools::test()
```

For snapshot testing of stan model outputs, we need the outputs to be exactly
reproducible. As well as setting a seed, this requires the machine environment
to be exactly the same, so we run these inside a Docker container, via a bash script:

```{shell}
./tests/snapshots/test-snapshots
```

This involves recompiling the model, so takes a while to run.

## Docker
To build a Docker image, run `docker/build`.
To push a new image to Dockerhub, `docker/push`. An image is built and pushed
Expand Down
15 changes: 15 additions & 0 deletions snapshot-tests.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM rocker/tidyverse:4

RUN apt-get update
RUN apt-get install libtbb-dev -y
RUN Rscript -e "install.packages('cmdstanr', repos = c('https://stan-dev.r-universe.dev', getOption('repos')))"
RUN Rscript -e "cmdstanr::install_cmdstan()"

WORKDIR /epikinetics
COPY DESCRIPTION /epikinetics

RUN Rscript -e "devtools::install_deps()"

COPY . /epikinetics
COPY tests/snapshots/test-snapshots.R /epikinetics/tests/testthat
COPY tests/snapshots/_snaps /epikinetics/tests/testhat
File renamed without changes.
5 changes: 5 additions & 0 deletions tests/snapshots/test-snapshots
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -ex

docker build . -f snapshot-tests.Dockerfile -t epikinetics-test
docker run epikinetics-test Rscript -e "devtools::test(filter='snapshot')"
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
instantiate::stan_package_compile(
models = instantiate::stan_package_model_files(path = "/epikinetics/bin/stan"),
cpp_options = list(stan_threads = TRUE),
stanc_options = list("O1"),
force_recompile = TRUE
)
dat <- data.table::fread(system.file("delta_full.rds", package = "epikinetics"))
mod <- biokinetics$new(data = dat, covariate_formula = ~0 + infection_history)
suppressWarnings({delta <- mod$fit(parallel_chains = 4,
delta <- mod$fit(parallel_chains = 4,
iter_warmup = 50,
iter_sampling = 100,
threads_per_chain = 4,
seed = 100)})
seed = 100)

local_edition(3)

Expand Down

0 comments on commit e5c95f0

Please sign in to comment.