1 | import fs from 'fs';
|
2 | import path from 'path';
|
3 |
|
4 | import { Logger } from '@stryker-mutator/api/logging';
|
5 | import { commonTokens, tokens } from '@stryker-mutator/api/plugin';
|
6 | import { propertyPath } from '@stryker-mutator/util';
|
7 |
|
8 | import { MochaOptions, MochaRunnerOptions } from '../src-generated/mocha-runner-options.js';
|
9 |
|
10 | import { LibWrapper } from './lib-wrapper.js';
|
11 | import { filterConfig, serializeMochaLoadOptionsArguments } from './utils.js';
|
12 | import { MochaRunnerWithStrykerOptions } from './mocha-runner-with-stryker-options.js';
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | export 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 |
|
32 | export 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 |