@zebrunner/javascript-agent-mocha
Version:
Zebrunner Agent for Mocha
240 lines (203 loc) • 7.65 kB
JavaScript
const Mocha = require('mocha');
const { fork } = require('child_process');
const { sleep } = require('deasync');
const ConfigResolver = require('./config-resolver');
const EventStorage = require('./event-storage');
const TestGroupsResolver = require('./test-group-resolver');
const { WORKER_EVENTS, REPORTING_EVENTS, IPC_EVENTS } = require('./events');
const { startIPCServer, stopIPCServer } = require('./ipc-server');
const { disconnectIPCClient } = require('./ipc-client');
const { parseTestInfo } = require('./object-transformer');
const { testStatuses } = require('./constants');
const {
EVENT_RUN_BEGIN,
EVENT_RUN_END,
EVENT_SUITE_BEGIN,
EVENT_SUITE_END,
EVENT_TEST_BEGIN,
EVENT_TEST_END,
EVENT_TEST_FAIL,
EVENT_TEST_PENDING,
} = Mocha.Runner.constants;
class ZebrunnerMochaReporter extends Mocha.reporters.Base {
constructor(runner, config) {
super(runner, config);
console.log(config);
this.runner = runner;
this.reporterConfig = config.reporterConfig;
this.configResolver = new ConfigResolver(this.reporterConfig);
if (!this.configResolver.isReportingEnabled()) {
console.log('Zebrunner Reporting is turned off');
return;
}
this.logicalClock = 0;
if (!ZebrunnerMochaReporter.worker) {
this.worker = fork(`${__dirname}/worker.js`, [], {
detached: true,
});
this.#sendEvent({ event: WORKER_EVENTS.INIT, config });
this.#initIPCServer();
ZebrunnerMochaReporter.worker = this.worker;
} else {
this.worker = ZebrunnerMochaReporter.worker;
}
this.eventStorage = new EventStorage(this.runner.isParallelMode(), this.worker.pid);
this.testGroupsResolver = new TestGroupsResolver(this.eventStorage);
this.#registerActionListeners();
}
#registerActionListeners() {
this.runner.on(EVENT_RUN_BEGIN, () => {
this.#sendEvent({ event: EVENT_RUN_BEGIN });
});
this.runner.on(EVENT_RUN_END, () => {
this.#sendEvent({ event: EVENT_RUN_END });
// workaround for exit = true flag when process is killed once all tests are done: https://github.com/cypress-io/cypress/issues/7139
this.#waitForRunEnd();
});
this.runner.on(EVENT_SUITE_BEGIN, (suite) => {
this.eventStorage.addSuite(suite);
});
this.runner.on(EVENT_SUITE_END, (suite) => {
this.eventStorage.removeSuite(suite);
});
this.runner.on(EVENT_TEST_BEGIN, (test) => {
const testInfo = parseTestInfo(test);
testInfo.testGroups = this.testGroupsResolver.resolve(test);
this.#sendEvent({
event: EVENT_TEST_BEGIN,
test: testInfo,
});
this.eventStorage.startTest(testInfo);
this.eventStorage.getTestEvents(testInfo).forEach((event) => this.#sendEvent(event));
});
this.runner.on(EVENT_TEST_END, (test) => {
this.#processAndSendLogs();
this.#sendEvent({
event: EVENT_TEST_END,
test: parseTestInfo(test),
});
});
this.runner.on(EVENT_TEST_FAIL, (test, err) => {
this.#sendEvent({
event: EVENT_TEST_FAIL,
test: parseTestInfo(test, testStatuses.FAILED, err),
});
});
this.runner.on(EVENT_TEST_PENDING, (test) => {
this.#sendEvent({
event: EVENT_TEST_PENDING,
test: parseTestInfo(test),
});
});
}
#waitForRunEnd() {
let attempt = 0;
const terminateTime = Date.now() + 10e3; // wait for 10 seconds to publish logs and finish the launch
let result;
setTimeout(() => {
result = 'done';
}, 11000); // deasync for 11 seconds
while (result === undefined) {
sleep(500);
if (Date.now() < terminateTime && attempt < 10) {
// buffered logs are received by IPC using timeout, necessary to wait the latest batch before finishing the launch
this.#processAndSendLogs();
attempt += 1;
}
if (Date.now() > terminateTime) {
// necessary to have a delay between sending last event and disconnecting from IPC
this.#terminateIPCServer();
process.exit(0);
}
}
return result;
}
#processAndSendLogs() {
this.eventStorage.processLogs();
this.eventStorage.getLogEvents().forEach((event) => this.#sendEvent(event));
}
#sendEvent(message) {
this.logicalClock += 1;
const event = { ...message, timestamp: this.logicalClock };
console.log(`Zebrunner: sendEvent ${event.event} with timestamp ${event.timestamp}`);
this.worker.send(event);
}
#saveEvent(message) {
if (this.eventStorage.isTestStarted(message.test)) {
this.#sendEvent(message);
return;
}
this.eventStorage.saveEvent(message);
}
#initIPCServer = () => {
startIPCServer(this.#subscribeServerEvents, this.#unsubscribeServerEvents);
};
#terminateIPCServer = () => {
disconnectIPCClient();
stopIPCServer(this.#unsubscribeServerEvents);
};
#subscribeServerEvents = (server) => {
server.on(IPC_EVENTS.ATTACH_TEST_RUN_LABELS, (labels) => this.#sendEvent({
event: REPORTING_EVENTS.ATTACH_TEST_RUN_LABELS,
labels,
}));
server.on(IPC_EVENTS.ATTACH_TEST_RUN_ARTIFACT_REFERENCES, (references) => this.#sendEvent({
event: REPORTING_EVENTS.ATTACH_TEST_RUN_ARTIFACT_REFERENCES,
references,
}));
server.on(IPC_EVENTS.UPLOAD_TEST_RUN_ARTIFACT, (file) => this.#sendEvent({
event: REPORTING_EVENTS.UPLOAD_TEST_RUN_ARTIFACT,
file,
}));
server.on(IPC_EVENTS.ATTACH_TEST_LABELS, (message) => this.#saveEvent({
event: REPORTING_EVENTS.ATTACH_TEST_LABELS,
test: message.testInfo,
labels: message.labels,
}));
server.on(IPC_EVENTS.ATTACH_TEST_ARTIFACT_REFERENCES, (message) => this.#saveEvent({
event: REPORTING_EVENTS.ATTACH_TEST_ARTIFACT_REFERENCES,
test: message.testInfo,
references: message.references,
}));
server.on(IPC_EVENTS.UPLOAD_TEST_ARTIFACT, (message) => this.#saveEvent({
event: REPORTING_EVENTS.UPLOAD_TEST_ARTIFACT,
test: message.testInfo,
file: message.file,
}));
server.on(IPC_EVENTS.SET_TEST_MAINTAINER, (message) => this.#saveEvent({
event: REPORTING_EVENTS.SET_TEST_MAINTAINER,
test: message.testInfo,
maintainer: message.maintainer,
}));
server.on(IPC_EVENTS.REVERT_TEST_REGISTRATION, (message) => this.#saveEvent({
event: REPORTING_EVENTS.REVERT_TEST_REGISTRATION,
test: message.testInfo,
}));
server.on(IPC_EVENTS.ADD_TEST_CASES, (message) => this.#saveEvent({
event: REPORTING_EVENTS.ADD_TEST_CASES,
test: message.testInfo,
testCase: message.testCase,
}));
server.on(IPC_EVENTS.SEND_TEST_LOGS, (message) => {
this.eventStorage.saveLogs(message.logs);
});
server.on(IPC_EVENTS.SAVE_TEST_WORKER_ID, (message) => {
this.eventStorage.saveTestPid(message.testInfo);
});
};
// eslint-disable-next-line class-methods-use-this
#unsubscribeServerEvents = (server) => {
server.off(IPC_EVENTS.ATTACH_TEST_RUN_LABELS, '*');
server.off(IPC_EVENTS.ATTACH_TEST_RUN_ARTIFACT_REFERENCES, '*');
server.off(IPC_EVENTS.UPLOAD_TEST_RUN_ARTIFACT, '*');
server.off(IPC_EVENTS.ATTACH_TEST_LABELS, '*');
server.off(IPC_EVENTS.ATTACH_TEST_ARTIFACT_REFERENCES, '*');
server.off(IPC_EVENTS.UPLOAD_TEST_ARTIFACT, '*');
server.off(IPC_EVENTS.SET_TEST_MAINTAINER, '*');
server.off(IPC_EVENTS.REVERT_TEST_REGISTRATION, '*');
server.off(IPC_EVENTS.ADD_TEST_CASES, '*');
server.off(IPC_EVENTS.SEND_TEST_LOGS, '*');
server.off(IPC_EVENTS.SAVE_TEST_WORKER_ID, '*');
};
}
module.exports = ZebrunnerMochaReporter;