diff --git a/tests/integration_test.rs b/tests/integration_test.rs index f096f5c..72a2fd5 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,19 +1,77 @@ +//! 集成测试:通过 shell out 到 `target/release/wl-webrtc` 二进制来验证 CLI 行为。 +//! +//! 与单元测试(在进程内调用库函数)不同,集成测试把产物当作黑盒,启动子进程 +//! 并检查其 stdout/stderr/exit code。这种模式类似 Go 的 `testing` 包配合 +//! `os/exec.Command(...)` —— Rust 这边对应 `std::process::Command::new(...)`, +//! 通过 `.arg(...)` 链式追加参数,最后 `.output()` 一次性等待子进程结束并 +//! 拿到 `Output { status, stdout, stderr }`。 +//! +//! # 运行前必读 +//! +//! 这些测试**依赖 release 版二进制存在**。`cargo test --test integration_test` +//! 本身不会触发 release 构建,必须先手动执行: +//! +//! ```bash +//! cargo build --release +//! ``` +//! +//! 否则 `Command::new("target/release/wl-webrtc")` 会因为找不到可执行文件而 panic, +//! 所有 `#[test]` 都将以 "failed to execute" 失败。详见 `AGENTS.md` +//! "Testing and verification" 章节。 +//! +//! # 测试发现与断言 +//! +//! `cargo test` 通过 `#[test]` 属性宏自动发现并运行标记的函数,无需像 Go 那样 +//! 约定 `TestXxx(t *testing.T)` 签名 —— 普通函数加 `#[test]` 即可。 +//! `#[ignore]` 标记的测试默认跳过,需 `cargo test -- --ignored` 显式开启。 +//! +//! 断言方面,`assert!(cond, "msg")` 类似 Go 的 `if !cond { t.Errorf("msg") }`, +//! 但 Rust 会在失败时立即 unwind 当前测试函数(而非 Go 那样继续执行后续断言)。 +//! 当测试函数返回 `Result<(), E>` 时,可用 `?` 操作符把 IO/解码错误直接传播 +//! 给测试 harness(失败时打印 `Err` 而非 `panic!`);本文件的测试为了聚焦于 +//! 子进程行为,全部用 `.expect(...)`/`assert!` 风格,不返回 `Result`。 + use std::process::Command; /// Helper: get the binary path. Uses the release build if available. +/// +/// 返回被测二进制的路径。集成测试 shell out 到 release 构建产物(debug 构建太慢, +/// 且无法真实反映发布行为)。返回 `&'static str` 而非 `PathBuf` 是因为这个路径 +/// 是编译期常量,无需在每次调用时分配。 fn bin_path() -> &'static str { "target/release/wl-webrtc" } +/// 测试 `--help` 子命令:应正常退出(exit 0),且 stdout 至少包含关键字段名。 +/// +/// 验证 README/AGENTS.md 中列出的核心 CLI 参数(output/fps/codec/bitrate/gop-size/drm-device) +/// 都能在 `--help` 输出中找到 —— 这是一道"防回归"测试:一旦某参数被改名或删除, +/// 此处 `assert!` 会立刻失败。 #[test] fn test_help_flag() { + // `Command::new(...)` 类似 Go 的 `exec.Command(...)`:构造一个待运行的 + // 子进程描述符,此时还未真正 fork/exec。链式 `.arg(...)` 把参数逐个追加到 + // 命令行末尾(保持顺序),`.output()` 则 fork、exec、等待子进程退出,并 + // 一次性捕获 stdout/stderr 到 `Output` 结构体。 + // + // `.expect(...)` 等价于 `match result { Ok(v) => v, Err(_) => panic!(...) }`, + // 用于在父进程侧(不是被测程序侧)报告"无法启动子进程"这种环境性错误。 let output = Command::new(bin_path()) .arg("--help") .output() .expect("failed to execute wl-webrtc --help"); + // `String::from_utf8_lossy(...)` 把 `Vec` 字节流解码成字符串;遇到非法 + // UTF-8 序列时用 U+FFFD 替换而非报错。这里用 `_lossy` 变体而非 + // `String::from_utf8(...)?` 是因为 stdout 理论上可能包含任意字节(如 ANSI + // color code 失控),不值得为解码失败让整个测试 panic。 let stdout = String::from_utf8_lossy(&output.stdout); + // `output.status.success()` 检查子进程退出码是否为 0(Unix 上即 WIFEXITED + // 且 exit code == 0)。`assert!(cond, "msg")` 失败时打印 `msg` 并 panic + // 当前测试函数,类似 Go 的 `t.Fatalf` 而非 `t.Errorf`。 assert!(output.status.success(), "--help should exit 0"); + // 后续 `assert!(stdout.contains(...), "...")` 检查帮助文本是否覆盖每个 + // 文档化参数。任何一个缺失都会让测试失败并打印自定义消息。 assert!( stdout.contains("output"), "help output should mention 'output'" @@ -37,6 +95,11 @@ fn test_help_flag() { ); } +/// 测试未知参数应被拒绝:非零退出码 + stderr 包含 error/unexpected/unrecognized 之一。 +/// +/// `clap` 默认对未识别的 flag 返回非零退出码并打印错误到 stderr。这里用 +/// `!output.status.success()` 断言"应该失败",再检查 stderr 文本以排除"碰巧 +/// 崩溃退出"的假阳性。 #[test] fn test_rejects_invalid_args() { let output = Command::new(bin_path()) @@ -44,8 +107,13 @@ fn test_rejects_invalid_args() { .output() .expect("failed to execute wl-webrtc with invalid args"); + // 断言"非零退出码"。注意 `!` 取反 —— 与 Go 的 `t.Errorf` 风格不同,Rust 的 + // `assert!` 直接接受 bool 表达式,没有 `assertFalse` 这种专门函数。 assert!(!output.status.success(), "should reject unrecognized flag"); let stderr = String::from_utf8_lossy(&output.stderr); + // 多个可能的错误措辞用 `||` 连接 —— 不同 clap 版本可能输出 "error: unexpected" + // 或 "error: unrecognized",任一匹配即可。自定义消息末尾的 `{stderr}` 利用 + // `format!` 占位符在失败时打印实际 stderr 内容,便于排错。 assert!( stderr.to_lowercase().contains("error") || stderr.to_lowercase().contains("unexpected") @@ -54,8 +122,11 @@ fn test_rejects_invalid_args() { ); } +/// 测试 `--codec hevc` 在 MVP 阶段应被拒绝:MVP 只支持 h264。 #[test] fn test_rejects_hevc_codec() { + // 多个 `.arg(...)` 链式调用按顺序追加参数,等价于命令行 + // `wl-webrtc --output /dev/null --codec hevc`。 let output = Command::new(bin_path()) .arg("--output") .arg("/dev/null") @@ -70,6 +141,13 @@ fn test_rejects_hevc_codec() { /// Tests requiring a live Wayland compositor and VAAPI hardware. /// Run with: cargo test -- --ignored +/// +/// 该测试需要真实 Wayland 会话 + VAAPI GPU + 可写输出路径,无法在 CI 中运行。 +/// `#[ignore]` 属性告诉 `cargo test` 默认跳过它,只有显式 +/// `cargo test -- --ignored` 时才执行。 +/// +/// 注意:此测试只验证"参数解析不立即报错",并未真正完成捕获 —— 真正的捕获 +/// 需要异步等待几秒再发 SIGINT,这里只做最小烟雾测试。 #[test] #[ignore] fn test_capture_starts_with_valid_output() {