From 5a231e3d91e540898ba462b757e47120b4597d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Fri, 12 Dec 2025 10:08:11 +0100 Subject: [PATCH 1/2] test(quinn-proto): Add proptests for assembler (#243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add proptests for assembler Also fix a bug in the assembler when going to unordered state after reading data, and add a regression test for that case. * Increase ops before we switch to unordered Otherwise this test mostly tests unordered mode! * Update quinn-proto/src/connection/assembler.rs Co-authored-by: Philipp Krüger * Add defragment to the prop test ops only to be used when in ordered mode for some reason :shrug: * fmt --------- Co-authored-by: Philipp Krüger --- quinn-proto/src/connection/assembler.rs | 189 +++++++++++++++++++++++- 1 file changed, 187 insertions(+), 2 deletions(-) diff --git a/quinn-proto/src/connection/assembler.rs b/quinn-proto/src/connection/assembler.rs index 735e65981..456d7da68 100644 --- a/quinn-proto/src/connection/assembler.rs +++ b/quinn-proto/src/connection/assembler.rs @@ -108,7 +108,7 @@ impl Assembler { let mut buffers = old.into_sorted_vec(); self.buffered = 0; let mut fragmented_buffered = 0; - let mut offset = 0; + let mut offset = self.bytes_read; for chunk in buffers.iter_mut().rev() { chunk.try_mark_defragment(offset); let size = chunk.bytes.len(); @@ -120,7 +120,7 @@ impl Assembler { } self.allocated = self.buffered; let mut buffer = BytesMut::with_capacity(fragmented_buffered); - let mut offset = 0; + let mut offset = self.bytes_read; for chunk in buffers.into_iter().rev() { if chunk.defragmented { // bytes might be empty after try_mark_defragment @@ -648,6 +648,20 @@ mod test { assert_eq!(x.read(3, false), None); } + #[test] + fn no_duplicate_after_mode_switch() { + // Regression test: bytes read in ordered mode should not be returned again in unordered mode + let mut x = Assembler::new(); + x.insert(0, Bytes::from_static(b"a"), 1); + x.insert(0, Bytes::from_static(b"a"), 1); // duplicate + assert_eq!( + x.read(1, true), + Some(Chunk::new(0, Bytes::from_static(b"a"))) + ); + x.ensure_ordering(false).unwrap(); + assert_eq!(x.read(1, false), None); // should be None, byte 0 already returned + } + fn next_unordered(x: &mut Assembler) -> Chunk { x.read(usize::MAX, false).unwrap() } @@ -656,3 +670,174 @@ mod test { x.read(size, true).map(|chunk| chunk.bytes) } } + +#[cfg(all(test, not(target_family = "wasm")))] +mod proptests { + use proptest::prelude::*; + use test_strategy::{Arbitrary, proptest}; + + use super::*; + + const MAX_OFFSET: u64 = 512; + const MAX_LEN: usize = 64; + + #[derive(Debug, Clone, Arbitrary)] + enum Op { + #[weight(10)] + Insert { + #[strategy(0..MAX_OFFSET)] + offset: u64, + #[strategy(1..MAX_LEN)] + len: usize, + }, + #[weight(10)] + Read { + #[strategy(1..MAX_LEN)] + max_len: usize, + }, + #[weight(1)] + EnsureOrdering { ordered: bool }, + #[weight(1)] + Defragment, + } + + /// Tracks the state of the assembler for verification + struct RefState { + received: Vec, + returned: Vec, + ordered: bool, + } + + fn set_range(bits: &mut [bool], start: u64, len: usize) { + for i in start..(start + len as u64).min(bits.len() as u64) { + bits[i as usize] = true; + } + } + + impl RefState { + fn new() -> Self { + Self { + received: vec![false; MAX_OFFSET as usize], + returned: vec![false; MAX_OFFSET as usize], + ordered: true, + } + } + + fn insert(&mut self, offset: u64, len: usize) { + set_range(&mut self.received, offset, len); + } + + fn ensure_ordering(&mut self, ordered: bool) -> bool { + if ordered && !self.ordered { + return false; + } + self.ordered = ordered; + true + } + + fn bytes_read(&self) -> u64 { + self.returned.iter().filter(|&&x| x).count() as u64 + } + } + + fn make_data() -> Vec { + use rand::{Rng, SeedableRng}; + let mut rng = rand::rngs::StdRng::seed_from_u64(0xDEADBEEF); + let mut data = vec![0u8; MAX_OFFSET as usize]; + rng.fill(data.as_mut_slice()); + data + } + + fn get_slice(data: &[u8], offset: u64, len: usize) -> Bytes { + let start = offset as usize; + let end = (start + len).min(data.len()); + Bytes::copy_from_slice(&data[start..end]) + } + + fn verify_chunk(data: &[u8], chunk: &Chunk) -> bool { + let start = chunk.offset as usize; + chunk.bytes[..] == data[start..start + chunk.bytes.len()] + } + + #[proptest] + fn assembler_matches_reference( + #[strategy(proptest::collection::vec(any::(), 1..100))] ops: Vec, + ) { + let data = make_data(); + let mut asm = Assembler::new(); + let mut reference = RefState::new(); + + for op in ops { + match op { + Op::Insert { offset, len } => { + let bytes = get_slice(&data, offset, len); + asm.insert(offset, bytes, len); + reference.insert(offset, len); + } + Op::Read { max_len } => { + let ordered = reference.ordered; + let actual = asm.read(max_len, ordered); + + match actual { + None => { + // Should only be None if no unreturned received bytes available + let has_available = if ordered { + // In ordered mode, check if the first unreturned byte is received + reference + .returned + .iter() + .position(|&x| !x) + .is_some_and(|pos| reference.received[pos]) + } else { + // In unordered mode, check if any unreturned received byte exists + reference + .received + .iter() + .zip(&reference.returned) + .any(|(&r, &ret)| r && !ret) + }; + prop_assert!( + !has_available, + "read returned None but data was available" + ); + } + Some(chunk) => { + prop_assert!(chunk.bytes.len() <= max_len, "chunk exceeds max_len"); + prop_assert!(verify_chunk(&data, &chunk), "data corruption"); + // Mark as returned, check for duplicates + for i in 0..chunk.bytes.len() { + let offset = chunk.offset as usize + i; + prop_assert!( + reference.received[offset], + "returned unreceived byte at {offset}" + ); + prop_assert!( + !reference.returned[offset], + "duplicate byte at {offset}" + ); + reference.returned[offset] = true; + } + } + } + } + Op::EnsureOrdering { ordered } => { + let actual = asm.ensure_ordering(ordered).is_ok(); + let expected = reference.ensure_ordering(ordered); + prop_assert_eq!(actual, expected, "ensure_ordering result mismatch"); + } + Op::Defragment => { + if asm.state.is_ordered() { + asm.defragment(); + } + } + } + } + + // Invariant: bytes_read matches + prop_assert_eq!( + asm.bytes_read(), + reference.bytes_read(), + "bytes_read mismatch" + ); + } +} From 38a7d1558f4a0d4681f22405ff8352cfc6c3fea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Fri, 12 Dec 2025 11:38:18 +0100 Subject: [PATCH 2/2] Use array range set everywhere (#212) * Use ArrayRangeSet in SendBuffer * Implement replace for ArrayRangeSet * Remove RangeSet * WIP * Remove complex op from ArrayRangeSet that is used nowhere also remove some stray println! from some tests * Shut up clippy * fmt * Replace replace with an iterator and insert. iter_range does a linear scan, but so do all other ArrayRangeSet ops. binary search only becomes worth it when it becomes very large. For typical sizes linear scan is perfectly fine! * fmt * Put back the former println! statements as info! * Remove common_set_tests macro we only have one range set now! --- quinn-proto/src/connection/assembler.rs | 10 +- quinn-proto/src/connection/send_buffer.rs | 6 +- quinn-proto/src/range_set/array_range_set.rs | 15 +- quinn-proto/src/range_set/btree_range_set.rs | 381 ------------------- quinn-proto/src/range_set/mod.rs | 2 - quinn-proto/src/range_set/tests.rs | 335 ++++++++-------- quinn-proto/src/token_memory_cache.rs | 7 +- 7 files changed, 184 insertions(+), 572 deletions(-) delete mode 100644 quinn-proto/src/range_set/btree_range_set.rs diff --git a/quinn-proto/src/connection/assembler.rs b/quinn-proto/src/connection/assembler.rs index 456d7da68..424920477 100644 --- a/quinn-proto/src/connection/assembler.rs +++ b/quinn-proto/src/connection/assembler.rs @@ -6,7 +6,7 @@ use std::{ use bytes::{Buf, Bytes, BytesMut}; -use crate::range_set::RangeSet; +use crate::range_set::ArrayRangeSet; /// Helper to assemble unordered stream frames into an ordered stream #[derive(Debug, Default)] @@ -46,7 +46,7 @@ impl Assembler { // Get rid of possible duplicates self.defragment(); } - let mut recvd = RangeSet::new(); + let mut recvd = ArrayRangeSet::new(); recvd.insert(0..self.bytes_read); for chunk in &self.data { recvd.insert(chunk.offset..chunk.offset + chunk.bytes.len() as u64); @@ -157,7 +157,8 @@ impl Assembler { self.end = self.end.max(offset + bytes.len() as u64); if let State::Unordered { ref mut recvd } = self.state { // Discard duplicate data - for duplicate in recvd.replace(offset..offset + bytes.len() as u64) { + let range = offset..offset + bytes.len() as u64; + for duplicate in recvd.iter_range(range.clone()) { if duplicate.start > offset { let buffer = Buffer::new( offset, @@ -172,6 +173,7 @@ impl Assembler { bytes.advance((duplicate.end - offset) as usize); offset = duplicate.end; } + recvd.insert(range); } else if offset < self.bytes_read { if (offset + bytes.len() as u64) <= self.bytes_read { return; @@ -321,7 +323,7 @@ enum State { Unordered { /// The set of offsets that have been received from the peer, including portions not yet /// read by the application. - recvd: RangeSet, + recvd: ArrayRangeSet, }, } diff --git a/quinn-proto/src/connection/send_buffer.rs b/quinn-proto/src/connection/send_buffer.rs index 53a7416ef..217d2bada 100644 --- a/quinn-proto/src/connection/send_buffer.rs +++ b/quinn-proto/src/connection/send_buffer.rs @@ -2,7 +2,7 @@ use std::{collections::VecDeque, ops::Range}; use bytes::{Buf, Bytes}; -use crate::{VarInt, range_set::RangeSet}; +use crate::{VarInt, range_set::ArrayRangeSet}; /// Buffer of outgoing retransmittable stream data #[derive(Default, Debug)] @@ -20,9 +20,9 @@ pub(super) struct SendBuffer { /// Acknowledged ranges which couldn't be discarded yet as they don't include the earliest /// offset in `unacked` // TODO: Recover storage from these by compacting (#700) - acks: RangeSet, + acks: ArrayRangeSet, /// Previously transmitted ranges deemed lost - retransmits: RangeSet, + retransmits: ArrayRangeSet, } impl SendBuffer { diff --git a/quinn-proto/src/range_set/array_range_set.rs b/quinn-proto/src/range_set/array_range_set.rs index f273391a9..56a4d3177 100644 --- a/quinn-proto/src/range_set/array_range_set.rs +++ b/quinn-proto/src/range_set/array_range_set.rs @@ -68,13 +68,14 @@ impl ArrayRangeSet { false } - pub fn subtract(&mut self, other: &Self) { - // TODO: This can potentially be made more efficient, since the we know - // individual ranges are not overlapping, and the next range must start - // after the last one finished - for range in &other.0 { - self.remove(range.clone()); - } + pub fn iter_range(&self, range: Range) -> impl Iterator> + '_ { + self.iter().filter_map(move |r| { + if r.end > range.start && r.start < range.end { + Some(r.start.max(range.start)..r.end.min(range.end)) + } else { + None + } + }) } pub fn insert_one(&mut self, x: u64) -> bool { diff --git a/quinn-proto/src/range_set/btree_range_set.rs b/quinn-proto/src/range_set/btree_range_set.rs deleted file mode 100644 index 9121bd9cb..000000000 --- a/quinn-proto/src/range_set/btree_range_set.rs +++ /dev/null @@ -1,381 +0,0 @@ -use std::{ - cmp, - cmp::Ordering, - collections::{BTreeMap, btree_map}, - ops::{ - Bound::{Excluded, Included}, - Range, - }, -}; - -/// A set of u64 values optimized for long runs and random insert/delete/contains -#[derive(Debug, Default, Clone)] -pub struct RangeSet(BTreeMap); - -impl RangeSet { - pub fn new() -> Self { - Default::default() - } - - pub fn contains(&self, x: u64) -> bool { - self.pred(x).is_some_and(|(_, end)| end > x) - } - - pub fn insert_one(&mut self, x: u64) -> bool { - if let Some((start, end)) = self.pred(x) { - match end.cmp(&x) { - // Wholly contained - Ordering::Greater => { - return false; - } - Ordering::Equal => { - // Extend existing - self.0.remove(&start); - let mut new_end = x + 1; - if let Some((next_start, next_end)) = self.succ(x) { - if next_start == new_end { - self.0.remove(&next_start); - new_end = next_end; - } - } - self.0.insert(start, new_end); - return true; - } - _ => {} - } - } - let mut new_end = x + 1; - if let Some((next_start, next_end)) = self.succ(x) { - if next_start == new_end { - self.0.remove(&next_start); - new_end = next_end; - } - } - self.0.insert(x, new_end); - true - } - - pub fn insert(&mut self, mut x: Range) -> bool { - if x.is_empty() { - return false; - } - if let Some((start, end)) = self.pred(x.start) { - if end >= x.end { - // Wholly contained - return false; - } else if end >= x.start { - // Extend overlapping predecessor - self.0.remove(&start); - x.start = start; - } - } - while let Some((next_start, next_end)) = self.succ(x.start) { - if next_start > x.end { - break; - } - // Overlaps with successor - self.0.remove(&next_start); - x.end = cmp::max(next_end, x.end); - } - self.0.insert(x.start, x.end); - true - } - - /// Find closest range to `x` that begins at or before it - fn pred(&self, x: u64) -> Option<(u64, u64)> { - self.0 - .range((Included(0), Included(x))) - .next_back() - .map(|(&x, &y)| (x, y)) - } - - /// Find the closest range to `x` that begins after it - fn succ(&self, x: u64) -> Option<(u64, u64)> { - self.0 - .range((Excluded(x), Included(u64::MAX))) - .next() - .map(|(&x, &y)| (x, y)) - } - - pub fn remove(&mut self, x: Range) -> bool { - if x.is_empty() { - return false; - } - - let before = match self.pred(x.start) { - Some((start, end)) if end > x.start => { - self.0.remove(&start); - if start < x.start { - self.0.insert(start, x.start); - } - if end > x.end { - self.0.insert(x.end, end); - } - // Short-circuit if we cannot possibly overlap with another range - if end >= x.end { - return true; - } - true - } - Some(_) | None => false, - }; - let mut after = false; - while let Some((start, end)) = self.succ(x.start) { - if start >= x.end { - break; - } - after = true; - self.0.remove(&start); - if end > x.end { - self.0.insert(x.end, end); - break; - } - } - before || after - } - - /// Add a range to the set, returning the intersection of current ranges with the new one - pub fn replace(&mut self, mut range: Range) -> Replace<'_> { - let pred = if let Some((prev_start, prev_end)) = self - .pred(range.start) - .filter(|&(_, end)| end >= range.start) - { - self.0.remove(&prev_start); - let replaced_start = range.start; - range.start = range.start.min(prev_start); - let replaced_end = range.end.min(prev_end); - range.end = range.end.max(prev_end); - if replaced_start != replaced_end { - Some(replaced_start..replaced_end) - } else { - None - } - } else { - None - }; - Replace { - set: self, - range, - pred, - } - } - - pub fn add(&mut self, other: &Self) { - for (&start, &end) in &other.0 { - self.insert(start..end); - } - } - - pub fn subtract(&mut self, other: &Self) { - for (&start, &end) in &other.0 { - self.remove(start..end); - } - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn min(&self) -> Option { - self.0.first_key_value().map(|(&start, _)| start) - } - - pub fn max(&self) -> Option { - self.0.last_key_value().map(|(_, &end)| end - 1) - } - - pub fn len(&self) -> usize { - self.0.len() - } - pub fn iter(&self) -> Iter<'_> { - Iter(self.0.iter()) - } - pub fn elts(&self) -> EltIter<'_> { - EltIter { - inner: self.0.iter(), - next: 0, - end: 0, - } - } - - pub fn peek_min(&self) -> Option> { - let (&start, &end) = self.0.iter().next()?; - Some(start..end) - } - - pub fn pop_min(&mut self) -> Option> { - let result = self.peek_min()?; - self.0.remove(&result.start); - Some(result) - } -} - -pub struct Iter<'a>(btree_map::Iter<'a, u64, u64>); - -impl Iterator for Iter<'_> { - type Item = Range; - fn next(&mut self) -> Option> { - let (&start, &end) = self.0.next()?; - Some(start..end) - } -} - -impl DoubleEndedIterator for Iter<'_> { - fn next_back(&mut self) -> Option> { - let (&start, &end) = self.0.next_back()?; - Some(start..end) - } -} - -impl<'a> IntoIterator for &'a RangeSet { - type Item = Range; - type IntoIter = Iter<'a>; - fn into_iter(self) -> Iter<'a> { - self.iter() - } -} - -pub struct EltIter<'a> { - inner: btree_map::Iter<'a, u64, u64>, - next: u64, - end: u64, -} - -impl Iterator for EltIter<'_> { - type Item = u64; - fn next(&mut self) -> Option { - if self.next == self.end { - let (&start, &end) = self.inner.next()?; - self.next = start; - self.end = end; - } - let x = self.next; - self.next += 1; - Some(x) - } -} - -impl DoubleEndedIterator for EltIter<'_> { - fn next_back(&mut self) -> Option { - if self.next == self.end { - let (&start, &end) = self.inner.next_back()?; - self.next = start; - self.end = end; - } - self.end -= 1; - Some(self.end) - } -} - -/// Iterator returned by `RangeSet::replace` -pub struct Replace<'a> { - set: &'a mut RangeSet, - /// Portion of the intersection arising from a range beginning at or before the newly inserted - /// range - pred: Option>, - /// Union of the input range and all ranges that have been visited by the iterator so far - range: Range, -} - -impl Iterator for Replace<'_> { - type Item = Range; - fn next(&mut self) -> Option> { - if let Some(pred) = self.pred.take() { - // If a range starting before the inserted range overlapped with it, return the - // corresponding overlap first - return Some(pred); - } - - let (next_start, next_end) = self.set.succ(self.range.start)?; - if next_start > self.range.end { - // If the next successor range starts after the current range ends, there can be no more - // overlaps. This is sound even when `self.range.end` is increased because `RangeSet` is - // guaranteed not to contain pairs of ranges that could be simplified. - return None; - } - // Remove the redundant range... - self.set.0.remove(&next_start); - // ...and handle the case where the redundant range ends later than the new range. - let replaced_end = self.range.end.min(next_end); - self.range.end = self.range.end.max(next_end); - if next_start == replaced_end { - // If the redundant range started exactly where the new range ended, there was no - // overlap with it or any later range. - None - } else { - Some(next_start..replaced_end) - } - } -} - -impl Drop for Replace<'_> { - fn drop(&mut self) { - // Ensure we drain all remaining overlapping ranges - for _ in &mut *self {} - // Insert the final aggregate range - self.set.0.insert(self.range.start, self.range.end); - } -} - -/// This module contains tests which only apply for this `RangeSet` implementation -/// -/// Tests which apply for all implementations can be found in the `tests.rs` module -#[cfg(test)] -mod tests { - #![allow(clippy::single_range_in_vec_init)] // https://github.com/rust-lang/rust-clippy/issues/11086 - use super::*; - - #[test] - fn replace_contained() { - let mut set = RangeSet::new(); - set.insert(2..4); - assert_eq!(set.replace(1..5).collect::>(), &[2..4]); - assert_eq!(set.len(), 1); - assert_eq!(set.peek_min().unwrap(), 1..5); - } - - #[test] - fn replace_contains() { - let mut set = RangeSet::new(); - set.insert(1..5); - assert_eq!(set.replace(2..4).collect::>(), &[2..4]); - assert_eq!(set.len(), 1); - assert_eq!(set.peek_min().unwrap(), 1..5); - } - - #[test] - fn replace_pred() { - let mut set = RangeSet::new(); - set.insert(2..4); - assert_eq!(set.replace(3..5).collect::>(), &[3..4]); - assert_eq!(set.len(), 1); - assert_eq!(set.peek_min().unwrap(), 2..5); - } - - #[test] - fn replace_succ() { - let mut set = RangeSet::new(); - set.insert(2..4); - assert_eq!(set.replace(1..3).collect::>(), &[2..3]); - assert_eq!(set.len(), 1); - assert_eq!(set.peek_min().unwrap(), 1..4); - } - - #[test] - fn replace_exact_pred() { - let mut set = RangeSet::new(); - set.insert(2..4); - assert_eq!(set.replace(4..6).collect::>(), &[]); - assert_eq!(set.len(), 1); - assert_eq!(set.peek_min().unwrap(), 2..6); - } - - #[test] - fn replace_exact_succ() { - let mut set = RangeSet::new(); - set.insert(2..4); - assert_eq!(set.replace(0..2).collect::>(), &[]); - assert_eq!(set.len(), 1); - assert_eq!(set.peek_min().unwrap(), 0..4); - } -} diff --git a/quinn-proto/src/range_set/mod.rs b/quinn-proto/src/range_set/mod.rs index 9f16e7e86..eb20ad40d 100644 --- a/quinn-proto/src/range_set/mod.rs +++ b/quinn-proto/src/range_set/mod.rs @@ -1,7 +1,5 @@ mod array_range_set; -mod btree_range_set; #[cfg(test)] mod tests; pub(crate) use array_range_set::ArrayRangeSet; -pub(crate) use btree_range_set::RangeSet; diff --git a/quinn-proto/src/range_set/tests.rs b/quinn-proto/src/range_set/tests.rs index 1e75da4dc..eb83936ee 100644 --- a/quinn-proto/src/range_set/tests.rs +++ b/quinn-proto/src/range_set/tests.rs @@ -2,194 +2,185 @@ use std::ops::Range; use super::*; -macro_rules! common_set_tests { - ($set_name:ident, $set_type:ident) => { - mod $set_name { - use super::*; +mod array_range_set { + use super::*; - #[test] - fn merge_and_split() { - let mut set = $set_type::new(); - assert!(set.insert(0..2)); - assert!(set.insert(2..4)); - assert!(!set.insert(1..3)); - assert_eq!(set.len(), 1); - assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 3]); - assert!(!set.contains(4)); - assert!(set.remove(2..3)); - assert_eq!(set.len(), 2); - assert!(!set.contains(2)); - assert_eq!(&set.elts().collect::>()[..], [0, 1, 3]); - } + #[test] + fn merge_and_split() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..2)); + assert!(set.insert(2..4)); + assert!(!set.insert(1..3)); + assert_eq!(set.len(), 1); + assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 3]); + assert!(!set.contains(4)); + assert!(set.remove(2..3)); + assert_eq!(set.len(), 2); + assert!(!set.contains(2)); + assert_eq!(&set.elts().collect::>()[..], [0, 1, 3]); + } - #[test] - fn double_merge_exact() { - let mut set = $set_type::new(); - assert!(set.insert(0..2)); - assert!(set.insert(4..6)); - assert_eq!(set.len(), 2); - assert!(set.insert(2..4)); - assert_eq!(set.len(), 1); - assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 3, 4, 5]); - } + #[test] + fn double_merge_exact() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..2)); + assert!(set.insert(4..6)); + assert_eq!(set.len(), 2); + assert!(set.insert(2..4)); + assert_eq!(set.len(), 1); + assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 3, 4, 5]); + } - #[test] - fn single_merge_low() { - let mut set = $set_type::new(); - assert!(set.insert(0..2)); - assert!(set.insert(4..6)); - assert_eq!(set.len(), 2); - assert!(set.insert(2..3)); - assert_eq!(set.len(), 2); - assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 4, 5]); - } + #[test] + fn single_merge_low() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..2)); + assert!(set.insert(4..6)); + assert_eq!(set.len(), 2); + assert!(set.insert(2..3)); + assert_eq!(set.len(), 2); + assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 4, 5]); + } - #[test] - fn single_merge_high() { - let mut set = $set_type::new(); - assert!(set.insert(0..2)); - assert!(set.insert(4..6)); - assert_eq!(set.len(), 2); - assert!(set.insert(3..4)); - assert_eq!(set.len(), 2); - assert_eq!(&set.elts().collect::>()[..], [0, 1, 3, 4, 5]); - } + #[test] + fn single_merge_high() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..2)); + assert!(set.insert(4..6)); + assert_eq!(set.len(), 2); + assert!(set.insert(3..4)); + assert_eq!(set.len(), 2); + assert_eq!(&set.elts().collect::>()[..], [0, 1, 3, 4, 5]); + } - #[test] - fn double_merge_wide() { - let mut set = $set_type::new(); - assert!(set.insert(0..2)); - assert!(set.insert(4..6)); - assert_eq!(set.len(), 2); - assert!(set.insert(1..5)); - assert_eq!(set.len(), 1); - assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 3, 4, 5]); - } + #[test] + fn double_merge_wide() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..2)); + assert!(set.insert(4..6)); + assert_eq!(set.len(), 2); + assert!(set.insert(1..5)); + assert_eq!(set.len(), 1); + assert_eq!(&set.elts().collect::>()[..], [0, 1, 2, 3, 4, 5]); + } - #[test] - fn double_remove() { - let mut set = $set_type::new(); - assert!(set.insert(0..2)); - assert!(set.insert(4..6)); - assert!(set.remove(1..5)); - assert_eq!(set.len(), 2); - assert_eq!(&set.elts().collect::>()[..], [0, 5]); - } + #[test] + fn double_remove() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..2)); + assert!(set.insert(4..6)); + assert!(set.remove(1..5)); + assert_eq!(set.len(), 2); + assert_eq!(&set.elts().collect::>()[..], [0, 5]); + } - #[test] - fn insert_multiple() { - let mut set = $set_type::new(); - assert!(set.insert(0..1)); - assert!(set.insert(2..3)); - assert!(set.insert(4..5)); - assert!(set.insert(0..5)); - assert_eq!(set.len(), 1); - } + #[test] + fn insert_multiple() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..1)); + assert!(set.insert(2..3)); + assert!(set.insert(4..5)); + assert!(set.insert(0..5)); + assert_eq!(set.len(), 1); + } - #[test] - fn remove_multiple() { - let mut set = $set_type::new(); - assert!(set.insert(0..1)); - assert!(set.insert(2..3)); - assert!(set.insert(4..5)); - assert!(set.remove(0..5)); - assert!(set.is_empty()); - } + #[test] + fn remove_multiple() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..1)); + assert!(set.insert(2..3)); + assert!(set.insert(4..5)); + assert!(set.remove(0..5)); + assert!(set.is_empty()); + } - #[test] - fn double_insert() { - let mut set = $set_type::new(); - assert!(set.insert(0..2)); - assert!(!set.insert(0..2)); - assert!(set.insert(2..4)); - assert!(!set.insert(2..4)); - assert!(!set.insert(0..4)); - assert!(!set.insert(1..2)); - assert!(!set.insert(1..3)); - assert!(!set.insert(1..4)); - assert_eq!(set.len(), 1); - } + #[test] + fn double_insert() { + let mut set = ArrayRangeSet::new(); + assert!(set.insert(0..2)); + assert!(!set.insert(0..2)); + assert!(set.insert(2..4)); + assert!(!set.insert(2..4)); + assert!(!set.insert(0..4)); + assert!(!set.insert(1..2)); + assert!(!set.insert(1..3)); + assert!(!set.insert(1..4)); + assert_eq!(set.len(), 1); + } - #[test] - fn skip_empty_ranges() { - let mut set = $set_type::new(); - assert!(!set.insert(2..2)); - assert_eq!(set.len(), 0); - assert!(!set.insert(4..4)); - assert_eq!(set.len(), 0); - assert!(!set.insert(0..0)); - assert_eq!(set.len(), 0); - } + #[test] + fn skip_empty_ranges() { + let mut set = ArrayRangeSet::new(); + assert!(!set.insert(2..2)); + assert_eq!(set.len(), 0); + assert!(!set.insert(4..4)); + assert_eq!(set.len(), 0); + assert!(!set.insert(0..0)); + assert_eq!(set.len(), 0); + } - #[test] - fn compare_insert_to_reference() { - const MAX_RANGE: u64 = 50; + #[test] + fn compare_insert_to_reference() { + const MAX_RANGE: u64 = 50; - for start in 0..=MAX_RANGE { - for end in 0..=MAX_RANGE { - println!("insert({}..{})", start, end); - let (mut set, mut reference) = create_initial_sets(MAX_RANGE); - assert_eq!(set.insert(start..end), reference.insert(start..end)); - assert_sets_equal(&set, &reference); - } - } - } - - #[test] - fn compare_remove_to_reference() { - const MAX_RANGE: u64 = 50; - - for start in 0..=MAX_RANGE { - for end in 0..=MAX_RANGE { - println!("remove({}..{})", start, end); - let (mut set, mut reference) = create_initial_sets(MAX_RANGE); - assert_eq!(set.remove(start..end), reference.remove(start..end)); - assert_sets_equal(&set, &reference); - } - } - } - - #[test] - fn min_max() { - let mut set = $set_type::new(); - set.insert(1..3); - set.insert(4..5); - set.insert(6..10); - assert_eq!(set.min(), Some(1)); - assert_eq!(set.max(), Some(9)); - } - - fn create_initial_sets(max_range: u64) -> ($set_type, RefRangeSet) { - let mut set = $set_type::new(); - let mut reference = RefRangeSet::new(max_range as usize); + for start in 0..=MAX_RANGE { + for end in 0..=MAX_RANGE { + let (mut set, mut reference) = create_initial_sets(MAX_RANGE); + assert_eq!(set.insert(start..end), reference.insert(start..end)); assert_sets_equal(&set, &reference); - - assert_eq!(set.insert(2..6), reference.insert(2..6)); - assert_eq!(set.insert(10..14), reference.insert(10..14)); - assert_eq!(set.insert(14..14), reference.insert(14..14)); - assert_eq!(set.insert(18..19), reference.insert(18..19)); - assert_eq!(set.insert(20..21), reference.insert(20..21)); - assert_eq!(set.insert(22..24), reference.insert(22..24)); - assert_eq!(set.insert(26..30), reference.insert(26..30)); - assert_eq!(set.insert(34..38), reference.insert(34..38)); - assert_eq!(set.insert(42..44), reference.insert(42..44)); - - assert_sets_equal(&set, &reference); - - (set, reference) - } - - fn assert_sets_equal(set: &$set_type, reference: &RefRangeSet) { - assert_eq!(set.len(), reference.len()); - assert_eq!(set.is_empty(), reference.is_empty()); - assert_eq!(set.elts().collect::>()[..], reference.elts()[..]); } } - }; -} + } -common_set_tests!(range_set, RangeSet); -common_set_tests!(array_range_set, ArrayRangeSet); + #[test] + fn compare_remove_to_reference() { + const MAX_RANGE: u64 = 50; + + for start in 0..=MAX_RANGE { + for end in 0..=MAX_RANGE { + let (mut set, mut reference) = create_initial_sets(MAX_RANGE); + assert_eq!(set.remove(start..end), reference.remove(start..end)); + assert_sets_equal(&set, &reference); + } + } + } + + #[test] + fn min_max() { + let mut set = ArrayRangeSet::new(); + set.insert(1..3); + set.insert(4..5); + set.insert(6..10); + assert_eq!(set.min(), Some(1)); + assert_eq!(set.max(), Some(9)); + } + + fn create_initial_sets(max_range: u64) -> (ArrayRangeSet, RefRangeSet) { + let mut set = ArrayRangeSet::new(); + let mut reference = RefRangeSet::new(max_range as usize); + assert_sets_equal(&set, &reference); + + assert_eq!(set.insert(2..6), reference.insert(2..6)); + assert_eq!(set.insert(10..14), reference.insert(10..14)); + assert_eq!(set.insert(14..14), reference.insert(14..14)); + assert_eq!(set.insert(18..19), reference.insert(18..19)); + assert_eq!(set.insert(20..21), reference.insert(20..21)); + assert_eq!(set.insert(22..24), reference.insert(22..24)); + assert_eq!(set.insert(26..30), reference.insert(26..30)); + assert_eq!(set.insert(34..38), reference.insert(34..38)); + assert_eq!(set.insert(42..44), reference.insert(42..44)); + + assert_sets_equal(&set, &reference); + + (set, reference) + } + + fn assert_sets_equal(set: &ArrayRangeSet, reference: &RefRangeSet) { + assert_eq!(set.len(), reference.len()); + assert_eq!(set.is_empty(), reference.is_empty()); + assert_eq!(set.elts().collect::>()[..], reference.elts()[..]); + } +} /// A very simple reference implementation of a RangeSet struct RefRangeSet { diff --git a/quinn-proto/src/token_memory_cache.rs b/quinn-proto/src/token_memory_cache.rs index 3fce05f8a..70bfd1272 100644 --- a/quinn-proto/src/token_memory_cache.rs +++ b/quinn-proto/src/token_memory_cache.rs @@ -157,6 +157,7 @@ mod tests { use super::*; use rand::prelude::*; use rand_pcg::Pcg32; + use tracing::info; fn new_rng() -> impl Rng { Pcg32::from_seed(0xdeadbeefdeadbeefdeadbeefdeadbeefu128.to_le_bytes()) @@ -176,7 +177,7 @@ mod tests { if rng.random_bool(0.666) { // store let token = Bytes::from(vec![i]); - println!("STORE {server_name} {token:?}"); + info!("STORE {server_name} {token:?}"); if let Some((j, _)) = cache_1 .iter() .enumerate() @@ -199,7 +200,7 @@ mod tests { cache_2.insert(&server_name.to_string(), token); } else { // take - println!("TAKE {server_name}"); + info!("TAKE {server_name}"); let expecting = cache_1 .iter() .enumerate() @@ -213,7 +214,7 @@ mod tests { } token }); - println!("EXPECTING {expecting:?}"); + info!("EXPECTING {expecting:?}"); assert_eq!(cache_2.take(&server_name.to_string()), expecting); } }