From 7951d3d83c3c74e12ffab65cc638f21e394feb06 Mon Sep 17 00:00:00 2001 From: SuhasSrinivasan <32346517+SuhasSrinivasan@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:58:05 -0700 Subject: [PATCH 1/2] Remove stray adjust-mods debug output --- modkit-core/src/modbam_util/subcommands.rs | 1 - modkit/tests/test_adjust_mods.rs | 25 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/modkit-core/src/modbam_util/subcommands.rs b/modkit-core/src/modbam_util/subcommands.rs index 509b270b..6a84dfc4 100644 --- a/modkit-core/src/modbam_util/subcommands.rs +++ b/modkit-core/src/modbam_util/subcommands.rs @@ -776,7 +776,6 @@ impl Adjust { .map(|raw| parse_edge_filter_input(raw, self.invert_edge_filter)) .transpose()?; - dbg!(&self.cpg); let motifs = parse_forward_motifs(&self.motif, self.cpg)?; if let Some(ms) = motifs.as_ref() { let patterns = ms.iter().map(|x| x.as_str()).join(","); diff --git a/modkit/tests/test_adjust_mods.rs b/modkit/tests/test_adjust_mods.rs index 76ae5962..bf58b5c1 100644 --- a/modkit/tests/test_adjust_mods.rs +++ b/modkit/tests/test_adjust_mods.rs @@ -5,6 +5,8 @@ use mod_kit::mod_bam::RawModTags; use mod_kit::mod_base_code::{BaseState, DnaBase}; use rust_htslib::{bam, bam::Read}; use std::path::PathBuf; +use std::process::Command; +use tempfile::tempdir; mod common; @@ -14,6 +16,29 @@ fn test_help() { let _out = run_modkit(&pileup_help_args).unwrap(); } +#[test] +fn test_adjust_mods_does_not_dump_cpg_to_stderr() { + let temp_dir = tempdir().unwrap(); + let output_bam = temp_dir.path().join("adjusted.bam"); + let output = Command::new(env!("CARGO_BIN_EXE_modkit")) + .args([ + "adjust-mods", + "--ignore", + "h", + "../tests/resources/bc_anchored_10_reads.sorted.bam", + output_bam.to_str().unwrap(), + ]) + .output() + .unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!(output.status.success(), "adjust-mods failed: {stderr}"); + let mut reader = bam::Reader::from_path(&output_bam).unwrap(); + assert!(reader.records().next().is_some(), "expected adjusted records"); + assert!(!stderr.contains("&self.cpg = false"), "{stderr}"); + assert!(!stderr.contains("modbam_util/subcommands.rs"), "{stderr}"); +} + fn tests_adjust_output( input_path: &str, output_path: &str, From 8eb4867b309a3f0eac4317d16652780ea3cec506 Mon Sep 17 00:00:00 2001 From: SuhasSrinivasan <32346517+SuhasSrinivasan@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:58:21 -0700 Subject: [PATCH 2/2] Prevent interval endpoint overflow --- modkit-core/src/interval_chunks.rs | 67 +++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/modkit-core/src/interval_chunks.rs b/modkit-core/src/interval_chunks.rs index a6aa5541..2cfdd1a9 100644 --- a/modkit-core/src/interval_chunks.rs +++ b/modkit-core/src/interval_chunks.rs @@ -271,7 +271,7 @@ impl ReferenceIntervalBatchesFeeder { // in the case where we're on a large chrom end will be < length, // but batch length will be equal to interval size let end = std::cmp::min( - start + self.interval_size, + start.saturating_add(self.interval_size), self.curr_contig.end(), ); // get the sequence here. @@ -660,8 +660,11 @@ where mod interval_chunks_tests { use rust_htslib::faidx; - use crate::interval_chunks::slice_dna_sequence; + use crate::interval_chunks::{ + slice_dna_sequence, ReferenceIntervalBatchesFeeder, + }; use crate::test_utils::load_test_sequence; + use crate::util::ReferenceRecord; #[test] fn test_check_sequence_slicing_is_same_as_fetch() { @@ -697,4 +700,64 @@ mod interval_chunks_tests { // let (s, e) = (s as usize, e as usize); // assert_eq!(&seq[s..e], ['E', 'F']); } + + #[test] + fn test_reference_interval_batches_near_u32_max() { + let requested_start = u32::MAX - 9; + let requested_end = u32::MAX; + let record = ReferenceRecord::new( + 7, + requested_start, + requested_end - requested_start, + "near-u32-max".to_string(), + ); + let mut feeder = ReferenceIntervalBatchesFeeder::new( + vec![record], + 1, + 8, + false, + None, + None, + ) + .unwrap(); + let mut intervals = Vec::new(); + let mut terminated = false; + + for _ in 0..4 { + match feeder.next() { + Some(Ok(batches)) => intervals.extend( + batches.into_iter().flat_map(|batch| batch.0).map( + |coordinates| { + (coordinates.start_pos, coordinates.end_pos) + }, + ), + ), + Some(Err(error)) => panic!("feeder failed: {error}"), + None => { + terminated = true; + break; + } + } + } + + assert!(terminated, "feeder did not terminate"); + assert_eq!( + intervals, + vec![ + (requested_start, u32::MAX - 1), + (u32::MAX - 1, requested_end), + ] + ); + let mut cursor = requested_start; + let mut covered = 0u64; + for (start, end) in intervals { + assert_eq!(start, cursor, "intervals must be monotonic"); + assert!(start < end, "intervals must be nonempty"); + assert!(end <= requested_end, "interval exceeds contig end"); + covered += u64::from(end - start); + cursor = end; + } + assert_eq!(cursor, requested_end); + assert_eq!(covered, u64::from(requested_end - requested_start)); + } }