(function(){
// Populated from: http://www.medcalc.be/manual/t-distribution.php
// 95% confidence for N - 1 = 4
var tDistribution = 2.776;
// The number of individual test iterations to do
var numTests = 5;
// The type of run that we're doing (options are "runs/s" or "ms")
var runStyle = "runs/s";
// A rough estimate, in seconds, of how long it'll take each test
// iteration to run
var timePerTest = runStyle === "runs/s" ? 1 : 0.5;
// Initialize a batch of tests
// name = The name of the test collection
this.startTest = function(name, version){
numloaded++;
if ( numloaded == totalTests )
setTimeout( init, 100 );
testName = name;
if ( !queues[testName] ) return;
testID = testName;
testNames[testID] = testName;
testVersions[testID] = version || 0;
testSummary[testID] = testSummaryNum[testID] = testDone[testID] = testNum[testID] = 0;
queues[testID].push(function(){
summary = 0;
dequeue();
});
};
// Anything that you want to have run in order, but not actually test
this.prep = function(fn){
if ( !queues[testName] ) return;
queues[testID].push(function(){
fn();
dequeue();
});
};
// End the tests and finalize the report
this.endTest = function(){
if ( !queues[testName] ) return;
// Save the summary output until all the test are complete
queues[testID].push(function(){
dequeue();
});
};
// Run a new test
// name = The unique name of the test
// num = The 'length' of the test (length of string, # of tests, etc.)
// fn = A function holding the test to run
this.test = function(name, num, fn){
if ( !queues[testName] ) return;
// Save the summary output until all the test are complete
var curTest = testName, curID = testID;
if ( arguments.length === 3 ) {
if ( !nameDone[name] )
nameDone[name] = 0;
nameDone[name]++;
if ( nameDone[name] != 3 )
return;
} else {
fn = num;
num = 1;
}
time += timePerTest * numTests;
testNum[curID]++;
// Don't execute the test immediately
queues[testID].push(function(){
title = name;
var times = [], start, pos = 0, cur;
setTimeout(function(){
// run tests
try {
if ( doShark(name) ) {
connectShark();
startShark();
}
start = (new Date()).getTime();
if ( runStyle === "runs/s" ) {
var runs = 0;
cur = (new Date()).getTime();
while ( (cur - start) < 1000 ) {
fn();
cur = (new Date()).getTime();
runs++;
}
} else {
fn();
cur = (new Date()).getTime();
}
if ( doShark(name) ) {
stopShark();
disconnectShark();
}
// For making Median and Variance
if ( runStyle === "runs/s" ) {
times.push( (runs * 1000) / (cur - start) );
} else {
times.push( cur - start );
}
} catch( e ) {
alert("FAIL " + name + " " + num + e);
return;
}
if ( pos < numTests ) {
updateTime();
updateTestPos({curID: curID, collection: testNames[curID], version: testVersions[curID]});
}
if ( ++pos < numTests ) {
setTimeout( arguments.callee, 1 );
} else {
var data = compute( times, numTests );
data.curID = curID;
data.collection = testNames[curID];
data.version = testVersions[curID];
data.name = title;
data.scale = num;
logTest(data);
dequeue();
}
}, 1);
});
function compute(times, runs){
var results = {runs: runs}, num = times.length, middle = num/2;
times = times.sort(function(a,b){
return a - b;
});
// Make Sum
results.sum = 0;
for ( var i = 0; i < num; i++ )
results.sum += times[i];
// Make Min
results.min = times[0];
// Make Max
results.max = times[ num - 1 ];
// Make Mean
results.mean = results.sum / num;
// Make Median
results.median = num % 2 == 0 ?
(times[middle-1] + times[middle]) / 2 :
times[Math.floor(middle)];
// Make Variance
results.variance = 0;
for ( var i = 0; i < num; i++ )
results.variance += Math.pow(times[i] - results.mean, 2);
results.variance /= num - 1;
// Make Standard Deviation
results.deviation = Math.sqrt( results.variance );
// Compute Standard Errors Mean
results.sem = (results.deviation / Math.sqrt(results.runs)) * tDistribution;
// Error
results.error = ((results.sem / results.mean) * 100) || 0;
return results;
}
};
// All the test data
var tests;
// The number of test files to load
var totalTests = 0;
// The number of test files loaded
var numloaded = 0;
// Queue of functions to run
var queue = [];
var queues = {};
var catnames = {
dromaeo: "Dromaeo JavaScript Tests",
sunspider: "SunSpider JavaScript Tests",
"v8": "V8 JavaScript Tests",
dom: "DOM Core Tests",
jslib: "JavaScript Library Tests",
cssquery: "CSS Selector Tests"
};
var testElems = {};
var testNum = {};
var testDone = {};
var testNames = {};
var testVersions = {};
var dataStore = [];
var names = [];
var interval;
var totalTime = 0;
var time = 0;
var title, testName, testID, testSummary = {} , testSummaryNum = {}, maxTotal = 0, maxTotalNum = 0;
var nameDone = {};
var automated = false;
var post_json = false;
// Query String Parsing
var search = window.limitSearch || (window.location.search || "?").substr(1);
search = search.replace(/&runStyle=([^&]+)/, function(all, type){
runStyle = type;
return "";
});
var parts = search.split("&");
if ( parts[0] === "recommended" ) {
parts[0] = "dromaeo|sunspider|v8|dom|jslib";
}
var none = !parts[0] || parts[0].match(/=/);
var filter = parts.length && !parts[0].match(/=/) && parts[0] !== "all" ?
new RegExp(parts.shift(), "i") :
/./;
// To enable shark debugging add &shark to the end of the URL
var doShark = function(name) { return false; };
for ( var i = 0; i < parts.length; i++ ) {
var m = /^shark(?:=(.*))?$/.exec(parts[i]);
if (m) {
if (m[1] === undefined) {
doShark = function(name) { return true; };
}
else {
var sharkMatch = new RegExp(m[1]);
doShark = function(name) {
return sharkMatch.test(name);
};
}
}
m = /^numTests=(\d+)$/.exec(parts[i]);
if (m)
numTests = Number(m[1]);
if (/^automated$/.exec(parts[i]))
automated = true;
if (/^post_json$/.exec(parts[i]))
post_json = true;
}
jQuery(function(){
var id = search.match(/id=([\d,]+)/);
if ( none && !id ) {
$("#overview").hide();
return;
}
var cat = filter.toString().slice(1,-2);
if ( catnames[cat] ) {
$("#overview span:first").html( catnames[cat] );
if ( catnames[cat].length > 22 ) {
$("#overview span:first").css("font-size", 22);
}
}
$("#tests").hide();
jQuery.getJSON("tests/MANIFEST.json", function(json){
tests = json;
names = [];
for ( var name in tests )
// Don't load tests that we aren't looking for
if ( filter.test( name ) )
names.push( name );
names = names.sort(function(a, b){
return tests[a].name < tests[b].name ? -1 :
tests[a].name == tests[b].name ? 0 : 1;
});
// Check if we're loading a specific result set
// ?id=NUM
if ( id ) {
jQuery.ajax({
url: "store.php?id=" + id[1],
dataType: "json",
success: function(data){
resultsLoaded(id[1], data);
}
});
// Otherwise we're loading a normal set of tests
} else {
$("#wrapper").append("<br style='clear:both;'/><center><a href='?" + names.join("|") + "'>Re-run tests</a></center>");
for ( var i = 0; i < names.length; i++ ) (function(name){
var test = tests[name];
queues[name] = [];
makeElem(name);
initTest(name);
totalTests++;
// Check if we're loading an HTML file
if ( test.file.match(/html$/) ) {
var iframe = document.createElement("iframe");
iframe.style.height = "1px";
iframe.style.width = "1px";
iframe.src = "tests/" + test.file;
document.body.appendChild( iframe );
// Otherwise we're loading a pure-JS test
} else {
jQuery.getScript("tests/" + test.file);
}
})(names[i]);
}
});
});
// Remove the next test from the queue and execute it
function dequeue(){
if ( interval && queue.length ) {
queue.shift()();
} else if ( queue.length == 0 ) {
interval = false;
time = 0;
$("#overview input").remove();
updateTimebar();
if ( window.limitSearch ) {
var summary = (runStyle === "runs/s" ? Math.pow(Math.E, maxTotal / maxTotalNum) : maxTotal).toFixed(2);
if ( typeof tpRecordTime !== "undefined" ) {
tpRecordTime( summary );
} else {
var pre = document.createElement("pre");
pre.style.display = "none";
pre.innerHTML = "__start_report" + summary + "__end_report";
document.body.appendChild( pre );
}
if ( typeof goQuitApplication !== "undefined" ) {
goQuitApplication();
}
} else if ( dataStore && dataStore.length ) {
if (!automated) {
$("body").addClass("alldone");
var div = jQuery("<div class='results'>Saving...</div>").insertBefore("#overview");
jQuery.ajax({
type: "POST",
url: "store.php",
data: "data=" + encodeURIComponent(JSON.stringify(dataStore)) + "&style=" + runStyle,
success: function(id){
var url = window.location.href.replace(/\?.*$/, "") + "?id=" + id;
div.html("Results saved. You can access them at a later time at the following URL:<br/><strong><a href='" + url + "'>" + url + "</a></strong></div>");
}
});
} else if (post_json) {
jQuery.ajax({
type: "POST",
url: "store.php",
data: "data=" + encodeURIComponent(JSON.stringify(window.automation.GetResults()))
});
}
else {
window.automation.SetDone();
}
}
}
}
function updateTimebar(){
$("#timebar").html("<span><strong>" + (runStyle === "runs/s" ? Math.pow(Math.E, maxTotal / maxTotalNum) : maxTotal).toFixed(2) + "</strong>" + runStyle + " (Total)</span>");
}
// Run once all the test files are fully loaded
function init(){
for ( var n = 0; n < names.length; n++ ) {
queue = queue.concat( queues[ names[n] ] );
}
totalTime = time;
time += timePerTest;
updateTime();
if (!automated) {
$("#pause")
.val("Run")
.click(function(){
if ( interval ) {
interval = null;
this.value = "Run";
} else {
if ( !interval ) {
interval = true;
dequeue();
}
this.value = "Pause";
}
});
} else {
$("#pause")
.val("Automated")
.click(function(){});
interval = true;
dequeue();
}
if ( window.limitSearch ) {
$("#pause").click();
}
}
function initTest(curID){
$("<div class='result-item'></div>")
.append( testElems[ curID ] )
.append( "<p>" + (tests[curID] ? tests[ curID ].desc : "") + "<br/><a href='" +
(tests[curID] && tests[curID].origin ? tests[ curID ].origin[1] : "") + "'>Origin</a>, <a href='tests/" +
(tests[curID] ? tests[ curID ].file : "") + "'>Source</a>, <b>Tests:</b> " +
(tests[curID] && tests[curID].tags ? tests[ curID ].tags.join(", ") : "") + "</p>" )
.append( "<ol class='results'></ol>" )
.appendTo("#main");
}
function resultsLoaded(id, datas){
var results = {};
var runs = {};
var output = "";
var excluded = [];
var overview = document.getElementById("overview");
for ( var d = 0; d < datas.length; d++ ) {
var data = datas[d];
runStyle = data.style;
if ( datas.length == 1 ) {
$("#overview").before("<div class='results'>Viewing test run #" + id +
", run on: " + data.created_at + " by:<br>" + data.useragent + "</div>");
}
runs[data.id] = data;
runs[data.id].mean = 0;
runs[data.id].error = 0;
runs[data.id].num = 0;
runs[data.id].name = (data.useragent.match(/(MSIE [\d.]+)/) ||
data.useragent.match(/((?:WebKit|Firefox|Shiretoko|Opera)\/[\w.]+)/) || [0,data.id])[1];
for ( var i = 0; i < data.results.length; i++ ) {
var result = data.results[i];
var curID = result.collection;
var run = result.run_id;
result.version += data.style;
if ( !results[curID] )
results[curID] = {tests:{}, total:{}, version: result.version};
if ( results[curID].version == result.version ) {
if ( !results[curID].total[run] ) {
results[curID].total[run] = {max:0, mean:0, median:0, min:0, deviation:0, error:0, num:0};
results[curID].tests[run] = [];
}
result.error = ((((result.deviation / Math.sqrt(result.runs)) * tDistribution) / result.mean) * 100) || 0;
results[curID].tests[run].push( result );
var error = (parseFloat(result.error) / 100) * parseFloat(result.mean);
error = (runStyle === "ms" ? error : error == 0 ? 0 : Math.log(error));
var total = results[curID].total[run];
total.num++;
for ( var type in total ) {
if ( type == "error" ) {
total.error += error;
} else if ( type == "mean" ) {
total.mean += (runStyle === "ms" ? parseFloat(result.mean) : Math.log(parseFloat(result.mean)));
} else if ( type !== "num" ) {
total[type] += parseFloat(result[type]);
}
}
runs[run].num++;
runs[run].mean += runStyle === "ms" ? parseFloat(result.mean) : Math.log(parseFloat(result.mean));
runs[run].error += error;
}
}
}
var runTests = [];
if ( datas.length == 1 ) {
$("body").addClass("alldone");
for ( var i = 0; i < data.results.length; i++ ) {
var item = data.results[i];
var result = item.curID = item.collection;
if ( !filter.test(result) )
continue;
if ( !testElems[result] ) {
runTests.push(result);
makeElem( result );
initTest( result );
}
// Compute Standard Errors Mean
item.sem = (item.deviation / Math.sqrt(item.runs)) * tDistribution;
// Error
item.error = ((item.sem / item.mean) * 100) || 0;
logTest( item );
// testDone, testNum, testSummary
testDone[ result ] = numTests - 1;
testNum[ result ] = 1;
updateTestPos( item );
}
$("div.result-item").addClass("done");
totalTime = time = timePerTest;
updateTime();
$("#overview input").remove();
updateTimebar();
} else {
// Remove results where there is only one comparison set
for ( var id in results ) {
var num = 0;
for ( var ntest in results[id].tests ) {
num++;
if ( num > 1 )
break;
}
if ( num <= 1 ) {
excluded.push( id );
delete results[id];
}
}
var preoutput = "<tr><td></td>";
for ( var run in runs )
preoutput += "<th><a href='?id=" + run + "'>" + runs[run].name + "</a></th>";
//preoutput += "<th>Winning %</th></tr>";
preoutput += "</tr>";
for ( var result in results ) {
// Skip results that we're filtering out
if ( !filter.test(result) )
continue;
runTests.push(result);
if ( runStyle === "runs/s" ) {
for ( var run in runs ) {
var mean = results[result].total[run].mean - 0;
var error = results[result].total[run].error - 0;
mean = Math.pow(Math.E, mean / results[result].total[run].num);
error = Math.pow(Math.E, error / results[result].total[run].num);
results[result].total[run].mean = mean;
results[result].total[run].error = error;
}
}
var name = tests[result] ? tests[result].name : result;
var tmp = processWinner(results[result].total);
output += "<tr><th class='name'><span onclick='toggleResults(this.nextSibling);'>▶ </span>" +
"<a href='' onclick='return toggleResults(this);'>" + name + "</a></th>";
for ( var run in runs ) {
var mean = results[result].total[run].mean - 0;
var error = results[result].total[run].error - 0;
output += "<td class='" + (tmp[run] || '') + "'>" + mean.toFixed(2) + "<small>" + runStyle + " ±" + ((error / mean) * 100).toFixed(2) + "%</small></td>";
}
//showWinner(tmp);
output += "</tr>";
var _tests = results[result].tests, _data = _tests[run], _num = _data.length;
for ( var i = 0; i < _num; i++ ) {
output += "<tr class='onetest hidden'><td><small>" + _data[i].name + "</small></td>";
for ( var run in runs ) {
output += "<td>" + (_tests[run][i].mean - 0).toFixed(2) + "<small>" + runStyle + " ±" + (_tests[run][i].error - 0).toFixed(2) + "%</small></td>";
}
output += "<td></td></tr>";
}
}
if ( runStyle === "runs/s" ) {
for ( var run in runs ) {
runs[run].mean = Math.pow(Math.E, runs[run].mean / runs[run].num);
runs[run].error = Math.pow(Math.E, runs[run].error / runs[run].num);
}
}
var tmp = processWinner(runs);
var totaloutput = "";
if ( runStyle === "ms" ) {
totaloutput += "<tr><th class='name'>Total:</th>";
} else {
totaloutput += "<tr><th class='name'>Total Score:</th>";
}
for ( var run in runs ) {
totaloutput += "<th class='name " + (tmp[run] || '') + "' title='" + (tmp[run + "title"] || '') + "'>" + runs[run].mean.toFixed(2) + "<small>" + runStyle + " ±" + ((runs[run].error / runs[run].mean) * 100).toFixed(2) + "%</small></th>";
}
//showWinner(tmp);
totaloutput += "</tr>";
overview.className = "";
overview.innerHTML = "<div class='resultwrap'><table class='results'>" + preoutput + totaloutput + output + totaloutput + "</table>" + (excluded.length ? "<div style='text-align:left;'><small><b>Excluded Tests:</b> " + excluded.sort().join(", ") + "</small></div>" : "") + "</div>";
}
$("#wrapper").append("<center><a href='?" + runTests.join("|") + "'>Re-run tests</a></center>");
function showWinner(tmp){
if ( datas.length > 1 ) {
if ( tmp.tie )
output += "<th>Tie</th>";
else
output += "<th>" + tmp.diff + "%</th>";
}
}
}
this.toggleResults = function(elem){
var span = elem.previousSibling;
elem.blur();
elem = elem.parentNode.parentNode.nextSibling;
span.innerHTML = elem.className.indexOf("hidden") < 0 ? "▶ " : "▼ ";
while ( elem && elem.className.indexOf("onetest") >= 0 ) {
elem.className = "onetest" + (elem.className.indexOf("hidden") >= 0 ? " " : " hidden");
elem = elem.nextSibling;
}
return false;
};
function updateTime(){
time -= timePerTest;
$("#left").html(Math.floor(time / 60) + ":" + (time % 60 < 10 ? "0" : "" ) + Math.floor(time % 60));
var w = ((totalTime - time) / totalTime) * 100;
$("#timebar").width((w < 1 ? 1 : w) + "%");
}
function logTest(data){
// Keep a running summary going
data.mean = parseFloat(data.mean);
var mean = (runStyle === "runs/s" ? Math.log(data.mean) : data.mean);
testSummary[data.curID] = (testSummary[data.curID] || 0) + mean;
testSummaryNum[data.curID] = (testSummaryNum[data.curID] || 0) + 1;
maxTotal += mean;
maxTotalNum++;
testDone[data.curID]--;
updateTestPos(data);
testElems[data.curID].next().next().append("<li><b>" + data.name +
":</b> " + data.mean.toFixed(2) + "<small>" + runStyle + " ±" + data.error.toFixed(2) + "%</small></li>");
dataStore.push(data);
}
function updateTestPos(data, update){
if ( !update )
testDone[data.curID]++;
var per = (testDone[data.curID] / (testNum[data.curID] * numTests)) * 100;
if ( update )
per = 1;
var mean = (runStyle === "runs/s" ?
Math.pow(Math.E, testSummary[data.curID] / testSummaryNum[data.curID]) :
testSummary[data.curID]);
testElems[data.curID].html("<b>" + (tests[data.curID] ? tests[data.curID].name : data.curID) +
":</b> <div class='bar'><div style='width:" +
per + "%;'>" + (per >= 100 ? "<span>" + mean.toFixed(2) + runStyle + "</span>" : "") + "</div></div>");
if ( per >= 100 && testSummary[data.curID] > 0 ) {
testElems[data.curID].parent().addClass("done");
}
}
function processWinner(data){
var minVal = -1, min2Val = -1, min, min2;
for ( var i in data ) {
var total = data[i].mean;
if ( minVal == -1 || (runStyle === "ms" && total <= minVal || runStyle === "runs/s" && total >= minVal) ) {
min2Val = minVal;
min2 = min;
minVal = total;
min = i;
} else if ( min2Val == -1 || (runStyle === "ms" && total <= minVal || runStyle === "runs/s" && total >= min2Val) ) {
min2Val = total;
min2 = i;
}
}
var tieVal = (runStyle === "ms" ? minVal : min2Val) + data[min].error + data[min2].error;
var ret = {
winner: min,
diff: runStyle === "ms" ?
-1 * Math.round((1 - (min2Val / minVal)) * 100) :
Math.round(((minVal / min2Val) - 1) * 100),
tie: minVal == min2Val || (runStyle === "ms" ? tieVal >= min2Val : tieVal >= minVal)
};
ret.tie = ret.tie || ret.diff == 0;
if ( ret.tie ) {
ret[ min ] = 'tie';
ret[ min2 ] = 'tie';
ret[ min + 'title' ] = "Tied with another run.";
ret[ min2 + 'title' ] = "Tied with another run.";
} else {
ret[ min ] = 'winner';
if ( min2Val > -1 ) {
ret[ min + 'title' ] = "Won by " + ret.diff + "%.";
}
}
return ret;
}
function makeElem(testID){
/*
if ( tests[testID] ) {
var cat = tests[testID].category, catsm = cat.replace(/[^\w]/g, "-");
if ( !$("#" + catsm).length ) {
$("#main").append("<h2 id='" + catsm + "' class='test'><a href='?cat=" + cat +"'>" + cat + '</a><div class="bar"><div id="timebar" style="width:25%;"><span class="left">Est. Time: <strong id="left">0:00</strong></span></div></div>');
}
}
*/
testElems[testID] = $("<div class='test'></div>")
.click(function(){
var next = jQuery(this).next().next();
if ( next.children().length == 0 ) return;
var display = next.css("display");
next.css("display", display == 'none' ? 'block' : 'none');
});
updateTestPos({curID: testID, collection: tests[testID] ? tests[testID].name : testID, version: testVersions[testID]}, true);
}
if (automated) {
// Add some more stuff if running in automated mode.
window.automation = {}
window.automation.SetDone = function() {
console.log("Total: " + this.GetScore());
window.document.cookie = "__done=1; path=/";
}
window.automation.GetScore = function() {
return (runStyle === "runs/s" ? Math.pow(Math.E, maxTotal / maxTotalNum) : maxTotal).toString();
}
window.automation.GetResults = function() {
var results = {};
var aggregated = {};
function normalizeName(name) {
// At least for ui_tests, dots are not allowed.
return name.replace(".", "_");
}
function appendToAggregated(name, value) {
name = normalizeName(name);
(aggregated[name] || (aggregated[name] = [])).push(Math.log(value));
}
for (var i = 0; i < dataStore.length; i++) {
var data = dataStore[i];
var topName = data.collection.split("-", 1)[0];
appendToAggregated(topName, data.mean);
appendToAggregated(data.collection, data.mean);
results[normalizeName(data.collection + "/" + data.name)] = data.mean.toString();
}
for (var name in aggregated) {
var means = aggregated[name];
var sum = 0;
for (var i = 0; i < means.length; i++) sum += means[i];
results[name] = Math.pow(Math.E, sum/means.length).toString();
}
return results;
}
}
})();