Skip to content

Commit

Permalink
fix(core): short circuit test executor when no tests and allowEmpty (#…
Browse files Browse the repository at this point in the history
…4477)

* fix(core): do not error when no tests and allowEmpty

* refactor test to use arrangeScenario

* add test when short circuit disabled

* fix eslint errors
  • Loading branch information
edno authored Oct 22, 2023
1 parent 418688b commit ce3e5cd
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 3 deletions.
10 changes: 9 additions & 1 deletion packages/core/src/process/4-mutation-test-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { from, partition, merge, Observable, lastValueFrom, EMPTY, concat, buffe
import { toArray, map, shareReplay, tap } from 'rxjs/operators';
import { tokens, commonTokens } from '@stryker-mutator/api/plugin';
import { MutantResult, MutantStatus, Mutant, StrykerOptions, PlanKind, MutantTestPlan, MutantRunPlan } from '@stryker-mutator/api/core';
import { TestRunner } from '@stryker-mutator/api/test-runner';
import { TestRunner, CompleteDryRunResult } from '@stryker-mutator/api/test-runner';
import { Logger } from '@stryker-mutator/api/logging';
import { I } from '@stryker-mutator/util';
import { CheckStatus } from '@stryker-mutator/api/check';
Expand All @@ -22,6 +22,7 @@ export interface MutationTestContext extends DryRunContext {
[coreTokens.timeOverheadMS]: number;
[coreTokens.mutationTestReportHelper]: MutationTestReportHelper;
[coreTokens.mutantTestPlanner]: MutantTestPlanner;
[coreTokens.dryRunResult]: I<CompleteDryRunResult>;
}

const CHECK_BUFFER_MS = 10_000;
Expand All @@ -48,6 +49,7 @@ export class MutationTestExecutor {
commonTokens.options,
coreTokens.timer,
coreTokens.concurrencyTokenProvider,
coreTokens.dryRunResult,
);

constructor(
Expand All @@ -61,6 +63,7 @@ export class MutationTestExecutor {
private readonly options: StrykerOptions,
private readonly timer: I<Timer>,
private readonly concurrencyTokenProvider: I<ConcurrencyTokenProvider>,
private readonly dryRunResult: CompleteDryRunResult,
) {}

public async execute(): Promise<MutantResult[]> {
Expand All @@ -69,6 +72,11 @@ export class MutationTestExecutor {
return [];
}

if (this.dryRunResult.tests.length === 0 && this.options.allowEmpty) {
this.logDone();
return [];
}

const mutantTestPlans = await this.planner.makePlan(this.mutants);
const { earlyResult$, runMutant$ } = this.executeEarlyResult(from(mutantTestPlans));
const { passedMutant$, checkResult$ } = this.executeCheck(runMutant$);
Expand Down
49 changes: 47 additions & 2 deletions packages/core/test/unit/process/4-mutation-test-executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import sinon from 'sinon';
import { expect } from 'chai';
import { testInjector, factory, tick } from '@stryker-mutator/test-helpers';
import { Reporter } from '@stryker-mutator/api/report';
import { TestRunner, MutantRunOptions, MutantRunResult, MutantRunStatus } from '@stryker-mutator/api/test-runner';
import { TestRunner, MutantRunOptions, MutantRunResult, MutantRunStatus, CompleteDryRunResult, TestResult } from '@stryker-mutator/api/test-runner';
import { CheckResult, CheckStatus } from '@stryker-mutator/api/check';
import { mergeMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
Expand Down Expand Up @@ -47,6 +47,7 @@ describe(MutationTestExecutor.name, () => {
let testRunner: sinon.SinonStubbedInstance<Required<TestRunner>>;
let concurrencyTokenProviderMock: sinon.SinonStubbedInstance<ConcurrencyTokenProvider>;
let sandboxMock: sinon.SinonStubbedInstance<Sandbox>;
let completeDryRunResult: sinon.SinonStubbedInstance<CompleteDryRunResult>;

beforeEach(() => {
reporterMock = factory.reporter();
Expand Down Expand Up @@ -74,6 +75,7 @@ describe(MutationTestExecutor.name, () => {

mutants = [factory.mutant()];
mutantTestPlans = [];
completeDryRunResult = factory.completeDryRunResult();
mutantTestPlannerMock.makePlan.resolves(mutantTestPlans);
sut = testInjector.injector
.provideValue(coreTokens.reporter, reporterMock)
Expand All @@ -87,6 +89,7 @@ describe(MutationTestExecutor.name, () => {
.provideValue(coreTokens.timer, timerMock)
.provideValue(coreTokens.testRunnerPool, testRunnerPoolMock)
.provideValue(coreTokens.concurrencyTokenProvider, concurrencyTokenProviderMock)
.provideValue(coreTokens.dryRunResult, completeDryRunResult)
.injectClass(MutationTestExecutor);
});

Expand All @@ -97,9 +100,15 @@ describe(MutationTestExecutor.name, () => {
mutationTestReportHelperMock.reportAll.returnsArg(0);
}

function arrangeScenario(overrides?: { mutantRunPlan?: MutantRunPlan; checkResult?: CheckResult; mutantRunResult?: MutantRunResult }) {
function arrangeScenario(overrides?: {
mutantRunPlan?: MutantRunPlan;
checkResult?: CheckResult;
mutantRunResult?: MutantRunResult;
dryRunTestResult?: TestResult[];
}) {
checker.check.resolves([[overrides?.mutantRunPlan ?? mutantRunPlan(), overrides?.checkResult ?? factory.checkResult()]]);
testRunner.mutantRun.resolves(overrides?.mutantRunResult ?? factory.survivedMutantRunResult());
completeDryRunResult.tests = overrides?.dryRunTestResult ?? [factory.testResult()];
arrangeMutationTestReportHelper();
}

Expand Down Expand Up @@ -401,4 +410,40 @@ describe(MutationTestExecutor.name, () => {
expect(checker.check).not.called;
expect(actualResults).empty;
});

it('should short circuit when no tests found and allowEmpty is enabled', async () => {
// Arrange
testInjector.options.allowEmpty = true;
arrangeScenario({ dryRunTestResult: [] });
const plan1 = mutantRunPlan({ id: '1' });
const plan2 = mutantRunPlan({ id: '2' });
mutantTestPlans.push(plan1, plan2);

// Act
const actualResults = await sut.execute();

// Assert
expect(mutantTestPlannerMock.makePlan).not.called;
expect(testRunner.mutantRun).not.called;
expect(checker.check).not.called;
expect(actualResults).empty;
});

it('should not short circuit if no tests found and allowEmpty is disabled', async () => {
// Arrange
testInjector.options.allowEmpty = false;
arrangeScenario({ dryRunTestResult: [] });
const plan1 = mutantRunPlan({ id: '1' });
const plan2 = mutantRunPlan({ id: '2' });
mutantTestPlans.push(plan1, plan2);

// Act
const actualResults = await sut.execute();

// Assert
expect(mutantTestPlannerMock.makePlan).called;
expect(testRunner.mutantRun).calledWithExactly(plan1.runOptions);
expect(testRunner.mutantRun).calledWithExactly(plan2.runOptions);
expect(actualResults).deep.eq([plan1.mutant, plan2.mutant]);
});
});

0 comments on commit ce3e5cd

Please sign in to comment.