time_factor <- 1 # Do things slower on GHA because of slow machines if (is_on_github_actions()) time_factor <- 4 test_that("cache_mem: handling missing values", { d <- cache_mem() expect_true(is.key_missing(d$get("abcd"))) d$set("a", 100) expect_identical(d$get("a"), 100) expect_identical(d$get("y", missing = NULL), NULL) expect_error( d$get("y", missing = stop("Missing key")), "^Missing key$", ) d <- cache_mem(missing = NULL) expect_true(is.null(d$get("abcd"))) d$set("a", 100) expect_identical(d$get("a"), 100) expect_identical(d$get("y", missing = -1), -1) expect_error( d$get("y", missing = stop("Missing key")), "^Missing key$", ) d <- cache_mem(missing = stop("Missing key")) expect_error(d$get("abcd"), "^Missing key$") d$set("x", NULL) d$set("a", 100) expect_identical(d$get("a"), 100) expect_error(d$get("y"), "^Missing key$") expect_identical(d$get("y", missing = NULL), NULL) expect_true(is.key_missing(d$get("y", missing = key_missing()))) expect_error( d$get("y", missing = stop("Missing key 2")), "^Missing key 2$", ) # Pass in a quosure expr <- rlang::quo(stop("Missing key")) d <- cache_mem(missing = !!expr) expect_error(d$get("y"), "^Missing key$") expect_error(d$get("y"), "^Missing key$") # Make sure a second time also throws }) test_that("cache_mem: reset", { mc <- cache_mem() mc$set("a", "A") mc$set("b", "B") mc$reset() expect_identical(mc$keys(), character()) expect_identical(mc$size(), 0L) mc$set("c", "C") expect_identical(mc$keys(), "c") expect_identical(mc$size(), 1L) expect_false(mc$exists("a")) expect_true(mc$exists("c")) }) test_that("cache_mem: pruning respects max_n", { delay <- 0.001 * time_factor d <- cache_mem(max_n = 3) # NOTE: The short delays after each item are meant to tests more reliable on # CI systems. d$set("a", rnorm(100)); Sys.sleep(delay) d$set("b", rnorm(100)); Sys.sleep(delay) d$set("c", rnorm(100)); Sys.sleep(delay) d$set("d", rnorm(100)); Sys.sleep(delay) d$set("e", rnorm(100)); Sys.sleep(delay) expect_identical(sort(d$keys()), c("c", "d", "e")) }) test_that("cache_mem: pruning respects max_size", { delay <- 0.001 * time_factor d <- cache_mem(max_size = object.size(123) * 3) d$set("a", rnorm(100)); Sys.sleep(delay) d$set("b", rnorm(100)); Sys.sleep(delay) d$set("c", 1); Sys.sleep(delay) expect_identical(sort(d$keys()), c("c")) d$set("d", rnorm(100)); Sys.sleep(delay) # Objects are pruned with oldest first, so even though "c" would fit in the # cache, it is removed after adding "d" (and "d" is removed as well because it # doesn't fit). expect_length(d$keys(), 0) d$set("e", 2); Sys.sleep(delay) d$set("f", 3); Sys.sleep(delay) expect_identical(sort(d$keys()), c("e", "f")) }) test_that("cache_mem: max_size=Inf", { mc <- cachem::cache_mem(max_size = Inf) mc$set("a", 123) expect_identical(mc$get("a"), 123) mc$prune() expect_identical(mc$get("a"), 123) }) test_that("cache_mem: pruning respects both max_n and max_size", { delay <- 0.001 * time_factor d <- cache_mem(max_n = 3, max_size = object.size(123) * 3) # Set some values. Use rnorm so that object size is large; a simple vector # like 1:100 will be stored very efficiently by R's ALTREP, and won't exceed # the max_size. We want each of these objects to exceed max_size so that # they'll be pruned. d$set("a", rnorm(100)); Sys.sleep(delay) d$set("b", rnorm(100)); Sys.sleep(delay) d$set("c", rnorm(100)); Sys.sleep(delay) d$set("d", rnorm(100)); Sys.sleep(delay) d$set("e", rnorm(100)); Sys.sleep(delay) d$set("f", 1); Sys.sleep(delay) d$set("g", 1); Sys.sleep(delay) d$set("h", 1); Sys.sleep(delay) expect_identical(sort(d$keys()), c("f", "g", "h")) # This will cause f to be pruned (due to max_n) and g to be pruned (due to # max_size). d$set("i", c(2, 3)); Sys.sleep(0.001) expect_identical(sort(d$keys()), c("h", "i")) }) test_that('cache_mem: pruning with evict="lru"', { delay <- 0.001 * time_factor d <- cache_mem(max_n = 2) d$set("a", 1); Sys.sleep(delay) d$set("b", 1); Sys.sleep(delay) d$set("c", 1); Sys.sleep(delay) expect_identical(sort(d$keys()), c("b", "c")) d$get("b") d$set("d", 1); Sys.sleep(delay) expect_identical(sort(d$keys()), c("b", "d")) d$get("b") d$set("e", 2); Sys.sleep(delay) d$get("b") d$set("f", 3); Sys.sleep(delay) expect_identical(sort(d$keys()), c("b", "f")) d <- cache_mem(max_n = 2, evict = "lru") d$set("a", 1); Sys.sleep(delay) d$set("b", 1); Sys.sleep(delay) d$set("c", 1); Sys.sleep(delay) d$set("b", 2); Sys.sleep(delay) d$set("d", 2); Sys.sleep(delay) expect_identical(sort(d$keys()), c("b", "d")) }) test_that('cache_mem: pruning with evict="fifo"', { delay <- 0.001 * time_factor d <- cache_mem(max_n = 2, evict = "fifo") d$set("a", 1); Sys.sleep(delay) d$set("b", 1); Sys.sleep(delay) d$set("c", 1); Sys.sleep(delay) expect_identical(sort(d$keys()), c("b", "c")) d$get("b") d$set("d", 1); Sys.sleep(delay) expect_identical(sort(d$keys()), c("c", "d")) d$get("b") d$set("e", 2); Sys.sleep(delay) d$get("b") d$set("f", 3); Sys.sleep(delay) expect_identical(sort(d$keys()), c("e", "f")) d <- cache_mem(max_n = 2, evict = "fifo") d$set("a", 1); Sys.sleep(delay) d$set("b", 1); Sys.sleep(delay) d$set("c", 1); Sys.sleep(delay) d$set("b", 2); Sys.sleep(delay) d$set("d", 2); Sys.sleep(delay) expect_identical(sort(d$keys()), c("b", "d")) }) test_that("Pruning by max_age", { skip_on_cran() # Should prune target item on get() d <- cache_mem(max_age = 0.25*time_factor) d$set("a", 1) expect_identical(d$get("a"), 1) Sys.sleep(0.3*time_factor) expect_identical(d$get("a"), key_missing()) expect_identical(d$get("x"), key_missing()) # Should prune all items on set() d <- cache_mem(max_age = 0.25*time_factor) d$set("a", 1) expect_identical(d$get("a"), 1) Sys.sleep(0.3*time_factor) d$set("b", 1) expect_identical(d$keys(), "b") # Should prune target item on exists() d <- cache_mem(max_age = 0.25*time_factor) d$set("a", 1) expect_identical(d$get("a"), 1) expect_true(d$exists("a")) expect_false(d$exists("b")) Sys.sleep(0.15*time_factor) d$set("b", 1) expect_true(d$exists("a")) expect_true(d$exists("b")) Sys.sleep(0.15*time_factor) expect_false(d$exists("a")) expect_true(d$exists("b")) # Should prune all items on keys() d <- cache_mem(max_age = 0.25*time_factor) d$set("a", 1) expect_identical(d$keys(), "a") Sys.sleep(0.15*time_factor) d$set("b", 1) Sys.sleep(0.15*time_factor) expect_identical(d$keys(), "b") # Should prune all items on size() d <- cache_mem(max_age = 0.25*time_factor) d$set("a", 1) expect_identical(d$size(), 1L) Sys.sleep(0.15*time_factor) d$set("b", 1) expect_identical(d$size(), 2L) Sys.sleep(0.15*time_factor) expect_identical(d$size(), 1L) }) test_that("Removed objects can be GC'd", { mc <- cache_mem() e <- new.env() finalized <- FALSE reg.finalizer(e, function(x) finalized <<- TRUE) mc$set("e", e) rm(e) mc$set("x", 1) gc() expect_false(finalized) expect_true(is.environment(mc$get("e"))) }) test_that("Pruned objects can be GC'd", { delay <- 0.001 * time_factor # Cache is large enough to hold one environment and one number mc <- cache_mem(max_size = object.size(new.env()) + object.size(1234)) e <- new.env() finalized <- FALSE reg.finalizer(e, function(x) finalized <<- TRUE) mc$set("e", e) rm(e) mc$set("x", 1) gc() expect_false(finalized) expect_true(is.environment(mc$get("e"))) # Get x so that the atime is updated Sys.sleep(delay) mc$get("x") Sys.sleep(delay) # e should be pruned when we add another item mc$set("y", 2) gc() expect_true(finalized) expect_true(is.key_missing(mc$get("e"))) }) # For https://github.com/r-lib/cachem/issues/47, https://github.com/r-lib/cachem/pull/48/ test_that("Cache doesn't shrink smaller than INITIAL_SIZE", { # This test also makes sure that the cache doesn't keep adding elements to the # storage vectors when there are zero items, then an item is added and # removed, repeatedly. m <- cache_mem() e <- environment(m$get) for (i in seq_len(e$INITIAL_SIZE)) { m$set(as.character(i), i) m$remove(as.character(i)) } expect_equal(e$total_n_, 0) expect_equal(e$last_idx_, e$INITIAL_SIZE) expect_length(e$key_, e$INITIAL_SIZE) expect_length(e$value_, e$INITIAL_SIZE) # Adding one more item should trigger a compact_() m$set("a", 1) m$remove("a") expect_equal(e$total_n_, 0) # last_idx_ should be reset after we pass the INITIAL_SIZE, even if there are # no items in the cache. Prior to the fix in #48, it could keep growing. expect_equal(e$last_idx_, 0) expect_length(e$key_, e$INITIAL_SIZE) expect_length(e$value_, e$INITIAL_SIZE) })