use snapbox::cmd::{self, Command};
pub mod common;
use crate::common::*;
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use sketchlib::sketch::{
multisketch::MultiSketch, sketch_datafile::SketchArrayReader, BIN_BITS,
};
use snapbox::assert_data_eq;
use super::*;
fn assert_dist_stdout_unordered_with_tolerance(actual: &str, expected: &snapbox::Data) {
let expected_str = expected
.render()
.expect("Failed to render expected snapshot data");
let actual_lines: Vec<&str> = actual.lines().filter(|l| !l.is_empty()).collect();
let mut expected_lines: Vec<&str> =
expected_str.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(
actual_lines.len(),
expected_lines.len(),
"Line count mismatch.\nActual:\n{actual}\nExpected:\n{expected_str}"
);
for actual_line in &actual_lines {
let actual_fields: Vec<&str> = actual_line.split('\t').collect();
let match_idx = expected_lines.iter().position(|expected_line| {
let expected_fields: Vec<&str> = expected_line.split('\t').collect();
actual_fields.len() == expected_fields.len()
&& actual_fields.iter().zip(expected_fields.iter()).all(
|(actual_field, expected_field)| match (
actual_field.parse::<f64>(),
expected_field.parse::<f64>(),
) {
(Ok(actual_val), Ok(expected_val)) => {
(actual_val - expected_val).abs() < 1e-4
}
_ => actual_field == expected_field,
},
)
});
match match_idx {
Some(idx) => {
expected_lines.remove(idx);
}
None => panic!(
"No matching expected line for actual line: {actual_line}\nRemaining expected lines: {expected_lines:?}"
),
}
}
}
fn unpack_skd_bin(bit_planes: &[u64], bin_idx: usize) -> u16 {
let word_idx = bin_idx / (u64::BITS as usize);
let bit_offset = bin_idx % (u64::BITS as usize);
let plane_offset = word_idx * BIN_BITS;
bit_planes[plane_offset..(plane_offset + BIN_BITS)]
.iter()
.enumerate()
.fold(0_u16, |bin_value, (bit_pos, &plane)| {
bin_value | (((plane >> bit_offset) & 1) as u16) << bit_pos
})
}
#[test]
fn inverted_sketch() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "31"])
.arg("14412_3#82.contigs_velvet.fa.gz")
.arg("14412_3#84.contigs_velvet.fa.gz")
.assert()
.success();
assert_eq!(true, sandbox.file_exists("inverted.ski"));
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("info")
.arg("inverted.ski")
.arg("-v")
.assert()
.stdout_eq(sandbox.snapbox_file("inverted_sketch_info.stdout", TestDir::Correct));
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("info")
.arg("--sample-info")
.arg("inverted.ski")
.arg("-v")
.assert()
.stdout_eq(sandbox.snapbox_file("inverted_sketch_full_info.stdout", TestDir::Correct));
}
#[test]
fn inverted_skq_matches_standard_skd_bins() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "21", "-s", "64"])
.arg("-f")
.arg("rfile.txt")
.arg("--write-skq")
.assert()
.success();
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("sketch")
.arg("-o")
.arg("standard")
.args(["-v", "--k-vals", "21", "-s", "64"])
.arg("-f")
.arg("rfile.txt")
.assert()
.success();
let standard_sketches =
MultiSketch::load(&sandbox.file_string("standard", TestDir::Output))
.expect("failed to load standard sketch data");
let sketch_size = standard_sketches.sketch_size as usize;
assert_eq!(sketch_size, 64);
assert_eq!(standard_sketches.kmer_lengths(), &[21]);
let mut skq_reader = SketchArrayReader::open(
&sandbox.file_string("inverted.skq", TestDir::Output),
false,
1,
1,
sketch_size,
);
let skq_bins =
skq_reader.read_all_from_skq(sketch_size * standard_sketches.number_samples_loaded());
assert_eq!(
skq_bins.len(),
sketch_size * standard_sketches.number_samples_loaded()
);
for sample_idx in 0..standard_sketches.number_samples_loaded() {
let skd_bins = standard_sketches.get_sketch_slice(sample_idx, 0);
let skq_offset = sample_idx * sketch_size;
for bin_idx in 0..sketch_size {
assert_eq!(
skq_bins[skq_offset + bin_idx],
unpack_skd_bin(skd_bins, bin_idx),
"sample {sample_idx} bin {bin_idx} differs between .skq and .skd"
);
}
}
}
#[test]
fn inverted_sketch_with_metadata() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile.txt");
sandbox.copy_input_file_to_wd("metadata.txt");
sandbox.copy_input_file_to_wd("species_names.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "31"])
.args(["-f", "rfile.txt"])
.args(["--species-names", "species_names.txt"])
.args(["--metadata", "metadata.txt"])
.assert()
.success();
assert_eq!(true, sandbox.file_exists("inverted.ski"));
}
#[test]
fn inverted_sketch_errors() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile.txt");
sandbox.copy_input_file_to_wd("species_names_empty.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "31"])
.args(["-f", "rfile.txt"])
.args(["--species-names", "species_names_empty.txt"])
.assert()
.success();
assert_eq!(true, sandbox.file_exists("inverted.ski"));
}
#[test]
fn inverted_sketch_multifile() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile_multi.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "31", "-s", "1000"])
.args(["-f", "rfile_multi.txt"])
.assert()
.success();
assert_eq!(true, sandbox.file_exists("inverted.ski"));
}
#[test]
fn inverted_reorder() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "61", "-s", "63"])
.arg("--species-names")
.arg(sandbox.file_string("species_names.txt", TestDir::Input))
.arg("14412_3#82.contigs_velvet.fa.gz")
.arg("14412_3#84.contigs_velvet.fa.gz")
.arg("R6.fa.gz")
.arg("TIGR4.fa.gz")
.assert()
.success();
assert_eq!(true, sandbox.file_exists("inverted.ski"));
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("info")
.arg("inverted.ski")
.arg("--sample-info")
.arg("-v")
.assert()
.stdout_eq(
sandbox.snapbox_file("inverted_sketch_info_reorder.stdout", TestDir::Correct),
);
}
#[test]
fn inverted_query() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "21", "-s", "10"])
.arg("-f")
.arg("rfile.txt")
.assert()
.success();
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("query")
.arg("-v")
.arg("-f")
.arg("rfile.txt")
.arg("inverted.ski")
.assert()
.stdout_eq(
sandbox
.snapbox_file("inverted_query_count.stdout", TestDir::Correct)
.unordered(),
);
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("query")
.arg("-v")
.arg("-f")
.arg("rfile.txt")
.arg("inverted.ski")
.args(&["--query-type", "any-bins"])
.assert()
.stdout_eq(
sandbox
.snapbox_file("inverted_query_any.stdout", TestDir::Correct)
.unordered(),
);
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("query")
.arg("-v")
.arg("-f")
.arg("rfile.txt")
.arg("inverted.ski")
.args(&["--query-type", "all-bins"])
.assert()
.stdout_eq(
sandbox
.snapbox_file("inverted_query_all.stdout", TestDir::Correct)
.unordered(),
);
}
#[test]
fn inverted_precluster() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("inverted")
.args(["-v", "-k", "21", "-s", "10"])
.arg("-f")
.arg("rfile.txt")
.arg("--write-skq")
.assert()
.success();
assert_data_eq!(
sandbox.snapbox_file("inverted.skq", TestDir::Output),
sandbox.snapbox_file("inverted.skq", TestDir::Correct)
);
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.arg("-v")
.arg("--count")
.arg("inverted.ski")
.assert()
.stdout_eq("Identified 2 prefilter pairs from a max of 6\n");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("sketch")
.arg("-o")
.arg("standard")
.args(["-v", "--k-vals", "21", "-s", "1000"])
.arg("-f")
.arg("rfile.txt")
.assert()
.success();
let precluster_output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--skd")
.arg("standard")
.arg("inverted.ski")
.output()
.expect("Failed to run precluster");
assert!(precluster_output.status.success());
assert_dist_stdout_unordered_with_tolerance(
&String::from_utf8(precluster_output.stdout).unwrap(),
&sandbox.snapbox_file("inverted_precluster.stdout", TestDir::Correct),
);
let precluster_ani_output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--ani")
.arg("--skd")
.arg("standard")
.arg("inverted.ski")
.output()
.expect("Failed to run precluster --ani");
assert!(precluster_ani_output.status.success());
assert_dist_stdout_unordered_with_tolerance(
&String::from_utf8(precluster_ani_output.stdout).unwrap(),
&sandbox.snapbox_file("inverted_precluster_ani.stdout", TestDir::Correct),
);
let precluster_knn50_output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "50"])
.arg("--skd")
.arg("standard")
.arg("inverted.ski")
.output()
.expect("Failed to run precluster --knn 50");
assert!(precluster_knn50_output.status.success());
assert_dist_stdout_unordered_with_tolerance(
&String::from_utf8(precluster_knn50_output.stdout).unwrap(),
&sandbox.snapbox_file("inverted_precluster.stdout", TestDir::Correct),
);
}
fn setup_reordered_precluster(sandbox: &TestSetup) {
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("reordered_inv")
.args(["-v", "-k", "21", "-s", "10"])
.arg("-f")
.arg("rfile.txt")
.arg("--write-skq")
.arg("--species-names")
.arg(sandbox.file_string("species_names.txt", TestDir::Input))
.assert()
.success();
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("sketch")
.arg("-o")
.arg("standard")
.args(["-v", "--k-vals", "21", "-s", "1000"])
.arg("-f")
.arg("rfile.txt")
.assert()
.success();
}
#[test]
fn inverted_precluster_reordered() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--skd")
.arg("standard")
.arg("reordered_inv.ski")
.output()
.expect("Failed to run precluster");
assert!(output.status.success());
assert_dist_stdout_unordered_with_tolerance(
&String::from_utf8(output.stdout).unwrap(),
&sandbox.snapbox_file("inverted_precluster.stdout", TestDir::Correct),
);
}
#[test]
fn inverted_precluster_reordered_ani() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--ani")
.arg("--skd")
.arg("standard")
.arg("reordered_inv.ski")
.output()
.expect("Failed to run precluster --ani");
assert!(output.status.success());
assert_dist_stdout_unordered_with_tolerance(
&String::from_utf8(output.stdout).unwrap(),
&sandbox.snapbox_file("inverted_precluster_ani.stdout", TestDir::Correct),
);
}
#[test]
fn inverted_precluster_reordered_knn_padding() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "50"])
.arg("--skd")
.arg("standard")
.arg("reordered_inv.ski")
.output()
.expect("Failed to run precluster --knn 50");
assert!(output.status.success());
assert_dist_stdout_unordered_with_tolerance(
&String::from_utf8(output.stdout).unwrap(),
&sandbox.snapbox_file("inverted_precluster.stdout", TestDir::Correct),
);
}
#[test]
fn inverted_precluster_reversed_rfile() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
let reversed_rfile = sandbox.create_named_rfile(
"reversed_rfile.txt",
&[
"TIGR4.fa.gz",
"R6.fa.gz",
"14412_3#84.contigs_velvet.fa.gz",
"14412_3#82.contigs_velvet.fa.gz",
],
);
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("reversed_inv")
.args(["-v", "-k", "21", "-s", "10"])
.arg("-f")
.arg(&reversed_rfile)
.arg("--write-skq")
.assert()
.success();
let standard_rfile = sandbox.create_named_rfile(
"standard_rfile.txt",
&[
"14412_3#82.contigs_velvet.fa.gz",
"14412_3#84.contigs_velvet.fa.gz",
"R6.fa.gz",
"TIGR4.fa.gz",
],
);
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("sketch")
.arg("-o")
.arg("standard")
.args(["-v", "--k-vals", "21", "-s", "1000"])
.arg("-f")
.arg(&standard_rfile)
.assert()
.success();
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--skd")
.arg("standard")
.arg("reversed_inv.ski")
.output()
.expect("Failed to run precluster");
assert!(output.status.success());
assert_dist_stdout_unordered_with_tolerance(
&String::from_utf8(output.stdout).unwrap(),
&sandbox.snapbox_file("inverted_precluster.stdout", TestDir::Correct),
);
}
#[test]
fn inverted_precluster_reordered_completeness() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
TestSetup::create_completeness_file(
&sandbox,
"completeness_reorder.txt",
&[
("14412_3#82.contigs_velvet.fa.gz", 0.7),
("14412_3#84.contigs_velvet.fa.gz", 0.8),
("R6.fa.gz", 0.9),
("TIGR4.fa.gz", 0.95),
],
);
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("sameorder_inv")
.args(["-v", "-k", "21", "-s", "10"])
.arg("-f")
.arg("rfile.txt")
.arg("--write-skq")
.assert()
.success();
let reordered_output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--skd")
.arg("standard")
.arg("--ref-completeness-file")
.arg(sandbox.file_string("completeness_reorder.txt", TestDir::Output))
.arg("reordered_inv.ski")
.assert()
.success()
.get_output()
.stdout
.clone();
let sameorder_output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--skd")
.arg("standard")
.arg("--ref-completeness-file")
.arg(sandbox.file_string("completeness_reorder.txt", TestDir::Output))
.arg("sameorder_inv.ski")
.assert()
.success()
.get_output()
.stdout
.clone();
let mut reordered_lines: Vec<String> = String::from_utf8(reordered_output)
.unwrap()
.lines()
.filter(|l| !l.is_empty())
.map(|l| l.to_string())
.collect();
let mut sameorder_lines: Vec<String> = String::from_utf8(sameorder_output)
.unwrap()
.lines()
.filter(|l| !l.is_empty())
.map(|l| l.to_string())
.collect();
reordered_lines.sort();
sameorder_lines.sort();
assert_eq!(
reordered_lines, sameorder_lines,
"Reordered and same-order precluster with completeness should produce identical results"
);
}
#[test]
fn inverted_precluster_all_genomes_in_output() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
let input_names = [
"14412_3#82.contigs_velvet.fa.gz",
"14412_3#84.contigs_velvet.fa.gz",
"R6.fa.gz",
"TIGR4.fa.gz",
];
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--skd")
.arg("standard")
.arg("reordered_inv.ski")
.assert()
.success()
.get_output()
.stdout
.clone();
let output_str = String::from_utf8(output).unwrap();
let mut found_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
for line in output_str.lines().filter(|l| !l.is_empty()) {
let fields: Vec<&str> = line.split('\t').collect();
found_names.insert(fields[0]);
found_names.insert(fields[1]);
}
for name in &input_names {
assert!(
found_names.contains(name),
"Genome {name} missing from precluster output"
);
}
assert_eq!(
found_names.len(),
input_names.len(),
"Number of unique genomes in output ({}) does not match input ({})",
found_names.len(),
input_names.len(),
);
}
#[test]
fn inverted_precluster_self_skip() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "3"])
.arg("--skd")
.arg("standard")
.arg("reordered_inv.ski")
.assert()
.success()
.get_output()
.stdout
.clone();
let output_str = String::from_utf8(output).unwrap();
for line in output_str.lines().filter(|l| !l.is_empty()) {
let fields: Vec<&str> = line.split('\t').collect();
assert!(
fields.len() >= 3,
"Output line should have at least 3 tab-separated fields: {line}"
);
assert_ne!(
fields[0], fields[1],
"Self-comparison found in output: {line}"
);
let dist: f64 = fields[2].parse().expect("Failed to parse distance");
assert!(
dist > 0.0 && dist < 1.0,
"Distance out of range (0,1): {dist} in line: {line}"
);
}
}
#[test]
fn inverted_precluster_retain_singleton() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
let input_names = [
"14412_3#82.contigs_velvet.fa.gz",
"14412_3#84.contigs_velvet.fa.gz",
"R6.fa.gz",
"TIGR4.fa.gz",
];
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--retain-unmatched")
.arg("singleton")
.arg("--skd")
.arg("standard")
.arg("reordered_inv.ski")
.assert()
.success()
.get_output()
.stdout
.clone();
let output_str = String::from_utf8(output).unwrap();
let mut found_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
for line in output_str.lines().filter(|l| !l.is_empty()) {
let fields: Vec<&str> = line.split('\t').collect();
found_names.insert(fields[0]);
found_names.insert(fields[1]);
if fields[0] == fields[1] {
let dist: f64 = fields[2].parse().expect("Failed to parse distance");
assert_abs_diff_eq!(dist, 0.0, epsilon = 1e-6);
}
}
for name in &input_names {
assert!(
found_names.contains(name),
"Genome {name} missing from output with --retain-unmatched singleton"
);
}
}
#[test]
fn inverted_precluster_retain_bruteforce() {
let sandbox = TestSetup::setup();
setup_reordered_precluster(&sandbox);
let input_names = [
"14412_3#82.contigs_velvet.fa.gz",
"14412_3#84.contigs_velvet.fa.gz",
"R6.fa.gz",
"TIGR4.fa.gz",
];
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("precluster")
.args(["-v", "--knn", "1"])
.arg("--retain-unmatched")
.arg("bruteforce")
.arg("--skd")
.arg("standard")
.arg("reordered_inv.ski")
.assert()
.success()
.get_output()
.stdout
.clone();
let output_str = String::from_utf8(output).unwrap();
let mut found_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
for line in output_str.lines().filter(|l| !l.is_empty()) {
let fields: Vec<&str> = line.split('\t').collect();
found_names.insert(fields[0]);
found_names.insert(fields[1]);
assert_ne!(
fields[0], fields[1],
"Self-comparison found in bruteforce output: {line}"
);
let dist: f64 = fields[2].parse().expect("Failed to parse distance");
assert!(
dist > 0.0 && dist < 1.0,
"Distance out of range (0,1): {dist} in line: {line}"
);
}
for name in &input_names {
assert!(
found_names.contains(name),
"Genome {name} missing from output with --retain-unmatched bruteforce"
);
}
}
#[test]
fn inverted_query_reordered() {
let sandbox = TestSetup::setup();
sandbox.copy_input_file_to_wd("14412_3#82.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("14412_3#84.contigs_velvet.fa.gz");
sandbox.copy_input_file_to_wd("R6.fa.gz");
sandbox.copy_input_file_to_wd("TIGR4.fa.gz");
sandbox.copy_input_file_to_wd("rfile.txt");
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("build")
.arg("-o")
.arg("reordered_inv")
.args(["-v", "-k", "21", "-s", "10"])
.arg("-f")
.arg("rfile.txt")
.arg("--species-names")
.arg(sandbox.file_string("species_names.txt", TestDir::Input))
.assert()
.success();
let output = Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("query")
.arg("-v")
.arg("-f")
.arg("rfile.txt")
.arg("reordered_inv.ski")
.assert()
.success()
.get_output()
.stdout
.clone();
let output_str = String::from_utf8(output).unwrap();
for line in output_str.lines().skip(1).filter(|l| !l.is_empty()) {
let fields: Vec<&str> = line.split('\t').collect();
let query_name = fields[0];
let header: Vec<&str> = output_str.lines().next().unwrap().split('\t').collect();
for (col_idx, &col_name) in header.iter().enumerate().skip(1) {
let count: u32 = fields[col_idx].parse().unwrap();
if col_name == query_name {
assert_eq!(count, 10, "Self-match count should be 10 for {query_name}");
}
}
}
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("query")
.arg("-v")
.arg("-f")
.arg("rfile.txt")
.arg("reordered_inv.ski")
.args(&["--query-type", "any-bins"])
.assert()
.success();
Command::new(cmd::cargo_bin!("sketchlib"))
.current_dir(sandbox.get_wd())
.arg("inverted")
.arg("query")
.arg("-v")
.arg("-f")
.arg("rfile.txt")
.arg("reordered_inv.ski")
.args(&["--query-type", "all-bins"])
.assert()
.success();
}
}