refactor: build context and run tests after gathering facts (#26)
* refactor: build context and run tests after gathering facts * test: update integration test to not check for case collection * refactor: further cleanup * refactor: leftover log statements * refactor: further cleaning * refactor: no more collector
This commit is contained in:
parent
6a9e9ee3fc
commit
912261443a
10 changed files with 96 additions and 117 deletions
|
@ -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)
|
||||
|
|
|
@ -14,5 +14,4 @@ addHashbang() {
|
|||
}
|
||||
|
||||
addHashbang dist/cli.js
|
||||
addHashbang dist/collector.js
|
||||
addHashbang dist/worker.js
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import { type Context } from './types'
|
||||
import { getContext, spawnProcess } from './utils'
|
||||
|
||||
async function collectCases(context: Context, collectedPaths: Array<string>): Promise<number> {
|
||||
const extraArgs: Array<string> = []
|
||||
|
||||
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<number>((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)
|
||||
})
|
|
@ -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<string>): Promise<Array<string>> {
|
|||
return collectedHere
|
||||
}
|
||||
|
||||
async function collectCases(context: Context, collectedPaths: Array<string>, workerCount: number = 1): Promise<number> {
|
||||
const batchedCollectedPaths = splitIntoBatches(collectedPaths, workerCount)
|
||||
|
||||
const batchResults = await Promise.all(
|
||||
batchedCollectedPaths.map(
|
||||
(batch): Promise<CollectorReport> =>
|
||||
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
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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', {
|
||||
|
|
72
src/testContext.ts
Normal file
72
src/testContext.ts
Normal file
|
@ -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<string, TestContext>
|
||||
tests: Map<TestCaseLabel, TestCaseFunction>
|
||||
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
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ export type TestCaseGroup = (...args: Array<unknown>) => 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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Reference in a new issue