Skip to content

Commit bff43b0

Browse files
AndreyBelymAlexanderMoskovkin
authored andcommitted
Handle SIGINT via async-exit-hook (closes DevExpress#1378) (DevExpress#1780)
1 parent 038997c commit bff43b0

15 files changed

Lines changed: 138 additions & 39 deletions

File tree

bin/testcafe-with-v8-flag-filter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
var path = require('path');
66
var v8FlagsFilter = require('bin-v8-flags-filter');
77

8-
v8FlagsFilter(path.join(__dirname, '../lib/cli'));
8+
v8FlagsFilter(path.join(__dirname, '../lib/cli'), { useShutdownMessage: true });

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@
5454
"prepublish": "publish-please guard"
5555
},
5656
"dependencies": {
57+
"async-exit-hook": "^1.1.2",
5758
"babel-core": "^6.22.1",
5859
"babel-plugin-transform-class-properties": "^6.24.1",
5960
"babel-plugin-transform-runtime": "^6.22.0",
6061
"babel-preset-env": "^1.1.8",
6162
"babel-preset-flow": "^6.23.0",
6263
"babel-preset-stage-2": "^6.22.0",
6364
"babel-runtime": "^6.22.0",
64-
"bin-v8-flags-filter": "^1.0.0",
65+
"bin-v8-flags-filter": "^1.1.2",
6566
"callsite": "^1.0.0",
6667
"callsite-record": "^4.0.0",
6768
"chai": "^3.0.0",
@@ -78,7 +79,7 @@
7879
"is-ci": "^1.0.10",
7980
"is-glob": "^2.0.1",
8081
"lodash": "^4.13.1",
81-
"log-update": "^1.0.2",
82+
"log-update-async-hook": "^2.0.2",
8283
"map-reverse": "^1.0.1",
8384
"mkdirp": "^0.5.1",
8485
"moment": "^2.10.3",

src/browser/connection/gateway.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ export default class BrowserConnectionGateway {
7575

7676
static onHeartbeat (req, res, connection) {
7777
if (BrowserConnectionGateway.ensureConnectionReady(res, connection)) {
78-
connection.heartbeat();
79-
res.end();
78+
var status = connection.heartbeat();
79+
80+
respondWithJSON(res, status);
8081
}
8182
}
8283

src/browser/connection/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { readSync as read } from 'read-file-relative';
77
import promisifyEvent from 'promisify-event';
88
import shortId from 'shortid';
99
import COMMAND from './command';
10+
import STATUS from './status';
1011
import { GeneralError } from '../../errors/runtime';
1112
import MESSAGE from '../../errors/runtime/message';
1213

@@ -36,6 +37,7 @@ export default class BrowserConnection extends EventEmitter {
3637
this.provider = browserInfo.provider;
3738

3839
this.permanent = permanent;
40+
this.closing = false;
3941
this.closed = false;
4042
this.ready = false;
4143
this.opened = false;
@@ -164,9 +166,11 @@ export default class BrowserConnection extends EventEmitter {
164166
}
165167

166168
close () {
167-
if (this.closed)
169+
if (this.closed || this.closing)
168170
return;
169171

172+
this.closing = true;
173+
170174
this._closeBrowser()
171175
.then(() => {
172176
this.browserConnectionGateway.stopServingConnection(this);
@@ -193,6 +197,11 @@ export default class BrowserConnection extends EventEmitter {
193197
heartbeat () {
194198
clearTimeout(this.heartbeatTimeout);
195199
this._waitForHeartbeat();
200+
201+
return {
202+
code: this.closing ? STATUS.closing : STATUS.ok,
203+
url: this.closing ? this.idleUrl : ''
204+
};
196205
}
197206

198207
renderIdlePage () {

src/browser/connection/status.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
ok: 'ok',
3+
closing: 'closing'
4+
};

src/browser/provider/built-in/locally-installed.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ export default {
1717
await browserTools.open(openParameters, pageUrl);
1818
},
1919

20-
async closeBrowser (browserId) {
21-
await browserTools.close(browserId);
22-
},
23-
2420
async isLocalBrowser () {
2521
return true;
2622
},

src/browser/provider/built-in/path.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,6 @@ export default {
5555
await browserTools.open(openParameters, pageUrl);
5656
},
5757

58-
async closeBrowser (browserId) {
59-
await browserTools.close(browserId);
60-
},
61-
6258
async isLocalBrowser () {
6359
return true;
6460
}

src/browser/provider/index.js

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ function subtractSizes (sizeA, sizeB) {
4848

4949
export default class BrowserProvider {
5050
constructor (plugin) {
51-
this.plugin = plugin;
52-
this.initPromise = Promise.resolve(false);
51+
this.plugin = plugin;
52+
this.initPromise = Promise.resolve(false);
5353

54-
this.isMultiBrowser = this.plugin.isMultiBrowser;
54+
this.isMultiBrowser = this.plugin.isMultiBrowser;
5555
// HACK: The browser window has different border sizes in normal and maximized modes. So, we need to be sure that the window is
5656
// not maximized before resizing it in order to keep the mechanism of correcting the client area size working. When browser is started,
5757
// we are resizing it for the first time to switch the window to normal mode, and for the second time - to restore the client area size.
@@ -195,7 +195,17 @@ export default class BrowserProvider {
195195
}
196196

197197
async closeBrowser (browserId) {
198-
await this.plugin.closeBrowser(browserId);
198+
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
199+
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
200+
var hasCustomCloseBrowser = customActionsInfo.hasCloseBrowser;
201+
var usePluginsCloseBrowser = hasCustomCloseBrowser || !isLocalBrowser;
202+
203+
if (usePluginsCloseBrowser) {
204+
await this.plugin.closeBrowser(browserId);
205+
return;
206+
}
207+
208+
await browserTools.close(this.windowDescriptors[browserId]);
199209
}
200210

201211
async getBrowserList () {
@@ -207,10 +217,12 @@ export default class BrowserProvider {
207217
}
208218

209219
async resizeWindow (browserId, width, height, currentWidth, currentHeight) {
210-
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
211-
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
220+
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
221+
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
222+
var hasCustomResizeWindow = customActionsInfo.hasResizeWindow;
223+
212224

213-
if (isLocalBrowser && !supportedFeatures.hasResizeWindow) {
225+
if (isLocalBrowser && !hasCustomResizeWindow) {
214226
await this._resizeLocalBrowserWindow(browserId, width, height, currentWidth, currentHeight);
215227
return;
216228
}
@@ -219,30 +231,34 @@ export default class BrowserProvider {
219231
}
220232

221233
async canResizeWindowToDimensions (browserId, width, height) {
222-
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
223-
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
234+
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
235+
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
236+
var hasCustomCanResizeToDimensions = customActionsInfo.hasCanResizeWindowToDimensions;
237+
224238

225-
if (isLocalBrowser && !supportedFeatures.hasCanResizeWindowToDimensions)
239+
if (isLocalBrowser && !hasCustomCanResizeToDimensions)
226240
return await this._canResizeLocalBrowserWindowToDimensions(browserId, width, height);
227241

228242
return await this.plugin.canResizeWindowToDimensions(browserId, width, height);
229243
}
230244

231245
async maximizeWindow (browserId) {
232-
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
233-
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
246+
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
247+
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
248+
var hasCustomMaximizeWindow = customActionsInfo.hasMaximizeWindow;
234249

235-
if (isLocalBrowser && !supportedFeatures.hasCanResizeWindowToDimensions)
250+
if (isLocalBrowser && !hasCustomMaximizeWindow)
236251
return await this._maximizeLocalBrowserWindow(browserId);
237252

238253
return await this.plugin.maximizeWindow(browserId);
239254
}
240255

241256
async takeScreenshot (browserId, screenshotPath, pageWidth, pageHeight) {
242-
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
243-
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
257+
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
258+
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
259+
var hasCustomTakeScreenshot = customActionsInfo.hasTakeScreenshot;
244260

245-
if (isLocalBrowser && !supportedFeatures.hasTakeScreenshot) {
261+
if (isLocalBrowser && !hasCustomTakeScreenshot) {
246262
await this._takeLocalBrowserScreenshot(browserId, screenshotPath, pageWidth, pageHeight);
247263
return;
248264
}

src/browser/provider/plugin-host.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export default class BrowserProviderPluginHost {
8787

8888
async hasCustomActionForBrowser (/* browserId */) {
8989
return {
90+
hasCloseBrowser: this.hasOwnProperty('closeBrowser'),
9091
hasResizeWindow: this.hasOwnProperty('resizeWindow'),
9192
hasTakeScreenshot: this.hasOwnProperty('takeScreenshot'),
9293
hasCanResizeWindowToDimensions: this.hasOwnProperty('canResizeWindowToDimensions'),

src/cli/index.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import fs from 'fs';
12
import chalk from 'chalk';
23
import resolveCwd from 'resolve-cwd';
3-
import fs from 'fs';
44
import browserProviderPool from '../browser/provider/pool';
55
import { GeneralError, APIError } from '../errors/runtime';
66
import MESSAGE from '../errors/runtime/message';
@@ -9,8 +9,53 @@ import log from './log';
99
import remotesWizard from './remotes-wizard';
1010
import createTestCafe from '../';
1111

12+
13+
const TERMINATION_TYPES = {
14+
sigint: 'sigint',
15+
sigbreak: 'sigbreak',
16+
shutdown: 'shutdown'
17+
};
18+
19+
var showMessageOnExit = true;
20+
var exitMessageShown = false;
21+
var exiting = false;
22+
23+
var handledSignalsCount = {
24+
[TERMINATION_TYPES.sigint]: 0,
25+
[TERMINATION_TYPES.sigbreak]: 0,
26+
[TERMINATION_TYPES.shutdown]: 0
27+
};
28+
29+
function exitHandler (terminationType) {
30+
handledSignalsCount[terminationType]++;
31+
32+
if (showMessageOnExit && !exitMessageShown) {
33+
exitMessageShown = true;
34+
35+
log.hideSpinner();
36+
log.write('Stopping TestCafe...');
37+
log.showSpinner();
38+
39+
process.on('exit', () => log.hideSpinner(true));
40+
}
41+
42+
if (exiting || handledSignalsCount[terminationType] < 2)
43+
return;
44+
45+
exiting = true;
46+
47+
exit(0);
48+
}
49+
50+
function setupExitHandler () {
51+
process.on('SIGINT', () => exitHandler(TERMINATION_TYPES.sigint));
52+
process.on('SIGBREAK', () => exitHandler(TERMINATION_TYPES.sigbreak));
53+
54+
process.on('message', message => message === 'shutdown' && exitHandler(TERMINATION_TYPES.shutdown));
55+
}
56+
1257
function exit (code) {
13-
log.hideSpinner();
58+
log.hideSpinner(true);
1459

1560
// NOTE: give a process time to flush the output.
1661
// It's necessary in some environments.
@@ -78,6 +123,7 @@ async function runTests (argParser) {
78123
}
79124

80125
finally {
126+
showMessageOnExit = false;
81127
await testCafe.close();
82128
}
83129

@@ -123,6 +169,8 @@ function useLocalInstallation () {
123169
if (useLocalInstallation())
124170
return;
125171

172+
setupExitHandler(exitHandler);
173+
126174
try {
127175
var argParser = new CliArgumentParser();
128176

@@ -134,6 +182,7 @@ function useLocalInstallation () {
134182
await runTests(argParser);
135183
}
136184
catch (err) {
185+
showMessageOnExit = false;
137186
error(err);
138187
}
139188
})();

0 commit comments

Comments
 (0)