Skip to content

Commit

Permalink
fix(mutators): avoid creating some unnecessary mutations (#3346)
Browse files Browse the repository at this point in the history
Avoid generating unnecessary mutations in the `ConditionalExpression` mutator.
  • Loading branch information
edi9999 committed Jan 17, 2022
1 parent dc3a63a commit 0f60ecf
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 53 deletions.
6 changes: 3 additions & 3 deletions e2e/test/babel-transpiling/verify/verify.ts
Expand Up @@ -3,10 +3,10 @@ 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 | 57.45 | 27 | 0 | 20 | 0 | 1 |
// All files | 55.56 | 25 | 0 | 20 | 0 | 1 |
await expectMetricsJson({
killed: 27,
mutationScore: 57.45,
killed: 25,
mutationScore: 55.56,
runtimeErrors: 1,
survived: 20,
noCoverage: 0,
Expand Down
6 changes: 3 additions & 3 deletions e2e/test/cucumber-ts/verify/verify.ts
Expand Up @@ -5,17 +5,17 @@ describe('After running stryker on a cucumber-ts project', () => {
await expectMetricsJson({
killed: 64,
ignored: 0,
survived: 48,
survived: 40,
timeout: 1,
noCoverage: 39,
runtimeErrors: 16,
mutationScore: 42.76,
mutationScore: 45.14,
});
/*
-----------|---------|----------|-----------|------------|----------|---------|
File | % score | # killed | # timeout | # survived | # no cov | # error |
-----------|---------|----------|-----------|------------|----------|---------|
All files | 42.76 | 64 | 1 | 48 | 39 | 16 |
All files | 45.14 | 64 | 1 | 40 | 39 | 16 |
-----------|---------|----------|-----------|------------|----------|---------|*/
});
});
8 changes: 4 additions & 4 deletions e2e/test/ignore-project/verify/verify.ts
Expand Up @@ -6,17 +6,17 @@ describe('After running stryker on jest-react project', () => {
it('should report expected scores', async () => {
await expectMetricsJson({
killed: 8,
ignored: 29,
ignored: 28,
mutationScore: 53.33,
});
});

/*
-----------|---------|----------|-----------|------------|----------|---------|
File | % score | # killed | # timeout | # survived | # no cov | # error |
-----------|---------|----------|-----------|------------|----------|---------|
All files | 53.33 | 8 | 0 | 0 | 7 | 0 |*/


it('should report mutants that are disabled by a comment with correct ignore reason', async () => {
const actualMetricsResult = await readMutationTestingJsonResult();
Expand All @@ -33,7 +33,7 @@ describe('After running stryker on jest-react project', () => {
expect(conditionalMutant.status).eq(MutantStatus.Ignored);
expect(conditionalMutant.statusReason).eq('Ignore boolean and conditions');
});

equalityOperatorMutants.forEach((equalityMutant) => {
expect(equalityMutant.status).eq(MutantStatus.NoCoverage);
});
Expand Down
6 changes: 3 additions & 3 deletions e2e/test/jest-react-ts/verify/verify.ts
Expand Up @@ -3,9 +3,9 @@ import { expectMetricsJson } from '../../../helpers';
describe('After running stryker on jest-react-ts project', () => {
it('should report expected scores', async () => {
await expectMetricsJson({
killed: 63,
noCoverage: 19,
survived: 25,
killed: 61,
noCoverage: 18,
survived: 24,
timeout: 5,
});
});
Expand Down
4 changes: 2 additions & 2 deletions e2e/test/jest-with-ts/verify/verify.ts
Expand Up @@ -4,8 +4,8 @@ describe('Verify stryker has ran correctly', () => {
it('should report correct score', async () => {
await expectMetricsJson({
ignored: 0,
killed: 17,
mutationScore: 62.96,
killed: 14,
mutationScore: 58.33,
noCoverage: 0,
survived: 10,
timeout: 0,
Expand Down
4 changes: 2 additions & 2 deletions e2e/test/karma-webpack-with-ts/verify/verify.ts
Expand Up @@ -4,10 +4,10 @@ describe('Verify stryker has ran correctly', () => {

it('should report correct score', async () => {
await expectMetrics({
mutationScore: 51.67,
mutationScore: 50.85,
compileErrors: 0,
ignored: 0,
killed: 30,
killed: 29,
noCoverage: 15,
runtimeErrors: 0,
timeout: 1,
Expand Down
Expand Up @@ -14,6 +14,22 @@ export const conditionalExpressionMutator: NodeMutator = {
yield types.booleanLiteral(true);
yield types.booleanLiteral(false);
} else if (isBooleanExpression(path)) {
if (path.parent?.type === 'LogicalExpression') {
// For (x || y), do not generate the (true || y) mutation as it
// has the same behavior as the (true) mutator, handled in the
// isTestOfCondition branch above
if (path.parent.operator === '||') {
yield types.booleanLiteral(false);
return;
}
// For (x && y), do not generate the (false && y) mutation as it
// has the same behavior as the (false) mutator, handled in the
// isTestOfCondition branch above
if (path.parent.operator === '&&') {
yield types.booleanLiteral(true);
return;
}
}
yield types.booleanLiteral(true);
yield types.booleanLiteral(false);
} else if (path.isForStatement() && !path.node.test) {
Expand Down
8 changes: 6 additions & 2 deletions packages/instrumenter/test/helpers/expect-mutation.ts
Expand Up @@ -52,6 +52,10 @@ export function expectJSMutation(sut: NodeMutator, originalCode: string, ...expe
}
},
});
expect(mutants).lengthOf(expectedReplacements.length);
expectedReplacements.forEach((expected) => expect(mutants, `was: ${mutants.join(',')}`).to.include(expected));
/* eslint-disable @typescript-eslint/require-array-sort-compare */
/* because we know mutants and expectedReplacements are strings */
mutants.sort();
expectedReplacements.sort();
/* eslint-enable @typescript-eslint/require-array-sort-compare */
expect(mutants).to.deep.equal(expectedReplacements);
}
Expand Up @@ -70,6 +70,50 @@ describe(sut.name, () => {
);
});

it('should not mutate (a || b) condition to (a || true)', () => {
expectJSMutation(
sut,
'if (b === 5 || c === 3) { a++ }',
'if (true) { a++ }',
'if (false) { a++ }',
'if (false || c === 3) { a++ }',
'if (b === 5 || false) { a++ }'
);
});

it('should not mutate (a && b) condition to (a && false)', () => {
expectJSMutation(
sut,
'if (b === 5 && c === 3) { a++ }',
'if (true) { a++ }',
'if (false) { a++ }',
'if (true && c === 3) { a++ }',
'if (b === 5 && true) { a++ }'
);
});

it('should mutate ((c1 && c2) || (c3 && c4))', () => {
expectJSMutation(
sut,
'if ((c1 && c2) || (c3 && c4)) { a++ }',
'if (true) { a++ }',
'if (false) { a++ }',
'if ((false) || (c3 && c4)) { a++ }',
'if ((c1 && c2) || (false)) { a++ }'
);
});

it('should mutate ((c1 || c2) && (c3 || c4))', () => {
expectJSMutation(
sut,
'if ((c1 || c2) && (c3 || c4)) { a++ }',
'if (true) { a++ }',
'if (false) { a++ }',
'if ((true) && (c3 || c4)) { a++ }',
'if ((c1 || c2) && (true)) { a++ }'
);
});

it('should mutate an expression to `true` and `false`', () => {
expectJSMutation(sut, 'if (something) { a++ }', 'if (true) { a++ }', 'if (false) { a++ }');
});
Expand Down
Expand Up @@ -186,11 +186,11 @@ export class MutationTestReportTotalsComponent extends LitElement {
stryCov_9fa48(\\"17\\");
let fullName: string = childResult.name;
while (stryMutAct_9fa48(\\"19\\") ? !childResult.file || childResult.childResults.length === 1 : stryMutAct_9fa48(\\"18\\") ? false : (stryCov_9fa48(\\"18\\", \\"19\\"), (stryMutAct_9fa48(\\"20\\") ? childResult.file : (stryCov_9fa48(\\"20\\"), !childResult.file)) && (stryMutAct_9fa48(\\"23\\") ? childResult.childResults.length !== 1 : stryMutAct_9fa48(\\"22\\") ? false : stryMutAct_9fa48(\\"21\\") ? true : (stryCov_9fa48(\\"21\\", \\"22\\", \\"23\\"), childResult.childResults.length === 1)))) {
if (stryMutAct_9fa48(\\"24\\")) {
while (stryMutAct_9fa48(\\"19\\") ? !childResult.file || childResult.childResults.length === 1 : stryMutAct_9fa48(\\"18\\") ? false : (stryCov_9fa48(\\"18\\", \\"19\\"), (stryMutAct_9fa48(\\"20\\") ? childResult.file : (stryCov_9fa48(\\"20\\"), !childResult.file)) && (stryMutAct_9fa48(\\"22\\") ? childResult.childResults.length !== 1 : stryMutAct_9fa48(\\"21\\") ? true : (stryCov_9fa48(\\"21\\", \\"22\\"), childResult.childResults.length === 1)))) {
if (stryMutAct_9fa48(\\"23\\")) {
{}
} else {
stryCov_9fa48(\\"24\\");
stryCov_9fa48(\\"23\\");
childResult = childResult.childResults[0];
fullName = pathJoin(fullName, childResult.name);
}
Expand All @@ -204,7 +204,7 @@ export class MutationTestReportTotalsComponent extends LitElement {
}
};
return stryMutAct_9fa48(\\"25\\") ? html\`\` : (stryCov_9fa48(\\"25\\"), html\`
return stryMutAct_9fa48(\\"24\\") ? html\`\` : (stryCov_9fa48(\\"24\\"), html\`
<tbody>
\${this.renderRow(model.name, model, undefined)} \${renderChildren()}
</tbody>
Expand All @@ -213,26 +213,26 @@ export class MutationTestReportTotalsComponent extends LitElement {
}
private renderRow(name: string, row: MetricsResult, path: string | undefined) {
if (stryMutAct_9fa48(\\"26\\")) {
if (stryMutAct_9fa48(\\"25\\")) {
{}
} else {
stryCov_9fa48(\\"26\\");
stryCov_9fa48(\\"25\\");
const {
mutationScore
} = row.metrics;
const scoreIsPresent = stryMutAct_9fa48(\\"27\\") ? isNaN(mutationScore) : (stryCov_9fa48(\\"27\\"), !isNaN(mutationScore));
const scoreIsPresent = stryMutAct_9fa48(\\"26\\") ? isNaN(mutationScore) : (stryCov_9fa48(\\"26\\"), !isNaN(mutationScore));
const coloringClass = this.determineColoringClass(mutationScore);
const mutationScoreRounded = mutationScore.toFixed(2);
const progressBarStyle = stryMutAct_9fa48(\\"28\\") ? \`\` : (stryCov_9fa48(\\"28\\"), \`width: \${mutationScore}%\`);
return stryMutAct_9fa48(\\"29\\") ? html\`\` : (stryCov_9fa48(\\"29\\"), html\` <tr title=\\"\${row.name}\\">
const progressBarStyle = stryMutAct_9fa48(\\"27\\") ? \`\` : (stryCov_9fa48(\\"27\\"), \`width: \${mutationScore}%\`);
return stryMutAct_9fa48(\\"28\\") ? html\`\` : (stryCov_9fa48(\\"28\\"), html\` <tr title=\\"\${row.name}\\">
<td style=\\"width: 32px;\\" class=\\"icon no-border-right\\"
>\${row.file ? this.fileIcon : this.directoryIcon}</td
>
<td width=\\"\\" class=\\"no-border-left\\"
>\${(stryMutAct_9fa48(\\"32\\") ? typeof path !== 'string' : stryMutAct_9fa48(\\"31\\") ? false : stryMutAct_9fa48(\\"30\\") ? true : (stryCov_9fa48(\\"30\\", \\"31\\", \\"32\\"), typeof path === (stryMutAct_9fa48(\\"33\\") ? \\"\\" : (stryCov_9fa48(\\"33\\"), 'string')))) ? stryMutAct_9fa48(\\"34\\") ? html\`\` : (stryCov_9fa48(\\"34\\"), html\`<a href=\\"\${toAbsoluteUrl(path)}\\">\${name}</a>\`) : stryMutAct_9fa48(\\"35\\") ? html\`\` : (stryCov_9fa48(\\"35\\"), html\`<span>\${row.name}</span>\`)}</td
>\${(stryMutAct_9fa48(\\"31\\") ? typeof path !== 'string' : stryMutAct_9fa48(\\"30\\") ? false : stryMutAct_9fa48(\\"29\\") ? true : (stryCov_9fa48(\\"29\\", \\"30\\", \\"31\\"), typeof path === (stryMutAct_9fa48(\\"32\\") ? \\"\\" : (stryCov_9fa48(\\"32\\"), 'string')))) ? stryMutAct_9fa48(\\"33\\") ? html\`\` : (stryCov_9fa48(\\"33\\"), html\`<a href=\\"\${toAbsoluteUrl(path)}\\">\${name}</a>\`) : stryMutAct_9fa48(\\"34\\") ? html\`\` : (stryCov_9fa48(\\"34\\"), html\`<span>\${row.name}</span>\`)}</td
>
<td class=\\"no-border-right vertical-middle\\">
\${scoreIsPresent ? stryMutAct_9fa48(\\"36\\") ? html\`\` : (stryCov_9fa48(\\"36\\"), html\` <div class=\\"progress\\">
\${scoreIsPresent ? stryMutAct_9fa48(\\"35\\") ? html\`\` : (stryCov_9fa48(\\"35\\"), html\` <div class=\\"progress\\">
<div
class=\\"progress-bar bg-\${coloringClass}\\"
role=\\"progressbar\\"
Expand All @@ -243,7 +243,7 @@ export class MutationTestReportTotalsComponent extends LitElement {
>
\${mutationScoreRounded}%
</div>
</div>\`) : stryMutAct_9fa48(\\"37\\") ? html\`\` : (stryCov_9fa48(\\"37\\"), html\` <span class=\\"font-weight-bold text-muted\\">N/A</span> \`)}
</div>\`) : stryMutAct_9fa48(\\"36\\") ? html\`\` : (stryCov_9fa48(\\"36\\"), html\` <span class=\\"font-weight-bold text-muted\\">N/A</span> \`)}
</td>
<td style=\\"width: 50px;\\" class=\\"no-border-left font-weight-bold text-center text-\${coloringClass}\\">
\${scoreIsPresent ? mutationScoreRounded : undefined}
Expand All @@ -263,46 +263,46 @@ export class MutationTestReportTotalsComponent extends LitElement {
}
private determineColoringClass(mutationScore: number) {
if (stryMutAct_9fa48(\\"38\\")) {
if (stryMutAct_9fa48(\\"37\\")) {
{}
} else {
stryCov_9fa48(\\"38\\");
stryCov_9fa48(\\"37\\");
if (stryMutAct_9fa48(\\"41\\") ? !isNaN(mutationScore) || this.thresholds : stryMutAct_9fa48(\\"40\\") ? false : stryMutAct_9fa48(\\"39\\") ? true : (stryCov_9fa48(\\"39\\", \\"40\\", \\"41\\"), (stryMutAct_9fa48(\\"42\\") ? isNaN(mutationScore) : (stryCov_9fa48(\\"42\\"), !isNaN(mutationScore))) && this.thresholds)) {
if (stryMutAct_9fa48(\\"43\\")) {
if (stryMutAct_9fa48(\\"40\\") ? !isNaN(mutationScore) || this.thresholds : stryMutAct_9fa48(\\"39\\") ? false : stryMutAct_9fa48(\\"38\\") ? true : (stryCov_9fa48(\\"38\\", \\"39\\", \\"40\\"), (stryMutAct_9fa48(\\"41\\") ? isNaN(mutationScore) : (stryCov_9fa48(\\"41\\"), !isNaN(mutationScore))) && this.thresholds)) {
if (stryMutAct_9fa48(\\"42\\")) {
{}
} else {
stryCov_9fa48(\\"43\\");
stryCov_9fa48(\\"42\\");
if (stryMutAct_9fa48(\\"47\\") ? mutationScore >= this.thresholds.low : stryMutAct_9fa48(\\"46\\") ? mutationScore <= this.thresholds.low : stryMutAct_9fa48(\\"45\\") ? false : stryMutAct_9fa48(\\"44\\") ? true : (stryCov_9fa48(\\"44\\", \\"45\\", \\"46\\", \\"47\\"), mutationScore < this.thresholds.low)) {
if (stryMutAct_9fa48(\\"48\\")) {
if (stryMutAct_9fa48(\\"46\\") ? mutationScore >= this.thresholds.low : stryMutAct_9fa48(\\"45\\") ? mutationScore <= this.thresholds.low : stryMutAct_9fa48(\\"44\\") ? false : stryMutAct_9fa48(\\"43\\") ? true : (stryCov_9fa48(\\"43\\", \\"44\\", \\"45\\", \\"46\\"), mutationScore < this.thresholds.low)) {
if (stryMutAct_9fa48(\\"47\\")) {
{}
} else {
stryCov_9fa48(\\"48\\");
return stryMutAct_9fa48(\\"49\\") ? \\"\\" : (stryCov_9fa48(\\"49\\"), 'danger');
stryCov_9fa48(\\"47\\");
return stryMutAct_9fa48(\\"48\\") ? \\"\\" : (stryCov_9fa48(\\"48\\"), 'danger');
}
} else if (stryMutAct_9fa48(\\"53\\") ? mutationScore >= this.thresholds.high : stryMutAct_9fa48(\\"52\\") ? mutationScore <= this.thresholds.high : stryMutAct_9fa48(\\"51\\") ? false : stryMutAct_9fa48(\\"50\\") ? true : (stryCov_9fa48(\\"50\\", \\"51\\", \\"52\\", \\"53\\"), mutationScore < this.thresholds.high)) {
if (stryMutAct_9fa48(\\"54\\")) {
} else if (stryMutAct_9fa48(\\"52\\") ? mutationScore >= this.thresholds.high : stryMutAct_9fa48(\\"51\\") ? mutationScore <= this.thresholds.high : stryMutAct_9fa48(\\"50\\") ? false : stryMutAct_9fa48(\\"49\\") ? true : (stryCov_9fa48(\\"49\\", \\"50\\", \\"51\\", \\"52\\"), mutationScore < this.thresholds.high)) {
if (stryMutAct_9fa48(\\"53\\")) {
{}
} else {
stryCov_9fa48(\\"54\\");
return stryMutAct_9fa48(\\"55\\") ? \\"\\" : (stryCov_9fa48(\\"55\\"), 'warning');
stryCov_9fa48(\\"53\\");
return stryMutAct_9fa48(\\"54\\") ? \\"\\" : (stryCov_9fa48(\\"54\\"), 'warning');
}
} else {
if (stryMutAct_9fa48(\\"56\\")) {
if (stryMutAct_9fa48(\\"55\\")) {
{}
} else {
stryCov_9fa48(\\"56\\");
return stryMutAct_9fa48(\\"57\\") ? \\"\\" : (stryCov_9fa48(\\"57\\"), 'success');
stryCov_9fa48(\\"55\\");
return stryMutAct_9fa48(\\"56\\") ? \\"\\" : (stryCov_9fa48(\\"56\\"), 'success');
}
}
}
} else {
if (stryMutAct_9fa48(\\"58\\")) {
if (stryMutAct_9fa48(\\"57\\")) {
{}
} else {
stryCov_9fa48(\\"58\\");
return stryMutAct_9fa48(\\"59\\") ? \\"\\" : (stryCov_9fa48(\\"59\\"), 'default');
stryCov_9fa48(\\"57\\");
return stryMutAct_9fa48(\\"58\\") ? \\"\\" : (stryCov_9fa48(\\"58\\"), 'default');
}
}
}
Expand Down
Expand Up @@ -66,11 +66,11 @@ function stryMutAct_9fa48(id) {
const a = stryMutAct_9fa48(\\"0\\") ? 1 - 1 : (stryCov_9fa48(\\"0\\"), 1 + 1);
const b = 1 - 1;
if ((stryMutAct_9fa48(\\"3\\") ? a !== 2 : stryMutAct_9fa48(\\"2\\") ? false : stryMutAct_9fa48(\\"1\\") ? true : (stryCov_9fa48(\\"1\\", \\"2\\", \\"3\\"), a === 2)) && b === 0) {
if ((stryMutAct_9fa48(\\"2\\") ? a !== 2 : stryMutAct_9fa48(\\"1\\") ? true : (stryCov_9fa48(\\"1\\", \\"2\\"), a === 2)) && b === 0) {
console.log('a');
}
if (a === 2 && (stryMutAct_9fa48(\\"6\\") ? b !== 0 : stryMutAct_9fa48(\\"5\\") ? false : stryMutAct_9fa48(\\"4\\") ? true : (stryCov_9fa48(\\"4\\", \\"5\\", \\"6\\"), b === 0))) {
if (a === 2 && (stryMutAct_9fa48(\\"4\\") ? b !== 0 : stryMutAct_9fa48(\\"3\\") ? true : (stryCov_9fa48(\\"3\\", \\"4\\"), b === 0))) {
console.log('b');
}
Expand All @@ -80,5 +80,5 @@ const itemWithLongName = {
longPropertyName3: 3
};
const item = () => stryMutAct_9fa48(\\"9\\") ? itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName2 || itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName3 : stryMutAct_9fa48(\\"8\\") ? false : stryMutAct_9fa48(\\"7\\") ? true : (stryCov_9fa48(\\"7\\", \\"8\\", \\"9\\"), (stryMutAct_9fa48(\\"12\\") ? itemWithLongName.longPropertyName1 !== itemWithLongName.longPropertyName2 : stryMutAct_9fa48(\\"11\\") ? false : stryMutAct_9fa48(\\"10\\") ? true : (stryCov_9fa48(\\"10\\", \\"11\\", \\"12\\"), itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName2)) && (stryMutAct_9fa48(\\"15\\") ? itemWithLongName.longPropertyName1 !== itemWithLongName.longPropertyName3 : stryMutAct_9fa48(\\"14\\") ? false : stryMutAct_9fa48(\\"13\\") ? true : (stryCov_9fa48(\\"13\\", \\"14\\", \\"15\\"), itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName3)));"
const item = () => stryMutAct_9fa48(\\"7\\") ? itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName2 || itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName3 : stryMutAct_9fa48(\\"6\\") ? false : stryMutAct_9fa48(\\"5\\") ? true : (stryCov_9fa48(\\"5\\", \\"6\\", \\"7\\"), (stryMutAct_9fa48(\\"9\\") ? itemWithLongName.longPropertyName1 !== itemWithLongName.longPropertyName2 : stryMutAct_9fa48(\\"8\\") ? true : (stryCov_9fa48(\\"8\\", \\"9\\"), itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName2)) && (stryMutAct_9fa48(\\"11\\") ? itemWithLongName.longPropertyName1 !== itemWithLongName.longPropertyName3 : stryMutAct_9fa48(\\"10\\") ? true : (stryCov_9fa48(\\"10\\", \\"11\\"), itemWithLongName.longPropertyName1 === itemWithLongName.longPropertyName3)));"
`;

0 comments on commit 0f60ecf

Please sign in to comment.