use std::panic::AssertUnwindSafe; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use codex_extension_api::ExtensionData; use pretty_assertions::assert_eq; #[test] fn typed_values_can_be_inserted_replaced_and_removed() { let data = ExtensionData::new("thread-1"); assert_eq!(data.insert(/*value*/ 41_u64), None); assert_eq!(data.insert("alpha".to_string()), None); assert_eq!(data.get::().as_deref(), Some(&41)); assert_eq!( data.get::().map(|value| value.as_str().to_string()), Some("alpha".to_string()) ); assert_eq!(data.insert(/*value*/ 42_u64).as_deref(), Some(&41)); assert_eq!(data.get::().as_deref(), Some(&42)); assert_eq!( data.remove::() .map(|value| value.as_str().to_string()), Some("alpha".to_string()) ); assert_eq!(data.get::(), None); assert_eq!(data.get::().as_deref(), Some(&42)); } #[test] fn get_or_init_initializes_once_and_returns_shared_value() { const CALLER_COUNT: usize = 8; #[derive(Debug, PartialEq, Eq)] struct SharedValue(usize); let data = Arc::new(ExtensionData::new("session")); let callers_started = Arc::new(AtomicUsize::new(0)); let initialization_count = Arc::new(AtomicUsize::new(0)); let handles: [_; CALLER_COUNT] = std::array::from_fn(|_| { let data = Arc::clone(&data); let callers_started = Arc::clone(&callers_started); let initialization_count = Arc::clone(&initialization_count); std::thread::spawn(move || { callers_started.fetch_add(1, Ordering::SeqCst); data.get_or_init(|| { initialization_count.fetch_add(1, Ordering::SeqCst); // Keep the first initializer active until every worker has attempted // get_or_init, forcing callers to overlap on the same missing entry. while callers_started.load(Ordering::SeqCst) < CALLER_COUNT { std::thread::yield_now(); } SharedValue(7) }) }) }); let values = handles .into_iter() .map(|handle| handle.join().expect("initializer thread should succeed")) .collect::>(); assert_eq!(initialization_count.load(Ordering::SeqCst), 1); assert_eq!( values.iter().map(Arc::as_ref).collect::>(), vec![&SharedValue(7); CALLER_COUNT] ); assert!( values .iter() .skip(1) .all(|value| Arc::ptr_eq(&values[0], value)) ); } #[test] fn stores_are_isolated_and_preserve_level_id() { let session_data = ExtensionData::new("root-1"); let thread_data = ExtensionData::new("root-1"); session_data.insert(/*value*/ 17_u32); thread_data.insert("thread value".to_string()); assert_eq!(session_data.level_id(), "root-1"); assert_eq!(thread_data.level_id(), "root-1"); assert_eq!(session_data.get::().as_deref(), Some(&17)); assert_eq!(session_data.get::(), None); assert_eq!(thread_data.get::(), None); assert_eq!( thread_data .get::() .map(|value| value.as_str().to_string()), Some("thread value".to_string()) ); } #[test] fn store_remains_usable_after_panicking_initializer() { let data = ExtensionData::new("turn-1"); let result = std::panic::catch_unwind(AssertUnwindSafe(|| { data.get_or_init::(|| panic!("initializer failed")); })); assert!(result.is_err()); assert_eq!(*data.get_or_init(|| 99_u64), 99); }