@stdlib/bench-harness
Version:
Benchmark harness.
737 lines (689 loc) • 17.4 kB
TypeScript
/*
* @license Apache-2.0
*
* Copyright (c) 2021 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TypeScript Version: 4.1
/// <reference types="node"/>
import { Transform } from 'stream';
/**
* Interface defining function options.
*/
interface Options {
/**
* Boolean indicating whether to skip a benchmark (default: false).
*/
skip?: boolean;
/**
* Number of repeats (default: 3).
*/
repeats?: number;
/**
* Number of iterations (default: null).
*/
iterations?: number | null;
/**
* Number of milliseconds before a benchmark automatically fails (default: 300000).
*/
timeout?: number;
}
/**
* Harness options.
*/
interface HarnessOptions {
/**
* Indicating whether to automatically close a harness after running all benchmarks.
*/
autoclose: boolean;
}
/**
* Interface defining stream options.
*/
interface StreamOptions {
/**
* Callback to invoke upon receiving a new chunk.
*/
transform?: Function;
/**
* Callback to invoke after receiving all chunks and prior to the stream closing.
*/
flush?: Function;
/**
* Specifies whether a stream should operate in object mode (default: `false`).
*/
objectMode?: boolean;
/**
* Specifies how `Buffer` objects should be decoded to strings (default: `null`).
*/
encoding?: string | null;
/**
* Specifies the `Buffer` level for when `write()` starts returning `false`.
*/
highWaterMark?: number;
/**
* Specifies whether the stream should remain open even if one side ends (default: `false`).
*/
allowHalfOpen?: boolean;
/**
* Specifies whether to decode `strings` into `Buffer` objects when writing (default: `true`).
*/
decodeStrings?: boolean;
}
/**
* Callback invoked when a harness finishes running all benchmarks.
*/
type Callback = () => void;
/**
* Benchmark interface.
*/
interface Benchmark {
/**
* **Read-only** property whose value is the benchmark `name`.
*
* @example
* var str = b.name;
* // returns <string>
*/
readonly name: string;
/**
* **Read-only** property whose value is the number of iterations.
*
* @example
* var iter = b.iterations;
* // returns <number>
*/
readonly iterations: number;
/**
* Starts a benchmark timer. In order to benchmark code, this method **must always** be called within a `benchmark` function.
*
* @example
* function benchmark( b ) {
* var x;
* var i;
*
* // Start a timer:
* b.tic();
*
* for ( i = 0; i < b.iterations; i++ ) {
* x = Math.sin( Math.random() );
* if ( x !== x ) {
* b.fail( 'should not return NaN' );
* }
* }
* b.toc();
* if ( x !== x ) {
* b.fail( 'should not return NaN' );
* }
* b.end();
* }
*/
tic(): void;
/**
* Stops a benchmark timer. In order to benchmark code, this method **must always** be called within a `benchmark` function.
*
* @example
* function benchmark( b ) {
* var x;
* var i;
*
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* x = Math.sin( Math.random() );
* if ( x !== x ) {
* b.fail( 'should not return NaN' );
* }
* }
* // Stop a timer:
* b.toc();
*
* if ( x !== x ) {
* b.fail( 'should not return NaN' );
* }
* b.end();
* }
*/
toc(): void;
/**
* Explicitly ends a benchmark. In order to benchmark code, this method **must always** be called within a `benchmark` function.
*
* ## Notes
*
* - **Warning**: no assertions should follow a call to `b.end()`. Including assertions after `b.end()` may result in interleaved [TAP][tap] output or an output stream closing before a benchmark executes pending assertions.
*
* @example
* function benchmark( b ) {
* var x;
* var i;
*
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* x = Math.sin( Math.random() );
* if ( x !== x ) {
* b.fail( 'should not return NaN' );
* }
* }
* b.toc();
*
* if ( x !== x ) {
* b.fail( 'should not return NaN' );
* }
*
* // Explicitly end the benchmark:
* b.end();
* }
*/
end(): void;
/**
* Writes a message without breaking [TAP][tap] output.
*
* ## Notes
*
* - To prevent confusing results parsers, **avoid** using comments. Comments are frequently used for demarcating the beginning of a new benchmark run and/or providing diagnostic information. Both of the aforementioned use cases typically fall under the domain of the harness, not the user.
*
* @param msg - message
*
* @example
* b.comment( 'This is a comment.' );
*/
comment( msg: string ): void;
/**
* Generates an assertion which will be skipped.
*
* @param value - value
* @param msg - message
*
* @example
* b.skip( false, 'This is skipped.' );
* b.skip( true, 'This is skipped.' );
*/
skip( value: any, msg: string ): void;
/**
* Generates an assertion which should be implemented.
*
* ## Notes
*
* - While `b.todo()` assertions typically fail, they do **not** contribute to the failed assertion count. If a benchmark includes `b.todo()` assertions and no other failing assertions, the benchmark is considered successful.
*
* @param value - value
* @param msg - message
*
* @example
* b.todo( false, 'This is a todo.' );
* b.todo( true, 'This is a todo.' );
*/
todo( value: any, msg: string ): void;
/**
* Generates a failing assertion.
*
* @param msg - message
*
* @example
* b.fail( 'This is a failing assertion.' );
*/
fail( msg: string ): void;
/**
* Generates a passing assertion.
*
* @param msg - message
*
* @example
* b.fail( 'This is a passing assertion.' );
*/
pass( msg: string ): void;
/**
* Asserts that a `value` is truthy.
*
* @param value - value
* @param msg - message
*
* @example
* b.ok( [] );
*
* @example
* b.ok( true, 'This asserts a value is truthy.' );
* b.ok( false, 'This asserts a value is truthy.' );
*/
ok( value: any, msg?: string ): void;
/**
* Asserts that a `value` is falsy.
*
* @param value - value
* @param msg - message
*
* @example
* b.notOk( null );
*
* @example
* b.notOk( false, 'This asserts a value is falsy.' );
* b.notOk( true, 'This asserts a value is falsy.' );
*/
notOk( value: any, msg?: string ): void;
/**
* Asserts that `actual` is **strictly** equal to `expected`.
*
* @param actual - actual value
* @param expected - expected value
* @param msg - message
*
* @example
* var expected = [];
* var actual = expected;
*
* b.equal( actual, expected );
*
* @example
* var expected = [];
* var actual = expected;
*
* b.equal( actual, expected, 'This asserts two values are strictly equal.' );
* b.equal( 1.0, 2.0, 'This asserts two values are strictly equal.' );
*/
equal( actual: any, expected: any, msg?: string ): void;
/**
* Asserts that `actual` is not **strictly** equal to `expected`.
*
* @param actual - actual value
* @param expected - expected value
* @param msg - message
*
* @example
* var expected = [];
* var actual = [];
*
* b.notEqual( actual, expected );
*
* @example
* var expected = [];
* var actual = [];
*
* b.notEqual( 1.0, 2.0, 'This asserts two values are not equal.' );
* b.notEqual( actual, expected, 'This asserts two values are not equal.' );
*/
notEqual( actual: any, expected: any, msg?: string ): void;
/**
* Asserts that `actual` is **deeply** equal to `expected`.
*
* @param actual - actual value
* @param expected - expected value
* @param msg - message
*
* @example
* var expected = {
* 'a': 'b'
* };
* var actual = {
* 'a': 'b'
* };
*
* b.deepEqual( actual, expected );
*
* @example
* var expected = {
* 'a': 'b'
* };
* var actual = {
* 'a': 'b'
* };
*
* b.deepEqual( actual, expected, 'This asserts two values are deeply equal.' );
*
* actual.a = 'c';
* b.deepEqual( actual, expected, 'This asserts two values are deeply equal.' );
*/
deepEqual( actual: any, expected: any, msg?: string ): void;
/**
* Asserts that `actual` is not **deeply** equal to `expected`.
*
* @param actual - actual value
* @param expected - expected value
* @param msg - message
*
* @example
* var expected = {
* 'a': 'b'
* };
* var actual = {
* 'a': 'c'
* };
*
* b.notDeepEqual( actual, expected );
*
* @example
* var expected = {
* 'a': 'b'
* };
* var actual = {
* 'a': 'c'
* };
*
* b.notDeepEqual( actual, expected, 'This asserts two values are not deeply equal.' );
*
* actual.a = 'b';
* b.notDeepEqual( actual, expected, 'This asserts two values are not deeply equal.' );
*/
notDeepEqual( actual: any, expected: any, msg?: string ): void;
}
/**
* Benchmark function.
*/
type BenchmarkFunction = ( b: Benchmark ) => void;
/**
* Harness interface.
*/
interface Harness {
/**
* Returns a results [stream][nodejs-stream].
*
* ## Notes
*
* - Benchmarks will **not** run until a destination stream has been created.
*
* @param options - stream options
*
* @example
* var stdout = require( '@stdlib/streams-node-stdout' );
*
* var harness = bench.createHarness();
* var stream = harness.createStream();
*
* // Direct all results to `stdout`:
* stream.pipe( stdout );
*
* var opts = {
* 'iterations': 1,
* 'repeats': 1
* };
*
* harness( 'beep', opts, function benchmark( b ) {
* var i;
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* b.equal( 'beep', 'beep', 'should be equal' );
* }
* b.toc();
* b.end();
* });
*/
createStream( options?: StreamOptions ): Transform;
/**
* Closes a benchmark harness. Any pending benchmarks are cleared from the harness stack.
*
* ## Notes
*
* - **Warning**: a running benchmark may finish **after** closing a harness.
*
* @example
* var stdout = require( '@stdlib/streams-node-stdout' );
*
* var harness = bench.createHarness();
*
* var stream = harness.createStream();
* stream.pipe( stdout );
*
* var opts = {
* 'iterations': 5,
* 'repeats': 5
* };
*
* harness( 'early close', opts, function benchmark( b ) {
* var i = 0;
* b.tic();
* setTimeout( next, 0 );
* function next() {
* i += 1;
* if ( i <= b.iterations ) {
* b.ok( true, 'should be truthy' );
* return setTimeout( next, 10 );
* }
* b.toc();
* b.end();
* }
* });
*
* // Early close:
* setTimeout( onTimeout, 50 );
*
* function onTimeout() {
* harness.close();
* }
*/
close(): void;
/**
* Forcefully exits a benchmark harness. All pending benchmarks will generate **failing** assertions.
*
* ## Notes
*
* - **Warning**: a running benchmark may finish **after** exiting a harness.
*
* @example
* var stdout = require( '@stdlib/streams-node-stdout' );
*
* var harness = bench.createHarness();
*
* var stream = harness.createStream();
* stream.pipe( stdout );
*
* var opts = {
* 'iterations': 5
* };
*
* harness( 'force exit', opts, function benchmark( b ) {
* var i = 0;
* b.tic();
* return next();
* function next() {
* i += 1;
* if ( i <= b.iterations ) {
* b.ok( true, 'should be truthy' );
* return setTimeout( next, 10 );
* }
* b.toc();
* b.end();
* }
* });
*
* // Forcefully exit:
* setTimeout( onTimeout, 20 );
*
* function onTimeout() {
* harness.exit();
* }
*/
exit(): void;
/**
* **Read-only** property whose value is the harness exit code. If all benchmarks run successfully (i.e., no failing assertions), the exit code is `0`; otherwise, the exit code is `1`.
*
* @example
* var harness = bench.createHarness();
*
* // Benchmarks only start running when results have a destination:
* var stream = harness.createStream();
*
* function onFinish() {
* console.log( harness.exitCode );
* // => 1
* }
*
* var opts = {
* 'iterations': 1,
* 'repeats': 1
* };
*
* harness( 'exit code', opts, function benchmark( b ) {
* var i;
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* b.fail( 'failing assertion' );
* }
* b.toc();
* b.end();
* });
*/
readonly exitCode: number;
}
/**
* Interface for the benchmark harness.
*/
interface Main {
/**
* Runs a benchmark.
*
* @param name - benchmark name
* @param benchmark - function containing benchmark code
* @returns benchmark harness
*
* @example
* bench( 'beep', function benchmark( b ) {
* var x;
* var i;
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* x = Math.sin( Math.random() );
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* }
* b.toc();
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* b.end();
* });
*/
( name: string, benchmark?: BenchmarkFunction ): Function;
/**
* Runs a benchmark.
*
* @param name - benchmark name
* @param options - benchmark options
* @param options.skip - boolean indicating whether to skip a benchmark (default: false)
* @param options.iterations - number of iterations (default: null)
* @param options.repeats - number of repeats (default: 3)
* @param options.timeout - number of milliseconds before a benchmark automatically fails (default: 300000)
* @param benchmark - function containing benchmark code
* @throws must provide valid options
* @returns benchmark harness
*
* @example
* var opts = {
* 'repeats': 5
* };
*
* bench( 'beep', opts, function benchmark( b ) {
* var x;
* var i;
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* x = Math.sin( Math.random() );
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* }
* b.toc();
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* b.end();
* });
*/
( name: string, options: Options, benchmark?: BenchmarkFunction ): Function;
/**
* Creates a benchmark harness with a new pending stack and state.
*
* @param clbk - callback to invoke when a harness finishes running all benchmarks
* @returns benchmark harness
*/
createHarness( clbk?: Callback ): Harness;
/**
* Creates a benchmark harness with a new pending stack and state.
*
* @param options - harness options
* @param options.autoclose - indicating whether to automatically close a harness after running all benchmarks
* @param clbk - callback to invoke when a harness finishes running all benchmarks
* @throws must provide valid options
*/
createHarness( options: HarnessOptions, clbk?: Callback ): Harness;
/**
* Creates a results stream.
*
* @param options - stream options
* @param options.transform - callback to invoke upon receiving a new chunk
* @param options.flush - callback to invoke after receiving all chunks and prior to the stream closing
* @param options.objectMode - specifies whether stream should operate in object mode (default: false)
* @param options.encoding - specifies how `Buffer` objects should be decoded to `strings` (default: null)
* @param options.highWaterMark - specifies the `Buffer` level for when `write()` starts returning `false`
* @param options.allowHalfOpen - specifies whether the stream should remain open even if one side ends (default: false)
* @param options.decodeStrings - specifies whether to decode `strings` into `Buffer` objects when writing (default: true)
* @throws must provide valid stream options
*/
createStream( options?: StreamOptions ): Transform;
/**
* Sets a listener which is invoked once the harness **finishes** running all benchmarks.
*
* @param clbk - listener
* @throws must provide a listener only once
*/
onFinish( clbk: Function ): void;
}
/**
* Runs a benchmark.
*
* @param name - benchmark name
* @param benchmark - function containing benchmark code
* @returns benchmark harness
*
* @example
* bench( 'beep', function benchmark( b ) {
* var x;
* var i;
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* x = Math.sin( Math.random() );
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* }
* b.toc();
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* b.end();
* });
*
* @example
* var opts = {
* 'repeats': 5
* };
*
* bench( 'beep', opts, function benchmark( b ) {
* var x;
* var i;
* b.tic();
* for ( i = 0; i < b.iterations; i++ ) {
* x = Math.sin( Math.random() );
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* }
* b.toc();
* if ( x !== x ) {
* b.ok( false, 'should not return NaN' );
* }
* b.end();
* });
*/
declare var main: Main;
// EXPORTS //
export = main;