use implicit_predicates_gen::{Generated, generate};
use proc_macro2::TokenStream;
const REGENERATE: &str = "run the tests.";
const ORIENT3D: &str = include_str!("fixtures/orient3d.pred");
const INSPHERE: &str = include_str!("fixtures/insphere.pred");
const SOURCES: [(&str, &str); 2] = [
(ORIENT3D, "predicates/orient3d.pred"),
(INSPHERE, "predicates/insphere.pred"),
];
fn generated(source: &str, file: &str) -> Generated {
let mut all = generate(source, file, REGENERATE).unwrap();
assert_eq!(all.len(), 1);
all.remove(0)
}
fn case_literals(checks: &str) -> Vec<&str> {
table_literals(checks, "const BOUND_EDGE_CASES")
}
fn table_literals<'a>(checks: &'a str, name: &str) -> Vec<&'a str> {
let start = checks.find(name).expect("a table of cases");
let table = &checks[start..];
let table = &table[table.find('=').unwrap()..table.find(";\n").unwrap()];
table
.split(|c: char| !c.is_ascii_alphanumeric())
.filter(|word| word.starts_with("0x"))
.collect()
}
#[test]
fn the_checks_file_parses_as_rust() {
for (source, file) in SOURCES {
let g = generated(source, file);
assert!(!g.checks.is_empty(), "{file}");
g.checks
.parse::<TokenStream>()
.unwrap_or_else(|e| panic!("{file}: {e}"));
assert!(g.checks.starts_with(&format!(
"// Generated by implicit-predicates-gen from {file}. Do not edit.\n"
)));
assert!(g.checks.contains("pub fn exact("), "{file}");
assert!(g.checks.contains("pub fn bound_edge_cases("), "{file}");
}
}
#[test]
fn edge_cases_are_emitted_as_bit_patterns() {
for (source, file, points) in [
(ORIENT3D, "predicates/orient3d.pred", 4),
(INSPHERE, "predicates/insphere.pred", 5),
] {
let g = generated(source, file);
assert!(g.checks.contains("f64::from_bits"), "{file}");
let literals = case_literals(&g.checks);
assert_eq!(literals.len() % (3 * points), 0, "{file}");
let cases = literals.len() / (3 * points);
assert!(cases >= 64, "{file}: {cases} cases");
assert_eq!(cases % 2, 0, "{file}");
for literal in literals {
let digits = literal.strip_prefix("0x").unwrap().replace('_', "");
assert_eq!(digits.len(), 16, "{literal}");
let x = f64::from_bits(u64::from_str_radix(&digits, 16).unwrap());
assert!(x.is_finite(), "{literal}");
}
}
}
#[test]
fn generation_of_checks_is_deterministic() {
for (source, file) in SOURCES {
let first = generated(source, file);
let second = generated(source, file);
assert_eq!(first.checks, second.checks, "{file}");
}
}
#[test]
fn edge_cases_with_uncertainty_are_emitted_for_a_per_axis_filter_only() {
let g = generated(ORIENT3D, "predicates/orient3d.pred");
assert!(
g.checks
.contains("pub fn uncertainty_edge_cases() -> Vec<([[f64; 3]; 4], [f64; 3])> {"),
"{}",
g.checks
);
let literals = table_literals(&g.checks, "const UNCERTAINTY_EDGE_CASES");
assert_eq!(literals.len() % (3 * 4 + 3), 0);
let cases = literals.len() / (3 * 4 + 3);
assert!(cases >= 64, "{cases} cases");
assert_eq!(cases % 2, 0);
for literal in literals {
let digits = literal.strip_prefix("0x").unwrap().replace('_', "");
let x = f64::from_bits(u64::from_str_radix(&digits, 16).unwrap());
assert!(x.is_finite(), "{literal}");
}
let s = generated(INSPHERE, "predicates/insphere.pred");
assert!(!s.checks.contains("uncertainty_edge_cases"));
}
fn uncertainty_check(source: &str) -> String {
let checks = generated(source, "t.pred").checks;
let start = checks
.rfind("/// Runs `decide` on every case of [`uncertainty_edge_cases`]")
.expect("an uncertainty check");
checks[start..].to_string()
}
fn many_points(points: usize) -> String {
let params: Vec<String> = (0..points).map(|i| format!("p{i}: Point3")).collect();
let terms: Vec<String> = (0..points / 6)
.map(|k| {
let b = 6 * k;
let p = |i: usize| format!("p{}", b + i);
format!(
"({}.x - {}.x) * ({}.y - {}.y) * ({}.z - {}.z)",
p(0),
p(1),
p(2),
p(3),
p(4),
p(5)
)
})
.collect();
format!(
"fn many({}) -> Sign {{\n {}\n}}\n",
params.join(", "),
terms.join(" - ")
)
}
#[test]
fn the_uncertainty_check_takes_every_corner_up_to_four_points_and_a_sample_above() {
let check = uncertainty_check(ORIENT3D);
assert!(
check.contains("for corner in 0..1u64 << (3 * 4) {"),
"{check}"
);
assert!(check.contains("each of the `2^12` corners"), "{check}");
for points in [6, 24, 60] {
let check = uncertainty_check(&many_points(points));
assert!(!check.contains("1u64 <<"), "{points}: {check}");
let sample = format!("at {} of the `2^{}`", 2 * (1 + 3 * points), 3 * points);
assert!(check.contains(&sample), "{points}: {check}");
assert!(
check.contains("does not show that the sign holds"),
"{points}: {check}"
);
}
}
#[test]
fn the_cases_on_every_axis_are_those_of_the_axes_of_positive_degree() {
let orient2d = "fn orient2d(a: Point3, b: Point3, c: Point3) -> Sign {\n \
(a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)\n}\n";
let check = uncertainty_check(orient2d);
assert!(check.contains("[true, true, false] => 3,"), "{check}");
assert!(!check.contains("[true, true, true]"), "{check}");
assert!(
check.contains("every axis of positive degree, x and y"),
"{check}"
);
assert!(uncertainty_check(ORIENT3D).contains("[true, true, true] => 3,"));
let flat = "fn flat(a: Point3, b: Point3, c: Point3, d: Point3) -> Sign {\n \
(a.x - b.x) * (c.x - d.x)\n}\n";
let check = uncertainty_check(flat);
assert!(!check.contains("=> 3,"), "{check}");
}