use super::super::tests::input_in;
use super::super::{ReorderRowsArgs, SubsetColumnsArgs, SubsetRowsArgs};
use crate::handlers::cell_qc::CellQcArgs;
use clap::Parser;
#[derive(Parser)]
struct Cols {
#[command(flatten)]
args: SubsetColumnsArgs,
}
#[derive(Parser)]
struct Rows {
#[command(flatten)]
args: SubsetRowsArgs,
}
#[derive(Parser)]
struct Reorder {
#[command(flatten)]
args: ReorderRowsArgs,
}
#[derive(Parser)]
struct Qc {
#[command(flatten)]
args: CellQcArgs,
}
#[test]
fn an_edit_handler_zips_its_output_like_every_builder_does() {
assert!(Cols::parse_from(["x", "in.zarr.zip", "-o", "out"]).args.zip);
assert!(Rows::parse_from(["x", "in.zarr.zip", "-o", "out"]).args.zip);
assert!(
Reorder::parse_from(["x", "in.zarr.zip", "-r", "rows.tsv", "-o", "out"])
.args
.zip
);
assert!(Qc::parse_from(["x", "in.zarr.zip", "-o", "out"]).args.zip);
}
#[test]
fn no_zip_still_asks_for_a_directory() {
assert!(
!Cols::parse_from(["x", "in.zarr.zip", "-o", "out", "--no-zip"])
.args
.zip
);
assert!(
!Rows::parse_from(["x", "in.zarr.zip", "-o", "out", "--no-zip"])
.args
.zip
);
}
#[test]
fn interactive_output_keeps_the_input_container() {
use super::zip_like_input;
assert!(
!zip_like_input("in.zarr", "out"),
"a directory stays a directory"
);
assert!(zip_like_input("in.zarr.zip", "out"), "a zip stays a zip");
assert!(!zip_like_input("in.h5", "out"), "HDF5 is never zipped");
assert!(
!zip_like_input("in.zarr.zip", "out.zarr"),
"an explicit .zarr output wins"
);
}
#[test]
fn a_zipped_subset_leaves_only_the_archive_in_its_folder() {
use crate::sparse_io::open_sparse_matrix_by_path;
let (out_dir, left) = subset_into("out.zarr.zip");
let out = out_dir.path().join("results/out.zarr.zip");
let out = out.to_str().unwrap();
assert_eq!(left, ["out.zarr.zip"]);
let (_, working) =
crate::hdf5_io::resolve_backend_file(out, Some(crate::sparse_io::SparseIoBackend::Zarr))
.unwrap();
let stage = std::path::Path::new(working.as_ref()).parent().unwrap();
assert_eq!(stage, out_dir.path().join("results/.data-beans-stage"));
assert!(!stage.exists(), "{} left behind", stage.display());
assert_eq!(
open_sparse_matrix_by_path(out).unwrap().num_columns(),
Some(3)
);
}
#[test]
fn a_directory_subset_leaves_only_the_directory_in_its_folder() {
let (_dir, left) = subset_into("out.zarr");
assert_eq!(left, ["out.zarr"]);
}
fn subset_into(out: &str) -> (tempfile::TempDir, Vec<String>) {
let dir = tempfile::tempdir().unwrap();
let input = input_in(dir.path());
let out = dir.path().join("results").join(out);
let mut argv = vec![
"x",
input.as_str(),
"-i",
"1-3",
"-o",
out.to_str().unwrap(),
"--batch-process",
];
if !out.to_str().unwrap().ends_with(".zip") {
argv.push("--no-zip");
}
let cols = Cols::parse_from(argv);
super::subset_columns(&cols.args).unwrap();
let left = std::fs::read_dir(dir.path().join("results"))
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect();
(dir, left)
}
fn rows_of(path: &str) -> (Vec<String>, Vec<Vec<f32>>) {
let data = crate::sparse_io::open_sparse_matrix_by_path(path).unwrap();
let (nrow, ncol) = (data.num_rows().unwrap(), data.num_columns().unwrap());
let dense = data.read_columns_dmatrix((0..ncol).collect()).unwrap();
let names = data.row_names().unwrap();
(
names.iter().map(|n| n.to_string()).collect(),
(0..nrow)
.map(|r| (0..ncol).map(|c| dense[(r, c)]).collect())
.collect(),
)
}
fn input_rows() -> (Vec<String>, Vec<Vec<f32>>) {
(
["g0", "g1", "g2"].map(String::from).to_vec(),
vec![
vec![1.0, 0.0, 0.0, 4.0],
vec![0.0, 2.0, 0.0, 0.0],
vec![0.0, 0.0, 3.0, 0.0],
],
)
}
#[test]
fn rows_are_subset_in_the_order_asked_and_the_input_is_only_read() {
let dir = tempfile::tempdir().unwrap();
let input = input_in(dir.path());
let out = dir.path().join("out.zarr");
let out = out.to_str().unwrap();
let rows = Rows::parse_from([
"x",
input.as_str(),
"-i",
"2,0",
"-o",
out,
"--batch-process",
"--no-zip",
]);
super::subset_rows(&rows.args).unwrap();
let (names, values) = rows_of(out);
assert_eq!(names, ["g2", "g0"]);
assert_eq!(values, [vec![0.0, 0.0, 3.0, 0.0], vec![1.0, 0.0, 0.0, 4.0]]);
assert_eq!(rows_of(&input), input_rows());
}
#[test]
fn rows_are_reordered_as_the_file_names_them() {
let dir = tempfile::tempdir().unwrap();
let input = input_in(dir.path());
let order = dir.path().join("rows.tsv");
std::fs::write(&order, "g2\nother\ng0\n").unwrap();
let out = dir.path().join("out.zarr");
let out = out.to_str().unwrap();
let reorder = Reorder::parse_from([
"x",
input.as_str(),
"-r",
order.to_str().unwrap(),
"-o",
out,
"--no-zip",
]);
super::reorder_rows(&reorder.args).unwrap();
let (names, values) = rows_of(out);
assert_eq!(names, ["g2", "other", "g0"]);
assert_eq!(
values,
[
vec![0.0, 0.0, 3.0, 0.0],
vec![0.0; 4],
vec![1.0, 0.0, 0.0, 4.0]
]
);
let by_row = crate::sparse_io::open_sparse_matrix_by_path(out)
.unwrap()
.read_rows_dmatrix(vec![2])
.unwrap();
assert_eq!(by_row.iter().copied().sum::<f32>(), 5.0);
assert_eq!(rows_of(&input), input_rows());
}
#[test]
fn an_output_that_is_the_input_is_refused_and_the_input_kept() {
let dir = tempfile::tempdir().unwrap();
let input = input_in(dir.path());
let bare = input.strip_suffix(".zarr").unwrap();
for out in [input.as_str(), bare] {
let argv = [
"x",
input.as_str(),
"-i",
"0-1",
"-o",
out,
"--batch-process",
"--no-zip",
];
let err = super::subset_columns(&Cols::parse_from(argv).args).unwrap_err();
assert!(format!("{err:#}").contains("is the input"), "{err:#}");
let err = super::subset_rows(&Rows::parse_from(argv).args).unwrap_err();
assert!(format!("{err:#}").contains("is the input"), "{err:#}");
assert_eq!(rows_of(&input), input_rows());
}
}
#[test]
fn an_output_that_is_the_zipped_input_is_refused_and_the_input_kept() {
let dir = tempfile::tempdir().unwrap();
let plain = input_in(dir.path());
let input = format!("{plain}.zip");
let input = input.as_str();
crate::zarr_io::finalize_zarr_output(&plain, input).unwrap();
assert_eq!(rows_of(input), input_rows());
let bytes = std::fs::read(input).unwrap();
let bare = input.strip_suffix(".zarr.zip").unwrap();
let order = dir.path().join("rows.tsv");
std::fs::write(&order, "g2\ng0\n").unwrap();
for out in [input, bare] {
let argv = ["x", input, "-i", "0-1", "-o", out, "--batch-process"];
let err = super::subset_columns(&Cols::parse_from(argv).args).unwrap_err();
assert!(format!("{err:#}").contains("is the input"), "{err:#}");
let err = super::subset_rows(&Rows::parse_from(argv).args).unwrap_err();
assert!(format!("{err:#}").contains("is the input"), "{err:#}");
let argv = ["x", input, "-r", order.to_str().unwrap(), "-o", out];
let err = super::reorder_rows(&Reorder::parse_from(argv).args).unwrap_err();
assert!(format!("{err:#}").contains("is the input"), "{err:#}");
assert_eq!(std::fs::read(input).unwrap(), bytes, "the archive changed");
}
let left: Vec<String> = std::fs::read_dir(dir.path())
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.filter(|name| name != "rows.tsv")
.collect();
assert_eq!(left, ["in.zarr.zip"]);
}
#[test]
fn an_order_that_places_a_row_twice_is_refused() {
let dir = tempfile::tempdir().unwrap();
let input = input_in(dir.path());
let order = dir.path().join("rows.tsv");
let out = dir.path().join("out.zarr");
let argv = [
"x",
input.as_str(),
"-r",
order.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--no-zip",
];
std::fs::write(&order, "g2\ng0\ng2\n").unwrap();
let err = super::reorder_rows(&Reorder::parse_from(argv).args).unwrap_err();
assert!(format!("{err:#}").contains("lists `g2` twice"), "{err:#}");
assert!(!out.exists());
let mut data = crate::sparse_io::open_sparse_matrix_by_path(&input).unwrap();
data.register_row_names_vec(&["g0".into(), "g1".into(), "g0".into()]);
drop(data);
std::fs::write(&order, "g1\ng0\n").unwrap();
let err = super::reorder_rows(&Reorder::parse_from(argv).args).unwrap_err();
assert!(
format!("{err:#}").contains("rows 0 and 2 are both named `g0`"),
"{err:#}"
);
assert!(!out.exists());
}
#[test]
fn blank_lines_ending_an_order_are_not_rows() {
let dir = tempfile::tempdir().unwrap();
let input = input_in(dir.path());
let order = dir.path().join("rows.tsv");
let reordered = |text: &str, name: &str| {
std::fs::write(&order, text).unwrap();
let out = dir.path().join(name);
let out = out.to_str().unwrap().to_string();
let argv = [
"x",
input.as_str(),
"-r",
order.to_str().unwrap(),
"-o",
out.as_str(),
"--no-zip",
];
super::reorder_rows(&Reorder::parse_from(argv).args).unwrap();
let data = crate::sparse_io::open_sparse_matrix_by_path(&out).unwrap();
let ncol = data.num_columns().unwrap();
(
data.row_names().unwrap(),
data.read_triplets_by_columns((0..ncol).collect()).unwrap(),
)
};
let plain = reordered("g2\ng0\n", "plain.zarr");
assert_eq!(plain.0.len(), 2);
assert_eq!(reordered("g2\ng0\n\n\n", "blank.zarr"), plain);
}
#[cfg(feature = "hdf5")]
#[test]
fn an_output_named_h5_is_an_hdf5_file() {
use crate::sparse_io::{open_sparse_matrix, SparseIoBackend};
let dir = tempfile::tempdir().unwrap();
let input = input_in(dir.path());
let out = dir.path().join("out.h5");
let out = out.to_str().unwrap();
let cols = Cols::parse_from([
"x",
input.as_str(),
"-i",
"1-3",
"-o",
out,
"--batch-process",
]);
super::subset_columns(&cols.args).unwrap();
assert!(std::path::Path::new(out).is_file());
let data = open_sparse_matrix(out, &SparseIoBackend::HDF5).unwrap();
assert_eq!(data.num_columns(), Some(3));
assert_eq!(
rows_of(out).1,
[
vec![0.0, 0.0, 4.0],
vec![2.0, 0.0, 0.0],
vec![0.0, 3.0, 0.0]
]
);
}