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\`