Skip to content

Commit 8cb61b0

Browse files
KSXGitHubclaude
andauthored
refactor(test): prefer cfg_attr(ignore) over cfg for conditional test skipping (#369)
* refactor(test): replace `#[cfg]` with `#[cfg_attr(..., ignore)]` for test skipping Use `#[cfg_attr(..., ignore = "reason")]` instead of `#[cfg(not(...))]` on tests that can still compile under the skipped condition. This keeps the tests compiled on all configurations, catching type errors early while only skipping execution at runtime. Changes: - cli_errors::fs_errors: replace `#[cfg(not(pdu_test_skip_fs_errors))]` with `#[cfg_attr(pdu_test_skip_fs_errors, ignore)]`; remove the same gate from its imports and helper function - test_remove_overlapping_paths: remove `#[cfg(unix)]` from module declaration; add `#[cfg_attr(not(unix), ignore)]` to each test - Add "Conditional Test Skipping" section to CONTRIBUTING.md - Add guideline bullet to AI instruction templates https://claude.ai/code/session_0184ceZayyCwvK7PEF9iB7K3 * refactor(test): address review feedback on ignore attributes - Change ignore reason to "only one path separator style is tested" - Reorder attributes: place #[cfg_attr(..., ignore)] below #[test] - Revert hint message in fs_errors back to "skip" - Soften "Always" to "Prefer" for reason strings in CONTRIBUTING.md - Rephrase CONTRIBUTING.md to mention tests are "still compiled" https://claude.ai/code/session_0184ceZayyCwvK7PEF9iB7K3 * docs(guide/dev): use generic `pdu_test_skip_*` name in example https://claude.ai/code/session_0184ceZayyCwvK7PEF9iB7K3 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ad1ee65 commit 8cb61b0

8 files changed

Lines changed: 42 additions & 6 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
1414
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
1515
- Error types: only derive `Display` and `Error` from `derive_more` when each is actually needed — not all displayable types are errors
1616
- Minimize `unwrap()` in non-test code — use proper error handling
17+
- Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` to skip tests — use `#[cfg]` on tests only when the code cannot compile under the condition (e.g., references types/functions that don't exist on other platforms)
1718
- Install toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`
1819
- Run `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh` to validate changes. If a test fails with a hint about `RUSTFLAGS` and `--cfg pdu_test_skip_*`, follow the hint and rerun with the suggested flags.
1920
- **ALWAYS run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before committing, regardless of how trivial the change seems — this includes documentation-only changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations; any type of change can break any of these checks.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
1414
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
1515
- Error types: only derive `Display` and `Error` from `derive_more` when each is actually needed — not all displayable types are errors
1616
- Minimize `unwrap()` in non-test code — use proper error handling
17+
- Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` to skip tests — use `#[cfg]` on tests only when the code cannot compile under the condition (e.g., references types/functions that don't exist on other platforms)
1718
- Install toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`
1819
- Run `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh` to validate changes. If a test fails with a hint about `RUSTFLAGS` and `--cfg pdu_test_skip_*`, follow the hint and rerun with the suggested flags.
1920
- **ALWAYS run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before committing, regardless of how trivial the change seems — this includes documentation-only changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations; any type of change can break any of these checks.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
1414
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
1515
- Error types: only derive `Display` and `Error` from `derive_more` when each is actually needed — not all displayable types are errors
1616
- Minimize `unwrap()` in non-test code — use proper error handling
17+
- Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` to skip tests — use `#[cfg]` on tests only when the code cannot compile under the condition (e.g., references types/functions that don't exist on other platforms)
1718
- Install toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`
1819
- Run `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh` to validate changes. If a test fails with a hint about `RUSTFLAGS` and `--cfg pdu_test_skip_*`, follow the hint and rerun with the suggested flags.
1920
- **ALWAYS run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before committing, regardless of how trivial the change seems — this includes documentation-only changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations; any type of change can break any of these checks.

CONTRIBUTING.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,36 @@ pub enum RuntimeError {
213213
}
214214
```
215215

216+
### Conditional Test Skipping: `#[cfg]` vs `#[cfg_attr(..., ignore)]`
217+
218+
When a test cannot run under certain conditions (e.g., wrong platform, running as root), prefer `#[cfg_attr(..., ignore)]` over `#[cfg(...)]` to skip it. This way the test is still compiled on all configurations — catching type errors and regressions early — but simply skipped at runtime.
219+
220+
Use `#[cfg]` on tests **only** when the code cannot compile under the condition — for example, when the test references types, functions, or trait methods that are gated behind `#[cfg]` and do not exist on other platforms or feature sets.
221+
222+
Prefer including a reason string in the `ignore` attribute to explain why the test is skipped.
223+
224+
```rust
225+
// Good — test compiles everywhere, skipped at runtime on non-unix
226+
#[test]
227+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
228+
fn unix_path_logic() { /* uses hardcoded unix paths but no unix-only types */ }
229+
230+
// Good — test CANNOT compile on non-unix (uses unix-only types)
231+
#[cfg(unix)]
232+
#[test]
233+
fn block_size() { /* uses GetBlockSize which only exists on unix */ }
234+
235+
// Good — test compiles with the flag, skipped at runtime
236+
#[test]
237+
#[cfg_attr(pdu_test_skip_some_test, ignore = "pdu_test_skip_some_test is set")]
238+
fn some_test() { /* ... */ }
239+
240+
// Bad — excludes the test from compilation entirely when it could still compile
241+
#[cfg(not(pdu_test_skip_some_test))]
242+
#[test]
243+
fn some_test() { /* ... */ }
244+
```
245+
216246
### Using `pipe-trait`
217247

218248
This codebase uses the [`pipe-trait`](https://docs.rs/pipe-trait) crate extensively. The `Pipe` trait enables method-chaining through unary functions, keeping code in a natural left-to-right reading order. Import it as `use pipe_trait::Pipe;`.

src/app/overlapping_arguments.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,5 @@ pub fn remove_items_from_vec_by_indices<Item>(vec: &mut Vec<Item>, indices: &Has
121121

122122
#[cfg(test)]
123123
mod test_remove_items_from_vec_by_indices;
124-
#[cfg(unix)]
125124
#[cfg(test)]
126125
mod test_remove_overlapping_paths;

src/app/overlapping_arguments/test_remove_overlapping_paths.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ impl Api for MockedApi {
7070
}
7171

7272
#[test]
73+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
7374
fn remove_nothing() {
7475
let original = vec!["foo", "bar", "abc/def", "0/1/2"];
7576
let mut actual = original.clone();
@@ -79,6 +80,7 @@ fn remove_nothing() {
7980
}
8081

8182
#[test]
83+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
8284
fn remove_duplicated_arguments() {
8385
let original = dbg!(vec![
8486
"foo",
@@ -110,6 +112,7 @@ fn remove_duplicated_arguments() {
110112
}
111113

112114
#[test]
115+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
113116
fn remove_overlapping_sub_paths() {
114117
let original = vec![
115118
"foo/child",
@@ -127,6 +130,7 @@ fn remove_overlapping_sub_paths() {
127130
}
128131

129132
#[test]
133+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
130134
fn remove_all_except_current_dir() {
131135
let original = dbg!(vec!["foo", "bar", ".", "abc/def", "0/1/2"]);
132136
let mut actual = original.clone();
@@ -162,6 +166,7 @@ fn remove_all_except_current_dir() {
162166
}
163167

164168
#[test]
169+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
165170
fn remove_all_except_parent_dir() {
166171
let original = dbg!(vec!["foo", "bar", "..", "abc/def", ".", "0/1/2"]);
167172
let mut actual = original.clone();
@@ -185,6 +190,7 @@ fn remove_all_except_parent_dir() {
185190
}
186191

187192
#[test]
193+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
188194
fn remove_overlapping_real_paths() {
189195
let original = dbg!(vec![
190196
"foo",
@@ -227,6 +233,7 @@ fn remove_overlapping_real_paths() {
227233
}
228234

229235
#[test]
236+
#[cfg_attr(not(unix), ignore = "only one path separator style is tested")]
230237
fn do_not_remove_symlinks() {
231238
let original = dbg!(vec![
232239
"foo",

template/ai-instructions/shared.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Read and follow the CONTRIBUTING.md file in this repository for all code style c
1414
- Derive order: std traits → comparison traits → `Hash` → derive_more → feature-gated
1515
- Error types: only derive `Display` and `Error` from `derive_more` when each is actually needed — not all displayable types are errors
1616
- Minimize `unwrap()` in non-test code — use proper error handling
17+
- Prefer `#[cfg_attr(..., ignore = "reason")]` over `#[cfg(...)]` to skip tests — use `#[cfg]` on tests only when the code cannot compile under the condition (e.g., references types/functions that don't exist on other platforms)
1718
- Install toolchain before running tests: `rustup toolchain install "$(< rust-toolchain)" && rustup component add --toolchain "$(< rust-toolchain)" rustfmt clippy`
1819
- Run `FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh` to validate changes. If a test fails with a hint about `RUSTFLAGS` and `--cfg pdu_test_skip_*`, follow the hint and rerun with the suggested flags.
1920
- **ALWAYS run the full test suite** (`FMT=true LINT=true BUILD=true TEST=true DOC=true ./test.sh`) before committing, regardless of how trivial the change seems — this includes documentation-only changes, comment edits, config changes, and refactors. The test suite checks formatting, linting, building, tests, and docs across multiple feature combinations; any type of change can break any of these checks.

tests/cli_errors.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ use std::process::{Command, Output, Stdio};
1010
use text_block_macros::text_block;
1111

1212
#[cfg(unix)]
13-
#[cfg(not(pdu_test_skip_fs_errors))]
1413
use maplit::btreeset;
1514
#[cfg(unix)]
16-
#[cfg(not(pdu_test_skip_fs_errors))]
1715
use parallel_disk_usage::{
1816
bytes_format::BytesFormat,
1917
data_tree::DataTree,
@@ -25,7 +23,6 @@ use parallel_disk_usage::{
2523
visualizer::{BarAlignment, ColumnWidthDistribution, Direction, Visualizer},
2624
};
2725
#[cfg(unix)]
28-
#[cfg(not(pdu_test_skip_fs_errors))]
2926
use std::{collections::BTreeSet, path::Path};
3027

3128
fn stdio(command: Command) -> Command {
@@ -36,7 +33,6 @@ fn stdio(command: Command) -> Command {
3633
}
3734

3835
#[cfg(unix)]
39-
#[cfg(not(pdu_test_skip_fs_errors))]
4036
fn fs_permission(path: impl AsRef<Path>, permission: &'static str, recursive: bool) {
4137
let Output { status, stderr, .. } = Command::new("chmod")
4238
.pipe(|cmd| if recursive { cmd.with_arg("-R") } else { cmd })
@@ -110,8 +106,8 @@ fn max_depth_0() {
110106
}
111107

112108
#[cfg(unix)]
113-
#[cfg(not(pdu_test_skip_fs_errors))]
114109
#[test]
110+
#[cfg_attr(pdu_test_skip_fs_errors, ignore = "pdu_test_skip_fs_errors is set")]
115111
fn fs_errors() {
116112
if unsafe { libc::geteuid() } == 0 {
117113
panic!(

0 commit comments

Comments
 (0)