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

kaidokert / picojson-rs / 16185949619

10 Jul 2025 04:11AM UTC coverage: 92.876% (-0.6%) from 93.523%
16185949619

push

github

kaidokert
Passing conformance tests

357 of 405 new or added lines in 3 files covered. (88.15%)

18 existing lines in 1 file now uncovered.

4550 of 4899 relevant lines covered (92.88%)

557.14 hits per line

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

90.82
/picojson/src/slice_parser.rs
1
// SPDX-License-Identifier: Apache-2.0
2

3
use crate::copy_on_escape::CopyOnEscape;
4
use crate::escape_processor::{EscapeProcessor, UnicodeEscapeCollector};
5
use crate::number_parser::NumberExtractor;
6
use crate::parse_error::ParseError;
7
use crate::shared::{ContentRange, Event, ParserState, PullParser, State, UnexpectedState};
8
use crate::slice_input_buffer::{InputBuffer, SliceInputBuffer};
9
use crate::ujson;
10
use ujson::{EventToken, Tokenizer};
11

12
use ujson::{BitStackConfig, DefaultConfig};
13

14
/// Result of processing a tokenizer event
15
enum EventResult<'a, 'b> {
16
    /// Event processing complete, return this event
17
    Complete(Event<'a, 'b>),
18
    /// Continue processing, no event to return yet
19
    Continue,
20
    /// Extract string content from current state
21
    ExtractString,
22
    /// Extract key content from current state
23
    ExtractKey,
24
    /// Extract number content from current state,
25
    /// bool indicates if number was terminated by container delimiter
26
    ExtractNumber(bool),
27
}
28

29
/// A pull parser that parses JSON from a slice.
30
///
31
/// Generic over BitStack storage type for configurable nesting depth.
32
// Lifetime 'a is the input buffer lifetime
33
// lifetime 'b is the scratch/copy buffer lifetime
34
pub struct SliceParser<'a, 'b, C: BitStackConfig = DefaultConfig> {
35
    tokenizer: Tokenizer<C::Bucket, C::Counter>,
36
    buffer: SliceInputBuffer<'a>,
37
    parser_state: ParserState,
38
    copy_on_escape: CopyOnEscape<'a, 'b>,
39
    /// Shared Unicode escape collector for \uXXXX sequences
40
    unicode_escape_collector: UnicodeEscapeCollector,
41
}
42

43
/// Methods for the pull parser.
44
impl<'a> SliceParser<'a, '_, DefaultConfig> {
45
    /// Creates a new parser for the given JSON input.
46
    ///
47
    /// This parser assumes no string escapes will be encountered. If escapes are found,
48
    /// parsing will fail with `ScratchBufferFull` error.
49
    ///
50
    /// For JSON with potential string escapes, use `with_buffer()` instead.
51
    ///
52
    /// # Arguments
53
    /// * `input` - A string slice containing the JSON data to be parsed.
54
    ///
55
    /// # Example
56
    /// ```
57
    /// use picojson::SliceParser;
58
    /// let parser = SliceParser::new(r#"{"name": "value"}"#);
59
    /// ```
60
    pub fn new(input: &'a str) -> Self {
9✔
61
        Self::new_from_slice(input.as_bytes())
9✔
62
    }
9✔
63

64
    /// Creates a new parser from a byte slice.
65
    ///
66
    /// Assumes no string escapes will be encountered. For JSON with escapes, use [`with_buffer_from_slice`].
67
    ///
68
    /// # Example
69
    /// ```
70
    /// # use picojson::SliceParser;
71
    /// let parser = SliceParser::new_from_slice(br#"{"name": "value"}"#);
72
    /// ```
73
    ///
74
    /// [`with_buffer_from_slice`]: Self::with_buffer_from_slice
75
    pub fn new_from_slice(input: &'a [u8]) -> Self {
9✔
76
        Self::with_config_from_slice(input)
9✔
77
    }
9✔
78
}
79

80
/// Constructor with scratch buffer for SliceParser using DefaultConfig
81
impl<'a, 'b> SliceParser<'a, 'b, DefaultConfig> {
82
    /// Creates a new parser for the given JSON input with external scratch buffer.
83
    ///
84
    /// Use this when your JSON contains string escapes (like `\n`, `\"`, `\u0041`) that
85
    /// need to be unescaped during parsing.
86
    ///
87
    /// # Arguments
88
    /// * `input` - A string slice containing the JSON data to be parsed.
89
    /// * `scratch_buffer` - A mutable byte slice for temporary string unescaping operations.
90
    ///   This buffer needs to be at least as long as the longest
91
    ///   contiguous token (string, key, number) in the input.
92
    ///
93
    /// # Example
94
    /// ```
95
    /// use picojson::SliceParser;
96
    /// let mut scratch = [0u8; 1024];
97
    /// let parser = SliceParser::with_buffer(r#"{"msg": "Hello\nWorld"}"#, &mut scratch);
98
    /// ```
99
    pub fn with_buffer(input: &'a str, scratch_buffer: &'b mut [u8]) -> Self {
57✔
100
        Self::with_buffer_from_slice(input.as_bytes(), scratch_buffer)
57✔
101
    }
57✔
102

103
    /// Creates a new parser from a byte slice with a scratch buffer.
104
    ///
105
    /// Use when JSON contains string escapes that need unescaping.
106
    ///
107
    /// # Example
108
    /// ```
109
    /// # use picojson::SliceParser;
110
    /// let mut scratch = [0u8; 1024];
111
    /// let parser = SliceParser::with_buffer_from_slice(br#"{"msg": "Hello\nWorld"}"#, &mut scratch);
112
    /// ```
113
    pub fn with_buffer_from_slice(input: &'a [u8], scratch_buffer: &'b mut [u8]) -> Self {
58✔
114
        Self::with_config_and_buffer_from_slice(input, scratch_buffer)
58✔
115
    }
58✔
116
}
117

118
/// Generic constructor for SliceParser with custom configurations
119
impl<'a, 'b, C: BitStackConfig> SliceParser<'a, 'b, C> {
120
    /// Creates a new parser with a custom `BitStackConfig`.
121
    ///
122
    /// This parser assumes no string escapes will be encountered. If escapes are found,
123
    /// parsing will fail. For JSON with escapes, use `with_config_and_buffer`.
124
    pub fn with_config(input: &'a str) -> Self {
1✔
125
        Self::with_config_from_slice(input.as_bytes())
1✔
126
    }
1✔
127

128
    /// Creates a new parser from a byte slice with a custom `BitStackConfig`.
129
    ///
130
    /// Assumes no string escapes will be encountered. For JSON with escapes, use [`with_config_and_buffer_from_slice`].
131
    ///
132
    /// [`with_config_and_buffer_from_slice`]: Self::with_config_and_buffer_from_slice
133
    pub fn with_config_from_slice(input: &'a [u8]) -> Self {
10✔
134
        Self::with_config_and_buffer_from_slice(input, &mut [])
10✔
135
    }
10✔
136

137
    /// Creates a new parser with a custom `BitStackConfig` and a user-provided scratch buffer.
138
    ///
139
    /// Use this when your JSON contains string escapes (like `\n`, `\"`, `\u0041`).
140
    ///
141
    /// # Arguments
142
    /// * `input` - A string slice containing the JSON data to be parsed.
143
    /// * `scratch_buffer` - A mutable byte slice for temporary string unescaping operations.
144
    ///   This buffer needs to be at least as long as the longest
145
    ///   contiguous token (string, key, number) in the input.
146
    pub fn with_config_and_buffer(input: &'a str, scratch_buffer: &'b mut [u8]) -> Self {
2✔
147
        Self::with_config_and_buffer_from_slice(input.as_bytes(), scratch_buffer)
2✔
148
    }
2✔
149

150
    /// Creates a new parser from a byte slice with a custom `BitStackConfig` and scratch buffer.
151
    ///
152
    /// Use when JSON contains string escapes that need unescaping.
153
    /// This is the core constructor that all other constructors delegate to.
154
    pub fn with_config_and_buffer_from_slice(
70✔
155
        input: &'a [u8],
70✔
156
        scratch_buffer: &'b mut [u8],
70✔
157
    ) -> Self {
70✔
158
        let copy_on_escape = CopyOnEscape::new(input, scratch_buffer);
70✔
159
        SliceParser {
70✔
160
            tokenizer: Tokenizer::new(),
70✔
161
            buffer: SliceInputBuffer::new(input),
70✔
162
            parser_state: ParserState::new(),
70✔
163
            copy_on_escape,
70✔
164
            unicode_escape_collector: UnicodeEscapeCollector::new(),
70✔
165
        }
70✔
166
    }
70✔
167

168
    fn have_events(&self) -> bool {
3,071✔
169
        self.parser_state.evts.iter().any(|evt| evt.is_some())
5,470✔
170
    }
3,071✔
171

172
    /// Helper function to parse a number from the buffer given a start position.
173
    /// SliceParser handles all delimiter logic directly.
174
    fn parse_number_from_buffer(
31✔
175
        &mut self,
31✔
176
        start: usize,
31✔
177
        from_container_end: bool,
31✔
178
    ) -> Result<Event<'_, '_>, ParseError> {
31✔
179
        // SliceParser delimiter logic (same as the legacy wrapper)
180
        let current_pos = self.buffer.current_position();
31✔
181
        let end_pos = if !from_container_end && self.buffer.is_empty() {
31✔
182
            // At EOF - use full span
183
            current_pos
×
184
        } else {
185
            // All other cases - exclude delimiter
186
            current_pos.saturating_sub(1)
31✔
187
        };
188

189
        crate::number_parser::parse_number_event(&self.buffer, start, end_pos)
31✔
190
    }
31✔
191

192
    /// Helper method to handle simple escape tokens using EscapeProcessor
193
    /// Converts EventToken back to original escape character and processes it
194
    fn handle_simple_escape_token(
21✔
195
        &mut self,
21✔
196
        escape_token: &EventToken,
21✔
197
    ) -> Result<Option<Event<'_, '_>>, ParseError> {
21✔
198
        // Use unified escape token processing
199
        let unescaped_char = EscapeProcessor::process_escape_token(escape_token)?;
21✔
200

201
        // Handle the escape using existing logic
202
        self.handle_escape_event(unescaped_char)
21✔
203
    }
21✔
204

205
    /// Handles escape sequence events by delegating to CopyOnEscape if we're inside a string or key
206
    fn handle_escape_event(
21✔
207
        &mut self,
21✔
208
        escape_char: u8,
21✔
209
    ) -> Result<Option<Event<'_, '_>>, ParseError> {
21✔
210
        // Clear any pending high surrogate state when we encounter a simple escape
211
        // This ensures that interrupted surrogate pairs (like \uD801\n\uDC37) are properly rejected
212
        self.unicode_escape_collector.reset_all();
21✔
213

214
        if let State::String(_) | State::Key(_) = self.parser_state.state {
21✔
215
            self.copy_on_escape
21✔
216
                .handle_escape(self.buffer.current_pos(), escape_char)?;
21✔
217
        }
×
218
        Ok(None)
19✔
219
    }
21✔
220

221
    /// Process Unicode escape sequence using shared UnicodeEscapeCollector
222
    /// Extracts hex digits from buffer and processes them through the collector
223
    fn process_unicode_escape_with_collector(&mut self) -> Result<(), ParseError> {
48✔
224
        let current_pos = self.buffer.current_pos();
48✔
225
        let hex_slice_provider = |start, end| self.buffer.slice(start, end).map_err(Into::into);
48✔
226

227
        // Check if we had a pending high surrogate before processing
228
        let had_pending_high_surrogate = self.unicode_escape_collector.has_pending_high_surrogate();
48✔
229

230
        let mut utf8_buf = [0u8; 4];
48✔
231
        let (utf8_bytes_opt, escape_start_pos) =
38✔
232
            crate::escape_processor::process_unicode_escape_sequence_with_surrogate_support(
48✔
233
                current_pos,
48✔
234
                &mut self.unicode_escape_collector,
48✔
235
                hex_slice_provider,
48✔
236
                &mut utf8_buf,
48✔
237
            )?;
10✔
238

239
        // Only process UTF-8 bytes if we have them (not a high surrogate waiting for low surrogate)
240
        if let Some(utf8_bytes) = utf8_bytes_opt {
38✔
241
            if had_pending_high_surrogate {
16✔
242
                // This is completing a surrogate pair - need to consume both escapes
243
                // First call: consume the high surrogate (6 bytes earlier)
244
                self.copy_on_escape
11✔
245
                    .handle_unicode_escape(escape_start_pos, &[])?;
11✔
246
                // Second call: consume the low surrogate and write UTF-8
247
                self.copy_on_escape
11✔
248
                    .handle_unicode_escape(escape_start_pos + 6, utf8_bytes)?;
11✔
249
            } else {
250
                // Single Unicode escape - normal processing
251
                self.copy_on_escape
5✔
252
                    .handle_unicode_escape(escape_start_pos, utf8_bytes)?;
5✔
253
            }
254
        }
22✔
255

256
        Ok(())
38✔
257
    }
48✔
258

259
    fn pull_tokenizer_events(&mut self) -> Result<(), ParseError> {
2,374✔
260
        use crate::slice_input_buffer::InputBuffer;
261
        if self.buffer.is_past_end() {
2,374✔
262
            return Err(ParseError::EndOfData);
×
263
        }
2,374✔
264
        let mut callback = |event, _len| {
2,374✔
265
            for evt in self.parser_state.evts.iter_mut() {
743✔
266
                if evt.is_none() {
743✔
267
                    *evt = Some(event);
708✔
268
                    return;
708✔
269
                }
35✔
270
            }
271
        };
708✔
272

273
        let res = match self.buffer.consume_byte() {
2,374✔
274
            Err(crate::slice_input_buffer::Error::ReachedEnd) => {
275
                self.tokenizer.finish(&mut callback)
40✔
276
            }
277
            Err(err) => {
×
278
                return Err(err.into());
×
279
            }
280
            Ok(byte) => self.tokenizer.parse_chunk(&[byte], &mut callback),
2,334✔
281
        };
282

283
        if let Err(_tokenizer_error) = res {
2,374✔
284
            return Err(ParseError::TokenizerError);
8✔
285
        }
2,366✔
286
        Ok(())
2,366✔
287
    }
2,374✔
288

289
    /// Returns the next JSON event or an error if parsing fails.
290
    /// Parsing continues until `EndDocument` is returned or an error occurs.
291
    fn next_event_impl(&mut self) -> Result<Event<'_, '_>, ParseError> {
359✔
292
        if self.buffer.is_past_end() {
359✔
293
            return Ok(Event::EndDocument);
2✔
294
        }
357✔
295
        loop {
296
            while !self.have_events() {
3,071✔
297
                self.pull_tokenizer_events()?;
2,374✔
298
                if self.buffer.is_past_end() {
2,366✔
299
                    return Ok(Event::EndDocument);
37✔
300
                }
2,329✔
301
            }
302
            // Find and move out the first available event to avoid holding mutable borrow during processing
303
            let taken_event = {
697✔
304
                let mut found_event = None;
697✔
305
                for evt in self.parser_state.evts.iter_mut() {
722✔
306
                    if evt.is_some() {
722✔
307
                        found_event = evt.take();
697✔
308
                        break;
697✔
309
                    }
25✔
310
                }
311
                found_event
697✔
312
            };
313

314
            if let Some(taken) = taken_event {
697✔
315
                let res = match taken {
685✔
316
                    // Container events
317
                    ujson::Event::ObjectStart => EventResult::Complete(Event::StartObject),
47✔
318
                    ujson::Event::ObjectEnd => EventResult::Complete(Event::EndObject),
33✔
319
                    ujson::Event::ArrayStart => EventResult::Complete(Event::StartArray),
32✔
320
                    ujson::Event::ArrayEnd => EventResult::Complete(Event::EndArray),
15✔
321

322
                    // String/Key events
323
                    ujson::Event::Begin(EventToken::Key) => {
324
                        self.parser_state.state = State::Key(self.buffer.current_pos());
75✔
325
                        self.copy_on_escape.begin_string(self.buffer.current_pos());
75✔
326
                        EventResult::Continue
75✔
327
                    }
328
                    ujson::Event::End(EventToken::Key) => EventResult::ExtractKey,
75✔
329
                    ujson::Event::Begin(EventToken::String) => {
330
                        self.parser_state.state = State::String(self.buffer.current_pos());
68✔
331
                        self.copy_on_escape.begin_string(self.buffer.current_pos());
68✔
332
                        EventResult::Continue
68✔
333
                    }
334
                    ujson::Event::End(EventToken::String) => EventResult::ExtractString,
53✔
335

336
                    // Number events
337
                    ujson::Event::Begin(
338
                        EventToken::Number
339
                        | EventToken::NumberAndArray
340
                        | EventToken::NumberAndObject,
341
                    ) => {
342
                        let number_start =
33✔
343
                            ContentRange::number_start_from_current(self.buffer.current_pos());
33✔
344
                        self.parser_state.state = State::Number(number_start);
33✔
345
                        EventResult::Continue
33✔
346
                    }
347
                    ujson::Event::End(EventToken::Number) => EventResult::ExtractNumber(false),
17✔
348
                    ujson::Event::End(EventToken::NumberAndArray) => {
349
                        EventResult::ExtractNumber(true)
7✔
350
                    }
351
                    ujson::Event::End(EventToken::NumberAndObject) => {
352
                        EventResult::ExtractNumber(true)
7✔
353
                    }
354
                    // Boolean and null values
355
                    ujson::Event::Begin(
356
                        EventToken::True | EventToken::False | EventToken::Null,
357
                    ) => EventResult::Continue,
14✔
358
                    ujson::Event::End(EventToken::True) => EventResult::Complete(Event::Bool(true)),
6✔
359
                    ujson::Event::End(EventToken::False) => {
360
                        EventResult::Complete(Event::Bool(false))
4✔
361
                    }
362
                    ujson::Event::End(EventToken::Null) => EventResult::Complete(Event::Null),
4✔
363
                    // Escape sequence handling
364
                    ujson::Event::Begin(
365
                        escape_token @ (EventToken::EscapeQuote
21✔
366
                        | EventToken::EscapeBackslash
367
                        | EventToken::EscapeSlash
368
                        | EventToken::EscapeBackspace
369
                        | EventToken::EscapeFormFeed
370
                        | EventToken::EscapeNewline
371
                        | EventToken::EscapeCarriageReturn
372
                        | EventToken::EscapeTab),
373
                    ) => {
374
                        // Use EscapeProcessor for all simple escape sequences
375
                        self.handle_simple_escape_token(&escape_token)?;
21✔
376
                        EventResult::Continue
19✔
377
                    }
378
                    ujson::Event::Begin(EventToken::UnicodeEscape) => {
379
                        // Start Unicode escape collection - reset collector for new sequence
380
                        // Only handle if we're inside a string or key
381
                        match self.parser_state.state {
48✔
382
                            State::String(_) | State::Key(_) => {
48✔
383
                                self.unicode_escape_collector.reset();
48✔
384
                            }
48✔
385
                            _ => {} // Ignore if not in string/key
×
386
                        }
387
                        EventResult::Continue
48✔
388
                    }
389
                    ujson::Event::End(EventToken::UnicodeEscape) => {
390
                        // Handle end of Unicode escape sequence (\uXXXX) using shared collector
391
                        match self.parser_state.state {
48✔
392
                            State::String(_) | State::Key(_) => {
393
                                // Process Unicode escape using shared collector logic
394
                                self.process_unicode_escape_with_collector()?;
48✔
395
                            }
396
                            _ => {} // Ignore if not in string/key context
×
397
                        }
398
                        EventResult::Continue
38✔
399
                    }
400
                    // EscapeSequence events (only emitted when flag is enabled, ignored in original parser)
401
                    ujson::Event::Begin(EventToken::EscapeSequence) => {
402
                        // Ignore in original parser since it uses slice-based parsing
403
                        EventResult::Continue
71✔
404
                    }
405
                    ujson::Event::End(EventToken::EscapeSequence) => {
406
                        // Ignore in original parser since it uses slice-based parsing
407
                        EventResult::Continue
×
408
                    }
409
                    ujson::Event::End(
410
                        EventToken::EscapeQuote
411
                        | EventToken::EscapeBackslash
412
                        | EventToken::EscapeSlash
413
                        | EventToken::EscapeBackspace
414
                        | EventToken::EscapeFormFeed
415
                        | EventToken::EscapeNewline
416
                        | EventToken::EscapeCarriageReturn
417
                        | EventToken::EscapeTab,
418
                    ) => {
419
                        // End of escape sequence - ignored here
420
                        EventResult::Continue
19✔
421
                    }
422
                    #[cfg(test)]
423
                    ujson::Event::Uninitialized => {
424
                        return Err(UnexpectedState::StateMismatch.into());
×
425
                    }
426
                };
427
                match res {
685✔
428
                    EventResult::Complete(event) => break Ok(event),
141✔
429
                    EventResult::Continue => continue,
385✔
430
                    EventResult::ExtractKey => {
431
                        if let State::Key(_start) = self.parser_state.state {
75✔
432
                            self.parser_state.state = State::None;
75✔
433
                            // Use CopyOnEscape to get the final key result
434
                            let end_pos = ContentRange::end_position_excluding_delimiter(
75✔
435
                                self.buffer.current_pos(),
75✔
436
                            );
437
                            let key_result = self.copy_on_escape.end_string(end_pos)?;
75✔
438
                            break Ok(Event::Key(key_result));
75✔
439
                        } else {
440
                            break Err(UnexpectedState::StateMismatch.into());
×
441
                        }
442
                    }
443
                    EventResult::ExtractString => {
444
                        if let State::String(_value) = self.parser_state.state {
53✔
445
                            self.parser_state.state = State::None;
53✔
446
                            // Use CopyOnEscape to get the final string result
447
                            let end_pos = ContentRange::end_position_excluding_delimiter(
53✔
448
                                self.buffer.current_pos(),
53✔
449
                            );
450
                            let value_result = self.copy_on_escape.end_string(end_pos)?;
53✔
451
                            break Ok(Event::String(value_result));
53✔
452
                        } else {
453
                            break Err(UnexpectedState::StateMismatch.into());
×
454
                        }
455
                    }
456
                    EventResult::ExtractNumber(from_container_end) => {
31✔
457
                        if let State::Number(start) = self.parser_state.state {
31✔
458
                            // Reset state before parsing to stop selective copying
459
                            self.parser_state.state = State::None;
31✔
460
                            let number_token_start = start;
31✔
461
                            let event = self
31✔
462
                                .parse_number_from_buffer(number_token_start, from_container_end)?;
31✔
463
                            break Ok(event);
31✔
464
                        } else {
465
                            break Err(UnexpectedState::StateMismatch.into());
×
466
                        }
467
                    }
468
                }
469
            } else {
470
                // No event available - this shouldn't happen since we ensured have_events() above
471
                break Err(UnexpectedState::StateMismatch.into());
×
472
            }
473
        }
474
    }
359✔
475
}
476

477
impl<'a, 'b, C: BitStackConfig> PullParser for SliceParser<'a, 'b, C> {
478
    fn next_event(&mut self) -> Result<Event<'_, '_>, ParseError> {
359✔
479
        self.next_event_impl()
359✔
480
    }
359✔
481
}
482

483
#[cfg(test)]
484
mod tests {
485
    use super::*;
486
    use crate::{ArrayBitStack, BitStackStruct, String};
487

488
    #[test]
489
    fn make_parser() {
1✔
490
        let input = r#"{"key": "value"}"#;
1✔
491
        let mut scratch = [0u8; 1024];
1✔
492
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
493
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
494
        assert_eq!(parser.next_event(), Ok(Event::Key(String::Borrowed("key"))));
1✔
495
        assert_eq!(
1✔
496
            parser.next_event(),
1✔
497
            Ok(Event::String(String::Borrowed("value")))
498
        );
499
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
500
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
501
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
502
    }
1✔
503

504
    #[test]
505
    fn parse_number() {
1✔
506
        let input = r#"{"key": 124}"#;
1✔
507
        let mut scratch = [0u8; 1024];
1✔
508
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
509
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
510
        assert_eq!(parser.next_event(), Ok(Event::Key(String::Borrowed("key"))));
1✔
511
        // Check number value using new JsonNumber API
512
        match parser.next_event() {
1✔
513
            Ok(Event::Number(num)) => {
1✔
514
                assert_eq!(num.as_str(), "124");
1✔
515
                assert_eq!(num.as_int(), Some(124));
1✔
516
            }
517
            other => panic!("Expected Number, got: {:?}", other),
×
518
        }
519
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
520
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
521
    }
1✔
522

523
    #[test]
524
    fn parse_bool_and_null() {
1✔
525
        let input = r#"{"key": true, "key2": false, "key3": null}"#;
1✔
526
        let mut scratch = [0u8; 1024];
1✔
527
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
528
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
529
        assert_eq!(parser.next_event(), Ok(Event::Key(String::Borrowed("key"))));
1✔
530
        assert_eq!(parser.next_event(), Ok(Event::Bool(true)));
1✔
531
        assert_eq!(
1✔
532
            parser.next_event(),
1✔
533
            Ok(Event::Key(String::Borrowed("key2")))
534
        );
535
        assert_eq!(parser.next_event(), Ok(Event::Bool(false)));
1✔
536
        assert_eq!(
1✔
537
            parser.next_event(),
1✔
538
            Ok(Event::Key(String::Borrowed("key3")))
539
        );
540
        assert_eq!(parser.next_event(), Ok(Event::Null));
1✔
541
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
542
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
543
    }
1✔
544

545
    #[test]
546
    fn parse_array() {
1✔
547
        #[cfg(feature = "float-error")]
548
        let input = r#"{"key": [1, 2, 3]}"#; // No floats for float-error config
549
        #[cfg(not(feature = "float-error"))]
550
        let input = r#"{"key": [1, 2.2, 3]}"#; // Include float for other configs
1✔
551

552
        let mut scratch = [0u8; 1024];
1✔
553
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
554
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
555
        assert_eq!(parser.next_event(), Ok(Event::Key(String::Borrowed("key"))));
1✔
556
        assert_eq!(parser.next_event(), Ok(Event::StartArray));
1✔
557

558
        // First number: 1 (integer)
559
        match parser.next_event() {
1✔
560
            Ok(Event::Number(num)) => {
1✔
561
                assert_eq!(num.as_str(), "1");
1✔
562
                assert_eq!(num.as_int(), Some(1));
1✔
563
            }
564
            other => panic!("Expected Number(1), got: {:?}", other),
×
565
        }
566

567
        // Second number: depends on configuration
568
        match parser.next_event() {
1✔
569
            Ok(Event::Number(num)) => {
1✔
570
                #[cfg(feature = "float-error")]
571
                {
572
                    assert_eq!(num.as_str(), "2");
573
                    assert_eq!(num.as_int(), Some(2));
574
                }
575
                #[cfg(not(feature = "float-error"))]
576
                {
577
                    assert_eq!(num.as_str(), "2.2");
1✔
578
                    #[cfg(feature = "float")]
579
                    assert_eq!(num.as_f64(), Some(2.2));
1✔
580
                    #[cfg(not(feature = "float-error"))]
581
                    assert!(num.is_float());
1✔
582
                }
583
            }
584
            other => panic!("Expected Number, got: {:?}", other),
×
585
        }
586

587
        // Third number: 3 (integer)
588
        match parser.next_event() {
1✔
589
            Ok(Event::Number(num)) => {
1✔
590
                assert_eq!(num.as_str(), "3");
1✔
591
                assert_eq!(num.as_int(), Some(3));
1✔
592
            }
593
            other => panic!("Expected Number(3), got: {:?}", other),
×
594
        }
595

596
        assert_eq!(parser.next_event(), Ok(Event::EndArray));
1✔
597
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
598
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
599
    }
1✔
600

601
    #[test]
602
    fn test_simple_parser_api() {
1✔
603
        let input = r#"{"name": "test"}"#;
1✔
604
        let mut scratch = [0u8; 1024];
1✔
605
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
606

607
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
608
        assert_eq!(
1✔
609
            parser.next_event(),
1✔
610
            Ok(Event::Key(String::Borrowed("name")))
611
        );
612
        assert_eq!(
1✔
613
            parser.next_event(),
1✔
614
            Ok(Event::String(String::Borrowed("test")))
615
        );
616
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
617
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
618
    }
1✔
619

620
    #[test]
621
    fn test_parser_with_escaped_strings() {
1✔
622
        // Use regular string literal to properly include escape sequences
623
        let input = "{\"name\": \"John\\nDoe\", \"message\": \"Hello\\tWorld!\"}";
1✔
624
        let mut scratch = [0u8; 1024];
1✔
625
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
626

627
        // Test that the parser correctly handles escaped strings
628
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
629

630
        // Key should be simple (no escapes) -> Borrowed
631
        if let Ok(Event::Key(key)) = parser.next_event() {
1✔
632
            assert_eq!(&*key, "name");
1✔
633
            // This should be the fast path (borrowed)
634
            assert!(matches!(key, String::Borrowed(_)));
1✔
635
        } else {
636
            panic!("Expected Key event");
×
637
        }
638

639
        // Value should have escapes -> Unescaped
640
        if let Ok(Event::String(value)) = parser.next_event() {
1✔
641
            assert_eq!(&*value, "John\nDoe");
1✔
642
            // This should be the slow path (unescaped)
643
            assert!(matches!(value, String::Unescaped(_)));
1✔
644
        } else {
645
            panic!("Expected String event");
×
646
        }
647

648
        // Second key should be simple -> Borrowed
649
        if let Ok(Event::Key(key)) = parser.next_event() {
1✔
650
            assert_eq!(&*key, "message");
1✔
651
            assert!(matches!(key, String::Borrowed(_)));
1✔
652
        } else {
653
            panic!("Expected Key event");
×
654
        }
655

656
        // Second value should have escapes -> Unescaped
657
        if let Ok(Event::String(value)) = parser.next_event() {
1✔
658
            assert_eq!(&*value, "Hello\tWorld!");
1✔
659
            assert!(matches!(value, String::Unescaped(_)));
1✔
660
        } else {
661
            panic!("Expected String event");
×
662
        }
663

664
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
665
    }
1✔
666

667
    #[test]
668
    fn test_copy_on_escape_optimization() {
1✔
669
        // Use regular string literal to include proper escape sequences
670
        let input = "{\"simple\": \"no escapes\", \"complex\": \"has\\nescapes\"}";
1✔
671
        let mut scratch = [0u8; 1024];
1✔
672
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
673

674
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
675

676
        // "simple" key should be borrowed (fast path)
677
        if let Ok(Event::Key(key)) = parser.next_event() {
1✔
678
            assert_eq!(&*key, "simple");
1✔
679
            assert!(matches!(key, String::Borrowed(_)));
1✔
680
        } else {
681
            panic!("Expected Key event");
×
682
        }
683

684
        // "no escapes" value should be borrowed (fast path)
685
        if let Ok(Event::String(value)) = parser.next_event() {
1✔
686
            assert_eq!(&*value, "no escapes");
1✔
687
            assert!(matches!(value, String::Borrowed(_)));
1✔
688
        } else {
689
            panic!("Expected String event");
×
690
        }
691

692
        // "complex" key should be borrowed (fast path)
693
        if let Ok(Event::Key(key)) = parser.next_event() {
1✔
694
            assert_eq!(&*key, "complex");
1✔
695
            assert!(matches!(key, String::Borrowed(_)));
1✔
696
        } else {
697
            panic!("Expected Key event");
×
698
        }
699

700
        // "has\\nescapes" value should be unescaped (slow path)
701
        if let Ok(Event::String(value)) = parser.next_event() {
1✔
702
            assert_eq!(&*value, "has\nescapes");
1✔
703
            assert!(matches!(value, String::Unescaped(_)));
1✔
704
        } else {
705
            panic!("Expected String event");
×
706
        }
707

708
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
709
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
710
    }
1✔
711

712
    #[test]
713
    fn test_coe2_integration_multiple_escapes() {
1✔
714
        let input = r#"{"key": "a\nb\tc\rd"}"#;
1✔
715
        let mut scratch = [0u8; 1024];
1✔
716
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
717

718
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
719
        assert_eq!(parser.next_event(), Ok(Event::Key(String::Borrowed("key"))));
1✔
720

721
        let string_event = parser.next_event().unwrap();
1✔
722
        match string_event {
1✔
723
            Event::String(String::Unescaped(s)) => {
1✔
724
                assert_eq!(s, "a\nb\tc\rd");
1✔
725
            }
726
            _ => panic!("Expected unescaped string value, got: {:?}", string_event),
×
727
        }
728
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
729
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
730
    }
1✔
731

732
    #[test]
733
    fn test_coe2_integration_zero_copy_path() {
1✔
734
        let input = r#"{"simple": "no_escapes_here"}"#;
1✔
735
        let mut scratch = [0u8; 1024];
1✔
736
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
737

738
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
739
        assert_eq!(
1✔
740
            parser.next_event(),
1✔
741
            Ok(Event::Key(String::Borrowed("simple")))
742
        );
743

744
        // This should be borrowed (zero-copy) since no escapes
745
        let string_event = parser.next_event().unwrap();
1✔
746
        match string_event {
1✔
747
            Event::String(String::Borrowed(s)) => {
1✔
748
                assert_eq!(s, "no_escapes_here");
1✔
749
            }
750
            _ => panic!(
×
751
                "Expected borrowed string value for zero-copy, got: {:?}",
×
752
                string_event
753
            ),
754
        }
755
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
756
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
757
    }
1✔
758

759
    #[test]
760
    fn test_coe2_integration_mixed_strings() {
1✔
761
        let input = r#"["plain", "with\nescapes", "plain2", "more\tescapes"]"#;
1✔
762
        let mut scratch = [0u8; 1024];
1✔
763
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
764

765
        assert_eq!(parser.next_event(), Ok(Event::StartArray));
1✔
766

767
        // First string: no escapes -> borrowed
768
        match parser.next_event().unwrap() {
1✔
769
            Event::String(String::Borrowed(s)) => assert_eq!(s, "plain"),
1✔
770
            other => panic!("Expected borrowed string, got: {:?}", other),
×
771
        }
772

773
        // Second string: has escapes -> unescaped
774
        match parser.next_event().unwrap() {
1✔
775
            Event::String(String::Unescaped(s)) => assert_eq!(s, "with\nescapes"),
1✔
776
            other => panic!("Expected unescaped string, got: {:?}", other),
×
777
        }
778

779
        // Third string: no escapes -> borrowed
780
        match parser.next_event().unwrap() {
1✔
781
            Event::String(String::Borrowed(s)) => assert_eq!(s, "plain2"),
1✔
782
            other => panic!("Expected borrowed string, got: {:?}", other),
×
783
        }
784

785
        // Fourth string: has escapes -> unescaped
786
        match parser.next_event().unwrap() {
1✔
787
            Event::String(String::Unescaped(s)) => assert_eq!(s, "more\tescapes"),
1✔
788
            other => panic!("Expected unescaped string, got: {:?}", other),
×
789
        }
790

791
        assert_eq!(parser.next_event(), Ok(Event::EndArray));
1✔
792
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
793
    }
1✔
794

795
    #[test]
796
    fn test_unicode_escape_integration() {
1✔
797
        let input = r#"{"key": "Hello\u0041World"}"#; // \u0041 = 'A'
1✔
798
        let mut scratch = [0u8; 1024];
1✔
799
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
800

801
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
802
        assert_eq!(parser.next_event(), Ok(Event::Key(String::Borrowed("key"))));
1✔
803

804
        // The string with Unicode escape should be unescaped
805
        match parser.next_event().unwrap() {
1✔
806
            Event::String(String::Unescaped(s)) => {
1✔
807
                assert_eq!(s, "HelloAWorld");
1✔
808
            }
809
            other => panic!("Expected unescaped string value, got: {:?}", other),
×
810
        }
811

812
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
813
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
814
    }
1✔
815

816
    #[test]
817
    fn test_original_parser_escape_trace() {
1✔
818
        // Test escape sequence processing with logging
819
        let input = r#""a\nb""#;
1✔
820
        let mut scratch = [0u8; 1024];
1✔
821
        let mut parser = SliceParser::with_buffer(input, &mut scratch);
1✔
822

823
        // Should get String with unescaped content
824
        let event = parser.next_event().unwrap();
1✔
825
        if let Event::String(s) = event {
1✔
826
            assert_eq!(&*s, "a\nb");
1✔
827
        } else {
828
            panic!("Expected String event, got {:?}", event);
×
829
        }
830

831
        // Should get EndDocument
832
        let event = parser.next_event().unwrap();
1✔
833
        assert_eq!(event, Event::EndDocument);
1✔
834
    }
1✔
835

836
    #[test]
837
    fn make_parser_from_slice() {
1✔
838
        let input = br#"{"key": "value"}"#;
1✔
839
        let mut scratch = [0u8; 1024];
1✔
840
        let mut parser = SliceParser::with_buffer_from_slice(input, &mut scratch);
1✔
841
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
842
        assert_eq!(parser.next_event(), Ok(Event::Key(String::Borrowed("key"))));
1✔
843
        assert_eq!(
1✔
844
            parser.next_event(),
1✔
845
            Ok(Event::String(String::Borrowed("value")))
846
        );
847
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
848
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
849
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
850
    }
1✔
851

852
    #[test]
853
    fn test_with_config_constructors() {
1✔
854
        // Test with_config constructor (no escapes)
855
        let json = r#"{"simple": "no_escapes"}"#;
1✔
856
        let mut parser = SliceParser::<BitStackStruct<u64, u16>>::with_config(json);
1✔
857

858
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
859
        assert_eq!(
1✔
860
            parser.next_event(),
1✔
861
            Ok(Event::Key(String::Borrowed("simple")))
862
        );
863
        assert_eq!(
1✔
864
            parser.next_event(),
1✔
865
            Ok(Event::String(String::Borrowed("no_escapes")))
866
        );
867
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
868
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
869
    }
1✔
870

871
    #[test]
872
    fn test_with_config_and_buffer_constructors() {
1✔
873
        // Test with_config_and_buffer constructor (with escapes)
874
        let json = r#"{"escaped": "hello\nworld"}"#;
1✔
875
        let mut scratch = [0u8; 64];
1✔
876
        let mut parser =
1✔
877
            SliceParser::<BitStackStruct<u64, u16>>::with_config_and_buffer(json, &mut scratch);
1✔
878

879
        assert_eq!(parser.next_event(), Ok(Event::StartObject));
1✔
880
        assert_eq!(
1✔
881
            parser.next_event(),
1✔
882
            Ok(Event::Key(String::Borrowed("escaped")))
883
        );
884

885
        if let Ok(Event::String(s)) = parser.next_event() {
1✔
886
            assert_eq!(s.as_ref(), "hello\nworld"); // Escape should be processed
1✔
887
        } else {
888
            panic!("Expected String event");
×
889
        }
890

891
        assert_eq!(parser.next_event(), Ok(Event::EndObject));
1✔
892
        assert_eq!(parser.next_event(), Ok(Event::EndDocument));
1✔
893
    }
1✔
894

895
    #[test]
896
    fn test_alternative_config_deep_nesting() {
1✔
897
        // Test that custom BitStack configs can handle deeper nesting
898
        let json = r#"{"a":{"b":{"c":{"d":{"e":"deep"}}}}}"#;
1✔
899
        let mut scratch = [0u8; 64];
1✔
900
        let mut parser =
1✔
901
            SliceParser::<ArrayBitStack<8, u32, u16>>::with_config_and_buffer(json, &mut scratch);
1✔
902

903
        // Parse the deep structure
904
        let mut depth = 0;
1✔
905
        while let Ok(event) = parser.next_event() {
17✔
906
            match event {
17✔
907
                Event::StartObject => depth += 1,
5✔
908
                Event::EndObject => depth -= 1,
5✔
909
                Event::EndDocument => break,
1✔
910
                _ => {}
6✔
911
            }
912
        }
913

914
        // Should have successfully parsed a 5-level deep structure
915
        assert_eq!(depth, 0); // All objects should be closed
1✔
916
    }
1✔
917

918
    // Note: Basic surrogate pair tests moved to tests/surrogate_pairs.rs for shared testing
919
    // This ensures both SliceParser and StreamParser handle surrogate pairs identically
920

921
    #[test]
922
    fn test_surrogate_pair_pathological_cases() {
1✔
923
        // Test 1: High surrogate at end of string (returns literal text, doesn't error)
924
        let input1 = r#"["\uD801"]"#;
1✔
925
        let mut scratch1 = [0u8; 1024];
1✔
926
        let mut parser1 = SliceParser::with_buffer(input1, &mut scratch1);
1✔
927
        assert_eq!(parser1.next_event(), Ok(Event::StartArray));
1✔
928
        match parser1.next_event() {
1✔
929
            Ok(Event::String(s)) => {
1✔
930
                let content = match s {
1✔
931
                    String::Borrowed(c) => c,
1✔
NEW
932
                    String::Unescaped(c) => c,
×
933
                };
934
                // High surrogate at end becomes literal text (not an error)
935
                assert_eq!(content, "\\uD801");
1✔
936
            }
NEW
937
            other => panic!(
×
NEW
938
                "Expected String with literal high surrogate, got: {:?}",
×
939
                other
940
            ),
941
        }
942

943
        // Test 2: High surrogate followed by another high surrogate
944
        let input2 = r#"["\uD801\uD802"]"#;
1✔
945
        let mut scratch2 = [0u8; 1024];
1✔
946
        let mut parser2 = SliceParser::with_buffer(input2, &mut scratch2);
1✔
947
        assert_eq!(parser2.next_event(), Ok(Event::StartArray));
1✔
948
        let result2 = parser2.next_event();
1✔
949
        assert!(
1✔
950
            result2.is_err(),
1✔
NEW
951
            "High surrogate + high surrogate should error"
×
952
        );
953

954
        // Test 3: Multiple valid surrogate pairs
955
        let input3 = r#"["\uD801\uDC37\uD834\uDD1E"]"#;
1✔
956
        let mut scratch3 = [0u8; 1024];
1✔
957
        let mut parser3 = SliceParser::with_buffer(input3, &mut scratch3);
1✔
958
        assert_eq!(parser3.next_event(), Ok(Event::StartArray));
1✔
959
        match parser3.next_event() {
1✔
960
            Ok(Event::String(s)) => {
1✔
961
                let content = match s {
1✔
NEW
962
                    String::Borrowed(c) => c,
×
963
                    String::Unescaped(c) => c,
1✔
964
                };
965
                // Should contain two characters: U+10437 (𐐷) + U+1D11E (𝄞)
966
                assert_eq!(content.chars().count(), 2);
1✔
967
                let chars: Vec<char> = content.chars().collect();
1✔
968
                assert_eq!(chars[0] as u32, 0x10437); // 𐐷
1✔
969
                assert_eq!(chars[1] as u32, 0x1D11E); // 𝄞 (musical G clef)
1✔
970
            }
NEW
971
            other => panic!(
×
NEW
972
                "Expected String with multiple surrogate pairs, got: {:?}",
×
973
                other
974
            ),
975
        }
976

977
        // Test 4: Surrogate pairs in object keys
978
        let input4 = r#"{"\uD801\uDC37": "value"}"#;
1✔
979
        let mut scratch4 = [0u8; 1024];
1✔
980
        let mut parser4 = SliceParser::with_buffer(input4, &mut scratch4);
1✔
981
        assert_eq!(parser4.next_event(), Ok(Event::StartObject));
1✔
982
        match parser4.next_event() {
1✔
983
            Ok(Event::Key(s)) => {
1✔
984
                let content = match s {
1✔
NEW
985
                    String::Borrowed(c) => c,
×
986
                    String::Unescaped(c) => c,
1✔
987
                };
988
                assert_eq!(content, "𐐷");
1✔
989
            }
NEW
990
            other => panic!("Expected Key with surrogate pair, got: {:?}", other),
×
991
        }
992

993
        // Test 5: High surrogate interrupted by other escape
994
        // Fixed: Now properly errors when \n interrupts surrogate pair
995
        // The \uDC37 should be treated as lone low surrogate after \n interruption
996
        let input5 = r#"["\uD801\n\uDC37"]"#;
1✔
997
        let mut scratch5 = [0u8; 1024];
1✔
998
        let mut parser5 = SliceParser::with_buffer(input5, &mut scratch5);
1✔
999
        assert_eq!(parser5.next_event(), Ok(Event::StartArray));
1✔
1000
        let result5 = parser5.next_event();
1✔
1001
        assert!(
1✔
1002
            result5.is_err(),
1✔
NEW
1003
            "High surrogate interrupted by other escape should error (lone low surrogate)"
×
1004
        );
1005

1006
        // Test 6: Characters outside surrogate ranges (should work normally)
1007
        let input6 = r#"["\uD7FF\uE000"]"#; // D7FF is just below high range, E000 is just above low range
1✔
1008
        let mut scratch6 = [0u8; 1024];
1✔
1009
        let mut parser6 = SliceParser::with_buffer(input6, &mut scratch6);
1✔
1010
        assert_eq!(parser6.next_event(), Ok(Event::StartArray));
1✔
1011
        match parser6.next_event() {
1✔
1012
            Ok(Event::String(s)) => {
1✔
1013
                let content = match s {
1✔
NEW
1014
                    String::Borrowed(c) => c,
×
1015
                    String::Unescaped(c) => c,
1✔
1016
                };
1017
                // Should be two separate characters, not a surrogate pair
1018
                assert_eq!(content.chars().count(), 2);
1✔
1019
                let chars: Vec<char> = content.chars().collect();
1✔
1020
                assert_eq!(chars[0] as u32, 0xD7FF); // Just below high surrogate range
1✔
1021
                assert_eq!(chars[1] as u32, 0xE000); // Just above low surrogate range
1✔
1022
            }
NEW
1023
            other => panic!(
×
NEW
1024
                "Expected String with non-surrogate Unicode, got: {:?}",
×
1025
                other
1026
            ),
1027
        }
1028
    }
1✔
1029
}
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