4.6 KiB
4.6 KiB
Task A3: Cache key and version resolution
Files:
- Modify:
R/patches.R - Test:
tests/testthat/test-patches.R
Interfaces:
-
Consumes: normalized entries.
-
Produces:
patch_cache_key(entry, version, platform, arch, r_minor)→ string"<pkg>_<ver>_<platform>_<arch>_<rminor>_<hash12>", wherehash12coversenv/configure_args/makevars/patch bytes;resolve_patch_version(entry)→ latest CRAN version satisfyingentry$versions, orNA_character_;describe_patch(entry)→ short human label. -
Step 1: Write the failing test
test_that("patch_cache_key is stable and sensitive to env/patch changes", {
e1 <- list(package = "P", env = list(A = "1"),
configure_args = character(0L), makevars = list(),
patch_path = NULL)
e2 <- e1; e2$env <- list(A = "2")
k1 <- patch_cache_key(e1, "1.0", "alpine-324", "amd64", "4.5")
expect_identical(k1, patch_cache_key(e1, "1.0", "alpine-324", "amd64", "4.5"))
expect_false(identical(
k1, patch_cache_key(e2, "1.0", "alpine-324", "amd64", "4.5")
))
expect_match(k1, "^P_1.0_alpine-324_amd64_4.5_[0-9a-f]{12}$")
})
test_that("resolve_patch_version returns latest for wildcard, NA when unmet", {
local_mocked_bindings(
cran_package = function(pkg) list(Version = "5.1.12"),
.package = "pkgsearch"
)
expect_identical(
resolve_patch_version(list(package = "RcppParallel", versions = "*")),
"5.1.12"
)
expect_identical(
resolve_patch_version(list(package = "RcppParallel", versions = ">=9.0")),
NA_character_
)
})
test_that("describe_patch summarizes the active overrides", {
expect_match(
describe_patch(list(env = list(RCPP_PARALLEL_USE_TBB = "0"),
configure_args = character(0L), makevars = list(),
patch_path = NULL)),
"env: RCPP_PARALLEL_USE_TBB=0"
)
expect_match(
describe_patch(list(env = list(), configure_args = character(0L),
makevars = list(), patch_path = "/x/fix.patch")),
"source patch"
)
})
- Step 2: Run test to verify it fails
Run: Rscript -e 'devtools::load_all("."); testthat::test_file("tests/testthat/test-patches.R")'
Expected: FAIL with "could not find function patch_cache_key".
- Step 3: Write minimal implementation (append to
R/patches.R)
#' Compute the cache key for a patched binary
#' @keywords internal
patch_cache_key <- function(entry, version, platform, arch, r_minor) {
payload <- list(
env = entry$env,
configure_args = entry$configure_args,
makevars = entry$makevars,
patch = if (!is.null(entry$patch_path)) {
readBin(entry$patch_path, "raw", file.size(entry$patch_path))
} else {
raw(0L)
}
)
tmp <- tempfile()
on.exit(unlink(tmp), add = TRUE)
saveRDS(payload, tmp)
hash <- substr(unname(tools::md5sum(tmp)), 1L, 12L)
sprintf(
"%s_%s_%s_%s_%s_%s",
entry$package, version, platform, arch, r_minor, hash
)
}
#' Resolve the CRAN version to build for a patch entry
#'
#' Returns the latest CRAN version satisfying the entry's `versions` constraint,
#' or `NA_character_` when CRAN's latest does not satisfy it or lookup fails.
#' @keywords internal
resolve_patch_version <- function(entry) {
latest <- tryCatch(
pkgsearch::cran_package(entry$package)$Version,
error = function(e) NA_character_
)
if (is.na(latest)) {
return(NA_character_)
}
if (identical(entry$versions, "*") || version_satisfies(latest, entry$versions)) {
return(latest)
}
NA_character_
}
#' Short human label describing a patch entry's overrides
#' @keywords internal
describe_patch <- function(entry) {
bits <- character(0L)
if (length(entry$env) > 0L) {
bits <- c(bits, sprintf(
"env: %s",
paste(
names(entry$env),
unlist(entry$env),
sep = "=", collapse = ","
)
))
}
if (length(entry$configure_args) > 0L) {
bits <- c(bits, sprintf("configure: %s", toString(entry$configure_args)))
}
if (length(entry$makevars) > 0L) {
bits <- c(bits, "makevars")
}
if (!is.null(entry$patch_path)) {
bits <- c(bits, "source patch")
}
if (length(bits) == 0L) "no-op" else paste(bits, collapse = "; ")
}
- Step 4: Run test to verify it passes
Run: Rscript -e 'devtools::load_all("."); testthat::test_file("tests/testthat/test-patches.R")'
Expected: PASS. (Note: local_mocked_bindings requires testthat >= 3.1.7; bincraft uses 3e.)
- Step 5: Commit
git add R/patches.R tests/testthat/test-patches.R
git commit -m "feat(patches): cache key, version resolution, and patch description"