用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/warpdotdev/warp --skill rust-unit-tests命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | rust-unit-tests |
| description | Write, improve, and run Rust unit tests in the warp Rust codebase. |
Default to a unit test when the logic is deterministic and reachable without booting the app:
Prefer unit tests for anything that fits: they are fast, deterministic, and point straight at the failure.
Be honest about this codebase. Warp is a terminal emulator with a GPU renderer, PTY and shell integration, and IPC. The common "80% unit tests" heuristic assumes a business-logic-heavy system where units exchange messages and transform data. Large parts of this repo are not that, and forcing them under a unit test usually means mocking away the only thing that could actually break.
Escalate to a higher level when any of these hold:
Where to go instead:
gui-integration-test — GUI end-to-end behavior, terminal and shell integration, settings and keybinding wiring.tui-testing — TUI element and screen rendering.gui-integration-test-video or computer_use — when the real question is visual and someone needs to look at it.Before escalating, try splitting the problem. Most "untestable" code is a thin shell of IO wrapped around logic that tests fine once separated: extract the decision-making into a pure function, unit test that, and let an integration test cover the thin shell that remains. That is usually cheaper than either a heavily stubbed unit test or a full app-boot test.
That said, weigh the indirection on its own merits. Code that is hard to test is sometimes genuinely badly designed — if extracting the logic would make the code clearer regardless of testing, do it. If the seam would exist only to satisfy a test, don't.
Tests cost real maintenance, and a bad test costs more than no test. Skip or delete these:
From/Into passthroughs, Default impls, plain struct construction. There is nothing that can break independently.tokio, or wgpu. Test your usage of them.Don't chase a coverage number. Coverage says a line executed, not that anything was verified, and a target reliably turns into a ceiling.
${filename}_tests.rs or mod_test.rs.#[cfg(test)]
#[path = "filename_tests.rs"] // or "mod_test.rs"
mod tests;
Aim for a test you never touch again unless the behavior changes. Refactors, new features, and bug fixes should not require editing existing tests; only a deliberate behavior change should. If a refactor breaks your tests, that is usually a defect in the tests.
Exercise the unit the way its callers do. Reaching into private state makes the test fail on refactors no caller would notice. If a helper type exists only to serve one or two callers, test it through them rather than directly.
Assert what the system is after the action, not which functions it called to get there.
// Brittle: still passes if the entry is dropped right after insertion, and
// fails on an equivalent refactor that calls a different internal method.
assert!(recorder.saw_call_to_insert(id));
// Better: asserts the outcome the caller actually cares about.
store.insert(id, entry.clone());
assert_eq!(store.get(id), Some(&entry));
The test name is often the only thing visible in a failure report, so make it a sentence about behavior rather than about the method:
#[test]
fn parses_utf8_sequence_when_valid() { /* ... */ }
#[test]
fn returns_replacement_char_for_invalid_utf8() { /* ... */ }
If the name needs an "and", you are testing two behaviors — split it. Structure the body as arrange / act / assert, separated by blank lines.
Everything a reader needs to understand the result belongs in the test body; everything irrelevant belongs out of it. Prefer a builder or helper constructor that takes only the fields the test cares about over one large shared fixture. If a test asserts on a specific value, set that value in the test rather than inheriting it from shared setup.
Test code has no tests of its own, so it has to be obviously correct on inspection. Some repetition is a fair price for a test that reads top to bottom. Extract a helper when it removes noise, not merely to remove repetition.
No conditionals, loops, arithmetic, or string concatenation to compute an expected value. Write expected values literally — computing them re-implements the code under test and can reproduce the same bug in the assertion.
assert_eq!/assert_ne! over assert! for readable diffs.assert_eq!(got, want, "cursor should clamp to line end for {input:?}").#[should_panic] only when panicking is intended API, and pin the message with expected = "...".model.lock() calls in the same call stack from tests, and prefer passing an already-locked reference down.Work down this list and stop at the first option that is fast and deterministic:
warpui::App::test, VirtualFS, TerminalModel::mock(..), TestBlockListBuilder/TestBlockBuilder, Appearance::mock(), and FeatureFlag::X.override_enabled(..). See "Common helpers to use" below for usage.A flaky test is worse than no test: once people learn to re-run a red test, they stop trusting every other test too. Fix the cause instead of adding retries.
OnceCell, and environment variables.serial_test's #[serial] or scope the state locally.If you can't make a test deterministic quickly, quarantine it (#[ignore] with a linked issue) rather than leaving an intermittently red test in the suite — and treat that as debt to pay down, not a place to leave it.
#[tokio::test] when the code requires a runtime.FeatureFlag::X.is_enabled()) over #[cfg(...)] so tests don’t require recompilation to toggle behavior.warpui::App::test for deterministic unit tests around views/models.update and assert via read.use warpui::App;
// In app crate tests prefer `crate::test_util::...`; from other crates use `warp::test_util::...`.
use warp::test_util::{terminal::initialize_app_for_terminal_view, add_window_with_terminal};
#[test]
fn example() {
App::test((), |mut app| async move {
// One-time app setup for terminal/view tests
initialize_app_for_terminal_view(&mut app); // includes settings init
let term = add_window_with_terminal(&mut app, None);
// Act
term.update(&mut app, |view, _ctx| {
view.model.lock().simulate_block("ls", "out");
});
// Assert
term.read(&app, |view, _ctx| {
assert!(view.model.lock().block_list().len() > 0);
});
})
}
Tests for the headless TUI render an element tree to text lines rather than drawing pixels. Use warpui_core::elements::tui::test_support::render_to_lines and TuiBuffer::to_lines, and keep them in *_tests.rs files next to the source in crates/warp_tui and crates/warpui_core/src/elements/tui. They are plain unit tests and do NOT use the GUI integration / real-display / computer_use framework. See the tui-testing skill for details. The warpui::App::test harness above still applies to shared model logic that both front-ends use.
TerminalModel::mock(..), .simulate_block(..), .finish_block(), .simulate_cmd(..).terminal::model::test_utils::{TestBlockListBuilder, TestBlockBuilder}.use virtual_fs::{VirtualFS, Stub};
VirtualFS::test("case", |_dirs, mut fs| {
fs.with_files(vec![Stub::FileWithContent("path/file.txt", "contents")]);
// run logic and assert
});
use warp::features::FeatureFlag; // or `use crate::features::FeatureFlag;` inside the app crate
let _flag = FeatureFlag::CreatingSharedSessions.override_enabled(true);
assert_lines_approx_eq!(actual_lines, INLINE_BANNER_HEIGHT);
model.lock() scopes minimal; avoid nested/re-entrant locks in the same call chain.initialize_settings_for_tests directly when using initialize_app_for_terminal_view (it already calls it).#[tokio::test] when a real runtime is required; otherwise prefer App::test.serial_test's #[serial] or local mocking instead of parallelism.cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2
cargo nextest run -p <crate_name>
cargo nextest run -E 'test(<substring>)'
cargo test --doc
Run before submitting changes:
./script/format
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
For a full local check before a PR, you can also run:
./script/presubmit