Skip to content

Commit

Permalink
Handle SIGINT via async-exit-hook (closes DevExpress#1378)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyBelym committed Sep 19, 2017
1 parent 9c04cd7 commit 6ad6ba4
Show file tree
Hide file tree
Showing 15 changed files with 138 additions and 39 deletions.
2 changes: 1 addition & 1 deletion bin/testcafe-with-v8-flag-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
var path = require('path');
var v8FlagsFilter = require('bin-v8-flags-filter');

v8FlagsFilter(path.join(__dirname, '../lib/cli'));
v8FlagsFilter(path.join(__dirname, '../lib/cli'), { useShutdownMessage: true });
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@
"prepublish": "publish-please guard"
},
"dependencies": {
"async-exit-hook": "^1.1.2",
"babel-core": "^6.22.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.1.8",
"babel-preset-flow": "^6.23.0",
"babel-preset-stage-2": "^6.22.0",
"babel-runtime": "^6.22.0",
"bin-v8-flags-filter": "^1.0.0",
"bin-v8-flags-filter": "^1.1.2",
"callsite": "^1.0.0",
"callsite-record": "^4.0.0",
"chai": "^3.0.0",
Expand All @@ -78,7 +79,7 @@
"is-ci": "^1.0.10",
"is-glob": "^2.0.1",
"lodash": "^4.13.1",
"log-update": "^1.0.2",
"log-update-async-hook": "^2.0.2",
"map-reverse": "^1.0.1",
"mkdirp": "^0.5.1",
"moment": "^2.10.3",
Expand Down
5 changes: 3 additions & 2 deletions src/browser/connection/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ export default class BrowserConnectionGateway {

static onHeartbeat (req, res, connection) {
if (BrowserConnectionGateway.ensureConnectionReady(res, connection)) {
connection.heartbeat();
res.end();
var status = connection.heartbeat();

respondWithJSON(res, status);
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/browser/connection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { readSync as read } from 'read-file-relative';
import promisifyEvent from 'promisify-event';
import shortId from 'shortid';
import COMMAND from './command';
import STATUS from './status';
import { GeneralError } from '../../errors/runtime';
import MESSAGE from '../../errors/runtime/message';

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

this.permanent = permanent;
this.closing = false;
this.closed = false;
this.ready = false;
this.opened = false;
Expand Down Expand Up @@ -164,9 +166,11 @@ export default class BrowserConnection extends EventEmitter {
}

close () {
if (this.closed)
if (this.closed || this.closing)
return;

this.closing = true;

this._closeBrowser()
.then(() => {
this.browserConnectionGateway.stopServingConnection(this);
Expand All @@ -193,6 +197,11 @@ export default class BrowserConnection extends EventEmitter {
heartbeat () {
clearTimeout(this.heartbeatTimeout);
this._waitForHeartbeat();

return {
code: this.closing ? STATUS.closing : STATUS.ok,
url: this.closing ? this.idleUrl : ''
};
}

renderIdlePage () {
Expand Down
4 changes: 4 additions & 0 deletions src/browser/connection/status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
ok: 'ok',
closing: 'closing'
};
4 changes: 0 additions & 4 deletions src/browser/provider/built-in/locally-installed.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ export default {
await browserTools.open(openParameters, pageUrl);
},

async closeBrowser (browserId) {
await browserTools.close(browserId);
},

async isLocalBrowser () {
return true;
},
Expand Down
4 changes: 0 additions & 4 deletions src/browser/provider/built-in/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ export default {
await browserTools.open(openParameters, pageUrl);
},

async closeBrowser (browserId) {
await browserTools.close(browserId);
},

async isLocalBrowser () {
return true;
}
Expand Down
48 changes: 32 additions & 16 deletions src/browser/provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ function subtractSizes (sizeA, sizeB) {

export default class BrowserProvider {
constructor (plugin) {
this.plugin = plugin;
this.initPromise = Promise.resolve(false);
this.plugin = plugin;
this.initPromise = Promise.resolve(false);

this.isMultiBrowser = this.plugin.isMultiBrowser;
this.isMultiBrowser = this.plugin.isMultiBrowser;
// HACK: The browser window has different border sizes in normal and maximized modes. So, we need to be sure that the window is
// not maximized before resizing it in order to keep the mechanism of correcting the client area size working. When browser is started,
// 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.
Expand Down Expand Up @@ -195,7 +195,17 @@ export default class BrowserProvider {
}

async closeBrowser (browserId) {
await this.plugin.closeBrowser(browserId);
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
var hasCustomCloseBrowser = customActionsInfo.hasCloseBrowser;
var usePluginsCloseBrowser = hasCustomCloseBrowser || !isLocalBrowser;

if (usePluginsCloseBrowser) {
await this.plugin.closeBrowser(browserId);
return;
}

await browserTools.close(this.windowDescriptors[browserId]);
}

async getBrowserList () {
Expand All @@ -207,10 +217,12 @@ export default class BrowserProvider {
}

async resizeWindow (browserId, width, height, currentWidth, currentHeight) {
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
var hasCustomResizeWindow = customActionsInfo.hasResizeWindow;


if (isLocalBrowser && !supportedFeatures.hasResizeWindow) {
if (isLocalBrowser && !hasCustomResizeWindow) {
await this._resizeLocalBrowserWindow(browserId, width, height, currentWidth, currentHeight);
return;
}
Expand All @@ -219,30 +231,34 @@ export default class BrowserProvider {
}

async canResizeWindowToDimensions (browserId, width, height) {
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
var hasCustomCanResizeToDimensions = customActionsInfo.hasCanResizeWindowToDimensions;


if (isLocalBrowser && !supportedFeatures.hasCanResizeWindowToDimensions)
if (isLocalBrowser && !hasCustomCanResizeToDimensions)
return await this._canResizeLocalBrowserWindowToDimensions(browserId, width, height);

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

async maximizeWindow (browserId) {
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
var hasCustomMaximizeWindow = customActionsInfo.hasMaximizeWindow;

if (isLocalBrowser && !supportedFeatures.hasCanResizeWindowToDimensions)
if (isLocalBrowser && !hasCustomMaximizeWindow)
return await this._maximizeLocalBrowserWindow(browserId);

return await this.plugin.maximizeWindow(browserId);
}

async takeScreenshot (browserId, screenshotPath, pageWidth, pageHeight) {
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId);
var isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
var customActionsInfo = await this.plugin.hasCustomActionForBrowser(browserId);
var hasCustomTakeScreenshot = customActionsInfo.hasTakeScreenshot;

if (isLocalBrowser && !supportedFeatures.hasTakeScreenshot) {
if (isLocalBrowser && !hasCustomTakeScreenshot) {
await this._takeLocalBrowserScreenshot(browserId, screenshotPath, pageWidth, pageHeight);
return;
}
Expand Down
1 change: 1 addition & 0 deletions src/browser/provider/plugin-host.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export default class BrowserProviderPluginHost {

async hasCustomActionForBrowser (/* browserId */) {
return {
hasCloseBrowser: this.hasOwnProperty('closeBrowser'),
hasResizeWindow: this.hasOwnProperty('resizeWindow'),
hasTakeScreenshot: this.hasOwnProperty('takeScreenshot'),
hasCanResizeWindowToDimensions: this.hasOwnProperty('canResizeWindowToDimensions'),
Expand Down
53 changes: 51 additions & 2 deletions src/cli/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';
import chalk from 'chalk';
import resolveCwd from 'resolve-cwd';
import fs from 'fs';
import browserProviderPool from '../browser/provider/pool';
import { GeneralError, APIError } from '../errors/runtime';
import MESSAGE from '../errors/runtime/message';
Expand All @@ -9,8 +9,53 @@ import log from './log';
import remotesWizard from './remotes-wizard';
import createTestCafe from '../';


const TERMINATION_TYPES = {
sigint: 'sigint',
sigbreak: 'sigbreak',
shutdown: 'shutdown'
};

var showMessageOnExit = true;
var exitMessageShown = false;
var exiting = false;

var handledSignalsCount = {
[TERMINATION_TYPES.sigint]: 0,
[TERMINATION_TYPES.sigbreak]: 0,
[TERMINATION_TYPES.shutdown]: 0
};

function exitHandler (terminationType) {
handledSignalsCount[terminationType]++;

if (showMessageOnExit && !exitMessageShown) {
exitMessageShown = true;

log.hideSpinner();
log.write('Stopping TestCafe...');
log.showSpinner();

process.on('exit', () => log.hideSpinner(true));
}

if (exiting || handledSignalsCount[terminationType] < 2)
return;

exiting = true;

exit(0);
}

function setupExitHandler () {
process.on('SIGINT', () => exitHandler(TERMINATION_TYPES.sigint));
process.on('SIGBREAK', () => exitHandler(TERMINATION_TYPES.sigbreak));

process.on('message', message => message === 'shutdown' && exitHandler(TERMINATION_TYPES.shutdown));
}

function exit (code) {
log.hideSpinner();
log.hideSpinner(true);

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

finally {
showMessageOnExit = false;
await testCafe.close();
}

Expand Down Expand Up @@ -123,6 +169,8 @@ function useLocalInstallation () {
if (useLocalInstallation())
return;

setupExitHandler(exitHandler);

try {
var argParser = new CliArgumentParser();

Expand All @@ -134,6 +182,7 @@ function useLocalInstallation () {
await runTests(argParser);
}
catch (err) {
showMessageOnExit = false;
error(err);
}
})();
Expand Down
7 changes: 5 additions & 2 deletions src/cli/log.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import tty from 'tty';
import elegantSpinner from 'elegant-spinner';
import logUpdate from 'log-update';
import logUpdate from 'log-update-async-hook';
import chalk from 'chalk';
import isCI from 'is-ci';

Expand All @@ -24,11 +24,14 @@ export default {
}
},

hideSpinner () {
hideSpinner (isExit) {
if (this.animation) {
clearInterval(this.animation);
logUpdate.stderr.clear();

if (isExit)
logUpdate.stderr.done();

this.animation = null;
}
},
Expand Down
18 changes: 15 additions & 3 deletions src/client/browser/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// TODO: once we'll have client commons load it from there instead of node modules (currently it's leads to two copies of this packages on client)
import Promise from 'pinkie';
import COMMAND from '../../browser/connection/command';
import STATUS from '../../browser/connection/status';

const HEARTBEAT_INTERVAL = 30 * 1000;
const HEARTBEAT_INTERVAL = 2 * 1000;

var allowInitScriptExecution = false;

Expand Down Expand Up @@ -37,8 +38,19 @@ function isCurrentLocation (url) {

//API
export function startHeartbeat (heartbeatUrl, createXHR) {
sendXHR(heartbeatUrl, createXHR);
window.setInterval(() => sendXHR(heartbeatUrl, createXHR), HEARTBEAT_INTERVAL);
function heartbeat () {
sendXHR(heartbeatUrl, createXHR)
.then(status => {
if (status.code === STATUS.closing && !isCurrentLocation(status.url)) {
stopInitScriptExecution();
document.location = status.url;
}
});
}

window.setInterval(heartbeat, HEARTBEAT_INTERVAL);

heartbeat();
}

function executeInitScript (initScriptUrl, createXHR) {
Expand Down
7 changes: 6 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Promise from 'pinkie';
import TestCafe from './testcafe';
import * as endpointUtils from 'endpoint-utils';
import setupExitHook from 'async-exit-hook';
import { GeneralError } from './errors/runtime';
import MESSAGE from './errors/runtime/message';
import embeddingUtils from './embedding-utils';
Expand Down Expand Up @@ -42,7 +43,11 @@ async function createTestCafe (hostname, port1, port2) {
getValidPort(port2)
]);

return new TestCafe(hostname, port1, port2);
var testcafe = new TestCafe(hostname, port1, port2);

setupExitHook(cb => testcafe.close().then(cb));

return testcafe;
}

// Embedding utils
Expand Down
2 changes: 1 addition & 1 deletion src/notifications/debug-logger.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import chalk from 'chalk';
import { findIndex } from 'lodash';
import logUpdate from 'log-update';
import logUpdate from 'log-update-async-hook';
import createStackFilter from '../errors/create-stack-filter';

export default {
Expand Down
Loading

0 comments on commit 6ad6ba4

Please sign in to comment.