use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use super::{Entry, compute_all, field_of, fixture_path, hash_mesh, render};
fn read_fixture() -> Vec<(String, String)> {
let text = std::fs::read_to_string(fixture_path()).unwrap_or_default();
text.lines()
.filter(|l| l.contains("\"hash\""))
.filter_map(|line| {
let key = format!(
"{}/{}/{}",
field_of(line, "algorithm")?,
field_of(line, "field")?,
field_of(line, "samples")?
);
let value = format!(
"{} verts, {} tris, hash {}",
field_of(line, "vertices")?,
field_of(line, "triangles")?,
field_of(line, "hash")?
);
Some((key, value))
})
.collect()
}
fn as_pairs(entries: &[Entry]) -> Vec<(String, String)> {
entries
.iter()
.map(|e| {
(
format!("{}/{}/{}", e.algorithm, e.field, e.samples),
format!(
"{} verts, {} tris, hash {:016x}",
e.vertices, e.triangles, e.hash
),
)
})
.collect()
}
#[test]
fn golden_hashes_are_unchanged() {
let computed = as_pairs(&compute_all());
if std::env::var("ISOMESH_BLESS").is_ok() {
let entries = compute_all();
std::fs::write(fixture_path(), render(&entries)).expect("write fixture");
std::println!(
"measured: blessed {} golden hashes to {}",
entries.len(),
fixture_path().display()
);
return;
}
let committed = read_fixture();
assert!(
!committed.is_empty(),
"no fixture at {} -- run with ISOMESH_BLESS=1 to create it",
fixture_path().display()
);
let mut drifted = Vec::new();
for (key, got) in &computed {
match committed.iter().find(|(k, _)| k == key) {
Some((_, want)) if want == got => {}
Some((_, want)) => drifted.push(format!(" {key}\n was {want}\n now {got}")),
None => drifted.push(format!(" {key}\n was ABSENT\n now {got}")),
}
}
for (key, want) in &committed {
if !computed.iter().any(|(k, _)| k == key) {
drifted.push(format!(" {key}\n was {want}\n now ABSENT"));
}
}
assert!(
drifted.is_empty(),
"{} of {} golden combinations drifted:\n{}\n\nIf this is intended, regenerate with \
ISOMESH_BLESS=1 and read the diff before committing it.",
drifted.len(),
computed.len(),
drifted.join("\n")
);
}
#[test]
fn a_corrupted_case_table_changes_the_hash_and_names_the_combination() {
use crate::RuntimeShape3;
use crate::fields::{ReferenceField, Sphere};
use crate::marching_cubes::table::CASES;
use crate::property::extraction::march_with_table;
let field = Sphere::<f64>::canonical();
let (lo, hi) = field.domain();
let samples = 17u32;
let cell_size = (hi[0] - lo[0]) / f64::from(samples - 1);
let shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let honest = march_with_table(&field, &shape, lo, cell_size, &CASES);
let honest_hash = hash_mesh(&honest);
let committed = read_fixture();
let want = committed
.iter()
.find(|(k, _)| k == "marching_cubes/sphere/17")
.map(|(_, v)| v.clone())
.expect("marching_cubes/sphere/17 in the fixture");
assert!(
want.contains(&format!("hash {honest_hash:016x}")),
"the test double disagrees with the committed hash: {want} vs {honest_hash:016x}"
);
let mut cases = CASES;
let victim = (1..255usize)
.find(|&c| cases[c].count > 0)
.expect("some non-empty case");
cases[victim].triangles[0].swap(1, 2);
let corrupted = march_with_table(&field, &shape, lo, cell_size, &cases);
let corrupted_hash = hash_mesh(&corrupted);
assert_ne!(
honest_hash, corrupted_hash,
"a flipped triangle in case {victim} did not change the hash, so the golden \
fixture cannot detect a case-table regression"
);
let message = format!(
" marching_cubes/sphere/{samples}\n was ... hash {honest_hash:016x}\n now ... hash {corrupted_hash:016x}"
);
assert!(message.contains("marching_cubes/sphere/17"), "{message}");
std::println!(
"measured: T-007 mutation check -- flipping one triangle in case {victim} moved the hash {honest_hash:016x} -> {corrupted_hash:016x}"
);
}
#[test]
fn the_hash_separates_signed_zero_and_notices_truncation() {
use crate::MeshBuffer;
let base = MeshBuffer::<f64> {
positions: alloc::vec![[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
normals: alloc::vec![[1.0, 0.0, 0.0]; 3],
indices: alloc::vec![0, 1, 2],
};
let mut signed = base.clone();
signed.positions[0][0] = -0.0;
assert_ne!(
hash_mesh(&base),
hash_mesh(&signed),
"a sign flip on a zero coordinate must change the hash -- it is exactly what a \
reordered summation produces"
);
let mut truncated = base.clone();
truncated.indices.clear();
assert_ne!(hash_mesh(&base), hash_mesh(&truncated));
assert_eq!(hash_mesh(&base), hash_mesh(&base.clone()));
}
#[test]
fn the_committed_fixture_is_canonically_formatted() {
if std::env::var("ISOMESH_BLESS").is_ok() {
return;
}
let on_disk = std::fs::read_to_string(fixture_path()).unwrap_or_default();
assert!(!on_disk.is_empty(), "no fixture committed");
assert_eq!(
on_disk,
render(&compute_all()),
"the fixture is not in canonical form; regenerate with ISOMESH_BLESS=1"
);
}
#[test]
fn every_combination_is_covered() {
let entries = compute_all();
assert_eq!(entries.len(), 7 * 8 * 3, "{} combinations", entries.len());
let unique: Vec<String> = {
let mut keys: Vec<String> = as_pairs(&entries).into_iter().map(|(k, _)| k).collect();
keys.sort();
keys.dedup();
keys
};
assert_eq!(unique.len(), entries.len(), "duplicate combination keys");
}