Files
wl-webrtc/tests/integration_test.rs
T

176 lines
8.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 集成测试:通过 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<u8>` 字节流解码成字符串;遇到非法
// UTF-8 序列时用 U+FFFD 替换而非报错。这里用 `_lossy` 变体而非
// `String::from_utf8(...)?` 是因为 stdout 理论上可能包含任意字节(如 ANSI
// color code 失控),不值得为解码失败让整个测试 panic。
let stdout = String::from_utf8_lossy(&output.stdout);
// `output.status.success()` 检查子进程退出码是否为 0Unix 上即 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'"
);
assert!(stdout.contains("fps"), "help output should mention 'fps'");
assert!(
stdout.contains("codec"),
"help output should mention 'codec'"
);
assert!(
stdout.contains("bitrate"),
"help output should mention 'bitrate'"
);
assert!(
stdout.contains("gop-size"),
"help output should mention 'gop-size'"
);
assert!(
stdout.contains("drm-device"),
"help output should mention 'drm-device'"
);
}
/// 测试未知参数应被拒绝:非零退出码 + stderr 包含 error/unexpected/unrecognized 之一。
///
/// `clap` 默认对未识别的 flag 返回非零退出码并打印错误到 stderr。这里用
/// `!output.status.success()` 断言"应该失败",再检查 stderr 文本以排除"碰巧
/// 崩溃退出"的假阳性。
#[test]
fn test_rejects_invalid_args() {
let output = Command::new(bin_path())
.arg("--nonexistent-flag-xyz")
.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")
|| stderr.to_lowercase().contains("unrecognized"),
"stderr should mention the error, got: {stderr}"
);
}
/// 测试 `--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")
.arg("--codec")
.arg("hevc")
.output()
.expect("failed to execute wl-webrtc --codec hevc");
// MVP only supports h264; hevc should be rejected.
assert!(!output.status.success(), "should reject hevc codec in MVP");
}
/// 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() {
// This test requires:
// - A running Wayland compositor
// - VAAPI-compatible GPU hardware
// - A writable output path
let output = Command::new(bin_path())
.arg("--output")
.arg("/tmp/wl-webrtc-test-capture.mp4")
.arg("--fps")
.arg("10")
.output()
.expect("failed to start wl-webrtc");
// If we get here, the process ran and exited.
// In a real test, we'd give it time to capture a few frames
// and then send SIGINT, but for now just check it didn't
// immediately fail with an argument error.
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("error: unexpected") && !stderr.contains("error: invalid"),
"should not have argument parsing errors, got: {stderr}"
);
}