From ddcb9eda2c9e228705371304626abd07bbb66a6a Mon Sep 17 00:00:00 2001 From: Dmitry Gorelenkov Date: Wed, 11 Mar 2020 19:41:58 +0100 Subject: [PATCH 1/4] fix(launcher): task promises run parallel --- lib/launcher.ts | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/lib/launcher.ts b/lib/launcher.ts index 3475a8433..3e93f1ec3 100644 --- a/lib/launcher.ts +++ b/lib/launcher.ts @@ -216,38 +216,36 @@ let initFn = async function(configFile: string, additionalConfig: Config) { } const createNextTaskRunner = async () => { - return new Promise(async (resolve) => { - const task = scheduler.nextTask(); - if (task) { - const taskRunner = new TaskRunner(configFile, additionalConfig, task, forkProcess); - try { - const result = await taskRunner.run(); - if (result.exitCode && !result.failedCount) { - logger.error('Runner process exited unexpectedly with error code: ' + result.exitCode); - } - taskResults_.add(result); - task.done(); - await createNextTaskRunner(); - // If all tasks are finished - if (scheduler.numTasksOutstanding() === 0) { - resolve(); - } - logger.info(scheduler.countActiveTasks() + ' instance(s) of WebDriver still running'); - } catch (err) { - const errorCode = ErrorHandler.parseError(err); - logger.error('Error:', (err as any).stack || err.message || err); - await cleanUpAndExit(errorCode ? errorCode : RUNNERS_FAILED_EXIT_CODE); + const task = scheduler.nextTask(); + if (task) { + const taskRunner = new TaskRunner(configFile, additionalConfig, task, forkProcess); + try { + const result = await taskRunner.run(); + if (result.exitCode && !result.failedCount) { + logger.error('Runner process exited unexpectedly with error code: ' + result.exitCode); } - } else { - resolve(); + taskResults_.add(result); + task.done(); + await createNextTaskRunner(); + // If all tasks are finished + if (scheduler.numTasksOutstanding() === 0) { + return; + } + logger.info(scheduler.countActiveTasks() + ' instance(s) of WebDriver still running'); + } catch (err) { + const errorCode = ErrorHandler.parseError(err); + logger.error('Error:', (err as any).stack || err.message || err); + await cleanUpAndExit(errorCode ? errorCode : RUNNERS_FAILED_EXIT_CODE); } - }); + } }; const maxConcurrentTasks = scheduler.maxConcurrentTasks(); + const tasks = []; for (let i = 0; i < maxConcurrentTasks; ++i) { - await createNextTaskRunner(); + tasks.push(createNextTaskRunner()); } + await Promise.all(tasks); logger.info('Running ' + scheduler.countActiveTasks() + ' instances of WebDriver'); // By now all runners have completed. From c49bbe0081aafee20f6eed3ae4f56b766c1aa9d8 Mon Sep 17 00:00:00 2001 From: Dmitry Gorelenkov Date: Wed, 11 Mar 2020 19:47:38 +0100 Subject: [PATCH 2/4] chore(jsdoc): fixed comments for functions in logger.ts and util.ts --- lib/logger.ts | 10 ++++++---- lib/util.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/logger.ts b/lib/logger.ts index 12f007b1e..cf5b401bb 100644 --- a/lib/logger.ts +++ b/lib/logger.ts @@ -83,7 +83,7 @@ export class Logger { /** * Log INFO - * @param ...msgs multiple arguments to be logged. + * @param msgs multiple arguments to be logged. */ info(...msgs: any[]): void { this.log_(LogLevel.INFO, msgs); @@ -91,7 +91,7 @@ export class Logger { /** * Log DEBUG - * @param ...msgs multiple arguments to be logged. + * @param msgs multiple arguments to be logged. */ debug(...msgs: any[]): void { this.log_(LogLevel.DEBUG, msgs); @@ -99,7 +99,7 @@ export class Logger { /** * Log WARN - * @param ...msgs multiple arguments to be logged. + * @param msgs multiple arguments to be logged. */ warn(...msgs: any[]): void { this.log_(LogLevel.WARN, msgs); @@ -107,7 +107,7 @@ export class Logger { /** * Log ERROR - * @param ...msgs multiple arguments to be logged. + * @param msgs multiple arguments to be logged. */ error(...msgs: any[]): void { this.log_(LogLevel.ERROR, msgs); @@ -221,6 +221,7 @@ export class Logger { /** * Get the identifier of the logger as '/' * @param logLevel The log level of the message. + * @param id not used yet * @param writeTo The enum for where to write the logs. * @return The string of the formatted id */ @@ -239,6 +240,7 @@ export class Logger { /** * Get the log level formatted with the first letter. For info, it is I. * @param logLevel The log level of the message. + * @param id not used yet * @param writeTo The enum for where to write the logs. * @return The string of the formatted log level */ diff --git a/lib/util.ts b/lib/util.ts index 58e34cc96..510730025 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -31,7 +31,7 @@ export function filterStackTrace(text: string): string { /** * Internal helper for abstraction of polymorphic filenameOrFn properties. * @param {object} filenameOrFn The filename or function that we will execute. - * @param {Array.}} args The args to pass into filenameOrFn. + * @param {Array.} args The args to pass into filenameOrFn. * @return {Promise} A promise that will resolve when filenameOrFn completes. */ export async function runFilenameOrFn_( @@ -82,7 +82,7 @@ export function joinTestLogs(log1: any, log2: any): any { * Returns false if an error indicates a missing or stale element, re-throws * the error otherwise * - * @param {*} The error to check + * @param {*} error The error to check * @throws {*} The error it was passed if it doesn't indicate a missing or stale * element * @return {boolean} false, if it doesn't re-throw the error From e555ba8bf32424d73c8d7c5ab70dedf5d5999360 Mon Sep 17 00:00:00 2001 From: Dmitry Gorelenkov Date: Wed, 11 Mar 2020 19:50:35 +0100 Subject: [PATCH 3/4] chore(jshint): changed jshint esversion 6 -> 8, since async/await is used --- spec/.jshintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/.jshintrc b/spec/.jshintrc index b8b730f77..91a66e648 100644 --- a/spec/.jshintrc +++ b/spec/.jshintrc @@ -1,6 +1,6 @@ { "strict": false, - "esversion": 6, + "esversion": 8, "predef": [ "protractor", "browser", From 1067955e211d2e53f06b9dea9b38887d24d70fb8 Mon Sep 17 00:00:00 2001 From: Dmitry Gorelenkov Date: Fri, 13 Mar 2020 16:52:27 +0100 Subject: [PATCH 4/4] test(launcher): kind of unit test for launcher init function --- spec/unit/launcher_test.js | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 spec/unit/launcher_test.js diff --git a/spec/unit/launcher_test.js b/spec/unit/launcher_test.js new file mode 100644 index 000000000..9c93a92f5 --- /dev/null +++ b/spec/unit/launcher_test.js @@ -0,0 +1,63 @@ +const Logger = require('../../built/logger').Logger; +const TaskRunner = require('../../built/taskRunner').TaskRunner; +const initFn = require('../../built/launcher').init; + + +describe('the launcher', function () { + let runningTasks; + let unblockTasks; + let blockTasksPromise; + + beforeAll(() => { + // disable launcher logs in console + spyOn(Logger, 'writeTo').and.stub(); + // process.exit is called by launcher, stub it + spyOn(process, 'exit').and.stub(); + }); + + beforeEach(() => { + blockTasksPromise = new Promise(resolve => { + unblockTasks = resolve; + }); + runningTasks = 0; + + let taskRunFn = async () => { + runningTasks++; + await blockTasksPromise; + runningTasks--; + return {taskId: 0, exitCode: 0, capabilities: {}}; + }; + + spyOn(TaskRunner.prototype, 'run').and.callFake(taskRunFn); + }); + + it('should be able to run tasks in parallel', async function () { + const conf = { + specs: [ + 'spec/unit/data/fakespecA.js', + 'spec/unit/data/fakespecB.js', + 'spec/unit/data/fakespecC.js', + ], + capabilities: { + browserName: 'chrome', + maxInstances: 3, + shardTestFiles: true + }, + }; + // no tasks should be run at beginning + expect(runningTasks).toEqual(0); + // start main launcher process + const initPromise = initFn(null, conf); + // wait for some promises inside initFn + await new Promise(res => setTimeout(res)); + // maxInstances tasks running now + expect(runningTasks).toBe(3); + // finish the tasks + unblockTasks(); + // wait until initFn done + await initPromise; + // all tasks should be done now + expect(runningTasks).toBe(0); + }); + +});