diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d2112058d2..8849ebc500 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,2 +1,4 @@ pub mod test_calc; +pub mod test_covcalc; +pub mod test_filtering; pub mod test_normalization; diff --git a/src/tests/test_covcalc.rs b/src/tests/test_covcalc.rs new file mode 100644 index 0000000000..a094d58915 --- /dev/null +++ b/src/tests/test_covcalc.rs @@ -0,0 +1,156 @@ +use crate::covcalc::{Region, Revalue}; + +fn region_u(strand: &str, start: u32, end: u32) -> Region { + Region { + chrom: String::from("chr1"), + start: Revalue::U(start), + end: Revalue::U(end), + score: String::from("."), + strand: strand.to_string(), + name: String::from("test"), + regionlength: end - start, + } +} + +fn region_v(strand: &str, starts: Vec, ends: Vec) -> Region { + let regionlength = starts.iter().zip(ends.iter()).map(|(s, e)| e - s).sum(); + Region { + chrom: String::from("chr1"), + start: Revalue::V(starts), + end: Revalue::V(ends), + score: String::from("."), + strand: strand.to_string(), + name: String::from("test"), + regionlength, + } +} + +mod get_startu_endu_tests { + use super::*; + + #[test] + fn test_startu_endu_non_metagene() { + let r = region_u("+", 100, 200); + assert_eq!(r.get_startu(), 100); + assert_eq!(r.get_endu(), 200); + } + + #[test] + fn test_startu_endu_metagene_uses_outer_bounds() { + let r = region_v("+", vec![100, 150], vec![120, 200]); + assert_eq!(r.get_startu(), 100); + assert_eq!(r.get_endu(), 200); + } +} + +mod anchorpoint_tss_tests { + use super::*; + + #[test] + fn test_tss_plus_strand_non_metagene() { + let r = region_u("+", 100, 200); + assert_eq!(r.get_anchorpoint("TSS"), 100); + } + + #[test] + fn test_tss_dot_strand_treated_as_plus() { + let r = region_u(".", 100, 200); + assert_eq!(r.get_anchorpoint("TSS"), 100); + } + + #[test] + fn test_tss_minus_strand_non_metagene() { + let r = region_u("-", 100, 200); + assert_eq!(r.get_anchorpoint("TSS"), 200); + } + + #[test] + fn test_tss_plus_strand_metagene_uses_first_exon_start() { + let r = region_v("+", vec![100, 150], vec![120, 200]); + assert_eq!(r.get_anchorpoint("TSS"), 100); + } + + #[test] + fn test_tss_minus_strand_metagene_uses_last_exon_end() { + let r = region_v("-", vec![100, 150], vec![120, 200]); + assert_eq!(r.get_anchorpoint("TSS"), 200); + } + + #[test] + #[should_panic(expected = "Strand should either be")] + fn test_tss_invalid_strand_panics() { + let r = region_u("x", 100, 200); + r.get_anchorpoint("TSS"); + } +} + +mod anchorpoint_tes_tests { + use super::*; + + #[test] + fn test_tes_plus_strand_non_metagene() { + let r = region_u("+", 100, 200); + assert_eq!(r.get_anchorpoint("TES"), 200); + } + + #[test] + fn test_tes_minus_strand_non_metagene() { + let r = region_u("-", 100, 200); + assert_eq!(r.get_anchorpoint("TES"), 100); + } + + #[test] + fn test_tes_plus_strand_metagene_uses_last_exon_end() { + let r = region_v("+", vec![100, 150], vec![120, 200]); + assert_eq!(r.get_anchorpoint("TES"), 200); + } + + #[test] + fn test_tes_minus_strand_metagene_uses_first_exon_start() { + let r = region_v("-", vec![100, 150], vec![120, 200]); + assert_eq!(r.get_anchorpoint("TES"), 100); + } +} + +mod anchorpoint_center_tests { + use super::*; + + #[test] + fn test_center_non_metagene_is_midpoint() { + let r = region_u("+", 100, 200); + assert_eq!(r.get_anchorpoint("center"), 150); + } + + #[test] + fn test_center_ignores_strand() { + let plus = region_u("+", 100, 200); + let minus = region_u("-", 100, 200); + assert_eq!( + plus.get_anchorpoint("center"), + minus.get_anchorpoint("center") + ); + } + + #[test] + fn test_center_metagene_walks_exon_blocks() { + let r = region_v("+", vec![100, 150], vec![120, 200]); + assert_eq!(r.get_anchorpoint("center"), 165); + } + + #[test] + fn test_center_metagene_midpoint_in_first_exon() { + let r = region_v("+", vec![100, 300], vec![180, 320]); + assert_eq!(r.get_anchorpoint("center"), 150); + } +} + +mod anchorpoint_invalid_referencepoint_tests { + use super::*; + + #[test] + #[should_panic(expected = "Reference should either be TSS, TES or center")] + fn test_unknown_referencepoint_panics() { + let r = region_u("+", 100, 200); + r.get_anchorpoint("bogus"); + } +} diff --git a/src/tests/test_filtering.rs b/src/tests/test_filtering.rs new file mode 100644 index 0000000000..7b74efdc2b --- /dev/null +++ b/src/tests/test_filtering.rs @@ -0,0 +1,244 @@ +use crate::covcalc::{Region, Revalue}; +use crate::filtering::{Alignmentfilters, BlacklistIndex}; + +fn region(chrom: &str, start: u32, end: u32) -> Region { + Region { + chrom: chrom.to_string(), + start: Revalue::U(start), + end: Revalue::U(end), + score: String::from("."), + strand: String::from("+"), + name: String::from("test"), + regionlength: end - start, + } +} + +mod blacklist_index_tests { + use super::*; + + #[test] + fn test_contains_inside_region() { + let regions = vec![region("chr1", 100, 200)]; + let idx = BlacklistIndex::from_regions(®ions); + assert!(idx.contains("chr1", 150)); + } + + #[test] + fn test_contains_at_region_boundaries() { + let regions = vec![region("chr1", 100, 200)]; + let idx = BlacklistIndex::from_regions(®ions); + assert!(idx.contains("chr1", 100)); + assert!(idx.contains("chr1", 200)); + } + + #[test] + fn test_does_not_contain_outside_region() { + let regions = vec![region("chr1", 100, 200)]; + let idx = BlacklistIndex::from_regions(®ions); + assert!(!idx.contains("chr1", 99)); + assert!(!idx.contains("chr1", 201)); + } + + #[test] + fn test_unknown_chrom_returns_false() { + let regions = vec![region("chr1", 100, 200)]; + let idx = BlacklistIndex::from_regions(®ions); + assert!(!idx.contains("chr2", 150)); + } + + #[test] + fn test_multiple_non_overlapping_regions() { + let regions = vec![region("chr1", 100, 200), region("chr1", 500, 600)]; + let idx = BlacklistIndex::from_regions(®ions); + assert!(idx.contains("chr1", 550)); + assert!(!idx.contains("chr1", 300)); + } + + #[test] + fn test_unsorted_input_regions_are_handled() { + let regions = vec![region("chr1", 500, 600), region("chr1", 100, 200)]; + let idx = BlacklistIndex::from_regions(®ions); + assert!(idx.contains("chr1", 150)); + assert!(idx.contains("chr1", 550)); + } + + #[test] + fn test_empty_index_never_contains() { + let idx = BlacklistIndex::from_regions(&[]); + assert!(!idx.contains("chr1", 1)); + } +} + +mod alignmentfilters_new_tests { + use super::*; + + #[test] + fn test_defaults_disable_filter_and_manipulate() { + let af = Alignmentfilters::new( + None, None, None, None, None, None, None, None, None, None, None, None, + ); + assert!(!af.filter); + assert!(!af.manipulate); + } + + #[test] + fn test_min_mapping_quality_enables_filter() { + let af = Alignmentfilters::new( + None, + Some(10), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); + assert!(af.filter); + assert!(!af.manipulate); + assert_eq!(af.minmappingquality, 10); + } + + #[test] + fn test_blacklist_enables_filter() { + let regions = vec![region("chr1", 100, 200)]; + let af = Alignmentfilters::new( + Some(regions), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); + assert!(af.filter); + } + + #[test] + fn test_extendreads_enables_manipulate_and_defaults_to_auto() { + let af = Alignmentfilters::new( + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some(true), + None, + None, + ); + assert!(af.manipulate); + assert!(af.extendreads); + assert!(af.extendreads_auto); + } + + #[test] + fn test_extendreads_with_explicit_len_disables_auto() { + let af = Alignmentfilters::new( + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some(true), + Some(150), + None, + ); + assert!(af.manipulate); + assert!(!af.extendreads_auto); + assert_eq!(af.extendreadslen, 150); + } + + #[test] + fn test_offset_enables_manipulate_only() { + let af = Alignmentfilters::new( + None, + None, + None, + None, + None, + None, + None, + Some((1, 2)), + None, + None, + None, + None, + ); + assert!(af.manipulate); + assert!(!af.filter); + } + + #[test] + fn test_mnase_enables_manipulate_only() { + let af = Alignmentfilters::new( + None, + None, + None, + None, + None, + None, + Some(true), + None, + None, + None, + None, + None, + ); + assert!(af.manipulate); + assert!(!af.filter); + } + + #[test] + fn test_centerreads_alone_enables_manipulate() { + let af = Alignmentfilters::new( + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some(true), + ); + assert!(af.manipulate); + } + + #[test] + fn test_filterrnastrand_enables_filter() { + let af = Alignmentfilters::new( + None, + None, + None, + None, + None, + None, + None, + None, + Some(String::from("forward")), + None, + None, + None, + ); + assert!(af.filter); + } +}