diff --git a/integration/requires-ts-node-for-ts-tests.ts b/integration/requires-ts-node-for-ts-tests.ts index 5d69f1f..032dd44 100644 --- a/integration/requires-ts-node-for-ts-tests.ts +++ b/integration/requires-ts-node-for-ts-tests.ts @@ -36,7 +36,6 @@ async function requires_ts_node_for_ts_tests() { assert.ok(stdout.includes('sample.test.ts is not supported without --ts and will be ignored'), 'Unsupported test notice not found') assert.ok(stdout.includes('Collected 0 test files'), 'Did not find notice of no test collected') - assert.ok(stdout.includes('Collected 0 test cases'), 'Did not find notice of no cases collected') } catch(e) { await tearDown() console.log(e) diff --git a/script/build b/script/build index d63de8e..c308278 100644 --- a/script/build +++ b/script/build @@ -14,5 +14,4 @@ addHashbang() { } addHashbang dist/cli.js -addHashbang dist/collector.js addHashbang dist/worker.js diff --git a/src/collector.ts b/src/collector.ts deleted file mode 100644 index 97f1e6b..0000000 --- a/src/collector.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { type Context } from './types' -import { getContext, spawnProcess } from './utils' - -async function collectCases(context: Context, collectedPaths: Array): Promise { - const extraArgs: Array = [] - - if (context.ts) extraArgs.push('--transpile-only') - - const runtime = context.ts ? 'ts-node' : 'node' - - let totalCases = 0 - for await (const collectedPath of collectedPaths) { - const collectedCount = await new Promise((resolve, reject) => { - let count = 0 - spawnProcess(runtime, [...extraArgs, collectedPath], { - extraEnv: { COLLECT: '1' }, - - onClose: (code) => { - resolve(count) - }, - onStdoutData: (message) => { - count += message.split('\n').filter((caseLabel: string) => caseLabel.length > 0).length - }, - }) - }) - - totalCases += collectedCount - } - - return totalCases -} - -/* - * Collector worker runtime - */ -async function work() { - const [, workerRuntime, ...assignedTestFiles] = process.argv - const tsMode = Boolean(process.env.TS === '1') - - const context = getContext(workerRuntime, tsMode) - const collectedCount = await collectCases(context, assignedTestFiles) - if (process.send) process.send(JSON.stringify({ total: collectedCount })) -} - -work().catch((e) => { - console.log(e) -}) diff --git a/src/runner.ts b/src/runner.ts index dacbbb7..454e1f4 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -1,5 +1,5 @@ -import { forkWorker, greenText, yellowText, redText, boldText, splitIntoBatches } from './utils' -import { type Args, type Context, type WorkerReport, type CollectorReport } from './types' +import { forkWorker, yellowText, boldText, splitIntoBatches } from './utils' +import { type Args, type Context, type WorkerReport } from './types' import { promises as fs } from 'fs' import path from 'path' @@ -35,34 +35,6 @@ async function collectTests(roots: Array): Promise> { return collectedHere } -async function collectCases(context: Context, collectedPaths: Array, workerCount: number = 1): Promise { - const batchedCollectedPaths = splitIntoBatches(collectedPaths, workerCount) - - const batchResults = await Promise.all( - batchedCollectedPaths.map( - (batch): Promise => - new Promise((resolve, reject) => { - const collectorReport: CollectorReport = { totalCases: 0 } - forkWorker(context.collectorRuntime, batch, { - onClose: (code) => { - resolve(collectorReport) - }, - onMessage: (message: string) => { - collectorReport.totalCases += JSON.parse(message).total - }, - extraEnv: { TS: context.nodeRuntime === 'ts-node' ? '1' : '0' }, - }) - }), - ), - ) - - const collectedCount = batchResults.reduce((total, batchResult) => { - return total + batchResult.totalCases - }, 0) - - return collectedCount -} - /* * Splits the list of collected test files into `workerCount` batches and starts * worker processes. @@ -132,11 +104,6 @@ async function run(args: Args, context: Context) { console.log(`Collected ${supportedTests.length} test files in ${boldText((testCollectTime / 1000).toFixed(3))}s`) - performance.mark('case-collect:start') - const collectedCaseCount = await collectCases(context, supportedTests) - performance.mark('case-collect:end') - const caseCollectTime = performance.measure('case-collect', 'case-collect:start', 'case-collect:end').duration - console.log(`Collected ${collectedCaseCount} test cases in ${boldText((caseCollectTime / 1000).toFixed(3))}s`) const summary = await assignTestsToWorkers(context, supportedTests, args.workers) const hasFailed = Object.values(summary).filter((workerReport) => !workerReport.pass).length > 0 diff --git a/src/testComponents/describe.ts b/src/testComponents/describe.ts index 625ecdb..f05bcaf 100644 --- a/src/testComponents/describe.ts +++ b/src/testComponents/describe.ts @@ -1,6 +1,7 @@ import { promises as fs } from 'fs' +import { performance } from 'perf_hooks' -import { greenText, redText } from '../utils' +import { getTestContext, setContext } from '../testContext' import { type TestCaseLabel, type TestCaseFunction, type TestCaseGroup } from '../types' /* @@ -15,13 +16,19 @@ import { type TestCaseLabel, type TestCaseFunction, type TestCaseGroup } from '. * ``` */ function describe(label: TestCaseLabel, testGroup: TestCaseGroup) { - if (process.env.COLLECT) { - testGroup() - return - } + const parentContext = getTestContext() + const currentContext = parentContext.addChildContext(label) + + setContext(currentContext) - console.log(greenText(label)) testGroup() + + setContext(parentContext) + + if (parentContext.isRootContext) { + parentContext.runTests() + setContext(null) + } } Object.defineProperty(describe, 'each', { diff --git a/src/testComponents/matchers.ts b/src/testComponents/matchers.ts index 3141d98..ff61ffe 100644 --- a/src/testComponents/matchers.ts +++ b/src/testComponents/matchers.ts @@ -104,7 +104,7 @@ function toNotThrow(func: () => unknown, negated: boolean = false): MatcherRepor function toHaveLength(value: unknown, length: unknown, negated: boolean = false): MatcherReport { let valueLength = 0 - const typedLength = length as number + const typedLength = length as number const typedValue = value as WithLength if (typeof typedValue === 'string' || typeof typedValue.length === 'number') valueLength = typedValue.length as number diff --git a/src/testComponents/test.ts b/src/testComponents/test.ts index 3618c80..0128009 100644 --- a/src/testComponents/test.ts +++ b/src/testComponents/test.ts @@ -1,7 +1,6 @@ -import { promises as fs } from 'fs' -import { performance } from 'perf_hooks' -import { greenText, redText } from '../utils' -import { type TestCaseLabel, type TestCaseFunction, type TestCaseGroup } from '../types' +import { type TestCaseLabel, type TestCaseFunction } from '../types' + +import { getTestContext, setContext } from '../testContext' /* * `test` defines a single test case. @@ -13,24 +12,13 @@ import { type TestCaseLabel, type TestCaseFunction, type TestCaseGroup } from '. * ``` */ function test(label: TestCaseLabel, testCase: TestCaseFunction): void { - performance.mark(`test-${label}:start`) - if (process.env.COLLECT) { - console.log(label) - return - } + const context = getTestContext() - let hasFailed = false - try { - testCase() - } catch (e) { - hasFailed = true - console.log(redText(String(e))) + context.addTest(label, testCase) + if (context.isRootContext) { + context.runTests() + setContext(null) } - performance.mark(`test-${label}:end`) - const testDuration = performance.measure(`test-${label}`, `test-${label}:start`, `test-${label}:end`).duration - - if (hasFailed) console.log(redText(`❌ [FAILED] ${label} (${(testDuration / 1000).toFixed(3)}s)`)) - else console.log(greenText(`✅ [PASS] ${label} (${(testDuration / 1000).toFixed(3)}s)`)) } Object.defineProperty(test, 'each', { diff --git a/src/testContext.ts b/src/testContext.ts new file mode 100644 index 0000000..828bac4 --- /dev/null +++ b/src/testContext.ts @@ -0,0 +1,72 @@ +import { performance } from 'perf_hooks' +import { redText, greenText } from './utils' +import { type TestCaseLabel, type TestCaseFunction } from './types' + +let _testContext: TestContext | undefined | null + +export function getTestContext(): TestContext { + if (!_testContext) _testContext = new TestContext() + + return _testContext +} + +export function setContext(context: TestContext | null) { + _testContext = context +} + +export class TestContext { + children: Map + tests: Map + parentContext?: TestContext | null + + constructor(parentContext: TestContext | null = null) { + this.tests = new Map() + this.children = new Map() + this.parentContext = parentContext + } + + addTest(testLabel: TestCaseLabel, testFunction: TestCaseFunction) { + this.tests.set(testLabel, testFunction) + } + + addChildContext(label: string): TestContext { + const childContext = new TestContext(this) + this.children.set(label, childContext) + return childContext + } + + runTest(label: TestCaseLabel, test: TestCaseFunction) { + performance.mark(`test-${label}:start`) + let hasFailed = false + try { + test() + } catch (e) { + hasFailed = true + console.log(redText(String(e))) + } + + performance.mark(`test-${label}:end`) + const testDuration = performance.measure(`test-${label}`, `test-${label}:start`, `test-${label}:end`).duration + + if (hasFailed) console.log(redText(`❌ [FAILED] ${label} (${(testDuration / 1000).toFixed(3)}s)`)) + else console.log(greenText(`✅ [PASS] ${label} (${(testDuration / 1000).toFixed(3)}s)`)) + } + + runTests() { + for (const test of this.tests) { + const [label, testFunction] = test + this.runTest(label, testFunction) + } + + for (const child of this.children) { + const [label, childContext] = child + console.group(greenText(label)) + childContext.runTests() + console.groupEnd() + } + } + + get isRootContext() { + return this.parentContext === null + } +} diff --git a/src/types.ts b/src/types.ts index 3593f4d..5997f8a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,6 @@ export type TestCaseGroup = (...args: Array) => void export interface Context { workerRuntime: string runnerRuntime: string - collectorRuntime: string nodeRuntime: 'ts-node' | 'node' ts: boolean } @@ -58,10 +57,6 @@ export interface WorkerReport { runtime: number | null } -export interface CollectorReport { - totalCases: number -} - export interface WithLength { length?: number | (() => number) size?: number | (() => number) diff --git a/src/utils.ts b/src/utils.ts index 3c1f4ce..3d75f0e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -54,7 +54,6 @@ export function getContext(runnerPath: string, ts: boolean = false): Context { return { workerRuntime: path.join(installDirectory, `worker${runnerExtension}`), runnerRuntime: runnerPath, - collectorRuntime: path.join(installDirectory, `collector${runnerExtension}`), nodeRuntime, ts, }