Skip to content

Commit

Permalink
Better report mutation in trait default fns (#172)
Browse files Browse the repository at this point in the history
  • Loading branch information
sourcefrog authored Nov 30, 2023
2 parents e18869a + acaf517 commit ab06125
Show file tree
Hide file tree
Showing 18 changed files with 294 additions and 30 deletions.
30 changes: 15 additions & 15 deletions src/mutate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,26 +118,26 @@ impl Mutant {
}
let mut v: Vec<StyledObject<String>> = Vec::new();
v.push(s("replace "));
if self.genre != Genre::FnValue {
if self.genre == Genre::FnValue {
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 {
v.push(s(self.original_text()).yellow());
v.push(s(" with "));
v.push(s(&self.replacement).bright().yellow());
v.push(s(" in "));
}
if let Some(function) = &self.function {
v.push(s(&function.function_name).bright().magenta());
} else {
v.push(s("module"));
}
if self.genre == Genre::FnValue {
if let Some(function) = &self.function {
if !function.return_type.is_empty() {
v.push(s(" "));
v.push(s(&function.return_type).magenta());
}
v.push(s(" in "));
v.push(s(&function.function_name).bright().magenta());
}
v.push(s(" with "));
v.push(s(self.replacement_text()).yellow());
}
v
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject<String> with Styl
src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject<String> with StyledObject::from_iter(["xyzzy".into()])
src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject<String> with StyledObject::new("xyzzy".into())
src/mutate.rs: replace Mutant::styled_parts::s -> StyledObject<String> with StyledObject::from("xyzzy".into())
src/mutate.rs: replace != with == in Mutant::styled_parts
src/mutate.rs: replace == with != in Mutant::styled_parts
src/mutate.rs: replace Mutant::original_text -> String with String::new()
src/mutate.rs: replace Mutant::original_text -> String with "xyzzy".into()
Expand Down Expand Up @@ -401,8 +400,11 @@ src/visit.rs: replace DiscoveryVisitor<'o>::in_namespace -> T with Default::defa
src/visit.rs: replace <impl Visit for DiscoveryVisitor<'_>>::visit_item_fn with ()
src/visit.rs: replace <impl Visit for DiscoveryVisitor<'_>>::visit_impl_item_fn with ()
src/visit.rs: replace == with != in <impl Visit for DiscoveryVisitor<'_>>::visit_impl_item_fn
src/visit.rs: replace <impl Visit for DiscoveryVisitor<'_>>::visit_trait_item_fn with ()
src/visit.rs: replace == with != in <impl Visit for DiscoveryVisitor<'_>>::visit_trait_item_fn
src/visit.rs: replace <impl Visit for DiscoveryVisitor<'_>>::visit_item_impl with ()
src/visit.rs: replace == with != in <impl Visit for DiscoveryVisitor<'_>>::visit_item_impl
src/visit.rs: replace <impl Visit for DiscoveryVisitor<'_>>::visit_item_trait with ()
src/visit.rs: replace <impl Visit for DiscoveryVisitor<'_>>::visit_item_mod with ()
src/visit.rs: replace <impl Visit for DiscoveryVisitor<'_>>::visit_expr_binary with ()
src/visit.rs: replace function_body_span -> Option<Span> with None
Expand Down
35 changes: 35 additions & 0 deletions src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> {
name = function_name
)
.entered();
trace!("visit fn");
if fn_sig_excluded(&i.sig) || attrs_excluded(&i.attrs) || block_is_empty(&i.block) {
return;
}
Expand Down Expand Up @@ -259,6 +260,29 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> {
self.leave_function(function);
}

/// Visit `fn foo() { ... }` within a trait, i.e. a default implementation of a function.
fn visit_trait_item_fn(&mut self, i: &'ast syn::TraitItemFn) {
let function_name = i.sig.ident.to_pretty_string();
let _span = trace_span!(
"fn",
line = i.sig.fn_token.span.start().line,
name = function_name
)
.entered();
if fn_sig_excluded(&i.sig) || attrs_excluded(&i.attrs) || i.sig.ident == "new" {
return;
}
if let Some(block) = &i.default {
if block_is_empty(block) {
return;
}
let function = self.enter_function(&i.sig.ident, &i.sig.output, i.span());
self.collect_fn_mutants(&i.sig, block);
syn::visit::visit_trait_item_fn(self, i);
self.leave_function(function);
}
}

/// Visit `impl Foo { ...}` or `impl Debug for Foo { ... }`.
fn visit_item_impl(&mut self, i: &'ast syn::ItemImpl) {
if attrs_excluded(&i.attrs) {
Expand All @@ -278,6 +302,16 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> {
self.in_namespace(&name, |v| syn::visit::visit_item_impl(v, i));
}

/// Visit `trait Foo { ... }`
fn visit_item_trait(&mut self, i: &'ast syn::ItemTrait) {
let name = i.ident.to_pretty_string();
let _span = trace_span!("trait", line = i.span().start().line, name).entered();
if attrs_excluded(&i.attrs) {
return;
}
self.in_namespace(&name, |v| syn::visit::visit_item_trait(v, i));
}

/// Visit `mod foo { ... }` or `mod foo;`.
fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
let mod_name = &node.ident.unraw().to_string();
Expand All @@ -298,6 +332,7 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> {
/// Visit `a op b` expressions.
fn visit_expr_binary(&mut self, i: &'ast syn::ExprBinary) {
let _span = trace_span!("binary", line = i.op.span().start().line).entered();
trace!("visit binary operator");
if attrs_excluded(&i.attrs) {
return;
}
Expand Down
1 change: 1 addition & 0 deletions testdata/well_tested/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ pub mod simple_fns;
mod slices;
mod static_item;
mod struct_with_lifetime;
mod traits;
22 changes: 22 additions & 0 deletions testdata/well_tested/src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! Test mutation of a default fn in a trait.
trait Something {
fn is_three(&self, a: usize) -> bool {
a == 3
}
}

#[cfg(test)]
mod test {
use super::*;

struct Three;

impl Something for Three {}

#[test]
fn test_is_three() {
assert!(Three.is_three(3));
assert!(!Three.is_three(4));
}
}
1 change: 1 addition & 0 deletions tests/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ fn exclude_file_argument_overrides_config() {
src/empty_fns.rs
src/methods.rs
src/result.rs
src/traits.rs
" }));
}

Expand Down
4 changes: 2 additions & 2 deletions tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,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"], 43);
assert_eq!(outcomes["caught"], 43);
assert_eq!(outcomes["total_mutants"], 46);
assert_eq!(outcomes["caught"], 46);
assert_eq!(outcomes["unviable"], 0);
assert_eq!(outcomes["missed"], 0);
}
Expand Down
4 changes: 4 additions & 0 deletions tests/cli/snapshots/cli__list_files_json_well_tested.snap
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ expression: "String::from_utf8_lossy(&output.stdout)"
{
"package": "cargo-mutants-testdata-well-tested",
"path": "src/struct_with_lifetime.rs"
},
{
"package": "cargo-mutants-testdata-well-tested",
"path": "src/traits.rs"
}
]

1 change: 1 addition & 0 deletions tests/cli/snapshots/cli__list_files_text_well_tested.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ src/simple_fns.rs
src/slices.rs
src/static_item.rs
src/struct_with_lifetime.rs
src/traits.rs

90 changes: 90 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 @@ -3789,6 +3789,96 @@ expression: buf
"line": 15
}
}
},
{
"file": "src/traits.rs",
"function": {
"function_name": "Something::is_three",
"return_type": "-> bool",
"span": {
"end": {
"column": 6,
"line": 6
},
"start": {
"column": 5,
"line": 4
}
}
},
"genre": "FnValue",
"package": "cargo-mutants-testdata-well-tested",
"replacement": "true",
"span": {
"end": {
"column": 15,
"line": 5
},
"start": {
"column": 9,
"line": 5
}
}
},
{
"file": "src/traits.rs",
"function": {
"function_name": "Something::is_three",
"return_type": "-> bool",
"span": {
"end": {
"column": 6,
"line": 6
},
"start": {
"column": 5,
"line": 4
}
}
},
"genre": "FnValue",
"package": "cargo-mutants-testdata-well-tested",
"replacement": "false",
"span": {
"end": {
"column": 15,
"line": 5
},
"start": {
"column": 9,
"line": 5
}
}
},
{
"file": "src/traits.rs",
"function": {
"function_name": "Something::is_three",
"return_type": "-> bool",
"span": {
"end": {
"column": 6,
"line": 6
},
"start": {
"column": 5,
"line": 4
}
}
},
"genre": "BinaryOperator",
"package": "cargo-mutants-testdata-well-tested",
"replacement": "!=",
"span": {
"end": {
"column": 13,
"line": 5
},
"start": {
"column": 11,
"line": 5
}
}
}
]
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,12 @@ src/slices.rs:4:5: replace pad -> &'a[Cow<'static, str>] with Vec::leak(vec![Cow
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])
src/static_item.rs:1:33: replace == with != in module
src/static_item.rs:1:33: replace == with !=
src/struct_with_lifetime.rs:15:9: replace Lex<'buf>::buf_len -> usize with 0
src/struct_with_lifetime.rs:15:9: replace Lex<'buf>::buf_len -> usize with 1
src/traits.rs:5:9: replace Something::is_three -> bool with true
src/traits.rs:5:9: replace Something::is_three -> bool with false
src/traits.rs:5:11: replace == with != in Something::is_three
```
## testdata/with_child_directories
Expand Down
90 changes: 90 additions & 0 deletions tests/cli/snapshots/cli__list_mutants_json_well_tested.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1279,5 +1279,95 @@ expression: "String::from_utf8_lossy(&output.stdout)"
"line": 15
}
}
},
{
"file": "src/traits.rs",
"function": {
"function_name": "Something::is_three",
"return_type": "-> bool",
"span": {
"end": {
"column": 6,
"line": 6
},
"start": {
"column": 5,
"line": 4
}
}
},
"genre": "FnValue",
"package": "cargo-mutants-testdata-well-tested",
"replacement": "true",
"span": {
"end": {
"column": 15,
"line": 5
},
"start": {
"column": 9,
"line": 5
}
}
},
{
"file": "src/traits.rs",
"function": {
"function_name": "Something::is_three",
"return_type": "-> bool",
"span": {
"end": {
"column": 6,
"line": 6
},
"start": {
"column": 5,
"line": 4
}
}
},
"genre": "FnValue",
"package": "cargo-mutants-testdata-well-tested",
"replacement": "false",
"span": {
"end": {
"column": 15,
"line": 5
},
"start": {
"column": 9,
"line": 5
}
}
},
{
"file": "src/traits.rs",
"function": {
"function_name": "Something::is_three",
"return_type": "-> bool",
"span": {
"end": {
"column": 6,
"line": 6
},
"start": {
"column": 5,
"line": 4
}
}
},
"genre": "BinaryOperator",
"package": "cargo-mutants-testdata-well-tested",
"replacement": "!=",
"span": {
"end": {
"column": 13,
"line": 5
},
"start": {
"column": 11,
"line": 5
}
}
}
]
Loading

0 comments on commit ab06125

Please sign in to comment.