Skip to main content

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