133 lines
6.3 KiB
Rust
133 lines
6.3 KiB
Rust
//! Standalone native test probe; never shipped or used to launch extensions.
|
|
use std::net::{SocketAddr, TcpStream, UdpSocket};
|
|
use std::time::Duration;
|
|
fn main() {
|
|
let args: Vec<_> = std::env::args().collect();
|
|
if args.get(1).is_some_and(|s| s.starts_with("mcp")) {
|
|
use std::io::{BufRead, Write};
|
|
fn read(reader: &mut impl BufRead) -> String { let mut line = String::new(); reader.read_line(&mut line).unwrap(); line }
|
|
fn id(line: &str) -> &str { line.split("\"id\":\"").nth(1).unwrap().split('"').next().unwrap() }
|
|
fn reply(id: &str, result: &str) { println!("{{\"jsonrpc\":\"2.0\",\"id\":\"{id}\",\"result\":{result}}}"); std::io::stdout().flush().unwrap(); }
|
|
let mut input = std::io::stdin().lock();
|
|
let request = read(&mut input);
|
|
assert!(request.contains("\"method\":\"initialize\""));
|
|
reply(id(&request), if args[1] == "mcp_bad_version" { r#"{"protocolVersion":"unknown","capabilities":{},"serverInfo":{"name":"fixture","version":"1"}}"# } else { r#"{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},"serverInfo":{"name":"fixture","version":"1"}}"# });
|
|
assert!(read(&mut input).contains("notifications/initialized"));
|
|
let request = read(&mut input);
|
|
assert!(request.contains("tools/list"));
|
|
reply(id(&request), r#"{"tools":[{"name":"echo","inputSchema":{"type":"object"}}]}"#);
|
|
let mut request = read(&mut input);
|
|
assert!(request.contains("tools/call"));
|
|
if args[1] == "mcp_cancel" || args[1] == "mcp_deadline" {
|
|
let _child = std::process::Command::new(std::env::current_exe().unwrap()).arg("wait").spawn().unwrap();
|
|
std::thread::sleep(Duration::from_secs(120));
|
|
return;
|
|
}
|
|
if args[1] == "mcp_remote_error" {
|
|
println!("{{\"jsonrpc\":\"2.0\",\"id\":\"{}\",\"error\":{{\"code\":-32602,\"message\":\"fixture error\"}}}}", id(&request));
|
|
std::io::stdout().flush().unwrap();
|
|
request = read(&mut input);
|
|
assert!(request.contains("tools/call"));
|
|
}
|
|
println!(r#"{{"jsonrpc":"2.0","id":"server-ping","method":"ping"}}"#);
|
|
std::io::stdout().flush().unwrap();
|
|
let ping = read(&mut input);
|
|
assert!(ping.contains("server-ping") && ping.contains("result"));
|
|
println!(r#"{{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}}"#);
|
|
reply(if args[1] == "mcp_wrong_id" { "wrong-request" } else { id(&request) }, r#"{"content":[{"type":"text","text":"native MCP success"}]}"#);
|
|
// MCP server remains alive between calls until Host closes stdin.
|
|
while !read(&mut input).is_empty() {}
|
|
return;
|
|
}
|
|
if args.get(1).is_some_and(|s| s == "stderr_flood" || s == "stdout_flood") {
|
|
use std::io::Write;
|
|
let _child = std::process::Command::new(std::env::current_exe().unwrap()).arg("wait").spawn().unwrap();
|
|
if args[1] == "stderr_flood" { std::io::stderr().write_all(&vec![b'x'; 2 * 1024 * 1024]).unwrap(); }
|
|
else { std::io::stdout().write_all(&vec![b'x'; 2 * 1024 * 1024 + 1]).unwrap(); }
|
|
std::thread::sleep(Duration::from_secs(120));
|
|
return;
|
|
}
|
|
if args.get(1).is_some_and(|s| s == "file_rpc") {
|
|
use std::io::{Read, Write};
|
|
#[link(name = "kernel32")]
|
|
extern "system" { fn GetHandleInformation(handle: *mut std::ffi::c_void, flags: *mut u32) -> i32; }
|
|
let sentinel: usize = args[2].parse().unwrap();
|
|
let mut flags = 0;
|
|
if unsafe { GetHandleInformation(sentinel as *mut _, &mut flags) } != 0 { std::process::exit(85); }
|
|
println!("{{\"method\":\"notes.read\",\"path\":\"fixture.md\"}}");
|
|
std::io::stdout().flush().unwrap();
|
|
let mut response = String::new();
|
|
std::io::stdin().take(4096).read_to_string(&mut response).unwrap();
|
|
if !response.contains("\"content\":\"from host broker\"") { std::process::exit(86); }
|
|
println!("{{\"ok\":true}}");
|
|
eprintln!("fixture diagnostic");
|
|
return;
|
|
}
|
|
if args.get(1).is_some_and(|s| s == "wait_tree") {
|
|
let mut child = std::process::Command::new(std::env::current_exe().unwrap())
|
|
.arg("wait")
|
|
.spawn()
|
|
.unwrap();
|
|
std::thread::sleep(Duration::from_secs(120));
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
std::process::exit(84);
|
|
}
|
|
if args.get(1).is_some_and(|s| s == "wait") {
|
|
std::thread::sleep(Duration::from_secs(120));
|
|
std::process::exit(84);
|
|
}
|
|
if args.get(1).is_some_and(|s| s == "launch") {
|
|
let expected = [
|
|
"launch",
|
|
"",
|
|
"space value",
|
|
"引号🦀",
|
|
"trailing\\",
|
|
"a\"b",
|
|
"slash\\\"quote",
|
|
"&|%PATH%",
|
|
"line\nbreak",
|
|
];
|
|
if args[1..] != expected {
|
|
std::process::exit(82);
|
|
}
|
|
let environment: std::collections::BTreeMap<_, _> = std::env::vars_os().collect();
|
|
let names: std::collections::BTreeSet<_> =
|
|
environment.keys().map(|k| k.to_str().unwrap()).collect();
|
|
if names
|
|
!= ["CUSTOM", "LOCALAPPDATA", "SYSTEMROOT", "TEMP", "TMP"]
|
|
.into_iter()
|
|
.collect()
|
|
|| std::env::var("CUSTOM").as_deref() != Ok("declared=值")
|
|
|| std::env::var_os("TEMP") != std::env::var_os("TMP")
|
|
|| std::path::PathBuf::from(std::env::var_os("LOCALAPPDATA").unwrap()).join("Temp")
|
|
!= std::path::PathBuf::from(std::env::var_os("TEMP").unwrap())
|
|
{
|
|
std::process::exit(83);
|
|
}
|
|
std::process::exit(0);
|
|
}
|
|
if args.len() != 3 {
|
|
std::process::exit(79);
|
|
}
|
|
let address: SocketAddr = args[2].parse().unwrap();
|
|
let result = match args[1].as_str() {
|
|
"tcp" => TcpStream::connect_timeout(&address, Duration::from_secs(2)).map(|_| ()),
|
|
"udp" => UdpSocket::bind(if address.is_ipv4() {
|
|
"0.0.0.0:0"
|
|
} else {
|
|
"[::]:0"
|
|
})
|
|
.and_then(|socket| socket.send_to(b"probe", address))
|
|
.map(|_| ()),
|
|
_ => std::process::exit(79),
|
|
};
|
|
std::process::exit(match result {
|
|
Ok(()) => 0,
|
|
Err(error) if error.raw_os_error() == Some(10013) => 77,
|
|
Err(error) if error.kind() == std::io::ErrorKind::TimedOut => 80,
|
|
Err(_) => 81,
|
|
});
|
|
}
|