From a137a978ea7d8b8424703c9543ce01f6a0da818a Mon Sep 17 00:00:00 2001 From: Alex van Assem Date: Fri, 28 Apr 2017 14:48:25 +0200 Subject: [PATCH] feat(Mutators): Add Boolean substitution mutators (#294) Add boolean substitution mutator which mutates 3 things: - `true` -> `false` - `false` -> `true` - `!a` -> `a` --- package.json | 1 + packages/stryker/.npmignore | 1 - packages/stryker/src/MutatorOrchestrator.ts | 2 + .../mutators/BooleanSubstitutionMutator.ts | 26 +++++++ packages/stryker/src/stryker-cli.ts | 10 +-- packages/stryker/stryker.conf.js | 2 +- .../BooleanSubstitutionMutatorSpec.ts | 74 +++++++++++++++++++ sampleProject/src/Add.js | 16 ++++ sampleProject/test/AddSpec.js | 12 +++ 9 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 packages/stryker/src/mutators/BooleanSubstitutionMutator.ts create mode 100644 packages/stryker/test/unit/mutators/BooleanSubstitutionMutatorSpec.ts diff --git a/package.json b/package.json index 63997c84ad..50691c8302 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "devDependencies": { "@types/chai-as-promised": "0.0.29", "@types/chalk": "^0.4.28", + "@types/commander": "^2.9.0", "@types/escodegen": "0.0.6", "@types/esprima": "^2.1.31", "@types/estree": "0.0.32", diff --git a/packages/stryker/.npmignore b/packages/stryker/.npmignore index 28360cc04c..9f32e6249c 100644 --- a/packages/stryker/.npmignore +++ b/packages/stryker/.npmignore @@ -16,7 +16,6 @@ testResources .tmp .tscache .vscode -stryker.conf.js tsconfig.json /coverage reports \ No newline at end of file diff --git a/packages/stryker/src/MutatorOrchestrator.ts b/packages/stryker/src/MutatorOrchestrator.ts index 6bd2f2d0ff..c12332025b 100644 --- a/packages/stryker/src/MutatorOrchestrator.ts +++ b/packages/stryker/src/MutatorOrchestrator.ts @@ -5,6 +5,7 @@ import RemoveConditionalsMutator from './mutators/RemoveConditionalsMutator'; import UnaryOperatorMutator from './mutators/UnaryOperatorMutator'; import UpdateOperatorMutator from './mutators/UpdateOperatorMutator'; import ArrayDeclaratorMutator from './mutators/ArrayDeclaratorMutator'; +import BooleanSubstitutionMutator from './mutators/BooleanSubstitutionMutator'; import { Mutator, MutatorFactory } from 'stryker-api/mutant'; import { SourceFile } from 'stryker-api/report'; import Mutant from './Mutant'; @@ -86,6 +87,7 @@ export default class MutatorOrchestrator { mutatorFactory.register('UnaryOperator', UnaryOperatorMutator); mutatorFactory.register('UpdateOperator', UpdateOperatorMutator); mutatorFactory.register('ArrayDeclarator', ArrayDeclaratorMutator); + mutatorFactory.register('BooleanSubstitution', BooleanSubstitutionMutator); } /** diff --git a/packages/stryker/src/mutators/BooleanSubstitutionMutator.ts b/packages/stryker/src/mutators/BooleanSubstitutionMutator.ts new file mode 100644 index 0000000000..959d304610 --- /dev/null +++ b/packages/stryker/src/mutators/BooleanSubstitutionMutator.ts @@ -0,0 +1,26 @@ +import { Mutator, IdentifiedNode } from 'stryker-api/mutant'; +import { Syntax } from 'esprima'; + +export default class BooleanSubstitutionMutator implements Mutator { + name = 'BooleanSubstitution'; + + applyMutations(node: IdentifiedNode, copy: (obj: T, deep?: boolean) => T): IdentifiedNode[] { + const nodes: IdentifiedNode[] = []; + + // !a -> a + if (node.type === Syntax.UnaryExpression && node.operator === '!') { + let mutatedNode = copy(node.argument) as IdentifiedNode; + mutatedNode.nodeID = node.nodeID; + nodes.push(mutatedNode); + } + + // true -> false or false -> true + if (node.type === Syntax.Literal && typeof node.value === 'boolean') { + let mutatedNode = copy(node); + mutatedNode.value = !mutatedNode.value; + nodes.push(mutatedNode); + } + return nodes; + } + +} \ No newline at end of file diff --git a/packages/stryker/src/stryker-cli.ts b/packages/stryker/src/stryker-cli.ts index 71ba1c52f2..1604cd5512 100644 --- a/packages/stryker/src/stryker-cli.ts +++ b/packages/stryker/src/stryker-cli.ts @@ -1,4 +1,4 @@ -const program = require('commander'); +import * as program from 'commander'; import { CONFIG_SYNTAX_HELP } from './ConfigReader'; import Stryker from './Stryker'; import * as log4js from 'log4js'; @@ -40,12 +40,12 @@ Optional location to the stryker.conf.js file as last argument. That file should log4js.setGlobalLogLevel(program['logLevel'] || 'info'); // Cleanup commander state -delete program.options; -delete program.rawArgs; +delete program['options']; +delete program['rawArgs']; delete program.args; delete program.Command; delete program.Option; -delete program.commands; +delete program['commands']; for (let i in program) { if (i.charAt(0) === '_') { delete program[i]; @@ -53,7 +53,7 @@ for (let i in program) { } if (strykerConfig) { - program.configFile = strykerConfig; + program['configFile'] = strykerConfig; } const commands: { [cmd: string]: () => void } = { diff --git a/packages/stryker/stryker.conf.js b/packages/stryker/stryker.conf.js index e5465c54d4..116216b37f 100644 --- a/packages/stryker/stryker.conf.js +++ b/packages/stryker/stryker.conf.js @@ -8,4 +8,4 @@ module.exports = function (config) { logLevel: 'info', plugins: ['stryker-mocha-runner'] }); -}; +}; \ No newline at end of file diff --git a/packages/stryker/test/unit/mutators/BooleanSubstitutionMutatorSpec.ts b/packages/stryker/test/unit/mutators/BooleanSubstitutionMutatorSpec.ts new file mode 100644 index 0000000000..944e7bf562 --- /dev/null +++ b/packages/stryker/test/unit/mutators/BooleanSubstitutionMutatorSpec.ts @@ -0,0 +1,74 @@ +import { expect } from 'chai'; +import * as estree from 'estree'; +import { Identified, IdentifiedNode } from 'stryker-api/mutant'; +import BooleanSubstitutionMutator from '../../../src/mutators/BooleanSubstitutionMutator'; +import { parse, generate } from '../../../src/utils/parserUtils'; +import { copy } from '../../../src/utils/objectUtils'; + +describe('BooleanSubstitutionMutator', () => { + let sut: BooleanSubstitutionMutator; + + beforeEach(() => { + sut = new BooleanSubstitutionMutator(); + }); + + it('should mutate when supplied a expression with !', () => { + // Arrange + const program = parse(`!a.a()`); + const nodeUnaryExpression = ((program.body[0] as estree.ExpressionStatement).expression as estree.UnaryExpression & Identified); + + // Act + const result = sut.applyMutations(nodeUnaryExpression, copy)[0] as estree.Expression & Identified; + + // Assert + expect(result).to.be.ok; + expect(result.type).to.be.eq(nodeUnaryExpression.argument.type); + expect(result.nodeID).to.be.eq(nodeUnaryExpression.nodeID); + expect(generate(result)).to.be.eq('a.a()'); + }); + + it('should mutate true -> false', () => { + // Arrange + const program = parse(`true`); + const nodeLiteral = ((program.body[0] as estree.ExpressionStatement).expression as estree.Literal & Identified); + + // Act + const result = sut.applyMutations(nodeLiteral, copy)[0] as estree.Literal & Identified; + + // Assert + expect(result).to.be.ok; + expect(result.value).to.be.false; + expect(result.nodeID).to.be.eq(nodeLiteral.nodeID); + expect(generate(result)).to.be.eq('false'); + }); + + it('should mutate false -> true', () => { + // Arrange + const program = parse(`false`); + const nodeLiteral = ((program.body[0] as estree.ExpressionStatement).expression as estree.Literal & Identified); + + // Act + const result = sut.applyMutations(nodeLiteral, copy)[0] as estree.Literal & Identified; + + // Assert + expect(result).to.be.ok; + expect(result.value).to.be.true; + expect(result.nodeID).to.be.eq(nodeLiteral.nodeID); + expect(generate(result)).to.be.eq('true'); + }); + + it('should not mutate other nodes', () => { + // Arrange + const invalidNode: IdentifiedNode = { + type: 'Identifier', + } as estree.Node & Identified; + + // Act + const result = sut.applyMutations(invalidNode, copy); + + // Assert + expect(result).to.have.lengthOf(0); + }); + + +}); \ No newline at end of file diff --git a/sampleProject/src/Add.js b/sampleProject/src/Add.js index 86bf9a10f4..09a176e96d 100644 --- a/sampleProject/src/Add.js +++ b/sampleProject/src/Add.js @@ -22,3 +22,19 @@ var isNegativeNumber = function(number) { } return isNegative; }; + +var isTrue = function(boolean) { + return boolean === true; +}; + +var isFalse = function(boolean) { + return boolean === false; +}; + +var getOppositeBoolean = function(boolean) { + if(!boolean){ + return true; + } else { + return false; + } +}; diff --git a/sampleProject/test/AddSpec.js b/sampleProject/test/AddSpec.js index ed3e94cf63..0b69443ad9 100644 --- a/sampleProject/test/AddSpec.js +++ b/sampleProject/test/AddSpec.js @@ -42,4 +42,16 @@ describe('Add', function() { expect(isNegative).toBe(false); }); + + it('should be able to recognize that true = true', function() { + expect(isTrue(true)).toBe(true); + }); + + it('should be able to recognize that false = false', function() { + expect(isFalse(false)).toBe(true); + }); + + it('should be able to recognize that !true = false', function() { + expect(getOppositeBoolean(true)).toBe(false); + }); });