;(function(window, undefined) {
|
|
'use strict';
|
|
|
|
/** Use a single load function */
|
|
var load = typeof require == 'function' ? require : window.load;
|
|
|
|
/** The `platform` object to check */
|
|
var platform =
|
|
window.platform ||
|
|
load('../vendor/platform.js/platform.js') ||
|
|
window.platform;
|
|
|
|
/** The unit testing framework */
|
|
var QUnit =
|
|
window.QUnit || (
|
|
window.setTimeout || (window.addEventListener = window.setTimeout = / /),
|
|
window.QUnit = load('../vendor/qunit/qunit/qunit' + (platform.name == 'Narwhal' ? '-1.8.0' : '') + '.js') || window.QUnit,
|
|
load('../vendor/qunit-clib/qunit-clib.js'),
|
|
(window.addEventListener || 0).test && delete window.addEventListener,
|
|
window.QUnit
|
|
);
|
|
|
|
/** The `Benchmark` constructor to test */
|
|
var Benchmark =
|
|
window.Benchmark || (
|
|
Benchmark = load('../benchmark.js') || window.Benchmark,
|
|
Benchmark.Benchmark || Benchmark
|
|
);
|
|
|
|
/** API shortcut */
|
|
var forOwn = Benchmark.forOwn;
|
|
|
|
/** Used to get property descriptors */
|
|
var getDescriptor = Object.getOwnPropertyDescriptor;
|
|
|
|
/** Used to set property descriptors */
|
|
var setDescriptor = Object.defineProperty;
|
|
|
|
/** Shortcut used to convert array-like objects to arrays */
|
|
var slice = [].slice;
|
|
|
|
/** Used to resolve a value's internal [[Class]] */
|
|
var toString = {}.toString;
|
|
|
|
/** Used to check problem JScript properties (a.k.a. the [[DontEnum]] bug) */
|
|
var shadowed = {
|
|
'constructor': 1,
|
|
'hasOwnProperty': 2,
|
|
'isPrototypeOf': 3,
|
|
'propertyIsEnumerable': 4,
|
|
'toLocaleString': 5,
|
|
'toString': 6,
|
|
'valueOf': 7
|
|
};
|
|
|
|
/** Used to flag environments/features */
|
|
var support = {
|
|
'descriptors': !!function() {
|
|
try {
|
|
var o = {};
|
|
return (setDescriptor(o, o, o), 'value' in getDescriptor(o, o));
|
|
} catch(e) { }
|
|
}()
|
|
};
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Skips a given number of tests with a passing result.
|
|
*
|
|
* @private
|
|
* @param {Number} [count=1] The number of tests to skip.
|
|
*/
|
|
function skipTest(count) {
|
|
count || (count = 1);
|
|
while (count--) {
|
|
ok(true, 'test skipped');
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
// init Benchmark.options.minTime
|
|
Benchmark(function() { throw 0; }).run();
|
|
|
|
// set a shorter max time
|
|
Benchmark.options.maxTime = Benchmark.options.minTime * 5;
|
|
|
|
// explicitly call `QUnit.module()` instead of `module()`
|
|
// in case we are in a CLI environment
|
|
QUnit.module('Benchmark');
|
|
|
|
(function() {
|
|
test('has the default `Benchmark.platform` value', function() {
|
|
if (window.document) {
|
|
equal(String(Benchmark.platform), navigator.userAgent);
|
|
} else {
|
|
skipTest(1)
|
|
}
|
|
});
|
|
|
|
test('supports loading Benchmark.js as a module', function() {
|
|
if (window.document && window.require) {
|
|
equal((Benchmark2 || {}).version, Benchmark.version);
|
|
} else {
|
|
skipTest(1)
|
|
}
|
|
});
|
|
|
|
test('supports loading Platform.js as a module', function() {
|
|
if (window.document && window.require) {
|
|
var platform = (Benchmark2 || {}).platform || {};
|
|
equal(typeof platform.name, 'string');
|
|
} else {
|
|
skipTest(1)
|
|
}
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark constructor');
|
|
|
|
(function() {
|
|
test('creates a new instance when called without the `new` operator', function() {
|
|
ok(Benchmark() instanceof Benchmark);
|
|
});
|
|
|
|
test('supports passing an options object', function() {
|
|
var bench = Benchmark({ 'name': 'foo', 'fn': function() { } });
|
|
ok(bench.fn && bench.name == 'foo');
|
|
});
|
|
|
|
test('supports passing a "name" and "fn" argument', function() {
|
|
var bench = Benchmark('foo', function() { });
|
|
ok(bench.fn && bench.name == 'foo');
|
|
});
|
|
|
|
test('supports passing a "name" argument and an options object', function() {
|
|
var bench = Benchmark('foo', { 'fn': function() { } });
|
|
ok(bench.fn && bench.name == 'foo');
|
|
});
|
|
|
|
test('supports passing a "name" argument and an options object', function() {
|
|
var bench = Benchmark('foo', function() { }, { 'id': 'bar' });
|
|
ok(bench.fn && bench.name == 'foo' && bench.id == 'bar');
|
|
});
|
|
|
|
test('supports passing an empy string for the "fn" options property', function() {
|
|
var bench = Benchmark({ 'fn': '' }).run();
|
|
ok(!bench.error);
|
|
});
|
|
|
|
test('detects dead code', function() {
|
|
var bench = Benchmark(function() { }).run();
|
|
ok(/setup\(\)/.test(bench.compiled) ? !bench.error : bench.error);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark compilation');
|
|
|
|
(function() {
|
|
test('compiles using the default `Function#toString`', function() {
|
|
var bench = Benchmark({
|
|
'setup': function() { var a = 1; },
|
|
'fn': function() { throw a; },
|
|
'teardown': function() { a = 2; }
|
|
}).run();
|
|
|
|
var compiled = bench.compiled;
|
|
if (/setup\(\)/.test(compiled)) {
|
|
skipTest();
|
|
}
|
|
else {
|
|
ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
|
|
}
|
|
});
|
|
|
|
test('compiles using a custom "toString" method', function() {
|
|
var bench = Benchmark({
|
|
'setup': function() { },
|
|
'fn': function() { },
|
|
'teardown': function() { }
|
|
});
|
|
|
|
bench.setup.toString = function() { return 'var a = 1;' };
|
|
bench.fn.toString = function() { return 'throw a;' };
|
|
bench.teardown.toString = function() { return 'a = 2;' };
|
|
bench.run();
|
|
|
|
var compiled = bench.compiled;
|
|
if (/setup\(\)/.test(compiled)) {
|
|
skipTest();
|
|
}
|
|
else {
|
|
ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
|
|
}
|
|
});
|
|
|
|
test('compiles using a string value', function() {
|
|
var bench = Benchmark({
|
|
'setup': 'var a = 1;',
|
|
'fn': 'throw a;',
|
|
'teardown': 'a = 2;'
|
|
}).run();
|
|
|
|
var compiled = bench.compiled;
|
|
if (/setup\(\)/.test(compiled)) {
|
|
skipTest();
|
|
}
|
|
else {
|
|
ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
|
|
}
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark test binding');
|
|
|
|
(function() {
|
|
var count = 0;
|
|
|
|
var tests = {
|
|
'inlined "setup", "fn", and "teardown"': (
|
|
'if(/ops/.test(this))this._fn=true;'
|
|
),
|
|
'called "fn" and inlined "setup"/"teardown" reached by error': function() {
|
|
count++;
|
|
if (/ops/.test(this)) {
|
|
this._fn = true;
|
|
}
|
|
},
|
|
'called "fn" and inlined "setup"/"teardown" reached by `return` statement': function() {
|
|
if (/ops/.test(this)) {
|
|
this._fn = true;
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
|
|
forOwn(tests, function(fn, title) {
|
|
test('has correct binding for ' + title, function() {
|
|
var bench = Benchmark({
|
|
'setup': 'if(/ops/.test(this))this._setup=true;',
|
|
'fn': fn,
|
|
'teardown': 'if(/ops/.test(this))this._teardown=true;',
|
|
'onCycle': function() { this.abort(); }
|
|
}).run();
|
|
|
|
var compiled = bench.compiled;
|
|
if (/setup\(\)/.test(compiled)) {
|
|
skipTest(3);
|
|
}
|
|
else {
|
|
ok(bench._setup, 'correct binding for "setup"');
|
|
ok(bench._fn, 'correct binding for "fn"');
|
|
ok(bench._teardown, 'correct binding for "teardown"');
|
|
}
|
|
});
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.deepClone');
|
|
|
|
(function() {
|
|
function createCircularObject() {
|
|
var result = {
|
|
'foo': { 'b': { 'foo': { 'c': { } } } },
|
|
'bar': { }
|
|
};
|
|
|
|
result.foo.b.foo.c.foo = result;
|
|
result.bar.b = result.foo.b;
|
|
return result;
|
|
}
|
|
|
|
function Klass() {
|
|
this.a = 1;
|
|
}
|
|
|
|
Klass.prototype = { 'b': 1 };
|
|
|
|
var notCloneable = {
|
|
'an arguments object': arguments,
|
|
'an element': window.document && document.body,
|
|
'a function': Klass,
|
|
'a Klass instance': new Klass
|
|
};
|
|
|
|
var objects = {
|
|
'an array': ['a', 'b', 'c', ''],
|
|
'an array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 },
|
|
'boolean': false,
|
|
'boolean object': Object(false),
|
|
'an object': { 'a': 0, 'b': 1, 'c': 3 },
|
|
'an object with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } },
|
|
'null': null,
|
|
'a number': 3,
|
|
'a number object': Object(3),
|
|
'a regexp': /x/gim,
|
|
'a string': 'x',
|
|
'a string object': Object('x'),
|
|
'undefined': undefined
|
|
};
|
|
|
|
objects['an array'].length = 5;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('clones ' + key + ' correctly', function() {
|
|
var kind = toString.call(object),
|
|
clone = Benchmark.deepClone(object);
|
|
|
|
if (object == null) {
|
|
equal(clone, object);
|
|
} else {
|
|
deepEqual(clone.valueOf(), object.valueOf());
|
|
}
|
|
if (object === Object(object)) {
|
|
ok(clone !== object);
|
|
} else {
|
|
skipTest();
|
|
}
|
|
});
|
|
});
|
|
|
|
forOwn(notCloneable, function(object, key) {
|
|
test('does not clone ' + key, function() {
|
|
ok(Benchmark.deepClone(object) === object);
|
|
});
|
|
});
|
|
|
|
test('clones using Klass#deepClone', function() {
|
|
var object = new Klass;
|
|
Klass.prototype.deepClone = function() { return new Klass; };
|
|
|
|
var clone = Benchmark.deepClone(object);
|
|
ok(clone !== object && clone instanceof Klass);
|
|
|
|
delete Klass.prototype.clone;
|
|
});
|
|
|
|
test('clones problem JScript properties', function() {
|
|
var clone = Benchmark.deepClone(shadowed);
|
|
deepEqual(clone, shadowed);
|
|
});
|
|
|
|
test('clones string object with custom property', function() {
|
|
var object = new String('x');
|
|
object.x = 1;
|
|
|
|
var clone = Benchmark.deepClone(object);
|
|
ok(clone == 'x' && typeof clone == 'object' && clone.x === 1 && toString.call(clone) == '[object String]');
|
|
});
|
|
|
|
test('clones objects with circular references', function() {
|
|
var object = createCircularObject(),
|
|
clone = Benchmark.deepClone(object);
|
|
|
|
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
|
|
});
|
|
|
|
test('clones non-extensible objects with circular references', function() {
|
|
if (Object.preventExtensions) {
|
|
var object = Object.preventExtensions(createCircularObject());
|
|
Object.preventExtensions(object.bar.b);
|
|
|
|
var clone = Benchmark.deepClone(object);
|
|
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
|
|
} else {
|
|
skipTest(1)
|
|
}
|
|
});
|
|
|
|
test('clones sealed objects with circular references', function() {
|
|
if (Object.seal) {
|
|
var object = Object.seal(createCircularObject());
|
|
Object.seal(object.bar.b);
|
|
|
|
var clone = Benchmark.deepClone(object);
|
|
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
|
|
} else {
|
|
skipTest(1)
|
|
}
|
|
});
|
|
|
|
test('clones frozen objects with circular references', function() {
|
|
if (Object.freeze) {
|
|
var object = Object.freeze(createCircularObject());
|
|
Object.freeze(object.bar.b);
|
|
|
|
var clone = Benchmark.deepClone(object);
|
|
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
|
|
} else {
|
|
skipTest(1)
|
|
}
|
|
});
|
|
|
|
test('clones objects with custom descriptors and circular references', function() {
|
|
var accessor,
|
|
descriptor;
|
|
|
|
if (support.descriptors) {
|
|
var object = setDescriptor({}, 'foo', {
|
|
'configurable': true,
|
|
'value': setDescriptor({}, 'b', {
|
|
'writable': true,
|
|
'value': setDescriptor({}, 'foo', {
|
|
'get': function() { return accessor; },
|
|
'set': function(value) { accessor = value; }
|
|
})
|
|
})
|
|
});
|
|
|
|
setDescriptor(object, 'bar', { 'value': {} });
|
|
object.foo.b.foo = { 'c': object };
|
|
object.bar.b = object.foo.b;
|
|
|
|
var clone = Benchmark.deepClone(object);
|
|
ok(clone !== object &&
|
|
clone.bar.b === clone.foo.b &&
|
|
clone !== clone.foo.b.foo.c.foo &&
|
|
(descriptor = getDescriptor(clone, 'foo')) &&
|
|
descriptor.configurable && !(descriptor.enumerable && descriptor.writable) &&
|
|
(descriptor = getDescriptor(clone.foo, 'b')) &&
|
|
descriptor.writable && !(descriptor.configurable && descriptor.enumerable) &&
|
|
(descriptor = getDescriptor(clone.foo.b, 'foo')) &&
|
|
descriptor.get && descriptor.set &&
|
|
(descriptor = getDescriptor(clone.foo.b, 'foo')) &&
|
|
!(descriptor.configurable && descriptor.enumerable && descriptor.writable) &&
|
|
(descriptor = getDescriptor(clone, 'bar')) &&
|
|
!(descriptor.configurable && descriptor.enumerable && descriptor.writable));
|
|
}
|
|
else {
|
|
skipTest(1)
|
|
}
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.each');
|
|
|
|
(function() {
|
|
var xpathResult;
|
|
|
|
var objects = {
|
|
'array': ['a', 'b', 'c', ''],
|
|
'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 },
|
|
'xpath snapshot': null
|
|
};
|
|
|
|
if (window.document && document.evaluate) {
|
|
xpathResult = [document.documentElement, document.getElementsByTagName('head')[0], document.body];
|
|
objects['xpath snapshot'] = document.evaluate('//*[self::html or self::head or self::body]', document, null, 7, null);
|
|
}
|
|
|
|
objects.array.length = 5;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('passes the correct arguments when passing an ' + key, function() {
|
|
if (object) {
|
|
var args
|
|
Benchmark.each(object, function() {
|
|
args || (args = slice.call(arguments));
|
|
});
|
|
|
|
if (key == 'xpath snapshot') {
|
|
ok(args[0] === xpathResult[0]);
|
|
} else {
|
|
equal(args[0], 'a');
|
|
}
|
|
equal(args[1], 0);
|
|
ok(args[2] === object);
|
|
}
|
|
else {
|
|
skipTest(3);
|
|
}
|
|
});
|
|
|
|
test('returns the passed object when passing an ' + key, function() {
|
|
if (object) {
|
|
var actual = Benchmark.each(object, function() { });
|
|
ok(actual === object);
|
|
}
|
|
else {
|
|
skipTest();
|
|
}
|
|
});
|
|
|
|
test('iterates over all indexes when passing an ' + key, function() {
|
|
if (object) {
|
|
var values = [];
|
|
Benchmark.each(object, function(value) {
|
|
values.push(value);
|
|
});
|
|
|
|
deepEqual(values, key == 'xpath snapshot' ? xpathResult : ['a', 'b', 'c', '']);
|
|
}
|
|
else {
|
|
skipTest();
|
|
}
|
|
});
|
|
|
|
test('exits early when returning `false` when passing an ' + key, function() {
|
|
if (object) {
|
|
var values = [];
|
|
Benchmark.each(object, function(value) {
|
|
values.push(value);
|
|
return values.length < 2;
|
|
});
|
|
|
|
deepEqual(values, key == 'xpath snapshot' ? xpathResult.slice(0, 2) : ['a', 'b']);
|
|
}
|
|
else {
|
|
skipTest();
|
|
}
|
|
});
|
|
});
|
|
|
|
test('passes the third callback argument as an object', function() {
|
|
var thirdArg;
|
|
Benchmark.each('hello', function(value, index, object) {
|
|
thirdArg = object;
|
|
});
|
|
|
|
ok(thirdArg && typeof thirdArg == 'object');
|
|
});
|
|
|
|
test('iterates over strings by index', function() {
|
|
var values = [];
|
|
Benchmark.each('hello', function(value) {
|
|
values.push(value)
|
|
});
|
|
|
|
deepEqual(values, ['h', 'e', 'l', 'l', 'o']);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.extend');
|
|
|
|
(function() {
|
|
test('allows no source argument', function() {
|
|
var object = {};
|
|
equal(Benchmark.extend(object), object);
|
|
});
|
|
|
|
test('allows a single source argument', function() {
|
|
var source = { 'x': 1, 'y': 1 },
|
|
actual = Benchmark.extend({}, source);
|
|
|
|
deepEqual(Benchmark.extend({}, source), { 'x': 1, 'y': 1 });
|
|
});
|
|
|
|
test('allows multiple source arguments', function() {
|
|
var source1 = { 'x': 1, 'y': 1 },
|
|
source2 = { 'y': 2, 'z': 2 },
|
|
actual = Benchmark.extend({}, source1, source2);
|
|
|
|
deepEqual(actual, { 'x': 1, 'y': 2, 'z': 2 });
|
|
});
|
|
|
|
test('will add inherited source properties', function() {
|
|
function Source() { }
|
|
Source.prototype.x = 1;
|
|
deepEqual(Benchmark.extend({}, new Source), { 'x': 1 });
|
|
});
|
|
|
|
test('will add problem JScript properties', function() {
|
|
deepEqual(Benchmark.extend({}, shadowed), shadowed);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.filter');
|
|
|
|
(function() {
|
|
var objects = {
|
|
'array': ['a', 'b', 'c', ''],
|
|
'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 }
|
|
};
|
|
|
|
objects.array.length = 5;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('passes the correct arguments when passing an ' + key, function() {
|
|
var args;
|
|
Benchmark.filter(object, function() {
|
|
args || (args = slice.call(arguments));
|
|
});
|
|
|
|
deepEqual(args, ['a', 0, object]);
|
|
});
|
|
|
|
test('produces the correct result when passing an ' + key, function() {
|
|
var actual = Benchmark.filter(object, function(value, index) {
|
|
return index > 0;
|
|
});
|
|
|
|
deepEqual(actual, ['b', 'c', '']);
|
|
});
|
|
|
|
test('iterates over sparse ' + key + 's correctly', function() {
|
|
var actual = Benchmark.filter(object, function(value) {
|
|
return value === undefined;
|
|
});
|
|
|
|
deepEqual(actual, []);
|
|
});
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.forOwn');
|
|
|
|
(function() {
|
|
function fn() {
|
|
// no-op
|
|
}
|
|
|
|
function KlassA() {
|
|
this.a = 1;
|
|
this.b = 2;
|
|
this.c = 3;
|
|
}
|
|
|
|
function KlassB() {
|
|
this.a = 1;
|
|
this.constructor = 2;
|
|
this.hasOwnProperty = 3;
|
|
this.isPrototypeOf = 4;
|
|
this.propertyIsEnumerable = 5;
|
|
this.toLocaleString = 6;
|
|
this.toString = 7;
|
|
this.valueOf = 8;
|
|
}
|
|
|
|
function KlassC() {
|
|
// no-op
|
|
}
|
|
|
|
fn.a = 1;
|
|
fn.b = 2;
|
|
fn.c = 3;
|
|
|
|
KlassC.prototype.a = 1;
|
|
KlassC.prototype.b = 2;
|
|
KlassC.prototype.c = 3;
|
|
|
|
var objects = {
|
|
'an arguments object': arguments,
|
|
'a function': fn,
|
|
'an object': new KlassA,
|
|
'an object shadowing properties on Object.prototype': new KlassB,
|
|
'a prototype object': KlassC.prototype,
|
|
'a string': 'abc'
|
|
};
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('passes the correct arguments when passing ' + key, function() {
|
|
var args;
|
|
Benchmark.forOwn(object, function() {
|
|
args || (args = slice.call(arguments));
|
|
});
|
|
|
|
equal(typeof args[0], key == 'a string' ? 'string' : 'number');
|
|
equal(typeof args[1], 'string');
|
|
equal(args[2] && typeof args[2], key == 'a function' ? 'function' : 'object');
|
|
});
|
|
|
|
test('returns the passed object when passing ' + key, function() {
|
|
var actual = Benchmark.forOwn(object, function() { });
|
|
deepEqual(actual, object);
|
|
});
|
|
|
|
test('iterates over own properties when passing ' + key, function() {
|
|
var values = [];
|
|
Benchmark.forOwn(object, function(value) {
|
|
values.push(value);
|
|
});
|
|
|
|
if (object instanceof KlassB) {
|
|
deepEqual(values.sort(), [1, 2, 3, 4, 5, 6, 7, 8]);
|
|
} else if (key == 'a string') {
|
|
deepEqual(values, ['a', 'b', 'c']);
|
|
} else {
|
|
deepEqual(values.sort(), [1, 2, 3]);
|
|
}
|
|
});
|
|
|
|
test('exits early when returning `false` when passing ' + key, function() {
|
|
var values = [];
|
|
Benchmark.forOwn(object, function(value) {
|
|
values.push(value);
|
|
return false;
|
|
});
|
|
|
|
equal(values.length, 1);
|
|
});
|
|
|
|
if (object instanceof KlassB) {
|
|
test('exits correctly when transitioning to the JScript [[DontEnum]] fix', function() {
|
|
var values = [];
|
|
Benchmark.forOwn(object, function(value) {
|
|
values.push(value);
|
|
return values.length < 2;
|
|
});
|
|
|
|
equal(values.length, 2);
|
|
});
|
|
}
|
|
});
|
|
}(1, 2, 3));
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.formatNumber');
|
|
|
|
(function() {
|
|
test('formats a million correctly', function() {
|
|
equal(Benchmark.formatNumber(1e6), '1,000,000');
|
|
});
|
|
|
|
test('formats less than 100 correctly', function() {
|
|
equal(Benchmark.formatNumber(23), '23');
|
|
});
|
|
|
|
test('formats numbers with decimal values correctly', function() {
|
|
equal(Benchmark.formatNumber(1234.56), '1,234.56');
|
|
});
|
|
|
|
test('formats negative numbers correctly', function() {
|
|
equal(Benchmark.formatNumber(-1234.56), '-1,234.56');
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.hasKey');
|
|
|
|
(function() {
|
|
test('returns `true` for own properties', function() {
|
|
var object = { 'x': 1 };
|
|
equal(Benchmark.hasKey(object, 'x'), true);
|
|
});
|
|
|
|
test('returns `false` for inherited properties', function() {
|
|
equal(Benchmark.hasKey({}, 'toString'), false);
|
|
});
|
|
|
|
test('doesn\'t use an object\'s `hasOwnProperty` method', function() {
|
|
var object = { 'hasOwnProperty': function() { return true; } };
|
|
equal(Benchmark.hasKey(object, 'x'), false);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.indexOf');
|
|
|
|
(function() {
|
|
var objects = {
|
|
'array': ['a', 'b', 'c', ''],
|
|
'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 }
|
|
};
|
|
|
|
objects.array.length = 5;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('produces the correct result when passing an ' + key, function() {
|
|
equal(Benchmark.indexOf(object, 'b'), 1);
|
|
});
|
|
|
|
test('matches values by strict equality when passing an ' + key, function() {
|
|
equal(Benchmark.indexOf(object, new String('b')), -1);
|
|
});
|
|
|
|
test('iterates over sparse ' + key + 's correctly', function() {
|
|
equal(Benchmark.indexOf(object, undefined), -1);
|
|
});
|
|
});
|
|
|
|
test('searches from the given `fromIndex`', function() {
|
|
var array = ['a', 'b', 'c', 'a'];
|
|
equal(Benchmark.indexOf(array, 'a', 1), 3);
|
|
});
|
|
|
|
test('handles extreme negative `fromIndex` values correctly', function() {
|
|
var array = ['a'];
|
|
array['-1'] = 'z';
|
|
equal(Benchmark.indexOf(array, 'z', -2), -1);
|
|
});
|
|
|
|
test('handles extreme positive `fromIndex` values correctly', function() {
|
|
var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 2 };
|
|
equal(Benchmark.indexOf(object, 'c', 2), -1);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.interpolate');
|
|
|
|
(function() {
|
|
test('replaces tokens correctly', function() {
|
|
var actual = Benchmark.interpolate('#{greeting} #{location}.', {
|
|
'greeting': 'Hello',
|
|
'location': 'world'
|
|
});
|
|
|
|
equal(actual, 'Hello world.');
|
|
});
|
|
|
|
test('ignores inherited object properties', function() {
|
|
var actual = Benchmark.interpolate('x#{toString}', {});
|
|
equal(actual, 'x#{toString}');
|
|
});
|
|
|
|
test('allows for no template object', function() {
|
|
var actual = Benchmark.interpolate('x');
|
|
equal(actual, 'x');
|
|
});
|
|
|
|
test('replaces duplicate tokens', function() {
|
|
var actual = Benchmark.interpolate('#{x}#{x}#{x}', { 'x': 'a' });
|
|
equal(actual, 'aaa');
|
|
});
|
|
|
|
test('handles keys containing RegExp special characters', function() {
|
|
var actual = Benchmark.interpolate('#{.*+?^=!:${}()|[]\\/}', { '.*+?^=!:${}()|[]\\/': 'x' });
|
|
equal(actual, 'x');
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.invoke');
|
|
|
|
(function() {
|
|
var objects = {
|
|
'array': ['a', ['b'], 'c', null],
|
|
'array-like-object': { '0': 'a', '1': ['b'], '2': 'c', '3': null, 'length': 5 }
|
|
};
|
|
|
|
objects.array.length = 5;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('produces the correct result when passing an ' + key, function() {
|
|
var actual = Benchmark.invoke(object, 'concat');
|
|
deepEqual(actual, ['a', ['b'], 'c', undefined, undefined]);
|
|
equal('4' in actual, false);
|
|
});
|
|
|
|
test('passes the correct arguments to the invoked method when passing an ' + key, function() {
|
|
var actual = Benchmark.invoke(object, 'concat', 'x', 'y', 'z');
|
|
deepEqual(actual, ['axyz', ['b', 'x', 'y', 'z'], 'cxyz', undefined, undefined]);
|
|
equal('4' in actual, false);
|
|
});
|
|
|
|
test('handles options object with callbacks correctly when passing an ' + key, function() {
|
|
function callback() {
|
|
callbacks.push(slice.call(arguments));
|
|
}
|
|
|
|
var callbacks = [];
|
|
var actual = Benchmark.invoke(object, {
|
|
'name': 'concat',
|
|
'args': ['x', 'y', 'z'],
|
|
'onStart': callback,
|
|
'onCycle': callback,
|
|
'onComplete': callback
|
|
});
|
|
|
|
deepEqual(actual, ['axyz', ['b', 'x', 'y', 'z'], 'cxyz', undefined, undefined]);
|
|
equal('4' in actual, false);
|
|
|
|
equal(callbacks[0].length, 1);
|
|
equal(callbacks[0][0].target, 'a');
|
|
deepEqual(callbacks[0][0].currentTarget, object);
|
|
equal(callbacks[0][0].type, 'start');
|
|
equal(callbacks[1][0].type, 'cycle');
|
|
equal(callbacks[5][0].type, 'complete');
|
|
});
|
|
|
|
test('supports queuing when passing an ' + key, function() {
|
|
var lengths = [];
|
|
var actual = Benchmark.invoke(object, {
|
|
'name': 'concat',
|
|
'queued': true,
|
|
'args': 'x',
|
|
'onCycle': function() {
|
|
lengths.push(object.length);
|
|
}
|
|
});
|
|
|
|
deepEqual(lengths, [5, 4, 3, 2]);
|
|
deepEqual(actual, ['ax', ['b', 'x'], 'cx', undefined, undefined]);
|
|
});
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.join');
|
|
|
|
(function() {
|
|
var objects = {
|
|
'array': ['a', 'b', ''],
|
|
'array-like-object': { '0': 'a', '1': 'b', '2': '', 'length': 4 },
|
|
'object': { 'a': '0', 'b': '1', '': '2' }
|
|
};
|
|
|
|
objects.array.length = 4;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('joins correctly using the default separator when passing an ' + key, function() {
|
|
equal(Benchmark.join(object), key == 'object' ? 'a: 0,b: 1,: 2' : 'a,b,');
|
|
});
|
|
|
|
test('joins correctly using a custom separator when passing an ' + key, function() {
|
|
equal(Benchmark.join(object, '+', '@'), key == 'object' ? 'a@0+b@1+@2' : 'a+b+');
|
|
});
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.map');
|
|
|
|
(function() {
|
|
var objects = {
|
|
'array': ['a', 'b', 'c', ''],
|
|
'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 }
|
|
};
|
|
|
|
objects.array.length = 5;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('passes the correct arguments when passing an ' + key, function() {
|
|
var args;
|
|
Benchmark.map(object, function() {
|
|
args || (args = slice.call(arguments));
|
|
});
|
|
|
|
deepEqual(args, ['a', 0, object]);
|
|
});
|
|
|
|
test('produces the correct result when passing an ' + key, function() {
|
|
var actual = Benchmark.map(object, function(value, index) {
|
|
return value + index;
|
|
});
|
|
|
|
deepEqual(actual, ['a0', 'b1', 'c2', '3', undefined]);
|
|
equal('4' in actual, false);
|
|
});
|
|
|
|
test('produces an array of the correct length for sparse ' + key + 's', function() {
|
|
equal(Benchmark.map(object, function() { }).length, 5);
|
|
});
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.pluck');
|
|
|
|
(function() {
|
|
var objects = {
|
|
'array': [{ '_': 'a' }, { '_': 'b' }, { '_': 'c' }, null],
|
|
'array-like-object': { '0': { '_': 'a' }, '1': { '_': 'b' }, '2': { '_': 'c' }, '3': null, 'length': 5 }
|
|
};
|
|
|
|
objects.array.length = 5;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('produces the correct result when passing an ' + key, function() {
|
|
var actual = Benchmark.pluck(object, '_');
|
|
deepEqual(actual, ['a', 'b', 'c', undefined, undefined]);
|
|
equal('4' in actual, false);
|
|
});
|
|
|
|
test('produces the correct result for non-existent keys when passing an ' + key, function() {
|
|
var actual = Benchmark.pluck(object, 'non-existent');
|
|
deepEqual(actual, [undefined, undefined, undefined, undefined, undefined]);
|
|
equal('4' in actual, false);
|
|
});
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.reduce');
|
|
|
|
(function() {
|
|
var objects = {
|
|
'array': ['b', 'c', ''],
|
|
'array-like-object': { '0': 'b', '1': 'c', '2': '', 'length': 4 }
|
|
};
|
|
|
|
objects.array.length = 4;
|
|
|
|
forOwn(objects, function(object, key) {
|
|
test('passes the correct arguments when passing an ' + key, function() {
|
|
var args;
|
|
Benchmark.reduce(object, function() {
|
|
args || (args = slice.call(arguments));
|
|
}, 'a');
|
|
|
|
deepEqual(args, ['a', 'b', 0, object]);
|
|
});
|
|
|
|
test('accumulates correctly when passing an ' + key, function() {
|
|
var actual = Benchmark.reduce(object, function(string, value) {
|
|
return string + value;
|
|
}, 'a');
|
|
|
|
equal(actual, 'abc');
|
|
});
|
|
|
|
test('handles arguments with no initial value correctly when passing an ' + key, function() {
|
|
var args;
|
|
Benchmark.reduce(object, function() {
|
|
args || (args = slice.call(arguments));
|
|
});
|
|
|
|
deepEqual(args, ['b', 'c', 1, object]);
|
|
});
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark#clone');
|
|
|
|
(function() {
|
|
var bench = Benchmark(function() { this.count += 0; }).run();
|
|
|
|
test('produces the correct result passing no arguments', function() {
|
|
var clone = bench.clone();
|
|
deepEqual(clone, bench);
|
|
ok(clone.stats != bench.stats && clone.times != bench.times && clone.options != bench.options);
|
|
});
|
|
|
|
test('produces the correct result passing a data object', function() {
|
|
var clone = bench.clone({ 'fn': '', 'name': 'foo' });
|
|
ok(clone.fn === '' && clone.options.fn === '');
|
|
ok(clone.name == 'foo' && clone.options.name == 'foo');
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark#run');
|
|
|
|
(function() {
|
|
var data = { 'onComplete': 0, 'onCycle': 0, 'onStart': 0 };
|
|
|
|
var bench = Benchmark({
|
|
'fn': function() {
|
|
this.count += 0;
|
|
},
|
|
'onStart': function() {
|
|
data.onStart++;
|
|
},
|
|
'onComplete': function() {
|
|
data.onComplete++;
|
|
}
|
|
})
|
|
.run();
|
|
|
|
test('onXYZ callbacks should not be triggered by internal benchmark clones', function() {
|
|
equal(data.onStart, 1);
|
|
equal(data.onComplete, 1);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
forOwn({
|
|
'Benchmark': Benchmark,
|
|
'Benchmark.Suite': Benchmark.Suite
|
|
},
|
|
function(Constructor, namespace) {
|
|
|
|
QUnit.module(namespace + '#emit');
|
|
|
|
(function() {
|
|
test('emits passed arguments', function() {
|
|
var args,
|
|
object = Constructor();
|
|
|
|
object.on('args', function() { args = slice.call(arguments, 1); });
|
|
object.emit('args', 'a', 'b', 'c');
|
|
deepEqual(args, ['a', 'b', 'c']);
|
|
});
|
|
|
|
test('emits with no listeners', function() {
|
|
var event = Benchmark.Event('empty'),
|
|
object = Constructor();
|
|
|
|
object.emit(event);
|
|
equal(event.cancelled, false);
|
|
});
|
|
|
|
test('emits with an event type of "toString"', function() {
|
|
var event = Benchmark.Event('toString'),
|
|
object = Constructor();
|
|
|
|
object.emit(event);
|
|
equal(event.cancelled, false);
|
|
});
|
|
|
|
test('returns the last listeners returned value', function() {
|
|
var event = Benchmark.Event('result'),
|
|
object = Constructor();
|
|
|
|
object.on('result', function() { return 'x'; });
|
|
object.on('result', function() { return 'y'; });
|
|
equal(object.emit(event), 'y');
|
|
});
|
|
|
|
test('aborts the emitters listener iteration when `event.aborted` is `true`', function() {
|
|
var event = Benchmark.Event('aborted'),
|
|
object = Constructor();
|
|
|
|
object.on('aborted', function(event) {
|
|
event.aborted = true;
|
|
return false;
|
|
});
|
|
|
|
object.on('aborted', function(event) {
|
|
// should not get here
|
|
event.aborted = false;
|
|
return true;
|
|
});
|
|
|
|
equal(object.emit(event), false);
|
|
equal(event.aborted, true);
|
|
});
|
|
|
|
test('cancels the event if a listener explicitly returns `false`', function() {
|
|
var event = Benchmark.Event('cancel'),
|
|
object = Constructor();
|
|
|
|
object.on('cancel', function() { return false; });
|
|
object.on('cancel', function() { return true; });
|
|
object.emit(event);
|
|
equal(event.cancelled, true);
|
|
});
|
|
|
|
test('uses a shallow clone of the listeners when emitting', function() {
|
|
var event,
|
|
listener2 = function(eventObject) { eventObject.listener2 = true },
|
|
object = Constructor();
|
|
|
|
object.on('shallowclone', function(eventObject) {
|
|
event = eventObject;
|
|
object.off(event.type, listener2);
|
|
})
|
|
.on('shallowclone', listener2)
|
|
.emit('shallowclone');
|
|
|
|
ok(event.listener2);
|
|
});
|
|
|
|
test('emits a custom event object', function() {
|
|
var event = Benchmark.Event('custom'),
|
|
object = Constructor();
|
|
|
|
object.on('custom', function(eventObject) { eventObject.touched = true; });
|
|
object.emit(event);
|
|
ok(event.touched);
|
|
});
|
|
|
|
test('sets `event.result` correctly', function() {
|
|
var event = Benchmark.Event('result'),
|
|
object = Constructor();
|
|
|
|
object.on('result', function() { return 'x'; });
|
|
object.emit(event);
|
|
equal(event.result, 'x');
|
|
});
|
|
|
|
test('sets `event.type` correctly', function() {
|
|
var event,
|
|
object = Constructor();
|
|
|
|
object.on('type', function(eventObj) {
|
|
event = eventObj;
|
|
});
|
|
|
|
object.emit('type');
|
|
equal(event.type, 'type');
|
|
});
|
|
}());
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
QUnit.module(namespace + '#listeners');
|
|
|
|
(function() {
|
|
test('returns the correct listeners', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener);
|
|
deepEqual(object.listeners('x'), [listener]);
|
|
});
|
|
|
|
test('returns an array and initializes previously uninitialized listeners', function() {
|
|
var object = Constructor();
|
|
deepEqual(object.listeners('x'), []);
|
|
deepEqual(object.events, { 'x': [] });
|
|
});
|
|
}());
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
QUnit.module(namespace + '#off');
|
|
|
|
(function() {
|
|
test('returns the benchmark', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener);
|
|
equal(object.off('x', listener), object);
|
|
});
|
|
|
|
test('will ignore inherited properties of the event cache', function() {
|
|
var Dummy = function() { },
|
|
listener = function() { },
|
|
object = Constructor();
|
|
|
|
Dummy.prototype.x = [listener];
|
|
object.events = new Dummy;
|
|
|
|
object.off('x', listener);
|
|
deepEqual(object.events.x, [listener]);
|
|
});
|
|
|
|
test('handles an event type and listener', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener);
|
|
object.off('x', listener);
|
|
deepEqual(object.events.x, []);
|
|
});
|
|
|
|
test('handles unregistering duplicate listeners', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener);
|
|
object.on('x', listener);
|
|
|
|
var events = object.events;
|
|
object.off('x', listener);
|
|
deepEqual(events.x, [listener]);
|
|
|
|
object.off('x', listener);
|
|
deepEqual(events.x, []);
|
|
});
|
|
|
|
test('handles a non-registered listener', function() {
|
|
var object = Constructor();
|
|
object.off('x', function() { });
|
|
equal(object.events, undefined);
|
|
});
|
|
|
|
test('handles space separated event type and listener', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener);
|
|
object.on('y', listener);
|
|
|
|
var events = object.events;
|
|
object.off('x y', listener);
|
|
deepEqual(events.x, []);
|
|
deepEqual(events.y, []);
|
|
});
|
|
|
|
test('handles space separated event type and no listener', function() {
|
|
var listener1 = function() { },
|
|
listener2 = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener1);
|
|
object.on('y', listener2);
|
|
|
|
var events = object.events;
|
|
object.off('x y');
|
|
deepEqual(events.x, []);
|
|
deepEqual(events.y, []);
|
|
});
|
|
|
|
test('handles no arguments', function() {
|
|
var listener1 = function() { },
|
|
listener2 = function() { },
|
|
listener3 = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener1);
|
|
object.on('y', listener2);
|
|
object.on('z', listener3);
|
|
|
|
var events = object.events;
|
|
object.off();
|
|
deepEqual(events.x, []);
|
|
deepEqual(events.y, []);
|
|
deepEqual(events.z, []);
|
|
});
|
|
}());
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
QUnit.module(namespace + '#on');
|
|
|
|
(function() {
|
|
test('returns the benchmark', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
equal(object.on('x', listener), object);
|
|
});
|
|
|
|
test('will ignore inherited properties of the event cache', function() {
|
|
var Dummy = function() { },
|
|
listener1 = function() { },
|
|
listener2 = function() { },
|
|
object = Constructor();
|
|
|
|
Dummy.prototype.x = [listener1];
|
|
object.events = new Dummy;
|
|
|
|
object.on('x', listener2);
|
|
deepEqual(object.events.x, [listener2]);
|
|
});
|
|
|
|
test('handles an event type and listener', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener);
|
|
deepEqual(object.events.x, [listener]);
|
|
});
|
|
|
|
test('handles registering duplicate listeners', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x', listener);
|
|
object.on('x', listener);
|
|
deepEqual(object.events.x, [listener, listener]);
|
|
});
|
|
|
|
test('handles space separated event type and listener', function() {
|
|
var listener = function() { },
|
|
object = Constructor();
|
|
|
|
object.on('x y', listener);
|
|
|
|
var events = object.events;
|
|
deepEqual(events.x, [listener]);
|
|
deepEqual(events.y, [listener]);
|
|
});
|
|
}());
|
|
});
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite#abort');
|
|
|
|
(function() {
|
|
test('igores abort calls when the suite isn\'t running', function() {
|
|
var fired = false;
|
|
var suite = Benchmark.Suite('suite', {
|
|
'onAbort': function() { fired = true; }
|
|
});
|
|
|
|
suite.add('foo', function() { });
|
|
suite.abort();
|
|
equal(fired, false);
|
|
});
|
|
|
|
test('ignores abort calls from `Benchmark.Suite#reset` when the suite isn\'t running', function() {
|
|
var fired = false;
|
|
var suite = Benchmark.Suite('suite', {
|
|
'onAbort': function() { fired = true; }
|
|
});
|
|
|
|
suite.add('foo', function() { });
|
|
suite.reset();
|
|
equal(fired, false);
|
|
});
|
|
|
|
asyncTest('emits an abort event when running', function() {
|
|
var fired = false;
|
|
|
|
Benchmark.Suite({
|
|
'onAbort': function() { fired = true; }
|
|
})
|
|
.on('start', function() {
|
|
this.abort();
|
|
})
|
|
.on('complete', function() {
|
|
ok(fired);
|
|
QUnit.start();
|
|
})
|
|
.add(function(){ })
|
|
.run({ 'async': true });
|
|
});
|
|
|
|
asyncTest('emits an abort event after calling `Benchmark.Suite#reset`', function() {
|
|
var fired = false;
|
|
|
|
Benchmark.Suite({
|
|
'onAbort': function() { fired = true; }
|
|
})
|
|
.on('start', function() {
|
|
this.reset();
|
|
})
|
|
.on('complete', function() {
|
|
ok(fired);
|
|
QUnit.start();
|
|
})
|
|
.add(function(){ })
|
|
.run({ 'async': true });
|
|
});
|
|
|
|
asyncTest('should abort deferred benchmark', function() {
|
|
var fired = false,
|
|
suite = Benchmark.Suite();
|
|
|
|
suite.on('complete', function() {
|
|
equal(fired, false);
|
|
QUnit.start();
|
|
})
|
|
.add('a', {
|
|
'defer': true,
|
|
'fn': function(deferred) {
|
|
// avoid test inlining
|
|
suite.name;
|
|
// delay resolve
|
|
setTimeout(function() {
|
|
deferred.resolve();
|
|
suite.abort();
|
|
}, 10);
|
|
}
|
|
})
|
|
.add('b', {
|
|
'defer': true,
|
|
'fn': function(deferred) {
|
|
// avoid test inlining
|
|
suite.name;
|
|
// delay resolve
|
|
setTimeout(function() {
|
|
deferred.resolve();
|
|
fired = true;
|
|
}, 10);
|
|
}
|
|
})
|
|
.run();
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite#concat');
|
|
|
|
(function() {
|
|
var args = arguments;
|
|
|
|
test('doesn\'t treat an arguments object like an array', function() {
|
|
var suite = Benchmark.Suite();
|
|
deepEqual(suite.concat(args), [args]);
|
|
});
|
|
|
|
test('flattens array arguments', function() {
|
|
var suite = Benchmark.Suite();
|
|
deepEqual(suite.concat([1, 2], 3, [4, 5]), [1, 2, 3, 4, 5]);
|
|
});
|
|
|
|
test('supports concating sparse arrays', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[2] = 2;
|
|
suite.length = 3;
|
|
|
|
var actual = suite.concat(3);
|
|
deepEqual(actual, [0, undefined, 2, 3]);
|
|
equal('1' in actual, false);
|
|
});
|
|
|
|
test('supports sparse arrays as arguments', function() {
|
|
var suite = Benchmark.Suite(),
|
|
sparse = [];
|
|
|
|
sparse[0] = 0;
|
|
sparse[2] = 2;
|
|
sparse.length = 3;
|
|
|
|
var actual = suite.concat(sparse);
|
|
deepEqual(actual, [0, undefined, 2]);
|
|
equal('1' in actual, false);
|
|
});
|
|
|
|
test('creates a new array', function() {
|
|
var suite = Benchmark.Suite();
|
|
ok(suite.concat(1) !== suite);
|
|
});
|
|
}(1, 2, 3));
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite#reverse');
|
|
|
|
(function() {
|
|
test('reverses the element order', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 1;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.reverse();
|
|
equal(actual, suite);
|
|
deepEqual(slice.call(actual), [1, 0]);
|
|
});
|
|
|
|
test('supports reversing sparse arrays', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[2] = 2;
|
|
suite.length = 3;
|
|
|
|
var actual = suite.reverse();
|
|
equal(actual, suite);
|
|
deepEqual(slice.call(actual), [2, undefined, 0]);
|
|
equal('1' in actual, false);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite#shift');
|
|
|
|
(function() {
|
|
test('removes the first element', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 1;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.shift();
|
|
equal(actual, 0);
|
|
deepEqual(slice.call(suite), [1]);
|
|
});
|
|
|
|
test('shifts an object with no elements', function() {
|
|
var suite = Benchmark.Suite(),
|
|
actual = suite.shift();
|
|
|
|
equal(actual, undefined);
|
|
deepEqual(slice.call(suite), []);
|
|
});
|
|
|
|
test('should have no elements when length is 0 after shift', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite.length = 1;
|
|
suite.shift();
|
|
|
|
// ensure element is removed
|
|
equal('0' in suite, false);
|
|
equal(suite.length, 0);
|
|
});
|
|
|
|
test('supports shifting sparse arrays', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[1] = 1;
|
|
suite[3] = 3;
|
|
suite.length = 4;
|
|
|
|
var actual = suite.shift();
|
|
equal(actual, undefined);
|
|
deepEqual(slice.call(suite), [1, undefined, 3]);
|
|
equal('1' in suite, false);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite#slice');
|
|
|
|
(function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 1;
|
|
suite[2] = 2;
|
|
suite[3] = 3;
|
|
suite.length = 4;
|
|
|
|
test('works with no arguments', function() {
|
|
var actual = suite.slice();
|
|
deepEqual(actual, [0, 1, 2, 3]);
|
|
ok(suite !== actual);
|
|
});
|
|
|
|
test('works with positive `start` argument', function() {
|
|
var actual = suite.slice(2);
|
|
deepEqual(actual, [2, 3]);
|
|
ok(suite !== actual);
|
|
});
|
|
|
|
test('works with positive `start` and `end` arguments', function() {
|
|
var actual = suite.slice(1, 3);
|
|
deepEqual(actual, [1, 2]);
|
|
ok(suite !== actual);
|
|
});
|
|
|
|
test('works with `end` values exceeding length', function() {
|
|
var actual = suite.slice(1, 10);
|
|
deepEqual(actual, [1, 2, 3]);
|
|
ok(suite !== actual);
|
|
});
|
|
|
|
test('works with negative `start` and `end` arguments', function() {
|
|
var actual = suite.slice(-3, -1);
|
|
deepEqual(actual, [1, 2]);
|
|
ok(suite !== actual);
|
|
});
|
|
|
|
test('works with an extreme negative `end` value', function() {
|
|
var actual = suite.slice(1, -10);
|
|
deepEqual(actual, []);
|
|
equal('-1' in actual, false);
|
|
ok(suite !== actual);
|
|
});
|
|
|
|
test('supports slicing sparse arrays', function() {
|
|
var sparse = Benchmark.Suite();
|
|
sparse[1] = 1;
|
|
sparse[3] = 3;
|
|
sparse.length = 4;
|
|
|
|
var actual = sparse.slice(0, 2);
|
|
deepEqual(actual, [undefined, 1]);
|
|
equal('0' in actual, false);
|
|
|
|
actual = sparse.slice(1);
|
|
deepEqual(actual, [1, undefined, 3]);
|
|
equal('1' in actual, false);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite#splice');
|
|
|
|
(function() {
|
|
test('works with no arguments', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite.length = 1;
|
|
|
|
var actual = suite.splice();
|
|
deepEqual(actual, []);
|
|
deepEqual(slice.call(suite), [0]);
|
|
});
|
|
|
|
test('works with only the `start` argument', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 1;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.splice(1);
|
|
deepEqual(actual, [1]);
|
|
deepEqual(slice.call(suite), [0]);
|
|
});
|
|
|
|
test('should have no elements when length is 0 after splice', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite.length = 1
|
|
suite.splice(0, 1);
|
|
|
|
// ensure element is removed
|
|
equal('0' in suite, false);
|
|
equal(suite.length, 0);
|
|
});
|
|
|
|
test('works with positive `start` argument', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 3;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.splice(1, 0, 1, 2);
|
|
deepEqual(actual, []);
|
|
deepEqual(slice.call(suite), [0, 1, 2, 3]);
|
|
});
|
|
|
|
test('works with positive `start` and `deleteCount` arguments', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 3;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.splice(1, 1, 1, 2);
|
|
deepEqual(actual, [3]);
|
|
deepEqual(slice.call(suite), [0, 1, 2]);
|
|
});
|
|
|
|
test('works with `deleteCount` values exceeding length', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 3;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.splice(1, 10, 1, 2);
|
|
deepEqual(actual, [3]);
|
|
deepEqual(slice.call(suite), [0, 1, 2]);
|
|
});
|
|
|
|
test('works with negative `start` and `deleteCount` arguments', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 3;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.splice(-1, -1, 1, 2);
|
|
deepEqual(actual, []);
|
|
deepEqual(slice.call(suite), [0, 1, 2, 3]);
|
|
});
|
|
|
|
test('works with an extreme negative `deleteCount` value', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 0;
|
|
suite[1] = 3;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.splice(0, -10, 1, 2);
|
|
deepEqual(actual, []);
|
|
deepEqual(slice.call(suite), [1, 2, 0, 3]);
|
|
});
|
|
|
|
test('supports splicing sparse arrays', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[1] = 1;
|
|
suite[3] = 3;
|
|
suite.length = 4;
|
|
|
|
var actual = suite.splice(1, 2, 1, 2);
|
|
deepEqual(actual, [1, undefined]);
|
|
equal(actual.length, 2);
|
|
deepEqual(slice.call(suite), [undefined, 1, 2, 3]);
|
|
equal('0' in suite, false);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite#unshift');
|
|
|
|
(function() {
|
|
test('adds a first element', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 1;
|
|
suite.length = 1;
|
|
|
|
var actual = suite.unshift(0);
|
|
equal(actual, 2);
|
|
deepEqual(slice.call(suite), [0, 1]);
|
|
});
|
|
|
|
test('adds multiple elements to the front', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[0] = 3;
|
|
suite.length = 1;
|
|
|
|
var actual = suite.unshift(0, 1, 2);
|
|
equal(actual, 4);
|
|
deepEqual(slice.call(suite), [0, 1, 2, 3]);
|
|
});
|
|
|
|
test('supports unshifting sparse arrays', function() {
|
|
var suite = Benchmark.Suite();
|
|
suite[1] = 2;
|
|
suite.length = 2;
|
|
|
|
var actual = suite.unshift(0);
|
|
equal(actual, 3);
|
|
deepEqual(slice.call(suite), [0, undefined, 2]);
|
|
equal('1' in suite, false);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite filtered results onComplete');
|
|
|
|
(function() {
|
|
var count = 0,
|
|
suite = Benchmark.Suite();
|
|
|
|
suite.add('a', function() {
|
|
count++;
|
|
})
|
|
.add('b', function() {
|
|
for (var i = 0; i < 1e6; i++) {
|
|
count++;
|
|
}
|
|
})
|
|
.add('c', function() {
|
|
throw new TypeError;
|
|
});
|
|
|
|
asyncTest('should filter by fastest', function() {
|
|
suite.on('complete', function() {
|
|
suite.off();
|
|
deepEqual(this.filter('fastest').pluck('name'), ['a']);
|
|
QUnit.start();
|
|
})
|
|
.run({ 'async': true });
|
|
});
|
|
|
|
asyncTest('should filter by slowest', function() {
|
|
suite.on('complete', function() {
|
|
suite.off();
|
|
deepEqual(this.filter('slowest').pluck('name'), ['b']);
|
|
QUnit.start();
|
|
})
|
|
.run({ 'async': true });
|
|
});
|
|
|
|
asyncTest('should filter by successful', function() {
|
|
suite.on('complete', function() {
|
|
suite.off();
|
|
deepEqual(this.filter('successful').pluck('name'), ['a', 'b']);
|
|
QUnit.start();
|
|
})
|
|
.run({ 'async': true });
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.Suite event flow');
|
|
|
|
(function() {
|
|
var events = [],
|
|
callback = function(event) { events.push(event); };
|
|
|
|
var suite = Benchmark.Suite('suite', {
|
|
'onAdd': callback,
|
|
'onAbort': callback,
|
|
'onClone': callback,
|
|
'onError': callback,
|
|
'onStart': callback,
|
|
'onCycle': callback,
|
|
'onComplete': callback,
|
|
'onReset': callback
|
|
})
|
|
.add('bench', function() {
|
|
throw null;
|
|
}, {
|
|
'onAbort': callback,
|
|
'onClone': callback,
|
|
'onError': callback,
|
|
'onStart': callback,
|
|
'onCycle': callback,
|
|
'onComplete': callback,
|
|
'onReset': callback
|
|
})
|
|
.run({ 'async': false });
|
|
|
|
// first Suite#onAdd
|
|
test('should emit the suite "add" event first', function() {
|
|
var event = events[0];
|
|
ok(event.type == 'add' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
|
|
});
|
|
|
|
// next we start the Suite because no reset was needed
|
|
test('should emit the suite "start" event', function() {
|
|
var event = events[1];
|
|
ok(event.type == 'start' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
|
|
});
|
|
|
|
// and so start the first benchmark
|
|
test('should emit the benchmark "start" event', function() {
|
|
var event = events[2];
|
|
ok(event.type == 'start' && event.currentTarget.name == 'bench');
|
|
});
|
|
|
|
// oh no! we abort because of an error
|
|
test('should emit the benchmark "error" event', function() {
|
|
var event = events[3];
|
|
ok(event.type == 'error' && event.currentTarget.name == 'bench');
|
|
});
|
|
|
|
// benchmark error triggered
|
|
test('should emit the benchmark "abort" event', function() {
|
|
var event = events[4];
|
|
ok(event.type == 'abort' && event.currentTarget.name == 'bench');
|
|
});
|
|
|
|
// we reset the benchmark as part of the abort
|
|
test('should emit the benchmark "reset" event', function() {
|
|
var event = events[5];
|
|
ok(event.type == 'reset' && event.currentTarget.name == 'bench');
|
|
});
|
|
|
|
// benchmark is cycle is finished
|
|
test('should emit the benchmark "cycle" event', function() {
|
|
var event = events[6];
|
|
ok(event.type == 'cycle' && event.currentTarget.name == 'bench');
|
|
});
|
|
|
|
// benchmark is complete
|
|
test('should emit the benchmark "complete" event', function() {
|
|
var event = events[7];
|
|
ok(event.type == 'complete' && event.currentTarget.name == 'bench');
|
|
});
|
|
|
|
// the benchmark error triggers a Suite error
|
|
test('should emit the suite "error" event', function() {
|
|
var event = events[8];
|
|
ok(event.type == 'error' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
|
|
});
|
|
|
|
// the Suite cycle finishes
|
|
test('should emit the suite "cycle" event', function() {
|
|
var event = events[9];
|
|
ok(event.type == 'cycle' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
|
|
});
|
|
|
|
// the Suite completes
|
|
test('finally it should emit the suite "complete" event', function() {
|
|
var event = events[10];
|
|
ok(event.type == 'complete' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
|
|
});
|
|
|
|
test('emitted all expected events', function() {
|
|
ok(events.length == 11);
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Deferred benchmarks');
|
|
|
|
(function() {
|
|
asyncTest('should run a deferred benchmark correctly', function() {
|
|
Benchmark(function(deferred) {
|
|
setTimeout(function() { deferred.resolve(); }, 1e3);
|
|
}, {
|
|
'defer': true,
|
|
'onComplete': function() {
|
|
equal(this.hz.toFixed(0), 1);
|
|
QUnit.start();
|
|
}
|
|
})
|
|
.run();
|
|
});
|
|
|
|
asyncTest('should run with string values for "fn", "setup", and "teardown"', function() {
|
|
Benchmark({
|
|
'defer': true,
|
|
'setup': 'var x = [3, 2, 1];',
|
|
'fn': 'setTimeout(function() { x.sort(); deferred.resolve(); }, 10);',
|
|
'teardown': 'x.length = 0;',
|
|
'onComplete': function() {
|
|
ok(true);
|
|
QUnit.start();
|
|
}
|
|
})
|
|
.run();
|
|
});
|
|
|
|
asyncTest('should run recursively', function() {
|
|
Benchmark({
|
|
'defer': true,
|
|
'setup': 'var x = [3, 2, 1];',
|
|
'fn': 'for (var i = 0; i < 100; i++) x[ i % 2 ? "sort" : "reverse" ](); deferred.resolve();',
|
|
'teardown': 'x.length = 0;',
|
|
'onComplete': function() {
|
|
ok(true);
|
|
QUnit.start();
|
|
}
|
|
})
|
|
.run();
|
|
});
|
|
|
|
asyncTest('should execute "setup", "fn", and "teardown" in correct order', function() {
|
|
var fired = [];
|
|
|
|
Benchmark({
|
|
'defer': true,
|
|
'setup': function() {
|
|
fired.push('setup');
|
|
},
|
|
'fn': function(deferred) {
|
|
fired.push('fn');
|
|
setTimeout(function() { deferred.resolve(); }, 10);
|
|
},
|
|
'teardown': function() {
|
|
fired.push('teardown');
|
|
},
|
|
'onComplete': function() {
|
|
var actual = fired.join().replace(/(fn,)+/g, '$1').replace(/(setup,fn,teardown(?:,|$))+/, '$1');
|
|
equal(actual, 'setup,fn,teardown');
|
|
QUnit.start();
|
|
}
|
|
})
|
|
.run();
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
QUnit.module('Benchmark.deepClone');
|
|
|
|
(function() {
|
|
asyncTest('avoids call stack limits', function() {
|
|
var result,
|
|
count = 0,
|
|
object = {},
|
|
recurse = function() { count++; recurse(); };
|
|
|
|
setTimeout(function() {
|
|
ok(result, 'avoids call stack limits (stack limit is ' + (count - 1) + ')');
|
|
QUnit.start();
|
|
}, 15);
|
|
|
|
if (toString.call(window.java) == '[object JavaPackage]') {
|
|
// Java throws uncatchable errors on call stack overflows, so to avoid
|
|
// them I chose a number higher than Rhino's call stack limit without
|
|
// dynamically testing for the actual limit
|
|
count = 3e3;
|
|
} else {
|
|
try { recurse(); } catch(e) { }
|
|
}
|
|
|
|
// exceed limit
|
|
count++;
|
|
for (var i = 0, sub = object; i <= count; i++) {
|
|
sub = sub[i] = {};
|
|
}
|
|
|
|
try {
|
|
for (var i = 0, sub = Benchmark.deepClone(object); sub = sub[i]; i++) { }
|
|
result = --i == count;
|
|
} catch(e) { }
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
// explicitly call `QUnit.start()` for Narwhal, Rhino, and RingoJS
|
|
if (!window.document) {
|
|
QUnit.start();
|
|
}
|
|
}(typeof global == 'object' && global || this));
|