Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(in place): support in place mutation (#2706)
Add `--inPlace` flag (or equivalent in stryker.conf.json file). When `inPlace` is `true` (default is `false`), mutations are written "in place", overriding the user's own files. This way, Stryker can support more exotic project configurations, where even copying to a temp directory breaks the test run. For example, when a project relies on [app-root-path](https://www.npmjs.com/package/app-root-path) or uses the [`NODE_PATH`](https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_loading_from_the_global_folders) environment variable. Support for `--inPlace` added in the `Sandbox` class. For each file that changes, it writes a backup to a ".stryker-tmp/backup123" directory. After mutation testing completes, the backup files are recovered from the backup directory. Stryker tries its best to recover the backup files and when the process exits unexpectantly, for example, using `CTRL+C` or by closing the window. This behavior is a best-effort since Stryker can't do anything for you when you kill it from the task manager or with `kill -9`.
- Loading branch information
Showing
41 changed files
with
820 additions
and
270 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"require": "./test/helpers/testSetup.js", | ||
"spec": ["test/unit/*.js"] | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,15 @@ | ||
{ | ||
"name": "in-place", | ||
"version": "0.0.0", | ||
"private": true, | ||
"description": "A module to perform an integration test", | ||
"main": "index.js", | ||
"scripts": { | ||
"pretest": "rimraf \"reports\"", | ||
"test": "mocha --no-config --require ../../tasks/ts-node-register.js --no-timeout verify/*.ts", | ||
"test:unit": "mocha", | ||
"test:mutation": "stryker run" | ||
}, | ||
"author": "", | ||
"license": "ISC" | ||
} |
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,24 @@ | ||
module.exports.add = function(num1, num2) { | ||
return num1 + num2; | ||
}; | ||
|
||
module.exports.addOne = function(number) { | ||
number++; | ||
return number; | ||
}; | ||
|
||
module.exports.negate = function(number) { | ||
return -number; | ||
}; | ||
|
||
module.exports.notCovered = function(number) { | ||
return number > 10; | ||
}; | ||
|
||
module.exports.isNegativeNumber = function(number) { | ||
var isNegative = false; | ||
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 |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", | ||
"testRunner": "mocha", | ||
"concurrency": 2, | ||
"coverageAnalysis": "perTest", | ||
"reporters": [ | ||
"clear-text", | ||
"html", | ||
"event-recorder" | ||
], | ||
"plugins": [ | ||
"@stryker-mutator/mocha-runner" | ||
], | ||
"inPlace": true | ||
} |
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 @@ | ||
exports.mochaHooks = { | ||
beforeAll() { | ||
global.expect = require('chai').expect; | ||
} | ||
} |
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,52 @@ | ||
var addModule = require('../../src/Add'); | ||
var add = addModule.add; | ||
var addOne = addModule.addOne; | ||
var isNegativeNumber = addModule.isNegativeNumber; | ||
var negate = addModule.negate; | ||
var notCovered = addModule.notCovered; | ||
|
||
describe('Add', function() { | ||
it('should be able to add two numbers', function() { | ||
var num1 = 2; | ||
var num2 = 5; | ||
var expected = num1 + num2; | ||
|
||
var actual = add(num1, num2); | ||
|
||
expect(actual).to.be.equal(expected); | ||
}); | ||
|
||
it('should be able 1 to a number', function() { | ||
var number = 2; | ||
var expected = 3; | ||
|
||
var actual = addOne(number); | ||
|
||
expect(actual).to.be.equal(expected); | ||
}); | ||
|
||
it('should be able negate a number', function() { | ||
var number = 2; | ||
var expected = -2; | ||
|
||
var actual = negate(number); | ||
|
||
expect(actual).to.be.equal(expected); | ||
}); | ||
|
||
it('should be able to recognize a negative number', function() { | ||
var number = -2; | ||
|
||
var isNegative = isNegativeNumber(number); | ||
|
||
expect(isNegative).to.be.true; | ||
}); | ||
|
||
it('should be able to recognize that 0 is not a negative number', function() { | ||
var number = 0; | ||
|
||
var isNegative = isNegativeNumber(number); | ||
|
||
expect(isNegative).to.be.false; | ||
}); | ||
}); |
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 @@ | ||
const { existsSync } = require('fs'); | ||
const path = require('path'); | ||
|
||
describe('wait', () =>{ | ||
it('should wait until `.lock` is removed', async () => { | ||
while(existsSync(path.resolve(__dirname, '..', '..', '.lock'))){ | ||
await sleep(10); | ||
} | ||
}); | ||
}) | ||
async function sleep(n) { | ||
return new Promise(res => setTimeout(res, n)); | ||
} |
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,69 @@ | ||
import { expectMetrics } from '../../../helpers'; | ||
import { promises as fsPromises } from 'fs'; | ||
import chai from 'chai'; | ||
import execa from 'execa'; | ||
import rimraf from 'rimraf'; | ||
import path from 'path'; | ||
import { promisify } from 'util'; | ||
import { it } from 'mocha'; | ||
import chaiAsPromised from 'chai-as-promised'; | ||
|
||
chai.use(chaiAsPromised); | ||
const expect = chai.expect; | ||
|
||
const rm = promisify(rimraf); | ||
|
||
const rootResolve: typeof path.resolve = path.resolve.bind(path, __dirname, '..'); | ||
|
||
describe('in place', () => { | ||
|
||
let originalAddJSContent: string; | ||
|
||
|
||
function readAddJS(): Promise<string> { | ||
return fsPromises.readFile(rootResolve('src', 'Add.js'), 'utf-8'); | ||
} | ||
|
||
before(async () => { | ||
originalAddJSContent = await readAddJS(); | ||
}) | ||
|
||
|
||
afterEach(async () => { | ||
await rm(rootResolve('reports')); | ||
await rm(rootResolve('.lock')); | ||
}) | ||
|
||
it('should reset files after a successful run', async () => { | ||
execa.sync('stryker', ['run']); | ||
const addJSContent = await fsPromises.readFile(rootResolve('src', 'Add.js'), 'utf-8'); | ||
expect(addJSContent).eq(originalAddJSContent); | ||
}); | ||
|
||
it('should report correct score', async () => { | ||
execa.sync('stryker', ['run']); | ||
await expectMetrics({ mutationScore: 73.68 }); | ||
}); | ||
|
||
it('should also reset the files if Stryker exits unexpectedly', async () => { | ||
// Arrange | ||
let addJSMutatedContent: string; | ||
await fsPromises.writeFile(rootResolve('.lock'), ''); // this will lock the test run completion | ||
const onGoingStrykerRun = execa('node', [path.resolve('..', '..', 'node_modules', '.bin', 'stryker'), 'run']); | ||
onGoingStrykerRun.stdout.on('data', async (data) => { | ||
if (data.toString().includes('Starting initial test run')) { | ||
addJSMutatedContent = await readAddJS(); | ||
|
||
// Now, mr bond, it is time to die! | ||
onGoingStrykerRun.kill(); | ||
} | ||
}); | ||
|
||
// Act | ||
await expect(onGoingStrykerRun).rejected; | ||
|
||
// Assert | ||
expect(await readAddJS()).eq(originalAddJSContent); | ||
expect(addJSMutatedContent).not.eq(originalAddJSContent); | ||
}); | ||
}); |
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
Oops, something went wrong.