Skip to content

Commit

Permalink
Match some known simple containers
Browse files Browse the repository at this point in the history
  • Loading branch information
sourcefrog committed Jun 11, 2023
1 parent 2516fcc commit 2727de2
Show file tree
Hide file tree
Showing 17 changed files with 162 additions and 10 deletions.
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# cargo-mutants changelog

## Unreleased

- Mutate `BinaryHeap`, `BTreeSet`, `HashSet`, `LinkedList`, and `VecDeque` to
generate empty and one-element containers.

## 23.6.0

- Generate `Box::leak(Box::new(...))` as a mutation of functions returning
Expand Down
1 change: 1 addition & 0 deletions book/src/mutants.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ More mutation genres and patterns will be added in future releases.
| `Vec<T>` | `vec![]`, `vec![...]` |
| `Arc<T>` | `Arc::new(...)` |
| `Rc<T>` | `Rc::new(...)` |
| `BinaryHeap`, `BTreeSet`, `HashSet`, `LinkedList`, `VecDeque` | empty and one-element collections |
| `[T; L]` | `[r; L]` for all replacements of T |
| `&T` | `&...` (all replacements for T) |
| (any other) | `Default::default()` |
Expand Down
55 changes: 53 additions & 2 deletions src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> Vec<TokenStream> {
quote! { vec![#rep] }
}),
)
} else if let Some((container_type, inner_type)) = known_simple_container(path) {
} else if let Some((container_type, inner_type)) = known_container(path) {
// Something like Arc, Mutex, etc.

// TODO: Ideally we should use the path without relying on it being
Expand All @@ -399,6 +399,15 @@ fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> Vec<TokenStream> {
quote! { #container_type::new(#rep) }
}),
)
} else if let Some((collection_type, inner_type)) = known_collection(path) {
reps.push(quote! { #collection_type::new() });
reps.extend(
type_replacements(inner_type, error_exprs)
.into_iter()
.map(|rep| {
quote! { #collection_type::from_iter([#rep]) }
}),
);
} else {
reps.push(quote! { Default::default() });
}
Expand Down Expand Up @@ -475,7 +484,7 @@ fn path_ends_with(path: &Path, ident: &str) -> bool {
/// like Box, Cell, Mutex, etc, that can be constructed with `T::new(inner_val)`.
///
/// If so, return the short name (like "Box") and the inner type.
fn known_simple_container(path: &Path) -> Option<(&Ident, &Type)> {
fn known_container(path: &Path) -> Option<(&Ident, &Type)> {
let last = path.segments.last()?;
if !["Box", "Cell", "RefCell", "Arc", "Rc", "Mutex"]
.iter()
Expand All @@ -497,6 +506,36 @@ fn known_simple_container(path: &Path) -> Option<(&Ident, &Type)> {
None
}

/// Match known simple collections that can be empty or constructed from an
/// iterator.
fn known_collection(path: &Path) -> Option<(&Ident, &Type)> {
let last = path.segments.last()?;
if ![
"BinaryHeap",
"BTreeSet",
"HashSet",
"LinkedList",
"VecDeque",
]
.iter()
.any(|v| last.ident == v)
{
return None;
}
if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) =
&last.arguments
{
// TODO: Skip lifetime args.
// TODO: Return the path with args stripped out.
if args.len() == 1 {
if let Some(GenericArgument::Type(inner_type)) = args.first() {
return Some((&last.ident, inner_type));
}
}
}
None
}

fn path_is_float(path: &Path) -> bool {
["f32", "f64"].iter().any(|s| path.is_ident(s))
}
Expand Down Expand Up @@ -908,6 +947,18 @@ mod test {
);
}

#[test]
fn btreeset_replacement() {
assert_eq!(
replace(&parse_quote! { -> std::collections::BTreeSet<String> }, &[]),
&[
"BTreeSet::new()",
"BTreeSet::from_iter([String::new()])",
"BTreeSet::from_iter([\"xyzzy\".into()])"
]
);
}

fn replace(return_type: &ReturnType, error_exprs: &[Expr]) -> Vec<String> {
return_type_replacements(return_type, error_exprs)
.into_iter()
Expand Down
1 change: 1 addition & 0 deletions testdata/tree/well_tested/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ mod methods;
mod nested_function;
mod numbers;
mod result;
mod sets;
pub mod simple_fns;
mod struct_with_lifetime;
13 changes: 13 additions & 0 deletions testdata/tree/well_tested/src/sets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::collections::BTreeSet;

fn make_a_set() -> BTreeSet<String> {
let mut s = BTreeSet::new();
s.insert("one".into());
s.insert("two".into());
s
}

#[test]
fn set_has_two_elements() {
assert_eq!(make_a_set().len(), 2);
}
4 changes: 2 additions & 2 deletions tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,8 +622,8 @@ fn well_tested_tree_quiet() {
fs::read_to_string(tmp_src_dir.path().join("mutants.out/outcomes.json")).unwrap();
println!("outcomes.json:\n{outcomes_json}");
let outcomes: serde_json::Value = outcomes_json.parse().unwrap();
assert_eq!(outcomes["total_mutants"], 27);
assert_eq!(outcomes["caught"], 27);
assert_eq!(outcomes["total_mutants"], 30);
assert_eq!(outcomes["caught"], 30);
assert_eq!(outcomes["unviable"], 0);
assert_eq!(outcomes["missed"], 0);
}
Expand Down
5 changes: 5 additions & 0 deletions tests/cli/snapshots/cli__list_files_json_well_tested.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: tests/cli/main.rs
assertion_line: 73
expression: "String::from_utf8_lossy(&output.stdout)"
---
[
Expand Down Expand Up @@ -39,6 +40,10 @@ expression: "String::from_utf8_lossy(&output.stdout)"
"package": "cargo-mutants-testdata-well-tested",
"path": "src/result.rs"
},
{
"package": "cargo-mutants-testdata-well-tested",
"path": "src/sets.rs"
},
{
"package": "cargo-mutants-testdata-well-tested",
"path": "src/simple_fns.rs"
Expand Down
2 changes: 2 additions & 0 deletions tests/cli/snapshots/cli__list_files_text_well_tested.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: tests/cli/main.rs
assertion_line: 73
expression: "String::from_utf8_lossy(&output.stdout)"
---
src/lib.rs
Expand All @@ -11,6 +12,7 @@ src/methods.rs
src/nested_function.rs
src/numbers.rs
src/result.rs
src/sets.rs
src/simple_fns.rs
src/struct_with_lifetime.rs

27 changes: 27 additions & 0 deletions tests/cli/snapshots/cli__list_mutants_in_all_trees_as_json.snap
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,33 @@ expression: buf
"replacement": "Ok(Default::default())",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/sets.rs",
"line": 3,
"function": "make_a_set",
"return_type": "-> BTreeSet<String>",
"replacement": "BTreeSet::new()",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/sets.rs",
"line": 3,
"function": "make_a_set",
"return_type": "-> BTreeSet<String>",
"replacement": "BTreeSet::from_iter([String::new()])",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/sets.rs",
"line": 3,
"function": "make_a_set",
"return_type": "-> BTreeSet<String>",
"replacement": "BTreeSet::from_iter([\"xyzzy\".into()])",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/simple_fns.rs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("")
src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy")
src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(())
src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default())
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::new()
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()])
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter(["xyzzy".into()])
src/simple_fns.rs:7: replace returns_unit with ()
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1
Expand Down
28 changes: 28 additions & 0 deletions tests/cli/snapshots/cli__list_mutants_json_well_tested.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: tests/cli/main.rs
assertion_line: 73
expression: "String::from_utf8_lossy(&output.stdout)"
---
[
Expand Down Expand Up @@ -165,6 +166,33 @@ expression: "String::from_utf8_lossy(&output.stdout)"
"replacement": "Ok(Default::default())",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/sets.rs",
"line": 3,
"function": "make_a_set",
"return_type": "-> BTreeSet<String>",
"replacement": "BTreeSet::new()",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/sets.rs",
"line": 3,
"function": "make_a_set",
"return_type": "-> BTreeSet<String>",
"replacement": "BTreeSet::from_iter([String::new()])",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/sets.rs",
"line": 3,
"function": "make_a_set",
"return_type": "-> BTreeSet<String>",
"replacement": "BTreeSet::from_iter([\"xyzzy\".into()])",
"genre": "FnValue"
},
{
"package": "cargo-mutants-testdata-well-tested",
"file": "src/simple_fns.rs",
Expand Down
4 changes: 4 additions & 0 deletions tests/cli/snapshots/cli__list_mutants_well_tested.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: tests/cli/main.rs
assertion_line: 73
expression: "String::from_utf8_lossy(&output.stdout)"
---
src/arc.rs:3: replace return_arc -> Arc<String> with Arc::new(String::new())
Expand All @@ -20,6 +21,9 @@ src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("")
src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy")
src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(())
src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default())
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::new()
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()])
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter(["xyzzy".into()])
src/simple_fns.rs:7: replace returns_unit with ()
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("")
src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy")
src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(())
src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default())
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::new()
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()])
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter(["xyzzy".into()])
src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0
src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1

7 changes: 5 additions & 2 deletions tests/cli/snapshots/cli__well_tested_tree_check_only.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: tests/cli/main.rs
expression: stdout
---
Found 27 mutants to test
Found 30 mutants to test
Unmutated baseline ... ok
src/arc.rs:3: replace return_arc -> Arc<String> with Arc::new(String::new()) ... ok
src/arc.rs:3: replace return_arc -> Arc<String> with Arc::new("xyzzy".into()) ... ok
Expand All @@ -22,6 +22,9 @@ src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") .
src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") ... ok
src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) ... ok
src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) ... ok
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::new() ... ok
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()]) ... ok
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter(["xyzzy".into()]) ... ok
src/simple_fns.rs:7: replace returns_unit with () ... ok
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0 ... ok
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1 ... ok
Expand All @@ -31,5 +34,5 @@ src/simple_fns.rs:26: replace double_string -> String with String::new() ... ok
src/simple_fns.rs:26: replace double_string -> String with "xyzzy".into() ... ok
src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 ... ok
src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 ... ok
27 mutants tested: 27 succeeded
30 mutants tested: 30 succeeded

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: tests/cli/main.rs
expression: stdout
---
Found 27 mutants to test
Found 30 mutants to test
Unmutated baseline ... ok
src/arc.rs:3: replace return_arc -> Arc<String> with Arc::new(String::new()) ... caught
src/arc.rs:3: replace return_arc -> Arc<String> with Arc::new("xyzzy".into()) ... caught
Expand All @@ -22,6 +22,9 @@ src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("") .
src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy") ... caught
src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(()) ... caught
src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default()) ... caught
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::new() ... caught
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()]) ... caught
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter(["xyzzy".into()]) ... caught
src/simple_fns.rs:7: replace returns_unit with () ... caught
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0 ... caught
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1 ... caught
Expand All @@ -31,5 +34,5 @@ src/simple_fns.rs:26: replace double_string -> String with String::new() ... cau
src/simple_fns.rs:26: replace double_string -> String with "xyzzy".into() ... caught
src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 0 ... caught
src/struct_with_lifetime.rs:14: replace Lex<'buf>::buf_len -> usize with 1 ... caught
27 mutants tested: 27 caught
30 mutants tested: 30 caught

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("")
src/result.rs:5: replace simple_result -> Result<&'static str, ()> with Ok("xyzzy")
src/result.rs:9: replace error_if_negative -> Result<(), ()> with Ok(())
src/result.rs:17: replace result_with_no_apparent_type_args -> std::fmt::Result with Ok(Default::default())
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::new()
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter([String::new()])
src/sets.rs:3: replace make_a_set -> BTreeSet<String> with BTreeSet::from_iter(["xyzzy".into()])
src/simple_fns.rs:7: replace returns_unit with ()
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 0
src/simple_fns.rs:12: replace returns_42u32 -> u32 with 1
Expand Down
4 changes: 2 additions & 2 deletions tests/cli/snapshots/cli__well_tested_tree_quiet.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: tests/cli/main.rs
expression: stdout
---
Found 27 mutants to test
Found 30 mutants to test
Unmutated baseline ... ok
27 mutants tested: 27 caught
30 mutants tested: 30 caught

0 comments on commit 2727de2

Please sign in to comment.