You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2074 lines
59 KiB

  1. ;(function(window, undefined) {
  2. 'use strict';
  3. /** Use a single load function */
  4. var load = typeof require == 'function' ? require : window.load;
  5. /** The `platform` object to check */
  6. var platform =
  7. window.platform ||
  8. load('../vendor/platform.js/platform.js') ||
  9. window.platform;
  10. /** The unit testing framework */
  11. var QUnit =
  12. window.QUnit || (
  13. window.setTimeout || (window.addEventListener = window.setTimeout = / /),
  14. window.QUnit = load('../vendor/qunit/qunit/qunit' + (platform.name == 'Narwhal' ? '-1.8.0' : '') + '.js') || window.QUnit,
  15. load('../vendor/qunit-clib/qunit-clib.js'),
  16. (window.addEventListener || 0).test && delete window.addEventListener,
  17. window.QUnit
  18. );
  19. /** The `Benchmark` constructor to test */
  20. var Benchmark =
  21. window.Benchmark || (
  22. Benchmark = load('../benchmark.js') || window.Benchmark,
  23. Benchmark.Benchmark || Benchmark
  24. );
  25. /** API shortcut */
  26. var forOwn = Benchmark.forOwn;
  27. /** Used to get property descriptors */
  28. var getDescriptor = Object.getOwnPropertyDescriptor;
  29. /** Used to set property descriptors */
  30. var setDescriptor = Object.defineProperty;
  31. /** Shortcut used to convert array-like objects to arrays */
  32. var slice = [].slice;
  33. /** Used to resolve a value's internal [[Class]] */
  34. var toString = {}.toString;
  35. /** Used to check problem JScript properties (a.k.a. the [[DontEnum]] bug) */
  36. var shadowed = {
  37. 'constructor': 1,
  38. 'hasOwnProperty': 2,
  39. 'isPrototypeOf': 3,
  40. 'propertyIsEnumerable': 4,
  41. 'toLocaleString': 5,
  42. 'toString': 6,
  43. 'valueOf': 7
  44. };
  45. /** Used to flag environments/features */
  46. var support = {
  47. 'descriptors': !!function() {
  48. try {
  49. var o = {};
  50. return (setDescriptor(o, o, o), 'value' in getDescriptor(o, o));
  51. } catch(e) { }
  52. }()
  53. };
  54. /*--------------------------------------------------------------------------*/
  55. /**
  56. * Skips a given number of tests with a passing result.
  57. *
  58. * @private
  59. * @param {Number} [count=1] The number of tests to skip.
  60. */
  61. function skipTest(count) {
  62. count || (count = 1);
  63. while (count--) {
  64. ok(true, 'test skipped');
  65. }
  66. }
  67. /*--------------------------------------------------------------------------*/
  68. // init Benchmark.options.minTime
  69. Benchmark(function() { throw 0; }).run();
  70. // set a shorter max time
  71. Benchmark.options.maxTime = Benchmark.options.minTime * 5;
  72. // explicitly call `QUnit.module()` instead of `module()`
  73. // in case we are in a CLI environment
  74. QUnit.module('Benchmark');
  75. (function() {
  76. test('has the default `Benchmark.platform` value', function() {
  77. if (window.document) {
  78. equal(String(Benchmark.platform), navigator.userAgent);
  79. } else {
  80. skipTest(1)
  81. }
  82. });
  83. test('supports loading Benchmark.js as a module', function() {
  84. if (window.document && window.require) {
  85. equal((Benchmark2 || {}).version, Benchmark.version);
  86. } else {
  87. skipTest(1)
  88. }
  89. });
  90. test('supports loading Platform.js as a module', function() {
  91. if (window.document && window.require) {
  92. var platform = (Benchmark2 || {}).platform || {};
  93. equal(typeof platform.name, 'string');
  94. } else {
  95. skipTest(1)
  96. }
  97. });
  98. }());
  99. /*--------------------------------------------------------------------------*/
  100. QUnit.module('Benchmark constructor');
  101. (function() {
  102. test('creates a new instance when called without the `new` operator', function() {
  103. ok(Benchmark() instanceof Benchmark);
  104. });
  105. test('supports passing an options object', function() {
  106. var bench = Benchmark({ 'name': 'foo', 'fn': function() { } });
  107. ok(bench.fn && bench.name == 'foo');
  108. });
  109. test('supports passing a "name" and "fn" argument', function() {
  110. var bench = Benchmark('foo', function() { });
  111. ok(bench.fn && bench.name == 'foo');
  112. });
  113. test('supports passing a "name" argument and an options object', function() {
  114. var bench = Benchmark('foo', { 'fn': function() { } });
  115. ok(bench.fn && bench.name == 'foo');
  116. });
  117. test('supports passing a "name" argument and an options object', function() {
  118. var bench = Benchmark('foo', function() { }, { 'id': 'bar' });
  119. ok(bench.fn && bench.name == 'foo' && bench.id == 'bar');
  120. });
  121. test('supports passing an empy string for the "fn" options property', function() {
  122. var bench = Benchmark({ 'fn': '' }).run();
  123. ok(!bench.error);
  124. });
  125. test('detects dead code', function() {
  126. var bench = Benchmark(function() { }).run();
  127. ok(/setup\(\)/.test(bench.compiled) ? !bench.error : bench.error);
  128. });
  129. }());
  130. /*--------------------------------------------------------------------------*/
  131. QUnit.module('Benchmark compilation');
  132. (function() {
  133. test('compiles using the default `Function#toString`', function() {
  134. var bench = Benchmark({
  135. 'setup': function() { var a = 1; },
  136. 'fn': function() { throw a; },
  137. 'teardown': function() { a = 2; }
  138. }).run();
  139. var compiled = bench.compiled;
  140. if (/setup\(\)/.test(compiled)) {
  141. skipTest();
  142. }
  143. else {
  144. ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
  145. }
  146. });
  147. test('compiles using a custom "toString" method', function() {
  148. var bench = Benchmark({
  149. 'setup': function() { },
  150. 'fn': function() { },
  151. 'teardown': function() { }
  152. });
  153. bench.setup.toString = function() { return 'var a = 1;' };
  154. bench.fn.toString = function() { return 'throw a;' };
  155. bench.teardown.toString = function() { return 'a = 2;' };
  156. bench.run();
  157. var compiled = bench.compiled;
  158. if (/setup\(\)/.test(compiled)) {
  159. skipTest();
  160. }
  161. else {
  162. ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
  163. }
  164. });
  165. test('compiles using a string value', function() {
  166. var bench = Benchmark({
  167. 'setup': 'var a = 1;',
  168. 'fn': 'throw a;',
  169. 'teardown': 'a = 2;'
  170. }).run();
  171. var compiled = bench.compiled;
  172. if (/setup\(\)/.test(compiled)) {
  173. skipTest();
  174. }
  175. else {
  176. ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
  177. }
  178. });
  179. }());
  180. /*--------------------------------------------------------------------------*/
  181. QUnit.module('Benchmark test binding');
  182. (function() {
  183. var count = 0;
  184. var tests = {
  185. 'inlined "setup", "fn", and "teardown"': (
  186. 'if(/ops/.test(this))this._fn=true;'
  187. ),
  188. 'called "fn" and inlined "setup"/"teardown" reached by error': function() {
  189. count++;
  190. if (/ops/.test(this)) {
  191. this._fn = true;
  192. }
  193. },
  194. 'called "fn" and inlined "setup"/"teardown" reached by `return` statement': function() {
  195. if (/ops/.test(this)) {
  196. this._fn = true;
  197. }
  198. return;
  199. }
  200. };
  201. forOwn(tests, function(fn, title) {
  202. test('has correct binding for ' + title, function() {
  203. var bench = Benchmark({
  204. 'setup': 'if(/ops/.test(this))this._setup=true;',
  205. 'fn': fn,
  206. 'teardown': 'if(/ops/.test(this))this._teardown=true;',
  207. 'onCycle': function() { this.abort(); }
  208. }).run();
  209. var compiled = bench.compiled;
  210. if (/setup\(\)/.test(compiled)) {
  211. skipTest(3);
  212. }
  213. else {
  214. ok(bench._setup, 'correct binding for "setup"');
  215. ok(bench._fn, 'correct binding for "fn"');
  216. ok(bench._teardown, 'correct binding for "teardown"');
  217. }
  218. });
  219. });
  220. }());
  221. /*--------------------------------------------------------------------------*/
  222. QUnit.module('Benchmark.deepClone');
  223. (function() {
  224. function createCircularObject() {
  225. var result = {
  226. 'foo': { 'b': { 'foo': { 'c': { } } } },
  227. 'bar': { }
  228. };
  229. result.foo.b.foo.c.foo = result;
  230. result.bar.b = result.foo.b;
  231. return result;
  232. }
  233. function Klass() {
  234. this.a = 1;
  235. }
  236. Klass.prototype = { 'b': 1 };
  237. var notCloneable = {
  238. 'an arguments object': arguments,
  239. 'an element': window.document && document.body,
  240. 'a function': Klass,
  241. 'a Klass instance': new Klass
  242. };
  243. var objects = {
  244. 'an array': ['a', 'b', 'c', ''],
  245. 'an array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 },
  246. 'boolean': false,
  247. 'boolean object': Object(false),
  248. 'an object': { 'a': 0, 'b': 1, 'c': 3 },
  249. 'an object with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } },
  250. 'null': null,
  251. 'a number': 3,
  252. 'a number object': Object(3),
  253. 'a regexp': /x/gim,
  254. 'a string': 'x',
  255. 'a string object': Object('x'),
  256. 'undefined': undefined
  257. };
  258. objects['an array'].length = 5;
  259. forOwn(objects, function(object, key) {
  260. test('clones ' + key + ' correctly', function() {
  261. var kind = toString.call(object),
  262. clone = Benchmark.deepClone(object);
  263. if (object == null) {
  264. equal(clone, object);
  265. } else {
  266. deepEqual(clone.valueOf(), object.valueOf());
  267. }
  268. if (object === Object(object)) {
  269. ok(clone !== object);
  270. } else {
  271. skipTest();
  272. }
  273. });
  274. });
  275. forOwn(notCloneable, function(object, key) {
  276. test('does not clone ' + key, function() {
  277. ok(Benchmark.deepClone(object) === object);
  278. });
  279. });
  280. test('clones using Klass#deepClone', function() {
  281. var object = new Klass;
  282. Klass.prototype.deepClone = function() { return new Klass; };
  283. var clone = Benchmark.deepClone(object);
  284. ok(clone !== object && clone instanceof Klass);
  285. delete Klass.prototype.clone;
  286. });
  287. test('clones problem JScript properties', function() {
  288. var clone = Benchmark.deepClone(shadowed);
  289. deepEqual(clone, shadowed);
  290. });
  291. test('clones string object with custom property', function() {
  292. var object = new String('x');
  293. object.x = 1;
  294. var clone = Benchmark.deepClone(object);
  295. ok(clone == 'x' && typeof clone == 'object' && clone.x === 1 && toString.call(clone) == '[object String]');
  296. });
  297. test('clones objects with circular references', function() {
  298. var object = createCircularObject(),
  299. clone = Benchmark.deepClone(object);
  300. ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
  301. });
  302. test('clones non-extensible objects with circular references', function() {
  303. if (Object.preventExtensions) {
  304. var object = Object.preventExtensions(createCircularObject());
  305. Object.preventExtensions(object.bar.b);
  306. var clone = Benchmark.deepClone(object);
  307. ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
  308. } else {
  309. skipTest(1)
  310. }
  311. });
  312. test('clones sealed objects with circular references', function() {
  313. if (Object.seal) {
  314. var object = Object.seal(createCircularObject());
  315. Object.seal(object.bar.b);
  316. var clone = Benchmark.deepClone(object);
  317. ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
  318. } else {
  319. skipTest(1)
  320. }
  321. });
  322. test('clones frozen objects with circular references', function() {
  323. if (Object.freeze) {
  324. var object = Object.freeze(createCircularObject());
  325. Object.freeze(object.bar.b);
  326. var clone = Benchmark.deepClone(object);
  327. ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
  328. } else {
  329. skipTest(1)
  330. }
  331. });
  332. test('clones objects with custom descriptors and circular references', function() {
  333. var accessor,
  334. descriptor;
  335. if (support.descriptors) {
  336. var object = setDescriptor({}, 'foo', {
  337. 'configurable': true,
  338. 'value': setDescriptor({}, 'b', {
  339. 'writable': true,
  340. 'value': setDescriptor({}, 'foo', {
  341. 'get': function() { return accessor; },
  342. 'set': function(value) { accessor = value; }
  343. })
  344. })
  345. });
  346. setDescriptor(object, 'bar', { 'value': {} });
  347. object.foo.b.foo = { 'c': object };
  348. object.bar.b = object.foo.b;
  349. var clone = Benchmark.deepClone(object);
  350. ok(clone !== object &&
  351. clone.bar.b === clone.foo.b &&
  352. clone !== clone.foo.b.foo.c.foo &&
  353. (descriptor = getDescriptor(clone, 'foo')) &&
  354. descriptor.configurable && !(descriptor.enumerable && descriptor.writable) &&
  355. (descriptor = getDescriptor(clone.foo, 'b')) &&
  356. descriptor.writable && !(descriptor.configurable && descriptor.enumerable) &&
  357. (descriptor = getDescriptor(clone.foo.b, 'foo')) &&
  358. descriptor.get && descriptor.set &&
  359. (descriptor = getDescriptor(clone.foo.b, 'foo')) &&
  360. !(descriptor.configurable && descriptor.enumerable && descriptor.writable) &&
  361. (descriptor = getDescriptor(clone, 'bar')) &&
  362. !(descriptor.configurable && descriptor.enumerable && descriptor.writable));
  363. }
  364. else {
  365. skipTest(1)
  366. }
  367. });
  368. }());
  369. /*--------------------------------------------------------------------------*/
  370. QUnit.module('Benchmark.each');
  371. (function() {
  372. var xpathResult;
  373. var objects = {
  374. 'array': ['a', 'b', 'c', ''],
  375. 'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 },
  376. 'xpath snapshot': null
  377. };
  378. if (window.document && document.evaluate) {
  379. xpathResult = [document.documentElement, document.getElementsByTagName('head')[0], document.body];
  380. objects['xpath snapshot'] = document.evaluate('//*[self::html or self::head or self::body]', document, null, 7, null);
  381. }
  382. objects.array.length = 5;
  383. forOwn(objects, function(object, key) {
  384. test('passes the correct arguments when passing an ' + key, function() {
  385. if (object) {
  386. var args
  387. Benchmark.each(object, function() {
  388. args || (args = slice.call(arguments));
  389. });
  390. if (key == 'xpath snapshot') {
  391. ok(args[0] === xpathResult[0]);
  392. } else {
  393. equal(args[0], 'a');
  394. }
  395. equal(args[1], 0);
  396. ok(args[2] === object);
  397. }
  398. else {
  399. skipTest(3);
  400. }
  401. });
  402. test('returns the passed object when passing an ' + key, function() {
  403. if (object) {
  404. var actual = Benchmark.each(object, function() { });
  405. ok(actual === object);
  406. }
  407. else {
  408. skipTest();
  409. }
  410. });
  411. test('iterates over all indexes when passing an ' + key, function() {
  412. if (object) {
  413. var values = [];
  414. Benchmark.each(object, function(value) {
  415. values.push(value);
  416. });
  417. deepEqual(values, key == 'xpath snapshot' ? xpathResult : ['a', 'b', 'c', '']);
  418. }
  419. else {
  420. skipTest();
  421. }
  422. });
  423. test('exits early when returning `false` when passing an ' + key, function() {
  424. if (object) {
  425. var values = [];
  426. Benchmark.each(object, function(value) {
  427. values.push(value);
  428. return values.length < 2;
  429. });
  430. deepEqual(values, key == 'xpath snapshot' ? xpathResult.slice(0, 2) : ['a', 'b']);
  431. }
  432. else {
  433. skipTest();
  434. }
  435. });
  436. });
  437. test('passes the third callback argument as an object', function() {
  438. var thirdArg;
  439. Benchmark.each('hello', function(value, index, object) {
  440. thirdArg = object;
  441. });
  442. ok(thirdArg && typeof thirdArg == 'object');
  443. });
  444. test('iterates over strings by index', function() {
  445. var values = [];
  446. Benchmark.each('hello', function(value) {
  447. values.push(value)
  448. });
  449. deepEqual(values, ['h', 'e', 'l', 'l', 'o']);
  450. });
  451. }());
  452. /*--------------------------------------------------------------------------*/
  453. QUnit.module('Benchmark.extend');
  454. (function() {
  455. test('allows no source argument', function() {
  456. var object = {};
  457. equal(Benchmark.extend(object), object);
  458. });
  459. test('allows a single source argument', function() {
  460. var source = { 'x': 1, 'y': 1 },
  461. actual = Benchmark.extend({}, source);
  462. deepEqual(Benchmark.extend({}, source), { 'x': 1, 'y': 1 });
  463. });
  464. test('allows multiple source arguments', function() {
  465. var source1 = { 'x': 1, 'y': 1 },
  466. source2 = { 'y': 2, 'z': 2 },
  467. actual = Benchmark.extend({}, source1, source2);
  468. deepEqual(actual, { 'x': 1, 'y': 2, 'z': 2 });
  469. });
  470. test('will add inherited source properties', function() {
  471. function Source() { }
  472. Source.prototype.x = 1;
  473. deepEqual(Benchmark.extend({}, new Source), { 'x': 1 });
  474. });
  475. test('will add problem JScript properties', function() {
  476. deepEqual(Benchmark.extend({}, shadowed), shadowed);
  477. });
  478. }());
  479. /*--------------------------------------------------------------------------*/
  480. QUnit.module('Benchmark.filter');
  481. (function() {
  482. var objects = {
  483. 'array': ['a', 'b', 'c', ''],
  484. 'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 }
  485. };
  486. objects.array.length = 5;
  487. forOwn(objects, function(object, key) {
  488. test('passes the correct arguments when passing an ' + key, function() {
  489. var args;
  490. Benchmark.filter(object, function() {
  491. args || (args = slice.call(arguments));
  492. });
  493. deepEqual(args, ['a', 0, object]);
  494. });
  495. test('produces the correct result when passing an ' + key, function() {
  496. var actual = Benchmark.filter(object, function(value, index) {
  497. return index > 0;
  498. });
  499. deepEqual(actual, ['b', 'c', '']);
  500. });
  501. test('iterates over sparse ' + key + 's correctly', function() {
  502. var actual = Benchmark.filter(object, function(value) {
  503. return value === undefined;
  504. });
  505. deepEqual(actual, []);
  506. });
  507. });
  508. }());
  509. /*--------------------------------------------------------------------------*/
  510. QUnit.module('Benchmark.forOwn');
  511. (function() {
  512. function fn() {
  513. // no-op
  514. }
  515. function KlassA() {
  516. this.a = 1;
  517. this.b = 2;
  518. this.c = 3;
  519. }
  520. function KlassB() {
  521. this.a = 1;
  522. this.constructor = 2;
  523. this.hasOwnProperty = 3;
  524. this.isPrototypeOf = 4;
  525. this.propertyIsEnumerable = 5;
  526. this.toLocaleString = 6;
  527. this.toString = 7;
  528. this.valueOf = 8;
  529. }
  530. function KlassC() {
  531. // no-op
  532. }
  533. fn.a = 1;
  534. fn.b = 2;
  535. fn.c = 3;
  536. KlassC.prototype.a = 1;
  537. KlassC.prototype.b = 2;
  538. KlassC.prototype.c = 3;
  539. var objects = {
  540. 'an arguments object': arguments,
  541. 'a function': fn,
  542. 'an object': new KlassA,
  543. 'an object shadowing properties on Object.prototype': new KlassB,
  544. 'a prototype object': KlassC.prototype,
  545. 'a string': 'abc'
  546. };
  547. forOwn(objects, function(object, key) {
  548. test('passes the correct arguments when passing ' + key, function() {
  549. var args;
  550. Benchmark.forOwn(object, function() {
  551. args || (args = slice.call(arguments));
  552. });
  553. equal(typeof args[0], key == 'a string' ? 'string' : 'number');
  554. equal(typeof args[1], 'string');
  555. equal(args[2] && typeof args[2], key == 'a function' ? 'function' : 'object');
  556. });
  557. test('returns the passed object when passing ' + key, function() {
  558. var actual = Benchmark.forOwn(object, function() { });
  559. deepEqual(actual, object);
  560. });
  561. test('iterates over own properties when passing ' + key, function() {
  562. var values = [];
  563. Benchmark.forOwn(object, function(value) {
  564. values.push(value);
  565. });
  566. if (object instanceof KlassB) {
  567. deepEqual(values.sort(), [1, 2, 3, 4, 5, 6, 7, 8]);
  568. } else if (key == 'a string') {
  569. deepEqual(values, ['a', 'b', 'c']);
  570. } else {
  571. deepEqual(values.sort(), [1, 2, 3]);
  572. }
  573. });
  574. test('exits early when returning `false` when passing ' + key, function() {
  575. var values = [];
  576. Benchmark.forOwn(object, function(value) {
  577. values.push(value);
  578. return false;
  579. });
  580. equal(values.length, 1);
  581. });
  582. if (object instanceof KlassB) {
  583. test('exits correctly when transitioning to the JScript [[DontEnum]] fix', function() {
  584. var values = [];
  585. Benchmark.forOwn(object, function(value) {
  586. values.push(value);
  587. return values.length < 2;
  588. });
  589. equal(values.length, 2);
  590. });
  591. }
  592. });
  593. }(1, 2, 3));
  594. /*--------------------------------------------------------------------------*/
  595. QUnit.module('Benchmark.formatNumber');
  596. (function() {
  597. test('formats a million correctly', function() {
  598. equal(Benchmark.formatNumber(1e6), '1,000,000');
  599. });
  600. test('formats less than 100 correctly', function() {
  601. equal(Benchmark.formatNumber(23), '23');
  602. });
  603. test('formats numbers with decimal values correctly', function() {
  604. equal(Benchmark.formatNumber(1234.56), '1,234.56');
  605. });
  606. test('formats negative numbers correctly', function() {
  607. equal(Benchmark.formatNumber(-1234.56), '-1,234.56');
  608. });
  609. }());
  610. /*--------------------------------------------------------------------------*/
  611. QUnit.module('Benchmark.hasKey');
  612. (function() {
  613. test('returns `true` for own properties', function() {
  614. var object = { 'x': 1 };
  615. equal(Benchmark.hasKey(object, 'x'), true);
  616. });
  617. test('returns `false` for inherited properties', function() {
  618. equal(Benchmark.hasKey({}, 'toString'), false);
  619. });
  620. test('doesn\'t use an object\'s `hasOwnProperty` method', function() {
  621. var object = { 'hasOwnProperty': function() { return true; } };
  622. equal(Benchmark.hasKey(object, 'x'), false);
  623. });
  624. }());
  625. /*--------------------------------------------------------------------------*/
  626. QUnit.module('Benchmark.indexOf');
  627. (function() {
  628. var objects = {
  629. 'array': ['a', 'b', 'c', ''],
  630. 'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 }
  631. };
  632. objects.array.length = 5;
  633. forOwn(objects, function(object, key) {
  634. test('produces the correct result when passing an ' + key, function() {
  635. equal(Benchmark.indexOf(object, 'b'), 1);
  636. });
  637. test('matches values by strict equality when passing an ' + key, function() {
  638. equal(Benchmark.indexOf(object, new String('b')), -1);
  639. });
  640. test('iterates over sparse ' + key + 's correctly', function() {
  641. equal(Benchmark.indexOf(object, undefined), -1);
  642. });
  643. });
  644. test('searches from the given `fromIndex`', function() {
  645. var array = ['a', 'b', 'c', 'a'];
  646. equal(Benchmark.indexOf(array, 'a', 1), 3);
  647. });
  648. test('handles extreme negative `fromIndex` values correctly', function() {
  649. var array = ['a'];
  650. array['-1'] = 'z';
  651. equal(Benchmark.indexOf(array, 'z', -2), -1);
  652. });
  653. test('handles extreme positive `fromIndex` values correctly', function() {
  654. var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 2 };
  655. equal(Benchmark.indexOf(object, 'c', 2), -1);
  656. });
  657. }());
  658. /*--------------------------------------------------------------------------*/
  659. QUnit.module('Benchmark.interpolate');
  660. (function() {
  661. test('replaces tokens correctly', function() {
  662. var actual = Benchmark.interpolate('#{greeting} #{location}.', {
  663. 'greeting': 'Hello',
  664. 'location': 'world'
  665. });
  666. equal(actual, 'Hello world.');
  667. });
  668. test('ignores inherited object properties', function() {
  669. var actual = Benchmark.interpolate('x#{toString}', {});
  670. equal(actual, 'x#{toString}');
  671. });
  672. test('allows for no template object', function() {
  673. var actual = Benchmark.interpolate('x');
  674. equal(actual, 'x');
  675. });
  676. test('replaces duplicate tokens', function() {
  677. var actual = Benchmark.interpolate('#{x}#{x}#{x}', { 'x': 'a' });
  678. equal(actual, 'aaa');
  679. });
  680. test('handles keys containing RegExp special characters', function() {
  681. var actual = Benchmark.interpolate('#{.*+?^=!:${}()|[]\\/}', { '.*+?^=!:${}()|[]\\/': 'x' });
  682. equal(actual, 'x');
  683. });
  684. }());
  685. /*--------------------------------------------------------------------------*/
  686. QUnit.module('Benchmark.invoke');
  687. (function() {
  688. var objects = {
  689. 'array': ['a', ['b'], 'c', null],
  690. 'array-like-object': { '0': 'a', '1': ['b'], '2': 'c', '3': null, 'length': 5 }
  691. };
  692. objects.array.length = 5;
  693. forOwn(objects, function(object, key) {
  694. test('produces the correct result when passing an ' + key, function() {
  695. var actual = Benchmark.invoke(object, 'concat');
  696. deepEqual(actual, ['a', ['b'], 'c', undefined, undefined]);
  697. equal('4' in actual, false);
  698. });
  699. test('passes the correct arguments to the invoked method when passing an ' + key, function() {
  700. var actual = Benchmark.invoke(object, 'concat', 'x', 'y', 'z');
  701. deepEqual(actual, ['axyz', ['b', 'x', 'y', 'z'], 'cxyz', undefined, undefined]);
  702. equal('4' in actual, false);
  703. });
  704. test('handles options object with callbacks correctly when passing an ' + key, function() {
  705. function callback() {
  706. callbacks.push(slice.call(arguments));
  707. }
  708. var callbacks = [];
  709. var actual = Benchmark.invoke(object, {
  710. 'name': 'concat',
  711. 'args': ['x', 'y', 'z'],
  712. 'onStart': callback,
  713. 'onCycle': callback,
  714. 'onComplete': callback
  715. });
  716. deepEqual(actual, ['axyz', ['b', 'x', 'y', 'z'], 'cxyz', undefined, undefined]);
  717. equal('4' in actual, false);
  718. equal(callbacks[0].length, 1);
  719. equal(callbacks[0][0].target, 'a');
  720. deepEqual(callbacks[0][0].currentTarget, object);
  721. equal(callbacks[0][0].type, 'start');
  722. equal(callbacks[1][0].type, 'cycle');
  723. equal(callbacks[5][0].type, 'complete');
  724. });
  725. test('supports queuing when passing an ' + key, function() {
  726. var lengths = [];
  727. var actual = Benchmark.invoke(object, {
  728. 'name': 'concat',
  729. 'queued': true,
  730. 'args': 'x',
  731. 'onCycle': function() {
  732. lengths.push(object.length);
  733. }
  734. });
  735. deepEqual(lengths, [5, 4, 3, 2]);
  736. deepEqual(actual, ['ax', ['b', 'x'], 'cx', undefined, undefined]);
  737. });
  738. });
  739. }());
  740. /*--------------------------------------------------------------------------*/
  741. QUnit.module('Benchmark.join');
  742. (function() {
  743. var objects = {
  744. 'array': ['a', 'b', ''],
  745. 'array-like-object': { '0': 'a', '1': 'b', '2': '', 'length': 4 },
  746. 'object': { 'a': '0', 'b': '1', '': '2' }
  747. };
  748. objects.array.length = 4;
  749. forOwn(objects, function(object, key) {
  750. test('joins correctly using the default separator when passing an ' + key, function() {
  751. equal(Benchmark.join(object), key == 'object' ? 'a: 0,b: 1,: 2' : 'a,b,');
  752. });
  753. test('joins correctly using a custom separator when passing an ' + key, function() {
  754. equal(Benchmark.join(object, '+', '@'), key == 'object' ? 'a@0+b@1+@2' : 'a+b+');
  755. });
  756. });
  757. }());
  758. /*--------------------------------------------------------------------------*/
  759. QUnit.module('Benchmark.map');
  760. (function() {
  761. var objects = {
  762. 'array': ['a', 'b', 'c', ''],
  763. 'array-like-object': { '0': 'a', '1': 'b', '2': 'c', '3': '', 'length': 5 }
  764. };
  765. objects.array.length = 5;
  766. forOwn(objects, function(object, key) {
  767. test('passes the correct arguments when passing an ' + key, function() {
  768. var args;
  769. Benchmark.map(object, function() {
  770. args || (args = slice.call(arguments));
  771. });
  772. deepEqual(args, ['a', 0, object]);
  773. });
  774. test('produces the correct result when passing an ' + key, function() {
  775. var actual = Benchmark.map(object, function(value, index) {
  776. return value + index;
  777. });
  778. deepEqual(actual, ['a0', 'b1', 'c2', '3', undefined]);
  779. equal('4' in actual, false);
  780. });
  781. test('produces an array of the correct length for sparse ' + key + 's', function() {
  782. equal(Benchmark.map(object, function() { }).length, 5);
  783. });
  784. });
  785. }());
  786. /*--------------------------------------------------------------------------*/
  787. QUnit.module('Benchmark.pluck');
  788. (function() {
  789. var objects = {
  790. 'array': [{ '_': 'a' }, { '_': 'b' }, { '_': 'c' }, null],
  791. 'array-like-object': { '0': { '_': 'a' }, '1': { '_': 'b' }, '2': { '_': 'c' }, '3': null, 'length': 5 }
  792. };
  793. objects.array.length = 5;
  794. forOwn(objects, function(object, key) {
  795. test('produces the correct result when passing an ' + key, function() {
  796. var actual = Benchmark.pluck(object, '_');
  797. deepEqual(actual, ['a', 'b', 'c', undefined, undefined]);
  798. equal('4' in actual, false);
  799. });
  800. test('produces the correct result for non-existent keys when passing an ' + key, function() {
  801. var actual = Benchmark.pluck(object, 'non-existent');
  802. deepEqual(actual, [undefined, undefined, undefined, undefined, undefined]);
  803. equal('4' in actual, false);
  804. });
  805. });
  806. }());
  807. /*--------------------------------------------------------------------------*/
  808. QUnit.module('Benchmark.reduce');
  809. (function() {
  810. var objects = {
  811. 'array': ['b', 'c', ''],
  812. 'array-like-object': { '0': 'b', '1': 'c', '2': '', 'length': 4 }
  813. };
  814. objects.array.length = 4;
  815. forOwn(objects, function(object, key) {
  816. test('passes the correct arguments when passing an ' + key, function() {
  817. var args;
  818. Benchmark.reduce(object, function() {
  819. args || (args = slice.call(arguments));
  820. }, 'a');
  821. deepEqual(args, ['a', 'b', 0, object]);
  822. });
  823. test('accumulates correctly when passing an ' + key, function() {
  824. var actual = Benchmark.reduce(object, function(string, value) {
  825. return string + value;
  826. }, 'a');
  827. equal(actual, 'abc');
  828. });
  829. test('handles arguments with no initial value correctly when passing an ' + key, function() {
  830. var args;
  831. Benchmark.reduce(object, function() {
  832. args || (args = slice.call(arguments));
  833. });
  834. deepEqual(args, ['b', 'c', 1, object]);
  835. });
  836. });
  837. }());
  838. /*--------------------------------------------------------------------------*/
  839. QUnit.module('Benchmark#clone');
  840. (function() {
  841. var bench = Benchmark(function() { this.count += 0; }).run();
  842. test('produces the correct result passing no arguments', function() {
  843. var clone = bench.clone();
  844. deepEqual(clone, bench);
  845. ok(clone.stats != bench.stats && clone.times != bench.times && clone.options != bench.options);
  846. });
  847. test('produces the correct result passing a data object', function() {
  848. var clone = bench.clone({ 'fn': '', 'name': 'foo' });
  849. ok(clone.fn === '' && clone.options.fn === '');
  850. ok(clone.name == 'foo' && clone.options.name == 'foo');
  851. });
  852. }());
  853. /*--------------------------------------------------------------------------*/
  854. QUnit.module('Benchmark#run');
  855. (function() {
  856. var data = { 'onComplete': 0, 'onCycle': 0, 'onStart': 0 };
  857. var bench = Benchmark({
  858. 'fn': function() {
  859. this.count += 0;
  860. },
  861. 'onStart': function() {
  862. data.onStart++;
  863. },
  864. 'onComplete': function() {
  865. data.onComplete++;
  866. }
  867. })
  868. .run();
  869. test('onXYZ callbacks should not be triggered by internal benchmark clones', function() {
  870. equal(data.onStart, 1);
  871. equal(data.onComplete, 1);
  872. });
  873. }());
  874. /*--------------------------------------------------------------------------*/
  875. forOwn({
  876. 'Benchmark': Benchmark,
  877. 'Benchmark.Suite': Benchmark.Suite
  878. },
  879. function(Constructor, namespace) {
  880. QUnit.module(namespace + '#emit');
  881. (function() {
  882. test('emits passed arguments', function() {
  883. var args,
  884. object = Constructor();
  885. object.on('args', function() { args = slice.call(arguments, 1); });
  886. object.emit('args', 'a', 'b', 'c');
  887. deepEqual(args, ['a', 'b', 'c']);
  888. });
  889. test('emits with no listeners', function() {
  890. var event = Benchmark.Event('empty'),
  891. object = Constructor();
  892. object.emit(event);
  893. equal(event.cancelled, false);
  894. });
  895. test('emits with an event type of "toString"', function() {
  896. var event = Benchmark.Event('toString'),
  897. object = Constructor();
  898. object.emit(event);
  899. equal(event.cancelled, false);
  900. });
  901. test('returns the last listeners returned value', function() {
  902. var event = Benchmark.Event('result'),
  903. object = Constructor();
  904. object.on('result', function() { return 'x'; });
  905. object.on('result', function() { return 'y'; });
  906. equal(object.emit(event), 'y');
  907. });
  908. test('aborts the emitters listener iteration when `event.aborted` is `true`', function() {
  909. var event = Benchmark.Event('aborted'),
  910. object = Constructor();
  911. object.on('aborted', function(event) {
  912. event.aborted = true;
  913. return false;
  914. });
  915. object.on('aborted', function(event) {
  916. // should not get here
  917. event.aborted = false;
  918. return true;
  919. });
  920. equal(object.emit(event), false);
  921. equal(event.aborted, true);
  922. });
  923. test('cancels the event if a listener explicitly returns `false`', function() {
  924. var event = Benchmark.Event('cancel'),
  925. object = Constructor();
  926. object.on('cancel', function() { return false; });
  927. object.on('cancel', function() { return true; });
  928. object.emit(event);
  929. equal(event.cancelled, true);
  930. });
  931. test('uses a shallow clone of the listeners when emitting', function() {
  932. var event,
  933. listener2 = function(eventObject) { eventObject.listener2 = true },
  934. object = Constructor();
  935. object.on('shallowclone', function(eventObject) {
  936. event = eventObject;
  937. object.off(event.type, listener2);
  938. })
  939. .on('shallowclone', listener2)
  940. .emit('shallowclone');
  941. ok(event.listener2);
  942. });
  943. test('emits a custom event object', function() {
  944. var event = Benchmark.Event('custom'),
  945. object = Constructor();
  946. object.on('custom', function(eventObject) { eventObject.touched = true; });
  947. object.emit(event);
  948. ok(event.touched);
  949. });
  950. test('sets `event.result` correctly', function() {
  951. var event = Benchmark.Event('result'),
  952. object = Constructor();
  953. object.on('result', function() { return 'x'; });
  954. object.emit(event);
  955. equal(event.result, 'x');
  956. });
  957. test('sets `event.type` correctly', function() {
  958. var event,
  959. object = Constructor();
  960. object.on('type', function(eventObj) {
  961. event = eventObj;
  962. });
  963. object.emit('type');
  964. equal(event.type, 'type');
  965. });
  966. }());
  967. /*------------------------------------------------------------------------*/
  968. QUnit.module(namespace + '#listeners');
  969. (function() {
  970. test('returns the correct listeners', function() {
  971. var listener = function() { },
  972. object = Constructor();
  973. object.on('x', listener);
  974. deepEqual(object.listeners('x'), [listener]);
  975. });
  976. test('returns an array and initializes previously uninitialized listeners', function() {
  977. var object = Constructor();
  978. deepEqual(object.listeners('x'), []);
  979. deepEqual(object.events, { 'x': [] });
  980. });
  981. }());
  982. /*------------------------------------------------------------------------*/
  983. QUnit.module(namespace + '#off');
  984. (function() {
  985. test('returns the benchmark', function() {
  986. var listener = function() { },
  987. object = Constructor();
  988. object.on('x', listener);
  989. equal(object.off('x', listener), object);
  990. });
  991. test('will ignore inherited properties of the event cache', function() {
  992. var Dummy = function() { },
  993. listener = function() { },
  994. object = Constructor();
  995. Dummy.prototype.x = [listener];
  996. object.events = new Dummy;
  997. object.off('x', listener);
  998. deepEqual(object.events.x, [listener]);
  999. });
  1000. test('handles an event type and listener', function() {
  1001. var listener = function() { },
  1002. object = Constructor();
  1003. object.on('x', listener);
  1004. object.off('x', listener);
  1005. deepEqual(object.events.x, []);
  1006. });
  1007. test('handles unregistering duplicate listeners', function() {
  1008. var listener = function() { },
  1009. object = Constructor();
  1010. object.on('x', listener);
  1011. object.on('x', listener);
  1012. var events = object.events;
  1013. object.off('x', listener);
  1014. deepEqual(events.x, [listener]);
  1015. object.off('x', listener);
  1016. deepEqual(events.x, []);
  1017. });
  1018. test('handles a non-registered listener', function() {
  1019. var object = Constructor();
  1020. object.off('x', function() { });
  1021. equal(object.events, undefined);
  1022. });
  1023. test('handles space separated event type and listener', function() {
  1024. var listener = function() { },
  1025. object = Constructor();
  1026. object.on('x', listener);
  1027. object.on('y', listener);
  1028. var events = object.events;
  1029. object.off('x y', listener);
  1030. deepEqual(events.x, []);
  1031. deepEqual(events.y, []);
  1032. });
  1033. test('handles space separated event type and no listener', function() {
  1034. var listener1 = function() { },
  1035. listener2 = function() { },
  1036. object = Constructor();
  1037. object.on('x', listener1);
  1038. object.on('y', listener2);
  1039. var events = object.events;
  1040. object.off('x y');
  1041. deepEqual(events.x, []);
  1042. deepEqual(events.y, []);
  1043. });
  1044. test('handles no arguments', function() {
  1045. var listener1 = function() { },
  1046. listener2 = function() { },
  1047. listener3 = function() { },
  1048. object = Constructor();
  1049. object.on('x', listener1);
  1050. object.on('y', listener2);
  1051. object.on('z', listener3);
  1052. var events = object.events;
  1053. object.off();
  1054. deepEqual(events.x, []);
  1055. deepEqual(events.y, []);
  1056. deepEqual(events.z, []);
  1057. });
  1058. }());
  1059. /*------------------------------------------------------------------------*/
  1060. QUnit.module(namespace + '#on');
  1061. (function() {
  1062. test('returns the benchmark', function() {
  1063. var listener = function() { },
  1064. object = Constructor();
  1065. equal(object.on('x', listener), object);
  1066. });
  1067. test('will ignore inherited properties of the event cache', function() {
  1068. var Dummy = function() { },
  1069. listener1 = function() { },
  1070. listener2 = function() { },
  1071. object = Constructor();
  1072. Dummy.prototype.x = [listener1];
  1073. object.events = new Dummy;
  1074. object.on('x', listener2);
  1075. deepEqual(object.events.x, [listener2]);
  1076. });
  1077. test('handles an event type and listener', function() {
  1078. var listener = function() { },
  1079. object = Constructor();
  1080. object.on('x', listener);
  1081. deepEqual(object.events.x, [listener]);
  1082. });
  1083. test('handles registering duplicate listeners', function() {
  1084. var listener = function() { },
  1085. object = Constructor();
  1086. object.on('x', listener);
  1087. object.on('x', listener);
  1088. deepEqual(object.events.x, [listener, listener]);
  1089. });
  1090. test('handles space separated event type and listener', function() {
  1091. var listener = function() { },
  1092. object = Constructor();
  1093. object.on('x y', listener);
  1094. var events = object.events;
  1095. deepEqual(events.x, [listener]);
  1096. deepEqual(events.y, [listener]);
  1097. });
  1098. }());
  1099. });
  1100. /*--------------------------------------------------------------------------*/
  1101. QUnit.module('Benchmark.Suite#abort');
  1102. (function() {
  1103. test('igores abort calls when the suite isn\'t running', function() {
  1104. var fired = false;
  1105. var suite = Benchmark.Suite('suite', {
  1106. 'onAbort': function() { fired = true; }
  1107. });
  1108. suite.add('foo', function() { });
  1109. suite.abort();
  1110. equal(fired, false);
  1111. });
  1112. test('ignores abort calls from `Benchmark.Suite#reset` when the suite isn\'t running', function() {
  1113. var fired = false;
  1114. var suite = Benchmark.Suite('suite', {
  1115. 'onAbort': function() { fired = true; }
  1116. });
  1117. suite.add('foo', function() { });
  1118. suite.reset();
  1119. equal(fired, false);
  1120. });
  1121. asyncTest('emits an abort event when running', function() {
  1122. var fired = false;
  1123. Benchmark.Suite({
  1124. 'onAbort': function() { fired = true; }
  1125. })
  1126. .on('start', function() {
  1127. this.abort();
  1128. })
  1129. .on('complete', function() {
  1130. ok(fired);
  1131. QUnit.start();
  1132. })
  1133. .add(function(){ })
  1134. .run({ 'async': true });
  1135. });
  1136. asyncTest('emits an abort event after calling `Benchmark.Suite#reset`', function() {
  1137. var fired = false;
  1138. Benchmark.Suite({
  1139. 'onAbort': function() { fired = true; }
  1140. })
  1141. .on('start', function() {
  1142. this.reset();
  1143. })
  1144. .on('complete', function() {
  1145. ok(fired);
  1146. QUnit.start();
  1147. })
  1148. .add(function(){ })
  1149. .run({ 'async': true });
  1150. });
  1151. asyncTest('should abort deferred benchmark', function() {
  1152. var fired = false,
  1153. suite = Benchmark.Suite();
  1154. suite.on('complete', function() {
  1155. equal(fired, false);
  1156. QUnit.start();
  1157. })
  1158. .add('a', {
  1159. 'defer': true,
  1160. 'fn': function(deferred) {
  1161. // avoid test inlining
  1162. suite.name;
  1163. // delay resolve
  1164. setTimeout(function() {
  1165. deferred.resolve();
  1166. suite.abort();
  1167. }, 10);
  1168. }
  1169. })
  1170. .add('b', {
  1171. 'defer': true,
  1172. 'fn': function(deferred) {
  1173. // avoid test inlining
  1174. suite.name;
  1175. // delay resolve
  1176. setTimeout(function() {
  1177. deferred.resolve();
  1178. fired = true;
  1179. }, 10);
  1180. }
  1181. })
  1182. .run();
  1183. });
  1184. }());
  1185. /*--------------------------------------------------------------------------*/
  1186. QUnit.module('Benchmark.Suite#concat');
  1187. (function() {
  1188. var args = arguments;
  1189. test('doesn\'t treat an arguments object like an array', function() {
  1190. var suite = Benchmark.Suite();
  1191. deepEqual(suite.concat(args), [args]);
  1192. });
  1193. test('flattens array arguments', function() {
  1194. var suite = Benchmark.Suite();
  1195. deepEqual(suite.concat([1, 2], 3, [4, 5]), [1, 2, 3, 4, 5]);
  1196. });
  1197. test('supports concating sparse arrays', function() {
  1198. var suite = Benchmark.Suite();
  1199. suite[0] = 0;
  1200. suite[2] = 2;
  1201. suite.length = 3;
  1202. var actual = suite.concat(3);
  1203. deepEqual(actual, [0, undefined, 2, 3]);
  1204. equal('1' in actual, false);
  1205. });
  1206. test('supports sparse arrays as arguments', function() {
  1207. var suite = Benchmark.Suite(),
  1208. sparse = [];
  1209. sparse[0] = 0;
  1210. sparse[2] = 2;
  1211. sparse.length = 3;
  1212. var actual = suite.concat(sparse);
  1213. deepEqual(actual, [0, undefined, 2]);
  1214. equal('1' in actual, false);
  1215. });
  1216. test('creates a new array', function() {
  1217. var suite = Benchmark.Suite();
  1218. ok(suite.concat(1) !== suite);
  1219. });
  1220. }(1, 2, 3));
  1221. /*--------------------------------------------------------------------------*/
  1222. QUnit.module('Benchmark.Suite#reverse');
  1223. (function() {
  1224. test('reverses the element order', function() {
  1225. var suite = Benchmark.Suite();
  1226. suite[0] = 0;
  1227. suite[1] = 1;
  1228. suite.length = 2;
  1229. var actual = suite.reverse();
  1230. equal(actual, suite);
  1231. deepEqual(slice.call(actual), [1, 0]);
  1232. });
  1233. test('supports reversing sparse arrays', function() {
  1234. var suite = Benchmark.Suite();
  1235. suite[0] = 0;
  1236. suite[2] = 2;
  1237. suite.length = 3;
  1238. var actual = suite.reverse();
  1239. equal(actual, suite);
  1240. deepEqual(slice.call(actual), [2, undefined, 0]);
  1241. equal('1' in actual, false);
  1242. });
  1243. }());
  1244. /*--------------------------------------------------------------------------*/
  1245. QUnit.module('Benchmark.Suite#shift');
  1246. (function() {
  1247. test('removes the first element', function() {
  1248. var suite = Benchmark.Suite();
  1249. suite[0] = 0;
  1250. suite[1] = 1;
  1251. suite.length = 2;
  1252. var actual = suite.shift();
  1253. equal(actual, 0);
  1254. deepEqual(slice.call(suite), [1]);
  1255. });
  1256. test('shifts an object with no elements', function() {
  1257. var suite = Benchmark.Suite(),
  1258. actual = suite.shift();
  1259. equal(actual, undefined);
  1260. deepEqual(slice.call(suite), []);
  1261. });
  1262. test('should have no elements when length is 0 after shift', function() {
  1263. var suite = Benchmark.Suite();
  1264. suite[0] = 0;
  1265. suite.length = 1;
  1266. suite.shift();
  1267. // ensure element is removed
  1268. equal('0' in suite, false);
  1269. equal(suite.length, 0);
  1270. });
  1271. test('supports shifting sparse arrays', function() {
  1272. var suite = Benchmark.Suite();
  1273. suite[1] = 1;
  1274. suite[3] = 3;
  1275. suite.length = 4;
  1276. var actual = suite.shift();
  1277. equal(actual, undefined);
  1278. deepEqual(slice.call(suite), [1, undefined, 3]);
  1279. equal('1' in suite, false);
  1280. });
  1281. }());
  1282. /*--------------------------------------------------------------------------*/
  1283. QUnit.module('Benchmark.Suite#slice');
  1284. (function() {
  1285. var suite = Benchmark.Suite();
  1286. suite[0] = 0;
  1287. suite[1] = 1;
  1288. suite[2] = 2;
  1289. suite[3] = 3;
  1290. suite.length = 4;
  1291. test('works with no arguments', function() {
  1292. var actual = suite.slice();
  1293. deepEqual(actual, [0, 1, 2, 3]);
  1294. ok(suite !== actual);
  1295. });
  1296. test('works with positive `start` argument', function() {
  1297. var actual = suite.slice(2);
  1298. deepEqual(actual, [2, 3]);
  1299. ok(suite !== actual);
  1300. });
  1301. test('works with positive `start` and `end` arguments', function() {
  1302. var actual = suite.slice(1, 3);
  1303. deepEqual(actual, [1, 2]);
  1304. ok(suite !== actual);
  1305. });
  1306. test('works with `end` values exceeding length', function() {
  1307. var actual = suite.slice(1, 10);
  1308. deepEqual(actual, [1, 2, 3]);
  1309. ok(suite !== actual);
  1310. });
  1311. test('works with negative `start` and `end` arguments', function() {
  1312. var actual = suite.slice(-3, -1);
  1313. deepEqual(actual, [1, 2]);
  1314. ok(suite !== actual);
  1315. });
  1316. test('works with an extreme negative `end` value', function() {
  1317. var actual = suite.slice(1, -10);
  1318. deepEqual(actual, []);
  1319. equal('-1' in actual, false);
  1320. ok(suite !== actual);
  1321. });
  1322. test('supports slicing sparse arrays', function() {
  1323. var sparse = Benchmark.Suite();
  1324. sparse[1] = 1;
  1325. sparse[3] = 3;
  1326. sparse.length = 4;
  1327. var actual = sparse.slice(0, 2);
  1328. deepEqual(actual, [undefined, 1]);
  1329. equal('0' in actual, false);
  1330. actual = sparse.slice(1);
  1331. deepEqual(actual, [1, undefined, 3]);
  1332. equal('1' in actual, false);
  1333. });
  1334. }());
  1335. /*--------------------------------------------------------------------------*/
  1336. QUnit.module('Benchmark.Suite#splice');
  1337. (function() {
  1338. test('works with no arguments', function() {
  1339. var suite = Benchmark.Suite();
  1340. suite[0] = 0;
  1341. suite.length = 1;
  1342. var actual = suite.splice();
  1343. deepEqual(actual, []);
  1344. deepEqual(slice.call(suite), [0]);
  1345. });
  1346. test('works with only the `start` argument', function() {
  1347. var suite = Benchmark.Suite();
  1348. suite[0] = 0;
  1349. suite[1] = 1;
  1350. suite.length = 2;
  1351. var actual = suite.splice(1);
  1352. deepEqual(actual, [1]);
  1353. deepEqual(slice.call(suite), [0]);
  1354. });
  1355. test('should have no elements when length is 0 after splice', function() {
  1356. var suite = Benchmark.Suite();
  1357. suite[0] = 0;
  1358. suite.length = 1
  1359. suite.splice(0, 1);
  1360. // ensure element is removed
  1361. equal('0' in suite, false);
  1362. equal(suite.length, 0);
  1363. });
  1364. test('works with positive `start` argument', function() {
  1365. var suite = Benchmark.Suite();
  1366. suite[0] = 0;
  1367. suite[1] = 3;
  1368. suite.length = 2;
  1369. var actual = suite.splice(1, 0, 1, 2);
  1370. deepEqual(actual, []);
  1371. deepEqual(slice.call(suite), [0, 1, 2, 3]);
  1372. });
  1373. test('works with positive `start` and `deleteCount` arguments', function() {
  1374. var suite = Benchmark.Suite();
  1375. suite[0] = 0;
  1376. suite[1] = 3;
  1377. suite.length = 2;
  1378. var actual = suite.splice(1, 1, 1, 2);
  1379. deepEqual(actual, [3]);
  1380. deepEqual(slice.call(suite), [0, 1, 2]);
  1381. });
  1382. test('works with `deleteCount` values exceeding length', function() {
  1383. var suite = Benchmark.Suite();
  1384. suite[0] = 0;
  1385. suite[1] = 3;
  1386. suite.length = 2;
  1387. var actual = suite.splice(1, 10, 1, 2);
  1388. deepEqual(actual, [3]);
  1389. deepEqual(slice.call(suite), [0, 1, 2]);
  1390. });
  1391. test('works with negative `start` and `deleteCount` arguments', function() {
  1392. var suite = Benchmark.Suite();
  1393. suite[0] = 0;
  1394. suite[1] = 3;
  1395. suite.length = 2;
  1396. var actual = suite.splice(-1, -1, 1, 2);
  1397. deepEqual(actual, []);
  1398. deepEqual(slice.call(suite), [0, 1, 2, 3]);
  1399. });
  1400. test('works with an extreme negative `deleteCount` value', function() {
  1401. var suite = Benchmark.Suite();
  1402. suite[0] = 0;
  1403. suite[1] = 3;
  1404. suite.length = 2;
  1405. var actual = suite.splice(0, -10, 1, 2);
  1406. deepEqual(actual, []);
  1407. deepEqual(slice.call(suite), [1, 2, 0, 3]);
  1408. });
  1409. test('supports splicing sparse arrays', function() {
  1410. var suite = Benchmark.Suite();
  1411. suite[1] = 1;
  1412. suite[3] = 3;
  1413. suite.length = 4;
  1414. var actual = suite.splice(1, 2, 1, 2);
  1415. deepEqual(actual, [1, undefined]);
  1416. equal(actual.length, 2);
  1417. deepEqual(slice.call(suite), [undefined, 1, 2, 3]);
  1418. equal('0' in suite, false);
  1419. });
  1420. }());
  1421. /*--------------------------------------------------------------------------*/
  1422. QUnit.module('Benchmark.Suite#unshift');
  1423. (function() {
  1424. test('adds a first element', function() {
  1425. var suite = Benchmark.Suite();
  1426. suite[0] = 1;
  1427. suite.length = 1;
  1428. var actual = suite.unshift(0);
  1429. equal(actual, 2);
  1430. deepEqual(slice.call(suite), [0, 1]);
  1431. });
  1432. test('adds multiple elements to the front', function() {
  1433. var suite = Benchmark.Suite();
  1434. suite[0] = 3;
  1435. suite.length = 1;
  1436. var actual = suite.unshift(0, 1, 2);
  1437. equal(actual, 4);
  1438. deepEqual(slice.call(suite), [0, 1, 2, 3]);
  1439. });
  1440. test('supports unshifting sparse arrays', function() {
  1441. var suite = Benchmark.Suite();
  1442. suite[1] = 2;
  1443. suite.length = 2;
  1444. var actual = suite.unshift(0);
  1445. equal(actual, 3);
  1446. deepEqual(slice.call(suite), [0, undefined, 2]);
  1447. equal('1' in suite, false);
  1448. });
  1449. }());
  1450. /*--------------------------------------------------------------------------*/
  1451. QUnit.module('Benchmark.Suite filtered results onComplete');
  1452. (function() {
  1453. var count = 0,
  1454. suite = Benchmark.Suite();
  1455. suite.add('a', function() {
  1456. count++;
  1457. })
  1458. .add('b', function() {
  1459. for (var i = 0; i < 1e6; i++) {
  1460. count++;
  1461. }
  1462. })
  1463. .add('c', function() {
  1464. throw new TypeError;
  1465. });
  1466. asyncTest('should filter by fastest', function() {
  1467. suite.on('complete', function() {
  1468. suite.off();
  1469. deepEqual(this.filter('fastest').pluck('name'), ['a']);
  1470. QUnit.start();
  1471. })
  1472. .run({ 'async': true });
  1473. });
  1474. asyncTest('should filter by slowest', function() {
  1475. suite.on('complete', function() {
  1476. suite.off();
  1477. deepEqual(this.filter('slowest').pluck('name'), ['b']);
  1478. QUnit.start();
  1479. })
  1480. .run({ 'async': true });
  1481. });
  1482. asyncTest('should filter by successful', function() {
  1483. suite.on('complete', function() {
  1484. suite.off();
  1485. deepEqual(this.filter('successful').pluck('name'), ['a', 'b']);
  1486. QUnit.start();
  1487. })
  1488. .run({ 'async': true });
  1489. });
  1490. }());
  1491. /*--------------------------------------------------------------------------*/
  1492. QUnit.module('Benchmark.Suite event flow');
  1493. (function() {
  1494. var events = [],
  1495. callback = function(event) { events.push(event); };
  1496. var suite = Benchmark.Suite('suite', {
  1497. 'onAdd': callback,
  1498. 'onAbort': callback,
  1499. 'onClone': callback,
  1500. 'onError': callback,
  1501. 'onStart': callback,
  1502. 'onCycle': callback,
  1503. 'onComplete': callback,
  1504. 'onReset': callback
  1505. })
  1506. .add('bench', function() {
  1507. throw null;
  1508. }, {
  1509. 'onAbort': callback,
  1510. 'onClone': callback,
  1511. 'onError': callback,
  1512. 'onStart': callback,
  1513. 'onCycle': callback,
  1514. 'onComplete': callback,
  1515. 'onReset': callback
  1516. })
  1517. .run({ 'async': false });
  1518. // first Suite#onAdd
  1519. test('should emit the suite "add" event first', function() {
  1520. var event = events[0];
  1521. ok(event.type == 'add' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
  1522. });
  1523. // next we start the Suite because no reset was needed
  1524. test('should emit the suite "start" event', function() {
  1525. var event = events[1];
  1526. ok(event.type == 'start' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
  1527. });
  1528. // and so start the first benchmark
  1529. test('should emit the benchmark "start" event', function() {
  1530. var event = events[2];
  1531. ok(event.type == 'start' && event.currentTarget.name == 'bench');
  1532. });
  1533. // oh no! we abort because of an error
  1534. test('should emit the benchmark "error" event', function() {
  1535. var event = events[3];
  1536. ok(event.type == 'error' && event.currentTarget.name == 'bench');
  1537. });
  1538. // benchmark error triggered
  1539. test('should emit the benchmark "abort" event', function() {
  1540. var event = events[4];
  1541. ok(event.type == 'abort' && event.currentTarget.name == 'bench');
  1542. });
  1543. // we reset the benchmark as part of the abort
  1544. test('should emit the benchmark "reset" event', function() {
  1545. var event = events[5];
  1546. ok(event.type == 'reset' && event.currentTarget.name == 'bench');
  1547. });
  1548. // benchmark is cycle is finished
  1549. test('should emit the benchmark "cycle" event', function() {
  1550. var event = events[6];
  1551. ok(event.type == 'cycle' && event.currentTarget.name == 'bench');
  1552. });
  1553. // benchmark is complete
  1554. test('should emit the benchmark "complete" event', function() {
  1555. var event = events[7];
  1556. ok(event.type == 'complete' && event.currentTarget.name == 'bench');
  1557. });
  1558. // the benchmark error triggers a Suite error
  1559. test('should emit the suite "error" event', function() {
  1560. var event = events[8];
  1561. ok(event.type == 'error' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
  1562. });
  1563. // the Suite cycle finishes
  1564. test('should emit the suite "cycle" event', function() {
  1565. var event = events[9];
  1566. ok(event.type == 'cycle' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
  1567. });
  1568. // the Suite completes
  1569. test('finally it should emit the suite "complete" event', function() {
  1570. var event = events[10];
  1571. ok(event.type == 'complete' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
  1572. });
  1573. test('emitted all expected events', function() {
  1574. ok(events.length == 11);
  1575. });
  1576. }());
  1577. /*--------------------------------------------------------------------------*/
  1578. QUnit.module('Deferred benchmarks');
  1579. (function() {
  1580. asyncTest('should run a deferred benchmark correctly', function() {
  1581. Benchmark(function(deferred) {
  1582. setTimeout(function() { deferred.resolve(); }, 1e3);
  1583. }, {
  1584. 'defer': true,
  1585. 'onComplete': function() {
  1586. equal(this.hz.toFixed(0), 1);
  1587. QUnit.start();
  1588. }
  1589. })
  1590. .run();
  1591. });
  1592. asyncTest('should run with string values for "fn", "setup", and "teardown"', function() {
  1593. Benchmark({
  1594. 'defer': true,
  1595. 'setup': 'var x = [3, 2, 1];',
  1596. 'fn': 'setTimeout(function() { x.sort(); deferred.resolve(); }, 10);',
  1597. 'teardown': 'x.length = 0;',
  1598. 'onComplete': function() {
  1599. ok(true);
  1600. QUnit.start();
  1601. }
  1602. })
  1603. .run();
  1604. });
  1605. asyncTest('should run recursively', function() {
  1606. Benchmark({
  1607. 'defer': true,
  1608. 'setup': 'var x = [3, 2, 1];',
  1609. 'fn': 'for (var i = 0; i < 100; i++) x[ i % 2 ? "sort" : "reverse" ](); deferred.resolve();',
  1610. 'teardown': 'x.length = 0;',
  1611. 'onComplete': function() {
  1612. ok(true);
  1613. QUnit.start();
  1614. }
  1615. })
  1616. .run();
  1617. });
  1618. asyncTest('should execute "setup", "fn", and "teardown" in correct order', function() {
  1619. var fired = [];
  1620. Benchmark({
  1621. 'defer': true,
  1622. 'setup': function() {
  1623. fired.push('setup');
  1624. },
  1625. 'fn': function(deferred) {
  1626. fired.push('fn');
  1627. setTimeout(function() { deferred.resolve(); }, 10);
  1628. },
  1629. 'teardown': function() {
  1630. fired.push('teardown');
  1631. },
  1632. 'onComplete': function() {
  1633. var actual = fired.join().replace(/(fn,)+/g, '$1').replace(/(setup,fn,teardown(?:,|$))+/, '$1');
  1634. equal(actual, 'setup,fn,teardown');
  1635. QUnit.start();
  1636. }
  1637. })
  1638. .run();
  1639. });
  1640. }());
  1641. /*--------------------------------------------------------------------------*/
  1642. QUnit.module('Benchmark.deepClone');
  1643. (function() {
  1644. asyncTest('avoids call stack limits', function() {
  1645. var result,
  1646. count = 0,
  1647. object = {},
  1648. recurse = function() { count++; recurse(); };
  1649. setTimeout(function() {
  1650. ok(result, 'avoids call stack limits (stack limit is ' + (count - 1) + ')');
  1651. QUnit.start();
  1652. }, 15);
  1653. if (toString.call(window.java) == '[object JavaPackage]') {
  1654. // Java throws uncatchable errors on call stack overflows, so to avoid
  1655. // them I chose a number higher than Rhino's call stack limit without
  1656. // dynamically testing for the actual limit
  1657. count = 3e3;
  1658. } else {
  1659. try { recurse(); } catch(e) { }
  1660. }
  1661. // exceed limit
  1662. count++;
  1663. for (var i = 0, sub = object; i <= count; i++) {
  1664. sub = sub[i] = {};
  1665. }
  1666. try {
  1667. for (var i = 0, sub = Benchmark.deepClone(object); sub = sub[i]; i++) { }
  1668. result = --i == count;
  1669. } catch(e) { }
  1670. });
  1671. }());
  1672. /*--------------------------------------------------------------------------*/
  1673. // explicitly call `QUnit.start()` for Narwhal, Rhino, and RingoJS
  1674. if (!window.document) {
  1675. QUnit.start();
  1676. }
  1677. }(typeof global == 'object' && global || this));