;(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));