dd-trace 5.105.0 → 5.107.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.
Files changed (108) hide show
  1. package/index.d.ts +20 -1
  2. package/package.json +5 -7
  3. package/packages/datadog-core/src/storage.js +47 -48
  4. package/packages/datadog-esbuild/index.js +6 -1
  5. package/packages/datadog-instrumentations/src/ai.js +12 -3
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -2
  7. package/packages/datadog-instrumentations/src/body-parser.js +5 -2
  8. package/packages/datadog-instrumentations/src/connect.js +3 -2
  9. package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
  10. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +319 -152
  12. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
  13. package/packages/datadog-instrumentations/src/express-session.js +12 -11
  14. package/packages/datadog-instrumentations/src/express.js +24 -20
  15. package/packages/datadog-instrumentations/src/fastify.js +18 -6
  16. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
  17. package/packages/datadog-instrumentations/src/http/client.js +9 -12
  18. package/packages/datadog-instrumentations/src/http/server.js +30 -16
  19. package/packages/datadog-instrumentations/src/http2/client.js +15 -12
  20. package/packages/datadog-instrumentations/src/http2/server.js +15 -8
  21. package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
  22. package/packages/datadog-instrumentations/src/jest.js +143 -73
  23. package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
  25. package/packages/datadog-instrumentations/src/multer.js +3 -2
  26. package/packages/datadog-instrumentations/src/mysql2.js +34 -0
  27. package/packages/datadog-instrumentations/src/net.js +8 -6
  28. package/packages/datadog-instrumentations/src/openai.js +19 -7
  29. package/packages/datadog-instrumentations/src/pg.js +19 -0
  30. package/packages/datadog-instrumentations/src/router.js +12 -10
  31. package/packages/datadog-instrumentations/src/vitest.js +29 -4
  32. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
  33. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  35. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
  36. package/packages/datadog-plugin-cucumber/src/index.js +2 -0
  37. package/packages/datadog-plugin-cypress/src/support.js +31 -1
  38. package/packages/datadog-plugin-http/src/client.js +0 -3
  39. package/packages/datadog-plugin-http/src/server.js +11 -1
  40. package/packages/datadog-plugin-mocha/src/index.js +2 -0
  41. package/packages/datadog-plugin-pg/src/index.js +10 -0
  42. package/packages/dd-trace/src/aiguard/index.js +34 -15
  43. package/packages/dd-trace/src/aiguard/sdk.js +34 -3
  44. package/packages/dd-trace/src/aiguard/tags.js +6 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  46. package/packages/dd-trace/src/config/defaults.js +14 -0
  47. package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
  48. package/packages/dd-trace/src/config/helper.js +1 -0
  49. package/packages/dd-trace/src/config/index.js +5 -9
  50. package/packages/dd-trace/src/config/parsers.js +8 -0
  51. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  52. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  53. package/packages/dd-trace/src/datastreams/writer.js +1 -2
  54. package/packages/dd-trace/src/debugger/config.js +1 -1
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
  56. package/packages/dd-trace/src/debugger/index.js +1 -2
  57. package/packages/dd-trace/src/dogstatsd.js +2 -3
  58. package/packages/dd-trace/src/encode/0.4.js +49 -41
  59. package/packages/dd-trace/src/encode/agentless-json.js +5 -1
  60. package/packages/dd-trace/src/encode/tags-processors.js +14 -0
  61. package/packages/dd-trace/src/exporters/agent/index.js +1 -2
  62. package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
  63. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
  64. package/packages/dd-trace/src/exporters/common/request.js +26 -0
  65. package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
  66. package/packages/dd-trace/src/id.js +15 -0
  67. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +91 -5
  68. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  69. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
  70. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
  71. package/packages/dd-trace/src/llmobs/sdk.js +4 -1
  72. package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
  73. package/packages/dd-trace/src/llmobs/tagger.js +5 -3
  74. package/packages/dd-trace/src/llmobs/util.js +54 -0
  75. package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
  76. package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
  77. package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
  78. package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
  79. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
  80. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
  81. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
  82. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +25 -5
  83. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -10
  84. package/packages/dd-trace/src/opentracing/span.js +23 -18
  85. package/packages/dd-trace/src/opentracing/span_context.js +1 -3
  86. package/packages/dd-trace/src/opentracing/tracer.js +16 -12
  87. package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
  88. package/packages/dd-trace/src/priority_sampler.js +6 -5
  89. package/packages/dd-trace/src/profiling/config.js +11 -25
  90. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  91. package/packages/dd-trace/src/profiling/profiler.js +19 -9
  92. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -3
  93. package/packages/dd-trace/src/proxy.js +13 -10
  94. package/packages/dd-trace/src/remote_config/index.js +1 -2
  95. package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
  96. package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
  97. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
  98. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
  99. package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
  100. package/packages/dd-trace/src/span_format.js +33 -25
  101. package/packages/dd-trace/src/span_stats.js +1 -1
  102. package/packages/dd-trace/src/startup-log.js +1 -2
  103. package/packages/dd-trace/src/telemetry/send-data.js +1 -1
  104. package/packages/dd-trace/src/tracer.js +1 -1
  105. package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
  106. package/vendor/dist/shell-quote/index.js +1 -1
  107. package/packages/dd-trace/src/agent/url.js +0 -28
  108. package/scripts/preinstall.js +0 -34
@@ -30,6 +30,8 @@ const { writeCoverageBackfillToCache } = require('../../dd-trace/src/ci-visibili
30
30
  const satisfies = require('../../../vendor/dist/semifies')
31
31
  const { addHook, channel } = require('./helpers/instrument')
32
32
 
33
+ const cucumberWorkerThreadsPatchModule = require.resolve('./cucumber-worker-threads')
34
+
33
35
  const testStartCh = channel('ci:cucumber:test:start')
34
36
  const testRetryCh = channel('ci:cucumber:test:retry')
35
37
  const testFinishCh = channel('ci:cucumber:test:finish') // used for test steps too
@@ -69,6 +71,7 @@ const originalCoverageMap = createCoverageMap()
69
71
 
70
72
  // TODO: remove in a later major version
71
73
  const patched = new WeakSet()
74
+ const patchedCucumberWorkers = new WeakSet()
72
75
 
73
76
  const lastStatusByPickleId = new Map()
74
77
  /** For ATR: statuses keyed by stable scenario id (uri:name) so retries accumulate correctly */
@@ -180,22 +183,270 @@ function getConfiguredEfdRetryCount () {
180
183
  }
181
184
 
182
185
  function publishWorkerEfdRetryCount (pickle, retryCount) {
183
- if (typeof process.send !== 'function') return
186
+ const message = {
187
+ [DD_EFD_RETRY_COUNT_MESSAGE]: {
188
+ pickleId: pickle.id,
189
+ retryCount,
190
+ testFileAbsolutePath: pickle.uri,
191
+ testName: pickle.name,
192
+ },
193
+ }
194
+
195
+ if (typeof process.send === 'function') {
196
+ try {
197
+ process.send(message)
198
+ } catch {
199
+ // ignore IPC errors
200
+ }
201
+ return
202
+ }
184
203
 
185
204
  try {
186
- process.send({
187
- [DD_EFD_RETRY_COUNT_MESSAGE]: {
188
- pickleId: pickle.id,
189
- retryCount,
190
- testFileAbsolutePath: pickle.uri,
191
- testName: pickle.name,
192
- },
193
- })
205
+ const { isMainThread, parentPort } = require('node:worker_threads')
206
+ if (isMainThread || !parentPort) return
207
+
208
+ parentPort.postMessage(message)
194
209
  } catch {
195
210
  // ignore IPC errors
196
211
  }
197
212
  }
198
213
 
214
+ function configureParallelWorkerWorldParameters (options) {
215
+ options.worldParameters ??= {}
216
+
217
+ if (isKnownTestsEnabled && isValidKnownTests(knownTests)) {
218
+ options.worldParameters._ddIsKnownTestsEnabled = true
219
+ options.worldParameters._ddIsEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
220
+ options.worldParameters._ddKnownTests = knownTests
221
+ options.worldParameters._ddEarlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
222
+ options.worldParameters._ddEarlyFlakeDetectionSlowTestRetries = earlyFlakeDetectionSlowTestRetries
223
+ } else {
224
+ isEarlyFlakeDetectionEnabled = false
225
+ isKnownTestsEnabled = false
226
+ options.worldParameters._ddIsEarlyFlakeDetectionEnabled = false
227
+ options.worldParameters._ddIsKnownTestsEnabled = false
228
+ options.worldParameters._ddEarlyFlakeDetectionNumRetries = 0
229
+ options.worldParameters._ddEarlyFlakeDetectionSlowTestRetries = {}
230
+ }
231
+
232
+ if (isImpactedTestsEnabled) {
233
+ options.worldParameters._ddImpactedTestsEnabled = isImpactedTestsEnabled
234
+ options.worldParameters._ddModifiedFiles = modifiedFiles
235
+ }
236
+
237
+ options.worldParameters._ddIsFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
238
+ options.worldParameters._ddNumTestRetries = numTestRetries
239
+
240
+ if (isTestManagementTestsEnabled) {
241
+ options.worldParameters._ddIsTestManagementTestsEnabled = true
242
+ options.worldParameters._ddTestManagementTests = testManagementTests
243
+ options.worldParameters._ddTestManagementAttemptToFixRetries = testManagementAttemptToFixRetries
244
+ }
245
+ }
246
+
247
+ function readParallelWorkerWorldParameters (options) {
248
+ const worldParameters = options?.worldParameters
249
+ if (!worldParameters) return
250
+
251
+ isKnownTestsEnabled = !!worldParameters._ddIsKnownTestsEnabled
252
+ if (isKnownTestsEnabled) {
253
+ knownTests = worldParameters._ddKnownTests
254
+ // if for whatever reason the worker does not receive valid known tests, we disable EFD and known tests
255
+ if (!isValidKnownTests(knownTests)) {
256
+ isKnownTestsEnabled = false
257
+ knownTests = {}
258
+ }
259
+ }
260
+ isEarlyFlakeDetectionEnabled = !!worldParameters._ddIsEarlyFlakeDetectionEnabled
261
+ if (isEarlyFlakeDetectionEnabled) {
262
+ earlyFlakeDetectionNumRetries = worldParameters._ddEarlyFlakeDetectionNumRetries
263
+ earlyFlakeDetectionSlowTestRetries = worldParameters._ddEarlyFlakeDetectionSlowTestRetries ?? {}
264
+ }
265
+ isImpactedTestsEnabled = !!worldParameters._ddImpactedTestsEnabled
266
+ if (isImpactedTestsEnabled) {
267
+ modifiedFiles = worldParameters._ddModifiedFiles
268
+ }
269
+ isFlakyTestRetriesEnabled = !!worldParameters._ddIsFlakyTestRetriesEnabled
270
+ numTestRetries = worldParameters._ddNumTestRetries ?? 0
271
+ isTestManagementTestsEnabled = !!worldParameters._ddIsTestManagementTestsEnabled
272
+ if (isTestManagementTestsEnabled) {
273
+ testManagementTests = worldParameters._ddTestManagementTests
274
+ testManagementAttemptToFixRetries = worldParameters._ddTestManagementAttemptToFixRetries
275
+ }
276
+ }
277
+
278
+ function handleDdWorkerMessage (message) {
279
+ if (Array.isArray(message)) {
280
+ const [messageCode, payload] = message
281
+ if (messageCode === CUCUMBER_WORKER_TRACE_PAYLOAD_CODE) {
282
+ collectAttemptToFixExecutionsFromTraces(payload, attemptToFixExecutions)
283
+ workerReportTraceCh.publish(payload)
284
+ return true
285
+ }
286
+ }
287
+
288
+ if (message?.[DD_EFD_RETRY_COUNT_MESSAGE]) {
289
+ handleEfdRetryCountMessage(message[DD_EFD_RETRY_COUNT_MESSAGE])
290
+ return true
291
+ }
292
+
293
+ return false
294
+ }
295
+
296
+ function onCucumberWorkerThreadMessage (message) {
297
+ if (!testSuiteFinishCh.hasSubscribers) return
298
+
299
+ handleDdWorkerMessage(message)
300
+ }
301
+
302
+ function registerWorkerThreadMessageHandlers (workers) {
303
+ if (!workers) return
304
+
305
+ for (const worker of workers) {
306
+ worker.workerThread.on('message', onCucumberWorkerThreadMessage)
307
+ }
308
+ }
309
+
310
+ function registerWorkerThreadPatchModule (supportCodeLibrary) {
311
+ const requireModules = supportCodeLibrary?.originalCoordinates?.requireModules
312
+ if (!Array.isArray(requireModules) || requireModules.includes(cucumberWorkerThreadsPatchModule)) return
313
+
314
+ requireModules.unshift(cucumberWorkerThreadsPatchModule)
315
+ }
316
+
317
+ function getRunningAssembledTestCase (adapter, worker) {
318
+ const command = adapter.running?.get(worker)
319
+ return command?.assembledTestCase
320
+ }
321
+
322
+ function maybeStartParallelSuite (pickle) {
323
+ if (!pickle) return
324
+
325
+ const testFileAbsolutePath = pickle.uri
326
+ if (pickleResultByFile[testFileAbsolutePath]) return
327
+
328
+ pickleResultByFile[testFileAbsolutePath] = []
329
+ testSuiteStartCh.publish({
330
+ testFileAbsolutePath,
331
+ })
332
+ }
333
+
334
+ function handleParallelTestCaseFinished (pickle, worstTestStepResult) {
335
+ const { status } = getStatusFromResultLatest(worstTestStepResult)
336
+ let isNew = false
337
+
338
+ if (isKnownTestsEnabled) {
339
+ isNew = isNewTest(pickle.uri, pickle.name)
340
+ }
341
+
342
+ const testFileAbsolutePath = pickle.uri
343
+ const finished = pickleResultByFile[testFileAbsolutePath] || (pickleResultByFile[testFileAbsolutePath] = [])
344
+
345
+ if (isEarlyFlakeDetectionEnabled && isNew) {
346
+ const testFullname = `${pickle.uri}:${pickle.name}`
347
+ let testStatuses = newTestsByTestFullname.get(testFullname)
348
+ if (testStatuses) {
349
+ testStatuses.push(status)
350
+ } else {
351
+ testStatuses = [status]
352
+ newTestsByTestFullname.set(testFullname, testStatuses)
353
+ }
354
+ let efdRetryCount = efdRetryCountByPickleId.get(pickle.id)
355
+ if (efdRetryCount === undefined) {
356
+ efdRetryCount = status === 'skip'
357
+ ? 0
358
+ : getConfiguredEfdRetryCount()
359
+ efdRetryCountByPickleId.set(pickle.id, efdRetryCount)
360
+ if (efdRetryCount === 0 && status !== 'skip') {
361
+ efdSlowAbortedPickleIds.add(pickle.id)
362
+ }
363
+ }
364
+ maybeRecordFinalParallelEfdStatus({ pickleId: pickle.id, testFileAbsolutePath, testFullname })
365
+ } else if (
366
+ isTestManagementTestsEnabled &&
367
+ getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
368
+ ) {
369
+ const testFullname = `${pickle.uri}:${pickle.name}`
370
+ let testStatuses = attemptToFixTestsByTestFullname.get(testFullname)
371
+ if (testStatuses) {
372
+ testStatuses.push(status)
373
+ } else {
374
+ testStatuses = [status]
375
+ attemptToFixTestsByTestFullname.set(testFullname, testStatuses)
376
+ }
377
+
378
+ if (status === 'skip' || testStatuses.length === testManagementAttemptToFixRetries + 1) {
379
+ finished.push(getTestStatusFromAttemptToFixExecutions(testStatuses))
380
+ attemptToFixTestsByTestFullname.delete(testFullname)
381
+ }
382
+ } else {
383
+ // TODO: can we get error message?
384
+ finished.push(status)
385
+ }
386
+
387
+ finishParallelSuiteIfDone(testFileAbsolutePath)
388
+ }
389
+
390
+ function getWrappedHandleWorkerThreadEvent (handleEventFromWorker) {
391
+ return function (worker, event) {
392
+ if (!testSuiteFinishCh.hasSubscribers) {
393
+ return handleEventFromWorker.apply(this, arguments)
394
+ }
395
+
396
+ if (handleDdWorkerMessage(event)) return
397
+
398
+ const envelope = event?.type === 'ENVELOPE' && event.envelope
399
+ if (!envelope) {
400
+ return handleEventFromWorker.apply(this, arguments)
401
+ }
402
+
403
+ const assembledTestCase = getRunningAssembledTestCase(this, worker)
404
+
405
+ if (envelope.testCaseStarted) {
406
+ maybeStartParallelSuite(assembledTestCase?.pickle)
407
+ }
408
+
409
+ const result = handleEventFromWorker.apply(this, arguments)
410
+
411
+ if (envelope.testCaseFinished && assembledTestCase?.pickle && eventDataCollector) {
412
+ const worstTestStepResult =
413
+ eventDataCollector.getTestCaseAttempt(envelope.testCaseFinished.testCaseStartedId).worstTestStepResult
414
+ handleParallelTestCaseFinished(assembledTestCase.pickle, worstTestStepResult)
415
+ }
416
+
417
+ return result
418
+ }
419
+ }
420
+
421
+ function getWrappedWorkerThreadsSetup (setup) {
422
+ return async function () {
423
+ if (testSuiteFinishCh.hasSubscribers) {
424
+ configureParallelWorkerWorldParameters(this.options)
425
+ registerWorkerThreadPatchModule(this.supportCodeLibrary)
426
+ }
427
+
428
+ const result = await setup.apply(this, arguments)
429
+
430
+ if (testSuiteFinishCh.hasSubscribers) {
431
+ registerWorkerThreadMessageHandlers(this.workers)
432
+ }
433
+
434
+ return result
435
+ }
436
+ }
437
+
438
+ function getWrappedWorkerThreadsTeardown (teardown) {
439
+ return function () {
440
+ if (testSuiteFinishCh.hasSubscribers && this.workers) {
441
+ for (const worker of this.workers) {
442
+ worker.workerThread.removeListener('message', onCucumberWorkerThreadMessage)
443
+ }
444
+ }
445
+
446
+ return teardown.apply(this, arguments)
447
+ }
448
+ }
449
+
199
450
  function finishParallelSuiteIfDone (testFileAbsolutePath) {
200
451
  const finished = pickleResultByFile[testFileAbsolutePath]
201
452
  const expectedPickles = pickleByFile[testFileAbsolutePath]
@@ -929,6 +1180,9 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
929
1180
  if (!testSuiteFinishCh.hasSubscribers) {
930
1181
  return runTestCaseFunction.apply(this, arguments)
931
1182
  }
1183
+ if (isWorker) {
1184
+ readParallelWorkerWorldParameters(this.options)
1185
+ }
932
1186
  const pickle = isNewerCucumberVersion
933
1187
  ? arguments[0].pickle
934
1188
  : this.eventDataCollector.getPickle(arguments[0])
@@ -998,6 +1252,11 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
998
1252
  numRetriesByPickleId.set(pickle.id, 0)
999
1253
  }
1000
1254
  }
1255
+ const originalRetry = this.options.retry
1256
+ const isManagedRetry = isAttemptToFix || (isEarlyFlakeDetectionEnabled && (isNew || isModified))
1257
+ if (isManagedRetry) {
1258
+ this.options.retry = 0
1259
+ }
1001
1260
  // TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
1002
1261
  const firstExecutionStart = performance.now()
1003
1262
  let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
@@ -1097,6 +1356,8 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1097
1356
  testSuiteFinishCh.publish({ status: testSuiteStatus, testSuitePath })
1098
1357
  }
1099
1358
 
1359
+ this.options.retry = originalRetry
1360
+
1100
1361
  if (isNewerCucumberVersion && isEarlyFlakeDetectionEnabled && (isNew || isModified)) {
1101
1362
  return shouldBePassedByEFD
1102
1363
  }
@@ -1113,26 +1374,25 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1113
1374
  }
1114
1375
  }
1115
1376
 
1377
+ function patchCucumberWorkerRunTestCase (workerPackage, isWorker) {
1378
+ const workerPrototype = workerPackage?.Worker?.prototype
1379
+ if (!workerPrototype || patchedCucumberWorkers.has(workerPrototype)) return
1380
+
1381
+ patchedCucumberWorkers.add(workerPrototype)
1382
+ shimmer.wrap(
1383
+ workerPrototype,
1384
+ 'runTestCase',
1385
+ runTestCase => getWrappedRunTestCase(runTestCase, true, isWorker)
1386
+ )
1387
+ }
1388
+
1116
1389
  function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion) {
1117
1390
  return function (worker, message) {
1118
1391
  if (!testSuiteFinishCh.hasSubscribers) {
1119
1392
  return parseWorkerMessageFunction.apply(this, arguments)
1120
1393
  }
1121
- // If the message is an array, it's a dd-trace message, so we need to stop cucumber processing,
1122
- // or cucumber will throw an error
1123
- // TODO: identify the message better
1124
- if (Array.isArray(message)) {
1125
- const [messageCode, payload] = message
1126
- if (messageCode === CUCUMBER_WORKER_TRACE_PAYLOAD_CODE) {
1127
- collectAttemptToFixExecutionsFromTraces(payload, attemptToFixExecutions)
1128
- workerReportTraceCh.publish(payload)
1129
- return
1130
- }
1131
- }
1132
- if (message[DD_EFD_RETRY_COUNT_MESSAGE]) {
1133
- handleEfdRetryCountMessage(message[DD_EFD_RETRY_COUNT_MESSAGE])
1134
- return
1135
- }
1394
+ // If it's a dd-trace message, stop cucumber processing or cucumber will throw an error.
1395
+ if (handleDdWorkerMessage(message)) return
1136
1396
 
1137
1397
  const envelope = isNewVersion ? message.envelope : message.jsonEnvelope
1138
1398
 
@@ -1149,10 +1409,7 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
1149
1409
  return parseWorkerMessageFunction.apply(this, arguments)
1150
1410
  }
1151
1411
  }
1152
- if (parsed[DD_EFD_RETRY_COUNT_MESSAGE]) {
1153
- handleEfdRetryCountMessage(parsed[DD_EFD_RETRY_COUNT_MESSAGE])
1154
- return
1155
- }
1412
+ if (handleDdWorkerMessage(parsed)) return
1156
1413
  let pickle
1157
1414
 
1158
1415
  if (parsed.testCaseStarted) {
@@ -1162,15 +1419,7 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
1162
1419
  const { pickleId } = this.eventDataCollector.testCaseMap[parsed.testCaseStarted.testCaseId]
1163
1420
  pickle = this.eventDataCollector.getPickle(pickleId)
1164
1421
  }
1165
- // THIS FAILS IN PARALLEL MODE
1166
- const testFileAbsolutePath = pickle.uri
1167
- // First test in suite
1168
- if (!pickleResultByFile[testFileAbsolutePath]) {
1169
- pickleResultByFile[testFileAbsolutePath] = []
1170
- testSuiteStartCh.publish({
1171
- testFileAbsolutePath,
1172
- })
1173
- }
1422
+ maybeStartParallelSuite(pickle)
1174
1423
  }
1175
1424
 
1176
1425
  const parseWorkerResponse = parseWorkerMessageFunction.apply(this, arguments)
@@ -1188,66 +1437,15 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
1188
1437
  pickle = testCase.pickle
1189
1438
  }
1190
1439
 
1191
- const { status } = getStatusFromResultLatest(worstTestStepResult)
1192
- let isNew = false
1193
-
1194
- if (isKnownTestsEnabled) {
1195
- isNew = isNewTest(pickle.uri, pickle.name)
1196
- }
1197
-
1198
- const testFileAbsolutePath = pickle.uri
1199
- const finished = pickleResultByFile[testFileAbsolutePath]
1200
-
1201
- if (isEarlyFlakeDetectionEnabled && isNew) {
1202
- const testFullname = `${pickle.uri}:${pickle.name}`
1203
- let testStatuses = newTestsByTestFullname.get(testFullname)
1204
- if (testStatuses) {
1205
- testStatuses.push(status)
1206
- } else {
1207
- testStatuses = [status]
1208
- newTestsByTestFullname.set(testFullname, testStatuses)
1209
- }
1210
- let efdRetryCount = efdRetryCountByPickleId.get(pickle.id)
1211
- if (efdRetryCount === undefined) {
1212
- efdRetryCount = status === 'skip'
1213
- ? 0
1214
- : getConfiguredEfdRetryCount()
1215
- efdRetryCountByPickleId.set(pickle.id, efdRetryCount)
1216
- if (efdRetryCount === 0 && status !== 'skip') {
1217
- efdSlowAbortedPickleIds.add(pickle.id)
1218
- }
1219
- }
1220
- maybeRecordFinalParallelEfdStatus({ pickleId: pickle.id, testFileAbsolutePath, testFullname })
1221
- } else if (
1222
- isTestManagementTestsEnabled &&
1223
- getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
1224
- ) {
1225
- const testFullname = `${pickle.uri}:${pickle.name}`
1226
- let testStatuses = attemptToFixTestsByTestFullname.get(testFullname)
1227
- if (testStatuses) {
1228
- testStatuses.push(status)
1229
- } else {
1230
- testStatuses = [status]
1231
- attemptToFixTestsByTestFullname.set(testFullname, testStatuses)
1232
- }
1233
-
1234
- if (status === 'skip' || testStatuses.length === testManagementAttemptToFixRetries + 1) {
1235
- finished.push(getTestStatusFromAttemptToFixExecutions(testStatuses))
1236
- attemptToFixTestsByTestFullname.delete(testFullname)
1237
- }
1238
- } else {
1239
- // TODO: can we get error message?
1240
- const finished = pickleResultByFile[testFileAbsolutePath]
1241
- finished.push(status)
1242
- }
1243
-
1244
- finishParallelSuiteIfDone(testFileAbsolutePath)
1440
+ handleParallelTestCaseFinished(pickle, worstTestStepResult)
1245
1441
  }
1246
1442
 
1247
1443
  return parseWorkerResponse
1248
1444
  }
1249
1445
  }
1250
1446
 
1447
+ module.exports.patchCucumberWorkerRunTestCase = patchCucumberWorkerRunTestCase
1448
+
1251
1449
  // Test start / finish for older versions. The only hook executed in workers when in parallel mode
1252
1450
  addHook({
1253
1451
  name: '@cucumber/cucumber',
@@ -1319,11 +1517,7 @@ addHook({
1319
1517
  versions: ['>=11.0.0'],
1320
1518
  file: 'lib/runtime/worker.js',
1321
1519
  }, (workerPackage) => {
1322
- shimmer.wrap(
1323
- workerPackage.Worker.prototype,
1324
- 'runTestCase',
1325
- runTestCase => getWrappedRunTestCase(runTestCase, true, !!getEnvironmentVariable('CUCUMBER_WORKER_ID'))
1326
- )
1520
+ patchCucumberWorkerRunTestCase(workerPackage, !!getEnvironmentVariable('CUCUMBER_WORKER_ID'))
1327
1521
  return workerPackage
1328
1522
  })
1329
1523
 
@@ -1359,7 +1553,7 @@ addHook({
1359
1553
  // In `startWorker` we pass early flake detection info to the worker.
1360
1554
  addHook({
1361
1555
  name: '@cucumber/cucumber',
1362
- versions: ['>=11.0.0'],
1556
+ versions: ['>=11.0.0 <13.0.0'],
1363
1557
  file: 'lib/runtime/parallel/adapter.js',
1364
1558
  }, (adapterPackage) => {
1365
1559
  shimmer.wrap(
@@ -1369,46 +1563,43 @@ addHook({
1369
1563
  )
1370
1564
  // EFD in parallel mode only supported in >=11.0.0
1371
1565
  shimmer.wrap(adapterPackage.ChildProcessAdapter.prototype, 'startWorker', startWorker => function (...args) {
1372
- if (isKnownTestsEnabled && isValidKnownTests(knownTests)) {
1373
- this.options.worldParameters._ddIsKnownTestsEnabled = true
1374
- this.options.worldParameters._ddIsEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
1375
- this.options.worldParameters._ddKnownTests = knownTests
1376
- this.options.worldParameters._ddEarlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
1377
- this.options.worldParameters._ddEarlyFlakeDetectionSlowTestRetries = earlyFlakeDetectionSlowTestRetries
1378
- } else {
1379
- isEarlyFlakeDetectionEnabled = false
1380
- isKnownTestsEnabled = false
1381
- this.options.worldParameters._ddIsEarlyFlakeDetectionEnabled = false
1382
- this.options.worldParameters._ddIsKnownTestsEnabled = false
1383
- this.options.worldParameters._ddEarlyFlakeDetectionNumRetries = 0
1384
- this.options.worldParameters._ddEarlyFlakeDetectionSlowTestRetries = {}
1385
- }
1386
-
1387
- if (isImpactedTestsEnabled) {
1388
- this.options.worldParameters._ddImpactedTestsEnabled = isImpactedTestsEnabled
1389
- this.options.worldParameters._ddModifiedFiles = modifiedFiles
1390
- }
1391
-
1392
- this.options.worldParameters._ddIsFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
1393
- this.options.worldParameters._ddNumTestRetries = numTestRetries
1394
-
1395
- if (isTestManagementTestsEnabled) {
1396
- this.options.worldParameters._ddIsTestManagementTestsEnabled = true
1397
- this.options.worldParameters._ddTestManagementTests = testManagementTests
1398
- this.options.worldParameters._ddTestManagementAttemptToFixRetries = testManagementAttemptToFixRetries
1399
- }
1400
-
1566
+ configureParallelWorkerWorldParameters(this.options)
1401
1567
  return startWorker.apply(this, args)
1402
1568
  })
1403
1569
  return adapterPackage
1404
1570
  })
1405
1571
 
1572
+ // Only executed in parallel mode for >=13, in the main process.
1573
+ // Cucumber v13 uses worker_threads and receives worker events via MessagePort.
1574
+ addHook({
1575
+ name: '@cucumber/cucumber',
1576
+ versions: ['>=13.0.0'],
1577
+ file: 'lib/runtime/parallel/adapter.js',
1578
+ }, (adapterPackage) => {
1579
+ shimmer.wrap(
1580
+ adapterPackage.WorkerThreadsAdapter.prototype,
1581
+ 'setup',
1582
+ setup => getWrappedWorkerThreadsSetup(setup)
1583
+ )
1584
+ shimmer.wrap(
1585
+ adapterPackage.WorkerThreadsAdapter.prototype,
1586
+ 'handleEventFromWorker',
1587
+ handleEventFromWorker => getWrappedHandleWorkerThreadEvent(handleEventFromWorker)
1588
+ )
1589
+ shimmer.wrap(
1590
+ adapterPackage.WorkerThreadsAdapter.prototype,
1591
+ 'teardown',
1592
+ teardown => getWrappedWorkerThreadsTeardown(teardown)
1593
+ )
1594
+ return adapterPackage
1595
+ })
1596
+
1406
1597
  // Hook executed in the worker process when in parallel mode.
1407
1598
  // In this hook we read the information passed in `worldParameters` and make it available for
1408
1599
  // `getWrappedRunTestCase`.
1409
1600
  addHook({
1410
1601
  name: '@cucumber/cucumber',
1411
- versions: ['>=11.0.0'],
1602
+ versions: ['>=11.0.0 <13.0.0'],
1412
1603
  file: 'lib/runtime/parallel/worker.js',
1413
1604
  }, (workerPackage) => {
1414
1605
  shimmer.wrap(
@@ -1416,31 +1607,7 @@ addHook({
1416
1607
  'initialize',
1417
1608
  initialize => async function () {
1418
1609
  await initialize.apply(this, arguments)
1419
- isKnownTestsEnabled = !!this.options.worldParameters._ddIsKnownTestsEnabled
1420
- if (isKnownTestsEnabled) {
1421
- knownTests = this.options.worldParameters._ddKnownTests
1422
- // if for whatever reason the worker does not receive valid known tests, we disable EFD and known tests
1423
- if (!isValidKnownTests(knownTests)) {
1424
- isKnownTestsEnabled = false
1425
- knownTests = {}
1426
- }
1427
- }
1428
- isEarlyFlakeDetectionEnabled = !!this.options.worldParameters._ddIsEarlyFlakeDetectionEnabled
1429
- if (isEarlyFlakeDetectionEnabled) {
1430
- earlyFlakeDetectionNumRetries = this.options.worldParameters._ddEarlyFlakeDetectionNumRetries
1431
- earlyFlakeDetectionSlowTestRetries = this.options.worldParameters._ddEarlyFlakeDetectionSlowTestRetries ?? {}
1432
- }
1433
- isImpactedTestsEnabled = !!this.options.worldParameters._ddImpactedTestsEnabled
1434
- if (isImpactedTestsEnabled) {
1435
- modifiedFiles = this.options.worldParameters._ddModifiedFiles
1436
- }
1437
- isFlakyTestRetriesEnabled = !!this.options.worldParameters._ddIsFlakyTestRetriesEnabled
1438
- numTestRetries = this.options.worldParameters._ddNumTestRetries ?? 0
1439
- isTestManagementTestsEnabled = !!this.options.worldParameters._ddIsTestManagementTestsEnabled
1440
- if (isTestManagementTestsEnabled) {
1441
- testManagementTests = this.options.worldParameters._ddTestManagementTests
1442
- testManagementAttemptToFixRetries = this.options.worldParameters._ddTestManagementAttemptToFixRetries
1443
- }
1610
+ readParallelWorkerWorldParameters(this.options)
1444
1611
  }
1445
1612
  )
1446
1613
  return workerPackage
@@ -25,21 +25,23 @@ addHook({ name: 'express-mongo-sanitize', versions: ['>=1.0.0'] }, expressMongoS
25
25
  return shimmer.wrapFunction(expressMongoSanitize, expressMongoSanitize => function (...args) {
26
26
  const middleware = expressMongoSanitize.apply(this, args)
27
27
 
28
- return shimmer.wrapFunction(middleware, middleware => function (req, res, next) {
28
+ return shimmer.wrapFunction(middleware, middleware => function (...args) {
29
29
  if (!sanitizeMiddlewareFinished.hasSubscribers) {
30
- return middleware.apply(this, arguments)
30
+ return Reflect.apply(middleware, this, args)
31
31
  }
32
32
 
33
- const wrappedNext = shimmer.wrapFunction(next, next => function (...args) {
33
+ const req = args[0]
34
+ // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
35
+ const wrappedNext = shimmer.wrapCallback(args[2], original => function next (_error) {
34
36
  sanitizeMiddlewareFinished.publish({
35
37
  sanitizedProperties: propertiesToSanitize,
36
38
  req,
37
39
  })
38
40
 
39
- return next.apply(this, args)
41
+ return original.apply(this, arguments)
40
42
  })
41
43
 
42
- return middleware.call(this, req, res, wrappedNext)
44
+ return middleware.call(this, req, args[1], wrappedNext)
43
45
  })
44
46
  })
45
47
  })
@@ -6,22 +6,23 @@ const { channel, addHook } = require('./helpers/instrument')
6
6
  const sessionMiddlewareFinishCh = channel('datadog:express-session:middleware:finish')
7
7
 
8
8
  function wrapSessionMiddleware (sessionMiddleware) {
9
- return function wrappedSessionMiddleware (req, res, next) {
10
- shimmer.wrap(arguments, 2, function wrapNext (next) {
11
- return function wrappedNext (...args) {
12
- if (sessionMiddlewareFinishCh.hasSubscribers) {
13
- const abortController = new AbortController()
9
+ return function wrappedSessionMiddleware (...args) {
10
+ const req = args[0]
11
+ const res = args[1]
12
+ // Mirror next's name/arity so wrapCallback skips its per-call identity rewrite.
13
+ args[2] = shimmer.wrapCallback(args[2], original => function next (_error) {
14
+ if (sessionMiddlewareFinishCh.hasSubscribers) {
15
+ const abortController = new AbortController()
14
16
 
15
- sessionMiddlewareFinishCh.publish({ req, res, sessionId: req.sessionID, abortController })
17
+ sessionMiddlewareFinishCh.publish({ req, res, sessionId: req.sessionID, abortController })
16
18
 
17
- if (abortController.signal.aborted) return
18
- }
19
-
20
- return next.apply(this, args)
19
+ if (abortController.signal.aborted) return
21
20
  }
21
+
22
+ return original.apply(this, arguments)
22
23
  })
23
24
 
24
- return sessionMiddleware.apply(this, arguments)
25
+ return Reflect.apply(sessionMiddleware, this, args)
25
26
  }
26
27
  }
27
28