-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(hit limit): infinite loop prevention in mocha-runner
Add infinite loop prevention using a hit counter to `@stryker-mutator/mocha-runner`. Co-authored-by: Nico Jansen <jansennico@gmail.com>
- Loading branch information
1 parent
9786a15
commit f5a7d1d
Showing
14 changed files
with
243 additions
and
8 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"spec": "test/**.mocha.spec.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
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 @@ | ||
const { loop } = require('../src/loop'); | ||
const { expect } = require('chai'); | ||
|
||
describe('loop', () => { | ||
it('should result in 15 for n=5 and a sum function', () => { | ||
let result = 0; | ||
loop(5, (n) => (result += n)); | ||
expect(result).to.eq(15); | ||
}); | ||
}); |
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
58 changes: 58 additions & 0 deletions
58
packages/mocha-runner/test/integration/timeout-on-infinite-loop.it.spec.ts
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,58 @@ | ||
import { testInjector, factory, assertions } from '@stryker-mutator/test-helpers'; | ||
import { expect } from 'chai'; | ||
|
||
import { createMochaOptions } from '../helpers/factories'; | ||
import { createMochaTestRunnerFactory, MochaTestRunner } from '../../src'; | ||
import { resolveTestResource } from '../helpers/resolve-test-resource'; | ||
|
||
describe('Infinite loop', () => { | ||
let sut: MochaTestRunner; | ||
|
||
beforeEach(async () => { | ||
const spec = [ | ||
resolveTestResource('infinite-loop-instrumented', 'infinite-loop.spec.js'), | ||
resolveTestResource('infinite-loop', 'infinite-loop.spec.js'), | ||
]; | ||
testInjector.options.mochaOptions = createMochaOptions({ spec }); | ||
sut = testInjector.injector.injectFunction(createMochaTestRunnerFactory('__stryker2__')); | ||
await sut.init(); | ||
}); | ||
|
||
it('should be able to recover using a hit counter', async () => { | ||
// Arrange | ||
const options = factory.mutantRunOptions({ | ||
activeMutant: factory.mutant({ id: '20' }), | ||
testFilter: ['should be able to break out of an infinite loop with a hit counter'], | ||
hitLimit: 10, | ||
}); | ||
|
||
// Act | ||
const result = await sut.mutantRun(options); | ||
|
||
// Assert | ||
assertions.expectTimeout(result); | ||
expect(result.reason).contains('Hit limit reached'); | ||
}); | ||
|
||
it('should reset hit counter state correctly between runs', async () => { | ||
const firstResult = await sut.mutantRun( | ||
factory.mutantRunOptions({ | ||
activeMutant: factory.mutant({ id: '20' }), | ||
testFilter: ['should be able to break out of an infinite loop with a hit counter'], | ||
hitLimit: 10, | ||
}) | ||
); | ||
const secondResult = await sut.mutantRun( | ||
factory.mutantRunOptions({ | ||
// 27 is a 'normal' mutant that should be killed | ||
activeMutant: factory.mutant({ id: '23' }), | ||
testFilter: ['should be able to break out of an infinite loop with a hit counter'], | ||
hitLimit: 10, | ||
}) | ||
); | ||
|
||
// Assert | ||
assertions.expectTimeout(firstResult); | ||
assertions.expectKilled(secondResult); | ||
}); | ||
}); |
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
84 changes: 84 additions & 0 deletions
84
packages/mocha-runner/testResources/infinite-loop-instrumented/infinite-loop.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,84 @@ | ||
// This file is generated with tasks/instrument-test-resources.js | ||
function stryNS_9fa48() { | ||
var g = new Function("return this")(); | ||
var ns = g.__stryker2__ || (g.__stryker2__ = {}); | ||
|
||
if (ns.activeMutant === undefined && g.process && g.process.env && g.process.env.__STRYKER_ACTIVE_MUTANT__) { | ||
ns.activeMutant = g.process.env.__STRYKER_ACTIVE_MUTANT__; | ||
} | ||
|
||
function retrieveNS() { | ||
return ns; | ||
} | ||
|
||
stryNS_9fa48 = retrieveNS; | ||
return retrieveNS(); | ||
} | ||
|
||
stryNS_9fa48(); | ||
|
||
function stryCov_9fa48() { | ||
var ns = stryNS_9fa48(); | ||
var cov = ns.mutantCoverage || (ns.mutantCoverage = { | ||
static: {}, | ||
perTest: {} | ||
}); | ||
|
||
function cover() { | ||
var c = cov.static; | ||
|
||
if (ns.currentTestId) { | ||
c = cov.perTest[ns.currentTestId] = cov.perTest[ns.currentTestId] || {}; | ||
} | ||
|
||
var a = arguments; | ||
|
||
for (var i = 0; i < a.length; i++) { | ||
c[a[i]] = (c[a[i]] || 0) + 1; | ||
} | ||
} | ||
|
||
stryCov_9fa48 = cover; | ||
cover.apply(null, arguments); | ||
} | ||
|
||
function stryMutAct_9fa48(id) { | ||
var ns = stryNS_9fa48(); | ||
|
||
function isActive(id) { | ||
if (ns.activeMutant === id) { | ||
if (ns.hitCount !== void 0 && ++ns.hitCount > ns.hitLimit) { | ||
throw new Error('Stryker: Hit count limit reached (' + ns.hitCount + ')'); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
stryMutAct_9fa48 = isActive; | ||
return isActive(id); | ||
} | ||
|
||
function loop(n, action) { | ||
if (stryMutAct_9fa48("16")) { | ||
{} | ||
} else { | ||
stryCov_9fa48("16"); | ||
let goOn = stryMutAct_9fa48("17") ? false : (stryCov_9fa48("17"), true); | ||
|
||
while (stryMutAct_9fa48("18") ? false : (stryCov_9fa48("18"), goOn)) { | ||
if (stryMutAct_9fa48("19")) { | ||
{} | ||
} else { | ||
stryCov_9fa48("19"); | ||
action(n); | ||
stryMutAct_9fa48("20") ? n++ : (stryCov_9fa48("20"), n--); | ||
goOn = stryMutAct_9fa48("24") ? n <= 0 : stryMutAct_9fa48("23") ? n >= 0 : stryMutAct_9fa48("22") ? false : stryMutAct_9fa48("21") ? true : (stryCov_9fa48("21", "22", "23", "24"), n > 0); | ||
} | ||
} | ||
} | ||
} | ||
|
||
module.exports = loop; |
17 changes: 17 additions & 0 deletions
17
packages/mocha-runner/testResources/infinite-loop-instrumented/infinite-loop.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,17 @@ | ||
var expect = require('chai').expect; | ||
var loop = require('./infinite-loop'); | ||
|
||
it('should handle an infinite loop as a timeout', () => { | ||
while (true); | ||
}); | ||
|
||
it('should be able to recover and test others', () => {}); | ||
|
||
it('should be able to break out of an infinite loop with a hit counter', () => { | ||
let total = 0; | ||
loop(5, (n) => { | ||
expect(n).not.eq(0); | ||
total += n; | ||
}); | ||
expect(total).eq(15); | ||
}); |
10 changes: 10 additions & 0 deletions
10
packages/mocha-runner/testResources/infinite-loop/infinite-loop.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 @@ | ||
function loop(n, action) { | ||
let goOn = true; | ||
while (goOn) { | ||
action(n); | ||
n--; | ||
goOn = n > 0; | ||
} | ||
} | ||
|
||
module.exports = loop; |
17 changes: 17 additions & 0 deletions
17
packages/mocha-runner/testResources/infinite-loop/infinite-loop.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,17 @@ | ||
var expect = require('chai').expect; | ||
var loop = require('./infinite-loop'); | ||
|
||
it('should handle an infinite loop as a timeout', () => { | ||
while (true); | ||
}); | ||
|
||
it('should be able to recover and test others', () => {}); | ||
|
||
it('should be able to break out of an infinite loop with a hit counter', () => { | ||
let total = 0; | ||
loop(5, (n) => { | ||
expect(n).not.eq(0); | ||
total += n; | ||
}); | ||
expect(total).eq(15); | ||
}); |
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