Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ring_buffer_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 https://github.com/lrleon/Aleph-w
6
7 This file is part of Aleph-w library
8
9 Copyright (c) 2002-2026 Leandro Rabindranath Leon
10
11 Permission is hereby granted, free of charge, to any person obtaining a copy
12 of this software and associated documentation files (the "Software"), to deal
13 in the Software without restriction, including without limitation the rights
14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17
18 The above copyright notice and this permission notice shall be included in all
19 copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 SOFTWARE.
28*/
29
41#include <deque>
42#include <memory>
43#include <random>
44#include <stdexcept>
45#include <string>
46#include <type_traits>
47#include <utility>
48
49#include <gtest/gtest.h>
50
51#include <tpl_ring_buffer.H>
52
54
55namespace
56{
57// Instrumented element type: counts live instances so tests can prove that
58// placement-constructed elements are destroyed exactly once.
59struct Probe
60{
61 static int live;
62 int value = 0;
63
64 Probe() { ++live; }
65 explicit Probe(int v) : value(v) { ++live; }
66 Probe(const Probe &p) : value(p.value) { ++live; }
67 Probe(Probe &&p) noexcept : value(p.value) { ++live; }
68 Probe &operator=(const Probe &) = default;
69 Probe &operator=(Probe &&) noexcept = default;
70 ~Probe() { --live; }
71};
72
73int Probe::live = 0;
74
75struct CopyAssignableOnly
76{
77 int value = 0;
78
79 explicit CopyAssignableOnly(int v) : value(v) {}
80 CopyAssignableOnly(const CopyAssignableOnly &) = delete;
81 CopyAssignableOnly(CopyAssignableOnly &&) noexcept = default;
82 CopyAssignableOnly &operator=(const CopyAssignableOnly &) = default;
83 CopyAssignableOnly &operator=(CopyAssignableOnly &&) noexcept = default;
84};
85
86static_assert(std::is_copy_assignable_v<CopyAssignableOnly>);
87static_assert(not std::is_copy_constructible_v<CopyAssignableOnly>);
88
89template <typename U, typename = void>
90struct Has_Const_Put_Overwrite : std::false_type
91{
92};
93
94template <typename U>
95struct Has_Const_Put_Overwrite
96 <U, std::void_t<decltype(std::declval<RingBuffer<U> &>().put_overwrite
97 (std::declval<const U &>()))>> : std::true_type
98{
99};
100
101static_assert(not Has_Const_Put_Overwrite<CopyAssignableOnly>::value);
102} // namespace
103
104TEST(RingBuffer, ConstructionAndBasicState)
105{
106 RingBuffer<int> rb(3);
107 EXPECT_EQ(rb.capacity(), 3u);
108 EXPECT_EQ(rb.size(), 0u);
109 EXPECT_EQ(rb.available(), 3u);
110 EXPECT_TRUE(rb.is_empty());
111 EXPECT_FALSE(rb.is_full());
112
113 EXPECT_THROW(RingBuffer<int>(0), std::invalid_argument);
114}
115
116TEST(RingBuffer, FifoOrderWithWrapAround)
117{
118 RingBuffer<int> rb(3);
119 rb.put(1);
120 rb.put(2);
121 rb.put(3);
122 EXPECT_TRUE(rb.is_full());
123
124 EXPECT_EQ(rb.get(), 1);
125 EXPECT_EQ(rb.get(), 2);
126 rb.put(4); // wraps physically
127 rb.put(5);
128 EXPECT_TRUE(rb.is_full());
129
130 EXPECT_EQ(rb.get(), 3);
131 EXPECT_EQ(rb.get(), 4);
132 EXPECT_EQ(rb.get(), 5);
133 EXPECT_TRUE(rb.is_empty());
134}
135
136TEST(RingBuffer, OverflowAndUnderflowThrow)
137{
138 RingBuffer<int> rb(2);
139 rb.put(1);
140 rb.put(2);
141 EXPECT_THROW(rb.put(3), std::overflow_error);
142 EXPECT_THROW(rb.emplace(3), std::overflow_error);
143
144 (void) rb.get();
145 (void) rb.get();
146 EXPECT_THROW((void) rb.get(), std::underflow_error);
147 EXPECT_THROW((void) rb.get_first(), std::underflow_error);
148 EXPECT_THROW((void) rb.get_last(), std::underflow_error);
149}
150
151TEST(RingBuffer, PutOverwriteImplementsSlidingWindow)
152{
153 RingBuffer<int> rb(3);
154 EXPECT_FALSE(rb.put_overwrite(1));
155 EXPECT_FALSE(rb.put_overwrite(2));
156 EXPECT_FALSE(rb.put_overwrite(3));
157
158 // Buffer full: each further put_overwrite evicts the oldest element.
159 EXPECT_TRUE(rb.put_overwrite(4)); // evicts 1 → window {2, 3, 4}
160 EXPECT_TRUE(rb.put_overwrite(5)); // evicts 2 → window {3, 4, 5}
161
162 ASSERT_EQ(rb.size(), 3u);
163 EXPECT_EQ(rb[0], 3);
164 EXPECT_EQ(rb[1], 4);
165 EXPECT_EQ(rb[2], 5);
166 EXPECT_EQ(rb.get_first(), 3);
167 EXPECT_EQ(rb.get_last(), 5);
168}
169
170TEST(RingBuffer, LogicalIndexingAndFrontBack)
171{
173 rb.put("a");
174 rb.put("b");
175 rb.put("c");
176 (void) rb.get(); // drop "a"; head is now physical index 1
177 rb.put("d"); // physically wraps to index 0
178
179 EXPECT_EQ(rb[0], "b");
180 EXPECT_EQ(rb[1], "c");
181 EXPECT_EQ(rb[2], "d");
182 EXPECT_EQ(rb.front(), "b");
183 EXPECT_EQ(rb.back(), "d");
184 EXPECT_THROW((void) rb[3], std::out_of_range);
185
186 rb[0] = "B"; // mutable indexed access
187 EXPECT_EQ(rb.get_first(), "B");
188}
189
190TEST(RingBuffer, IterationOldestToNewestAcrossWrap)
191{
192 RingBuffer<int> rb(4);
193 for (int i = 1; i <= 4; ++i)
194 rb.put(i);
195 (void) rb.get();
196 (void) rb.get();
197 rb.put(5);
198 rb.put(6); // logical: 3 4 5 6, physically wrapped
199
200 std::deque<int> seen;
201 for (int x : rb)
202 seen.push_back(x);
203 EXPECT_EQ(seen, (std::deque<int>{3, 4, 5, 6}));
204
205 // Random access iterator operations.
206 auto it = rb.begin();
207 EXPECT_EQ(rb.end() - it, 4);
208 EXPECT_EQ(it[2], 5);
209 EXPECT_EQ(*(it + 3), 6);
210 auto tail = rb.end();
211 tail += -1;
212 EXPECT_EQ(*tail, 6);
213 EXPECT_EQ(tail[-2], 4);
214 auto mid = rb.begin() + 1;
215 mid -= -2;
216 EXPECT_EQ(*mid, 6);
217 RingBuffer<int> other(4);
218 other.put(30);
219 other.put(40);
220 other.put(50);
221 other.put(60);
222 EXPECT_NE(rb.begin(), other.begin());
223 EXPECT_TRUE(rb.begin() == rb.begin());
224
225 // Mutation through iterators.
226 for (int &x : rb)
227 x *= 10;
228 EXPECT_EQ(rb[0], 30);
229 EXPECT_EQ(rb[3], 60);
230
231 // Const iteration.
232 const RingBuffer<int> &crb = rb;
233 int sum = 0;
234 for (int x : crb)
235 sum += x;
236 EXPECT_EQ(sum, 180);
237}
238
239TEST(RingBuffer, TraverseInFifoOrderWithEarlyStop)
240{
241 RingBuffer<int> rb(3);
242 rb.put(1);
243 rb.put(2);
244 rb.put(3);
245
246 int sum = 0;
247 EXPECT_TRUE(rb.traverse([&sum] (int &x) { sum += x; return true; }));
248 EXPECT_EQ(sum, 6);
249
250 int visited = 0;
251 const RingBuffer<int> &crb = rb;
252 EXPECT_FALSE(crb.traverse([&visited] (const int &x)
253 {
254 ++visited;
255 return x < 2;
256 }));
257 EXPECT_EQ(visited, 2);
258}
259
260TEST(RingBuffer, MoveOnlyElementsAreSupported)
261{
263 rb.put(std::make_unique<int>(1));
264 rb.emplace(std::make_unique<int>(2));
265 ASSERT_TRUE(rb.is_full());
266
267 std::unique_ptr<int> p = rb.get();
268 EXPECT_EQ(*p, 1);
269
270 rb.put_overwrite(std::make_unique<int>(3));
271 rb.put_overwrite(std::make_unique<int>(4)); // evicts 2
272 ASSERT_EQ(rb.size(), 2u);
273 EXPECT_EQ(*rb[0], 3);
274 EXPECT_EQ(*rb[1], 4);
275}
276
277TEST(RingBuffer, ElementLifetimesAreBalanced)
278{
279 ASSERT_EQ(Probe::live, 0);
280 {
281 RingBuffer<Probe> rb(3);
282 EXPECT_EQ(Probe::live, 0); // raw storage: no default constructions
283 rb.emplace(1);
284 rb.emplace(2);
285 rb.emplace(3);
286 EXPECT_EQ(Probe::live, 3);
287 (void) rb.get();
288 EXPECT_EQ(Probe::live, 2);
289 rb.put_overwrite(Probe(7));
290 rb.put_overwrite(Probe(8)); // eviction by assignment: count stable
291 EXPECT_EQ(Probe::live, 3);
292 rb.clear();
293 EXPECT_EQ(Probe::live, 0);
294 rb.emplace(9);
295 }
296 EXPECT_EQ(Probe::live, 0); // destructor cleaned everything
297}
298
299TEST(RingBuffer, CopyAndMoveSemantics)
300{
301 RingBuffer<int> rb(3);
302 rb.put(1);
303 rb.put(2);
304 rb.put(3);
305 (void) rb.get();
306 rb.put(4); // wrapped: logical {2, 3, 4}
307
308 RingBuffer<int> copy = rb;
309 EXPECT_EQ(copy, rb);
310 EXPECT_EQ(copy.capacity(), rb.capacity());
311 EXPECT_EQ(copy.get(), 2); // copy is independent
312 EXPECT_EQ(rb.size(), 3u);
313
314 RingBuffer<int> moved = std::move(rb);
315 ASSERT_EQ(moved.size(), 3u);
316 EXPECT_EQ(moved[0], 2);
317 EXPECT_EQ(moved[2], 4);
318
319 RingBuffer<int> assigned(1);
320 assigned = moved; // copy assignment replaces capacity and contents
321 EXPECT_EQ(assigned.capacity(), 3u);
322 EXPECT_EQ(assigned, moved);
323}
324
325TEST(RingBuffer, EqualityComparesLogicalContentOnly)
326{
327 RingBuffer<int> a(3);
328 RingBuffer<int> b(5); // different capacity
329 a.put(1);
330 a.put(2);
331 b.put(1);
332 b.put(2);
333 EXPECT_TRUE(a == b);
334 b.put(3);
335 EXPECT_TRUE(a != b);
336}
337
338TEST(RingBuffer, AlephConventionsEmptyClear)
339{
340 RingBuffer<int> rb(2);
341 rb.put(1);
342 rb.put(2);
343 rb.empty(); // Aleph convention: empty() clears
344 EXPECT_TRUE(rb.is_empty());
345 EXPECT_EQ(rb.available(), 2u);
346 rb.put(3); // reusable after clearing
347 EXPECT_EQ(rb.get_first(), 3);
348}
349
350// Randomized parity test: RingBuffer must behave like a std::deque bounded
351// to the same capacity under an arbitrary interleaving of puts, gets and
352// sliding-window overwrites.
353TEST(RingBuffer, RandomizedParityWithBoundedDeque)
354{
355 std::mt19937 rng(20260702);
356 std::uniform_int_distribution<int> val_dist(0, 1000000);
357 std::uniform_int_distribution<int> op_dist(0, 3);
358 constexpr size_t cap = 8;
359
360 RingBuffer<int> rb(cap);
361 std::deque<int> ref;
362
363 for (int step = 0; step < 4000; ++step)
364 {
365 const int v = val_dist(rng);
366 switch (op_dist(rng))
367 {
368 case 0: // bounded put
369 if (ref.size() < cap)
370 {
371 rb.put(v);
372 ref.push_back(v);
373 }
374 else
375 EXPECT_THROW(rb.put(v), std::overflow_error);
376 break;
377 case 1: // sliding-window put
378 rb.put_overwrite(v);
379 ref.push_back(v);
380 if (ref.size() > cap)
381 ref.pop_front();
382 break;
383 case 2: // extraction
384 if (ref.empty())
385 EXPECT_THROW((void) rb.get(), std::underflow_error);
386 else
387 {
388 EXPECT_EQ(rb.get(), ref.front());
389 ref.pop_front();
390 }
391 break;
392 default: // full logical window comparison
393 ASSERT_EQ(rb.size(), ref.size());
394 for (size_t i = 0; i < ref.size(); ++i)
395 EXPECT_EQ(rb[i], ref[i]);
396 break;
397 }
398 }
399}
size_t size_t int32_t value
Definition ca-c-api.h:116
Fixed-capacity circular FIFO buffer over contiguous storage.
void empty() noexcept
Destroy all elements (Aleph convention).
T & front()
Oldest element (checked). Alias of get_first().
size_t size() const noexcept
Return the number of stored elements. O(1).
iterator begin() noexcept
Iterator on the oldest element. O(1).
T & emplace(Args &&...args)
Construct an element in place at the tail.
T & put(const T &item)
Append a copy of item at the tail.
bool is_empty() const noexcept
Return true if no elements are stored. O(1).
T & get_last()
Newest element — the last one inserted (checked).
void clear() noexcept
Destroy all elements. Alias of empty(). Capacity is kept.
T & get_first()
Oldest element — the next to leave (checked).
bool is_full() const noexcept
Return true if the buffer holds capacity() elements. O(1).
iterator end() noexcept
Iterator past the newest element. O(1).
T & back()
Newest element (checked). Alias of get_last().
T get()
Extract the oldest element from the head.
bool traverse(Operation operation)
Traverse from oldest to newest while operation returns true.
size_t capacity() const noexcept
Return the fixed capacity chosen at construction. O(1).
bool put_overwrite(const T &item)
Append at the tail, evicting the oldest element when full.
size_t available() const noexcept
Return the number of free slots. O(1).
#define TEST(name)
static mt19937 rng
STL namespace.
Bounded circular buffer (Aleph::RingBuffer) for FIFO streaming.