Skip to main content

xchecker_utils/
ring_buffer.rs

1//! Ring buffer implementation for bounded output capture
2//!
3//! Provides fixed-size ring buffers for stdout and stderr capture with automatic truncation.
4
5use std::collections::VecDeque;
6use std::fmt;
7
8/// A ring buffer that maintains a fixed maximum size
9#[derive(Debug, Clone)]
10pub struct RingBuffer {
11    buffer: VecDeque<u8>,
12    max_bytes: usize,
13    total_bytes_written: usize,
14}
15
16impl RingBuffer {
17    /// Create a new ring buffer with the specified maximum size
18    #[must_use]
19    pub fn new(max_bytes: usize) -> Self {
20        Self {
21            buffer: VecDeque::with_capacity(max_bytes.min(8192)),
22            max_bytes,
23            total_bytes_written: 0,
24        }
25    }
26
27    /// Write data to the ring buffer
28    ///
29    /// If the buffer would exceed `max_bytes`, old data is dropped from the front.
30    pub fn write(&mut self, data: &[u8]) {
31        self.total_bytes_written += data.len();
32
33        for &byte in data {
34            if self.buffer.len() >= self.max_bytes {
35                // Buffer is full, remove oldest byte
36                self.buffer.pop_front();
37            }
38            self.buffer.push_back(byte);
39        }
40    }
41
42    /// Get the current size of the buffer in bytes
43    #[must_use]
44    #[allow(dead_code)] // Standard collection API method
45    pub fn len(&self) -> usize {
46        self.buffer.len()
47    }
48
49    /// Check if the buffer is empty
50    #[must_use]
51    #[allow(dead_code)] // Standard collection API method
52    pub fn is_empty(&self) -> bool {
53        self.buffer.is_empty()
54    }
55
56    /// Get the total number of bytes written (including truncated bytes)
57    #[must_use]
58    pub const fn total_bytes_written(&self) -> usize {
59        self.total_bytes_written
60    }
61
62    /// Check if any data was truncated
63    #[must_use]
64    pub const fn was_truncated(&self) -> bool {
65        self.total_bytes_written > self.max_bytes
66    }
67}
68
69impl fmt::Display for RingBuffer {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        let bytes: Vec<u8> = self.buffer.iter().copied().collect();
72        write!(f, "{}", String::from_utf8_lossy(&bytes))
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_ring_buffer_basic() {
82        let mut buffer = RingBuffer::new(10);
83        buffer.write(b"hello");
84        assert_eq!(buffer.to_string(), "hello");
85        assert_eq!(buffer.len(), 5);
86        assert!(!buffer.is_empty());
87    }
88
89    #[test]
90    fn test_ring_buffer_truncation() {
91        let mut buffer = RingBuffer::new(10);
92        buffer.write(b"hello");
93        buffer.write(b"world");
94        buffer.write(b"!");
95
96        // Total written: 11 bytes, but buffer only holds 10
97        assert_eq!(buffer.len(), 10);
98        assert_eq!(buffer.to_string(), "elloworld!");
99        assert_eq!(buffer.total_bytes_written(), 11);
100        assert!(buffer.was_truncated());
101    }
102
103    #[test]
104    fn test_ring_buffer_large_write() {
105        let mut buffer = RingBuffer::new(5);
106        buffer.write(b"hello world");
107
108        // Should only keep the last 5 bytes
109        assert_eq!(buffer.len(), 5);
110        assert_eq!(buffer.to_string(), "world");
111        assert_eq!(buffer.total_bytes_written(), 11);
112        assert!(buffer.was_truncated());
113    }
114
115    #[test]
116    fn test_ring_buffer_exact_capacity() {
117        let mut buffer = RingBuffer::new(10);
118        buffer.write(b"1234567890");
119
120        assert_eq!(buffer.len(), 10);
121        assert_eq!(buffer.to_string(), "1234567890");
122        assert!(!buffer.was_truncated());
123    }
124
125    #[test]
126    fn test_ring_buffer_multiple_writes() {
127        let mut buffer = RingBuffer::new(10);
128        buffer.write(b"12345");
129        buffer.write(b"67890");
130        buffer.write(b"ABCDE");
131
132        // Should keep last 10 bytes: "67890ABCDE"
133        assert_eq!(buffer.len(), 10);
134        assert_eq!(buffer.to_string(), "67890ABCDE");
135        assert_eq!(buffer.total_bytes_written(), 15);
136        assert!(buffer.was_truncated());
137    }
138
139    #[test]
140    fn test_ring_buffer_empty() {
141        let buffer = RingBuffer::new(10);
142        assert!(buffer.is_empty());
143        assert_eq!(buffer.len(), 0);
144        assert_eq!(buffer.to_string(), "");
145        assert!(!buffer.was_truncated());
146    }
147
148    #[test]
149    fn test_ring_buffer_utf8_handling() {
150        let mut buffer = RingBuffer::new(20);
151        buffer.write("Hello 世界".as_bytes());
152        assert_eq!(buffer.to_string(), "Hello 世界");
153    }
154
155    #[test]
156    fn test_ring_buffer_invalid_utf8() {
157        let mut buffer = RingBuffer::new(10);
158        // Write invalid UTF-8 sequence
159        buffer.write(&[0xFF, 0xFE, 0xFD]);
160        // Should use replacement character
161        let result = buffer.to_string();
162        assert!(!result.is_empty());
163    }
164}