UNPKG

@stdlib/bench-harness

Version:
376 lines (336 loc) 8.72 kB
/** * @license Apache-2.0 * * Copyright (c) 2018 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. */ 'use strict'; // MODULES // var EventEmitter = require( 'events' ).EventEmitter; var inherit = require( '@stdlib/utils-inherit' ); var defineProperty = require( '@stdlib/utils-define-property' ); var setReadOnly = require( '@stdlib/utils-define-nonenumerable-read-only-property' ); var tic = require( '@stdlib/time-tic' ); var toc = require( '@stdlib/time-toc' ); var run = require( './run.js' ); var exit = require( './exit.js' ); var ended = require( './ended.js' ); var assert = require( './assert.js' ); var comment = require( './comment.js' ); var skip = require( './skip.js' ); var todo = require( './todo.js' ); var fail = require( './fail.js' ); var pass = require( './pass.js' ); var ok = require( './ok.js' ); var notOk = require( './not_ok.js' ); var equal = require( './equal.js' ); var notEqual = require( './not_equal.js' ); var deepEqual = require( './deep_equal.js' ); var notDeepEqual = require( './not_deep_equal.js' ); var end = require( './end.js' ); // MAIN // /** * Benchmark constructor. * * @constructor * @param {string} name - benchmark name * @param {Options} opts - benchmark options * @param {boolean} opts.skip - boolean indicating whether to skip a benchmark * @param {PositiveInteger} opts.iterations - number of iterations * @param {PositiveInteger} opts.timeout - number of milliseconds before a benchmark automatically fails * @param {Function} [benchmark] - function containing benchmark code * @returns {Benchmark} Benchmark instance * * @example * var bench = new Benchmark( 'beep', function benchmark( b ) { * var x; * var i; * b.comment( 'Running benchmarks...' ); * 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.comment( 'Finished running benchmarks.' ); * b.end(); * }); */ function Benchmark( name, opts, benchmark ) { var hasTicked; var hasTocked; var self; var time; if ( !( this instanceof Benchmark ) ) { return new Benchmark( name, opts, benchmark ); } self = this; hasTicked = false; hasTocked = false; EventEmitter.call( this ); // Private properties: setReadOnly( this, '_benchmark', benchmark ); setReadOnly( this, '_skip', opts.skip ); defineProperty( this, '_ended', { 'configurable': false, 'enumerable': false, 'writable': true, 'value': false }); defineProperty( this, '_running', { 'configurable': false, 'enumerable': false, 'writable': true, 'value': false }); defineProperty( this, '_exited', { 'configurable': false, 'enumerable': false, 'writable': true, 'value': false }); defineProperty( this, '_count', { 'configurable': false, 'enumerable': false, 'writable': true, 'value': 0 }); // Read-only: setReadOnly( this, 'name', name ); setReadOnly( this, 'tic', start ); setReadOnly( this, 'toc', stop ); setReadOnly( this, 'iterations', opts.iterations ); setReadOnly( this, 'timeout', opts.timeout ); return this; /** * Starts a benchmark timer. * * ## Notes * * - Using a scoped variable prevents nefarious mutation by bad actors hoping to manipulate benchmark results. * - The one attack vector which remains is manipulation of the `require` cache for `tic` and `toc`. * - One way to combat cache manipulation is by comparing the checksum of `Function#toString()` against known values. * * @private */ function start() { if ( hasTicked ) { self.fail( '.tic() called more than once' ); } else { self.emit( 'tic' ); hasTicked = true; time = tic(); } } /** * Stops a benchmark timer. * * @private * @returns {void} */ function stop() { var elapsed; var secs; var rate; var out; if ( hasTicked === false ) { return self.fail( '.toc() called before .tic()' ); } elapsed = toc( time ); if ( hasTocked ) { return self.fail( '.toc() called more than once' ); } hasTocked = true; self.emit( 'toc' ); secs = elapsed[ 0 ] + ( elapsed[ 1 ]/1e9 ); rate = self.iterations / secs; out = { 'ok': true, 'operator': 'result', 'iterations': self.iterations, 'elapsed': secs, 'rate': rate }; self.emit( 'result', out ); } } /* * Inherit from the `EventEmitter` prototype. */ inherit( Benchmark, EventEmitter ); /** * Runs a benchmark. * * @private * @name run * @memberof Benchmark.prototype * @type {Function} */ setReadOnly( Benchmark.prototype, 'run', run ); /** * Forcefully ends a benchmark. * * @private * @name exit * @memberof Benchmark.prototype * @type {Function} */ setReadOnly( Benchmark.prototype, 'exit', exit ); /** * Returns a `boolean` indicating if a benchmark has ended. * * @private * @name ended * @memberof Benchmark.prototype * @type {Function} * @returns {boolean} boolean indicating if a benchmark has ended */ setReadOnly( Benchmark.prototype, 'ended', ended ); /** * Generates an assertion. * * @private * @name _assert * @memberof Benchmark.prototype * @type {Function} * @param {boolean} ok - assertion outcome * @param {Options} opts - options */ setReadOnly( Benchmark.prototype, '_assert', assert ); /** * Writes a comment. * * @name comment * @memberof Benchmark.prototype * @type {Function} * @param {string} msg - comment message */ setReadOnly( Benchmark.prototype, 'comment', comment ); /** * Generates an assertion which will be skipped. * * @name skip * @memberof Benchmark.prototype * @type {Function} * @param {*} value - value * @param {string} msg - message */ setReadOnly( Benchmark.prototype, 'skip', skip ); /** * Generates an assertion which should be implemented. * * @name todo * @memberof Benchmark.prototype * @type {Function} * @param {*} value - value * @param {string} msg - message */ setReadOnly( Benchmark.prototype, 'todo', todo ); /** * Generates a failing assertion. * * @name fail * @memberof Benchmark.prototype * @type {Function} * @param {string} msg - message */ setReadOnly( Benchmark.prototype, 'fail', fail ); /** * Generates a passing assertion. * * @name pass * @memberof Benchmark.prototype * @type {Function} * @param {string} msg - message */ setReadOnly( Benchmark.prototype, 'pass', pass ); /** * Asserts that a `value` is truthy. * * @name ok * @memberof Benchmark.prototype * @type {Function} * @param {*} value - value * @param {string} [msg] - message */ setReadOnly( Benchmark.prototype, 'ok', ok ); /** * Asserts that a `value` is falsy. * * @name notOk * @memberof Benchmark.prototype * @type {Function} * @param {*} value - value * @param {string} [msg] - message */ setReadOnly( Benchmark.prototype, 'notOk', notOk ); /** * Asserts that `actual` is strictly equal to `expected`. * * @name equal * @memberof Benchmark.prototype * @type {Function} * @param {*} actual - actual value * @param {*} expected - expected value * @param {string} [msg] - message */ setReadOnly( Benchmark.prototype, 'equal', equal ); /** * Asserts that `actual` is not strictly equal to `expected`. * * @name notEqual * @memberof Benchmark.prototype * @type {Function} * @param {*} actual - actual value * @param {*} expected - expected value * @param {string} [msg] - message */ setReadOnly( Benchmark.prototype, 'notEqual', notEqual ); /** * Asserts that `actual` is deeply equal to `expected`. * * @name deepEqual * @memberof Benchmark.prototype * @type {Function} * @param {*} actual - actual value * @param {*} expected - expected value * @param {string} [msg] message */ setReadOnly( Benchmark.prototype, 'deepEqual', deepEqual ); /** * Asserts that `actual` is not deeply equal to `expected`. * * @name notDeepEqual * @memberof Benchmark.prototype * @type {Function} * @param {*} actual - actual value * @param {*} expected - expected value * @param {string} [msg] message */ setReadOnly( Benchmark.prototype, 'notDeepEqual', notDeepEqual ); /** * Ends a benchmark. * * @name end * @memberof Benchmark.prototype * @type {Function} */ setReadOnly( Benchmark.prototype, 'end', end ); // EXPORTS // module.exports = Benchmark;