geolog-zeta-fork/tests/proptest_structure.rs

240 lines
7.8 KiB
Rust
Raw Permalink Normal View History

2026-02-26 11:50:51 +01:00
//! Property tests for Structure invariants
mod generators;
use generators::{StructureOp, StructureParams, check_structure_invariants};
use geolog::core::Structure;
use geolog::id::{NumericId, Slid};
use geolog::universe::Universe;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(2048))]
/// Empty structure maintains invariants
#[test]
fn empty_structure_invariants(num_sorts in 1usize..10) {
let structure = Structure::new(num_sorts);
prop_assert!(check_structure_invariants(&structure).is_ok());
prop_assert_eq!(structure.len(), 0);
prop_assert_eq!(structure.num_sorts(), num_sorts);
}
/// Structure maintains invariants after adding elements
#[test]
fn structure_invariants_after_adds(
(structure, _universe) in generators::arb_structure(StructureParams {
num_sorts: 4,
max_elements_per_sort: 10,
})
) {
prop_assert!(check_structure_invariants(&structure).is_ok());
}
/// add_element correctly sets up bijection
#[test]
fn add_element_bijection(
num_sorts in 1usize..5,
sort_id in any::<prop::sample::Index>(),
) {
let sort_id = sort_id.index(num_sorts);
let mut universe = Universe::new();
let mut structure = Structure::new(num_sorts);
let (slid, luid) = structure.add_element(&mut universe, sort_id);
// Forward: slid → luid
prop_assert_eq!(structure.luids[slid.index()], luid);
// Reverse: luid → slid
prop_assert_eq!(structure.luid_to_slid.get(&luid), Some(&slid));
prop_assert_eq!(structure.lookup_luid(luid), Some(slid));
// Sort is correct
prop_assert_eq!(structure.sorts[slid.index()], sort_id);
// Carrier contains the element
prop_assert!(structure.carriers[sort_id].contains(slid.index() as u64));
}
/// Carrier membership is exclusive (element in exactly one carrier)
#[test]
fn carrier_membership_exclusive(
ops in generators::arb_structure_ops(5, 20)
) {
let mut universe = Universe::new();
let mut structure = Structure::new(5);
for op in ops {
match op {
StructureOp::AddElement { sort_id } => {
structure.add_element(&mut universe, sort_id);
}
}
}
// Check each element appears in exactly one carrier
for slid in 0..structure.len() {
let sort_id = structure.sorts[slid];
let mut found_in = Vec::new();
for (carrier_id, carrier) in structure.carriers.iter().enumerate() {
if carrier.contains(slid as u64) {
found_in.push(carrier_id);
}
}
prop_assert_eq!(
found_in.len(), 1,
"slid {} should be in exactly one carrier, found in {:?}",
slid, found_in
);
prop_assert_eq!(found_in[0], sort_id);
}
}
/// sort_local_id is consistent with carrier rank
#[test]
fn sort_local_id_consistency(
(structure, _universe) in generators::arb_structure(StructureParams {
num_sorts: 3,
max_elements_per_sort: 8,
})
) {
for slid_idx in 0..structure.len() {
let slid = Slid::from_usize(slid_idx);
let sort_id = structure.sorts[slid_idx];
let sort_slid = structure.sort_local_id(slid);
// sort_slid should be in range [0, carrier_size)
let carrier_size = structure.carrier_size(sort_id);
prop_assert!(
sort_slid.index() < carrier_size,
"sort_slid {} should be < carrier_size {}",
sort_slid, carrier_size
);
}
}
/// carrier_size matches number of elements with that sort
#[test]
fn carrier_size_matches_count(
(structure, _universe) in generators::arb_structure(StructureParams {
num_sorts: 4,
max_elements_per_sort: 12,
})
) {
for sort_id in 0..structure.num_sorts() {
let carrier_size = structure.carrier_size(sort_id);
let count = structure.sorts.iter().filter(|&&s| s == sort_id).count();
prop_assert_eq!(carrier_size, count);
}
}
/// add_element_with_luid preserves existing element identity
#[test]
fn add_with_existing_luid_identity(num_sorts in 1usize..5) {
let mut universe = Universe::new();
let mut structure1 = Structure::new(num_sorts);
// Create element in first structure
let (slid1, luid1) = structure1.add_element(&mut universe, 0);
// Create second structure and add element with same luid
let mut structure2 = Structure::new(num_sorts);
let slid2 = structure2.add_element_with_luid(luid1, 0);
// Should have same luid
prop_assert_eq!(structure2.luids[slid2.index()], luid1);
prop_assert_eq!(structure2.lookup_luid(luid1), Some(slid2));
// Both structures should maintain invariants
prop_assert!(check_structure_invariants(&structure1).is_ok());
prop_assert!(check_structure_invariants(&structure2).is_ok());
let _ = slid1; // silence warning
}
/// get_luid returns correct luid for slid
#[test]
fn get_luid_correctness(
(structure, _universe) in generators::arb_structure(StructureParams {
num_sorts: 3,
max_elements_per_sort: 10,
})
) {
for slid_idx in 0..structure.len() {
let slid = Slid::from_usize(slid_idx);
let luid = structure.get_luid(slid);
prop_assert_eq!(structure.luids[slid_idx], luid);
prop_assert_eq!(structure.lookup_luid(luid), Some(slid));
}
}
/// Total elements equals sum of carrier sizes
#[test]
fn total_equals_carrier_sum(
(structure, _universe) in generators::arb_structure(StructureParams {
num_sorts: 5,
max_elements_per_sort: 8,
})
) {
let carrier_total: usize = (0..structure.num_sorts())
.map(|s| structure.carrier_size(s))
.sum();
prop_assert_eq!(structure.len(), carrier_total);
}
/// Sequential add_elements produce sequential slids
#[test]
fn sequential_slids(ops in generators::arb_structure_ops(3, 15)) {
let mut universe = Universe::new();
let mut structure = Structure::new(3);
let mut expected_slid_idx: usize = 0;
for op in ops {
match op {
StructureOp::AddElement { sort_id } => {
let (slid, _) = structure.add_element(&mut universe, sort_id);
prop_assert_eq!(slid, Slid::from_usize(expected_slid_idx));
expected_slid_idx += 1;
}
}
}
prop_assert_eq!(structure.len(), expected_slid_idx);
}
}
// Additional focused tests
proptest! {
/// Function initialization creates correct storage
#[test]
fn function_init_correct_size(
(mut structure, _universe) in generators::arb_structure(StructureParams {
num_sorts: 3,
max_elements_per_sort: 5,
})
) {
// Initialize functions with domain sort IDs
let domain_sort_ids: Vec<Option<usize>> = vec![Some(0), Some(1), None];
structure.init_functions(&domain_sort_ids);
prop_assert_eq!(structure.num_functions(), 3);
// Check sizes match carrier sizes
prop_assert_eq!(
structure.functions[0].len(),
structure.carrier_size(0)
);
prop_assert_eq!(
structure.functions[1].len(),
structure.carrier_size(1)
);
// Function 2 has None domain, so size should be 0
prop_assert_eq!(structure.functions[2].len(), 0);
}
}