diff --git a/book/src/mutants.md b/book/src/mutants.md index e102c680..6acf818c 100644 --- a/book/src/mutants.md +++ b/book/src/mutants.md @@ -89,5 +89,14 @@ too prone to generate false positives, for example when unsigned integers are co ## Unary operators Unary operators are deleted in expressions like `-a` and `!a`. -They are not currently replaced with other unary operators because they are too prone to +They are not currently replaced with other unary operators because they are too prone to generate unviable cases (e.g. `!1.0`, `-false`). + +## Match arms + +Entire match arms are deleted in match expressions when a wildcard pattern is present in one of the arms. +Match expressions without a wildcard pattern would be too prone to unviable mutations of this kind. + +## Match arm guards + +Match arm guard expressions are replaced with `true` and `false`. diff --git a/src/fnvalue.rs b/src/fnvalue.rs index 5a49d87d..3e2d691a 100644 --- a/src/fnvalue.rs +++ b/src/fnvalue.rs @@ -202,9 +202,6 @@ fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> impl Iterator { - vec![quote! { () }] - } Type::Tuple(TypeTuple { elems, .. }) => { // Generate the cartesian product of replacements of every type within the tuple. elems diff --git a/src/mutant.rs b/src/mutant.rs index e08facd5..3163b452 100644 --- a/src/mutant.rs +++ b/src/mutant.rs @@ -26,6 +26,10 @@ pub enum Genre { /// Replace `==` with `!=` and so on. BinaryOperator, UnaryOperator, + /// Delete match arm. + MatchArm, + /// Replace the expression of a match arm guard with a fixed value. + MatchArmGuard, } /// A mutation applied to source code. @@ -131,33 +135,43 @@ impl Mutant { style(s.to_string()) } let mut v: Vec> = Vec::new(); - if self.genre == Genre::FnValue { - v.push(s("replace ")); - let function = self - .function - .as_ref() - .expect("FnValue mutant should have a function"); - v.push(s(&function.function_name).bright().magenta()); - if !function.return_type.is_empty() { - v.push(s(" ")); - v.push(s(&function.return_type).magenta()); - } - v.push(s(" with ")); - v.push(s(self.replacement_text()).yellow()); - } else { - if self.replacement.is_empty() { - v.push(s("delete ")); - } else { + match self.genre { + Genre::FnValue => { v.push(s("replace ")); - } - v.push(s(self.original_text()).yellow()); - if !self.replacement.is_empty() { + let function = self + .function + .as_ref() + .expect("FnValue mutant should have a function"); + v.push(s(&function.function_name).bright().magenta()); + if !function.return_type.is_empty() { + v.push(s(" ")); + v.push(s(&function.return_type).magenta()); + } v.push(s(" with ")); - v.push(s(&self.replacement).bright().yellow()); + v.push(s(self.replacement_text()).yellow()); } - if let Some(function) = &self.function { - v.push(s(" in ")); - v.push(s(&function.function_name).bright().magenta()); + Genre::MatchArmGuard => { + v.push(s("replace match guard with ")); + v.push(s(self.replacement_text()).yellow()); + } + Genre::MatchArm => { + v.push(s("delete match arm")); + } + _ => { + if self.replacement.is_empty() { + v.push(s("delete ")); + } else { + v.push(s("replace ")); + } + v.push(s(self.original_text()).yellow()); + if !self.replacement.is_empty() { + v.push(s(" with ")); + v.push(s(&self.replacement).bright().yellow()); + } + if let Some(function) = &self.function { + v.push(s(" in ")); + v.push(s(&function.function_name).bright().magenta()); + } } } v diff --git a/src/visit.rs b/src/visit.rs index cdc937fc..715454b2 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -595,6 +595,53 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> { }; syn::visit::visit_expr_unary(self, i); } + + fn visit_expr_match(&mut self, i: &'ast syn::ExprMatch) { + let _span = trace_span!("match", line = i.span().start().line).entered(); + + // While it's not currently possible to annotate expressions with custom attributes, this + // limitation could be lifted in the future. + if attrs_excluded(&i.attrs) { + trace!("match excluded by attrs"); + return; + } + + let has_catchall = i + .arms + .iter() + .any(|arm| matches!(arm.pat, syn::Pat::Wild(_))); + if has_catchall { + i.arms + .iter() + // Don't mutate the wild arm, because that will likely be unviable, and also + // skip it if a guard is present, because the replacement of the guard with 'false' + // below is logically equivalent to removing the arm. + .filter(|arm| !matches!(arm.pat, syn::Pat::Wild(_)) && arm.guard.is_none()) + .for_each(|arm| { + self.collect_mutant(arm.span().into(), "e! {}, Genre::MatchArm); + }); + } else { + trace!("match has no `_` pattern"); + } + + i.arms + .iter() + .flat_map(|arm| &arm.guard) + .for_each(|(_if, guard_expr)| { + self.collect_mutant( + guard_expr.span().into(), + "e! { true }, + Genre::MatchArmGuard, + ); + self.collect_mutant( + guard_expr.span().into(), + "e! { false }, + Genre::MatchArmGuard, + ); + }); + + syn::visit::visit_expr_match(self, i); + } } // Get the span of the block excluding the braces, or None if it is empty. @@ -1084,4 +1131,122 @@ mod test { "#} ); } + + #[test] + fn mutate_match_arms_with_fallback() { + let options = Options::default(); + let mutants = mutate_source_str( + indoc! {" + fn main() { + match x { + X::A => {}, + X::B => {}, + _ => {}, + } + } + "}, + &options, + ) + .unwrap(); + assert_eq!( + mutants + .iter() + .filter(|m| m.genre == Genre::MatchArm) + .map(|m| m.name(true)) + .collect_vec(), + [ + "src/main.rs:3:9: delete match arm", + "src/main.rs:4:9: delete match arm", + ] + ); + } + + #[test] + fn skip_match_arms_without_fallback() { + let options = Options::default(); + let mutants = mutate_source_str( + indoc! {" + fn main() { + match x { + X::A => {}, + X::B => {}, + } + } + "}, + &options, + ) + .unwrap(); + + let empty: &[&str] = &[]; + assert_eq!( + mutants + .iter() + .filter(|m| m.genre == Genre::MatchArm) + .map(|m| m.name(true)) + .collect_vec(), + empty + ); + } + + #[test] + fn mutate_match_guard() { + let options = Options::default(); + let mutants = mutate_source_str( + indoc! {" + fn main() { + match x { + X::A if foo() => {}, + X::A => {}, + X::B => {}, + X::C if bar() => {}, + } + } + "}, + &options, + ) + .unwrap(); + assert_eq!( + mutants + .iter() + .filter(|m| m.genre == Genre::MatchArmGuard) + .map(|m| m.name(true)) + .collect_vec(), + [ + "src/main.rs:3:17: replace match guard with true", + "src/main.rs:3:17: replace match guard with false", + "src/main.rs:6:17: replace match guard with true", + "src/main.rs:6:17: replace match guard with false", + ] + ); + } + + #[test] + fn skip_removing_match_arm_with_guard() { + let options = Options::default(); + let mutants = mutate_source_str( + indoc! {" + fn main() { + match x { + X::A if foo() => {}, + X::A => {}, + _ => {}, + } + } + "}, + &options, + ) + .unwrap(); + assert_eq!( + mutants + .iter() + .filter(|m| matches!(m.genre, Genre::MatchArmGuard | Genre::MatchArm)) + .map(|m| m.name(true)) + .collect_vec(), + [ + "src/main.rs:4:9: delete match arm", + "src/main.rs:3:17: replace match guard with true", + "src/main.rs:3:17: replace match guard with false", + ] + ); + } } diff --git a/src/workspace.rs b/src/workspace.rs index 79e81db7..97872dc9 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -201,10 +201,6 @@ impl Workspace { // in that case we'll just fall back to everything, for lack of a better option. // TODO: Use the new cargo_metadata API that doesn't panic? match catch_unwind(|| metadata.workspace_default_packages()) { - Ok(default_packages) if default_packages.is_empty() => { - debug!("manifest has no explicit default packages"); - PackageSelection::All - } Ok(default_packages) => { let default_package_names: Vec<&str> = default_packages .iter() diff --git a/testdata/many_patterns/src/binops.rs b/testdata/many_patterns/src/binops.rs index 4a5ae69e..fd0bc3b8 100644 --- a/testdata/many_patterns/src/binops.rs +++ b/testdata/many_patterns/src/binops.rs @@ -6,6 +6,14 @@ pub fn binops() { a -= 2; a *= 3; a /= 2; + + let mut b; + b = a < 0; + b = a <= 0; + b = a > 0; + b = a >= 0; + b = a == 0; + b = a != 0; } pub fn bin_assign() -> i32 { @@ -15,5 +23,6 @@ pub fn bin_assign() -> i32 { a &= 0x0f; a >>= 4; a <<= 1; + a %= 1; a } diff --git a/testdata/workspace_default_members/Cargo_test.toml b/testdata/workspace_default_members/Cargo_test.toml new file mode 100644 index 00000000..ebb5e5c1 --- /dev/null +++ b/testdata/workspace_default_members/Cargo_test.toml @@ -0,0 +1,6 @@ +# This workspace has default-members set, which influences which packages are mutated by default. + +[workspace] +members = ["main", "excluded"] +default-members = ["main"] +resolver = "2" diff --git a/testdata/workspace_default_members/excluded/Cargo_test.toml b/testdata/workspace_default_members/excluded/Cargo_test.toml new file mode 100644 index 00000000..b1d58b61 --- /dev/null +++ b/testdata/workspace_default_members/excluded/Cargo_test.toml @@ -0,0 +1,10 @@ +[package] +name = "excluded" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +mutants = "0.0.3" diff --git a/testdata/workspace_default_members/excluded/src/main.rs b/testdata/workspace_default_members/excluded/src/main.rs new file mode 100644 index 00000000..42b7812b --- /dev/null +++ b/testdata/workspace_default_members/excluded/src/main.rs @@ -0,0 +1,3 @@ +fn main() -> i32 { + 100 +} \ No newline at end of file diff --git a/testdata/workspace_default_members/main/Cargo_test.toml b/testdata/workspace_default_members/main/Cargo_test.toml new file mode 100644 index 00000000..b8d2f40d --- /dev/null +++ b/testdata/workspace_default_members/main/Cargo_test.toml @@ -0,0 +1,10 @@ +[package] +name = "main" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +mutants = "0.0.3" diff --git a/testdata/workspace_default_members/main/src/main.rs b/testdata/workspace_default_members/main/src/main.rs new file mode 100644 index 00000000..1873f2f9 --- /dev/null +++ b/testdata/workspace_default_members/main/src/main.rs @@ -0,0 +1,6 @@ +const X: u32 = 2 + 1; + +#[test] +fn test() { + assert_eq!(X, 3); +} diff --git a/tests/main.rs b/tests/main.rs index f428de98..605f239b 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -45,6 +45,18 @@ fn show_version() { .stdout(predicates::str::is_match(r"^cargo-mutants \d+\.\d+\.\d+(-.*)?\n$").unwrap()); } +#[test] +fn show_help() { + // Asserting on the entire help message would be a bit too annoying to maintain. + run() + .args(["mutants", "--help"]) + .assert() + .success() + .stdout(predicates::str::contains( + "Usage: cargo mutants [OPTIONS] [-- ...]", + )); +} + #[test] fn uses_cargo_env_var_to_run_cargo_so_invalid_value_fails() { let tmp_src_dir = copy_of_testdata("well_tested"); diff --git a/tests/snapshots/list__list_mutants_in_all_trees_as_json.snap b/tests/snapshots/list__list_mutants_in_all_trees_as_json.snap index c0aac58e..412a899f 100644 --- a/tests/snapshots/list__list_mutants_in_all_trees_as_json.snap +++ b/tests/snapshots/list__list_mutants_in_all_trees_as_json.snap @@ -2205,7 +2205,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2218,8 +2218,8 @@ snapshot_kind: text "replacement": "()", "span": { "end": { - "column": 12, - "line": 8 + "column": 16, + "line": 16 }, "start": { "column": 5, @@ -2235,7 +2235,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2265,7 +2265,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2295,7 +2295,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2325,7 +2325,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2355,7 +2355,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2385,7 +2385,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2415,7 +2415,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2445,7 +2445,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2475,7 +2475,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2505,7 +2505,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2535,7 +2535,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2565,7 +2565,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2595,7 +2595,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2625,7 +2625,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2655,7 +2655,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2685,7 +2685,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2715,7 +2715,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2745,7 +2745,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2775,7 +2775,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2805,7 +2805,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2835,7 +2835,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2865,7 +2865,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2895,7 +2895,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2925,7 +2925,7 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 9 + "line": 17 }, "start": { "column": 1, @@ -2947,6 +2947,246 @@ snapshot_kind: text } } }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "==", + "span": { + "end": { + "column": 12, + "line": 11 + }, + "start": { + "column": 11, + "line": 11 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": ">", + "span": { + "end": { + "column": 12, + "line": 11 + }, + "start": { + "column": 11, + "line": 11 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": ">", + "span": { + "end": { + "column": 13, + "line": 12 + }, + "start": { + "column": 11, + "line": 12 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "==", + "span": { + "end": { + "column": 12, + "line": 13 + }, + "start": { + "column": 11, + "line": 13 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "<", + "span": { + "end": { + "column": 12, + "line": 13 + }, + "start": { + "column": 11, + "line": 13 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "<", + "span": { + "end": { + "column": 13, + "line": 14 + }, + "start": { + "column": 11, + "line": 14 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "!=", + "span": { + "end": { + "column": 13, + "line": 15 + }, + "start": { + "column": 11, + "line": 15 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "binops", + "return_type": "", + "span": { + "end": { + "column": 2, + "line": 17 + }, + "start": { + "column": 1, + "line": 1 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "==", + "span": { + "end": { + "column": 13, + "line": 16 + }, + "start": { + "column": 11, + "line": 16 + } + } + }, { "file": "src/binops.rs", "function": { @@ -2955,11 +3195,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -2969,11 +3209,11 @@ snapshot_kind: text "span": { "end": { "column": 6, - "line": 18 + "line": 27 }, "start": { "column": 5, - "line": 12 + "line": 20 } } }, @@ -2985,11 +3225,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -2999,11 +3239,11 @@ snapshot_kind: text "span": { "end": { "column": 6, - "line": 18 + "line": 27 }, "start": { "column": 5, - "line": 12 + "line": 20 } } }, @@ -3015,11 +3255,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3029,11 +3269,11 @@ snapshot_kind: text "span": { "end": { "column": 6, - "line": 18 + "line": 27 }, "start": { "column": 5, - "line": 12 + "line": 20 } } }, @@ -3045,11 +3285,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3059,11 +3299,11 @@ snapshot_kind: text "span": { "end": { "column": 9, - "line": 13 + "line": 21 }, "start": { "column": 7, - "line": 13 + "line": 21 } } }, @@ -3075,11 +3315,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3089,11 +3329,11 @@ snapshot_kind: text "span": { "end": { "column": 9, - "line": 13 + "line": 21 }, "start": { "column": 7, - "line": 13 + "line": 21 } } }, @@ -3105,11 +3345,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3119,11 +3359,11 @@ snapshot_kind: text "span": { "end": { "column": 9, - "line": 14 + "line": 22 }, "start": { "column": 7, - "line": 14 + "line": 22 } } }, @@ -3135,11 +3375,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3149,11 +3389,11 @@ snapshot_kind: text "span": { "end": { "column": 9, - "line": 14 + "line": 22 }, "start": { "column": 7, - "line": 14 + "line": 22 } } }, @@ -3165,11 +3405,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3179,11 +3419,11 @@ snapshot_kind: text "span": { "end": { "column": 9, - "line": 15 + "line": 23 }, "start": { "column": 7, - "line": 15 + "line": 23 } } }, @@ -3195,11 +3435,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3209,11 +3449,11 @@ snapshot_kind: text "span": { "end": { "column": 9, - "line": 15 + "line": 23 }, "start": { "column": 7, - "line": 15 + "line": 23 } } }, @@ -3225,11 +3465,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3239,11 +3479,11 @@ snapshot_kind: text "span": { "end": { "column": 10, - "line": 16 + "line": 24 }, "start": { "column": 7, - "line": 16 + "line": 24 } } }, @@ -3255,11 +3495,11 @@ snapshot_kind: text "span": { "end": { "column": 2, - "line": 19 + "line": 28 }, "start": { "column": 1, - "line": 11 + "line": 19 } } }, @@ -3269,11 +3509,71 @@ snapshot_kind: text "span": { "end": { "column": 10, - "line": 17 + "line": 25 }, "start": { "column": 7, - "line": 17 + "line": 25 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "bin_assign", + "return_type": "-> i32", + "span": { + "end": { + "column": 2, + "line": 28 + }, + "start": { + "column": 1, + "line": 19 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "/=", + "span": { + "end": { + "column": 9, + "line": 26 + }, + "start": { + "column": 7, + "line": 26 + } + } + }, + { + "file": "src/binops.rs", + "function": { + "function_name": "bin_assign", + "return_type": "-> i32", + "span": { + "end": { + "column": 2, + "line": 28 + }, + "start": { + "column": 1, + "line": 19 + } + } + }, + "genre": "BinaryOperator", + "package": "cargo-mutants-testdata-many-patterns", + "replacement": "+=", + "span": { + "end": { + "column": 9, + "line": 26 + }, + "start": { + "column": 7, + "line": 26 } } } @@ -9849,3 +10149,44 @@ snapshot_kind: text } ] ``` + +## testdata/workspace_default_members + +```json +[ + { + "file": "main/src/main.rs", + "function": null, + "genre": "BinaryOperator", + "package": "main", + "replacement": "-", + "span": { + "end": { + "column": 19, + "line": 1 + }, + "start": { + "column": 18, + "line": 1 + } + } + }, + { + "file": "main/src/main.rs", + "function": null, + "genre": "BinaryOperator", + "package": "main", + "replacement": "*", + "span": { + "end": { + "column": 19, + "line": 1 + }, + "start": { + "column": 18, + "line": 1 + } + } + } +] +``` diff --git a/tests/snapshots/list__list_mutants_in_all_trees_as_text.snap b/tests/snapshots/list__list_mutants_in_all_trees_as_text.snap index b66d933f..7688b670 100644 --- a/tests/snapshots/list__list_mutants_in_all_trees_as_text.snap +++ b/tests/snapshots/list__list_mutants_in_all_trees_as_text.snap @@ -209,17 +209,27 @@ src/binops.rs:7:7: replace *= with += in binops src/binops.rs:7:7: replace *= with /= in binops src/binops.rs:8:7: replace /= with %= in binops src/binops.rs:8:7: replace /= with *= in binops -src/binops.rs:12:5: replace bin_assign -> i32 with 0 -src/binops.rs:12:5: replace bin_assign -> i32 with 1 -src/binops.rs:12:5: replace bin_assign -> i32 with -1 -src/binops.rs:13:7: replace |= with &= in bin_assign -src/binops.rs:13:7: replace |= with ^= in bin_assign -src/binops.rs:14:7: replace ^= with |= in bin_assign -src/binops.rs:14:7: replace ^= with &= in bin_assign -src/binops.rs:15:7: replace &= with |= in bin_assign -src/binops.rs:15:7: replace &= with ^= in bin_assign -src/binops.rs:16:7: replace >>= with <<= in bin_assign -src/binops.rs:17:7: replace <<= with >>= in bin_assign +src/binops.rs:11:11: replace < with == in binops +src/binops.rs:11:11: replace < with > in binops +src/binops.rs:12:11: replace <= with > in binops +src/binops.rs:13:11: replace > with == in binops +src/binops.rs:13:11: replace > with < in binops +src/binops.rs:14:11: replace >= with < in binops +src/binops.rs:15:11: replace == with != in binops +src/binops.rs:16:11: replace != with == in binops +src/binops.rs:20:5: replace bin_assign -> i32 with 0 +src/binops.rs:20:5: replace bin_assign -> i32 with 1 +src/binops.rs:20:5: replace bin_assign -> i32 with -1 +src/binops.rs:21:7: replace |= with &= in bin_assign +src/binops.rs:21:7: replace |= with ^= in bin_assign +src/binops.rs:22:7: replace ^= with |= in bin_assign +src/binops.rs:22:7: replace ^= with &= in bin_assign +src/binops.rs:23:7: replace &= with |= in bin_assign +src/binops.rs:23:7: replace &= with ^= in bin_assign +src/binops.rs:24:7: replace >>= with <<= in bin_assign +src/binops.rs:25:7: replace <<= with >>= in bin_assign +src/binops.rs:26:7: replace %= with /= in bin_assign +src/binops.rs:26:7: replace %= with += in bin_assign ``` ## testdata/missing_test @@ -551,3 +561,10 @@ main2/src/main.rs:10:5: replace triple_3 -> i32 with 0 main2/src/main.rs:10:5: replace triple_3 -> i32 with 1 main2/src/main.rs:10:5: replace triple_3 -> i32 with -1 ``` + +## testdata/workspace_default_members + +``` +main/src/main.rs:1:18: replace + with - +main/src/main.rs:1:18: replace + with * +``` diff --git a/tests/workspace.rs b/tests/workspace.rs index 7e35a86d..8d21b3f1 100644 --- a/tests/workspace.rs +++ b/tests/workspace.rs @@ -6,7 +6,7 @@ use std::fs::{self, create_dir, read_to_string, write}; use insta::assert_snapshot; use itertools::Itertools; -use predicates::prelude::predicate; +use predicates::{prelude::predicate, str::PredicateStrExt}; use pretty_assertions::assert_eq; use serde_json::json; @@ -295,6 +295,25 @@ fn baseline_test_respects_package_options() { ); } +#[test] +fn in_workspace_only_default_members_are_mutated_if_present() { + let tmp = copy_of_testdata("workspace_default_members"); + run() + .args(["mutants", "--no-shuffle", "--list", "-d"]) + .arg(tmp.path()) + .assert() + .success() + .stdout( + predicates::ord::eq( + "\ + main/src/main.rs:1:18: replace + with -\n\ + main/src/main.rs:1:18: replace + with *\n\ + ", + ) + .normalize(), + ); +} + #[test] fn cross_package_tests() { // This workspace has two packages, one of which contains the tests.