fix(patches): validator portability, clearer docs, broader hook trigger
This commit is contained in:
parent
a826a27246
commit
f3f3bfa20d
1 changed files with 2428 additions and 8 deletions
148
.superpowers/handoff/task-A3-brief.md
Normal file
148
.superpowers/handoff/task-A3-brief.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
### 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"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Loading…
Reference in a new issue