@stryker-mutator/mocha-runner
Version:
A plugin to use the mocha test runner in Stryker, the JavaScript mutation testing framework
152 lines (139 loc) • 5.37 kB
text/typescript
import fs from 'fs';
import path from 'path';
import { Logger } from '@stryker-mutator/api/logging';
import { commonTokens, tokens } from '@stryker-mutator/api/plugin';
import { propertyPath } from '@stryker-mutator/util';
import { MochaOptions, MochaRunnerOptions } from '../src-generated/mocha-runner-options.js';
import { LibWrapper } from './lib-wrapper.js';
import { filterConfig, serializeMochaLoadOptionsArguments } from './utils.js';
import { MochaRunnerWithStrykerOptions } from './mocha-runner-with-stryker-options.js';
/**
* Subset of defaults for mocha options
* @see https://github.com/mochajs/mocha/blob/master/lib/mocharc.json
*/
export const DEFAULT_MOCHA_OPTIONS: Readonly<MochaOptions> = Object.freeze({
extension: ['js'],
require: [],
file: [],
ignore: [],
opts: './test/mocha.opts',
spec: ['test'],
ui: 'bdd',
'no-package': false,
'no-opts': false,
'no-config': false,
'async-only': false,
});
export class MochaOptionsLoader {
public static inject = tokens(commonTokens.logger);
constructor(private readonly log: Logger) {}
public load(strykerOptions: MochaRunnerWithStrykerOptions): MochaOptions {
const mochaOptions = { ...strykerOptions.mochaOptions } as MochaOptions;
const options = { ...DEFAULT_MOCHA_OPTIONS, ...this.loadMochaOptions(mochaOptions), ...mochaOptions };
if (this.log.isDebugEnabled()) {
this.log.debug(`Loaded options: ${JSON.stringify(options, null, 2)}`);
}
return options;
}
private loadMochaOptions(overrides: MochaOptions) {
if (LibWrapper.loadOptions) {
this.log.debug("Mocha >= 6 detected. Using mocha's `%s` to load mocha options", LibWrapper.loadOptions.name);
return this.loadMocha6Options(overrides);
} else {
this.log.warn('DEPRECATED: Mocha < 6 detected. Please upgrade to at least Mocha version 6. Stryker will drop support for Mocha < 6 in V5.');
this.log.debug('Mocha < 6 detected. Using custom logic to parse mocha options');
return this.loadLegacyMochaOptsFile(overrides);
}
}
private loadMocha6Options(overrides: MochaOptions) {
const args = serializeMochaLoadOptionsArguments(overrides);
const rawConfig = LibWrapper.loadOptions(args) ?? {};
if (this.log.isTraceEnabled()) {
this.log.trace(`Mocha: ${LibWrapper.loadOptions.name}([${args.map((arg) => `'${arg}'`).join(',')}]) => ${JSON.stringify(rawConfig)}`);
}
const options = filterConfig(rawConfig);
return options;
}
private loadLegacyMochaOptsFile(options: MochaOptions): Partial<MochaOptions> {
if (options['no-opts']) {
this.log.debug('Not reading additional mochaOpts from a file');
return options;
}
switch (typeof options.opts) {
case 'undefined': {
const defaultMochaOptsFileName = path.resolve(DEFAULT_MOCHA_OPTIONS.opts!);
if (fs.existsSync(defaultMochaOptsFileName)) {
return this.readMochaOptsFile(defaultMochaOptsFileName);
} else {
this.log.debug(
'No mocha opts file found, not loading additional mocha options (%s was not defined).',
propertyPath<MochaRunnerOptions>()('mochaOptions', 'opts'),
);
return {};
}
}
case 'string': {
const optsFileName = path.resolve(options.opts);
if (fs.existsSync(optsFileName)) {
return this.readMochaOptsFile(optsFileName);
} else {
this.log.error(`Could not load opts from "${optsFileName}". Please make sure opts file exists.`);
return {};
}
}
default:
return {};
}
}
private readMochaOptsFile(optsFileName: string) {
this.log.info(`Loading mochaOpts from "${optsFileName}"`);
return this.parseOptsFile(fs.readFileSync(optsFileName, 'utf8'));
}
private parseOptsFile(optsFileContent: string): MochaOptions {
const options = optsFileContent.split('\n').map((val) => val.trim());
const mochaRunnerOptions: MochaOptions = Object.create(null);
options.forEach((option) => {
const args = option.split(' ').filter(Boolean);
if (args[0]) {
switch (args[0]) {
case '--require':
case '-r':
args.shift();
if (!mochaRunnerOptions.require) {
mochaRunnerOptions.require = [];
}
mochaRunnerOptions.require.push(...args);
break;
case '--async-only':
case '-A':
mochaRunnerOptions['async-only'] = true;
break;
case '--ui':
case '-u':
mochaRunnerOptions.ui = (this.parseNextString(args) as 'bdd' | 'exports' | 'qunit' | 'tdd') ?? DEFAULT_MOCHA_OPTIONS.ui!;
break;
case '--grep':
case '-g': {
let arg = `${this.parseNextString(args)}`;
if (arg.startsWith('/') && arg.endsWith('/')) {
arg = arg.substring(1, arg.length - 1);
}
mochaRunnerOptions.grep = arg;
break;
}
default:
this.log.debug(`Ignoring option "${args[0]}" as it is not supported.`);
break;
}
}
});
return mochaRunnerOptions;
}
private parseNextString(args: string[]): string | undefined {
if (args.length > 1) {
return args[1];
} else {
return undefined;
}
}
}