Skip to content

Commit

Permalink
feat(Mutators): Add Boolean substitution mutators (#294)
Browse files Browse the repository at this point in the history
Add boolean substitution mutator which mutates 3 things:
- `true` -> `false`
- `false` -> `true`
- `!a` -> `a`
  • Loading branch information
avassem85 authored and nicojs committed Apr 28, 2017
1 parent 6fbbc1e commit a137a97
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 7 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 0 additions & 1 deletion packages/stryker/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ testResources
.tmp
.tscache
.vscode
stryker.conf.js
tsconfig.json
/coverage
reports
2 changes: 2 additions & 0 deletions packages/stryker/src/MutatorOrchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -86,6 +87,7 @@ export default class MutatorOrchestrator {
mutatorFactory.register('UnaryOperator', UnaryOperatorMutator);
mutatorFactory.register('UpdateOperator', UpdateOperatorMutator);
mutatorFactory.register('ArrayDeclarator', ArrayDeclaratorMutator);
mutatorFactory.register('BooleanSubstitution', BooleanSubstitutionMutator);
}

/**
Expand Down
26 changes: 26 additions & 0 deletions packages/stryker/src/mutators/BooleanSubstitutionMutator.ts
Original file line number Diff line number Diff line change
@@ -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: <T>(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;
}

}
10 changes: 5 additions & 5 deletions packages/stryker/src/stryker-cli.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -40,20 +40,20 @@ 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];
}
}

if (strykerConfig) {
program.configFile = strykerConfig;
program['configFile'] = strykerConfig;
}

const commands: { [cmd: string]: () => void } = {
Expand Down
2 changes: 1 addition & 1 deletion packages/stryker/stryker.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ module.exports = function (config) {
logLevel: 'info',
plugins: ['stryker-mocha-runner']
});
};
};
Original file line number Diff line number Diff line change
@@ -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);
});


});
16 changes: 16 additions & 0 deletions sampleProject/src/Add.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
};
12 changes: 12 additions & 0 deletions sampleProject/test/AddSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

0 comments on commit a137a97

Please sign in to comment.