1use std::sync::Arc;
18
19use crate::bam::bai::{BamIndex, MAX_MERGE_SPAN};
20use crate::bam::bgzf::Chunk;
21use crate::bam::header::SamHeader;
22use crate::bam::record::{decode_block, BamRecord, EntryFilter, RecordFilter};
23use crate::error::{Error, Result};
24use crate::genomic::{ChrMap, Locs};
25use crate::parallel::Executor;
26use crate::progress::{ProgressFn, ProgressTracker};
27use crate::source::ByteSource;
28
29const CHUNK_CACHE_SIZE: usize = 4;
32
33const CURSOR_CACHE_BYTES: usize = 8 << 20;
38
39const MIN_RUN_SIZE: u64 = 64 * 1024;
42
43#[derive(Debug)]
44struct Inner {
45 source: Arc<dyn ByteSource>,
46 executor: Executor,
47 index: Option<BamIndex>,
48}
49
50pub struct BamReader {
51 inner: Option<Inner>,
52 path: String,
53 index_path: String,
54 header: SamHeader,
55 chr_map: ChrMap,
56 chr_names: Arc<Vec<String>>,
59 index_error: String,
62 indexed: bool,
67}
68
69impl std::fmt::Debug for BamReader {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct("BamReader")
72 .field("path", &self.path)
73 .field("references", &self.chr_map.len())
74 .field("indexed", &self.is_indexed())
75 .field("closed", &self.is_closed())
76 .finish()
77 }
78}
79
80#[derive(Debug, Default)]
89pub struct Cursor {
90 cache: Vec<(Chunk, bytes::Bytes)>,
91 next: usize,
92 bytes: usize,
95}
96
97#[derive(Debug, Default)]
106pub struct Cursors(Vec<parking_lot::Mutex<Cursor>>);
107
108impl Cursors {
109 pub fn new(count: usize) -> Self {
110 Self(
111 (0..count.max(1))
112 .map(|_| parking_lot::Mutex::new(Cursor::default()))
113 .collect(),
114 )
115 }
116
117 fn get(&self, index: usize) -> parking_lot::MutexGuard<'_, Cursor> {
123 self.0[index % self.0.len()].lock()
124 }
125}
126
127impl BamReader {
128 pub fn open(
129 path: &str,
130 index_path: Option<&str>,
131 parallel: i64,
132 block_size: Option<u64>,
133 max_blocks: Option<usize>,
134 ) -> Result<Self> {
135 let source = crate::source::open(path, block_size, max_blocks)?;
136 Self::from_source(source, path, index_path, parallel, block_size, max_blocks)
137 }
138
139 pub(crate) fn from_source(
140 source: Arc<dyn ByteSource>,
141 path: &str,
142 index_path: Option<&str>,
143 parallel: i64,
144 block_size: Option<u64>,
145 max_blocks: Option<usize>,
146 ) -> Result<Self> {
147 super::bgzf::check_eof(source.as_ref())?;
150 let (header, chr_map) = super::header::read(source.as_ref())?;
151
152 let mut names = vec![String::new(); chr_map.iter().map(|e| e.index + 1).max().unwrap_or(0)];
153 for entry in chr_map.iter() {
154 names[entry.index] = entry.id.clone();
155 }
156
157 let index_path = index_path
158 .map(str::to_string)
159 .unwrap_or_else(|| format!("{path}.bai"));
160 let (index, index_error) =
165 if !crate::source::is_url(&index_path) && !std::path::Path::new(&index_path).exists() {
166 (None, String::new())
167 } else {
168 match crate::source::open(&index_path, block_size, max_blocks)
169 .and_then(|s| BamIndex::read(s.as_ref()))
170 {
171 Ok(index) => (Some(index), String::new()),
172 Err(e) => (None, e.to_string()),
173 }
174 };
175
176 Ok(Self {
177 indexed: index.is_some(),
178 inner: Some(Inner {
179 source,
180 executor: Executor::new(parallel)?,
181 index,
182 }),
183 path: path.to_string(),
184 index_path,
185 header,
186 chr_map,
187 chr_names: Arc::new(names),
188 index_error,
189 })
190 }
191
192 pub fn header(&self) -> &SamHeader {
193 &self.header
194 }
195 pub fn chr_sizes(&self) -> &ChrMap {
196 &self.chr_map
197 }
198 pub fn index_error(&self) -> &str {
199 &self.index_error
200 }
201 pub fn is_indexed(&self) -> bool {
202 self.indexed
203 }
204 pub fn is_closed(&self) -> bool {
205 self.inner.is_none()
206 }
207 pub fn path(&self) -> &str {
208 &self.path
209 }
210 pub fn parallel(&self) -> usize {
211 self.inner.as_ref().map_or(0, |i| i.executor.parallel())
212 }
213
214 pub fn close(&mut self) {
215 if let Some(inner) = self.inner.take() {
216 inner.source.close();
217 }
218 }
219
220 fn inner(&self) -> Result<&Inner> {
221 self.inner.as_ref().ok_or_else(|| Error::Closed {
222 path: self.path.clone(),
223 })
224 }
225
226 fn index<'a>(&self, inner: &'a Inner) -> Result<&'a BamIndex> {
231 inner.index.as_ref().ok_or_else(|| {
232 Error::invalid(if self.index_error.is_empty() {
233 format!("bam file is not indexed ({} not found)", self.index_path)
234 } else {
235 format!(
236 "bam index {} could not be read: {}",
237 self.index_path, self.index_error
238 )
239 })
240 })
241 }
242
243 pub fn read_entries(&self, req: &EntriesRequest) -> Result<Vec<Vec<BamRecord>>> {
248 let inner = self.inner()?;
249 let resolved = self.resolve(&req.locs)?;
250 let coverage = resolved.iter().map(|(_, s, e)| (e - s).max(0) as u64).sum();
251 let tracker = ProgressTracker::with_callback(coverage, req.progress.clone());
252 let out = self.read_loci(inner, &resolved, req, &tracker)?;
253 tracker.done_report();
254 Ok(out)
255 }
256
257 pub fn read_all_entries(&self, req: &EntriesRequest) -> Result<Vec<BamRecord>> {
263 let locs = Locs::whole_chromosomes(&self.chr_map, &req.locs.chr_ids)?;
264 let whole = EntriesRequest {
265 locs,
266 ..req.clone()
267 };
268 Ok(self.read_entries(&whole)?.into_iter().flatten().collect())
269 }
270
271 pub fn iter_entries(&self, req: &EntriesRequest) -> Result<LocusEntries<'_>> {
273 LocusEntries::plan(self, req)
274 }
275
276 pub fn iter_all_entries(&self, req: &EntriesRequest, window: i64) -> Result<WindowEntries<'_>> {
281 WindowEntries::plan(self, req, window)
282 }
283
284 fn resolve(&self, locs: &Locs) -> Result<Vec<(usize, i64, i64)>> {
286 (0..locs.len())
287 .map(|i| {
288 let entry = self.chr_map.resolve(&locs.chr_ids[i])?;
289 Ok((entry.index, locs.starts[i], locs.ends[i]))
290 })
291 .collect()
292 }
293
294 fn read_loci(
300 &self,
301 inner: &Inner,
302 loci: &[(usize, i64, i64)],
303 req: &EntriesRequest,
304 tracker: &ProgressTracker,
305 ) -> Result<Vec<Vec<BamRecord>>> {
306 if loci.is_empty() {
307 return Ok(Vec::new());
308 }
309 let index = self.index(inner)?;
310 let workers = inner.executor.parallel().min(loci.len()).max(1);
311 let per_worker = loci.len().div_ceil(workers);
312 let batches: Vec<(usize, usize)> = (0..workers)
313 .map(|w| (w * per_worker, ((w + 1) * per_worker).min(loci.len())))
314 .filter(|(from, to)| from < to)
315 .collect();
316
317 let lists = inner.executor.map_batches(&batches, |_, (from, to)| {
318 let mut cursor = Cursor::default();
319 let mut out = Vec::with_capacity(to - from);
320 for (chr, start, end) in &loci[*from..*to] {
321 let chunks = index.chunks(*chr, *start, *end, Some(MAX_MERGE_SPAN))?;
322 let mut records = Vec::new();
323 self.read_chunks(
324 inner,
325 &mut cursor,
326 &chunks,
327 0..chunks.len(),
328 (*chr, *start, *end),
329 req,
330 &mut records,
331 )?;
332 out.push(records);
333 tracker.add((end - start).max(0) as u64);
334 }
335 Ok(out)
336 })?;
337 Ok(lists.into_iter().flatten().collect())
338 }
339
340 fn read_locus_split(
346 &self,
347 inner: &Inner,
348 locus: (usize, i64, i64),
349 req: &EntriesRequest,
350 cursors: &Cursors,
351 ) -> Result<Vec<BamRecord>> {
352 let index = self.index(inner)?;
353 let (chr, start, end) = locus;
354 let chunks = index.chunks(chr, start, end, Some(MAX_MERGE_SPAN))?;
355 let runs = chunk_runs(&chunks, inner.executor.parallel());
356 if runs.is_empty() {
357 return Ok(Vec::new());
358 }
359 if runs.len() == 1 {
360 let mut cursor = cursors.get(0);
366 let mut out = Vec::new();
367 self.read_chunks(
368 inner,
369 &mut cursor,
370 &chunks,
371 runs[0].clone(),
372 locus,
373 req,
374 &mut out,
375 )?;
376 return Ok(out);
377 }
378 let lists = inner.executor.map_batches(&runs, |index, run| {
379 let mut cursor = cursors.get(index);
380 let mut out = Vec::new();
381 self.read_chunks(
382 inner,
383 &mut cursor,
384 &chunks,
385 run.clone(),
386 locus,
387 req,
388 &mut out,
389 )?;
390 Ok(out)
391 })?;
392 Ok(lists.into_iter().flatten().collect())
393 }
394
395 #[allow(clippy::too_many_arguments)]
402 fn read_chunks(
403 &self,
404 inner: &Inner,
405 cursor: &mut Cursor,
406 chunks: &[Chunk],
407 run: std::ops::Range<usize>,
408 locus: (usize, i64, i64),
409 req: &EntriesRequest,
410 out: &mut Vec<BamRecord>,
411 ) -> Result<()> {
412 let (chr, start, end) = locus;
413 let filter = EntryFilter {
414 chr_index: Some(chr as i32),
415 start,
416 end: Some(end),
417 standard_flags: req.filter.enabled,
418 };
419 for chunk in &chunks[run] {
420 let data = cursor.get_or_read(inner.source.as_ref(), *chunk, &self.path)?;
421 out.extend(decode_block(
422 &data,
423 req.parse_tags,
424 &filter,
425 &self.chr_names,
426 &self.path,
427 )?);
428 }
429 Ok(())
430 }
431}
432
433impl Cursor {
434 fn get_or_read(
440 &mut self,
441 source: &dyn ByteSource,
442 chunk: Chunk,
443 path: &str,
444 ) -> Result<bytes::Bytes> {
445 if let Some((_, data)) = self.cache.iter().find(|(c, _)| *c == chunk) {
446 return Ok(data.clone());
447 }
448 let data = super::bgzf::decompress_chunk(source, chunk, path)?;
449 if data.len() <= CURSOR_CACHE_BYTES {
455 if self.cache.len() < CHUNK_CACHE_SIZE {
456 self.bytes += data.len();
457 self.cache.push((chunk, data.clone()));
458 } else {
459 self.bytes -= self.cache[self.next].1.len();
460 self.bytes += data.len();
461 self.cache[self.next] = (chunk, data.clone());
462 self.next = (self.next + 1) % CHUNK_CACHE_SIZE;
463 }
464 while self.bytes > CURSOR_CACHE_BYTES && self.cache.len() > 1 {
465 let oldest = self.next % self.cache.len();
468 self.bytes -= self.cache[oldest].1.len();
469 self.cache.remove(oldest);
470 self.next = oldest.min(self.cache.len().saturating_sub(1));
471 }
472 }
473 Ok(data)
474 }
475}
476
477fn chunk_runs(chunks: &[Chunk], workers: usize) -> Vec<std::ops::Range<usize>> {
485 if chunks.is_empty() || workers < 1 {
486 return Vec::new();
487 }
488 let total: u64 = chunks.iter().map(|c| c.compressed_size()).sum();
489 let wanted = (workers as u64).min(total / MIN_RUN_SIZE).max(1);
490 let per_run = total.div_ceil(wanted).max(1);
492
493 #[allow(clippy::single_range_in_vec_init)]
494 let mut runs = vec![0usize..0];
495 let mut size = 0u64;
496 for (i, chunk) in chunks.iter().enumerate().take(chunks.len() - 1) {
497 size += chunk.compressed_size();
498 if size < per_run {
499 continue;
500 }
501 if runs.len() as u64 >= wanted {
504 continue;
505 }
506 runs.last_mut().expect("pushed one above").end = i + 1;
507 runs.push(i + 1..i + 1);
508 size = 0;
509 }
510 runs.last_mut().expect("pushed one above").end = chunks.len();
511 runs
512}
513
514#[derive(Clone)]
515pub struct EntriesRequest {
516 pub locs: Locs,
517 pub filter: RecordFilter,
518 pub parse_tags: bool,
521 pub sort_locations: bool,
525 pub progress: Option<ProgressFn>,
526}
527
528impl std::fmt::Debug for EntriesRequest {
529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
530 f.debug_struct("EntriesRequest")
531 .field("loci", &self.locs.len())
532 .field("filter", &self.filter.enabled)
533 .field("parse_tags", &self.parse_tags)
534 .field("sort_locations", &self.sort_locations)
535 .finish()
536 }
537}
538
539impl EntriesRequest {
540 pub fn new(locs: Locs) -> Self {
541 Self {
542 locs,
543 filter: RecordFilter::default(),
544 parse_tags: true,
545 sort_locations: false,
546 progress: None,
547 }
548 }
549 pub fn filter(mut self, enabled: bool) -> Self {
550 self.filter.enabled = enabled;
551 self
552 }
553 pub fn parse_tags(mut self, v: bool) -> Self {
554 self.parse_tags = v;
555 self
556 }
557 pub fn sort_locations(mut self, v: bool) -> Self {
558 self.sort_locations = v;
559 self
560 }
561 pub fn progress(mut self, f: ProgressFn) -> Self {
562 self.progress = Some(f);
563 self
564 }
565}
566
567struct WalkPlan {
579 loci: Vec<(usize, i64, i64)>,
581 order: Vec<usize>,
583 min_starts: Vec<Option<i64>>,
590 request: EntriesRequest,
591 coverage: u64,
593}
594
595pub struct LocusWalk {
596 plan: Arc<WalkPlan>,
597 next: usize,
598 tracker: Arc<ProgressTracker>,
599 cursors: Cursors,
603}
604
605impl std::fmt::Debug for LocusWalk {
606 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
607 f.debug_struct("LocusWalk")
608 .field("loci", &self.plan.loci.len())
609 .field("next", &self.next)
610 .finish()
611 }
612}
613
614impl LocusWalk {
615 pub fn plan(reader: &BamReader, req: &EntriesRequest) -> Result<Self> {
618 Self::plan_with(reader, req, false)
619 }
620
621 fn plan_with(reader: &BamReader, req: &EntriesRequest, from_locus_start: bool) -> Result<Self> {
625 let inner = reader.inner()?;
626 reader.index(inner)?;
627 let resolved = reader.resolve(&req.locs)?;
628 let mut order: Vec<usize> = (0..resolved.len()).collect();
629 if req.sort_locations {
630 order.sort_by_key(|i| resolved[*i]);
631 }
632 let loci: Vec<(usize, i64, i64)> = order.iter().map(|i| resolved[*i]).collect();
633 let coverage = loci.iter().map(|(_, s, e)| (e - s).max(0) as u64).sum();
635 let min_starts = if from_locus_start {
636 loci.iter().map(|(_, start, _)| Some(*start)).collect()
637 } else {
638 vec![None; loci.len()]
639 };
640 Ok(Self {
641 tracker: Arc::new(ProgressTracker::with_callback(
642 coverage,
643 req.progress.clone(),
644 )),
645 plan: Arc::new(WalkPlan {
646 min_starts,
647 loci,
648 order,
649 request: req.clone(),
650 coverage,
651 }),
652 next: 0,
653 cursors: Cursors::new(reader.parallel()),
654 })
655 }
656
657 pub fn restarted(&self) -> Self {
663 Self {
664 plan: self.plan.clone(),
665 next: 0,
666 tracker: Arc::new(ProgressTracker::with_callback(
667 self.plan.coverage,
668 self.plan.request.progress.clone(),
669 )),
670 cursors: Cursors::new(self.cursors.0.len()),
674 }
675 }
676
677 pub fn plan_windows(reader: &BamReader, req: &EntriesRequest, span: i64) -> Result<Self> {
679 if span < 1 {
680 return Err(Error::invalid(format!(
681 "span must be positive (got {span})"
682 )));
683 }
684 let locs = window_locs(&reader.chr_map, &req.locs.chr_ids, span)?;
685 let windowed = EntriesRequest {
686 locs,
687 sort_locations: false,
688 ..req.clone()
689 };
690 Self::plan_with(reader, &windowed, true)
691 }
692
693 pub fn len(&self) -> usize {
694 self.plan.loci.len()
695 }
696 pub fn is_empty(&self) -> bool {
697 self.plan.loci.is_empty()
698 }
699 pub fn order(&self) -> &[usize] {
702 &self.plan.order
703 }
704
705 pub fn next_window(&mut self, reader: &BamReader) -> Option<Result<Vec<BamRecord>>> {
706 if self.next >= self.plan.loci.len() {
707 self.tracker.done_report();
708 return None;
709 }
710 let index = self.next;
711 let locus = self.plan.loci[index];
712 let outcome = reader.inner().and_then(|inner| {
713 reader.read_locus_split(inner, locus, &self.plan.request, &self.cursors)
714 });
715 match outcome {
716 Err(e) => Some(Err(e)),
722 Ok(mut records) => {
723 self.next += 1;
724 if let Some(min_start) = self.plan.min_starts[index] {
725 records.retain(|r| r.start() >= min_start);
726 }
727 let (_, start, end) = locus;
728 self.tracker.add((end - start).max(0) as u64);
729 Some(Ok(records))
730 }
731 }
732 }
733}
734
735#[derive(Debug)]
737pub struct LocusEntries<'a> {
738 reader: &'a BamReader,
739 walk: LocusWalk,
740}
741
742impl<'a> LocusEntries<'a> {
743 fn plan(reader: &'a BamReader, req: &EntriesRequest) -> Result<Self> {
744 Ok(Self {
745 reader,
746 walk: LocusWalk::plan(reader, req)?,
747 })
748 }
749 pub fn len(&self) -> usize {
750 self.walk.len()
751 }
752 pub fn is_empty(&self) -> bool {
753 self.walk.is_empty()
754 }
755 pub fn order(&self) -> &[usize] {
756 self.walk.order()
757 }
758}
759
760impl Iterator for LocusEntries<'_> {
761 type Item = Result<Vec<BamRecord>>;
762 fn next(&mut self) -> Option<Self::Item> {
763 self.walk.next_window(self.reader)
764 }
765}
766
767#[derive(Debug)]
769pub struct WindowEntries<'a> {
770 reader: &'a BamReader,
771 walk: LocusWalk,
772}
773
774pub fn window_locs(map: &ChrMap, chr_ids: &[String], span: i64) -> Result<Locs> {
776 let mut ids = Vec::new();
777 let mut starts = Vec::new();
778 let mut ends = Vec::new();
779 for chr in map.select(chr_ids)? {
780 let mut start = 0;
781 while start < chr.size {
782 ids.push(chr.id.clone());
783 starts.push(start);
784 ends.push((start + span).min(chr.size));
785 start += span;
786 }
787 }
788 Locs::spans(&ids, &starts, &ends)
789}
790
791impl<'a> WindowEntries<'a> {
792 fn plan(reader: &'a BamReader, req: &EntriesRequest, span: i64) -> Result<Self> {
793 if span < 1 {
794 return Err(Error::invalid(format!(
795 "span must be positive (got {span})"
796 )));
797 }
798 let locs = window_locs(&reader.chr_map, &req.locs.chr_ids, span)?;
799 let windowed = EntriesRequest {
800 locs,
801 sort_locations: false,
802 ..req.clone()
803 };
804 let walk = LocusWalk::plan_with(reader, &windowed, true)?;
805 Ok(Self { reader, walk })
806 }
807 pub fn len(&self) -> usize {
808 self.walk.len()
809 }
810 pub fn is_empty(&self) -> bool {
811 self.walk.is_empty()
812 }
813}
814
815impl Iterator for WindowEntries<'_> {
816 type Item = Result<Vec<BamRecord>>;
817 fn next(&mut self) -> Option<Self::Item> {
818 self.walk.next_window(self.reader)
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825 use crate::bam::bgzf::VirtualOffset;
826
827 fn chunk(a: u64, b: u64) -> Chunk {
828 Chunk {
829 begin: VirtualOffset::new(a, 0),
830 end: VirtualOffset::new(b, 0),
831 }
832 }
833
834 #[test]
835 fn no_chunks_means_no_runs() {
836 assert!(chunk_runs(&[], 4).is_empty());
837 assert!(chunk_runs(&[chunk(0, 100)], 0).is_empty());
838 }
839
840 #[test]
841 fn chunks_holding_too_little_are_read_as_one_run() {
842 let chunks = [chunk(0, 100), chunk(100, 200), chunk(200, 300)];
844 #[allow(clippy::single_range_in_vec_init)]
845 let one_run = [0..3];
846 assert_eq!(chunk_runs(&chunks, 8), one_run);
847 }
848
849 #[test]
850 fn a_big_locus_splits_into_at_most_one_run_per_worker() {
851 let big = MIN_RUN_SIZE * 4;
852 let chunks: Vec<Chunk> = (0..8).map(|i| chunk(i * big, (i + 1) * big)).collect();
853 let runs = chunk_runs(&chunks, 4);
854 assert_eq!(runs.len(), 4);
855 assert_eq!(runs[0].start, 0);
857 assert_eq!(runs.last().unwrap().end, chunks.len());
858 for pair in runs.windows(2) {
859 assert_eq!(pair[0].end, pair[1].start);
860 }
861 }
862
863 #[test]
864 fn a_run_is_never_empty_and_a_chunk_is_never_split() {
865 let big = MIN_RUN_SIZE * 100;
866 let chunks = [chunk(0, big), chunk(big, big * 2)];
867 let runs = chunk_runs(&chunks, 8);
869 assert!(runs.len() <= chunks.len());
870 assert!(runs.iter().all(|r| r.start < r.end));
871 }
872}
873
874#[cfg(test)]
875mod cursor_tests {
876 use super::*;
877 use crate::bam::bgzf::VirtualOffset;
878 use crate::source::testing::MemorySource;
879 use std::sync::atomic::{AtomicUsize, Ordering};
880
881 #[derive(Debug)]
884 struct CountingSource {
885 inner: MemorySource,
886 reads: AtomicUsize,
887 }
888
889 impl ByteSource for CountingSource {
890 fn path(&self) -> &str {
891 self.inner.path()
892 }
893 fn len(&self) -> Result<u64> {
894 self.inner.len()
895 }
896 fn read_at(&self, offset: u64, len: usize) -> Result<bytes::Bytes> {
897 self.reads.fetch_add(1, Ordering::SeqCst);
898 self.inner.read_at(offset, len)
899 }
900 }
901
902 fn bgzf_block(payload: &[u8]) -> Vec<u8> {
904 use std::io::Write as _;
905 let mut encoder =
906 flate2::write::DeflateEncoder::new(Vec::new(), flate2::Compression::new(6));
907 encoder.write_all(payload).expect("deflate to a Vec");
908 let deflated = encoder.finish().expect("deflate to a Vec");
909 let total = 18 + deflated.len() + 8;
910 let mut out = Vec::with_capacity(total);
911 out.extend_from_slice(&[0x1f, 0x8b, 8, 4, 0, 0, 0, 0, 0, 0xff]);
912 out.extend_from_slice(&6u16.to_le_bytes());
913 out.extend_from_slice(b"BC");
914 out.extend_from_slice(&2u16.to_le_bytes());
915 out.extend_from_slice(&((total - 1) as u16).to_le_bytes());
916 out.extend_from_slice(&deflated);
917 let mut crc = flate2::Crc::new();
918 crc.update(payload);
919 out.extend_from_slice(&crc.sum().to_le_bytes());
920 out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
921 out
922 }
923
924 fn blocks(count: usize, size: usize) -> (CountingSource, Vec<u64>) {
926 let mut bytes = Vec::new();
927 let mut offsets = Vec::new();
928 for i in 0..count {
929 offsets.push(bytes.len() as u64);
930 bytes.extend_from_slice(&bgzf_block(&vec![(i % 251) as u8; size]));
931 }
932 offsets.push(bytes.len() as u64);
933 (
934 CountingSource {
935 inner: MemorySource::new(bytes),
936 reads: AtomicUsize::new(0),
937 },
938 offsets,
939 )
940 }
941
942 fn chunk(from: u64, to: u64) -> Chunk {
943 Chunk {
944 begin: VirtualOffset::new(from, 0),
945 end: VirtualOffset::new(to, 0),
946 }
947 }
948
949 #[test]
955 fn a_chunk_a_cursor_has_already_read_is_not_read_again() {
956 let (source, offsets) = blocks(4, 4096);
957 let mut cursor = Cursor::default();
958 let first = chunk(offsets[0], offsets[1]);
959
960 let a = cursor.get_or_read(&source, first, "x.bam").unwrap();
961 assert_eq!(source.reads.load(Ordering::SeqCst), 1);
962 let b = cursor.get_or_read(&source, first, "x.bam").unwrap();
963 assert_eq!(a, b);
964 assert_eq!(
965 source.reads.load(Ordering::SeqCst),
966 1,
967 "the second read of one chunk reached the file"
968 );
969
970 let second = chunk(offsets[1], offsets[2]);
972 cursor.get_or_read(&source, second, "x.bam").unwrap();
973 let before = source.reads.load(Ordering::SeqCst);
974 cursor.get_or_read(&source, first, "x.bam").unwrap();
975 assert_eq!(source.reads.load(Ordering::SeqCst), before);
976
977 let mut fresh = Cursor::default();
980 fresh.get_or_read(&source, first, "x.bam").unwrap();
981 assert!(source.reads.load(Ordering::SeqCst) > before);
982 }
983
984 #[test]
987 fn a_cursor_stays_under_its_byte_budget() {
988 let each = CURSOR_CACHE_BYTES / 2 + 1;
990 let (source, offsets) = blocks(4, each);
991 let mut cursor = Cursor::default();
992 for i in 0..4 {
993 cursor
994 .get_or_read(&source, chunk(offsets[i], offsets[i + 1]), "x.bam")
995 .unwrap();
996 let held: usize = cursor.cache.iter().map(|(_, d)| d.len()).sum();
997 assert_eq!(held, cursor.bytes, "the running total drifted");
998 assert!(
999 cursor.bytes <= CURSOR_CACHE_BYTES || cursor.cache.len() == 1,
1000 "after {} chunks the cursor holds {} bytes",
1001 i + 1,
1002 cursor.bytes
1003 );
1004 }
1005 let (big_source, big_offsets) = blocks(1, CURSOR_CACHE_BYTES * 2);
1007 let mut cursor = Cursor::default();
1008 let data = cursor
1009 .get_or_read(&big_source, chunk(big_offsets[0], big_offsets[1]), "x.bam")
1010 .unwrap();
1011 assert_eq!(data.len(), CURSOR_CACHE_BYTES * 2);
1012 assert!(cursor.cache.is_empty(), "an oversized chunk was cached");
1013 }
1014}