chore(test runner): simplify code around running tasks (#32557)

This avoids complex boilerplate around `onConfigure`/`onEnd`/`onExit`
and managing the resulting status.
This commit is contained in:
Dmitry Gozman 2024-09-11 13:09:00 -07:00 committed by GitHub
parent 6f52834f74
commit 29a0f49e9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 154 additions and 217 deletions

View File

@ -15,12 +15,11 @@
* limitations under the License.
*/
import { monotonicTime } from 'playwright-core/lib/utils';
import type { FullResult, TestError } from '../../types/testReporter';
import { webServerPluginsForConfig } from '../plugins/webServerPlugin';
import { collectFilesForProject, filterProjects } from './projectUtils';
import { createErrorCollectingReporter, createReporters } from './reporters';
import { TestRun, createTaskRunner, createTaskRunnerForClearCache, createTaskRunnerForDevServer, createTaskRunnerForList, createTaskRunnerForRelatedTestFiles } from './tasks';
import { TestRun, createClearCacheTask, createGlobalSetupTasks, createLoadTask, createPluginSetupTasks, createReportBeginTask, createRunTestsTasks, createStartDevServerTask, runTasks } from './tasks';
import type { FullConfigInternal } from '../common/config';
import { affectedTestFiles } from '../transform/compilationCache';
import { InternalReporter } from '../reporters/internalReporter';
@ -69,7 +68,6 @@ export class Runner {
async runAllTests(): Promise<FullResult['status']> {
const config = this._config;
const listOnly = config.cliListOnly;
const deadline = config.config.globalTimeout ? monotonicTime() + config.config.globalTimeout : 0;
// Legacy webServer support.
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
@ -80,24 +78,15 @@ export class Runner {
await lastRun.filterLastFailed();
const reporter = new InternalReporter([...reporters, lastRun]);
const taskRunner = listOnly ? createTaskRunnerForList(
config,
reporter,
'in-process',
{ failOnLoadErrors: true }) : createTaskRunner(config, reporter);
const testRun = new TestRun(config);
reporter.onConfigure(config.config);
const taskStatus = await taskRunner.run(testRun, deadline);
let status: FullResult['status'] = testRun.failureTracker.result();
if (status === 'passed' && taskStatus !== 'passed')
status = taskStatus;
const modifiedResult = await reporter.onEnd({ status });
if (modifiedResult && modifiedResult.status)
status = modifiedResult.status;
await reporter.onExit();
const tasks = listOnly ? [
createLoadTask('in-process', { failOnLoadErrors: true, filterOnly: false }),
createReportBeginTask(),
] : [
...createGlobalSetupTasks(config),
createLoadTask('in-process', { filterOnly: true, failOnLoadErrors: true }),
...createRunTestsTasks(config),
];
const status = await runTasks(new TestRun(config, reporter), tasks, config.config.globalTimeout);
// Calling process.exit() might truncate large stdout/stderr output.
// See https://github.com/nodejs/node/issues/6456.
@ -110,12 +99,10 @@ export class Runner {
async findRelatedTestFiles(files: string[]): Promise<FindRelatedTestFilesReport> {
const errorReporter = createErrorCollectingReporter();
const reporter = new InternalReporter([errorReporter]);
const taskRunner = createTaskRunnerForRelatedTestFiles(this._config, reporter, 'in-process', true);
const testRun = new TestRun(this._config);
reporter.onConfigure(this._config.config);
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
const status = await runTasks(new TestRun(this._config, reporter), [
...createPluginSetupTasks(this._config),
createLoadTask('in-process', { failOnLoadErrors: true, filterOnly: false, populateDependencies: true }),
]);
if (status !== 'passed')
return { errors: errorReporter.errors(), testFiles: [] };
return { testFiles: affectedTestFiles(files) };
@ -123,23 +110,21 @@ export class Runner {
async runDevServer() {
const reporter = new InternalReporter([createErrorCollectingReporter(true)]);
const taskRunner = createTaskRunnerForDevServer(this._config, reporter, 'in-process', true);
const testRun = new TestRun(this._config);
reporter.onConfigure(this._config.config);
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
const status = await runTasks(new TestRun(this._config, reporter), [
...createPluginSetupTasks(this._config),
createLoadTask('in-process', { failOnLoadErrors: true, filterOnly: false }),
createStartDevServerTask(),
{ title: 'wait until interrupted', setup: async () => new Promise(() => {}) },
]);
return { status };
}
async clearCache() {
const reporter = new InternalReporter([createErrorCollectingReporter(true)]);
const taskRunner = createTaskRunnerForClearCache(this._config, reporter, 'in-process', true);
const testRun = new TestRun(this._config);
reporter.onConfigure(this._config.config);
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
const status = await runTasks(new TestRun(this._config, reporter), [
...createPluginSetupTasks(this._config),
createClearCacheTask(this._config),
]);
return { status };
}
}

View File

@ -19,31 +19,26 @@ import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils';
import type { FullResult, TestError } from '../../types/testReporter';
import { SigIntWatcher } from './sigIntWatcher';
import { serializeError } from '../util';
import type { ReporterV2 } from '../reporters/reporterV2';
import type { InternalReporter } from '../reporters/internalReporter';
type TaskPhase<Context> = (reporter: ReporterV2, context: Context, errors: TestError[], softErrors: TestError[]) => Promise<void> | void;
export type Task<Context> = { setup?: TaskPhase<Context>, teardown?: TaskPhase<Context> };
type TaskPhase<Context> = (context: Context, errors: TestError[], softErrors: TestError[]) => Promise<void> | void;
export type Task<Context> = { title: string, setup?: TaskPhase<Context>, teardown?: TaskPhase<Context> };
export class TaskRunner<Context> {
private _tasks: { name: string, task: Task<Context> }[] = [];
private _tasks: Task<Context>[] = [];
private _reporter: InternalReporter;
private _hasErrors = false;
private _interrupted = false;
private _isTearDown = false;
private _globalTimeoutForError: number;
static create<Context>(reporter: InternalReporter, globalTimeoutForError: number = 0) {
return new TaskRunner<Context>(reporter, globalTimeoutForError);
}
private constructor(reporter: InternalReporter, globalTimeoutForError: number) {
constructor(reporter: InternalReporter, globalTimeoutForError: number) {
this._reporter = reporter;
this._globalTimeoutForError = globalTimeoutForError;
}
addTask(name: string, task: Task<Context>) {
this._tasks.push({ name, task });
addTask(task: Task<Context>) {
this._tasks.push(task);
}
async run(context: Context, deadline: number, cancelPromise?: ManualPromise<void>): Promise<FullResult['status']> {
@ -61,18 +56,18 @@ export class TaskRunner<Context> {
let currentTaskName: string | undefined;
const taskLoop = async () => {
for (const { name, task } of this._tasks) {
currentTaskName = name;
for (const task of this._tasks) {
currentTaskName = task.title;
if (this._interrupted)
break;
debug('pw:test:task')(`"${name}" started`);
debug('pw:test:task')(`"${task.title}" started`);
const errors: TestError[] = [];
const softErrors: TestError[] = [];
try {
teardownRunner._tasks.unshift({ name: `teardown for ${name}`, task: { setup: task.teardown } });
await task.setup?.(this._reporter, context, errors, softErrors);
teardownRunner._tasks.unshift({ title: `teardown for ${task.title}`, setup: task.teardown });
await task.setup?.(context, errors, softErrors);
} catch (e) {
debug('pw:test:task')(`error in "${name}": `, e);
debug('pw:test:task')(`error in "${task.title}": `, e);
errors.push(serializeError(e));
} finally {
for (const error of [...softErrors, ...errors])
@ -83,7 +78,7 @@ export class TaskRunner<Context> {
this._hasErrors = true;
}
}
debug('pw:test:task')(`"${name}" finished`);
debug('pw:test:task')(`"${task.title}" finished`);
}
};

View File

@ -18,7 +18,7 @@ import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { debug } from 'playwright-core/lib/utilsBundle';
import { removeFolders } from 'playwright-core/lib/utils';
import { type ManualPromise, monotonicTime, removeFolders } from 'playwright-core/lib/utils';
import { Dispatcher, type EnvByProjectId } from './dispatcher';
import type { TestRunnerPluginRegistration } from '../plugins';
import { createTestGroups, type TestGroup } from '../runner/testGroups';
@ -33,6 +33,7 @@ import { FailureTracker } from './failureTracker';
import { detectChangedTestFiles } from './vcs';
import type { InternalReporter } from '../reporters/internalReporter';
import { cacheDir } from '../transform/compilationCache';
import type { FullResult } from '../../types/testReporter';
const readDirAsync = promisify(fs.readdir);
@ -42,132 +43,100 @@ type ProjectWithTestGroups = {
testGroups: TestGroup[];
};
export type Phase = {
type Phase = {
dispatcher: Dispatcher,
projects: ProjectWithTestGroups[]
};
export class TestRun {
readonly config: FullConfigInternal;
readonly reporter: InternalReporter;
readonly failureTracker: FailureTracker;
rootSuite: Suite | undefined = undefined;
readonly phases: Phase[] = [];
projectFiles: Map<FullProjectInternal, string[]> = new Map();
projectSuites: Map<FullProjectInternal, Suite[]> = new Map();
constructor(config: FullConfigInternal) {
constructor(config: FullConfigInternal, reporter: InternalReporter) {
this.config = config;
this.reporter = reporter;
this.failureTracker = new FailureTracker(config);
}
}
export function createTaskRunner(config: FullConfigInternal, reporter: InternalReporter): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter, config.config.globalTimeout);
addGlobalSetupTasks(taskRunner, config);
taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, failOnLoadErrors: true }));
addRunTasks(taskRunner, config);
return taskRunner;
export async function runTasks(testRun: TestRun, tasks: Task<TestRun>[], globalTimeout?: number, cancelPromise?: ManualPromise<void>) {
const deadline = globalTimeout ? monotonicTime() + globalTimeout : 0;
const taskRunner = new TaskRunner<TestRun>(testRun.reporter, globalTimeout || 0);
for (const task of tasks)
taskRunner.addTask(task);
testRun.reporter.onConfigure(testRun.config.config);
const status = await taskRunner.run(testRun, deadline, cancelPromise);
return await finishTaskRun(testRun, status);
}
export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporter: InternalReporter): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter);
addGlobalSetupTasks(taskRunner, config);
return taskRunner;
export async function runTasksDeferCleanup(testRun: TestRun, tasks: Task<TestRun>[]) {
const taskRunner = new TaskRunner<TestRun>(testRun.reporter, 0);
for (const task of tasks)
taskRunner.addTask(task);
testRun.reporter.onConfigure(testRun.config.config);
const { status, cleanup } = await taskRunner.runDeferCleanup(testRun, 0);
return { status: await finishTaskRun(testRun, status), cleanup };
}
export function createTaskRunnerForTestServer(config: FullConfigInternal, reporter: InternalReporter): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter);
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }));
addRunTasks(taskRunner, config);
return taskRunner;
async function finishTaskRun(testRun: TestRun, status: FullResult['status']) {
if (status === 'passed')
status = testRun.failureTracker.result();
const modifiedResult = await testRun.reporter.onEnd({ status });
if (modifiedResult && modifiedResult.status)
status = modifiedResult.status;
await testRun.reporter.onExit();
return status;
}
function addGlobalSetupTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
export function createGlobalSetupTasks(config: FullConfigInternal) {
const tasks: Task<TestRun>[] = [];
if (!config.configCLIOverrides.preserveOutputDir && !process.env.PW_TEST_NO_REMOVE_OUTPUT_DIRS)
taskRunner.addTask('clear output', createRemoveOutputDirsTask());
for (const plugin of config.plugins)
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
tasks.push(createRemoveOutputDirsTask());
tasks.push(...createPluginSetupTasks(config));
if (config.config.globalSetup || config.config.globalTeardown)
taskRunner.addTask('global setup', createGlobalSetupTask());
tasks.push(createGlobalSetupTask());
return tasks;
}
function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
taskRunner.addTask('create phases', createPhasesTask());
taskRunner.addTask('report begin', createReportBeginTask());
for (const plugin of config.plugins)
taskRunner.addTask('plugin begin', createPluginBeginTask(plugin));
taskRunner.addTask('test suite', createRunTestsTask());
return taskRunner;
export function createRunTestsTasks(config: FullConfigInternal) {
return [
createPhasesTask(),
createReportBeginTask(),
...config.plugins.map(plugin => createPluginBeginTask(plugin)),
createRunTestsTask(),
];
}
export function createTaskRunnerForList(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter, config.config.globalTimeout);
taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false }));
taskRunner.addTask('report begin', createReportBeginTask());
return taskRunner;
}
export function createTaskRunnerForListFiles(config: FullConfigInternal, reporter: InternalReporter): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter, config.config.globalTimeout);
taskRunner.addTask('load tests', createListFilesTask());
taskRunner.addTask('report begin', createReportBeginTask());
return taskRunner;
}
export function createTaskRunnerForDevServer(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process', setupAndWait: boolean): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter, config.config.globalTimeout);
if (setupAndWait) {
for (const plugin of config.plugins)
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
}
taskRunner.addTask('load tests', createLoadTask(mode, { failOnLoadErrors: true, filterOnly: false }));
taskRunner.addTask('start dev server', createStartDevServerTask());
if (setupAndWait) {
taskRunner.addTask('wait until interrupted', {
setup: async () => new Promise(() => {}),
});
}
return taskRunner;
}
export function createTaskRunnerForRelatedTestFiles(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process', setupPlugins: boolean): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter, config.config.globalTimeout);
if (setupPlugins) {
for (const plugin of config.plugins)
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
}
taskRunner.addTask('load tests', createLoadTask(mode, { failOnLoadErrors: true, filterOnly: false, populateDependencies: true }));
return taskRunner;
}
export function createTaskRunnerForClearCache(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process', setupPlugins: boolean): TaskRunner<TestRun> {
const taskRunner = TaskRunner.create<TestRun>(reporter, config.config.globalTimeout);
if (setupPlugins) {
for (const plugin of config.plugins)
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
}
taskRunner.addTask('clear cache', {
export function createClearCacheTask(config: FullConfigInternal): Task<TestRun> {
return {
title: 'clear cache',
setup: async () => {
await removeDirAndLogToConsole(cacheDir);
for (const plugin of config.plugins)
await plugin.instance?.clearCache?.();
},
});
return taskRunner;
};
}
function createReportBeginTask(): Task<TestRun> {
export function createReportBeginTask(): Task<TestRun> {
return {
setup: async (reporter, { rootSuite }) => {
reporter.onBegin?.(rootSuite!);
title: 'report begin',
setup: async testRun => {
testRun.reporter.onBegin?.(testRun.rootSuite!);
},
teardown: async ({}) => {},
};
}
function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestRun> {
return {
setup: async (reporter, { config }) => {
export function createPluginSetupTasks(config: FullConfigInternal): Task<TestRun>[] {
return config.plugins.map(plugin => ({
title: 'plugin setup',
setup: async ({ reporter }) => {
if (typeof plugin.factory === 'function')
plugin.instance = await plugin.factory();
else
@ -177,13 +146,14 @@ function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestR
teardown: async () => {
await plugin.instance?.teardown?.();
},
};
}));
}
function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task<TestRun> {
return {
setup: async (reporter, { rootSuite }) => {
await plugin.instance?.begin?.(rootSuite!);
title: 'plugin begin',
setup: async testRun => {
await plugin.instance?.begin?.(testRun.rootSuite!);
},
teardown: async () => {
await plugin.instance?.end?.();
@ -196,13 +166,14 @@ function createGlobalSetupTask(): Task<TestRun> {
let globalSetupFinished = false;
let teardownHook: any;
return {
setup: async (reporter, { config }) => {
title: 'global setup',
setup: async ({ config }) => {
const setupHook = config.config.globalSetup ? await loadGlobalHook(config, config.config.globalSetup) : undefined;
teardownHook = config.config.globalTeardown ? await loadGlobalHook(config, config.config.globalTeardown) : undefined;
globalSetupResult = setupHook ? await setupHook(config.config) : undefined;
globalSetupFinished = true;
},
teardown: async (reporter, { config }) => {
teardown: async ({ config }) => {
if (typeof globalSetupResult === 'function')
await globalSetupResult();
if (globalSetupFinished)
@ -213,7 +184,8 @@ function createGlobalSetupTask(): Task<TestRun> {
function createRemoveOutputDirsTask(): Task<TestRun> {
return {
setup: async (reporter, { config }) => {
title: 'clear output',
setup: async ({ config }) => {
const outputDirs = new Set<string>();
const projects = filterProjects(config.projects, config.cliProjectFilter);
projects.forEach(p => outputDirs.add(p.project.outputDir));
@ -235,9 +207,10 @@ function createRemoveOutputDirsTask(): Task<TestRun> {
};
}
function createListFilesTask(): Task<TestRun> {
export function createListFilesTask(): Task<TestRun> {
return {
setup: async (reporter, testRun, errors) => {
title: 'load tests',
setup: async (testRun, errors) => {
testRun.rootSuite = await createRootSuite(testRun, errors, false);
testRun.failureTracker.onRootSuite(testRun.rootSuite);
await collectProjectsAndTestFiles(testRun, false);
@ -258,9 +231,10 @@ function createListFilesTask(): Task<TestRun> {
};
}
function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, populateDependencies?: boolean }): Task<TestRun> {
export function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, populateDependencies?: boolean }): Task<TestRun> {
return {
setup: async (reporter, testRun, errors, softErrors) => {
title: 'load tests',
setup: async (testRun, errors, softErrors) => {
await collectProjectsAndTestFiles(testRun, !!options.doNotRunDepsOutsideProjectFilter);
await loadFileSuites(testRun, mode, options.failOnLoadErrors ? errors : softErrors);
@ -294,7 +268,8 @@ function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filter
function createPhasesTask(): Task<TestRun> {
return {
setup: async (reporter, testRun) => {
title: 'create phases',
setup: async testRun => {
let maxConcurrentTestGroups = 0;
const processed = new Set<FullProjectInternal>();
@ -325,7 +300,7 @@ function createPhasesTask(): Task<TestRun> {
processed.add(project);
if (phaseProjects.length) {
let testGroupsInPhase = 0;
const phase: Phase = { dispatcher: new Dispatcher(testRun.config, reporter, testRun.failureTracker), projects: [] };
const phase: Phase = { dispatcher: new Dispatcher(testRun.config, testRun.reporter, testRun.failureTracker), projects: [] };
testRun.phases.push(phase);
for (const project of phaseProjects) {
const projectSuite = projectToSuite.get(project)!;
@ -345,7 +320,8 @@ function createPhasesTask(): Task<TestRun> {
function createRunTestsTask(): Task<TestRun> {
return {
setup: async (reporter, { phases, failureTracker }) => {
title: 'test suite',
setup: async ({ phases, failureTracker }) => {
const successfulProjects = new Set<FullProjectInternal>();
const extraEnvByProjectId: EnvByProjectId = new Map();
const teardownToSetups = buildTeardownToSetupsMap(phases.map(phase => phase.projects.map(p => p.project)).flat());
@ -389,28 +365,29 @@ function createRunTestsTask(): Task<TestRun> {
}
}
},
teardown: async (reporter, { phases }) => {
teardown: async ({ phases }) => {
for (const { dispatcher } of phases.reverse())
await dispatcher.stop();
},
};
}
function createStartDevServerTask(): Task<TestRun> {
export function createStartDevServerTask(): Task<TestRun> {
return {
setup: async (reporter, testRun, errors, softErrors) => {
if (testRun.config.plugins.some(plugin => !!plugin.devServerCleanup)) {
title: 'start dev server',
setup: async ({ config }, errors, softErrors) => {
if (config.plugins.some(plugin => !!plugin.devServerCleanup)) {
errors.push({ message: `DevServer is already running` });
return;
}
for (const plugin of testRun.config.plugins)
for (const plugin of config.plugins)
plugin.devServerCleanup = await plugin.instance?.startDevServer?.();
if (!testRun.config.plugins.some(plugin => !!plugin.devServerCleanup))
if (!config.plugins.some(plugin => !!plugin.devServerCleanup))
errors.push({ message: `DevServer is not available in the package you are using. Did you mean to use component testing?` });
},
teardown: async (reporter, testRun) => {
for (const plugin of testRun.config.plugins) {
teardown: async ({ config }) => {
for (const plugin of config.plugins) {
await plugin.devServerCleanup?.();
plugin.devServerCleanup = undefined;
}

View File

@ -23,7 +23,7 @@ import type * as reporterTypes from '../../types/testReporter';
import { affectedTestFiles, collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache';
import type { ConfigLocation, FullConfigInternal } from '../common/config';
import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters';
import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup, createTaskRunnerForListFiles, createTaskRunnerForDevServer, createTaskRunnerForRelatedTestFiles, createTaskRunnerForClearCache } from './tasks';
import { TestRun, runTasks, createLoadTask, createRunTestsTasks, createReportBeginTask, createListFilesTask, runTasksDeferCleanup, createClearCacheTask, createGlobalSetupTasks, createStartDevServerTask } from './tasks';
import { open } from 'playwright-core/lib/utilsBundle';
import ListReporter from '../reporters/list';
import { SigIntWatcher } from './sigIntWatcher';
@ -150,17 +150,13 @@ export class TestServerDispatcher implements TestServerInterface {
if (!config)
return { status: 'failed', report };
const taskRunner = createTaskRunnerForWatchSetup(config, reporter);
reporter.onConfigure(config.config);
const testRun = new TestRun(config);
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
if (status !== 'passed') {
await globalCleanup();
return { report, status };
}
this._globalSetup = { cleanup: globalCleanup, report };
const { status, cleanup } = await runTasksDeferCleanup(new TestRun(config, reporter), [
...createGlobalSetupTasks(config),
]);
if (status !== 'passed')
await cleanup();
else
this._globalSetup = { cleanup, report };
return { report, status };
}
@ -179,17 +175,14 @@ export class TestServerDispatcher implements TestServerInterface {
if (!config)
return { report, status: 'failed' };
const taskRunner = createTaskRunnerForDevServer(config, reporter, 'out-of-process', false);
const testRun = new TestRun(config);
reporter.onConfigure(config.config);
const { status, cleanup } = await taskRunner.runDeferCleanup(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
if (status !== 'passed') {
const { status, cleanup } = await runTasksDeferCleanup(new TestRun(config, reporter), [
createLoadTask('out-of-process', { failOnLoadErrors: true, filterOnly: false }),
createStartDevServerTask(),
]);
if (status !== 'passed')
await cleanup();
return { report, status };
}
this._devServer = { cleanup, report };
else
this._devServer = { cleanup, report };
return { report, status };
}
@ -205,13 +198,9 @@ export class TestServerDispatcher implements TestServerInterface {
const config = await this._loadConfigOrReportError(reporter);
if (!config)
return;
const taskRunner = createTaskRunnerForClearCache(config, reporter, 'out-of-process', false);
const testRun = new TestRun(config);
reporter.onConfigure(config.config);
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
await runTasks(new TestRun(config, reporter), [
createClearCacheTask(config),
]);
}
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
@ -221,12 +210,10 @@ export class TestServerDispatcher implements TestServerInterface {
return { status: 'failed', report };
config.cliProjectFilter = params.projects?.length ? params.projects : undefined;
const taskRunner = createTaskRunnerForListFiles(config, reporter);
reporter.onConfigure(config.config);
const testRun = new TestRun(config);
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
const status = await runTasks(new TestRun(config, reporter), [
createListFilesTask(),
createReportBeginTask(),
]);
return { report, status };
}
@ -264,12 +251,10 @@ export class TestServerDispatcher implements TestServerInterface {
config.cliProjectFilter = params.projects?.length ? params.projects : undefined;
config.cliListOnly = true;
const taskRunner = createTaskRunnerForList(config, reporter, 'out-of-process', { failOnLoadErrors: false });
const testRun = new TestRun(config);
reporter.onConfigure(config.config);
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
const status = await runTasks(new TestRun(config, reporter), [
createLoadTask('out-of-process', { failOnLoadErrors: false, filterOnly: false }),
createReportBeginTask(),
]);
return { config, report, reporter, status };
}
@ -344,13 +329,12 @@ export class TestServerDispatcher implements TestServerInterface {
const configReporters = await createReporters(config, 'test', true);
const reporter = new InternalReporter([...configReporters, wireReporter]);
const taskRunner = createTaskRunnerForTestServer(config, reporter);
const testRun = new TestRun(config);
reporter.onConfigure(config.config);
const stop = new ManualPromise();
const run = taskRunner.run(testRun, 0, stop).then(async status => {
await reporter.onEnd({ status });
await reporter.onExit();
const tasks = [
createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }),
...createRunTestsTasks(config),
];
const run = runTasks(new TestRun(config, reporter), tasks, 0, stop).then(async status => {
this._testRun = undefined;
return status;
});
@ -373,13 +357,9 @@ export class TestServerDispatcher implements TestServerInterface {
const config = await this._loadConfigOrReportError(reporter);
if (!config)
return { errors: errorReporter.errors(), testFiles: [] };
const taskRunner = createTaskRunnerForRelatedTestFiles(config, reporter, 'out-of-process', false);
const testRun = new TestRun(config);
reporter.onConfigure(config.config);
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
const status = await runTasks(new TestRun(config, reporter), [
createLoadTask('out-of-process', { failOnLoadErrors: true, filterOnly: false, populateDependencies: true }),
]);
if (status !== 'passed')
return { errors: errorReporter.errors(), testFiles: [] };
return { testFiles: affectedTestFiles(params.files) };