Skip to content

Commit 803ac98

Browse files
[Test Optimization] Fix DI setup for jest workers (#5110)
1 parent 26722b3 commit 803ac98

7 files changed

Lines changed: 119 additions & 7 deletions

File tree

integration-tests/jest/jest.spec.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,79 @@ describe('jest CommonJS', () => {
502502
done()
503503
}).catch(done)
504504
})
505+
506+
it('can work with Dynamic Instrumentation', (done) => {
507+
receiver.setSettings({
508+
flaky_test_retries_enabled: true,
509+
di_enabled: true
510+
})
511+
let snapshotIdByTest, snapshotIdByLog
512+
let spanIdByTest, spanIdByLog, traceIdByTest, traceIdByLog
513+
const eventsPromise = receiver
514+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
515+
const events = payloads.flatMap(({ payload }) => payload.events)
516+
517+
const tests = events.filter(event => event.type === 'test').map(event => event.content)
518+
const retriedTests = tests.filter(test => test.meta[TEST_IS_RETRY] === 'true')
519+
520+
assert.equal(retriedTests.length, 2)
521+
const [retriedTest] = retriedTests
522+
523+
assert.propertyVal(retriedTest.meta, DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
524+
525+
assert.isTrue(
526+
retriedTest.meta[`${DI_DEBUG_ERROR_PREFIX}.0.${DI_DEBUG_ERROR_FILE_SUFFIX}`]
527+
.endsWith('ci-visibility/dynamic-instrumentation/dependency.js')
528+
)
529+
assert.equal(retriedTest.metrics[`${DI_DEBUG_ERROR_PREFIX}.0.${DI_DEBUG_ERROR_LINE_SUFFIX}`], 4)
530+
531+
const snapshotIdKey = `${DI_DEBUG_ERROR_PREFIX}.0.${DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX}`
532+
assert.exists(retriedTest.meta[snapshotIdKey])
533+
534+
snapshotIdByTest = retriedTest.meta[snapshotIdKey]
535+
spanIdByTest = retriedTest.span_id.toString()
536+
traceIdByTest = retriedTest.trace_id.toString()
537+
538+
const notRetriedTest = tests.find(test => test.meta[TEST_NAME].includes('is not retried'))
539+
540+
assert.notProperty(notRetriedTest.meta, DI_ERROR_DEBUG_INFO_CAPTURED)
541+
})
542+
543+
const logsPromise = receiver
544+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/logs'), (payloads) => {
545+
const [{ logMessage: [diLog] }] = payloads
546+
assert.deepInclude(diLog, {
547+
ddsource: 'dd_debugger',
548+
level: 'error'
549+
})
550+
assert.equal(diLog.debugger.snapshot.language, 'javascript')
551+
spanIdByLog = diLog.dd.span_id
552+
traceIdByLog = diLog.dd.trace_id
553+
snapshotIdByLog = diLog.debugger.snapshot.id
554+
})
555+
556+
childProcess = exec(runTestsWithCoverageCommand,
557+
{
558+
cwd,
559+
env: {
560+
...getCiVisAgentlessConfig(receiver.port),
561+
TESTS_TO_RUN: 'dynamic-instrumentation/test-',
562+
DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED: 'true',
563+
RUN_IN_PARALLEL: true
564+
},
565+
stdio: 'inherit'
566+
}
567+
)
568+
569+
childProcess.on('exit', () => {
570+
Promise.all([eventsPromise, logsPromise]).then(() => {
571+
assert.equal(snapshotIdByTest, snapshotIdByLog)
572+
assert.equal(spanIdByTest, spanIdByLog)
573+
assert.equal(traceIdByTest, traceIdByLog)
574+
done()
575+
}).catch(done)
576+
})
577+
})
505578
})
506579

507580
it('reports timeout error message', (done) => {

packages/datadog-instrumentations/src/jest.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ const {
1212
getTestParametersString,
1313
addEfdStringToTestName,
1414
removeEfdStringFromTestName,
15-
getIsFaultyEarlyFlakeDetection
15+
getIsFaultyEarlyFlakeDetection,
16+
JEST_WORKER_LOGS_PAYLOAD_CODE
1617
} = require('../../dd-trace/src/plugins/util/test')
1718
const {
1819
getFormattedJestTestParameters,
@@ -30,6 +31,7 @@ const testSuiteFinishCh = channel('ci:jest:test-suite:finish')
3031

3132
const workerReportTraceCh = channel('ci:jest:worker-report:trace')
3233
const workerReportCoverageCh = channel('ci:jest:worker-report:coverage')
34+
const workerReportLogsCh = channel('ci:jest:worker-report:logs')
3335

3436
const testSuiteCodeCoverageCh = channel('ci:jest:test-suite:code-coverage')
3537

@@ -979,6 +981,12 @@ addHook({
979981
})
980982
return
981983
}
984+
if (code === JEST_WORKER_LOGS_PAYLOAD_CODE) { // datadog logs payload
985+
sessionAsyncResource.runInAsyncScope(() => {
986+
workerReportLogsCh.publish(data)
987+
})
988+
return
989+
}
982990
return _onMessage.apply(this, arguments)
983991
})
984992
return childProcessWorker

packages/datadog-plugin-jest/src/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,12 @@ class JestPlugin extends CiPlugin {
265265
})
266266
})
267267

268+
this.addSub('ci:jest:worker-report:logs', (logsPayloads) => {
269+
JSON.parse(logsPayloads).forEach(({ testConfiguration, logMessage }) => {
270+
this.tracer._exporter.exportDiLogs(testConfiguration, logMessage)
271+
})
272+
})
273+
268274
this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
269275
this.testSuiteSpan.setTag(TEST_STATUS, status)
270276
if (error) {

packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ class TestVisDynamicInstrumentation {
5555
start (config) {
5656
if (this.worker) return
5757

58-
const { NODE_OPTIONS, ...envWithoutNodeOptions } = process.env
59-
6058
log.debug('Starting Test Visibility - Dynamic Instrumentation client...')
6159

6260
const rcChannel = new MessageChannel() // mock channel
@@ -66,7 +64,14 @@ class TestVisDynamicInstrumentation {
6664
join(__dirname, 'worker', 'index.js'),
6765
{
6866
execArgv: [],
69-
env: envWithoutNodeOptions,
67+
// Not passing `NODE_OPTIONS` results in issues with yarn, which relies on NODE_OPTIONS
68+
// for PnP support, hence why we deviate from the DI pattern here.
69+
// To avoid infinite initialization loops, we're disabling DI and tracing in the worker.
70+
env: {
71+
...process.env,
72+
DD_TRACE_ENABLED: 0,
73+
DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED: 0
74+
},
7075
workerData: {
7176
config: config.serialize(),
7277
parentThreadId,
@@ -89,9 +94,11 @@ class TestVisDynamicInstrumentation {
8994
log.debug('Test Visibility - Dynamic Instrumentation client is ready')
9095
this._onReady()
9196
})
97+
9298
this.worker.on('error', (err) => {
9399
log.error('Test Visibility - Dynamic Instrumentation worker error', err)
94100
})
101+
95102
this.worker.on('messageerror', (err) => {
96103
log.error('Test Visibility - Dynamic Instrumentation worker messageerror', err)
97104
})

packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ const {
55
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
66
JEST_WORKER_TRACE_PAYLOAD_CODE,
77
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
8-
MOCHA_WORKER_TRACE_PAYLOAD_CODE
8+
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
9+
JEST_WORKER_LOGS_PAYLOAD_CODE
910
} = require('../../../plugins/util/test')
1011

1112
function getInterprocessTraceCode () {
@@ -29,18 +30,27 @@ function getInterprocessCoverageCode () {
2930
return null
3031
}
3132

33+
function getInterprocessLogsCode () {
34+
if (process.env.JEST_WORKER_ID) {
35+
return JEST_WORKER_LOGS_PAYLOAD_CODE
36+
}
37+
return null
38+
}
39+
3240
/**
3341
* Lightweight exporter whose writers only do simple JSON serialization
34-
* of trace and coverage payloads, which they send to the test framework's main process.
35-
* Currently used by Jest and Cucumber workers.
42+
* of trace, coverage and logs payloads, which they send to the test framework's main process.
43+
* Currently used by Jest, Cucumber and Mocha workers.
3644
*/
3745
class TestWorkerCiVisibilityExporter {
3846
constructor () {
3947
const interprocessTraceCode = getInterprocessTraceCode()
4048
const interprocessCoverageCode = getInterprocessCoverageCode()
49+
const interprocessLogsCode = getInterprocessLogsCode()
4150

4251
this._writer = new Writer(interprocessTraceCode)
4352
this._coverageWriter = new Writer(interprocessCoverageCode)
53+
this._logsWriter = new Writer(interprocessLogsCode)
4454
}
4555

4656
export (payload) {
@@ -51,9 +61,14 @@ class TestWorkerCiVisibilityExporter {
5161
this._coverageWriter.append(formattedCoverage)
5262
}
5363

64+
exportDiLogs (testConfiguration, logMessage) {
65+
this._logsWriter.append({ testConfiguration, logMessage })
66+
}
67+
5468
flush () {
5569
this._writer.flush()
5670
this._coverageWriter.flush()
71+
this._logsWriter.flush()
5772
}
5873
}
5974

packages/dd-trace/src/plugins/ci_plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ module.exports = class CiPlugin extends Plugin {
320320
)
321321

322322
const activeTestSpanContext = this.activeTestSpan.context()
323+
323324
this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
324325
debugger: { snapshot },
325326
dd: {

packages/dd-trace/src/plugins/util/test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const TEST_BROWSER_VERSION = 'test.browser.version'
8888
// jest worker variables
8989
const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
9090
const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
91+
const JEST_WORKER_LOGS_PAYLOAD_CODE = 62
9192

9293
// cucumber worker variables
9394
const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
@@ -134,6 +135,7 @@ module.exports = {
134135
LIBRARY_VERSION,
135136
JEST_WORKER_TRACE_PAYLOAD_CODE,
136137
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
138+
JEST_WORKER_LOGS_PAYLOAD_CODE,
137139
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
138140
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
139141
TEST_SOURCE_START,

0 commit comments

Comments
 (0)