From 1255892b1e7047b5000cbdd962199effe770406b Mon Sep 17 00:00:00 2001 From: kingchenc Date: Sat, 23 May 2026 23:30:10 +0200 Subject: [PATCH] test(bollinger): cover classic()+accessors+metadata, drop dead naive arm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codecov flagged 16 uncovered lines in crates/wickra-core/src/indicators/bollinger.rs (file at 91.30%): - classic() convenience constructor (91-93) — every test passed explicit parameters to BollingerBands::new, so the classic-defaults path was dead - const accessors period (96-98), multiplier (101-103) — never queried - Indicator-impl bodies warmup_period (156-158), name (164-166) — never queried - `return None;` (line 177) inside the test-only `naive` helper's `if prices.len() < period` early-return — every caller passes a slice of length >= period (matches_naive_definition uses `&prices[..=i]` for `i in 19..` with period=20; long_stream_drift_ stays_bounded fills the window before measuring), so the arm is dead Add classic_and_accessors_and_metadata to cover the constructor and the four getter bodies, and refactor naive to return BollingerOutput directly with an `assert!(prices.len() >= period)` precondition. The two existing callers were already using .unwrap()/.expect() on the Option result and simplify to direct calls. bollinger.rs is now at 184/184 lines, no behavioural change. --- .../wickra-core/src/indicators/bollinger.rs | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/wickra-core/src/indicators/bollinger.rs b/crates/wickra-core/src/indicators/bollinger.rs index cb5c5a53..42745e8b 100644 --- a/crates/wickra-core/src/indicators/bollinger.rs +++ b/crates/wickra-core/src/indicators/bollinger.rs @@ -172,20 +172,23 @@ mod tests { use crate::traits::BatchExt; use approx::assert_relative_eq; - fn naive(prices: &[f64], period: usize, mult: f64) -> Option { - if prices.len() < period { - return None; - } + fn naive(prices: &[f64], period: usize, mult: f64) -> BollingerOutput { + assert!( + prices.len() >= period, + "naive: need at least `period` prices (got {}, period {})", + prices.len(), + period, + ); let w = &prices[prices.len() - period..]; let mean = w.iter().sum::() / period as f64; let var = w.iter().map(|x| (x - mean).powi(2)).sum::() / period as f64; let s = var.sqrt(); - Some(BollingerOutput { + BollingerOutput { upper: mean + mult * s, middle: mean, lower: mean - mult * s, stddev: s, - }) + } } #[test] @@ -212,6 +215,20 @@ mod tests { )); } + /// Cover the convenience constructor `BollingerBands::classic()` plus the + /// const accessors `period` / `multiplier` and the Indicator-impl + /// metadata methods `warmup_period` / `name`. Existing tests never + /// invoked `classic()` (every test passed explicit parameters to + /// `new`) and never queried any of the four getters. + #[test] + fn classic_and_accessors_and_metadata() { + let bb = BollingerBands::classic(); + assert_eq!(bb.period(), 20); + assert_relative_eq!(bb.multiplier(), 2.0, epsilon = 1e-12); + assert_eq!(bb.warmup_period(), 20); + assert_eq!(bb.name(), "BollingerBands"); + } + #[test] fn warmup_returns_none() { let mut bb = BollingerBands::new(5, 2.0).unwrap(); @@ -241,7 +258,7 @@ mod tests { let out = bb.batch(&prices); for i in 19..prices.len() { let got = out[i].unwrap(); - let want = naive(&prices[..=i], 20, 2.0).unwrap(); + let want = naive(&prices[..=i], 20, 2.0); assert_relative_eq!(got.middle, want.middle, epsilon = 1e-9); assert_relative_eq!(got.stddev, want.stddev, epsilon = 1e-9); assert_relative_eq!(got.upper, want.upper, epsilon = 1e-9); @@ -301,8 +318,7 @@ mod tests { } window.push_back(v); } - let scratch = - naive(&window.iter().copied().collect::>(), period, mult).expect("warmed up"); + let scratch = naive(&window.iter().copied().collect::>(), period, mult); let got = last.expect("warmed up"); assert!( (got.middle - scratch.middle).abs() < 1e-3,