diff options
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 161 |
1 files changed, 161 insertions, 0 deletions
| @@ -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 | ||
| 13 | for 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 | ||
| 31 | use assert_cmd::Command; | ||
| 32 | |||
| 33 | // Run a Cargo binary | ||
| 34 | let mut cmd = Command::cargo_bin("demon").unwrap(); | ||
| 35 | cmd.assert().success(); // Basic success assertion | ||
| 36 | ``` | ||
| 37 | |||
| 38 | #### 2. Command with Arguments | ||
| 39 | ```rust | ||
| 40 | let mut cmd = Command::cargo_bin("demon").unwrap(); | ||
| 41 | cmd.args(&["run", "--id", "test", "sleep", "5"]) | ||
| 42 | .assert() | ||
| 43 | .success(); | ||
| 44 | ``` | ||
| 45 | |||
| 46 | #### 3. Output Validation | ||
| 47 | ```rust | ||
| 48 | use predicates::prelude::*; | ||
| 49 | |||
| 50 | let mut cmd = Command::cargo_bin("demon").unwrap(); | ||
| 51 | cmd.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 | ||
| 60 | let mut cmd = Command::cargo_bin("demon").unwrap(); | ||
| 61 | cmd.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 | ||
| 86 | use 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 | ||
| 105 | For testing CLI tools that create/modify files, combine with `tempfile` and `assert_fs`: | ||
| 106 | |||
| 107 | ```rust | ||
| 108 | use tempfile::TempDir; | ||
| 109 | use std::fs; | ||
| 110 | |||
| 111 | #[test] | ||
| 112 | fn 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 | |||
| 129 | 1. **Use `cargo_bin()`**: Automatically locate project binaries | ||
| 130 | 2. **Chain configuration**: Configure all arguments/env before calling `assert()` | ||
| 131 | 3. **Test various scenarios**: Success, failure, edge cases | ||
| 132 | 4. **Use predicates**: More flexible than exact string matching | ||
| 133 | 5. **Isolate tests**: Use temporary directories for file-based tests | ||
| 134 | 6. **Test error conditions**: Verify proper error handling and messages | ||
| 135 | |||
| 136 | ### Common Patterns for CLI Testing | ||
| 137 | |||
| 138 | #### Testing Help Output | ||
| 139 | ```rust | ||
| 140 | let mut cmd = Command::cargo_bin("demon").unwrap(); | ||
| 141 | cmd.args(&["--help"]) | ||
| 142 | .assert() | ||
| 143 | .success() | ||
| 144 | .stdout(predicate::str::contains("daemon process management")); | ||
| 145 | ``` | ||
| 146 | |||
| 147 | #### Testing Subcommands | ||
| 148 | ```rust | ||
| 149 | let mut cmd = Command::cargo_bin("demon").unwrap(); | ||
| 150 | cmd.args(&["list"]) | ||
| 151 | .assert() | ||
| 152 | .success() | ||
| 153 | .stdout(predicate::str::contains("ID")); | ||
| 154 | ``` | ||
| 155 | |||
| 156 | #### Testing with Timeouts | ||
| 157 | ```rust | ||
| 158 | use std::time::Duration; | ||
| 159 | |||
| 160 | let mut cmd = Command::cargo_bin("demon").unwrap(); | ||
| 161 | cmd.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 | ||
