diff --git a/src/arc_hashmap.rs b/src/arc_hashmap.rs index 68e0225..5bba749 100644 --- a/src/arc_hashmap.rs +++ b/src/arc_hashmap.rs @@ -94,6 +94,8 @@ impl ArcHashMap { #[cfg(test)] mod tests { + use std::{thread, time::Duration}; + use super::*; #[test] @@ -111,4 +113,194 @@ mod tests { } assert_eq!(m.len(), 0); } + + #[test] + fn test_insert_and_get() { + let map = ArcHashMap::default(); + + let value = map.insert("key1", "value1"); + assert_eq!(*value, "value1"); + + let retrieved = map.get(&"key1").unwrap(); + assert_eq!(*retrieved, "value1"); + } + + #[test] + fn test_get_or_insert_with() { + let map = ArcHashMap::default(); + let value = map.get_or_insert_with("key2", || "value2"); + assert_eq!(*value, "value2"); + + let retrieved = map.get(&"key2").unwrap(); + assert_eq!(*retrieved, "value2"); + + let existing = map.get_or_insert_with("key2", || "new value"); + assert_eq!(*existing, "value2"); + } + + #[test] + fn test_holder_behavior() { + let map = ArcHashMap::default(); + map.insert(1, "one"); + assert!(map.is_empty()); + let v = map.insert(1, "1"); + assert!(!map.is_empty()); + assert_eq!(map.len(), 1); + assert_eq!(*v, "1"); + } + + #[test] + fn test_len_and_is_emtpy() { + let map = ArcHashMap::default(); + assert!(map.is_empty()); + assert_eq!(map.len(), 0); + + let _v = map.insert("key1", "value"); + assert!(!map.is_empty()); + assert_eq!(map.len(), 1); + let _v1 = map.insert("key2", "value"); + assert_eq!(map.len(), 2); + } + + #[test] + fn test_drop_behavior() { + let map = ArcHashMap::default(); + { + let _v1 = map.insert("key1", "value1"); + assert_eq!(map.len(), 1); + { + let _v2 = map.insert("key2", "value2"); + assert_eq!(map.len(), 2); + } + assert_eq!(map.len(), 1); + } + assert_eq!(map.len(), 0) + } + #[test] + fn test_concurrent_access() { + let map = Arc::new(ArcHashMap::default()); + let threads: Vec<_> = (0..10) + .map(|i| { + let map_clone = Arc::clone(&map); + thread::spawn(move || { + let key = format!("key{}", i); + let value = format!("value{}", i); + let _v = map_clone.insert(key.clone(), value); + thread::sleep(Duration::from_millis(10)); + let retrieved = map_clone.get(&key).unwrap(); + assert_eq!(*retrieved, format!("value{}", i)); + }) + }) + .collect(); + + for thread in threads { + thread.join().unwrap(); + } + } + + #[test] + fn test_purge_behavior() { + let map = ArcHashMap::default(); + { + let _value1 = map.insert("key7", "value7"); + let _value2 = map.insert("key8", "value8"); + assert_eq!(map.len(), 2); + } + // After dropping both values, the map should be empty + assert_eq!(map.len(), 0); + + // Insert a new value to ensure the map still works after purging + let _value3 = map.insert("key9", "value9"); + assert_eq!(map.len(), 1); + } + + // New tests and edge cases + + #[test] + fn test_overwrite_existing_key() { + let map = ArcHashMap::default(); + let value1 = map.insert("key", "value1"); + assert_eq!(*value1, "value1"); + + let value2 = map.insert("key", "value2"); + assert_eq!(*value2, "value1"); // Should return the existing value + + let retrieved = map.get(&"key").unwrap(); + assert_eq!(*retrieved, "value1"); // Should still be the original value + } + + #[test] + fn test_get_nonexistent_key() { + let map: ArcHashMap<&str, &str> = ArcHashMap::default(); + assert!(map.get(&"nonexistent").is_none()); + } + + #[test] + fn test_multiple_references() { + let map = ArcHashMap::default(); + let value1 = map.insert("key", "value"); + let value2 = map.get(&"key").unwrap(); + let value3 = map.get(&"key").unwrap(); + + assert_eq!(*value1, "value"); + assert_eq!(*value2, "value"); + assert_eq!(*value3, "value"); + assert_eq!(map.len(), 1); + + drop(value1); + assert_eq!(map.len(), 1); + + drop(value2); + assert_eq!(map.len(), 1); + + drop(value3); + assert_eq!(map.len(), 0); + } + + #[test] + fn test_large_number_of_insertions() { + let map: ArcHashMap = ArcHashMap::default(); + + for i in 0..10000 { + let value = map.get_or_insert_with(i, || i.to_string()); + assert_eq!(*value, i.to_string()); + } + } + + #[test] + fn test_concurrent_insert_and_drop() { + let map = Arc::new(ArcHashMap::default()); + let threads: Vec<_> = (0..100) + .map(|i| { + let map_clone = Arc::clone(&map); + thread::spawn(move || { + let key = i % 10; // Use only 10 keys to force contention + let _value = map_clone.insert(key, i); + thread::sleep(Duration::from_millis(1)); + // The value is immediately dropped here + }) + }) + .collect(); + + for thread in threads { + thread.join().unwrap(); + } + + // All values should have been dropped + assert_eq!(map.len(), 0); + } + + #[test] + fn test_zero_sized_values() { + let map = ArcHashMap::default(); + let _v1 = map.insert("key1", ()); + let _v2 = map.insert("key2", ()); + + assert_eq!(map.len(), 2); + assert!(map.get(&"key1").is_some()); + assert!(map.get(&"key2").is_some()); + + drop(_v1); + assert_eq!(map.len(), 1); + } } diff --git a/src/expire_value.rs b/src/expire_value.rs index 197c4ab..ff3e09e 100644 --- a/src/expire_value.rs +++ b/src/expire_value.rs @@ -152,4 +152,233 @@ mod tests { // ensure provider was called again assert_eq!(*called.lock().await, 3); } + + #[tokio::test] + async fn test_basic_functionality() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + + let expire_value = ExpireValue::new(provider, Duration::from_secs(1)); + + // First call should call the provider + let v1 = expire_value.get().await.unwrap(); + assert_eq!(*v1, "test"); + assert_eq!(*called.lock().await, 1); + + // Second immediate call should use cached value + let v2 = expire_value.get().await.unwrap(); + assert_eq!(*v2, "test"); + assert_eq!(*called.lock().await, 1); + + // Wait for cache to expire + tokio::time::sleep(Duration::from_secs(2)).await; + + // This call should still use the weak reference + let v3 = expire_value.get().await.unwrap(); + assert_eq!(*v3, "test"); + assert_eq!(*called.lock().await, 1); + + // Drop all strong references + drop(v1); + drop(v2); + drop(v3); + + // This call should call the provider again + let v4 = expire_value.get().await.unwrap(); + assert_eq!(*v4, "test"); + assert_eq!(*called.lock().await, 2); + } + + #[tokio::test] + async fn test_clear_cache() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + + let expire_value = ExpireValue::new(provider, Duration::from_secs(10)); + + // First call + let _ = expire_value.get().await.unwrap(); + assert_eq!(*called.lock().await, 1); + + // Clear cache + expire_value.clear().await; + + // This should call the provider again + let _ = expire_value.get().await.unwrap(); + assert_eq!(*called.lock().await, 2); + } + + #[tokio::test] + async fn test_concurrent_access() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + + let expire_value = Arc::new(ExpireValue::new(provider, Duration::from_secs(1))); + + let mut handles = vec![]; + for _ in 0..10 { + let ev = expire_value.clone(); + handles.push(tokio::spawn(async move { ev.get().await.unwrap() })); + } + + for handle in handles { + let _ = handle.await.unwrap(); + } + + // Provider should only be called once despite concurrent access + assert_eq!(*called.lock().await, 1); + } + + #[tokio::test] + async fn test_error_propagation() { + struct ErrorProvider; + + #[async_trait] + impl ValueProvider for ErrorProvider { + async fn provide(&self) -> Result { + Err(std::io::Error::new(std::io::ErrorKind::Other, "Test error")) + } + } + + let expire_value = ExpireValue::new(ErrorProvider, Duration::from_secs(1)); + + let result = expire_value.get().await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::Other); + } + + #[tokio::test] + async fn test_very_long_expiration() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + + let expire_value = ExpireValue::new(provider, Duration::from_secs(1000)); + + // First call + let v1 = expire_value.get().await.unwrap(); + assert_eq!(*v1, "test"); + assert_eq!(*called.lock().await, 1); + + // Wait for a reasonable amount of time (e.g., 5 seconds) + tokio::time::sleep(Duration::from_secs(5)).await; + + // Second call should still use cached value due to very long expiration + let v2 = expire_value.get().await.unwrap(); + assert_eq!(*v2, "test"); + assert_eq!(*called.lock().await, 1); + } + #[tokio::test] + async fn test_rapid_consecutive_calls() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + + let expire_value = Arc::new(ExpireValue::new(provider, Duration::from_secs(1))); + + let mut handles = vec![]; + for _ in 0..100 { + let ev = expire_value.clone(); + handles.push(tokio::spawn(async move { ev.get().await.unwrap() })); + } + + for handle in handles { + let _ = handle.await.unwrap(); + } + + // Provider should only be called once despite rapid consecutive calls + assert_eq!(*called.lock().await, 1); + } + + #[tokio::test] + async fn test_alternating_clear_and_get() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + let expire_value = ExpireValue::new(provider, Duration::from_secs(1)); + for _ in 0..10 { + let _ = expire_value.get().await.unwrap(); + expire_value.clear().await; + } + + // Provider should be called 10 times due to alternating clear and get + assert_eq!(*called.lock().await, 10); + } + + #[tokio::test] + async fn test_expired_value_with_living_reference() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + let expire_value = ExpireValue::new(provider, Duration::from_millis(50)); + + // Get the initial value + let initial_value = expire_value.get().await.unwrap(); + assert_eq!(*called.lock().await, 1); + + // Wait for the value to expire + tokio::time::sleep(Duration::from_secs(1)).await; + + // ensure it's taken from Weak ref + let _ = expire_value.get().await.unwrap(); + assert_eq!(*called.lock().await, 1); + + // The initial value should still be valid and contain the original data + assert_eq!(*initial_value, "test"); + } + + #[tokio::test] + async fn test_drop_expire_value_with_living_reference() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + + let expire_value = ExpireValue::new(provider, Duration::from_secs(1)); + + // Get the initial value + let value = expire_value.get().await.unwrap(); + assert_eq!(*called.lock().await, 1); + + // Drop the ExpireValue instance + drop(expire_value); + + // The value should still be valid and contain the original data + assert_eq!(*value, "test"); + } + + #[tokio::test] + async fn test_multiple_clears_between_gets() { + let called = Arc::new(Mutex::new(0)); + let provider = TestProvider { + called: called.clone(), + }; + + let expire_value = ExpireValue::new(provider, Duration::from_secs(1)); + + // Initial get + let _ = expire_value.get().await.unwrap(); + assert_eq!(*called.lock().await, 1); + + // Multiple clears + for _ in 0..5 { + expire_value.clear().await; + } + + // Get after multiple clears + let _ = expire_value.get().await.unwrap(); + + // Provider should only be called twice (once for initial, once after clears) + assert_eq!(*called.lock().await, 2); + } }