From 3e5a19c94ac5447a980a24a8a4f599c06dfa7a18 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Thu, 11 Jun 2026 17:08:33 +0200 Subject: [PATCH] fix(r): skip golden-fixture test in standalone package builds + document parity (#257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The r-universe build of `wickra` 0.8.4 fails (`R CMD check` ERROR): the golden-fixture parity test added in #255 walks up from the working directory looking for `testdata/golden` and `stop()`s when it cannot find it. Standalone package builds (r-universe / CRAN) package only `bindings/r`, so the repo-root `testdata/golden` fixtures are unreachable there — whereas the monorepo CI checks out the full repo, so the walk-up succeeds and the test passes. Run: https://github.com/r-universe/wickra-lib/actions/runs/27354800361 ## Fix - **`bindings/r/tests/testthat/test-golden.R`** — the fixture-directory lookup now returns `NULL` instead of erroring when the fixtures are absent, and each test starts with `skip_if(is.null(golden_dir), ...)`. The repository CI (full repo present) still runs the parity checks; standalone builds skip them. The per-test `golden_input` read moved inside the (post-skip) test bodies so nothing runs at source time when the fixtures are missing. - **`CHANGELOG.md`** — `[Unreleased]` Fixed entry. ## Docs (B6) - **`README.md` `## Testing`** — the four C-ABI bindings (C#, Go, Java, R) were described as covering one indicator per FFI archetype; document that they additionally replay the shared golden fixture and assert exact parity with the Rust reference outputs. --- CHANGELOG.md | 6 +++++ README.md | 6 +++++ bindings/r/tests/testthat/test-golden.R | 31 +++++++++++++++++++++---- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 894ee810..ecb4e181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- The R binding's golden-fixture parity test now skips gracefully when the shared + `testdata/golden` fixtures are not bundled with the package — standalone + r-universe / CRAN builds package only `bindings/r`, so the repo-root fixtures + are unreachable there. The parity stays enforced by the repository CI, where + the fixtures are present. ## [0.8.4] - 2026-06-11 ### Fixed diff --git a/README.md b/README.md index 8a060dcc..dedc5bb5 100644 --- a/README.md +++ b/README.md @@ -358,6 +358,12 @@ Every layer is covered; run the suites with the commands in - `bindings/java`: JUnit cases covering one indicator per FFI archetype (scalar/batch, multi-output, bars, profile, array input) plus batch equivalence. +The four C-ABI bindings (C#, Go, Java, R) additionally replay a shared, +language-neutral golden fixture (`testdata/golden/*.csv`, generated by +`cargo run -p wickra-examples --bin gen_golden`) and assert exact parity with the +Rust reference outputs across every archetype (SMA, EMA, RSI, ATR, MACD, ADX, +Beta), catching FFI wiring bugs the math-only core tests cannot see. + ## Contributing Contributions are very welcome — issues, bug reports, ideas, and pull requests diff --git a/bindings/r/tests/testthat/test-golden.R b/bindings/r/tests/testthat/test-golden.R index 055d5b7c..1ccb45d5 100644 --- a/bindings/r/tests/testthat/test-golden.R +++ b/bindings/r/tests/testthat/test-golden.R @@ -3,23 +3,36 @@ # archetype test only checks finiteness, this pins exact values, catching wiring # bugs (swapped params, wrong multi-output field). Fixtures are generated by # `cargo run -p wickra-examples --bin gen_golden`. +# +# The fixtures live at the repository root (testdata/golden) and are present +# during the monorepo test run, but they are NOT bundled into the standalone R +# package. A packaged check (r-universe / CRAN) therefore cannot find them, so +# these tests skip there; the parity is already enforced by the repository CI. -golden_dir <- local({ +find_golden_dir <- function() { d <- normalizePath(getwd(), winslash = "/", mustWork = FALSE) repeat { g <- file.path(d, "testdata", "golden") if (dir.exists(g)) return(g) parent <- dirname(d) - if (identical(parent, d)) stop("testdata/golden not found from ", getwd()) + if (identical(parent, d)) return(NULL) d <- parent } -}) +} + +golden_dir <- find_golden_dir() + +skip_if_no_golden <- function() { + skip_if(is.null(golden_dir), "golden fixtures not bundled with the package") +} read_golden <- function(name) { read.csv(file.path(golden_dir, paste0(name, ".csv")), colClasses = "character", check.names = FALSE) } +read_golden_input <- function() read.csv(file.path(golden_dir, "input.csv")) + gcell <- function(s) if (identical(s, "nan")) NA_real_ else as.numeric(s) expect_close <- function(got, want, row, field) { @@ -31,9 +44,9 @@ expect_close <- function(got, want, row, field) { } } -golden_input <- read.csv(file.path(golden_dir, "input.csv")) - test_that("scalar indicators match golden", { + skip_if_no_golden() + golden_input <- read_golden_input() specs <- list(c("sma", 14), c("ema", 14), c("rsi", 14)) for (spec in specs) { name <- spec[[1]] @@ -47,6 +60,8 @@ test_that("scalar indicators match golden", { }) test_that("candle Atr matches golden", { + skip_if_no_golden() + golden_input <- read_golden_input() atr <- Atr(14) exp <- read_golden("atr") for (i in seq_len(nrow(golden_input))) { @@ -57,6 +72,8 @@ test_that("candle Atr matches golden", { }) test_that("pairwise Beta matches golden", { + skip_if_no_golden() + golden_input <- read_golden_input() beta <- Beta(20) exp <- read_golden("beta") for (i in seq_len(nrow(golden_input))) { @@ -67,6 +84,8 @@ test_that("pairwise Beta matches golden", { }) test_that("multi-output MACD matches golden", { + skip_if_no_golden() + golden_input <- read_golden_input() macd <- MacdIndicator(12, 26, 9) exp <- read_golden("macd") for (i in seq_len(nrow(golden_input))) { @@ -82,6 +101,8 @@ test_that("multi-output MACD matches golden", { }) test_that("multi-output ADX matches golden", { + skip_if_no_golden() + golden_input <- read_golden_input() adx <- Adx(14) exp <- read_golden("adx") for (i in seq_len(nrow(golden_input))) {