Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- New: Generate more comparison operator mutant variants: `<` and `>` now also mutate to `>=` and `<=` respectively as a way of negating the condition, and `<=` and `>=` now mutate to `<` and `>` as well to catch off-by-one errors.

- New: `#[mutants::exclude_re("pattern")]` attribute to exclude specific mutations by regex, without disabling all mutations on the function. The attribute can be placed on functions, `impl` blocks, `trait` blocks, modules, files, and on expressions that can carry an attribute (such as `match`, struct literals, call expressions, method calls, and unary expressions). Multiple patterns can be applied. Also supported within `cfg_attr`. Requires the [mutants](https://crates.io/crates/mutants) crate version `0.0.5` or later.

- Fixed: `#[mutants::skip]` (and `#[cfg_attr(..., mutants::skip)]`) is now honoured when placed on `const` and `static` items, including associated constants in `impl` and `trait` blocks. Previously the attribute was silently ignored on these items and operator mutants inside the initializer expression were still generated ([#508](https://github.com/sourcefrog/cargo-mutants/issues/508)).
Expand Down
8 changes: 4 additions & 4 deletions book/src/mutants.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ like `a == 0`.
| `!=` | `==` |
| `&&` | `\|\|` |
| `\|\|` | `&&`, |
| `<` | `==`, `>` |
| `>` | `==`, `<` |
| `<=` | `>` |
| `>=` | `<` |
| `<` | `==`, `>`, `<=`, `>=` |
| `>` | `==`, `<`, `>=`, `<=` |
| `<=` | `<`, `>` |
| `>=` | `>`, `<` |
| `+` | `-`, `*` |
| `-` | `+`, `/` |
| `*` | `+`, `/` |
Expand Down
1 change: 1 addition & 0 deletions src/mutant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ mod test {
"replace > with == in controlled_loop",
"replace > with < in controlled_loop",
"replace > with >= in controlled_loop",
"replace > with <= in controlled_loop",
"replace * with + in controlled_loop",
"replace * with / in controlled_loop",
]
Expand Down
32 changes: 24 additions & 8 deletions src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -799,10 +799,10 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> {
BinOp::Ne(_) => vec![quote! { == }],
BinOp::And(_) => vec![quote! { || }],
BinOp::Or(_) => vec![quote! { && }],
BinOp::Lt(_) => vec![quote! { == }, quote! {>}, quote! { <= }],
BinOp::Gt(_) => vec![quote! { == }, quote! {<}, quote! { >= }],
BinOp::Le(_) => vec![quote! {>}],
BinOp::Ge(_) => vec![quote! {<}],
BinOp::Lt(_) => vec![quote! { == }, quote! {>}, quote! { <= }, quote! { >= }],
BinOp::Gt(_) => vec![quote! { == }, quote! {<}, quote! { >= }, quote! { <= }],
BinOp::Le(_) => vec![quote! {<}, quote! {>}],
BinOp::Ge(_) => vec![quote! {>}, quote! {<}],
BinOp::Add(_) => vec![quote! {-}, quote! {*}],
BinOp::AddAssign(_) => vec![quote! {-=}, quote! {*=}],
BinOp::Sub(_) | BinOp::Mul(_) => vec![quote! {+}, quote! {/}],
Expand Down Expand Up @@ -1821,16 +1821,32 @@ mod test {
fn mutate_comparisons() {
assert_eq!(
mutate_expr("a > b"),
&["replace > with ==", "replace > with <", "replace > with >="]
&[
"replace > with ==",
"replace > with <",
"replace > with >=",
"replace > with <="
]
);
assert_eq!(
mutate_expr("a < b "),
&["replace < with ==", "replace < with >", "replace < with <="]
&[
"replace < with ==",
"replace < with >",
"replace < with <=",
"replace < with >="
]
);
assert_eq!(mutate_expr("a == b"), &["replace == with !="]);
assert_eq!(mutate_expr("a != b"), &["replace != with =="]);
assert_eq!(mutate_expr("a >= b"), &["replace >= with <"]);
assert_eq!(mutate_expr("a <= b"), &["replace <= with >"]);
assert_eq!(
mutate_expr("a >= b"),
&["replace >= with >", "replace >= with <"]
);
assert_eq!(
mutate_expr("a <= b"),
&["replace <= with <", "replace <= with >"]
);
}

#[test]
Expand Down
9 changes: 5 additions & 4 deletions tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2605,26 +2605,27 @@ fn check_tree_with_mutants_skip() {
.assert()
.success()
.stdout(indoc! { r"
Found 6 mutants to test
Found 7 mutants to test
ok Unmutated baseline
ok src/lib.rs:15:5: replace controlled_loop with ()
ok src/lib.rs:21:28: replace > with == in controlled_loop
ok src/lib.rs:21:28: replace > with < in controlled_loop
ok src/lib.rs:21:28: replace > with >= in controlled_loop
ok src/lib.rs:21:28: replace > with <= in controlled_loop
ok src/lib.rs:21:53: replace * with + in controlled_loop
ok src/lib.rs:21:53: replace * with / in controlled_loop
6 mutants tested: 6 succeeded
7 mutants tested: 7 succeeded
"})
.stderr("");
assert_eq!(
outcome_json_counts(&tmp_src_dir),
serde_json::json!({
"caught": 0,
"missed": 0,
"success": 6,
"success": 7,
"timeout": 0,
"unviable": 0,
"total_mutants": 6,
"total_mutants": 7,
})
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
---
source: tests/main.rs
expression: stdout
snapshot_kind: text
---
Found 7 mutants to test
Found 8 mutants to test
ok Unmutated baseline
7 mutants tested: 7 caught
8 mutants tested: 8 caught
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
---
source: tests/util/mod.rs
assertion_line: 33
expression: "String::from_utf8_lossy(&output.stdout)"

---
[
{
Expand Down Expand Up @@ -1893,6 +1891,38 @@ expression: "String::from_utf8_lossy(&output.stdout)"
}
}
},
{
"diff": "--- src/result.rs\n+++ replace < with >= in error_if_negative\n@@ -2,17 +2,17 @@\n // fn io_result() -> std::io::Result<\n \n /// Simple easily-recognizable Result.\n fn simple_result() -> Result<&'static str, ()> {\n Ok(\"success\")\n }\n \n fn error_if_negative(a: i32) -> Result<(), ()> {\n- if a < 0 {\n+ if a >= /* ~ changed by cargo-mutants ~ */ 0 {\n Err(())\n } else {\n Ok(())\n }\n }\n \n fn result_with_no_apparent_type_args() -> std::fmt::Result {\n Err(Default::default())\n",
"file": "src/result.rs",
"function": {
"function_name": "error_if_negative",
"return_type": "-> Result<(), ()>",
"span": {
"end": {
"column": 2,
"line": 15
},
"start": {
"column": 1,
"line": 9
}
}
},
"genre": "BinaryOperator",
"name": "src/result.rs:10:10: replace < with >= in error_if_negative",
"package": "cargo-mutants-testdata-well-tested",
"replacement": ">=",
"span": {
"end": {
"column": 11,
"line": 10
},
"start": {
"column": 10,
"line": 10
}
}
},
{
"diff": "--- src/result.rs\n+++ replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default())\n@@ -10,17 +10,17 @@\n if a < 0 {\n Err(())\n } else {\n Ok(())\n }\n }\n \n fn result_with_no_apparent_type_args() -> std::fmt::Result {\n- Err(Default::default())\n+ Ok(Default::default()) /* ~ changed by cargo-mutants ~ */\n }\n \n mod test {\n use super::*;\n \n #[test]\n fn simple_result_success() {\n assert_eq!(simple_result(), Ok(\"success\"));\n",
"file": "src/result.rs",
Expand Down Expand Up @@ -2661,6 +2691,38 @@ expression: "String::from_utf8_lossy(&output.stdout)"
}
}
},
{
"diff": "--- src/slices.rs\n+++ replace < with >= in pad\n@@ -1,13 +1,13 @@\n use std::borrow::Cow;\n \n fn pad<'a>(aa: &'a mut [Cow<'static, str>]) -> &'a [Cow<'static, str>] {\n for a in aa.iter_mut() {\n- if a.len() < 3 {\n+ if a.len() >= /* ~ changed by cargo-mutants ~ */ 3 {\n a.to_mut().push_str(\"___\");\n }\n }\n aa\n }\n \n fn return_mut_slice(a: &mut [usize]) -> &mut [usize] {\n for x in a.iter_mut() {\n",
"file": "src/slices.rs",
"function": {
"function_name": "pad",
"return_type": "-> &'a[Cow<'static, str>]",
"span": {
"end": {
"column": 2,
"line": 10
},
"start": {
"column": 1,
"line": 3
}
}
},
"genre": "BinaryOperator",
"name": "src/slices.rs:5:20: replace < with >= in pad",
"package": "cargo-mutants-testdata-well-tested",
"replacement": ">=",
"span": {
"end": {
"column": 21,
"line": 5
},
"start": {
"column": 20,
"line": 5
}
}
},
{
"diff": "--- src/slices.rs\n+++ replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new())\n@@ -5,20 +5,17 @@\n if a.len() < 3 {\n a.to_mut().push_str(\"___\");\n }\n }\n aa\n }\n \n fn return_mut_slice(a: &mut [usize]) -> &mut [usize] {\n- for x in a.iter_mut() {\n- *x *= 2\n- }\n- a\n+ Vec::leak(Vec::new()) /* ~ changed by cargo-mutants ~ */\n }\n \n #[cfg(test)]\n mod test {\n #[test]\n fn test_pad() {\n assert_eq!(\n super::pad(&mut [\"hello\".into(), \"ok\".into(), \"cat\".into()]),\n",
"file": "src/slices.rs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ src/result.rs:10:5: replace error_if_negative -> Result<(), ()> with Ok(())
src/result.rs:10:10: replace < with == in error_if_negative
src/result.rs:10:10: replace < with > in error_if_negative
src/result.rs:10:10: replace < with <= in error_if_negative
src/result.rs:10:10: replace < with >= in error_if_negative
src/result.rs:18:5: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default())
src/sets.rs:4:5: replace make_a_set -> BTreeSet<String> with BTreeSet::new()
src/sets.rs:4:5: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()])
Expand All @@ -85,6 +86,7 @@ src/slices.rs:4:5: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow
src/slices.rs:5:20: replace < with == in pad
src/slices.rs:5:20: replace < with > in pad
src/slices.rs:5:20: replace < with <= in pad
src/slices.rs:5:20: replace < with >= in pad
src/slices.rs:13:5: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new())
src/slices.rs:13:5: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0])
src/slices.rs:13:5: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ src/result.rs:10:5: replace error_if_negative -> Result<(), ()> with Ok(())
src/result.rs:10:10: replace < with == in error_if_negative
src/result.rs:10:10: replace < with > in error_if_negative
src/result.rs:10:10: replace < with <= in error_if_negative
src/result.rs:10:10: replace < with >= in error_if_negative
src/result.rs:18:5: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default())
src/sets.rs:4:5: replace make_a_set -> BTreeSet<String> with BTreeSet::new()
src/sets.rs:4:5: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()])
Expand All @@ -73,6 +74,7 @@ src/slices.rs:4:5: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow
src/slices.rs:5:20: replace < with == in pad
src/slices.rs:5:20: replace < with > in pad
src/slices.rs:5:20: replace < with <= in pad
src/slices.rs:5:20: replace < with >= in pad
src/slices.rs:13:5: replace return_mut_slice -> &mut[usize] with Vec::leak(Vec::new())
src/slices.rs:13:5: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![0])
src/slices.rs:13:5: replace return_mut_slice -> &mut[usize] with Vec::leak(vec![1])
Expand Down