More granular FnValue mutations
#360
john-h-kastner
started this conversation in
Ideas
Replies: 1 comment
-
|
Yep, this sounds like a great extension, and a patch or series of patches to do it would be welcome. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This idea is an extension to the current function value replacement mutations. In brief: instead of replacing whole function bodies
cargo-mutantswould replace one branch at a time to obtain more granular mutants. I've implemented and been trying out these changes on a local build already, so I can polish everything and put up a PR pretty quickly if you like the idea.Motivating Example
A function recursively visiting a data structure will often look something like this.
The current function value mutation replace the whole function body.
fn eval(e: Expr) -> i32 { - match e { - Num(n) => n, - Plus(l, r) => eval(l) + eval(r), - Minus(l, r) => eval(l) - eval(r), - } + 0 }Killing this mutant doesn't tell us if our tests exercise all the branches. With this idea,
cargo-mutantswould instead find and replace each expression in "return position". In this function, it would mutate each branch of thematchindependently, so we could be confident that every branch is exercised by our tests.For example,
cargo-mutantswould generate a mutant replacing the expression in theMinusbranch. Detecting this mutant requires at least one test case properly exercisingMinuswhile the original mutant would be detected by any test properly exercising theevalfunction, even if it never constructed aMinus.fn eval(e: Expr) -> i32 { match e { Num(n) => n, Plus(l, r) => eval(l) + eval(r), - Minus(l, r) => eval(l) - eval(r), + Minus(l, r) => 0, } }Details
Extend function value rewriting is a recursive procedure with the following cases:
ifexpression, we recursively generate mutants for both branches, but do not rewrite the condition.matchexpression we recursively generate mutants for allmatcharms, but do not rewrite the matched expression.return_type_replacementsprocedure.In the following example, the block and
ifcases work together to allow replacing just the finalelsebranch without touching anything else. Note that thethenbranch uses the localb, which we have left untouched.fn foo(a: i32) -> i32 { let b = a - 1; let c = b - 1; if a == 0 { b } else { - c + 0 } }Implemented as a separate pass,
cargo-mutantsshould also find explicitreturnstatements and and mutates the returned value. I expect that early returns tend to handle uncommon cases and error conditions, so it is important that they are exercised by a test-suit. Implementing this properly requires some additional book-keeping to track if the visitor is inside a closure.fn foo(i: i32, j: i32) -> Option<i32> { if j == 0 { - return None; + return Some(0); } Some(i / j) }Drawbacks
There are some cases where the more granular mutation breaks type inference for local variable bindings. In the following function, Rust cannot infer a collection type for
sif it is not returned. The whole body function mutation deletess, so type inference is not an issue.fn foo(i : impl IntoIterator<Item = i32>) -> HashSet<i32> { let s = i.into_iter().collect(); - s + HashSet::new() }Beta Was this translation helpful? Give feedback.
All reactions