From b8932d0515858ace7dc8c3d076a45760f6f2154f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 8 Jul 2026 22:43:44 +0900 Subject: [PATCH] fix(coverage): bound R coverage tooling install so it defers instead of failing The coverage-evidence gate hard-fails on R packages (e.g. nonnest2 #36/#37) with exit 124. run_and_capture wraps every command in `timeout 900`, and the R coverage tooling install compiles covr/testthat plus the package's declared dependencies from CRAN source. It harvested Suggests too, pulling in heavy C++ builds (OpenMx, mirt, tidySEM, mlogit, AER) that exceed 15 minutes, so `timeout` SIGKILLs bash before the existing `|| ... exit 0` deferral can run, yielding exit 124 and a failed Coverage Decision. - Wrap the install in an inner `timeout 780` so a slow/unavailable toolchain triggers the intended graceful deferral (rc 0) rather than a 124 hard-fail. - Drop Suggests from the dependency harvest: package tests guard optional deps with require()/requireNamespace(), so they self-skip when absent (CRAN policy), while covr/testthat plus Depends/Imports/LinkingTo still install fast enough for real coverage to run. The coverage-% step stays advisory. - Prefer Posit Public Package Manager binaries (source fallback) to cut install time further where binaries match the runner's R. Verified: nested bash/R quoting tokenizes cleanly, R code parses, YAML valid, and a slow install now returns rc 0 (deferred) instead of 124. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P --- .github/workflows/opencode-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index b0f387961..de78708e6 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -583,7 +583,7 @@ jobs: export R_LIBS_USER="${RUNNER_TEMP}/R-library" mkdir -p "$R_LIBS_USER" run_and_capture "R coverage tooling (covr/testthat)" \ - bash -c 'Rscript -e '\''repos <- "https://cloud.r-project.org"; lib <- Sys.getenv("R_LIBS_USER"); install_deps <- c("Depends", "Imports", "LinkingTo"); dir.create(lib, recursive = TRUE, showWarnings = FALSE); .libPaths(c(lib, .libPaths())); required <- c("covr", "testthat"); if (file.exists("DESCRIPTION")) { desc <- read.dcf("DESCRIPTION")[1, , drop = FALSE]; fields <- intersect(c("Depends", "Imports", "LinkingTo", "Suggests"), colnames(desc)); values <- as.character(desc[, fields, drop = TRUE]); values <- values[!is.na(values)]; package_deps <- trimws(gsub("\\s*\\([^)]*\\)", "", unlist(strsplit(paste(values, collapse = ","), ","), use.names = FALSE))); package_deps <- setdiff(package_deps[nzchar(package_deps)], "R"); required <- unique(c(required, package_deps)); }; for (pkg in required) if (!requireNamespace(pkg, quietly = TRUE)) install.packages(pkg, repos = repos, lib = lib, dependencies = install_deps); missing <- required[!vapply(required, requireNamespace, logical(1), quietly = TRUE)]; if (length(missing)) stop("R coverage tooling packages unavailable after install: ", paste(missing, collapse = ", "))'\'' || { echo "R coverage tooling install unavailable in coverage runner; deferring to required peer R CMD check evidence."; exit 0; }' + bash -c 'timeout 780 Rscript -e '\''user_repo <- "https://cloud.r-project.org"; codename <- tryCatch(sub("[[:space:]]+$", "", system2("lsb_release", "-cs", stdout = TRUE, stderr = FALSE)[1]), error = function(e) ""); if (length(codename) == 1 && !is.na(codename) && nzchar(codename)) { options(HTTPUserAgent = sprintf("R/%s R (%s)", getRversion(), paste(getRversion(), R.version$platform, R.version$arch, R.version$os))); repos <- c(sprintf("https://p3m.dev/cran/__linux__/%s/latest", codename), user_repo) } else { repos <- user_repo }; lib <- Sys.getenv("R_LIBS_USER"); install_deps <- c("Depends", "Imports", "LinkingTo"); dir.create(lib, recursive = TRUE, showWarnings = FALSE); .libPaths(c(lib, .libPaths())); required <- c("covr", "testthat"); if (file.exists("DESCRIPTION")) { desc <- read.dcf("DESCRIPTION")[1, , drop = FALSE]; fields <- intersect(c("Depends", "Imports", "LinkingTo"), colnames(desc)); values <- as.character(desc[, fields, drop = TRUE]); values <- values[!is.na(values)]; package_deps <- trimws(gsub("\\s*\\([^)]*\\)", "", unlist(strsplit(paste(values, collapse = ","), ","), use.names = FALSE))); package_deps <- setdiff(package_deps[nzchar(package_deps)], "R"); required <- unique(c(required, package_deps)); }; for (pkg in required) if (!requireNamespace(pkg, quietly = TRUE)) install.packages(pkg, repos = repos, lib = lib, dependencies = install_deps); missing <- required[!vapply(required, requireNamespace, logical(1), quietly = TRUE)]; if (length(missing)) stop("R coverage tooling packages unavailable after install: ", paste(missing, collapse = ", "))'\'' || { echo "R coverage tooling install unavailable or exceeded the runner time budget; deferring to required peer R CMD check evidence."; exit 0; }' if [ -f DESCRIPTION ]; then if [ -d tests/testthat ]; then run_and_capture "R package testthat suite" \