react / wstein / node_modules / jest-cli / node_modules / cover / node_modules / underscore.string / test / test_underscore / vendor / jslitmus.js
80698 views// JSLitmus.js1//2// History:3// 2008-10-27: Initial release4// 2008-11-09: Account for iteration loop overhead5// 2008-11-13: Added OS detection6// 2009-02-25: Create tinyURL automatically, shift-click runs tests in reverse7//8// Copyright (c) 2008-2009, Robert Kieffer9// All Rights Reserved10//11// Permission is hereby granted, free of charge, to any person obtaining a copy12// of this software and associated documentation files (the13// Software), to deal in the Software without restriction, including14// without limitation the rights to use, copy, modify, merge, publish,15// distribute, sublicense, and/or sell copies of the Software, and to permit16// persons to whom the Software is furnished to do so, subject to the17// following conditions:18//19// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,20// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF21// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN22// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,23// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR24// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE25// USE OR OTHER DEALINGS IN THE SOFTWARE.2627(function() {28// Private methods and state2930// Get platform info but don't go crazy trying to recognize everything31// that's out there. This is just for the major platforms and OSes.32var platform = 'unknown platform', ua = navigator.userAgent;3334// Detect OS35var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');36var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;37if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;3839// Detect browser40var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;4142// Detect version43var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');44var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;45var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';4647/**48* A smattering of methods that are needed to implement the JSLitmus testbed.49*/50var jsl = {51/**52* Enhanced version of escape()53*/54escape: function(s) {55s = s.replace(/,/g, '\\,');56s = escape(s);57s = s.replace(/\+/g, '%2b');58s = s.replace(/ /g, '+');59return s;60},6162/**63* Get an element by ID.64*/65$: function(id) {66return document.getElementById(id);67},6869/**70* Null function71*/72F: function() {},7374/**75* Set the status shown in the UI76*/77status: function(msg) {78var el = jsl.$('jsl_status');79if (el) el.innerHTML = msg || '';80},8182/**83* Convert a number to an abbreviated string like, "15K" or "10M"84*/85toLabel: function(n) {86if (n == Infinity) {87return 'Infinity';88} else if (n > 1e9) {89n = Math.round(n/1e8);90return n/10 + 'B';91} else if (n > 1e6) {92n = Math.round(n/1e5);93return n/10 + 'M';94} else if (n > 1e3) {95n = Math.round(n/1e2);96return n/10 + 'K';97}98return n;99},100101/**102* Copy properties from src to dst103*/104extend: function(dst, src) {105for (var k in src) dst[k] = src[k]; return dst;106},107108/**109* Like Array.join(), but for the key-value pairs in an object110*/111join: function(o, delimit1, delimit2) {112if (o.join) return o.join(delimit1); // If it's an array113var pairs = [];114for (var k in o) pairs.push(k + delimit1 + o[k]);115return pairs.join(delimit2);116},117118/**119* Array#indexOf isn't supported in IE, so we use this as a cross-browser solution120*/121indexOf: function(arr, o) {122if (arr.indexOf) return arr.indexOf(o);123for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;124return -1;125}126};127128/**129* Test manages a single test (created with130* JSLitmus.test())131*132* @private133*/134var Test = function (name, f) {135if (!f) throw new Error('Undefined test function');136if (!(/function[^\(]*\(([^,\)]*)/).test(f.toString())) {137throw new Error('"' + name + '" test: Test is not a valid Function object');138}139this.loopArg = RegExp.$1;140this.name = name;141this.f = f;142};143144jsl.extend(Test, /** @lends Test */ {145/** Calibration tests for establishing iteration loop overhead */146CALIBRATIONS: [147new Test('calibrating loop', function(count) {while (count--);}),148new Test('calibrating function', jsl.F)149],150151/**152* Run calibration tests. Returns true if calibrations are not yet153* complete (in which case calling code should run the tests yet again).154* onCalibrated - Callback to invoke when calibrations have finished155*/156calibrate: function(onCalibrated) {157for (var i = 0; i < Test.CALIBRATIONS.length; i++) {158var cal = Test.CALIBRATIONS[i];159if (cal.running) return true;160if (!cal.count) {161cal.isCalibration = true;162cal.onStop = onCalibrated;163//cal.MIN_TIME = .1; // Do calibrations quickly164cal.run(2e4);165return true;166}167}168return false;169}170});171172jsl.extend(Test.prototype, {/** @lends Test.prototype */173/** Initial number of iterations */174INIT_COUNT: 10,175/** Max iterations allowed (i.e. used to detect bad looping functions) */176MAX_COUNT: 1e9,177/** Minimum time a test should take to get valid results (secs) */178MIN_TIME: .5,179180/** Callback invoked when test state changes */181onChange: jsl.F,182183/** Callback invoked when test is finished */184onStop: jsl.F,185186/**187* Reset test state188*/189reset: function() {190delete this.count;191delete this.time;192delete this.running;193delete this.error;194},195196/**197* Run the test (in a timeout). We use a timeout to make sure the browser198* has a chance to finish rendering any UI changes we've made, like199* updating the status message.200*/201run: function(count) {202count = count || this.INIT_COUNT;203jsl.status(this.name + ' x ' + count);204this.running = true;205var me = this;206setTimeout(function() {me._run(count);}, 200);207},208209/**210* The nuts and bolts code that actually runs a test211*/212_run: function(count) {213var me = this;214215// Make sure calibration tests have run216if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;217this.error = null;218219try {220var start, f = this.f, now, i = count;221222// Start the timer223start = new Date();224225// Now for the money shot. If this is a looping function ...226if (this.loopArg) {227// ... let it do the iteration itself228f(count);229} else {230// ... otherwise do the iteration for it231while (i--) f();232}233234// Get time test took (in secs)235this.time = Math.max(1,new Date() - start)/1000;236237// Store iteration count and per-operation time taken238this.count = count;239this.period = this.time/count;240241// Do we need to do another run?242this.running = this.time <= this.MIN_TIME;243244// ... if so, compute how many times we should iterate245if (this.running) {246// Bump the count to the nearest power of 2247var x = this.MIN_TIME/this.time;248var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));249count *= pow;250if (count > this.MAX_COUNT) {251throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');252}253}254} catch (e) {255// Exceptions are caught and displayed in the test UI256this.reset();257this.error = e;258}259260// Figure out what to do next261if (this.running) {262me.run(count);263} else {264jsl.status('');265me.onStop(me);266}267268// Finish up269this.onChange(this);270},271272/**273* Get the number of operations per second for this test.274*275* @param normalize if true, iteration loop overhead taken into account276*/277getHz: function(/**Boolean*/ normalize) {278var p = this.period;279280// Adjust period based on the calibration test time281if (normalize && !this.isCalibration) {282var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];283284// If the period is within 20% of the calibration time, then zero the285// it out286p = p < cal.period*1.2 ? 0 : p - cal.period;287}288289return Math.round(1/p);290},291292/**293* Get a friendly string describing the test294*/295toString: function() {296return this.name + ' - ' + this.time/this.count + ' secs';297}298});299300// CSS we need for the UI301var STYLESHEET = '<style> \302#jslitmus {font-family:sans-serif; font-size: 12px;} \303#jslitmus a {text-decoration: none;} \304#jslitmus a:hover {text-decoration: underline;} \305#jsl_status { \306margin-top: 10px; \307font-size: 10px; \308color: #888; \309} \310A IMG {border:none} \311#test_results { \312margin-top: 10px; \313font-size: 12px; \314font-family: sans-serif; \315border-collapse: collapse; \316border-spacing: 0px; \317} \318#test_results th, #test_results td { \319border: solid 1px #ccc; \320vertical-align: top; \321padding: 3px; \322} \323#test_results th { \324vertical-align: bottom; \325background-color: #ccc; \326padding: 1px; \327font-size: 10px; \328} \329#test_results #test_platform { \330color: #444; \331text-align:center; \332} \333#test_results .test_row { \334color: #006; \335cursor: pointer; \336} \337#test_results .test_nonlooping { \338border-left-style: dotted; \339border-left-width: 2px; \340} \341#test_results .test_looping { \342border-left-style: solid; \343border-left-width: 2px; \344} \345#test_results .test_name {white-space: nowrap;} \346#test_results .test_pending { \347} \348#test_results .test_running { \349font-style: italic; \350} \351#test_results .test_done {} \352#test_results .test_done { \353text-align: right; \354font-family: monospace; \355} \356#test_results .test_error {color: #600;} \357#test_results .test_error .error_head {font-weight:bold;} \358#test_results .test_error .error_body {font-size:85%;} \359#test_results .test_row:hover td { \360background-color: #ffc; \361text-decoration: underline; \362} \363#chart { \364margin: 10px 0px; \365width: 250px; \366} \367#chart img { \368border: solid 1px #ccc; \369margin-bottom: 5px; \370} \371#chart #tiny_url { \372height: 40px; \373width: 250px; \374} \375#jslitmus_credit { \376font-size: 10px; \377color: #888; \378margin-top: 8px; \379} \380</style>';381382// HTML markup for the UI383var MARKUP = '<div id="jslitmus"> \384<button onclick="JSLitmus.runAll(event)">Run Tests</button> \385<button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \386<br \> \387<br \> \388<input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \389<table id="test_results"> \390<colgroup> \391<col /> \392<col width="100" /> \393</colgroup> \394<tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \395<tr><th>Test</th><th>Ops/sec</th></tr> \396<tr id="test_row_template" class="test_row" style="display:none"> \397<td class="test_name"></td> \398<td class="test_result">Ready</td> \399</tr> \400</table> \401<div id="jsl_status"></div> \402<div id="chart" style="display:none"> \403<a id="chart_link" target="_blank"><img id="chart_image"></a> \404TinyURL (for chart): \405<iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \406</div> \407<a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \408</div>';409410/**411* The public API for creating and running tests412*/413window.JSLitmus = {414/** The list of all tests that have been registered with JSLitmus.test */415_tests: [],416/** The queue of tests that need to be run */417_queue: [],418419/**420* The parsed query parameters the current page URL. This is provided as a421* convenience for test functions - it's not used by JSLitmus proper422*/423params: {},424425/**426* Initialize427*/428_init: function() {429// Parse query params into JSLitmus.params[] hash430var match = (location + '').match(/([^?#]*)(#.*)?$/);431if (match) {432var pairs = match[1].split('&');433for (var i = 0; i < pairs.length; i++) {434var pair = pairs[i].split('=');435if (pair.length > 1) {436var key = pair.shift();437var value = pair.length > 1 ? pair.join('=') : pair[0];438this.params[key] = value;439}440}441}442443// Write out the stylesheet. We have to do this here because IE444// doesn't honor sheets written after the document has loaded.445document.write(STYLESHEET);446447// Setup the rest of the UI once the document is loaded448if (window.addEventListener) {449window.addEventListener('load', this._setup, false);450} else if (document.addEventListener) {451document.addEventListener('load', this._setup, false);452} else if (window.attachEvent) {453window.attachEvent('onload', this._setup);454}455456return this;457},458459/**460* Set up the UI461*/462_setup: function() {463var el = jsl.$('jslitmus_container');464if (!el) document.body.appendChild(el = document.createElement('div'));465466el.innerHTML = MARKUP;467468// Render the UI for all our tests469for (var i=0; i < JSLitmus._tests.length; i++)470JSLitmus.renderTest(JSLitmus._tests[i]);471},472473/**474* (Re)render all the test results475*/476renderAll: function() {477for (var i = 0; i < JSLitmus._tests.length; i++)478JSLitmus.renderTest(JSLitmus._tests[i]);479JSLitmus.renderChart();480},481482/**483* (Re)render the chart graphics484*/485renderChart: function() {486var url = JSLitmus.chartUrl();487jsl.$('chart_link').href = url;488jsl.$('chart_image').src = url;489jsl.$('chart').style.display = '';490491// Update the tiny URL492jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);493},494495/**496* (Re)render the results for a specific test497*/498renderTest: function(test) {499// Make a new row if needed500if (!test._row) {501var trow = jsl.$('test_row_template');502if (!trow) return;503504test._row = trow.cloneNode(true);505test._row.style.display = '';506test._row.id = '';507test._row.onclick = function() {JSLitmus._queueTest(test);};508test._row.title = 'Run ' + test.name + ' test';509trow.parentNode.appendChild(test._row);510test._row.cells[0].innerHTML = test.name;511}512513var cell = test._row.cells[1];514var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];515516if (test.error) {517cns.push('test_error');518cell.innerHTML =519'<div class="error_head">' + test.error + '</div>' +520'<ul class="error_body"><li>' +521jsl.join(test.error, ': ', '</li><li>') +522'</li></ul>';523} else {524if (test.running) {525cns.push('test_running');526cell.innerHTML = 'running';527} else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {528cns.push('test_pending');529cell.innerHTML = 'pending';530} else if (test.count) {531cns.push('test_done');532var hz = test.getHz(jsl.$('test_normalize').checked);533cell.innerHTML = hz != Infinity ? hz : '∞';534} else {535cell.innerHTML = 'ready';536}537}538cell.className = cns.join(' ');539},540541/**542* Create a new test543*/544test: function(name, f) {545// Create the Test object546var test = new Test(name, f);547JSLitmus._tests.push(test);548549// Re-render if the test state changes550test.onChange = JSLitmus.renderTest;551552// Run the next test if this one finished553test.onStop = function(test) {554if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);555JSLitmus.currentTest = null;556JSLitmus._nextTest();557};558559// Render the new test560this.renderTest(test);561},562563/**564* Add all tests to the run queue565*/566runAll: function(e) {567e = e || window.event;568var reverse = e && e.shiftKey, len = JSLitmus._tests.length;569for (var i = 0; i < len; i++) {570JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);571}572},573574/**575* Remove all tests from the run queue. The current test has to finish on576* it's own though577*/578stop: function() {579while (JSLitmus._queue.length) {580var test = JSLitmus._queue.shift();581JSLitmus.renderTest(test);582}583},584585/**586* Run the next test in the run queue587*/588_nextTest: function() {589if (!JSLitmus.currentTest) {590var test = JSLitmus._queue.shift();591if (test) {592jsl.$('stop_button').disabled = false;593JSLitmus.currentTest = test;594test.run();595JSLitmus.renderTest(test);596if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);597} else {598jsl.$('stop_button').disabled = true;599JSLitmus.renderChart();600}601}602},603604/**605* Add a test to the run queue606*/607_queueTest: function(test) {608if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;609JSLitmus._queue.push(test);610JSLitmus.renderTest(test);611JSLitmus._nextTest();612},613614/**615* Generate a Google Chart URL that shows the data for all tests616*/617chartUrl: function() {618var n = JSLitmus._tests.length, markers = [], data = [];619var d, min = 0, max = -1e10;620var normalize = jsl.$('test_normalize').checked;621622// Gather test data623for (var i=0; i < JSLitmus._tests.length; i++) {624var test = JSLitmus._tests[i];625if (test.count) {626var hz = test.getHz(normalize);627var v = hz != Infinity ? hz : 0;628data.push(v);629markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +630markers.length + ',10');631max = Math.max(v, max);632}633}634if (markers.length <= 0) return null;635636// Build chart title637var title = document.getElementsByTagName('title');638title = (title && title.length) ? title[0].innerHTML : null;639var chart_title = [];640if (title) chart_title.push(title);641chart_title.push('Ops/sec (' + platform + ')');642643// Build labels644var labels = [jsl.toLabel(min), jsl.toLabel(max)];645646var w = 250, bw = 15;647var bs = 5;648var h = markers.length*(bw + bs) + 30 + chart_title.length*20;649650var params = {651chtt: escape(chart_title.join('|')),652chts: '000000,10',653cht: 'bhg', // chart type654chd: 't:' + data.join(','), // data set655chds: min + ',' + max, // max/min of data656chxt: 'x', // label axes657chxl: '0:|' + labels.join('|'), // labels658chsp: '0,1',659chm: markers.join('|'), // test names660chbh: [bw, 0, bs].join(','), // bar widths661// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient662chs: w + 'x' + h663};664return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');665}666};667668JSLitmus._init();669})();670671