use std::path::{Path, PathBuf};
use std::process::Command;
fn bin() -> Command {
Command::new(env!("CARGO_BIN_EXE_rsomics-seq-grep"))
}
fn golden(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/golden")
.join(name)
}
fn assert_matches_golden(args: &[&str], golden_name: &str) {
let fa = golden("small.fa");
let out = bin()
.args(args)
.arg(&fa)
.output()
.expect("rsomics-seq-grep failed to run");
assert!(
out.status.success(),
"exit {}: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
let expected = std::fs::read(golden(golden_name)).expect("golden file missing");
assert_eq!(
out.stdout,
expected,
"mismatch for {golden_name}.\nours:\n{}\nexpected:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&expected)
);
}
#[test]
fn id_exact_match() {
assert_matches_golden(&["-p", "seq1,seq3"], "id_exact.out");
}
#[test]
fn by_name_full_header() {
assert_matches_golden(&["-n", "-p", "seq1 description one"], "by_name.out");
}
#[test]
fn regex_anchored() {
assert_matches_golden(&["-r", "-p", "seq[13]$"], "regex_anchor.out");
}
#[test]
fn ignore_case() {
assert_matches_golden(&["-i", "-p", "SEQ1"], "ignore_case.out");
}
#[test]
fn invert_match() {
assert_matches_golden(&["--invert-match", "-p", "seq1"], "invert.out");
}
#[test]
fn by_seq_substring() {
assert_matches_golden(&["-s", "-p", "TTTT"], "by_seq.out");
}
#[test]
fn by_seq_minus_strand_by_default() {
assert_matches_golden(&["-s", "-p", "GGGGTTTT"], "by_seq_minus.out");
}
#[test]
fn by_seq_only_positive_strand() {
assert_matches_golden(&["-s", "-P", "-p", "GGGGTTTT"], "by_seq_positive.out");
}
#[test]
fn degenerate_iupac_motif() {
assert_matches_golden(&["-s", "-d", "-p", "RYSW"], "degenerate.out");
}
#[test]
fn region_restricts_search() {
assert_matches_golden(&["-s", "-R", "1:4", "-p", "TTTT"], "region.out");
}
#[test]
fn line_width_10() {
assert_matches_golden(&["-p", "seq1", "-w", "10"], "linewidth10.out");
}
#[test]
fn line_width_0_no_wrap() {
assert_matches_golden(&["-p", "seq1", "-w", "0"], "linewidth0.out");
}
#[test]
fn pattern_file() {
let pats = golden("pats.txt");
let fa = golden("small.fa");
let out = bin()
.args(["-f"])
.arg(&pats)
.arg(&fa)
.output()
.expect("run failed");
assert!(out.status.success());
let expected = std::fs::read(golden("pattern_file.out")).unwrap();
assert_eq!(out.stdout, expected);
}
#[test]
fn pattern_file_overrides_inline_pattern() {
let pats = golden("pats.txt");
let fa = golden("small.fa");
let out = bin()
.args(["-p", "seq2", "-f"])
.arg(&pats)
.arg(&fa)
.output()
.expect("run failed");
assert!(out.status.success());
let expected = std::fs::read(golden("pattern_file.out")).unwrap();
assert_eq!(out.stdout, expected);
}
#[test]
fn gz_input_autodetected() {
let gz = golden("small.fa.gz");
let out = bin()
.args(["-p", "seq1"])
.arg(&gz)
.output()
.expect("run failed");
assert!(out.status.success());
let expected = std::fs::read(golden("gz_input.out")).unwrap();
assert_eq!(out.stdout, expected);
}
#[test]
fn stdin_input() {
use std::io::Write;
use std::process::Stdio;
let fa_bytes = std::fs::read(golden("small.fa")).unwrap();
let mut child = bin()
.args(["-p", "seq1", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("spawn failed");
child
.stdin
.take()
.unwrap()
.write_all(&fa_bytes)
.expect("write stdin");
let out = child.wait_with_output().expect("wait failed");
assert!(out.status.success());
let expected = std::fs::read(golden("seq1_only.out")).unwrap();
assert_eq!(out.stdout, expected);
}
#[test]
fn fastq_default_line_width_is_ignored() {
let fq = golden("small.fq");
let out = bin()
.args(["-n", "-p", "read1 some desc"])
.arg(&fq)
.output()
.expect("run failed");
assert!(out.status.success());
let expected = std::fs::read(golden("fastq_default.out")).unwrap();
assert_eq!(out.stdout, expected);
}
#[test]
fn fastq_ignores_explicit_line_width() {
let fq = golden("small.fq");
let out = bin()
.args(["-n", "-p", "read1 some desc", "-w", "5"])
.arg(&fq)
.output()
.expect("run failed");
assert!(out.status.success());
let expected = std::fs::read(golden("fastq_ignore_width.out")).unwrap();
assert_eq!(out.stdout, expected);
}
#[test]
fn count_only() {
assert_matches_golden(&["-C", "-p", "seq1,seq3"], "count_only.out");
}
#[test]
fn empty_pattern_matches_empty_id_only() {
assert_matches_golden(&["-p", ""], "empty_pattern_id.out");
}
#[test]
fn empty_pattern_by_seq_matches_empty_seq_only() {
assert_matches_golden(&["-s", "-p", ""], "empty_pattern_seq.out");
}
#[test]
fn missing_pattern_source_is_an_error() {
let fa = golden("small.fa");
let out = bin().arg(&fa).output().expect("run failed");
assert!(!out.status.success());
}
#[test]
fn regexp_and_degenerate_conflict_is_an_error() {
let fa = golden("small.fa");
let out = bin()
.args(["-r", "-d", "-p", "ATG"])
.arg(&fa)
.output()
.expect("run failed");
assert!(!out.status.success());
}
#[test]
fn region_without_pattern_is_an_error() {
let fa = golden("small.fa");
let out = bin()
.args(["-R", "1:4"])
.arg(&fa)
.output()
.expect("run failed");
assert!(!out.status.success());
}
#[test]
fn illegal_by_seq_pattern_is_an_error() {
let fa = golden("small.fa");
let out = bin()
.args(["-s", "-p", "T1A"])
.arg(&fa)
.output()
.expect("run failed");
assert!(!out.status.success());
}
#[test]
fn degenerate_non_iupac_passthrough() {
assert_matches_golden(&["-d", "-p", "ACG."], "degenerate_passthrough.out");
}
#[test]
fn degenerate_literal_digit_runs() {
let fa = golden("small.fa");
let out = bin()
.args(["-d", "-p", "RY9SW"])
.arg(&fa)
.output()
.expect("run failed");
assert!(out.status.success());
assert!(out.stdout.is_empty());
}