use serde_json::json;
use std::sync::Arc;
use std::time::Instant;
use things3_cli::mcp::test_harness::McpTestHarness;
use tokio::task::JoinSet;
#[tokio::test]
async fn load_test_concurrent_tool_calls() {
let harness = Arc::new(McpTestHarness::new());
let concurrent_requests = 10;
let start = Instant::now();
let mut tasks = JoinSet::new();
for i in 0..concurrent_requests {
let harness_clone = Arc::clone(&harness);
tasks.spawn(async move {
let args = json!({"limit": 10});
let result = harness_clone.call_tool("get_inbox", Some(args)).await;
(i, result.is_error)
});
}
let mut error_count = 0;
while let Some(result) = tasks.join_next().await {
let (_, is_error) = result.unwrap();
if is_error {
error_count += 1;
}
}
let duration = start.elapsed();
println!(
"Concurrent requests ({}) completed in {:?}, errors: {}",
concurrent_requests, duration, error_count
);
assert_eq!(error_count, 0, "All concurrent requests should succeed");
assert!(
duration.as_secs() < 10,
"Concurrent requests should complete in under 10 seconds"
);
}
#[tokio::test]
async fn load_test_sustained_requests() {
let harness = McpTestHarness::new();
let total_requests = 50;
let start = Instant::now();
let mut error_count = 0;
let mut total_duration = std::time::Duration::ZERO;
for _ in 0..total_requests {
let request_start = Instant::now();
let result = harness
.call_tool("get_inbox", Some(json!({"limit": 5})))
.await;
let request_duration = request_start.elapsed();
total_duration += request_duration;
if result.is_error {
error_count += 1;
}
}
let total_time = start.elapsed();
let avg_response_time = total_duration / total_requests;
println!("Sustained load test:");
println!(" Total requests: {}", total_requests);
println!(" Total time: {:?}", total_time);
println!(" Average response time: {:?}", avg_response_time);
println!(" Errors: {}", error_count);
assert_eq!(error_count, 0, "All sustained requests should succeed");
assert!(
avg_response_time.as_millis() < 500,
"Average response time should be under 500ms"
);
}
#[tokio::test]
async fn load_test_mixed_workload() {
let harness = Arc::new(McpTestHarness::new());
let requests_per_tool = 5;
let tools = vec!["get_inbox", "get_today", "get_projects", "get_areas"];
let start = Instant::now();
let mut tasks = JoinSet::new();
for tool in &tools {
for _ in 0..requests_per_tool {
let harness_clone = Arc::clone(&harness);
let tool_name = tool.to_string();
tasks.spawn(async move {
let args = if tool_name == "get_areas" {
None
} else {
Some(json!({"limit": 5}))
};
harness_clone.call_tool(&tool_name, args).await.is_error
});
}
}
let mut error_count = 0;
while let Some(result) = tasks.join_next().await {
if result.unwrap() {
error_count += 1;
}
}
let duration = start.elapsed();
let total_requests = tools.len() * requests_per_tool;
println!("Mixed workload test:");
println!(" Total requests: {}", total_requests);
println!(" Duration: {:?}", duration);
println!(" Errors: {}", error_count);
assert_eq!(error_count, 0, "All mixed workload requests should succeed");
}
#[tokio::test]
async fn load_test_rapid_sequential() {
let harness = McpTestHarness::new();
let num_requests = 30;
let start = Instant::now();
let mut error_count = 0;
for _ in 0..num_requests {
let result = harness
.call_tool("get_inbox", Some(json!({"limit": 1})))
.await;
if result.is_error {
error_count += 1;
}
}
let duration = start.elapsed();
let avg_time = duration.as_millis() / num_requests as u128;
println!("Rapid sequential test:");
println!(" Requests: {}", num_requests);
println!(" Total time: {:?}", duration);
println!(" Average time: {}ms", avg_time);
println!(" Errors: {}", error_count);
assert_eq!(error_count, 0, "All rapid requests should succeed");
assert!(
avg_time < 200,
"Average request time should be under 200ms, was {}ms",
avg_time
);
}
#[tokio::test]
async fn load_test_high_concurrency() {
let harness = Arc::new(McpTestHarness::new());
let concurrent_requests = 25;
let start = Instant::now();
let mut tasks = JoinSet::new();
for _ in 0..concurrent_requests {
let harness_clone = Arc::clone(&harness);
tasks.spawn(async move {
harness_clone
.call_tool("get_inbox", Some(json!({"limit": 10})))
.await
.is_error
});
}
let mut error_count = 0;
while let Some(result) = tasks.join_next().await {
if result.unwrap() {
error_count += 1;
}
}
let duration = start.elapsed();
println!("High concurrency stress test:");
println!(" Concurrent requests: {}", concurrent_requests);
println!(" Duration: {:?}", duration);
println!(" Errors: {}", error_count);
assert!(
error_count < concurrent_requests / 5,
"Error rate should be less than 20% under stress"
);
}
#[tokio::test]
async fn load_test_response_times() {
let harness = McpTestHarness::new();
let num_samples = 20;
let mut response_times = Vec::new();
for _ in 0..num_samples {
let start = Instant::now();
let _ = harness
.call_tool("get_inbox", Some(json!({"limit": 5})))
.await;
let duration = start.elapsed();
response_times.push(duration.as_millis());
}
response_times.sort();
let p50_idx = ((50.0 / 100.0) * (num_samples - 1) as f64).round() as usize;
let p95_idx = ((95.0 / 100.0) * (num_samples - 1) as f64).round() as usize;
let p99_idx = ((99.0 / 100.0) * (num_samples - 1) as f64).round() as usize;
let p50 = response_times[p50_idx];
let p95 = response_times[p95_idx];
let p99 = response_times[p99_idx];
println!("Response time percentiles:");
println!(" P50: {}ms", p50);
println!(" P95: {}ms", p95);
println!(" P99: {}ms", p99);
assert!(p50 < 300, "P50 response time should be under 300ms");
assert!(p95 < 500, "P95 response time should be under 500ms");
assert!(p99 < 1000, "P99 response time should be under 1 second");
}
#[tokio::test]
async fn load_test_search_queries() {
let harness = McpTestHarness::new();
let queries = vec!["a", "test", "project", "important task"];
let start = Instant::now();
let mut error_count = 0;
for query in &queries {
for _ in 0..5 {
let result = harness
.call_tool("search_tasks", Some(json!({"query": query})))
.await;
if result.is_error {
error_count += 1;
}
}
}
let duration = start.elapsed();
let total_queries = queries.len() * 5;
println!("Search load test:");
println!(" Total queries: {}", total_queries);
println!(" Duration: {:?}", duration);
println!(" Errors: {}", error_count);
assert_eq!(error_count, 0, "All search queries should succeed");
}
#[tokio::test]
async fn load_test_concurrent_mixed_operations() {
let harness = Arc::new(McpTestHarness::new());
let operations_per_type = 5;
let start = Instant::now();
let mut tasks = JoinSet::new();
for _ in 0..operations_per_type {
let harness_clone = Arc::clone(&harness);
tasks.spawn(async move {
harness_clone
.call_tool("get_inbox", Some(json!({"limit": 10})))
.await
.is_error
});
}
for _ in 0..operations_per_type {
let harness_clone = Arc::clone(&harness);
tasks.spawn(async move {
harness_clone
.call_tool("search_tasks", Some(json!({"query": "test"})))
.await
.is_error
});
}
for _ in 0..operations_per_type {
let harness_clone = Arc::clone(&harness);
tasks.spawn(async move {
harness_clone
.call_tool("get_projects", Some(json!({"limit": 10})))
.await
.is_error
});
}
let mut error_count = 0;
while let Some(result) = tasks.join_next().await {
if result.unwrap() {
error_count += 1;
}
}
let duration = start.elapsed();
let total_ops = operations_per_type * 3;
println!("Concurrent mixed operations:");
println!(" Total operations: {}", total_ops);
println!(" Duration: {:?}", duration);
println!(" Errors: {}", error_count);
assert_eq!(
error_count, 0,
"All concurrent mixed operations should succeed"
);
}