Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Test Optimization] [SDTEST-1355] Fix DI setup for jest workers #5110

Merged
merged 13 commits into from
Jan 16, 2025
73 changes: 73 additions & 0 deletions integration-tests/jest/jest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,79 @@ describe('jest CommonJS', () => {
done()
}).catch(done)
})

it('can work with Dynamic Instrumentation', (done) => {
receiver.setSettings({
flaky_test_retries_enabled: true,
di_enabled: true
})
let snapshotIdByTest, snapshotIdByLog
let spanIdByTest, spanIdByLog, traceIdByTest, traceIdByLog
const eventsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
const events = payloads.flatMap(({ payload }) => payload.events)

const tests = events.filter(event => event.type === 'test').map(event => event.content)
const retriedTests = tests.filter(test => test.meta[TEST_IS_RETRY] === 'true')

assert.equal(retriedTests.length, 2)
const [retriedTest] = retriedTests

assert.propertyVal(retriedTest.meta, DI_ERROR_DEBUG_INFO_CAPTURED, 'true')

assert.isTrue(
retriedTest.meta[`${DI_DEBUG_ERROR_PREFIX}.0.${DI_DEBUG_ERROR_FILE_SUFFIX}`]
.endsWith('ci-visibility/dynamic-instrumentation/dependency.js')
)
assert.equal(retriedTest.metrics[`${DI_DEBUG_ERROR_PREFIX}.0.${DI_DEBUG_ERROR_LINE_SUFFIX}`], 4)

const snapshotIdKey = `${DI_DEBUG_ERROR_PREFIX}.0.${DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX}`
assert.exists(retriedTest.meta[snapshotIdKey])

snapshotIdByTest = retriedTest.meta[snapshotIdKey]
spanIdByTest = retriedTest.span_id.toString()
traceIdByTest = retriedTest.trace_id.toString()

const notRetriedTest = tests.find(test => test.meta[TEST_NAME].includes('is not retried'))

assert.notProperty(notRetriedTest.meta, DI_ERROR_DEBUG_INFO_CAPTURED)
})

const logsPromise = receiver
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/logs'), (payloads) => {
const [{ logMessage: [diLog] }] = payloads
assert.deepInclude(diLog, {
ddsource: 'dd_debugger',
level: 'error'
})
assert.equal(diLog.debugger.snapshot.language, 'javascript')
spanIdByLog = diLog.dd.span_id
traceIdByLog = diLog.dd.trace_id
snapshotIdByLog = diLog.debugger.snapshot.id
})

childProcess = exec(runTestsWithCoverageCommand,
{
cwd,
env: {
...getCiVisAgentlessConfig(receiver.port),
TESTS_TO_RUN: 'dynamic-instrumentation/test-',
DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED: 'true',
RUN_IN_PARALLEL: true
},
stdio: 'inherit'
}
)

childProcess.on('exit', () => {
Promise.all([eventsPromise, logsPromise]).then(() => {
assert.equal(snapshotIdByTest, snapshotIdByLog)
assert.equal(spanIdByTest, spanIdByLog)
assert.equal(traceIdByTest, traceIdByLog)
done()
}).catch(done)
})
})
})

it('reports timeout error message', (done) => {
Expand Down
10 changes: 9 additions & 1 deletion packages/datadog-instrumentations/src/jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const {
getTestParametersString,
addEfdStringToTestName,
removeEfdStringFromTestName,
getIsFaultyEarlyFlakeDetection
getIsFaultyEarlyFlakeDetection,
JEST_WORKER_LOGS_PAYLOAD_CODE
} = require('../../dd-trace/src/plugins/util/test')
const {
getFormattedJestTestParameters,
Expand All @@ -30,6 +31,7 @@ const testSuiteFinishCh = channel('ci:jest:test-suite:finish')

const workerReportTraceCh = channel('ci:jest:worker-report:trace')
const workerReportCoverageCh = channel('ci:jest:worker-report:coverage')
const workerReportLogsCh = channel('ci:jest:worker-report:logs')

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

Expand Down Expand Up @@ -979,6 +981,12 @@ addHook({
})
return
}
if (code === JEST_WORKER_LOGS_PAYLOAD_CODE) { // datadog logs payload
sessionAsyncResource.runInAsyncScope(() => {
workerReportLogsCh.publish(data)
})
return
}
return _onMessage.apply(this, arguments)
})
return childProcessWorker
Expand Down
6 changes: 6 additions & 0 deletions packages/datadog-plugin-jest/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ class JestPlugin extends CiPlugin {
})
})

this.addSub('ci:jest:worker-report:logs', (logsPayloads) => {
JSON.parse(logsPayloads).forEach(({ testConfiguration, logMessage }) => {
this.tracer._exporter.exportDiLogs(testConfiguration, logMessage)
})
})

this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
this.testSuiteSpan.setTag(TEST_STATUS, status)
if (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ class TestVisDynamicInstrumentation {
start (config) {
if (this.worker) return

const { NODE_OPTIONS, ...envWithoutNodeOptions } = process.env

log.debug('Starting Test Visibility - Dynamic Instrumentation client...')

const rcChannel = new MessageChannel() // mock channel
Expand All @@ -66,7 +64,14 @@ class TestVisDynamicInstrumentation {
join(__dirname, 'worker', 'index.js'),
{
execArgv: [],
env: envWithoutNodeOptions,
// Not passing `NODE_OPTIONS` results in issues with yarn, which relies on NODE_OPTIONS
// for PnP support, hence why we deviate from the DI pattern here.
// To avoid infinite initialization loops, we're disabling DI and tracing in the worker.
env: {
...process.env,
DD_TRACE_ENABLED: 0,
DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED: 0
},
workerData: {
config: config.serialize(),
parentThreadId,
Expand All @@ -89,9 +94,11 @@ class TestVisDynamicInstrumentation {
log.debug('Test Visibility - Dynamic Instrumentation client is ready')
this._onReady()
})

this.worker.on('error', (err) => {
log.error('Test Visibility - Dynamic Instrumentation worker error', err)
})

this.worker.on('messageerror', (err) => {
log.error('Test Visibility - Dynamic Instrumentation worker messageerror', err)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const {
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
JEST_WORKER_TRACE_PAYLOAD_CODE,
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
MOCHA_WORKER_TRACE_PAYLOAD_CODE
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
JEST_WORKER_LOGS_PAYLOAD_CODE
} = require('../../../plugins/util/test')

function getInterprocessTraceCode () {
Expand All @@ -29,18 +30,27 @@ function getInterprocessCoverageCode () {
return null
}

function getInterprocessLogsCode () {
if (process.env.JEST_WORKER_ID) {
return JEST_WORKER_LOGS_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.
* of trace, coverage and logs payloads, which they send to the test framework's main process.
* Currently used by Jest, Cucumber and Mocha workers.
*/
class TestWorkerCiVisibilityExporter {
constructor () {
const interprocessTraceCode = getInterprocessTraceCode()
const interprocessCoverageCode = getInterprocessCoverageCode()
const interprocessLogsCode = getInterprocessLogsCode()

this._writer = new Writer(interprocessTraceCode)
this._coverageWriter = new Writer(interprocessCoverageCode)
this._logsWriter = new Writer(interprocessLogsCode)
}

export (payload) {
Expand All @@ -51,9 +61,14 @@ class TestWorkerCiVisibilityExporter {
this._coverageWriter.append(formattedCoverage)
}

exportDiLogs (testConfiguration, logMessage) {
this._logsWriter.append({ testConfiguration, logMessage })
}

flush () {
this._writer.flush()
this._coverageWriter.flush()
this._logsWriter.flush()
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/dd-trace/src/plugins/ci_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ module.exports = class CiPlugin extends Plugin {
)

const activeTestSpanContext = this.activeTestSpan.context()

this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
debugger: { snapshot },
dd: {
Expand Down
2 changes: 2 additions & 0 deletions packages/dd-trace/src/plugins/util/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const TEST_BROWSER_VERSION = 'test.browser.version'
// jest worker variables
const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
const JEST_WORKER_LOGS_PAYLOAD_CODE = 62

// cucumber worker variables
const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
Expand Down Expand Up @@ -134,6 +135,7 @@ module.exports = {
LIBRARY_VERSION,
JEST_WORKER_TRACE_PAYLOAD_CODE,
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
JEST_WORKER_LOGS_PAYLOAD_CODE,
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
TEST_SOURCE_START,
Expand Down
Loading