• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kaidokert / picojson-rs / 16710739468

03 Aug 2025 11:44PM UTC coverage: 93.537% (-0.9%) from 94.472%
16710739468

Pull #81

github

web-flow
Merge 089ae7d88 into 31328b537
Pull Request #81: Re-introduce Push parser

319 of 398 new or added lines in 5 files covered. (80.15%)

15 existing lines in 1 file now uncovered.

4979 of 5323 relevant lines covered (93.54%)

1315.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.83
/picojson/src/stream_buffer.rs
1
// SPDX-License-Identifier: Apache-2.0
2

3
/// Error types for StreamBuffer operations
4
#[derive(Debug, PartialEq)]
5
pub enum StreamBufferError {
6
    /// Buffer is full and cannot accommodate more data
7
    BufferFull,
8
    /// Attempted to read beyond available data
9
    EndOfData,
10
    /// An unexpected error occurred.
11
    Unexpected,
12
    /// Invalid slice bounds provided for string extraction
13
    InvalidSliceBounds,
14
}
15

16
/// StreamBuffer manages a single buffer for both input and escape processing
17
///
18
/// Key design principles:
19
/// - Reader fills unused portions of buffer directly
20
/// - Unescaped content is copied to buffer start when needed
21
/// - Zero-copy string extraction when no escapes are present
22
/// - Guaranteed space for escape processing (unescaped ≤ escaped)
23
pub struct StreamBuffer<'a> {
24
    /// The entire buffer slice
25
    buffer: &'a mut [u8],
26
    /// Current position where tokenizer is reading
27
    tokenize_pos: usize,
28
    /// End of valid data from Reader (buffer[0..data_end] contains valid data)
29
    data_end: usize,
30
    /// Length of unescaped content at buffer start (0 if no unescaping active)
31
    unescaped_len: usize,
32
}
33

34
impl<'a> StreamBuffer<'a> {
35
    /// Panic-free copy_within implementation that handles overlapping ranges
36
    /// Based on memmove behavior but without panic machinery
37
    fn safe_copy_within(&mut self, src_start: usize, src_end: usize, dest: usize) {
892✔
38
        let count = src_end.saturating_sub(src_start);
892✔
39

40
        // Early return if nothing to copy or bounds are invalid
41
        if count == 0
892✔
42
            || src_start >= self.buffer.len()
892✔
43
            || src_end > self.buffer.len()
892✔
44
            || dest >= self.buffer.len()
892✔
45
        {
46
            return;
×
47
        }
892✔
48

49
        // Ensure dest + count doesn't exceed buffer
50
        let max_copy = (self.buffer.len().saturating_sub(dest)).min(count);
892✔
51
        if max_copy == 0 {
892✔
52
            return;
×
53
        }
892✔
54

55
        let iterator: &mut dyn Iterator<Item = usize> = if dest <= src_start {
892✔
56
            &mut (0..max_copy)
892✔
57
        } else {
58
            &mut (0..max_copy).rev()
×
59
        };
60

61
        for i in iterator {
8,314✔
62
            if let (Some(src_byte), Some(dest_slot)) = (
7,422✔
63
                self.buffer.get(src_start.wrapping_add(i)).copied(),
7,422✔
64
                self.buffer.get_mut(dest.wrapping_add(i)),
7,422✔
65
            ) {
7,422✔
66
                *dest_slot = src_byte;
7,422✔
67
            }
7,422✔
68
        }
69
    }
892✔
70
    /// Create a new StreamBuffer with the given buffer slice
71
    pub fn new(buffer: &'a mut [u8]) -> Self {
2,157✔
72
        Self {
2,157✔
73
            buffer,
2,157✔
74
            tokenize_pos: 0,
2,157✔
75
            data_end: 0,
2,157✔
76
            unescaped_len: 0,
2,157✔
77
        }
2,157✔
78
    }
2,157✔
79

80
    /// Get the current byte at tokenize position
81
    pub fn current_byte(&self) -> Result<u8, StreamBufferError> {
18,391✔
82
        if self.tokenize_pos >= self.data_end {
18,391✔
83
            return Err(StreamBufferError::EndOfData);
1✔
84
        }
18,390✔
85
        self.buffer
18,390✔
86
            .get(self.tokenize_pos)
18,390✔
87
            .copied()
18,390✔
88
            .ok_or(StreamBufferError::EndOfData)
18,390✔
89
    }
18,391✔
90

91
    /// Advance the tokenize position by one byte
92
    pub fn advance(&mut self) -> Result<(), StreamBufferError> {
18,420✔
93
        if self.tokenize_pos >= self.data_end {
18,420✔
94
            return Err(StreamBufferError::EndOfData);
1✔
95
        }
18,419✔
96
        self.tokenize_pos = self.tokenize_pos.wrapping_add(1);
18,419✔
97
        Ok(())
18,419✔
98
    }
18,420✔
99

100
    /// Get remaining bytes available for reading
101
    pub fn remaining_bytes(&self) -> usize {
356✔
102
        self.data_end.saturating_sub(self.tokenize_pos)
356✔
103
    }
356✔
104

105
    /// Get slice for Reader to fill with new data
106
    /// Returns None if no space available
107
    pub fn get_fill_slice(&mut self) -> Option<&mut [u8]> {
13,226✔
108
        if self.data_end >= self.buffer.len() {
13,226✔
109
            return None;
1,796✔
110
        }
11,430✔
111
        self.buffer.get_mut(self.data_end..)
11,430✔
112
    }
13,226✔
113

114
    /// Compact buffer by moving unprocessed data from a given start offset to the beginning.
115
    ///
116
    /// # Arguments
117
    /// * `start_offset` - The position from which to preserve data.
118
    ///
119
    /// Returns the offset by which data was moved.
120
    pub fn compact_from(&mut self, start_offset: usize) -> Result<usize, StreamBufferError> {
1,801✔
121
        if start_offset == 0 {
1,801✔
122
            // Already at start, no compaction possible
123
            return Ok(0);
532✔
124
        }
1,269✔
125

126
        let offset = start_offset;
1,269✔
127

128
        if start_offset >= self.data_end {
1,269✔
129
            // All data has been processed, reset to start
130
            self.tokenize_pos = 0;
648✔
131
            self.data_end = 0;
648✔
132
            return Ok(offset);
648✔
133
        }
621✔
134

135
        // Move unprocessed data to start of buffer
136
        let remaining_data = self.data_end.saturating_sub(start_offset);
621✔
137

138
        // Copy existing content if there is any - EXACT same pattern as start_unescaping_with_copy
139
        if self.data_end > start_offset {
621✔
140
            let span_len = remaining_data;
621✔
141

142
            // Ensure the span fits in the buffer - return error instead of silent truncation
143
            if span_len > self.buffer.len() {
621✔
144
                return Err(StreamBufferError::BufferFull);
×
145
            }
621✔
146

147
            let src_range_end = start_offset
621✔
148
                .checked_add(span_len)
621✔
149
                .ok_or(StreamBufferError::InvalidSliceBounds)?;
621✔
150

151
            if src_range_end > self.buffer.len() {
621✔
152
                return Err(StreamBufferError::InvalidSliceBounds);
×
153
            }
621✔
154
            let src_range = start_offset..src_range_end;
621✔
155

156
            // Copy within the same buffer: move data from [start_offset..end] to [0..span_len]
157
            // Use our panic-free copy implementation
158
            self.safe_copy_within(src_range.start, src_range.end, 0);
621✔
159
        }
×
160

161
        // Update positions
162
        self.tokenize_pos = self.tokenize_pos.saturating_sub(offset);
621✔
163
        self.data_end = remaining_data;
621✔
164

165
        Ok(offset)
621✔
166
    }
1,801✔
167

168
    /// Mark that Reader filled `bytes_read` bytes
169
    pub fn mark_filled(&mut self, bytes_read: usize) -> Result<(), StreamBufferError> {
6,352✔
170
        let new_data_end = self.data_end.wrapping_add(bytes_read);
6,352✔
171
        if new_data_end > self.buffer.len() {
6,352✔
172
            return Err(StreamBufferError::Unexpected);
×
173
        }
6,352✔
174
        self.data_end = new_data_end;
6,352✔
175
        Ok(())
6,352✔
176
    }
6,352✔
177

178
    /// Start unescaping and copy existing content from a range in the buffer
179
    /// This handles the common case of starting escape processing partway through a string
180
    pub fn start_unescaping_with_copy(
347✔
181
        &mut self,
347✔
182
        max_escaped_len: usize,
347✔
183
        copy_start: usize,
347✔
184
        copy_end: usize,
347✔
185
    ) -> Result<(), StreamBufferError> {
347✔
186
        // Clear any previous unescaped content
187
        self.unescaped_len = 0;
347✔
188

189
        // Ensure we have space at the start for unescaping
190
        if max_escaped_len > self.buffer.len() {
347✔
191
            return Err(StreamBufferError::BufferFull);
×
192
        }
347✔
193

194
        // Copy existing content if there is any
195
        if copy_end > copy_start && copy_start < self.data_end {
347✔
196
            let span_len = copy_end.saturating_sub(copy_start);
272✔
197

198
            // Ensure the span fits in the buffer - return error instead of silent truncation
199
            if span_len > self.buffer.len() {
272✔
200
                return Err(StreamBufferError::BufferFull);
1✔
201
            }
271✔
202

203
            let src_range = copy_start..copy_start.wrapping_add(span_len);
271✔
204
            if src_range.end > self.buffer.len() {
271✔
205
                return Err(StreamBufferError::InvalidSliceBounds);
×
206
            }
271✔
207

208
            // Copy within the same buffer: move data from [copy_start..copy_end] to [0..span_len]
209
            // Use our panic-free copy implementation to handle overlapping ranges safely
210
            self.safe_copy_within(src_range.start, src_range.end, 0);
271✔
211
            self.unescaped_len = span_len;
271✔
212
        }
75✔
213

214
        Ok(())
346✔
215
    }
347✔
216

217
    /// Get the unescaped content slice
218
    pub fn get_unescaped_slice(&self) -> Result<&[u8], StreamBufferError> {
2,203✔
219
        if self.unescaped_len == 0 {
2,203✔
220
            return Err(StreamBufferError::InvalidSliceBounds);
1✔
221
        }
2,202✔
222
        self.buffer
2,202✔
223
            .get(0..self.unescaped_len)
2,202✔
224
            .ok_or(StreamBufferError::Unexpected)
2,202✔
225
    }
2,203✔
226

227
    /// Clear unescaped content (call after yielding unescaped string)
228
    pub fn clear_unescaped(&mut self) {
3,302✔
229
        self.unescaped_len = 0;
3,302✔
230
    }
3,302✔
231

232
    /// Get current tokenize position (for string start tracking)
233
    pub fn current_position(&self) -> usize {
3,388✔
234
        self.tokenize_pos
3,388✔
235
    }
3,388✔
236

237
    /// Check if buffer is empty (no more data to process)
238
    pub fn is_empty(&self) -> bool {
38,260✔
239
        self.tokenize_pos >= self.data_end
38,260✔
240
    }
38,260✔
241

242
    /// Check if we have unescaped content ready
243
    pub fn has_unescaped_content(&self) -> bool {
8,144✔
244
        self.unescaped_len > 0
8,144✔
245
    }
8,144✔
246

247
    /// Append a single byte to the unescaped content
248
    pub fn append_unescaped_byte(&mut self, byte: u8) -> Result<(), StreamBufferError> {
10,422✔
249
        if let Some(b) = self.buffer.get_mut(self.unescaped_len) {
10,422✔
250
            *b = byte;
10,322✔
251
            self.unescaped_len = self.unescaped_len.wrapping_add(1);
10,322✔
252
            Ok(())
10,322✔
253
        } else {
254
            Err(StreamBufferError::BufferFull)
100✔
255
        }
256
    }
10,422✔
257

258
    /// Get a string slice from the buffer (zero-copy)
259
    /// Used for strings without escapes
260
    pub fn get_string_slice(&self, start: usize, end: usize) -> Result<&[u8], StreamBufferError> {
872✔
261
        if start > end || end > self.data_end {
872✔
NEW
262
            return Err(StreamBufferError::InvalidSliceBounds);
×
263
        }
872✔
264
        self.buffer
872✔
265
            .get(start..end)
872✔
266
            .ok_or(StreamBufferError::InvalidSliceBounds)
872✔
267
    }
872✔
268
}
269

270
#[cfg(test)]
271
mod tests {
272
    use super::*;
273
    use crate::shared::State;
274

275
    #[test]
276
    fn test_lifetime_expectations() {
1✔
277
        // This test demonstrates how StreamBuffer lifetimes should work
278
        let mut buffer = [0u8; 100];
1✔
279
        let mut stream_buffer = StreamBuffer::new(&mut buffer);
1✔
280

281
        // Simulate some data being in the buffer
282
        let test_data = b"hello world";
1✔
283
        stream_buffer.buffer[0..test_data.len()].copy_from_slice(test_data);
1✔
284
        stream_buffer.data_end = test_data.len();
1✔
285

286
        // Test that we can get buffer data
287

288
        // Test unescaped content - add some unescaped data
289
        stream_buffer.unescaped_len = 3;
1✔
290
        stream_buffer.buffer[0..3].copy_from_slice(b"abc");
1✔
291

292
        let unescaped_slice = stream_buffer.get_unescaped_slice().unwrap();
1✔
293
        assert_eq!(unescaped_slice, b"abc");
1✔
294

295
        // The key expectation: these slices should live as long as the original buffer
296
        // and be usable to create String::Borrowed(&'buffer str) and String::Unescaped(&'buffer str)
297
    }
1✔
298

299
    #[test]
300
    fn test_new_stream_buffer() {
1✔
301
        let mut buffer = [0u8; 100];
1✔
302
        let db = StreamBuffer::new(&mut buffer);
1✔
303

304
        assert_eq!(db.tokenize_pos, 0);
1✔
305
        assert_eq!(db.data_end, 0);
1✔
306
        assert_eq!(db.unescaped_len, 0);
1✔
307
        assert!(db.is_empty());
1✔
308
    }
1✔
309

310
    #[test]
311
    fn test_fill_and_advance() {
1✔
312
        let mut buffer = [0u8; 100];
1✔
313
        let mut db = StreamBuffer::new(&mut buffer);
1✔
314

315
        // Fill with some data
316
        {
1✔
317
            let fill_slice = db.get_fill_slice().unwrap();
1✔
318
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
319
        }
1✔
320
        db.mark_filled(5).unwrap();
1✔
321

322
        assert_eq!(db.data_end, 5);
1✔
323
        assert_eq!(db.remaining_bytes(), 5);
1✔
324

325
        // Read bytes
326
        assert_eq!(db.current_byte().unwrap(), b'h');
1✔
327
        db.advance().unwrap();
1✔
328
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
329
        assert_eq!(db.remaining_bytes(), 4);
1✔
330
    }
1✔
331

332
    #[test]
333
    fn test_error_conditions() {
1✔
334
        let mut buffer = [0u8; 10];
1✔
335
        let mut db = StreamBuffer::new(&mut buffer);
1✔
336

337
        // EndOfData errors
338
        assert_eq!(db.current_byte().unwrap_err(), StreamBufferError::EndOfData);
1✔
339
        assert_eq!(db.advance().unwrap_err(), StreamBufferError::EndOfData);
1✔
340

341
        // No unescaped content
342
        assert!(db.get_unescaped_slice().is_err());
1✔
343
    }
1✔
344

345
    #[test]
346
    fn test_buffer_full_scenario() {
1✔
347
        // Test what happens when buffer gets completely full
348
        let mut buffer = [0u8; 10];
1✔
349
        let mut db = StreamBuffer::new(&mut buffer);
1✔
350

351
        // Fill buffer completely
352
        {
1✔
353
            let fill_slice = db.get_fill_slice().unwrap();
1✔
354
            fill_slice.copy_from_slice(b"0123456789");
1✔
355
        }
1✔
356
        db.mark_filled(10).unwrap();
1✔
357

358
        // No more space for filling
359
        assert!(db.get_fill_slice().is_none());
1✔
360

361
        // We can still read from buffer
362
        assert_eq!(db.current_byte().unwrap(), b'0');
1✔
363
        assert_eq!(db.remaining_bytes(), 10);
1✔
364
    }
1✔
365

366
    #[test]
367
    fn test_minimal_buffer_with_long_token() {
1✔
368
        // Test very small buffer with a token that doesn't fit
369
        let mut buffer = [0u8; 8]; // Very small buffer
1✔
370
        let mut db = StreamBuffer::new(&mut buffer);
1✔
371

372
        // Try to put a string that's almost as big as the buffer
373
        {
1✔
374
            let fill_slice = db.get_fill_slice().unwrap();
1✔
375
            fill_slice[0..6].copy_from_slice(b"\"hello"); // Start of a long string, no closing quote
1✔
376
        }
1✔
377
        db.mark_filled(6).unwrap();
1✔
378

379
        // Advance through the data
380
        for _ in 0..6 {
7✔
381
            db.advance().unwrap();
6✔
382
        }
6✔
383

384
        // Now buffer is exhausted but we don't have a complete token
385
        assert!(db.is_empty());
1✔
386
        assert_eq!(db.remaining_bytes(), 0);
1✔
387

388
        // This simulates the scenario where we need more data but can't fit it
389
        // The parser would need to handle this by buffering the incomplete token
390
    }
1✔
391

392
    #[test]
393
    fn test_reader_returns_zero_bytes() {
1✔
394
        let mut buffer = [0u8; 20];
1✔
395
        let mut db = StreamBuffer::new(&mut buffer);
1✔
396

397
        // Simulate Reader returning 0 bytes (EOF)
398
        {
399
            let fill_slice = db.get_fill_slice().unwrap();
1✔
400
            assert_eq!(fill_slice.len(), 20);
1✔
401
            // Reader returns 0 bytes - simulating EOF or no data available
402
        }
403
        db.mark_filled(0).unwrap(); // Reader returned 0
1✔
404

405
        assert!(db.is_empty());
1✔
406
        assert_eq!(db.data_end, 0);
1✔
407
        assert_eq!(db.remaining_bytes(), 0);
1✔
408

409
        // Should still be able to get fill slice for next attempt
410
        let fill_slice = db.get_fill_slice().unwrap();
1✔
411
        assert_eq!(fill_slice.len(), 20);
1✔
412
    }
1✔
413

414
    #[test]
415
    fn test_boundary_conditions() {
1✔
416
        let mut buffer = [0u8; 3]; // Absolute minimum
1✔
417
        let mut db = StreamBuffer::new(&mut buffer);
1✔
418

419
        // Can't even hold a proper JSON token, but should not crash
420
        {
1✔
421
            let fill_slice = db.get_fill_slice().unwrap();
1✔
422
            fill_slice.copy_from_slice(b"\"a\"");
1✔
423
        }
1✔
424
        db.mark_filled(3).unwrap();
1✔
425

426
        // Should be able to read through it
427
        assert_eq!(db.current_byte().unwrap(), b'"');
1✔
428
        db.advance().unwrap();
1✔
429
        assert_eq!(db.current_byte().unwrap(), b'a');
1✔
430
        db.advance().unwrap();
1✔
431
        assert_eq!(db.current_byte().unwrap(), b'"');
1✔
432
        db.advance().unwrap();
1✔
433

434
        assert!(db.is_empty());
1✔
435
    }
1✔
436

437
    #[test]
438
    fn test_start_unescaping_with_copy_span_too_large() {
1✔
439
        let mut buffer = [0u8; 10]; // Small buffer
1✔
440
        let mut db = StreamBuffer::new(&mut buffer);
1✔
441

442
        // Fill buffer with some data
443
        {
1✔
444
            let fill_slice = db.get_fill_slice().unwrap();
1✔
445
            fill_slice.copy_from_slice(b"0123456789");
1✔
446
        }
1✔
447
        db.mark_filled(10).unwrap();
1✔
448

449
        // Try to copy a span that's larger than the entire buffer
450
        let copy_start = 0;
1✔
451
        let copy_end = 15; // This span (15 bytes) is larger than buffer (10 bytes)
1✔
452
        let max_escaped_len = 5; // This is fine
1✔
453

454
        // Should return BufferFull error instead of silently truncating
455
        let result = db.start_unescaping_with_copy(max_escaped_len, copy_start, copy_end);
1✔
456
        assert_eq!(result.unwrap_err(), StreamBufferError::BufferFull);
1✔
457

458
        // Test boundary case: span exactly equals buffer size should work
459
        let copy_end_exact = 10; // Span of exactly 10 bytes (buffer size)
1✔
460
        let result = db.start_unescaping_with_copy(max_escaped_len, 0, copy_end_exact);
1✔
461
        assert!(result.is_ok());
1✔
462
        assert_eq!(db.unescaped_len, 10);
1✔
463

464
        // Test valid smaller span should work
465
        db.clear_unescaped();
1✔
466
        let result = db.start_unescaping_with_copy(max_escaped_len, 2, 6); // 4 byte span
1✔
467
        assert!(result.is_ok());
1✔
468
        assert_eq!(db.unescaped_len, 4);
1✔
469
        assert_eq!(db.get_unescaped_slice().unwrap(), b"2345");
1✔
470
    }
1✔
471

472
    #[test]
473
    fn test_append_unescaped_byte_uses_full_buffer() {
1✔
474
        let mut buffer = [0u8; 10]; // 10 byte buffer
1✔
475
        let mut db = StreamBuffer::new(&mut buffer);
1✔
476

477
        // Should be able to append up to buffer_len bytes (no more escape reserve!)
478
        for i in 0..10 {
11✔
479
            let result = db.append_unescaped_byte(b'A');
10✔
480
            assert!(result.is_ok(), "Failed at byte {}", i);
10✔
481
        }
482

483
        assert_eq!(db.unescaped_len, 10);
1✔
484

485
        // One more byte should fail because buffer is full
486
        let result = db.append_unescaped_byte(b'B');
1✔
487
        assert_eq!(result.unwrap_err(), StreamBufferError::BufferFull);
1✔
488
    }
1✔
489

490
    #[test]
491
    fn test_compact_basic() {
1✔
492
        let mut buffer = [0u8; 10];
1✔
493
        let mut db = StreamBuffer::new(&mut buffer);
1✔
494

495
        // Fill buffer with data: "0123456789"
496
        {
1✔
497
            let fill_slice = db.get_fill_slice().unwrap();
1✔
498
            fill_slice.copy_from_slice(b"0123456789");
1✔
499
        }
1✔
500
        db.mark_filled(10).unwrap();
1✔
501

502
        // Process some data (advance tokenize_pos to position 4)
503
        for _ in 0..4 {
5✔
504
            db.advance().unwrap();
4✔
505
        }
4✔
506

507
        // Before compact: tokenize_pos=4, data_end=10, remaining="456789"
508
        assert_eq!(db.tokenize_pos, 4);
1✔
509
        assert_eq!(db.data_end, 10);
1✔
510
        assert_eq!(db.remaining_bytes(), 6);
1✔
511

512
        // Compact the buffer
513
        let offset = db.compact_from(4).unwrap();
1✔
514
        assert_eq!(offset, 4); // Data was moved by 4 positions
1✔
515

516
        // After compact: tokenize_pos=0, data_end=6, buffer starts with "456789"
517
        assert_eq!(db.tokenize_pos, 0);
1✔
518
        assert_eq!(db.data_end, 6);
1✔
519
        assert_eq!(db.remaining_bytes(), 6);
1✔
520

521
        // Verify the data was moved correctly
522
        assert_eq!(db.current_byte().unwrap(), b'4');
1✔
523
        db.advance().unwrap();
1✔
524
        assert_eq!(db.current_byte().unwrap(), b'5');
1✔
525
        db.advance().unwrap();
1✔
526
        assert_eq!(db.current_byte().unwrap(), b'6');
1✔
527
    }
1✔
528

529
    #[test]
530
    fn test_compact_from_preserves_number() {
1✔
531
        let mut buffer = [0u8; 10];
1✔
532
        let mut db = StreamBuffer::new(&mut buffer);
1✔
533
        db.buffer.copy_from_slice(b"0123456789");
1✔
534
        db.data_end = 10;
1✔
535
        db.tokenize_pos = 5;
1✔
536
        let number_start_pos = 3;
1✔
537

538
        let offset = db.compact_from(number_start_pos).unwrap();
1✔
539
        assert_eq!(offset, 3);
1✔
540
        assert_eq!(db.tokenize_pos, 2); // 5 - 3
1✔
541
        assert_eq!(db.data_end, 7); // 10 - 3
1✔
542
        assert_eq!(&db.buffer[..db.data_end], b"3456789");
1✔
543
    }
1✔
544

545
    #[test]
546
    fn test_compact_no_op_when_at_start() {
1✔
547
        let mut buffer = [0u8; 10];
1✔
548
        let mut db = StreamBuffer::new(&mut buffer);
1✔
549

550
        // Fill buffer with data
551
        {
1✔
552
            let fill_slice = db.get_fill_slice().unwrap();
1✔
553
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
554
        }
1✔
555
        db.mark_filled(5).unwrap();
1✔
556

557
        // Don't advance tokenize_pos (stays at 0)
558
        assert_eq!(db.tokenize_pos, 0);
1✔
559
        assert_eq!(db.data_end, 5);
1✔
560

561
        // Compact should be no-op
562
        let offset = db.compact_from(0).unwrap();
1✔
563
        assert_eq!(offset, 0); // No movement occurred
1✔
564

565
        // Should be unchanged
566
        assert_eq!(db.tokenize_pos, 0);
1✔
567
        assert_eq!(db.data_end, 5);
1✔
568
        assert_eq!(db.current_byte().unwrap(), b'h');
1✔
569
    }
1✔
570

571
    #[test]
572
    fn test_compact_all_data_processed() {
1✔
573
        let mut buffer = [0u8; 10];
1✔
574
        let mut db = StreamBuffer::new(&mut buffer);
1✔
575

576
        // Fill buffer with data
577
        {
1✔
578
            let fill_slice = db.get_fill_slice().unwrap();
1✔
579
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
580
        }
1✔
581
        db.mark_filled(5).unwrap();
1✔
582

583
        // Process all data
584
        for _ in 0..5 {
6✔
585
            db.advance().unwrap();
5✔
586
        }
5✔
587

588
        // All data processed
589
        assert_eq!(db.tokenize_pos, 5);
1✔
590
        assert_eq!(db.data_end, 5);
1✔
591
        assert!(db.is_empty());
1✔
592

593
        // Compact should reset to start
594
        let offset = db.compact_from(5).unwrap();
1✔
595
        assert_eq!(offset, 5); // All data was processed, moved by 5
1✔
596

597
        // Should be reset to empty state
598
        assert_eq!(db.tokenize_pos, 0);
1✔
599
        assert_eq!(db.data_end, 0);
1✔
600
        assert!(db.is_empty());
1✔
601
    }
1✔
602

603
    #[test]
604
    fn test_compact_enables_new_data_fill() {
1✔
605
        let mut buffer = [0u8; 10];
1✔
606
        let mut db = StreamBuffer::new(&mut buffer);
1✔
607

608
        // Fill buffer completely
609
        {
1✔
610
            let fill_slice = db.get_fill_slice().unwrap();
1✔
611
            fill_slice.copy_from_slice(b"0123456789");
1✔
612
        }
1✔
613
        db.mark_filled(10).unwrap();
1✔
614

615
        // Process half the data
616
        for _ in 0..5 {
6✔
617
            db.advance().unwrap();
5✔
618
        }
5✔
619

620
        // Buffer is full, can't get fill slice
621
        assert!(db.get_fill_slice().is_none());
1✔
622

623
        // Compact to make space
624
        let offset = db.compact_from(5).unwrap();
1✔
625
        assert_eq!(offset, 5); // Data moved by 5 positions
1✔
626

627
        // Now should be able to get fill slice again
628
        let fill_slice = db.get_fill_slice().unwrap();
1✔
629
        assert_eq!(fill_slice.len(), 5); // 5 bytes available (10 - 5 remaining)
1✔
630

631
        // Fill with new data
632
        fill_slice[0..5].copy_from_slice(b"ABCDE");
1✔
633
        db.mark_filled(5).unwrap();
1✔
634

635
        // Verify combined data: "56789ABCDE"
636
        assert_eq!(db.data_end, 10);
1✔
637
        assert_eq!(db.current_byte().unwrap(), b'5');
1✔
638
        db.advance().unwrap();
1✔
639
        assert_eq!(db.current_byte().unwrap(), b'6');
1✔
640
        db.advance().unwrap();
1✔
641
        assert_eq!(db.current_byte().unwrap(), b'7');
1✔
642
        db.advance().unwrap();
1✔
643
        assert_eq!(db.current_byte().unwrap(), b'8');
1✔
644
        db.advance().unwrap();
1✔
645
        assert_eq!(db.current_byte().unwrap(), b'9');
1✔
646
        db.advance().unwrap();
1✔
647
        assert_eq!(db.current_byte().unwrap(), b'A');
1✔
648
    }
1✔
649

650
    #[test]
651
    fn test_compact_with_single_byte_remaining() {
1✔
652
        let mut buffer = [0u8; 5];
1✔
653
        let mut db = StreamBuffer::new(&mut buffer);
1✔
654

655
        // Fill buffer: "abcde"
656
        {
1✔
657
            let fill_slice = db.get_fill_slice().unwrap();
1✔
658
            fill_slice.copy_from_slice(b"abcde");
1✔
659
        }
1✔
660
        db.mark_filled(5).unwrap();
1✔
661

662
        // Process almost all data (leave one byte)
663
        for _ in 0..4 {
5✔
664
            db.advance().unwrap();
4✔
665
        }
4✔
666

667
        // One byte remaining
668
        assert_eq!(db.remaining_bytes(), 1);
1✔
669
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
670

671
        // Compact
672
        let offset = db.compact_from(4).unwrap();
1✔
673
        assert_eq!(offset, 4); // Moved by 4 positions
1✔
674

675
        // Should have moved the last byte to start
676
        assert_eq!(db.tokenize_pos, 0);
1✔
677
        assert_eq!(db.data_end, 1);
1✔
678
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
679
        assert_eq!(db.remaining_bytes(), 1);
1✔
680

681
        // Should have space for 4 more bytes
682
        let fill_slice = db.get_fill_slice().unwrap();
1✔
683
        assert_eq!(fill_slice.len(), 4);
1✔
684
    }
1✔
685

686
    #[test]
687
    fn test_compact_buffer_wall_scenario() {
1✔
688
        // Simulate hitting the buffer wall during token processing
689
        // This tests the "always compact when buffer full" strategy
690

691
        let mut buffer = [0u8; 10];
1✔
692
        let mut db = StreamBuffer::new(&mut buffer);
1✔
693

694
        // Fill buffer completely with: `{"hello_wo` (10 bytes, fills buffer exactly)
695
        {
1✔
696
            let fill_slice = db.get_fill_slice().unwrap();
1✔
697
            fill_slice.copy_from_slice(b"{\"hello_wo");
1✔
698
        }
1✔
699
        db.mark_filled(10).unwrap();
1✔
700

701
        // Process tokens: { " h e l l o _ w o
702
        // Parser is in State::String(2) tracking string start at position 2
703
        let mut _string_start_pos = 2; // Parser's state: string started at pos 2
1✔
704

705
        // Advance to simulate tokenizer processing
706
        for _ in 0..10 {
11✔
707
            db.advance().unwrap();
10✔
708
        }
10✔
709

710
        // Buffer is now empty, we hit the wall
711
        assert!(db.is_empty());
1✔
712
        assert!(db.get_fill_slice().is_none()); // No space to read more
1✔
713

714
        // ALWAYS compact when hitting buffer wall
715
        let offset = db.compact_from(10).unwrap();
1✔
716
        assert_eq!(offset, 10); // Moved by 10 positions (everything was processed)
1✔
717

718
        // Parser updates state: string_start_pos = 2 - 10 = -8
719
        // Since string_start_pos < 0, the original string start was discarded!
720
        // Parser must now switch to escape/copy mode for the continuation
721
        if _string_start_pos < offset {
1✔
722
            // Original string start was discarded - must use escape/copy mode
1✔
723
            // In real implementation, parser would copy what it had processed to unescaped buffer
1✔
724
            _string_start_pos = 0; // Reset for escape mode
1✔
725
        } else {
1✔
UNCOV
726
            _string_start_pos = _string_start_pos.saturating_sub(offset); // Normal position update
×
UNCOV
727
        }
×
728

729
        // After compaction, buffer is reset and ready for new data
730
        assert_eq!(db.tokenize_pos, 0);
1✔
731
        assert_eq!(db.data_end, 0);
1✔
732

733
        // Now we can read more data
734
        {
735
            let fill_slice = db.get_fill_slice().unwrap();
1✔
736
            assert_eq!(fill_slice.len(), 10); // Full buffer available
1✔
737
            fill_slice[0..3].copy_from_slice(b"rld");
1✔
738
        }
739
        db.mark_filled(3).unwrap();
1✔
740

741
        // Continue processing the string continuation
742
        assert_eq!(db.current_byte().unwrap(), b'r');
1✔
743
        assert_eq!(db.remaining_bytes(), 3);
1✔
744
    }
1✔
745

746
    #[test]
747
    fn test_compact_saves_partial_token() {
1✔
748
        // Test case where compaction saves partial token at end of buffer
749
        let mut buffer = [0u8; 8];
1✔
750
        let mut db = StreamBuffer::new(&mut buffer);
1✔
751

752
        // Fill buffer: {"hel|lo"} where we process up to 'l' and hit wall with "lo\"}" remaining
753
        {
1✔
754
            let fill_slice = db.get_fill_slice().unwrap();
1✔
755
            fill_slice.copy_from_slice(b"{\"hello\"");
1✔
756
        }
1✔
757
        db.mark_filled(8).unwrap();
1✔
758

759
        // Process: { " h e l - stop here with "lo\"" remaining
760
        for _ in 0..5 {
6✔
761
            db.advance().unwrap();
5✔
762
        }
5✔
763

764
        // Current state: parser at position 5, with "lo\"" remaining (3 bytes)
765
        let mut _string_start_pos = 2; // Parser state: string started at position 2
1✔
766
        assert_eq!(db.current_byte().unwrap(), b'l');
1✔
767
        assert_eq!(db.remaining_bytes(), 3);
1✔
768

769
        // Hit buffer wall, compact
770
        let offset = db.compact_from(5).unwrap();
1✔
771
        assert_eq!(offset, 5); // Moved data by 5 positions
1✔
772

773
        // Update parser state
774
        _string_start_pos = if _string_start_pos < offset {
1✔
775
            0 // Switch to escape mode - original start was discarded
1✔
776
        } else {
UNCOV
777
            _string_start_pos - offset // Normal position update: 2 - 5 = -3, so switch to escape mode
×
778
        };
779

780
        // After compaction: "lo\"" is now at start of buffer
781
        assert_eq!(db.tokenize_pos, 0);
1✔
782
        assert_eq!(db.data_end, 3);
1✔
783
        assert_eq!(db.current_byte().unwrap(), b'l');
1✔
784
        assert_eq!(db.remaining_bytes(), 3);
1✔
785

786
        // We saved 3 bytes, gained 5 bytes of space
787
        let fill_slice = db.get_fill_slice().unwrap();
1✔
788
        assert_eq!(fill_slice.len(), 5);
1✔
789
    }
1✔
790

791
    #[test]
792
    fn test_position_update_after_compaction_normal_case() {
1✔
793
        // Test normal position updates where positions are preserved
794

795
        // Case 1: String position preserved after compaction
796
        let _state = State::String(10);
1✔
797
        let offset = 5;
1✔
798

799
        // Simulate the position update logic
800
        let updated_pos = if 10 < offset {
1✔
UNCOV
801
            0 // Would need escape mode
×
802
        } else {
803
            10 - offset // Normal position update: 10 - 5 = 5
1✔
804
        };
805

806
        assert_eq!(updated_pos, 5);
1✔
807

808
        // Case 2: Key position preserved after compaction
809
        let key_pos = 8;
1✔
810
        let offset = 3;
1✔
811

812
        let updated_key_pos = if key_pos < offset {
1✔
UNCOV
813
            0 // Would need escape mode
×
814
        } else {
815
            key_pos - offset // Normal position update: 8 - 3 = 5
1✔
816
        };
817

818
        assert_eq!(updated_key_pos, 5);
1✔
819

820
        // Case 3: Number position preserved after compaction
821
        let number_pos = 15;
1✔
822
        let offset = 7;
1✔
823

824
        let updated_number_pos = if number_pos < offset {
1✔
825
            // Numbers should not normally lose their start position
UNCOV
826
            panic!("Number position discarded - buffer too small");
×
827
        } else {
828
            number_pos - offset // Normal position update: 15 - 7 = 8
1✔
829
        };
830

831
        assert_eq!(updated_number_pos, 8);
1✔
832
    }
1✔
833

834
    #[test]
835
    fn test_position_update_after_compaction_escape_mode_case() {
1✔
836
        // Test position updates where original positions are discarded (need escape mode)
837

838
        // Case 1: String position discarded - needs escape mode
839
        let string_pos = 3;
1✔
840
        let offset = 7; // Offset is larger than string position
1✔
841

842
        let needs_escape_mode = string_pos < offset;
1✔
843
        assert!(needs_escape_mode);
1✔
844

845
        let updated_string_pos = if needs_escape_mode {
1✔
846
            0 // Reset for escape mode
1✔
847
        } else {
UNCOV
848
            string_pos - offset
×
849
        };
850

851
        assert_eq!(updated_string_pos, 0);
1✔
852

853
        // Case 2: Key position discarded - needs escape mode
854
        let key_pos = 2;
1✔
855
        let offset = 8;
1✔
856

857
        let needs_escape_mode = key_pos < offset;
1✔
858
        assert!(needs_escape_mode);
1✔
859

860
        let updated_key_pos = if needs_escape_mode {
1✔
861
            0 // Reset for escape mode
1✔
862
        } else {
UNCOV
863
            key_pos - offset
×
864
        };
865

866
        assert_eq!(updated_key_pos, 0);
1✔
867

868
        // Case 3: Number position discarded - should be an error
869
        let number_pos = 1;
1✔
870
        let offset = 5;
1✔
871

872
        let should_error = number_pos < offset;
1✔
873
        assert!(should_error); // Numbers spanning compaction boundaries should error
1✔
874
    }
1✔
875

876
    #[test]
877
    fn test_position_update_boundary_conditions() {
1✔
878
        // Test exact boundary conditions for position updates
879

880
        // Case 1: Position exactly equals offset
881
        let pos = 5;
1✔
882
        let offset = 5;
1✔
883

884
        let needs_escape_mode = pos < offset; // false, pos == offset
1✔
885
        assert!(!needs_escape_mode);
1✔
886

887
        let updated_pos = pos - offset; // 5 - 5 = 0
1✔
888
        assert_eq!(updated_pos, 0);
1✔
889

890
        // Case 2: Position one less than offset (boundary case)
891
        let pos = 4;
1✔
892
        let offset = 5;
1✔
893

894
        let needs_escape_mode = pos < offset; // true, pos < offset
1✔
895
        assert!(needs_escape_mode);
1✔
896

897
        // Case 3: Position one more than offset (boundary case)
898
        let pos = 6;
1✔
899
        let offset = 5;
1✔
900

901
        let needs_escape_mode = pos < offset; // false, pos > offset
1✔
902
        assert!(!needs_escape_mode);
1✔
903

904
        let updated_pos = pos - offset; // 6 - 5 = 1
1✔
905
        assert_eq!(updated_pos, 1);
1✔
906

907
        // Case 4: Zero offset (no compaction occurred)
908
        let pos = 10;
1✔
909
        let offset = 0;
1✔
910

911
        let needs_escape_mode = pos < offset; // false, 10 < 0
1✔
912
        assert!(!needs_escape_mode);
1✔
913

914
        let updated_pos = pos - offset; // 10 - 0 = 10 (unchanged)
1✔
915
        assert_eq!(updated_pos, 10);
1✔
916
    }
1✔
917

918
    #[test]
919
    fn test_position_update_state_transitions() {
1✔
920
        // Test the complete state transition logic for different parser states
921

922
        // Case 1: State::None - no position to update
923
        let state = State::None;
1✔
924
        // No position updates needed for None state
925
        match state {
1✔
926
            State::None => {
1✔
927
                // No action needed - test passes
1✔
928
            }
1✔
UNCOV
929
            _ => panic!("Expected State::None"),
×
930
        }
931

932
        // Case 2: String state position updates
933
        let mut string_state = State::String(12);
1✔
934
        let offset = 8;
1✔
935

936
        match &mut string_state {
1✔
937
            State::String(pos) => {
1✔
938
                if *pos < offset {
1✔
UNCOV
939
                    // Would need escape mode
×
UNCOV
940
                    *pos = 0;
×
941
                } else {
1✔
942
                    *pos = pos.saturating_sub(offset); // 12 - 8 = 4
1✔
943
                }
1✔
944
            }
945
            _ => panic!("Expected State::String"),
×
946
        }
947

948
        match string_state {
1✔
949
            State::String(pos) => assert_eq!(pos, 4),
1✔
950
            _ => panic!("Expected State::String"),
×
951
        }
952

953
        // Case 3: Key state needing escape mode
954
        let mut key_state = State::Key(3);
1✔
955
        let offset = 10;
1✔
956

957
        match &mut key_state {
1✔
958
            State::Key(pos) => {
1✔
959
                if *pos < offset {
1✔
960
                    // Needs escape mode
1✔
961
                    *pos = 0;
1✔
962
                } else {
1✔
UNCOV
963
                    *pos = pos.saturating_sub(offset);
×
UNCOV
964
                }
×
965
            }
UNCOV
966
            _ => panic!("Expected State::Key"),
×
967
        }
968

969
        match key_state {
1✔
970
            State::Key(pos) => assert_eq!(pos, 0), // Reset for escape mode
1✔
971
            _ => panic!("Expected State::Key"),
×
972
        }
973

974
        // Case 4: Number state normal update
975
        let mut number_state = State::Number(20);
1✔
976
        let offset = 6;
1✔
977

978
        match &mut number_state {
1✔
979
            State::Number(pos) => {
1✔
980
                if *pos < offset {
1✔
981
                    // This should not happen for numbers in normal operation
UNCOV
982
                    panic!("Number position discarded - buffer too small");
×
983
                } else {
1✔
984
                    *pos = pos.saturating_sub(offset); // 20 - 6 = 14
1✔
985
                }
1✔
986
            }
987
            _ => panic!("Expected State::Number"),
×
988
        }
989

990
        match number_state {
1✔
991
            State::Number(pos) => assert_eq!(pos, 14),
1✔
992
            _ => panic!("Expected State::Number"),
×
993
        }
994
    }
1✔
995
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc