dd-trace 3.46.0 → 3.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -3
- package/README.md +1 -32
- package/ci/init.js +1 -4
- package/index.d.ts +21 -0
- package/package.json +6 -8
- package/packages/datadog-instrumentations/src/amqplib.js +2 -2
- package/packages/datadog-instrumentations/src/child_process.js +150 -0
- package/packages/datadog-instrumentations/src/cucumber.js +12 -12
- package/packages/datadog-instrumentations/src/express.js +20 -0
- package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -2
- package/packages/datadog-instrumentations/src/jest.js +147 -10
- package/packages/datadog-instrumentations/src/mocha.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +23 -10
- package/packages/datadog-instrumentations/src/mquery.js +65 -0
- package/packages/datadog-instrumentations/src/next.js +17 -3
- package/packages/datadog-instrumentations/src/playwright.js +41 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +10 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +14 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +134 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +19 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -10
- package/packages/datadog-plugin-child_process/src/index.js +91 -0
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
- package/packages/datadog-plugin-cucumber/src/index.js +16 -11
- package/packages/datadog-plugin-cypress/src/plugin.js +25 -12
- package/packages/datadog-plugin-graphql/src/index.js +1 -6
- package/packages/datadog-plugin-grpc/src/client.js +16 -2
- package/packages/datadog-plugin-grpc/src/util.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +47 -6
- package/packages/datadog-plugin-mocha/src/index.js +14 -5
- package/packages/datadog-plugin-playwright/src/index.js +19 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +11 -1
- package/packages/datadog-plugin-rhea/src/producer.js +11 -0
- package/packages/dd-trace/src/appsec/addresses.js +2 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +16 -3
- package/packages/dd-trace/src/appsec/channels.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +22 -17
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -1
- package/packages/dd-trace/src/appsec/index.js +17 -2
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +83 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +83 -41
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +30 -8
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -1
- package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → requests/get-library-configuration.js} +18 -6
- package/packages/dd-trace/src/config.js +22 -9
- package/packages/dd-trace/src/datastreams/processor.js +36 -5
- package/packages/dd-trace/src/datastreams/writer.js +11 -5
- package/packages/dd-trace/src/dogstatsd.js +3 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +5 -3
- package/packages/dd-trace/src/exporters/common/request.js +21 -3
- package/packages/dd-trace/src/format.js +25 -1
- package/packages/dd-trace/src/noop/span.js +1 -0
- package/packages/dd-trace/src/opentelemetry/span.js +9 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -1
- package/packages/dd-trace/src/opentracing/span.js +38 -0
- package/packages/dd-trace/src/opentracing/span_context.js +12 -6
- package/packages/dd-trace/src/opentracing/tracer.js +2 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +24 -8
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +6 -0
- package/packages/dd-trace/src/plugins/util/test.js +36 -7
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/profiling/config.js +22 -22
- package/packages/dd-trace/src/proxy.js +31 -23
- package/packages/dd-trace/src/span_processor.js +5 -1
- package/packages/dd-trace/src/telemetry/index.js +3 -0
- package/packages/dd-trace/src/tracer.js +1 -0
- package/packages/utils/src/kebabcase.js +16 -0
- package/packages/utils/src/pick.js +11 -0
- package/packages/utils/src/uniq.js +5 -0
- package/packages/datadog-instrumentations/src/child-process.js +0 -29
- package/packages/dd-trace/src/plugins/util/exec.js +0 -34
|
@@ -30,7 +30,7 @@ module.exports = {
|
|
|
30
30
|
'body-parser': () => require('../body-parser'),
|
|
31
31
|
'bunyan': () => require('../bunyan'),
|
|
32
32
|
'cassandra-driver': () => require('../cassandra-driver'),
|
|
33
|
-
'child_process': () => require('../
|
|
33
|
+
'child_process': () => require('../child_process'),
|
|
34
34
|
'connect': () => require('../connect'),
|
|
35
35
|
'cookie': () => require('../cookie'),
|
|
36
36
|
'cookie-parser': () => require('../cookie-parser'),
|
|
@@ -73,11 +73,12 @@ module.exports = {
|
|
|
73
73
|
'mongodb': () => require('../mongodb'),
|
|
74
74
|
'mongodb-core': () => require('../mongodb-core'),
|
|
75
75
|
'mongoose': () => require('../mongoose'),
|
|
76
|
+
'mquery': () => require('../mquery'),
|
|
76
77
|
'mysql': () => require('../mysql'),
|
|
77
78
|
'mysql2': () => require('../mysql2'),
|
|
78
79
|
'net': () => require('../net'),
|
|
79
80
|
'next': () => require('../next'),
|
|
80
|
-
'node:child_process': () => require('../
|
|
81
|
+
'node:child_process': () => require('../child_process'),
|
|
81
82
|
'node:crypto': () => require('../crypto'),
|
|
82
83
|
'node:dns': () => require('../dns'),
|
|
83
84
|
'node:http': () => require('../http'),
|
|
@@ -37,11 +37,16 @@ const testRunFinishCh = channel('ci:jest:test:finish')
|
|
|
37
37
|
const testErrCh = channel('ci:jest:test:err')
|
|
38
38
|
|
|
39
39
|
const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
|
|
40
|
-
const
|
|
40
|
+
const libraryConfigurationCh = channel('ci:jest:library-configuration')
|
|
41
|
+
const knownTestsCh = channel('ci:jest:known-tests')
|
|
41
42
|
|
|
42
43
|
const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
|
|
43
44
|
|
|
45
|
+
// Maximum time we'll wait for the tracer to flush
|
|
46
|
+
const FLUSH_TIMEOUT = 10000
|
|
47
|
+
|
|
44
48
|
let skippableSuites = []
|
|
49
|
+
let knownTests = []
|
|
45
50
|
let isCodeCoverageEnabled = false
|
|
46
51
|
let isSuitesSkippingEnabled = false
|
|
47
52
|
let isUserCodeCoverageEnabled = false
|
|
@@ -49,6 +54,11 @@ let isSuitesSkipped = false
|
|
|
49
54
|
let numSkippedSuites = 0
|
|
50
55
|
let hasUnskippableSuites = false
|
|
51
56
|
let hasForcedToRunSuites = false
|
|
57
|
+
let isEarlyFlakeDetectionEnabled = false
|
|
58
|
+
let earlyFlakeDetectionNumRetries = 0
|
|
59
|
+
|
|
60
|
+
const EFD_STRING = "Retried by Datadog's Early Flake Detection"
|
|
61
|
+
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
|
|
52
62
|
|
|
53
63
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
54
64
|
|
|
@@ -62,6 +72,7 @@ const specStatusToTestStatus = {
|
|
|
62
72
|
|
|
63
73
|
const asyncResources = new WeakMap()
|
|
64
74
|
const originalTestFns = new WeakMap()
|
|
75
|
+
const retriedTestsToNumAttempts = new Map()
|
|
65
76
|
|
|
66
77
|
// based on https://github.com/facebook/jest/blob/main/packages/jest-circus/src/formatNodeAssertErrors.ts#L41
|
|
67
78
|
function formatJestError (errors) {
|
|
@@ -90,6 +101,14 @@ function getTestEnvironmentOptions (config) {
|
|
|
90
101
|
return {}
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
function getEfdTestName (testName, numAttempt) {
|
|
105
|
+
return `${EFD_STRING} (#${numAttempt}): ${testName}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function removeEfdTestName (testName) {
|
|
109
|
+
return testName.replace(EFD_TEST_NAME_REGEX, '')
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
94
113
|
return class DatadogEnvironment extends BaseEnvironment {
|
|
95
114
|
constructor (config, context) {
|
|
@@ -97,10 +116,43 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
97
116
|
const rootDir = config.globalConfig ? config.globalConfig.rootDir : config.rootDir
|
|
98
117
|
this.rootDir = rootDir
|
|
99
118
|
this.testSuite = getTestSuitePath(context.testPath, rootDir)
|
|
119
|
+
this.testFileAbsolutePath = context.testPath
|
|
100
120
|
this.nameToParams = {}
|
|
101
121
|
this.global._ddtrace = global._ddtrace
|
|
102
122
|
|
|
103
123
|
this.testEnvironmentOptions = getTestEnvironmentOptions(config)
|
|
124
|
+
|
|
125
|
+
this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
|
|
126
|
+
|
|
127
|
+
if (this.isEarlyFlakeDetectionEnabled) {
|
|
128
|
+
earlyFlakeDetectionNumRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionNumRetries
|
|
129
|
+
try {
|
|
130
|
+
this.knownTestsForThisSuite = this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// If there has been an error parsing the tests, we'll disable Early Flake Deteciton
|
|
133
|
+
this.isEarlyFlakeDetectionEnabled = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Function that receives a list of known tests for a test service and
|
|
139
|
+
// returns the ones that belong to the current suite
|
|
140
|
+
getKnownTestsForSuite (knownTests) {
|
|
141
|
+
let knownTestsForSuite = knownTests
|
|
142
|
+
// If jest runs in band, the known tests are not serialized, so they're an array.
|
|
143
|
+
if (!Array.isArray(knownTests)) {
|
|
144
|
+
knownTestsForSuite = JSON.parse(knownTestsForSuite)
|
|
145
|
+
}
|
|
146
|
+
return knownTestsForSuite
|
|
147
|
+
.filter(test => test.includes(this.testSuite))
|
|
148
|
+
.map(test => test.replace(`jest.${this.testSuite}.`, '').trim())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Add the `add_test` event we don't have the test object yet, so
|
|
152
|
+
// we use its describe block to get the full name
|
|
153
|
+
getTestNameFromAddTestEvent (event, state) {
|
|
154
|
+
const describeSuffix = getJestTestName(state.currentDescribeBlock)
|
|
155
|
+
return removeEfdTestName(`${describeSuffix} ${event.testName}`).trim()
|
|
104
156
|
}
|
|
105
157
|
|
|
106
158
|
async handleTestEvent (event, state) {
|
|
@@ -124,23 +176,56 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
124
176
|
}
|
|
125
177
|
}
|
|
126
178
|
if (event.name === 'test_start') {
|
|
179
|
+
let isNewTest = false
|
|
180
|
+
let numEfdRetry = null
|
|
127
181
|
const testParameters = getTestParametersString(this.nameToParams, event.test.name)
|
|
128
182
|
// Async resource for this test is created here
|
|
129
183
|
// It is used later on by the test_done handler
|
|
130
184
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
131
185
|
asyncResources.set(event.test, asyncResource)
|
|
186
|
+
const testName = getJestTestName(event.test)
|
|
187
|
+
|
|
188
|
+
if (this.isEarlyFlakeDetectionEnabled) {
|
|
189
|
+
const originalTestName = removeEfdTestName(testName)
|
|
190
|
+
isNewTest = retriedTestsToNumAttempts.has(originalTestName)
|
|
191
|
+
if (isNewTest) {
|
|
192
|
+
numEfdRetry = retriedTestsToNumAttempts.get(originalTestName)
|
|
193
|
+
retriedTestsToNumAttempts.set(originalTestName, numEfdRetry + 1)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
132
197
|
asyncResource.runInAsyncScope(() => {
|
|
133
198
|
testStartCh.publish({
|
|
134
|
-
name:
|
|
199
|
+
name: removeEfdTestName(testName),
|
|
135
200
|
suite: this.testSuite,
|
|
201
|
+
testFileAbsolutePath: this.testFileAbsolutePath,
|
|
136
202
|
runner: 'jest-circus',
|
|
137
203
|
testParameters,
|
|
138
|
-
frameworkVersion: jestVersion
|
|
204
|
+
frameworkVersion: jestVersion,
|
|
205
|
+
isNew: isNewTest,
|
|
206
|
+
isEfdRetry: numEfdRetry > 0
|
|
139
207
|
})
|
|
140
208
|
originalTestFns.set(event.test, event.test.fn)
|
|
141
209
|
event.test.fn = asyncResource.bind(event.test.fn)
|
|
142
210
|
})
|
|
143
211
|
}
|
|
212
|
+
if (event.name === 'add_test') {
|
|
213
|
+
if (this.isEarlyFlakeDetectionEnabled) {
|
|
214
|
+
const testName = this.getTestNameFromAddTestEvent(event, state)
|
|
215
|
+
const isNew = !this.knownTestsForThisSuite?.includes(testName)
|
|
216
|
+
const isSkipped = event.mode === 'todo' || event.mode === 'skip'
|
|
217
|
+
if (isNew && !isSkipped && !retriedTestsToNumAttempts.has(testName)) {
|
|
218
|
+
retriedTestsToNumAttempts.set(testName, 0)
|
|
219
|
+
for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
|
|
220
|
+
if (this.global.test) {
|
|
221
|
+
this.global.test(getEfdTestName(event.testName, retryIndex), event.fn, event.timeout)
|
|
222
|
+
} else {
|
|
223
|
+
log.error('Early flake detection could not retry test because global.test is undefined')
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
144
229
|
if (event.name === 'test_done') {
|
|
145
230
|
const asyncResource = asyncResources.get(event.test)
|
|
146
231
|
asyncResource.runInAsyncScope(() => {
|
|
@@ -164,6 +249,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
164
249
|
testSkippedCh.publish({
|
|
165
250
|
name: getJestTestName(event.test),
|
|
166
251
|
suite: this.testSuite,
|
|
252
|
+
testFileAbsolutePath: this.testFileAbsolutePath,
|
|
167
253
|
runner: 'jest-circus',
|
|
168
254
|
frameworkVersion: jestVersion,
|
|
169
255
|
testStartLine: getTestLineStart(event.test.asyncError, this.testSuite)
|
|
@@ -206,7 +292,7 @@ addHook({
|
|
|
206
292
|
}
|
|
207
293
|
// TODO: could we get the rootDir from each test?
|
|
208
294
|
const [test] = shardedTests
|
|
209
|
-
const rootDir = test
|
|
295
|
+
const rootDir = test?.context?.config?.rootDir
|
|
210
296
|
|
|
211
297
|
const jestSuitesToRun = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
|
|
212
298
|
|
|
@@ -234,24 +320,45 @@ function cliWrapper (cli, jestVersion) {
|
|
|
234
320
|
const configurationPromise = new Promise((resolve) => {
|
|
235
321
|
onDone = resolve
|
|
236
322
|
})
|
|
237
|
-
if (!
|
|
323
|
+
if (!libraryConfigurationCh.hasSubscribers) {
|
|
238
324
|
return runCLI.apply(this, arguments)
|
|
239
325
|
}
|
|
240
326
|
|
|
241
327
|
sessionAsyncResource.runInAsyncScope(() => {
|
|
242
|
-
|
|
328
|
+
libraryConfigurationCh.publish({ onDone })
|
|
243
329
|
})
|
|
244
330
|
|
|
245
331
|
try {
|
|
246
|
-
const { err,
|
|
332
|
+
const { err, libraryConfig } = await configurationPromise
|
|
247
333
|
if (!err) {
|
|
248
|
-
isCodeCoverageEnabled =
|
|
249
|
-
isSuitesSkippingEnabled =
|
|
334
|
+
isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
|
|
335
|
+
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
336
|
+
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
337
|
+
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
250
338
|
}
|
|
251
339
|
} catch (err) {
|
|
252
340
|
log.error(err)
|
|
253
341
|
}
|
|
254
342
|
|
|
343
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
344
|
+
const knownTestsPromise = new Promise((resolve) => {
|
|
345
|
+
onDone = resolve
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
sessionAsyncResource.runInAsyncScope(() => {
|
|
349
|
+
knownTestsCh.publish({ onDone })
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const { err, knownTests: receivedKnownTests } = await knownTestsPromise
|
|
354
|
+
if (!err) {
|
|
355
|
+
knownTests = receivedKnownTests
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
log.error(err)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
255
362
|
if (isSuitesSkippingEnabled) {
|
|
256
363
|
const skippableSuitesPromise = new Promise((resolve) => {
|
|
257
364
|
onDone = resolve
|
|
@@ -311,6 +418,21 @@ function cliWrapper (cli, jestVersion) {
|
|
|
311
418
|
status = 'fail'
|
|
312
419
|
error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
|
|
313
420
|
}
|
|
421
|
+
let timeoutId
|
|
422
|
+
|
|
423
|
+
// Pass the resolve callback to defer it to DC listener
|
|
424
|
+
const flushPromise = new Promise((resolve) => {
|
|
425
|
+
onDone = () => {
|
|
426
|
+
clearTimeout(timeoutId)
|
|
427
|
+
resolve()
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
432
|
+
timeoutId = setTimeout(() => {
|
|
433
|
+
resolve('timeout')
|
|
434
|
+
}, FLUSH_TIMEOUT).unref()
|
|
435
|
+
})
|
|
314
436
|
|
|
315
437
|
sessionAsyncResource.runInAsyncScope(() => {
|
|
316
438
|
testSessionFinishCh.publish({
|
|
@@ -322,9 +444,16 @@ function cliWrapper (cli, jestVersion) {
|
|
|
322
444
|
numSkippedSuites,
|
|
323
445
|
hasUnskippableSuites,
|
|
324
446
|
hasForcedToRunSuites,
|
|
325
|
-
error
|
|
447
|
+
error,
|
|
448
|
+
isEarlyFlakeDetectionEnabled,
|
|
449
|
+
onDone
|
|
326
450
|
})
|
|
327
451
|
})
|
|
452
|
+
const waitingResult = await Promise.race([flushPromise, timeoutPromise])
|
|
453
|
+
|
|
454
|
+
if (waitingResult === 'timeout') {
|
|
455
|
+
log.error('Timeout waiting for the tracer to flush')
|
|
456
|
+
}
|
|
328
457
|
|
|
329
458
|
numSkippedSuites = 0
|
|
330
459
|
|
|
@@ -438,10 +567,15 @@ function configureTestEnvironment (readConfigsResult) {
|
|
|
438
567
|
// because `jestAdapterWrapper` runs in a different process. We have to go through `testEnvironmentOptions`
|
|
439
568
|
configs.forEach(config => {
|
|
440
569
|
config.testEnvironmentOptions._ddTestCodeCoverageEnabled = isCodeCoverageEnabled
|
|
570
|
+
config.testEnvironmentOptions._ddKnownTests = knownTests
|
|
441
571
|
})
|
|
442
572
|
|
|
443
573
|
isUserCodeCoverageEnabled = !!readConfigsResult.globalConfig.collectCoverage
|
|
444
574
|
|
|
575
|
+
if (readConfigsResult.globalConfig.forceExit) {
|
|
576
|
+
log.warn("Jest's '--forceExit' flag has been passed. This may cause loss of data.")
|
|
577
|
+
}
|
|
578
|
+
|
|
445
579
|
if (isCodeCoverageEnabled) {
|
|
446
580
|
const globalConfig = {
|
|
447
581
|
...readConfigsResult.globalConfig,
|
|
@@ -498,6 +632,9 @@ addHook({
|
|
|
498
632
|
_ddForcedToRun,
|
|
499
633
|
_ddUnskippable,
|
|
500
634
|
_ddItrCorrelationId,
|
|
635
|
+
_ddKnownTests,
|
|
636
|
+
_ddIsEarlyFlakeDetectionEnabled,
|
|
637
|
+
_ddEarlyFlakeDetectionNumRetries,
|
|
501
638
|
...restOfTestEnvironmentOptions
|
|
502
639
|
} = testEnvironmentOptions
|
|
503
640
|
|
|
@@ -20,7 +20,7 @@ const skipCh = channel('ci:mocha:test:skip')
|
|
|
20
20
|
const testFinishCh = channel('ci:mocha:test:finish')
|
|
21
21
|
const parameterizedTestCh = channel('ci:mocha:test:parameterize')
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const libraryConfigurationCh = channel('ci:mocha:library-configuration')
|
|
24
24
|
const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
|
|
25
25
|
|
|
26
26
|
const testSessionStartCh = channel('ci:mocha:session:start')
|
|
@@ -384,7 +384,7 @@ addHook({
|
|
|
384
384
|
return run.apply(this, arguments)
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
if (!
|
|
387
|
+
if (!libraryConfigurationCh.hasSubscribers || this.isWorker) {
|
|
388
388
|
if (this.isWorker) {
|
|
389
389
|
isWorker = true
|
|
390
390
|
}
|
|
@@ -439,7 +439,7 @@ addHook({
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
mochaRunAsyncResource.runInAsyncScope(() => {
|
|
442
|
-
|
|
442
|
+
libraryConfigurationCh.publish({
|
|
443
443
|
onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
|
|
444
444
|
})
|
|
445
445
|
})
|
|
@@ -79,21 +79,26 @@ addHook({
|
|
|
79
79
|
})
|
|
80
80
|
|
|
81
81
|
let callbackWrapped = false
|
|
82
|
-
const lastArgumentIndex = arguments.length - 1
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
shimmer.wrap(arguments, lastArgumentIndex, originalCb => {
|
|
87
|
-
return function () {
|
|
88
|
-
finish()
|
|
83
|
+
const wrapCallbackIfExist = (args) => {
|
|
84
|
+
const lastArgumentIndex = args.length - 1
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
if (typeof args[lastArgumentIndex] === 'function') {
|
|
87
|
+
// is a callback, wrap it to execute finish()
|
|
88
|
+
shimmer.wrap(args, lastArgumentIndex, originalCb => {
|
|
89
|
+
return function () {
|
|
90
|
+
finish()
|
|
91
|
+
|
|
92
|
+
return originalCb.apply(this, arguments)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
callbackWrapped = true
|
|
97
|
+
}
|
|
95
98
|
}
|
|
96
99
|
|
|
100
|
+
wrapCallbackIfExist(arguments)
|
|
101
|
+
|
|
97
102
|
return asyncResource.runInAsyncScope(() => {
|
|
98
103
|
startCh.publish({
|
|
99
104
|
filters,
|
|
@@ -106,8 +111,16 @@ addHook({
|
|
|
106
111
|
if (!callbackWrapped) {
|
|
107
112
|
shimmer.wrap(res, 'exec', originalExec => {
|
|
108
113
|
return function wrappedExec () {
|
|
114
|
+
if (!callbackWrapped) {
|
|
115
|
+
wrapCallbackIfExist(arguments)
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
const execResult = originalExec.apply(this, arguments)
|
|
110
119
|
|
|
120
|
+
if (callbackWrapped || typeof execResult?.then !== 'function') {
|
|
121
|
+
return execResult
|
|
122
|
+
}
|
|
123
|
+
|
|
111
124
|
// wrap them method, wrap resolve and reject methods
|
|
112
125
|
shimmer.wrap(execResult, 'then', originalThen => {
|
|
113
126
|
return function wrappedThen () {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const dc = require('dc-polyfill')
|
|
4
|
+
const {
|
|
5
|
+
channel,
|
|
6
|
+
addHook
|
|
7
|
+
} = require('./helpers/instrument')
|
|
8
|
+
const shimmer = require('../../datadog-shimmer')
|
|
9
|
+
|
|
10
|
+
const prepareCh = channel('datadog:mquery:filter:prepare')
|
|
11
|
+
const tracingCh = dc.tracingChannel('datadog:mquery:filter')
|
|
12
|
+
|
|
13
|
+
const methods = [
|
|
14
|
+
'find',
|
|
15
|
+
'findOne',
|
|
16
|
+
'findOneAndRemove',
|
|
17
|
+
'findOneAndDelete',
|
|
18
|
+
'count',
|
|
19
|
+
'distinct',
|
|
20
|
+
'where'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const methodsOptionalArgs = ['findOneAndUpdate']
|
|
24
|
+
|
|
25
|
+
function getFilters (args, methodName) {
|
|
26
|
+
const [arg0, arg1] = args
|
|
27
|
+
|
|
28
|
+
const filters = arg0 && typeof arg0 === 'object' ? [arg0] : []
|
|
29
|
+
|
|
30
|
+
if (arg1 && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) {
|
|
31
|
+
filters.push(arg1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return filters
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
addHook({
|
|
38
|
+
name: 'mquery',
|
|
39
|
+
versions: ['>=5.0.0']
|
|
40
|
+
}, Query => {
|
|
41
|
+
[...methods, ...methodsOptionalArgs].forEach(methodName => {
|
|
42
|
+
if (!(methodName in Query.prototype)) return
|
|
43
|
+
|
|
44
|
+
shimmer.wrap(Query.prototype, methodName, method => {
|
|
45
|
+
return function () {
|
|
46
|
+
if (prepareCh.hasSubscribers) {
|
|
47
|
+
const filters = getFilters(arguments, methodName)
|
|
48
|
+
if (filters?.length) {
|
|
49
|
+
prepareCh.publish({ filters })
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return method.apply(this, arguments)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
shimmer.wrap(Query.prototype, 'exec', originalExec => {
|
|
59
|
+
return function wrappedExec () {
|
|
60
|
+
return tracingCh.tracePromise(originalExec, {}, this, arguments)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return Query
|
|
65
|
+
})
|
|
@@ -290,9 +290,23 @@ addHook({
|
|
|
290
290
|
shimmer.massWrap(request.NextRequest.prototype, ['text', 'json'], function (originalMethod) {
|
|
291
291
|
return async function wrappedJson () {
|
|
292
292
|
const body = await originalMethod.apply(this, arguments)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
|
|
294
|
+
bodyParsedChannel.publish({ body })
|
|
295
|
+
|
|
296
|
+
return body
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
shimmer.wrap(request.NextRequest.prototype, 'formData', function (originalFormData) {
|
|
301
|
+
return async function wrappedFormData () {
|
|
302
|
+
const body = await originalFormData.apply(this, arguments)
|
|
303
|
+
|
|
304
|
+
let normalizedBody = body
|
|
305
|
+
if (typeof body.entries === 'function') {
|
|
306
|
+
normalizedBody = Object.fromEntries(body.entries())
|
|
307
|
+
}
|
|
308
|
+
bodyParsedChannel.publish({ body: normalizedBody })
|
|
309
|
+
|
|
296
310
|
return body
|
|
297
311
|
}
|
|
298
312
|
})
|
|
@@ -73,14 +73,41 @@ function getRootDir (playwrightRunner) {
|
|
|
73
73
|
if (playwrightRunner._configDir) {
|
|
74
74
|
return playwrightRunner._configDir
|
|
75
75
|
}
|
|
76
|
-
if (playwrightRunner._config
|
|
77
|
-
return playwrightRunner._config.config.
|
|
76
|
+
if (playwrightRunner._config) {
|
|
77
|
+
return playwrightRunner._config.config?.rootDir || process.cwd()
|
|
78
78
|
}
|
|
79
79
|
return process.cwd()
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function
|
|
83
|
-
const
|
|
82
|
+
function getProjectsFromRunner (runner) {
|
|
83
|
+
const config = getPlaywrightConfig(runner)
|
|
84
|
+
return config.projects?.map(({ project }) => project)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getProjectsFromDispatcher (dispatcher) {
|
|
88
|
+
const newConfig = dispatcher._config?.config?.projects
|
|
89
|
+
if (newConfig) {
|
|
90
|
+
return newConfig
|
|
91
|
+
}
|
|
92
|
+
// old
|
|
93
|
+
return dispatcher._loader?.fullConfig()?.projects
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getBrowserNameFromProjects (projects, projectId) {
|
|
97
|
+
if (!projects) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
return projects.find(project =>
|
|
101
|
+
project.__projectId === projectId || project._id === projectId
|
|
102
|
+
)?.name
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function testBeginHandler (test, browserName) {
|
|
106
|
+
const {
|
|
107
|
+
_requireFile: testSuiteAbsolutePath,
|
|
108
|
+
title: testName, _type,
|
|
109
|
+
location: { line: testSourceLine }
|
|
110
|
+
} = test
|
|
84
111
|
|
|
85
112
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
86
113
|
return
|
|
@@ -100,7 +127,7 @@ function testBeginHandler (test) {
|
|
|
100
127
|
const testAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
101
128
|
testToAr.set(test, testAsyncResource)
|
|
102
129
|
testAsyncResource.runInAsyncScope(() => {
|
|
103
|
-
testStartCh.publish({ testName, testSuiteAbsolutePath, testSourceLine })
|
|
130
|
+
testStartCh.publish({ testName, testSuiteAbsolutePath, testSourceLine, browserName })
|
|
104
131
|
})
|
|
105
132
|
}
|
|
106
133
|
|
|
@@ -166,11 +193,12 @@ function dispatcherHook (dispatcherExport) {
|
|
|
166
193
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
|
|
167
194
|
const dispatcher = this
|
|
168
195
|
const worker = createWorker.apply(this, arguments)
|
|
169
|
-
|
|
170
196
|
worker.process.on('message', ({ method, params }) => {
|
|
171
197
|
if (method === 'testBegin') {
|
|
172
198
|
const { test } = dispatcher._testById.get(params.testId)
|
|
173
|
-
|
|
199
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
200
|
+
const browser = getBrowserNameFromProjects(projects, test._projectId)
|
|
201
|
+
testBeginHandler(test, browser)
|
|
174
202
|
} else if (method === 'testEnd') {
|
|
175
203
|
const { test } = dispatcher._testById.get(params.testId)
|
|
176
204
|
|
|
@@ -203,7 +231,9 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
203
231
|
|
|
204
232
|
worker.on('testBegin', ({ testId }) => {
|
|
205
233
|
const test = getTestByTestId(dispatcher, testId)
|
|
206
|
-
|
|
234
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
235
|
+
const browser = getBrowserNameFromProjects(projects, test._projectId)
|
|
236
|
+
testBeginHandler(test, browser)
|
|
207
237
|
})
|
|
208
238
|
worker.on('testEnd', ({ testId, status, errors, annotations }) => {
|
|
209
239
|
const test = getTestByTestId(dispatcher, testId)
|
|
@@ -226,6 +256,7 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
226
256
|
testSessionAsyncResource.runInAsyncScope(() => {
|
|
227
257
|
testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
|
|
228
258
|
})
|
|
259
|
+
const projects = getProjectsFromRunner(this)
|
|
229
260
|
|
|
230
261
|
const runAllTestsReturn = await runAllTests.apply(this, arguments)
|
|
231
262
|
|
|
@@ -234,7 +265,8 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
234
265
|
// there were tests that did not go through `testBegin` or `testEnd`,
|
|
235
266
|
// because they were skipped
|
|
236
267
|
tests.forEach(test => {
|
|
237
|
-
|
|
268
|
+
const browser = getBrowserNameFromProjects(projects, test._projectId)
|
|
269
|
+
testBeginHandler(test, browser)
|
|
238
270
|
testEndHandler(test, [], 'skip')
|
|
239
271
|
})
|
|
240
272
|
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { TEXT_MAP } = require('../../../ext/formats')
|
|
4
4
|
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
|
|
5
|
+
const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
|
|
5
6
|
const { getResourceName } = require('./util')
|
|
6
7
|
|
|
7
8
|
class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
@@ -13,7 +14,7 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
|
13
14
|
|
|
14
15
|
const childOf = extract(this.tracer, message)
|
|
15
16
|
|
|
16
|
-
this.startSpan({
|
|
17
|
+
const span = this.startSpan({
|
|
17
18
|
childOf,
|
|
18
19
|
resource: getResourceName(method, fields),
|
|
19
20
|
type: 'worker',
|
|
@@ -26,6 +27,14 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
|
|
|
26
27
|
'amqp.destination': fields.destination
|
|
27
28
|
}
|
|
28
29
|
})
|
|
30
|
+
|
|
31
|
+
if (this.config.dsmEnabled && message) {
|
|
32
|
+
const payloadSize = getAmqpMessageSize({ headers: message.properties.headers, content: message.content })
|
|
33
|
+
const queue = fields.queue ?? fields.routingKey
|
|
34
|
+
this.tracer.decodeDataStreamsContext(message.properties.headers[CONTEXT_PROPAGATION_KEY])
|
|
35
|
+
this.tracer
|
|
36
|
+
.setCheckpoint(['direction:in', `topic:${queue}`, 'type:rabbitmq'], span, payloadSize)
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
const { TEXT_MAP } = require('../../../ext/formats')
|
|
4
4
|
const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
5
5
|
const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
|
|
6
|
+
const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
|
|
7
|
+
const { getAmqpMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
|
|
6
8
|
const { getResourceName } = require('./util')
|
|
7
9
|
|
|
8
10
|
class AmqplibProducerPlugin extends ProducerPlugin {
|
|
9
11
|
static get id () { return 'amqplib' }
|
|
10
12
|
static get operation () { return 'command' }
|
|
11
13
|
|
|
12
|
-
start ({ channel = {}, method, fields }) {
|
|
14
|
+
start ({ channel = {}, method, fields, message }) {
|
|
13
15
|
if (method !== 'basic.publish') return
|
|
14
16
|
|
|
15
17
|
const stream = (channel.connection && channel.connection.stream) || {}
|
|
@@ -30,6 +32,17 @@ class AmqplibProducerPlugin extends ProducerPlugin {
|
|
|
30
32
|
fields.headers = fields.headers || {}
|
|
31
33
|
|
|
32
34
|
this.tracer.inject(span, TEXT_MAP, fields.headers)
|
|
35
|
+
|
|
36
|
+
if (this.config.dsmEnabled) {
|
|
37
|
+
const hasRoutingKey = fields.routingKey != null
|
|
38
|
+
const payloadSize = getAmqpMessageSize({ content: message, headers: fields.headers })
|
|
39
|
+
const dataStreamsContext = this.tracer
|
|
40
|
+
.setCheckpoint(
|
|
41
|
+
['direction:out', `exchange:${fields.exchange}`, `has_routing_key:${hasRoutingKey}`, 'type:rabbitmq']
|
|
42
|
+
, span, payloadSize)
|
|
43
|
+
const pathwayCtx = encodePathwayContext(dataStreamsContext)
|
|
44
|
+
fields.headers[CONTEXT_PROPAGATION_KEY] = pathwayCtx
|
|
45
|
+
}
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
|