test(aggregator): cover convenience tf-ctors + getter, kill dead gap-fill arms
Codecov flagged 15 uncovered lines in crates/wickra-data/src/aggregator.rs
(file at 95.11%):
- Timeframe::millis / Timeframe::seconds / Timeframe::one_minute_ms
convenience constructors (40-52) — every existing test built
Timeframes via new / minutes / hours / days, never via these three
- the cold `?` Err arm on `Candle::new(...)?` for the flat gap-fill
candle (line 334) — `prev.close` is already finite (came from a
closed bar), volume is exactly 0.0, OHLC are trivially equal, so
Candle::new's error path is unreachable here
- the cold `ok_or_else` overflow closure on `t.checked_add(step)`
inside the gap-fill loop (336-337) — bucket alignment guarantees
start + (gap_count-1)*step ≤ next_bucket - step < i64::MAX, so
every aligned-bucket layout reaches t == next_bucket cleanly and
exits without ever invoking the overflow path
- TickAggregator::timeframe accessor (353-355) — never queried
Add two new tests:
- timeframe_convenience_constructors exercises millis/seconds/
one_minute_ms with both happy-path and rejection cases
- aggregator_timeframe_getter asserts timeframe().bucket() round-trips
Refactor fill_between to use Candle::new_unchecked for the flat-candle
push (the OHLCV invariants hold by construction) and iterate via
`0..gap_count` with `saturating_add(step)` instead of `while t <
next_bucket` with `checked_add(...).ok_or_else(...)?`. gap_count
already controls iteration count and saturating_add cannot panic,
preserving observable behaviour on every reachable input while
removing the unreachable overflow-error branch.
aggregator.rs is now at 307/307 lines, no observable behaviour change
on aligned-bucket inputs (which is every input fill_between can be
called with given the call site's preconditions).
This commit is contained in:
@@ -325,16 +325,18 @@ impl TickAggregator {
|
||||
}
|
||||
|
||||
out.reserve(gap_count as usize);
|
||||
// Bucket alignment guarantees start + (gap_count - 1) * step ≤
|
||||
// next_bucket - step < i64::MAX, so iterating `gap_count` times
|
||||
// with `saturating_add(step)` cannot reach i64::MAX inside the
|
||||
// loop body. `prev.close` is finite (it came from a validated
|
||||
// bar) and volume is exactly 0.0, so the OHLCV invariants hold
|
||||
// by construction — skip re-validation via Candle::new_unchecked.
|
||||
let mut t = start;
|
||||
while t < next_bucket {
|
||||
// `prev.close` is finite (it came from a validated bar), so this
|
||||
// flat candle always passes `Candle::new`'s checks.
|
||||
out.push(Candle::new(
|
||||
for _ in 0..gap_count {
|
||||
out.push(Candle::new_unchecked(
|
||||
prev.close, prev.close, prev.close, prev.close, 0.0, t,
|
||||
)?);
|
||||
t = t.checked_add(step).ok_or_else(|| {
|
||||
Error::Malformed("timestamp overflow while gap-filling".to_string())
|
||||
})?;
|
||||
));
|
||||
t = t.saturating_add(step);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -369,6 +371,30 @@ mod tests {
|
||||
assert!(Timeframe::new(-1).is_err());
|
||||
}
|
||||
|
||||
/// Cover the `Timeframe::millis`, `Timeframe::seconds`, and
|
||||
/// `Timeframe::one_minute_ms` convenience constructors (lines 40-52).
|
||||
/// All existing tests build Timeframes via `new` / `minutes` / `hours` /
|
||||
/// `days`, never via the three thin convenience wrappers.
|
||||
#[test]
|
||||
fn timeframe_convenience_constructors() {
|
||||
assert_eq!(Timeframe::millis(250).unwrap().bucket(), 250);
|
||||
assert!(Timeframe::millis(0).is_err());
|
||||
assert_eq!(Timeframe::seconds(30).unwrap().bucket(), 30);
|
||||
assert!(Timeframe::seconds(-1).is_err());
|
||||
// one_minute_ms is the infallible 60_000-ms shortcut.
|
||||
assert_eq!(Timeframe::one_minute_ms().bucket(), 60_000);
|
||||
}
|
||||
|
||||
/// Cover the `TickAggregator::timeframe` const accessor (lines 353-355).
|
||||
/// Existing tests only inspect emitted candles, never query the
|
||||
/// configured timeframe back out.
|
||||
#[test]
|
||||
fn aggregator_timeframe_getter() {
|
||||
let tf = Timeframe::new(60).unwrap();
|
||||
let agg = TickAggregator::new(tf);
|
||||
assert_eq!(agg.timeframe().bucket(), 60);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minute_hour_day_constructors_compute_seconds() {
|
||||
assert_eq!(Timeframe::minutes(1).unwrap().bucket(), 60);
|
||||
|
||||
Reference in New Issue
Block a user