Skip to content
This repository has been archived by the owner on Sep 21, 2022. It is now read-only.

Commit

Permalink
dev
Browse files Browse the repository at this point in the history
  • Loading branch information
eGavr committed Nov 10, 2016
1 parent 8fa16a3 commit 56e332a
Show file tree
Hide file tree
Showing 24 changed files with 535 additions and 131 deletions.
16 changes: 16 additions & 0 deletions doc/programmatic-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,19 @@ module.exports = (gemini, options) => {
});
};
```

## Cancellation

Use `gemini.cancel(exitCode)` method.

`exitCode` is an exit code with which gemini will be canceled. If exit code has not been passed, gemini will be canceled with exit code `0`. Method is asynchronous. It can be used to cancel gemini from plugins:

```js
module.exports = (gemini, options) => {
gemini.on(gemini.events.START_RUNNER, () => {
if (/* something terrible has happened */) {
return gemini.cancel(100500);
}
});
};
```
16 changes: 5 additions & 11 deletions lib/browser-pool/basic-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
var util = require('util'),
Browser = require('../browser'),
Pool = require('./pool'),
signalHandler = require('../signal-handler'),
_ = require('lodash'),
Promise = require('bluebird');

Expand Down Expand Up @@ -46,15 +45,10 @@ Pool.prototype.freeBrowser = function(browser) {
return browser.quit();
};

signalHandler.on('exit', function() {
console.log('Killing browsers...');
return _(activeSessions)
.map(function(session) {
var quit_ = session.browser.quit.bind(session.browser);
return session.launchPromise.then(quit_);
})
.thru(Promise.all)
.value();
});
Pool.prototype.cancel = function() {
const sessions = _.values(activeSessions);

return Promise.map(sessions, (session) => session.launchPromise.then(() => session.browser.quit()).catch(_.noop));
};

module.exports = BasicPool;
2 changes: 1 addition & 1 deletion lib/browser-pool/caching-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ CachingPool.prototype.freeBrowser = function(browser, options) {

CachingPool.prototype.cancel = function() {
log('cancel');
this.underlyingPool.cancel();
return this.underlyingPool.cancel();
};

module.exports = CachingPool;
4 changes: 2 additions & 2 deletions lib/browser-pool/limited-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ module.exports = class LimitedPool extends Pool {

cancel() {
log('cancel');
this._requestQueue.forEach((entry) => entry.reject(new CancelledError()));
this._requestQueue.forEach((queued) => queued.reject(new CancelledError()));

this._requestQueue.length = 0;
this.underlyingPool.cancel();
return this.underlyingPool.cancel();
}

_getBrowser(id) {
Expand Down
3 changes: 2 additions & 1 deletion lib/browser-pool/per-browser-limited-pool.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const Pool = require('./pool');
const LimitedPool = require('./limited-pool');
const log = require('debug')('gemini:pool:per-browser-limited');
Expand Down Expand Up @@ -28,6 +29,6 @@ module.exports = class PerBrowserLimitedPool extends Pool {

cancel() {
log('cancel');
_.forEach(this._browserPools, (pool) => pool.cancel());
return Promise.map(_.values(this._browserPools), (pool) => pool.cancel());
}
};
2 changes: 1 addition & 1 deletion lib/browser/new-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ module.exports = class NewBrowser extends Browser {
.then(() => this._wd.quit())
.then(() => this.log('kill browser %o', this))
.then(() => this._setHttpTimeout())
.catch((err) => console.warn(err));
.catch((err) => this.log(err));
}

inspect() {
Expand Down
7 changes: 7 additions & 0 deletions lib/constants/signal-events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

module.exports = {
SIGHUP: 'SIGHUP',
SIGINT: 'SIGINT',
SIGTERM: 'SIGTERM'
};
5 changes: 5 additions & 0 deletions lib/constants/signal-handler-events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = {
EXIT: 'exit'
};
13 changes: 12 additions & 1 deletion lib/gemini.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ module.exports = class Gemini extends PassthroughEmitter {

setupLog(this.config.system.debug);
this._loadPlugins();

this._runner = null;
}

static readRawConfig(filePath) {
Expand All @@ -58,7 +60,7 @@ module.exports = class Gemini extends PassthroughEmitter {

temp.init(this.config.system.tempDir);

const runner = Runner.create(this.config, stateProcessor);
const runner = this._runner = Runner.create(this.config, stateProcessor);
const envBrowsers = parseBrowsers(process.env.GEMINI_BROWSERS);
const envSkipBrowsers = parseBrowsers(process.env.GEMINI_SKIP_BROWSERS);
const browsers = options.browsers || envBrowsers;
Expand Down Expand Up @@ -89,6 +91,7 @@ module.exports = class Gemini extends PassthroughEmitter {
const stats = new RunnerStats(runner);

return runner.run(suiteCollection)
.then(() => this._runner = null)
.then(() => stats.get());
});
}
Expand Down Expand Up @@ -146,6 +149,14 @@ module.exports = class Gemini extends PassthroughEmitter {
return q(this._run(StateProcessor.createTester(this.config), paths, options));
}

cancel(exitCode) {
if (!this._runner) {
return Promise.reject(new GeminiError('Gemini was not started, nothing to cancel'));
}

return this._runner.cancel().then(() => process.exit(exitCode));
}

getScreenshotPath(suite, stateName, browserId) {
return this.config.forBrowser(browserId).getScreenshotPath(suite, stateName);
}
Expand Down
20 changes: 14 additions & 6 deletions lib/reporters/html/view-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,36 @@ module.exports = class ViewModel {
* @param {TestStateResult} result
*/
addFail(result) {
this._addFailResult(result);

this._counter.onFailed(result);
}

_addFailResult(result) {
this._addTestResult(result, {
fail: true,
actualPath: lib.currentPath(result),
expectedPath: lib.referencePath(result),
diffPath: lib.diffPath(result)
});

this._counter.onFailed(result);
}

/**
* @param {ErrorStateResult} result
*/
addError(result) {
this._addErrorResult(result);

this._counter.onFailed(result);
}

_addErrorResult(result) {
this._addTestResult(result, {
actualPath: result.state ? lib.currentPath(result) : '',
error: true,
image: !!result.imagePath || !!result.currentPath,
reason: (result.stack || result.message || result || '')
});

this._counter.onFailed(result);
}

/**
Expand All @@ -91,9 +99,9 @@ module.exports = class ViewModel {
*/
addRetry(result) {
if (result.hasOwnProperty('equal')) {
this.addFail(result);
this._addFailResult(result);
} else {
this.addError(result);
this._addErrorResult(result);
}

this._counter.onRetry(result);
Expand Down
6 changes: 0 additions & 6 deletions lib/runner/browser-runner/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const promiseUtils = require('q-promise-utils');
const BrowserAgent = require('./browser-agent');
const Runner = require('../runner');
Expand Down Expand Up @@ -35,14 +34,9 @@ module.exports = class BrowserRunner extends Runner {
}

cancel() {
this._runSuite = this._doNothing;
this._suiteRunners.forEach((runner) => runner.cancel());
}

_doNothing() {
return Promise.resolve();
}

_runSuites(suiteCollection, stateProcessor) {
const suites = suiteCollection.clone().allSuites();

Expand Down
21 changes: 11 additions & 10 deletions lib/runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ const promiseUtils = require('q-promise-utils');
const pool = require('../browser-pool');
const BrowserRunner = require('./browser-runner');
const Events = require('../constants/events');
const SignalHandlerEvents = require('../constants/signal-handler-events');
const Coverage = require('../coverage');
const Runner = require('./runner');
const SignalHandler = require('../signal-handler');
const SuiteMonitor = require('../suite-monitor');

module.exports = class TestsRunner extends Runner {
Expand Down Expand Up @@ -37,14 +39,17 @@ module.exports = class TestsRunner extends Runner {
}

run(suiteCollection) {
const signalHandler = SignalHandler.create();

return Promise.resolve(this.emitAndWait(Events.START_RUNNER, this))
.then(() => this.emit(Events.BEGIN, this._formatBeginEventData(suiteCollection)))
.then(() => this._stateProcessor.prepare(this))
.then(() => signalHandler.start().on(SignalHandlerEvents.EXIT, () => this.cancel()))
.then(() => this._runTests(suiteCollection))
.then(() => this.coverage && this.coverage.processStats())
.finally(() => {
this.emit(Events.END);
return this.emitAndWait(Events.END_RUNNER, this);
return this.emitAndWait(Events.END_RUNNER, this).then(() => signalHandler.end());
});
}

Expand Down Expand Up @@ -76,8 +81,6 @@ module.exports = class TestsRunner extends Runner {
_runTestsInBrowser(suiteCollection, browserId) {
const runner = BrowserRunner.create(browserId, this.config, this._browserPool);

this._browserRunners.push(runner);

this.passthroughEvent(runner, [
Events.RETRY,
Events.START_BROWSER,
Expand All @@ -97,11 +100,8 @@ module.exports = class TestsRunner extends Runner {
runner.on(Events.TEST_RESULT, (result) => this._handleResult(result, [Events.END_TEST, Events.TEST_RESULT]));
runner.on(Events.UPDATE_RESULT, (result) => this._handleResult(result, Events.UPDATE_RESULT));

return runner.run(suiteCollection, this._stateProcessor)
.catch((e) => {
this._cancel();
return Promise.reject(e);
});
this._browserRunners.push(runner);
return runner.run(suiteCollection, this._stateProcessor);
}

_handleResult(result, events) {
Expand All @@ -116,8 +116,9 @@ module.exports = class TestsRunner extends Runner {
}
}

_cancel() {
cancel() {
this._browserRunners.forEach((runner) => runner.cancel());
this._browserPool.cancel();

return this._browserPool.cancel();
}
};
6 changes: 1 addition & 5 deletions lib/runner/suite-runner/insistent-suite-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ module.exports = class InsistentSuiteRunner extends SuiteRunner {
this._retriesPerformed = 0;
}

cancel() {
this._suiteRunner.cancel();
}

_doRun(stateProcessor) {
this._suiteRunner = this._initSuiteRunner();
this._statesToRetry = [];
Expand Down Expand Up @@ -61,7 +57,7 @@ module.exports = class InsistentSuiteRunner extends SuiteRunner {
}

_retry(stateProcessor) {
if (_.isEmpty(this._statesToRetry)) {
if (_.isEmpty(this._statesToRetry) || this._canceled) {
return;
}

Expand Down
6 changes: 5 additions & 1 deletion lib/runner/suite-runner/suite-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ var SuiteRunner = inherit(Runner, {
throw new Error('Not implemented');
},

cancel: _.noop
cancel: function() {
this._canceled = true;

this.emit = _.noop;
}
});

module.exports = SuiteRunner;
52 changes: 30 additions & 22 deletions lib/signal-handler.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
'use strict';

var QEmitter = require('qemitter'),
signalHandler = new QEmitter();
const QEmitter = require('qemitter');
const SignalEvents = require('./constants/signal-events');
const SignalHandlerEvents = require('./constants/signal-handler-events');
const logger = require('./utils').logger;

module.exports = signalHandler;
module.exports = class SignalHandler extends QEmitter {
static create() {
return new SignalHandler();
}

process.on('SIGHUP', notifyAndExit(1));
process.on('SIGINT', notifyAndExit(2));
process.on('SIGTERM', notifyAndExit(15));
start() {
process.on(SignalEvents.SIGHUP, () => this._handleSignal(1));
process.on(SignalEvents.SIGINT, () => this._handleSignal(2));
process.on(SignalEvents.SIGTERM, () => this._handleSignal(15));

var callCount = 0;
return this;
}

function notifyAndExit(signalNo) {
var exitCode = 128 + signalNo;

return function() {
if (callCount++ > 0) {
console.log('Force quit.');
process.exit(exitCode);
_handleSignal(signalNum) {
if (this._exitCode) {
logger.warn('Force exit.');
process.exit(this._exitCode);
}

signalHandler.emitAndWait('exit')
.then(function() {
console.log('Done.');
process.exit(exitCode);
})
.done();
};
}
this._exitCode = 128 + signalNum;

logger.warn('Exiting...');
return this.emitAndWait(SignalHandlerEvents.EXIT);
}

end() {
if (this._exitCode) {
process.exit(this._exitCode);
}
}
};
Loading

0 comments on commit 56e332a

Please sign in to comment.