Skip to content

Commit

Permalink
feat(mutators): Implement missing AssignmentOperatorMutator (#3203)
Browse files Browse the repository at this point in the history
Add the [AssignmentOperatorMutator](https://stryker-mutator.io/docs/mutation-testing-elements/supported-mutators/#assignment-expression)

| Original | Mutated |
| -------- | ------- |
| `+=`     | `-=`    |
| `-=`     | `+=`    |
| `*=`     | `/=`    |
| `/=`     | `*=`    |
| `%=`     | `*=`    |
| `<<=`    | `>>=`   |
| `>>=`    | `<<=`   |
| `&=`     | `\|=`   |
| `|=`     | `&=`    |
| `&&=`    | `||=`   |
|  `||=`   | `&&=`   |
|  `??=`   | `&&=`   |
  • Loading branch information
Qvdpost committed Oct 20, 2021
1 parent 9379c82 commit 95b694b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 19 deletions.
2 changes: 1 addition & 1 deletion e2e/test/babel-transpiling/stryker.conf.js
Expand Up @@ -6,7 +6,7 @@ module.exports = {
coverageAnalysis: 'off',
buildCommand: 'npm run build',
timeoutMS: 60000,
reporters: ['clear-text', 'html', 'event-recorder', 'progress'],
reporters: ['clear-text', 'html', 'json', 'progress'],
concurrency: 1,
logLevel: 'info',
plugins: [
Expand Down
25 changes: 8 additions & 17 deletions e2e/test/babel-transpiling/verify/verify.ts
@@ -1,24 +1,15 @@
import { expectMetricsResult, produceMetrics } from '../../../helpers';
import { expectMetricsJson } from '../../../helpers';

describe('Verify stryker has ran correctly', () => {

it('should report expected score', async () => {
// File | % score | # killed | # timeout | # survived | # no cov | # error |
// All files | 58.54 | 24 | 0 | 17 | 0 | 1 |
await expectMetricsResult({
metrics: produceMetrics({
killed: 24,
mutationScore: 55.81,
mutationScoreBasedOnCoveredCode: 55.81,
runtimeErrors: 1,
survived: 19,
totalCovered: 43,
totalDetected: 24,
totalInvalid: 1,
totalMutants: 44,
totalUndetected: 19,
totalValid: 43
})
// All files | 57.45 | 27 | 0 | 20 | 0 | 1 |
await expectMetricsJson({
killed: 27,
mutationScore: 57.45,
runtimeErrors: 1,
survived: 20,
noCoverage: 0,
});
});
});
47 changes: 47 additions & 0 deletions packages/instrumenter/src/mutators/assignment-operator-mutator.ts
@@ -0,0 +1,47 @@
import * as types from '@babel/types';

import { NodeMutator } from '.';

enum AssignmentOperators {
'+=' = '-=',
'-=' = '+=',
'*=' = '/=',
'/=' = '*=',
'%=' = '*=',
'<<=' = '>>=',
'>>=' = '<<=',
'&=' = '|=',
'|=' = '&=',
'&&=' = '||=',
'||=' = '&&=',
'??=' = '&&=',
}

const stringTypes = Object.freeze(['StringLiteral', 'TemplateLiteral']);
const stringAssignmentTypes = Object.freeze(['&&=', '||=', '??=']);

export const assignmentOperatorMutator: NodeMutator = {
name: 'AssignmentOperator',

*mutate(path) {
if (path.isAssignmentExpression() && isSupportedAssignmentOperator(path.node.operator) && isSupported(path.node)) {
const mutatedOperator = AssignmentOperators[path.node.operator];
const replacement = types.cloneNode(path.node, false);
replacement.operator = mutatedOperator;
yield replacement;
}
},
};

function isSupportedAssignmentOperator(operator: string): operator is keyof typeof AssignmentOperators {
return Object.keys(AssignmentOperators).includes(operator);
}

function isSupported(node: types.AssignmentExpression): boolean {
// Excludes assignment operators that apply to strings.
if (stringTypes.includes(node.right.type) && !stringAssignmentTypes.includes(node.operator)) {
return false;
}

return true;
}
2 changes: 2 additions & 0 deletions packages/instrumenter/src/mutators/mutate.ts
Expand Up @@ -13,6 +13,7 @@ import { unaryOperatorMutator } from './unary-operator-mutator';
import { updateOperatorMutator } from './update-operator-mutator';
import { regexMutator } from './regex-mutator';
import { optionalChainingMutator } from './optional-chaining-mutator';
import { assignmentOperatorMutator } from './assignment-operator-mutator';

export const allMutators: NodeMutator[] = [
arithmeticOperatorMutator,
Expand All @@ -29,4 +30,5 @@ export const allMutators: NodeMutator[] = [
updateOperatorMutator,
regexMutator,
optionalChainingMutator,
assignmentOperatorMutator,
];
@@ -0,0 +1,75 @@
import { expect } from 'chai';

import { assignmentOperatorMutator as sut } from '../../../src/mutators/assignment-operator-mutator';
import { expectJSMutation } from '../../helpers/expect-mutation';

describe(sut.name, () => {
it('should have name "AssignmentOperator"', () => {
expect(sut.name).eq('AssignmentOperator');
});

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

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

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

it('should mutate <<=, >>=, &= and |=', () => {
expectJSMutation(sut, 'a *= b', 'a /= b');
expectJSMutation(sut, 'a /= b', 'a *= b');
expectJSMutation(sut, 'a %= b', 'a *= b');
});

it('should mutate &&=, ||= and ??=', () => {
expectJSMutation(sut, 'a &&= b', 'a ||= b');
expectJSMutation(sut, 'a ||= b', 'a &&= b');
expectJSMutation(sut, 'a ??= b', 'a &&= b');
});

it('should not mutate a string literal unless it is &&=, ||=, ??=', () => {
expectJSMutation(sut, 'a += "b"');
expectJSMutation(sut, 'a -= "b"');
expectJSMutation(sut, 'a *= "b"');
expectJSMutation(sut, 'a /= "b"');
expectJSMutation(sut, 'a %= "b"');
expectJSMutation(sut, 'a <<= "b"');
expectJSMutation(sut, 'a >>= "b"');
expectJSMutation(sut, 'a &= "b"');
expectJSMutation(sut, 'a |= "b"');
});

it('should mutate a string literal using &&=, ||=, ??=', () => {
expectJSMutation(sut, 'a &&= "b"', 'a ||= "b"');
expectJSMutation(sut, 'a ||= "b"', 'a &&= "b"');
expectJSMutation(sut, 'a ??= "b"', 'a &&= "b"');
});

it('should not mutate string template unless it is &&=, ||=, ??=', () => {
expectJSMutation(sut, 'a += `b`');
expectJSMutation(sut, 'a -= `b`');
expectJSMutation(sut, 'a *= `b`');
expectJSMutation(sut, 'a /= `b`');
expectJSMutation(sut, 'a %= `b`');
expectJSMutation(sut, 'a <<= `b`');
expectJSMutation(sut, 'a >>= `b`');
expectJSMutation(sut, 'a &= `b`');
expectJSMutation(sut, 'a |= `b`');
});

it('should mutate string template using &&=, ||=, ??=', () => {
expectJSMutation(sut, 'a &&= `b`', 'a ||= `b`');
expectJSMutation(sut, 'a ||= `b`', 'a &&= `b`');
expectJSMutation(sut, 'a ??= `b`', 'a &&= `b`');
});
});
Expand Up @@ -75,7 +75,7 @@ function factorial(num) {
while (stryMutAct_9fa48(\\"13\\") ? i > num : stryMutAct_9fa48(\\"12\\") ? i < num : stryMutAct_9fa48(\\"11\\") ? false : (stryCov_9fa48(\\"11\\", \\"12\\", \\"13\\"), i <= num)) {
// Stryker disable next-line UpdateOperator: Infinite loop
o *= i++;
stryMutAct_9fa48(\\"15\\") ? o /= i++ : (stryCov_9fa48(\\"15\\"), o *= i++);
}
return o;
Expand Down

0 comments on commit 95b694b

Please sign in to comment.