// 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 if((url.substr(0, 7) == "http://") || (url.substr(0, 2) == "//") || (url.substr(0, 5) == "data:") || (url.substr(0, 11) == "javascript:")) { throw "Not loading remote test scripts"; } if (typeof document != "undefined") document.write(''); }; 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) { $("
    ").appendTo($("td.details", row)); } $("
  1. Exception raised:
  2. ") .find("code").text(JSON.stringify(e)).end() .appendTo($("td.details ol", row)); if (debug) { currentRow = null; throw e; } } if ($("td.details ol", row).length) { $("Run with debugger").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 = $("") .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(); $("").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(""+testsRun.length + " of " + tests.length + " test(s) run, " + testsFailed.length + " failures (" + totalDuration + " ms) "); } // 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) { $("
      ").appendTo($("td.details", currentRow)); } var message = (arg2 != null ? arg2 : arg1).toString(); $("
    1. Assertion " + (testName ? "'" + testName + "'" : "") + " failed:
    2. ") .find("code").text(message).end() .appendTo($("td.details ol", currentRow)); } numFailures += 1; } } function TEquals(expected, actual, testName) { T(equals(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 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) { try { // set the settings for(var i=0; i < settings.length; i++) { var s = settings[i]; var xhr = CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, { body: JSON.stringify(s.value), headers: {"X-Couch-Persist": "false"} }); CouchDB.maybeThrowError(xhr); s.oldValue = xhr.responseText; } // run the thing fun(); } finally { // unset the settings for(var j=0; j < i; j++) { var s = settings[j]; if(s.oldValue == "\"\"\n") { // unset value CouchDB.request("DELETE", "/_config/" + s.section + "/" + s.key, { headers: {"X-Couch-Persist": "false"} }); } else { CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, { body: s.oldValue, 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 CouchDB.request("GET", "/test_suite_db/?tag="+encodeURIComponent(tag)); } } } function waitForRestart() { var waiting = true; // Wait for the server to go down but don't // wait too long because we might miss the // unavailable period. var count = 25; while (waiting && count > 0) { count--; try { CouchDB.request("GET", "/"); } catch(e) { waiting = false; } } // Wait for it to come back up waiting = true; while (waiting) { try { CouchDB.request("GET", "/"); waiting = false; } catch(e) { // the request will fail until restart completes } } }; function restartServer() { var xhr; try { CouchDB.request("POST", "/_restart"); } catch(e) { // this request may sometimes fail } waitForRestart(); }