From 648836bc802fcbdf808285aa5b67205c78f9a5f2 Mon Sep 17 00:00:00 2001 From: Andrey Belym Date: Wed, 12 Jul 2017 18:28:32 +0300 Subject: [PATCH] Handle SIGINT via async-exit-hook (closes #1378) (#1576) * Handle SIGINT via async-exit-hook (closes #1378) * Use updated package * Fix typo * Add message --- package.json | 3 ++- src/browser/connection/gateway.js | 5 +++-- src/browser/connection/index.js | 11 ++++++++++- src/browser/connection/status.js | 4 ++++ src/cli/index.js | 17 +++++++++++++++++ src/cli/log.js | 2 +- src/client/browser/index.js | 19 +++++++++++++++---- src/index.js | 7 ++++++- src/testcafe.js | 6 ++++++ 9 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 src/browser/connection/status.js diff --git a/package.json b/package.json index 3aef14d546d..e554f2e76cc 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "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", @@ -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", diff --git a/src/browser/connection/gateway.js b/src/browser/connection/gateway.js index 6d5a70397ff..2b2f0a19334 100644 --- a/src/browser/connection/gateway.js +++ b/src/browser/connection/gateway.js @@ -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); } } diff --git a/src/browser/connection/index.js b/src/browser/connection/index.js index a7ba2e0c14f..7f7b70a335d 100644 --- a/src/browser/connection/index.js +++ b/src/browser/connection/index.js @@ -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'; @@ -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; @@ -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); @@ -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 () { diff --git a/src/browser/connection/status.js b/src/browser/connection/status.js new file mode 100644 index 00000000000..ce916980555 --- /dev/null +++ b/src/browser/connection/status.js @@ -0,0 +1,4 @@ +export default { + ok: 'ok', + closing: 'closing' +}; diff --git a/src/cli/index.js b/src/cli/index.js index 3bf4ac77361..50d405eddf2 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -1,5 +1,6 @@ import chalk from 'chalk'; import resolveCwd from 'resolve-cwd'; +import setupExitHook from 'async-exit-hook'; import browserProviderPool from '../browser/provider/pool'; import { GeneralError, APIError } from '../errors/runtime'; import MESSAGE from '../errors/runtime/message'; @@ -8,6 +9,18 @@ import log from './log'; import remotesWizard from './remotes-wizard'; import createTestCafe from '../'; + +var showMessageOnExit = true; + +function exitHandler () { + if (!showMessageOnExit) + return; + + log.hideSpinner(); + log.write('Stopping TestCafe...'); + log.showSpinner(); +} + function exit (code) { log.hideSpinner(); @@ -70,6 +83,7 @@ async function runTests (argParser) { } finally { + showMessageOnExit = false; await testCafe.close(); } @@ -115,6 +129,8 @@ function useLocalInstallation () { if (useLocalInstallation()) return; + setupExitHook(exitHandler); + try { var argParser = new CliArgumentParser(); @@ -126,6 +142,7 @@ function useLocalInstallation () { await runTests(argParser); } catch (err) { + showMessageOnExit = false; error(err); } })(); diff --git a/src/cli/log.js b/src/cli/log.js index a55bb8baa72..64eee0ecdf8 100644 --- a/src/cli/log.js +++ b/src/cli/log.js @@ -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'; diff --git a/src/client/browser/index.js b/src/client/browser/index.js index fe3a6780c8e..d79ff9771f8 100644 --- a/src/client/browser/index.js +++ b/src/client/browser/index.js @@ -1,9 +1,10 @@ // 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; @@ -36,9 +37,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) { diff --git a/src/index.js b/src/index.js index e312599808a..b03ebd9c922 100644 --- a/src/index.js +++ b/src/index.js @@ -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'; @@ -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 diff --git a/src/testcafe.js b/src/testcafe.js index c8b88590bf0..253a3332a50 100644 --- a/src/testcafe.js +++ b/src/testcafe.js @@ -19,6 +19,7 @@ const FAVICON = read('./client/ui/favicon.ico', true); export default class TestCafe { constructor (hostname, port1, port2) { + this.closed = false; this.proxy = new Proxy(hostname, port1, port2); this.browserConnectionGateway = new BrowserConnectionGateway(this.proxy); this.runners = []; @@ -62,6 +63,11 @@ export default class TestCafe { } async close () { + if (this.closed) + return; + + this.closed = true; + await Promise.all(this.runners.map(runner => runner.stop())); await browserProviderPool.dispose();