| use core::fmt; |
| |
| use crate::config::ASSEMBLER_MAX_SEGMENT_COUNT; |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| pub struct TooManyHolesError; |
| |
| impl fmt::Display for TooManyHolesError { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "too many holes") |
| } |
| } |
| |
| #[cfg(feature = "std")] |
| impl std::error::Error for TooManyHolesError {} |
| |
| /// A contiguous chunk of absent data, followed by a contiguous chunk of present data. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| struct Contig { |
| hole_size: usize, |
| data_size: usize, |
| } |
| |
| impl fmt::Display for Contig { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| if self.has_hole() { |
| write!(f, "({})", self.hole_size)?; |
| } |
| if self.has_hole() && self.has_data() { |
| write!(f, " ")?; |
| } |
| if self.has_data() { |
| write!(f, "{}", self.data_size)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(feature = "defmt")] |
| impl defmt::Format for Contig { |
| fn format(&self, fmt: defmt::Formatter) { |
| if self.has_hole() { |
| defmt::write!(fmt, "({})", self.hole_size); |
| } |
| if self.has_hole() && self.has_data() { |
| defmt::write!(fmt, " "); |
| } |
| if self.has_data() { |
| defmt::write!(fmt, "{}", self.data_size); |
| } |
| } |
| } |
| |
| impl Contig { |
| const fn empty() -> Contig { |
| Contig { |
| hole_size: 0, |
| data_size: 0, |
| } |
| } |
| |
| fn hole_and_data(hole_size: usize, data_size: usize) -> Contig { |
| Contig { |
| hole_size, |
| data_size, |
| } |
| } |
| |
| fn has_hole(&self) -> bool { |
| self.hole_size != 0 |
| } |
| |
| fn has_data(&self) -> bool { |
| self.data_size != 0 |
| } |
| |
| fn total_size(&self) -> usize { |
| self.hole_size + self.data_size |
| } |
| |
| fn shrink_hole_by(&mut self, size: usize) { |
| self.hole_size -= size; |
| } |
| |
| fn shrink_hole_to(&mut self, size: usize) { |
| debug_assert!(self.hole_size >= size); |
| |
| let total_size = self.total_size(); |
| self.hole_size = size; |
| self.data_size = total_size - size; |
| } |
| } |
| |
| /// A buffer (re)assembler. |
| /// |
| /// Currently, up to a hardcoded limit of 4 or 32 holes can be tracked in the buffer. |
| #[derive(Debug, PartialEq, Eq, Clone)] |
| pub struct Assembler { |
| contigs: [Contig; ASSEMBLER_MAX_SEGMENT_COUNT], |
| } |
| |
| impl fmt::Display for Assembler { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "[ ")?; |
| for contig in self.contigs.iter() { |
| if !contig.has_data() { |
| break; |
| } |
| write!(f, "{contig} ")?; |
| } |
| write!(f, "]")?; |
| Ok(()) |
| } |
| } |
| |
| #[cfg(feature = "defmt")] |
| impl defmt::Format for Assembler { |
| fn format(&self, fmt: defmt::Formatter) { |
| defmt::write!(fmt, "[ "); |
| for contig in self.contigs.iter() { |
| if !contig.has_data() { |
| break; |
| } |
| defmt::write!(fmt, "{} ", contig); |
| } |
| defmt::write!(fmt, "]"); |
| } |
| } |
| |
| // Invariant on Assembler::contigs: |
| // - There's an index `i` where all contigs before have data, and all contigs after don't (are unused). |
| // - All contigs with data must have hole_size != 0, except the first. |
| |
| impl Assembler { |
| /// Create a new buffer assembler. |
| pub const fn new() -> Assembler { |
| const EMPTY: Contig = Contig::empty(); |
| Assembler { |
| contigs: [EMPTY; ASSEMBLER_MAX_SEGMENT_COUNT], |
| } |
| } |
| |
| pub fn clear(&mut self) { |
| self.contigs.fill(Contig::empty()); |
| } |
| |
| fn front(&self) -> Contig { |
| self.contigs[0] |
| } |
| |
| /// Return length of the front contiguous range without removing it from the assembler |
| pub fn peek_front(&self) -> usize { |
| let front = self.front(); |
| if front.has_hole() { |
| 0 |
| } else { |
| front.data_size |
| } |
| } |
| |
| fn back(&self) -> Contig { |
| self.contigs[self.contigs.len() - 1] |
| } |
| |
| /// Return whether the assembler contains no data. |
| pub fn is_empty(&self) -> bool { |
| !self.front().has_data() |
| } |
| |
| /// Remove a contig at the given index. |
| fn remove_contig_at(&mut self, at: usize) { |
| debug_assert!(self.contigs[at].has_data()); |
| |
| for i in at..self.contigs.len() - 1 { |
| if !self.contigs[i].has_data() { |
| return; |
| } |
| self.contigs[i] = self.contigs[i + 1]; |
| } |
| |
| // Removing the last one. |
| self.contigs[self.contigs.len() - 1] = Contig::empty(); |
| } |
| |
| /// Add a contig at the given index, and return a pointer to it. |
| fn add_contig_at(&mut self, at: usize) -> Result<&mut Contig, TooManyHolesError> { |
| if self.back().has_data() { |
| return Err(TooManyHolesError); |
| } |
| |
| for i in (at + 1..self.contigs.len()).rev() { |
| self.contigs[i] = self.contigs[i - 1]; |
| } |
| |
| self.contigs[at] = Contig::empty(); |
| Ok(&mut self.contigs[at]) |
| } |
| |
| /// Add a new contiguous range to the assembler, |
| /// or return `Err(TooManyHolesError)` if too many discontinuities are already recorded. |
| pub fn add(&mut self, mut offset: usize, size: usize) -> Result<(), TooManyHolesError> { |
| if size == 0 { |
| return Ok(()); |
| } |
| |
| let mut i = 0; |
| |
| // Find index of the contig containing the start of the range. |
| loop { |
| if i == self.contigs.len() { |
| // The new range is after all the previous ranges, but there/s no space to add it. |
| return Err(TooManyHolesError); |
| } |
| let contig = &mut self.contigs[i]; |
| if !contig.has_data() { |
| // The new range is after all the previous ranges. Add it. |
| *contig = Contig::hole_and_data(offset, size); |
| return Ok(()); |
| } |
| if offset <= contig.total_size() { |
| break; |
| } |
| offset -= contig.total_size(); |
| i += 1; |
| } |
| |
| let contig = &mut self.contigs[i]; |
| if offset < contig.hole_size { |
| // Range starts within the hole. |
| |
| if offset + size < contig.hole_size { |
| // Range also ends within the hole. |
| let new_contig = self.add_contig_at(i)?; |
| new_contig.hole_size = offset; |
| new_contig.data_size = size; |
| |
| // Previous contigs[index] got moved to contigs[index+1] |
| self.contigs[i + 1].shrink_hole_by(offset + size); |
| return Ok(()); |
| } |
| |
| // The range being added covers both a part of the hole and a part of the data |
| // in this contig, shrink the hole in this contig. |
| contig.shrink_hole_to(offset); |
| } |
| |
| // coalesce contigs to the right. |
| let mut j = i + 1; |
| while j < self.contigs.len() |
| && self.contigs[j].has_data() |
| && offset + size >= self.contigs[i].total_size() + self.contigs[j].hole_size |
| { |
| self.contigs[i].data_size += self.contigs[j].total_size(); |
| j += 1; |
| } |
| let shift = j - i - 1; |
| if shift != 0 { |
| for x in i + 1..self.contigs.len() { |
| if !self.contigs[x].has_data() { |
| break; |
| } |
| |
| self.contigs[x] = self |
| .contigs |
| .get(x + shift) |
| .copied() |
| .unwrap_or_else(Contig::empty); |
| } |
| } |
| |
| if offset + size > self.contigs[i].total_size() { |
| // The added range still extends beyond the current contig. Increase data size. |
| let left = offset + size - self.contigs[i].total_size(); |
| self.contigs[i].data_size += left; |
| |
| // Decrease hole size of the next, if any. |
| if i + 1 < self.contigs.len() && self.contigs[i + 1].has_data() { |
| self.contigs[i + 1].hole_size -= left; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// Remove a contiguous range from the front of the assembler. |
| /// If no such range, return 0. |
| pub fn remove_front(&mut self) -> usize { |
| let front = self.front(); |
| if front.has_hole() || !front.has_data() { |
| 0 |
| } else { |
| self.remove_contig_at(0); |
| debug_assert!(front.data_size > 0); |
| front.data_size |
| } |
| } |
| |
| /// Add a segment, then remove_front. |
| /// |
| /// This is equivalent to calling `add` then `remove_front` individually, |
| /// except it's guaranteed to not fail when offset = 0. |
| /// This is required for TCP: we must never drop the next expected segment, or |
| /// the protocol might get stuck. |
| pub fn add_then_remove_front( |
| &mut self, |
| offset: usize, |
| size: usize, |
| ) -> Result<usize, TooManyHolesError> { |
| // This is the only case where a segment at offset=0 would cause the |
| // total amount of contigs to rise (and therefore can potentially cause |
| // a TooManyHolesError). Handle it in a way that is guaranteed to succeed. |
| if offset == 0 && size < self.contigs[0].hole_size { |
| self.contigs[0].hole_size -= size; |
| return Ok(size); |
| } |
| |
| self.add(offset, size)?; |
| Ok(self.remove_front()) |
| } |
| |
| /// Iterate over all of the contiguous data ranges. |
| /// |
| /// This is used in calculating what data ranges have been received. The offset indicates the |
| /// number of bytes of contiguous data received before the beginnings of this Assembler. |
| /// |
| /// Data Hole Data |
| /// |--- 100 ---|--- 200 ---|--- 100 ---| |
| /// |
| /// An offset of 1500 would return the ranges: ``(1500, 1600), (1800, 1900)`` |
| pub fn iter_data(&self, first_offset: usize) -> AssemblerIter { |
| AssemblerIter::new(self, first_offset) |
| } |
| } |
| |
| pub struct AssemblerIter<'a> { |
| assembler: &'a Assembler, |
| offset: usize, |
| index: usize, |
| left: usize, |
| right: usize, |
| } |
| |
| impl<'a> AssemblerIter<'a> { |
| fn new(assembler: &'a Assembler, offset: usize) -> AssemblerIter<'a> { |
| AssemblerIter { |
| assembler, |
| offset, |
| index: 0, |
| left: 0, |
| right: 0, |
| } |
| } |
| } |
| |
| impl<'a> Iterator for AssemblerIter<'a> { |
| type Item = (usize, usize); |
| |
| fn next(&mut self) -> Option<(usize, usize)> { |
| let mut data_range = None; |
| while data_range.is_none() && self.index < self.assembler.contigs.len() { |
| let contig = self.assembler.contigs[self.index]; |
| self.left += contig.hole_size; |
| self.right = self.left + contig.data_size; |
| data_range = if self.left < self.right { |
| let data_range = (self.left + self.offset, self.right + self.offset); |
| self.left = self.right; |
| Some(data_range) |
| } else { |
| None |
| }; |
| self.index += 1; |
| } |
| data_range |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use std::vec::Vec; |
| |
| impl From<Vec<(usize, usize)>> for Assembler { |
| fn from(vec: Vec<(usize, usize)>) -> Assembler { |
| const EMPTY: Contig = Contig::empty(); |
| |
| let mut contigs = [EMPTY; ASSEMBLER_MAX_SEGMENT_COUNT]; |
| for (i, &(hole_size, data_size)) in vec.iter().enumerate() { |
| contigs[i] = Contig { |
| hole_size, |
| data_size, |
| }; |
| } |
| Assembler { contigs } |
| } |
| } |
| |
| macro_rules! contigs { |
| [$( $x:expr ),*] => ({ |
| Assembler::from(vec![$( $x ),*]) |
| }) |
| } |
| |
| #[test] |
| fn test_new() { |
| let assr = Assembler::new(); |
| assert_eq!(assr, contigs![]); |
| } |
| |
| #[test] |
| fn test_empty_add_full() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(0, 16), Ok(())); |
| assert_eq!(assr, contigs![(0, 16)]); |
| } |
| |
| #[test] |
| fn test_empty_add_front() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(0, 4), Ok(())); |
| assert_eq!(assr, contigs![(0, 4)]); |
| } |
| |
| #[test] |
| fn test_empty_add_back() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(12, 4), Ok(())); |
| assert_eq!(assr, contigs![(12, 4)]); |
| } |
| |
| #[test] |
| fn test_empty_add_mid() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(4, 8), Ok(())); |
| assert_eq!(assr, contigs![(4, 8)]); |
| } |
| |
| #[test] |
| fn test_partial_add_front() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(0, 4), Ok(())); |
| assert_eq!(assr, contigs![(0, 12)]); |
| } |
| |
| #[test] |
| fn test_partial_add_back() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(12, 4), Ok(())); |
| assert_eq!(assr, contigs![(4, 12)]); |
| } |
| |
| #[test] |
| fn test_partial_add_front_overlap() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(0, 8), Ok(())); |
| assert_eq!(assr, contigs![(0, 12)]); |
| } |
| |
| #[test] |
| fn test_partial_add_front_overlap_split() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(2, 6), Ok(())); |
| assert_eq!(assr, contigs![(2, 10)]); |
| } |
| |
| #[test] |
| fn test_partial_add_back_overlap() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(8, 8), Ok(())); |
| assert_eq!(assr, contigs![(4, 12)]); |
| } |
| |
| #[test] |
| fn test_partial_add_back_overlap_split() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(10, 4), Ok(())); |
| assert_eq!(assr, contigs![(4, 10)]); |
| } |
| |
| #[test] |
| fn test_partial_add_both_overlap() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(0, 16), Ok(())); |
| assert_eq!(assr, contigs![(0, 16)]); |
| } |
| |
| #[test] |
| fn test_partial_add_both_overlap_split() { |
| let mut assr = contigs![(4, 8)]; |
| assert_eq!(assr.add(2, 12), Ok(())); |
| assert_eq!(assr, contigs![(2, 12)]); |
| } |
| |
| #[test] |
| fn test_rejected_add_keeps_state() { |
| let mut assr = Assembler::new(); |
| for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT { |
| assert_eq!(assr.add(c * 10, 3), Ok(())); |
| } |
| // Maximum of allowed holes is reached |
| let assr_before = assr.clone(); |
| assert_eq!(assr.add(1, 3), Err(TooManyHolesError)); |
| assert_eq!(assr_before, assr); |
| } |
| |
| #[test] |
| fn test_empty_remove_front() { |
| let mut assr = contigs![]; |
| assert_eq!(assr.remove_front(), 0); |
| } |
| |
| #[test] |
| fn test_trailing_hole_remove_front() { |
| let mut assr = contigs![(0, 4)]; |
| assert_eq!(assr.remove_front(), 4); |
| assert_eq!(assr, contigs![]); |
| } |
| |
| #[test] |
| fn test_trailing_data_remove_front() { |
| let mut assr = contigs![(0, 4), (4, 4)]; |
| assert_eq!(assr.remove_front(), 4); |
| assert_eq!(assr, contigs![(4, 4)]); |
| } |
| |
| #[test] |
| fn test_boundary_case_remove_front() { |
| let mut vec = vec![(1, 1); ASSEMBLER_MAX_SEGMENT_COUNT]; |
| vec[0] = (0, 2); |
| let mut assr = Assembler::from(vec); |
| assert_eq!(assr.remove_front(), 2); |
| let mut vec = vec![(1, 1); ASSEMBLER_MAX_SEGMENT_COUNT]; |
| vec[ASSEMBLER_MAX_SEGMENT_COUNT - 1] = (0, 0); |
| let exp_assr = Assembler::from(vec); |
| assert_eq!(assr, exp_assr); |
| } |
| |
| #[test] |
| fn test_shrink_next_hole() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(100, 10), Ok(())); |
| assert_eq!(assr.add(50, 10), Ok(())); |
| assert_eq!(assr.add(40, 30), Ok(())); |
| assert_eq!(assr, contigs![(40, 30), (30, 10)]); |
| } |
| |
| #[test] |
| fn test_join_two() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(10, 10), Ok(())); |
| assert_eq!(assr.add(50, 10), Ok(())); |
| assert_eq!(assr.add(15, 40), Ok(())); |
| assert_eq!(assr, contigs![(10, 50)]); |
| } |
| |
| #[test] |
| fn test_join_two_reversed() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(50, 10), Ok(())); |
| assert_eq!(assr.add(10, 10), Ok(())); |
| assert_eq!(assr.add(15, 40), Ok(())); |
| assert_eq!(assr, contigs![(10, 50)]); |
| } |
| |
| #[test] |
| fn test_join_two_overlong() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(50, 10), Ok(())); |
| assert_eq!(assr.add(10, 10), Ok(())); |
| assert_eq!(assr.add(15, 60), Ok(())); |
| assert_eq!(assr, contigs![(10, 65)]); |
| } |
| |
| #[test] |
| fn test_iter_empty() { |
| let assr = Assembler::new(); |
| let segments: Vec<_> = assr.iter_data(10).collect(); |
| assert_eq!(segments, vec![]); |
| } |
| |
| #[test] |
| fn test_iter_full() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(0, 16), Ok(())); |
| let segments: Vec<_> = assr.iter_data(10).collect(); |
| assert_eq!(segments, vec![(10, 26)]); |
| } |
| |
| #[test] |
| fn test_iter_offset() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(0, 16), Ok(())); |
| let segments: Vec<_> = assr.iter_data(100).collect(); |
| assert_eq!(segments, vec![(100, 116)]); |
| } |
| |
| #[test] |
| fn test_iter_one_front() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(0, 4), Ok(())); |
| let segments: Vec<_> = assr.iter_data(10).collect(); |
| assert_eq!(segments, vec![(10, 14)]); |
| } |
| |
| #[test] |
| fn test_iter_one_back() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(12, 4), Ok(())); |
| let segments: Vec<_> = assr.iter_data(10).collect(); |
| assert_eq!(segments, vec![(22, 26)]); |
| } |
| |
| #[test] |
| fn test_iter_one_mid() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(4, 8), Ok(())); |
| let segments: Vec<_> = assr.iter_data(10).collect(); |
| assert_eq!(segments, vec![(14, 22)]); |
| } |
| |
| #[test] |
| fn test_iter_one_trailing_gap() { |
| let assr = contigs![(4, 8)]; |
| let segments: Vec<_> = assr.iter_data(100).collect(); |
| assert_eq!(segments, vec![(104, 112)]); |
| } |
| |
| #[test] |
| fn test_iter_two_split() { |
| let assr = contigs![(2, 6), (4, 1)]; |
| let segments: Vec<_> = assr.iter_data(100).collect(); |
| assert_eq!(segments, vec![(102, 108), (112, 113)]); |
| } |
| |
| #[test] |
| fn test_iter_three_split() { |
| let assr = contigs![(2, 6), (2, 1), (2, 2)]; |
| let segments: Vec<_> = assr.iter_data(100).collect(); |
| assert_eq!(segments, vec![(102, 108), (110, 111), (113, 115)]); |
| } |
| |
| #[test] |
| fn test_issue_694() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(0, 1), Ok(())); |
| assert_eq!(assr.add(2, 1), Ok(())); |
| assert_eq!(assr.add(1, 1), Ok(())); |
| } |
| |
| #[test] |
| fn test_add_then_remove_front() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(50, 10), Ok(())); |
| assert_eq!(assr.add_then_remove_front(10, 10), Ok(0)); |
| assert_eq!(assr, contigs![(10, 10), (30, 10)]); |
| } |
| |
| #[test] |
| fn test_add_then_remove_front_at_front() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(50, 10), Ok(())); |
| assert_eq!(assr.add_then_remove_front(0, 10), Ok(10)); |
| assert_eq!(assr, contigs![(40, 10)]); |
| } |
| |
| #[test] |
| fn test_add_then_remove_front_at_front_touch() { |
| let mut assr = Assembler::new(); |
| assert_eq!(assr.add(50, 10), Ok(())); |
| assert_eq!(assr.add_then_remove_front(0, 50), Ok(60)); |
| assert_eq!(assr, contigs![]); |
| } |
| |
| #[test] |
| fn test_add_then_remove_front_at_front_full() { |
| let mut assr = Assembler::new(); |
| for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT { |
| assert_eq!(assr.add(c * 10, 3), Ok(())); |
| } |
| // Maximum of allowed holes is reached |
| let assr_before = assr.clone(); |
| assert_eq!(assr.add_then_remove_front(1, 3), Err(TooManyHolesError)); |
| assert_eq!(assr_before, assr); |
| } |
| |
| #[test] |
| fn test_add_then_remove_front_at_front_full_offset_0() { |
| let mut assr = Assembler::new(); |
| for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT { |
| assert_eq!(assr.add(c * 10, 3), Ok(())); |
| } |
| assert_eq!(assr.add_then_remove_front(0, 3), Ok(3)); |
| } |
| |
| // Test against an obviously-correct but inefficient bitmap impl. |
| #[test] |
| fn test_random() { |
| use rand::Rng; |
| |
| const MAX_INDEX: usize = 256; |
| |
| for max_size in [2, 5, 10, 100] { |
| for _ in 0..300 { |
| //println!("==="); |
| let mut assr = Assembler::new(); |
| let mut map = [false; MAX_INDEX]; |
| |
| for _ in 0..60 { |
| let offset = rand::thread_rng().gen_range(0..MAX_INDEX - max_size - 1); |
| let size = rand::thread_rng().gen_range(1..=max_size); |
| |
| //println!("add {}..{} {}", offset, offset + size, size); |
| // Real impl |
| let res = assr.add(offset, size); |
| |
| // Bitmap impl |
| let mut map2 = map; |
| map2[offset..][..size].fill(true); |
| |
| let mut contigs = vec![]; |
| let mut hole: usize = 0; |
| let mut data: usize = 0; |
| for b in map2 { |
| if b { |
| data += 1; |
| } else { |
| if data != 0 { |
| contigs.push((hole, data)); |
| hole = 0; |
| data = 0; |
| } |
| hole += 1; |
| } |
| } |
| |
| // Compare. |
| let wanted_res = if contigs.len() > ASSEMBLER_MAX_SEGMENT_COUNT { |
| Err(TooManyHolesError) |
| } else { |
| Ok(()) |
| }; |
| assert_eq!(res, wanted_res); |
| if res.is_ok() { |
| map = map2; |
| assert_eq!(assr, Assembler::from(contigs)); |
| } |
| } |
| } |
| } |
| } |
| } |