@stdlib/bench-harness
Version:
Benchmark harness.
376 lines (336 loc) • 8.72 kB
JavaScript
/**
* @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;