blob: 47f1ad95cde7e28dfaa453c1df8211cca75e6127 [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
// *********************** Test Framework of Sorts ************************* //
function loadScript(url) {
// disallow loading remote URLs
var re = /^[a-z0-9_]+(\/[a-z0-9_]+)*\.js#?$/;
if (!re.test(url)) {
throw "Not loading remote test scripts";
}
if (typeof document != "undefined") document.write('<script src="'+url+'"></script>');
};
function patchTest(fun) {
var source = fun.toString();
var output = "";
var i = 0;
var testMarker = "T(";
while (i < source.length) {
var testStart = source.indexOf(testMarker, i);
if (testStart == -1) {
output = output + source.substring(i, source.length);
break;
}
var testEnd = source.indexOf(");", testStart);
var testCode = source.substring(testStart + testMarker.length, testEnd);
output += source.substring(i, testStart) + "T(" + testCode + "," + JSON.stringify(testCode);
i = testEnd;
}
try {
return eval("(" + output + ")");
} catch (e) {
return null;
}
}
function runAllTests() {
var rows = $("#tests tbody.content tr");
$("td", rows).text("");
$("td.status", rows).removeClass("error").removeClass("failure").removeClass("success").text("not run");
var offset = 0;
function runNext() {
if (offset < rows.length) {
var row = rows.get(offset);
runTest($("th button", row).get(0), function() {
offset += 1;
setTimeout(runNext, 100);
}, false, true);
} else {
saveTestReport();
}
}
runNext();
}
var numFailures = 0;
var currentRow = null;
function runTest(button, callback, debug, noSave) {
// offer to save admins
if (currentRow != null) {
alert("Can not run multiple tests simultaneously.");
return;
}
var row = currentRow = $(button).parents("tr").get(0);
$("td.status", row).removeClass("error").removeClass("failure").removeClass("success");
$("td", row).text("");
$("#toolbar li.current").text("Running: "+row.id);
var testFun = couchTests[row.id];
function run() {
numFailures = 0;
var start = new Date().getTime();
try {
if (debug == undefined || !debug) {
testFun = patchTest(testFun) || testFun;
}
testFun(debug);
var status = numFailures > 0 ? "failure" : "success";
} catch (e) {
var status = "error";
if ($("td.details ol", row).length == 0) {
$("<ol></ol>").appendTo($("td.details", row));
}
$("<li><b>Exception raised:</b> <code class='error'></code></li>")
.find("code").text(JSON.stringify(e)).end()
.appendTo($("td.details ol", row));
if (debug) {
currentRow = null;
throw e;
}
}
if ($("td.details ol", row).length) {
$("<a href='#'>Run with debugger</a>").click(function() {
runTest(this, undefined, true);
}).prependTo($("td.details ol", row));
}
var duration = new Date().getTime() - start;
$("td.status", row).removeClass("running").addClass(status).text(status);
$("td.duration", row).text(duration + "ms");
$("#toolbar li.current").text("Finished: "+row.id);
updateTestsFooter();
currentRow = null;
if (callback) callback();
if (!noSave) saveTestReport();
}
$("td.status", row).addClass("running").text("running…");
setTimeout(run, 100);
}
function showSource(cell) {
var name = $(cell).text();
var win = window.open("", name, "width=700,height=500,resizable=yes,scrollbars=yes");
win.document.location = "script/test/" + name + ".js";
}
var readyToRun;
function setupAdminParty(fun) {
if (readyToRun) {
fun();
} else {
function removeAdmins(confs, doneFun) {
// iterate through the config and remove current user last
// current user is at front of list
var remove = confs.pop();
if (remove) {
$.couch.config({
success : function() {
removeAdmins(confs, doneFun);
}
}, "admins", remove[0], null);
} else {
doneFun();
}
};
$.couch.session({
success : function(resp) {
var userCtx = resp.userCtx;
if (userCtx.name && userCtx.roles.indexOf("_admin") != -1) {
// admin but not admin party. dialog offering to make admin party
$.showDialog("dialog/_admin_party.html", {
submit: function(data, callback) {
$.couch.config({
success : function(conf) {
var meAdmin, adminConfs = [];
for (var name in conf) {
if (name == userCtx.name) {
meAdmin = [name, conf[name]];
} else {
adminConfs.push([name, conf[name]]);
}
}
adminConfs.unshift(meAdmin);
removeAdmins(adminConfs, function() {
callback();
$.futon.session.sidebar();
readyToRun = true;
setTimeout(fun, 500);
});
}
}, "admins");
}
});
} else if (userCtx.roles.indexOf("_admin") != -1) {
// admin party!
readyToRun = true;
fun();
} else {
// not an admin
alert("Error: You need to be an admin to run the tests.");
};
}
});
}
};
function updateTestsListing() {
for (var name in couchTests) {
var testFunction = couchTests[name];
var row = $("<tr><th></th><td></td><td></td><td></td></tr>")
.find("th").text(name).attr("title", "Show source").click(function() {
showSource(this);
}).end()
.find("td:nth(0)").addClass("status").text("not run").end()
.find("td:nth(1)").addClass("duration").end()
.find("td:nth(2)").addClass("details").end();
$("<button type='button' class='run' title='Run test'></button>").click(function() {
this.blur();
var self = this;
// check for admin party
setupAdminParty(function() {
runTest(self);
});
return false;
}).prependTo(row.find("th"));
row.attr("id", name).appendTo("#tests tbody.content");
}
$("#tests tr").removeClass("odd").filter(":odd").addClass("odd");
updateTestsFooter();
}
function updateTestsFooter() {
var tests = $("#tests tbody.content tr td.status");
var testsRun = tests.filter(".success, .error, .failure");
var testsFailed = testsRun.not(".success");
var totalDuration = 0;
$("#tests tbody.content tr td.duration:contains('ms')").each(function() {
var text = $(this).text();
totalDuration += parseInt(text.substr(0, text.length - 2), 10);
});
$("#tests tbody.footer td").html("<span>"+testsRun.length + " of " + tests.length +
" test(s) run, " + testsFailed.length + " failures (" +
totalDuration + " ms)</span> ");
}
// make report and save to local db
// display how many reports need replicating to the mothership
// have button to replicate them
function saveTestReport(report) {
var report = makeTestReport();
if (report) {
var db = $.couch.db("test_suite_reports");
var saveReport = function(db_info) {
report.db = db_info;
$.couch.info({success : function(node_info) {
report.node = node_info;
db.saveDoc(report);
}});
};
var createDb = function() {
db.create({success: function() {
db.info({success:saveReport});
}});
};
db.info({error: createDb, success:saveReport});
}
};
function makeTestReport() {
var report = {};
report.summary = $("#tests tbody.footer td").text();
report.platform = testPlatform();
var date = new Date();
report.timestamp = date.getTime();
report.timezone = date.getTimezoneOffset();
report.tests = [];
$("#tests tbody.content tr").each(function() {
var status = $("td.status", this).text();
if (status != "not run") {
var test = {};
test.name = this.id;
test.status = status;
test.duration = parseInt($("td.duration", this).text());
test.details = [];
$("td.details li", this).each(function() {
test.details.push($(this).text());
});
if (test.details.length == 0) {
delete test.details;
}
report.tests.push(test);
}
});
if (report.tests.length > 0) return report;
};
function testPlatform() {
var b = $.browser;
var bs = ["mozilla", "msie", "opera", "safari"];
for (var i=0; i < bs.length; i++) {
if (b[bs[i]]) {
return {"browser" : bs[i], "version" : b.version};
}
};
return {"browser" : "undetected"};
}
function reportTests() {
// replicate the database to couchdb.couchdb.org
}
// Use T to perform a test that returns false on failure and if the test fails,
// display the line that failed.
// Example:
// T(MyValue==1);
function T(arg1, arg2, testName) {
if (!arg1) {
if (currentRow) {
if ($("td.details ol", currentRow).length == 0) {
$("<ol></ol>").appendTo($("td.details", currentRow));
}
var message = (arg2 != null ? arg2 : arg1).toString();
$("<li><b>Assertion " + (testName ? "'" + testName + "'" : "") + " failed:</b> <code class='failure'></code></li>")
.find("code").text(message).end()
.appendTo($("td.details ol", currentRow));
}
numFailures += 1;
}
}
function TIsnull(actual, testName) {
T(actual === null, "expected 'null', got '"
+ repr(actual) + "'", testName);
}
function TEquals(expected, actual, testName) {
T(equals(expected, actual), "expected '" + repr(expected) +
"', got '" + repr(actual) + "'", testName);
}
function TNotEquals(expected, actual, testName) {
T(notEquals(expected, actual), "expected != '" + repr(expected) +
"', got '" + repr(actual) + "'", testName);
}
function TEqualsIgnoreCase(expected, actual, testName) {
T(equals(expected.toUpperCase(), actual.toUpperCase()), "expected '" + repr(expected) +
"', got '" + repr(actual) + "'", testName);
}
function equals(a,b) {
if (a === b) return true;
try {
return repr(a) === repr(b);
} catch (e) {
return false;
}
}
function notEquals(a,b) {
if (a != b) return true;
return false;
}
function repr(val) {
if (val === undefined) {
return null;
} else if (val === null) {
return "null";
} else {
return JSON.stringify(val);
}
}
function makeDocs(start, end, templateDoc) {
var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}";
if (end === undefined) {
end = start;
start = 0;
}
var docs = [];
for (var i = start; i < end; i++) {
var newDoc = eval("(" + templateDocSrc + ")");
newDoc._id = (i).toString();
newDoc.integer = i;
newDoc.string = (i).toString();
docs.push(newDoc);
}
return docs;
}
function run_on_modified_server(settings, fun) {
var xhr = CouchDB.request("GET", "/_membership");
var nodes = JSON.parse(xhr.responseText).all_nodes;
try {
// set the settings
for(var i=0; i < settings.length; i++) {
var s = settings[i];
for (var n in nodes) {
xhr = CouchDB.request("PUT", "/_node/" + nodes[n] + "/_config/" + s.section + "/" + s.key, {
body: JSON.stringify(s.value),
headers: {"X-Couch-Persist": "false"}
});
CouchDB.maybeThrowError(xhr);
if (typeof s[nodes[n]] === 'undefined') {
s[nodes[n]] = {};
}
s[nodes[n]] = xhr.responseText;
}
}
// run the thing
fun();
} finally {
// unset the settings
for(var j=0; j < i; j++) {
for (var n in nodes) {
var s = settings[j];
if(s[nodes[n]] == "\"\"\n") { // unset value
CouchDB.request("DELETE", "/_node/" + nodes[n] + "/_config/" + s.section + "/" + s.key, {
headers: {"X-Couch-Persist": "false"}
});
} else {
CouchDB.request("PUT", "/_node/" + nodes[n] + "/_config/" + s.section + "/" + s.key, {
body: s[nodes[n]],
headers: {"X-Couch-Persist": "false"}
});
}
}
}
}
}
function stringFun(fun) {
var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
return string;
}
function waitForSuccess(fun, tag) {
var start = new Date();
while(true) {
if (new Date() - start > 5000) {
throw("timeout: "+tag);
} else {
try {
fun();
break;
} catch (e) {}
// sync http req allow async req to happen
try {
CouchDB.request("GET", "/test_suite_db/?tag="+encodeURIComponent(tag));
} catch (e) {}
}
}
}
// legacy functions for CouchDB < 1.2.0
// we keep them to make sure we keep BC
CouchDB.user_prefix = "org.couchdb.user:";
CouchDB.prepareUserDoc = function(user_doc, new_password) {
user_doc._id = user_doc._id || CouchDB.user_prefix + user_doc.name;
if (new_password) {
user_doc.password = new_password;
}
user_doc.type = "user";
if (!user_doc.roles) {
user_doc.roles = [];
}
return user_doc;
};
function get_random_string() {
return Math.random()
.toString(36)
.replace(/[^a-z]+/g, '')
.substr(0, 8);
}
function get_random_db_name() {
return "test_suite_db_" + get_random_string()
}
// for Heisenbug-prone spots: retry n times (e.g. quora not met immediately)
// if the problem still persists afterwards, we need sth else (similar to e.g. webdriver)
function retry_part(fct, n, duration) {
n = n || 3;
duration = (duration == undefined ? 100 : duration);
for(var i=1; i<=n; i++){
try {
return fct();
}catch(e){
if(i<n){
// wait
sleep(duration);
}else{
throw e;
}
}
}
}
function wait(ms) {
sleep(ms);
}