Skip to main content

extended_htslib/bam/
mod.rs

1// Copyright 2014 Christopher Schröder, Johannes Köster.
2// Licensed under the MIT license (http://opensource.org/licenses/MIT)
3// This file may not be copied, modified, or distributed
4// except according to those terms.
5
6//! Module for working with SAM, BAM, and CRAM files.
7
8pub mod buffer;
9pub mod ext;
10pub mod header;
11pub mod index;
12pub mod pileup;
13pub mod record;
14#[cfg(feature = "serde_feature")]
15pub mod record_serde;
16
17use std::ffi;
18use std::os::raw::c_char;
19use std::path::Path;
20use std::rc::Rc;
21use std::slice;
22use std::str;
23use std::sync::Arc;
24
25use url::Url;
26
27use crate::errors::{Error, Result};
28use crate::htslib;
29use crate::tpool::ThreadPool;
30use crate::utils::path_as_bytes;
31
32pub use crate::bam::buffer::RecordBuffer;
33pub use crate::bam::header::Header;
34pub use crate::bam::record::Record;
35use hts_sys::{hts_fmt_option, sam_fields};
36use std::convert::{TryFrom, TryInto};
37use std::mem::MaybeUninit;
38
39/// # Safety
40///
41/// Implementation for `Read::set_threads` and `Writer::set_threads`.
42unsafe fn set_threads(htsfile: *mut htslib::htsFile, n_threads: usize) -> Result<()> {
43    assert!(n_threads != 0, "n_threads must be > 0");
44
45    if htslib::hts_set_threads(htsfile, n_threads as i32) != 0 {
46        Err(Error::SetThreads)
47    } else {
48        Ok(())
49    }
50}
51
52unsafe fn set_thread_pool(htsfile: *mut htslib::htsFile, tpool: &ThreadPool) -> Result<()> {
53    let mut b = tpool.handle.borrow_mut();
54
55    if htslib::hts_set_thread_pool(htsfile, &mut b.inner as *mut _) != 0 {
56        Err(Error::ThreadPool)
57    } else {
58        Ok(())
59    }
60}
61
62/// # Safety
63///
64/// Set the reference FAI index path in a `htslib::htsFile` struct for reading CRAM format.
65pub unsafe fn set_fai_filename<P: AsRef<Path>>(
66    htsfile: *mut htslib::htsFile,
67    fasta_path: P,
68) -> Result<()> {
69    let path = if let Some(ext) = fasta_path.as_ref().extension() {
70        fasta_path
71            .as_ref()
72            .with_extension(format!("{}.fai", ext.to_str().unwrap()))
73    } else {
74        fasta_path.as_ref().with_extension(".fai")
75    };
76    let p: &Path = path.as_ref();
77    let c_str = ffi::CString::new(p.to_str().unwrap().as_bytes()).unwrap();
78    if htslib::hts_set_fai_filename(htsfile, c_str.as_ptr()) == 0 {
79        Ok(())
80    } else {
81        Err(Error::BamInvalidReferencePath { path: p.to_owned() })
82    }
83}
84
85/// A trait for a BAM reader with a read method.
86pub trait Read: Sized {
87    /// Read next BAM record into given record.
88    /// Use this method in combination with a single allocated record to avoid the reallocations
89    /// occurring with the iterator.
90    ///
91    /// # Arguments
92    ///
93    /// * `record` - the record to be filled
94    ///
95    /// # Returns
96    ///
97    /// Some(Ok(())) if the record was read and None if no more records to read
98    ///
99    /// Example:
100    /// ```
101    /// use extended_htslib::errors::Error;
102    /// use extended_htslib::bam::{Read, IndexedReader, Record};
103    ///
104    /// let mut bam = IndexedReader::from_path(&"test/test.bam").unwrap();
105    /// bam.fetch((0, 1000, 2000)); // reads on tid 0, from 1000bp to 2000bp
106    /// let mut record = Record::new();
107    /// while let Some(result) = bam.read(&mut record) {
108    ///     match result {
109    ///         Ok(_) => {
110    ///             println!("Read sequence: {:?}", record.seq().as_bytes());
111    ///         }
112    ///         Err(_) => panic!("BAM parsing failed...")
113    ///     }
114    /// }
115    /// ```
116    ///
117    /// Consider using [`rc_records`](#tymethod.rc_records) instead.
118    fn read(&mut self, record: &mut record::Record) -> Option<Result<()>>;
119
120    /// Iterator over the records of the seeked region.
121    /// Note that, while being convenient, this is less efficient than pre-allocating a
122    /// `Record` and reading into it with the `read` method, since every iteration involves
123    /// the allocation of a new `Record`.
124    ///
125    /// This is about 10% slower than record in micro benchmarks.
126    ///
127    /// Consider using [`rc_records`](#tymethod.rc_records) instead.
128    fn records(&mut self) -> Records<'_, Self>;
129
130    /// Records iterator using an Rc to avoid allocating a Record each turn.
131    /// This is about 1% slower than the [`read`](#tymethod.read) based API in micro benchmarks,
132    /// but has nicer ergonomics (and might not actually be slower in your applications).
133    ///
134    /// Example:
135    /// ```
136    /// use extended_htslib::errors::Error;
137    /// use extended_htslib::bam::{Read, Reader, Record};
138    /// use extended_htslib::htslib; // for BAM_F*
139    /// let mut bam = Reader::from_path(&"test/test.bam").unwrap();
140    ///
141    /// for read in
142    ///     bam.rc_records()
143    ///     .map(|x| x.expect("Failure parsing Bam file"))
144    ///     .filter(|read|
145    ///         read.flags()
146    ///          & (htslib::BAM_FUNMAP
147    ///              | htslib::BAM_FSECONDARY
148    ///              | htslib::BAM_FQCFAIL
149    ///              | htslib::BAM_FDUP) as u16
150    ///          == 0
151    ///     )
152    ///     .filter(|read| !read.is_reverse()) {
153    ///     println!("Found a forward read: {:?}", read.qname());
154    /// }
155    ///
156    /// //or to add the read qnames into a Vec
157    /// let collected: Vec<_> = bam.rc_records().map(|read| read.unwrap().qname().to_vec()).collect();
158    ///
159    ///
160    /// ```
161    fn rc_records(&mut self) -> RcRecords<'_, Self>;
162
163    /// Iterator over pileups.
164    fn pileup(&mut self) -> pileup::Pileups<'_, Self>;
165
166    /// Return the htsFile struct
167    fn htsfile(&self) -> *mut htslib::htsFile;
168
169    /// Return the header.
170    fn header(&self) -> &HeaderView;
171
172    /// Seek to the given virtual offset in the file
173    fn seek(&mut self, offset: i64) -> Result<()> {
174        let htsfile = unsafe { self.htsfile().as_ref() }.expect("bug: null pointer to htsFile");
175        let ret = match htsfile.format.format {
176            htslib::htsExactFormat_cram => unsafe {
177                i64::from(htslib::cram_seek(
178                    htsfile.fp.cram,
179                    offset as libc::off_t,
180                    libc::SEEK_SET,
181                ))
182            },
183            _ => unsafe { htslib::bgzf_seek(htsfile.fp.bgzf, offset, libc::SEEK_SET) },
184        };
185
186        if ret == 0 {
187            Ok(())
188        } else {
189            Err(Error::FileSeek)
190        }
191    }
192
193    /// Report the current virtual offset
194    fn tell(&self) -> i64 {
195        // this reimplements the bgzf_tell macro
196        let htsfile = unsafe { self.htsfile().as_ref() }.expect("bug: null pointer to htsFile");
197        let bgzf = unsafe { *htsfile.fp.bgzf };
198        (bgzf.block_address << 16) | (i64::from(bgzf.block_offset) & 0xFFFF)
199    }
200
201    /// Activate multi-threaded BAM read support in htslib. This should permit faster
202    /// reading of large BAM files.
203    ///
204    /// Setting `nthreads` to `0` does not change the current state.  Note that it is not
205    /// possible to set the number of background threads below `1` once it has been set.
206    ///
207    /// # Arguments
208    ///
209    /// * `n_threads` - number of extra background writer threads to use, must be `> 0`.
210    fn set_threads(&mut self, n_threads: usize) -> Result<()> {
211        unsafe { set_threads(self.htsfile(), n_threads) }
212    }
213
214    /// Use a shared thread-pool for writing. This permits controlling the total
215    /// thread count when multiple readers and writers are working simultaneously.
216    /// A thread pool can be created with `crate::tpool::ThreadPool::new(n_threads)`
217    ///
218    /// # Arguments
219    ///
220    /// * `tpool` - thread pool to use for compression work.
221    fn set_thread_pool(&mut self, tpool: &ThreadPool) -> Result<()>;
222
223    /// If the underlying file is in CRAM format, allows modifying CRAM options.
224    /// Note that this method does *not* check that the underlying file actually is in CRAM format.
225    ///
226    /// # Examples
227    ///
228    /// Set the required fields to RNAME and FLAG,
229    /// potentially allowing htslib to skip over the rest,
230    /// resulting in faster iteration:
231    /// ```
232    /// use extended_htslib::bam::{Read, Reader};
233    /// use hts_sys;
234    /// let mut cram = Reader::from_path(&"test/test_cram.cram").unwrap();
235    /// cram.set_cram_options(hts_sys::hts_fmt_option_CRAM_OPT_REQUIRED_FIELDS,
236    ///             hts_sys::sam_fields_SAM_RNAME | hts_sys::sam_fields_SAM_FLAG).unwrap();
237    /// ```
238    fn set_cram_options(&mut self, fmt_opt: hts_fmt_option, fields: sam_fields) -> Result<()> {
239        unsafe {
240            if hts_sys::hts_set_opt(self.htsfile(), fmt_opt, fields) != 0 {
241                Err(Error::HtsSetOpt)
242            } else {
243                Ok(())
244            }
245        }
246    }
247}
248
249/// A BAM reader.
250#[derive(Debug)]
251pub struct Reader {
252    htsfile: *mut htslib::htsFile,
253    header: Arc<HeaderView>,
254    tpool: Option<ThreadPool>,
255}
256
257unsafe impl Send for Reader {}
258
259impl Reader {
260    /// Create a new Reader from path.
261    ///
262    /// # Arguments
263    ///
264    /// * `path` - the path to open.
265    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
266        Self::new(&path_as_bytes(path, true)?)
267    }
268
269    /// Create a new Reader from STDIN.
270    pub fn from_stdin() -> Result<Self> {
271        Self::new(b"-")
272    }
273
274    /// Create a new Reader from URL.
275    pub fn from_url(url: &Url) -> Result<Self> {
276        Self::new(url.as_str().as_bytes())
277    }
278
279    /// Create a new Reader.
280    ///
281    /// # Arguments
282    ///
283    /// * `path` - the path to open. Use "-" for stdin.
284    fn new(path: &[u8]) -> Result<Self> {
285        let htsfile = hts_open(path, b"r")?;
286
287        let header = unsafe { htslib::sam_hdr_read(htsfile) };
288        if header.is_null() {
289            return Err(Error::BamOpen {
290                target: String::from_utf8_lossy(path).to_string(),
291            });
292        }
293
294        // Invalidate the `text` representation of the header
295        unsafe {
296            let _ = htslib::sam_hdr_line_name(header, b"SQ".as_ptr().cast::<c_char>(), 0);
297        }
298
299        Ok(Reader {
300            htsfile,
301            header: Arc::new(HeaderView::new(header)),
302            tpool: None,
303        })
304    }
305
306    extern "C" fn pileup_read(
307        data: *mut ::std::os::raw::c_void,
308        record: *mut htslib::bam1_t,
309    ) -> i32 {
310        let mut _self = unsafe { (data as *mut Self).as_mut().unwrap() };
311        unsafe {
312            htslib::sam_read1(
313                _self.htsfile(),
314                _self.header().inner_ptr() as *mut hts_sys::sam_hdr_t,
315                record,
316            )
317        }
318    }
319
320    /// Iterator over the records between the (optional) virtual offsets `start` and `end`
321    ///
322    /// # Arguments
323    ///
324    /// * `start` - Optional starting virtual offset to seek to. Throws an error if it is not
325    /// a valid virtual offset.
326    ///
327    /// * `end` - Read until the virtual offset is less than `end`
328    pub fn iter_chunk(&mut self, start: Option<i64>, end: Option<i64>) -> ChunkIterator<'_, Self> {
329        if let Some(pos) = start {
330            self.seek(pos)
331                .expect("Failed to seek to the starting position");
332        };
333
334        ChunkIterator { reader: self, end }
335    }
336
337    /// Set the reference path for reading CRAM files.
338    ///
339    /// # Arguments
340    ///
341    /// * `path` - path to the FASTA reference
342    pub fn set_reference<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
343        unsafe { set_fai_filename(self.htsfile, path) }
344    }
345}
346
347impl Read for Reader {
348    /// Read the next BAM record into the given `Record`.
349    /// Returns `None` if there are no more records.
350    ///
351    /// This method is useful if you want to read records as fast as possible as the
352    /// `Record` can be reused. A more ergonomic approach is to use the [records](Reader::records)
353    /// iterator.
354    ///
355    /// # Errors
356    /// If there are any issues with reading the next record an error will be returned.
357    ///
358    /// # Examples
359    ///
360    /// ```
361    /// use extended_htslib::errors::Error;
362    /// use extended_htslib::bam::{Read, Reader, Record};
363    ///
364    /// let mut bam = Reader::from_path(&"test/test.bam")?;
365    /// let mut record = Record::new();
366    ///
367    /// // Print the TID of each record
368    /// while let Some(r) = bam.read(&mut record) {
369    ///    r.expect("Failed to parse record");
370    ///    println!("TID: {}", record.tid())
371    /// }
372    /// # Ok::<(), Error>(())
373    /// ```
374    fn read(&mut self, record: &mut record::Record) -> Option<Result<()>> {
375        match unsafe {
376            htslib::sam_read1(
377                self.htsfile,
378                self.header().inner_ptr() as *mut hts_sys::sam_hdr_t,
379                record.inner_ptr_mut(),
380            )
381        } {
382            -1 => None,
383            -2 => Some(Err(Error::BamTruncatedRecord)),
384            -4 => Some(Err(Error::BamInvalidRecord)),
385            _ => {
386                record.set_header(Arc::clone(&self.header));
387
388                Some(Ok(()))
389            }
390        }
391    }
392
393    /// Iterator over the records of the fetched region.
394    /// Note that, while being convenient, this is less efficient than pre-allocating a
395    /// `Record` and reading into it with the `read` method, since every iteration involves
396    /// the allocation of a new `Record`.
397    fn records(&mut self) -> Records<'_, Self> {
398        Records { reader: self }
399    }
400
401    fn rc_records(&mut self) -> RcRecords<'_, Self> {
402        RcRecords {
403            reader: self,
404            record: Rc::new(record::Record::new()),
405        }
406    }
407
408    fn pileup(&mut self) -> pileup::Pileups<'_, Self> {
409        let _self = self as *const Self;
410        let itr = unsafe {
411            htslib::bam_plp_init(
412                Some(Reader::pileup_read),
413                _self as *mut ::std::os::raw::c_void,
414            )
415        };
416        pileup::Pileups::new(self, itr)
417    }
418
419    fn htsfile(&self) -> *mut htslib::htsFile {
420        self.htsfile
421    }
422
423    fn header(&self) -> &HeaderView {
424        &self.header
425    }
426
427    fn set_thread_pool(&mut self, tpool: &ThreadPool) -> Result<()> {
428        unsafe { set_thread_pool(self.htsfile(), tpool)? }
429        self.tpool = Some(tpool.clone());
430        Ok(())
431    }
432}
433
434impl Drop for Reader {
435    fn drop(&mut self) {
436        unsafe {
437            htslib::hts_close(self.htsfile);
438        }
439    }
440}
441
442/// Conversion type for start/stop coordinates
443/// only public because it's leaked by the conversions
444#[doc(hidden)]
445pub struct FetchCoordinate(i64);
446
447//the old sam spec
448impl From<i32> for FetchCoordinate {
449    fn from(coord: i32) -> FetchCoordinate {
450        FetchCoordinate(coord as i64)
451    }
452}
453
454// to support un-annotated literals (type interference fails on those)
455impl From<u32> for FetchCoordinate {
456    fn from(coord: u32) -> FetchCoordinate {
457        FetchCoordinate(coord as i64)
458    }
459}
460
461//the new sam spec
462impl From<i64> for FetchCoordinate {
463    fn from(coord: i64) -> FetchCoordinate {
464        FetchCoordinate(coord)
465    }
466}
467
468//what some of our header methods return
469impl From<u64> for FetchCoordinate {
470    fn from(coord: u64) -> FetchCoordinate {
471        FetchCoordinate(coord.try_into().expect("Coordinate exceeded 2^^63-1"))
472    }
473}
474
475/// Enum for [IndexdReader.fetch()](struct.IndexedReader.html#method.fetch) arguments.
476///
477/// tids may be converted From<>:
478/// * i32 (correct as per spec)
479/// * u32 (because of header.tid. Will panic if above 2^31-1).
480///
481///Coordinates may be (via FetchCoordinate)
482/// * i32 (as of the sam v1 spec)
483/// * i64 (as of the htslib 'large coordinate' extension (even though they are not supported in BAM)
484/// * u32 (because that's what rust literals will default to)
485/// * u64 (because of header.target_len(). Will panic if above 2^^63-1).
486#[derive(Debug, Clone)]
487pub enum FetchDefinition<'a> {
488    /// tid, start, stop,
489    Region(i32, i64, i64),
490    /// 'named-reference', start, stop tuple.
491    RegionString(&'a [u8], i64, i64),
492    ///complete reference. May be i32 or u32 (which panics if above 2^31-')
493    CompleteTid(i32),
494    ///complete reference by name (&[u8] or &str)
495    String(&'a [u8]),
496    /// Every read
497    All,
498    /// Only reads with the BAM flag BAM_FUNMAP (which might not be all reads with reference = -1)
499    Unmapped,
500}
501
502impl<'a, X: Into<FetchCoordinate>, Y: Into<FetchCoordinate>> From<(i32, X, Y)>
503    for FetchDefinition<'a>
504{
505    fn from(tup: (i32, X, Y)) -> FetchDefinition<'a> {
506        let start: FetchCoordinate = tup.1.into();
507        let stop: FetchCoordinate = tup.2.into();
508        FetchDefinition::Region(tup.0, start.0, stop.0)
509    }
510}
511
512impl<'a, X: Into<FetchCoordinate>, Y: Into<FetchCoordinate>> From<(u32, X, Y)>
513    for FetchDefinition<'a>
514{
515    fn from(tup: (u32, X, Y)) -> FetchDefinition<'a> {
516        let start: FetchCoordinate = tup.1.into();
517        let stop: FetchCoordinate = tup.2.into();
518        FetchDefinition::Region(
519            tup.0.try_into().expect("Tid exceeded 2^31-1"),
520            start.0,
521            stop.0,
522        )
523    }
524}
525
526//non tuple impls
527impl<'a> From<i32> for FetchDefinition<'a> {
528    fn from(tid: i32) -> FetchDefinition<'a> {
529        FetchDefinition::CompleteTid(tid)
530    }
531}
532
533impl<'a> From<u32> for FetchDefinition<'a> {
534    fn from(tid: u32) -> FetchDefinition<'a> {
535        let tid: i32 = tid.try_into().expect("tid exceeded 2^31-1");
536        FetchDefinition::CompleteTid(tid)
537    }
538}
539
540impl<'a> From<&'a str> for FetchDefinition<'a> {
541    fn from(s: &'a str) -> FetchDefinition<'a> {
542        FetchDefinition::String(s.as_bytes())
543    }
544}
545
546//also accept &[u8;n] literals
547impl<'a> From<&'a [u8]> for FetchDefinition<'a> {
548    fn from(s: &'a [u8]) -> FetchDefinition<'a> {
549        FetchDefinition::String(s)
550    }
551}
552
553//also accept &[u8;n] literals
554impl<'a, T: AsRef<[u8]>> From<&'a T> for FetchDefinition<'a> {
555    fn from(s: &'a T) -> FetchDefinition<'a> {
556        FetchDefinition::String(s.as_ref())
557    }
558}
559
560impl<'a, X: Into<FetchCoordinate>, Y: Into<FetchCoordinate>> From<(&'a str, X, Y)>
561    for FetchDefinition<'a>
562{
563    fn from(tup: (&'a str, X, Y)) -> FetchDefinition<'a> {
564        let start: FetchCoordinate = tup.1.into();
565        let stop: FetchCoordinate = tup.2.into();
566        FetchDefinition::RegionString(tup.0.as_bytes(), start.0, stop.0)
567    }
568}
569
570impl<'a, X: Into<FetchCoordinate>, Y: Into<FetchCoordinate>> From<(&'a [u8], X, Y)>
571    for FetchDefinition<'a>
572{
573    fn from(tup: (&'a [u8], X, Y)) -> FetchDefinition<'a> {
574        let start: FetchCoordinate = tup.1.into();
575        let stop: FetchCoordinate = tup.2.into();
576        FetchDefinition::RegionString(tup.0, start.0, stop.0)
577    }
578}
579
580//also accept &[u8;n] literals
581impl<'a, T: AsRef<[u8]>, X: Into<FetchCoordinate>, Y: Into<FetchCoordinate>> From<(&'a T, X, Y)>
582    for FetchDefinition<'a>
583{
584    fn from(tup: (&'a T, X, Y)) -> FetchDefinition<'a> {
585        let start: FetchCoordinate = tup.1.into();
586        let stop: FetchCoordinate = tup.2.into();
587        FetchDefinition::RegionString(tup.0.as_ref(), start.0, stop.0)
588    }
589}
590
591#[derive(Debug)]
592pub struct IndexedReader {
593    htsfile: *mut htslib::htsFile,
594    header: Arc<HeaderView>,
595    idx: Option<IndexView>,
596    itr: Option<*mut htslib::hts_itr_t>,
597    tpool: Option<ThreadPool>,
598}
599
600unsafe impl Send for IndexedReader {}
601
602impl IndexedReader {
603    /// Create a new Reader from path.
604    ///
605    /// # Arguments
606    ///
607    /// * `path` - the path to open.
608    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
609        Self::new(&path_as_bytes(path, true)?)
610    }
611
612    pub fn from_path_and_index<P: AsRef<Path>>(path: P, index_path: P) -> Result<Self> {
613        Self::new_with_index_path(
614            &path_as_bytes(path, true)?,
615            &path_as_bytes(index_path, true)?,
616        )
617    }
618
619    pub fn from_url(url: &Url) -> Result<Self> {
620        Self::new(url.as_str().as_bytes())
621    }
622
623    /// Create a new Reader.
624    ///
625    /// # Arguments
626    ///
627    /// * `path` - the path. Use "-" for stdin.
628    fn new(path: &[u8]) -> Result<Self> {
629        let htsfile = hts_open(path, b"r")?;
630        let header = unsafe { htslib::sam_hdr_read(htsfile) };
631        let c_str = ffi::CString::new(path).unwrap();
632        let idx = unsafe { htslib::sam_index_load(htsfile, c_str.as_ptr()) };
633        if idx.is_null() {
634            Err(Error::BamInvalidIndex {
635                target: str::from_utf8(path).unwrap().to_owned(),
636            })
637        } else {
638            Ok(IndexedReader {
639                htsfile,
640                header: Arc::new(HeaderView::new(header)),
641                idx: Some(IndexView::new(idx)),
642                itr: None,
643                tpool: None,
644            })
645        }
646    }
647    /// Create a new Reader.
648    ///
649    /// # Arguments
650    ///
651    /// * `path` - the path. Use "-" for stdin.
652    /// * `index_path` - the index path to use
653    fn new_with_index_path(path: &[u8], index_path: &[u8]) -> Result<Self> {
654        let htsfile = hts_open(path, b"r")?;
655        let header = unsafe { htslib::sam_hdr_read(htsfile) };
656        let c_str_path = ffi::CString::new(path).unwrap();
657        let c_str_index_path = ffi::CString::new(index_path).unwrap();
658        let idx = unsafe {
659            htslib::sam_index_load2(htsfile, c_str_path.as_ptr(), c_str_index_path.as_ptr())
660        };
661        if idx.is_null() {
662            Err(Error::BamInvalidIndex {
663                target: str::from_utf8(path).unwrap().to_owned(),
664            })
665        } else {
666            Ok(IndexedReader {
667                htsfile,
668                header: Arc::new(HeaderView::new(header)),
669                idx: Some(IndexView::new(idx)),
670                itr: None,
671                tpool: None,
672            })
673        }
674    }
675
676    /// Define the region from which .read() or .records will retrieve reads.
677    ///
678    /// Both iterating (with [.records()](trait.Read.html#tymethod.records)) and looping without allocation (with [.read()](trait.Read.html#tymethod.read) are a two stage process:
679    /// 1. 'fetch' the region of interest
680    /// 2. iter/loop through the reads.
681    ///
682    /// Example:
683    /// ```
684    /// use extended_htslib::bam::{IndexedReader, Read};
685    /// let mut bam = IndexedReader::from_path(&"test/test.bam").unwrap();
686    /// bam.fetch(("chrX", 10000, 20000)); // coordinates 10000..20000 on reference named "chrX"
687    /// for read in bam.records() {
688    ///     println!("read name: {:?}", read.unwrap().qname());
689    /// }
690    /// ```
691    ///
692    /// The arguments may be anything that can be converted into a FetchDefinition
693    /// such as
694    ///
695    /// * fetch(tid: u32) -> fetch everything on this reference
696    /// * fetch(reference_name: &[u8] | &str) -> fetch everything on this reference
697    /// * fetch((tid: i32, start: i64, stop: i64)): -> fetch in this region on this tid
698    /// * fetch((reference_name: &[u8] | &str, start: i64, stop: i64) -> fetch in this region on this tid
699    /// * fetch(FetchDefinition::All) or fetch(".") -> Fetch overything
700    /// * fetch(FetchDefinition::Unmapped) or fetch("*") -> Fetch unmapped (as signified by the 'unmapped' flag in the BAM - might be unreliable with some aligners.
701    ///
702    /// The start / stop coordinates will take i64 (the correct type as of htslib's 'large
703    /// coordinates' expansion), i32, u32, and u64 (with a possible panic! if the coordinate
704    /// won't fit an i64).
705    ///
706    /// `start` and `stop` are zero-based. `start` is inclusive, `stop` is exclusive.
707    ///
708    /// This replaces the old fetch and fetch_str implementations.
709    pub fn fetch<'a, T: Into<FetchDefinition<'a>>>(&mut self, fetch_definition: T) -> Result<()> {
710        //this 'compile time redirect' safes us
711        //from monomorphing the 'meat' of the fetch function
712        self._inner_fetch(fetch_definition.into())
713    }
714
715    fn _inner_fetch(&mut self, fetch_definition: FetchDefinition) -> Result<()> {
716        match fetch_definition {
717            FetchDefinition::Region(tid, start, stop) => {
718                self._fetch_by_coord_tuple(tid, start, stop)
719            }
720            FetchDefinition::RegionString(s, start, stop) => {
721                let tid = self.header().tid(s);
722                match tid {
723                    Some(tid) => self._fetch_by_coord_tuple(tid as i32, start, stop),
724                    None => Err(Error::Fetch),
725                }
726            }
727            FetchDefinition::CompleteTid(tid) => {
728                let len = self.header().target_len(tid as u32);
729                match len {
730                    Some(len) => self._fetch_by_coord_tuple(tid, 0, len as i64),
731                    None => Err(Error::Fetch),
732                }
733            }
734            FetchDefinition::String(s) => {
735                // either a target-name or a samtools style definition
736                let tid = self.header().tid(s);
737                match tid {
738                    Some(tid) => {
739                        //'large position' spec says target len must will fit into an i64.
740                        let len: i64 = self.header.target_len(tid).unwrap().try_into().unwrap();
741                        self._fetch_by_coord_tuple(tid as i32, 0, len)
742                    }
743                    None => self._fetch_by_str(s),
744                }
745            }
746            FetchDefinition::All => self._fetch_by_str(b"."),
747            FetchDefinition::Unmapped => self._fetch_by_str(b"*"),
748        }
749    }
750
751    fn _fetch_by_coord_tuple(&mut self, tid: i32, beg: i64, end: i64) -> Result<()> {
752        if let Some(itr) = self.itr {
753            unsafe { htslib::hts_itr_destroy(itr) }
754        }
755        let itr = unsafe { htslib::sam_itr_queryi(self.index().inner_ptr(), tid, beg, end) };
756        if itr.is_null() {
757            self.itr = None;
758            Err(Error::Fetch)
759        } else {
760            self.itr = Some(itr);
761            Ok(())
762        }
763    }
764
765    fn _fetch_by_str(&mut self, region: &[u8]) -> Result<()> {
766        if let Some(itr) = self.itr {
767            unsafe { htslib::hts_itr_destroy(itr) }
768        }
769        let rstr = ffi::CString::new(region).unwrap();
770        let rptr = rstr.as_ptr();
771        let itr = unsafe {
772            htslib::sam_itr_querys(
773                self.index().inner_ptr(),
774                self.header().inner_ptr() as *mut hts_sys::sam_hdr_t,
775                rptr,
776            )
777        };
778        if itr.is_null() {
779            self.itr = None;
780            Err(Error::Fetch)
781        } else {
782            self.itr = Some(itr);
783            Ok(())
784        }
785    }
786
787    extern "C" fn pileup_read(
788        data: *mut ::std::os::raw::c_void,
789        record: *mut htslib::bam1_t,
790    ) -> i32 {
791        let _self = unsafe { (data as *mut Self).as_mut().unwrap() };
792        match _self.itr {
793            Some(itr) => itr_next(_self.htsfile, itr, record), // read fetched region
794            None => unsafe {
795                htslib::sam_read1(
796                    _self.htsfile,
797                    _self.header().inner_ptr() as *mut hts_sys::sam_hdr_t,
798                    record,
799                )
800            }, // ordinary reading
801        }
802    }
803
804    /// Set the reference path for reading CRAM files.
805    ///
806    /// # Arguments
807    ///
808    /// * `path` - path to the FASTA reference
809    pub fn set_reference<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
810        unsafe { set_fai_filename(self.htsfile, path) }
811    }
812
813    pub fn index(&self) -> &IndexView {
814        self.idx.as_ref().unwrap()
815    }
816
817    // Analogous to slow_idxstats in samtools, see
818    // https://github.com/samtools/samtools/blob/556c60fdff977c0e6cadc4c2581661f187098b4d/bam_index.c#L140-L199
819    unsafe fn slow_idxstats(&mut self) -> Result<Vec<(i64, u64, u64, u64)>> {
820        self.set_cram_options(
821            hts_sys::hts_fmt_option_CRAM_OPT_REQUIRED_FIELDS,
822            hts_sys::sam_fields_SAM_RNAME | hts_sys::sam_fields_SAM_FLAG,
823        )?;
824        let header = self.header();
825        let h = header.inner;
826        let mut ret;
827        let mut last_tid = -2;
828        let fp = self.htsfile();
829
830        let nref =
831            usize::try_from(hts_sys::sam_hdr_nref(h)).map_err(|_| Error::NoSequencesInReference)?;
832        if nref == 0 {
833            return Ok(vec![]);
834        }
835        let mut counts = vec![vec![0; 2]; nref + 1];
836        let mut bb: hts_sys::bam1_t = MaybeUninit::zeroed().assume_init();
837        let b = &mut bb as *mut hts_sys::bam1_t;
838        loop {
839            ret = hts_sys::sam_read1(fp, h, b);
840            if ret < 0 {
841                break;
842            }
843            let tid = (*b).core.tid;
844            if tid >= nref as i32 || tid < -1 {
845                return Err(Error::InvalidTid { tid });
846            }
847
848            // Map unmapped reads (tid == -1) to the last slot (nref) to avoid usize wrapping.
849            let count_idx = if tid == -1 { nref } else { tid as usize };
850
851            if tid != last_tid {
852                if (last_tid >= -1) && (counts[count_idx][0] + counts[count_idx][1]) > 0 {
853                    return Err(Error::BamUnsorted);
854                }
855                last_tid = tid;
856            }
857
858            let idx = if ((*b).core.flag as u32 & hts_sys::BAM_FUNMAP) > 0 {
859                1
860            } else {
861                0
862            };
863            counts[count_idx][idx] += 1;
864        }
865
866        if ret == -1 {
867            let res = (0..nref)
868                .map(|i| {
869                    (
870                        i as i64,
871                        header.target_len(i as u32).unwrap(),
872                        counts[i][0],
873                        counts[i][1],
874                    )
875                })
876                .chain([(-1, 0, counts[nref][0], counts[nref][1])])
877                .collect();
878            Ok(res)
879        } else {
880            Err(Error::SlowIdxStats)
881        }
882    }
883
884    /// Similar to samtools idxstats, this returns a vector of tuples
885    /// containing the target id, length, number of mapped reads, and number of unmapped reads.
886    /// The last entry in the vector corresponds to the unmapped reads for the entire file, with
887    /// the tid set to -1.
888    pub fn index_stats(&mut self) -> Result<Vec<(i64, u64, u64, u64)>> {
889        let header = self.header();
890        let index = self.index();
891        if index.inner_ptr().is_null() {
892            panic!("Index is null");
893        }
894        // the quick index stats method only works for BAM files, not SAM or CRAM
895        unsafe {
896            if (*self.htsfile()).format.format != htslib::htsExactFormat_bam {
897                return self.slow_idxstats();
898            }
899        }
900        Ok((0..header.target_count())
901            .map(|tid| {
902                let (mapped, unmapped) = index.number_mapped_unmapped(tid);
903                let tlen = header.target_len(tid).unwrap();
904                (tid as i64, tlen, mapped, unmapped)
905            })
906            .chain([(-1, 0, 0, index.number_unmapped())])
907            .collect::<_>())
908    }
909}
910
911#[derive(Debug)]
912pub struct IndexView {
913    inner: *mut hts_sys::hts_idx_t,
914    owned: bool,
915}
916
917impl IndexView {
918    fn new(hts_idx: *mut hts_sys::hts_idx_t) -> Self {
919        Self {
920            inner: hts_idx,
921            owned: true,
922        }
923    }
924
925    #[inline]
926    pub fn inner(&self) -> &hts_sys::hts_idx_t {
927        unsafe { self.inner.as_ref().unwrap() }
928    }
929
930    #[inline]
931    // Pointer to inner hts_idx_t struct
932    pub fn inner_ptr(&self) -> *const hts_sys::hts_idx_t {
933        self.inner
934    }
935
936    #[inline]
937    pub fn inner_mut(&mut self) -> &mut hts_sys::hts_idx_t {
938        unsafe { self.inner.as_mut().unwrap() }
939    }
940
941    #[inline]
942    // Mutable pointer to hts_idx_t struct
943    pub fn inner_ptr_mut(&mut self) -> *mut hts_sys::hts_idx_t {
944        self.inner
945    }
946
947    /// Get the number of mapped and unmapped reads for a given target id
948    /// FIXME only valid for BAM, not SAM/CRAM
949    fn number_mapped_unmapped(&self, tid: u32) -> (u64, u64) {
950        let (mut mapped, mut unmapped) = (0, 0);
951        unsafe {
952            hts_sys::hts_idx_get_stat(self.inner, tid as i32, &mut mapped, &mut unmapped);
953        }
954        (mapped, unmapped)
955    }
956
957    /// Get the total number of unmapped reads in the file
958    /// FIXME only valid for BAM, not SAM/CRAM
959    fn number_unmapped(&self) -> u64 {
960        unsafe { hts_sys::hts_idx_get_n_no_coor(self.inner) }
961    }
962}
963
964impl Drop for IndexView {
965    fn drop(&mut self) {
966        if self.owned {
967            unsafe {
968                htslib::hts_idx_destroy(self.inner);
969            }
970        }
971    }
972}
973
974impl Read for IndexedReader {
975    fn read(&mut self, record: &mut record::Record) -> Option<Result<()>> {
976        match self.itr {
977            Some(itr) => {
978                match itr_next(self.htsfile, itr, &mut record.inner as *mut htslib::bam1_t) {
979                    -1 => None,
980                    -2 => Some(Err(Error::BamTruncatedRecord)),
981                    -4 => Some(Err(Error::BamInvalidRecord)),
982                    _ => {
983                        record.set_header(Arc::clone(&self.header));
984
985                        Some(Ok(()))
986                    }
987                }
988            }
989            None => None,
990        }
991    }
992
993    /// Iterator over the records of the fetched region.
994    /// Note that, while being convenient, this is less efficient than pre-allocating a
995    /// `Record` and reading into it with the `read` method, since every iteration involves
996    /// the allocation of a new `Record`.
997    fn records(&mut self) -> Records<'_, Self> {
998        Records { reader: self }
999    }
1000
1001    fn rc_records(&mut self) -> RcRecords<'_, Self> {
1002        RcRecords {
1003            reader: self,
1004            record: Rc::new(record::Record::new()),
1005        }
1006    }
1007
1008    fn pileup(&mut self) -> pileup::Pileups<'_, Self> {
1009        let _self = self as *const Self;
1010        let itr = unsafe {
1011            htslib::bam_plp_init(
1012                Some(IndexedReader::pileup_read),
1013                _self as *mut ::std::os::raw::c_void,
1014            )
1015        };
1016        pileup::Pileups::new(self, itr)
1017    }
1018
1019    fn htsfile(&self) -> *mut htslib::htsFile {
1020        self.htsfile
1021    }
1022
1023    fn header(&self) -> &HeaderView {
1024        &self.header
1025    }
1026
1027    fn set_thread_pool(&mut self, tpool: &ThreadPool) -> Result<()> {
1028        unsafe { set_thread_pool(self.htsfile(), tpool)? }
1029        self.tpool = Some(tpool.clone());
1030        Ok(())
1031    }
1032}
1033
1034impl Drop for IndexedReader {
1035    fn drop(&mut self) {
1036        unsafe {
1037            if let Some(itr) = self.itr.take() {
1038                htslib::hts_itr_destroy(itr);
1039            }
1040
1041            // A CRAM index contains a pointer to the CRAM file handle.
1042            // Destroy the index before hts_close frees that handle.
1043            drop(self.idx.take());
1044            htslib::hts_close(self.htsfile);
1045        }
1046    }
1047}
1048
1049#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1050pub enum Format {
1051    Sam,
1052    Bam,
1053    Cram,
1054}
1055
1056impl Format {
1057    fn write_mode(self) -> &'static [u8] {
1058        match self {
1059            Format::Sam => b"w",
1060            Format::Bam => b"wb",
1061            Format::Cram => b"wc",
1062        }
1063    }
1064}
1065
1066/// A BAM writer.
1067#[derive(Debug)]
1068pub struct Writer {
1069    f: *mut htslib::htsFile,
1070    header: Arc<HeaderView>,
1071    tpool: Option<ThreadPool>,
1072}
1073
1074unsafe impl Send for Writer {}
1075
1076impl Writer {
1077    /// Create a new SAM/BAM/CRAM file.
1078    ///
1079    /// # Arguments
1080    ///
1081    /// * `path` - the path.
1082    /// * `header` - header definition to use
1083    /// * `format` - the format to use (SAM/BAM/CRAM)
1084    pub fn from_path<P: AsRef<Path>>(
1085        path: P,
1086        header: &header::Header,
1087        format: Format,
1088    ) -> Result<Self> {
1089        Self::new(&path_as_bytes(path, false)?, format.write_mode(), header)
1090    }
1091
1092    /// Create a new SAM/BAM/CRAM file at STDOUT.
1093    ///
1094    /// # Arguments
1095    ///
1096    /// * `header` - header definition to use
1097    /// * `format` - the format to use (SAM/BAM/CRAM)
1098    pub fn from_stdout(header: &header::Header, format: Format) -> Result<Self> {
1099        Self::new(b"-", format.write_mode(), header)
1100    }
1101
1102    /// Create a new SAM/BAM/CRAM file.
1103    ///
1104    /// # Arguments
1105    ///
1106    /// * `path` - the path. Use "-" for stdout.
1107    /// * `mode` - write mode, refer to htslib::hts_open()
1108    /// * `header` - header definition to use
1109    fn new(path: &[u8], mode: &[u8], header: &header::Header) -> Result<Self> {
1110        let f = hts_open(path, mode)?;
1111
1112        // sam_hdr_parse does not populate the text and l_text fields of the header_record.
1113        // This causes non-SQ headers to be dropped in the output BAM file.
1114        // To avoid this, we copy the All header to a new C-string that is allocated with malloc,
1115        // and set this into header_record manually.
1116        let header_record = unsafe {
1117            let mut header_string = header.to_bytes();
1118            if !header_string.is_empty() && header_string[header_string.len() - 1] != b'\n' {
1119                header_string.push(b'\n');
1120            }
1121            let l_text = header_string.len();
1122            let text = ::libc::malloc(l_text + 1);
1123            libc::memset(text, 0, l_text + 1);
1124            libc::memcpy(
1125                text,
1126                header_string.as_ptr() as *const ::libc::c_void,
1127                header_string.len(),
1128            );
1129
1130            //println!("{}", str::from_utf8(&header_string).unwrap());
1131            let rec = htslib::sam_hdr_parse(l_text + 1, text as *const c_char);
1132
1133            (*rec).text = text as *mut c_char;
1134            (*rec).l_text = l_text;
1135            rec
1136        };
1137
1138        unsafe {
1139            htslib::sam_hdr_write(f, header_record);
1140        }
1141
1142        Ok(Writer {
1143            f,
1144            header: Arc::new(HeaderView::new(header_record)),
1145            tpool: None,
1146        })
1147    }
1148
1149    /// Activate multi-threaded BAM write support in htslib. This should permit faster
1150    /// writing of large BAM files.
1151    ///
1152    /// # Arguments
1153    ///
1154    /// * `n_threads` - number of extra background writer threads to use, must be `> 0`.
1155    pub fn set_threads(&mut self, n_threads: usize) -> Result<()> {
1156        unsafe { set_threads(self.f, n_threads) }
1157    }
1158
1159    /// Use a shared thread-pool for writing. This permits controlling the total
1160    /// thread count when multiple readers and writers are working simultaneously.
1161    /// A thread pool can be created with `crate::tpool::ThreadPool::new(n_threads)`
1162    ///
1163    /// # Arguments
1164    ///
1165    /// * `tpool` - thread pool to use for compression work.
1166    pub fn set_thread_pool(&mut self, tpool: &ThreadPool) -> Result<()> {
1167        unsafe { set_thread_pool(self.f, tpool)? }
1168        self.tpool = Some(tpool.clone());
1169        Ok(())
1170    }
1171
1172    /// Write record to BAM.
1173    ///
1174    /// # Arguments
1175    ///
1176    /// * `record` - the record to write
1177    pub fn write(&mut self, record: &record::Record) -> Result<()> {
1178        if unsafe { htslib::sam_write1(self.f, self.header.inner(), record.inner_ptr()) } == -1 {
1179            Err(Error::WriteRecord)
1180        } else {
1181            Ok(())
1182        }
1183    }
1184
1185    /// Return the header.
1186    pub fn header(&self) -> &HeaderView {
1187        &self.header
1188    }
1189
1190    /// Set the reference path for reading CRAM files.
1191    ///
1192    /// # Arguments
1193    ///
1194    /// * `path` - path to the FASTA reference
1195    pub fn set_reference<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
1196        unsafe { set_fai_filename(self.f, path) }
1197    }
1198
1199    /// Set the compression level for writing BAM/CRAM files.
1200    ///
1201    /// # Arguments
1202    ///
1203    /// * `compression_level` - `CompressionLevel` enum variant
1204    pub fn set_compression_level(&mut self, compression_level: CompressionLevel) -> Result<()> {
1205        let level = compression_level.convert()?;
1206        match unsafe {
1207            htslib::hts_set_opt(
1208                self.f,
1209                htslib::hts_fmt_option_HTS_OPT_COMPRESSION_LEVEL,
1210                level,
1211            )
1212        } {
1213            0 => Ok(()),
1214            _ => Err(Error::BamInvalidCompressionLevel { level }),
1215        }
1216    }
1217}
1218
1219/// Compression levels in BAM/CRAM files
1220///
1221/// * Uncompressed: No compression, zlib level 0
1222/// * Fastest: Lowest compression level, zlib level 1
1223/// * Maximum: Highest compression level, zlib level 9
1224/// * Level(i): Custom compression level in the range [0, 9]
1225#[derive(Debug, Clone, Copy)]
1226pub enum CompressionLevel {
1227    Uncompressed,
1228    Fastest,
1229    Maximum,
1230    Level(u32),
1231}
1232
1233impl CompressionLevel {
1234    // Convert and check the variants of the `CompressionLevel` enum to a numeric level
1235    fn convert(self) -> Result<u32> {
1236        match self {
1237            CompressionLevel::Uncompressed => Ok(0),
1238            CompressionLevel::Fastest => Ok(1),
1239            CompressionLevel::Maximum => Ok(9),
1240            CompressionLevel::Level(i @ 0..=9) => Ok(i),
1241            CompressionLevel::Level(i) => Err(Error::BamInvalidCompressionLevel { level: i }),
1242        }
1243    }
1244}
1245
1246impl Drop for Writer {
1247    fn drop(&mut self) {
1248        unsafe {
1249            htslib::hts_close(self.f);
1250        }
1251    }
1252}
1253
1254/// Iterator over the records of a BAM.
1255#[derive(Debug)]
1256pub struct Records<'a, R: Read> {
1257    reader: &'a mut R,
1258}
1259
1260impl<R: Read> Iterator for Records<'_, R> {
1261    type Item = Result<record::Record>;
1262
1263    fn next(&mut self) -> Option<Result<record::Record>> {
1264        let mut record = record::Record::new();
1265        match self.reader.read(&mut record) {
1266            None => None,
1267            Some(Ok(_)) => Some(Ok(record)),
1268            Some(Err(err)) => Some(Err(err)),
1269        }
1270    }
1271}
1272
1273/// Iterator over the records of a BAM, using an Rc.
1274///
1275/// See [rc_records](trait.Read.html#tymethod.rc_records).
1276#[derive(Debug)]
1277pub struct RcRecords<'a, R: Read> {
1278    reader: &'a mut R,
1279    record: Rc<record::Record>,
1280}
1281
1282impl<R: Read> Iterator for RcRecords<'_, R> {
1283    type Item = Result<Rc<record::Record>>;
1284
1285    fn next(&mut self) -> Option<Self::Item> {
1286        let record = match Rc::get_mut(&mut self.record) {
1287            //not make_mut, we don't need a clone
1288            Some(x) => x,
1289            None => {
1290                self.record = Rc::new(record::Record::new());
1291                Rc::get_mut(&mut self.record).unwrap()
1292            }
1293        };
1294
1295        match self.reader.read(record) {
1296            None => None,
1297            Some(Ok(_)) => Some(Ok(Rc::clone(&self.record))),
1298            Some(Err(err)) => Some(Err(err)),
1299        }
1300    }
1301}
1302
1303/// Iterator over the records of a BAM until the virtual offset is less than `end`
1304pub struct ChunkIterator<'a, R: Read> {
1305    reader: &'a mut R,
1306    end: Option<i64>,
1307}
1308
1309impl<R: Read> Iterator for ChunkIterator<'_, R> {
1310    type Item = Result<record::Record>;
1311    fn next(&mut self) -> Option<Result<record::Record>> {
1312        if let Some(pos) = self.end {
1313            if self.reader.tell() >= pos {
1314                return None;
1315            }
1316        }
1317        let mut record = record::Record::new();
1318        match self.reader.read(&mut record) {
1319            None => None,
1320            Some(Ok(_)) => Some(Ok(record)),
1321            Some(Err(err)) => Some(Err(err)),
1322        }
1323    }
1324}
1325
1326/// Wrapper for opening a BAM file.
1327fn hts_open(path: &[u8], mode: &[u8]) -> Result<*mut htslib::htsFile> {
1328    let cpath = ffi::CString::new(path).unwrap();
1329    let path = str::from_utf8(path).unwrap();
1330    let c_str = ffi::CString::new(mode).unwrap();
1331    let ret = unsafe { htslib::hts_open(cpath.as_ptr(), c_str.as_ptr()) };
1332    if ret.is_null() {
1333        Err(Error::BamOpen {
1334            target: path.to_owned(),
1335        })
1336    } else {
1337        if !mode.contains(&b'w') {
1338            unsafe {
1339                // Comparison against 'htsFormatCategory_sequence_data' doesn't handle text files correctly
1340                // hence the explicit checks against all supported exact formats
1341                if (*ret).format.format != htslib::htsExactFormat_sam
1342                    && (*ret).format.format != htslib::htsExactFormat_bam
1343                    && (*ret).format.format != htslib::htsExactFormat_cram
1344                {
1345                    return Err(Error::BamOpen {
1346                        target: path.to_owned(),
1347                    });
1348                }
1349            }
1350        }
1351        Ok(ret)
1352    }
1353}
1354
1355/// Wrapper for iterating an indexed BAM file.
1356fn itr_next(
1357    htsfile: *mut htslib::htsFile,
1358    itr: *mut htslib::hts_itr_t,
1359    record: *mut htslib::bam1_t,
1360) -> i32 {
1361    unsafe {
1362        htslib::hts_itr_next(
1363            (*htsfile).fp.bgzf,
1364            itr,
1365            record as *mut ::std::os::raw::c_void,
1366            htsfile as *mut ::std::os::raw::c_void,
1367        )
1368    }
1369}
1370
1371#[derive(Debug)]
1372pub struct HeaderView {
1373    inner: *mut htslib::bam_hdr_t,
1374}
1375
1376unsafe impl Send for HeaderView {}
1377unsafe impl Sync for HeaderView {}
1378
1379impl HeaderView {
1380    /// Create a new HeaderView from a pre-populated Header object
1381    pub fn from_header(header: &Header) -> Self {
1382        let mut header_string = header.to_bytes();
1383        if !header_string.is_empty() && header_string[header_string.len() - 1] != b'\n' {
1384            header_string.push(b'\n');
1385        }
1386        Self::from_bytes(&header_string)
1387    }
1388
1389    /// Create a new HeaderView from bytes
1390    pub fn from_bytes(header_string: &[u8]) -> Self {
1391        let header_record = unsafe {
1392            let l_text = header_string.len();
1393            let text = ::libc::malloc(l_text + 1);
1394            ::libc::memset(text, 0, l_text + 1);
1395            ::libc::memcpy(
1396                text,
1397                header_string.as_ptr() as *const ::libc::c_void,
1398                header_string.len(),
1399            );
1400
1401            let rec = htslib::sam_hdr_parse(l_text + 1, text as *const c_char);
1402            (*rec).text = text as *mut c_char;
1403            (*rec).l_text = l_text;
1404            rec
1405        };
1406
1407        HeaderView::new(header_record)
1408    }
1409
1410    /// Create a new HeaderView from the underlying Htslib type, and own it.
1411    fn new(inner: *mut htslib::bam_hdr_t) -> Self {
1412        HeaderView { inner }
1413    }
1414
1415    #[inline]
1416    pub fn inner(&self) -> &htslib::bam_hdr_t {
1417        unsafe { self.inner.as_ref().unwrap() }
1418    }
1419
1420    #[inline]
1421    // Pointer to inner bam_hdr_t struct
1422    pub fn inner_ptr(&self) -> *const htslib::bam_hdr_t {
1423        self.inner
1424    }
1425
1426    #[inline]
1427    pub fn inner_mut(&mut self) -> &mut htslib::bam_hdr_t {
1428        unsafe { self.inner.as_mut().unwrap() }
1429    }
1430
1431    #[inline]
1432    // Mutable pointer to bam_hdr_t struct
1433    pub fn inner_ptr_mut(&mut self) -> *mut htslib::bam_hdr_t {
1434        self.inner
1435    }
1436
1437    pub fn tid(&self, name: &[u8]) -> Option<u32> {
1438        let c_str = ffi::CString::new(name).expect("Expected valid name.");
1439        let tid = unsafe { htslib::sam_hdr_name2tid(self.inner, c_str.as_ptr()) };
1440        if tid < 0 { None } else { Some(tid as u32) }
1441    }
1442
1443    pub fn tid2name(&self, tid: u32) -> &[u8] {
1444        let ptr = unsafe { htslib::sam_hdr_tid2name(self.inner, tid as i32) };
1445        if ptr.is_null() {
1446            b""
1447        } else {
1448            unsafe { ffi::CStr::from_ptr(ptr).to_bytes() }
1449        }
1450    }
1451
1452    pub fn target_count(&self) -> u32 {
1453        self.inner().n_targets as u32
1454    }
1455
1456    pub fn target_names(&self) -> Vec<&[u8]> {
1457        let names = unsafe {
1458            slice::from_raw_parts(self.inner().target_name, self.target_count() as usize)
1459        };
1460        names
1461            .iter()
1462            .map(|name| unsafe { ffi::CStr::from_ptr(*name).to_bytes() })
1463            .collect()
1464    }
1465
1466    pub fn target_len(&self, tid: u32) -> Option<u64> {
1467        let inner = unsafe { *self.inner };
1468        if (tid as i32) < inner.n_targets {
1469            let l: &[u32] =
1470                unsafe { slice::from_raw_parts(inner.target_len, inner.n_targets as usize) };
1471            Some(l[tid as usize] as u64)
1472        } else {
1473            None
1474        }
1475    }
1476
1477    /// Retrieve the textual SAM header as bytes
1478    pub fn as_bytes(&self) -> &[u8] {
1479        unsafe {
1480            let rebuilt_hdr = htslib::sam_hdr_str(self.inner);
1481            if rebuilt_hdr.is_null() {
1482                return b"";
1483            }
1484            ffi::CStr::from_ptr(rebuilt_hdr).to_bytes()
1485        }
1486    }
1487}
1488
1489impl Clone for HeaderView {
1490    fn clone(&self) -> Self {
1491        HeaderView {
1492            inner: unsafe { htslib::sam_hdr_dup(self.inner) },
1493        }
1494    }
1495}
1496
1497impl Drop for HeaderView {
1498    fn drop(&mut self) {
1499        unsafe {
1500            htslib::sam_hdr_destroy(self.inner);
1501        }
1502    }
1503}
1504
1505#[cfg(test)]
1506mod tests {
1507    use super::header::HeaderRecord;
1508    use super::record::{Aux, Cigar, CigarString};
1509    use super::*;
1510    use std::collections::HashMap;
1511    use std::fs;
1512    use std::path::Path;
1513    use std::str;
1514
1515    type GoldType = (
1516        [&'static [u8]; 6],
1517        [u16; 6],
1518        [&'static [u8]; 6],
1519        [&'static [u8]; 6],
1520        [CigarString; 6],
1521    );
1522    fn gold() -> GoldType {
1523        let names = [
1524            &b"I"[..],
1525            &b"II.14978392"[..],
1526            &b"III"[..],
1527            &b"IV"[..],
1528            &b"V"[..],
1529            &b"VI"[..],
1530        ];
1531        let flags = [16u16, 16u16, 16u16, 16u16, 16u16, 2048u16];
1532        let seqs = [
1533            &b"CCTAGCCCTAACCCTAACCCTAACCCTAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCC\
1534TAAGCCTAAGCCTAAGCCTAA"[..],
1535            &b"CCTAGCCCTAACCCTAACCCTAACCCTAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCC\
1536TAAGCCTAAGCCTAAGCCTAA"[..],
1537            &b"CCTAGCCCTAACCCTAACCCTAACCCTAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCC\
1538TAAGCCTAAGCCTAAGCCTAA"[..],
1539            &b"CCTAGCCCTAACCCTAACCCTAACCCTAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCC\
1540TAAGCCTAAGCCTAAGCCTAA"[..],
1541            &b"CCTAGCCCTAACCCTAACCCTAACCCTAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCCTAAGCC\
1542TAAGCCTAAGCCTAAGCCTAA"[..],
1543            &b"ACTAAGCCTAAGCCTAAGCCTAAGCCAATTATCGATTTCTGAAAAAATTATCGAATTTTCTAGAAATTTTGCAAATTTT\
1544TTCATAAAATTATCGATTTTA"[..],
1545        ];
1546        let quals = [
1547            &b"#############################@B?8B?BA@@DDBCDDCBC@CDCDCCCCCCCCCCCCCCCCCCCCCCCCCCCC\
1548CCCCCCCCCCCCCCCCCCC"[..],
1549            &b"#############################@B?8B?BA@@DDBCDDCBC@CDCDCCCCCCCCCCCCCCCCCCCCCCCCCCCC\
1550CCCCCCCCCCCCCCCCCCC"[..],
1551            &b"#############################@B?8B?BA@@DDBCDDCBC@CDCDCCCCCCCCCCCCCCCCCCCCCCCCCCCC\
1552CCCCCCCCCCCCCCCCCCC"[..],
1553            &b"#############################@B?8B?BA@@DDBCDDCBC@CDCDCCCCCCCCCCCCCCCCCCCCCCCCCCCC\
1554CCCCCCCCCCCCCCCCCCC"[..],
1555            &b"#############################@B?8B?BA@@DDBCDDCBC@CDCDCCCCCCCCCCCCCCCCCCCCCCCCCCCC\
1556CCCCCCCCCCCCCCCCCCC"[..],
1557            &b"#############################@B?8B?BA@@DDBCDDCBC@CDCDCCCCCCCCCCCCCCCCCCCCCCCCCCCC\
1558CCCCCCCCCCCCCCCCCCC"[..],
1559        ];
1560        let cigars = [
1561            CigarString(vec![Cigar::Match(27), Cigar::Del(1), Cigar::Match(73)]),
1562            CigarString(vec![Cigar::Match(27), Cigar::Del(1), Cigar::Match(73)]),
1563            CigarString(vec![Cigar::Match(27), Cigar::Del(1), Cigar::Match(73)]),
1564            CigarString(vec![Cigar::Match(27), Cigar::Del(1), Cigar::Match(73)]),
1565            CigarString(vec![Cigar::Match(27), Cigar::Del(1), Cigar::Match(73)]),
1566            CigarString(vec![Cigar::Match(27), Cigar::Del(100000), Cigar::Match(73)]),
1567        ];
1568        (names, flags, seqs, quals, cigars)
1569    }
1570
1571    fn compare_inner_bam_cram_records(cram_records: &[Record], bam_records: &[Record]) {
1572        // Selectively compares bam1_t struct fields from BAM and CRAM
1573        for (c1, b1) in cram_records.iter().zip(bam_records.iter()) {
1574            // CRAM vs BAM l_data is off by 3, see: https://github.com/rust-bio/rust-htslib/pull/184#issuecomment-590133544
1575            // The rest of the fields should be identical:
1576            assert_eq!(c1.cigar(), b1.cigar());
1577            assert_eq!(c1.inner().core.pos, b1.inner().core.pos);
1578            assert_eq!(c1.inner().core.mpos, b1.inner().core.mpos);
1579            assert_eq!(c1.inner().core.mtid, b1.inner().core.mtid);
1580            assert_eq!(c1.inner().core.tid, b1.inner().core.tid);
1581            assert_eq!(c1.inner().core.bin, b1.inner().core.bin);
1582            assert_eq!(c1.inner().core.qual, b1.inner().core.qual);
1583            assert_eq!(c1.inner().core.l_extranul, b1.inner().core.l_extranul);
1584            assert_eq!(c1.inner().core.flag, b1.inner().core.flag);
1585            assert_eq!(c1.inner().core.l_qname, b1.inner().core.l_qname);
1586            assert_eq!(c1.inner().core.n_cigar, b1.inner().core.n_cigar);
1587            assert_eq!(c1.inner().core.l_qseq, b1.inner().core.l_qseq);
1588            assert_eq!(c1.inner().core.isize_, b1.inner().core.isize_);
1589            //... except m_data
1590        }
1591    }
1592
1593    #[test]
1594    fn test_read() {
1595        let (names, flags, seqs, quals, cigars) = gold();
1596        let mut bam = Reader::from_path(Path::new("test/test.bam")).expect("Error opening file.");
1597        let del_len = [1, 1, 1, 1, 1, 100000];
1598
1599        for (i, record) in bam.records().enumerate() {
1600            let rec = record.expect("Expected valid record");
1601            assert_eq!(rec.qname(), names[i]);
1602            assert_eq!(rec.flags(), flags[i]);
1603            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1604
1605            let cigar = rec.cigar();
1606            assert_eq!(*cigar, cigars[i]);
1607
1608            let end_pos = cigar.end_pos();
1609            assert_eq!(end_pos, rec.pos() + 100 + del_len[i]);
1610            assert_eq!(
1611                cigar
1612                    .read_pos(end_pos as u32 - 10, false, false)
1613                    .unwrap()
1614                    .unwrap(),
1615                90
1616            );
1617            assert_eq!(
1618                cigar
1619                    .read_pos(rec.pos() as u32 + 20, false, false)
1620                    .unwrap()
1621                    .unwrap(),
1622                20
1623            );
1624            assert_eq!(cigar.read_pos(4000000, false, false).unwrap(), None);
1625            // fix qual offset
1626            let qual: Vec<u8> = quals[i].iter().map(|&q| q - 33).collect();
1627            assert_eq!(rec.qual(), &qual[..]);
1628        }
1629    }
1630
1631    #[test]
1632    fn test_seek() {
1633        let mut bam = Reader::from_path(Path::new("test/test.bam")).expect("Error opening file.");
1634
1635        let mut names_by_voffset = HashMap::new();
1636
1637        let mut offset = bam.tell();
1638        let mut rec = Record::new();
1639        while let Some(r) = bam.read(&mut rec) {
1640            r.expect("error reading bam");
1641            let qname = str::from_utf8(rec.qname()).unwrap().to_string();
1642            println!("{} {}", offset, qname);
1643            names_by_voffset.insert(offset, qname);
1644            offset = bam.tell();
1645        }
1646
1647        for (offset, qname) in names_by_voffset.iter() {
1648            println!("{} {}", offset, qname);
1649            bam.seek(*offset).unwrap();
1650            if let Some(r) = bam.read(&mut rec) {
1651                r.unwrap();
1652            };
1653            let rec_qname = str::from_utf8(rec.qname()).unwrap().to_string();
1654            assert_eq!(qname, &rec_qname);
1655        }
1656    }
1657
1658    #[test]
1659    fn test_read_sam_header() {
1660        let bam = Reader::from_path("test/test.bam").expect("Error opening file.");
1661
1662        let true_header = "@SQ\tSN:CHROMOSOME_I\tLN:15072423\n@SQ\tSN:CHROMOSOME_II\tLN:15279345\
1663             \n@SQ\tSN:CHROMOSOME_III\tLN:13783700\n@SQ\tSN:CHROMOSOME_IV\tLN:17493793\n@SQ\t\
1664             SN:CHROMOSOME_V\tLN:20924149\n"
1665            .to_string();
1666        let header_text = String::from_utf8(bam.header.as_bytes().to_owned()).unwrap();
1667        assert_eq!(header_text, true_header);
1668    }
1669
1670    #[test]
1671    fn test_read_against_sam() {
1672        let mut bam = Reader::from_path("./test/bam2sam_out.sam").unwrap();
1673        for read in bam.records() {
1674            let _read = read.unwrap();
1675        }
1676    }
1677
1678    fn _test_read_indexed_common(mut bam: IndexedReader) {
1679        let (names, flags, seqs, quals, cigars) = gold();
1680        let sq_1 = b"CHROMOSOME_I";
1681        let sq_2 = b"CHROMOSOME_II";
1682        let tid_1 = bam.header.tid(sq_1).expect("Expected tid.");
1683        let tid_2 = bam.header.tid(sq_2).expect("Expected tid.");
1684        assert!(bam.header.target_len(tid_1).expect("Expected target len.") == 15072423);
1685
1686        // fetch to position containing reads
1687        bam.fetch((tid_1, 0, 2))
1688            .expect("Expected successful fetch.");
1689        assert!(bam.records().count() == 6);
1690
1691        // compare reads
1692        bam.fetch((tid_1, 0, 2))
1693            .expect("Expected successful fetch.");
1694        for (i, record) in bam.records().enumerate() {
1695            let rec = record.expect("Expected valid record");
1696
1697            println!("{}", str::from_utf8(rec.qname()).unwrap());
1698            assert_eq!(rec.qname(), names[i]);
1699            assert_eq!(rec.flags(), flags[i]);
1700            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1701            assert_eq!(*rec.cigar(), cigars[i]);
1702            // fix qual offset
1703            let qual: Vec<u8> = quals[i].iter().map(|&q| q - 33).collect();
1704            assert_eq!(rec.qual(), &qual[..]);
1705            assert_eq!(rec.aux(b"X"), Err(Error::BamAuxStringError));
1706            assert_eq!(rec.aux(b"NotAvailableAux"), Err(Error::BamAuxTagNotFound));
1707        }
1708
1709        // fetch to empty position
1710        bam.fetch((tid_2, 1, 1))
1711            .expect("Expected successful fetch.");
1712        assert!(bam.records().count() == 0);
1713
1714        // repeat with byte-string based fetch
1715
1716        // fetch to position containing reads
1717        // using coordinate-string chr:start-stop
1718        bam.fetch(format!("{}:{}-{}", str::from_utf8(sq_1).unwrap(), 0, 2).as_bytes())
1719            .expect("Expected successful fetch.");
1720        assert!(bam.records().count() == 6);
1721        // using &str and exercising some of the coordinate conversion funcs
1722        bam.fetch((str::from_utf8(sq_1).unwrap(), 0_u32, 2_u64))
1723            .expect("Expected successful fetch.");
1724        assert!(bam.records().count() == 6);
1725        // using a slice
1726        bam.fetch((&sq_1[..], 0, 2))
1727            .expect("Expected successful fetch.");
1728        assert!(bam.records().count() == 6);
1729        // using a literal
1730        bam.fetch((sq_1, 0, 2)).expect("Expected successful fetch.");
1731        assert!(bam.records().count() == 6);
1732
1733        // using a tid
1734        bam.fetch((0i32, 0u32, 2i64))
1735            .expect("Expected successful fetch.");
1736        assert!(bam.records().count() == 6);
1737        // using a tid:u32
1738        bam.fetch((0u32, 0u32, 2i64))
1739            .expect("Expected successful fetch.");
1740        assert!(bam.records().count() == 6);
1741
1742        // compare reads
1743        bam.fetch(format!("{}:{}-{}", str::from_utf8(sq_1).unwrap(), 0, 2).as_bytes())
1744            .expect("Expected successful fetch.");
1745        for (i, record) in bam.records().enumerate() {
1746            let rec = record.expect("Expected valid record");
1747
1748            println!("{}", str::from_utf8(rec.qname()).unwrap());
1749            assert_eq!(rec.qname(), names[i]);
1750            assert_eq!(rec.flags(), flags[i]);
1751            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1752            assert_eq!(*rec.cigar(), cigars[i]);
1753            // fix qual offset
1754            let qual: Vec<u8> = quals[i].iter().map(|&q| q - 33).collect();
1755            assert_eq!(rec.qual(), &qual[..]);
1756            assert_eq!(rec.aux(b"NotAvailableAux"), Err(Error::BamAuxTagNotFound));
1757        }
1758
1759        // fetch to empty position
1760        bam.fetch(format!("{}:{}-{}", str::from_utf8(sq_2).unwrap(), 1, 1).as_bytes())
1761            .expect("Expected successful fetch.");
1762        assert!(bam.records().count() == 0);
1763
1764        //all on a tid
1765        bam.fetch(0).expect("Expected successful fetch.");
1766        assert!(bam.records().count() == 6);
1767        //all on a tid:u32
1768        bam.fetch(0u32).expect("Expected successful fetch.");
1769        assert!(bam.records().count() == 6);
1770
1771        //all on a tid - by &[u8]
1772        bam.fetch(sq_1).expect("Expected successful fetch.");
1773        assert!(bam.records().count() == 6);
1774        //all on a tid - by str
1775        bam.fetch(str::from_utf8(sq_1).unwrap())
1776            .expect("Expected successful fetch.");
1777        assert!(bam.records().count() == 6);
1778
1779        //all reads
1780        bam.fetch(FetchDefinition::All)
1781            .expect("Expected successful fetch.");
1782        assert!(bam.records().count() == 6);
1783
1784        //all reads
1785        bam.fetch(".").expect("Expected successful fetch.");
1786        assert!(bam.records().count() == 6);
1787
1788        //all unmapped
1789        bam.fetch(FetchDefinition::Unmapped)
1790            .expect("Expected successful fetch.");
1791        assert_eq!(bam.records().count(), 1); // expect one 'truncade record' Record.
1792
1793        bam.fetch("*").expect("Expected successful fetch.");
1794        assert_eq!(bam.records().count(), 1); // expect one 'truncade record' Record.
1795    }
1796
1797    #[test]
1798    fn test_read_indexed() {
1799        let bam = IndexedReader::from_path("test/test.bam").expect("Expected valid index.");
1800        _test_read_indexed_common(bam);
1801    }
1802
1803    #[test]
1804    fn test_read_indexed_cram() {
1805        let mut reader = IndexedReader::from_path("test/test_cram.cram").unwrap();
1806        reader.set_reference("test/test_cram.fa").unwrap();
1807        reader.fetch(("chr1", 0, 120)).unwrap();
1808
1809        let mut record = Record::new();
1810        reader.read(&mut record).unwrap().unwrap();
1811        assert_eq!(record.qname(), b"chr1.1");
1812
1813        drop(reader);
1814    }
1815
1816    #[test]
1817    fn test_read_indexed_different_index_name() {
1818        let bam = IndexedReader::from_path_and_index(
1819            &"test/test_different_index_name.bam",
1820            &"test/test.bam.bai",
1821        )
1822        .expect("Expected valid index.");
1823        _test_read_indexed_common(bam);
1824    }
1825
1826    #[test]
1827    fn test_set_record() {
1828        let (names, _, seqs, quals, cigars) = gold();
1829
1830        let mut rec = record::Record::new();
1831        rec.set_reverse();
1832        rec.set(names[0], Some(&cigars[0]), seqs[0], quals[0]);
1833        // note: this segfaults if you push_aux() before set()
1834        //       because set() obliterates aux
1835        rec.push_aux(b"NM", Aux::I32(15)).unwrap();
1836
1837        assert_eq!(rec.qname(), names[0]);
1838        assert_eq!(*rec.cigar(), cigars[0]);
1839        assert_eq!(rec.seq().as_bytes(), seqs[0]);
1840        assert_eq!(rec.qual(), quals[0]);
1841        assert!(rec.is_reverse());
1842        assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
1843    }
1844
1845    #[test]
1846    fn test_set_repeated() {
1847        let mut rec = Record::new();
1848        rec.set(
1849            b"123",
1850            Some(&CigarString(vec![Cigar::Match(3)])),
1851            b"AAA",
1852            b"III",
1853        );
1854        rec.push_aux(b"AS", Aux::I32(12345)).unwrap();
1855        assert_eq!(rec.qname(), b"123");
1856        assert_eq!(rec.seq().as_bytes(), b"AAA");
1857        assert_eq!(rec.qual(), b"III");
1858        assert_eq!(rec.aux(b"AS").unwrap(), Aux::I32(12345));
1859
1860        rec.set(
1861            b"1234",
1862            Some(&CigarString(vec![Cigar::SoftClip(1), Cigar::Match(3)])),
1863            b"AAAA",
1864            b"IIII",
1865        );
1866        assert_eq!(rec.qname(), b"1234");
1867        assert_eq!(rec.seq().as_bytes(), b"AAAA");
1868        assert_eq!(rec.qual(), b"IIII");
1869        assert_eq!(rec.aux(b"AS").unwrap(), Aux::I32(12345));
1870
1871        rec.set(
1872            b"12",
1873            Some(&CigarString(vec![Cigar::Match(2)])),
1874            b"AA",
1875            b"II",
1876        );
1877        assert_eq!(rec.qname(), b"12");
1878        assert_eq!(rec.seq().as_bytes(), b"AA");
1879        assert_eq!(rec.qual(), b"II");
1880        assert_eq!(rec.aux(b"AS").unwrap(), Aux::I32(12345));
1881    }
1882
1883    #[test]
1884    fn test_set_qname() {
1885        let (names, _, seqs, quals, cigars) = gold();
1886
1887        assert!(names[0] != names[1]);
1888
1889        for i in 0..names.len() {
1890            let mut rec = record::Record::new();
1891            rec.set(names[i], Some(&cigars[i]), seqs[i], quals[i]);
1892            rec.push_aux(b"NM", Aux::I32(15)).unwrap();
1893
1894            assert_eq!(rec.qname(), names[i]);
1895            assert_eq!(*rec.cigar(), cigars[i]);
1896            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1897            assert_eq!(rec.qual(), quals[i]);
1898            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
1899
1900            // Equal length qname
1901            assert!(rec.qname()[0] != b'X');
1902            rec.set_qname(b"X");
1903            assert_eq!(rec.qname(), b"X");
1904
1905            // Longer qname
1906            let mut longer_name = names[i].to_owned().clone();
1907            let extension = b"BuffaloBUffaloBUFFaloBUFFAloBUFFALoBUFFALO";
1908            longer_name.extend(extension.iter());
1909            rec.set_qname(&longer_name);
1910
1911            assert_eq!(rec.qname(), longer_name.as_slice());
1912            assert_eq!(*rec.cigar(), cigars[i]);
1913            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1914            assert_eq!(rec.qual(), quals[i]);
1915            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
1916
1917            // Shorter qname
1918            let shorter_name = b"42";
1919            rec.set_qname(shorter_name);
1920
1921            assert_eq!(rec.qname(), shorter_name);
1922            assert_eq!(*rec.cigar(), cigars[i]);
1923            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1924            assert_eq!(rec.qual(), quals[i]);
1925            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
1926
1927            // Zero-length qname
1928            rec.set_qname(b"");
1929
1930            assert_eq!(rec.qname(), b"");
1931            assert_eq!(*rec.cigar(), cigars[i]);
1932            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1933            assert_eq!(rec.qual(), quals[i]);
1934            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
1935        }
1936    }
1937
1938    #[test]
1939    fn test_set_qname2() {
1940        let mut _header = Header::new();
1941        _header.push_record(
1942            HeaderRecord::new(b"SQ")
1943                .push_tag(b"SN", "1")
1944                .push_tag(b"LN", 10000000),
1945        );
1946        let header = HeaderView::from_header(&_header);
1947
1948        let line =
1949            b"blah1	0	1	1	255	1M	*	0	0	A	F	CB:Z:AAAA-1	UR:Z:AAAA	UB:Z:AAAA	GX:Z:G1	xf:i:1	fx:Z:G1\tli:i:0\ttf:Z:cC";
1950
1951        let mut rec = Record::from_sam(&header, line).unwrap();
1952        assert_eq!(rec.qname(), b"blah1");
1953        rec.set_qname(b"r0");
1954        assert_eq!(rec.qname(), b"r0");
1955    }
1956
1957    #[test]
1958    fn test_set_cigar() {
1959        let (names, _, seqs, quals, cigars) = gold();
1960
1961        assert!(names[0] != names[1]);
1962
1963        for i in 0..names.len() {
1964            let mut rec = record::Record::new();
1965            rec.set(names[i], Some(&cigars[i]), seqs[i], quals[i]);
1966            rec.push_aux(b"NM", Aux::I32(15)).unwrap();
1967
1968            assert_eq!(rec.qname(), names[i]);
1969            assert_eq!(*rec.cigar(), cigars[i]);
1970            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1971            assert_eq!(rec.qual(), quals[i]);
1972            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
1973
1974            // boring cigar
1975            let new_cigar = CigarString(vec![Cigar::Match(rec.seq_len() as u32)]);
1976            assert_ne!(*rec.cigar(), new_cigar);
1977            rec.set_cigar(Some(&new_cigar));
1978            assert_eq!(*rec.cigar(), new_cigar);
1979
1980            assert_eq!(rec.qname(), names[i]);
1981            assert_eq!(rec.seq().as_bytes(), seqs[i]);
1982            assert_eq!(rec.qual(), quals[i]);
1983            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
1984
1985            // bizarre cigar
1986            let new_cigar = (0..rec.seq_len())
1987                .map(|i| {
1988                    if i % 2 == 0 {
1989                        Cigar::Match(1)
1990                    } else {
1991                        Cigar::Ins(1)
1992                    }
1993                })
1994                .collect::<Vec<_>>();
1995            let new_cigar = CigarString(new_cigar);
1996            assert_ne!(*rec.cigar(), new_cigar);
1997            rec.set_cigar(Some(&new_cigar));
1998            assert_eq!(*rec.cigar(), new_cigar);
1999
2000            assert_eq!(rec.qname(), names[i]);
2001            assert_eq!(rec.seq().as_bytes(), seqs[i]);
2002            assert_eq!(rec.qual(), quals[i]);
2003            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
2004
2005            // empty cigar
2006            let new_cigar = CigarString(Vec::new());
2007            assert_ne!(*rec.cigar(), new_cigar);
2008            rec.set_cigar(None);
2009            assert_eq!(*rec.cigar(), new_cigar);
2010
2011            assert_eq!(rec.qname(), names[i]);
2012            assert_eq!(rec.seq().as_bytes(), seqs[i]);
2013            assert_eq!(rec.qual(), quals[i]);
2014            assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
2015        }
2016    }
2017
2018    #[test]
2019    fn test_remove_aux() {
2020        let mut bam = Reader::from_path(Path::new("test/test.bam")).expect("Error opening file.");
2021
2022        for record in bam.records() {
2023            let mut rec = record.expect("Expected valid record");
2024
2025            if rec.aux(b"XS").is_ok() {
2026                rec.remove_aux(b"XS").unwrap();
2027            }
2028
2029            if rec.aux(b"YT").is_ok() {
2030                rec.remove_aux(b"YT").unwrap();
2031            }
2032
2033            assert_eq!(rec.remove_aux(b"X"), Err(Error::BamAuxStringError));
2034            assert_eq!(rec.remove_aux(b"ab"), Err(Error::BamAuxTagNotFound));
2035
2036            assert_eq!(rec.aux(b"XS"), Err(Error::BamAuxTagNotFound));
2037            assert_eq!(rec.aux(b"YT"), Err(Error::BamAuxTagNotFound));
2038        }
2039    }
2040
2041    #[test]
2042    fn test_write() {
2043        let (names, _, seqs, quals, cigars) = gold();
2044
2045        let tmp = tempfile::Builder::new()
2046            .prefix("rust-htslib")
2047            .tempdir()
2048            .expect("Cannot create temp dir");
2049        let bampath = tmp.path().join("test.bam");
2050        println!("{:?}", bampath);
2051        {
2052            let mut bam = Writer::from_path(
2053                &bampath,
2054                Header::new().push_record(
2055                    HeaderRecord::new(b"SQ")
2056                        .push_tag(b"SN", "chr1")
2057                        .push_tag(b"LN", 15072423),
2058                ),
2059                Format::Bam,
2060            )
2061            .expect("Error opening file.");
2062
2063            for i in 0..names.len() {
2064                let mut rec = record::Record::new();
2065                rec.set(names[i], Some(&cigars[i]), seqs[i], quals[i]);
2066                rec.push_aux(b"NM", Aux::I32(15)).unwrap();
2067
2068                bam.write(&rec).expect("Failed to write record.");
2069            }
2070        }
2071
2072        {
2073            let mut bam = Reader::from_path(bampath).expect("Error opening file.");
2074
2075            for i in 0..names.len() {
2076                let mut rec = record::Record::new();
2077                if let Some(r) = bam.read(&mut rec) {
2078                    r.expect("Failed to read record.");
2079                };
2080
2081                assert_eq!(rec.qname(), names[i]);
2082                assert_eq!(*rec.cigar(), cigars[i]);
2083                assert_eq!(rec.seq().as_bytes(), seqs[i]);
2084                assert_eq!(rec.qual(), quals[i]);
2085                assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
2086            }
2087        }
2088
2089        tmp.close().expect("Failed to delete temp dir");
2090    }
2091
2092    #[test]
2093    fn test_write_threaded() {
2094        let (names, _, seqs, quals, cigars) = gold();
2095
2096        let tmp = tempfile::Builder::new()
2097            .prefix("rust-htslib")
2098            .tempdir()
2099            .expect("Cannot create temp dir");
2100        let bampath = tmp.path().join("test.bam");
2101        println!("{:?}", bampath);
2102        {
2103            let mut bam = Writer::from_path(
2104                &bampath,
2105                Header::new().push_record(
2106                    HeaderRecord::new(b"SQ")
2107                        .push_tag(b"SN", "chr1")
2108                        .push_tag(b"LN", 15072423),
2109                ),
2110                Format::Bam,
2111            )
2112            .expect("Error opening file.");
2113            bam.set_threads(4).unwrap();
2114
2115            for i in 0..10000 {
2116                let mut rec = record::Record::new();
2117                let idx = i % names.len();
2118                rec.set(names[idx], Some(&cigars[idx]), seqs[idx], quals[idx]);
2119                rec.push_aux(b"NM", Aux::I32(15)).unwrap();
2120                rec.set_pos(i as i64);
2121
2122                bam.write(&rec).expect("Failed to write record.");
2123            }
2124        }
2125
2126        {
2127            let mut bam = Reader::from_path(bampath).expect("Error opening file.");
2128
2129            for (i, _rec) in bam.records().enumerate() {
2130                let idx = i % names.len();
2131
2132                let rec = _rec.expect("Failed to read record.");
2133
2134                assert_eq!(rec.pos(), i as i64);
2135                assert_eq!(rec.qname(), names[idx]);
2136                assert_eq!(*rec.cigar(), cigars[idx]);
2137                assert_eq!(rec.seq().as_bytes(), seqs[idx]);
2138                assert_eq!(rec.qual(), quals[idx]);
2139                assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
2140            }
2141        }
2142
2143        tmp.close().expect("Failed to delete temp dir");
2144    }
2145
2146    #[test]
2147    fn test_write_shared_tpool() {
2148        let (names, _, seqs, quals, cigars) = gold();
2149
2150        let tmp = tempfile::Builder::new()
2151            .prefix("rust-htslib")
2152            .tempdir()
2153            .expect("Cannot create temp dir");
2154        let bampath1 = tmp.path().join("test1.bam");
2155        let bampath2 = tmp.path().join("test2.bam");
2156
2157        {
2158            let (mut bam1, mut bam2) = {
2159                let pool = crate::tpool::ThreadPool::new(4).unwrap();
2160
2161                let mut bam1 = Writer::from_path(
2162                    &bampath1,
2163                    Header::new().push_record(
2164                        HeaderRecord::new(b"SQ")
2165                            .push_tag(b"SN", "chr1")
2166                            .push_tag(b"LN", 15072423),
2167                    ),
2168                    Format::Bam,
2169                )
2170                .expect("Error opening file.");
2171
2172                let mut bam2 = Writer::from_path(
2173                    &bampath2,
2174                    Header::new().push_record(
2175                        HeaderRecord::new(b"SQ")
2176                            .push_tag(b"SN", "chr1")
2177                            .push_tag(b"LN", 15072423),
2178                    ),
2179                    Format::Bam,
2180                )
2181                .expect("Error opening file.");
2182
2183                bam1.set_thread_pool(&pool).unwrap();
2184                bam2.set_thread_pool(&pool).unwrap();
2185                (bam1, bam2)
2186            };
2187
2188            for i in 0..10000 {
2189                let mut rec = record::Record::new();
2190                let idx = i % names.len();
2191                rec.set(names[idx], Some(&cigars[idx]), seqs[idx], quals[idx]);
2192                rec.push_aux(b"NM", Aux::I32(15)).unwrap();
2193                rec.set_pos(i as i64);
2194
2195                bam1.write(&rec).expect("Failed to write record.");
2196                bam2.write(&rec).expect("Failed to write record.");
2197            }
2198        }
2199
2200        {
2201            let pool = crate::tpool::ThreadPool::new(2).unwrap();
2202
2203            for p in [bampath1, bampath2] {
2204                let mut bam = Reader::from_path(p).expect("Error opening file.");
2205                bam.set_thread_pool(&pool).unwrap();
2206
2207                for (i, _rec) in bam.iter_chunk(None, None).enumerate() {
2208                    let idx = i % names.len();
2209
2210                    let rec = _rec.expect("Failed to read record.");
2211
2212                    assert_eq!(rec.pos(), i as i64);
2213                    assert_eq!(rec.qname(), names[idx]);
2214                    assert_eq!(*rec.cigar(), cigars[idx]);
2215                    assert_eq!(rec.seq().as_bytes(), seqs[idx]);
2216                    assert_eq!(rec.qual(), quals[idx]);
2217                    assert_eq!(rec.aux(b"NM").unwrap(), Aux::I32(15));
2218                }
2219            }
2220        }
2221
2222        tmp.close().expect("Failed to delete temp dir");
2223    }
2224
2225    #[test]
2226    fn test_copy_template() {
2227        // Verify that BAM headers are transmitted correctly when using an existing BAM as a
2228        // template for headers.
2229
2230        let tmp = tempfile::Builder::new()
2231            .prefix("rust-htslib")
2232            .tempdir()
2233            .expect("Cannot create temp dir");
2234        let bampath = tmp.path().join("test.bam");
2235        println!("{:?}", bampath);
2236
2237        let mut input_bam = Reader::from_path("test/test.bam").expect("Error opening file.");
2238
2239        {
2240            let mut bam = Writer::from_path(
2241                &bampath,
2242                &Header::from_template(input_bam.header()),
2243                Format::Bam,
2244            )
2245            .expect("Error opening file.");
2246
2247            for rec in input_bam.records() {
2248                bam.write(&rec.unwrap()).expect("Failed to write record.");
2249            }
2250        }
2251
2252        {
2253            let copy_bam = Reader::from_path(bampath).expect("Error opening file.");
2254
2255            // Verify that the header came across correctly
2256            assert_eq!(input_bam.header().as_bytes(), copy_bam.header().as_bytes());
2257        }
2258
2259        tmp.close().expect("Failed to delete temp dir");
2260    }
2261
2262    #[test]
2263    fn test_pileup() {
2264        let (_, _, seqs, quals, _) = gold();
2265
2266        let mut bam = Reader::from_path("test/test.bam").expect("Error opening file.");
2267        let pileups = bam.pileup();
2268        for pileup in pileups.take(26) {
2269            let _pileup = pileup.expect("Expected successful pileup.");
2270            let pos = _pileup.pos() as usize;
2271            assert_eq!(_pileup.depth(), 6);
2272            assert!(_pileup.tid() == 0);
2273            for (i, a) in _pileup.alignments().enumerate() {
2274                assert_eq!(a.indel(), pileup::Indel::None);
2275                let qpos = a.qpos().unwrap();
2276                assert_eq!(qpos, pos - 1);
2277                assert_eq!(a.record().seq()[qpos], seqs[i][qpos]);
2278                assert_eq!(a.record().qual()[qpos], quals[i][qpos] - 33);
2279            }
2280        }
2281    }
2282
2283    #[test]
2284    fn test_idx_pileup() {
2285        let mut bam = IndexedReader::from_path("test/test.bam").expect("Error opening file.");
2286        // read without fetch
2287        for pileup in bam.pileup() {
2288            pileup.unwrap();
2289        }
2290        // go back again
2291        let tid = bam.header().tid(b"CHROMOSOME_I").unwrap();
2292        bam.fetch((tid, 0, 5)).unwrap();
2293        for p in bam.pileup() {
2294            println!("{}", p.unwrap().pos())
2295        }
2296    }
2297
2298    #[test]
2299    fn parse_from_sam() {
2300        use std::fs::File;
2301        use std::io::Read;
2302
2303        let bamfile = "./test/bam2sam_test.bam";
2304        let samfile = "./test/bam2sam_expected.sam";
2305
2306        // Load BAM file:
2307        let mut rdr = Reader::from_path(bamfile).unwrap();
2308        let bam_recs: Vec<Record> = rdr.records().map(|v| v.unwrap()).collect();
2309
2310        let mut sam = Vec::new();
2311        assert!(File::open(samfile).unwrap().read_to_end(&mut sam).is_ok());
2312
2313        let sam_recs: Vec<Record> = sam
2314            .split(|x| *x == b'\n')
2315            .filter(|x| !x.is_empty() && x[0] != b'@')
2316            .map(|line| Record::from_sam(rdr.header(), line).unwrap())
2317            .collect();
2318
2319        for (b1, s1) in bam_recs.iter().zip(sam_recs.iter()) {
2320            assert!(b1 == s1);
2321        }
2322    }
2323
2324    #[test]
2325    fn test_cigar_modes() {
2326        // test the cached and uncached ways of getting the cigar string.
2327
2328        let (_, _, _, _, cigars) = gold();
2329        let mut bam = Reader::from_path(Path::new("test/test.bam")).expect("Error opening file.");
2330
2331        for (i, record) in bam.records().enumerate() {
2332            let rec = record.expect("Expected valid record");
2333
2334            let cigar = rec.cigar();
2335            assert_eq!(*cigar, cigars[i]);
2336        }
2337
2338        for (i, record) in bam.records().enumerate() {
2339            let mut rec = record.expect("Expected valid record");
2340            rec.cache_cigar();
2341
2342            let cigar = rec.cigar_cached().unwrap();
2343            assert_eq!(**cigar, cigars[i]);
2344
2345            let cigar = rec.cigar();
2346            assert_eq!(*cigar, cigars[i]);
2347        }
2348    }
2349
2350    #[test]
2351    fn test_read_cram() {
2352        let cram_path = "./test/test_cram.cram";
2353        let bam_path = "./test/test_cram.bam";
2354        let ref_path = "./test/test_cram.fa";
2355
2356        // Load CRAM file, records
2357        let mut cram_reader = Reader::from_path(cram_path).unwrap();
2358        cram_reader.set_reference(ref_path).unwrap();
2359        let cram_records: Vec<Record> = cram_reader.records().map(|v| v.unwrap()).collect();
2360
2361        // Load BAM file, records
2362        let mut bam_reader = Reader::from_path(bam_path).unwrap();
2363        let bam_records: Vec<Record> = bam_reader.records().map(|v| v.unwrap()).collect();
2364
2365        compare_inner_bam_cram_records(&cram_records, &bam_records);
2366    }
2367
2368    #[test]
2369    fn test_write_cram() {
2370        // BAM file, records
2371        let bam_path = "./test/test_cram.bam";
2372        let ref_path = "./test/test_cram.fa";
2373        let mut bam_reader = Reader::from_path(bam_path).unwrap();
2374        let bam_records: Vec<Record> = bam_reader.records().map(|v| v.unwrap()).collect();
2375
2376        // New CRAM file
2377        let tmp = tempfile::Builder::new()
2378            .prefix("rust-htslib")
2379            .tempdir()
2380            .expect("Cannot create temp dir");
2381        let cram_path = tmp.path().join("test.cram");
2382
2383        // Write BAM records to new CRAM file
2384        {
2385            let mut header = Header::new();
2386            header.push_record(
2387                HeaderRecord::new(b"HD")
2388                    .push_tag(b"VN", "1.5")
2389                    .push_tag(b"SO", "coordinate"),
2390            );
2391            header.push_record(
2392                HeaderRecord::new(b"SQ")
2393                    .push_tag(b"SN", "chr1")
2394                    .push_tag(b"LN", 120)
2395                    .push_tag(b"M5", "20a9a0fb770814e6c5e49946750f9724")
2396                    .push_tag(b"UR", "test/test_cram.fa"),
2397            );
2398            header.push_record(
2399                HeaderRecord::new(b"SQ")
2400                    .push_tag(b"SN", "chr2")
2401                    .push_tag(b"LN", 120)
2402                    .push_tag(b"M5", "7a2006ccca94ea92b6dae5997e1b0d70")
2403                    .push_tag(b"UR", "test/test_cram.fa"),
2404            );
2405            header.push_record(
2406                HeaderRecord::new(b"SQ")
2407                    .push_tag(b"SN", "chr3")
2408                    .push_tag(b"LN", 120)
2409                    .push_tag(b"M5", "a66b336bfe3ee8801c744c9545c87e24")
2410                    .push_tag(b"UR", "test/test_cram.fa"),
2411            );
2412
2413            let mut cram_writer = Writer::from_path(&cram_path, &header, Format::Cram)
2414                .expect("Error opening CRAM file.");
2415            cram_writer.set_reference(ref_path).unwrap();
2416
2417            // Write BAM records to CRAM file
2418            for rec in bam_records.iter() {
2419                cram_writer
2420                    .write(rec)
2421                    .expect("Faied to write record to CRAM.");
2422            }
2423        }
2424
2425        // Compare written CRAM records with BAM records
2426        {
2427            // Load written CRAM file
2428            let mut cram_reader = Reader::from_path(cram_path).unwrap();
2429            cram_reader.set_reference(ref_path).unwrap();
2430            let cram_records: Vec<Record> = cram_reader.records().map(|v| v.unwrap()).collect();
2431
2432            // Compare CRAM records to BAM records
2433            compare_inner_bam_cram_records(&cram_records, &bam_records);
2434        }
2435
2436        tmp.close().expect("Failed to delete temp dir");
2437    }
2438
2439    #[test]
2440    fn test_compression_level_conversion() {
2441        // predefined compression levels
2442        assert_eq!(CompressionLevel::Uncompressed.convert().unwrap(), 0);
2443        assert_eq!(CompressionLevel::Fastest.convert().unwrap(), 1);
2444        assert_eq!(CompressionLevel::Maximum.convert().unwrap(), 9);
2445
2446        // numeric compression levels
2447        for level in 0..=9 {
2448            assert_eq!(CompressionLevel::Level(level).convert().unwrap(), level);
2449        }
2450        // invalid levels
2451        assert!(CompressionLevel::Level(10).convert().is_err());
2452    }
2453
2454    #[test]
2455    fn test_write_compression() {
2456        let tmp = tempfile::Builder::new()
2457            .prefix("rust-htslib")
2458            .tempdir()
2459            .expect("Cannot create temp dir");
2460        let input_bam_path = "test/test.bam";
2461
2462        // test levels with decreasing compression factor
2463        let levels_to_test = vec![
2464            CompressionLevel::Maximum,
2465            CompressionLevel::Level(6),
2466            CompressionLevel::Fastest,
2467            CompressionLevel::Uncompressed,
2468        ];
2469        let file_sizes: Vec<_> = levels_to_test
2470            .iter()
2471            .map(|level| {
2472                let output_bam_path = tmp.path().join("test.bam");
2473                {
2474                    let mut reader = Reader::from_path(input_bam_path).unwrap();
2475                    let header = Header::from_template(reader.header());
2476                    let mut writer =
2477                        Writer::from_path(&output_bam_path, &header, Format::Bam).unwrap();
2478                    writer.set_compression_level(*level).unwrap();
2479                    for record in reader.records() {
2480                        let r = record.unwrap();
2481                        writer.write(&r).unwrap();
2482                    }
2483                }
2484                fs::metadata(output_bam_path).unwrap().len()
2485            })
2486            .collect();
2487
2488        // check that out BAM file sizes are in decreasing order, in line with the expected compression factor
2489        println!("testing compression leves: {:?}", levels_to_test);
2490        println!("got compressed sizes: {:?}", file_sizes);
2491
2492        // libdeflate comes out with a slightly bigger file on Max compression
2493        // than on Level(6), so skip that check
2494        #[cfg(feature = "libdeflate")]
2495        assert!(file_sizes[1..].windows(2).all(|size| size[0] <= size[1]));
2496
2497        #[cfg(not(feature = "libdeflate"))]
2498        assert!(file_sizes.windows(2).all(|size| size[0] <= size[1]));
2499
2500        tmp.close().expect("Failed to delete temp dir");
2501    }
2502
2503    #[test]
2504    fn test_bam_fails_on_vcf() {
2505        let bam_path = "./test/test_left.vcf";
2506        let bam_reader = Reader::from_path(bam_path);
2507        assert!(bam_reader.is_err());
2508    }
2509
2510    #[test]
2511    fn test_indexde_bam_fails_on_vcf() {
2512        let bam_path = "./test/test_left.vcf";
2513        let bam_reader = IndexedReader::from_path(bam_path);
2514        assert!(bam_reader.is_err());
2515    }
2516
2517    #[test]
2518    fn test_bam_fails_on_toml() {
2519        let bam_path = "./Cargo.toml";
2520        let bam_reader = Reader::from_path(bam_path);
2521        assert!(bam_reader.is_err());
2522    }
2523
2524    #[test]
2525    fn test_sam_writer_example() {
2526        fn from_bam_with_filter<F>(bamfile: &str, samfile: &str, f: F) -> bool
2527        where
2528            F: Fn(&record::Record) -> Option<bool>,
2529        {
2530            let mut bam_reader = Reader::from_path(bamfile).unwrap(); // internal functions, just unwrap
2531            let header = header::Header::from_template(bam_reader.header());
2532            let mut sam_writer = Writer::from_path(samfile, &header, Format::Sam).unwrap();
2533            for record in bam_reader.records() {
2534                if record.is_err() {
2535                    return false;
2536                }
2537                let parsed = record.unwrap();
2538                match f(&parsed) {
2539                    None => return true,
2540                    Some(false) => {}
2541                    Some(true) => {
2542                        if sam_writer.write(&parsed).is_err() {
2543                            return false;
2544                        }
2545                    }
2546                }
2547            }
2548            true
2549        }
2550        use std::fs::File;
2551        use std::io::Read;
2552        let bamfile = "./test/bam2sam_test.bam";
2553        let samfile = "./test/bam2sam_out.sam";
2554        let expectedfile = "./test/bam2sam_expected.sam";
2555        let result = from_bam_with_filter(bamfile, samfile, |_| Some(true));
2556        assert!(result);
2557        let mut expected = Vec::new();
2558        let mut written = Vec::new();
2559        assert!(
2560            File::open(expectedfile)
2561                .unwrap()
2562                .read_to_end(&mut expected)
2563                .is_ok()
2564        );
2565        assert!(
2566            File::open(samfile)
2567                .unwrap()
2568                .read_to_end(&mut written)
2569                .is_ok()
2570        );
2571        assert_eq!(expected, written);
2572    }
2573
2574    // #[cfg(feature = "curl")]
2575    // #[test]
2576    // fn test_http_connect() {
2577    //     let url: Url = Url::parse(
2578    //         "https://raw.githubusercontent.com/brainstorm/tiny-test-data/master/wgs/mt.bam",
2579    //     )
2580    //     .unwrap();
2581    //     let r = Reader::from_url(&url);
2582    //     println!("{:#?}", r);
2583    //     let r = r.unwrap();
2584
2585    //     assert_eq!(r.header().target_names()[0], b"chr1");
2586    // }
2587
2588    #[test]
2589    fn test_rc_records() {
2590        let (names, flags, seqs, quals, cigars) = gold();
2591        let mut bam = Reader::from_path(Path::new("test/test.bam")).expect("Error opening file.");
2592        let del_len = [1, 1, 1, 1, 1, 100000];
2593
2594        for (i, record) in bam.rc_records().enumerate() {
2595            //let rec = record.expect("Expected valid record");
2596            let rec = record.unwrap();
2597            println!("{}", str::from_utf8(rec.qname()).ok().unwrap());
2598            assert_eq!(rec.qname(), names[i]);
2599            assert_eq!(rec.flags(), flags[i]);
2600            assert_eq!(rec.seq().as_bytes(), seqs[i]);
2601
2602            let cigar = rec.cigar();
2603            assert_eq!(*cigar, cigars[i]);
2604
2605            let end_pos = cigar.end_pos();
2606            assert_eq!(end_pos, rec.pos() + 100 + del_len[i]);
2607            assert_eq!(
2608                cigar
2609                    .read_pos(end_pos as u32 - 10, false, false)
2610                    .unwrap()
2611                    .unwrap(),
2612                90
2613            );
2614            assert_eq!(
2615                cigar
2616                    .read_pos(rec.pos() as u32 + 20, false, false)
2617                    .unwrap()
2618                    .unwrap(),
2619                20
2620            );
2621            assert_eq!(cigar.read_pos(4000000, false, false).unwrap(), None);
2622            // fix qual offset
2623            let qual: Vec<u8> = quals[i].iter().map(|&q| q - 33).collect();
2624            assert_eq!(rec.qual(), &qual[..]);
2625        }
2626    }
2627
2628    #[test]
2629    fn test_aux_arrays() {
2630        let bam_header = Header::new();
2631        let mut test_record = Record::from_sam(
2632            &HeaderView::from_header(&bam_header),
2633            "ali1\t4\t*\t0\t0\t*\t*\t0\t0\tACGT\tFFFF".as_bytes(),
2634        )
2635        .unwrap();
2636
2637        let array_i8: Vec<i8> = vec![i8::MIN, -1, 0, 1, i8::MAX];
2638        let array_u8: Vec<u8> = vec![u8::MIN, 0, 1, u8::MAX];
2639        let array_i16: Vec<i16> = vec![i16::MIN, -1, 0, 1, i16::MAX];
2640        let array_u16: Vec<u16> = vec![u16::MIN, 0, 1, u16::MAX];
2641        let array_i32: Vec<i32> = vec![i32::MIN, -1, 0, 1, i32::MAX];
2642        let array_u32: Vec<u32> = vec![u32::MIN, 0, 1, u32::MAX];
2643        let array_f32: Vec<f32> = vec![f32::MIN, 0.0, -0.0, 0.1, 0.99, f32::MAX];
2644
2645        test_record
2646            .push_aux(b"XA", Aux::ArrayI8((&array_i8).into()))
2647            .unwrap();
2648        test_record
2649            .push_aux(b"XB", Aux::ArrayU8((&array_u8).into()))
2650            .unwrap();
2651        test_record
2652            .push_aux(b"XC", Aux::ArrayI16((&array_i16).into()))
2653            .unwrap();
2654        test_record
2655            .push_aux(b"XD", Aux::ArrayU16((&array_u16).into()))
2656            .unwrap();
2657        test_record
2658            .push_aux(b"XE", Aux::ArrayI32((&array_i32).into()))
2659            .unwrap();
2660        test_record
2661            .push_aux(b"XF", Aux::ArrayU32((&array_u32).into()))
2662            .unwrap();
2663        test_record
2664            .push_aux(b"XG", Aux::ArrayFloat((&array_f32).into()))
2665            .unwrap();
2666
2667        {
2668            let tag = b"XA";
2669            if let Ok(Aux::ArrayI8(array)) = test_record.aux(tag) {
2670                // Retrieve aux array
2671                let aux_array_content = array.iter().collect::<Vec<_>>();
2672                assert_eq!(aux_array_content, array_i8);
2673
2674                // Copy the stored aux array to another record
2675                {
2676                    let mut copy_test_record = test_record.clone();
2677
2678                    // Pushing a field with an existing tag should fail
2679                    assert!(copy_test_record.push_aux(tag, Aux::I8(3)).is_err());
2680
2681                    // Remove aux array from target record
2682                    copy_test_record.remove_aux(tag).unwrap();
2683                    assert!(copy_test_record.aux(tag).is_err());
2684
2685                    // Copy array to target record
2686                    let src_aux = test_record.aux(tag).unwrap();
2687                    assert!(copy_test_record.push_aux(tag, src_aux).is_ok());
2688                    if let Ok(Aux::ArrayI8(array)) = copy_test_record.aux(tag) {
2689                        let aux_array_content_copied = array.iter().collect::<Vec<_>>();
2690                        assert_eq!(aux_array_content_copied, array_i8);
2691                    } else {
2692                        panic!("Aux tag not found");
2693                    }
2694                }
2695            } else {
2696                panic!("Aux tag not found");
2697            }
2698        }
2699
2700        {
2701            let tag = b"XB";
2702            if let Ok(Aux::ArrayU8(array)) = test_record.aux(tag) {
2703                // Retrieve aux array
2704                let aux_array_content = array.iter().collect::<Vec<_>>();
2705                assert_eq!(aux_array_content, array_u8);
2706
2707                // Copy the stored aux array to another record
2708                {
2709                    let mut copy_test_record = test_record.clone();
2710
2711                    // Pushing a field with an existing tag should fail
2712                    assert!(copy_test_record.push_aux(tag, Aux::U8(3)).is_err());
2713
2714                    // Remove aux array from target record
2715                    copy_test_record.remove_aux(tag).unwrap();
2716                    assert!(copy_test_record.aux(tag).is_err());
2717
2718                    // Copy array to target record
2719                    let src_aux = test_record.aux(tag).unwrap();
2720                    assert!(copy_test_record.push_aux(tag, src_aux).is_ok());
2721                    if let Ok(Aux::ArrayU8(array)) = copy_test_record.aux(tag) {
2722                        let aux_array_content_copied = array.iter().collect::<Vec<_>>();
2723                        assert_eq!(aux_array_content_copied, array_u8);
2724                    } else {
2725                        panic!("Aux tag not found");
2726                    }
2727                }
2728            } else {
2729                panic!("Aux tag not found");
2730            }
2731        }
2732
2733        {
2734            let tag = b"XC";
2735            if let Ok(Aux::ArrayI16(array)) = test_record.aux(tag) {
2736                // Retrieve aux array
2737                let aux_array_content = array.iter().collect::<Vec<_>>();
2738                assert_eq!(aux_array_content, array_i16);
2739
2740                // Copy the stored aux array to another record
2741                {
2742                    let mut copy_test_record = test_record.clone();
2743
2744                    // Pushing a field with an existing tag should fail
2745                    assert!(copy_test_record.push_aux(tag, Aux::I16(3)).is_err());
2746
2747                    // Remove aux array from target record
2748                    copy_test_record.remove_aux(tag).unwrap();
2749                    assert!(copy_test_record.aux(tag).is_err());
2750
2751                    // Copy array to target record
2752                    let src_aux = test_record.aux(tag).unwrap();
2753                    assert!(copy_test_record.push_aux(tag, src_aux).is_ok());
2754                    if let Ok(Aux::ArrayI16(array)) = copy_test_record.aux(tag) {
2755                        let aux_array_content_copied = array.iter().collect::<Vec<_>>();
2756                        assert_eq!(aux_array_content_copied, array_i16);
2757                    } else {
2758                        panic!("Aux tag not found");
2759                    }
2760                }
2761            } else {
2762                panic!("Aux tag not found");
2763            }
2764        }
2765
2766        {
2767            let tag = b"XD";
2768            if let Ok(Aux::ArrayU16(array)) = test_record.aux(tag) {
2769                // Retrieve aux array
2770                let aux_array_content = array.iter().collect::<Vec<_>>();
2771                assert_eq!(aux_array_content, array_u16);
2772
2773                // Copy the stored aux array to another record
2774                {
2775                    let mut copy_test_record = test_record.clone();
2776
2777                    // Pushing a field with an existing tag should fail
2778                    assert!(copy_test_record.push_aux(tag, Aux::U16(3)).is_err());
2779
2780                    // Remove aux array from target record
2781                    copy_test_record.remove_aux(tag).unwrap();
2782                    assert!(copy_test_record.aux(tag).is_err());
2783
2784                    // Copy array to target record
2785                    let src_aux = test_record.aux(tag).unwrap();
2786                    assert!(copy_test_record.push_aux(tag, src_aux).is_ok());
2787                    if let Ok(Aux::ArrayU16(array)) = copy_test_record.aux(tag) {
2788                        let aux_array_content_copied = array.iter().collect::<Vec<_>>();
2789                        assert_eq!(aux_array_content_copied, array_u16);
2790                    } else {
2791                        panic!("Aux tag not found");
2792                    }
2793                }
2794            } else {
2795                panic!("Aux tag not found");
2796            }
2797        }
2798
2799        {
2800            let tag = b"XE";
2801            if let Ok(Aux::ArrayI32(array)) = test_record.aux(tag) {
2802                // Retrieve aux array
2803                let aux_array_content = array.iter().collect::<Vec<_>>();
2804                assert_eq!(aux_array_content, array_i32);
2805
2806                // Copy the stored aux array to another record
2807                {
2808                    let mut copy_test_record = test_record.clone();
2809
2810                    // Pushing a field with an existing tag should fail
2811                    assert!(copy_test_record.push_aux(tag, Aux::I32(3)).is_err());
2812
2813                    // Remove aux array from target record
2814                    copy_test_record.remove_aux(tag).unwrap();
2815                    assert!(copy_test_record.aux(tag).is_err());
2816
2817                    // Copy array to target record
2818                    let src_aux = test_record.aux(tag).unwrap();
2819                    assert!(copy_test_record.push_aux(tag, src_aux).is_ok());
2820                    if let Ok(Aux::ArrayI32(array)) = copy_test_record.aux(tag) {
2821                        let aux_array_content_copied = array.iter().collect::<Vec<_>>();
2822                        assert_eq!(aux_array_content_copied, array_i32);
2823                    } else {
2824                        panic!("Aux tag not found");
2825                    }
2826                }
2827            } else {
2828                panic!("Aux tag not found");
2829            }
2830        }
2831
2832        {
2833            let tag = b"XF";
2834            if let Ok(Aux::ArrayU32(array)) = test_record.aux(tag) {
2835                // Retrieve aux array
2836                let aux_array_content = array.iter().collect::<Vec<_>>();
2837                assert_eq!(aux_array_content, array_u32);
2838
2839                // Copy the stored aux array to another record
2840                {
2841                    let mut copy_test_record = test_record.clone();
2842
2843                    // Pushing a field with an existing tag should fail
2844                    assert!(copy_test_record.push_aux(tag, Aux::U32(3)).is_err());
2845
2846                    // Remove aux array from target record
2847                    copy_test_record.remove_aux(tag).unwrap();
2848                    assert!(copy_test_record.aux(tag).is_err());
2849
2850                    // Copy array to target record
2851                    let src_aux = test_record.aux(tag).unwrap();
2852                    assert!(copy_test_record.push_aux(tag, src_aux).is_ok());
2853                    if let Ok(Aux::ArrayU32(array)) = copy_test_record.aux(tag) {
2854                        let aux_array_content_copied = array.iter().collect::<Vec<_>>();
2855                        assert_eq!(aux_array_content_copied, array_u32);
2856                    } else {
2857                        panic!("Aux tag not found");
2858                    }
2859                }
2860            } else {
2861                panic!("Aux tag not found");
2862            }
2863        }
2864
2865        {
2866            let tag = b"XG";
2867            if let Ok(Aux::ArrayFloat(array)) = test_record.aux(tag) {
2868                // Retrieve aux array
2869                let aux_array_content = array.iter().collect::<Vec<_>>();
2870                assert_eq!(aux_array_content, array_f32);
2871
2872                // Copy the stored aux array to another record
2873                {
2874                    let mut copy_test_record = test_record.clone();
2875
2876                    // Pushing a field with an existing tag should fail
2877                    assert!(copy_test_record.push_aux(tag, Aux::Float(3.0)).is_err());
2878
2879                    // Remove aux array from target record
2880                    copy_test_record.remove_aux(tag).unwrap();
2881                    assert!(copy_test_record.aux(tag).is_err());
2882
2883                    // Copy array to target record
2884                    let src_aux = test_record.aux(tag).unwrap();
2885                    assert!(copy_test_record.push_aux(tag, src_aux).is_ok());
2886                    if let Ok(Aux::ArrayFloat(array)) = copy_test_record.aux(tag) {
2887                        let aux_array_content_copied = array.iter().collect::<Vec<_>>();
2888                        assert_eq!(aux_array_content_copied, array_f32);
2889                    } else {
2890                        panic!("Aux tag not found");
2891                    }
2892                }
2893            } else {
2894                panic!("Aux tag not found");
2895            }
2896        }
2897
2898        // Test via `Iterator` impl
2899        for item in test_record.aux_iter() {
2900            match item.unwrap() {
2901                (b"XA", Aux::ArrayI8(array)) => {
2902                    assert_eq!(&array.iter().collect::<Vec<_>>(), &array_i8);
2903                }
2904                (b"XB", Aux::ArrayU8(array)) => {
2905                    assert_eq!(&array.iter().collect::<Vec<_>>(), &array_u8);
2906                }
2907                (b"XC", Aux::ArrayI16(array)) => {
2908                    assert_eq!(&array.iter().collect::<Vec<_>>(), &array_i16);
2909                }
2910                (b"XD", Aux::ArrayU16(array)) => {
2911                    assert_eq!(&array.iter().collect::<Vec<_>>(), &array_u16);
2912                }
2913                (b"XE", Aux::ArrayI32(array)) => {
2914                    assert_eq!(&array.iter().collect::<Vec<_>>(), &array_i32);
2915                }
2916                (b"XF", Aux::ArrayU32(array)) => {
2917                    assert_eq!(&array.iter().collect::<Vec<_>>(), &array_u32);
2918                }
2919                (b"XG", Aux::ArrayFloat(array)) => {
2920                    assert_eq!(&array.iter().collect::<Vec<_>>(), &array_f32);
2921                }
2922                _ => {
2923                    panic!();
2924                }
2925            }
2926        }
2927
2928        // Test via `PartialEq` impl
2929        assert_eq!(
2930            test_record.aux(b"XA").unwrap(),
2931            Aux::ArrayI8((&array_i8).into())
2932        );
2933        assert_eq!(
2934            test_record.aux(b"XB").unwrap(),
2935            Aux::ArrayU8((&array_u8).into())
2936        );
2937        assert_eq!(
2938            test_record.aux(b"XC").unwrap(),
2939            Aux::ArrayI16((&array_i16).into())
2940        );
2941        assert_eq!(
2942            test_record.aux(b"XD").unwrap(),
2943            Aux::ArrayU16((&array_u16).into())
2944        );
2945        assert_eq!(
2946            test_record.aux(b"XE").unwrap(),
2947            Aux::ArrayI32((&array_i32).into())
2948        );
2949        assert_eq!(
2950            test_record.aux(b"XF").unwrap(),
2951            Aux::ArrayU32((&array_u32).into())
2952        );
2953        assert_eq!(
2954            test_record.aux(b"XG").unwrap(),
2955            Aux::ArrayFloat((&array_f32).into())
2956        );
2957    }
2958
2959    #[test]
2960    fn test_aux_scalars() {
2961        let bam_header = Header::new();
2962        let mut test_record = Record::from_sam(
2963            &HeaderView::from_header(&bam_header),
2964            "ali1\t4\t*\t0\t0\t*\t*\t0\t0\tACGT\tFFFF".as_bytes(),
2965        )
2966        .unwrap();
2967
2968        test_record.push_aux(b"XA", Aux::I8(i8::MIN)).unwrap();
2969        test_record.push_aux(b"XB", Aux::I8(i8::MAX)).unwrap();
2970        test_record.push_aux(b"XC", Aux::U8(u8::MIN)).unwrap();
2971        test_record.push_aux(b"XD", Aux::U8(u8::MAX)).unwrap();
2972        test_record.push_aux(b"XE", Aux::I16(i16::MIN)).unwrap();
2973        test_record.push_aux(b"XF", Aux::I16(i16::MAX)).unwrap();
2974        test_record.push_aux(b"XG", Aux::U16(u16::MIN)).unwrap();
2975        test_record.push_aux(b"XH", Aux::U16(u16::MAX)).unwrap();
2976        test_record.push_aux(b"XI", Aux::I32(i32::MIN)).unwrap();
2977        test_record.push_aux(b"XJ", Aux::I32(i32::MAX)).unwrap();
2978        test_record.push_aux(b"XK", Aux::U32(u32::MIN)).unwrap();
2979        test_record.push_aux(b"XL", Aux::U32(u32::MAX)).unwrap();
2980        test_record
2981            .push_aux(b"XM", Aux::Float(std::f32::consts::PI))
2982            .unwrap();
2983        test_record
2984            .push_aux(b"XN", Aux::Double(std::f64::consts::PI))
2985            .unwrap();
2986        test_record
2987            .push_aux(b"XO", Aux::String("Test str"))
2988            .unwrap();
2989        test_record.push_aux(b"XP", Aux::I8(0)).unwrap();
2990
2991        let collected_aux_fields = test_record.aux_iter().collect::<Result<Vec<_>>>().unwrap();
2992        assert_eq!(
2993            collected_aux_fields,
2994            vec![
2995                (&b"XA"[..], Aux::I8(i8::MIN)),
2996                (&b"XB"[..], Aux::I8(i8::MAX)),
2997                (&b"XC"[..], Aux::U8(u8::MIN)),
2998                (&b"XD"[..], Aux::U8(u8::MAX)),
2999                (&b"XE"[..], Aux::I16(i16::MIN)),
3000                (&b"XF"[..], Aux::I16(i16::MAX)),
3001                (&b"XG"[..], Aux::U16(u16::MIN)),
3002                (&b"XH"[..], Aux::U16(u16::MAX)),
3003                (&b"XI"[..], Aux::I32(i32::MIN)),
3004                (&b"XJ"[..], Aux::I32(i32::MAX)),
3005                (&b"XK"[..], Aux::U32(u32::MIN)),
3006                (&b"XL"[..], Aux::U32(u32::MAX)),
3007                (&b"XM"[..], Aux::Float(std::f32::consts::PI)),
3008                (&b"XN"[..], Aux::Double(std::f64::consts::PI)),
3009                (&b"XO"[..], Aux::String("Test str")),
3010                (&b"XP"[..], Aux::I8(0)),
3011            ]
3012        );
3013    }
3014
3015    #[test]
3016    fn test_aux_array_partial_eq() {
3017        use record::AuxArray;
3018
3019        // Target types
3020        let one_data: Vec<i8> = vec![0, 1, 2, 3, 4, 5, 6];
3021        let one_aux_array = AuxArray::from(&one_data);
3022
3023        let two_data: Vec<i8> = vec![0, 1, 2, 3, 4, 5];
3024        let two_aux_array = AuxArray::from(&two_data);
3025
3026        assert_ne!(&one_data, &two_data);
3027        assert_ne!(&one_aux_array, &two_aux_array);
3028
3029        let one_aux = Aux::ArrayI8(one_aux_array);
3030        let two_aux = Aux::ArrayI8(two_aux_array);
3031        assert_ne!(&one_aux, &two_aux);
3032
3033        // Raw bytes
3034        let bam_header = Header::new();
3035        let mut test_record = Record::from_sam(
3036            &HeaderView::from_header(&bam_header),
3037            "ali1\t4\t*\t0\t0\t*\t*\t0\t0\tACGT\tFFFF".as_bytes(),
3038        )
3039        .unwrap();
3040
3041        test_record.push_aux(b"XA", one_aux).unwrap();
3042        test_record.push_aux(b"XB", two_aux).unwrap();
3043
3044        // RawLeBytes == RawLeBytes
3045        assert_eq!(
3046            test_record.aux(b"XA").unwrap(),
3047            test_record.aux(b"XA").unwrap()
3048        );
3049        // RawLeBytes != RawLeBytes
3050        assert_ne!(
3051            test_record.aux(b"XA").unwrap(),
3052            test_record.aux(b"XB").unwrap()
3053        );
3054
3055        // RawLeBytes == TargetType
3056        assert_eq!(
3057            test_record.aux(b"XA").unwrap(),
3058            Aux::ArrayI8((&one_data).into())
3059        );
3060        assert_eq!(
3061            test_record.aux(b"XB").unwrap(),
3062            Aux::ArrayI8((&two_data).into())
3063        );
3064        // RawLeBytes != TargetType
3065        assert_ne!(
3066            test_record.aux(b"XA").unwrap(),
3067            Aux::ArrayI8((&two_data).into())
3068        );
3069        assert_ne!(
3070            test_record.aux(b"XB").unwrap(),
3071            Aux::ArrayI8((&one_data).into())
3072        );
3073    }
3074
3075    /// Test if both text and binary representations of a BAM header are in sync (#156)
3076    #[test]
3077    fn test_bam_header_sync() {
3078        let reader = Reader::from_path("test/test_issue_156_no_text.bam").unwrap();
3079        let header_hashmap = Header::from_template(reader.header()).to_hashmap().unwrap();
3080        let header_refseqs = header_hashmap.get("SQ").unwrap();
3081
3082        assert_eq!(header_refseqs[0].get("SN").unwrap(), "ref_1",);
3083        assert_eq!(header_refseqs[0].get("LN").unwrap(), "10000000",);
3084    }
3085
3086    #[test]
3087    fn test_bam_new() {
3088        // Create the path to write the tmp test BAM
3089        let tmp = tempfile::Builder::new()
3090            .prefix("rust-htslib")
3091            .tempdir()
3092            .expect("Cannot create temp dir");
3093        let bampath = tmp.path().join("test.bam");
3094
3095        // write an unmapped BAM record (uBAM)
3096        {
3097            // Build the header
3098            let mut header = Header::new();
3099
3100            // Add the version
3101            header.push_record(
3102                HeaderRecord::new(b"HD")
3103                    .push_tag(b"VN", "1.6")
3104                    .push_tag(b"SO", "unsorted"),
3105            );
3106
3107            // Build the writer
3108            let mut writer = Writer::from_path(&bampath, &header, Format::Bam).unwrap();
3109
3110            // Build an empty record
3111            let record = Record::new();
3112
3113            // Write the record (this previously seg-faulted)
3114            assert!(writer.write(&record).is_ok());
3115        }
3116
3117        // Read the record
3118        {
3119            // Build th reader
3120            let mut reader = Reader::from_path(bampath).expect("Error opening file.");
3121
3122            // Read the record
3123            let mut rec = Record::new();
3124            match reader.read(&mut rec) {
3125                Some(r) => r.expect("Failed to read record."),
3126                None => panic!("No record read."),
3127            };
3128
3129            // Check a few things
3130            assert!(rec.is_unmapped());
3131            assert_eq!(rec.tid(), -1);
3132            assert_eq!(rec.pos(), -1);
3133            assert_eq!(rec.mtid(), -1);
3134            assert_eq!(rec.mpos(), -1);
3135        }
3136    }
3137
3138    #[test]
3139    fn test_idxstats_bam() {
3140        let mut reader = IndexedReader::from_path("test/test.bam").unwrap();
3141        let expected = vec![
3142            (0, 15072423, 6, 0),
3143            (1, 15279345, 0, 0),
3144            (2, 13783700, 0, 0),
3145            (3, 17493793, 0, 0),
3146            (4, 20924149, 0, 0),
3147            (-1, 0, 0, 0),
3148        ];
3149        let actual = reader.index_stats().unwrap();
3150        assert_eq!(expected, actual);
3151    }
3152
3153    #[test]
3154    fn test_number_mapped_and_unmapped_bam() {
3155        let reader = IndexedReader::from_path("test/test.bam").unwrap();
3156        let expected = (6, 0);
3157        let actual = reader.index().number_mapped_unmapped(0);
3158        assert_eq!(expected, actual);
3159    }
3160
3161    #[test]
3162    fn test_number_unmapped_global_bam() {
3163        let reader = IndexedReader::from_path("test/test_unmapped.bam").unwrap();
3164        let expected = 8;
3165        let actual = reader.index().number_unmapped();
3166        assert_eq!(expected, actual);
3167    }
3168
3169    #[test]
3170    fn test_idxstats_cram() {
3171        let mut reader = IndexedReader::from_path("test/test_cram.cram").unwrap();
3172        reader.set_reference("test/test_cram.fa").unwrap();
3173        let expected = vec![
3174            (0, 120, 2, 0),
3175            (1, 120, 2, 0),
3176            (2, 120, 2, 0),
3177            (-1, 0, 0, 0),
3178        ];
3179        let actual = reader.index_stats().unwrap();
3180        assert_eq!(expected, actual);
3181    }
3182
3183    #[test]
3184    fn test_slow_idxstats_cram() {
3185        let mut reader = IndexedReader::from_path("test/test_cram.cram").unwrap();
3186        reader.set_reference("test/test_cram.fa").unwrap();
3187        let expected = vec![
3188            (0, 120, 2, 0),
3189            (1, 120, 2, 0),
3190            (2, 120, 2, 0),
3191            (-1, 0, 0, 0),
3192        ];
3193        let actual = reader.index_stats().unwrap();
3194        assert_eq!(expected, actual);
3195    }
3196
3197    #[test]
3198    fn test_slow_idxstats_cram_unmapped() {
3199        let mut reader = IndexedReader::from_path("test/test_cram_unmapped.cram").unwrap();
3200        reader.set_reference("test/test_cram.fa").unwrap();
3201        let expected = vec![
3202            (0, 120, 2, 0),
3203            (1, 120, 2, 0),
3204            (2, 120, 2, 0),
3205            (-1, 0, 0, 2),
3206        ];
3207        let actual = reader.index_stats().unwrap();
3208        assert_eq!(expected, actual);
3209    }
3210
3211    #[test]
3212    fn test_nonexistent_tidname() {
3213        let header = Header::new();
3214        let header_view = HeaderView::from_header(&header);
3215        assert_eq!(b"", header_view.tid2name(0));
3216    }
3217
3218    // #[test]
3219    // fn test_number_mapped_and_unmapped_cram() {
3220    //     let mut reader = IndexedReader::from_path("test/test_cram.cram").unwrap();
3221    //     reader.set_reference("test/test_cram.fa").unwrap();
3222    //     let expected = (2, 0);
3223    //     let actual = reader.index().number_mapped_unmapped(0);
3224    //     assert_eq!(expected, actual);
3225    // }
3226    //
3227    // #[test]
3228    // fn test_number_unmapped_global_cram() {
3229    //     let mut reader = IndexedReader::from_path("test/test_unmapped.cram").unwrap();
3230    //     let expected = 8;
3231    //     let actual = reader.index().number_unmapped();
3232    //     assert_eq!(expected, actual);
3233    // }
3234}