From 608104b73face6e305e2c197de1e2b94a313d097 Mon Sep 17 00:00:00 2001 From: Craig Nishina Date: Tue, 16 Feb 2016 14:57:58 -0800 Subject: [PATCH] chore(typescript): converting task files to typescript --- lib/configParser.ts | 3 + lib/launcher.js | 4 +- lib/taskLogger.js | 88 ------------------ lib/taskLogger.ts | 84 +++++++++++++++++ lib/taskRunner.js | 129 -------------------------- lib/taskRunner.ts | 135 +++++++++++++++++++++++++++ lib/taskScheduler.js | 153 ------------------------------ lib/taskScheduler.ts | 159 ++++++++++++++++++++++++++++++++ spec/unit/taskScheduler_test.js | 2 +- tsconfig.json | 2 +- 10 files changed, 385 insertions(+), 374 deletions(-) delete mode 100644 lib/taskLogger.js create mode 100644 lib/taskLogger.ts delete mode 100644 lib/taskRunner.js create mode 100644 lib/taskRunner.ts delete mode 100644 lib/taskScheduler.js create mode 100644 lib/taskScheduler.ts diff --git a/lib/configParser.ts b/lib/configParser.ts index c261e728b..7db405162 100644 --- a/lib/configParser.ts +++ b/lib/configParser.ts @@ -19,6 +19,7 @@ try { export interface Config { specs: Array; multiCapabilities: Array; + capabilities?: any; rootElement: string; allScriptsTimeout: number; getPageTimeout: number; @@ -35,6 +36,8 @@ export interface Config { suite?: string; suites?: any; troubleshoot?: boolean; + exclude?: Array| string; + maxSessions?: number; } export default class ConfigParser { diff --git a/lib/launcher.js b/lib/launcher.js index 8a00e5ebe..33ee0998b 100644 --- a/lib/launcher.js +++ b/lib/launcher.js @@ -5,11 +5,11 @@ 'use strict'; var ConfigParser = require('./configParser').default, - TaskScheduler = require('./taskScheduler'), + TaskScheduler = require('./taskScheduler').default, helper = require('./util'), log = require('./logger'), q = require('q'), - TaskRunner = require('./taskRunner'); + TaskRunner = require('./taskRunner').default; var logPrefix = '[launcher]'; var RUNNERS_FAILED_EXIT_CODE = 100; diff --git a/lib/taskLogger.js b/lib/taskLogger.js deleted file mode 100644 index c34b27660..000000000 --- a/lib/taskLogger.js +++ /dev/null @@ -1,88 +0,0 @@ -var EOL = require('os').EOL; -var log = require('./logger'); - -/** - * 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_(); -}; - -/** - * Log 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 - log.print(EOL + '------------------------------------' + EOL); - log.print(this.buffer); - log.print(EOL); - this.buffer = ''; - } -}; - -/** - * Log the data in the argument such that metadata are appended. - * 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.logName) ? capabilities.logName - : (capabilities.browserName) ? capabilities.browserName : ''; - tag += (capabilities.version) ? (' ' + capabilities.version) : ''; - tag += (capabilities.platform) ? (' ' + capabilities.platform) : ''; - tag += (capabilities.logName && capabilities.count < 2) ? - '' : ' #' + 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/taskLogger.ts b/lib/taskLogger.ts new file mode 100644 index 000000000..bdac4ecdd --- /dev/null +++ b/lib/taskLogger.ts @@ -0,0 +1,84 @@ +import {EOL} from 'os'; +import * as Logger from './logger'; + +export default class TaskLogger { + private buffer: string = ''; + private insertTag: boolean = true; + + /** + * 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. + */ + constructor(private task: any, private pid: number) { this.logHeader_(); } + + /** + * Log the header for the current task including information such as + * PID, browser name/version, task Id, specs being run. + * + * @private + */ + private logHeader_(): void { + let 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. + */ + public flush(): void { + if (this.buffer) { + // Flush buffer if nonempty + Logger.print(EOL + '------------------------------------' + EOL); + Logger.print(this.buffer); + Logger.print(EOL); + this.buffer = ''; + } + } + + /** + * Log the data in the argument such that metadata are appended. + * The data will be saved to a buffer until flush() is called. + * + * @param {string} data + */ + public log(data: string): void { + var tag = '['; + var capabilities = this.task.capabilities; + tag += (capabilities.logName) ? + capabilities.logName : + (capabilities.browserName) ? capabilities.browserName : ''; + tag += (capabilities.version) ? (' ' + capabilities.version) : ''; + tag += (capabilities.platform) ? (' ' + capabilities.platform) : ''; + tag += (capabilities.logName && capabilities.count < 2) ? '' : ' #' + + 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]; + } + } +} diff --git a/lib/taskRunner.js b/lib/taskRunner.js deleted file mode 100644 index b3021dcdd..000000000 --- a/lib/taskRunner.js +++ /dev/null @@ -1,129 +0,0 @@ -var child = require('child_process'); -var q = require('q'); -var TaskLogger = require('./taskLogger'); -var EventEmitter = require('events').EventEmitter; -var util = require('util'); -var log = require('./logger'); -var ConfigParser = require('./configParser').default; - -/** - * A runner for running a specified task (capabilities + specs). - * The TaskRunner can either run the task from the current process (via - * './runner.js') or from a new process (via './runnerCli.js'). - * - * @constructor - * @param {string} configFile Path of test configuration. - * @param {object} additionalConfig Additional configuration. - * @param {object} task Task to run. - * @param {boolean} runInFork Whether to run test in a forked process. - * @constructor - */ -var TaskRunner = function(configFile, additionalConfig, task, runInFork) { - this.configFile = configFile; - this.additionalConfig = additionalConfig; - this.task = task; - this.runInFork = runInFork; -}; -util.inherits(TaskRunner, EventEmitter); - -/** - * Sends the run command. - * @return {q.Promise} A promise that will resolve when the task finishes - * running. The promise contains the following parameters representing the - * result of the run: - * taskId, specs, capabilities, failedCount, exitCode, specResults - */ -TaskRunner.prototype.run = function() { - 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 + '/runnerCli.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': - log.print('.'); - break; - case 'testFail': - log.print('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 = 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 = TaskRunner; diff --git a/lib/taskRunner.ts b/lib/taskRunner.ts new file mode 100644 index 000000000..40f4c42f4 --- /dev/null +++ b/lib/taskRunner.ts @@ -0,0 +1,135 @@ +import {fork} from 'child_process'; +import {EventEmitter} from 'events'; +import {defer, Promise} from 'q'; +import {inherits} from 'util'; + +import ConfigParser, {Config} from './configParser'; +import * as Logger from './logger'; +import TaskLogger from './taskLogger'; + +export interface RunResults { + taskId: number; + specs: Array; + capabilities: any; + failedCount: number; + exitCode: number; + specResults: Array; +} + +export default class TaskRunner extends EventEmitter { + /** + * A runner for running a specified task (capabilities + specs). + * The TaskRunner can either run the task from the current process (via + * './runner.js') or from a new process (via './runnerCli.js'). + * + * @constructor + * @param {string} configFile Path of test configuration. + * @param {object} additionalConfig Additional configuration. + * @param {object} task Task to run. + * @param {boolean} runInFork Whether to run test in a forked process. + * @constructor + */ + constructor( + private configFile: string, private additionalConfig: Config, + private task: any, private runInFork: boolean) { + super(); + } + + /** + * Sends the run command. + * @return {q.Promise} A promise that will resolve when the task finishes + * running. The promise contains the following parameters representing the + * result of the run: + * taskId, specs, capabilities, failedCount, exitCode, specResults + */ + public run(): Promise { + let runResults: 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) { + let deferred = defer(); + + let childProcess = fork( + __dirname + '/runnerCli.js', process.argv.slice(2), + {cwd: process.cwd(), silent: true}); + let taskLogger = new TaskLogger(this.task, childProcess.pid); + + // stdout pipe + childProcess.stdout.on( + 'data', (data: string) => { taskLogger.log(data); }); + + // stderr pipe + childProcess.stderr.on( + 'data', (data: string) => { taskLogger.log(data); }); + + childProcess + .on('message', + (m: any) => { + console.log(m); + switch (m.event) { + case 'testPass': + Logger.print('.'); + break; + case 'testFail': + Logger.print('F'); + break; + case 'testsDone': + runResults.failedCount = m.results.failedCount; + runResults.specResults = m.results.specResults; + break; + } + }) + .on('error', + (err: any) => { + taskLogger.flush(); + deferred.reject(err); + }) + .on('exit', (code: number) => { + 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 { + let configParser = new ConfigParser(); + if (this.configFile) { + configParser.addFileConfig(this.configFile); + } + if (this.additionalConfig) { + configParser.addConfig(this.additionalConfig); + } + let config = configParser.getConfig(); + config.capabilities = this.task.capabilities; + config.specs = this.task.specs; + + let Runner = require('./runner'); + let runner = new Runner(config); + + runner.on('testsDone', (results: RunResults) => { + runResults.failedCount = results.failedCount; + runResults.specResults = results.specResults; + }); + + return runner.run().then((exitCode: number) => { + runResults.exitCode = exitCode; + return runResults; + }); + } + } +} diff --git a/lib/taskScheduler.js b/lib/taskScheduler.js deleted file mode 100644 index d94566405..000000000 --- a/lib/taskScheduler.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * The taskScheduler keeps track of the spec files that needs to run next - * and which task is running what. - */ -'use strict'; - -var ConfigParser = require('./configParser').default; - -// A queue of specs for a particular capacity -var TaskQueue = function(capabilities, specLists) { - this.capabilities = capabilities; - this.numRunningInstances = 0; - this.maxInstance = capabilities.maxInstances || 1; - this.specsIndex = 0; - this.specLists = specLists; -}; - -/** - * A scheduler to keep track of specs that need running and their associated - * capabilities. It will suggest a task (combination of capabilities and spec) - * to run while observing the following config rules: - * multiCapabilities, shardTestFiles, and maxInstance. - * Precondition: multiCapabilities is a non-empty array - * (capabilities and getCapabilities will both be ignored) - * - * @constructor - * @param {Object} config parsed from the config file - */ -var TaskScheduler = function(config) { - var excludes = ConfigParser.resolveFilePatterns(config.exclude, true, config.configDir); - var allSpecs = ConfigParser.resolveFilePatterns( - ConfigParser.getSpecs(config), false, config.configDir).filter(function(path) { - return excludes.indexOf(path) < 0; - }); - - var taskQueues = []; - 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 (capabilities.exclude) { - var capabilitiesSpecExcludes = ConfigParser.resolveFilePatterns( - capabilities.exclude, true, config.configDir); - capabilitiesSpecs = capabilitiesSpecs.filter(function(path) { - return capabilitiesSpecExcludes.indexOf(path) < 0; - }); - } - - var specLists = []; - // 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 (capabilities.shardTestFiles) { - capabilitiesSpecs.forEach(function(spec) { - specLists.push([spec]); - }); - } else { - specLists.push(capabilitiesSpecs); - } - - capabilities.count = capabilities.count || 1; - - for (var i = 0; i < capabilities.count; ++i) { - taskQueues.push(new TaskQueue(capabilities, specLists)); - } - }); - this.taskQueues = taskQueues; - this.config = config; - this.rotationIndex = 0; // Helps suggestions to rotate amongst capabilities -}; - -/** - * Get the next task that is allowed to run without going over maxInstance. - * - * @return {{capabilities: Object, specs: Array., taskId: string, done: function()}} - */ -TaskScheduler.prototype.nextTask = function() { - for (var i = 0; i < this.taskQueues.length; ++i) { - var rotatedIndex = ((i + this.rotationIndex) % this.taskQueues.length); - var queue = this.taskQueues[rotatedIndex]; - if (queue.numRunningInstances < queue.maxInstance && - queue.specsIndex < queue.specLists.length) { - this.rotationIndex = rotatedIndex + 1; - ++queue.numRunningInstances; - var taskId = rotatedIndex + 1; - if (queue.specLists.length > 1) { - taskId += '-' + queue.specsIndex; - } - var specs = queue.specLists[queue.specsIndex]; - ++queue.specsIndex; - - return { - capabilities: queue.capabilities, - specs: specs, - taskId: taskId, - done: function() { - --queue.numRunningInstances; - } - }; - } - } - - return null; -}; - -/** - * Get the number of tasks left to run or are currently running. - * - * @return {number} - */ -TaskScheduler.prototype.numTasksOutstanding = function() { - var count = 0; - this.taskQueues.forEach(function(queue) { - count += queue.numRunningInstances + (queue.specLists.length - queue.specsIndex); - }); - return count; -}; - -/** - * Get maximum number of concurrent tasks required/permitted. - * - * @return {number} - */ -TaskScheduler.prototype.maxConcurrentTasks = function() { - if (this.config.maxSessions && this.config.maxSessions > 0) { - return this.config.maxSessions; - } else { - var count = 0; - this.taskQueues.forEach(function(queue) { - count += Math.min(queue.maxInstance, queue.specLists.length); - }); - return count; - } -}; - -/** - * Returns number of tasks currently running. - * - * @return {number} - */ -TaskScheduler.prototype.countActiveTasks = function() { - var count = 0; - this.taskQueues.forEach(function(queue) { - count += queue.numRunningInstances; - }); - return count; -}; - -module.exports = TaskScheduler; diff --git a/lib/taskScheduler.ts b/lib/taskScheduler.ts new file mode 100644 index 000000000..a6dca215b --- /dev/null +++ b/lib/taskScheduler.ts @@ -0,0 +1,159 @@ +import ConfigParser, {Config} from './configParser'; + + +export interface Task { + capabilities: any; + specs: Array; + taskId: string; + done: any; +} + +/** + * The taskScheduler keeps track of the spec files that needs to run next + * and which task is running what. + */ +export class TaskQueue { + numRunningInstances: number = 0; + maxInstance: number; + specsIndex: number = 0; + + // A queue of specs for a particular capacity + constructor(public capabilities: any, public specLists: any) { + this.maxInstance = capabilities.maxInstances || 1; + } +} + +export default class TaskScheduler { + taskQueues: Array; + rotationIndex: number; + + /** + * A scheduler to keep track of specs that need running and their associated + * capabilities. It will suggest a task (combination of capabilities and spec) + * to run while observing the following config rules: + * multiCapabilities, shardTestFiles, and maxInstance. + * Precondition: multiCapabilities is a non-empty array + * (capabilities and getCapabilities will both be ignored) + * + * @constructor + * @param {Object} config parsed from the config file + */ + constructor(private config: Config) { + let excludes = ConfigParser.resolveFilePatterns( + config.exclude, true, config.configDir); + let allSpecs = + ConfigParser + .resolveFilePatterns( + ConfigParser.getSpecs(config), false, config.configDir) + .filter((path) => { return excludes.indexOf(path) < 0; }); + + let taskQueues: Array = []; + config.multiCapabilities.forEach((capabilities) => { + let capabilitiesSpecs = allSpecs; + if (capabilities.specs) { + let capabilitiesSpecificSpecs = ConfigParser.resolveFilePatterns( + capabilities.specs, false, config.configDir); + capabilitiesSpecs = capabilitiesSpecs.concat(capabilitiesSpecificSpecs); + } + + if (capabilities.exclude) { + let capabilitiesSpecExcludes = ConfigParser.resolveFilePatterns( + capabilities.exclude, true, config.configDir); + capabilitiesSpecs = capabilitiesSpecs.filter( + (path) => { return capabilitiesSpecExcludes.indexOf(path) < 0; }); + } + + let specLists: Array = []; + // 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 (capabilities.shardTestFiles) { + capabilitiesSpecs.forEach((spec) => { specLists.push([spec]); }); + } else { + specLists.push(capabilitiesSpecs); + } + + capabilities.count = capabilities.count || 1; + + for (var i = 0; i < capabilities.count; ++i) { + taskQueues.push(new TaskQueue(capabilities, specLists)); + } + }); + this.taskQueues = taskQueues; + this.rotationIndex = 0; // Helps suggestions to rotate amongst capabilities + } + + /** + * Get the next task that is allowed to run without going over maxInstance. + * + * @return {{capabilities: Object, specs: Array., taskId: string, + * done: function()}} + */ + public nextTask(): Task { + for (var i = 0; i < this.taskQueues.length; ++i) { + var rotatedIndex = ((i + this.rotationIndex) % this.taskQueues.length); + var queue = this.taskQueues[rotatedIndex]; + if (queue.numRunningInstances < queue.maxInstance && + queue.specsIndex < queue.specLists.length) { + this.rotationIndex = rotatedIndex + 1; + ++queue.numRunningInstances; + let taskId = '' + rotatedIndex + 1; + if (queue.specLists.length > 1) { + taskId += '-' + queue.specsIndex; + } + let specs = queue.specLists[queue.specsIndex]; + ++queue.specsIndex; + + return { + capabilities: queue.capabilities, + specs: specs, + taskId: taskId, + done: function() { --queue.numRunningInstances; } + }; + } + } + return null; + } + + /** + * Get the number of tasks left to run or are currently running. + * + * @return {number} + */ + public numTasksOutstanding(): number { + var count = 0; + this.taskQueues.forEach((queue) => { + count += queue.numRunningInstances + + (queue.specLists.length - queue.specsIndex); + }); + return count; + } + + /** + * Get maximum number of concurrent tasks required/permitted. + * + * @return {number} + */ + public maxConcurrentTasks(): number { + if (this.config.maxSessions && this.config.maxSessions > 0) { + return this.config.maxSessions; + } else { + var count = 0; + this.taskQueues.forEach((queue) => { + count += Math.min(queue.maxInstance, queue.specLists.length); + }); + return count; + } + } + + /** + * Returns number of tasks currently running. + * + * @return {number} + */ + public countActiveTasks() { + var count = 0; + this.taskQueues.forEach((queue) => { count += queue.numRunningInstances; }); + return count; + } +} diff --git a/spec/unit/taskScheduler_test.js b/spec/unit/taskScheduler_test.js index a18159ff1..138b59941 100644 --- a/spec/unit/taskScheduler_test.js +++ b/spec/unit/taskScheduler_test.js @@ -1,4 +1,4 @@ -var TaskScheduler = require('../../built/taskScheduler.js'); +var TaskScheduler = require('../../built/taskScheduler').default; var ConfigParser = require('../../built/configParser').default; describe('the task scheduler', function() { diff --git a/tsconfig.json b/tsconfig.json index 1ea4a669f..89448d51c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "sourceMap": false, "declaration": true, "removeComments": false, - "noImplicitAny": false, + "noImplicitAny": true, "outDir": "built/" }, "exclude": [