aboutsummaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md161
1 files changed, 161 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 6b97b70..c940c92 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -8,3 +8,164 @@ do not add dependencies manually, instead, use the following tools:
8+ for logging, prefer the `tracing` crate with `tracing-subscriber` and fully qualify the log macros (ex: `tracing::info!`) 8+ for logging, prefer the `tracing` crate with `tracing-subscriber` and fully qualify the log macros (ex: `tracing::info!`)
9+ for cli use the `clap` crate. when implementing subcommands use an `enum` and separate structs for each subcommand's arguments 9+ for cli use the `clap` crate. when implementing subcommands use an `enum` and separate structs for each subcommand's arguments
10+ use the `anyhow` crate for error handling 10+ use the `anyhow` crate for error handling
11
12## testing guidelines
13for testing cli applications, use the `assert_cmd` crate for integration testing
14
15## assert_cmd crate reference
16
17### Overview
18`assert_cmd` is a Rust testing library designed to simplify integration testing of command-line applications. It provides easy command initialization, simplified configuration, and robust assertion mechanisms.
19
20### Key Features
21- Easy command initialization and execution
22- Cargo binary testing support
23- Flexible output validation with predicates
24- Environment variable and stdin management
25- Comprehensive assertion mechanisms
26
27### Basic Usage Patterns
28
29#### 1. Basic Command Testing
30```rust
31use assert_cmd::Command;
32
33// Run a Cargo binary
34let mut cmd = Command::cargo_bin("demon").unwrap();
35cmd.assert().success(); // Basic success assertion
36```
37
38#### 2. Command with Arguments
39```rust
40let mut cmd = Command::cargo_bin("demon").unwrap();
41cmd.args(&["run", "--id", "test", "sleep", "5"])
42 .assert()
43 .success();
44```
45
46#### 3. Output Validation
47```rust
48use predicates::prelude::*;
49
50let mut cmd = Command::cargo_bin("demon").unwrap();
51cmd.args(&["list"])
52 .assert()
53 .success()
54 .stdout(predicate::str::contains("ID"))
55 .stderr(predicate::str::is_empty());
56```
57
58#### 4. Testing Failures
59```rust
60let mut cmd = Command::cargo_bin("demon").unwrap();
61cmd.args(&["run", "--id", "test"]) // Missing command
62 .assert()
63 .failure()
64 .stderr(predicate::str::contains("Command cannot be empty"));
65```
66
67### Key Methods
68
69#### Command Configuration
70- `Command::cargo_bin("binary_name")`: Find and initialize a Cargo project binary
71- `arg(arg)` / `args(&[args])`: Add command arguments
72- `env(key, value)` / `envs(vars)`: Set environment variables
73- `current_dir(path)`: Set working directory
74- `write_stdin(input)`: Provide stdin input
75
76#### Assertions
77- `assert()`: Start assertion chain
78- `success()`: Check for successful execution (exit code 0)
79- `failure()`: Check for command failure (exit code != 0)
80- `code(expected)`: Validate specific exit code
81- `stdout(predicate)`: Validate stdout content
82- `stderr(predicate)`: Validate stderr content
83
84### Predicates for Output Validation
85```rust
86use predicates::prelude::*;
87
88// Exact match
89.stdout("exact output")
90
91// Contains text
92.stdout(predicate::str::contains("partial"))
93
94// Regex match
95.stdout(predicate::str::is_match(r"PID: \d+").unwrap())
96
97// Empty output
98.stderr(predicate::str::is_empty())
99
100// Multiple conditions
101.stdout(predicate::str::contains("SUCCESS").and(predicate::str::contains("ID")))
102```
103
104### Testing File I/O
105For testing CLI tools that create/modify files, combine with `tempfile` and `assert_fs`:
106
107```rust
108use tempfile::TempDir;
109use std::fs;
110
111#[test]
112fn test_file_creation() {
113 let temp_dir = TempDir::new().unwrap();
114
115 let mut cmd = Command::cargo_bin("demon").unwrap();
116 cmd.current_dir(temp_dir.path())
117 .args(&["run", "--id", "test", "echo", "hello"])
118 .assert()
119 .success();
120
121 // Verify files were created
122 assert!(temp_dir.path().join("test.pid").exists());
123 assert!(temp_dir.path().join("test.stdout").exists());
124}
125```
126
127### Best Practices
128
1291. **Use `cargo_bin()`**: Automatically locate project binaries
1302. **Chain configuration**: Configure all arguments/env before calling `assert()`
1313. **Test various scenarios**: Success, failure, edge cases
1324. **Use predicates**: More flexible than exact string matching
1335. **Isolate tests**: Use temporary directories for file-based tests
1346. **Test error conditions**: Verify proper error handling and messages
135
136### Common Patterns for CLI Testing
137
138#### Testing Help Output
139```rust
140let mut cmd = Command::cargo_bin("demon").unwrap();
141cmd.args(&["--help"])
142 .assert()
143 .success()
144 .stdout(predicate::str::contains("daemon process management"));
145```
146
147#### Testing Subcommands
148```rust
149let mut cmd = Command::cargo_bin("demon").unwrap();
150cmd.args(&["list"])
151 .assert()
152 .success()
153 .stdout(predicate::str::contains("ID"));
154```
155
156#### Testing with Timeouts
157```rust
158use std::time::Duration;
159
160let mut cmd = Command::cargo_bin("demon").unwrap();
161cmd.timeout(Duration::from_secs(30)) // Prevent hanging tests
162 .args(&["run", "--id", "long", "sleep", "10"])
163 .assert()
164 .success();
165```
166
167### Integration with Other Test Crates
168- **`assert_fs`**: Filesystem testing utilities
169- **`predicates`**: Advanced output validation
170- **`tempfile`**: Temporary file/directory management
171- **`serial_test`**: Serialize tests that can't run concurrently