jcp-gateway: the stdio tests' awk server answers on any awk #234
1 changed files with 34 additions and 3 deletions
|
|
@ -6,7 +6,7 @@
|
|||
//!
|
||||
//! The server is awk, answering only the lines that carry an `id`, as an MCP
|
||||
//! server answers requests and never notifications. It runs on every host
|
||||
//! this crate builds on.
|
||||
//! this crate builds on, whichever awk the host has ([`awk_server`]).
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
|
|
@ -18,6 +18,33 @@ use std::time::Duration;
|
|||
/// call takes, on a host at a load of 14 to 260.
|
||||
const LINE_DEADLINE: Duration = Duration::from_secs(120);
|
||||
|
||||
/// The command line of an awk server running `script` over a pipe.
|
||||
///
|
||||
/// An awk reading a pipe must hand each line to the script as it arrives, or
|
||||
/// the server never answers a request until its client closes the pipe. mawk,
|
||||
/// the default awk on Debian and Ubuntu hosts, buffers piped input in blocks
|
||||
/// unless it is run with `-W interactive`; the other awks read lines as they
|
||||
/// come and reject or ignore that option. So the awk is asked what it is: a
|
||||
/// host whose awk is mawk once failed both tests at their deadline, while the
|
||||
/// same tests passed where the awk was another.
|
||||
fn awk_server(script: &str) -> Vec<String> {
|
||||
const AWK: &str = "/usr/bin/awk";
|
||||
let is_mawk = Command::new(AWK)
|
||||
.args(["-W", "version"])
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.map(|o| {
|
||||
String::from_utf8_lossy(&o.stdout).contains("mawk") || String::from_utf8_lossy(&o.stderr).contains("mawk")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let mut argv = vec![AWK.to_string()];
|
||||
if is_mawk {
|
||||
argv.extend(["-W".to_string(), "interactive".to_string()]);
|
||||
}
|
||||
argv.push(script.to_string());
|
||||
argv
|
||||
}
|
||||
|
||||
/// An MCP-ish server in awk: a request (a line with an `id`) gets one
|
||||
/// response carrying that id; a line without one gets nothing.
|
||||
const ANSWERS_REQUESTS_ONLY: &str = r#"/"id"/ {
|
||||
|
|
@ -111,7 +138,7 @@ fn call(id: i64) -> Value {
|
|||
/// without waiting for a reply, or it never reads another line.
|
||||
#[test]
|
||||
fn the_binary_serves_calls_after_the_initialized_notification() {
|
||||
let mut g = Gateway::spawn("smoke", &["/usr/bin/awk", ANSWERS_REQUESTS_ONLY], &[]);
|
||||
let mut g = Gateway::spawn("smoke", &awk_server(ANSWERS_REQUESTS_ONLY).iter().map(String::as_str).collect::<Vec<_>>(), &[]);
|
||||
g.send(&json!({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}));
|
||||
let init = g.next("the initialize response");
|
||||
assert_eq!(init["id"], json!(1), "{init}");
|
||||
|
|
@ -166,7 +193,11 @@ const TALKS_MID_CALL: &str = r#"/"method":"tools\/call"/ {
|
|||
/// the call's id that ends its span.
|
||||
#[test]
|
||||
fn the_binary_relays_what_a_server_sends_in_the_middle_of_a_call() {
|
||||
let mut g = Gateway::spawn("chatty", &["/usr/bin/awk", TALKS_MID_CALL], &["--upstream-self-contained"]);
|
||||
let mut g = Gateway::spawn(
|
||||
"chatty",
|
||||
&awk_server(TALKS_MID_CALL).iter().map(String::as_str).collect::<Vec<_>>(),
|
||||
&["--upstream-self-contained"],
|
||||
);
|
||||
g.send(&json!({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}));
|
||||
assert_eq!(g.next("the initialize response")["id"], json!(1));
|
||||
g.send(&json!({"jsonrpc":"2.0","method":"notifications/initialized"}));
|
||||
|
|
|
|||
Loading…
Reference in a new issue