tests/testthat/test-qs.R

context("qs cache file format")

skip_if_not_installed("qs")

qsCacheFileFormat <- function() {
  config <- .read.config()
  config$cache_file_format <- "qs"
  .save.config(config)
}

test_that("cached variable names are assessed correctly", {
  test_project <- tempfile("test_project")
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)
  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  var_to_cache <- "cache.qs"
  test_data <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))
  assign(var_to_cache, test_data, envir = .TargetEnv)

  cache(var_to_cache)

  expect_identical(
    .cached.variables(),
    var_to_cache
  )

  tidy_up()
})

#### from test-cache.R ####
test_that('re-caching is skipped when a cached variable hasnt changed', {
  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  var_to_cache <- "xxxx"
  test_data <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))
  assign(var_to_cache, test_data, envir = .TargetEnv)

  # Create initial cached version
  cache(var_to_cache, CODE = NULL, depends = NULL)
  initial_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime

  # wait two seconds
  Sys.sleep(2)

  # Remove it from Global Environment
  rm(list=var_to_cache, envir = .TargetEnv)

  # Load up from cache and attempt to re-cache
  suppressMessages(load.project())
  expect_message(cache(var_to_cache, CODE = NULL, depends = NULL),
                 "Skipping cache update for")

  # Check that modification time hasn't changed
  new_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime

  expect_equal(initial_mtime, new_mtime)

  tidy_up()
})

test_that('re-caching is done again when a cached variable has changed', {
  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  var_to_cache <- "xxxx"
  test_data <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))
  assign(var_to_cache, test_data, envir = .TargetEnv)

  # Create initial cached version
  cache(var_to_cache, CODE = NULL, depends = NULL)
  initial_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime

  # wait two seconds
  Sys.sleep(2)

  # Remove it from Global Environment
  rm(list=var_to_cache, envir = .TargetEnv)

  # Load up from cache
  suppressMessages(load.project())

  # change the variable and attempt to re-cache
  test_data2 <- data.frame(Names=c("aaa", "b", "c"), Ages=c(20,30,40))
  assign(var_to_cache, test_data2, envir = .TargetEnv)

  expect_message(cache(var_to_cache, CODE = NULL, depends = NULL),
                 "Updating existing cache entry from global environment")

  # Check that modification time has changed
  new_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime
  expect_false(isTRUE(all.equal(initial_mtime, new_mtime)))

  # Reload and check that re-cached value is different
  rm(list=var_to_cache, envir = .TargetEnv)
  suppressMessages(load.project())

  expect_false(isTRUE(all.equal(get(var_to_cache, envir = .TargetEnv) , test_data)))

  tidy_up()
})

test_that('re-caching fails with correct message if cached variable is not in global env', {
  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  var_to_cache <- "xxxx"
  test_data <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))
  assign(var_to_cache, test_data, envir = .TargetEnv)

  # Create initial cached version
  cache(var_to_cache, CODE = NULL, depends = NULL)
  initial_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime

  # wait two seconds
  Sys.sleep(2)

  # Remove it from Global Environment
  rm(list=var_to_cache, envir = .TargetEnv)

  # Load up from cache
  suppressMessages(load.project())

  # And remove it from Global Environment again
  rm(list=var_to_cache, envir = .TargetEnv)

  expect_message(cache(var_to_cache, CODE = NULL, depends = NULL),
                 "Unable to update cache for")

  # Check that modification time hasn't changed
  new_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime
  expect_equal(initial_mtime, new_mtime)

  tidy_up()
})

test_that('re-caching a variable created from CODE only happens if code changes, not comments or white space', {
  skip_if_not_installed("formatR")
  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  var_to_cache <- "xxxx"
  test_data <- data.frame(Names=c("a", "b", "c"),
                          Ages=c(20000000,300,400))

  # Create a cached version created from CODE
  expect_message(cache(var_to_cache, depends = NULL, CODE = {
    data.frame(Names=c("a", "b", "c"),
               Ages=c(200,300,400))
  }),
  "Creating cache entry from CODE")

  initial_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime

  # wait two seconds
  Sys.sleep(2)

  # Remove it from Global Environment (rm will fail if it's not created from CODE)
  expect_error(rm(list=var_to_cache, envir = .TargetEnv), NA)

  # Load up from cache again
  suppressMessages(load.project())

  # re-cache, this time adding some comments and whitespace, but not changing code
  # should skip re-caching
  expect_message(cache(var_to_cache, depends = NULL, CODE = {
    #  New comments add in
    data.frame(Names=c("a", "b", "c"),
               Ages=c(200,300,400))    # but code remains the same

    # extra new lines added for good measure
  }),
  "Skipping cache update for ")

  # Check that modification time hasn't changed
  new_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime

  expect_equal(initial_mtime, new_mtime)

  # re-cache agin, keeping the comments and whitespace, but changing the code
  expect_message(cache(var_to_cache, depends = NULL, CODE = {
    #  New comments add in
    data.frame(Names=c("a", "b", "c"),
               Ages=c(20000000,300,400))    # but code remains the same

    # extra new lines added for good measure
  }),
  "Updating existing cache entry from CODE")

  # Check that modification time has also changed
  new_mtime <- file.info(file.path('cache', paste0(var_to_cache, ".qs")))$mtime
  expect_false(isTRUE(all.equal(initial_mtime, new_mtime)))

  # Finally check that the new code evaluated correctly
  expect_equal(get(var_to_cache, envir = .TargetEnv) , test_data)

  tidy_up()
})

#### from test-load.R ####
test_that('auto loaded data is cached by default', {
  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  # clear the global environment
  rm(list=ls(envir = .TargetEnv), envir = .TargetEnv)

  test_data <- tibble::as_tibble(data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40)))

  # save test data as a csv in the data directory
  write.csv(test_data, file="data/test.csv", row.names = FALSE)

  suppressMessages(load.project())

  # check that the cached file loads without error
  expect_error(qs::qload("cache/test.qs", env = environment()), NA)

  # and check that the loaded data from the cache is what we saved
  expect_equal(test, test_data)
})

test_that('auto loaded data is not cached when cached_loaded_data is FALSE', {
  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  # clear the global environment
  rm(list=ls(envir = .TargetEnv), envir = .TargetEnv)

  test_data <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))

  # save test data as a csv in the data directory
  write.csv(test_data, file="data/test.csv", row.names = FALSE)

  # Read the config data and set cache_loaded_data to FALSE
  config <- .read.config()
  expect_error(config$cache_loaded_data <- FALSE, NA)
  .save.config(config)

  suppressMessages(load.project())

  # check that the the test variable has not been cached
  expect_error(suppressWarnings(qs::qload("cache/test.qs", env = environment())))
})

test_that('auto loaded data from an R script is cached correctly', {
  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  # clear the global environment
  rm(list=ls(envir = .TargetEnv), envir = .TargetEnv)

  # create some variables in the global env that shouldn't be cached
  test_data11 <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))
  test_data21 <- data.frame(Names=c("a1", "b1", "c1"), Ages=c(20,30,40))

  # Create some R code and put in data directory
  CODE <- paste0(deparse(substitute({
    test_data12 <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))
    test_data22 <- data.frame(Names=c("a1", "b1", "c1"), Ages=c(20,30,40))
  })), collapse ="\n")

  # save R code in the data directory
  writeLines(CODE, "data/test.R")

  # load the project and R code
  suppressMessages(load.project())

  # check that the test variables have been cached correctly
  expect_error(qs::qload("cache/test_data12.qs", env = environment()), NA)
  expect_error(qs::qload("cache/test_data22.qs", env = environment()), NA)

  # check that the other test variables have not been cached
  expect_error(suppressWarnings(qs::qload("cache/test_data11.qs", env = environment())))
  expect_error(suppressWarnings(qs::qload("cache/test_data21.qs", env = environment())))
})

#### from test-migration.R ####
test_that('projects without the cached_loaded_data config have their migrated config set to FALSE ', {

  test_project <- tempfile('test_project')
  suppressMessages(create.project(test_project))
  on.exit(unlink(test_project, recursive = TRUE), add = TRUE)

  oldwd <- setwd(test_project)
  on.exit(setwd(oldwd), add = TRUE)

  qsCacheFileFormat()

  # Read the config data and remove the cache_loaded_data flag
  config <- .read.config()
  expect_error(config$cache_loaded_data <- NULL, NA)
  .save.config(config)

  # should get a warning because of the missing cache_loaded_data
  expect_warning(suppressMessages(load.project()), "missing the following entries")

  test_data <- data.frame(Names=c("a", "b", "c"), Ages=c(20,30,40))

  # save test data as a csv in the data directory
  write.csv(test_data, file="data/test.csv", row.names = FALSE)

  # run load.project again and check that the the test variable has not been cached
  # because the default should be FALSE if the cache_loaded_data is missing before migrate.project
  # is called
  expect_warning(suppressMessages(load.project()), "missing the following entries: cache_loaded_data")
  expect_error(suppressWarnings(qs::qload("cache/test.qs", env = environment())))

  # Migrate the project
  expect_message(migrate.project(), "new config item called cache_loaded_data")

  # Read the config data and check cached_loaded_data is FALSE
  config <- .read.config()
  expect_equal(config$cache_loaded_data, FALSE)

  # Should be a clean load.project
  expect_warning(suppressMessages(load.project()), NA)

  # check that the the test variable has not been cached
  expect_error(suppressWarnings(qs::qload("cache/test.qs", env = environment())))
})

Try the ProjectTemplate package in your browser

Any scripts or data that you put into this service are public.

ProjectTemplate documentation built on Nov. 20, 2023, 1:06 a.m.