## ----include = FALSE---------------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>") knitr::read_chunk( system.file("examples", "store-laws.R", package = "s7contract") ) ## ----store-setup-------------------------------------------------------------- library(S7) library(s7contract) ## ----store-interface---------------------------------------------------------- store_put <- new_generic("store_put", "x", function(x, key, value) S7_dispatch()) store_get <- new_generic("store_get", "x", function(x, key) S7_dispatch()) store_delete <- new_generic("store_delete", "x", function(x, key) S7_dispatch()) store_reset <- new_generic("store_reset", "x") store_keys <- new_generic("store_keys", "x") KeyValue <- new_interface("KeyValue", generics = list( put = interface_requirement(store_put, args = list(key = class_character, value = class_integer)), get = interface_requirement(store_get, args = list(key = class_character)), delete = interface_requirement(store_delete, args = list(key = class_character)), reset = store_reset, keys = interface_requirement(store_keys, returns = class_character) )) ## ----store-implementations---------------------------------------------------- EnvStore <- new_class("EnvStore", properties = list(data = class_environment)) method(store_put, EnvStore) <- function(x, key, value) { assign(key, value, envir = x@data) invisible(NULL) } method(store_get, EnvStore) <- function(x, key) { if (exists(key, envir = x@data, inherits = FALSE)) get(key, envir = x@data) } method(store_delete, EnvStore) <- function(x, key) { if (exists(key, envir = x@data, inherits = FALSE)) rm(list = key, envir = x@data) invisible(NULL) } method(store_reset, EnvStore) <- function(x) { rm(list = ls(x@data, all.names = TRUE), envir = x@data) invisible(NULL) } method(store_keys, EnvStore) <- function(x) sort(ls(x@data, all.names = TRUE)) ListStore <- new_class("ListStore", properties = list(data = class_environment)) method(store_put, ListStore) <- function(x, key, value) { x@data$values[key] <- list(value) invisible(NULL) } method(store_get, ListStore) <- function(x, key) x@data$values[[key]] method(store_delete, ListStore) <- function(x, key) { x@data$values[key] <- NULL invisible(NULL) } method(store_reset, ListStore) <- function(x) { x@data$values <- list() invisible(NULL) } method(store_keys, ListStore) <- function(x) sort(as.character(names(x@data$values))) ## ----store-strings------------------------------------------------------------ string_generator <- function(alphabet, min = 0L, max = 4L) { if (!is.character(alphabet) || anyNA(alphabet)) { stop("alphabet must contain non-missing characters") } if (any(Encoding(alphabet) == "bytes")) stop("byte strings are not supported") alphabet <- enc2utf8(alphabet) if (any(!validUTF8(alphabet))) stop("alphabet must be valid UTF-8") if (any(nchar(alphabet, type = "chars") != 1L)) { stop("each alphabet entry must be one Unicode code point") } gen_map(gen_vector(gen_element(alphabet), min, max), function(parts) paste0(parts, collapse = ""), prototype = character()) } store_keys_generator <- string_generator(c("a", "b", "c", "\u00e9"), min = 1L) ## ----store-commands----------------------------------------------------------- existing_key <- function(state) { if (length(state) == 0L) return(NULL) gen_element(names(state)) } store_commands <- list( new_command("put", generate = function(state) gen_product( key = store_keys_generator, value = gen_integer(-10L, 10L)), execute = function(fixture, input) with(KeyValue, { store_put(fixture, input$key, input$value) store_get(fixture, input$key) }), update = function(state, input, output) { state[input$key] <- list(input$value) state }, ensure = function(state, input, output) identical(output, input$value) ), new_command("get", generate = existing_key, require = function(state, input) input %in% names(state), execute = function(fixture, input) with(KeyValue, store_get(fixture, input)), ensure = function(state, input, output) identical(output, state[[input]]) ), new_command("delete", generate = existing_key, require = function(state, input) input %in% names(state), execute = function(fixture, input) with(KeyValue, { store_delete(fixture, input) store_keys(fixture) }), update = function(state, input, output) { state[input] <- NULL state }, ensure = function(state, input, output) { identical(output, sort(setdiff(names(state), input))) } ), new_command("reset", generate = function(state) gen_constant(NULL), execute = function(fixture, input) with(KeyValue, { store_reset(fixture) store_keys(fixture) }), update = function(state, input, output) list(), ensure = function(state, input, output) identical(output, character()) ) ) ## ----store-law---------------------------------------------------------------- store_law <- function(make) { new_state_law("key/value operations follow the model", list(), store_commands, setup = function() { fixture <- make() assert_implements(fixture, KeyValue) fixture }, teardown = function(fixture) { rm(list = ls(fixture@data, all.names = TRUE), envir = fixture@data) }, max_commands = 12L, classify = function(sequence) { puts <- Filter(function(step) step$command == "put", sequence) keys <- vapply(puts, function(step) step$input$key, character(1)) c(if (any(nchar(keys, type = "chars") > 1L)) "multi_character", if (any(grepl("\u00e9", keys, fixed = TRUE))) "non_ascii") }, min_coverage = c(multi_character = 0.3, non_ascii = 0.2) ) } stores <- list( environment = function() EnvStore(data = new.env(parent = emptyenv())), list = function() ListStore(data = list2env(list(values = list()), parent = emptyenv())) ) store_results <- lapply(stores, function(make) check_law(store_law(make), tests = 100L, seed = 1L)) vapply(store_results, function(result) result@status, character(1)) ## ----store-key-coverage------------------------------------------------------- store_results$environment@coverage ## ----store-truncated---------------------------------------------------------- TruncatedStore <- new_class("TruncatedStore", parent = EnvStore) method(store_put, TruncatedStore) <- function(x, key, value) { assign(substr(key, 1L, 1L), value, envir = x@data) invisible(NULL) } truncated_failure <- check_law( store_law(function() TruncatedStore(data = new.env(parent = emptyenv()))), tests = 100L, shrinks = 200L, seed = 1L ) truncated_failure ## ----store-broken------------------------------------------------------------- StickyStore <- new_class("StickyStore", parent = EnvStore) method(store_reset, StickyStore) <- function(x) invisible(NULL) implements(StickyStore, KeyValue) store_failure <- check_law( store_law(function() StickyStore(data = new.env(parent = emptyenv()))), tests = 100L, shrinks = 200L, seed = 1L ) store_failure ## ----store-trace-------------------------------------------------------------- store_failure@counterexample@condition$trace ## ----store-replay------------------------------------------------------------- store_replayed <- do.call(check_law, c(list(law = store_failure@law), store_failure@parameters)) identical(store_replayed@counterexample@minimal, store_failure@counterexample@minimal)