From ddcb9eda2c9e228705371304626abd07bbb66a6a Mon Sep 17 00:00:00 2001
From: Dmitry Gorelenkov <dmitry.gorelenkov@gmx.net>
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 <dmitry.gorelenkov@gmx.net>
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 '/<id>'
    * @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.<object>}} args The args to pass into filenameOrFn.
+ * @param {Array.<object>} 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 <dmitry.gorelenkov@gmx.net>
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 <dmitry.gorelenkov@gmx.net>
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);
+    });
+
+});