Skip to content

Commit

Permalink
feat(Conditional expression mutator): Mutate conditional operators (#…
Browse files Browse the repository at this point in the history
…1253)

The `ConditionalExpressionMutator` now also mutates conditional expressions in the wild. Not only in `for`, `while` and `if` statements.

For example: `const foo = bar < baz` will be mutated to `const foo = bar <= baz`. 

Loops are still not mutated to obvious infinite loops (for example `while (i < 10) { i++ }` **isn't** mutated to `while (true) { i++ }`)
  • Loading branch information
bharaninb authored and nicojs committed Nov 27, 2018
1 parent c764ccd commit be4c990
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 15 deletions.
4 changes: 2 additions & 2 deletions integrationTest/test/command/verify/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import * as path from 'path';
import { readScoreResult } from '../../../helpers';

describe('After running stryker with the command test runner', () => {
it('should report 69% mutation score', async () => {
it('should report 64% mutation score', async () => {
const scoreResult = await readScoreResult(path.resolve('reports', 'mutation', 'events'));
expect(scoreResult.killed).eq(16);
expect(scoreResult.noCoverage).eq(0);
expect(scoreResult.mutationScore).greaterThan(69).and.lessThan(70);
expect(scoreResult.mutationScore).eq(64);
});

it('should write to a log file', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,55 @@
import * as types from '@babel/types';
import NodeMutator from './NodeMutator';
import NodeGenerator from '../helpers/NodeGenerator';
import { NodeWithParent } from '../helpers/ParentNode';

/**
* Represents a mutator which can remove the conditional clause from statements.
*/
export default class ConditionalExpressionMutator implements NodeMutator {
private readonly validOperators: string[] = [
'!=',
'!==',
'&&',
'<',
'<=',
'==',
'===',
'>',
'>=',
'||',
];

public name = 'ConditionalExpression';

constructor() { }
constructor() {}

private hasValidParent(node: NodeWithParent): boolean {
return (
!node.parent ||
!(
types.isForStatement(node.parent) ||
types.isWhileStatement(node.parent) ||
types.isIfStatement(node.parent) ||
types.isDoWhileStatement(node.parent)
)
);
}

private isValidOperator(operator: string): boolean {
return this.validOperators.indexOf(operator) !== -1;
}

public mutate(node: types.Node): types.Node[] | void {
if (types.isConditionalExpression(node)) {
return [
NodeGenerator.createBooleanLiteralNode(node.test, false),
NodeGenerator.createBooleanLiteralNode(node.test, true)
];
if (
(types.isBinaryExpression(node) || types.isLogicalExpression(node)) &&
this.hasValidParent(node) &&
this.isValidOperator(node.operator)
) {
return [
NodeGenerator.createBooleanLiteralNode(node, false),
NodeGenerator.createBooleanLiteralNode(node, true),
];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,60 @@ export default function ConditionalExpressionMutatorSpec(name: string, expectMut
expect(name).eq('ConditionalExpression');
});

it('should replace conditional expressions', () => {
it('should mutate ternary operator', () => {
expectMutation('a < 3? b : c', 'false? b : c', 'true? b : c');
});

it('should mutate < and >', () => {
expectMutation('a < b', 'true', 'false');
expectMutation('a > b', 'true', 'false');
});

it('should mutate <= and >=', () => {
expectMutation('a <= b', 'true', 'false');
expectMutation('a >= b', 'true', 'false');
});

it('should mutate == and ===', () => {
expectMutation('a == b', 'true', 'false');
expectMutation('a === b', 'true', 'false');
});

it('should mutate != and !==', () => {
expectMutation('a != b', 'true', 'false');
expectMutation('a !== b', 'true', 'false');
});

it('should mutate && and ||', () => {
expectMutation('a && b', 'true', 'false');
expectMutation('a || b', 'true', 'false');
});

it('should not mutate + and -', () => {
expectMutation('a + b');
expectMutation('a - b');
});

it('should not mutate *, % and /', () => {
expectMutation('a * b');
expectMutation('a / b');
expectMutation('a % b');
});

it('should not mutate `if statement`', () => {
expectMutation('if (a < 6) { a++ }');
});

it('should not mutate `for statement`', () => {
expectMutation('for(let i=0;i<10; i++) { console.log(); }');
});

it('should not mutate `while statement`', () => {
expectMutation('while(a < b) { console.log(); }');
});

it('should not mutate `do while statement`', () => {
expectMutation('do { console.log(); } while(a < b);');
});
});
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import * as ts from 'typescript';
import NodeMutator, { NodeReplacement } from './NodeMutator';

export default class ConditionalExpressionMutator extends NodeMutator<ts.ConditionalExpression> {
export default class ConditionalExpressionMutator extends NodeMutator<ts.BinaryExpression> {
public name = 'ConditionalExpression';

public guard(node: ts.Node): node is ts.ConditionalExpression {
return node.kind === ts.SyntaxKind.ConditionalExpression;
public guard(node: ts.Node): node is ts.BinaryExpression {
return node.kind === ts.SyntaxKind.BinaryExpression;
}

protected identifyReplacements(node: ts.ConditionalExpression): NodeReplacement[] {
private isInvalidParent(parent: ts.Node): boolean {
switch (parent.kind) {
case ts.SyntaxKind.IfStatement:
case ts.SyntaxKind.ForStatement:
case ts.SyntaxKind.WhileStatement:
case ts.SyntaxKind.DoStatement:
case ts.SyntaxKind.LiteralType:
return true;
default:
return false;
}
}

private isInvalidOperator(operatorToken: ts.BinaryOperatorToken): boolean {
switch (operatorToken.kind) {
case ts.SyntaxKind.PlusToken:
case ts.SyntaxKind.MinusToken:
case ts.SyntaxKind.SlashToken:
case ts.SyntaxKind.AsteriskToken:
case ts.SyntaxKind.PercentToken:
return true;
default:
return false;
}
}

protected identifyReplacements(node: ts.BinaryExpression): NodeReplacement[] {
if ((node.parent && this.isInvalidParent(node.parent)) || this.isInvalidOperator(node.operatorToken)) {
return [];
}

return [
{ node: node.condition, replacement: 'false' },
{ node: node.condition, replacement: 'true' }
{ node, replacement: 'false' },
{ node, replacement: 'true' }
];
}

Expand Down

0 comments on commit be4c990

Please sign in to comment.