var XULTestRunner = Class.create({ initialize: function(options) { this.options = options; this._testResults = []; this.appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); }, handleExit: function (exitWithFailure) { if (exitWithFailure) { puts("Test Failures!"); // We kill ourselves this way because we need a non-zero exit status. This is really hacky. // Really, really hacky. var envFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); envFile.initWithPath("/usr/bin/env"); var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); process.init(envFile); process.run(true, ["ruby", "-e", "Process.kill(9, Process.ppid)"], 3); } this.appStartup.quit(Ci.nsIAppStartup.eForceQuit); }, go: function () { this._registerObservers(); var testsToRun = $A(this.options.tests).collect(function(testFile) { return '"' + testFile.path + '"'; }); puts("Running tests..."); puts(testsToRun.join(" ") + "\n"); this._runNextTest(); }, observe: function(subject, topic, data) { // Need to pass between observers as JSON to avoid crashes. Wee. this._testResults.push(data.evalJSON()); this._runNextTest(); }, _runNextTest: function() { var testFile = this.options.tests.shift(); if (testFile) { this._runTest(testFile); } else { this._finishTestRun() } }, _runTest: function(testFile) { var qs = Object.toQueryString({testFile: encodeURI(testFile.path)}); var harnessWithPath = this.options.harnessURI + "?" + qs; this.options.browser.loadURI(harnessWithPath); }, _finishTestRun: function() { print("\n\n"); var exitWithFailure = this._doSummary(); this.handleExit(exitWithFailure); }, _doSummary: function() { var testRunners = 0, testCases = 0, unpassedLength = 0, assertions = 0, failures = 0, errors = 0; $A(this._testResults).each(function(testResult) { $A(testResult.unpassedTests).each(function(unpassedTest) { unpassedLength += 1; this._showSummaryForTestResult(testResult.name, unpassedTest, unpassedLength); }, this); testRunners++; testCases += testResult.testLength; assertions += testResult.summary.assertions; failures += testResult.summary.failures; errors += testResult.summary.errors; }, this); var overallSummary = "\n\n#{runners} runners, #{cases} cases, #{unpassed} unpassed, #{assertions} assertions, #{failures} failures, #{errors} errors.\n"; puts(overallSummary.interpolate({runners: testRunners, cases: testCases, unpassed: unpassedLength, assertions: assertions, failures: failures, errors: errors})); return unpassedLength > 0; }, _showSummaryForTestResult: function(testName, testResult, failureIndex) { puts("#{index}) #{testName} - #{name}".interpolate({index: failureIndex, testName: testName, name: testResult.name})); puts(" " + testResult.status.toUpperCase() + ":\n"); puts(testResult.summary.replace(/^/mg, " ") + "\n\n"); }, _registerObservers: function() { var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); observerService.addObserver(this, "xultestrunner-testdone", false); } }); Object.extend(XULTestRunner, { testFileMatcher: /_test\.js$/, initializeFromCmdArgs: function(cmdArgs, options) { if (cmdArgs.testDir) { var tests = this._findTestsFromDir(cmdArgs.testDir); } else if (cmdArgs.testFile) { var tests = [cmdArgs.testFile]; } return new this(Object.extend({tests: $A(tests)}, options)); }, _findTestsFromDir: function(rootDir) { var testFiles = []; var entries = rootDir.directoryEntries; while (entries.hasMoreElements()) { var entry = entries.getNext().QueryInterface(Ci.nsIFile); if (entry.isDirectory()) { testFiles = testFiles.concat(this._findTestsFromDir(entry)); } else if (this.testFileMatcher.test(entry.path)) { testFiles.push(entry); } } return testFiles; } });