mod common;
use common::{run_parser, split_by_sizes};
use proptest::prelude::*;
use sse_parse::SseParser;
fn feed_all(parser: &mut SseParser, chunks: &[Vec<u8>], total: usize) -> usize {
let mut events = 0;
let mut fed = 0;
for chunk in chunks {
parser.feed(chunk);
fed += chunk.len();
let held = parser.stats().buffered_bytes;
assert!(
held <= 3 * fed + 8,
"holding {held} bytes after feeding {fed} of {total}"
);
while let Some(ev) = parser.next_event() {
assert!(!ev.event.is_empty());
events += 1;
}
}
parser.finish();
assert_eq!(parser.stats().buffered_bytes, 0);
while parser.next_event().is_some() {
events += 1;
}
events
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(3000))]
#[test]
fn arbitrary_input_never_panics_and_memory_is_bounded(
bytes in prop::collection::vec(any::<u8>(), 0..512),
sizes in prop::collection::vec(1usize..64, 1..16),
) {
let chunks = split_by_sizes(&bytes, &sizes);
let mut parser = SseParser::new();
let events = feed_all(&mut parser, &chunks, bytes.len());
prop_assert_eq!(events, run_parser(&chunks).events.len());
}
#[test]
fn terminator_heavy_input_never_panics(
bytes in prop::collection::vec(
prop_oneof![Just(b'\r'), Just(b'\n'), Just(b':'), Just(b' '), Just(0xEF), any::<u8>()],
0..512,
),
sizes in prop::collection::vec(1usize..8, 1..16),
) {
let chunks = split_by_sizes(&bytes, &sizes);
let mut parser = SseParser::new();
feed_all(&mut parser, &chunks, bytes.len());
}
#[test]
fn capped_parser_never_panics_and_holds_at_most_cap_plus_chunk(
bytes in prop::collection::vec(any::<u8>(), 0..512),
sizes in prop::collection::vec(1usize..64, 1..16),
cap in 0usize..128,
) {
let chunks = split_by_sizes(&bytes, &sizes);
let mut parser = SseParser::new().max_buffer(cap);
let max_chunk = sizes.iter().copied().max().unwrap_or(1);
for chunk in &chunks {
parser.feed(chunk);
let held = parser.stats().buffered_bytes;
prop_assert!(held <= cap + max_chunk + 1, "held {held} with cap {cap}");
while parser.next_event().is_some() {}
}
parser.finish();
while parser.next_event().is_some() {}
}
}
#[test]
fn seeded_id_never_panics_on_junk() {
let mut parser = SseParser::with_last_event_id("seed");
parser.feed(&[0xFF, 0xFE, 0x00, b'\r', b'\r', b'\n', 0xEF, 0xBB]);
parser.finish();
parser.finish();
while parser.next_event().is_some() {}
assert_eq!(parser.last_event_id(), "seed");
}
#[test]
fn empty_feeds_are_harmless() {
let mut parser = SseParser::new();
parser.feed(b"");
parser.feed(b"data: x");
parser.feed(b"");
parser.feed(b"\n\n");
parser.feed(b"");
assert_eq!(parser.next_event().unwrap().data, "x");
parser.finish();
assert!(parser.next_event().is_none());
}