mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Extends the workspace clippy.toml — which already bans std:🧵:sleep, std:🧵:spawn, and std::process::Command::new on Tokio paths — with: - disallowed-types: std::io::{Read, Write, BufRead, BufReader, BufWriter} and std::net::{TcpStream, TcpListener, UdpSocket} - disallowed-methods: std::io::{stdin, stdout, stderr} Non-blocking std::io items (Error, ErrorKind, Result, IsTerminal, Cursor) remain allowed. std::fs is intentionally deferred. Annotates ~24 pre-existing sync call sites with #[expect(..., reason = "...")] matching the established pattern. All annotations describe why blocking I/O is intentional in that context (sync CLI command, test helper, pre-fork flush, etc.), so a future conversion to async will surface as an unfulfilled lint expectation instead of silently drifting. Fixes one real Tokio-path issue surfaced by the new lint: fabro-cli's server-start daemon-health poller (try_connect) was a sync fn called from async execute_daemon; std::net::TcpStream::connect_timeout blocked a Tokio worker for up to 100ms per poll iteration. Converted to tokio::net::{TcpStream, UnixStream} with tokio::time::timeout. One follow-up flagged in-code: fabro-agent/src/cli.rs's JSON event writer uses std::io::stdout() inside tokio::spawn. Annotated with a FOLLOW-UP reason pointing at tokio::io::stdout; left unchanged since volume is low and scope exceeded this pass. Verified: clippy clean, cargo +nightly fmt --check clean, full nextest workspace run (4131 passed, 182 skipped). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
111 lines
3.4 KiB
Rust
111 lines
3.4 KiB
Rust
#![expect(
|
|
clippy::disallowed_methods,
|
|
reason = "These CLI integration tests intentionally spawn the real fabro binary and stream DOT over stdio to verify the internal render subprocess contract."
|
|
)]
|
|
#![expect(
|
|
clippy::disallowed_types,
|
|
reason = "integration tests write DOT to the spawned child's stdin via std::io::Write"
|
|
)]
|
|
|
|
use std::io::Write;
|
|
use std::process::{Command, Stdio};
|
|
|
|
use fabro_test::{fabro_snapshot, test_context};
|
|
|
|
fn render_graph_command(context: &fabro_test::TestContext) -> Command {
|
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_fabro"));
|
|
fabro_test::apply_test_isolation(&mut cmd, &context.home_dir);
|
|
cmd.current_dir(&context.temp_dir);
|
|
cmd
|
|
}
|
|
|
|
#[test]
|
|
fn help() {
|
|
let context = test_context!();
|
|
let mut cmd = context.command();
|
|
cmd.args(["__render-graph", "--help"]);
|
|
fabro_snapshot!(context.filters(), cmd, @"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Render a DOT graph to SVG (internal)
|
|
|
|
Usage: fabro __render-graph [OPTIONS]
|
|
|
|
Options:
|
|
--json Output as JSON [env: FABRO_JSON=]
|
|
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
|
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
|
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
|
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
|
-h, --help Print help
|
|
----- stderr -----
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn render_graph_outputs_svg() {
|
|
let context = test_context!();
|
|
let mut cmd = render_graph_command(&context);
|
|
cmd.args(["__render-graph"])
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
|
|
let mut child = cmd.spawn().expect("render-graph subprocess should spawn");
|
|
child
|
|
.stdin
|
|
.as_mut()
|
|
.expect("stdin should be piped")
|
|
.write_all(b"digraph { a -> b }")
|
|
.expect("stdin write should succeed");
|
|
|
|
let output = child
|
|
.wait_with_output()
|
|
.expect("render-graph subprocess should exit");
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"stderr: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
let stdout = String::from_utf8(output.stdout).expect("stdout should be valid UTF-8");
|
|
assert!(
|
|
stdout.contains("<svg"),
|
|
"expected SVG output, got: {}",
|
|
&stdout[..stdout.len().min(200)]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn render_graph_bad_input_uses_render_error_protocol() {
|
|
let context = test_context!();
|
|
let mut cmd = render_graph_command(&context);
|
|
cmd.args(["__render-graph"])
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped());
|
|
|
|
let mut child = cmd.spawn().expect("render-graph subprocess should spawn");
|
|
child
|
|
.stdin
|
|
.as_mut()
|
|
.expect("stdin should be piped")
|
|
.write_all(b"not valid dot")
|
|
.expect("stdin write should succeed");
|
|
|
|
let output = child
|
|
.wait_with_output()
|
|
.expect("render-graph subprocess should exit");
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"stderr: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
let stdout = String::from_utf8(output.stdout).expect("stdout should be valid UTF-8");
|
|
assert!(
|
|
stdout.starts_with("RENDER_ERROR:"),
|
|
"expected render error protocol, got: {stdout}"
|
|
);
|
|
}
|