diff --git a/lib/RunnerFork.js b/lib/RunnerFork.js new file mode 100644 index 000000000..1384f078d --- /dev/null +++ b/lib/RunnerFork.js @@ -0,0 +1,122 @@ +var child = require('child_process'); +var q = require('q'); +var TaskLogger = require('./TaskLogger.js'); +var EventEmitter = require('events').EventEmitter; +var util = require('util'); + + +/** + * A fork of a runner for running a specified task. The RunnerFork will + * start a new process that calls on '/runFromLauncher.js'. + * + * @constructor + * @param {object} task Task to run. + */ +var RunnerFork = function(configFile, additionalConfig, task, runInFork) { + this.configFile = configFile; + this.additionalConfig = additionalConfig; + this.task = task; + this.runInFork = runInFork; +}; +util.inherits(RunnerFork, EventEmitter); + +/** + * Sends the run command. + */ +RunnerFork.prototype.run = function() { + var self = this; + + var runResults = { + taskId: this.task.taskId, + specs: this.task.specs, + capabilities: this.task.capabilities, + // The following are populated while running the test: + failedCount: 0, + exitCode: -1, + specResults: [] + } + + if (this.runInFork) { + var deferred = q.defer(); + + var childProcess = child.fork( + __dirname + '/runFromLauncher.js', + process.argv.slice(2), { + cwd: process.cwd(), + silent: true + } + ); + var taskLogger = new TaskLogger(this.task, childProcess.pid); + + // stdout pipe + childProcess.stdout.on('data', function(data) { + taskLogger.log(data); + }); + + // stderr pipe + childProcess.stderr.on('data', function(data) { + taskLogger.log(data); + }); + + childProcess.on('message', function(m) { + switch (m.event) { + case 'testPass': + process.stdout.write('.'); + break; + case 'testFail': + process.stdout.write('F'); + break; + case 'testsDone': + runResults.failedCount = m.results.failedCount; + runResults.specResults = m.results.specResults; + break; + } + }) + .on('error', function(err) { + taskLogger.flush(); + deferred.reject(err); + }) + .on('exit', function(code) { + taskLogger.flush(); + runResults.exitCode = code; + deferred.resolve(runResults); + }); + + childProcess.send({ + command: 'run', + configFile: this.configFile, + additionalConfig: this.additionalConfig, + capabilities: this.task.capabilities, + specs: this.task.specs + }); + + return deferred.promise; + } else { + var ConfigParser = require('./configParser'); + var configParser = new ConfigParser(); + if (this.configFile) { + configParser.addFileConfig(this.configFile); + } + if (this.additionalConfig) { + configParser.addConfig(this.additionalConfig); + } + var config = configParser.getConfig(); + config.capabilities = this.task.capabilities; + config.specs = this.task.specs; + + var Runner = require('./runner'); + var runner = new Runner(config); + + runner.on('testsDone', function(results) { + runResults.failedCount = results.failedCount; + runResults.specResults = results.specResults; + }) + + return runner.run().then(function(exitCode) { + runResults.exitCode = exitCode; + return runResults; + }); + } +}; + +module.exports = RunnerFork; diff --git a/lib/TaskLogger.js b/lib/TaskLogger.js new file mode 100644 index 000000000..ab3744d60 --- /dev/null +++ b/lib/TaskLogger.js @@ -0,0 +1,88 @@ +var EOL = require('os').EOL; + +/** + * Log output such that metadata are appended. + * Calling log(data) will not flush to console until you call flush() + * + * @constructor + * @param {object} task Task that is being reported. + * @param {number} pid PID of process running the task. + */ +var TaskLogger = function(task, pid) { + this.task = task; + this.pid = pid; + this.buffer = ''; + this.insertTag = true; + + this.logHeader_(); +}; + +/** + * Report the header for the current task including information such as + * PID, browser name/version, task Id, specs being run. + * + * @private + */ +TaskLogger.prototype.logHeader_ = function() { + var output = 'PID: ' + this.pid + EOL; + if (this.task.specs.length === 1) { + output += 'Specs: '+ this.task.specs.toString() + EOL + EOL; + } + this.log(output); +}; + + +/** + * Flushes the buffer to stdout. + */ +TaskLogger.prototype.flush = function() { + if (this.buffer) { + // Flush buffer if nonempty + process.stdout.write(EOL + '------------------------------------' + EOL); + process.stdout.write(this.buffer); + process.stdout.write(EOL); + this.buffer = ''; + } +}; + +/** + * Log the data in the argument. The data will be saved to a buffer + * until flush() is called. + * + * @param {string} data + */ +TaskLogger.prototype.log = function(data) { + var tag = '['; + var capabilities = this.task.capabilities; + tag += (capabilities.browserName) ? + capabilities.browserName : ''; + tag += (capabilities.version) ? + (' ' + capabilities.version) : ''; + tag += (capabilities.platform) ? + (' ' + capabilities.platform) : ''; + tag += (' #' + this.task.taskId); + tag += '] '; + + data = data.toString(); + for ( var i = 0; i < data.length; i++ ) { + if (this.insertTag) { + this.insertTag = false; + // This ensures that the '\x1B[0m' appears before the tag, so that + // data remains correct when color is not processed. + // See https://github.com/angular/protractor/pull/1216 + if (data[i] === '\x1B' && data.substring(i, i+4) === '\x1B[0m' ) { + this.buffer += ('\x1B[0m' + tag); + i += 3; + continue; + } + + this.buffer += tag; + } + if (data[i] === '\n') { + this.insertTag = true; + } + this.buffer += data[i]; + } +}; + +module.exports = TaskLogger; diff --git a/lib/cli.js b/lib/cli.js index b9fe16361..e47e66759 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -50,6 +50,7 @@ var optimist = require('optimist'). describe('stackTrace', 'Print stack trace on error'). describe('params', 'Param object to be passed to the tests'). describe('framework', 'Test framework to use: jasmine, cucumber or mocha'). + describe('testResultOutput', 'Path to save JSON test result'). alias('browser', 'capabilities.browserName'). alias('name', 'capabilities.name'). alias('platform', 'capabilities.platform'). diff --git a/lib/frameworks/jasmine.js b/lib/frameworks/jasmine.js index 4cae42b82..747418936 100644 --- a/lib/frameworks/jasmine.js +++ b/lib/frameworks/jasmine.js @@ -13,6 +13,8 @@ exports.run = function(runner, specs) { require('jasminewd'); /* global jasmine */ + testResult = []; + var RunnerReporter = function(emitter) { this.emitter = emitter; }; @@ -20,13 +22,29 @@ exports.run = function(runner, specs) { RunnerReporter.prototype.reportRunnerStarting = function() {}; RunnerReporter.prototype.reportRunnerResults = function() {}; RunnerReporter.prototype.reportSuiteResults = function() {}; - RunnerReporter.prototype.reportSpecStarting = function() {}; + RunnerReporter.prototype.reportSpecStarting = function() { + this.startTime = new Date(); + }; RunnerReporter.prototype.reportSpecResults = function(spec) { if (spec.results().passed()) { this.emitter.emit('testPass'); } else { this.emitter.emit('testFail'); } + + var entry = { + description: spec.results().description, + result: [], + duration: new Date().getTime() - this.startTime.getTime() + }; + spec.results().getItems().forEach(function(item) { + entry.result.push({ + passed: item.passed(), + errorMsg: item.passed() ? undefined : item.message, + stacktrace: item.passed() ? undefined : item.trace.stack + }); + }); + testResult.push(entry); }; RunnerReporter.prototype.log = function() {}; @@ -47,7 +65,10 @@ exports.run = function(runner, specs) { if (originalOnComplete) { originalOnComplete(jasmineRunner, log); } - resolve(jasmineRunner.results()); + resolve({ + failedCount: jasmineRunner.results().failedCount, + specResults: testResult + }); } catch(err) { reject(err); } diff --git a/lib/frameworks/mocha.js b/lib/frameworks/mocha.js index b7c5c443c..c2742bfe5 100644 --- a/lib/frameworks/mocha.js +++ b/lib/frameworks/mocha.js @@ -35,19 +35,11 @@ exports.run = function(runner, specs) { global.it.only = global.iit = originalOnly; global.it.skip = global.xit = mochaAdapters.xit; - }catch(err){ + } catch(err) { deferred.reject(err); } }); - mocha.suite.on('pass', function() { - runner.emit('testPass'); - }); - - mocha.suite.on('fail', function() { - runner.emit('testFail'); - }); - mocha.loadFiles(); runner.runTestPreparer().then(function() { @@ -56,21 +48,51 @@ exports.run = function(runner, specs) { mocha.addFile(file); }); - mocha.run(function(failures) { + var testResult = []; + + var mochaRunner = mocha.run(function(failures) { try { if (runner.getConfig().onComplete) { runner.getConfig().onComplete(); } var resolvedObj = { - failedCount: failures + failedCount: failures, + }; - deferred.resolve(resolvedObj); - }catch(err){ + deferred.resolve({ + failedCount: failures, + specResults: testResult + }); + } catch(err) { deferred.reject(err); } }); - }).catch(function(reason){ + + mochaRunner.on('pass', function(test) { + runner.emit('testPass'); + testResult.push({ + description: test.title, + result: [{ + passed: true + }], + duration: test.duration + }); + }); + + mochaRunner.on('fail', function(test) { + runner.emit('testFail'); + testResult.push({ + description: test.title, + result: [{ + passed: false, + errorMsg: test.err.message, + stacktrace: test.err.stack + }], + duration: test.duration + }); + }); + }).catch(function(reason) { deferred.reject(reason); }); diff --git a/lib/launcher.js b/lib/launcher.js index ac044294e..ebda26526 100644 --- a/lib/launcher.js +++ b/lib/launcher.js @@ -4,13 +4,12 @@ */ 'use strict'; -var child = require('child_process'), - ConfigParser = require('./configParser'), +var ConfigParser = require('./configParser'), TaskScheduler = require('./taskScheduler'), helper = require('./util'), q = require('q'); -var launcherPrefix = '[launcher]'; +var logPrefix = '[launcher]'; var RUNNERS_FAILED_EXIT_CODE = 100; /** @@ -19,7 +18,7 @@ var RUNNERS_FAILED_EXIT_CODE = 100; * @private */ var log_ = function() { - var args = [launcherPrefix].concat([].slice.call(arguments)); + var args = [logPrefix].concat([].slice.call(arguments)); console.log.apply(console, args); }; @@ -40,100 +39,15 @@ var init = function(configFile, additionalConfig) { var config = configParser.getConfig(); var scheduler = new TaskScheduler(config); - /** - * A fork of a runner for running a specified task. The RunnerFork will - * start a new process that calls on '/runFromLauncher.js' and report the - * result to a reporter. - * - * @constructor - * @param {object} task Task to run. - */ - var RunnerFork = function(task) { - this.capability = task.capability; - this.specs = task.specs; - this.process = child.fork( - __dirname + '/runFromLauncher.js', - process.argv.slice(2), { - cwd: process.cwd(), - silent: true - } - ); - this.reporter = reporter.addTaskReporter(task, this.process.pid); - }; - - /** - * Add handlers for the RunnerFork for events like stdout, stderr, testsDone, - * testPass, testFail, error, and exit. Optionally, you can pass in a - * callback function to be called when a test completes. - * - * @param {function()} testsDoneCallback Callback function for testsDone events. - */ - RunnerFork.prototype.addEventHandlers = function(testsDoneCallback) { - var self = this; - - // stdout pipe - this.process.stdout.on('data', function(chunk) { - self.reporter.logStdout(chunk); - }); - - // stderr pipe - this.process.stderr.on('data', function(chunk) { - self.reporter.logStderr(chunk); - }); - - this.process.on('message', function(m) { - switch (m.event) { - case 'testPass': - process.stdout.write('.'); - break; - case 'testFail': - process.stdout.write('F'); - break; - case 'testsDone': - self.reporter.testsDone(m.failedCount); - break; - } - }); - - this.process.on('error', function(err) { - self.reporter.flush(); - log_('Runner Process(' + self.process.pid + ') Error: ' + err); - self.reporter.exitCode = RUNNERS_FAILED_EXIT_CODE; - }); - - this.process.on('exit', function(code) { - self.reporter.flush(); - if (code) { - if (self.reporter.failedCount) { - log_('Test runner exited with ' + self.reporter.failedCount + - ' failed test(s)'); - } else { - log_('Runner process exited unexpectedly with error code: ' + code); - } - } - self.reporter.exitCode = code; - - if (typeof testsDoneCallback === 'function') { - testsDoneCallback(); - } - log_(scheduler.countActiveTasks() + - ' instance(s) of WebDriver still running'); - }); - }; - - /** - * Sends the run command. - */ - RunnerFork.prototype.run = function() { - this.process.send({ - command: 'run', - configFile: configFile, - additionalConfig: additionalConfig, - capability: this.capability, - specs: this.specs - }); - this.reporter.reportHeader_(); - }; + process.on('exit', function(code) { + if (code) { + log_('Process exited with error code ' + code); + } else if (scheduler.numTasksOutstanding() > 0) { + log_('BUG: launcher exited with ' + + scheduler.numTasksOutstanding() + ' tasks remaining'); + process.exit(RUNNERS_FAILED_EXIT_CODE); + } + }); var cleanUpAndExit = function(exitCode) { return helper.runFilenameOrFn_( @@ -153,242 +67,130 @@ var init = function(configFile, additionalConfig) { helper.runFilenameOrFn_(config.configDir, config.beforeLaunch).then(function() { // Don't start new process if there is only 1 task. var totalTasks = scheduler.numTasksOutstanding(); - if (totalTasks === 1) { - var Runner = require('./runner'); - var task = scheduler.nextTask(); - config.capabilities = task.capability; - config.specs = task.specs; - - var runner = new Runner(config); - runner.run().then(function(exitCode) { - cleanUpAndExit(exitCode); - }).catch(function(err) { - log_('Error:', err.stack || err.message || err); - cleanUpAndExit(RUNNERS_FAILED_EXIT_CODE); - }); - } else { + var forkProcess = false; + if (totalTasks > 1) { + forkProcess = true; if (config.debug) { throw new Error('Cannot run in debug mode with ' + 'multiCapabilities, count > 1, or sharding'); } - var deferred = q.defer(); - for (var i = 0; i < scheduler.maxConcurrentTasks(); ++i) { - var createNextRunnerFork = function() { - var task = scheduler.nextTask(); - if (task) { - var done = function() { - task.done(); - createNextRunnerFork(); - if (scheduler.numTasksOutstanding() === 0) { - deferred.fulfill(); - } - }; - var runnerFork = new RunnerFork(task); - runnerFork.addEventHandlers(done); - runnerFork.run(); + } + + var deferred = q.defer(); + var createNextRunnerFork = function() { + var task = scheduler.nextTask(); + if (task) { + var RunnerFork = require('./RunnerFork'); + var runnerFork = new RunnerFork(configFile, additionalConfig, task, forkProcess); + + runnerFork.run().then(function(result) { + if (result.exitCode && !result.failedCount) { + log_('Runner process exited unexpectedly with error code: ' + result.exitCode); } - }; - createNextRunnerFork(); + taskResults_.add(result); + task.done(); + createNextRunnerFork(); + if (scheduler.numTasksOutstanding() === 0) { + deferred.fulfill(); + } + log_(scheduler.countActiveTasks() + + ' instance(s) of WebDriver still running'); + }).catch(function(err) { + log_('Error:', err.stack || err.message || err); + cleanUpAndExit(RUNNERS_FAILED_EXIT_CODE); + }); } - log_('Running ' + scheduler.countActiveTasks() + ' instances of WebDriver'); + }; + for (var i = 0; i < scheduler.maxConcurrentTasks(); ++i) { + createNextRunnerFork(); + } + log_('Running ' + scheduler.countActiveTasks() + ' instances of WebDriver'); - deferred.promise.then(function() { - reporter.reportSummary(); - var exitCode = 0; - if (reporter.totalProcessFailures() > 0) { - exitCode = RUNNERS_FAILED_EXIT_CODE; - } else if (reporter.totalSpecFailures() > 0) { - exitCode = 1; - } - return cleanUpAndExit(exitCode); - }); + // By now all runners have completed. + deferred.promise.then(function() { + // Save results if desired + if (config.testResultOutput) { + taskResults_.saveResults(config.testResultOutput); + } - process.on('exit', function(code) { - if (code) { - log_('Process exited with error code ' + code); - } else if (scheduler.numTasksOutstanding() > 0) { - code = RUNNERS_FAILED_EXIT_CODE; - log_('BUG: launcher exited with ' + - scheduler.numTasksOutstanding() + ' tasks remaining'); - } - process.exit(code); - }); - } + taskResults_.reportSummary(); + var exitCode = 0; + if (taskResults_.totalProcessFailures() > 0) { + exitCode = RUNNERS_FAILED_EXIT_CODE; + } else if (taskResults_.totalSpecFailures() > 0) { + exitCode = 1; + } + return cleanUpAndExit(exitCode); + }).done(); }).done(); }; //###### REPORTER #######// /** - * Keeps track of a list of task reporters. Provides method to add a new - * reporter and to aggregate the reports into a summary. + * Keeps track of a list of test results. Provides method to add a new + * result and to aggregate the results into a summary. */ -var reporter = { - taskReporters_: [], +var taskResults_ = { + results_: [], - addTaskReporter: function(task, pid) { - var taskReporter = new TaskReporter_(task, pid); - this.taskReporters_.push(taskReporter); - return taskReporter; + add: function(result) { + this.results_.push(result); }, totalSpecFailures: function() { var specFailures = 0; - this.taskReporters_.forEach(function(taskReporter) { - specFailures += taskReporter.failedCount; + this.results_.forEach(function(result) { + specFailures += result.failedCount; }); return specFailures; }, totalProcessFailures: function() { var processFailures = 0; - this.taskReporters_.forEach(function(taskReporter) { - if (!taskReporter.failedCount && taskReporter.exitCode !== 0) { + this.results_.forEach(function(result) { + if (!result.failedCount && result.exitCode !== 0) { processFailures += 1; } }); return processFailures; }, + saveResults: function(filepath) { + var jsonOutput = []; + this.results_.forEach(function(result) { + jsonOutput = jsonOutput.concat(result.specResults); + }) + + var json = JSON.stringify(jsonOutput, null, ' '); + var fs = require('fs'); + fs.writeFileSync(filepath, json); + }, + reportSummary: function() { var specFailures = this.totalSpecFailures(); var processFailures = this.totalProcessFailures(); - this.taskReporters_.forEach(function(taskReporter) { - var capability = taskReporter.task.capability; - var shortName = (capability.browserName) ? capability.browserName : ''; - shortName += (capability.version) ? capability.version : ''; - shortName += (' #' + taskReporter.task.taskId); - if (taskReporter.failedCount) { - log_(shortName + ' failed ' + taskReporter.failedCount + ' test(s)'); - } else if (taskReporter.exitCode !== 0) { - log_(shortName + ' failed with exit code: ' + taskReporter.exitCode); + this.results_.forEach(function(result) { + var capabilities = result.capabilities; + var shortName = (capabilities.browserName) ? capabilities.browserName : ''; + shortName += (capabilities.version) ? capabilities.version : ''; + shortName += (' #' + result.taskId); + if (result.failedCount) { + log_(shortName + ' failed ' + result.failedCount + ' test(s)'); + } else if (result.exitCode !== 0) { + log_(shortName + ' failed with exit code: ' + result.exitCode); } else { log_(shortName + ' passed'); } }); - if (this.taskReporters_.length > 1) { - if (specFailures && processFailures) { - log_('overall: ' + specFailures + ' failed spec(s) and ' + - processFailures + ' process(es) failed to complete'); - } else if (specFailures) { - log_('overall: ' + specFailures + ' failed spec(s)'); - } else if (processFailures) { - log_('overall: ' + processFailures + ' process(es) failed to complete'); - } - } - } -}; - -/** - * A reporter for a specific task. - * - * @constructor - * @param {object} task Task that is being reported. - * @param {number} pid PID of process running the task. - */ -var TaskReporter_ = function(task, pid) { - this.task = task; - this.pid = pid; - this.failedCount = 0; - this.buffer = ''; - this.exitCode = -1; - this.insertTag = true; -}; - -/** - * Report the header for the current task including information such as - * PID, browser name/version, task Id, specs being run. - */ -TaskReporter_.prototype.reportHeader_ = function() { - var eol = require('os').EOL; - var output = 'PID: ' + this.pid + eol; - if (this.task.specs.length === 1) { - output += 'Specs: '+ this.task.specs.toString() + eol + eol; - } - this.log_(output); -}; - -/** - * Log the stdout. The reporter is responsible for reporting this data when - * appropriate. - * - * @param {string} stdout Stdout data to log - */ -TaskReporter_.prototype.logStdout = function(stdout) { - this.log_(stdout); -}; - -/** - * Log the stderr. The reporter is responsible for reporting this data when - * appropriate. - * - * @param {string} stderr Stderr data to log - */ -TaskReporter_.prototype.logStderr = function(stderr) { - this.log_(stderr); -}; - -/** - * Signal that the task is completed. This must be called at the end of a task. - * - * @param {number} failedCount Number of failures - */ -TaskReporter_.prototype.testsDone = function(failedCount) { - this.failedCount = failedCount; - this.flush(); -}; - -/** - * Flushes the buffer to stdout. - */ -TaskReporter_.prototype.flush = function() { - if (this.buffer) { - // Flush buffer if nonempty - var eol = require('os').EOL; - process.stdout.write(eol + '------------------------------------' + eol); - process.stdout.write(this.buffer); - process.stdout.write(eol); - this.buffer = ''; - } -}; -/** - * Report the following data. The data will be saved to a buffer - * until it is flushed by the function testsDone. - * - * @private - * @param {string} data - */ -TaskReporter_.prototype.log_ = function(data) { - var tag = '['; - var capability = this.task.capability; - tag += (capability.browserName) ? - capability.browserName : ''; - tag += (capability.version) ? - (' ' + capability.version) : ''; - tag += (capability.platform) ? - (' ' + capability.platform) : ''; - tag += (' #' + this.task.taskId); - tag += '] '; - - data = data.toString(); - for ( var i = 0; i < data.length; i++ ) { - if (this.insertTag) { - this.insertTag = false; - // This ensures that the '\x1B[0m' appears before the tag, so that - // data remains correct when color is not processed. - // See https://github.com/angular/protractor/pull/1216 - if (data[i] === '\x1B' && data.substring(i, i+4) === '\x1B[0m' ) { - this.buffer += ('\x1B[0m' + tag); - i += 3; - continue; - } - - this.buffer += tag; - } - if (data[i] === '\n') { - this.insertTag = true; + if (specFailures && processFailures) { + log_('overall: ' + specFailures + ' failed spec(s) and ' + + processFailures + ' process(es) failed to complete'); + } else if (specFailures) { + log_('overall: ' + specFailures + ' failed spec(s)'); + } else if (processFailures) { + log_('overall: ' + processFailures + ' process(es) failed to complete'); } - this.buffer += data[i]; } }; diff --git a/lib/runFromLauncher.js b/lib/runFromLauncher.js index 9eff8dbbb..3dd64d2d6 100644 --- a/lib/runFromLauncher.js +++ b/lib/runFromLauncher.js @@ -9,8 +9,8 @@ var Runner = require('./runner'); process.on('message', function(m) { switch (m.command) { case 'run': - if (!m.capability) { - throw new Error('Run message missing capability'); + if (!m.capabilities) { + throw new Error('Run message missing capabilities'); } // Merge in config file options. var configParser = new ConfigParser(); @@ -22,8 +22,8 @@ process.on('message', function(m) { } var config = configParser.getConfig(); - // Grab capability to run from launcher. - config.capabilities = m.capability; + // Grab capabilities to run from launcher. + config.capabilities = m.capabilities; //Get specs to be executed by this runner config.specs = m.specs; @@ -42,10 +42,10 @@ process.on('message', function(m) { event: 'testFail' }); }); - runner.on('testsDone', function(failedCount) { + runner.on('testsDone', function(results) { process.send({ event: 'testsDone', - failedCount: failedCount + results: results }); }); diff --git a/lib/runner.js b/lib/runner.js index 50db577a6..227b80799 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -190,7 +190,7 @@ Runner.prototype.run = function() { var self = this, driver, specs, - testResult; + testPassed; specs = this.config_.specs; @@ -248,11 +248,11 @@ Runner.prototype.run = function() { return require(frameworkPath).run(self, specs); // 4) Teardown }).then(function(result) { - self.emit('testsDone', result.failedCount); - testResult = result; + self.emit('testsDone', result); + testPassed = result.failedCount === 0; if (self.driverprovider_.updateJob) { return self.driverprovider_.updateJob({ - 'passed': testResult.failedCount === 0 + 'passed': testPassed }).then(function() { return self.driverprovider_.teardownEnv(); }); @@ -261,8 +261,7 @@ Runner.prototype.run = function() { } // 5) Exit process }).then(function() { - var passed = testResult.failedCount === 0; - var exitCode = passed ? 0 : 1; + var exitCode = testPassed ? 0 : 1; return self.exit_(exitCode); }).fin(function() { return gracefulShutdown(driver); diff --git a/lib/taskScheduler.js b/lib/taskScheduler.js index 205ab8273..656e74440 100644 --- a/lib/taskScheduler.js +++ b/lib/taskScheduler.js @@ -7,17 +7,17 @@ var ConfigParser = require('./configParser'); // A queue of specs for a particular capacity -var TaskQueue = function(capability, specLists) { - this.capability = capability; +var TaskQueue = function(capabilities, specLists) { + this.capabilities = capabilities; this.numRunningInstances = 0; - this.maxInstance = capability.maxInstances || 1; + this.maxInstance = capabilities.maxInstances || 1; this.specsIndex = 0; this.specLists = specLists; }; /** * A scheduler to keep track of specs that need running and their associated - * capability. It will suggest a task (combination of capability and spec) + * capabilities. It will suggest a task (combination of capabilities and spec) * to run while observing the following config rules: capabilities, * multiCapabilities, shardTestFiles, and maxInstance. * @@ -40,27 +40,27 @@ var TaskScheduler = function(config) { config.multiCapabilities = [config.capabilities]; } } else if (!config.multiCapabilities.length) { - // Default to chrome if no capability given + // Default to chrome if no capabilities given config.multiCapabilities = [{ browserName: 'chrome' }]; } var taskQueues = []; - config.multiCapabilities.forEach(function(capability) { - var capabilitySpecs = allSpecs; - if (capability.specs) { - var capabilitySpecificSpecs = ConfigParser.resolveFilePatterns( - capability.specs, false, config.configDir); - capabilitySpecs = capabilitySpecs.concat(capabilitySpecificSpecs); + config.multiCapabilities.forEach(function(capabilities) { + var capabilitiesSpecs = allSpecs; + if (capabilities.specs) { + var capabilitiesSpecificSpecs = ConfigParser.resolveFilePatterns( + capabilities.specs, false, config.configDir); + capabilitiesSpecs = capabilitiesSpecs.concat(capabilitiesSpecificSpecs); } - if (capability.exclude) { - var capabilitySpecExcludes = ConfigParser.resolveFilePatterns( - capability.exclude, true, config.configDir); - capabilitySpecs = ConfigParser.resolveFilePatterns( - capabilitySpecs).filter(function(path) { - return capabilitySpecExcludes.indexOf(path) < 0; + if (capabilities.exclude) { + var capabilitiesSpecExcludes = ConfigParser.resolveFilePatterns( + capabilities.exclude, true, config.configDir); + capabilitiesSpecs = ConfigParser.resolveFilePatterns( + capabilitiesSpecs).filter(function(path) { + return capabilitiesSpecExcludes.indexOf(path) < 0; }); } @@ -68,18 +68,18 @@ var TaskScheduler = function(config) { // If we shard, we return an array of one element arrays, each containing // the spec file. If we don't shard, we return an one element array // containing an array of all the spec files - if (capability.shardTestFiles) { - capabilitySpecs.forEach(function(spec) { + if (capabilities.shardTestFiles) { + capabilitiesSpecs.forEach(function(spec) { specLists.push([spec]); }); } else { - specLists.push(capabilitySpecs); + specLists.push(capabilitiesSpecs); } - capability.count = capability.count || 1; + capabilities.count = capabilities.count || 1; - for (var i = 0; i < capability.count; ++i) { - taskQueues.push(new TaskQueue(capability, specLists)); + for (var i = 0; i < capabilities.count; ++i) { + taskQueues.push(new TaskQueue(capabilities, specLists)); } }); this.taskQueues = taskQueues; @@ -90,7 +90,7 @@ var TaskScheduler = function(config) { /** * Get the next task that is allowed to run without going over maxInstance. * - * @return {{capability: Object, specs: Array., taskId: string, done: function()}} + * @return {{capabilities: Object, specs: Array., taskId: string, done: function()}} */ TaskScheduler.prototype.nextTask = function() { for (var i = 0; i < this.taskQueues.length; ++i) { @@ -108,7 +108,7 @@ TaskScheduler.prototype.nextTask = function() { ++queue.specsIndex; return { - capability: queue.capability, + capabilities: queue.capabilities, specs: specs, taskId: taskId, done: function() {