Skip to content

Commit

Permalink
feat: Add setup option to prepare tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogunPanda committed Sep 18, 2020
1 parent 6a97a9d commit ec4cc39
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 20 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ If the callback is provided, it will also be called with an error or the results

The supported options are the following:

- `setup`: An object whose properties names are the same as the test and values are setup functions. The functions will be run before the test. Both async/promise and callback style functions are accepted.
- `iterations`: The number of iterations to run for each test. Must be a positive number. The default is `10000`.
- `errorThreshold`: If active, it stops the test run before the desider number of iterations if the standard error is below the provided value and at least 10% of the iterations have been run. Must be a number between `0` (which disables this option) and `100`. The default is `1`.
- `warmup`: Run the suite twice, the first time without collecting results. The default is `true`.
Expand Down Expand Up @@ -127,7 +128,14 @@ const results = cronometro(
subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c')
}
},
{ print: { compare: true } },
{
setup: {
single(cb) {
cb()
}
},
print: { compare: true }
},
(err, results) => {
if (err) {
throw err
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function cronometro(tests, options, cb) {
/* istanbul ignore next */
if (!worker_threads_1.isMainThread) {
worker_threads_1.workerData.tests = Object.entries(tests);
worker_threads_1.workerData.setup = options && typeof options !== 'function' && options.setup ? options.setup : {};
return;
}
let promise;
Expand Down
31 changes: 28 additions & 3 deletions lib/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const models_1 = require("./models");
function noOp() {
// No-op
}
function noSetup(cb) {
cb();
}
function handleTestIteration(context, error) {
// Grab duration even in case of error to make sure we don't add any overhead to the benchmark
const duration = Number(process.hrtime.bigint() - context.start);
Expand Down Expand Up @@ -85,10 +88,28 @@ function runTestIteration(context) {
throw e;
}
}
function afterSetup(testContext, err) {
if (err) {
return testContext.callback({
success: false,
error: err,
size: 0,
min: 0,
max: 0,
mean: 0,
stddev: 0,
percentiles: {},
standardError: 0
});
}
// Schedule the first run
return process.nextTick(() => runTestIteration(testContext));
}
function runWorker(context, notifier, cb) {
const { warmup, tests, index, iterations, errorThreshold } = context;
const { warmup, tests, index, iterations, errorThreshold, setup } = context;
// Require the original file to build tests
const [name, test] = tests[index];
const testSetup = typeof setup[name] === 'function' ? setup[name] : noSetup;
// Prepare the context
const testContext = {
name,
Expand Down Expand Up @@ -116,7 +137,11 @@ function runWorker(context, notifier, cb) {
};
// Bind the handler to the context
testContext.handler = handleTestIteration.bind(null, testContext);
// Schedule the first run
process.nextTick(() => runTestIteration(testContext));
// Run the test setup, then start the test
const callback = afterSetup.bind(null, testContext);
const testSetupResponse = testSetup(callback);
if (testSetupResponse && typeof testSetupResponse.then === 'function') {
testSetupResponse.then(callback, callback);
}
}
exports.runWorker = runWorker;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function cronometro(
/* istanbul ignore next */
if (!isMainThread) {
workerData.tests = Object.entries(tests)
workerData.setup = options && typeof options !== 'function' && options.setup ? options.setup : {}
return
}

Expand Down
6 changes: 5 additions & 1 deletion src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ export interface PrintOptions {
compareMode?: 'base' | 'previous'
}

export type SetupFunction = (cb: (err?: Error | null) => void) => Promise<any> | void

export interface Options {
iterations: number
setup: { [key: string]: SetupFunction }
errorThreshold: number
print: boolean | PrintOptions
warmup: boolean
Expand All @@ -19,7 +22,7 @@ export type PromiseTest = () => Promise<any>

export type Test = StaticTest | AsyncTest | PromiseTest

export type Callback = ((err: Error | null) => any) | ((err: null, results: Results) => any)
export type Callback = ((err: Error | null) => void) | ((err: null, results: Results) => any)

export interface Percentiles {
[key: string]: number
Expand Down Expand Up @@ -59,6 +62,7 @@ export interface Context {
export interface WorkerContext {
path: string
tests: Array<[string, Test]>
setup: { [key: string]: SetupFunction }
index: number
iterations: number
warmup: boolean
Expand Down
37 changes: 33 additions & 4 deletions src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { build as buildHistogram } from 'hdr-histogram-js'
import { Percentiles, percentiles, Result, TestContext, WorkerContext } from './models'
import { Percentiles, percentiles, Result, SetupFunction, TestContext, WorkerContext } from './models'

/* istanbul ignore next */
function noOp(): void {
// No-op
}

function noSetup(cb: (err?: Error | null) => void): void {
cb()
}

function handleTestIteration(context: TestContext, error?: Error | null): void {
// Grab duration even in case of error to make sure we don't add any overhead to the benchmark
const duration = Number(process.hrtime.bigint() - context.start)
Expand Down Expand Up @@ -97,11 +101,31 @@ function runTestIteration(context: TestContext): void {
}
}

function afterSetup(testContext: TestContext, err?: Error | null): void {
if (err) {
return testContext.callback({
success: false,
error: err,
size: 0,
min: 0,
max: 0,
mean: 0,
stddev: 0,
percentiles: {},
standardError: 0
})
}

// Schedule the first run
return process.nextTick(() => runTestIteration(testContext))
}

export function runWorker(context: WorkerContext, notifier: (value: any) => void, cb: (code: number) => void): void {
const { warmup, tests, index, iterations, errorThreshold } = context
const { warmup, tests, index, iterations, errorThreshold, setup } = context

// Require the original file to build tests
const [name, test] = tests[index]
const testSetup: SetupFunction = typeof setup[name] === 'function' ? setup[name] : noSetup

// Prepare the context
const testContext: TestContext = {
Expand Down Expand Up @@ -133,6 +157,11 @@ export function runWorker(context: WorkerContext, notifier: (value: any) => void
// Bind the handler to the context
testContext.handler = handleTestIteration.bind(null, testContext)

// Schedule the first run
process.nextTick(() => runTestIteration(testContext))
// Run the test setup, then start the test
const callback = afterSetup.bind(null, testContext)
const testSetupResponse = testSetup(callback)

if (testSetupResponse && typeof testSetupResponse.then === 'function') {
testSetupResponse.then(callback, callback)
}
}
Loading

0 comments on commit ec4cc39

Please sign in to comment.