"use strict";
/**
* `GlaceJS` common reporter.
*
* @class
* @name GlaceReporter
* @arg {object} runner - `MochaJS` runner.
*/
const fs = require("fs");
const util = require("util");
const _ = require("lodash");
const colors = require("colors");
const MochaReporter = require("mocha").reporters.base;
const U = require("glace-utils");
const LOG = U.logger;
const CONF = require("../config");
const TestCase = require("../testing").TestCase;
const utils = require("../utils");
/**
* Registered reporters.
*
* @type {object[]}
*/
let reporters = [];
const GlaceReporter = function (runner) {
MochaReporter.call(this, runner);
runner.on("start", () => {
for (const reporter of reporters) {
if (reporter.start) reporter.start();
}
});
runner.on("end", () => {
const failedTests = CONF.test.cases.filter(t => t.status === TestCase.FAILED);
if (!failedTests.length) {
if (!CONF.session.errors.length) CONF.session.isPassed = true;
}
saveFailedTests(failedTests);
if (fs.existsSync(CONF.report.dir)) {
U.clearEmptyFolders(CONF.report.dir);
}
for (const reporter of reporters) {
if (reporter.end) reporter.end();
}
});
runner.on("suite", mochaSuite => {
if (mochaSuite.root) return; // skip mocha root suite;
const methodName = {
undefined: "scope",
suite: "suite",
test: "test",
}[mochaSuite.title.type];
for (const reporter of reporters) {
if (reporter[methodName]) reporter[methodName](mochaSuite);
}
});
runner.on("suite end", mochaSuite => {
if (mochaSuite.root) return; // skip mocha root suite;
const methodName = {
undefined: "scopeEnd",
suite: "suiteEnd",
test: "testEnd",
}[mochaSuite.title.type];
for (const reporter of reporters) {
if (reporter[methodName]) reporter[methodName](mochaSuite);
}
if (methodName === "testEnd") {
CONF.test.curCase = null;
CONF.report.testDir = null;
utils.setLog(); // Current test case is finished, need to reinit log
}
});
runner.on("test", mochaTest => {
for (const reporter of reporters) {
if (reporter.chunk) reporter.chunk(mochaTest);
}
});
runner.on("test end", mochaTest => {
for (const reporter of reporters) {
if (reporter.chunkEnd) reporter.chunkEnd(mochaTest);
}
});
runner.on("hook", mochaHook => {
for (const reporter of reporters) {
if (reporter.hook) reporter.hook(mochaHook);
}
});
runner.on("hook end", mochaHook => {
for (const reporter of reporters) {
if (reporter.hookEnd) reporter.hookEnd(mochaHook);
}
});
runner.on("pass", mochaTest => {
passChunkId();
handleSkipState(mochaTest);
const method = mochaTest.state === "skipped" ? "skip" : "pass";
for (const reporter of reporters) {
if (reporter[method]) reporter[method](mochaTest);
}
});
runner.on("fail", (mochaTest, err) => {
if (err.actual && err.expected) {
try {
err.diff = colors.strip(
MochaReporter.generateDiff(err.actual, err.expected));
if (err.diff.trim() === "+ expected - actual") {
delete err.diff;
}
} catch (e) {
/* do nothing */
}
}
utils.accountError(mochaTest.title, err);
for (const reporter of reporters) {
if (reporter.fail) reporter.fail(mochaTest, err);
}
if (CONF.session.exitOnFail) {
CONF.test.curCase.end(TestCase.FAILED);
runner.emit("end");
}
});
runner.on("pending", mochaTest => {
for (const reporter of reporters) {
if (reporter.pending) reporter.pending(mochaTest);
}
});
};
util.inherits(GlaceReporter, MochaReporter);
module.exports = GlaceReporter;
/**
* Finalizes reporting.
*
* @function
* @async
* @arg {Array.<*>} failures - Tests failures.
* @arg {function} fn - Finalizator.
*/
GlaceReporter.prototype.done = function (failures, fn) {
let prms = Promise.resolve();
reporters.forEach(reporter => {
if (reporter.done) {
prms = prms
.then(() => reporter.done())
.catch(e => LOG.error(e));
}
});
return prms.then(() => fn(failures));
};
/**
* Registers reporters if they are not.
*
* @method
* @static
* @arg {...object} reporters - Sequence of reporters to register.
*/
GlaceReporter.register = function () {
for (const reporter of arguments) {
if (!reporters.includes(reporter)) {
reporters.push(reporter);
}
}
};
/**
* Removes reporters if they are registered.
*
* @method
* @static
* @arg {...object} reporters - Sequence of reporters to remove.
*/
GlaceReporter.remove = function () {
const args = Array.from(arguments);
args.unshift(reporters);
reporters = _.without.apply(_, args);
};
/**
* Mark chunk as passed via its ID.
* @ignore
*/
const passChunkId = () => {
if (!CONF.chunk.curId) return;
if (CONF.chunk.passedIds.includes(CONF.chunk.curId)) return;
CONF.chunk.passedIds.push(CONF.chunk.curId);
if (CONF.test.curCase) CONF.test.curCase.addPassedChunkId(CONF.chunk.curId);
CONF.chunk.curId = null;
};
/**
* Handle skip state of mocha test.
* @ignore
*/
const handleSkipState = mochaTest => {
if (!CONF.test.curCase) return;
if (CONF.test.curCase.skipChunk !== mochaTest.title) return;
mochaTest.state = "skipped";
CONF.test.curCase.skipChunk = null;
};
const saveFailedTests = failedTests => {
if (fs.existsSync(CONF.report.failedTestsPath)) {
try {
fs.unlinkSync(CONF.report.failedTestsPath);
} catch (e) {
LOG.error(util.format(`Can't remove file '${CONF.report.failedTestsPath}'`, e));
return;
}
}
const data = [];
for (const failedTest of failedTests) {
data.push({ id: failedTest.id, passed_chunk_ids: failedTest.passedChunkIds });
}
try {
fs.writeFileSync(
CONF.report.failedTestsPath, JSON.stringify(data, null, " "));
} catch (e) {
LOG.error(util.format(`Can't write file '${CONF.report.failedTestsPath}'`, e));
}
};