mod support;
use proptest::prelude::*;
use spoars::graph::Graph;
use support::generators::{
deterministic_config, small_dna, upstream_fastq, upstream_fastq_with_names,
};
use support::oracle::{run_oracle, AlignType, OracleCase, OracleResult};
fn build_graph_from_oracle_alignments(case: &OracleCase, exp: &OracleResult) -> Graph {
let mut g = Graph::new();
for i in 0..case.seqs.len() {
let seq = case.seqs[i].as_bytes();
let alignment = &exp.alignments[i];
if let Some(quals) = &case.quals {
g.add_alignment_quality(alignment, seq, quals[i].as_bytes())
.unwrap_or_else(|e| panic!("add_alignment_quality failed for seq {i}: {e}"));
} else {
g.add_alignment_weight(alignment, seq, 1)
.unwrap_or_else(|e| panic!("add_alignment_weight failed for seq {i}: {e}"));
}
}
g
}
#[test]
fn consensus_matches_oracle_on_upstream_fastq() {
let (seqs, _quals) = upstream_fastq();
let seq_refs: Vec<&str> = seqs.iter().map(|s| s.as_str()).collect();
let cases = vec![OracleCase::nw(&seq_refs)];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
assert_eq!(
g.generate_consensus(),
exp.consensus,
"case id={} consensus mismatch",
case.id
);
}
}
#[test]
fn msa_matches_oracle_on_upstream_fastq() {
let (seqs, _quals) = upstream_fastq();
let seq_refs: Vec<&str> = seqs.iter().map(|s| s.as_str()).collect();
let cases = vec![OracleCase::nw(&seq_refs)];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
assert_eq!(
g.generate_msa(false),
exp.msa,
"case id={} msa mismatch",
case.id
);
}
}
#[test]
fn gfa_matches_oracle_on_upstream_fastq() {
let (seqs, _quals, names) = upstream_fastq_with_names();
let seq_refs: Vec<&str> = seqs.iter().map(|s| s.as_str()).collect();
let mut case = OracleCase::nw(&seq_refs);
case.names = Some(names.clone());
let cases = vec![case];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
g.generate_consensus();
assert_eq!(
g.to_gfa(&names, &[], false),
exp.gfa,
"case id={} gfa mismatch",
case.id
);
}
}
#[test]
fn gfa_uses_supplied_non_index_names_end_to_end() {
let seqs = ["ACGT", "ACGT", "AGT"];
let names = vec![
"read_alpha".to_string(),
"contig_7".to_string(),
"sampleXYZ".to_string(),
];
let mut case = OracleCase::nw(&seqs);
case.names = Some(names.clone());
let cases = vec![case];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
g.generate_consensus();
assert_eq!(
g.to_gfa(&names, &[], false),
exp.gfa,
"case id={} gfa mismatch",
case.id
);
assert!(
exp.gfa.contains("P\tread_alpha\t"),
"oracle GFA missing 'read_alpha' P-line; names not flowing end-to-end:\n{}",
exp.gfa
);
assert!(
exp.gfa.contains("P\tcontig_7\t"),
"oracle GFA missing 'contig_7' P-line; names not flowing end-to-end:\n{}",
exp.gfa
);
assert!(
exp.gfa.contains("P\tsampleXYZ\t"),
"oracle GFA missing 'sampleXYZ' P-line; names not flowing end-to-end:\n{}",
exp.gfa
);
}
}
#[test]
fn dot_matches_oracle_on_upstream_fastq() {
let (seqs, _quals) = upstream_fastq();
let seq_refs: Vec<&str> = seqs.iter().map(|s| s.as_str()).collect();
let cases = vec![OracleCase::nw(&seq_refs)];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
g.generate_consensus();
assert_eq!(g.to_dot(), exp.dot, "case id={} dot mismatch", case.id);
}
}
#[test]
fn consensus_min_coverage_matches_oracle_on_upstream_fastq() {
let (seqs, _quals) = upstream_fastq();
let seq_refs: Vec<&str> = seqs.iter().map(|s| s.as_str()).collect();
let min_coverage = 5i32;
let mut case = OracleCase::nw(&seq_refs);
case.min_coverage = min_coverage;
let cases = vec![case];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
assert_eq!(
g.generate_consensus_min_coverage(min_coverage),
exp.consensus,
"case id={} min_coverage consensus mismatch",
case.id
);
}
}
#[test]
fn consensus_and_msa_match_oracle_with_qualities() {
let (seqs, quals) = upstream_fastq();
let seq_refs: Vec<&str> = seqs.iter().map(|s| s.as_str()).collect();
let qual_refs: Vec<&str> = quals.iter().map(|q| q.as_str()).collect();
let tie_break_seqs = ["ACGT", "ACGT", "ACGA"];
let tie_break_quals = ["####", "####", "IIII"];
let cases = vec![
OracleCase::with_quals(AlignType::Nw, &seq_refs, &qual_refs),
OracleCase::with_quals(AlignType::Nw, &tie_break_seqs, &tie_break_quals),
];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
assert_eq!(
g.generate_consensus(),
exp.consensus,
"case id={} consensus mismatch (quality-weighted); input {:?}",
case.id,
case.seqs
);
assert_eq!(
g.generate_msa(false),
exp.msa,
"case id={} msa mismatch (quality-weighted); input {:?}",
case.id,
case.seqs
);
}
assert_eq!(
expected[1].consensus, "ACGA",
"tie-break fixture no longer discriminates quality weighting from uniform \
weighting (expected the Phred-weighted consensus \"ACGA\", not the naive-majority \
\"ACGT\"); the fixture's constants need revisiting"
);
}
#[test]
fn consensus_and_msa_match_oracle_on_degenerate_inputs() {
let corpora: Vec<Vec<&str>> = vec![
vec!["ACGTACGT"], vec!["A", "A", "C"], vec!["acgt", "acgt"], vec!["ACGN", "ACGT"], ];
for seqs in corpora {
let cases = vec![OracleCase::nw(&seqs)];
let expected = run_oracle(&cases);
for (case, exp) in cases.iter().zip(&expected) {
if seqs.len() == 1 {
assert_eq!(
exp.consensus, seqs[0],
"sanity check: oracle consensus for a single sequence should equal it \
verbatim; corpus {seqs:?}"
);
}
let mut g = build_graph_from_oracle_alignments(case, exp);
assert_eq!(
g.generate_consensus(),
exp.consensus,
"corpus {seqs:?}: consensus mismatch"
);
assert_eq!(
g.generate_msa(false),
exp.msa,
"corpus {seqs:?}: msa mismatch"
);
}
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 48, ..deterministic_config() })]
#[test]
fn consensus_msa_gfa_dot_match_oracle_across_align_types(seqs in small_dna(12, 5)) {
let seq_refs: Vec<&str> = seqs.iter().map(|s| s.as_str()).collect();
let cases = vec![
OracleCase::nw(&seq_refs),
OracleCase::sw(&seq_refs),
OracleCase::ov(&seq_refs),
];
let expected = run_oracle(&cases);
let names: Vec<String> = (0..seq_refs.len()).map(|i| i.to_string()).collect();
for (case, exp) in cases.iter().zip(&expected) {
let mut g = build_graph_from_oracle_alignments(case, exp);
let consensus = g.generate_consensus();
prop_assert_eq!(
&consensus, &exp.consensus,
"consensus mismatch for align type {:?} on input {:?}", case.ty, seqs
);
let msa = g.generate_msa(false);
prop_assert_eq!(
&msa, &exp.msa,
"msa mismatch for align type {:?} on input {:?}", case.ty, seqs
);
let gfa = g.to_gfa(&names, &[], false);
prop_assert_eq!(
&gfa, &exp.gfa,
"gfa mismatch for align type {:?} on input {:?}", case.ty, seqs
);
let dot = g.to_dot();
prop_assert_eq!(
&dot, &exp.dot,
"dot mismatch for align type {:?} on input {:?}", case.ty, seqs
);
}
}
}