-
Notifications
You must be signed in to change notification settings - Fork 250
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ignore): support disable directives in source code (#3072)
Add support `// Stryker disable` and `// Stryker restore` directives in source code. Some examples: ```js // Stryker disable all const foo = 2 + 40; // mutants here are ignored // Stryker restore all // Stryker disable BooleanLiteral const foo = true || false; // => BooleanLiteral mutant is ignored, but other mutants are tested. // Stryker restore all // Stryker disable next-line all const foo = true || false; // => ignored mutants const bar = true || false; // => mutants // Stryker disable next-line all: I don't care about this piece of code const foo = bar || baz; // ignored mutants with reason "I don't care about this piece of code" ``` Ignored mutants _do end up in your mutation report_, but are not placed or tested and they don't count towards your mutation score. ![image](https://user-images.githubusercontent.com/1828233/131149477-cf91ce19-9d87-4005-8af5-68e2a4b920c8.png)
- Loading branch information
Showing
18 changed files
with
842 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
--- | ||
title: Disable mutants | ||
custom_edit_url: https://github.com/stryker-mutator/stryker-js/edit/master/docs/disable-mutants.md | ||
--- | ||
|
||
During mutation testing, you might run into [equivalent mutants](../mutation-testing-elements/equivalent-mutants) or simply mutants that you are not interested in. | ||
|
||
## An example | ||
|
||
Given this code: | ||
|
||
```js | ||
function max(a, b) { | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
And these tests: | ||
|
||
```js | ||
describe('math', () => { | ||
it('should return 4 for max(4, 3)', () => { | ||
expect(max(4, 3)).eq(4); | ||
}); | ||
it('should return 4 for max(3, 4)', () => { | ||
expect(max(3, 4)).eq(4); | ||
}); | ||
}); | ||
``` | ||
|
||
Stryker will generate (amongst others) these mutants: | ||
|
||
```diff | ||
function max(a, b) { | ||
- return a < b ? b : a; | ||
+ return true ? b : a; // 👽 1 | ||
+ return false ? b : a; // 👽 2 | ||
+ return a >= b ? b : a; // 👽 3 | ||
} | ||
``` | ||
|
||
Mutant 1 and 2 are killed by the tests. However, mutant 3 isn't killed. In fact, mutant 3 _cannot be killed_ because the mutated code is equivalent to the original. It is therefore called _equivalent mutant_. | ||
|
||
![equivalent mutant](./images/disable-mutants-equivalent-mutant.png) | ||
|
||
## Disable mutants | ||
|
||
StrykerJS supports 2 ways to disable mutants. | ||
|
||
1. [Exclude the mutator](#exclude-the-mutator). | ||
2. [Using a `// Stryker disable` comment](#using-a--stryker-disable-comment). | ||
|
||
Disabled mutants will still end up in your report, but will get the `ignored` status. This means that they don't influence your mutation score, but are still visible if you want to look for them. This has no impact on the performance of mutation testing. | ||
|
||
## Exclude the mutator | ||
|
||
You can simply disable the mutator entirely. This is done by stating the mutator name in the `mutator.excludedMutations` array in your stryker configuration file: | ||
|
||
```json | ||
{ | ||
"mutator": { | ||
"excludedMutations": ["EqualityOperator"] | ||
} | ||
} | ||
``` | ||
|
||
The mutator name can be found in the clear-text or html report. | ||
|
||
If you've enabled the clear-text reporter (enabled by default), you can find the mutator name in your console: | ||
|
||
``` | ||
#3. [Survived] EqualityOperator | ||
src/math.js:3:12 | ||
- return a < b ? b : a; | ||
+ return a <= b ? b : a; | ||
Tests ran: | ||
math should return 4 for max(4, 3) | ||
math should return 4 for max(3, 4) | ||
``` | ||
|
||
In the html report, you will need to select the mutant you want to ignore, the drawer at the bottom has the mutator name in its title. | ||
|
||
However, disable the mutator for all your files is kind of a shotgun approach. Sure it works, but the mutator is now also disabled for other files and places. You probably want to use a comment instead. | ||
|
||
## Using a `// Stryker disable` comment. | ||
|
||
_Available since Stryker 5.4_ | ||
|
||
You can disable Stryker for a specific line of code using a comment. | ||
|
||
|
||
```js | ||
function max(a, b) { | ||
// Stryker disable next-line all | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
After running Stryker again, the report looks like this: | ||
|
||
![disable all](./images/disable-mutants-disable-all.png) | ||
|
||
This works, but is not exactly what we want. As you can see, all mutants on line 4 are not disabled. | ||
|
||
We can do better by specifying which mutator we want to ignore: | ||
|
||
```js | ||
function max(a, b) { | ||
// Stryker disable next-line EqualityOperator | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
We can even provide a custom reason for disabling this mutator behind a colon (`:`). This reason will also end up in your report (drawer below) | ||
|
||
```js | ||
function max(a, b) { | ||
// Stryker disable next-line EqualityOperator: The <= mutant results in an equivalent mutant | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
After running Stryker again, the report looks like this: | ||
|
||
![disable equality operator](./images/disable-mutants-disable-equality-operator.png) | ||
|
||
## Disable comment syntax | ||
|
||
_Available since Stryker 5.4_ | ||
|
||
The disabled comment is pretty powerful. Some more examples: | ||
|
||
Disable an entire file: | ||
|
||
```js | ||
// Stryker disable all | ||
function max(a, b) { | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
Disable parts of a file: | ||
|
||
```js | ||
// Stryker disable all | ||
function max(a, b) { | ||
return a < b ? b : a; | ||
} | ||
// Stryker restore all | ||
function min(a, b) { | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
Disable 2 mutators for an entire file with a custom reason: | ||
|
||
```js | ||
// Stryker disable EqualityOperator,ObjectLiteral: We'll implement tests for these next sprint | ||
function max(a, b) { | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
Disable all mutators for an entire file, but restore the EqualityOperator for 1 line: | ||
|
||
```js | ||
// Stryker disable all | ||
function max(a, b) { | ||
// Stryker restore EqualityOperator | ||
return a < b ? b : a; | ||
} | ||
``` | ||
|
||
The syntax looks like this: | ||
|
||
``` | ||
// Stryker [disable|restore] [next-line] *mutatorList*[: custom reason] | ||
``` | ||
|
||
The comment always starts with `// Stryker`, followed by either `disable` or `restore`. Next, you can specify whether or not this comment targets the `next-line`, or all lines from this point on. The next part is the mutator list. This is either a comma separated list of mutators, or the "all" text signaling this comment targets all mutators. Last is an optional custom reason text, which follows the colon. | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,41 @@ | ||
module.exports.add = function(num1, num2) { | ||
module.exports.add = function (num1, num2) { | ||
return num1 + num2; | ||
}; | ||
|
||
module.exports.addOne = function(number) { | ||
module.exports.addOne = function (number) { | ||
number++; | ||
return number; | ||
}; | ||
|
||
module.exports.negate = function(number) { | ||
module.exports.negate = function (number) { | ||
return -number; | ||
}; | ||
|
||
module.exports.notCovered = function(number) { | ||
module.exports.notCovered = function (number) { | ||
return number > 10; | ||
}; | ||
|
||
module.exports.isNegativeNumber = function(number) { | ||
module.exports.userNextLineIgnored = function (number) { | ||
// Stryker disable next-line all: Ignoring this on purpose | ||
return number > 10; | ||
}; | ||
|
||
// Stryker disable all | ||
module.exports.blockUserIgnored = function (number) { | ||
return number > 10; | ||
}; | ||
// Stryker restore all | ||
|
||
module.exports.userNextLineSpecificMutator = function (number) { | ||
// Stryker disable next-line BooleanLiteral, ConditionalExpression: Ignore boolean and conditions | ||
return true && number > 10; | ||
}; | ||
|
||
|
||
module.exports.isNegativeNumber = function (number) { | ||
var isNegative = false; | ||
if(number < 0){ | ||
if (number < 0) { | ||
isNegative = true; | ||
} | ||
return isNegative; | ||
}; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
"reporters": [ | ||
"clear-text", | ||
"html", | ||
"json", | ||
"event-recorder" | ||
], | ||
"plugins": [ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,52 @@ | ||
import { expectMetrics } from '../../../helpers'; | ||
import { expect } from 'chai'; | ||
import { MutantStatus } from 'mutation-testing-report-schema'; | ||
import { expectMetricsJson, readMutationTestingJsonResult } from '../../../helpers'; | ||
|
||
describe('After running stryker on jest-react project', () => { | ||
it('should report expected scores', async () => { | ||
await expectMetrics({ | ||
await expectMetricsJson({ | ||
killed: 8, | ||
ignored: 13, | ||
mutationScore: 66.67, | ||
ignored: 29, | ||
mutationScore: 53.33, | ||
}); | ||
}); | ||
|
||
/* | ||
-----------|---------|----------|-----------|------------|----------|---------| | ||
File | % score | # killed | # timeout | # survived | # no cov | # error | | ||
-----------|---------|----------|-----------|------------|----------|---------| | ||
All files | 53.33 | 8 | 0 | 0 | 7 | 0 |*/ | ||
|
||
|
||
it('should report mutants that are disabled by a comment with correct ignore reason', async () => { | ||
const actualMetricsResult = await readMutationTestingJsonResult(); | ||
const addResult = actualMetricsResult.childResults.find(file => file.name.endsWith('Add.js')).file!; | ||
const mutantsAtLine31 = addResult.mutants.filter(({ location }) => location.start.line === 31) | ||
const booleanLiteralMutants = mutantsAtLine31.filter(({mutatorName}) => mutatorName === 'BooleanLiteral'); | ||
const conditionalExpressionMutants = mutantsAtLine31.filter(({mutatorName}) => mutatorName === 'ConditionalExpression'); | ||
const equalityOperatorMutants = mutantsAtLine31.filter(({mutatorName}) => mutatorName === 'EqualityOperator'); | ||
booleanLiteralMutants.forEach((booleanMutant) => { | ||
expect(booleanMutant.status).eq(MutantStatus.Ignored); | ||
expect(booleanMutant.statusReason).eq('Ignore boolean and conditions'); | ||
}); | ||
conditionalExpressionMutants.forEach((conditionalMutant) => { | ||
expect(conditionalMutant.status).eq(MutantStatus.Ignored); | ||
expect(conditionalMutant.statusReason).eq('Ignore boolean and conditions'); | ||
}); | ||
|
||
equalityOperatorMutants.forEach((equalityMutant) => { | ||
expect(equalityMutant.status).eq(MutantStatus.NoCoverage); | ||
}); | ||
}); | ||
|
||
it('should report mutants that result from excluded mutators with the correct ignore reason', async () => { | ||
const actualMetricsResult = await readMutationTestingJsonResult(); | ||
const circleResult = actualMetricsResult.childResults.find(file => file.name.endsWith('Circle.js')).file!; | ||
const mutantsAtLine3 = circleResult.mutants.filter(({ location }) => location.start.line === 3) | ||
|
||
mutantsAtLine3.forEach((mutant) => { | ||
expect(mutant.status).eq(MutantStatus.Ignored); | ||
expect(mutant.statusReason).eq('Ignored because of excluded mutation "ArithmeticOperator"'); | ||
}); | ||
/* | ||
-----------|---------|----------|-----------|------------|----------|---------| | ||
File | % score | # killed | # timeout | # survived | # no cov | # error | | ||
-----------|---------|----------|-----------|------------|----------|---------| | ||
All files | 66.67 | 8 | 0 | 0 | 4 | 0 |*/ | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.