Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jasmine-runner): implement new test runner api #2256

Merged
merged 28 commits into from
Jun 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a0b6055
feat(api): add new test runner api
nicojs Jun 9, 2020
3b91826
Add plugin type for test runner 2
nicojs Jun 9, 2020
7c78079
Merge branch 'epic/mutation-switching' into feature/new-test-runner-api
nicojs Jun 10, 2020
e495688
fix: Import mutant from core
nicojs Jun 10, 2020
738493a
refactor(util): move Task to @stryker-mutator/util
nicojs Jun 4, 2020
703e646
Merge branch 'epic/mutation-switching' of github.com:stryker-mutator/…
nicojs Jun 10, 2020
b8c6ae6
Merge branch 'epic/mutation-switching' into feature/new-test-runner-api
nicojs Jun 10, 2020
7c575b8
Remove error messages array
nicojs Jun 10, 2020
36f2f43
chore(api): add id to Mutant interface
nicojs Jun 10, 2020
ff02536
Merge branch 'fix/mutant-api-add-id' into feat/jasmine-runner-2
nicojs Jun 10, 2020
989fd33
feat(jasmine-runner): implement new test runner api
nicojs Jun 10, 2020
c28c3d3
Merge branch 'epic/mutation-switching' into feature/new-test-runner-api
nicojs Jun 11, 2020
bc44350
Add new suggestion for test runner api
nicojs Jun 11, 2020
a44409b
Review test runner api
nicojs Jun 11, 2020
cff2f37
feat(api): further cleanup test runner api
nicojs Jun 11, 2020
e70eec6
Add runResult helper to convert a dry run result to a mutant run result
nicojs Jun 11, 2020
1ec54db
Add tests for toMutantRunResult
nicojs Jun 11, 2020
df0b741
Merge branch 'feature/new-test-runner-api' into feat/jasmine-runner-2
nicojs Jun 11, 2020
97f281b
Implement latest test runner api
nicojs Jun 12, 2020
c17eade
Add tests for coverage analysis
nicojs Jun 12, 2020
ca0b255
Small improvements
nicojs Jun 12, 2020
1c14f51
Merge branch 'feature/new-test-runner-api' into feat/jasmine-runner-2
nicojs Jun 12, 2020
79d3445
Add unit tests for test selection and mutation coverage
nicojs Jun 12, 2020
a8d8528
Merge branch 'epic/mutation-switching' into feat/jasmine-runner-2
nicojs Jun 12, 2020
e3c563a
Merge branch 'epic/mutation-switching' into feat/jasmine-runner-2
nicojs Jun 16, 2020
faa2787
Merge branch 'epic/mutation-switching' into feat/jasmine-runner-2
nicojs Jun 16, 2020
8e4d3a2
Merge branch 'epic/mutation-switching' into feat/jasmine-runner-2
nicojs Jun 16, 2020
6b0d93b
refactor(assertions): move assertions to test-helpers
nicojs Jun 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/jasmine-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"jasmine": ">=2"
},
"devDependencies": {
"@stryker-mutator/jasmine-framework": "^3.3.0",
"@stryker-mutator/test-helpers": "^3.3.0",
"@types/node": "^14.0.1"
},
Expand Down
86 changes: 60 additions & 26 deletions packages/jasmine-runner/src/JasmineTestRunner.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { EOL } from 'os';

import { StrykerOptions } from '@stryker-mutator/api/core';
import { StrykerOptions, CoverageAnalysis } from '@stryker-mutator/api/core';
import { commonTokens, tokens } from '@stryker-mutator/api/plugin';
import { RunResult, RunStatus, TestResult, TestRunner } from '@stryker-mutator/api/test_runner';
import { errorToString } from '@stryker-mutator/util';
import {
RunStatus,
TestResult,
TestRunner2,
MutantRunOptions,
DryRunResult,
MutantRunResult,
toMutantRunResult,
ErrorDryRunResult,
DryRunOptions,
MutantCoverage,
} from '@stryker-mutator/api/test_runner2';
import { errorToString, Task } from '@stryker-mutator/util';

import { JasmineRunnerOptions } from '../src-generated/jasmine-runner-options';

import { evalGlobal, Jasmine, toStrykerTestResult } from './helpers';
import { Jasmine, toStrykerTestResult } from './helpers';

export default class JasmineTestRunner implements TestRunner {
export default class JasmineTestRunner implements TestRunner2 {
private readonly jasmineConfigFile: string | undefined;
private readonly Date: typeof Date = Date; // take Date prototype now we still can (user might choose to mock it away)

Expand All @@ -18,49 +29,72 @@ export default class JasmineTestRunner implements TestRunner {
this.jasmineConfigFile = (options as JasmineRunnerOptions).jasmineConfigFile;
}

public run(options: { testHooks?: string }): Promise<RunResult> {
public dryRun(options: DryRunOptions): Promise<DryRunResult> {
return this.run(undefined, options.coverageAnalysis);
}

public async mutantRun(options: MutantRunOptions): Promise<MutantRunResult> {
global.__activeMutant__ = options.activeMutant.id;
const runResult = await this.run(options.testFilter);
return toMutantRunResult(runResult);
}

private async run(testFilter?: string[], coverageAnalysis?: CoverageAnalysis): Promise<DryRunResult> {
this.clearRequireCache();
const tests: TestResult[] = [];
let startTimeCurrentSpec = 0;
const jasmine = this.createJasmineRunner();
const self = this;
if (options.testHooks) {
evalGlobal(options.testHooks);
}
return new Promise<RunResult>((resolve) => {
try {
const jasmine = this.createJasmineRunner(testFilter);
const self = this;
const tests: TestResult[] = [];
const runTask = new Task<DryRunResult>();
let startTimeCurrentSpec = 0;
const reporter: jasmine.CustomReporter = {
specStarted() {
specStarted(spec) {
if (coverageAnalysis && coverageAnalysis === 'perTest') {
global.__currentTestId__ = spec.id;
}
startTimeCurrentSpec = new self.Date().getTime();
},

specDone(result: jasmine.CustomReporterResult) {
tests.push(toStrykerTestResult(result, new self.Date().getTime() - startTimeCurrentSpec));
},

jasmineDone() {
resolve({
errorMessages: [],
let mutantCoverage: MutantCoverage | undefined = undefined;
if (coverageAnalysis === 'all' || coverageAnalysis === 'perTest') {
mutantCoverage = global.__mutantCoverage__;
}
runTask.resolve({
status: RunStatus.Complete,
tests,
mutantCoverage,
});
},
};

jasmine.env.addReporter(reporter);
jasmine.execute();
}).catch((error) => ({
errorMessages: [`An error occurred while loading your jasmine specs${EOL}${errorToString(error)}`],
status: RunStatus.Error,
tests: [],
}));
const result = await runTask.promise;
return result;
} catch (error) {
const errorResult: ErrorDryRunResult = {
errorMessage: `An error occurred while loading your jasmine specs${EOL}${errorToString(error)}`,
status: RunStatus.Error,
};
return errorResult;
}
}

private createJasmineRunner() {
private createJasmineRunner(testFilter: undefined | string[]) {
let specFilter: undefined | Function = undefined;
if (testFilter) {
specFilter = (spec: jasmine.Spec) => testFilter.includes(spec.id.toString());
}
const jasmine = new Jasmine({ projectBaseDir: process.cwd() });
// The `loadConfigFile` will fallback on the default
jasmine.loadConfigFile(this.jasmineConfigFile);
jasmine.env.configure({
failFast: true,
oneFailurePerSpec: true,
specFilter,
});

jasmine.exit = () => {};
Expand All @@ -69,7 +103,7 @@ export default class JasmineTestRunner implements TestRunner {
return jasmine;
}

public clearRequireCache() {
private clearRequireCache() {
this.fileNames.forEach((fileName) => {
delete require.cache[fileName];
});
Expand Down
37 changes: 20 additions & 17 deletions packages/jasmine-runner/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import { TestResult, TestStatus } from '@stryker-mutator/api/test_runner';
import { TestResult } from '@stryker-mutator/api/test_runner2';
import { TestStatus } from '@stryker-mutator/api/test_runner';

import JasmineConstructor = require('jasmine');

export function toStrykerTestResult(specResult: jasmine.CustomReporterResult, timeSpentMs: number): TestResult {
let status = TestStatus.Failed;
let failureMessages: string[] | undefined;
const baseResult = {
id: specResult.id.toString(),
name: specResult.fullName,
timeSpentMs,
};
if (specResult.status === 'disabled' || specResult.status === 'pending' || specResult.status === 'excluded') {
status = TestStatus.Skipped;
return {
...baseResult,
status: TestStatus.Skipped,
};
} else if (!specResult.failedExpectations || specResult.failedExpectations.length === 0) {
status = TestStatus.Success;
return {
...baseResult,
status: TestStatus.Success,
};
} else {
failureMessages = specResult.failedExpectations.map((failedExpectation) => failedExpectation.message);
return {
...baseResult,
status: TestStatus.Failed,
failureMessage: specResult.failedExpectations.map((failedExpectation) => failedExpectation.message).join(','),
};
}
return {
failureMessages,
name: specResult.fullName,
status,
timeSpentMs,
};
}

export function evalGlobal(body: string) {
const fn = new Function('require', body);
fn(require);
}

export const Jasmine = JasmineConstructor;
2 changes: 1 addition & 1 deletion packages/jasmine-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import * as strykerValidationSchema from '../schema/jasmine-runner-options.json'

import JasmineTestRunner from './JasmineTestRunner';

export const strykerPlugins = [declareClassPlugin(PluginKind.TestRunner, 'jasmine', JasmineTestRunner)];
export const strykerPlugins = [declareClassPlugin(PluginKind.TestRunner2, 'jasmine', JasmineTestRunner)];

export { strykerValidationSchema };
15 changes: 7 additions & 8 deletions packages/jasmine-runner/test/helpers/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { TestResult, TestStatus } from '@stryker-mutator/api/test_runner';
import { TestResult, SuccessTestResult, FailedTestResult, SkippedTestResult } from '@stryker-mutator/api/test_runner2';
import { expect } from 'chai';

export function expectTestResultsToEqual(
actualTestResults: TestResult[],
expectedResults: ReadonlyArray<{ name: string; status: TestStatus; failureMessages: string[] | undefined }>
) {
type TimelessTestResult = Omit<SuccessTestResult, 'timeSpentMs'> | Omit<FailedTestResult, 'timeSpentMs'> | Omit<SkippedTestResult, 'timeSpentMs'>;

export function expectTestResultsToEqual(actualTestResults: TestResult[], expectedResults: readonly TimelessTestResult[]) {
expect(actualTestResults).lengthOf(
expectedResults.length,
`Expected ${JSON.stringify(actualTestResults, null, 2)} to equal ${JSON.stringify(expectedResults, null, 2)}`
);
expectedResults.forEach((expectedResult) => {
const actualTestResult = actualTestResults.find((testResult) => testResult.name === expectedResult.name);
if (actualTestResult) {
expect({ name: actualTestResult.name, status: actualTestResult.status, failureMessages: actualTestResult.failureMessages }).deep.equal(
expectedResult
);
const actualWithoutTiming = { ...actualTestResult };
delete actualWithoutTiming.timeSpentMs;
expect(actualWithoutTiming).deep.equal(expectedResult);
} else {
expect.fail(
undefined,
Expand Down
5 changes: 5 additions & 0 deletions packages/jasmine-runner/test/helpers/global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare namespace NodeJS {
interface Global {
__testsInCurrentJasmineRun: string[];
}
}
9 changes: 9 additions & 0 deletions packages/jasmine-runner/test/helpers/mockFactories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ export function createRunDetails(): jasmine.RunDetails {
};
}

export function createCustomReporterResult(overrides?: Partial<jasmine.CustomReporterResult>): jasmine.CustomReporterResult {
return {
description: 'should have bar',
fullName: 'Foo should have bar',
id: 'spec0',
...overrides,
};
}

export function createEnvStub(): sinon.SinonStubbedInstance<jasmine.Env> {
return {
currentSpec: sinon.stub(),
Expand Down
7 changes: 6 additions & 1 deletion packages/jasmine-runner/test/helpers/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ chai.use(sinonChai);
chai.use(chaiAsPromised);

afterEach(() => {
sinon.reset();
delete global.__activeMutant__;
delete global.__currentTestId__;
delete global.__mutantCoverage__;
delete global.__testsInCurrentJasmineRun;

sinon.restore();
testInjector.reset();
});
Loading