160 lines
4.8 KiB
Rust
160 lines
4.8 KiB
Rust
//! Property tests for Universe (UUID ↔ Luid bijection)
|
|
|
|
mod generators;
|
|
|
|
use geolog::id::{Luid, NumericId, Uuid};
|
|
use geolog::universe::Universe;
|
|
use proptest::prelude::*;
|
|
use std::collections::HashSet;
|
|
use tempfile::tempdir;
|
|
|
|
proptest! {
|
|
/// Interning the same UUID twice returns the same Luid
|
|
#[test]
|
|
fn intern_idempotent(uuid in generators::arb_uuid()) {
|
|
let mut universe = Universe::new();
|
|
|
|
let luid1 = universe.intern(uuid);
|
|
let luid2 = universe.intern(uuid);
|
|
|
|
prop_assert_eq!(luid1, luid2);
|
|
}
|
|
|
|
/// Interning then looking up returns the original UUID
|
|
#[test]
|
|
fn intern_lookup_roundtrip(uuid in generators::arb_uuid()) {
|
|
let mut universe = Universe::new();
|
|
|
|
let luid = universe.intern(uuid);
|
|
let retrieved = universe.get(luid);
|
|
|
|
prop_assert_eq!(retrieved, Some(uuid));
|
|
}
|
|
|
|
/// Reverse lookup (UUID → Luid) works correctly
|
|
#[test]
|
|
fn reverse_lookup_roundtrip(uuid in generators::arb_uuid()) {
|
|
let mut universe = Universe::new();
|
|
|
|
let luid = universe.intern(uuid);
|
|
let found_luid = universe.lookup(&uuid);
|
|
|
|
prop_assert_eq!(found_luid, Some(luid));
|
|
}
|
|
|
|
/// After bulk interning, bijection holds for all entries
|
|
#[test]
|
|
fn bijection_after_bulk_intern(uuids in proptest::collection::vec(generators::arb_uuid(), 1..50)) {
|
|
let mut universe = Universe::new();
|
|
|
|
// Intern all UUIDs
|
|
let luids: Vec<_> = uuids.iter().map(|&uuid| universe.intern(uuid)).collect();
|
|
|
|
// Forward direction: Luid → UUID
|
|
for (&uuid, &luid) in uuids.iter().zip(luids.iter()) {
|
|
prop_assert_eq!(universe.get(luid), Some(uuid));
|
|
}
|
|
|
|
// Reverse direction: UUID → Luid
|
|
for &uuid in &uuids {
|
|
prop_assert!(universe.lookup(&uuid).is_some());
|
|
}
|
|
|
|
// Uniqueness: unique UUIDs produce unique Luids
|
|
let unique_uuids: HashSet<_> = uuids.iter().collect();
|
|
let unique_luids: HashSet<_> = luids.iter().collect();
|
|
// Note: Luids may have fewer unique values if there are duplicate UUIDs
|
|
prop_assert!(unique_luids.len() <= unique_uuids.len());
|
|
}
|
|
|
|
/// Luids are assigned sequentially starting from 0
|
|
#[test]
|
|
fn luids_sequential(count in 1usize..20) {
|
|
let mut universe = Universe::new();
|
|
|
|
for i in 0..count {
|
|
let uuid = Uuid::now_v7();
|
|
let luid = universe.intern(uuid);
|
|
prop_assert_eq!(luid, Luid::from_usize(i), "Luid {} should be {}", luid, i);
|
|
}
|
|
}
|
|
|
|
/// Save and load preserves all mappings
|
|
#[test]
|
|
fn save_load_roundtrip(uuids in generators::arb_unique_uuids(10)) {
|
|
let dir = tempdir().unwrap();
|
|
let path = dir.path().join("universe.bin");
|
|
|
|
// Save
|
|
let original_luids: Vec<_>;
|
|
{
|
|
let mut universe = Universe::with_path(&path);
|
|
original_luids = uuids.iter().map(|&uuid| universe.intern(uuid)).collect();
|
|
universe.save().unwrap();
|
|
}
|
|
|
|
// Load
|
|
{
|
|
let loaded = Universe::load(&path).unwrap();
|
|
|
|
// Check all mappings preserved
|
|
for (&uuid, &expected_luid) in uuids.iter().zip(original_luids.iter()) {
|
|
let retrieved = loaded.get(expected_luid);
|
|
prop_assert_eq!(retrieved, Some(uuid));
|
|
|
|
let found_luid = loaded.lookup(&uuid);
|
|
prop_assert_eq!(found_luid, Some(expected_luid));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Dirty flag is set after intern, cleared after save
|
|
#[test]
|
|
fn dirty_flag_consistency(uuid in generators::arb_uuid()) {
|
|
let dir = tempdir().unwrap();
|
|
let path = dir.path().join("universe.bin");
|
|
|
|
let mut universe = Universe::with_path(&path);
|
|
|
|
// Initially clean
|
|
prop_assert!(!universe.is_dirty());
|
|
|
|
// Dirty after intern
|
|
universe.intern(uuid);
|
|
prop_assert!(universe.is_dirty());
|
|
|
|
// Clean after save
|
|
universe.save().unwrap();
|
|
prop_assert!(!universe.is_dirty());
|
|
}
|
|
|
|
/// Iterator yields all interned UUIDs in order
|
|
#[test]
|
|
fn iter_yields_all(uuids in generators::arb_unique_uuids(15)) {
|
|
let mut universe = Universe::new();
|
|
|
|
for &uuid in &uuids {
|
|
universe.intern(uuid);
|
|
}
|
|
|
|
let iter_results: Vec<_> = universe.iter().collect();
|
|
|
|
prop_assert_eq!(iter_results.len(), uuids.len());
|
|
|
|
for (i, (luid, uuid)) in iter_results.iter().enumerate() {
|
|
prop_assert_eq!(*luid, Luid::from_usize(i));
|
|
prop_assert_eq!(*uuid, uuids[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Non-property unit tests for edge cases
|
|
|
|
#[test]
|
|
fn test_load_nonexistent() {
|
|
let dir = tempdir().unwrap();
|
|
let path = dir.path().join("nonexistent.bin");
|
|
let universe = Universe::load(&path).expect("load should succeed for nonexistent");
|
|
assert!(universe.is_empty());
|
|
}
|