diff --git a/tests/integration/helpers/assertions.rs b/tests/integration/helpers/assertions.rs index 95b46ba98..da5ca24c1 100644 --- a/tests/integration/helpers/assertions.rs +++ b/tests/integration/helpers/assertions.rs @@ -299,12 +299,63 @@ pub fn assert_bam_sorted(bam: &std::path::Path, order: &str, key_types: Option<& ); } +/// Asserts two text files exist and are byte-for-byte identical, panicking +/// with `label` and the offending path named on either a read error or a +/// content mismatch. +/// +/// This is the shared home for the `assert_eq!(read_to_string(a), +/// read_to_string(b))` idiom that the cutover-parity tests (dedup, group, +/// clip, retag, correct, …) each reimplemented inline. It intentionally does +/// NOT guard for non-empty / data-bearing content: whether an output file +/// must carry data rows is specific to the metric being compared, so any such +/// check belongs with the individual caller that needs it, not in a general +/// text-file comparison. +pub fn assert_text_files_eq(actual: &std::path::Path, expected: &std::path::Path, label: &str) { + let a = std::fs::read_to_string(actual) + .unwrap_or_else(|e| panic!("{label}: reading actual {}: {e}", actual.display())); + let e = std::fs::read_to_string(expected) + .unwrap_or_else(|e| panic!("{label}: reading expected {}: {e}", expected.display())); + assert_eq!(a, e, "{label}: {} differs from {}", actual.display(), expected.display()); +} + #[cfg(test)] mod tests { use super::*; use crate::helpers::bam_generator::to_record_buf; use fgumi_raw_bam::{SamBuilder, flags}; + #[test] + fn assert_text_files_eq_passes_on_identical_content() { + let dir = tempfile::tempdir().expect("tempdir"); + let a = dir.path().join("a"); + let b = dir.path().join("b"); + std::fs::write(&a, "col1\tcol2\n1\t2\n").expect("write a"); + std::fs::write(&b, "col1\tcol2\n1\t2\n").expect("write b"); + assert_text_files_eq(&a, &b, "identical files"); + } + + #[test] + #[should_panic(expected = "differs from")] + fn assert_text_files_eq_panics_on_divergent_content() { + let dir = tempfile::tempdir().expect("tempdir"); + let a = dir.path().join("a"); + let b = dir.path().join("b"); + std::fs::write(&a, "1\n").expect("write a"); + std::fs::write(&b, "2\n").expect("write b"); + assert_text_files_eq(&a, &b, "divergent files"); + } + + #[test] + #[should_panic(expected = "reading actual")] + fn assert_text_files_eq_panics_naming_a_missing_file() { + let dir = tempfile::tempdir().expect("tempdir"); + assert_text_files_eq( + &dir.path().join("missing-actual"), + &dir.path().join("missing-expected"), + "missing file", + ); + } + /// Locks down the noodles behavior that `assert_rejects_header_matches_input` /// relies on: SO/GO/SS are accessible via the typed `Other` tag constants on /// `Map
::other_fields()`. If noodles ever moves these to first-class diff --git a/tests/integration/test_clip_cutover_parity.rs b/tests/integration/test_clip_cutover_parity.rs index af5ab0994..533c622e3 100644 --- a/tests/integration/test_clip_cutover_parity.rs +++ b/tests/integration/test_clip_cutover_parity.rs @@ -50,6 +50,7 @@ use fgumi_raw_bam::{RawRecord, SamBuilder, flags}; use rstest::rstest; use tempfile::TempDir; +use crate::helpers::assertions::assert_text_files_eq; use crate::helpers::bam_generator::{create_minimal_header, create_test_reference, write_bam}; use crate::helpers::read_bam_output; @@ -491,10 +492,10 @@ fn cutover_matches_baseline(#[case] with_metrics: bool) { baseline.display(), ); if with_metrics { - assert_eq!( - std::fs::read_to_string(¤t_tsv).expect("current tsv"), - std::fs::read_to_string(&baseline_tsv).expect("baseline tsv"), - "chain --metrics TSV diverges from the serial baseline binary" + assert_text_files_eq( + ¤t_tsv, + &baseline_tsv, + "chain --metrics TSV diverges from the serial baseline binary", ); } } else { diff --git a/tests/integration/test_correct_cutover_parity.rs b/tests/integration/test_correct_cutover_parity.rs index ae23d013d..f0ef9187d 100644 --- a/tests/integration/test_correct_cutover_parity.rs +++ b/tests/integration/test_correct_cutover_parity.rs @@ -46,6 +46,7 @@ use std::process::Command; use rstest::rstest; use tempfile::TempDir; +use crate::helpers::assertions::assert_text_files_eq; use crate::helpers::bam_generator::create_umi_family; use crate::helpers::read_bam_output; use crate::test_correct_command::create_umi_bam; @@ -284,10 +285,10 @@ fn cutover_matches_baseline(#[case] with_metrics: bool) { baseline.display(), ); if with_metrics { - assert_eq!( - std::fs::read_to_string(¤t_tsv).expect("current tsv"), - std::fs::read_to_string(&baseline_tsv).expect("baseline tsv"), - "chain --metrics TSV diverges from the serial baseline binary" + assert_text_files_eq( + ¤t_tsv, + &baseline_tsv, + "chain --metrics TSV diverges from the serial baseline binary", ); } } else { @@ -406,9 +407,9 @@ fn cutover_min_corrected_failure_writes_metrics() { baseline_tsv.exists(), "the baseline's legacy path must also leave a --metrics TSV behind on the failure" ); - assert_eq!( - std::fs::read_to_string(¤t_tsv).expect("current tsv"), - std::fs::read_to_string(&baseline_tsv).expect("baseline tsv"), + assert_text_files_eq( + ¤t_tsv, + &baseline_tsv, "chain --metrics TSV on a --min-corrected failure diverges from the serial baseline", ); assert_eq!( diff --git a/tests/integration/test_dedup_cutover_parity.rs b/tests/integration/test_dedup_cutover_parity.rs index 7dd04873a..282a4e579 100644 --- a/tests/integration/test_dedup_cutover_parity.rs +++ b/tests/integration/test_dedup_cutover_parity.rs @@ -40,6 +40,7 @@ use tempfile::TempDir; use fgumi_lib::sam::SamTag; use fgumi_raw_bam::{RawRecord, SamBuilder, flags}; +use crate::helpers::assertions::assert_text_files_eq; use crate::helpers::bam_generator::{create_minimal_header, write_bam}; use crate::helpers::read_bam_output; @@ -578,16 +579,20 @@ fn cutover_matches_baseline_by_strategy(#[case] strategy: &str) { baseline binary ({}) after stripping @PG — a real cutover parity bug", baseline.display(), ); - assert_eq!( - std::fs::read_to_string(¤t_metrics).expect("current metrics"), - std::fs::read_to_string(&baseline_metrics).expect("baseline metrics"), - "chain --metrics TSV (--strategy {strategy}) diverges from the legacy baseline" + assert_text_files_eq( + ¤t_metrics, + &baseline_metrics, + &format!( + "chain --metrics TSV (--strategy {strategy}) diverges from the legacy baseline" + ), ); - assert_eq!( - std::fs::read_to_string(¤t_hist).expect("current histogram"), - std::fs::read_to_string(&baseline_hist).expect("baseline histogram"), - "chain --family-size-histogram TSV (--strategy {strategy}) diverges from the legacy \ - baseline" + assert_text_files_eq( + ¤t_hist, + &baseline_hist, + &format!( + "chain --family-size-histogram TSV (--strategy {strategy}) diverges from the \ + legacy baseline" + ), ); } else { eprintln!( diff --git a/tests/integration/test_group_cutover_parity.rs b/tests/integration/test_group_cutover_parity.rs index d569257ba..991fdf14e 100644 --- a/tests/integration/test_group_cutover_parity.rs +++ b/tests/integration/test_group_cutover_parity.rs @@ -40,6 +40,7 @@ use tempfile::TempDir; use fgumi_lib::sam::SamTag; use fgumi_raw_bam::{RawRecord, SamBuilder, flags}; +use crate::helpers::assertions::assert_text_files_eq; use crate::helpers::bam_generator::{create_minimal_header, write_bam}; use crate::helpers::read_bam_output; @@ -534,11 +535,13 @@ fn cutover_matches_baseline_by_strategy(#[case] strategy: &str) { ("grouping_metrics", cur_grp, base_grp), ("position_group_sizes", cur_pos, base_pos), ] { - assert_eq!( - std::fs::read_to_string(&cur).unwrap_or_else(|e| panic!("current {label}: {e}")), - std::fs::read_to_string(&base).unwrap_or_else(|e| panic!("baseline {label}: {e}")), - "chain --metrics {label} TSV (--strategy {strategy}) diverges from the legacy \ - baseline" + assert_text_files_eq( + &cur, + &base, + &format!( + "chain --metrics {label} TSV (--strategy {strategy}) diverges from the legacy \ + baseline" + ), ); } } else { diff --git a/tests/integration/test_retag_cutover_parity.rs b/tests/integration/test_retag_cutover_parity.rs index 5335c4e6f..a8b652ede 100644 --- a/tests/integration/test_retag_cutover_parity.rs +++ b/tests/integration/test_retag_cutover_parity.rs @@ -37,6 +37,7 @@ use fgumi_raw_bam::{RawRecord, SamBuilder, flags}; use rstest::rstest; use tempfile::TempDir; +use crate::helpers::assertions::assert_text_files_eq; use crate::helpers::bam_generator::{create_minimal_header, write_bam}; use crate::helpers::cutover::{baseline_bin, decompressed_records_without_pg}; use crate::helpers::read_bam_output; @@ -206,10 +207,10 @@ fn cutover_matches_baseline(#[case] ops: &[&str], #[case] with_metrics: bool) { baseline.display(), ); if with_metrics { - assert_eq!( - std::fs::read_to_string(¤t_tsv).expect("current tsv"), - std::fs::read_to_string(&baseline_tsv).expect("baseline tsv"), - "chain --metrics TSV diverges from the serial baseline binary" + assert_text_files_eq( + ¤t_tsv, + &baseline_tsv, + "chain --metrics TSV diverges from the serial baseline binary", ); } } else {