-
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(instrumenter): add mutant placers (#2224)
Add mutant placers. * `switchCaseMutantPlacer`: places a mutant in a switch case * `conditionalExpressionMutantPacer`: places a mutant in a conditional expression
- Loading branch information
Showing
13 changed files
with
538 additions
and
136 deletions.
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
packages/instrumenter/src/mutant-placers/conditional-expression-mutant-placer.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,33 @@ | ||
import { NodePath, types } from '@babel/core'; | ||
|
||
import { Mutant } from '../mutant'; | ||
import { createMutatedAst, mutantTestExpression, mutationCoverageSequenceExpression } from '../util/syntax-helpers'; | ||
|
||
import { MutantPlacer } from './mutant-placer'; | ||
|
||
/** | ||
* Places the mutants with a conditional expression: `global.activeMutant === 1? mutatedCode : regularCode`; | ||
*/ | ||
const conditionalExpressionMutantPlacer: MutantPlacer = (path: NodePath, mutants: Mutant[]): boolean => { | ||
if (path.isExpression() && !path.parentPath.isObjectProperty() && !path.parentPath.isTaggedTemplateExpression()) { | ||
// First calculated the mutated ast before we start to apply mutants. | ||
const appliedMutants = mutants.map((mutant) => ({ | ||
mutant, | ||
ast: createMutatedAst<types.BinaryExpression>(path as NodePath<types.BinaryExpression>, mutant), | ||
})); | ||
|
||
// Second add the mutation coverage expression | ||
path.replaceWith(mutationCoverageSequenceExpression(mutants, path.node)); | ||
|
||
// Now apply the mutants | ||
for (const appliedMutant of appliedMutants) { | ||
path.replaceWith(types.conditionalExpression(mutantTestExpression(appliedMutant.mutant.id), appliedMutant.ast, path.node)); | ||
} | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}; | ||
|
||
// Export it after initializing so `fn.name` is properly set | ||
export { conditionalExpressionMutantPlacer }; |
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,33 @@ | ||
export * from './mutant-placer'; | ||
import { NodePath } from '@babel/core'; | ||
|
||
import { Mutant } from '../mutant'; | ||
|
||
import { MutantPlacer } from './mutant-placer'; | ||
import { switchCaseMutantPlacer } from './switch-case-mutant-placer'; | ||
import { conditionalExpressionMutantPlacer } from './conditional-expression-mutant-placer'; | ||
|
||
export const MUTANT_PLACERS = Object.freeze([conditionalExpressionMutantPlacer, switchCaseMutantPlacer]); | ||
|
||
/** | ||
* Represents a mutant placer, tries to place a mutant in the AST with corresponding mutation switch and mutant covering expression | ||
* @see https://github.com/stryker-mutator/stryker/issues/1514 | ||
* @param node The ast node to try and replace with a mutated | ||
* @param mutants The mutants to place in the AST node | ||
*/ | ||
export function placeMutant(node: NodePath, mutants: Mutant[], mutantPlacers: readonly MutantPlacer[] = MUTANT_PLACERS) { | ||
if (mutants.length) { | ||
for (const placer of mutantPlacers) { | ||
try { | ||
if (placer(node, mutants)) { | ||
return true; | ||
} | ||
} catch (error) { | ||
throw new Error( | ||
`Error while placing mutants on ${node.node.loc?.start.line}:${node.node.loc?.start.column} with ${placer.name}. ${error.stack}` | ||
); | ||
} | ||
} | ||
} | ||
return 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,5 @@ | ||
import { NodePath } from '@babel/core'; | ||
|
||
import { Mutant } from '../mutant'; | ||
|
||
export type MutantPlacer = (node: NodePath, mutants: Mutant[]) => boolean; |
36 changes: 36 additions & 0 deletions
36
packages/instrumenter/src/mutant-placers/switch-case-mutant-placer.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,36 @@ | ||
import { types } from '@babel/core'; | ||
|
||
import { memberExpressionChain, createMutatedAst, mutationCoverageSequenceExpression, ID } from '../util/syntax-helpers'; | ||
|
||
import { MutantPlacer } from './mutant-placer'; | ||
|
||
const switchCaseMutantPlacer: MutantPlacer = (path, mutants) => { | ||
if (path.isStatement()) { | ||
// First calculate the mutated ast before we start to apply mutants. | ||
const appliedMutants = mutants.map((mutant) => ({ | ||
mutant, | ||
ast: createMutatedAst(path, mutant), | ||
})); | ||
|
||
// Add switch statement | ||
path.replaceWith( | ||
types.blockStatement([ | ||
types.switchStatement(memberExpressionChain(ID.GLOBAL, ID.ACTIVE_MUTANT), [ | ||
...appliedMutants.map(({ ast, mutant }) => types.switchCase(types.numericLiteral(mutant.id), [ast, types.breakStatement()])), | ||
types.switchCase(null, [ | ||
// Add mutation covering statement | ||
types.expressionStatement(mutationCoverageSequenceExpression(mutants)), | ||
path.node, | ||
types.breakStatement(), | ||
]), | ||
]), | ||
]) | ||
); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}; | ||
|
||
// Export it after initializing so `fn.name` is properly set | ||
export { switchCaseMutantPlacer }; |
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 was deleted.
Oops, something went wrong.
Oops, something went wrong.