diff --git a/crates/oxide_kernel/src/shape_forge.rs b/crates/oxide_kernel/src/shape_forge.rs index 211c8c2d..09f4a1c2 100644 --- a/crates/oxide_kernel/src/shape_forge.rs +++ b/crates/oxide_kernel/src/shape_forge.rs @@ -245,86 +245,6 @@ mod tests { use super::*; use std::sync::Barrier; - #[test] - fn empty_shape_exists() { - let forge = ShapeForge::new(); - let s = forge.get_shape(EMPTY_SHAPE_ID); - assert!(s.is_some()); - let s = s.unwrap(); - assert_eq!(s.id, EMPTY_SHAPE_ID); - assert!(s.parent.is_none()); - assert_eq!(s.depth, 0); - } - - #[test] - fn make_shape_creates_new_id() { - let forge = ShapeForge::new(); - let key: StringIndex = 1_000_000; - let s1 = forge.make_shape(EMPTY_SHAPE_ID, key); - assert!(s1 > EMPTY_SHAPE_ID); - let shape = forge.get_shape(s1).unwrap(); - assert_eq!(shape.property_name, key); - assert_eq!(shape.parent, Some(EMPTY_SHAPE_ID)); - assert_eq!(shape.depth, 1); - } - - #[test] - fn hash_cons_returns_same_id() { - let forge = ShapeForge::new(); - let key: StringIndex = 1_000_001; - let a = forge.make_shape(EMPTY_SHAPE_ID, key); - let b = forge.make_shape(EMPTY_SHAPE_ID, key); - assert_eq!(a, b); - } - - #[test] - fn different_props_different_ids() { - let forge = ShapeForge::new(); - let a = forge.make_shape(EMPTY_SHAPE_ID, 1_000_002); - let b = forge.make_shape(EMPTY_SHAPE_ID, 1_000_003); - assert_ne!(a, b); - } - - #[test] - fn chain_of_three() { - let forge = ShapeForge::new(); - let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); - let s2 = forge.make_shape(s1, 1_000_020); - let s3 = forge.make_shape(s2, 1_000_030); - - assert_eq!(forge.shape_prop_count(s3), 3); - - assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); - assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); - assert_eq!(forge.lookup_position(s3, 1_000_010), Some(0)); - assert_eq!(forge.lookup_position(s3, 99), None); - } - - #[test] - fn lookup_position_cached_second_call() { - let forge = ShapeForge::new(); - let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); - let s2 = forge.make_shape(s1, 1_000_020); - let s3 = forge.make_shape(s2, 1_000_030); - - // 首次调用填充缓存。 - assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); - // 二次调用命中缓存(验证无回归)。 - assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); - assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); - assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); - } - - #[test] - fn clear_transient_clears_positions() { - let forge = ShapeForge::new(); - let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); - assert_eq!(forge.lookup_position(s1, 1_000_010), Some(0)); - forge.clear_transient(); - // 清理后只剩 EMPTY_SHAPE,其余全部移除。 - assert_eq!(forge.shapes.read().unwrap().len(), 1); - } - /// overflow 态复位钉:曾触发 overflow 后清空,overflow 标志与映射须一并清空, /// 后续 make_shape 从新空间取小 id(而非旧 overflow 映射 id)。 #[test] @@ -342,78 +262,6 @@ mod tests { assert_eq!(id, 2); } - #[test] - fn two_branches_share_ancestor() { - let forge = ShapeForge::new(); - let base = forge.make_shape(EMPTY_SHAPE_ID, 1_000_040); - let branch_a = forge.make_shape(base, 1_000_050); - let branch_b = forge.make_shape(base, 1_000_060); - assert_ne!(branch_a, branch_b); - let a_shape = forge.get_shape(branch_a).unwrap(); - let b_shape = forge.get_shape(branch_b).unwrap(); - assert_eq!(a_shape.parent, Some(base)); - assert_eq!(b_shape.parent, Some(base)); - } - - #[test] - fn edge_same_structure_different_names() { - let forge = ShapeForge::new(); - let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_070); - let s2 = forge.make_shape(s1, 1_000_080); - let s3 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_090); - let s4 = forge.make_shape(s3, 1_000_100); - assert_ne!(s1, s3); - assert_ne!(s2, s4); - assert_eq!(forge.lookup_position(s2, 1_000_070), Some(0)); - assert_eq!(forge.lookup_position(s4, 1_000_090), Some(0)); - } - - #[test] - fn concurrent_make_same_key() { - let forge = Arc::new(ShapeForge::new()); - let key: StringIndex = 1_000_200; - let barrier = Arc::new(Barrier::new(2)); - - let f1 = Arc::clone(&forge); - let b1 = Arc::clone(&barrier); - let h1 = std::thread::spawn(move || { - b1.wait(); - f1.make_shape(EMPTY_SHAPE_ID, key) - }); - - let f2 = Arc::clone(&forge); - let b2 = Arc::clone(&barrier); - let h2 = std::thread::spawn(move || { - b2.wait(); - f2.make_shape(EMPTY_SHAPE_ID, key) - }); - - let id1 = h1.join().unwrap(); - let id2 = h2.join().unwrap(); - assert_eq!(id1, id2); - assert!(id1 > EMPTY_SHAPE_ID); - } - - #[test] - fn has_property_works() { - let forge = ShapeForge::new(); - let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_300); - let s2 = forge.make_shape(s1, 1_000_310); - assert!(forge.has_property(s2, 1_000_300)); - assert!(forge.has_property(s2, 1_000_310)); - assert!(!forge.has_property(s2, 99)); - } - - #[test] - fn private_band_keys_are_shape_properties() { - let forge = ShapeForge::new(); - let private_key = oxide_types::private_key::make_private_name_id(12); - let shape = forge.make_shape(EMPTY_SHAPE_ID, private_key); - assert!(oxide_types::private_key::is_private_name_key(private_key)); - assert!(forge.has_property(shape, private_key)); - assert_eq!(forge.lookup_position(shape, private_key), Some(0)); - } - #[test] #[cfg_attr(debug_assertions, should_panic(expected = "exceeds 24-bit"))] fn overflow_debug_assert_fires() { diff --git a/crates/oxide_kernel/tests/shape_forge_tests.rs b/crates/oxide_kernel/tests/shape_forge_tests.rs new file mode 100644 index 00000000..2731fb92 --- /dev/null +++ b/crates/oxide_kernel/tests/shape_forge_tests.rs @@ -0,0 +1,153 @@ +use oxide_kernel::shape_forge::{ShapeForge, StringIndex, EMPTY_SHAPE_ID}; +use std::sync::{Arc, Barrier}; + +#[test] +fn empty_shape_exists() { + let forge = ShapeForge::new(); + let s = forge.get_shape(EMPTY_SHAPE_ID); + assert!(s.is_some()); + let s = s.unwrap(); + assert_eq!(s.id, EMPTY_SHAPE_ID); + assert!(s.parent.is_none()); + assert_eq!(s.depth, 0); +} + +#[test] +fn make_shape_creates_new_id() { + let forge = ShapeForge::new(); + let key: StringIndex = 1_000_000; + let s1 = forge.make_shape(EMPTY_SHAPE_ID, key); + assert!(s1 > EMPTY_SHAPE_ID); + let shape = forge.get_shape(s1).unwrap(); + assert_eq!(shape.property_name, key); + assert_eq!(shape.parent, Some(EMPTY_SHAPE_ID)); + assert_eq!(shape.depth, 1); +} + +#[test] +fn hash_cons_returns_same_id() { + let forge = ShapeForge::new(); + let key: StringIndex = 1_000_001; + let a = forge.make_shape(EMPTY_SHAPE_ID, key); + let b = forge.make_shape(EMPTY_SHAPE_ID, key); + assert_eq!(a, b); +} + +#[test] +fn different_props_different_ids() { + let forge = ShapeForge::new(); + let a = forge.make_shape(EMPTY_SHAPE_ID, 1_000_002); + let b = forge.make_shape(EMPTY_SHAPE_ID, 1_000_003); + assert_ne!(a, b); +} + +#[test] +fn chain_of_three() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); + let s2 = forge.make_shape(s1, 1_000_020); + let s3 = forge.make_shape(s2, 1_000_030); + + assert_eq!(forge.shape_prop_count(s3), 3); + + assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); + assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); + assert_eq!(forge.lookup_position(s3, 1_000_010), Some(0)); + assert_eq!(forge.lookup_position(s3, 99), None); +} + +#[test] +fn lookup_position_cached_second_call() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); + let s2 = forge.make_shape(s1, 1_000_020); + let s3 = forge.make_shape(s2, 1_000_030); + + // 首次调用填充缓存。 + assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); + // 二次调用命中缓存(验证无回归)。 + assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); + assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); + assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); +} + +#[test] +fn clear_transient_clears_positions() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_010); + assert_eq!(forge.lookup_position(s1, 1_000_010), Some(0)); + forge.clear_transient(); + // 清理后只剩 EMPTY_SHAPE,其余全部移除。 + assert_eq!(forge.len(), 1); +} +#[test] +fn two_branches_share_ancestor() { + let forge = ShapeForge::new(); + let base = forge.make_shape(EMPTY_SHAPE_ID, 1_000_040); + let branch_a = forge.make_shape(base, 1_000_050); + let branch_b = forge.make_shape(base, 1_000_060); + assert_ne!(branch_a, branch_b); + let a_shape = forge.get_shape(branch_a).unwrap(); + let b_shape = forge.get_shape(branch_b).unwrap(); + assert_eq!(a_shape.parent, Some(base)); + assert_eq!(b_shape.parent, Some(base)); +} + +#[test] +fn edge_same_structure_different_names() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_070); + let s2 = forge.make_shape(s1, 1_000_080); + let s3 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_090); + let s4 = forge.make_shape(s3, 1_000_100); + assert_ne!(s1, s3); + assert_ne!(s2, s4); + assert_eq!(forge.lookup_position(s2, 1_000_070), Some(0)); + assert_eq!(forge.lookup_position(s4, 1_000_090), Some(0)); +} + +#[test] +fn concurrent_make_same_key() { + let forge = Arc::new(ShapeForge::new()); + let key: StringIndex = 1_000_200; + let barrier = Arc::new(Barrier::new(2)); + + let f1 = Arc::clone(&forge); + let b1 = Arc::clone(&barrier); + let h1 = std::thread::spawn(move || { + b1.wait(); + f1.make_shape(EMPTY_SHAPE_ID, key) + }); + + let f2 = Arc::clone(&forge); + let b2 = Arc::clone(&barrier); + let h2 = std::thread::spawn(move || { + b2.wait(); + f2.make_shape(EMPTY_SHAPE_ID, key) + }); + + let id1 = h1.join().unwrap(); + let id2 = h2.join().unwrap(); + assert_eq!(id1, id2); + assert!(id1 > EMPTY_SHAPE_ID); +} + +#[test] +fn has_property_works() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_300); + let s2 = forge.make_shape(s1, 1_000_310); + assert!(forge.has_property(s2, 1_000_300)); + assert!(forge.has_property(s2, 1_000_310)); + assert!(!forge.has_property(s2, 99)); +} + +#[test] +fn private_band_keys_are_shape_properties() { + let forge = ShapeForge::new(); + let private_key = oxide_types::private_key::make_private_name_id(12); + let shape = forge.make_shape(EMPTY_SHAPE_ID, private_key); + assert!(oxide_types::private_key::is_private_name_key(private_key)); + assert!(forge.has_property(shape, private_key)); + assert_eq!(forge.lookup_position(shape, private_key), Some(0)); +}