UNPKG

@stryker-mutator/mocha-runner

Version:

A plugin to use the mocha test runner in Stryker, the JavaScript mutation testing framework

153 lines 6.41 kB
import { commonTokens, tokens } from '@stryker-mutator/api/plugin'; import { escapeRegExp } from '@stryker-mutator/util'; import { DryRunStatus, toMutantRunResult, determineHitLimitReached, } from '@stryker-mutator/api/test-runner'; import { StrykerMochaReporter } from './stryker-mocha-reporter.js'; import * as pluginTokens from './plugin-tokens.js'; export class MochaTestRunner { log; options; loader; mochaAdapter; mocha; instrumenterContext; originalGrep; beforeEach; static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.loader, pluginTokens.mochaAdapter, pluginTokens.globalNamespace); loadedEnv = false; constructor(log, options, loader, mochaAdapter, globalNamespace) { this.log = log; this.options = options; this.loader = loader; this.mochaAdapter = mochaAdapter; StrykerMochaReporter.log = log; this.instrumenterContext = global[globalNamespace] ?? (global[globalNamespace] = {}); } capabilities() { return Promise.resolve({ // Mocha directly uses `import`, so reloading files once they are loaded is impossible reloadEnvironment: false, }); } async init() { const mochaOptions = this.loader.load(this.options); const testFileNames = this.mochaAdapter.collectFiles(mochaOptions); let rootHooks; if (mochaOptions.require) { if (mochaOptions.require.includes('esm')) { throw new Error('Config option "mochaOptions.require" does not support "esm", please use `"testRunnerNodeArgs": ["--require", "esm"]` instead. See https://github.com/stryker-mutator/stryker-js/issues/3014 for more information.'); } rootHooks = await this.mochaAdapter.handleRequires(mochaOptions.require); } this.mocha = this.mochaAdapter.create({ reporter: StrykerMochaReporter, timeout: 0, rootHooks, }); this.mocha.cleanReferencesAfterRun(false); testFileNames.forEach((fileName) => this.mocha.addFile(fileName)); this.setIfDefined(mochaOptions['async-only'], (asyncOnly) => asyncOnly && this.mocha.asyncOnly()); this.setIfDefined(mochaOptions.ui, this.mocha.ui); this.setIfDefined(mochaOptions.grep, this.mocha.grep); this.originalGrep = mochaOptions.grep; // Bind beforeEach, so we can use that for perTest code coverage in dry run // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; this.mocha.suite.beforeEach(function () { self.beforeEach?.(this); }); } setIfDefined(value, operation) { if (typeof value !== 'undefined') { operation.apply(this.mocha, [value]); } } async dryRun({ coverageAnalysis, disableBail }) { if (coverageAnalysis === 'perTest') { this.beforeEach = (context) => { this.instrumenterContext.currentTestId = context.currentTest?.fullTitle(); }; } const runResult = await this.run(disableBail); if (runResult.status === DryRunStatus.Complete && coverageAnalysis !== 'off') { runResult.mutantCoverage = this.instrumenterContext.mutantCoverage; } delete this.beforeEach; return runResult; } async mutantRun({ activeMutant, testFilter, disableBail, hitLimit, mutantActivation }) { this.instrumenterContext.hitLimit = hitLimit; this.instrumenterContext.hitCount = hitLimit ? 0 : undefined; if (testFilter) { const metaRegExp = testFilter.map((testId) => `(^${escapeRegExp(testId)}$)`).join('|'); const regex = new RegExp(metaRegExp); this.mocha.grep(regex); } else { this.setIfDefined(this.originalGrep, this.mocha.grep); } const dryRunResult = await this.run(disableBail, activeMutant.id, mutantActivation); return toMutantRunResult(dryRunResult); } async run(disableBail, activeMutantId, mutantActivation) { setBail(!disableBail, this.mocha.suite); try { if (!this.loadedEnv) { this.instrumenterContext.activeMutant = mutantActivation === 'static' ? activeMutantId : undefined; // Loading files Async is needed to support native esm modules // See https://mochajs.org/api/mocha#loadFilesAsync await this.mocha.loadFilesAsync(); this.loadedEnv = true; } this.instrumenterContext.activeMutant = activeMutantId; await this.runMocha(); const reporter = StrykerMochaReporter.currentInstance; if (reporter) { const timeoutResult = determineHitLimitReached(this.instrumenterContext.hitCount, this.instrumenterContext.hitLimit); if (timeoutResult) { return timeoutResult; } const result = { status: DryRunStatus.Complete, tests: reporter.tests, }; return result; } else { const errorMessage = `Mocha didn't instantiate the ${StrykerMochaReporter.name} correctly. Test result cannot be reported.`; this.log.error(errorMessage); return { status: DryRunStatus.Error, errorMessage, }; } } catch (errorMessage) { return { errorMessage, status: DryRunStatus.Error, }; } function setBail(bail, suite) { suite.bail(bail); suite.suites.forEach((childSuite) => setBail(bail, childSuite)); } } // eslint-disable-next-line @typescript-eslint/require-await async dispose() { try { this.mocha?.dispose(); } catch (err) { if (err?.code !== 'ERR_MOCHA_INSTANCE_ALREADY_RUNNING') { // Oops, didn't mean to catch this one throw err; } } } async runMocha() { return new Promise((res) => { this.mocha.run(() => res()); }); } } //# sourceMappingURL=mocha-test-runner.js.map