Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(launcher): task promises run parallel #5406

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 23 additions & 25 deletions lib/launcher.ts
Expand Up @@ -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.
Expand Down
10 changes: 6 additions & 4 deletions lib/logger.ts
Expand Up @@ -83,31 +83,31 @@ 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);
}

/**
* 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);
}

/**
* 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);
}

/**
* 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);
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
*/
Expand Down
4 changes: 2 additions & 2 deletions lib/util.ts
Expand Up @@ -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_(
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/.jshintrc
@@ -1,6 +1,6 @@
{
"strict": false,
"esversion": 6,
"esversion": 8,
"predef": [
"protractor",
"browser",
Expand Down
63 changes: 63 additions & 0 deletions 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);
});

});