UNPKG

@stdlib/bench-harness

Version:
737 lines (689 loc) 17.4 kB
/* * @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;