Coverage for tests/io/test_buffer.py: 100%
68 statements
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-18 05:24 +0000
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-18 05:24 +0000
1"""EventAccumulator tests (evutils.io.buffer).
3The accumulator is the staging buffer behind the streaming pipeline generators.
4The stream tests only push small data, so the capacity-pressure paths -- rotate
5(reclaim consumed front), trigger-buffer grow, and the overflow guard -- are
6covered here directly.
7"""
8import numpy as np
9import pytest
11from evutils.types import EventArray, TriggerArray
12from evutils.io.buffer import EventAccumulator
15def ev(ts) -> EventArray:
16 ts = list(ts)
17 n = len(ts)
18 return EventArray(t=ts, x=[0] * n, y=[0] * n, p=[0] * n)
21def tr(ts) -> TriggerArray:
22 ts = list(ts)
23 n = len(ts)
24 return TriggerArray(t=ts, p=[0] * n, id=list(range(n)))
27def test_accumulator_slice_copy_advances():
28 acc = EventAccumulator(capacity=100)
29 acc.append(ev([0, 1, 2, 3]))
30 assert len(acc) == 4
31 e, _ = acc.slice_copy(2, 0)
32 assert e.t.tolist() == [0, 1]
33 assert len(acc) == 2
34 assert acc.t_window().tolist() == [2, 3]
37def test_accumulator_slice_copy_is_independent():
38 acc = EventAccumulator(capacity=100)
39 acc.append(ev([5, 6, 7]))
40 e, _ = acc.slice_copy(3, 0)
41 acc.reset()
42 acc.append(ev([99, 99, 99]))
43 assert e.t.tolist() == [5, 6, 7] # earlier copy untouched by later writes
46def test_accumulator_rotate_events_and_triggers():
47 """Consuming the front then appending past the tail room triggers _rotate,
48 which must preserve the unconsumed remainder of both events and triggers."""
49 acc = EventAccumulator(capacity=64) # trigger cap = 64 // 16 = 4
50 acc.append(ev(range(8)), tr([0, 3, 6]))
51 acc.slice_copy(5, 2) # consume 5 events, 2 triggers
52 assert acc.t_window().tolist() == [5, 6, 7]
54 acc.append(ev(range(100, 160)), tr([200, 201])) # 64-8 < 60 -> rotate
55 assert len(acc) == 63 # 3 remainder + 60 new
56 assert acc.t_window()[:3].tolist() == [5, 6, 7]
57 assert acc.t_window()[3:].tolist() == list(range(100, 160))
58 assert acc.t_window_tr().tolist() == [6, 200, 201]
61def test_accumulator_rotate_after_full_consume():
62 """Rotate when everything is consumed: the remainder is zero, so _rotate
63 resets the offsets without copying (the n==0 / n_tr==0 branches)."""
64 acc = EventAccumulator(capacity=10)
65 acc.append(ev(range(6)), tr([1, 2]))
66 acc.slice_copy(6, 2) # consume all events and triggers
67 assert len(acc) == 0
68 acc.append(ev(range(100, 108))) # 10-6 < 8 -> rotate, nothing to move
69 assert acc.t_window().tolist() == list(range(100, 108))
72def test_accumulator_grows_past_capacity():
73 """Appending more than the current capacity grows the buffer (rather than
74 erroring) so oversized windows and drain-to-EOF reads always fit."""
75 acc = EventAccumulator(capacity=5)
76 acc.append(ev(range(4)))
77 acc.append(ev(range(4))) # 8 > capacity 5 -> grow, no error
78 assert len(acc) == 8
79 assert acc.t_window().tolist() == [0, 1, 2, 3, 0, 1, 2, 3]
82def test_accumulator_trigger_buffer_grows():
83 """More triggers than the trigger buffer's capacity trigger a grow()."""
84 acc = EventAccumulator(capacity=32) # trigger cap = 32 // 16 = 2
85 acc.append(ev([0]), tr([1, 2, 3, 4, 5])) # 5 > 2 -> grow
86 assert acc.t_window_tr().tolist() == [1, 2, 3, 4, 5]
89def test_accumulator_prepare_rotates_for_headroom():
90 acc = EventAccumulator(capacity=10)
91 acc.append(ev(range(8)))
92 acc.slice_copy(5, 0) # start=5, 3 unconsumed
93 b, _ = acc.prepare(5) # 10-8 < 5 -> rotate
94 assert acc.t_window().tolist() == [5, 6, 7]
95 assert b.c.capacity == 8 # min(capacity, size + step) = min(10, 3+5)
98def test_accumulator_reset_clears():
99 acc = EventAccumulator(capacity=16)
100 acc.append(ev([1, 2, 3]), tr([1]))
101 acc.reset()
102 assert len(acc) == 0
103 assert acc.t_window().tolist() == []
104 assert acc.t_window_tr().tolist() == []