use crate::common::TestDatabaseManager;
use anyhow::Result;
use codex_memory::models::{SearchParams, SearchStrategy};
use codex_memory::Storage;
use serial_test::serial;
use std::sync::Arc;
use std::time::Instant;
use tokio::time::Duration;
#[tokio::test]
#[serial]
async fn test_search_performance_baseline() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let start_setup = Instant::now();
for i in 1..=100 {
let content = format!("Test memory content number {} with various programming topics including rust, python, javascript, and database design", i);
let context = format!("Performance test context for memory {}", i);
let summary = format!("Performance test summary {}", i);
let tags = vec![
format!("test-{}", i % 10),
"performance".to_string(),
if i % 3 == 0 {
"rust".to_string()
} else if i % 3 == 1 {
"python".to_string()
} else {
"javascript".to_string()
},
"programming".to_string(),
];
storage
.store(&content, context, summary, Some(tags))
.await?;
}
let setup_duration = start_setup.elapsed();
println!("Setup time for 100 memories: {:?}", setup_duration);
assert!(
setup_duration < Duration::from_secs(10),
"Setup should complete within 10 seconds"
);
let search_params = SearchParams {
query: "programming rust python".to_string(),
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
for _ in 0..3 {
let _ = storage.search_memories(search_params.clone()).await?;
}
let search_start = Instant::now();
let results = storage.search_memories(search_params.clone()).await?;
let search_duration = search_start.elapsed();
println!("Search time for 100 memories: {:?}", search_duration);
assert!(
search_duration < Duration::from_millis(500),
"Search should complete within 500ms"
);
assert!(!results.is_empty(), "Should find results");
let concurrent_start = Instant::now();
let mut handles = vec![];
for i in 0..10 {
let storage_clone = storage.clone();
let params = SearchParams {
query: format!("programming test {}", i),
similarity_threshold: 0.1,
max_results: 5,
..Default::default()
};
let handle = tokio::spawn(async move { storage_clone.search_memories(params).await });
handles.push(handle);
}
let mut concurrent_results = vec![];
for handle in handles {
let result = handle.await??;
concurrent_results.push(result);
}
let concurrent_duration = concurrent_start.elapsed();
println!(
"Concurrent search time (10 parallel): {:?}",
concurrent_duration
);
assert!(
concurrent_duration < Duration::from_secs(2),
"Concurrent searches should complete within 2 seconds"
);
assert_eq!(
concurrent_results.len(),
10,
"All concurrent searches should complete"
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_strategy_performance_comparison() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let content_types = [
(
"Machine learning algorithms and neural networks",
"AI research",
vec![
"machine-learning".to_string(),
"ai".to_string(),
"algorithms".to_string(),
],
),
(
"Web development with React and Node.js",
"Frontend development",
vec![
"web".to_string(),
"react".to_string(),
"javascript".to_string(),
],
),
(
"Database optimization techniques",
"Backend development",
vec![
"database".to_string(),
"optimization".to_string(),
"sql".to_string(),
],
),
(
"Rust systems programming guide",
"Programming tutorial",
vec![
"rust".to_string(),
"systems".to_string(),
"programming".to_string(),
],
),
(
"Python data analysis tutorial",
"Data science",
vec![
"python".to_string(),
"data-science".to_string(),
"analysis".to_string(),
],
),
];
for (base_content, base_context, base_tags) in content_types {
for i in 1..=20 {
let content = format!("{} - example {}", base_content, i);
let context = format!("{} - instance {}", base_context, i);
let summary = format!("Summary for example {}", i);
storage
.store(&content, context, summary, Some(base_tags.clone()))
.await?;
}
}
let query = "machine learning algorithms";
let strategies = [
SearchStrategy::TagsFirst,
SearchStrategy::ContentFirst,
SearchStrategy::Hybrid,
];
let mut strategy_timings = vec![];
for strategy in strategies {
let params = SearchParams {
query: query.to_string(),
search_strategy: strategy.clone(),
similarity_threshold: 0.1,
max_results: 10,
..Default::default()
};
let _ = storage.search_memories(params.clone()).await?;
let start = Instant::now();
let results = storage.search_memories(params).await?;
let duration = start.elapsed();
let strategy_clone = strategy.clone();
strategy_timings.push((strategy_clone, duration, results.len()));
println!(
"Strategy {:?}: {:?} ({} results)",
strategy,
duration,
results.len()
);
assert!(
duration < Duration::from_millis(200),
"Strategy {:?} should complete within 200ms",
strategy
);
assert!(
!results.is_empty(),
"Strategy {:?} should find results",
strategy
);
}
for (strategy, timing, _) in &strategy_timings {
assert!(
timing < &Duration::from_millis(200),
"Strategy {:?} performance regression",
strategy
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_large_result_set_performance() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let start_bulk_store = Instant::now();
for i in 1..=500 {
let content = format!("Programming tutorial {} covering rust python javascript and web development frameworks", i);
let context = "Tutorial content".to_string();
let summary = format!("Tutorial summary {}", i);
let tags = vec!["programming".to_string(), "tutorial".to_string()];
storage
.store(&content, context, summary, Some(tags))
.await?;
}
let bulk_store_duration = start_bulk_store.elapsed();
println!(
"Bulk store time for 500 memories: {:?}",
bulk_store_duration
);
let result_sizes = [10, 50, 100, 200];
for &max_results in &result_sizes {
let params = SearchParams {
query: "programming tutorial".to_string(),
max_results,
similarity_threshold: 0.1,
..Default::default()
};
let start = Instant::now();
let results = storage.search_memories(params).await?;
let duration = start.elapsed();
println!(
"Search for {} results: {:?} (found {})",
max_results,
duration,
results.len()
);
assert!(
duration < Duration::from_secs(1),
"Search for {} results should complete within 1 second",
max_results
);
assert!(
results.len() <= max_results,
"Should not exceed max_results limit"
);
assert!(!results.is_empty(), "Should find results");
let mut prev_score = 1.0;
for result in &results {
assert!(
result.combined_score <= prev_score,
"Results should be sorted by descending score"
);
prev_score = result.combined_score;
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_memory_usage_under_load() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let content_sizes = [100, 500, 1000, 2000];
for &size in &content_sizes {
for i in 1..=25 {
let content = "Programming tutorial content "
.repeat(size / 30)
.chars()
.take(size)
.collect::<String>();
let context = format!("Context for size {} memory {}", size, i);
let summary = format!("Summary {}", i);
let tags = vec![format!("size-{}", size), "performance".to_string()];
storage
.store(&content, context, summary, Some(tags))
.await?;
}
}
for &size in &content_sizes {
let params = SearchParams {
query: "programming tutorial".to_string(),
tag_filter: Some(vec![format!("size-{}", size)]),
max_results: 20,
..Default::default()
};
let start = Instant::now();
let results = storage.search_memories(params).await?;
let duration = start.elapsed();
println!(
"Search content size {}: {:?} ({} results)",
size,
duration,
results.len()
);
assert!(
duration < Duration::from_millis(300),
"Search should handle content size {} efficiently",
size
);
for result in &results {
assert!(result.memory.tags.contains(&format!("size-{}", size)));
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_stress_concurrent_operations() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
for i in 1..=50 {
let content = format!("Stress test content {}", i);
let context = "Stress test context".to_string();
let summary = format!("Stress summary {}", i);
let tags = vec!["stress".to_string(), "test".to_string()];
storage
.store(&content, context, summary, Some(tags))
.await?;
}
let start_stress = Instant::now();
let mut handles = vec![];
for i in 0..20 {
let storage_clone = storage.clone();
if i % 3 == 0 {
let handle = tokio::spawn(async move {
let content = format!("Concurrent store operation {}", i);
let context = "Concurrent context".to_string();
let summary = format!("Concurrent summary {}", i);
let tags = Some(vec!["concurrent".to_string()]);
storage_clone.store(&content, context, summary, tags).await
});
handles.push(handle);
} else {
let handle = tokio::spawn(async move {
let params = SearchParams {
query: format!("stress test {}", i),
max_results: 5,
..Default::default()
};
storage_clone
.search_memories(params)
.await
.map(|_| uuid::Uuid::new_v4())
});
handles.push(handle);
}
}
let mut results = vec![];
for handle in handles {
let result = handle.await?;
results.push(result);
}
let stress_duration = start_stress.elapsed();
println!(
"Stress test duration (20 concurrent ops): {:?}",
stress_duration
);
assert!(
stress_duration < Duration::from_secs(5),
"Stress test should complete within 5 seconds"
);
assert_eq!(
results.len(),
20,
"All concurrent operations should complete"
);
let final_params = SearchParams {
query: "stress test".to_string(),
max_results: 100,
..Default::default()
};
let final_results = storage.search_memories(final_params).await?;
assert!(
!final_results.is_empty(),
"Database should remain functional after stress"
);
manager.cleanup().await?;
Ok(())
}