use super::*;
use std::cell::Cell;
fn run(tagged: &[(u64, u64, u64)], n: u64) -> (Vec<(u64, u64, f32)>, usize) {
let calls = Cell::new(0usize);
let out = coalesce_and_emit(
tagged,
n as usize,
|tag, inner, val| (inner, tag, val),
|s, e| {
calls.set(calls.get() + 1);
let data: Vec<f32> = (s..e).map(|v| v as f32).collect();
let indices: Vec<u64> = (s..e).collect();
Ok((data, indices))
},
)
.expect("emit");
(out, calls.get())
}
#[test]
fn small_gaps_fuse_into_one_retrieve() {
let tagged: Vec<(u64, u64, u64)> = (0..1000).map(|k| (k, k * 120, k * 120 + 60)).collect();
let (out, calls) = run(&tagged, 200_000);
assert_eq!(out.len(), 60_000, "every requested entry is emitted");
assert!(
calls <= 8,
"1000 near-adjacent ranges must fuse into a few reads, got {calls}"
);
assert!(out.iter().all(|&(inner, _, _)| inner % 120 < 60));
}
#[test]
fn a_huge_gap_still_splits_the_read() {
let tagged = vec![(0u64, 0u64, 10u64), (1, 100_000_000, 100_000_010)];
let (out, calls) = run(&tagged, 200_000_000);
assert_eq!(out.len(), 20);
assert_eq!(calls, 2, "fusing across the void would read 100M entries");
}
#[test]
fn merged_spans_stay_bounded() {
let tagged: Vec<(u64, u64, u64)> = (0..100_000).map(|k| (k, k * 120, k * 120 + 60)).collect();
let (out, calls) = run(&tagged, 100_000 * 120);
assert_eq!(out.len(), 6_000_000);
assert!(calls >= 2, "a 12M-entry span must not become one retrieve");
assert!(
calls <= 64,
"but it must stay a bounded handful, got {calls}"
);
}
fn mtx(
shape: (usize, usize, usize),
indptr: &[u64],
indices: &[u64],
values: &[f32],
block: u64,
) -> anyhow::Result<(String, usize)> {
let calls = Cell::new(0usize);
let mut out = Vec::new();
write_mtx(&mut out, shape, indptr, block, |s, e| {
calls.set(calls.get() + 1);
let (s, e) = (s as usize, e as usize);
Ok((values[s..e].to_vec(), indices[s..e].to_vec()))
})?;
Ok((String::from_utf8(out)?, calls.get()))
}
const MTX_MAGIC: &str = "%%MatrixMarket matrix coordinate real general\n";
#[test]
fn mtx_blocks_cut_through_columns_and_skip_empty_ones() {
let indptr = [0u64, 0, 2, 2, 5, 6, 6];
let indices = [0u64, 3, 0, 1, 2, 3];
let values = [1.5f32, 2.0, 3.0, 4.0, 5.0, -6.25];
let body = "1\t2\t1.5\n4\t2\t2\n1\t4\t3\n2\t4\t4\n3\t4\t5\n4\t5\t-6.25\n";
let expected = format!("{MTX_MAGIC}4\t6\t6\n{body}");
for (block, reads) in [(1u64, 6usize), (2, 3), (4, 2), (5, 2), (6, 1), (1 << 20, 1)] {
let (text, calls) = mtx((4, 6, 6), &indptr, &indices, &values, block).unwrap();
assert_eq!(text, expected, "block {block}");
assert_eq!(
calls, reads,
"block {block}: reads follow the data, not the columns"
);
}
}
#[test]
fn mtx_of_an_empty_matrix_is_the_header() {
let (text, calls) = mtx((3, 2, 0), &[0, 0, 0], &[], &[], 4).unwrap();
assert_eq!(text, format!("{MTX_MAGIC}3\t2\t0\n"));
assert_eq!(calls, 0);
}
#[test]
fn mtx_refuses_index_pointers_that_do_not_describe_the_matrix() {
let indices = [0u64, 1, 2];
let values = [1.0f32, 2.0, 3.0];
for (what, indptr, nnz) in [
("shorter than ncol + 1", vec![0u64, 3], 3usize),
("decreasing", vec![0, 2, 1, 3], 3),
("ending short of the declared nnz", vec![0, 1, 2, 2], 3),
("ending past the declared nnz", vec![0, 1, 2, 3], 2),
] {
let err = mtx((3, 3, nnz), &indptr, &indices, &values, 2);
let msg = format!("{:#}", err.expect_err(what));
assert!(msg.contains("to_mtx_file"), "{what}: {msg}");
}
}
#[test]
fn mtx_refuses_a_row_index_outside_the_matrix() {
let err = mtx((2, 1, 2), &[0, 2], &[0, 2], &[1.0, 2.0], 8);
let msg = format!("{:#}", err.expect_err("row 2 of a 2-row matrix"));
assert!(msg.contains("to_mtx_file"), "{msg}");
}