148 lines
4.6 KiB
Markdown
148 lines
4.6 KiB
Markdown
### 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>"`, where `hash12` covers `env`/`configure_args`/`makevars`/patch bytes; `resolve_patch_version(entry)` → latest CRAN version satisfying `entry$versions`, or `NA_character_`; `describe_patch(entry)` → short human label.
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
```r
|
|
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`)**
|
|
|
|
```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**
|
|
|
|
```bash
|
|
git add R/patches.R tests/testthat/test-patches.R
|
|
git commit -m "feat(patches): cache key, version resolution, and patch description"
|
|
```
|
|
|
|
---
|
|
|