diff --git a/src/api/run_cucumber.ts b/src/api/run_cucumber.ts index ea62ab1cb..aa271e058 100644 --- a/src/api/run_cucumber.ts +++ b/src/api/run_cucumber.ts @@ -156,7 +156,7 @@ Running from: ${__dirname} supportCodeLibrary, options: options.runtime, }) - const success = await runtime.start() + const success = await runtime.run() await pluginManager.cleanup() await cleanupFormatters() diff --git a/src/api/runtime.ts b/src/api/runtime.ts index 897a7ee54..308fb15e3 100644 --- a/src/api/runtime.ts +++ b/src/api/runtime.ts @@ -1,14 +1,12 @@ import { EventEmitter } from 'node:events' import { IdGenerator } from '@cucumber/messages' -import { IRuntime } from '../runtime' import { EventDataCollector } from '../formatter/helpers' import { SupportCodeLibrary } from '../support_code_library_builder/types' import { ILogger } from '../logger' -import { Coordinator } from '../runtime/coordinator' +import { Runtime, Coordinator, RuntimeAdapter } from '../runtime' import { ChildProcessAdapter } from '../runtime/parallel/adapter' import { IFilterablePickle } from '../filter' import { InProcessAdapter } from '../runtime/serial/adapter' -import { RuntimeAdapter } from '../runtime/types' import { IRunEnvironment, IRunOptionsRuntime } from './types' export async function makeRuntime({ @@ -29,7 +27,7 @@ export async function makeRuntime({ filteredPickles: ReadonlyArray supportCodeLibrary: SupportCodeLibrary options: IRunOptionsRuntime -}): Promise { +}): Promise { const adapter: RuntimeAdapter = options.parallel > 0 ? new ChildProcessAdapter( diff --git a/src/formatter/helpers/summary_helpers_spec.ts b/src/formatter/helpers/summary_helpers_spec.ts index 375c31cee..c49d508ba 100644 --- a/src/formatter/helpers/summary_helpers_spec.ts +++ b/src/formatter/helpers/summary_helpers_spec.ts @@ -8,13 +8,13 @@ import { getTestCaseAttempts } from '../../../test/formatter_helpers' import { getBaseSupportCodeLibrary } from '../../../test/fixtures/steps' import timeMethods, { durationBetweenTimestamps } from '../../time' import { buildSupportCodeLibrary } from '../../../test/runtime_helpers' -import { IRuntimeOptions } from '../../runtime' +import { RuntimeOptions } from '../../runtime' import { SupportCodeLibrary } from '../../support_code_library_builder/types' import { doesNotHaveValue } from '../../value_checker' import { formatSummary } from './summary_helpers' interface ITestFormatSummaryOptions { - runtimeOptions?: Partial + runtimeOptions?: Partial sourceData: string supportCodeLibrary?: SupportCodeLibrary testRunStarted?: messages.TestRunStarted diff --git a/src/formatter/progress_bar_formatter_spec.ts b/src/formatter/progress_bar_formatter_spec.ts index aac4f8beb..15a761ad8 100644 --- a/src/formatter/progress_bar_formatter_spec.ts +++ b/src/formatter/progress_bar_formatter_spec.ts @@ -15,7 +15,7 @@ import { import { buildSupportCodeLibrary } from '../../test/runtime_helpers' import { getBaseSupportCodeLibrary } from '../../test/fixtures/steps' import timeMethods from '../time' -import { IRuntimeOptions } from '../runtime' +import { RuntimeOptions } from '../runtime' import { SupportCodeLibrary } from '../support_code_library_builder/types' import { doesHaveValue, doesNotHaveValue } from '../value_checker' import ProgressBarFormatter from './progress_bar_formatter' @@ -23,7 +23,7 @@ import FormatterBuilder from './builder' import { EventDataCollector } from './helpers' interface ITestProgressBarFormatterOptions { - runtimeOptions?: Partial + runtimeOptions?: Partial shouldStopFn: (envelope: messages.Envelope) => boolean sources?: ITestSource[] supportCodeLibrary?: SupportCodeLibrary diff --git a/src/runtime/coordinator.ts b/src/runtime/coordinator.ts index a0be84e4f..e958572f6 100644 --- a/src/runtime/coordinator.ts +++ b/src/runtime/coordinator.ts @@ -5,9 +5,9 @@ import { assembleTestCases } from '../assemble' import { SupportCodeLibrary } from '../support_code_library_builder/types' import { RuntimeAdapter } from './types' import { timestamp } from './stopwatch' -import { IRuntime } from './index' +import { Runtime } from './index' -export class Coordinator implements IRuntime { +export class Coordinator implements Runtime { constructor( private eventBroadcaster: EventEmitter, private newId: IdGenerator.NewId, @@ -16,7 +16,7 @@ export class Coordinator implements IRuntime { private adapter: RuntimeAdapter ) {} - async start(): Promise { + async run(): Promise { this.eventBroadcaster.emit('envelope', { testRunStarted: { timestamp: timestamp(), @@ -30,7 +30,7 @@ export class Coordinator implements IRuntime { supportCodeLibrary: this.supportCodeLibrary, }) - const success = await this.adapter.start(assembledTestCases) + const success = await this.adapter.run(assembledTestCases) this.eventBroadcaster.emit('envelope', { testRunFinished: { diff --git a/src/runtime/helpers.ts b/src/runtime/helpers.ts index 770811ffe..7a5c25d72 100644 --- a/src/runtime/helpers.ts +++ b/src/runtime/helpers.ts @@ -4,7 +4,7 @@ import * as messages from '@cucumber/messages' import { formatLocation } from '../formatter/helpers/location_helpers' import { PickleTagFilter } from '../pickle_filter' import StepDefinition from '../models/step_definition' -import { IRuntimeOptions } from '.' +import { RuntimeOptions } from '.' export function getAmbiguousStepException( stepDefinitions: StepDefinition[] @@ -47,7 +47,7 @@ export function getAmbiguousStepException( export function retriesForPickle( pickle: messages.Pickle, - options: IRuntimeOptions + options: RuntimeOptions ): number { if (!options.retry) { return 0 @@ -69,7 +69,7 @@ export function retriesForPickle( export function shouldCauseFailure( status: messages.TestStepResultStatus, - options: IRuntimeOptions + options: RuntimeOptions ): boolean { if (options.dryRun) { return false diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 1d82cab21..eefce5d8c 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -1,133 +1,2 @@ -import { EventEmitter } from 'node:events' -import * as messages from '@cucumber/messages' -import { IdGenerator } from '@cucumber/messages' -import { JsonObject } from 'type-fest' -import { EventDataCollector } from '../formatter/helpers' -import { SupportCodeLibrary } from '../support_code_library_builder/types' -import { assembleTestCasesByPickleId } from '../assemble' -import { retriesForPickle, shouldCauseFailure } from './helpers' -import { makeRunTestRunHooks, RunsTestRunHooks } from './run_test_run_hooks' -import { IStopwatch, create, timestamp } from './stopwatch' -import TestCaseRunner from './test_case_runner' - -export interface IRuntime { - start: () => Promise -} - -export interface INewRuntimeOptions { - eventBroadcaster: EventEmitter - eventDataCollector: EventDataCollector - newId: IdGenerator.NewId - options: IRuntimeOptions - pickleIds: string[] - supportCodeLibrary: SupportCodeLibrary -} - -export interface IRuntimeOptions { - dryRun: boolean - failFast: boolean - filterStacktraces: boolean - retry: number - retryTagFilter: string - strict: boolean - worldParameters: JsonObject -} - -export default class Runtime implements IRuntime { - private readonly eventBroadcaster: EventEmitter - private readonly eventDataCollector: EventDataCollector - private readonly stopwatch: IStopwatch - private readonly newId: IdGenerator.NewId - private readonly options: IRuntimeOptions - private readonly pickleIds: string[] - private readonly supportCodeLibrary: SupportCodeLibrary - private success: boolean - private readonly runTestRunHooks: RunsTestRunHooks - - constructor({ - eventBroadcaster, - eventDataCollector, - newId, - options, - pickleIds, - supportCodeLibrary, - }: INewRuntimeOptions) { - this.eventBroadcaster = eventBroadcaster - this.eventDataCollector = eventDataCollector - this.stopwatch = create() - this.newId = newId - this.options = options - this.pickleIds = pickleIds - this.supportCodeLibrary = supportCodeLibrary - this.success = true - this.runTestRunHooks = makeRunTestRunHooks( - this.options.dryRun, - this.supportCodeLibrary.defaultTimeout, - this.options.worldParameters, - (name, location) => `${name} hook errored, process exiting: ${location}` - ) - } - - async runTestCase( - pickleId: string, - testCase: messages.TestCase - ): Promise { - const pickle = this.eventDataCollector.getPickle(pickleId) - const retries = retriesForPickle(pickle, this.options) - const skip = this.options.dryRun || (this.options.failFast && !this.success) - const testCaseRunner = new TestCaseRunner({ - eventBroadcaster: this.eventBroadcaster, - gherkinDocument: this.eventDataCollector.getGherkinDocument(pickle.uri), - newId: this.newId, - pickle, - testCase, - retries, - skip, - filterStackTraces: this.options.filterStacktraces, - supportCodeLibrary: this.supportCodeLibrary, - worldParameters: this.options.worldParameters, - }) - const status = await testCaseRunner.run() - if (shouldCauseFailure(status, this.options)) { - this.success = false - } - } - - async start(): Promise { - const testRunStarted: messages.Envelope = { - testRunStarted: { - timestamp: timestamp(), - }, - } - this.eventBroadcaster.emit('envelope', testRunStarted) - this.stopwatch.start() - await this.runTestRunHooks( - this.supportCodeLibrary.beforeTestRunHookDefinitions, - 'a BeforeAll' - ) - const assembledTestCases = await assembleTestCasesByPickleId({ - eventBroadcaster: this.eventBroadcaster, - newId: this.newId, - pickles: this.pickleIds.map((pickleId) => - this.eventDataCollector.getPickle(pickleId) - ), - supportCodeLibrary: this.supportCodeLibrary, - }) - for (const pickleId of this.pickleIds) { - await this.runTestCase(pickleId, assembledTestCases[pickleId]) - } - await this.runTestRunHooks( - this.supportCodeLibrary.afterTestRunHookDefinitions.slice(0).reverse(), - 'an AfterAll' - ) - this.stopwatch.stop() - const testRunFinished: messages.Envelope = { - testRunFinished: { - timestamp: timestamp(), - success: this.success, - }, - } - this.eventBroadcaster.emit('envelope', testRunFinished) - return this.success - } -} +export * from './coordinator' +export * from './types' diff --git a/src/runtime/parallel/adapter.ts b/src/runtime/parallel/adapter.ts index fc4ae151a..00d031abc 100644 --- a/src/runtime/parallel/adapter.ts +++ b/src/runtime/parallel/adapter.ts @@ -151,7 +151,7 @@ export class ChildProcessAdapter implements RuntimeAdapter { } } - async start( + async run( assembledTestCases: ReadonlyArray ): Promise { this.todo = Array.from(assembledTestCases) diff --git a/src/runtime/parallel/command_types.ts b/src/runtime/parallel/command_types.ts index e2be04339..4d0d3254c 100644 --- a/src/runtime/parallel/command_types.ts +++ b/src/runtime/parallel/command_types.ts @@ -1,5 +1,5 @@ import { Envelope } from '@cucumber/messages' -import { IRuntimeOptions } from '../index' +import { RuntimeOptions } from '../index' import { ISupportCodeCoordinates } from '../../api' import { AssembledTestCase } from '../../assemble' @@ -14,7 +14,7 @@ export interface IWorkerCommand { export interface IWorkerCommandInitialize { supportCodeCoordinates: ISupportCodeCoordinates supportCodeIds?: ICanonicalSupportCodeIds - options: IRuntimeOptions + options: RuntimeOptions } export interface ICanonicalSupportCodeIds { diff --git a/src/runtime/parallel/worker.ts b/src/runtime/parallel/worker.ts index cb2e99317..e836ae5dc 100644 --- a/src/runtime/parallel/worker.ts +++ b/src/runtime/parallel/worker.ts @@ -8,7 +8,7 @@ import { SupportCodeLibrary } from '../../support_code_library_builder/types' import { doesHaveValue } from '../../value_checker' import tryRequire from '../../try_require' import { Worker } from '../worker' -import { IRuntimeOptions } from '../index' +import { RuntimeOptions } from '../index' import { AssembledTestCase } from '../../assemble' import { ICoordinatorReport, @@ -29,7 +29,7 @@ export class ChildProcessWorker { private readonly eventBroadcaster: EventEmitter private readonly newId: IdGenerator.NewId private readonly sendMessage: IMessageSender - private options: IRuntimeOptions + private options: RuntimeOptions private supportCodeLibrary: SupportCodeLibrary private worker: Worker diff --git a/src/runtime/serial/adapter.ts b/src/runtime/serial/adapter.ts index 7dd85a991..3522bf35d 100644 --- a/src/runtime/serial/adapter.ts +++ b/src/runtime/serial/adapter.ts @@ -3,7 +3,7 @@ import { IdGenerator } from '@cucumber/messages' import { RuntimeAdapter } from '../types' import { AssembledTestCase } from '../../assemble' import { Worker } from '../worker' -import { IRuntimeOptions } from '../index' +import { RuntimeOptions } from '../index' import { SupportCodeLibrary } from '../../support_code_library_builder/types' export class InProcessAdapter implements RuntimeAdapter { @@ -12,7 +12,7 @@ export class InProcessAdapter implements RuntimeAdapter { constructor( eventBroadcaster: EventEmitter, newId: IdGenerator.NewId, - options: IRuntimeOptions, + options: RuntimeOptions, supportCodeLibrary: SupportCodeLibrary ) { this.#worker = new Worker( @@ -24,7 +24,7 @@ export class InProcessAdapter implements RuntimeAdapter { ) } - async start( + async run( assembledTestCases: ReadonlyArray ): Promise { await this.#worker.runBeforeAllHooks() diff --git a/src/runtime/types.ts b/src/runtime/types.ts index c381cf592..207b24fd6 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -1,5 +1,20 @@ +import { JsonObject } from 'type-fest' import { AssembledTestCase } from '../assemble' +export interface RuntimeOptions { + dryRun: boolean + failFast: boolean + filterStacktraces: boolean + retry: number + retryTagFilter: string + strict: boolean + worldParameters: JsonObject +} + +export interface Runtime { + run: () => Promise +} + export interface RuntimeAdapter { - start(assembledTestCases: ReadonlyArray): Promise + run(assembledTestCases: ReadonlyArray): Promise } diff --git a/src/runtime/worker.ts b/src/runtime/worker.ts index 049215cb1..4eccc8cbd 100644 --- a/src/runtime/worker.ts +++ b/src/runtime/worker.ts @@ -5,7 +5,7 @@ import { SupportCodeLibrary } from '../support_code_library_builder/types' import TestCaseRunner from './test_case_runner' import { retriesForPickle, shouldCauseFailure } from './helpers' import { makeRunTestRunHooks, RunsTestRunHooks } from './run_test_run_hooks' -import { IRuntimeOptions } from './index' +import { RuntimeOptions } from './index' export class Worker { #success: boolean = true @@ -15,7 +15,7 @@ export class Worker { private readonly workerId: string | undefined, private readonly eventBroadcaster: EventEmitter, private readonly newId: IdGenerator.NewId, - private readonly options: IRuntimeOptions, + private readonly options: RuntimeOptions, private readonly supportCodeLibrary: SupportCodeLibrary ) { this.runTestRunHooks = makeRunTestRunHooks( diff --git a/test/formatter_helpers.ts b/test/formatter_helpers.ts index 65a2af137..aa27b40cd 100644 --- a/test/formatter_helpers.ts +++ b/test/formatter_helpers.ts @@ -3,7 +3,7 @@ import { PassThrough } from 'node:stream' import { promisify } from 'node:util' import { IdGenerator } from '@cucumber/messages' import * as messages from '@cucumber/messages' -import Runtime, { IRuntimeOptions } from '../src/runtime' +import { RuntimeOptions } from '../src/runtime' import { EventDataCollector } from '../src/formatter/helpers' import FormatterBuilder from '../src/formatter/builder' import { SupportCodeLibrary } from '../src/support_code_library_builder/types' @@ -11,7 +11,10 @@ import { ITestCaseAttempt } from '../src/formatter/helpers/event_data_collector' import { doesNotHaveValue } from '../src/value_checker' import { emitSupportCodeMessages } from '../src/cli/helpers' import { FormatOptions } from '../src/formatter' -import { generateEvents } from './gherkin_helpers' +import { Coordinator } from '../src/runtime/coordinator' +import { IFilterablePickle } from '../src/filter' +import { InProcessAdapter } from '../src/runtime/serial/adapter' +import { generatePickles } from './gherkin_helpers' import { buildOptions, buildSupportCodeLibrary } from './runtime_helpers' const { uuid } = IdGenerator @@ -22,7 +25,7 @@ export interface ITestSource { } export interface ITestRunOptions { - runtimeOptions?: Partial + runtimeOptions?: Partial supportCodeLibrary?: SupportCodeLibrary sources?: ITestSource[] pickleFilter?: (pickle: messages.Pickle) => boolean @@ -71,25 +74,29 @@ export async function testFormatter({ cleanup: promisify(passThrough.end.bind(passThrough)), supportCodeLibrary, }) - let pickleIds: string[] = [] + let filteredPickles: IFilterablePickle[] = [] for (const source of sources) { - const { pickles } = await generateEvents({ + const generated = await generatePickles({ data: source.data, eventBroadcaster, uri: source.uri, }) - pickleIds = pickleIds.concat(pickles.map((p) => p.id)) + filteredPickles = filteredPickles.concat(generated) } - const runtime = new Runtime({ + const runtime = new Coordinator( eventBroadcaster, - eventDataCollector, - newId: uuid(), - options: buildOptions(runtimeOptions), - pickleIds, + uuid(), + filteredPickles, supportCodeLibrary, - }) + new InProcessAdapter( + eventBroadcaster, + uuid(), + buildOptions(runtimeOptions), + supportCodeLibrary + ) + ) - await runtime.start() + await runtime.run() return normalizeSummaryDuration(output) } @@ -104,25 +111,29 @@ export async function getTestCaseAttempts({ } const eventBroadcaster = new EventEmitter() const eventDataCollector = new EventDataCollector(eventBroadcaster) - let pickleIds: string[] = [] + let filteredPickles: IFilterablePickle[] = [] for (const source of sources) { - const { pickles } = await generateEvents({ + const generated = await generatePickles({ data: source.data, eventBroadcaster, uri: source.uri, }) - pickleIds = pickleIds.concat(pickles.map((p) => p.id)) + filteredPickles = filteredPickles.concat(generated) } - const runtime = new Runtime({ + const runtime = new Coordinator( eventBroadcaster, - eventDataCollector, - newId: uuid(), - options: buildOptions(runtimeOptions), - pickleIds, + uuid(), + filteredPickles, supportCodeLibrary, - }) + new InProcessAdapter( + eventBroadcaster, + uuid(), + buildOptions(runtimeOptions), + supportCodeLibrary + ) + ) - await runtime.start() + await runtime.run() return eventDataCollector.getTestCaseAttempts() } @@ -145,25 +156,33 @@ export async function getEnvelopesAndEventDataCollector({ eventBroadcaster, newId: IdGenerator.uuid(), }) - let pickleIds: string[] = [] + let filteredPickles: IFilterablePickle[] = [] + for (const source of sources) { - const { pickles } = await generateEvents({ + const generated = await generatePickles({ data: source.data, eventBroadcaster, uri: source.uri, }) - pickleIds = pickleIds.concat(pickles.filter(pickleFilter).map((p) => p.id)) + filteredPickles = filteredPickles.concat( + generated.filter((item) => pickleFilter(item.pickle)) + ) } - const runtime = new Runtime({ + + const runtime = new Coordinator( eventBroadcaster, - eventDataCollector, - newId: uuid(), - options: buildOptions(runtimeOptions), - pickleIds, + uuid(), + filteredPickles, supportCodeLibrary, - }) + new InProcessAdapter( + eventBroadcaster, + uuid(), + buildOptions(runtimeOptions), + supportCodeLibrary + ) + ) - await runtime.start() + await runtime.run() return { envelopes, eventDataCollector } } diff --git a/test/gherkin_helpers.ts b/test/gherkin_helpers.ts index 1e544f98f..a1b3d749b 100644 --- a/test/gherkin_helpers.ts +++ b/test/gherkin_helpers.ts @@ -4,6 +4,7 @@ import { SourceMediaType } from '@cucumber/messages' import { IGherkinOptions } from '@cucumber/gherkin' import { GherkinStreams } from '@cucumber/gherkin-streams' import { doesHaveValue } from '../src/value_checker' +import { IFilterablePickle } from '../src/filter' export interface IParsedSource { pickles: messages.Pickle[] @@ -70,23 +71,28 @@ export async function parse({ }) } -export interface IGenerateEventsRequest { +export interface GeneratePicklesRequest { data: string eventBroadcaster: EventEmitter uri: string } -export async function generateEvents({ +export async function generatePickles({ data, eventBroadcaster, uri, -}: IGenerateEventsRequest): Promise { - const { envelopes, source, gherkinDocument, pickles } = await parse({ +}: GeneratePicklesRequest): Promise> { + const { envelopes, gherkinDocument, pickles } = await parse({ data, uri, }) envelopes.forEach((envelope) => eventBroadcaster.emit('envelope', envelope)) - return { source, gherkinDocument, pickles } + return pickles.map((pickle) => { + return { + gherkinDocument, + pickle, + } as IFilterablePickle + }) } export async function getPickleWithTags( diff --git a/test/runtime_helpers.ts b/test/runtime_helpers.ts index 5b9acc70f..cd9f8fbe4 100644 --- a/test/runtime_helpers.ts +++ b/test/runtime_helpers.ts @@ -1,6 +1,6 @@ import { IdGenerator } from '@cucumber/messages' import { SupportCodeLibraryBuilder } from '../src/support_code_library_builder' -import { IRuntimeOptions } from '../src/runtime' +import { RuntimeOptions } from '../src/runtime' import { IDefineSupportCodeMethods, SupportCodeLibrary, @@ -8,8 +8,8 @@ import { import { doesHaveValue } from '../src/value_checker' export function buildOptions( - overrides: Partial -): IRuntimeOptions { + overrides: Partial +): RuntimeOptions { return { dryRun: false, failFast: false,