use crate::services::memory_integration::{
AstBufferPool, InternedStringSet, MemoryAwareCache, MemoryString, MemoryVec,
};
use crate::services::memory_manager::{
init_global_memory_manager_with_config, MemoryConfig, MemoryManager, PoolType,
};
use anyhow::Result;
use proptest::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tracing::info;
proptest! {
#[test]
fn test_memory_manager_allocation_patterns(
allocation_sizes in prop::collection::vec(1usize..64*1024, 1..20),
pool_types in prop::collection::vec(prop::sample::select(vec![
PoolType::AstParsing,
PoolType::StringIntern,
PoolType::AnalysisCache,
PoolType::FileContent,
PoolType::GraphConstruction,
]), 1..20)
) {
prop_assert!(test_allocation_patterns(allocation_sizes, pool_types).is_ok());
}
#[test]
fn test_string_interning_properties(
strings in prop::collection::vec(prop::string::string_regex("[a-zA-Z0-9_]{1,20}").unwrap(), 5..50),
duplication_factor in 1usize..3
) {
prop_assert!(test_string_interning(strings, duplication_factor).is_ok());
}
#[test]
fn test_memory_cleanup_properties(
initial_allocations in 10usize..50,
pressure_threshold in 0.5f64..0.95f64
) {
prop_assert!(test_cleanup_under_pressure(initial_allocations, pressure_threshold).is_ok());
}
#[test]
fn test_concurrent_memory_operations(
thread_count in 2usize..8,
operations_per_thread in 10usize..100
) {
prop_assert!(test_concurrent_operations(thread_count, operations_per_thread).is_ok());
}
#[test]
fn test_pool_efficiency_patterns(
buffer_sizes in prop::collection::vec(64usize..8192, 10..100),
reuse_probability in 0.1f64..0.9f64
) {
prop_assert!(test_pool_efficiency(buffer_sizes, reuse_probability).is_ok());
}
}
fn test_allocation_patterns(sizes: Vec<usize>, pool_types: Vec<PoolType>) -> Result<()> {
let manager = MemoryManager::new()?;
let mut buffers = Vec::new();
for (size, pool_type) in sizes.iter().zip(pool_types.iter().cycle()) {
let buffer = manager.allocate_buffer(*pool_type, *size)?;
assert!(buffer.capacity() >= *size);
assert_eq!(buffer.as_slice().len(), *size);
buffers.push(buffer);
}
let stats = manager.stats();
assert!(stats.total_allocated > 0);
assert!(stats.peak_usage >= stats.total_allocated);
drop(buffers);
let _cleaned = manager.cleanup()?;
Ok(())
}
fn test_string_interning(mut strings: Vec<String>, duplication_factor: usize) -> Result<()> {
let manager = MemoryManager::new()?;
let mut interned_strings = Vec::new();
for _ in 0..duplication_factor {
strings.extend(strings.clone());
}
for s in &strings {
let interned = manager.intern_string(s)?;
interned_strings.push(interned);
}
let mut string_map: HashMap<String, Arc<str>> = HashMap::new();
for interned in &interned_strings {
let key = interned.to_string();
if let Some(existing) = string_map.get(&key) {
assert!(
Arc::ptr_eq(existing, interned),
"Identical strings should share memory"
);
} else {
string_map.insert(key, Arc::clone(interned));
}
}
let unique_count = string_map.len();
let total_count = interned_strings.len();
if duplication_factor > 1 {
assert!(
unique_count < total_count,
"String interning should reduce memory usage"
);
}
Ok(())
}
fn test_cleanup_under_pressure(allocations: usize, pressure_threshold: f64) -> Result<()> {
let config = MemoryConfig {
max_total_memory: 16 * 1024 * 1024, cache_pressure_threshold: pressure_threshold,
..Default::default()
};
let manager = MemoryManager::with_config(config)?;
let mut buffers = Vec::new();
for i in 0..allocations {
let size = 1024 * (i % 100 + 1); let pool_type = match i % 5 {
0 => PoolType::AstParsing,
1 => PoolType::StringIntern,
2 => PoolType::AnalysisCache,
3 => PoolType::FileContent,
_ => PoolType::GraphConstruction,
};
if let Ok(buffer) = manager.allocate_buffer(pool_type, size) {
buffers.push(buffer);
}
let stats = manager.stats();
if stats.allocation_pressure > pressure_threshold {
break;
}
}
let stats_before = manager.stats();
let pressure_before = stats_before.allocation_pressure;
let cleaned = manager.cleanup()?;
let stats_after = manager.stats();
let _pressure_after = stats_after.allocation_pressure;
if pressure_before > pressure_threshold {
info!(
"Cleaned {} bytes when pressure was {:.1}%",
cleaned,
pressure_before * 100.0
);
}
Ok(())
}
fn test_concurrent_operations(thread_count: usize, operations: usize) -> Result<()> {
let manager = MemoryManager::new()?;
let manager = Arc::new(manager);
let handles: Vec<_> = (0..thread_count)
.map(|thread_id| {
let manager = Arc::clone(&manager);
thread::spawn(move || -> Result<()> {
let mut buffers = Vec::new();
let mut strings = Vec::new();
for i in 0..operations {
if i % 2 == 0 {
let size = 1024 + (i * 100) % 4096;
let pool_type = match thread_id % 3 {
0 => PoolType::AstParsing,
1 => PoolType::FileContent,
_ => PoolType::AnalysisCache,
};
if let Ok(buffer) = manager.allocate_buffer(pool_type, size) {
buffers.push(buffer);
}
} else {
let test_string = format!("thread_{}_op_{}", thread_id, i);
if let Ok(interned) = manager.intern_string(&test_string) {
strings.push(interned);
}
}
if i % 50 == 0 {
let _ = manager.cleanup();
}
thread::sleep(Duration::from_micros(1));
}
Ok(())
})
})
.collect();
for handle in handles {
handle.join().unwrap()?;
}
let _stats = manager.stats();
Ok(())
}
fn test_pool_efficiency(buffer_sizes: Vec<usize>, reuse_probability: f64) -> Result<()> {
let manager = MemoryManager::new()?;
let mut active_buffers = Vec::new();
for (i, &size) in buffer_sizes.iter().enumerate() {
let pool_type = PoolType::AstParsing;
let buffer = manager.allocate_buffer(pool_type, size)?;
active_buffers.push(buffer);
if (i % 100) as f64 / 100.0 < reuse_probability {
if !active_buffers.is_empty() {
let index = i % active_buffers.len();
active_buffers.remove(index);
}
}
if i % 20 == 0 {
let stats = manager.stats();
if let Some(pool_stats) = stats.pool_stats.get(&pool_type) {
if pool_stats.allocation_count > 10 {
assert!(pool_stats.allocation_count > 0);
}
}
}
}
active_buffers.clear();
let stats = manager.stats();
if let Some(pool_stats) = stats.pool_stats.get(&PoolType::AstParsing) {
if pool_stats.allocation_count > 0 {
let efficiency = pool_stats.reuse_ratio;
let capped_efficiency = efficiency.min(1.0).max(0.0);
assert!(
(0.0..=1.0).contains(&capped_efficiency),
"Efficiency should be in range [0.0, 1.0], got: {}",
efficiency
);
if reuse_probability > 0.5 && pool_stats.allocation_count > 20 {
assert!(
capped_efficiency >= 0.0,
"Should see valid efficiency with high reuse probability, got: {}",
capped_efficiency
);
}
}
}
Ok(())
}
#[test]
fn test_memory_vec_operations() -> Result<()> {
let config = MemoryConfig::default();
match init_global_memory_manager_with_config(config) {
Ok(()) => {}
Err(e) if e.to_string().contains("already initialized") => {}
Err(e) => return Err(e),
}
let mut vec = MemoryVec::new(PoolType::AstParsing)?;
vec.push("test1".to_string())?;
vec.push("test2".to_string())?;
vec.push("test3".to_string())?;
assert_eq!(vec.len(), 3);
assert!(vec.memory_usage() > 0);
let total_length =
vec.process_with_memory_awareness(|items| items.iter().map(|s| s.len()).sum::<usize>())?;
assert_eq!(total_length, "test1".len() + "test2".len() + "test3".len());
Ok(())
}
#[test]
fn test_memory_string_integration() -> Result<()> {
let config = MemoryConfig::default();
match init_global_memory_manager_with_config(config) {
Ok(()) => {}
Err(e) if e.to_string().contains("already initialized") => {}
Err(e) => return Err(e),
}
let str1 = MemoryString::new("shared_identifier")?;
let str2 = MemoryString::new("shared_identifier")?;
let str3 = MemoryString::new("different_identifier")?;
assert!(str1.shares_memory_with(&str2));
assert!(!str1.shares_memory_with(&str3));
assert_eq!(str1.as_str(), "shared_identifier");
assert_eq!(str2.as_str(), "shared_identifier");
assert_eq!(str3.as_str(), "different_identifier");
Ok(())
}
#[test]
fn test_ast_buffer_pool_integration() -> Result<()> {
let config = MemoryConfig::default();
match init_global_memory_manager_with_config(config) {
Ok(()) => {}
Err(e) if e.to_string().contains("already initialized") => {}
Err(e) => return Err(e),
}
let pool = AstBufferPool::new(PoolType::AstParsing)?;
let buffer1 = pool.get_buffer(1024)?;
let buffer2 = pool.get_buffer(2048)?;
let buffer3 = pool.get_buffer_for_content("fn main() { println!(\"Hello, world!\"); }")?;
assert!(buffer1.capacity() >= 1024);
assert!(buffer2.capacity() >= 2048);
assert!(buffer3.capacity() > 0);
Ok(())
}
#[test]
fn test_interned_string_set() -> Result<()> {
let config = MemoryConfig::default();
match init_global_memory_manager_with_config(config) {
Ok(()) => {}
Err(e) if e.to_string().contains("already initialized") => {}
Err(e) => return Err(e),
}
let mut set = InternedStringSet::new()?;
assert!(set.insert("identifier1")?);
assert!(!set.insert("identifier1")?); assert!(set.insert("identifier2")?);
let identifiers: Vec<_> = set.iter().collect();
assert_eq!(identifiers.len(), 2);
assert!(identifiers.contains(&"identifier1"));
assert!(identifiers.contains(&"identifier2"));
assert!(set.memory_usage() > 0);
Ok(())
}
#[test]
fn test_memory_aware_cache() -> Result<()> {
let config = MemoryConfig::default();
match init_global_memory_manager_with_config(config) {
Ok(()) => {}
Err(e) if e.to_string().contains("already initialized") => {}
Err(e) => return Err(e),
}
let mut cache = MemoryAwareCache::new(PoolType::AnalysisCache, 10)?;
cache.insert("key1", "value1")?;
cache.insert("key2", "value2")?;
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert_eq!(cache.get(&"key2"), Some(&"value2"));
assert_eq!(cache.get(&"nonexistent"), None);
let stats = cache.stats();
assert_eq!(stats.item_count, 2);
assert_eq!(stats.max_items, 10);
assert!(stats.estimated_memory > 0);
Ok(())
}
#[test]
fn test_extreme_memory_conditions() -> Result<()> {
let config = MemoryConfig {
max_total_memory: 1024 * 1024, ..Default::default()
};
let manager = MemoryManager::with_config(config)?;
let mut buffers = Vec::new();
let mut allocation_count = 0;
for _i in 0..1000 {
let size = 4096; match manager.allocate_buffer(PoolType::AstParsing, size) {
Ok(buffer) => {
buffers.push(buffer);
allocation_count += 1;
}
Err(_) => {
break;
}
}
let stats = manager.stats();
if stats.allocation_pressure > 0.9 {
let _ = manager.cleanup()?;
}
}
assert!(allocation_count > 0);
buffers.clear();
let _ = manager.cleanup()?;
Ok(())
}