Skip to content

Commit

Permalink
feat: implement core functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
DudaGod committed Feb 28, 2017
1 parent a2f2445 commit 65fc627
Show file tree
Hide file tree
Showing 17 changed files with 1,044 additions and 0 deletions.
27 changes: 27 additions & 0 deletions gemini.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

const Collector = require('./lib/collector');
const geminiCollector = require('./lib/collector/gemini');
const parseConfig = require('./lib/config');

module.exports = (gemini, opts) => {
const config = parseConfig(opts);

if (!config.enabled) {
return;
}

const collector = Collector.create(geminiCollector, config);

gemini.on(gemini.events.TEST_RESULT, (data) => {
data.equal ? collector.addSuccess(data) : collector.addFail(data);
});

gemini.on(gemini.events.SKIP_STATE, (data) => collector.addSkipped(data));

gemini.on(gemini.events.RETRY, (data) => collector.addRetry(data));

gemini.on(gemini.events.ERROR, (data) => collector.addError(data));

gemini.on(gemini.events.END_RUNNER, () => collector.saveFile());
};
28 changes: 28 additions & 0 deletions hermione.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

const Collector = require('./lib/collector');
const hermioneCollector = require('./lib/collector/hermione');
const parseConfig = require('./lib/config');

module.exports = (hermione, opts) => {
const config = parseConfig(opts);

if (!config.enabled) {
return;
}

const collector = Collector.create(hermioneCollector, config);

hermione.on(hermione.events.TEST_PASS, (data) => collector.addSuccess(data));

hermione.on(hermione.events.TEST_FAIL, (data) => collector.addFail(data));
hermione.on(hermione.events.SUITE_FAIL, (data) => collector.addFail(data));

hermione.on(hermione.events.TEST_PENDING, (data) => collector.addSkipped(data));

hermione.on(hermione.events.RETRY, (data) => collector.addRetry(data));

hermione.on(hermione.events.ERROR, (data) => collector.addError(data));

hermione.on(hermione.events.RUNNER_END, () => collector.saveFile());
};
29 changes: 29 additions & 0 deletions lib/collector/data-collector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const _ = require('lodash');

module.exports = class DataCollector {
static create() {
return new DataCollector();
}

constructor() {
this._data = {};
}

append(test) {
const id = this._generateId(test);
this._data[id] = test;
}

getData() {
return this._data;
}

_generateId(test) {
return _(test)
.at(['fullName', 'browserId'])
.compact()
.join('.');
}
};
22 changes: 22 additions & 0 deletions lib/collector/gemini.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const _ = require('lodash');
const utils = require('./utils');

exports.configureTestResult = (result) => {
return {
fullName: result.state.fullName,
browserId: result.browserId,
file: result.suite.file,
referencePath: utils.getRelativePath(result.referencePath),
url: result.suite.url
};
};

exports.isFailedTest = (result) => {
return result.hasOwnProperty('equal');
};

exports.getSkipReason = (result) => {
return _.get(result, 'suite.skipComment', 'No skip reason');
};
29 changes: 29 additions & 0 deletions lib/collector/hermione.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const _ = require('lodash');
const url = require('url');
const utils = require('./utils');

const getFilePath = (test) => test && test.file || getFilePath(test.parent);

exports.configureTestResult = (result) => {
const filePath = getFilePath(result) || '';
const metaUrl = _.get(result, 'meta.url', '');

return {
fullName: result.fullTitle(),
browserId: result.browserId,
file: utils.getRelativePath(filePath),
url: url.parse(metaUrl).path
};
};

exports.isFailedTest = (result) => {
return result.state === 'failed' || result.hook && result.hook.state === 'failed';
};

exports.getSkipReason = (result) => {
const findSkipReason = (test) => test.parent && findSkipReason(test.parent) || test.skipReason;

return findSkipReason(result) || 'No skip reason';
};
59 changes: 59 additions & 0 deletions lib/collector/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs-extra'));

const DataCollector = require('./data-collector');

module.exports = class Collector {
static create(toolCollector, config) {
return new Collector(toolCollector, config);
}

constructor(toolCollector, config) {
this._toolCollector = toolCollector;
this._dataCollector = DataCollector.create();
this._config = config;
}

addSuccess(result) {
this._addTestResult(result, {status: 'success'});
}

addFail(result) {
this._addTestResult(result, {status: 'fail'});
}

addSkipped(result) {
this._addTestResult(result, {
status: 'skipped',
skipReason: this._toolCollector.getSkipReason(result)
});
}

addRetry(result) {
this._toolCollector.isFailedTest(result)
? this.addFail(result)
: this.addError(result);
}

addError(result) {
this._addTestResult(result, {
status: 'error',
errorReason: result.stack || result.message || ''
});
}

saveFile() {
return fs.outputJsonAsync(this._config.path, this._dataCollector.getData())
.catch(console.error);
}

_addTestResult(result, props) {
const configuredResult = this._toolCollector.configureTestResult(result);
const test = _.extend(configuredResult, props);

this._dataCollector.append(test);
}
};
9 changes: 9 additions & 0 deletions lib/collector/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

const path = require('path');

exports.getRelativePath = (fullPath) => {
return fullPath
? path.relative(process.cwd(), fullPath)
: null;
};
31 changes: 31 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

const _ = require('lodash');
const configParser = require('gemini-configparser');

const root = configParser.root;
const section = configParser.section;
const option = configParser.option;

const ENV_PREFIX = 'json_reporter_';

const getParser = () => {
return root(section({
enabled: option({
defaultValue: true,
parseEnv: JSON.parse,
validate: _.isBoolean
}),
path: option({
defaultValue: 'json-reporter.json',
validate: _.isString
})
}), {envPrefix: ENV_PREFIX});
};

module.exports = (options) => {
const env = process.env;
const argv = process.argv;

return getParser()({options, env, argv});
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dependencies": {
"bluebird": "^3.4.7",
"fs-extra": "^2.0.0",
"gemini-configparser": "^0.3.0",
"lodash": "^4.17.4"
},
"devDependencies": {
Expand All @@ -45,6 +46,7 @@
"husky": "^0.11.9",
"istanbul": "^0.4.5",
"mocha": "^3.0.2",
"proxyquire": "^1.7.11",
"sinon": "^1.17.5",
"standard-version": "^4.0.0"
}
Expand Down
139 changes: 139 additions & 0 deletions test/gemini.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use strict';

const _ = require('lodash');
const EventEmitter = require('events').EventEmitter;
const proxyquire = require('proxyquire');

const Collector = require('../lib/collector');
const geminiCollector = require('../lib/collector/gemini');

describe('json-reporter/gemini', () => {
const sandbox = sinon.sandbox.create();

let gemini;
let parseConfig;

const mkGemini_ = () => {
const emitter = new EventEmitter();

emitter.events = {
TEST_RESULT: 'testResult',
SKIP_STATE: 'skipState',
RETRY: 'retry',
ERROR: 'err',
END_RUNNER: 'endRunner'
};

return emitter;
};

const initReporter_ = (opts) => {
opts = _.defaults(opts || {}, {
enabled: true,
path: '/default/path'
});

parseConfig = sandbox.stub().returns(opts);

const geminiReporter = proxyquire('../gemini', {
'./lib/config': parseConfig
});

return geminiReporter(gemini, opts);
};

beforeEach(() => {
gemini = mkGemini_();
});

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

it('should do nothing if plugin is disabled', () => {
sandbox.stub(Collector, 'create');

initReporter_({enabled: false});

assert.notCalled(Collector.create);
});

it('should parse config', () => {
initReporter_({enabled: true, path: '/some/path'});

assert.calledOnce(parseConfig);
assert.calledWithMatch(parseConfig, {enabled: true, path: '/some/path'});
});

it('should create collector', () => {
sandbox.stub(Collector, 'create');

initReporter_({enabled: true, path: '/some/path'});

assert.calledWith(
Collector.create,
geminiCollector,
{enabled: true, path: '/some/path'}
);
});

describe('call the appropriate event handlers', () => {
beforeEach(() => initReporter_());

it('should call appropriate method for passed test', () => {
const data = {equal: true};
sandbox.stub(Collector.prototype, 'addSuccess');

gemini.emit(gemini.events.TEST_RESULT, data);

assert.calledOnce(Collector.prototype.addSuccess);
assert.calledWith(Collector.prototype.addSuccess, data);
});

it('should call appropriate method for failed test', () => {
const data = {equal: false};
sandbox.stub(Collector.prototype, 'addFail');

gemini.emit(gemini.events.TEST_RESULT, data);

assert.calledOnce(Collector.prototype.addFail);
assert.calledWith(Collector.prototype.addFail, data);
});

it('should call appropriate method for skipped test', () => {
const data = {foo: 'bar'};
sandbox.stub(Collector.prototype, 'addSkipped');

gemini.emit(gemini.events.SKIP_STATE, data);

assert.calledOnce(Collector.prototype.addSkipped);
assert.calledWith(Collector.prototype.addSkipped, data);
});

it('should call appropriate method for retried test', () => {
const data = {foo: 'bar'};
sandbox.stub(Collector.prototype, 'addRetry');

gemini.emit(gemini.events.RETRY, data);

assert.calledOnce(Collector.prototype.addRetry);
assert.calledWith(Collector.prototype.addRetry, data);
});

it('should call appropriate method for error occurred on test execution', () => {
const data = {foo: 'bar'};
sandbox.stub(Collector.prototype, 'addError');

gemini.emit(gemini.events.ERROR, data);

assert.calledOnce(Collector.prototype.addError);
assert.calledWith(Collector.prototype.addError, data);
});

it('should save collected test data into file when the tests are completed', () => {
sandbox.stub(Collector.prototype, 'saveFile');

gemini.emit(gemini.events.END_RUNNER);

assert.calledOnce(Collector.prototype.saveFile);
});
});
});
Loading

0 comments on commit 65fc627

Please sign in to comment.