diff --git a/.config/nextest.toml b/.config/nextest.toml index ecede1fb6..3a09e73c1 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -2,8 +2,8 @@ # Kill any test once it crosses 60s. slow-timeout = { period = "60s", terminate-after = 1, grace-period = "5s" } -# Put a hard ceiling on the whole run. -global-timeout = "5m" +# Accommodate cargo-spawning tests that deliberately run one at a time. +global-timeout = "20m" [test-groups.cargo-spawning] # Tests that shell out to `cargo` contend for the package-cache lock and for @@ -27,4 +27,4 @@ test-group = "cargo-spawning" [profile.long] slow-timeout = { period = "180s", terminate-after = 1, grace-period = "5s" } -global-timeout = "15m" +global-timeout = "30m" diff --git a/AGENTS.md b/AGENTS.md index 7c75306ea..e163a21f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -273,14 +273,16 @@ project: diagnostics. - In production code and shared fixtures, avoid `.expect()` entirely: return `Result` and use `?` to propagate errors instead of panicking. -- Keep `expect_used` **strict**; do not suppress the lint. +- Keep `expect_used` strict outside recognized test cases; do not suppress the + lint. - Recognize that `allow-expect-in-tests = true` **doesn’t cover** helpers outside `#[cfg(test)]` or `#[test]`; avoid `expect` in such fixtures. - Use `anyhow`/`eyre` with `.context(...)` to **preserve backtraces** and provide clear, typed failure paths. - Update helpers (e.g., `set_dir`) to **return errors** rather than panicking. -- Consume fallible fixtures in `rstest` by **making the test return `Result`** - and applying `?` to the fixture. +- Tests may consume fallible `rstest` fixtures with `.expect(...)` when setup + failure should fail the test; their signatures need not return `Result` only + to propagate fixture errors. ### Observability diff --git a/clippy.toml b/clippy.toml index 3d702373a..39ced6c04 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,7 @@ # Align with CodeScene’s ceiling cognitive-complexity-threshold = 12 # default is 25 + +# Tests may use `.expect(...)` or `panic!(...)` for fallible setup at their +# own boundary. This applies to built-in `#[test]` and `rstest` cases. +allow-expect-in-tests = true +allow-panic-in-tests = true diff --git a/crates/cargo-bdd/src/cli.rs b/crates/cargo-bdd/src/cli.rs index 09aa7b77f..97bb71343 100644 --- a/crates/cargo-bdd/src/cli.rs +++ b/crates/cargo-bdd/src/cli.rs @@ -235,7 +235,7 @@ mod tests { use super::*; #[test] - fn write_skip_reports_json_emits_fields() -> eyre::Result<()> { + fn write_skip_reports_json_emits_fields() { let report = SkipReport { feature: "feature", scenario: "scenario", @@ -250,12 +250,14 @@ mod tests { }), }; let mut buffer = Vec::new(); - serde_json::to_writer(&mut buffer, &[report])?; - let parsed: serde_json::Value = serde_json::from_slice(&buffer)?; + serde_json::to_writer(&mut buffer, &[report]).expect("test setup should succeed"); + let parsed: serde_json::Value = + serde_json::from_slice(&buffer).expect("test setup should succeed"); let entry = parsed .as_array() .and_then(|array| array.first()) - .ok_or_else(|| eyre::eyre!("missing entry"))?; + .ok_or_else(|| eyre::eyre!("missing entry")) + .expect("test setup should succeed"); assert_eq!( entry.get("feature"), Some(&serde_json::Value::String("feature".into())) @@ -272,7 +274,8 @@ mod tests { let step = entry .get("step") .and_then(serde_json::Value::as_object) - .ok_or_else(|| eyre::eyre!("missing step object"))?; + .ok_or_else(|| eyre::eyre!("missing step object")) + .expect("test setup should succeed"); assert_eq!( step.get("keyword"), Some(&serde_json::Value::String("Given".into())) @@ -281,6 +284,5 @@ mod tests { step.get("pattern"), Some(&serde_json::Value::String("x".into())) ); - Ok(()) } } diff --git a/crates/cargo-bdd/src/registry/tests.rs b/crates/cargo-bdd/src/registry/tests.rs index 21e29ca04..50cb94064 100644 --- a/crates/cargo-bdd/src/registry/tests.rs +++ b/crates/cargo-bdd/src/registry/tests.rs @@ -26,10 +26,6 @@ fn ignores_unrelated_failures_containing_dump_steps() { } #[test] -#[expect( - clippy::expect_used, - reason = "Test should fail fast when the registry dump JSON is invalid." -)] fn parses_registry_dump_with_bypassed_steps() { let json = r#" { diff --git a/crates/cargo-bdd/tests/cli.rs b/crates/cargo-bdd/tests/cli.rs index c1095b1ca..aa0ad1211 100644 --- a/crates/cargo-bdd/tests/cli.rs +++ b/crates/cargo-bdd/tests/cli.rs @@ -109,19 +109,18 @@ fn run_cargo_bdd_steps() -> Result { #[test] #[serial] -fn list_steps_runs() -> Result<()> { - let stdout = run_cargo_bdd_steps()?; +fn list_steps_runs() { + let stdout = run_cargo_bdd_steps().expect("test setup should succeed"); assert!( !stdout.is_empty(), "Expected non-empty output from steps command", ); - Ok(()) } #[test] #[serial] -fn steps_output_includes_skipped_statuses() -> Result<()> { - let stdout = run_cargo_bdd_steps()?; +fn steps_output_includes_skipped_statuses() { + let stdout = run_cargo_bdd_steps().expect("test setup should succeed"); assert!( stdout.contains("skipped tests/features/diagnostics.fixture :: fixture skipped scenario"), "expected skipped scenario heading in cargo bdd output: {stdout}" @@ -130,13 +129,12 @@ fn steps_output_includes_skipped_statuses() -> Result<()> { stdout.contains("fixture skip message"), "expected skip message to appear in cargo bdd output: {stdout}" ); - Ok(()) } #[test] #[serial] -fn steps_output_marks_forced_failure_skips() -> Result<()> { - let stdout = run_cargo_bdd_steps()?; +fn steps_output_marks_forced_failure_skips() { + let stdout = run_cargo_bdd_steps().expect("test setup should succeed"); assert!( stdout.contains("[forced failure]"), "expected forced failure annotation in cargo bdd output: {stdout}", @@ -149,13 +147,12 @@ fn steps_output_marks_forced_failure_skips() -> Result<()> { stdout.contains("fixture forced skip"), "expected forced skip message in cargo bdd output: {stdout}", ); - Ok(()) } #[test] #[serial] -fn skipped_subcommand_includes_reasons_and_lines() -> Result<()> { - let stdout = run_cargo_bdd(&["skipped", "--reasons"])?; +fn skipped_subcommand_includes_reasons_and_lines() { + let stdout = run_cargo_bdd(&["skipped", "--reasons"]).expect("test setup should succeed"); assert!( stdout.contains("tests/features/diagnostics.fixture:7"), "skipped output should include feature location", @@ -165,18 +162,19 @@ fn skipped_subcommand_includes_reasons_and_lines() -> Result<()> { "skip reason should appear in skipped output", ); assert!(stdout.contains("[forced failure]")); - Ok(()) } #[test] #[serial] -fn skipped_subcommand_emits_json() -> Result<()> { - let stdout = run_cargo_bdd(&["skipped", "--json"])?; - let entries: Vec = serde_json::from_str(&stdout)?; +fn skipped_subcommand_emits_json() { + let stdout = run_cargo_bdd(&["skipped", "--json"]).expect("test setup should succeed"); + let entries: Vec = + serde_json::from_str(&stdout).expect("test setup should succeed"); let fixture_entry = entries .iter() .find(|entry| entry.scenario == "fixture skipped scenario") - .ok_or_else(|| eyre::eyre!("expected fixture skipped scenario entry"))?; + .ok_or_else(|| eyre::eyre!("expected fixture skipped scenario entry")) + .expect("test setup should succeed"); assert_eq!(fixture_entry.feature, "tests/features/diagnostics.fixture",); assert_eq!( fixture_entry.reason.as_deref(), @@ -188,27 +186,28 @@ fn skipped_subcommand_emits_json() -> Result<()> { vec!["@allow_skipped".to_string()], "expected fixture skipped scenario tags to be preserved" ); - Ok(()) } #[test] #[serial] -fn steps_skipped_outputs_bypassed_definitions() -> Result<()> { - let stdout = run_cargo_bdd(&["steps", "--skipped"])?; +fn steps_skipped_outputs_bypassed_definitions() { + let stdout = run_cargo_bdd(&["steps", "--skipped"]).expect("test setup should succeed"); assert!(stdout.contains("fixture bypassed step")); assert!(stdout.contains("fixture skip message")); - Ok(()) } #[test] #[serial] -fn steps_skipped_emits_json() -> Result<()> { - let stdout = run_cargo_bdd(&["steps", "--skipped", "--json"])?; - let entries: Vec = serde_json::from_str(&stdout)?; +fn steps_skipped_emits_json() { + let stdout = + run_cargo_bdd(&["steps", "--skipped", "--json"]).expect("test setup should succeed"); + let entries: Vec = + serde_json::from_str(&stdout).expect("test setup should succeed"); let forced_entry = entries .iter() .find(|entry| entry.scenario == "fixture forced failure skip") - .ok_or_else(|| eyre::eyre!("expected forced failure skip entry"))?; + .ok_or_else(|| eyre::eyre!("expected forced failure skip entry")) + .expect("test setup should succeed"); assert_eq!(forced_entry.reason.as_deref(), Some("fixture forced skip"),); assert!( forced_entry.step.is_some(), @@ -228,16 +227,14 @@ fn steps_skipped_emits_json() -> Result<()> { step.line > 0, "expected bypassed step to include a line number" ); - Ok(()) } #[test] #[serial] -fn steps_json_requires_skipped_flag() -> Result<()> { - let stderr = run_cargo_bdd_failure(&["steps", "--json"])?; +fn steps_json_requires_skipped_flag() { + let stderr = run_cargo_bdd_failure(&["steps", "--json"]).expect("test setup should succeed"); assert!( stderr.contains("--json") && stderr.contains("--skipped"), "error should mention --json requires --skipped: {stderr}" ); - Ok(()) } diff --git a/crates/rstest-bdd-harness/src/trybuild_staging/prop_tests.rs b/crates/rstest-bdd-harness/src/trybuild_staging/prop_tests.rs index 9a5ec886e..2d4feced9 100644 --- a/crates/rstest-bdd-harness/src/trybuild_staging/prop_tests.rs +++ b/crates/rstest-bdd-harness/src/trybuild_staging/prop_tests.rs @@ -115,10 +115,6 @@ proptest! { for index in 0..existing_depth { existing = existing.join(format!("existing_{index}")); } - #[expect( - clippy::expect_used, - reason = "property-test temp-dir setup and canonicalization after explicit setup" - )] { fs::create_dir_all(&existing).expect("create existing ancestor"); let mut expected = fs::canonicalize(&existing).expect("canonicalize ancestor"); @@ -142,10 +138,6 @@ proptest! { let _ = fs::remove_dir_all(&root); let src = root.join("src"); let destination = path_resolving_back_to_source(&root, "src", missing_depth); - #[expect( - clippy::expect_used, - reason = "property-test temp-dir setup and canonicalization after explicit setup" - )] { fs::create_dir_all(&src).expect("create src"); let canonical_src = fs::canonicalize(&src).expect("canonicalize src"); @@ -167,10 +159,6 @@ proptest! { let _ = fs::remove_dir_all(&root); let src = root.join("src"); let dst = path_resolving_back_to_source(&root, "src", missing_depth); - #[expect( - clippy::expect_used, - reason = "property-test temp-dir setup and err-kind extraction after explicit guards" - )] { fs::create_dir_all(&src).expect("create src"); fs::write(src.join("f.txt"), b"x").expect("write f.txt"); @@ -198,10 +186,6 @@ proptest! { ) { let root = unique_root("top_level"); let _ = fs::remove_dir_all(&root); - #[expect( - clippy::expect_used, - reason = "property-test temp-dir setup failures abort the test" - )] let (src, dst) = build_flat_source_with_symlink( &root, &file_names, @@ -211,10 +195,6 @@ proptest! { let _ = fs::remove_dir_all(&root); prop_assert_eq!( { - #[expect( - clippy::expect_used, - reason = "property-test temp-dir setup and err-kind extraction after explicit guards" - )] result .expect_err("expected error from copy_dir_tree property test") .kind() @@ -232,10 +212,6 @@ proptest! { ) { let root = unique_root("nested"); let _ = fs::remove_dir_all(&root); - #[expect( - clippy::expect_used, - reason = "property-test temp-dir setup failures abort the test" - )] let (src, dst) = build_nested_source_with_symlink( &root, depth, @@ -245,10 +221,6 @@ proptest! { let _ = fs::remove_dir_all(&root); prop_assert_eq!( { - #[expect( - clippy::expect_used, - reason = "property-test temp-dir setup and err-kind extraction after explicit guards" - )] result .expect_err("expected error from copy_dir_tree property test") .kind() diff --git a/crates/rstest-bdd-harness/src/trybuild_staging/tests.rs b/crates/rstest-bdd-harness/src/trybuild_staging/tests.rs index 1cb934313..892b09601 100644 --- a/crates/rstest-bdd-harness/src/trybuild_staging/tests.rs +++ b/crates/rstest-bdd-harness/src/trybuild_staging/tests.rs @@ -31,14 +31,11 @@ fn copy_file_staging() -> io::Result { } #[rstest] -fn copy_file_overwrites_existing_destination( - copy_file_staging: io::Result, -) -> io::Result<()> { - let staging = copy_file_staging?; +fn copy_file_overwrites_existing_destination(copy_file_staging: io::Result) { + let staging = copy_file_staging.expect("test setup should succeed"); let CopyFileStaging { src, dst, .. } = &staging; - copy_file(src, dst)?; - assert_eq!(fs::read(dst)?, b"new"); - Ok(()) + copy_file(src, dst).expect("test setup should succeed"); + assert_eq!(fs::read(dst).expect("test setup should succeed"), b"new"); } struct ReplaceDstStaging { @@ -83,28 +80,24 @@ fn replace_dir_staging() -> io::Result { } #[rstest] -fn copy_dir_tree_replaces_existing_directory( - replace_dir_staging: io::Result, -) -> io::Result<()> { - let staging = replace_dir_staging?; +fn copy_dir_tree_replaces_existing_directory(replace_dir_staging: io::Result) { + let staging = replace_dir_staging.expect("test setup should succeed"); let ReplaceDstStaging { src, dst, .. } = &staging; - copy_dir_tree(src, dst)?; + copy_dir_tree(src, dst).expect("test setup should succeed"); assert!(dst.join("sub").join("a.txt").exists()); // Stale directory must be gone. assert!(!dst.join("stale").exists()); - Ok(()) } #[rstest] fn copy_dir_tree_creates_missing_destination_parents( replace_dir_staging: io::Result, -) -> io::Result<()> { - let staging = replace_dir_staging?; +) { + let staging = replace_dir_staging.expect("test setup should succeed"); let ReplaceDstStaging { src, dst, .. } = &staging; let nested_dst = dst.join("nested").join("tree"); - copy_dir_tree(src, &nested_dst)?; + copy_dir_tree(src, &nested_dst).expect("test setup should succeed"); assert!(nested_dst.join("sub").join("a.txt").exists()); - Ok(()) } #[fixture] @@ -124,26 +117,27 @@ fn replace_file_dest_staging() -> io::Result { #[rstest] fn copy_dir_tree_replaces_existing_file_destination( replace_file_dest_staging: io::Result, -) -> io::Result<()> { - let staging = replace_file_dest_staging?; +) { + let staging = replace_file_dest_staging.expect("test setup should succeed"); let ReplaceDstStaging { src, dst, .. } = &staging; - copy_dir_tree(src, dst)?; + copy_dir_tree(src, dst).expect("test setup should succeed"); assert!(dst.join("f.txt").exists()); - Ok(()) } #[test] -fn copy_dir_tree_creates_missing_destination_parent_chain() -> io::Result<()> { - let (root, src, dst) = make_src_dst_scaffold()?; +fn copy_dir_tree_creates_missing_destination_parent_chain() { + let (root, src, dst) = make_src_dst_scaffold().expect("test setup should succeed"); let dst = dst.join("missing").join("parents"); - fs::create_dir_all(&src)?; - fs::write(src.join("f.txt"), b"hello")?; + fs::create_dir_all(&src).expect("test setup should succeed"); + fs::write(src.join("f.txt"), b"hello").expect("test setup should succeed"); - copy_dir_tree(&src, &dst)?; + copy_dir_tree(&src, &dst).expect("test setup should succeed"); - assert_eq!(fs::read(dst.join("f.txt"))?, b"hello"); + assert_eq!( + fs::read(dst.join("f.txt")).expect("test setup should succeed"), + b"hello" + ); drop(root); - Ok(()) } #[derive(Clone)] @@ -212,32 +206,29 @@ fn symlink_in_source_staging() -> io::Result { #[cfg(unix)] fn copy_dir_tree_rejects_symlink_in_source( symlink_in_source_staging: io::Result, -) -> io::Result<()> { - let staging = symlink_in_source_staging?; +) { + let staging = symlink_in_source_staging.expect("test setup should succeed"); let SymlinkInSourceStaging { src, dst, .. } = &staging; - #[expect(clippy::expect_used, reason = "the test asserts the copy is rejected")] let err = { copy_dir_tree(src, dst).expect_err("failed to copy dir tree") }; assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert!( err.to_string().contains("refusing to follow symlink"), "unexpected error message: {err}" ); - Ok(()) } #[test] #[cfg(unix)] -fn copy_dir_tree_rejects_symlink_as_source_root() -> io::Result<()> { +fn copy_dir_tree_rejects_symlink_as_source_root() { use std::os::unix::fs::symlink; - let root = TempDir::new()?; + let root = TempDir::new().expect("test setup should succeed"); let tree = root.path().join("tree"); let src = root.path().join("src"); let dst = root.path().join("dst"); - fs::create_dir_all(&tree)?; - fs::write(tree.join("f.txt"), b"x")?; - symlink(&tree, &src)?; - #[expect(clippy::expect_used, reason = "the test asserts the copy is rejected")] + fs::create_dir_all(&tree).expect("test setup should succeed"); + fs::write(tree.join("f.txt"), b"x").expect("test setup should succeed"); + symlink(&tree, &src).expect("test setup should succeed"); let err = copy_dir_tree(&src, &dst).expect_err("expected symlink source root rejection"); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); assert!( @@ -248,24 +239,22 @@ fn copy_dir_tree_rejects_symlink_as_source_root() -> io::Result<()> { !dst.exists(), "destination should not be created when source root is a symlink" ); - Ok(()) } #[test] #[cfg(unix)] -fn copy_dir_tree_symlink_source_does_not_remove_destination() -> io::Result<()> { +fn copy_dir_tree_symlink_source_does_not_remove_destination() { use std::os::unix::fs::symlink; - let root = TempDir::new()?; + let root = TempDir::new().expect("test setup should succeed"); let tree = root.path().join("tree"); let src = root.path().join("src"); let dst = root.path().join("dst"); - fs::create_dir_all(&tree)?; - fs::write(tree.join("in-tree.txt"), b"inside-tree")?; - fs::create_dir_all(&dst)?; - fs::write(dst.join("marker.txt"), b"untouched")?; - symlink(&tree, &src)?; - #[expect(clippy::expect_used, reason = "the test asserts the copy is rejected")] + fs::create_dir_all(&tree).expect("test setup should succeed"); + fs::write(tree.join("in-tree.txt"), b"inside-tree").expect("test setup should succeed"); + fs::create_dir_all(&dst).expect("test setup should succeed"); + fs::write(dst.join("marker.txt"), b"untouched").expect("test setup should succeed"); + symlink(&tree, &src).expect("test setup should succeed"); let err = copy_dir_tree(&src, &dst).expect_err("symlink source must be rejected"); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); assert!( @@ -274,59 +263,52 @@ fn copy_dir_tree_symlink_source_does_not_remove_destination() -> io::Result<()> ); assert!(dst.is_dir(), "destination directory must still exist"); assert_eq!( - fs::read_to_string(dst.join("marker.txt"))?, + fs::read_to_string(dst.join("marker.txt")).expect("test setup should succeed"), "untouched", "destination contents must be unchanged (remove_destination must not run)" ); - Ok(()) } #[test] -fn copy_dir_tree_rejects_identical_source_and_destination() -> io::Result<()> { - let root = TempDir::new()?; +fn copy_dir_tree_rejects_identical_source_and_destination() { + let root = TempDir::new().expect("test setup should succeed"); let dir = root.path().join("tree"); - fs::create_dir_all(&dir)?; - #[expect(clippy::expect_used, reason = "the test asserts the copy is rejected")] + fs::create_dir_all(&dir).expect("test setup should succeed"); let err = copy_dir_tree(&dir, &dir).expect_err("identical source and destination"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert!( err.to_string().contains("refusing overlapping"), "unexpected error message: {err}" ); - Ok(()) } #[test] -fn copy_dir_tree_rejects_destination_inside_source() -> io::Result<()> { - let root = TempDir::new()?; +fn copy_dir_tree_rejects_destination_inside_source() { + let root = TempDir::new().expect("test setup should succeed"); let src = root.path().join("src"); - fs::create_dir_all(&src)?; - fs::write(src.join("f.txt"), b"x")?; + fs::create_dir_all(&src).expect("test setup should succeed"); + fs::write(src.join("f.txt"), b"x").expect("test setup should succeed"); let dst = src.join("nested_dst"); - #[expect(clippy::expect_used, reason = "the test asserts the copy is rejected")] let err = copy_dir_tree(&src, &dst).expect_err("destination inside source"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert!( err.to_string().contains("refusing overlapping"), "unexpected error message: {err}" ); - Ok(()) } #[test] -fn copy_dir_tree_rejects_source_inside_destination() -> io::Result<()> { - let root = TempDir::new()?; +fn copy_dir_tree_rejects_source_inside_destination() { + let root = TempDir::new().expect("test setup should succeed"); let dst = root.path().join("dst"); - fs::create_dir_all(&dst)?; + fs::create_dir_all(&dst).expect("test setup should succeed"); let src = dst.join("inner_src"); - fs::create_dir_all(&src)?; - fs::write(src.join("g.txt"), b"y")?; - #[expect(clippy::expect_used, reason = "the test asserts the copy is rejected")] + fs::create_dir_all(&src).expect("test setup should succeed"); + fs::write(src.join("g.txt"), b"y").expect("test setup should succeed"); let err = copy_dir_tree(&src, &dst).expect_err("source inside destination"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert!( err.to_string().contains("refusing overlapping"), "unexpected error message: {err}" ); - Ok(()) } diff --git a/crates/rstest-bdd-harness/tests/binary_test_support_cargo.rs b/crates/rstest-bdd-harness/tests/binary_test_support_cargo.rs index f277deb7a..a9b7f0237 100644 --- a/crates/rstest-bdd-harness/tests/binary_test_support_cargo.rs +++ b/crates/rstest-bdd-harness/tests/binary_test_support_cargo.rs @@ -41,8 +41,10 @@ fn workspace_root() -> PathBuf { } #[test] -fn target_directory_for_invalid_manifest_returns_err() -> std::io::Result<()> { - let manifest = unique_absent_temp_dir("missing_manifest")?.join("Cargo.toml"); +fn target_directory_for_invalid_manifest_returns_err() { + let manifest = unique_absent_temp_dir("missing_manifest") + .expect("test setup should succeed") + .join("Cargo.toml"); assert!( !manifest.exists(), "test setup: manifest path must not exist: {}", @@ -53,7 +55,6 @@ fn target_directory_for_invalid_manifest_returns_err() -> std::io::Result<()> { result.is_err(), "expected error for non-existent manifest, got: {result:?}" ); - Ok(()) } #[test] @@ -74,20 +75,15 @@ fn target_directory_for_workspace_manifest_returns_ok() { } #[test] -fn build_binary_returns_err_for_nonexistent_workspace() -> std::io::Result<()> { - let workspace = unique_absent_temp_dir("no_workspace")?; +fn build_binary_returns_err_for_nonexistent_workspace() { + let workspace = unique_absent_temp_dir("no_workspace").expect("test setup should succeed"); let result = build_binary(&workspace, BinaryName::new("nonexistent-binary")); assert!( result.is_err(), "expected build_binary to fail when the workspace directory does not exist, got: {result:?}" ); - Ok(()) } -#[expect( - clippy::expect_used, - reason = "integration-style tests panic if cargo cannot be spawned for the workspace root" -)] #[test] fn build_binary_captures_output_on_failure() { let workspace_root = workspace_root(); @@ -108,9 +104,12 @@ fn build_binary_captures_output_on_failure() { } #[test] -fn locate_or_build_binary_returns_err_for_invalid_manifest() -> std::io::Result<()> { - let workspace = unique_absent_temp_dir("locate_invalid_workspace")?; - let manifest = unique_absent_temp_dir("locate_invalid_manifest")?.join("Cargo.toml"); +fn locate_or_build_binary_returns_err_for_invalid_manifest() { + let workspace = + unique_absent_temp_dir("locate_invalid_workspace").expect("test setup should succeed"); + let manifest = unique_absent_temp_dir("locate_invalid_manifest") + .expect("test setup should succeed") + .join("Cargo.toml"); assert!( !manifest.exists(), "test setup: manifest path must not exist: {}", @@ -125,13 +124,8 @@ fn locate_or_build_binary_returns_err_for_invalid_manifest() -> std::io::Result< result.is_err(), "expected error for invalid manifest, got: {result:?}" ); - Ok(()) } -#[expect( - clippy::expect_used, - reason = "integration-style tests panic on improbable locate or cargo metadata failures" -)] #[test] fn locate_or_build_reports_build_failed_for_nonexistent_binary() { let root = workspace_root(); @@ -146,10 +140,6 @@ fn locate_or_build_reports_build_failed_for_nonexistent_binary() { }); } -#[expect( - clippy::expect_used, - reason = "integration-style tests panic on improbable locate or spawn failures" -)] #[test] fn locate_or_build_returns_command_for_workspace_binary() { let root = workspace_root(); diff --git a/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests.rs b/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests.rs index 512babe32..e90755fc3 100644 --- a/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests.rs +++ b/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests.rs @@ -67,10 +67,6 @@ impl<'a> StepExecutorExpectation<'a> { /// * `tokens` - The generated token stream to parse /// * `function_name` - The name of the function to find in the generated code /// * `description` - A human-readable description for error messages -#[expect( - clippy::panic, - reason = "test helper panics for clearer failure messages" -)] fn assert_step_executor_delegates_to_runtime( tokens: proc_macro2::TokenStream, expectation: StepExecutorExpectation<'_>, @@ -175,10 +171,6 @@ fn step_executor_delegates_to_runtime(#[case] executor_type: ExecutorType) { /// `ExecutionError` reference and calls its `is_skip()` and `skip_message()` /// methods to extract skip information. #[test] -#[expect( - clippy::expect_used, - reason = "test parses generated tokens and uses expect for clearer failures" -)] fn skip_extractor_references_execution_error() { let file: syn::File = syn::parse2(generate_skip_extractor()).expect("generate_skip_extractor parses as a file"); @@ -220,7 +212,6 @@ fn skip_extractor_references_execution_error() { ); } -#[expect(clippy::panic, reason = "test helper panics for clearer failures")] fn assert_skip_handler_returns( return_kind: ScenarioReturnKind, empty_message: &str, diff --git a/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests/support.rs b/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests/support.rs index 805699bb2..8207526d0 100644 --- a/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests/support.rs +++ b/crates/rstest-bdd-macros/src/codegen/scenario/runtime/tests/support.rs @@ -19,7 +19,6 @@ pub(super) fn path_last_ident(path: &syn::Path) -> Option<&syn::Ident> { /// # Panics /// /// Panics if the expression is not a path expression. -#[expect(clippy::panic, reason = "test helper panics for clearer failures")] pub(super) fn extract_path(expr: &syn::Expr) -> &syn::Path { match expr { syn::Expr::Path(expr_path) => &expr_path.path, @@ -82,7 +81,6 @@ pub(super) fn assert_path_is_execution_execute_step_async(path: &syn::Path) { /// # Panics /// /// Panics if the file does not contain a function with the requested name. -#[expect(clippy::panic, reason = "test helper panics for clearer failures")] pub(super) fn find_function_by_name<'a>(file: &'a syn::File, name: &str) -> &'a syn::ItemFn { let Some(function) = file.items.iter().find_map(|item| match item { syn::Item::Fn(f) if f.sig.ident == name => Some(f), @@ -162,7 +160,6 @@ pub(super) fn find_call_in_block( /// /// Panics if the generated tokens fail to parse or do not produce an if /// expression. -#[expect(clippy::panic, reason = "test helper panics for clearer failures")] pub(super) fn parse_skip_handler(return_kind: ScenarioReturnKind) -> syn::ExprIf { let stmt: syn::Stmt = match syn::parse2(generate_skip_handler(return_kind)) { Ok(stmt) => stmt, diff --git a/crates/rstest-bdd-macros/src/codegen/wrapper/args/extract.rs b/crates/rstest-bdd-macros/src/codegen/wrapper/args/extract.rs index 8ea5439c2..c57c0952d 100644 --- a/crates/rstest-bdd-macros/src/codegen/wrapper/args/extract.rs +++ b/crates/rstest-bdd-macros/src/codegen/wrapper/args/extract.rs @@ -225,7 +225,6 @@ mod tests { } #[test] - #[expect(clippy::expect_used, reason = "test asserts error contents and span")] fn next_typed_argument_reports_pattern_in_error() { let src = "fn step((a, b): (i32, i32)) {}"; let func = parse_fn(src); @@ -281,10 +280,6 @@ mod tests { } #[test] - #[expect( - clippy::expect_used, - reason = "test asserts the returned span covers the destructuring group" - )] fn span_for_pattern_points_to_full_destructuring_pattern() { let src = "fn step(User { name }: User) {}"; let func = parse_fn(src); diff --git a/crates/rstest-bdd-macros/src/codegen/wrapper/arguments/tests/bindings.rs b/crates/rstest-bdd-macros/src/codegen/wrapper/arguments/tests/bindings.rs index 9ea648aca..8cd42aa1d 100644 --- a/crates/rstest-bdd-macros/src/codegen/wrapper/arguments/tests/bindings.rs +++ b/crates/rstest-bdd-macros/src/codegen/wrapper/arguments/tests/bindings.rs @@ -24,7 +24,6 @@ fn collect_ordered_arguments_preserves_call_order() { ); } -#[expect(clippy::expect_used, reason = "test asserts fixture declaration")] #[test] fn wrapper_bindings_avoid_leading_underscores() { let fixture = Arg::Fixture { diff --git a/crates/rstest-bdd-macros/src/codegen/wrapper/emit/tests.rs b/crates/rstest-bdd-macros/src/codegen/wrapper/emit/tests.rs index 74a0bb311..b7cb51262 100644 --- a/crates/rstest-bdd-macros/src/codegen/wrapper/emit/tests.rs +++ b/crates/rstest-bdd-macros/src/codegen/wrapper/emit/tests.rs @@ -38,7 +38,6 @@ fn generates_ascii_only_idents( #[case] expected_const: &str, #[case] expected_pattern: &str, ) { - #[expect(clippy::expect_used, reason = "raw identifiers are test inputs")] let ident = parse_str::(raw).expect("parse identifier"); let WrapperIdents { sync_wrapper, diff --git a/crates/rstest-bdd-macros/src/datatable/row/attributes.rs b/crates/rstest-bdd-macros/src/datatable/row/attributes.rs index 5cf153955..f4129b7b3 100644 --- a/crates/rstest-bdd-macros/src/datatable/row/attributes.rs +++ b/crates/rstest-bdd-macros/src/datatable/row/attributes.rs @@ -254,7 +254,6 @@ mod tests { value: String, } }; - #[expect(clippy::expect_used, reason = "test asserts parsed config")] let config = parse_struct_config(&input.attrs).expect("failed to parse struct config"); assert!(matches!(config.rename_rule, Some(RenameRule::Title))); } @@ -268,7 +267,6 @@ mod tests { let base = Accessor::Column { name: String::from("flag"), }; - #[expect(clippy::expect_used, reason = "test asserts parsed config")] let config = parse_field_attributes(&field.attrs, base).expect("failed to parse field attributes"); assert!(config.optional); @@ -285,7 +283,6 @@ mod tests { let base = Accessor::Column { name: String::from("value"), }; - #[expect(clippy::expect_used, reason = "test asserts error handling")] let err = parse_field_attributes(&field.attrs, base) .err() .expect("duplicate default must error"); @@ -300,13 +297,11 @@ mod tests { flag: bool, } }; - #[expect(clippy::expect_used, reason = "test asserts parsed config")] let config = parse_struct_config(&input.attrs).expect("failed to parse struct config"); let Data::Struct(data) = &input.data else { unreachable!("test input must be a struct"); }; let fields = &data.fields; - #[expect(clippy::expect_used, reason = "test asserts error handling")] let err = collect_fields(fields, &config) .err() .expect("optional on non-Option should error"); @@ -324,13 +319,11 @@ mod tests { value: String, } }; - #[expect(clippy::expect_used, reason = "test asserts parsed config")] let config = parse_struct_config(&input.attrs).expect("failed to parse struct config"); let Data::Struct(data) = &input.data else { unreachable!("test input must be a struct"); }; let fields = &data.fields; - #[expect(clippy::expect_used, reason = "test asserts error handling")] let err = collect_fields(fields, &config) .err() .expect("truthy on non-bool should error"); diff --git a/crates/rstest-bdd-macros/src/datatable/table/attributes.rs b/crates/rstest-bdd-macros/src/datatable/table/attributes.rs index 062800221..beacddbaf 100644 --- a/crates/rstest-bdd-macros/src/datatable/table/attributes.rs +++ b/crates/rstest-bdd-macros/src/datatable/table/attributes.rs @@ -78,7 +78,6 @@ mod tests { parse_quote!(#[datatable(row = Example)]), parse_quote!(#[datatable(map = transform)]), ]; - #[expect(clippy::expect_used, reason = "test asserts parsed config")] let config = parse_struct_attrs(&attrs).expect("failed to parse struct attrs"); assert!(config.row_ty.is_some()); assert!(matches!(config.map, Some(MapKind::Direct(_)))); @@ -90,7 +89,6 @@ mod tests { parse_quote!(#[datatable(map = transform)]), parse_quote!(#[datatable(try_map = fallible_transform)]), ]; - #[expect(clippy::expect_used, reason = "test asserts error handling")] let err = parse_struct_attrs(&attrs) .err() .expect("map and try_map together should trigger an error"); diff --git a/crates/rstest-bdd-macros/src/macros/scenario/args.rs b/crates/rstest-bdd-macros/src/macros/scenario/args.rs index cb4b19ce0..24da4509f 100644 --- a/crates/rstest-bdd-macros/src/macros/scenario/args.rs +++ b/crates/rstest-bdd-macros/src/macros/scenario/args.rs @@ -188,10 +188,6 @@ fn selector_conflict_error( } #[cfg(test)] -#[expect( - clippy::expect_used, - reason = "test code uses infallible expects for clarity" -)] mod tests { //! Unit tests for `#[scenario]` attribute argument parsing. diff --git a/crates/rstest-bdd-macros/src/macros/scenario/paths.rs b/crates/rstest-bdd-macros/src/macros/scenario/paths.rs index 4d07e5f80..ee16138e0 100644 --- a/crates/rstest-bdd-macros/src/macros/scenario/paths.rs +++ b/crates/rstest-bdd-macros/src/macros/scenario/paths.rs @@ -230,10 +230,6 @@ mod tests { #[serial] #[rstest] - #[expect( - clippy::expect_used, - reason = "tests require explicit failure messages" - )] fn canonicalizes_with_manifest_dir(_cache_cleared: ()) { let manifest = PathBuf::from( env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is required for tests"), @@ -263,10 +259,6 @@ mod tests { #[serial] #[rstest] - #[expect( - clippy::expect_used, - reason = "tests require explicit failure messages" - )] fn caches_paths_between_calls(_cache_cleared: ()) { use std::time::{SystemTime, UNIX_EPOCH}; diff --git a/crates/rstest-bdd-macros/src/macros/scenario/selection.rs b/crates/rstest-bdd-macros/src/macros/scenario/selection.rs index e9ad9551c..a884a9e5e 100644 --- a/crates/rstest-bdd-macros/src/macros/scenario/selection.rs +++ b/crates/rstest-bdd-macros/src/macros/scenario/selection.rs @@ -240,16 +240,11 @@ mod tests { } #[test] - #[expect( - clippy::expect_used, - reason = "this test asserts successful scenario selection" - )] - fn finds_a_uniquely_named_scenario() -> Result<(), gherkin::ParseError> { - let feature = parse_feature(TWO_SCENARIOS)?; + fn finds_a_uniquely_named_scenario() { + let feature = parse_feature(TWO_SCENARIOS).expect("test setup should succeed"); let index = find_scenario_by_name(&feature, "second", Span::call_site()) .expect("uniquely named scenario should resolve"); assert_eq!(index, 1); - Ok(()) } #[rstest] @@ -289,14 +284,13 @@ mod tests { } #[test] - fn missing_name_diagnostic_notes_empty_features() -> Result<(), gherkin::ParseError> { - let feature = parse_feature("Feature: demo\n")?; + fn missing_name_diagnostic_notes_empty_features() { + let feature = parse_feature("Feature: demo\n").expect("test setup should succeed"); let message = scenario_not_found_error(&feature, "any", Span::call_site()).to_string(); assert!( message.contains("feature contains no scenarios"), "diagnostic should note the empty feature: {message}" ); - Ok(()) } #[test] diff --git a/crates/rstest-bdd-macros/src/macros/scenarios/macro_args/tests.rs b/crates/rstest-bdd-macros/src/macros/scenarios/macro_args/tests.rs index db0d285fe..e0e15ef41 100644 --- a/crates/rstest-bdd-macros/src/macros/scenarios/macro_args/tests.rs +++ b/crates/rstest-bdd-macros/src/macros/scenarios/macro_args/tests.rs @@ -64,7 +64,6 @@ macro_rules! assert_tag_filter_eq { mod combined_arguments; #[test] -#[expect(clippy::expect_used, reason = "test with descriptive failures")] fn fixture_spec_parses_simple_type() { let spec: FixtureSpec = parse_fixture_spec(parse_quote!(world: TestWorld)).expect("fixture spec should parse"); @@ -73,7 +72,6 @@ fn fixture_spec_parses_simple_type() { } #[test] -#[expect(clippy::expect_used, reason = "test with descriptive failures")] fn fixture_spec_parses_generic_type() { let spec: FixtureSpec = parse_fixture_spec(parse_quote!(counter: RefCell)) .expect("fixture spec should parse"); @@ -84,7 +82,6 @@ fn fixture_spec_parses_generic_type() { } #[test] -#[expect(clippy::expect_used, reason = "test with descriptive failures")] fn fixture_spec_parses_path_type() { let spec: FixtureSpec = parse_fixture_spec(parse_quote!(db: std::sync::Arc)) .expect("fixture spec should parse"); @@ -128,7 +125,6 @@ fn scenarios_args_parses_with_tags() { } #[test] -#[expect(clippy::expect_used, reason = "test with descriptive failures")] fn scenarios_args_parses_single_fixture() { let args: ScenariosArgs = parse_scenarios_args!(parse_quote!("tests/features", fixtures = [world: TestWorld])); @@ -144,7 +140,6 @@ fn scenarios_args_parses_single_fixture() { } #[test] -#[expect(clippy::expect_used, reason = "test with descriptive failures")] fn scenarios_args_parses_multiple_fixtures() { let args: ScenariosArgs = parse_scenarios_args!(parse_quote!( "tests/features", diff --git a/crates/rstest-bdd-macros/src/macros/scenarios/mod.rs b/crates/rstest-bdd-macros/src/macros/scenarios/mod.rs index d4c456b21..9e7ad0091 100644 --- a/crates/rstest-bdd-macros/src/macros/scenarios/mod.rs +++ b/crates/rstest-bdd-macros/src/macros/scenarios/mod.rs @@ -267,36 +267,35 @@ mod tests { use super::feature_discovery::collect_feature_files; use std::fs; - use std::io; use std::os::unix::fs::symlink; use std::path::Path; use tempfile::tempdir; #[test] - fn collects_symlinked_feature_files_without_following_directory_loops() -> io::Result<()> { - let temp = tempdir()?; + fn collects_symlinked_feature_files_without_following_directory_loops() { + let temp = tempdir().expect("test setup should succeed"); let features_root = temp.path().join("features"); - fs::create_dir_all(features_root.join("nested"))?; + fs::create_dir_all(features_root.join("nested")).expect("test setup should succeed"); let feature_path = features_root.join("nested/example.feature"); - fs::write(&feature_path, "Feature: Example\n")?; + fs::write(&feature_path, "Feature: Example\n").expect("test setup should succeed"); let symlink_path = features_root.join("symlink.feature"); - symlink(&feature_path, &symlink_path)?; + symlink(&feature_path, &symlink_path).expect("test setup should succeed"); let relative_symlink_path = features_root.join("relative_link.feature"); - symlink(Path::new("nested/example.feature"), &relative_symlink_path)?; + symlink(Path::new("nested/example.feature"), &relative_symlink_path) + .expect("test setup should succeed"); let loop_dir = features_root.join("loop"); - symlink(&features_root, &loop_dir)?; + symlink(&features_root, &loop_dir).expect("test setup should succeed"); - let files = collect_feature_files(features_root.as_path())?; + let files = + collect_feature_files(features_root.as_path()).expect("test setup should succeed"); let mut expected = vec![feature_path, symlink_path, relative_symlink_path]; expected.sort(); assert_eq!(files, expected); - - Ok(()) } } diff --git a/crates/rstest-bdd-macros/src/macros/scenarios/test_generation.rs b/crates/rstest-bdd-macros/src/macros/scenarios/test_generation.rs index 94a7e4b95..3af144449 100644 --- a/crates/rstest-bdd-macros/src/macros/scenarios/test_generation.rs +++ b/crates/rstest-bdd-macros/src/macros/scenarios/test_generation.rs @@ -340,7 +340,6 @@ pub(super) fn generate_scenario_test( #[cfg(test)] #[expect( - clippy::expect_used, clippy::indexing_slicing, reason = "test code uses infallible expects and indexed access for clarity" )] diff --git a/crates/rstest-bdd-macros/src/parsing/examples.rs b/crates/rstest-bdd-macros/src/parsing/examples.rs index 72e14910b..443ebe444 100644 --- a/crates/rstest-bdd-macros/src/parsing/examples.rs +++ b/crates/rstest-bdd-macros/src/parsing/examples.rs @@ -108,10 +108,6 @@ mod tests { } } - #[expect( - clippy::expect_used, - reason = "tests assert specific error paths; panics aid debugging" - )] #[test] fn missing_examples_error_includes_scenario_name() { let scenario = scenario_outline_without_examples("outline without examples"); diff --git a/crates/rstest-bdd-macros/src/parsing/feature/missing_examples_tests.rs b/crates/rstest-bdd-macros/src/parsing/feature/missing_examples_tests.rs index 3b2e291b4..dd591e7a2 100644 --- a/crates/rstest-bdd-macros/src/parsing/feature/missing_examples_tests.rs +++ b/crates/rstest-bdd-macros/src/parsing/feature/missing_examples_tests.rs @@ -3,10 +3,6 @@ use super::*; use gherkin::{Feature, LineCol, Scenario, Span}; -#[expect( - clippy::expect_used, - reason = "tests assert error paths; panics surface unexpected success" -)] #[test] fn scenario_outline_missing_examples_surfaces_scenario_name() { let scenario_name = "outline without examples"; diff --git a/crates/rstest-bdd-macros/src/parsing/feature/tests.rs b/crates/rstest-bdd-macros/src/parsing/feature/tests.rs index e5fd504cb..22c913fe5 100644 --- a/crates/rstest-bdd-macros/src/parsing/feature/tests.rs +++ b/crates/rstest-bdd-macros/src/parsing/feature/tests.rs @@ -65,10 +65,6 @@ fn reports_requested_index_and_available_count_on_oob() { ); } -#[expect( - clippy::expect_used, - reason = "test asserts cache behaviour; panics simplify failures" -)] #[test] fn caches_features_by_path() { use std::io::Write; diff --git a/crates/rstest-bdd-macros/src/step_args.rs b/crates/rstest-bdd-macros/src/step_args.rs index b2e14780e..5ef1e24b5 100644 --- a/crates/rstest-bdd-macros/src/step_args.rs +++ b/crates/rstest-bdd-macros/src/step_args.rs @@ -213,7 +213,6 @@ mod tests { } #[test] - #[expect(clippy::expect_used, reason = "test asserts derive success path")] fn derives_step_args_for_named_struct() { let tokens = expand_tokens(quote! { struct AccountArgs { @@ -232,7 +231,6 @@ mod tests { } #[test] - #[expect(clippy::expect_used, reason = "test asserts derive failure path")] fn rejects_tuple_structs() { let err = expand_tokens(quote! { struct TupleArgs(u32, String); diff --git a/crates/rstest-bdd-macros/src/utils/fixtures.rs b/crates/rstest-bdd-macros/src/utils/fixtures.rs index 5f937a7bb..c1ef3d1e8 100644 --- a/crates/rstest-bdd-macros/src/utils/fixtures.rs +++ b/crates/rstest-bdd-macros/src/utils/fixtures.rs @@ -213,10 +213,6 @@ mod tests { use syn::parse_quote; #[test] - #[expect( - clippy::expect_used, - reason = "test asserts fixture extraction for underscore bindings" - )] fn non_ref_fixture_cell_ident_uses_index() { let mut sig: syn::Signature = parse_quote! { fn scenario(_state: MyState) @@ -245,19 +241,11 @@ mod tests { fn resolve_fixture_name_normalizes_param(#[case] input: &str, #[case] expected: &str) { let ident = syn::Ident::new(input, proc_macro2::Span::call_site()); let pat_ty: syn::PatType = parse_quote! { #ident: WorldFixture }; - #[expect( - clippy::expect_used, - reason = "test asserts fixture name normalization" - )] let name = resolve_fixture_name(&pat_ty).expect("fixture name resolution should succeed"); assert_eq!(name, expected); } #[test] - #[expect( - clippy::expect_used, - reason = "test asserts from attribute takes precedence" - )] fn resolve_fixture_name_from_attr_unchanged() { let sig: syn::Signature = parse_quote! { fn test(#[from(state)] _world: WorldFixture) }; let syn::FnArg::Typed(pat_ty) = sig.inputs.first().expect("signature has one arg") else { @@ -275,7 +263,6 @@ mod tests { #[case] mut sig: syn::Signature, #[case] expected: bool, ) { - #[expect(clippy::expect_used, reason = "test asserts fixture extraction")] let (_idents, code) = extract_function_fixtures(&mut sig).expect("fixture extraction should succeed"); assert_eq!( @@ -288,7 +275,6 @@ mod tests { #[case(parse_quote! { fn scenario(world: (&MyWorld)) })] #[case(parse_quote! { fn scenario(world: (&mut MyWorld)) })] fn parenthesized_references_are_treated_as_references(#[case] mut sig: syn::Signature) { - #[expect(clippy::expect_used, reason = "test asserts fixture extraction")] let (_idents, code) = extract_function_fixtures(&mut sig).expect("fixture extraction should succeed"); // Parenthesized references should be treated as references, not owned @@ -307,10 +293,6 @@ mod tests { } #[test] - #[expect( - clippy::expect_used, - reason = "test asserts Result fixture generates correct bindings" - )] fn result_fixture_extraction_generates_correct_bindings() { let mut sig: syn::Signature = parse_quote! { fn scenario(world: Result) @@ -338,10 +320,6 @@ mod tests { } #[test] - #[expect( - clippy::expect_used, - reason = "test asserts StepResult fixture generates correct bindings" - )] fn step_result_fixture_extraction_generates_correct_bindings() { let mut sig: syn::Signature = parse_quote! { fn scenario(world: StepResult) diff --git a/crates/rstest-bdd-macros/src/utils/pattern/tests.rs b/crates/rstest-bdd-macros/src/utils/pattern/tests.rs index f125210bd..1d7893744 100644 --- a/crates/rstest-bdd-macros/src/utils/pattern/tests.rs +++ b/crates/rstest-bdd-macros/src/utils/pattern/tests.rs @@ -61,11 +61,7 @@ fn placeholder_hint_extraction( } #[test] -#[expect( - clippy::expect_used, - clippy::indexing_slicing, - reason = "test asserts valid pattern" -)] +#[expect(clippy::indexing_slicing, reason = "test asserts valid pattern")] fn multiple_placeholders_with_mixed_hints() { let summary = placeholder_names("given {name} has {count:u32} items").expect("valid pattern"); assert_eq!(summary.ordered.len(), 2); @@ -76,11 +72,7 @@ fn multiple_placeholders_with_mixed_hints() { } #[test] -#[expect( - clippy::expect_used, - clippy::indexing_slicing, - reason = "test asserts valid pattern" -)] +#[expect(clippy::indexing_slicing, reason = "test asserts valid pattern")] fn placeholder_hints_align_with_names_for_wrapper_config() { // This test verifies that hints extracted from PlaceholderSummary maintain // correct alignment with placeholder names when converted to separate vectors. diff --git a/crates/rstest-bdd-macros/src/utils/result_type.rs b/crates/rstest-bdd-macros/src/utils/result_type.rs index c9a0a6e84..b94c654ed 100644 --- a/crates/rstest-bdd-macros/src/utils/result_type.rs +++ b/crates/rstest-bdd-macros/src/utils/result_type.rs @@ -104,7 +104,6 @@ pub(crate) fn try_extract_result_error_type(ty: &Type) -> Option { } #[cfg(test)] -#[expect(clippy::expect_used, reason = "test code uses infallible type parsing")] mod tests { //! Unit tests for detecting `Result`-like return types. diff --git a/crates/rstest-bdd-macros/src/validation/steps/tests.rs b/crates/rstest-bdd-macros/src/validation/steps/tests.rs index 0820e9c70..4fbaf6257 100644 --- a/crates/rstest-bdd-macros/src/validation/steps/tests.rs +++ b/crates/rstest-bdd-macros/src/validation/steps/tests.rs @@ -134,24 +134,28 @@ fn normalizes_windows_drive_letter_out_dir() { assert_eq!(id.as_ref(), "demo:C:/a/b"); } -#[test] #[serial] -fn normalizes_relative_out_dir_paths() -> Result<(), Box> { - let temp = tempdir_in(".")?; - let abs = Utf8PathBuf::from_path_buf(temp.path().to_path_buf()).map_err(|path| { - format!( - "temporary directory should be valid UTF-8: {}", - path.display() - ) - })?; - let cwd = std::env::current_dir()?; - let cwd = Utf8PathBuf::from_path_buf(cwd).map_err(|path| { - format!( - "current directory should be valid UTF-8: {}", - path.display() - ) - })?; - let relative = abs.strip_prefix(&cwd)?; +#[test] +fn normalizes_relative_out_dir_paths() { + let temp = tempdir_in(".").expect("test setup should succeed"); + let abs = Utf8PathBuf::from_path_buf(temp.path().to_path_buf()) + .map_err(|path| { + format!( + "temporary directory should be valid UTF-8: {}", + path.display() + ) + }) + .expect("test setup should succeed"); + let cwd = std::env::current_dir().expect("test setup should succeed"); + let cwd = Utf8PathBuf::from_path_buf(cwd) + .map_err(|path| { + format!( + "current directory should be valid UTF-8: {}", + path.display() + ) + }) + .expect("test setup should succeed"); + let relative = abs.strip_prefix(&cwd).expect("test setup should succeed"); let crate_id = format!("demo:./{}", relative.as_str()); let normalized = normalize_crate_id(&crate_id); let canonical_abs = abs @@ -160,14 +164,9 @@ fn normalizes_relative_out_dir_paths() -> Result<(), Box> .unwrap_or_else(|_| abs.clone()); let expected = format!("demo:{}", canonical_abs.as_str()); assert_eq!(normalized.as_ref(), expected); - Ok(()) } #[test] -#[expect( - clippy::expect_used, - reason = "test documents fallback behaviour with explicit expect messaging" -)] fn leaves_unresolvable_out_dir_paths_unchanged() { let temp = tempdir().expect("create temp directory"); let missing = temp.path().join("missing"); @@ -181,10 +180,10 @@ fn leaves_unresolvable_out_dir_paths_unchanged() { #[serial] fn canonicalize_out_dir_resolves_relative_components( temp_working_dir: std::io::Result, -) -> std::io::Result<()> { - let temp_working_dir = temp_working_dir?; +) { + let temp_working_dir = temp_working_dir.expect("test setup should succeed"); let nested_dir = temp_working_dir.join("nested"); - create_dir_all_cap(nested_dir.as_path())?; + create_dir_all_cap(nested_dir.as_path()).expect("test setup should succeed"); let nested = temp_working_dir.join("nested/."); let canonical = canonicalize_out_dir(nested.as_path()); let expected_dir = temp_working_dir.path().join("nested"); @@ -198,15 +197,10 @@ fn canonicalize_out_dir_resolves_relative_components( canonical.is_absolute(), "canonical path should be absolute: {canonical}" ); - Ok(()) } #[cfg(unix)] #[test] -#[expect( - clippy::expect_used, - reason = "symlink setup uses expect to surface filesystem failures" -)] fn canonicalize_out_dir_resolves_symlinks() { let temp = tempdir().expect("create temp directory"); let base = Utf8PathBuf::from_path_buf(temp.path().to_path_buf()) @@ -228,10 +222,6 @@ fn canonicalize_out_dir_resolves_symlinks() { } #[test] -#[expect( - clippy::expect_used, - reason = "test asserts fallback path handling with explicit expect messaging" -)] fn canonicalize_out_dir_returns_original_when_unresolvable() { let temp = tempdir().expect("create temp directory"); let missing = temp.path().join("missing"); @@ -239,17 +229,19 @@ fn canonicalize_out_dir_returns_original_when_unresolvable() { assert_eq!(canonicalize_out_dir(missing.as_path()), missing); } -#[test] #[serial] -fn canonicalizes_equivalent_crate_paths_in_registry() -> Result<(), Box> { +#[test] +fn canonicalizes_equivalent_crate_paths_in_registry() { clear_registry(); - let temp = tempdir()?; - let abs = Utf8PathBuf::from_path_buf(temp.path().to_path_buf()).map_err(|path| { - format!( - "temporary directory should be valid UTF-8: {}", - path.display() - ) - })?; + let temp = tempdir().expect("test setup should succeed"); + let abs = Utf8PathBuf::from_path_buf(temp.path().to_path_buf()) + .map_err(|path| { + format!( + "temporary directory should be valid UTF-8: {}", + path.display() + ) + }) + .expect("test setup should succeed"); let crate_id = format!("demo:{}", abs.as_str()); let alt_id = format!("demo:{}/.", abs.as_str()); @@ -267,7 +259,8 @@ fn canonicalizes_equivalent_crate_paths_in_registry() -> Result<(), Box Result<(), Box>, docstring: String, b: bool) {} }; let mut placeholders: HashSet = ["a".into(), "b".into()].into_iter().collect(); - #[expect(clippy::expect_used, reason = "test asserts valid extraction")] let args = extract_args(&mut func, &mut placeholders).expect("failed to extract args"); let ordered = ordered_parameter_names(&args); assert_eq!(ordered, ["f", "a", "datatable", "docstring", "b"]); @@ -158,14 +153,11 @@ fn datatable_attribute_recognized_and_preserves_type() { let mut func: syn::ItemFn = parse_quote! { fn step(#[datatable] table: my_mod::MyTable) {} }; - #[expect(clippy::expect_used, reason = "test asserts valid extraction")] let args = extract_args(&mut func, &mut HashSet::new()).expect("failed to extract args"); - #[expect(clippy::expect_used, reason = "datatable presence required")] let dt = find_datatable(&args).expect("missing datatable"); if let Arg::DataTable { pat, ty } = dt { assert_eq!(pat.to_string(), "table"); if let syn::Type::Path(tp) = ty { - #[expect(clippy::expect_used, reason = "path has at least one segment")] let seg = tp.path.segments.last().expect("missing segment"); assert_eq!(seg.ident, "MyTable"); let rendered = tp @@ -189,15 +181,12 @@ fn datatable_attribute_removed_from_signature() { let mut func: syn::ItemFn = parse_quote! { fn step(#[datatable] data: Vec>) {} }; - #[expect(clippy::expect_used, reason = "test asserts valid extraction")] let args = extract_args(&mut func, &mut HashSet::new()).expect("failed to extract args"); - #[expect(clippy::expect_used, reason = "datatable presence required")] let dt = find_datatable(&args).expect("missing datatable after strip"); match dt { Arg::DataTable { pat, .. } => assert_eq!(pat.to_string(), "data"), _ => panic!("expected datatable argument"), } - #[expect(clippy::expect_used, reason = "test inspects parameter attributes")] let syn::FnArg::Typed(arg) = func.sig.inputs.first().expect("missing arg") else { panic!("expected typed argument"); }; @@ -221,10 +210,8 @@ fn step_with_cached_table_is_classified_as_datatable() { fn step(datatable: rstest_bdd::datatable::CachedTable) {} }; - #[expect(clippy::expect_used, reason = "test asserts valid extraction")] let args = extract_args(&mut func, &mut HashSet::new()).expect("failed to extract args"); - #[expect(clippy::expect_used, reason = "test requires datatable")] let dt = find_datatable(&args).expect("cached table should be classified as datatable"); if let Arg::DataTable { ty, .. } = dt { @@ -241,7 +228,6 @@ fn step_with_cached_table_is_classified_as_datatable() { #[rstest] fn implicit_fixture_injected_without_from() { let func = parse_quote! { fn step(fixture: usize, count: u32) {} }; - #[expect(clippy::expect_used, reason = "test asserts valid extraction")] let args = test_extract_args_scenario(func, vec!["count"]).expect("failed to extract args"); assert_eq!(fixture_count(&args), 1); assert_eq!(step_arg_count(&args), 1); @@ -254,7 +240,6 @@ fn error_when_placeholder_missing_parameter() { fn step(fixture: usize) {} }; let mut placeholders: HashSet = ["count".into()].into_iter().collect(); - #[expect(clippy::expect_used, reason = "test asserts error message")] let err = extract_args(&mut func, &mut placeholders).expect_err("missing placeholder"); let msg = err.to_string(); assert!(msg.contains("count"), "unexpected error: {msg}"); @@ -268,7 +253,6 @@ fn placeholders_named_like_reserved_args_are_step_args() { let mut placeholders: HashSet = ["datatable".into(), "docstring".into()] .into_iter() .collect(); - #[expect(clippy::expect_used, reason = "test asserts classification")] let args = extract_args(&mut func, &mut placeholders).expect("failed to extract args"); assert_eq!(step_arg_count(&args), 2); assert!(find_datatable(&args).is_none()); @@ -278,7 +262,6 @@ fn placeholders_named_like_reserved_args_are_step_args() { #[rstest] fn from_attribute_targets_placeholder() { let func = parse_quote! { fn step(#[from(count)] renamed: u32) {} }; - #[expect(clippy::expect_used, reason = "test asserts classification")] let args = test_extract_args_scenario(func, vec!["count"]).expect("failed to extract args"); assert_eq!(fixture_count(&args), 0); assert_eq!(step_arg_count(&args), 1); @@ -288,7 +271,6 @@ fn from_attribute_targets_placeholder() { #[rstest] fn underscore_prefixed_param_matches_placeholder() { let func = parse_quote! { fn step(_count: u32) {} }; - #[expect(clippy::expect_used, reason = "test asserts classification")] let args = test_extract_args_scenario(func, vec!["count"]).expect("extraction"); assert_eq!(step_arg_count(&args), 1); assert_eq!(fixture_count(&args), 0); @@ -298,7 +280,6 @@ fn underscore_prefixed_param_matches_placeholder() { #[rstest] fn underscore_prefixed_param_preserves_original_name() { let func = parse_quote! { fn step(_value: String, other: i32) {} }; - #[expect(clippy::expect_used, reason = "test asserts classification")] let args = test_extract_args_scenario(func, vec!["value", "other"]).expect("extraction"); assert_eq!(step_arg_count(&args), 2); assert_eq!(ordered_parameter_names(&args), ["_value", "other"]); @@ -310,7 +291,6 @@ fn step_struct_argument_is_classified() { fn step(#[step_args] params: OrderArgs, account: usize) {} }; let mut placeholders: HashSet = ["count".into(), "name".into()].into_iter().collect(); - #[expect(clippy::expect_used, reason = "test asserts classification")] let args = extract_args(&mut func, &mut placeholders).expect("failed to extract args"); assert!(args.step_struct().is_some()); assert_eq!(step_arg_count(&args), 0); @@ -355,7 +335,6 @@ fn test_step_struct_errors( #[case] description: &str, ) { let mut placeholder_set: HashSet = placeholders.into_iter().map(String::from).collect(); - #[expect(clippy::expect_used, reason = "test asserts error path")] let err = extract_args(&mut func, &mut placeholder_set).expect_err(description); let msg = err.to_string(); assert!( diff --git a/crates/rstest-bdd-macros/tests/args_str_ref.rs b/crates/rstest-bdd-macros/tests/args_str_ref.rs index b98b4aade..93ff84569 100644 --- a/crates/rstest-bdd-macros/tests/args_str_ref.rs +++ b/crates/rstest-bdd-macros/tests/args_str_ref.rs @@ -56,7 +56,6 @@ fn str_reference_variants_are_classified_as_step_arguments( #[rstest] fn mixed_str_reference_and_parsed_types() { let func = parse_quote! { fn step(tag: &str, count: u32, name: String) {} }; - #[expect(clippy::expect_used, reason = "test asserts valid extraction")] let args = test_extract_args_scenario(func, vec!["tag", "count", "name"]).expect("extraction failed"); assert_eq!(step_arg_count(&args), 3); diff --git a/crates/rstest-bdd-patterns/src/keyword.rs b/crates/rstest-bdd-patterns/src/keyword.rs index d5599438e..9e1f7a2e8 100644 --- a/crates/rstest-bdd-patterns/src/keyword.rs +++ b/crates/rstest-bdd-patterns/src/keyword.rs @@ -217,10 +217,6 @@ mod tests { } #[test] - #[expect( - clippy::expect_used, - reason = "test verifies error case with descriptive failure" - )] fn rejects_invalid_keyword() { let result = "invalid".parse::(); assert!(result.is_err()); diff --git a/crates/rstest-bdd-server/src/config.rs b/crates/rstest-bdd-server/src/config.rs index 12475dd87..5039df382 100644 --- a/crates/rstest-bdd-server/src/config.rs +++ b/crates/rstest-bdd-server/src/config.rs @@ -185,7 +185,6 @@ impl ServerConfig { #[cfg(test)] #[expect( clippy::unwrap_used, - clippy::expect_used, reason = "tests require explicit panic messages for debugging failures" )] mod tests { diff --git a/crates/rstest-bdd-server/src/discovery/workspace.rs b/crates/rstest-bdd-server/src/discovery/workspace.rs index e41b6370f..046fe00a6 100644 --- a/crates/rstest-bdd-server/src/discovery/workspace.rs +++ b/crates/rstest-bdd-server/src/discovery/workspace.rs @@ -180,7 +180,6 @@ fn collect_feature_files_recursive(dir: &Path, features: &mut Vec) { #[cfg(test)] #[expect( clippy::unwrap_used, - clippy::expect_used, reason = "tests require explicit panic messages for debugging failures" )] mod tests { @@ -214,27 +213,23 @@ edition = "2024" } #[rstest] - fn discovers_workspace_from_root(create_test_workspace: io::Result) -> io::Result<()> { - let workspace = create_test_workspace?; + fn discovers_workspace_from_root(create_test_workspace: io::Result) { + let workspace = create_test_workspace.expect("test setup should succeed"); let result = discover_workspace(workspace.path()); assert!(result.is_ok()); let info = result.expect("should discover workspace"); assert_eq!(info.root, workspace.path()); assert!(info.packages.contains(&"test-project".to_string())); - Ok(()) } #[rstest] - fn discovers_workspace_from_subdirectory( - create_test_workspace: io::Result, - ) -> io::Result<()> { - let workspace = create_test_workspace?; + fn discovers_workspace_from_subdirectory(create_test_workspace: io::Result) { + let workspace = create_test_workspace.expect("test setup should succeed"); let subdir = workspace.path().join("src"); let result = discover_workspace(&subdir); assert!(result.is_ok()); let info = result.expect("should discover workspace"); assert_eq!(info.root, workspace.path()); - Ok(()) } #[rstest] @@ -294,11 +289,12 @@ edition = "2024" } #[rstest] - fn returns_empty_when_no_feature_files( - create_test_workspace: io::Result, - ) -> io::Result<()> { - let features = find_feature_files(create_test_workspace?.path()); + fn returns_empty_when_no_feature_files(create_test_workspace: io::Result) { + let features = find_feature_files( + create_test_workspace + .expect("test setup should succeed") + .path(), + ); assert!(features.is_empty()); - Ok(()) } } diff --git a/crates/rstest-bdd-server/src/handlers/definition.rs b/crates/rstest-bdd-server/src/handlers/definition.rs index 73eb54073..97d066f20 100644 --- a/crates/rstest-bdd-server/src/handlers/definition.rs +++ b/crates/rstest-bdd-server/src/handlers/definition.rs @@ -171,10 +171,6 @@ fn find_matching_feature_locations( } #[cfg(test)] -#[expect( - clippy::expect_used, - reason = "tests require explicit panic messages for debugging failures" -)] mod tests { //! Unit tests for go-to-definition handling. diff --git a/crates/rstest-bdd-server/src/handlers/diagnostics/publish.rs b/crates/rstest-bdd-server/src/handlers/diagnostics/publish.rs index b16a6385e..24f2fd6e5 100644 --- a/crates/rstest-bdd-server/src/handlers/diagnostics/publish.rs +++ b/crates/rstest-bdd-server/src/handlers/diagnostics/publish.rs @@ -252,7 +252,6 @@ mod tests { select_path(&publish_scenario), compute, ); - #[expect(clippy::expect_used, reason = "staged files publish diagnostics")] let params = params.expect("staged file publishes diagnostics"); snapshot_settings(publish_scenario.dir.path()).bind(|| { insta::assert_debug_snapshot!(snapshot, params); @@ -363,7 +362,6 @@ mod tests { TestCaseError::fail("valid path must produce publish params") })?; prop_assert_eq!(¶ms.diagnostics, &expected); - #[expect(clippy::expect_used, reason = "fixed absolute path converts")] let expected_uri = Url::from_file_path(&path).expect("uri"); prop_assert_eq!(params.uri, expected_uri); } diff --git a/crates/rstest-bdd-server/src/handlers/diagnostics/tests/basic.rs b/crates/rstest-bdd-server/src/handlers/diagnostics/tests/basic.rs index 7994b9b62..ce5f236de 100644 --- a/crates/rstest-bdd-server/src/handlers/diagnostics/tests/basic.rs +++ b/crates/rstest-bdd-server/src/handlers/diagnostics/tests/basic.rs @@ -3,9 +3,7 @@ use super::*; #[rstest] -fn unimplemented_step_produces_diagnostic( - scenario_builder: ScenarioBuilder, -) -> std::io::Result<()> { +fn unimplemented_step_produces_diagnostic(scenario_builder: ScenarioBuilder) { let scenario = scenario_builder.with_single_file_pair( "Feature: test\n Scenario: s\n Given an unimplemented step\n", concat!( @@ -18,14 +16,15 @@ fn unimplemented_step_produces_diagnostic( let feature_index = scenario .state .feature_index(&scenario.feature_path) - .ok_or_else(|| std::io::Error::other("feature index missing"))?; + .ok_or_else(|| std::io::Error::other("feature index missing")) + .expect("test setup should succeed"); let diagnostics = compute_unimplemented_step_diagnostics(&scenario.state, feature_index); let [diag] = diagnostics.as_slice() else { - return Err(std::io::Error::other(format!( + panic!( "expected exactly one diagnostic, found {}", diagnostics.len() - ))); + ); }; assert_eq!(diag.severity, Some(DiagnosticSeverity::WARNING)); assert!(diag.message.contains("an unimplemented step")); @@ -35,14 +34,10 @@ fn unimplemented_step_produces_diagnostic( CODE_UNIMPLEMENTED_STEP.to_owned() )) ); - - Ok(()) } #[rstest] -fn unused_step_definition_produces_diagnostic( - scenario_builder: ScenarioBuilder, -) -> std::io::Result<()> { +fn unused_step_definition_produces_diagnostic(scenario_builder: ScenarioBuilder) { let scenario = scenario_builder.with_single_file_pair( "Feature: test\n Scenario: s\n Given a step\n", concat!( @@ -57,10 +52,10 @@ fn unused_step_definition_produces_diagnostic( let diagnostics = compute_unused_step_diagnostics(&scenario.state, &scenario.rust_path); let [diag] = diagnostics.as_slice() else { - return Err(std::io::Error::other(format!( + panic!( "expected exactly one diagnostic, found {}", diagnostics.len() - ))); + ); }; assert!(diag.message.contains("unused step")); assert_eq!( @@ -69,8 +64,6 @@ fn unused_step_definition_produces_diagnostic( CODE_UNUSED_STEP_DEFINITION.to_owned() )) ); - - Ok(()) } #[rstest] diff --git a/crates/rstest-bdd-server/src/handlers/diagnostics/tests/outline.rs b/crates/rstest-bdd-server/src/handlers/diagnostics/tests/outline.rs index 779b7b954..8bd083bff 100644 --- a/crates/rstest-bdd-server/src/handlers/diagnostics/tests/outline.rs +++ b/crates/rstest-bdd-server/src/handlers/diagnostics/tests/outline.rs @@ -127,9 +127,7 @@ fn scenario_outline_column_validation( } #[rstest] -fn regular_scenario_no_column_diagnostics( - scenario_builder: ScenarioBuilder, -) -> std::io::Result<()> { +fn regular_scenario_no_column_diagnostics(scenario_builder: ScenarioBuilder) { // Regular scenarios (not outlines) should not produce column diagnostics let scenario = scenario_builder.with_single_file_pair( concat!( @@ -140,13 +138,12 @@ fn regular_scenario_no_column_diagnostics( "// no step definitions\n", ); let diagnostics = - compute_scenario_outline_diagnostics_for_path(&scenario.state, &scenario.feature_path)?; + compute_scenario_outline_diagnostics_for_path(&scenario.state, &scenario.feature_path) + .expect("test setup should succeed"); assert!( diagnostics.is_empty(), "regular scenarios should produce no column diagnostics" ); - - Ok(()) } #[rstest] diff --git a/crates/rstest-bdd-server/src/handlers/implementation.rs b/crates/rstest-bdd-server/src/handlers/implementation.rs index 4376b4f2b..2a6e6815d 100644 --- a/crates/rstest-bdd-server/src/handlers/implementation.rs +++ b/crates/rstest-bdd-server/src/handlers/implementation.rs @@ -126,10 +126,6 @@ fn build_rust_location(step_def: &Arc) -> Option ResponseErro } #[cfg(test)] -#[expect( - clippy::expect_used, - reason = "tests require explicit panic messages for debugging failures" -)] mod tests { //! Unit tests for server lifecycle handling. diff --git a/crates/rstest-bdd-server/src/indexing/feature/tests.rs b/crates/rstest-bdd-server/src/indexing/feature/tests.rs index cf1b82ae5..8c35f4c21 100644 --- a/crates/rstest-bdd-server/src/indexing/feature/tests.rs +++ b/crates/rstest-bdd-server/src/indexing/feature/tests.rs @@ -3,10 +3,6 @@ use super::*; use tempfile::TempDir; -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn indexes_steps_tables_docstrings_and_example_columns() { let dir = TempDir::new().expect("temp dir"); @@ -76,10 +72,6 @@ fn indexes_steps_tables_docstrings_and_example_columns() { assert_eq!(second_col.name, "Extra"); } -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn indexes_multiple_examples_tables() { let dir = TempDir::new().expect("temp dir"); @@ -121,10 +113,6 @@ fn indexes_multiple_examples_tables() { assert_eq!(col1.name, "extra"); } -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn regular_scenario_not_indexed_as_outline() { let dir = TempDir::new().expect("temp dir"); @@ -146,10 +134,6 @@ fn regular_scenario_not_indexed_as_outline() { ); } -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn docstring_span_includes_backtick_delimiters() { let dir = TempDir::new().expect("temp dir"); diff --git a/crates/rstest-bdd-server/src/indexing/registry.rs b/crates/rstest-bdd-server/src/indexing/registry.rs index 7d3db8117..f33896023 100644 --- a/crates/rstest-bdd-server/src/indexing/registry.rs +++ b/crates/rstest-bdd-server/src/indexing/registry.rs @@ -178,31 +178,33 @@ impl StepDefinitionRegistry { return; }; - for ReverseIndexEntry { keyword, key } in entries { - let Some(steps) = self.steps_by_keyword.get_mut(&keyword) else { - continue; - }; - let Some(positions) = self.keyword_positions.get_mut(&keyword) else { - continue; - }; - let Some(&index) = positions.get(&key) else { - continue; - }; - - let _removed = steps.swap_remove(index); - positions.remove(&key); - - if index < steps.len() { - if let Some(moved) = steps.get(index) { - let moved_key = Arc::as_ptr(moved) as usize; - positions.insert(moved_key, index); - } - } + for entry in entries { + self.remove_keyword_entry(entry); + } + } - if steps.is_empty() { - self.steps_by_keyword.remove(&keyword); - self.keyword_positions.remove(&keyword); - } + fn remove_keyword_entry(&mut self, ReverseIndexEntry { keyword, key }: ReverseIndexEntry) { + let Some(steps) = self.steps_by_keyword.get_mut(&keyword) else { + return; + }; + let Some(positions) = self.keyword_positions.get_mut(&keyword) else { + return; + }; + let Some(&index) = positions.get(&key) else { + return; + }; + + let _removed = steps.swap_remove(index); + positions.remove(&key); + + if let Some(moved) = steps.get(index) { + let moved_key = Arc::as_ptr(moved) as usize; + positions.insert(moved_key, index); + } + + if steps.is_empty() { + self.steps_by_keyword.remove(&keyword); + self.keyword_positions.remove(&keyword); } } @@ -243,10 +245,6 @@ fn compile_step_definition( } #[cfg(test)] -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] mod tests { //! Unit tests for the step registry index. @@ -318,4 +316,35 @@ mod tests { assert_eq!(registry.steps_for_keyword(StepType::When).len(), 0); assert_eq!(registry.steps_for_file(&path).len(), 1); } + + #[test] + fn invalidates_swapped_keyword_entries_by_their_updated_position() { + let first_path = PathBuf::from("/tmp/first_steps.rs"); + let middle_path = PathBuf::from("/tmp/middle_steps.rs"); + let final_path = PathBuf::from("/tmp/final_steps.rs"); + let source = concat!( + "use rstest_bdd_macros::given;\n", + "\n", + "#[given(\"a step\")]\n", + "fn a_step() {}\n", + ); + + let first = index_rust_source(first_path.clone(), source).expect("index first source"); + let middle = index_rust_source(middle_path.clone(), source).expect("index middle source"); + let final_index = + index_rust_source(final_path.clone(), source).expect("index final source"); + + let mut registry = StepDefinitionRegistry::default(); + registry.replace_rust_file(&first); + registry.replace_rust_file(&middle); + registry.replace_rust_file(&final_index); + + registry.invalidate_file(&middle_path); + registry.invalidate_file(&final_path); + + assert_eq!(registry.steps_for_file(&first_path).len(), 1); + assert_eq!(registry.steps_for_keyword(StepType::Given).len(), 1); + assert!(registry.steps_for_file(&middle_path).is_empty()); + assert!(registry.steps_for_file(&final_path).is_empty()); + } } diff --git a/crates/rstest-bdd-server/src/indexing/rust/tests.rs b/crates/rstest-bdd-server/src/indexing/rust/tests.rs index 82efbb212..c504c34b6 100644 --- a/crates/rstest-bdd-server/src/indexing/rust/tests.rs +++ b/crates/rstest-bdd-server/src/indexing/rust/tests.rs @@ -3,10 +3,6 @@ use super::*; use rstest::rstest; -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn indexes_step_definitions_and_infers_patterns() { let source = concat!( @@ -76,10 +72,6 @@ fn indexes_step_definitions_and_infers_patterns() { assert_eq!(qualified.pattern, "qualified"); } -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn indexes_parameter_expectations_for_tables_and_docstrings() { let source = concat!( @@ -149,10 +141,6 @@ fn indexes_parameter_expectations_for_tables_and_docstrings() { assert!(!docstring_wrong_type.expects_docstring); } -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn preserves_module_path_for_nested_definitions() { let source = concat!( @@ -180,10 +168,6 @@ fn preserves_module_path_for_nested_definitions() { assert_eq!(step.function.name, "nested_step"); } -#[expect( - clippy::expect_used, - reason = "tests use explicit failures for clarity" -)] #[test] fn returns_error_when_multiple_step_attributes_present() { let source = concat!( diff --git a/crates/rstest-bdd-server/tests/definition_navigation.rs b/crates/rstest-bdd-server/tests/definition_navigation.rs index 1b1a5c843..dc65e7eeb 100644 --- a/crates/rstest-bdd-server/tests/definition_navigation.rs +++ b/crates/rstest-bdd-server/tests/definition_navigation.rs @@ -130,10 +130,6 @@ fn get_definition_locations( }) } -#[expect( - clippy::expect_used, - reason = "behavioural tests use explicit panics for clarity" -)] #[test] fn definition_navigates_from_rust_step_to_matching_feature_step() { let (_dir, rust_path, state) = DefinitionTestScenario::new() @@ -167,10 +163,6 @@ fn definition_navigates_from_rust_step_to_matching_feature_step() { assert_eq!(loc.range.start.line, 2, "step is on line 2 (0-indexed)"); } -#[expect( - clippy::expect_used, - reason = "behavioural tests use explicit panics for clarity" -)] #[test] fn definition_returns_multiple_locations_for_multiple_matches() { let (_dir, rust_path, state) = DefinitionTestScenario::new() @@ -204,10 +196,6 @@ fn definition_returns_multiple_locations_for_multiple_matches() { assert_eq!(locations.len(), 2, "expected two matching feature steps"); } -#[expect( - clippy::expect_used, - reason = "behavioural tests use explicit panics for clarity" -)] #[test] fn definition_respects_keyword_matching() { let (_dir, rust_path, state) = DefinitionTestScenario::new() @@ -242,10 +230,6 @@ fn definition_respects_keyword_matching() { assert_eq!(loc.range.start.line, 2, "should match Given on line 2"); } -#[expect( - clippy::expect_used, - reason = "behavioural tests use explicit panics for clarity" -)] #[test] fn definition_matches_parameterized_patterns() { let (_dir, rust_path, state) = DefinitionTestScenario::new() @@ -273,10 +257,6 @@ fn definition_matches_parameterized_patterns() { assert_eq!(locations.len(), 2, "expected two matching feature steps"); } -#[expect( - clippy::expect_used, - reason = "behavioural tests use explicit panics for clarity" -)] #[test] fn definition_returns_none_for_non_rust_file() { let dir = TempDir::new().expect("temp dir"); @@ -294,10 +274,6 @@ fn definition_returns_none_for_non_rust_file() { assert!(response.is_none(), "should return None for non-Rust files"); } -#[expect( - clippy::expect_used, - reason = "behavioural tests use explicit panics for clarity" -)] #[test] fn definition_returns_none_when_no_step_at_position() { let dir = TempDir::new().expect("temp dir"); diff --git a/crates/rstest-bdd-server/tests/diagnostics_table_docstring.rs b/crates/rstest-bdd-server/tests/diagnostics_table_docstring.rs index 7f9d2c202..77d739f5b 100644 --- a/crates/rstest-bdd-server/tests/diagnostics_table_docstring.rs +++ b/crates/rstest-bdd-server/tests/diagnostics_table_docstring.rs @@ -166,7 +166,6 @@ fn table_docstring_validation( "expected {expected_count} diagnostic(s)" ); if let Some(substring) = message_substring { - #[expect(clippy::expect_used, reason = "checked count > 0 in conditional")] let diag = diagnostics.first().expect("checked count > 0"); assert!( diag.message.contains(substring), diff --git a/crates/rstest-bdd-server/tests/feature_indexing_on_save.rs b/crates/rstest-bdd-server/tests/feature_indexing_on_save.rs index 2ae5f0514..f023edaae 100644 --- a/crates/rstest-bdd-server/tests/feature_indexing_on_save.rs +++ b/crates/rstest-bdd-server/tests/feature_indexing_on_save.rs @@ -6,7 +6,6 @@ use rstest_bdd_server::handlers::handle_did_save_text_document; use rstest_bdd_server::server::ServerState; use tempfile::TempDir; -#[expect(clippy::expect_used, reason = "behavioural tests use explicit panics")] #[test] fn did_save_indexes_feature_files_and_caches_result() { let dir = TempDir::new().expect("temp dir"); diff --git a/crates/rstest-bdd-server/tests/implementation_navigation.rs b/crates/rstest-bdd-server/tests/implementation_navigation.rs index 2b12a2e74..c9ad303e7 100644 --- a/crates/rstest-bdd-server/tests/implementation_navigation.rs +++ b/crates/rstest-bdd-server/tests/implementation_navigation.rs @@ -94,7 +94,6 @@ fn get_implementation_locations( }) } -#[expect(clippy::expect_used, reason = "test uses expect for clarity")] #[test] fn implementation_navigates_from_feature_step_to_rust_function() { let (_dir, feature_path, state) = ImplementationTestScenario::new() @@ -122,7 +121,6 @@ fn implementation_navigates_from_feature_step_to_rust_function() { assert_eq!(loc.range.end.character, 0); } -#[expect(clippy::expect_used, reason = "test uses expect for clarity")] #[test] fn implementation_returns_multiple_locations_for_duplicate_implementations() { let (_dir, feature_path, state) = ImplementationTestScenario::new() @@ -145,7 +143,6 @@ fn implementation_returns_multiple_locations_for_duplicate_implementations() { assert_eq!(locations.len(), 2, "expected two matching implementations"); } -#[expect(clippy::expect_used, reason = "test uses expect for clarity")] #[test] fn implementation_respects_keyword_matching() { let (_dir, feature_path, state) = ImplementationTestScenario::new() @@ -169,7 +166,6 @@ fn implementation_respects_keyword_matching() { assert!(get_implementation_locations(&state, &feature_path, 4, 4).is_none()); } -#[expect(clippy::expect_used, reason = "test uses expect for clarity")] #[test] fn implementation_matches_parameterized_patterns() { let (_dir, feature_path, state) = ImplementationTestScenario::new() @@ -195,7 +191,6 @@ fn implementation_matches_parameterized_patterns() { ); } -#[expect(clippy::expect_used, reason = "test uses expect for clarity")] #[test] fn implementation_returns_none_for_non_feature_file() { let dir = TempDir::new().expect("temp dir"); @@ -247,7 +242,6 @@ fn implementation_returns_none_when_not_on_step_line() { assert!(get_implementation_locations(&state, &feature_path, 1, 0).is_none()); } -#[expect(clippy::expect_used, reason = "test uses expect for clarity")] #[test] fn implementation_resolves_and_but_keywords_to_preceding_step_type() { // And/But keywords inherit their step type from the preceding Given/When/Then step. @@ -315,7 +309,6 @@ fn implementation_resolves_and_but_keywords_to_preceding_step_type() { ); } -#[expect(clippy::expect_used, reason = "test uses expect for clarity")] #[test] fn implementation_returns_none_for_unindexed_feature_file() { // Create a feature file on disk but do NOT index it in the server state. diff --git a/crates/rstest-bdd-server/tests/rust_step_indexing_on_save.rs b/crates/rstest-bdd-server/tests/rust_step_indexing_on_save.rs index 2a654605d..7dc9e1a16 100644 --- a/crates/rstest-bdd-server/tests/rust_step_indexing_on_save.rs +++ b/crates/rstest-bdd-server/tests/rust_step_indexing_on_save.rs @@ -6,7 +6,6 @@ use rstest_bdd_server::handlers::handle_did_save_text_document; use rstest_bdd_server::server::ServerState; use tempfile::TempDir; -#[expect(clippy::expect_used, reason = "behavioural tests use explicit panics")] #[test] fn did_save_indexes_rust_step_files_and_caches_result() { let dir = TempDir::new().expect("temp dir"); @@ -47,7 +46,6 @@ fn did_save_indexes_rust_step_files_and_caches_result() { assert_eq!(inferred.pattern, "I do the thing"); } -#[expect(clippy::expect_used, reason = "behavioural tests use explicit panics")] #[test] fn did_save_prefers_provided_text_over_filesystem_contents() { let dir = TempDir::new().expect("temp dir"); diff --git a/crates/rstest-bdd-server/tests/smoke_lsp/clearing.rs b/crates/rstest-bdd-server/tests/smoke_lsp/clearing.rs index 9d0936686..4093dd176 100644 --- a/crates/rstest-bdd-server/tests/smoke_lsp/clearing.rs +++ b/crates/rstest-bdd-server/tests/smoke_lsp/clearing.rs @@ -18,7 +18,6 @@ use super::{MAX_RECV_MESSAGES, ServerHandle, server}; /// payload-only `prepare_publish` tests cannot observe. #[rstest] #[expect( - clippy::expect_used, clippy::indexing_slicing, reason = "test assertions use .expect() and indexing for clear failure messages" )] @@ -94,7 +93,6 @@ fn smoke_feature_diagnostics_cleared_once_step_implemented(mut server: ServerHan /// which the payload-only `prepare_publish` tests cannot observe. #[rstest] #[expect( - clippy::expect_used, clippy::indexing_slicing, reason = "test assertions use .expect() and indexing for clear failure messages" )] diff --git a/crates/rstest-bdd-server/tests/smoke_lsp/main.rs b/crates/rstest-bdd-server/tests/smoke_lsp/main.rs index 6e1558955..74acc3ea7 100644 --- a/crates/rstest-bdd-server/tests/smoke_lsp/main.rs +++ b/crates/rstest-bdd-server/tests/smoke_lsp/main.rs @@ -264,7 +264,6 @@ fn smoke_definition_request_returns_locations(mut server: ServerHandle) { #[rstest] #[expect( - clippy::expect_used, clippy::indexing_slicing, reason = "test assertions use .expect() and indexing for clear failure messages" )] diff --git a/crates/rstest-bdd-server/tests/step_registry_on_save.rs b/crates/rstest-bdd-server/tests/step_registry_on_save.rs index c7a2e70bd..08eb2a14f 100644 --- a/crates/rstest-bdd-server/tests/step_registry_on_save.rs +++ b/crates/rstest-bdd-server/tests/step_registry_on_save.rs @@ -7,7 +7,6 @@ use rstest_bdd_server::handlers::handle_did_save_text_document; use rstest_bdd_server::server::ServerState; use tempfile::TempDir; -#[expect(clippy::expect_used, reason = "behavioural tests use explicit panics")] #[test] fn did_save_compiles_step_patterns_and_updates_registry_incrementally() { let dir = TempDir::new().expect("temp dir"); @@ -72,7 +71,6 @@ fn did_save_compiles_step_patterns_and_updates_registry_incrementally() { assert_eq!(state.step_registry().steps_for_file(&path).len(), 1); } -#[expect(clippy::expect_used, reason = "behavioural tests use explicit panics")] #[test] fn did_save_skips_invalid_step_patterns_without_blocking_valid_steps() { let dir = TempDir::new().expect("temp dir"); diff --git a/crates/rstest-bdd/src/context/tests.rs b/crates/rstest-bdd/src/context/tests.rs index 30a7615fa..846ee0ab2 100644 --- a/crates/rstest-bdd/src/context/tests.rs +++ b/crates/rstest-bdd/src/context/tests.rs @@ -31,10 +31,6 @@ fn logger() { } #[test] -#[expect( - clippy::expect_used, - reason = "downcast must succeed for the typed fixture under test" -)] fn borrow_mut_returns_mutable_fixture() { let cell: RefCell> = RefCell::new(Box::new(String::from("seed"))); let mut ctx = StepContext::default(); @@ -269,10 +265,6 @@ fn insert_harness_context_exposes_shared_reference() { } #[test] -#[expect( - clippy::expect_used, - reason = "downcast must succeed for the typed fixture under test" -)] fn insert_owned_harness_context_supports_mutation() { let harness_cell: RefCell> = RefCell::new(Box::new(String::from("harness"))); let mut ctx = StepContext::default(); diff --git a/crates/rstest-bdd/src/context/tests/guard_borrowing.rs b/crates/rstest-bdd/src/context/tests/guard_borrowing.rs index 147b32dd0..a4d3f76a3 100644 --- a/crates/rstest-bdd/src/context/tests/guard_borrowing.rs +++ b/crates/rstest-bdd/src/context/tests/guard_borrowing.rs @@ -14,7 +14,6 @@ fn owned_pair_cells() -> (OwnedCell, OwnedCell) { } #[test] -#[expect(clippy::expect_used, reason = "tests require explicit panic messages")] fn distinct_fixtures_can_be_borrowed_mutably_at_once() { let (first, second) = owned_pair_cells(); let mut ctx = StepContext::default(); @@ -34,7 +33,6 @@ fn distinct_fixtures_can_be_borrowed_mutably_at_once() { } #[test] -#[expect(clippy::expect_used, reason = "tests require explicit panic messages")] fn conflicting_borrows_of_same_fixture_report_already_borrowed() { let (first, _second) = owned_pair_cells(); let mut ctx = StepContext::default(); @@ -72,7 +70,6 @@ fn conflicting_borrows_of_same_fixture_report_already_borrowed() { } #[test] -#[expect(clippy::expect_used, reason = "tests require explicit panic messages")] fn try_borrow_reports_not_found_type_mismatch_and_not_mutable() { let shared = 9_i32; let (first, _second) = owned_pair_cells(); @@ -112,7 +109,6 @@ fn try_borrow_reports_not_found_type_mismatch_and_not_mutable() { } #[test] -#[expect(clippy::expect_used, reason = "tests require explicit panic messages")] fn override_values_participate_in_guard_borrowing() { let fixture = 1_u32; let mut ctx = StepContext::default(); @@ -132,7 +128,6 @@ fn override_values_participate_in_guard_borrowing() { } #[test] -#[expect(clippy::expect_used, reason = "tests require explicit panic messages")] fn multiple_shared_borrows_of_same_fixture_coexist() { let (first, _second) = owned_pair_cells(); let mut ctx = StepContext::default(); diff --git a/crates/rstest-bdd/src/pattern.rs b/crates/rstest-bdd/src/pattern.rs index 3fe7be419..c62b03159 100644 --- a/crates/rstest-bdd/src/pattern.rs +++ b/crates/rstest-bdd/src/pattern.rs @@ -143,7 +143,6 @@ mod tests { use std::ptr; #[test] - #[expect(clippy::expect_used, reason = "test helper validates success path")] fn compiled_regex_returns_cached_regex_after_compilation() { let pattern = StepPattern::from("literal text"); pattern.compile().expect("literal pattern should compile"); @@ -157,7 +156,6 @@ mod tests { } #[test] - #[expect(clippy::expect_used, reason = "test validates compilation")] fn compile_is_idempotent() { let pattern = StepPattern::from("literal text"); diff --git a/crates/rstest-bdd/src/registry/introspection.rs b/crates/rstest-bdd/src/registry/introspection.rs index 36d207b0f..984db3a4d 100644 --- a/crates/rstest-bdd/src/registry/introspection.rs +++ b/crates/rstest-bdd/src/registry/introspection.rs @@ -121,10 +121,6 @@ mod tests { ); #[test] - #[expect( - clippy::expect_used, - reason = "test requires the registered introspection fixture" - )] fn unused_steps_exclude_a_known_used_step() { let used_step = all_steps() .into_iter() @@ -160,19 +156,16 @@ mod tests { #[cfg(feature = "diagnostics")] #[test] - #[expect( - clippy::expect_used, - reason = "test validates required registry dump structure" - )] - fn dump_registry_serializes_step_state() -> serde_json::Result<()> { + fn dump_registry_serializes_step_state() { let used_step = all_steps() .into_iter() .find(|step| step.pattern.as_str() == USED_PATTERN) .expect("registered introspection used step should be present"); super::super::mark_used((used_step.keyword, used_step.pattern)); - let json = super::dump_registry()?; - let dump: serde_json::Value = serde_json::from_str(&json)?; + let json = super::dump_registry().expect("test setup should succeed"); + let dump: serde_json::Value = + serde_json::from_str(&json).expect("test setup should succeed"); let steps = dump .get("steps") .expect("registry dump should contain a steps field") @@ -184,6 +177,5 @@ mod tests { .expect("registry dump should contain the registered used step"); assert_eq!(dumped_used_step["used"].as_bool(), Some(true)); assert_eq!(dumped_used_step["bypassed"].as_bool(), Some(false)); - Ok(()) } } diff --git a/crates/rstest-bdd/src/skip.rs b/crates/rstest-bdd/src/skip.rs index 539d5ed1e..e48a7f851 100644 --- a/crates/rstest-bdd/src/skip.rs +++ b/crates/rstest-bdd/src/skip.rs @@ -302,10 +302,6 @@ mod tests { } #[test] - #[expect( - clippy::expect_used, - reason = "test asserts join success and panic on thread mismatch" - )] fn request_skip_complains_when_thread_changes() { let mut guard = enter_scope(ScopeKind::Step, "thread_check", file!(), line!()); let other_id = std::thread::spawn(|| thread::current().id()) diff --git a/crates/rstest-bdd/src/types/tests.rs b/crates/rstest-bdd/src/types/tests.rs index f4eb4ab24..6ee4b221c 100644 --- a/crates/rstest-bdd/src/types/tests.rs +++ b/crates/rstest-bdd/src/types/tests.rs @@ -77,10 +77,6 @@ fn step_execution_from_value_with_payload() { } StepExecution::Skipped { .. } => panic!("skip variant is unexpected"), }; - #[expect( - clippy::expect_used, - reason = "test ensures payload can be downcast to original type" - )] let number = payload.downcast::().expect("payload must be a u8"); assert_eq!(*number, 99); } @@ -130,7 +126,6 @@ fn placeholder_error_from_placeholder_syntax_returns_invalid_placeholder() { #[test] #[expect( clippy::invalid_regex, - clippy::expect_used, reason = "deliberate invalid regex to test error conversion" )] fn placeholder_error_from_invalid_pattern_returns_invalid_pattern() { @@ -216,7 +211,6 @@ fn sync_to_async_builds_async_wrapper_without_explicit_fixture_lifetime() { } #[test] -#[expect(clippy::expect_used, reason = "test validates downcast succeeds")] fn step_future_resolves_to_expected_value() { fn make_future<'a>() -> StepFuture<'a> { Box::pin(std::future::ready(Ok(StepExecution::from_value(Some( diff --git a/crates/rstest-bdd/tests/async_registry.rs b/crates/rstest-bdd/tests/async_registry.rs index 271fa8158..cf9d56fc4 100644 --- a/crates/rstest-bdd/tests/async_registry.rs +++ b/crates/rstest-bdd/tests/async_registry.rs @@ -92,10 +92,6 @@ fn async_step_fn_can_be_stored_and_invoked() { } #[test] -#[expect( - clippy::expect_used, - reason = "test asserts Option is Some before expect" -)] fn step_struct_has_run_async_field() { let found = iter:: .into_iter() diff --git a/crates/rstest-bdd/tests/async_step_functions.rs b/crates/rstest-bdd/tests/async_step_functions.rs index bb420aee5..31d18ca6d 100644 --- a/crates/rstest-bdd/tests/async_step_functions.rs +++ b/crates/rstest-bdd/tests/async_step_functions.rs @@ -63,10 +63,6 @@ fn sync_scenario_can_block_on_async_steps(state: CounterState) { } #[tokio::test(flavor = "current_thread")] -#[expect( - clippy::expect_used, - reason = "test asserts that the async step is registered before invoking its sync wrapper" -)] async fn sync_wrapper_refuses_to_create_nested_runtime() { let step = find_step_with_metadata( StepKeyword::When, @@ -112,10 +108,6 @@ fn manual_async_wrapper<'ctx>( } #[tokio::test(flavor = "current_thread")] -#[expect( - clippy::expect_used, - reason = "test validates payload downcast from wrapper result" -)] async fn public_sync_to_async_helper_supports_alias_based_wrapper_signatures() { let _: rstest_bdd::AsyncStepFn = manual_async_wrapper; diff --git a/crates/rstest-bdd/tests/datatable.rs b/crates/rstest-bdd/tests/datatable.rs index e1c4d6892..b4d88c115 100644 --- a/crates/rstest-bdd/tests/datatable.rs +++ b/crates/rstest-bdd/tests/datatable.rs @@ -194,7 +194,6 @@ fn derive_data_table_row_parses_and_maps_columns() { String::from(" 42 "), ], ]; - #[expect(clippy::expect_used, reason = "test asserts successful parse")] let rows = Rows::::try_from(table).expect("rows should parse"); assert_eq!( rows.into_vec(), @@ -275,7 +274,6 @@ fn datatable_tuple_struct_support() { String::from("false"), ], ]; - #[expect(clippy::expect_used, reason = "test asserts successful parse")] let rows = Rows::::try_from(table).expect("tuple rows should parse"); assert_eq!( rows.into_vec(), @@ -311,15 +309,12 @@ fn derive_data_table_supports_collection_wrappers_and_hooks() { String::from("43"), ], ]; - #[expect(clippy::expect_used, reason = "test asserts successful parse")] let collection = DerivedRowCollection::try_from(table.clone()).expect("collection should parse"); assert_eq!(collection.0.len(), 2); - #[expect(clippy::expect_used, reason = "test asserts successful parse")] let DerivedRowVecCollection(vec_rows) = DerivedRowVecCollection::try_from(table.clone()).expect("vec should parse"); assert_eq!(vec_rows.len(), 2); - #[expect(clippy::expect_used, reason = "test asserts successful parse")] let ActiveNames(active) = ActiveNames::try_from(table).expect("hook should parse"); assert_eq!(active, vec![String::from("Alice")]); } diff --git a/crates/rstest-bdd/tests/datatable_cache.rs b/crates/rstest-bdd/tests/datatable_cache.rs index 81220148e..179366116 100644 --- a/crates/rstest-bdd/tests/datatable_cache.rs +++ b/crates/rstest-bdd/tests/datatable_cache.rs @@ -97,10 +97,6 @@ fn counting_table(#[datatable] mut datatable: CountingTable) { } #[test] -#[expect( - clippy::expect_used, - reason = "Using expect in tests provides clearer diagnostics for step lookup failures." -)] fn cached_table_reuses_conversion_for_identical_table_pointer() { const TABLE: &[&[&str]] = &[&["foo", "bar"], &["baz", "qux"]]; @@ -123,10 +119,6 @@ fn cached_table_reuses_conversion_for_identical_table_pointer() { } #[test] -#[expect( - clippy::expect_used, - reason = "Using expect in tests provides clearer diagnostics for step lookup failures." -)] fn cached_table_cache_separates_distinct_tables() { const TABLE_ONE: &[&[&str]] = &[&["alpha"], &["beta"]]; const TABLE_TWO: &[&[&str]] = &[&["gamma"], &["delta"]]; @@ -153,10 +145,6 @@ fn cached_table_cache_separates_distinct_tables() { } #[test] -#[expect( - clippy::expect_used, - reason = "Using expect in tests provides clearer diagnostics for step lookup failures." -)] fn cached_table_cache_is_scoped_per_step_wrapper() { const TABLE: &[&[&str]] = &[&["foo", "bar"], &["baz", "qux"]]; @@ -189,10 +177,6 @@ fn cached_table_cache_is_scoped_per_step_wrapper() { } #[test] -#[expect( - clippy::expect_used, - reason = "Using expect in tests provides clearer diagnostics for step lookup failures." -)] fn datatable_vec_path_clones_per_call_and_preserves_isolation() { const TABLE: &[&[&str]] = &[&["foo", "bar"], &["baz", "qux"]]; diff --git a/crates/rstest-bdd/tests/fallible_scenario.rs b/crates/rstest-bdd/tests/fallible_scenario.rs index 06c4c11db..980c180d2 100644 --- a/crates/rstest-bdd/tests/fallible_scenario.rs +++ b/crates/rstest-bdd/tests/fallible_scenario.rs @@ -101,7 +101,6 @@ fn fallible_success_records_pass() { #[tokio::test] async fn fallible_async_success_records_pass() { - #[expect(clippy::panic, reason = "test helper panics for clearer failures")] async fn assert_fallible_async_success_records_pass() { let join = tokio::task::spawn_blocking(|| { serial_test::local_serial_core_with_return("", || { diff --git a/crates/rstest-bdd/tests/fixture_context.rs b/crates/rstest-bdd/tests/fixture_context.rs index e8b4375f0..e5c7bb5dd 100644 --- a/crates/rstest-bdd/tests/fixture_context.rs +++ b/crates/rstest-bdd/tests/fixture_context.rs @@ -21,7 +21,6 @@ fn panicking_value_step(number: &u32) -> Result<(), String> { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn context_passes_fixture() { let number = 42u32; let mut ctx = StepContext::default(); @@ -32,7 +31,6 @@ fn context_passes_fixture() { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn context_missing_fixture_returns_error() { let mut ctx = StepContext::default(); let step_fn = lookup_step(StepKeyword::Given, "a value".into()) @@ -54,7 +52,6 @@ fn context_missing_fixture_returns_error() { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn context_missing_fixture_localizes_error() { let guard = match ScopedLocalization::new(&[langid!("fr")]) { Ok(guard) => guard, @@ -70,7 +67,6 @@ fn context_missing_fixture_localizes_error() { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn fixture_step_panic_returns_panic_error() { let number = 1u32; let mut ctx = StepContext::default(); diff --git a/crates/rstest-bdd/tests/inferred_step_patterns.rs b/crates/rstest-bdd/tests/inferred_step_patterns.rs index 34afacb81..60ac945b7 100644 --- a/crates/rstest-bdd/tests/inferred_step_patterns.rs +++ b/crates/rstest-bdd/tests/inferred_step_patterns.rs @@ -45,7 +45,6 @@ fn r#match_logs_in() {} #[case(StepKeyword::When, "match logs in")] fn steps_with_inferred_patterns_execute(#[case] kw: StepKeyword, #[case] pattern: &str) { let mut ctx = StepContext::default(); - #[expect(clippy::expect_used, reason = "test ensures step exists")] let step_fn = find_step(kw, pattern.into()).expect("step not found"); if let Err(e) = step_fn(&mut ctx, pattern, None, None) { panic!("step failed: {e:?}"); diff --git a/crates/rstest-bdd/tests/localization.rs b/crates/rstest-bdd/tests/localization.rs index 928095617..476be7aa7 100644 --- a/crates/rstest-bdd/tests/localization.rs +++ b/crates/rstest-bdd/tests/localization.rs @@ -1,10 +1,5 @@ //! Behavioural coverage for localization helpers and diagnostics. -#![expect( - clippy::expect_used, - reason = "localization tests use expect for concise setup failures" -)] - use i18n_embed::fluent::fluent_language_loader; use rstest_bdd::localization::{ ScopedLocalization, current_languages, install_localization_loader, message, message_with_args, diff --git a/crates/rstest-bdd/tests/mutable_fixture.rs b/crates/rstest-bdd/tests/mutable_fixture.rs index b950cd147..254a02332 100644 --- a/crates/rstest-bdd/tests/mutable_fixture.rs +++ b/crates/rstest-bdd/tests/mutable_fixture.rs @@ -21,10 +21,6 @@ struct CounterWorld { } #[test] -#[expect( - clippy::expect_used, - reason = "downcast must succeed when reconstructing the owned fixture" -)] fn mutable_owned_fixture_round_trip() { let world = StepContext::owned_cell(CounterWorld::default()); let mut ctx = StepContext::default(); diff --git a/crates/rstest-bdd/tests/outline_placeholder.rs b/crates/rstest-bdd/tests/outline_placeholder.rs index 861066cf6..9ac5a277d 100644 --- a/crates/rstest-bdd/tests/outline_placeholder.rs +++ b/crates/rstest-bdd/tests/outline_placeholder.rs @@ -3,11 +3,6 @@ //! These tests verify that `` tokens in step text are substituted //! with values from the Examples table before step matching occurs. -#![expect( - clippy::expect_used, - reason = "integration tests use expect for clarity" -)] - use rstest_bdd_macros::{given, scenario, then, when}; use serial_test::serial; use std::sync::{LazyLock, Mutex, MutexGuard}; diff --git a/crates/rstest-bdd/tests/pattern_mismatch.rs b/crates/rstest-bdd/tests/pattern_mismatch.rs index b3b160b2d..f70bd35c3 100644 --- a/crates/rstest-bdd/tests/pattern_mismatch.rs +++ b/crates/rstest-bdd/tests/pattern_mismatch.rs @@ -15,18 +15,15 @@ fn number(value: u32) { #[test] fn passes_captured_value() { CAPTURED.store(0, Ordering::Relaxed); - #[expect(clippy::expect_used, reason = "step registered above")] let step_fn = lookup_step(StepKeyword::Given, "number {value:u32}".into()).expect("step missing"); let mut ctx = StepContext::default(); - #[expect(clippy::expect_used, reason = "matching text should succeed")] let _ = step_fn(&mut ctx, "number 41", None, None).expect("step should match"); assert_eq!(CAPTURED.load(Ordering::Relaxed), 41); } #[test] fn returns_error_on_pattern_mismatch() { - #[expect(clippy::expect_used, reason = "step registered above")] let step_fn = lookup_step(StepKeyword::Given, "number {value:u32}".into()).expect("step missing"); let mut ctx = StepContext::default(); diff --git a/crates/rstest-bdd/tests/placeholder_braces.rs b/crates/rstest-bdd/tests/placeholder_braces.rs index 71021ff85..9acdf2eed 100644 --- a/crates/rstest-bdd/tests/placeholder_braces.rs +++ b/crates/rstest-bdd/tests/placeholder_braces.rs @@ -31,7 +31,6 @@ fn unknown_escape_is_literal( #[case] nonmatching: &'static str, ) { let pat = compiled(pattern); - #[expect(clippy::expect_used, reason = "test asserts literal match")] let caps = extract_placeholders(&pat, StepText::from(matching)) .expect("literal character should match"); assert!(caps.is_empty(), "no placeholders expected"); @@ -45,7 +44,6 @@ fn unknown_escape_is_literal( fn trailing_backslash_is_literal() { // Use a normal string here; raw strings cannot end with a backslash. let pat = compiled("foo\\"); - #[expect(clippy::expect_used, reason = "test asserts literal match")] let caps = extract_placeholders(&pat, StepText::from("foo\\")) .expect("literal backslash should match"); assert!(caps.is_empty(), "no placeholders expected"); @@ -59,7 +57,6 @@ fn trailing_backslash_is_literal() { fn unknown_escape_inside_stray_depth_is_literal() { // The opening "{" puts the scanner into stray-depth mode; "\d" must stay literal. let pat = compiled(r"start{ \d }end"); - #[expect(clippy::expect_used, reason = "test asserts literal match")] let caps = extract_placeholders(&pat, StepText::from(r"start{ d }end")) .expect("literal d should match inside stray depth"); assert!(caps.is_empty(), "no placeholders expected"); diff --git a/crates/rstest-bdd/tests/placeholder_parsing.rs b/crates/rstest-bdd/tests/placeholder_parsing.rs index 82aa1b3c3..9801bfdec 100644 --- a/crates/rstest-bdd/tests/placeholder_parsing.rs +++ b/crates/rstest-bdd/tests/placeholder_parsing.rs @@ -170,7 +170,6 @@ fn invalid_type_hint_is_generic( ) { // Unknown type hints fall back to a non-greedy match. let pat = compiled(pattern); - #[expect(clippy::expect_used, reason = "test asserts placeholder match")] let caps = extract_placeholders(&pat, StepText::from(input)) .expect("invalid type hint should still capture"); assert_eq!(caps, vec![expected]); @@ -208,7 +207,6 @@ fn whitespace_before_closing_brace_is_error() { #[test] fn extraction_reports_invalid_placeholder_error() { let pat = StepPattern::from("value {n:}"); - #[expect(clippy::expect_used, reason = "test asserts error variant")] let err = extract_placeholders(&pat, StepText::from("value 1")) .expect_err("placeholder error expected"); assert!(matches!(err, PlaceholderError::InvalidPlaceholder(_))); @@ -222,7 +220,6 @@ fn extraction_reports_invalid_placeholder_error() { fn invalid_pattern_error_display() { #[expect( clippy::invalid_regex, - clippy::expect_used, reason = "deliberate invalid regex to test error display" )] let regex_err = regex::Regex::new("(").expect_err("invalid regex should error"); @@ -239,7 +236,6 @@ fn placeholder_error_display_in_french() { Err(error) => panic!("failed to scope French locale: {error}"), }; let pat = StepPattern::from("value {n:}"); - #[expect(clippy::expect_used, reason = "test asserts error variant")] let err = extract_placeholders(&pat, StepText::from("value 1")) .expect_err("placeholder error expected"); let display = strip_directional_isolates(&err.to_string()); diff --git a/crates/rstest-bdd/tests/step_definition_matching.rs b/crates/rstest-bdd/tests/step_definition_matching.rs index e1325dc3a..55e95da19 100644 --- a/crates/rstest-bdd/tests/step_definition_matching.rs +++ b/crates/rstest-bdd/tests/step_definition_matching.rs @@ -29,7 +29,6 @@ fn find_step_returns_none_for_missing() { #[test] fn find_step_executes_single_match() { - #[expect(clippy::expect_used, reason = "test ensures step exists")] let step_fn = find_step(StepKeyword::Given, "a unique step".into()).expect("step not found"); let mut ctx = StepContext::default(); match step_fn(&mut ctx, "a unique step", None, None) { @@ -43,7 +42,6 @@ fn find_step_executes_single_match() { fn find_step_runs_one_of_multiple_matches() { GENERIC_CALLED.store(0, Ordering::Relaxed); SPECIFIC_CALLED.store(0, Ordering::Relaxed); - #[expect(clippy::expect_used, reason = "test ensures step exists")] let step_fn = find_step(StepKeyword::Given, "overlap apples".into()).expect("step not found"); let mut ctx = StepContext::default(); match step_fn(&mut ctx, "overlap apples", None, None) { diff --git a/crates/rstest-bdd/tests/step_error_behaviour.rs b/crates/rstest-bdd/tests/step_error_behaviour.rs index 3bad18652..cd606630b 100644 --- a/crates/rstest-bdd/tests/step_error_behaviour.rs +++ b/crates/rstest-bdd/tests/step_error_behaviour.rs @@ -9,10 +9,6 @@ use step_error_common::{FancyValue, StepInvocation, invoke_step}; #[test] fn successful_step_execution() { - #[expect( - clippy::expect_used, - reason = "test ensures successful step execution propagates" - )] match invoke_step(&StepInvocation::new( StepKeyword::Given, "a successful step", @@ -26,10 +22,6 @@ fn successful_step_execution() { } #[test] -#[expect( - clippy::expect_used, - reason = "test ensures step success is propagated" -)] fn fallible_unit_step_execution_returns_none() { let outcome = invoke_step(&StepInvocation::new( StepKeyword::Given, @@ -47,10 +39,6 @@ fn fallible_unit_step_execution_returns_none() { #[test] fn fallible_value_step_execution_returns_value() { - #[expect( - clippy::expect_used, - reason = "test asserts success path and payload presence" - )] let payload = invoke_step(&StepInvocation::new( StepKeyword::Given, "a fallible value step succeeds", @@ -64,10 +52,6 @@ fn fallible_value_step_execution_returns_value() { } StepExecution::Skipped { .. } => panic!("step unexpectedly skipped"), }; - #[expect( - clippy::expect_used, - reason = "test asserts success path and payload presence" - )] let value = boxed .downcast::() .expect("expected FancyValue payload"); @@ -76,10 +60,6 @@ fn fallible_value_step_execution_returns_value() { #[test] fn skip_request_step_returns_skipped_outcome() { - #[expect( - clippy::expect_used, - reason = "test asserts skip handling returns a skipped outcome" - )] let outcome = invoke_step(&StepInvocation::new( StepKeyword::Given, "a skip request step", @@ -91,7 +71,6 @@ fn skip_request_step_returns_skipped_outcome() { panic!("skip request should not report continuation"); } StepExecution::Skipped { message } => { - #[expect(clippy::expect_used, reason = "test asserts skip message propagation")] let detail = message.expect("skip should include message"); assert!( detail.contains("behavioural skip test"), @@ -124,10 +103,6 @@ fn datatable_or_docstring_executes(#[case] payload: Payload<'_>) { ) .with_docstring(text), }; - #[expect( - clippy::expect_used, - reason = "test ensures both table and docstring steps execute successfully" - )] match invoke_step(&invocation).expect("unexpected error passing payload") { StepExecution::Continue { .. } => {} StepExecution::Skipped { .. } => panic!("step unexpectedly skipped"), diff --git a/crates/rstest-bdd/tests/step_registry/main.rs b/crates/rstest-bdd/tests/step_registry/main.rs index cdf93202c..1098e5d39 100644 --- a/crates/rstest-bdd/tests/step_registry/main.rs +++ b/crates/rstest-bdd/tests/step_registry/main.rs @@ -125,7 +125,6 @@ fn wrapper_errors_localize( } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn find_step_with_metadata_returns_step_with_fixtures() { let step = find_step_with_metadata(StepKeyword::Then, StepText::from("needs fixture")) .expect("step 'needs fixture' not found in registry"); @@ -146,7 +145,6 @@ fn find_step_with_metadata_returns_none_for_unknown_step() { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn find_step_with_metadata_returns_empty_fixtures_for_no_fixture_step() { let step = find_step_with_metadata(StepKeyword::When, StepText::from("behavioural")) .expect("step 'behavioural' not found in registry"); @@ -171,7 +169,6 @@ fn available_fixtures_lists_scenario_fixtures() { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn fixture_validation_detects_missing_fixtures() { // This test validates the fixture validation logic that is used in // execute_single_step() by replicating the same check here. @@ -196,7 +193,6 @@ fn fixture_validation_detects_missing_fixtures() { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn fixture_validation_passes_when_all_fixtures_present() { let step = find_step_with_metadata(StepKeyword::Then, StepText::from("needs fixture")) .expect("step 'needs fixture' not found in registry"); @@ -218,7 +214,6 @@ fn fixture_validation_passes_when_all_fixtures_present() { } #[test] -#[expect(clippy::expect_used, reason = "step lookup must succeed for test")] fn find_step_with_metadata_marks_step_as_used() { // The step "needs fixture" should be marked as used after find_step_with_metadata let step = find_step_with_metadata(StepKeyword::Then, StepText::from("needs fixture")) @@ -247,7 +242,6 @@ fn step_with_auto_async_is_registered() { } #[test] -#[expect(clippy::expect_used, reason = "test validates step lookup succeeds")] fn step_with_auto_async_sync_handler_works() { let step = iter:: .into_iter() @@ -260,7 +254,6 @@ fn step_with_auto_async_sync_handler_works() { } #[test] -#[expect(clippy::expect_used, reason = "test validates step lookup succeeds")] fn step_with_auto_async_handler_works() { let step = iter:: .into_iter() diff --git a/crates/rstest-bdd/tests/wrapper_shadow.rs b/crates/rstest-bdd/tests/wrapper_shadow.rs index eb796e1fe..f39a0cde9 100644 --- a/crates/rstest-bdd/tests/wrapper_shadow.rs +++ b/crates/rstest-bdd/tests/wrapper_shadow.rs @@ -19,10 +19,6 @@ fn capture_text(ctx: &str, text: String) { } #[test] -#[expect( - clippy::expect_used, - reason = "test asserts deterministic macro expansion and registry lookups" -)] fn wrapper_handles_text_capture_without_shadowing() { let mut ctx = StepContext::default(); let fixture = "fixture ctx"; @@ -43,10 +39,6 @@ fn wrapper_handles_text_capture_without_shadowing() { } #[test] -#[expect( - clippy::expect_used, - reason = "test inspects placeholder mismatch error formatting" -)] fn placeholder_mismatch_reports_original_step_text() { let mut ctx = StepContext::default(); let step_fn = lookup_step(StepKeyword::Given, "{text} arrives".into()) diff --git a/docs/adr-013-adopt-whitaker-no-unwrap-or-else-panic.md b/docs/adr-013-adopt-whitaker-no-unwrap-or-else-panic.md index a0c50db37..a58994610 100644 --- a/docs/adr-013-adopt-whitaker-no-unwrap-or-else-panic.md +++ b/docs/adr-013-adopt-whitaker-no-unwrap-or-else-panic.md @@ -25,11 +25,12 @@ pedantic lint profile that includes `clippy::shadow_reuse`, The in-house lint is implemented by Whitaker, a Dylint lint library, in the `crates/no_unwrap_or_else_panic` crate. It rejects `unwrap_or_else(|| panic!(…))` and nested `unwrap_or_else(|| value.unwrap())` -forms on `Option` and `Result`. The repository already denies -`clippy::expect_used` and `clippy::unwrap_used`, including in tests, so -contributors had used `unwrap_or_else(|| panic!(…))` as the remaining escape -hatch for invariant failures. Closing that escape hatch requires a replacement -shape that still preserves clear panic messages in test-only invariant paths. +forms on `Option` and `Result`. The repository denies `clippy::expect_used` and +`clippy::unwrap_used` outside recognized tests. `clippy.toml` permits +`.expect(...)` and `panic!(...)` in test cases, including `rstest` cases, where +unexpected setup failures should fail the test. `unwrap_or_else(|| panic!(…))` +remains rejected, so non-test invariant failures need a replacement shape that +preserves clear panic messages. The compatible shape is Rust's `let … else` syntax: @@ -205,5 +206,7 @@ Contributor-facing setup and maintenance steps are documented in The adopted lint does not replace Clippy. `clippy::shadow_reuse`, `clippy::expect_used`, and `clippy::unwrap_used` remain separate policy -surfaces. The playbook form is chosen because it satisfies all of them -together, not because Whitaker enforces shadowing or `.expect(...)` directly. +surfaces. `.expect(...)` and `panic!(...)` are allowed in recognized tests, +while `.unwrap()` remains denied. The playbook form is chosen because it +satisfies the non-test policy surfaces together, not because Whitaker enforces +shadowing or `.expect(...)` directly. diff --git a/docs/debugging/debugging-plan-20260815-nextest-global-timeout.md b/docs/debugging/debugging-plan-20260815-nextest-global-timeout.md new file mode 100644 index 000000000..15caccd2b --- /dev/null +++ b/docs/debugging/debugging-plan-20260815-nextest-global-timeout.md @@ -0,0 +1,125 @@ +# Debugging plan: Nextest global timeout + +**Generated**: 2026-08-15 **Issue ID**: Commit gate failure **Severity**: High +**Falsification sub-agent**: alchemist **Planning agent boundary**: This +document was prepared by the planning agent. Falsification must be executed by +the named sub-agent, not by the planning agent. + +## Problem Statement + +`make test` must complete successfully, but the default Nextest profile +terminated `gpui_macro_fixtures_compile` when the whole run reached its +five-minute global timeout. The test had been running for 37 seconds and was +still compiling its trybuild fixture; 1,680 preceding tests had passed. + +## Context Summary + +| Aspect | Details | +| ------------------- | ---------------------------------------------------- | +| First observed | 2026-08-15 during the required commit gate | +| Reproduction rate | One full cold-cache-adjacent run | +| Affected components | `.config/nextest.toml` and cargo-spawning tests | +| Recent changes | Registry refactor and lint-policy documentation only | + +_Table 1: Debugging context summary._ + +### Error Artefacts + +```plaintext +Cancelling due to global timeout: 1 test still running +SIGTERM [37.121s] rstest-bdd-harness-gpui::macro_compile::gpui_macro_fixtures_compile +Summary [300.011s] 1681/1685 tests run: 1680 passed, 1 failed, 7 skipped +``` + +### Information Gaps + +- The duration of the full suite on a completely cold Cargo cache is unknown. +- The complete sequence of cargo-spawning test binaries before cancellation is + not visible in the summary. + +______________________________________________________________________ + +## Hypotheses + +### H1: The default global timeout is shorter than the serial test schedule + +**Claim**: The five-minute global timeout conflicts with cargo-spawning tests +that are deliberately serialized and can each run for up to 300 seconds. + +**Plausibility**: High — the interrupted test was within its 300-second +per-test allowance, and serialization means its budget is additive rather than +parallel. + +**Prediction**: The affected GPUI trybuild binary completes when it is run with +the other compile binary but without the full-suite scheduling load. + +#### H1 Falsification Plan + +| Step | Action | Expected Negative Result | +| ---- | ------------------------------------------------------------ | ----------------------------------------------- | +| 1 | Run the two compile-test binaries under the default profile. | Either binary times out or fails independently. | + +_Table 2: H1 falsification step and expected negative result._ + +**Tooling**: `cargo nextest run` with an expression that selects only +`rstest-bdd::trybuild_macros` and `rstest-bdd-harness-gpui::macro_compile`. + +**Confidence on falsification**: High for an intrinsic failure; a passing run +supports a scheduling-budget repair but does not measure the whole suite. + +______________________________________________________________________ + +### H2: The GPUI trybuild fixture itself is broken + +**Claim**: `gpui_macro_fixtures_compile` is failing or hanging independently of +Nextest's full-suite time budget. + +**Plausibility**: Low — the captured output shows an active compilation and no +test assertion or compiler error before Nextest sent `SIGTERM`. + +**Prediction**: Selecting only the GPUI compile-test binary still fails or +exceeds its per-test 300-second allowance. + +#### H2 Falsification Plan + +| Step | Action | Expected Negative Result | +| ---- | --------------------------------------- | ----------------------------------------------------- | +| 1 | Run the selected compile-test binaries. | The GPUI binary passes within its per-test allowance. | + +_Table 3: H2 falsification step and expected negative result._ + +**Tooling**: The same targeted `cargo nextest run` command as H1. + +**Confidence on falsification**: Decisive for a fixture-level regression. + +**Result**: Falsified. The selected test binaries passed in 49.786 seconds; the +GPUI fixture passed in 21.972 seconds. Evidence: +`/tmp/alchemist-h2-gpui-timeout-20260815.log`. + +**Representative-cache validation (2026-08-16):** `make test` completed in +136.79 s, and +`cargo nextest run --profile long --workspace --all-targets --all-features` +completed in 23.54 s. Neither run emitted timeout warnings; the configured +`20m` default and `30m` long-profile global budgets retain cold-cache headroom. +This validation completes the timeout-budget remediation. + +______________________________________________________________________ + +## Recommended Execution Order + +1. **H2** — the selected test is the smallest decisive experiment. +2. **H1** — apply a configuration repair only if H2 is falsified. + +## Termination Criteria + +- **Root cause identified**: The GPUI binary passes in isolation and the + configured global timeout is shown to be incompatible with tests that run one + at a time. +- **Escalation trigger**: The selected GPUI binary fails independently or + exceeds 300 seconds. + +## Notes for Executing Agent + +Do not edit files or run full repository gates. Run only the supplied targeted +experiment, record elapsed time and the result, then return a verdict of +falsified, not-falsified, or inconclusive for H2. diff --git a/docs/developers-guide.md b/docs/developers-guide.md index bc87d4b36..a3fe93eb0 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -147,8 +147,9 @@ workspace root; this is the only nextest configuration file the runner loads. The file sets the timeout policy for the test suite: - The default profile kills any test that runs past a 60 s `slow-timeout` - (`terminate-after = 1`, 5 s grace period) and applies a 5 m `global-timeout` - to the whole run. + (`terminate-after = 1`, 5 s grace period) and applies a 20 m `global-timeout` + to the whole run. This allows the cargo-spawning group to run its bounded + tests one at a time without exhausting the whole-suite budget. - A `[[profile.default.overrides]]` entry raises the `slow-timeout` to 180 s for `cargo-bdd::cli`, whose smoke tests spawn `cargo` to build fixture crates and can legitimately exceed 60 s on cold caches. @@ -163,7 +164,7 @@ The file sets the timeout policy for the test suite: (`max-threads = 1`), so `cargo-bdd::cli` and the three trybuild binaries run one at a time instead of contending for CPU with concurrent `cargo` builds. - A `long` profile (`--profile long`) relaxes the limits further (180 s - `slow-timeout`, 15 m `global-timeout`) for deliberately slow local runs. + `slow-timeout`, 30 m `global-timeout`) for deliberately slow local runs. When adding a test binary that shells out to `cargo`, extend the relevant override's `filter` expression rather than raising the default `slow-timeout`: @@ -209,15 +210,15 @@ Mitigation: matrix legs (see `.github/workflows/ci.yml`). Windows coverage runs use `cargo llvm-cov test` (libtest) instead. - `step_macros_compile` (`crates/rstest-bdd/tests/trybuild_macros.rs`) guards - its early return with `cfg!(windows) && env::var_os("NEXTEST_RUN_ID")`, so - it only skips its trybuild and Clippy UI fixtures under nextest on Windows, + its early return with `cfg!(windows) && env::var_os("NEXTEST_RUN_ID")`, so it + only skips its trybuild and Clippy UI fixtures under nextest on Windows, where this deadlock applies. On Linux and macOS the fixtures run under nextest like any other test. - `.config/nextest.toml` raises the `slow-timeout` for the trybuild compile-test binaries (including both `macro_compile` binaries and `rstest-bdd::trybuild_macros`) to 300 s as a local-development safety net. - This does not fix the deadlock; it only delays termination to allow the - build to complete on fast machines. + This does not fix the deadlock; it only delays termination to allow the build + to complete on fast machines. - `.config/nextest.toml` also places the `cargo-bdd::cli` and trybuild binaries in a `cargo-spawning` test group with `max-threads = 1`, so these cargo-spawning tests run one at a time rather than contending for the @@ -419,9 +420,9 @@ adapter. `rstest-bdd-harness`'s own test targets (for example rstest-bdd-harness = { path = ".", features = ["testing"] } ``` -This keeps `FailingHarness` defined once, with no local duplicate in the -test binary, and works without requiring `--all-features`. Downstream crates -enable it only for tests: +This keeps `FailingHarness` defined once, with no local duplicate in the test +binary, and works without requiring `--all-features`. Downstream crates enable +it only for tests: ```toml [dev-dependencies] @@ -488,10 +489,10 @@ preserved. A trybuild compile-pass mirror, `tests/fixtures_macros/scenario_bulk_migration_cookbook.rs`, compile-checks the same shape and is registered in `run_passing_macro_tests` (`tests/trybuild_macros.rs`). `step_macros_compile` runs this fixture under -nextest on Linux and macOS; it is skipped only under nextest on Windows, -where the Job Object capture-pipe deadlock applies (see "nextest on Windows: -trybuild deadlock" above), and must instead be validated with plain -`cargo test` (or `cargo llvm-cov test` for coverage). +nextest on Linux and macOS; it is skipped only under nextest on Windows, where +the Job Object capture-pipe deadlock applies (see "nextest on Windows: trybuild +deadlock" above), and must instead be validated with plain `cargo test` (or +`cargo llvm-cov test` for coverage). Doc↔suite parity for this cookbook is guarded by prose, not a checker (the subsection states "if a snippet drifts, the suite wins"), matching the @@ -1288,14 +1289,21 @@ When maintaining the pin: 2. Re-run `make lint-whitaker`, then the full `make lint` gate. 3. Update ADR-013 only if the mechanism or adopted lint set changes. -Do not replace invariant checks with `.expect(...)`, `.unwrap()`, or -`unwrap_or_else(|| panic!(...))`. Use a copyable invariant check such as -`let Some(value) = value else { panic!("expected value to be present"); };`, -or return `Result` and use `?` when an operation is fallible. Fixture functions -and test helpers that perform fallible operations must return `Result` and -propagate errors with `?`; infallible helpers need not introduce an artificial -`Result` type. Shared helpers should avoid `.expect(...)` for invariant checks -and instead use explicit, context-appropriate invariant handling. Shared +The root `clippy.toml` sets `allow-expect-in-tests = true` and +`allow-panic-in-tests = true`. These keys are narrowly scoped: recognized +built-in `#[test]` and `rstest` cases may use `.expect(...)` and `panic!(...)` +at their test boundary; they do not permit `.unwrap()` or use in shared helpers. + +Outside recognized test cases, do not replace invariant checks with +`.expect(...)`, `.unwrap()`, or `unwrap_or_else(|| panic!(...))`. Use a +copyable invariant check such as +`let Some(value) = value else { panic!("expected value to be present"); };`, or +return `Result` and use `?` when an operation is fallible. Recognized built-in +`#[test]` and `rstest` cases may use `.expect(...)` or `panic!(...)` for +unexpected setup failures, aligned with ADR-013; `.unwrap()` and +`unwrap_or_else(|| panic!(...))` remain disallowed. Their signatures need not +return `Result` simply to propagate fixture errors. Reusable fixture functions +and shared helpers should still return `Result` rather than panic. Shared assertion shapes belong in macros so panic line numbers point at the calling test.