feat(test runner): show last tests in the worker on teardown error (#13139)

This commit is contained in:
Dmitry Gozman 2022-03-28 19:58:24 -07:00 committed by GitHub
parent 8758cf8cbf
commit de0af27837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 3 deletions

View File

@ -103,8 +103,11 @@ async function gracefullyCloseAndExit() {
if (workerIndex !== undefined) if (workerIndex !== undefined)
await stopProfiling(workerIndex); await stopProfiling(workerIndex);
} catch (e) { } catch (e) {
const payload: TeardownErrorsPayload = { fatalErrors: [serializeError(e)] }; try {
process.send!({ method: 'teardownErrors', params: payload }); const payload: TeardownErrorsPayload = { fatalErrors: [serializeError(e)] };
process.send!({ method: 'teardownErrors', params: payload });
} catch {
}
} }
process.exit(0); process.exit(0);
} }

View File

@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
import colors from 'colors/safe';
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import util from 'util'; import util from 'util';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { serializeError } from './util'; import { relativeFilePath, serializeError } from './util';
import { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, StepBeginPayload, StepEndPayload, TeardownErrorsPayload } from './ipc'; import { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, StepBeginPayload, StepEndPayload, TeardownErrorsPayload } from './ipc';
import { setCurrentTestInfo } from './globals'; import { setCurrentTestInfo } from './globals';
import { Loader } from './loader'; import { Loader } from './loader';
@ -49,6 +50,8 @@ export class WorkerRunner extends EventEmitter {
// This promise resolves once the single "run test group" call finishes. // This promise resolves once the single "run test group" call finishes.
private _runFinished = new ManualPromise<void>(); private _runFinished = new ManualPromise<void>();
_currentTest: TestInfoImpl | null = null; _currentTest: TestInfoImpl | null = null;
private _lastRunningTests: TestInfoImpl[] = [];
private _totalRunningTests = 0;
// Dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`. // Dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`.
private _extraSuiteAnnotations = new Map<Suite, Annotation[]>(); private _extraSuiteAnnotations = new Map<Suite, Annotation[]>();
// Suites that had their beforeAll hooks, but not afterAll hooks executed. // Suites that had their beforeAll hooks, but not afterAll hooks executed.
@ -84,11 +87,28 @@ export class WorkerRunner extends EventEmitter {
await this._loadIfNeeded(); await this._loadIfNeeded();
await this._teardownScopes(); await this._teardownScopes();
if (this._fatalErrors.length) { if (this._fatalErrors.length) {
const diagnostics = this._createWorkerTeardownDiagnostics();
if (diagnostics)
this._fatalErrors.unshift(diagnostics);
const payload: TeardownErrorsPayload = { fatalErrors: this._fatalErrors }; const payload: TeardownErrorsPayload = { fatalErrors: this._fatalErrors };
this.emit('teardownErrors', payload); this.emit('teardownErrors', payload);
} }
} }
private _createWorkerTeardownDiagnostics(): TestError | undefined {
if (!this._lastRunningTests.length)
return;
const count = this._totalRunningTests === 1 ? '1 test' : `${this._totalRunningTests} tests`;
let lastMessage = '';
if (this._lastRunningTests.length < this._totalRunningTests)
lastMessage = `, last ${this._lastRunningTests.length} tests were`;
const message = [
colors.red(`Worker teardown error. This worker ran ${count}${lastMessage}:`),
...this._lastRunningTests.map(testInfo => formatTestTitle(testInfo._test, testInfo.project.name)),
].join('\n');
return { message };
}
private async _teardownScopes() { private async _teardownScopes() {
// TODO: separate timeout for teardown? // TODO: separate timeout for teardown?
const timeoutManager = new TimeoutManager(this._project.config.timeout); const timeoutManager = new TimeoutManager(this._project.config.timeout);
@ -268,6 +288,10 @@ export class WorkerRunner extends EventEmitter {
return; return;
} }
this._totalRunningTests++;
this._lastRunningTests.push(testInfo);
if (this._lastRunningTests.length > 10)
this._lastRunningTests.shift();
let didFailBeforeAllForSuite: Suite | undefined; let didFailBeforeAllForSuite: Suite | undefined;
let shouldRunAfterEachHooks = false; let shouldRunAfterEachHooks = false;
@ -539,3 +563,11 @@ function getSuites(test: TestCase | undefined): Suite[] {
suites.reverse(); // Put root suite first. suites.reverse(); // Put root suite first.
return suites; return suites;
} }
function formatTestTitle(test: TestCase, projectName: string) {
// file, ...describes, test
const [, ...titles] = test.titlePath();
const location = `${relativeFilePath(test.location.file)}:${test.location.line}:${test.location.column}`;
const projectTitle = projectName ? `[${projectName}] ` : '';
return `${projectTitle}${location} ${titles.join(' ')}`;
}

View File

@ -500,3 +500,35 @@ test('should handle fixture teardown error after test timeout and continue', asy
expect(result.output).toContain('Timeout of 100ms exceeded'); expect(result.output).toContain('Timeout of 100ms exceeded');
expect(result.output).toContain('Error: Oh my error'); expect(result.output).toContain('Error: Oh my error');
}); });
test('should report worker fixture teardown with debug info', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.spec.ts': `
const test = pwt.test.extend({
fixture: [ async ({ }, use) => {
await use();
await new Promise(() => {});
}, { scope: 'worker' } ],
});
for (let i = 0; i < 20; i++)
test('good' + i, async ({ fixture }) => {});
`,
}, { reporter: 'list', timeout: 1000 });
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(20);
expect(stripAnsi(result.output)).toContain([
'Worker teardown error. This worker ran 20 tests, last 10 tests were:',
'a.spec.ts:12:9 good10',
'a.spec.ts:12:9 good11',
'a.spec.ts:12:9 good12',
'a.spec.ts:12:9 good13',
'a.spec.ts:12:9 good14',
'a.spec.ts:12:9 good15',
'a.spec.ts:12:9 good16',
'a.spec.ts:12:9 good17',
'a.spec.ts:12:9 good18',
'a.spec.ts:12:9 good19',
'',
'Timeout of 1000ms exceeded in fixtures teardown.',
].join('\n'));
});