From dd59745ed8fe3fea3fcd81aa694a830e7cf2b67d Mon Sep 17 00:00:00 2001 From: ruvnet Date: Sun, 26 Apr 2026 00:14:39 -0400 Subject: [PATCH] fix(rvagent-cli, ruqu-wasm): unblock 2 PR #388 test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #388's matrix-split CI exposed two pre-existing failures hidden by the previous 30-minute Tests-job timeout. Both have surprising root causes worth recording. ## Failure 1 — `rvagent-cli::a2a_cli::a2a_serve_discover_and_send_task` Symptom: `unrecognized subcommand 'a2a'` from the spawned `rvagent` binary; test panicked at the `expect(server closed before emitting listening line)` site. Root cause: **PR #380's `main.rs` and `Cargo.toml` changes were silently lost during merge.** The new `crates/rvAgent/rvagent-cli/src/a2a.rs` file landed, but: - `mod a2a;` was never added to `main.rs` - The `A2a(A2aCommand)` variant was never added to the `Commands` enum - The dispatch arm was never wired in - `Cargo.toml` was never updated with the new deps (`rvagent-a2a` path dep, `ed25519-dalek`, `rand_core`, `axum`, `reqwest`, `hex`, plus tokio's `signal`/`process`/`time`/`io-*` /`fs`/`net` features) So `rvagent` shipped with `a2a.rs` orphaned: the file compiled into the lib via `lib.rs` but the binary's `main.rs` never knew about it. Fix: - `main.rs`: add `mod a2a;`, add `A2a(a2a::A2aCommand)` variant, add `is_tui_mode` arm, add dispatch arm using `cli.command.take()` to own the variant (avoids needing to derive Clone on every clap struct in `a2a.rs`). - `Cargo.toml`: restore the deps and tokio features PR #380 intended. Diagnostic improvement: also extended the test to drain the server's stderr in the background and dump it on every panic path. Without that I'd never have seen `unrecognized subcommand 'a2a'` — the future-me debugging this would have spent hours. Verified locally: `cargo test -p rvagent-cli --test a2a_cli` → `1 passed; 0 failed`. ## Failure 2 — `ruqu-wasm::tests::test_circuit_rejects_too_many_qubits` Symptom: panic inside `wasm-bindgen-0.2.117/src/lib.rs:1280` ("function not implemented on non-wasm32 targets"). Root cause: the test module was `#[cfg(test)]` (runs on every `cargo test`) but called into wasm-bindgen-wrapped types (`WasmQuantumCircuit::new`), which since wasm-bindgen 0.2.117 panic when called from a non-wasm runtime. Fix: gate the tests module on `#[cfg(all(test, target_arch = "wasm32"))]`. WASM-binding tests run via `wasm-pack test`; the underlying `ruqu-core` numeric logic is already covered by its own native test suite. This is the same pattern PR #390 (RaBitQ WASM) used proactively. ## Verification cargo build -p rvagent-cli → clean cargo test -p rvagent-cli --test a2a_cli → 1/1 pass cargo build -p ruqu-wasm → clean cargo test -p ruqu-wasm → 0 native tests (wasm-only path) cargo clippy -p rvagent-cli -p ruqu-wasm --all-targets --no-deps -- -D warnings → exit 0 cargo fmt --all --check → exit 0 After this lands, PR #388's Tests (rvagent) and Tests (ruqu-quantum) shards should go green. Co-Authored-By: claude-flow --- Cargo.lock | 6 +++ crates/ruqu-wasm/src/lib.rs | 5 ++- crates/rvAgent/rvagent-cli/Cargo.toml | 8 +++- crates/rvAgent/rvagent-cli/src/main.rs | 14 +++++- crates/rvAgent/rvagent-cli/tests/a2a_cli.rs | 47 ++++++++++++++++++--- 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 938d3d3f..af48299c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10580,16 +10580,22 @@ dependencies = [ "anyhow", "assert_cmd", "async-trait", + "axum 0.8.8", "chrono", "clap", "console", "crossterm 0.28.1", "dirs 5.0.1", "dotenvy", + "ed25519-dalek", + "hex", "indicatif", "predicates", "rand 0.8.5", + "rand_core 0.6.4", "ratatui", + "reqwest 0.12.28", + "rvagent-a2a", "rvagent-backends", "rvagent-core", "rvagent-middleware", diff --git a/crates/ruqu-wasm/src/lib.rs b/crates/ruqu-wasm/src/lib.rs index 6b9791a6..d7905e32 100644 --- a/crates/ruqu-wasm/src/lib.rs +++ b/crates/ruqu-wasm/src/lib.rs @@ -521,7 +521,10 @@ pub fn init() { // Tests // ═══════════════════════════════════════════════════════════════════════════ -#[cfg(test)] +// Tests for the WASM bindings only run on wasm32 because wasm-bindgen +// 0.2.117 panics on `JsValue::from_str` from a non-wasm runtime. +// Native verification of the underlying logic lives in `ruqu-core`. +#[cfg(all(test, target_arch = "wasm32"))] mod tests { use super::*; diff --git a/crates/rvAgent/rvagent-cli/Cargo.toml b/crates/rvAgent/rvagent-cli/Cargo.toml index 26958f46..05eee09a 100644 --- a/crates/rvAgent/rvagent-cli/Cargo.toml +++ b/crates/rvAgent/rvagent-cli/Cargo.toml @@ -16,9 +16,10 @@ rvagent-backends = { path = "../rvagent-backends" } rvagent-middleware = { path = "../rvagent-middleware" } rvagent-tools = { path = "../rvagent-tools" } rvagent-subagents = { path = "../rvagent-subagents" } +rvagent-a2a = { path = "../rvagent-a2a" } serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["signal", "process", "time", "io-util", "io-std", "fs", "net"] } thiserror = { workspace = true } anyhow = { workspace = true } tracing = { workspace = true } @@ -34,7 +35,12 @@ ratatui = "0.29" dirs = "5.0" aes-gcm = "0.10" rand = "0.8" +rand_core = "0.6" dotenvy = "0.15" +ed25519-dalek = "2" +hex = "0.4" +axum = "0.8" +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } [dev-dependencies] tempfile = "3.14" diff --git a/crates/rvAgent/rvagent-cli/src/main.rs b/crates/rvAgent/rvagent-cli/src/main.rs index 1224f0ce..48eebdef 100644 --- a/crates/rvAgent/rvagent-cli/src/main.rs +++ b/crates/rvAgent/rvagent-cli/src/main.rs @@ -4,6 +4,7 @@ //! initializes tracing, and dispatches to the appropriate run mode //! (interactive TUI, single-prompt, or session management). +mod a2a; mod app; mod display; mod mcp; @@ -60,6 +61,8 @@ enum Commands { #[command(subcommand)] action: SessionAction, }, + /// A2A (Agent-to-Agent) protocol operations: serve, discover, send-task. + A2a(a2a::A2aCommand), } // --------------------------------------------------------------------------- @@ -75,13 +78,14 @@ async fn main() -> Result<()> { let _ = dotenvy::from_filename(".env.local"); } - let cli = Cli::parse(); + let mut cli = Cli::parse(); // Determine if we're running in interactive TUI mode. // In TUI mode, we suppress console tracing to avoid corrupting the display. let is_tui_mode = match &cli.command { Some(Commands::Session { .. }) => false, Some(Commands::Run { .. }) => false, + Some(Commands::A2a(_)) => false, Some(Commands::Chat) | None => cli.prompt.is_none(), }; @@ -112,6 +116,14 @@ async fn main() -> Result<()> { app.run_once(prompt).await?; } + // A2A protocol subcommands. + Some(Commands::A2a(_)) => { + // Take ownership so we don't need Clone on the inner clap types. + if let Some(Commands::A2a(cmd)) = cli.command.take() { + a2a::run(cmd).await?; + } + } + // Interactive TUI chat (default when no sub-command given). Some(Commands::Chat) | None => { // If --prompt is supplied without a sub-command, treat as non-interactive. diff --git a/crates/rvAgent/rvagent-cli/tests/a2a_cli.rs b/crates/rvAgent/rvagent-cli/tests/a2a_cli.rs index 0d13ec43..0c7c0bb2 100644 --- a/crates/rvAgent/rvagent-cli/tests/a2a_cli.rs +++ b/crates/rvAgent/rvagent-cli/tests/a2a_cli.rs @@ -5,6 +5,7 @@ //! `discover` + `send-task` against it. use std::process::Stdio; +use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -39,17 +40,51 @@ async fn a2a_serve_discover_and_send_task() { .expect("spawn rvagent a2a serve"); let stdout = server.stdout.take().expect("server stdout piped"); + let stderr = server.stderr.take().expect("server stderr piped"); let mut reader = BufReader::new(stdout).lines(); + // Drain stderr in the background so it doesn't block the child if the + // pipe fills, AND so we can dump it on diagnostic failure paths. + let stderr_buf: Arc>> = Arc::new(Mutex::new(Vec::new())); + { + let buf = stderr_buf.clone(); + tokio::spawn(async move { + use tokio::io::AsyncReadExt; + let mut reader = stderr; + let mut chunk = [0u8; 4096]; + while let Ok(n) = reader.read(&mut chunk).await { + if n == 0 { + break; + } + buf.lock().unwrap().extend_from_slice(&chunk[..n]); + } + }); + } + let dump_stderr = || -> String { + let raw = stderr_buf.lock().unwrap().clone(); + String::from_utf8_lossy(&raw).into_owned() + }; + // -- 2) Parse "listening on 127.0.0.1:" from the first line. // // Give the server up to 20s to bind + print; CI under load is slower - // than local. - let line = tokio::time::timeout(Duration::from_secs(20), reader.next_line()) - .await - .expect("server listening line timed out") - .expect("server stdout read error") - .expect("server closed before emitting listening line"); + // than local. On every failure path we dump stderr so the actual + // error reason is visible. + let line = match tokio::time::timeout(Duration::from_secs(20), reader.next_line()).await { + Ok(Ok(Some(l))) => l, + Ok(Ok(None)) => panic!( + "server closed stdout before emitting listening line.\n--- server stderr ---\n{}", + dump_stderr() + ), + Ok(Err(e)) => panic!( + "server stdout read error: {e}\n--- server stderr ---\n{}", + dump_stderr() + ), + Err(_) => panic!( + "timed out waiting for server listening line (>20s)\n--- server stderr ---\n{}", + dump_stderr() + ), + }; let addr = line .strip_prefix("listening on ") .unwrap_or_else(|| panic!("unexpected first-line stdout from server: {:?}", line))