From 7b50d6abd407ad833efdb86884edee1ce3c6ce86 Mon Sep 17 00:00:00 2001 From: David Li Date: Wed, 13 May 2026 14:37:59 +0800 Subject: [PATCH] =?UTF-8?q?test:=20Added=20real=20TCP=20daemon=20integrati?= =?UTF-8?q?on=20tests=20that=20spawn=20the=20actual=20wx=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/cli/transport.rs GSD context: - Milestone: M001 - TCP Transport - Slice: S04 - Task: T01 - Added real TCP daemon integration tests that spawn the actual wx binary, connect via TCP, verify ping round-trip, and test connection refused GSD-Task: S04/T01 --- src/cli/transport.rs | 117 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/cli/transport.rs b/src/cli/transport.rs index 6f8fd4e..bc69725 100644 --- a/src/cli/transport.rs +++ b/src/cli/transport.rs @@ -389,3 +389,120 @@ mod integration_tests { assert!(!result, "Expected is_alive_tcp to return false for unused port"); } } + +/// Real TCP daemon integration tests — spawn the actual `wx` daemon binary, +/// connect via TCP, and verify end-to-end request/response round-trip. +/// +/// These tests are `#[cfg(unix)]` only and require the `wx` binary to have +/// been built with `cargo build --bin wx`. +#[cfg(unix)] +#[cfg(test)] +mod tcp_integration_tests { + use super::*; + use crate::ipc::Request; + use std::process::Command; + + /// Build the `wx` binary so the daemon subprocess is available. + fn ensure_binary() -> std::path::PathBuf { + let status = Command::new("cargo") + .args(["build", "--bin", "wx"]) + .output() + .expect("cargo build failed to execute"); + if !status.status.success() { + panic!( + "cargo build --bin wx failed:\n{}", + String::from_utf8_lossy(&status.stderr) + ); + } + // Binary path: target/debug/wx + let mut p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + p.push("target/debug/wx"); + assert!(p.exists(), "wx binary not found at {:?}", p); + p + } + + /// Wait for the daemon TCP endpoint to become ready. + fn wait_for_tcp_ready(addr: &str) -> bool { + let deadline = std::time::Instant::now() + + std::time::Duration::from_secs(STARTUP_TIMEOUT_SECS); + while std::time::Instant::now() < deadline { + std::thread::sleep(std::time::Duration::from_millis(300)); + if is_alive_tcp(addr) { + return true; + } + } + false + } + + #[test] + fn test_tcp_daemon_ping_round_trip() { + let binary = ensure_binary(); + + // Pick a free ephemeral port + let port = { + let listener = std::net::TcpListener::bind("127.0.0.1:0") + .expect("failed to bind ephemeral port"); + listener.local_addr().unwrap().port() + }; + let addr = format!("127.0.0.1:{}", port); + + // Spawn the daemon subprocess in TCP-only mode + let mut child = Command::new(&binary) + .env("WX_DAEMON_MODE", "1") + .env("WX_DAEMON_TCP_ADDR", &addr) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("failed to spawn wx daemon"); + + let pid = child.id(); + eprintln!("[test] spawned daemon PID {}", pid); + + // Wait for TCP readiness + if !wait_for_tcp_ready(&addr) { + let _ = child.kill(); + let _ = child.wait(); + panic!( + "daemon did not become ready on {} within {}s (PID {})", + addr, STARTUP_TIMEOUT_SECS, pid + ); + } + eprintln!("[test] daemon ready on {}", addr); + + // Send Ping request and verify pong + let resp = send_tcp(Request::Ping, &addr) + .expect("send_tcp(Ping) should succeed"); + assert!(resp.ok, "Response ok flag should be true"); + + let pong = resp.data.get("pong").and_then(|v| v.as_bool()); + assert!( + pong == Some(true), + "Expected pong=true in response, got: {:?}", + resp.data + ); + + // Terminate daemon + unsafe { libc::kill(pid as libc::pid_t, libc::SIGTERM) }; + + // Verify clean exit + let exit_status = child.wait().expect("failed to wait on daemon"); + assert!( + exit_status.success(), + "daemon should exit cleanly, got: {:?}", + exit_status + ); + } + + #[test] + fn test_tcp_daemon_connection_refused() { + // Port 59889 is very unlikely to have a listener + let addr = "127.0.0.1:59889"; + let result = send_tcp(Request::Ping, addr); + assert!( + result.is_err(), + "Expected connection refused error when no daemon is listening on {}", + addr + ); + } +}