chore: respect sigint in global setup (#14805)

This commit is contained in:
Pavel Feldman 2022-06-12 12:06:00 -08:00 committed by GitHub
parent c7b3f4646f
commit 41529bb1a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 149 additions and 17 deletions

View file

@ -397,11 +397,10 @@ export class Runner {
} }
// 13. Run Global setup. // 13. Run Global setup.
const globalTearDown = await this._performGlobalSetup(config, rootSuite);
if (!globalTearDown)
return { status: 'failed' };
const result: FullResult = { status: 'passed' }; const result: FullResult = { status: 'passed' };
const globalTearDown = await this._performGlobalSetup(config, rootSuite, result);
if (result.status !== 'passed')
return result;
// 14. Run tests. // 14. Run tests.
try { try {
@ -433,9 +432,10 @@ export class Runner {
return result; return result;
} }
private async _performGlobalSetup(config: FullConfigInternal, rootSuite: Suite): Promise<(() => Promise<void>) | undefined> { private async _performGlobalSetup(config: FullConfigInternal, rootSuite: Suite, result: FullResult): Promise<(() => Promise<void>) | undefined> {
const result: FullResult = { status: 'passed' };
let globalSetupResult: any; let globalSetupResult: any;
const pluginsThatWereSetUp: TestRunnerPlugin[] = [];
const sigintWatcher = new SigIntWatcher();
const tearDown = async () => { const tearDown = async () => {
// Reverse to setup. // Reverse to setup.
@ -445,34 +445,49 @@ export class Runner {
}, result); }, result);
await this._runAndReportError(async () => { await this._runAndReportError(async () => {
if (config.globalTeardown) if (globalSetupResult && config.globalTeardown)
await (await this._loader.loadGlobalHook(config.globalTeardown, 'globalTeardown'))(this._loader.fullConfig()); await (await this._loader.loadGlobalHook(config.globalTeardown, 'globalTeardown'))(this._loader.fullConfig());
}, result); }, result);
for (const plugin of [...this._plugins].reverse()) { for (const plugin of pluginsThatWereSetUp.reverse()) {
await this._runAndReportError(async () => { await this._runAndReportError(async () => {
await plugin.teardown?.(); await plugin.teardown?.();
}, result); }, result);
} }
}; };
await this._runAndReportError(async () => { // Legacy webServer support.
// Legacy webServer support. if (config.webServer)
if (config.webServer) this._plugins.push(webServerPluginForConfig(config, this._reporter));
this._plugins.push(webServerPluginForConfig(config, this._reporter));
await this._runAndReportError(async () => {
// First run the plugins, if plugin is a web server we want it to run before the // First run the plugins, if plugin is a web server we want it to run before the
// config's global setup. // config's global setup.
for (const plugin of this._plugins) for (const plugin of this._plugins) {
await plugin.setup?.(config, config._configDir, rootSuite); await Promise.race([
plugin.setup?.(config, config._configDir, rootSuite),
sigintWatcher.promise(),
]);
if (sigintWatcher.hadSignal())
break;
pluginsThatWereSetUp.push(plugin);
}
// The do global setup. // The do global setup.
if (config.globalSetup) if (!sigintWatcher.hadSignal() && config.globalSetup) {
globalSetupResult = await (await this._loader.loadGlobalHook(config.globalSetup, 'globalSetup'))(this._loader.fullConfig()); const hook = await this._loader.loadGlobalHook(config.globalSetup, 'globalSetup');
await Promise.race([
Promise.resolve().then(() => hook(this._loader.fullConfig())).then((r: any) => globalSetupResult = r || '<noop>'),
sigintWatcher.promise(),
]);
}
}, result); }, result);
if (result.status !== 'passed') { sigintWatcher.disarm();
if (result.status !== 'passed' || sigintWatcher.hadSignal()) {
await tearDown(); await tearDown();
result.status = sigintWatcher.hadSignal() ? 'interrupted' : 'failed';
return; return;
} }

View file

@ -306,3 +306,120 @@ test('should not hang if test suites in worker are inconsistent with runner', as
expect(result.report.suites[0].specs[1].tests[0].results[0].error.message).toBe('Unknown test(s) in worker:\nproject-name > a.spec.js > Test 1 - bar\nproject-name > a.spec.js > Test 2 - baz'); expect(result.report.suites[0].specs[1].tests[0].results[0].error.message).toBe('Unknown test(s) in worker:\nproject-name > a.spec.js > Test 1 - bar\nproject-name > a.spec.js > Test 2 - baz');
}); });
test('sigint should stop global setup', async ({ runInlineTest }) => {
test.skip(process.platform === 'win32', 'No sending SIGINT on Windows');
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
globalSetup: './globalSetup',
globalTeardown: './globalTeardown.ts',
};
`,
'globalSetup.ts': `
module.exports = () => {
console.log('Global setup');
console.log('%%SEND-SIGINT%%');
return new Promise(f => setTimeout(f, 30000));
};
`,
'globalTeardown.ts': `
module.exports = () => {
console.log('Global teardown');
};
`,
'a.spec.js': `
const { test } = pwt;
test('test', async () => { });
`,
}, { 'workers': 1 }, {}, { sendSIGINTAfter: 1 });
expect(result.exitCode).toBe(130);
expect(result.passed).toBe(0);
const output = stripAnsi(result.output);
expect(output).toContain('Global setup');
expect(output).not.toContain('Global teardown');
});
test('sigint should stop plugins', async ({ runInlineTest }) => {
test.skip(process.platform === 'win32', 'No sending SIGINT on Windows');
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
};
require('@playwright/test')._addRunnerPlugin(() => ({
setup: async () => {
console.log('Plugin1 setup');
console.log('%%SEND-SIGINT%%');
return new Promise(f => setTimeout(f, 30000));
},
teardown: async () => {
console.log('Plugin1 teardown');
}
}));
require('@playwright/test')._addRunnerPlugin(() => ({
setup: async () => {
console.log('Plugin2 setup');
},
teardown: async () => {
console.log('Plugin2 teardown');
}
}));
`,
'a.spec.js': `
const { test } = pwt;
test('test', async () => { });
`,
}, { 'workers': 1 }, {}, { sendSIGINTAfter: 1 });
expect(result.exitCode).toBe(130);
expect(result.passed).toBe(0);
const output = stripAnsi(result.output);
expect(output).toContain('Plugin1 setup');
expect(output).not.toContain('Plugin1 teardown');
expect(output).not.toContain('Plugin2 setup');
expect(output).not.toContain('Plugin2 teardown');
});
test('sigint should stop plugins 2', async ({ runInlineTest }) => {
test.skip(process.platform === 'win32', 'No sending SIGINT on Windows');
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
};
require('@playwright/test')._addRunnerPlugin(() => ({
setup: async () => {
console.log('Plugin1 setup');
},
teardown: async () => {
console.log('Plugin1 teardown');
}
}));
require('@playwright/test')._addRunnerPlugin(() => ({
setup: async () => {
console.log('Plugin2 setup');
console.log('%%SEND-SIGINT%%');
return new Promise(f => setTimeout(f, 30000));
},
teardown: async () => {
console.log('Plugin2 teardown');
}
}));
`,
'a.spec.js': `
const { test } = pwt;
test('test', async () => { });
`,
}, { 'workers': 1 }, {}, { sendSIGINTAfter: 1 });
expect(result.exitCode).toBe(130);
expect(result.passed).toBe(0);
const output = stripAnsi(result.output);
expect(output).toContain('Plugin1 setup');
expect(output).toContain('Plugin2 setup');
expect(output).toContain('Plugin1 teardown');
expect(output).not.toContain('Plugin2 teardown');
});