- Added native E2E test suite in stub to validate server<->stub<->nvim IPC - Added win-nvim integration tests - Fixed empty capabilities schema in initialize response in server and win-nvim - Refactored server to accept dynamic MCP_PORT for test isolation - Made server exit gracefully on port 3000 collision
135 lines
4.2 KiB
Rust
135 lines
4.2 KiB
Rust
use serde_json::{json, Value};
|
|
use std::io::{BufRead, BufReader, Read, Write};
|
|
use std::process::{Command, Stdio};
|
|
use std::time::Duration;
|
|
use std::sync::Arc;
|
|
use tokio::sync::Mutex;
|
|
|
|
fn send_message(stdin: &mut std::process::ChildStdin, msg: Value) {
|
|
let s = serde_json::to_string(&msg).unwrap();
|
|
let payload = format!("Content-Length: {}\r\n\r\n{}", s.len(), s);
|
|
stdin.write_all(payload.as_bytes()).unwrap();
|
|
stdin.flush().unwrap();
|
|
}
|
|
|
|
fn read_message(stdout: &mut std::process::ChildStdout) -> Option<Value> {
|
|
let mut reader = BufReader::new(stdout);
|
|
let mut length = 0;
|
|
|
|
loop {
|
|
let mut line = String::new();
|
|
if reader.read_line(&mut line).unwrap_or(0) == 0 {
|
|
return None;
|
|
}
|
|
let line = line.trim_end();
|
|
if line.is_empty() {
|
|
break;
|
|
}
|
|
if let Some(len_str) = line.strip_prefix("Content-Length: ") {
|
|
length = len_str.parse().unwrap_or(0);
|
|
}
|
|
}
|
|
|
|
if length == 0 {
|
|
return None;
|
|
}
|
|
|
|
let mut buf = vec![0u8; length];
|
|
reader.read_exact(&mut buf).unwrap();
|
|
let body_str = String::from_utf8_lossy(&buf);
|
|
|
|
Some(serde_json::from_str(&body_str).unwrap())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_full_system_e2e_performance() {
|
|
let temp_dir = std::env::temp_dir().join(format!("mcp_e2e_{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs()));
|
|
std::fs::create_dir_all(&temp_dir).unwrap();
|
|
|
|
let test_port = "3031"; // Use a distinct port
|
|
|
|
let mut server_exe = std::env::current_exe().unwrap();
|
|
server_exe.pop();
|
|
server_exe.pop();
|
|
server_exe.push("mcp-memory-server.exe");
|
|
|
|
let mut nvim_exe = server_exe.clone();
|
|
nvim_exe.pop();
|
|
nvim_exe.push("mcp-memory-win-nvim.exe");
|
|
|
|
// 1. Start Server
|
|
let mut server = Command::new(&server_exe)
|
|
.env("MCP_PORT", test_port)
|
|
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.expect("Failed to start server");
|
|
|
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
|
|
// 2. Start Stub
|
|
let stub_exe = env!("CARGO_BIN_EXE_mcp-memory-stub");
|
|
let mut stub = Command::new(stub_exe)
|
|
.arg("--target")
|
|
.arg(format!("http://127.0.0.1:{}", test_port))
|
|
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.expect("Failed to start stub");
|
|
|
|
let mut stub_stdin = stub.stdin.take().unwrap();
|
|
let mut stub_stdout = stub.stdout.take().unwrap();
|
|
|
|
// 3. Start Win-Nvim
|
|
let mut win_nvim = Command::new(&nvim_exe)
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.expect("Failed to start win-nvim");
|
|
|
|
let mut nvim_stdin = win_nvim.stdin.take().unwrap();
|
|
let mut nvim_stdout = win_nvim.stdout.take().unwrap();
|
|
|
|
// Send 100 concurrent-like sequential rapid requests to Stub
|
|
let start_time = std::time::Instant::now();
|
|
for i in 1..=100 {
|
|
let tools_req = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/list",
|
|
"params": {},
|
|
"id": i
|
|
});
|
|
send_message(&mut stub_stdin, tools_req);
|
|
let resp = read_message(&mut stub_stdout).expect("Failed to read rapid response from stub");
|
|
assert_eq!(resp["id"], i);
|
|
}
|
|
let stub_duration = start_time.elapsed();
|
|
|
|
// Send 100 concurrent-like sequential rapid requests to Win-Nvim
|
|
let start_time_nvim = std::time::Instant::now();
|
|
for i in 1..=100 {
|
|
let tools_req = json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/list",
|
|
"params": {},
|
|
"id": i
|
|
});
|
|
send_message(&mut nvim_stdin, tools_req);
|
|
let resp = read_message(&mut nvim_stdout).expect("Failed to read rapid response from win-nvim");
|
|
assert_eq!(resp["id"], i);
|
|
}
|
|
let nvim_duration = start_time_nvim.elapsed();
|
|
|
|
println!("Stub 100 requests: {:?}", stub_duration);
|
|
println!("Win-Nvim 100 requests: {:?}", nvim_duration);
|
|
|
|
// Cleanup
|
|
let _ = stub.kill();
|
|
let _ = win_nvim.kill();
|
|
let _ = server.kill();
|
|
}
|