sync from monorepo @ 2452e92e
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
//! Integration test for concurrent agent requests (T050)
|
||||
//!
|
||||
//! This test verifies that the system can handle multiple agent requests
|
||||
//! simultaneously without cross-contamination.
|
||||
//!
|
||||
//! Test scenario:
|
||||
//! 1. Register multiple pending requests concurrently
|
||||
//! 2. Complete them in random/different order
|
||||
//! 3. Verify each response goes to the correct request
|
||||
//! 4. Verify no cross-contamination
|
||||
|
||||
use dirigent_acp_api::agent_requests::AgentRequestTracker;
|
||||
use serde_json::json;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_requests_basic() {
|
||||
let tracker = AgentRequestTracker::new();
|
||||
|
||||
let client_id = "test-client";
|
||||
|
||||
// Register 5 concurrent requests
|
||||
let mut receivers = Vec::new();
|
||||
for i in 0..5 {
|
||||
let rx = tracker.register(client_id, json!(i));
|
||||
receivers.push((i, rx));
|
||||
}
|
||||
|
||||
assert_eq!(tracker.pending_count(), 5);
|
||||
|
||||
// Complete them in reverse order
|
||||
for i in (0..5).rev() {
|
||||
let response = json!({"request": i, "result": "success"});
|
||||
tracker.complete(client_id, json!(i), response).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(tracker.pending_count(), 0);
|
||||
|
||||
// Verify each receiver got the correct response
|
||||
for (i, rx) in receivers {
|
||||
let response = rx.await.unwrap();
|
||||
assert_eq!(response["request"], i);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_requests_multiple_clients() {
|
||||
let tracker = AgentRequestTracker::new();
|
||||
|
||||
// Two clients, each with 3 requests
|
||||
let client1 = "client-1";
|
||||
let client2 = "client-2";
|
||||
|
||||
let mut receivers1 = Vec::new();
|
||||
let mut receivers2 = Vec::new();
|
||||
|
||||
for i in 0..3 {
|
||||
let rx1 = tracker.register(client1, json!(i));
|
||||
let rx2 = tracker.register(client2, json!(i));
|
||||
receivers1.push((i, rx1));
|
||||
receivers2.push((i, rx2));
|
||||
}
|
||||
|
||||
assert_eq!(tracker.pending_count(), 6);
|
||||
assert_eq!(tracker.client_pending_count(client1), 3);
|
||||
assert_eq!(tracker.client_pending_count(client2), 3);
|
||||
|
||||
// Complete client1's requests
|
||||
for i in 0..3 {
|
||||
let response = json!({"client": 1, "request": i});
|
||||
tracker.complete(client1, json!(i), response).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(tracker.client_pending_count(client1), 0);
|
||||
assert_eq!(tracker.client_pending_count(client2), 3);
|
||||
|
||||
// Complete client2's requests
|
||||
for i in 0..3 {
|
||||
let response = json!({"client": 2, "request": i});
|
||||
tracker.complete(client2, json!(i), response).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(tracker.pending_count(), 0);
|
||||
|
||||
// Verify each receiver got the correct response
|
||||
for (i, rx) in receivers1 {
|
||||
let response = rx.await.unwrap();
|
||||
assert_eq!(response["client"], 1);
|
||||
assert_eq!(response["request"], i);
|
||||
}
|
||||
|
||||
for (i, rx) in receivers2 {
|
||||
let response = rx.await.unwrap();
|
||||
assert_eq!(response["client"], 2);
|
||||
assert_eq!(response["request"], i);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_requests_same_id_different_clients() {
|
||||
// Test that same request_id for different clients are handled independently
|
||||
let tracker = AgentRequestTracker::new();
|
||||
|
||||
let client1 = "client-1";
|
||||
let client2 = "client-2";
|
||||
let request_id = json!(0); // Same ID for both
|
||||
|
||||
let rx1 = tracker.register(client1, request_id.clone());
|
||||
let rx2 = tracker.register(client2, request_id.clone());
|
||||
|
||||
assert_eq!(tracker.pending_count(), 2);
|
||||
|
||||
// Complete client1's request
|
||||
let response1 = json!({"client": "client-1"});
|
||||
tracker.complete(client1, request_id.clone(), response1.clone()).unwrap();
|
||||
|
||||
// Complete client2's request
|
||||
let response2 = json!({"client": "client-2"});
|
||||
tracker.complete(client2, request_id, response2.clone()).unwrap();
|
||||
|
||||
assert_eq!(tracker.pending_count(), 0);
|
||||
|
||||
// Verify each got the correct response
|
||||
let received1 = rx1.await.unwrap();
|
||||
let received2 = rx2.await.unwrap();
|
||||
|
||||
assert_eq!(received1["client"], "client-1");
|
||||
assert_eq!(received2["client"], "client-2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_async_completion() {
|
||||
// Test completing requests from multiple async tasks concurrently
|
||||
let tracker = AgentRequestTracker::new();
|
||||
|
||||
let client_id = "test-client";
|
||||
let num_requests = 10;
|
||||
|
||||
// Register requests
|
||||
let mut receivers = Vec::new();
|
||||
for i in 0..num_requests {
|
||||
let rx = tracker.register(client_id, json!(i));
|
||||
receivers.push((i, rx));
|
||||
}
|
||||
|
||||
assert_eq!(tracker.pending_count(), num_requests);
|
||||
|
||||
// Spawn tasks to complete requests concurrently
|
||||
let mut join_set = JoinSet::new();
|
||||
|
||||
for i in 0..num_requests {
|
||||
let tracker_clone = tracker.clone();
|
||||
join_set.spawn(async move {
|
||||
// Small delay to ensure concurrency
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(((i % 3) * 10) as u64)).await;
|
||||
let response = json!({"request": i, "result": "success"});
|
||||
tracker_clone.complete(client_id, json!(i), response)
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all completions
|
||||
while let Some(result) = join_set.join_next().await {
|
||||
assert!(result.unwrap().is_ok());
|
||||
}
|
||||
|
||||
assert_eq!(tracker.pending_count(), 0);
|
||||
|
||||
// Verify all receivers got correct responses
|
||||
for (i, rx) in receivers {
|
||||
let response = rx.await.unwrap();
|
||||
assert_eq!(response["request"], i);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_register_and_complete() {
|
||||
// Test registering and completing requests concurrently
|
||||
let tracker = AgentRequestTracker::new();
|
||||
|
||||
let client_id = "test-client";
|
||||
let num_requests = 20;
|
||||
|
||||
let mut join_set = JoinSet::new();
|
||||
|
||||
// Spawn tasks to register and complete requests
|
||||
for i in 0..num_requests {
|
||||
let tracker_clone = tracker.clone();
|
||||
join_set.spawn(async move {
|
||||
// Register
|
||||
let rx = tracker_clone.register(client_id, json!(i));
|
||||
|
||||
// Small delay
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
|
||||
// Complete
|
||||
let response = json!({"request": i});
|
||||
tracker_clone.complete(client_id, json!(i), response.clone()).unwrap();
|
||||
|
||||
// Wait for response
|
||||
let received = rx.await.unwrap();
|
||||
assert_eq!(received["request"], i);
|
||||
|
||||
i
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all tasks
|
||||
let mut completed = Vec::new();
|
||||
while let Some(result) = join_set.join_next().await {
|
||||
completed.push(result.unwrap());
|
||||
}
|
||||
|
||||
// All requests should have completed
|
||||
assert_eq!(completed.len(), num_requests);
|
||||
assert_eq!(tracker.pending_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_mixed_operations() {
|
||||
// Test mix of register, complete, and timeout operations
|
||||
let tracker = AgentRequestTracker::new();
|
||||
|
||||
let client_id = "test-client";
|
||||
|
||||
let mut join_set = JoinSet::new();
|
||||
|
||||
// Spawn 15 tasks with different behaviors
|
||||
for i in 0..15 {
|
||||
let tracker_clone = tracker.clone();
|
||||
join_set.spawn(async move {
|
||||
let rx = tracker_clone.register(client_id, json!(i));
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
|
||||
match i % 3 {
|
||||
0 => {
|
||||
// Complete normally
|
||||
let response = json!({"request": i, "type": "complete"});
|
||||
tracker_clone.complete(client_id, json!(i), response.clone()).unwrap();
|
||||
let received = rx.await.unwrap();
|
||||
assert_eq!(received["type"], "complete");
|
||||
"completed"
|
||||
}
|
||||
1 => {
|
||||
// Timeout
|
||||
tracker_clone.timeout(client_id, json!(i));
|
||||
assert!(rx.await.is_err());
|
||||
"timeout"
|
||||
}
|
||||
_ => {
|
||||
// Complete with delay
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(20)).await;
|
||||
let response = json!({"request": i, "type": "delayed"});
|
||||
tracker_clone.complete(client_id, json!(i), response.clone()).unwrap();
|
||||
let received = rx.await.unwrap();
|
||||
assert_eq!(received["type"], "delayed");
|
||||
"delayed"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all tasks
|
||||
let mut results = Vec::new();
|
||||
while let Some(result) = join_set.join_next().await {
|
||||
results.push(result.unwrap());
|
||||
}
|
||||
|
||||
assert_eq!(results.len(), 15);
|
||||
|
||||
// Count outcomes
|
||||
let completed = results.iter().filter(|&r| r == &"completed").count();
|
||||
let timeout = results.iter().filter(|&r| r == &"timeout").count();
|
||||
let delayed = results.iter().filter(|&r| r == &"delayed").count();
|
||||
|
||||
assert_eq!(completed, 5);
|
||||
assert_eq!(timeout, 5);
|
||||
assert_eq!(delayed, 5);
|
||||
|
||||
// All should be cleaned up
|
||||
assert_eq!(tracker.pending_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_high_concurrency() {
|
||||
// Stress test with many concurrent requests
|
||||
let tracker = AgentRequestTracker::new();
|
||||
|
||||
let client_id = "test-client";
|
||||
let num_requests = 100;
|
||||
|
||||
let mut join_set = JoinSet::new();
|
||||
|
||||
for i in 0..num_requests {
|
||||
let tracker_clone = tracker.clone();
|
||||
join_set.spawn(async move {
|
||||
let rx = tracker_clone.register(client_id, json!(i));
|
||||
|
||||
// Random-ish delay
|
||||
let delay = ((i * 7) % 20) as u64;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(delay)).await;
|
||||
|
||||
let response = json!({"request": i});
|
||||
tracker_clone.complete(client_id, json!(i), response).unwrap();
|
||||
|
||||
rx.await.unwrap()["request"].as_u64().unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
// Collect all results
|
||||
let mut results = Vec::new();
|
||||
while let Some(result) = join_set.join_next().await {
|
||||
results.push(result.unwrap());
|
||||
}
|
||||
|
||||
// Verify all requests completed
|
||||
assert_eq!(results.len(), num_requests);
|
||||
|
||||
// Verify all request IDs are present
|
||||
results.sort();
|
||||
for (idx, &val) in results.iter().enumerate() {
|
||||
assert_eq!(val, idx as u64);
|
||||
}
|
||||
|
||||
// All cleaned up
|
||||
assert_eq!(tracker.pending_count(), 0);
|
||||
}
|
||||
Reference in New Issue
Block a user