From 29a0f49e9b8607277ea4d3e7f8ba63be72c587dc Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 11 Sep 2024 13:09:00 -0700 Subject: [PATCH] chore(test runner): simplify code around running tasks (#32557) This avoids complex boilerplate around `onConfigure`/`onEnd`/`onExit` and managing the resulting status. --- packages/playwright/src/runner/runner.ts | 63 +++---- packages/playwright/src/runner/taskRunner.ts | 31 ++- packages/playwright/src/runner/tasks.ts | 189 ++++++++----------- packages/playwright/src/runner/testServer.ts | 88 ++++----- 4 files changed, 154 insertions(+), 217 deletions(-) diff --git a/packages/playwright/src/runner/runner.ts b/packages/playwright/src/runner/runner.ts index 22f8204592..923bf36072 100644 --- a/packages/playwright/src/runner/runner.ts +++ b/packages/playwright/src/runner/runner.ts @@ -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 { 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 { 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 }; } } diff --git a/packages/playwright/src/runner/taskRunner.ts b/packages/playwright/src/runner/taskRunner.ts index 52a06aa98c..dd29d6b0f8 100644 --- a/packages/playwright/src/runner/taskRunner.ts +++ b/packages/playwright/src/runner/taskRunner.ts @@ -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 = (reporter: ReporterV2, context: Context, errors: TestError[], softErrors: TestError[]) => Promise | void; -export type Task = { setup?: TaskPhase, teardown?: TaskPhase }; +type TaskPhase = (context: Context, errors: TestError[], softErrors: TestError[]) => Promise | void; +export type Task = { title: string, setup?: TaskPhase, teardown?: TaskPhase }; export class TaskRunner { - private _tasks: { name: string, task: Task }[] = []; + private _tasks: Task[] = []; private _reporter: InternalReporter; private _hasErrors = false; private _interrupted = false; private _isTearDown = false; private _globalTimeoutForError: number; - static create(reporter: InternalReporter, globalTimeoutForError: number = 0) { - return new TaskRunner(reporter, globalTimeoutForError); - } - - private constructor(reporter: InternalReporter, globalTimeoutForError: number) { + constructor(reporter: InternalReporter, globalTimeoutForError: number) { this._reporter = reporter; this._globalTimeoutForError = globalTimeoutForError; } - addTask(name: string, task: Task) { - this._tasks.push({ name, task }); + addTask(task: Task) { + this._tasks.push(task); } async run(context: Context, deadline: number, cancelPromise?: ManualPromise): Promise { @@ -61,18 +56,18 @@ export class TaskRunner { 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 { this._hasErrors = true; } } - debug('pw:test:task')(`"${name}" finished`); + debug('pw:test:task')(`"${task.title}" finished`); } }; diff --git a/packages/playwright/src/runner/tasks.ts b/packages/playwright/src/runner/tasks.ts index 8281cc3d39..77d84419f4 100644 --- a/packages/playwright/src/runner/tasks.ts +++ b/packages/playwright/src/runner/tasks.ts @@ -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 = new Map(); projectSuites: Map = 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 { - const taskRunner = TaskRunner.create(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[], globalTimeout?: number, cancelPromise?: ManualPromise) { + const deadline = globalTimeout ? monotonicTime() + globalTimeout : 0; + const taskRunner = new TaskRunner(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 { - const taskRunner = TaskRunner.create(reporter); - addGlobalSetupTasks(taskRunner, config); - return taskRunner; +export async function runTasksDeferCleanup(testRun: TestRun, tasks: Task[]) { + const taskRunner = new TaskRunner(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 { - const taskRunner = TaskRunner.create(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, config: FullConfigInternal) { +export function createGlobalSetupTasks(config: FullConfigInternal) { + const tasks: Task[] = []; 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, 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 { - const taskRunner = TaskRunner.create(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 { - const taskRunner = TaskRunner.create(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 { - const taskRunner = TaskRunner.create(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 { - const taskRunner = TaskRunner.create(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 { - const taskRunner = TaskRunner.create(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 { + return { + title: 'clear cache', setup: async () => { await removeDirAndLogToConsole(cacheDir); for (const plugin of config.plugins) await plugin.instance?.clearCache?.(); }, - }); - return taskRunner; + }; } -function createReportBeginTask(): Task { +export function createReportBeginTask(): Task { 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 { - return { - setup: async (reporter, { config }) => { +export function createPluginSetupTasks(config: FullConfigInternal): Task[] { + 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 { await plugin.instance?.teardown?.(); }, - }; + })); } function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task { 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 { 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 { function createRemoveOutputDirsTask(): Task { return { - setup: async (reporter, { config }) => { + title: 'clear output', + setup: async ({ config }) => { const outputDirs = new Set(); const projects = filterProjects(config.projects, config.cliProjectFilter); projects.forEach(p => outputDirs.add(p.project.outputDir)); @@ -235,9 +207,10 @@ function createRemoveOutputDirsTask(): Task { }; } -function createListFilesTask(): Task { +export function createListFilesTask(): Task { 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 { }; } -function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, populateDependencies?: boolean }): Task { +export function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, populateDependencies?: boolean }): Task { 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 { return { - setup: async (reporter, testRun) => { + title: 'create phases', + setup: async testRun => { let maxConcurrentTestGroups = 0; const processed = new Set(); @@ -325,7 +300,7 @@ function createPhasesTask(): Task { 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 { function createRunTestsTask(): Task { return { - setup: async (reporter, { phases, failureTracker }) => { + title: 'test suite', + setup: async ({ phases, failureTracker }) => { const successfulProjects = new Set(); 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 { } } }, - teardown: async (reporter, { phases }) => { + teardown: async ({ phases }) => { for (const { dispatcher } of phases.reverse()) await dispatcher.stop(); }, }; } -function createStartDevServerTask(): Task { +export function createStartDevServerTask(): Task { 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; } diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index e6522bad46..f34a7314f1 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -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[0]): ReturnType { @@ -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) };