diff --git a/e2e/test/babel-transpiling/verify/verify.ts b/e2e/test/babel-transpiling/verify/verify.ts index e13e35edfd..0c6495c350 100644 --- a/e2e/test/babel-transpiling/verify/verify.ts +++ b/e2e/test/babel-transpiling/verify/verify.ts @@ -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, diff --git a/e2e/test/cucumber-ts/verify/verify.ts b/e2e/test/cucumber-ts/verify/verify.ts index 42d4cdf6a5..d4157046b4 100644 --- a/e2e/test/cucumber-ts/verify/verify.ts +++ b/e2e/test/cucumber-ts/verify/verify.ts @@ -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 | -----------|---------|----------|-----------|------------|----------|---------|*/ }); }); diff --git a/e2e/test/ignore-project/verify/verify.ts b/e2e/test/ignore-project/verify/verify.ts index 484b599581..4b5b27df24 100644 --- a/e2e/test/ignore-project/verify/verify.ts +++ b/e2e/test/ignore-project/verify/verify.ts @@ -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(); @@ -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); }); diff --git a/packages/instrumenter/src/mutators/conditional-expression-mutator.ts b/packages/instrumenter/src/mutators/conditional-expression-mutator.ts index 18fdb4fb1f..c3904477e9 100644 --- a/packages/instrumenter/src/mutators/conditional-expression-mutator.ts +++ b/packages/instrumenter/src/mutators/conditional-expression-mutator.ts @@ -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) { diff --git a/packages/instrumenter/test/helpers/expect-mutation.ts b/packages/instrumenter/test/helpers/expect-mutation.ts index 86d61d9a42..4175c8def8 100644 --- a/packages/instrumenter/test/helpers/expect-mutation.ts +++ b/packages/instrumenter/test/helpers/expect-mutation.ts @@ -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); } diff --git a/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts b/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts index 2f2fdcc825..5f7ef40dfc 100644 --- a/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts +++ b/packages/instrumenter/test/unit/mutators/conditional-expression-mutator.spec.ts @@ -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++ }'); }); diff --git a/packages/instrumenter/testResources/instrumenter/lit-html-sample.ts.out.snap b/packages/instrumenter/testResources/instrumenter/lit-html-sample.ts.out.snap index 748b6ef735..d5e5d93a29 100644 --- a/packages/instrumenter/testResources/instrumenter/lit-html-sample.ts.out.snap +++ b/packages/instrumenter/testResources/instrumenter/lit-html-sample.ts.out.snap @@ -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); } @@ -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\` \${this.renderRow(model.name, model, undefined)} \${renderChildren()} @@ -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\` + const progressBarStyle = stryMutAct_9fa48(\\"27\\") ? \`\` : (stryCov_9fa48(\\"27\\"), \`width: \${mutationScore}%\`); + return stryMutAct_9fa48(\\"28\\") ? html\`\` : (stryCov_9fa48(\\"28\\"), html\` \${row.file ? this.fileIcon : this.directoryIcon} \${(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\`\${name}\`) : stryMutAct_9fa48(\\"35\\") ? html\`\` : (stryCov_9fa48(\\"35\\"), html\`\${row.name}\`)}\${(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\`\${name}\`) : stryMutAct_9fa48(\\"34\\") ? html\`\` : (stryCov_9fa48(\\"34\\"), html\`\${row.name}\`)} - \${scoreIsPresent ? stryMutAct_9fa48(\\"36\\") ? html\`\` : (stryCov_9fa48(\\"36\\"), html\`
+ \${scoreIsPresent ? stryMutAct_9fa48(\\"35\\") ? html\`\` : (stryCov_9fa48(\\"35\\"), html\`
\${mutationScoreRounded}%
-
\`) : stryMutAct_9fa48(\\"37\\") ? html\`\` : (stryCov_9fa48(\\"37\\"), html\` N/A \`)} +
\`) : stryMutAct_9fa48(\\"36\\") ? html\`\` : (stryCov_9fa48(\\"36\\"), html\` N/A \`)} \${scoreIsPresent ? mutationScoreRounded : undefined} @@ -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'); } } } diff --git a/packages/instrumenter/testResources/instrumenter/specific-mutants.ts.out.snap b/packages/instrumenter/testResources/instrumenter/specific-mutants.ts.out.snap index 7070b3c7ae..c6a3858160 100644 --- a/packages/instrumenter/testResources/instrumenter/specific-mutants.ts.out.snap +++ b/packages/instrumenter/testResources/instrumenter/specific-mutants.ts.out.snap @@ -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'); } @@ -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)));" `;