-
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(incremental): add incremental mode (#3609)
Add `--incremental` mode. With incremental mode active, Stryker will use the results from the previous run to determine which mutants need to be retested, yielding much faster results. You can enable incremental mode with `--incremental` (or using `"incremental": true` in the config file), These changes also introduce `--incrementalFile` and `--force` to help with several incremental use cases. See https://stryker-mutator.io/docs/stryker-js/incremental for more details
- Loading branch information
Showing
127 changed files
with
5,679 additions
and
625 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
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
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,92 @@ | ||
--- | ||
title: Incremental | ||
custom_edit_url: https://github.com/stryker-mutator/stryker-js/edit/master/docs/incremental.md | ||
--- | ||
|
||
StrykerJS is fast! It uses advanced techniques to speed up mutation testing, like coverage analysis, hit limit counters, and hot-reload. However, mutation testing still takes time. You might want to speed things up further for larger code bases, fast feedback, or Continuous Integration (CI) scenarios. | ||
|
||
## Incremental mode | ||
|
||
_Available since Stryker 6.2_ | ||
|
||
StrykerJS supports incremental mutation testing to speed things up further. When running in [`--incremental`](./configuration.md#incremental-boolean) mode, StrykerJS will track the changes you make to your code and tests and only runs mutation testing on the changed code. But, of course, it will still provide you with the full mutation report at the end! | ||
|
||
You enable incremental mode with the `--incremental` flag: | ||
|
||
``` | ||
npx stryker run --incremental | ||
``` | ||
|
||
_Setting `"incremental": true` in your stryker.conf.json file is also supported_ | ||
|
||
StrykerJS stores the previous result in a "reports/stryker-incremental.json" file (determined by the [--incrementalFile](./configuration.md#incrementalfile-string) option). The next time StrykerJS runs, it will read this JSON file and try to reuse as much of it as possible. | ||
|
||
Reuse is possible when: | ||
- A mutant was "Killed"; the culprit test still exists, and it didn't change. | ||
- A mutant was not "Killed"; no new test covers it, and no tests changed. | ||
|
||
StrykerJS will do a git-like diff of your code and test files to the previous version it finds in the incremental report file in order to match the mutants and tests to the current version of the code. | ||
|
||
You can see the statistics of the incremental analysis right after the dry run is performed. It looks like this: | ||
|
||
``` | ||
Mutants: 1 files changed (+2 -2) | ||
Tests: 2 files changed (+22 -21) | ||
Result: 3731 of 3965 mutant result(s). | ||
``` | ||
|
||
Here you can see that: | ||
- One file with mutants changed (2 mutants added, 2 mutants removed) | ||
- Two test files changed (22 tests added and 21 tests removed) | ||
- In total, Stryker will reuse 3731 mutant results, and only 234 mutants need to run. | ||
|
||
**Note**: The dry run remains required; as it discovers tests, mutation coverage per test, and ensures Stryker runs successfully when no mutant is active. | ||
|
||
## Limitations | ||
|
||
Running in incremental mode, Stryker will do its best to produce an accurate mutation testing report. However, there are some limitations here: | ||
- Stryker will not detect any changes you've made in files other than mutated files and test files. | ||
- Detecting test file changes is only supported if the test runner plugin supports reporting the test files. (see support table below) | ||
- Detecting test changes is only supported if the test runner plugin supports reporting test locations. (see support table below) | ||
- Any other changes to your environment are not detected, such as updates to other files, updated (dev) dependencies, changes to environment variables, changes to `.snap` files, readme files, etc. | ||
- [Static mutants](../../mutation-testing-elements/static-mutants/) don't have test coverage; thus, Stryker won't detect test changes for them. | ||
|
||
| Test runner plugin | Test reporting | | ||
| ------------------ | ------------------------------- | | ||
| 🃏 Jest | ✅ Full | | ||
| ☕ Mocha | ⚠ Tests per file without location | | ||
| 🟣 Jasmine | ⚠ Test names only | | ||
| 🔵 Karma | ⚠ Test names only | | ||
| 🥒 CucumberJS | ✅ Full | | ||
| ▶ Command | ❌ Nothing | | ||
|
||
You can use this table to understand why StrykerJS decides not to rerun a specific mutant even though you've changed tests covering that mutant. | ||
|
||
- **Full** | ||
Tests are reported together with their exact locations. Stryker will do a detailed diff to see which specific tests changed. | ||
- **Tests per file without location** | ||
Stryker knows from which files the tests originated, but not their exact locations. Therefore, Stryker assumes all tests inside a file changed when that file changed. | ||
- **Test names only** | ||
Stryker can't determine where the tests are located and thus cannot detect when a test changed. As a result, Stryker will only see test changes for tests that are added or removed. | ||
- **Nothing** | ||
All test details are unknown incremental mode will only detect changes in mutants, not their tests. | ||
|
||
## Forcing reruns | ||
|
||
With these limitations in mind, you can probably imagine a scenario where you want to force specific mutants to run while using incremental mode. You can do this with `--force`. If you run `--force`, you tell StrykerJS to rerun all mutants in scope, regardless of the incremental file. | ||
|
||
Using `--force` is especially beneficial when combined with a custom `--mutate` pattern. I.e., if you only want to rerun the mutants in `src/app.js`, you use: | ||
|
||
``` | ||
npx stryker run --incremental --force --mutate src/app.js | ||
``` | ||
|
||
You can even specify individual lines to mutate: | ||
|
||
``` | ||
npx stryker run --incremental --force --mutate src/app.js:5-7 | ||
``` | ||
|
||
In this example, you tell Stryker to only run mutation testing for lines 5 through 7 in the `src/app.js` file and update the incremental report. | ||
|
||
Using the combination of `--incremental` with a custom `--mutate` pattern, StrykerJS will not remove mutants that are not in scope and still report a full mutation report. |
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,4 @@ | ||
{ | ||
"require": ["spec/chai-register.js", "spec/chai-setup.js"], | ||
"spec": ["spec/*.spec.js"] | ||
} |
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,4 @@ | ||
export default { | ||
publishQuiet: true, | ||
import: ['features/**/*.js'] | ||
} |
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,10 @@ | ||
Feature: concat | ||
|
||
Scenario: concat a and b | ||
Given input 'foo' | ||
When concat with 'bar' | ||
Then the result should be 'foobar' | ||
|
||
Scenario: greet me | ||
When I greet 'me' | ||
Then the result should be '👋 me' |
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,10 @@ | ||
Feature: concat | ||
|
||
Scenario: concat a and b | ||
Given input 'foo' | ||
When concat with 'bar' | ||
Then the result should be 'foobar' | ||
|
||
Scenario: greet me | ||
When I greet 'me' | ||
Then the result should be '👋 me 🙋♀️' |
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,10 @@ | ||
Feature: concat | ||
|
||
Scenario: concat a and b | ||
Given input 'foo' | ||
When concat with 'bar' | ||
Then the result should be 'foobar' | ||
|
||
Scenario: greet me | ||
When I greet 'me' | ||
Then the result should be '👋 me' |
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,7 @@ | ||
Feature: Log | ||
|
||
Scenario: log | ||
Given input 1 | ||
When I log | ||
Then there should be no Error | ||
|
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,13 @@ | ||
Feature: Math | ||
|
||
Scenario: add | ||
Given input 1 | ||
When add with 0 | ||
Then the result should be 1 | ||
|
||
Scenario: multiply | ||
Given input 2 | ||
When multiplied with 0 | ||
Then the result should be 0 | ||
|
||
# Missing feature for `addOne` -> surviving / noCoverage mutant |
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,13 @@ | ||
Feature: Math | ||
|
||
Scenario: add | ||
Given input 1 | ||
When add with 0 | ||
Then the result should be 1 | ||
|
||
Scenario: multiply | ||
Given input 2 | ||
When multiplied with 3 | ||
Then the result should be 6 | ||
|
||
# Missing feature for `addOne` -> surviving / noCoverage mutant |
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,13 @@ | ||
Feature: Math | ||
|
||
Scenario: add | ||
Given input 1 | ||
When add with 0 | ||
Then the result should be 1 | ||
|
||
Scenario: multiply | ||
Given input 2 | ||
When multiplied with 0 | ||
Then the result should be 0 | ||
|
||
# Missing feature for `addOne` -> surviving / noCoverage mutant |
46 changes: 46 additions & 0 deletions
46
e2e/test/incremental/features/step-definitions/general-steps.js
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,46 @@ | ||
import { Given, When, Then } from '@cucumber/cucumber'; | ||
import { expect } from 'chai'; | ||
import { concat, greet } from '../../src/concat.js'; | ||
import { log } from '../../src/log.js'; | ||
import { add, multiply } from '../../src/math.js'; | ||
|
||
|
||
Given('input {string}', function (input) { | ||
this.input = input; | ||
}); | ||
|
||
Given('input {int}', function (input) { | ||
this.input = input; | ||
}); | ||
|
||
When('concat with {string}', function (other) { | ||
this.result = concat(this.input, other); | ||
}); | ||
|
||
When('I greet {string}', function (subject) { | ||
this.result = greet(subject); | ||
}); | ||
|
||
When('add with {int}', function (other) { | ||
this.result = add(this.input, other); | ||
}); | ||
|
||
When('multiplied with {int}', function (other) { | ||
this.result = multiply(this.input, other); | ||
}); | ||
|
||
Then('the result should be {string}', function (expected) { | ||
expect(this.result).eq(expected); | ||
}); | ||
Then('the result should be {int}', function (expected) { | ||
expect(this.result).eq(expected); | ||
}); | ||
|
||
When('I log', function () { | ||
// Write code here that turns the phrase above into concrete actions | ||
this.result = log(this.input); | ||
}); | ||
|
||
Then('there should be no Error', function () { | ||
}); | ||
|
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,7 @@ | ||
{ | ||
"spec_files": [ | ||
"*.spec.js" | ||
], | ||
"spec_dir": "spec", | ||
"jsLoader": "import" | ||
} |
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,5 @@ | ||
{ | ||
"testEnvironment": "node", | ||
"testMatch": ["<rootDir>/spec/*.spec.js"], | ||
"transform": {} | ||
} |
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,22 @@ | ||
// Karma configuration | ||
// Generated on Tue Nov 30 2021 09:57:14 GMT+0100 (Central European Standard Time) | ||
|
||
module.exports = function (config) { | ||
config.set({ | ||
basePath: '', | ||
frameworks: ['chai', 'jasmine'], | ||
files: [ | ||
{ pattern: 'src/**/*.js', type: 'module' }, | ||
{ pattern: 'spec/chai-setup.js', type: 'module' }, | ||
{ pattern: 'spec/**/*.spec.js', type: 'module' }, | ||
], | ||
reporters: ['progress'], | ||
port: 9876, | ||
colors: true, | ||
logLevel: config.LOG_INFO, | ||
autoWatch: false, | ||
browsers: ['ChromeHeadless'], | ||
singleRun: true, | ||
concurrency: Infinity, | ||
}); | ||
}; |
Oops, something went wrong.