123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- /* appjet:version 0.1 */
- /* appjet:library */
- // Copyright (c) 2009, Herbert Vojčík
- // Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)
- /**
- * @overview This is a library that runs unit tests
- * and prints the results. It is stripped-down port
- * of existing jsunit 2.2 test runner. So far, it only has
- * html rendering of test results in a table.
- * It honours setUp and tearDown functions and treats
- * any function that begins with "test" as test function.
- * You can write setUp, tearDown and testXxx function
- * with prepended underscore (to ignore them in docs).
- * The runner works with both alternatives, though,
- * it's behaviour is undefined if you mix both forms
- * for the same function name.
- *
- * @example
- im port("lib-jsunit22");
- function _testXxx() { ... }
- ...
- import("lib-testrunner");
- if (you want auto redirection) { redirectTestRunnerResults(); }
- if (you want footer link) { setTestRunnerFooterLink(); }
- */
- _runner = {
- start: function () {
- this._clear();
- var timeRunStarted = new Date();
- this._runAllTests();
- this.timeTaken = (new Date() - timeRunStarted) / 1000;
- },
- _clear: function() {
- this.totalCount = 0;
- this.errorCount = 0;
- this.failureCount = 0;
- this.results = [];
- },
- _runAllTests: function () {
- this.addResults(new _suite().runAllTests());
- },
- addResults: function (suiteRawResults) {
- var self = this;
- suiteRawResults.forEach(function(rawResult) {
- var result = {
- name: rawResult.test.name,
- time: rawResult.time,
- status: "S",
- message: "passed",
- altName: rawResult.test.testName
- };
- self.totalCount++;
- if (rawResult.exception != null) {
- if (_isTestException(rawResult.exception)) {
- result.status = "F";
- self.failureCount++;
- } else {
- result.status = "E";
- self.errorCount++;
- }
- result.message = _problemDetailMessageFor(rawResult.exception);
- }
- self.results.push(result);
- });
- }
- };
- function _problemDetailMessageFor(excep) {
- var result;
- if (_isTestException(excep)) {
- result = '';
- if (excep.comment != null)
- result += ('"' + excep.comment + '"\n');
- result += excep.testMessage;
- if (excep.stackTrace)
- result += '\n\nStack trace follows:\n' + excep.stackTrace;
- } else {
- result = 'Error message is:\n"';
- result +=
- (typeof(excep.description) == 'undefined') ?
- excep : excep.description;
- result += '"';
- if (typeof(excep.stack) != 'undefined') // Mozilla only
- result += '\n\nStack trace follows:\n' + excep.stack;
- }
- return result;
- }
- function _suite() {
- this.tests = [].concat(page.testSuite);
- this.setup = _find(this.tests, "setUp") || function() {};
- this.teardown = _find(this.tests, "tearDown") || function() {};
- }
- function _find(array, name) {
- var index = -1;
- // array.every(function(value, i) { if (value.name === name) { index = i; return false; } return true; });
- array.some(function(value, i) { if (value.name === name) { index = i; return true; } return false; });
- if (index !== -1) { return array.splice(index, 1)[0]; }
- }
- _suite.prototype = {
- runAllTests: function() {
- var self = this;
- return this.tests.map(function(test) { return self.executeTestFunction(test); });
- },
- executeTestFunction: function (test) {
- var excep;
- var timeBefore = new Date();
- try {
- this.setup();
- test();
- }
- catch (e1) {
- excep = e1;
- }
- try {
- this.teardown();
- }
- catch (e2) {
- //Unlike JUnit, only assign a tearDown exception to excep if there is not already an exception from the test body
- if (!excep) { excep = e2; }
- }
- var time = (new Date() - timeBefore) / 1000;
- return {test:test, time:time, exception: excep};
- },
- executeUnsafeByName: function (name) {
- var run = false;
- var self = this;
- this.tests.forEach(function (test) {
- if (test.name === name) {
- run = true;
- self.setup();
- test();
- self.teardown();
- }
- });
- if (!run) { throw new Error("Test function <" + name + "> not found."); }
- }
- };
- function _isTestException(exception) {
- return exception.isTestException === true;
- }
- /**
- * Runs the tests and fills the results.
- * You don not need to call it directly,
- * it is called when path is /testrunner/results.
- */
- function runTheTests() {
- _runner.start();
- }
- /**
- * This function renders test results, in normal html.
- * If argument is true, failed tests are rendered with
- * links to /testrunner/test?RestOfTheName in status field.
- * It is called automatically when path is /testrunner/results.
- * @see get_testrunner_test
- */
- function renderTestsAsHtml(links) {
- page.setTitle(appjet.appName+" Test Runner");
- page.head.write('<style type="text/css">');
- page.head.write(_testRunnerCss);
- page.head.write('</style>');
- print(P(
- {className:"testresult"+(
- _runner.errorCount?"E":_runner.failureCount?"F":"S")},
- _runner.totalCount, " test(s) run, ",
- _runner.errorCount, " error(s), ",
- _runner.failureCount, " failure(s)."
- ));
- var table = UL({className:"testresults"});
- for (var ri in _runner.results) {
- var result = _runner.results[ri],
- testStatus = result.status;
- testMessage = "";
- if (links && testStatus != "S") {
- testStatus = A({href:"/testrunner/test?"+result.name}, testStatus);
- testMessage = UL(LI(result.message));
- }
- table.push(LI(
- SPAN({className:"testresult"+result.status},"[",testStatus,"]"),
- " ",
- result.altName || _nameToTestCase(result.name),
- testMessage
- ));
- }
- print(table);
- }
- function _nameToTestCase(name) {
- var n = name.replace(/([A-Z])/g, " $1").replace("_", ", ","g").replace(/ /g, " ");
- return n.charAt(0).toUpperCase()+n.substring(1);
- }
- var _testRunnerCss = "" +
- "table.testresults td, table.testresults th { border: 1px solid silver; padding-left: 1ex; padding-right: 1ex }\n" +
- ".testresultS { background-color: lightgreen }\n" +
- ".testresultF { background-color: yellow }\n" +
- ".testresultE { background-color: red }\n" +
- "td.testcase, td.testmessage { text-align: left }\n" +
- "td.teststatus { text-align: center }\n";
- /** Overwrites default CSS used when rendering tests. */
- function setTestRunnerCss(css) {
- _testRunnerCss = css;
- }
- if (appjet.isPreview) { setTestRunnerFooterLink(); }
- switch (request.path) {
- case '/testrunner/test':
- new _suite().executeUnsafeByName(request.query);
- response.stop();
- break;
- case '/testrunner/results':
- runTheTests();
- renderTestsAsHtml(true);
- response.stop();
- break;
- }
- /**
- * Redirects from / to /testrunner/results or returns if path is different than /.
- */
- function runTestsByDefault() {
- if (request.path === "/") { response.redirect("/testrunner/results"); }
- }
- /**
- * Puts footer link to testrunner results.
- * Called automatically in preview. It is safe to call it more times
- * (you can call it again without checking isPreview).
- */
- function setTestRunnerFooterLink() {
- import({}, "lib-support/footer-links").setFooterLink("/testrunner/results", "Test Runner");
- }
- /* appjet:server */
- var _globalEx = new Error("setUp probably wasn't started");
- function thr(x) { if (x) throw x; }
- page.testSuite = [
- function setUp() { _globalEx = null; },
- function tearDown() { thr(_globalEx); },
- function so_NothingBadHappens_ThatsIt()
- { var ex = _globalEx; _globalEx = null; thr(ex); },
- function errorIsRaised_InsideTest()
- { throw new Error("Intentional error"); },
- function failureIsRaised_InsideTest() {
- error = new Error();
- error.isTestException = true;
- error.testMessage = "Intentional failure";
- throw error;
- },
- function errorIsRaised_InTearDown()
- { _globalEx = new Error("Intentional error in tearDown"); },
- function failureIsRaised_InTearDown() {
- _globalEx = new Error();
- _globalEx.isTestException = true;
- _globalEx.testMessage = "Intentional failure in tearDown";
- },
- ];
- import("lib-support/runner");
- runTestsByDefault();
- setTestRunnerFooterLink();
|