Skip to content

Commit f4c1a32

Browse files
committed
feat: Added callbacks.
1 parent c3f12e5 commit f4c1a32

File tree

8 files changed

+167
-14
lines changed

8 files changed

+167
-14
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ The supported options are the following:
7676
- `colors`: If use colors. Default is `true`.
7777
- `compare`: If compare tests in the output. Default is `false`.
7878
- `compareMode`: When comparing is enabled, this can be set to `base` in order to always compare a test to the slowest one. The default is to compare a test to the immediate slower one.
79+
- `onTestStart`: Callback invoked every time a test is started.
80+
- `onTestEnd`: Callback invoked every time a test has finished.
81+
- `onTestError`: Callback invoked every time a test could not be loaded. If the test function throws an error or rejects, `onTestEnd` will be invoked instead.
7982

8083
## Results structure
8184

src/index.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,19 @@ function scheduleNextTest(context: Context): void {
2929

3030
function run(context: Context): void {
3131
const name = context.tests[context.current][0]
32+
const workerData = {
33+
path: process.argv[1],
34+
index: context.current,
35+
iterations: context.iterations,
36+
warmup: context.warmup,
37+
errorThreshold: context.errorThreshold
38+
}
3239

33-
const worker = new Worker(runnerPath, {
34-
workerData: {
35-
path: process.argv[1],
36-
index: context.current,
37-
iterations: context.iterations,
38-
warmup: context.warmup,
39-
errorThreshold: context.errorThreshold
40-
}
41-
})
40+
const worker = new Worker(runnerPath, { workerData })
41+
42+
if (context.onTestStart) {
43+
context.onTestStart(name, workerData, worker)
44+
}
4245

4346
worker.on('error', error => {
4447
context.results[name] = {
@@ -55,13 +58,21 @@ function run(context: Context): void {
5558

5659
context.current++
5760

61+
if (context.onTestError) {
62+
context.onTestError(name, error, worker)
63+
}
64+
5865
scheduleNextTest(context)
5966
})
6067

6168
worker.on('message', result => {
6269
context.results[name] = result
6370
context.current++
6471

72+
if (context.onTestEnd) {
73+
context.onTestEnd(name, result, worker)
74+
}
75+
6576
scheduleNextTest(context)
6677
})
6778
}
@@ -109,7 +120,10 @@ export function cronometro(
109120
}
110121

111122
// Parse and validate options
112-
const { iterations, errorThreshold, print, warmup } = { ...defaultOptions, ...options }
123+
const { iterations, errorThreshold, print, warmup, onTestStart, onTestEnd, onTestError } = {
124+
...defaultOptions,
125+
...options
126+
}
113127

114128
if (typeof iterations !== 'number' || iterations < 1) {
115129
callback(new Error('The iterations option must be a positive number.'))
@@ -121,6 +135,21 @@ export function cronometro(
121135
return promise
122136
}
123137

138+
if (onTestStart && typeof onTestStart !== 'function') {
139+
callback(new Error('The onTestStart option must be a function.'))
140+
return promise
141+
}
142+
143+
if (onTestEnd && typeof onTestEnd !== 'function') {
144+
callback(new Error('The onTestEnd option must be a function.'))
145+
return promise
146+
}
147+
148+
if (onTestError && typeof onTestError !== 'function') {
149+
callback(new Error('The onTestError option must be a function.'))
150+
return promise
151+
}
152+
124153
// Prepare the test
125154
const context: Context = {
126155
warmup,
@@ -130,7 +159,10 @@ export function cronometro(
130159
tests: Object.entries(tests), // Convert tests to a easier to process [name, func] list,
131160
results: {},
132161
current: 0,
133-
callback
162+
callback,
163+
onTestStart,
164+
onTestEnd,
165+
onTestError
134166
}
135167

136168
process.nextTick(() => run(context))

src/models.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Histogram } from 'hdr-histogram-js'
22
import { resolve } from 'node:path'
3+
import { Worker } from 'node:worker_threads'
34

45
export interface PrintOptions {
56
colors?: boolean
@@ -16,6 +17,9 @@ export interface Options {
1617
errorThreshold: number
1718
print: boolean | PrintOptions
1819
warmup: boolean
20+
onTestStart?: (name: string, data: object, worker: Worker) => void
21+
onTestEnd?: (name: string, result: Result, worker: Worker) => void
22+
onTestError?: (name: string, error: Error, worker: Worker) => void
1923
}
2024

2125
export type StaticTest = () => any
@@ -64,6 +68,9 @@ export interface Context {
6468
results: Results
6569
current: number
6670
callback: Callback
71+
onTestStart?: (name: string, data: object, worker: Worker) => void
72+
onTestEnd?: (name: string, result: Result, worker: Worker) => void
73+
onTestError?: (name: string, error: Error, worker: Worker) => void
6774
}
6875

6976
export interface WorkerContext {

test/asyncImport.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async function main(): Promise<void> {
2121
() => false
2222
)
2323
} else {
24-
t.only('Collecting results', async t => {
24+
t.test('Collecting results', async t => {
2525
const results = await cronometro(
2626
{
2727
single() {

test/callbacks.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* eslint-disable @typescript-eslint/no-floating-promises */
2+
3+
import { isMainThread, Worker } from 'node:worker_threads'
4+
import t from 'tap'
5+
import { cronometro, Result, TestFunction } from '../src/index.js'
6+
7+
if (!isMainThread) {
8+
cronometro(
9+
{
10+
single() {
11+
Buffer.alloc(10)
12+
},
13+
multiple() {
14+
throw new Error('INVALID')
15+
},
16+
missing: undefined as unknown as TestFunction
17+
},
18+
() => false
19+
)
20+
} else {
21+
t.test('Callbacks', async t => {
22+
await cronometro(
23+
{
24+
single() {
25+
Buffer.alloc(10)
26+
},
27+
multiple() {
28+
throw new Error('INVALID')
29+
},
30+
missing() {
31+
Buffer.alloc(10)
32+
}
33+
},
34+
{
35+
iterations: 10,
36+
print: false,
37+
onTestStart(name: string, data: any, worker: Worker) {
38+
t.match(name, /single|multiple|missing/)
39+
t.ok(data.index < 3)
40+
t.ok(worker instanceof Worker)
41+
},
42+
onTestEnd(name: string, result: Result, worker: Worker) {
43+
if (result.success) {
44+
t.same(name, 'single')
45+
t.ok(result.size > 0)
46+
} else {
47+
t.same(name, 'multiple')
48+
t.same(result.error!.message, 'INVALID')
49+
}
50+
t.ok(worker instanceof Worker)
51+
},
52+
onTestError(name: string, error: Error, worker: Worker) {
53+
t.same(name, 'missing')
54+
t.same(error.message, "Cannot read properties of undefined (reading 'test')")
55+
t.ok(worker instanceof Worker)
56+
}
57+
}
58+
)
59+
})
60+
}

test/errorHandling.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ if (!isMainThread) {
6161
await t.rejects(import('../src/runner.js'), { message: 'Do not run this file as main script.' })
6262
})
6363

64-
t.only('Runner reports setup errors', async t => {
64+
t.test('Runner reports setup errors', async t => {
6565
const results = await cronometro(
6666
{
6767
notDefined() {

test/optionsValidation.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,55 @@ t.test('Options validation', async t => {
3737
t.equal(err!.message, 'The errorThreshold option must be a number between 0 and 100.')
3838
}
3939
)
40+
41+
cronometro(
42+
{
43+
single() {
44+
Buffer.alloc(10)
45+
},
46+
multiple() {
47+
Buffer.alloc(10)
48+
Buffer.alloc(20)
49+
}
50+
},
51+
{ onTestStart: 1 as any },
52+
err => {
53+
t.type(err, 'Error')
54+
t.equal(err!.message, 'The onTestStart option must be a function.')
55+
}
56+
)
57+
58+
cronometro(
59+
{
60+
single() {
61+
Buffer.alloc(10)
62+
},
63+
multiple() {
64+
Buffer.alloc(10)
65+
Buffer.alloc(20)
66+
}
67+
},
68+
{ onTestEnd: 1 as any },
69+
err => {
70+
t.type(err, 'Error')
71+
t.equal(err!.message, 'The onTestEnd option must be a function.')
72+
}
73+
)
74+
75+
cronometro(
76+
{
77+
single() {
78+
Buffer.alloc(10)
79+
},
80+
multiple() {
81+
Buffer.alloc(10)
82+
Buffer.alloc(20)
83+
}
84+
},
85+
{ onTestError: 1 as any },
86+
err => {
87+
t.type(err, 'Error')
88+
t.equal(err!.message, 'The onTestError option must be a function.')
89+
}
90+
)
4091
})

test/success.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ if (!isMainThread) {
1818
() => false
1919
)
2020
} else {
21-
t.only('Collecting results', async t => {
21+
t.test('Collecting results', async t => {
2222
const results = await cronometro(
2323
{
2424
single() {

0 commit comments

Comments
 (0)