diff --git a/packages/datadog-instrumentations/src/cucumber.js b/packages/datadog-instrumentations/src/cucumber.js index d532592d7f6..1843091abb4 100644 --- a/packages/datadog-instrumentations/src/cucumber.js +++ b/packages/datadog-instrumentations/src/cucumber.js @@ -32,7 +32,7 @@ const { mergeCoverage, fromCoverageMapToCoverage, getTestSuitePath, - JEST_WORKER_TRACE_PAYLOAD_CODE + CUCUMBER_WORKER_TRACE_PAYLOAD_CODE } = require('../../dd-trace/src/plugins/util/test') const isMarkedAsUnskippable = (pickle) => { @@ -459,15 +459,16 @@ function getWrappedGiveWork (giveWorkFunction) { function getWrappedParseWorkerMessage (parseWorkerMessageFunction) { return function (worker, message) { // If the message is an array, it's a dd-trace message, so we need to stop cucumber processing + // Otherwise cucumber code will throw an error // TODO: identify the message better if (Array.isArray(message)) { const [messageCode, payload] = message - if (messageCode === JEST_WORKER_TRACE_PAYLOAD_CODE) { + if (messageCode === CUCUMBER_WORKER_TRACE_PAYLOAD_CODE) { sessionAsyncResource.runInAsyncScope(() => { workerReportTraceCh.publish(payload) }) + return } - return } const { jsonEnvelope } = message diff --git a/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js b/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js deleted file mode 100644 index 8f96937e22f..00000000000 --- a/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict' - -const Writer = require('./writer') -const { - JEST_WORKER_COVERAGE_PAYLOAD_CODE, - JEST_WORKER_TRACE_PAYLOAD_CODE -} = require('../../../plugins/util/test') - -/** - * Lightweight exporter whose writers only do simple JSON serialization - * of trace and coverage payloads, which they send to the jest main process. - */ -class JestWorkerCiVisibilityExporter { - constructor () { - this._writer = new Writer(JEST_WORKER_TRACE_PAYLOAD_CODE) - this._coverageWriter = new Writer(JEST_WORKER_COVERAGE_PAYLOAD_CODE) - } - - export (payload) { - this._writer.append(payload) - } - - exportCoverage (formattedCoverage) { - this._coverageWriter.append(formattedCoverage) - } - - flush () { - this._writer.flush() - this._coverageWriter.flush() - } -} - -module.exports = JestWorkerCiVisibilityExporter diff --git a/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js b/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js new file mode 100644 index 00000000000..f91bdd52090 --- /dev/null +++ b/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js @@ -0,0 +1,56 @@ +'use strict' + +const Writer = require('./writer') +const { + JEST_WORKER_COVERAGE_PAYLOAD_CODE, + JEST_WORKER_TRACE_PAYLOAD_CODE, + CUCUMBER_WORKER_TRACE_PAYLOAD_CODE +} = require('../../../plugins/util/test') + +function getInterprocessTraceCode () { + if (process.env.JEST_WORKER_ID) { + return JEST_WORKER_TRACE_PAYLOAD_CODE + } + if (process.env.CUCUMBER_WORKER_ID) { + return CUCUMBER_WORKER_TRACE_PAYLOAD_CODE + } + return null +} + +// TODO: make it available with cucumber +function getInterprocessCoverageCode () { + if (process.env.JEST_WORKER_ID) { + return JEST_WORKER_COVERAGE_PAYLOAD_CODE + } + return null +} + +/** + * Lightweight exporter whose writers only do simple JSON serialization + * of trace and coverage payloads, which they send to the test framework's main process. + * Currently used by Jest and Cucumber workers. + */ +class TestWorkerCiVisibilityExporter { + constructor () { + const interprocessTraceCode = getInterprocessTraceCode() + const interprocessCoverageCode = getInterprocessCoverageCode() + + this._writer = new Writer(interprocessTraceCode) + this._coverageWriter = new Writer(interprocessCoverageCode) + } + + export (payload) { + this._writer.append(payload) + } + + exportCoverage (formattedCoverage) { + this._coverageWriter.append(formattedCoverage) + } + + flush () { + this._writer.flush() + this._coverageWriter.flush() + } +} + +module.exports = TestWorkerCiVisibilityExporter diff --git a/packages/dd-trace/src/ci-visibility/exporters/jest-worker/writer.js b/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js similarity index 76% rename from packages/dd-trace/src/ci-visibility/exporters/jest-worker/writer.js rename to packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js index 8b4ada0c500..d5004b28273 100644 --- a/packages/dd-trace/src/ci-visibility/exporters/jest-worker/writer.js +++ b/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js @@ -23,11 +23,18 @@ class Writer { } _sendPayload (data) { + // ## Jest // Only available when `child_process` is used for the jest worker. // eslint-disable-next-line // https://github.com/facebook/jest/blob/bb39cb2c617a3334bf18daeca66bd87b7ccab28b/packages/jest-worker/README.md#experimental-worker // If worker_threads is used, this will not work // TODO: make it compatible with worker_threads + + // ## Cucumber + // This reports to the test's main process the same way test data is reported by Cucumber + // See cucumber code: + // eslint-disable-next-line + // https://github.com/cucumber/cucumber-js/blob/5ce371870b677fe3d1a14915dc535688946f734c/src/runtime/parallel/run_worker.ts#L13 if (process.send) { // it only works if process.send is available process.send([this._interprocessCode, data]) } diff --git a/packages/dd-trace/src/exporter.js b/packages/dd-trace/src/exporter.js index f556aa0a49f..01bb96ac380 100644 --- a/packages/dd-trace/src/exporter.js +++ b/packages/dd-trace/src/exporter.js @@ -18,10 +18,8 @@ module.exports = name => { case exporters.AGENT_PROXY: return require('./ci-visibility/exporters/agent-proxy') case exporters.JEST_WORKER: - return require('./ci-visibility/exporters/jest-worker') case exporters.CUCUMBER_WORKER: - // for the moment we'll use the same, but we have to change this! - return require('./ci-visibility/exporters/jest-worker') + return require('./ci-visibility/exporters/test-worker') default: return inAWSLambda && !usingLambdaExtension ? require('./exporters/log') : require('./exporters/agent') } diff --git a/packages/dd-trace/src/plugins/util/test.js b/packages/dd-trace/src/plugins/util/test.js index 5f771de5b4b..23ce067670a 100644 --- a/packages/dd-trace/src/plugins/util/test.js +++ b/packages/dd-trace/src/plugins/util/test.js @@ -84,6 +84,9 @@ const TEST_BROWSER_VERSION = 'test.browser.version' const JEST_WORKER_TRACE_PAYLOAD_CODE = 60 const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61 +// cucumber worker variables +const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70 + // Early flake detection util strings const EFD_STRING = "Retried by Datadog's Early Flake Detection" const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g') @@ -107,6 +110,7 @@ module.exports = { LIBRARY_VERSION, JEST_WORKER_TRACE_PAYLOAD_CODE, JEST_WORKER_COVERAGE_PAYLOAD_CODE, + CUCUMBER_WORKER_TRACE_PAYLOAD_CODE, TEST_SOURCE_START, TEST_SKIPPED_BY_ITR, TEST_CONFIGURATION_BROWSER_NAME, diff --git a/packages/dd-trace/test/ci-visibility/exporters/jest-worker/exporter.spec.js b/packages/dd-trace/test/ci-visibility/exporters/jest-worker/exporter.spec.js deleted file mode 100644 index dd673400d28..00000000000 --- a/packages/dd-trace/test/ci-visibility/exporters/jest-worker/exporter.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict' - -require('../../../../../dd-trace/test/setup/tap') - -const JestWorkerCiVisibilityExporter = require('../../../../src/ci-visibility/exporters/jest-worker') -const { - JEST_WORKER_TRACE_PAYLOAD_CODE, - JEST_WORKER_COVERAGE_PAYLOAD_CODE -} = require('../../../../src/plugins/util/test') - -describe('CI Visibility Jest Worker Exporter', () => { - let send, originalSend - beforeEach(() => { - send = sinon.spy() - originalSend = process.send - process.send = send - }) - afterEach(() => { - process.send = originalSend - }) - it('can export traces', () => { - const trace = [{ type: 'test' }] - const traceSecond = [{ type: 'test', name: 'other' }] - const jestWorkerExporter = new JestWorkerCiVisibilityExporter() - jestWorkerExporter.export(trace) - jestWorkerExporter.export(traceSecond) - jestWorkerExporter.flush() - expect(send).to.have.been.calledWith([JEST_WORKER_TRACE_PAYLOAD_CODE, JSON.stringify([trace, traceSecond])]) - }) - it('can export coverages', () => { - const coverage = { sessionId: '1', suiteId: '1', files: ['test.js'] } - const coverageSecond = { sessionId: '2', suiteId: '2', files: ['test2.js'] } - const jestWorkerExporter = new JestWorkerCiVisibilityExporter() - jestWorkerExporter.exportCoverage(coverage) - jestWorkerExporter.exportCoverage(coverageSecond) - jestWorkerExporter.flush() - expect(send).to.have.been.calledWith( - [JEST_WORKER_COVERAGE_PAYLOAD_CODE, JSON.stringify([coverage, coverageSecond])] - ) - }) - it('does not break if process.send is undefined', () => { - delete process.send - const trace = [{ type: 'test' }] - const jestWorkerExporter = new JestWorkerCiVisibilityExporter() - jestWorkerExporter.export(trace) - jestWorkerExporter.flush() - expect(send).not.to.have.been.called - }) -}) diff --git a/packages/dd-trace/test/ci-visibility/exporters/test-worker/exporter.spec.js b/packages/dd-trace/test/ci-visibility/exporters/test-worker/exporter.spec.js new file mode 100644 index 00000000000..c368176b66d --- /dev/null +++ b/packages/dd-trace/test/ci-visibility/exporters/test-worker/exporter.spec.js @@ -0,0 +1,84 @@ +'use strict' + +require('../../../../../dd-trace/test/setup/tap') + +const TestWorkerCiVisibilityExporter = require('../../../../src/ci-visibility/exporters/test-worker') +const { + JEST_WORKER_TRACE_PAYLOAD_CODE, + JEST_WORKER_COVERAGE_PAYLOAD_CODE, + CUCUMBER_WORKER_TRACE_PAYLOAD_CODE +} = require('../../../../src/plugins/util/test') + +describe('CI Visibility Test Worker Exporter', () => { + let send, originalSend + beforeEach(() => { + send = sinon.spy() + originalSend = process.send + process.send = send + }) + afterEach(() => { + process.send = originalSend + }) + context('when the process is a jest worker', () => { + beforeEach(() => { + process.env.JEST_WORKER_ID = '1' + }) + afterEach(() => { + delete process.env.JEST_WORKER_ID + }) + it('can export traces', () => { + const trace = [{ type: 'test' }] + const traceSecond = [{ type: 'test', name: 'other' }] + const jestWorkerExporter = new TestWorkerCiVisibilityExporter() + jestWorkerExporter.export(trace) + jestWorkerExporter.export(traceSecond) + jestWorkerExporter.flush() + expect(send).to.have.been.calledWith([JEST_WORKER_TRACE_PAYLOAD_CODE, JSON.stringify([trace, traceSecond])]) + }) + it('can export coverages', () => { + const coverage = { sessionId: '1', suiteId: '1', files: ['test.js'] } + const coverageSecond = { sessionId: '2', suiteId: '2', files: ['test2.js'] } + const jestWorkerExporter = new TestWorkerCiVisibilityExporter() + jestWorkerExporter.exportCoverage(coverage) + jestWorkerExporter.exportCoverage(coverageSecond) + jestWorkerExporter.flush() + expect(send).to.have.been.calledWith( + [JEST_WORKER_COVERAGE_PAYLOAD_CODE, JSON.stringify([coverage, coverageSecond])] + ) + }) + it('does not break if process.send is undefined', () => { + delete process.send + const trace = [{ type: 'test' }] + const jestWorkerExporter = new TestWorkerCiVisibilityExporter() + jestWorkerExporter.export(trace) + jestWorkerExporter.flush() + expect(send).not.to.have.been.called + }) + }) + context('when the process is a cucumber worker', () => { + beforeEach(() => { + process.env.CUCUMBER_WORKER_ID = '1' + }) + afterEach(() => { + delete process.env.CUCUMBER_WORKER_ID + }) + it('can export traces', () => { + debugger + const trace = [{ type: 'test' }] + const traceSecond = [{ type: 'test', name: 'other' }] + const cucumberWorkerExporter = new TestWorkerCiVisibilityExporter() + cucumberWorkerExporter.export(trace) + cucumberWorkerExporter.export(traceSecond) + cucumberWorkerExporter.flush() + expect(send).to.have.been.calledWith([CUCUMBER_WORKER_TRACE_PAYLOAD_CODE, JSON.stringify([trace, traceSecond])]) + }) + it('does not break if process.send is undefined', () => { + delete process.send + const trace = [{ type: 'test' }] + const cucumberWorkerExporter = new TestWorkerCiVisibilityExporter() + cucumberWorkerExporter.export(trace) + cucumberWorkerExporter.flush() + expect(send).not.to.have.been.called + }) + }) +})