chore(test runner): decouple runJob from the Dispacther (#26756)
In preparation to extracting a class for it.
This commit is contained in:
parent
38f1e62642
commit
4bbd16d316
|
|
@ -136,7 +136,21 @@ export class Dispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Run the job.
|
// 3. Run the job.
|
||||||
await this._runJob(worker, job);
|
const result = await this._runJob(worker, job);
|
||||||
|
this._updateCounterForWorkerHash(job.workerHash, -1);
|
||||||
|
|
||||||
|
// 4. When worker encounters error, we stop it and create a new one.
|
||||||
|
// We also do not keep the worker alive if it cannot serve any more jobs.
|
||||||
|
if (result.didFail)
|
||||||
|
void worker.stop(true /* didFail */);
|
||||||
|
else if (this._isWorkerRedundant(worker))
|
||||||
|
void worker.stop();
|
||||||
|
|
||||||
|
// 5. Possibly schedule a new job with leftover tests and/or retries.
|
||||||
|
if (!this._isStopped && result.newJob) {
|
||||||
|
this._queue.unshift(result.newJob);
|
||||||
|
this._updateCounterForWorkerHash(job.workerHash, +1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _checkFinished() {
|
private _checkFinished() {
|
||||||
|
|
@ -168,11 +182,15 @@ export class Dispatcher {
|
||||||
return workersWithSameHash > this._queuedOrRunningHashCount.get(worker.hash())!;
|
return workersWithSameHash > this._queuedOrRunningHashCount.get(worker.hash())!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _updateCounterForWorkerHash(hash: string, delta: number) {
|
||||||
|
this._queuedOrRunningHashCount.set(hash, delta + (this._queuedOrRunningHashCount.get(hash) || 0));
|
||||||
|
}
|
||||||
|
|
||||||
async run(testGroups: TestGroup[], extraEnvByProjectId: EnvByProjectId) {
|
async run(testGroups: TestGroup[], extraEnvByProjectId: EnvByProjectId) {
|
||||||
this._extraEnvByProjectId = extraEnvByProjectId;
|
this._extraEnvByProjectId = extraEnvByProjectId;
|
||||||
this._queue = testGroups;
|
this._queue = testGroups;
|
||||||
for (const group of testGroups) {
|
for (const group of testGroups) {
|
||||||
this._queuedOrRunningHashCount.set(group.workerHash, 1 + (this._queuedOrRunningHashCount.get(group.workerHash) || 0));
|
this._updateCounterForWorkerHash(group.workerHash, +1);
|
||||||
for (const test of group.tests)
|
for (const test of group.tests)
|
||||||
this._testById.set(test.id, { test, resultByWorkerIndex: new Map() });
|
this._testById.set(test.id, { test, resultByWorkerIndex: new Map() });
|
||||||
}
|
}
|
||||||
|
|
@ -185,12 +203,12 @@ export class Dispatcher {
|
||||||
for (let i = 0; i < this._workerSlots.length; i++)
|
for (let i = 0; i < this._workerSlots.length; i++)
|
||||||
void this._scheduleJob();
|
void this._scheduleJob();
|
||||||
this._checkFinished();
|
this._checkFinished();
|
||||||
// 3. More jobs are scheduled when the worker becomes free, or a new job is added.
|
// 3. More jobs are scheduled when the worker becomes free.
|
||||||
// 4. Wait for all jobs to finish.
|
// 4. Wait for all jobs to finish.
|
||||||
await this._finished;
|
await this._finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _runJob(worker: WorkerHost, testGroup: TestGroup) {
|
async _runJob(worker: WorkerHost, testGroup: TestGroup): Promise<{ newJob?: TestGroup, didFail: boolean }> {
|
||||||
const runPayload: RunPayload = {
|
const runPayload: RunPayload = {
|
||||||
file: testGroup.requireFile,
|
file: testGroup.requireFile,
|
||||||
entries: testGroup.tests.map(test => {
|
entries: testGroup.tests.map(test => {
|
||||||
|
|
@ -199,8 +217,7 @@ export class Dispatcher {
|
||||||
};
|
};
|
||||||
worker.runTestGroup(runPayload);
|
worker.runTestGroup(runPayload);
|
||||||
|
|
||||||
let doneCallback = () => {};
|
const result = new ManualPromise<{ newJob?: TestGroup, didFail: boolean }>();
|
||||||
const result = new Promise<void>(f => doneCallback = f);
|
|
||||||
const doneWithJob = () => {
|
const doneWithJob = () => {
|
||||||
worker.removeListener('testBegin', onTestBegin);
|
worker.removeListener('testBegin', onTestBegin);
|
||||||
worker.removeListener('testEnd', onTestEnd);
|
worker.removeListener('testEnd', onTestEnd);
|
||||||
|
|
@ -209,7 +226,6 @@ export class Dispatcher {
|
||||||
worker.removeListener('attach', onAttach);
|
worker.removeListener('attach', onAttach);
|
||||||
worker.removeListener('done', onDone);
|
worker.removeListener('done', onDone);
|
||||||
worker.removeListener('exit', onExit);
|
worker.removeListener('exit', onExit);
|
||||||
doneCallback();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const remainingByTestId = new Map(testGroup.tests.map(e => [e.id, e]));
|
const remainingByTestId = new Map(testGroup.tests.map(e => [e.id, e]));
|
||||||
|
|
@ -320,7 +336,6 @@ export class Dispatcher {
|
||||||
worker.on('attach', onAttach);
|
worker.on('attach', onAttach);
|
||||||
|
|
||||||
const onDone = (params: DonePayload & { unexpectedExitError?: TestError }) => {
|
const onDone = (params: DonePayload & { unexpectedExitError?: TestError }) => {
|
||||||
this._queuedOrRunningHashCount.set(worker.hash(), this._queuedOrRunningHashCount.get(worker.hash())! - 1);
|
|
||||||
let remaining = [...remainingByTestId.values()];
|
let remaining = [...remainingByTestId.values()];
|
||||||
|
|
||||||
// We won't file remaining if:
|
// We won't file remaining if:
|
||||||
|
|
@ -328,15 +343,11 @@ export class Dispatcher {
|
||||||
// - we are here not because something failed
|
// - we are here not because something failed
|
||||||
// - no unrecoverable worker error
|
// - no unrecoverable worker error
|
||||||
if (!remaining.length && !failedTestIds.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError) {
|
if (!remaining.length && !failedTestIds.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError) {
|
||||||
if (this._isWorkerRedundant(worker))
|
|
||||||
void worker.stop();
|
|
||||||
doneWithJob();
|
doneWithJob();
|
||||||
|
result.resolve({ didFail: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When worker encounters error, we will stop it and create a new one.
|
|
||||||
void worker.stop(true /* didFail */);
|
|
||||||
|
|
||||||
const massSkipTestsFromRemaining = (testIds: Set<string>, errors: TestError[], onlyStartedTests?: boolean) => {
|
const massSkipTestsFromRemaining = (testIds: Set<string>, errors: TestError[], onlyStartedTests?: boolean) => {
|
||||||
remaining = remaining.filter(test => {
|
remaining = remaining.filter(test => {
|
||||||
if (!testIds.has(test.id))
|
if (!testIds.has(test.id))
|
||||||
|
|
@ -367,7 +378,7 @@ export class Dispatcher {
|
||||||
// We had fatal errors after all tests have passed - most likely in some teardown.
|
// We had fatal errors after all tests have passed - most likely in some teardown.
|
||||||
// Let's just fail the test run.
|
// Let's just fail the test run.
|
||||||
this._hasWorkerErrors = true;
|
this._hasWorkerErrors = true;
|
||||||
for (const error of params.fatalErrors)
|
for (const error of errors)
|
||||||
this._reporter.onError(error);
|
this._reporter.onError(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -435,19 +446,14 @@ export class Dispatcher {
|
||||||
|
|
||||||
for (const testId of retryCandidates) {
|
for (const testId of retryCandidates) {
|
||||||
const pair = this._testById.get(testId)!;
|
const pair = this._testById.get(testId)!;
|
||||||
if (!this._isStopped && pair.test.results.length < pair.test.retries + 1)
|
if (pair.test.results.length < pair.test.retries + 1)
|
||||||
remaining.push(pair.test);
|
remaining.push(pair.test);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remaining.length) {
|
// This job is over, we will schedule another one.
|
||||||
this._queue.unshift({ ...testGroup, tests: remaining });
|
|
||||||
this._queuedOrRunningHashCount.set(testGroup.workerHash, this._queuedOrRunningHashCount.get(testGroup.workerHash)! + 1);
|
|
||||||
// Perhaps we can immediately start the new job if there is a worker available?
|
|
||||||
void this._scheduleJob();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This job is over, we just scheduled another one.
|
|
||||||
doneWithJob();
|
doneWithJob();
|
||||||
|
const newJob = remaining.length ? { ...testGroup, tests: remaining } : undefined;
|
||||||
|
result.resolve({ didFail: true, newJob });
|
||||||
};
|
};
|
||||||
worker.on('done', onDone);
|
worker.on('done', onDone);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue