build-cran-binaries/local/tests/test-failing-builds-classify.R
pat-s a8c8b23f70
fix(local): stop proposing per-dependent patches for dependency-cascade failures
The `--open-issue` run proposed 800+ entries, all `rcppparallel-bundled-tbb` on
ubuntu-2604, each pre-filling `RcppParallel/disable-tbb.patch` for an unrelated
package (ACEsimFit, AovBay, ...). Those packages fail only because their
RcppParallel *dependency* does not build there, so bincraft records
RcppParallel's TBB error text against each dependent and they all match the
signature. Applying a RcppParallel source patch to a dependent's source is not
just noise -- it is broken (the diff targets RcppParallel's Makevars.in).

Add an optional `applies_to` field pinning a package-specific fix to the package
it targets. A signature whose fix is a curated per-package source patch
(rcppparallel-bundled-tbb -> RcppParallel) is only ever proposed for that
package; other matching packages are downstream failures reported as "blocked on
<dependency>" and never proposed a bogus entry. Fix the dependency once and the
whole cascade clears.

- gate proposal generation in build_triage_report on applies_to, add blocked_on
- surface blocked groups in the report and proposer instead of a misleading
  "already registered" note
- cover both the dependent-blocked and RcppParallel-itself-proposed cases
2026-07-14 17:41:07 +00:00

122 lines
4.5 KiB
R

source(file.path("..", "failing-builds-classify.R"))
test_that("normalise_error collapses temp paths, versions, and package tokens", {
a <- normalise_error(
"In file /tmp/RtmpAb12/foo.c: RcppParallel 5.1.9 failed at 0xdeadbeef",
package = "RcppParallel"
)
expect_false(grepl("RtmpAb12", a))
expect_false(grepl("5\\.1\\.9", a))
expect_false(grepl("0xdeadbeef", a))
expect_true(grepl("<pkg>", a))
expect_true(grepl("<tmp>", a))
# Same root error across two versions collapses to one fingerprint.
e1 <- "StanHeaders 2.32.1: tbb/tbb_stddef.h: No such file or directory"
e2 <- "StanHeaders 2.33.0: tbb/tbb_stddef.h: No such file or directory"
expect_identical(
fingerprint_error(e1, "StanHeaders"),
fingerprint_error(e2, "StanHeaders")
)
})
test_that("normalise_error is safe on NA/empty input", {
expect_identical(normalise_error(NA_character_), "")
expect_identical(normalise_error(""), "")
expect_identical(fingerprint_error(NA_character_), "<empty>")
})
test_that("fingerprint_error picks the salient error line, not the last line", {
txt <- paste(
"* installing *source* package 'foo' ...",
"error: bar.h: No such file or directory",
"* removing '/tmp/lib/foo'",
sep = "\n"
)
fp <- fingerprint_error(txt, "foo")
expect_true(grepl("no such file", fp))
expect_false(grepl("removing", fp))
})
test_that("classify_error matches the removed TBB header signature (makevars, auto)", {
sig <- classify_error(
"fatal error: tbb/tbb_stddef.h: No such file or directory"
)
expect_identical(sig$id, "tbb-stddef-removed")
expect_identical(sig$tier, "makevars")
expect_true(sig$auto)
expect_true(sig$matched)
})
test_that("classify_error matches RcppParallel bundled-TBB failures", {
sig <- classify_error(
"Error: USE_TBB=Linux is not supported on this toolchain"
)
expect_identical(sig$id, "rcppparallel-bundled-tbb")
expect_identical(sig$tier, "patch")
expect_true(sig$auto)
})
test_that("system libuv link leak is classified but stays human-only (novel diff)", {
sig <- classify_error("cannot open shared object file: libuv.so.1")
expect_identical(sig$id, "system-libuv-link-leak")
expect_true(sig$matched)
expect_false(sig$auto)
})
test_that("unknown signatures are never guessed at", {
sig <- classify_error("segfault: memory not mapped at address")
expect_identical(sig$id, "unclassified")
expect_false(sig$matched)
expect_false(sig$auto)
expect_true(is.na(sig$tier))
})
test_that("propose_registry_entry fills a schema-valid entry for a known lever", {
sig <- classify_error("tbb/tbb_stddef.h: No such file")
json <- propose_registry_entry(sig, "StanHeaders", c("alpine", "ubuntu-2604"))
entry <- jsonlite::fromJSON(json, simplifyVector = FALSE)
expect_identical(entry$package, "StanHeaders")
expect_identical(entry$versions, "*")
expect_identical(entry$platforms, list("alpine", "ubuntu-2604"))
expect_identical(entry$makevars$CPPFLAGS, "-DTBB_INTERFACE_NEW")
expect_true(nzchar(entry$reason))
# No template -> no proposal (unclassified path).
expect_null(propose_registry_entry(classify_error("weird"), "x", "alpine"))
})
test_that("RcppParallel dependents are blocked, not proposed a per-package patch", {
# Hundreds of packages fail on ubuntu-2604 only because their RcppParallel
# dependency fails to build, so they carry RcppParallel's TBB error text.
failures <- data.frame(
name = c("ACEsimFit", "AovBay", "AdaptGauss"),
platform = "ubuntu-2604",
arch = "amd64",
error_text = "Error: USE_TBB=Linux is not supported on this toolchain",
stringsAsFactors = FALSE
)
report <- build_triage_report(failures, registered_pkgs = character(0L))
grp <- Filter(function(g) g$signature == "rcppparallel-bundled-tbb", report)[[
1L
]]
# No bogus per-dependent entries (the patch targets RcppParallel's source).
expect_null(grp$proposed_entries)
expect_identical(grp$blocked_on, "RcppParallel")
})
test_that("RcppParallel itself is still proposed when it is the failing package", {
failures <- data.frame(
name = "RcppParallel",
platform = "ubuntu-2604",
arch = "amd64",
error_text = "Error: USE_TBB=Linux is not supported on this toolchain",
stringsAsFactors = FALSE
)
report <- build_triage_report(failures, registered_pkgs = character(0L))
grp <- report[[1L]]
expect_identical(grp$signature, "rcppparallel-bundled-tbb")
expect_false(is.null(grp$proposed_entries))
expect_true("RcppParallel" %in% names(grp$proposed_entries))
expect_null(grp$blocked_on) # RcppParallel is the target, not a dependent
})