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

Commit

Permalink
feat: wait for plugins load
Browse files Browse the repository at this point in the history
j0tunn committed Oct 31, 2017

Verified

This commit was signed with the committer’s verified signature. The key has expired.
ripcurlx Christoph Atteneder
1 parent 3604dcb commit 340286f
Showing 3 changed files with 147 additions and 123 deletions.
170 changes: 87 additions & 83 deletions lib/gemini.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ const chalk = require('chalk');
const _ = require('lodash');
const PassthroughEmitter = require('./passthrough-emitter');
const Promise = require('bluebird');
const q = require('bluebird-q');
const bluebirdQ = require('bluebird-q');
const pluginsLoader = require('plugins-loader');
const gracefulFs = require('graceful-fs');

@@ -37,6 +37,10 @@ module.exports = class Gemini extends PassthroughEmitter {
return new Gemini(config, allowOverrides);
}

static readRawConfig(filePath) {
return Config.readRawConfig(filePath);
}

constructor(config, allowOverrides) {
super();

@@ -46,74 +50,42 @@ module.exports = class Gemini extends PassthroughEmitter {
this.SuiteCollection = SuiteCollection;

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

static readRawConfig(filePath) {
return Config.readRawConfig(filePath);
getScreenshotPath(suite, stateName, browserId) {
return this.config.forBrowser(browserId).getScreenshotPath(suite, stateName);
}

_run(stateProcessor, paths, options) {
if (!options) {
//if there are only two arguments, they are
//(stateProcessor, options) and paths are
//the default.
options = paths;
paths = undefined;
}
options = options || {};
options.reporters = options.reporters || [];

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

const runner = Runner.create(this.config, stateProcessor);
const envBrowsers = parseBrowsers(process.env.GEMINI_BROWSERS);
const envSkipBrowsers = parseBrowsers(process.env.GEMINI_SKIP_BROWSERS);

options.browsers = options.browsers || envBrowsers;

this._passThroughEvents(runner);
getBrowserCapabilites(browserId) {
return this.config.forBrowser(browserId).desiredCapabilities;
}

// it is important to require signal handler here in order to guarantee subscribing to "INTERRUPT" event
require('./signal-handler').on(Events.INTERRUPT, (data) => {
this.emit(Events.INTERRUPT, data);
getValidBrowsers(browsers) {
return _.intersection(browsers, this.browserIds);
}

runner.cancel();
});
checkUnknownBrowsers(browsers) {
const browsersFromConfig = this.browserIds;
const unknownBrowsers = _.difference(browsers, browsersFromConfig);

if (options.browsers) {
this.checkUnknownBrowsers(options.browsers);
if (unknownBrowsers.length) {
console.warn(
`${chalk.yellow('WARNING:')} Unknown browsers id: ${unknownBrowsers.join(', ')}.\n` +
`Use one of the browser ids specified in config file: ${browsersFromConfig.join(', ')}`
);
}
}

const getTests = (source, options) => {
return source instanceof SuiteCollection
? Promise.resolve(source)
: this.readTests(source, options);
};

return getTests(paths, options)
.then((suiteCollection) => {
this.checkUnknownBrowsers(envSkipBrowsers);

const validSkippedBrowsers = this.getValidBrowsers(envSkipBrowsers);

suiteCollection.skipBrowsers(validSkippedBrowsers);
options.reporters.forEach((reporter) => applyReporter(runner, reporter));

let testsStatistic;
runner.on(Events.END, (stats) => testsStatistic = stats);

return runner.run(suiteCollection)
.then(() => testsStatistic);
});
get browserIds() {
return this.config.getBrowserIds();
}

_passThroughEvents(runner) {
this.passthroughEvent(runner, _.values(Events));
update(paths, options) {
return this._exec(() => this._run(StateProcessor.createScreenUpdater(options), paths, options));
}

_loadPlugins() {
pluginsLoader.load(this, this.config.system.plugins, PREFIX);
test(paths, options) {
return this._exec(() => this._run(StateProcessor.createTester(this.config), paths, options));
}

readTests(paths, options) {
@@ -127,16 +99,25 @@ module.exports = class Gemini extends PassthroughEmitter {
options = options || {};
}

return this._exec(() => this._readTests(paths, options));
}

_exec(fn) {
const plugins = pluginsLoader.load(this, this.config.system.plugins, PREFIX);
return bluebirdQ(Promise.all(plugins).then(() => fn()));
}

_readTests(paths, options) {
options = _.assignIn(options, {paths});

return q(readTests(this, this.config, options)
return readTests(this, this.config, options)
.then((rootSuite) => {
if (options.grep) {
applyGrep_(options.grep, rootSuite);
}

return new SuiteCollection(rootSuite.children);
}));
});

function applyGrep_(grep, suite) {
if (!suite.hasStates) {
@@ -155,40 +136,63 @@ module.exports = class Gemini extends PassthroughEmitter {
}
}

update(paths, options) {
return q(this._run(StateProcessor.createScreenUpdater(options), paths, options));
}
_run(stateProcessor, paths, options) {
if (!options) {
//if there are only two arguments, they are
//(stateProcessor, options) and paths are
//the default.
options = paths;
paths = undefined;
}
options = options || {};
options.reporters = options.reporters || [];

test(paths, options) {
return q(this._run(StateProcessor.createTester(this.config), paths, options));
}
temp.init(this.config.system.tempDir);

getScreenshotPath(suite, stateName, browserId) {
return this.config.forBrowser(browserId).getScreenshotPath(suite, stateName);
}
const runner = Runner.create(this.config, stateProcessor);
const envBrowsers = parseBrowsers(process.env.GEMINI_BROWSERS);
const envSkipBrowsers = parseBrowsers(process.env.GEMINI_SKIP_BROWSERS);

getBrowserCapabilites(browserId) {
return this.config.forBrowser(browserId).desiredCapabilities;
}
options.browsers = options.browsers || envBrowsers;

getValidBrowsers(browsers) {
return _.intersection(browsers, this.browserIds);
}
this._passThroughEvents(runner);

checkUnknownBrowsers(browsers) {
const browsersFromConfig = this.browserIds;
const unknownBrowsers = _.difference(browsers, browsersFromConfig);
// it is important to require signal handler here in order to guarantee subscribing to "INTERRUPT" event
require('./signal-handler').on(Events.INTERRUPT, (data) => {
this.emit(Events.INTERRUPT, data);

if (unknownBrowsers.length) {
console.warn(
`${chalk.yellow('WARNING:')} Unknown browsers id: ${unknownBrowsers.join(', ')}.\n` +
`Use one of the browser ids specified in config file: ${browsersFromConfig.join(', ')}`
);
runner.cancel();
});

if (options.browsers) {
this.checkUnknownBrowsers(options.browsers);
}

const getTests = (source, options) => {
return source instanceof SuiteCollection
? Promise.resolve(source)
: this._readTests(source, options);
};

return getTests(paths, options)
.then((suiteCollection) => {
this.checkUnknownBrowsers(envSkipBrowsers);

const validSkippedBrowsers = this.getValidBrowsers(envSkipBrowsers);

suiteCollection.skipBrowsers(validSkippedBrowsers);
options.reporters.forEach((reporter) => applyReporter(runner, reporter));

let testsStatistic;
runner.on(Events.END, (stats) => testsStatistic = stats);

return runner.run(suiteCollection)
.then(() => testsStatistic);
});
}

get browserIds() {
return this.config.getBrowserIds();
_passThroughEvents(runner) {
this.passthroughEvent(runner, _.values(Events));
}
};

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@
"looks-same": "^3.0.0",
"micromatch": "^2.3.11",
"node-fetch": "^1.6.3",
"plugins-loader": "^1.0.1",
"plugins-loader": "^1.1.0",
"png-img": "^2.1.0",
"q-promise-utils": "^1.1.0",
"resolve": "^1.1.0",
98 changes: 59 additions & 39 deletions test/unit/gemini.js
Original file line number Diff line number Diff line change
@@ -67,57 +67,57 @@ describe('gemini', () => {
beforeEach(() => {
sandbox.stub(Runner.prototype, 'cancel').returns(Promise.resolve());
sandbox.stub(console, 'warn');
sandbox.stub(pluginsLoader, 'load');
sandbox.stub(pluginsLoader, 'load').returns([]);
sandbox.stub(temp, 'init');
});

afterEach(() => sandbox.restore());

it('should passthrough runner events', () => {
const runner = new EventEmitter();
sandbox.stub(Runner, 'create').returns(runner);
[
Events.START_RUNNER,
Events.END_RUNNER,
Events.BEGIN,
Events.END,

const gemini = initGemini({});
gemini.test();

[
Events.START_RUNNER,
Events.END_RUNNER,
Events.BEGIN,
Events.END,
Events.BEGIN_SESSION,
Events.END_SESSION,

Events.BEGIN_SESSION,
Events.END_SESSION,
Events.RETRY,

Events.RETRY,
Events.START_BROWSER,
Events.STOP_BROWSER,

Events.START_BROWSER,
Events.STOP_BROWSER,
Events.BEGIN_SUITE,
Events.END_SUITE,

Events.BEGIN_SUITE,
Events.END_SUITE,
Events.SKIP_STATE,
Events.BEGIN_STATE,
Events.END_STATE,

Events.SKIP_STATE,
Events.BEGIN_STATE,
Events.END_STATE,
Events.INFO,
Events.WARNING,
Events.ERROR,

Events.INFO,
Events.WARNING,
Events.ERROR,
Events.END_TEST,
Events.CAPTURE,

Events.END_TEST,
Events.CAPTURE,
Events.TEST_RESULT,
Events.UPDATE_RESULT
].forEach((event) => {
it(`should passthrough '${event}' runner event`, () => {
const runner = new EventEmitter();
runner.run = () => {
runner.emit(event, 'foo');
return Promise.resolve();
};
sandbox.stub(Runner, 'create').returns(runner);

Events.TEST_RESULT,
Events.UPDATE_RESULT
].forEach((event, name) => {
const spy = sinon.spy().named(`${name} handler`);
const gemini = initGemini({});
const spy = sinon.spy();
gemini.on(event, spy);

runner.emit(event, 'value');

assert.calledOnce(spy);
assert.calledWith(spy, 'value');
return gemini.test()
.then(() => assert.calledOnceWith(spy, 'foo'));
});
});

@@ -161,6 +161,28 @@ describe('gemini', () => {
return runGeminiTest()
.then(() => assert.calledWith(pluginsLoader.load, sinon.match.any, sinon.match.any, prefix));
});

it('should wait until plugins loaded', () => {
const afterPluginLoad = sinon.spy();
pluginsLoader.load.callsFake(() => {
return [Promise.delay(20).then(afterPluginLoad)];
});
sandbox.stub(Runner.prototype, 'run').returns(Promise.resolve());

return runGeminiTest()
.then(() => assert.callOrder(afterPluginLoad, Runner.prototype.run));
});

it('should not run tests if plugin failed on load', () => {
const err = new Error('o.O');
pluginsLoader.load.callsFake(() => [Promise.reject(err)]);
sandbox.stub(Runner.prototype, 'run').returns(Promise.resolve());

const result = runGeminiTest();

return assert.isRejected(result, err)
.then(() => assert.notCalled(Runner.prototype.run));
});
});

describe('readTests', () => {
@@ -318,10 +340,8 @@ describe('gemini', () => {
});

it('should initialize temp with specified temp dir', () => {
runGeminiTest({tempDir: '/some/dir'});

assert.calledOnce(temp.init);
assert.calledWith(temp.init, '/some/dir');
return runGeminiTest({tempDir: '/some/dir'})
.then(() => assert.calledOnceWith(temp.init, '/some/dir'));
});

it('should initialize temp before start runner', () => {

0 comments on commit 340286f

Please sign in to comment.