mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-23 04:27:11 +00:00
fix(rvagent-cli, ruqu-wasm): unblock 2 PR #388 test failures
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 <ruv@ruv.net>
This commit is contained in:
parent
ffba4d86d4
commit
dd59745ed8
5 changed files with 71 additions and 9 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<Mutex<Vec<u8>>> = 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:<port>" 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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue