build-cran-binaries/local/tests/test-failing-builds-classify.R
pat-s c98aaa548f
feat(local): detect dependency-cascade failures generally, not just RcppParallel
The first trial-build gate run correctly went red: the 10 auto-proposed
tbb-stddef packages (BFpack, BayesERtools, GMLTM, ...) all fail while building
their shared `rstan` dependency, not in their own code -- the `tbb/tbb_stddef.h`
error is in rstan's Module.cpp. So a per-package makevars entry is useless for
them; they are blocked on rstan (which already has an entry). This is the same
cascade the RcppParallel `applies_to` guard catches, but the tbb-stddef
signature is generic and had no such pin.

Add `failing_dependency(error_text, package)`: when the log shows a DIFFERENT
package failed to compile ("Failed to build source package X", "compilation
failed for package 'X'", ...), that package is the real cause. build_triage_report
now blocks any package whose every failing build is such a cascade -- reported
as blocked_on the dependency, never proposed a bogus entry. A package that fails
in its OWN compilation is still proposed. This unifies the applies_to and
data-driven cases into one `blocked_packages` / `blocked_on` model.

- report/proposer/blocked_summary now count the actually-blocked packages
- blocked note shows even when a group also has genuine proposals
- tests cover the rstan-cascade vs own-compile split (with the real log strings)
2026-07-15 21:33:54 +00:00

165 lines
6.3 KiB
R
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("failing_dependency names the dependency that actually failed", {
# A leaf package (BFpack) that fails building its rstan dependency.
txt <- paste(
"Error in installing dependencies for package BFpack with tag 1.6.1",
"Failed to build source package rstan.",
"ERROR: compilation failed for package rstan",
sep = "\n"
)
expect_identical(failing_dependency(txt, "BFpack"), "rstan")
# A package failing in its OWN compilation is not a cascade.
own <- "ERROR: compilation failed for package BFpack"
expect_true(is.na(failing_dependency(own, "BFpack")))
expect_true(is.na(failing_dependency(NA_character_, "x")))
expect_true(is.na(failing_dependency("some unrelated error", "x")))
})
test_that("Stan packages blocked on rstan are not proposed a per-package entry", {
# BFpack/GMLTM fail building rstan; the tbb error is in rstan's compile.
cascade <- paste(
"Failed to build source package rstan.",
"fatal error: tbb/tbb_stddef.h: No such file or directory",
sep = "\n"
)
failures <- data.frame(
name = c("BFpack", "GMLTM", "someOwnPkg"),
platform = "ubuntu-2604",
arch = "amd64",
error_text = c(
cascade,
cascade,
# someOwnPkg fails in its OWN compile on the same header -> fixable.
"someOwnPkg.cpp: fatal error: tbb/tbb_stddef.h: No such file or directory"
),
stringsAsFactors = FALSE
)
report <- build_triage_report(failures, registered_pkgs = character(0L))
grp <- Filter(function(g) g$signature == "tbb-stddef-removed", report)[[1L]]
# Only the own-compile package is proposed; the rstan cascades are blocked.
expect_identical(names(grp$proposed_entries), "someOwnPkg")
expect_setequal(grp$blocked_packages, c("BFpack", "GMLTM"))
expect_identical(grp$blocked_on, "rstan")
})
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
})