sl-node-mocha2
Version:
SeaLights mocha reporter
342 lines (301 loc) • 10.6 kB
JavaScript
var mocha = require("mocha");
var Base = mocha.reporters.Base;
var utils = mocha.utils;
var inherits = utils.inherits;
var logger;
var sealights = require('sl-node2');
var SlJsInfra = require('sl-js-infra').SlJsInfra;
var Constants = require("./constants");
var childProcess = require("child_process");
var path = require("path");
function log(msg) {
var args = Array.prototype.slice.apply(arguments);
if (logger) {
logger.info.apply(logger, args);
} else {
args[0] = "[SeaLights for Mocha] " + args[0];
console.log.apply(console, args);
}
}
function warn(msg) {
var args = Array.prototype.slice.apply(arguments);
if (logger) {
logger.warn.apply(logger, args);
} else {
args[0] = "[SeaLights for Mocha] " + args[0];
console.warn.apply(console, args);
}
}
function error(msg) {
var args = Array.prototype.slice.apply(arguments);
if (logger) {
logger.error.apply(logger, args);
} else {
args[0] = "[SeaLights for Mocha] " + args[0];
console.error.apply(console, args);
}
}
/**
* Expose `SeaLightsReporter`.
*/
exports = module.exports = SeaLightsReporter;
/**
* Initialize a new `SeaLightsReporter` reporter.
*
* @param {Runner} runner
* @api public
*/
function SeaLightsReporter(runner, options) {
// Use for silent running during tests (Summary of test result not printed)
if(options && options.reporterOptions && options.reporterOptions.skipEpilogue) {
Base.prototype.epilogue = function () {
}
}
Base.call(this, runner, options);
var reporter = this;
var slProcess = null;
var executionId = null;
var shutDownTimeout = 60 * 1000; //Allow 1 minute to send all remaining footprints and events
var counters = {
start: 0,
fail: 0,
pass: 0,
end: 0,
skipped: 0
};
var warnedOnce = false;
var reporterName = (options && options.reporterOptions && options.reporterOptions.reporter) || "spec";
var mochaReporters = mocha.reporters;
var ChainedReporterType = mochaReporters.spec;
var chainedReporter = null;
var afterAllCalled = false;
if (mochaReporters.hasOwnProperty(reporterName)) {
ChainedReporterType = mochaReporters[reporterName];
} else {
try {
ChainedReporterType = require(reporterName);
} catch (e) {
warn('Cannot find reporter ' + reporterName + ', using default Spec reporter');
//ChainedReporterType = mochaReporters.spec; //default
}
}
chainedReporter = new ChainedReporterType(runner, options);
// Before running any suite, initialize SL and create a test execution ID
runner.suite.beforeAll(function (done) {
this.timeout(65 * 1000);// TIA polling time out is 60 seconds take extra 5 seconds
log('Version ' + reporter.getVersion() + ", agent version " + sealights.getVersion());
log('Initializing');
initTestListener(done);
});
// After all tests have been executed, wait for the SL process to shut down
runner.suite.afterAll(function (done) {
afterAllCalled = true;
if (!slProcess) return done();
pushExecutionEndEvent();
slProcess.resume && slProcess.resume();
executionId = null;
var doneCallbackInvoked = false;
function invokeDoneCallback(err) {
if (doneCallbackInvoked) return;
if (handle) {
try {
clearTimeout(handle); //Don't keep the process running because of this timeout
handle = null;
} catch (e) {
error(e);
}
}
doneCallbackInvoked = true;
if (done) {
done(err);
}
}
var handle = setTimeout(function () {
if (doneCallbackInvoked) return; //callback already called
handle = null;
warn('Send operation timed out.');
invokeDoneCallback();
}, shutDownTimeout);
this.timeout(0); //Disable mocha's timeout, since we handle the timeout here^^.
try {
log('Sending remaining data to server');
slProcess.stop(invokeDoneCallback);
} catch (err) {
error("Error send remaining data to the server", err);
invokeDoneCallback();
}
});
runner.on(Constants.MOCHA.EVENT_TEST_BEGIN, function (test) {
counters.start++;
try {
if (!slProcess && !warnedOnce) {
warn('Mocha reporter was not properly intiailized.')
warnedOnce = true;
return;
}
pushTestStartEvent(test);
} catch (e) {
error('error in test event handler: ' + e);
}
});
runner.on(Constants.MOCHA.EVENT_TEST_FAIL, function (test, err) {
if(test.type !== Constants.MOCHA.TYPE_TEST) return;
counters.fail++;
try {
if (!slProcess) return;
pushTestEndEvent(test, Constants.SEALIGHTS.TEST_STATUS.FAILED);
} catch (e) {
error('error in fail event handler: ' + e);
}
});
runner.on(Constants.MOCHA.EVENT_TEST_PASS, function (test, err) {
counters.pass++;
if (err)
error('pass error: ' + err + '\n' + err.stack);
try {
if (!slProcess) return;
pushTestEndEvent(test, Constants.SEALIGHTS.TEST_STATUS.PASSED);
} catch (e) {
error('error in test pass event handler: ' + e);
}
});
runner.on(Constants.MOCHA.EVENT_TEST_END, function (test) {
counters.end++;
try {
if (!slProcess) return;
slProcess.setCurrentTestIdentifier(null);
} catch (e) {
error('error in test end event handler: ' + e);
}
});
runner.on(Constants.MOCHA.EVENT_TEST_PENDING, function (test) {
counters.skipped++;
try {
if (!slProcess) return;
pushTestStartEvent(test);
pushTestEndEvent(test, Constants.SEALIGHTS.TEST_STATUS.SKIPPED);
} catch (e) {
error('error in pending test event handler: ' + e);
}
});
function getSuitePath(t) {
var parts = [];
while (t) {
if (t.parent)
parts.unshift(t.title);
t = t.parent;
}
return parts;
}
function clone(o) {
return JSON.parse(JSON.stringify(o));
}
this.getExecutionId = function getExecutionId() {
return executionId;
}
this.getVersion = function getVersion() {
return require('./package.json').version;
}
function initTestListener(done) {
try {
const toolInfo = {
name: "mocha",
version: mocha.prototype.version
}
var agentConfig = new SlJsInfra.ConfigLoader().loadAgentConfiguration();
agentConfig.shouldCheckForActiveExecutionOnStartUp.value = false;
slProcess = sealights.initAsync(agentConfig, function (err, agent) {
slProcess = agent;
logger = agent.getLogger("SeaLights for Mocha");
if (err) {
error("Error initializing agent. ", err);
agent.stop();
return done();
}
log('Initialized')
pushExecutionStartedEvent().then(function () {
var __initTestId = slProcess.createTestId(executionId, '__init');
slProcess.setCurrentTestIdentifier(__initTestId, true);
excludeTests(done)
});
}, toolInfo);
} catch (err) {
error('Initialization error', err);
done();
};
}
function pushExecutionStartedEvent() {
executionId = slProcess.createTestSuiteId();
return slProcess.startColoredExecution(executionId);
}
function pushExecutionEndEvent() {
slProcess.pushEvent({
type: Constants.SEALIGHTS.EVENT_TYPES.EXECUTION.END,
executionId: executionId,
meta: {
counters: clone(counters)
}
});
}
function pushTestStartEvent(test) {
var testName = test.fullTitle();
var testId = slProcess.createTestId(executionId, testName);
slProcess.setCurrentTestIdentifier(testId);
pushTestEvent(test, Constants.SEALIGHTS.EVENT_TYPES.TESTS.START)
}
function pushTestEndEvent(test, testStatus) {
pushTestEvent(test, Constants.SEALIGHTS.EVENT_TYPES.TESTS.END, testStatus)
}
function pushTestEvent(test, eventType, testStatus) {
var testName = test.fullTitle();
var suitePath = getSuitePath(test);
var event = {
type: eventType,
testName: testName,
executionId: executionId,
testPath: suitePath,
meta: {
counters: clone(counters)
}
};
if(eventType === Constants.SEALIGHTS.EVENT_TYPES.TESTS.END){
event.result = testStatus;
event.duration = test.duration;
}
slProcess.pushEvent(event);
}
function excludeTests(done) {
slProcess.testRecommendationHandler.getExcludedTestsAsync(function (excludedTestsMap) {
markSkippedTests(runner.suite, excludedTestsMap);
done();
})
}
function markSkippedTests(suite, excludedTestsMap) {
if (suite.suites && suite.suites.length) {
suite.suites.forEach(function (currSuite) {
markSkippedTests(currSuite, excludedTestsMap);
})
}
if (suite.tests && suite.tests.length) {
suite.tests.forEach(function (tst) {
if (excludedTestsMap[tst.fullTitle()]) {
log("Test " + tst.fullTitle() + " skipped");
tst.pending = true;
}
});
}
}
process.on('exit', function () {
if (!afterAllCalled) {
endExecutionSync();
}
})
function endExecutionSync() {
var scriptPath = path.resolve(__dirname, 'end-execution.js');
childProcess.spawnSync(process.argv0, [scriptPath, executionId], {
stdio: [process.stdin, process.stdout, process.stderr],
encoding: 'utf-8'
});
}
}
inherits(SeaLightsReporter, Base);