UNPKG

5.34 kBPlain TextView Raw
1import fs from 'fs';
2import path from 'path';
3
4import { Logger } from '@stryker-mutator/api/logging';
5import { commonTokens, tokens } from '@stryker-mutator/api/plugin';
6import { propertyPath } from '@stryker-mutator/util';
7
8import { MochaOptions, MochaRunnerOptions } from '../src-generated/mocha-runner-options.js';
9
10import { LibWrapper } from './lib-wrapper.js';
11import { filterConfig, serializeMochaLoadOptionsArguments } from './utils.js';
12import { MochaRunnerWithStrykerOptions } from './mocha-runner-with-stryker-options.js';
13
14/**
15 * Subset of defaults for mocha options
16 * @see https://github.com/mochajs/mocha/blob/master/lib/mocharc.json
17 */
18export const DEFAULT_MOCHA_OPTIONS: Readonly<MochaOptions> = Object.freeze({
19 extension: ['js'],
20 require: [],
21 file: [],
22 ignore: [],
23 opts: './test/mocha.opts',
24 spec: ['test'],
25 ui: 'bdd',
26 'no-package': false,
27 'no-opts': false,
28 'no-config': false,
29 'async-only': false,
30});
31
32export class MochaOptionsLoader {
33 public static inject = tokens(commonTokens.logger);
34 constructor(private readonly log: Logger) {}
35
36 public load(strykerOptions: MochaRunnerWithStrykerOptions): MochaOptions {
37 const mochaOptions = { ...strykerOptions.mochaOptions } as MochaOptions;
38 const options = { ...DEFAULT_MOCHA_OPTIONS, ...this.loadMochaOptions(mochaOptions), ...mochaOptions };
39 if (this.log.isDebugEnabled()) {
40 this.log.debug(`Loaded options: ${JSON.stringify(options, null, 2)}`);
41 }
42 return options;
43 }
44
45 private loadMochaOptions(overrides: MochaOptions) {
46 if (LibWrapper.loadOptions) {
47 this.log.debug("Mocha >= 6 detected. Using mocha's `%s` to load mocha options", LibWrapper.loadOptions.name);
48 return this.loadMocha6Options(overrides);
49 } else {
50 this.log.warn('DEPRECATED: Mocha < 6 detected. Please upgrade to at least Mocha version 6. Stryker will drop support for Mocha < 6 in V5.');
51 this.log.debug('Mocha < 6 detected. Using custom logic to parse mocha options');
52 return this.loadLegacyMochaOptsFile(overrides);
53 }
54 }
55
56 private loadMocha6Options(overrides: MochaOptions) {
57 const args = serializeMochaLoadOptionsArguments(overrides);
58 const rawConfig = LibWrapper.loadOptions(args) ?? {};
59 if (this.log.isTraceEnabled()) {
60 this.log.trace(`Mocha: ${LibWrapper.loadOptions.name}([${args.map((arg) => `'${arg}'`).join(',')}]) => ${JSON.stringify(rawConfig)}`);
61 }
62 const options = filterConfig(rawConfig);
63 return options;
64 }
65
66 private loadLegacyMochaOptsFile(options: MochaOptions): Partial<MochaOptions> {
67 if (options['no-opts']) {
68 this.log.debug('Not reading additional mochaOpts from a file');
69 return options;
70 }
71 switch (typeof options.opts) {
72 case 'undefined':
73 const defaultMochaOptsFileName = path.resolve(DEFAULT_MOCHA_OPTIONS.opts!);
74 if (fs.existsSync(defaultMochaOptsFileName)) {
75 return this.readMochaOptsFile(defaultMochaOptsFileName);
76 } else {
77 this.log.debug(
78 'No mocha opts file found, not loading additional mocha options (%s was not defined).',
79 propertyPath<MochaRunnerOptions>()('mochaOptions', 'opts')
80 );
81 return {};
82 }
83 case 'string':
84 const optsFileName = path.resolve(options.opts);
85 if (fs.existsSync(optsFileName)) {
86 return this.readMochaOptsFile(optsFileName);
87 } else {
88 this.log.error(`Could not load opts from "${optsFileName}". Please make sure opts file exists.`);
89 return {};
90 }
91 default:
92 return {};
93 }
94 }
95
96 private readMochaOptsFile(optsFileName: string) {
97 this.log.info(`Loading mochaOpts from "${optsFileName}"`);
98 return this.parseOptsFile(fs.readFileSync(optsFileName, 'utf8'));
99 }
100
101 private parseOptsFile(optsFileContent: string): MochaOptions {
102 const options = optsFileContent.split('\n').map((val) => val.trim());
103 const mochaRunnerOptions: MochaOptions = Object.create(null);
104 options.forEach((option) => {
105 const args = option.split(' ').filter(Boolean);
106 if (args[0]) {
107 switch (args[0]) {
108 case '--require':
109 case '-r':
110 args.shift();
111 if (!mochaRunnerOptions.require) {
112 mochaRunnerOptions.require = [];
113 }
114 mochaRunnerOptions.require.push(...args);
115 break;
116 case '--async-only':
117 case '-A':
118 mochaRunnerOptions['async-only'] = true;
119 break;
120 case '--ui':
121 case '-u':
122 mochaRunnerOptions.ui = (this.parseNextString(args) as 'bdd' | 'exports' | 'qunit' | 'tdd') ?? DEFAULT_MOCHA_OPTIONS.ui!;
123 break;
124 case '--grep':
125 case '-g':
126 let arg = `${this.parseNextString(args)}`;
127 if (arg.startsWith('/') && arg.endsWith('/')) {
128 arg = arg.substring(1, arg.length - 1);
129 }
130 mochaRunnerOptions.grep = arg;
131 break;
132 default:
133 this.log.debug(`Ignoring option "${args[0]}" as it is not supported.`);
134 break;
135 }
136 }
137 });
138 return mochaRunnerOptions;
139 }
140
141 private parseNextString(args: string[]): string | undefined {
142 if (args.length > 1) {
143 return args[1];
144 } else {
145 return undefined;
146 }
147 }
148}
149
\No newline at end of file