2021-06-06 17:09:53 -07:00
|
|
|
/**
|
|
|
|
* Copyright Microsoft Corporation. All rights reserved.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import { installTransform } from './transform';
|
|
|
|
import type { FullConfig, Config, FullProject, Project, ReporterDescription, PreserveOutput } from './types';
|
2021-07-19 12:20:24 -05:00
|
|
|
import { isRegExp, mergeObjects, errorWithFile } from './util';
|
2021-06-06 17:09:53 -07:00
|
|
|
import { setCurrentlyLoadingFileSuite } from './globals';
|
|
|
|
import { Suite } from './test';
|
|
|
|
import { SerializedLoaderData } from './ipc';
|
|
|
|
import * as path from 'path';
|
2021-06-29 15:28:41 -07:00
|
|
|
import * as url from 'url';
|
2021-07-20 15:13:40 -05:00
|
|
|
import * as fs from 'fs';
|
2021-06-06 17:09:53 -07:00
|
|
|
import { ProjectImpl } from './project';
|
2021-07-16 21:15:03 -07:00
|
|
|
import { Reporter } from '../../types/testReporter';
|
2021-07-15 01:19:45 +02:00
|
|
|
import { LaunchConfig } from '../../types/test';
|
2021-07-20 15:03:01 -05:00
|
|
|
import { BuiltInReporter, builtInReporters } from './runner';
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
|
|
export class Loader {
|
|
|
|
private _defaultConfig: Config;
|
|
|
|
private _configOverrides: Config;
|
|
|
|
private _fullConfig: FullConfig;
|
|
|
|
private _config: Config = {};
|
|
|
|
private _configFile: string | undefined;
|
|
|
|
private _projects: ProjectImpl[] = [];
|
|
|
|
private _fileSuites = new Map<string, Suite>();
|
|
|
|
|
|
|
|
constructor(defaultConfig: Config, configOverrides: Config) {
|
|
|
|
this._defaultConfig = defaultConfig;
|
|
|
|
this._configOverrides = configOverrides;
|
|
|
|
this._fullConfig = baseFullConfig;
|
|
|
|
}
|
|
|
|
|
2021-07-12 11:59:58 -05:00
|
|
|
static async deserialize(data: SerializedLoaderData): Promise<Loader> {
|
2021-06-06 17:09:53 -07:00
|
|
|
const loader = new Loader(data.defaultConfig, data.overrides);
|
|
|
|
if ('file' in data.configFile)
|
2021-07-12 11:59:58 -05:00
|
|
|
await loader.loadConfigFile(data.configFile.file);
|
2021-06-06 17:09:53 -07:00
|
|
|
else
|
|
|
|
loader.loadEmptyConfig(data.configFile.rootDir);
|
|
|
|
return loader;
|
|
|
|
}
|
|
|
|
|
2021-07-12 11:59:58 -05:00
|
|
|
async loadConfigFile(file: string): Promise<Config> {
|
2021-06-06 17:09:53 -07:00
|
|
|
if (this._configFile)
|
|
|
|
throw new Error('Cannot load two config files');
|
2021-07-12 11:59:58 -05:00
|
|
|
let config = await this._requireOrImport(file);
|
|
|
|
if (config && typeof config === 'object' && ('default' in config))
|
|
|
|
config = config['default'];
|
|
|
|
this._config = config;
|
|
|
|
this._configFile = file;
|
|
|
|
const rawConfig = { ...config };
|
|
|
|
this._processConfigObject(path.dirname(file));
|
|
|
|
return rawConfig;
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
loadEmptyConfig(rootDir: string) {
|
|
|
|
this._config = {};
|
|
|
|
this._processConfigObject(rootDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
private _processConfigObject(rootDir: string) {
|
2021-06-23 10:30:54 -07:00
|
|
|
validateConfig(this._configFile || '<default config>', this._config);
|
2021-06-06 17:09:53 -07:00
|
|
|
|
2021-06-10 22:31:27 -07:00
|
|
|
// Resolve script hooks relative to the root dir.
|
|
|
|
if (this._config.globalSetup)
|
2021-07-20 15:13:40 -05:00
|
|
|
this._config.globalSetup = resolveScript(this._config.globalSetup, rootDir);
|
2021-06-10 22:31:27 -07:00
|
|
|
if (this._config.globalTeardown)
|
2021-07-20 15:13:40 -05:00
|
|
|
this._config.globalTeardown = resolveScript(this._config.globalTeardown, rootDir);
|
2021-06-10 22:31:27 -07:00
|
|
|
|
2021-06-06 17:09:53 -07:00
|
|
|
const configUse = mergeObjects(this._defaultConfig.use, this._config.use);
|
|
|
|
this._config = mergeObjects(mergeObjects(this._defaultConfig, this._config), { use: configUse });
|
|
|
|
|
2021-07-20 15:03:01 -05:00
|
|
|
if (this._config.testDir !== undefined)
|
2021-06-06 17:09:53 -07:00
|
|
|
this._config.testDir = path.resolve(rootDir, this._config.testDir);
|
|
|
|
const projects: Project[] = ('projects' in this._config) && this._config.projects !== undefined ? this._config.projects : [this._config];
|
|
|
|
|
|
|
|
this._fullConfig.rootDir = this._config.testDir || rootDir;
|
|
|
|
this._fullConfig.forbidOnly = takeFirst(this._configOverrides.forbidOnly, this._config.forbidOnly, baseFullConfig.forbidOnly);
|
|
|
|
this._fullConfig.globalSetup = takeFirst(this._configOverrides.globalSetup, this._config.globalSetup, baseFullConfig.globalSetup);
|
|
|
|
this._fullConfig.globalTeardown = takeFirst(this._configOverrides.globalTeardown, this._config.globalTeardown, baseFullConfig.globalTeardown);
|
|
|
|
this._fullConfig.globalTimeout = takeFirst(this._configOverrides.globalTimeout, this._configOverrides.globalTimeout, this._config.globalTimeout, baseFullConfig.globalTimeout);
|
|
|
|
this._fullConfig.grep = takeFirst(this._configOverrides.grep, this._config.grep, baseFullConfig.grep);
|
2021-06-18 17:56:59 -07:00
|
|
|
this._fullConfig.grepInvert = takeFirst(this._configOverrides.grepInvert, this._config.grepInvert, baseFullConfig.grepInvert);
|
2021-06-06 17:09:53 -07:00
|
|
|
this._fullConfig.maxFailures = takeFirst(this._configOverrides.maxFailures, this._config.maxFailures, baseFullConfig.maxFailures);
|
|
|
|
this._fullConfig.preserveOutput = takeFirst<PreserveOutput>(this._configOverrides.preserveOutput, this._config.preserveOutput, baseFullConfig.preserveOutput);
|
2021-07-20 15:03:01 -05:00
|
|
|
this._fullConfig.reporter = takeFirst(toReporters(this._configOverrides.reporter as any), resolveReporters(this._config.reporter, rootDir), baseFullConfig.reporter);
|
2021-06-14 22:45:58 -07:00
|
|
|
this._fullConfig.reportSlowTests = takeFirst(this._configOverrides.reportSlowTests, this._config.reportSlowTests, baseFullConfig.reportSlowTests);
|
2021-06-06 17:09:53 -07:00
|
|
|
this._fullConfig.quiet = takeFirst(this._configOverrides.quiet, this._config.quiet, baseFullConfig.quiet);
|
|
|
|
this._fullConfig.shard = takeFirst(this._configOverrides.shard, this._config.shard, baseFullConfig.shard);
|
|
|
|
this._fullConfig.updateSnapshots = takeFirst(this._configOverrides.updateSnapshots, this._config.updateSnapshots, baseFullConfig.updateSnapshots);
|
|
|
|
this._fullConfig.workers = takeFirst(this._configOverrides.workers, this._config.workers, baseFullConfig.workers);
|
2021-07-21 09:51:27 -05:00
|
|
|
this._fullConfig._launch = takeFirst(toLaunchServers(this._configOverrides._launch), toLaunchServers(this._config._launch), baseFullConfig._launch);
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
|
|
for (const project of projects)
|
|
|
|
this._addProject(project, this._fullConfig.rootDir);
|
|
|
|
this._fullConfig.projects = this._projects.map(p => p.config);
|
|
|
|
}
|
|
|
|
|
2021-06-29 15:28:41 -07:00
|
|
|
async loadTestFile(file: string) {
|
2021-06-06 17:09:53 -07:00
|
|
|
if (this._fileSuites.has(file))
|
|
|
|
return this._fileSuites.get(file)!;
|
|
|
|
try {
|
2021-07-16 15:23:50 -07:00
|
|
|
const suite = new Suite(path.relative(this._fullConfig.rootDir, file) || path.basename(file));
|
2021-06-21 11:25:15 -07:00
|
|
|
suite._requireFile = file;
|
2021-07-18 17:40:59 -07:00
|
|
|
suite.location = { file, line: 0, column: 0 };
|
2021-06-06 17:09:53 -07:00
|
|
|
setCurrentlyLoadingFileSuite(suite);
|
2021-07-12 11:59:58 -05:00
|
|
|
await this._requireOrImport(file);
|
2021-06-06 17:09:53 -07:00
|
|
|
this._fileSuites.set(file, suite);
|
|
|
|
return suite;
|
|
|
|
} finally {
|
|
|
|
setCurrentlyLoadingFileSuite(undefined);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-12 11:59:58 -05:00
|
|
|
async loadGlobalHook(file: string, name: string): Promise<(config: FullConfig) => any> {
|
|
|
|
let hook = await this._requireOrImport(file);
|
|
|
|
if (hook && typeof hook === 'object' && ('default' in hook))
|
|
|
|
hook = hook['default'];
|
|
|
|
if (typeof hook !== 'function')
|
|
|
|
throw errorWithFile(file, `${name} file must export a single function.`);
|
|
|
|
return hook;
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
2021-07-12 11:59:58 -05:00
|
|
|
async loadReporter(file: string): Promise<new (arg?: any) => Reporter> {
|
|
|
|
let func = await this._requireOrImport(path.resolve(this._fullConfig.rootDir, file));
|
|
|
|
if (func && typeof func === 'object' && ('default' in func))
|
|
|
|
func = func['default'];
|
|
|
|
if (typeof func !== 'function')
|
|
|
|
throw errorWithFile(file, `reporter file must export a single class.`);
|
|
|
|
return func;
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fullConfig(): FullConfig {
|
|
|
|
return this._fullConfig;
|
|
|
|
}
|
|
|
|
|
|
|
|
projects() {
|
|
|
|
return this._projects;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileSuites() {
|
|
|
|
return this._fileSuites;
|
|
|
|
}
|
|
|
|
|
|
|
|
serialize(): SerializedLoaderData {
|
|
|
|
return {
|
|
|
|
defaultConfig: this._defaultConfig,
|
|
|
|
configFile: this._configFile ? { file: this._configFile } : { rootDir: this._fullConfig.rootDir },
|
|
|
|
overrides: this._configOverrides,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private _addProject(projectConfig: Project, rootDir: string) {
|
|
|
|
let testDir = takeFirst(projectConfig.testDir, rootDir);
|
|
|
|
if (!path.isAbsolute(testDir))
|
|
|
|
testDir = path.resolve(rootDir, testDir);
|
2021-06-23 11:08:35 +02:00
|
|
|
let outputDir = takeFirst(this._configOverrides.outputDir, projectConfig.outputDir, this._config.outputDir, path.resolve(process.cwd(), 'test-results'));
|
2021-06-15 13:39:07 -07:00
|
|
|
if (!path.isAbsolute(outputDir))
|
|
|
|
outputDir = path.resolve(rootDir, outputDir);
|
2021-06-06 17:09:53 -07:00
|
|
|
const fullProject: FullProject = {
|
|
|
|
define: takeFirst(this._configOverrides.define, projectConfig.define, this._config.define, []),
|
2021-06-14 21:52:10 -07:00
|
|
|
expect: takeFirst(this._configOverrides.expect, projectConfig.expect, this._config.expect, undefined),
|
2021-06-15 13:39:07 -07:00
|
|
|
outputDir,
|
2021-06-06 17:09:53 -07:00
|
|
|
repeatEach: takeFirst(this._configOverrides.repeatEach, projectConfig.repeatEach, this._config.repeatEach, 1),
|
|
|
|
retries: takeFirst(this._configOverrides.retries, projectConfig.retries, this._config.retries, 0),
|
|
|
|
metadata: takeFirst(this._configOverrides.metadata, projectConfig.metadata, this._config.metadata, undefined),
|
|
|
|
name: takeFirst(this._configOverrides.name, projectConfig.name, this._config.name, ''),
|
|
|
|
testDir,
|
|
|
|
testIgnore: takeFirst(this._configOverrides.testIgnore, projectConfig.testIgnore, this._config.testIgnore, []),
|
2021-06-29 15:28:41 -07:00
|
|
|
testMatch: takeFirst(this._configOverrides.testMatch, projectConfig.testMatch, this._config.testMatch, '**/?(*.)@(spec|test).@(ts|js|mjs)'),
|
2021-06-06 17:09:53 -07:00
|
|
|
timeout: takeFirst(this._configOverrides.timeout, projectConfig.timeout, this._config.timeout, 10000),
|
|
|
|
use: mergeObjects(mergeObjects(this._config.use, projectConfig.use), this._configOverrides.use),
|
|
|
|
};
|
|
|
|
this._projects.push(new ProjectImpl(fullProject, this._projects.length));
|
|
|
|
}
|
2021-07-12 11:59:58 -05:00
|
|
|
|
|
|
|
|
|
|
|
private async _requireOrImport(file: string) {
|
|
|
|
const revertBabelRequire = installTransform();
|
|
|
|
try {
|
|
|
|
const esmImport = () => eval(`import(${JSON.stringify(url.pathToFileURL(file))})`);
|
|
|
|
if (file.endsWith('.mjs')) {
|
|
|
|
return await esmImport();
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
return require(file);
|
|
|
|
} catch (e) {
|
|
|
|
// Attempt to load this module as ESM if a normal require didn't work.
|
|
|
|
if (e.code === 'ERR_REQUIRE_ESM')
|
|
|
|
return await esmImport();
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
if (error instanceof SyntaxError && error.message.includes('Cannot use import statement outside a module'))
|
|
|
|
throw errorWithFile(file, 'JavaScript files must end with .mjs to use import.');
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
revertBabelRequire();
|
|
|
|
}
|
|
|
|
}
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function takeFirst<T>(...args: (T | undefined)[]): T {
|
|
|
|
for (const arg of args) {
|
|
|
|
if (arg !== undefined)
|
|
|
|
return arg;
|
|
|
|
}
|
|
|
|
return undefined as any as T;
|
|
|
|
}
|
|
|
|
|
2021-07-20 15:03:01 -05:00
|
|
|
function toReporters(reporters: BuiltInReporter | ReporterDescription[] | undefined): ReporterDescription[] | undefined {
|
2021-06-06 17:09:53 -07:00
|
|
|
if (!reporters)
|
|
|
|
return;
|
|
|
|
if (typeof reporters === 'string')
|
|
|
|
return [ [reporters] ];
|
|
|
|
return reporters;
|
|
|
|
}
|
|
|
|
|
2021-07-15 01:19:45 +02:00
|
|
|
function toLaunchServers(launchConfigs?: LaunchConfig | LaunchConfig[]): LaunchConfig[]|undefined {
|
|
|
|
if (!launchConfigs)
|
|
|
|
return;
|
|
|
|
if (!Array.isArray(launchConfigs))
|
|
|
|
return [launchConfigs];
|
|
|
|
return launchConfigs;
|
|
|
|
}
|
|
|
|
|
2021-06-23 10:30:54 -07:00
|
|
|
function validateConfig(file: string, config: Config) {
|
2021-06-06 17:09:53 -07:00
|
|
|
if (typeof config !== 'object' || !config)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `Configuration file must export a single object`);
|
2021-06-06 17:09:53 -07:00
|
|
|
|
2021-06-23 10:30:54 -07:00
|
|
|
validateProject(file, config, 'config');
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
|
|
if ('forbidOnly' in config && config.forbidOnly !== undefined) {
|
|
|
|
if (typeof config.forbidOnly !== 'boolean')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.forbidOnly must be a boolean`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('globalSetup' in config && config.globalSetup !== undefined) {
|
|
|
|
if (typeof config.globalSetup !== 'string')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.globalSetup must be a string`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('globalTeardown' in config && config.globalTeardown !== undefined) {
|
|
|
|
if (typeof config.globalTeardown !== 'string')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.globalTeardown must be a string`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('globalTimeout' in config && config.globalTimeout !== undefined) {
|
|
|
|
if (typeof config.globalTimeout !== 'number' || config.globalTimeout < 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.globalTimeout must be a non-negative number`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('grep' in config && config.grep !== undefined) {
|
|
|
|
if (Array.isArray(config.grep)) {
|
|
|
|
config.grep.forEach((item, index) => {
|
|
|
|
if (!isRegExp(item))
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.grep[${index}] must be a RegExp`);
|
2021-06-06 17:09:53 -07:00
|
|
|
});
|
|
|
|
} else if (!isRegExp(config.grep)) {
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.grep must be a RegExp`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-18 17:56:59 -07:00
|
|
|
if ('grepInvert' in config && config.grepInvert !== undefined) {
|
|
|
|
if (Array.isArray(config.grepInvert)) {
|
|
|
|
config.grepInvert.forEach((item, index) => {
|
|
|
|
if (!isRegExp(item))
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.grepInvert[${index}] must be a RegExp`);
|
2021-06-18 17:56:59 -07:00
|
|
|
});
|
|
|
|
} else if (!isRegExp(config.grepInvert)) {
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.grep must be a RegExp`);
|
2021-06-18 17:56:59 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-06 17:09:53 -07:00
|
|
|
if ('maxFailures' in config && config.maxFailures !== undefined) {
|
|
|
|
if (typeof config.maxFailures !== 'number' || config.maxFailures < 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.maxFailures must be a non-negative number`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('preserveOutput' in config && config.preserveOutput !== undefined) {
|
|
|
|
if (typeof config.preserveOutput !== 'string' || !['always', 'never', 'failures-only'].includes(config.preserveOutput))
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.preserveOutput must be one of "always", "never" or "failures-only"`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('projects' in config && config.projects !== undefined) {
|
|
|
|
if (!Array.isArray(config.projects))
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.projects must be an array`);
|
2021-06-06 17:09:53 -07:00
|
|
|
config.projects.forEach((project, index) => {
|
2021-06-23 10:30:54 -07:00
|
|
|
validateProject(file, project, `config.projects[${index}]`);
|
2021-06-06 17:09:53 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if ('quiet' in config && config.quiet !== undefined) {
|
|
|
|
if (typeof config.quiet !== 'boolean')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.quiet must be a boolean`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('reporter' in config && config.reporter !== undefined) {
|
|
|
|
if (Array.isArray(config.reporter)) {
|
|
|
|
config.reporter.forEach((item, index) => {
|
|
|
|
if (!Array.isArray(item) || item.length <= 0 || item.length > 2 || typeof item[0] !== 'string')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.reporter[${index}] must be a tuple [name, optionalArgument]`);
|
2021-06-06 17:09:53 -07:00
|
|
|
});
|
2021-07-20 15:03:01 -05:00
|
|
|
} else if (typeof config.reporter !== 'string') {
|
|
|
|
throw errorWithFile(file, `config.reporter must be a string`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:45:58 -07:00
|
|
|
if ('reportSlowTests' in config && config.reportSlowTests !== undefined && config.reportSlowTests !== null) {
|
|
|
|
if (!config.reportSlowTests || typeof config.reportSlowTests !== 'object')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.reportSlowTests must be an object`);
|
2021-06-14 22:45:58 -07:00
|
|
|
if (!('max' in config.reportSlowTests) || typeof config.reportSlowTests.max !== 'number' || config.reportSlowTests.max < 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.reportSlowTests.max must be a non-negative number`);
|
2021-06-14 22:45:58 -07:00
|
|
|
if (!('threshold' in config.reportSlowTests) || typeof config.reportSlowTests.threshold !== 'number' || config.reportSlowTests.threshold < 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.reportSlowTests.threshold must be a non-negative number`);
|
2021-06-14 22:45:58 -07:00
|
|
|
}
|
|
|
|
|
2021-06-06 17:09:53 -07:00
|
|
|
if ('shard' in config && config.shard !== undefined && config.shard !== null) {
|
|
|
|
if (!config.shard || typeof config.shard !== 'object')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.shard must be an object`);
|
2021-06-06 17:09:53 -07:00
|
|
|
if (!('total' in config.shard) || typeof config.shard.total !== 'number' || config.shard.total < 1)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.shard.total must be a positive number`);
|
2021-06-06 17:09:53 -07:00
|
|
|
if (!('current' in config.shard) || typeof config.shard.current !== 'number' || config.shard.current < 1 || config.shard.current > config.shard.total)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.shard.current must be a positive number, not greater than config.shard.total`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('updateSnapshots' in config && config.updateSnapshots !== undefined) {
|
|
|
|
if (typeof config.updateSnapshots !== 'string' || !['all', 'none', 'missing'].includes(config.updateSnapshots))
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.updateSnapshots must be one of "all", "none" or "missing"`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('workers' in config && config.workers !== undefined) {
|
|
|
|
if (typeof config.workers !== 'number' || config.workers <= 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `config.workers must be a positive number`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-23 10:30:54 -07:00
|
|
|
function validateProject(file: string, project: Project, title: string) {
|
2021-06-06 17:09:53 -07:00
|
|
|
if (typeof project !== 'object' || !project)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title} must be an object`);
|
2021-06-06 17:09:53 -07:00
|
|
|
|
|
|
|
if ('define' in project && project.define !== undefined) {
|
|
|
|
if (Array.isArray(project.define)) {
|
|
|
|
project.define.forEach((item, index) => {
|
2021-06-23 10:30:54 -07:00
|
|
|
validateDefine(file, item, `${title}.define[${index}]`);
|
2021-06-06 17:09:53 -07:00
|
|
|
});
|
|
|
|
} else {
|
2021-06-23 10:30:54 -07:00
|
|
|
validateDefine(file, project.define, `${title}.define`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ('name' in project && project.name !== undefined) {
|
|
|
|
if (typeof project.name !== 'string')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.name must be a string`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('outputDir' in project && project.outputDir !== undefined) {
|
|
|
|
if (typeof project.outputDir !== 'string')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.outputDir must be a string`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('repeatEach' in project && project.repeatEach !== undefined) {
|
|
|
|
if (typeof project.repeatEach !== 'number' || project.repeatEach < 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.repeatEach must be a non-negative number`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('retries' in project && project.retries !== undefined) {
|
|
|
|
if (typeof project.retries !== 'number' || project.retries < 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.retries must be a non-negative number`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('testDir' in project && project.testDir !== undefined) {
|
|
|
|
if (typeof project.testDir !== 'string')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.testDir must be a string`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const prop of ['testIgnore', 'testMatch'] as const) {
|
|
|
|
if (prop in project && project[prop] !== undefined) {
|
|
|
|
const value = project[prop];
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
value.forEach((item, index) => {
|
|
|
|
if (typeof item !== 'string' && !isRegExp(item))
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.${prop}[${index}] must be a string or a RegExp`);
|
2021-06-06 17:09:53 -07:00
|
|
|
});
|
|
|
|
} else if (typeof value !== 'string' && !isRegExp(value)) {
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.${prop} must be a string or a RegExp`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ('timeout' in project && project.timeout !== undefined) {
|
|
|
|
if (typeof project.timeout !== 'number' || project.timeout < 0)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.timeout must be a non-negative number`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('use' in project && project.use !== undefined) {
|
|
|
|
if (!project.use || typeof project.use !== 'object')
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title}.use must be an object`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-23 10:30:54 -07:00
|
|
|
function validateDefine(file: string, define: any, title: string) {
|
2021-06-06 17:09:53 -07:00
|
|
|
if (!define || typeof define !== 'object' || !define.test || !define.fixtures)
|
2021-06-23 10:30:54 -07:00
|
|
|
throw errorWithFile(file, `${title} must be an object with "test" and "fixtures" properties`);
|
2021-06-06 17:09:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const baseFullConfig: FullConfig = {
|
|
|
|
forbidOnly: false,
|
|
|
|
globalSetup: null,
|
|
|
|
globalTeardown: null,
|
|
|
|
globalTimeout: 0,
|
|
|
|
grep: /.*/,
|
2021-06-18 17:56:59 -07:00
|
|
|
grepInvert: null,
|
2021-06-06 17:09:53 -07:00
|
|
|
maxFailures: 0,
|
|
|
|
preserveOutput: 'always',
|
|
|
|
projects: [],
|
|
|
|
reporter: [ ['list'] ],
|
2021-06-14 22:45:58 -07:00
|
|
|
reportSlowTests: null,
|
2021-06-06 17:09:53 -07:00
|
|
|
rootDir: path.resolve(process.cwd()),
|
|
|
|
quiet: false,
|
|
|
|
shard: null,
|
|
|
|
updateSnapshots: 'missing',
|
|
|
|
workers: 1,
|
2021-07-21 09:51:27 -05:00
|
|
|
_launch: [],
|
2021-06-06 17:09:53 -07:00
|
|
|
};
|
2021-07-20 15:03:01 -05:00
|
|
|
|
|
|
|
function resolveReporters(reporters: Config['reporter'], rootDir: string): ReporterDescription[]|undefined {
|
|
|
|
return toReporters(reporters as any)?.map(([id, arg]) => {
|
|
|
|
if (builtInReporters.includes(id as any))
|
|
|
|
return [id, arg];
|
|
|
|
return [require.resolve(id, { paths: [ rootDir ] }), arg];
|
|
|
|
});
|
|
|
|
}
|
2021-07-20 15:13:40 -05:00
|
|
|
|
|
|
|
function resolveScript(id: string, rootDir: string) {
|
|
|
|
const localPath = path.resolve(rootDir, id);
|
|
|
|
if (fs.existsSync(localPath))
|
|
|
|
return localPath;
|
|
|
|
return require.resolve(id, { paths: [rootDir] });
|
|
|
|
}
|