dd-trace 5.99.1 → 5.101.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 (101) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/index.d.ts +14 -0
  3. package/package.json +8 -8
  4. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  5. package/packages/datadog-instrumentations/src/cypress.js +5 -3
  6. package/packages/datadog-instrumentations/src/express.js +3 -2
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/hono.js +15 -4
  9. package/packages/datadog-instrumentations/src/http/client.js +20 -3
  10. package/packages/datadog-instrumentations/src/jest.js +146 -90
  11. package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
  12. package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
  15. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
  16. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  17. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  18. package/packages/datadog-instrumentations/src/router.js +53 -33
  19. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  22. package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
  23. package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
  24. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
  25. package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
  26. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  27. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  28. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  29. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
  30. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
  31. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  32. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  33. package/packages/datadog-plugin-router/src/index.js +13 -0
  34. package/packages/dd-trace/index.js +4 -3
  35. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  36. package/packages/dd-trace/src/appsec/reporter.js +4 -1
  37. package/packages/dd-trace/src/baggage.js +10 -0
  38. package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
  39. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
  40. package/packages/dd-trace/src/config/config-types.d.ts +0 -2
  41. package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
  42. package/packages/dd-trace/src/config/index.js +7 -60
  43. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +15 -32
  45. package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
  46. package/packages/dd-trace/src/datastreams/encoding.js +39 -28
  47. package/packages/dd-trace/src/datastreams/pathway.js +29 -26
  48. package/packages/dd-trace/src/datastreams/processor.js +17 -15
  49. package/packages/dd-trace/src/datastreams/size.js +6 -2
  50. package/packages/dd-trace/src/debugger/config.js +6 -3
  51. package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
  52. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  53. package/packages/dd-trace/src/dogstatsd.js +10 -7
  54. package/packages/dd-trace/src/encode/0.4.js +3 -3
  55. package/packages/dd-trace/src/encode/0.5.js +2 -2
  56. package/packages/dd-trace/src/encode/agentless-json.js +2 -2
  57. package/packages/dd-trace/src/encode/tags-processors.js +2 -27
  58. package/packages/dd-trace/src/exporters/common/request.js +22 -11
  59. package/packages/dd-trace/src/exporters/common/retry.js +104 -0
  60. package/packages/dd-trace/src/git_metadata.js +66 -0
  61. package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
  62. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  63. package/packages/dd-trace/src/id.js +15 -26
  64. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  65. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
  66. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
  67. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
  68. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
  69. package/packages/dd-trace/src/llmobs/sdk.js +5 -1
  70. package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
  71. package/packages/dd-trace/src/llmobs/tagger.js +42 -0
  72. package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
  73. package/packages/dd-trace/src/llmobs/util.js +80 -5
  74. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  75. package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
  76. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
  77. package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
  78. package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
  79. package/packages/dd-trace/src/opentelemetry/span.js +42 -108
  80. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  81. package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
  82. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
  83. package/packages/dd-trace/src/opentracing/span.js +58 -49
  84. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  85. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  86. package/packages/dd-trace/src/plugins/util/test.js +293 -27
  87. package/packages/dd-trace/src/priority_sampler.js +6 -4
  88. package/packages/dd-trace/src/profiling/config.js +5 -4
  89. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  90. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  91. package/packages/dd-trace/src/proxy.js +3 -3
  92. package/packages/dd-trace/src/remote_config/index.js +5 -3
  93. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  94. package/packages/dd-trace/src/span_format.js +52 -5
  95. package/packages/dd-trace/src/span_processor.js +1 -5
  96. package/packages/dd-trace/src/spanleak.js +0 -1
  97. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  98. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  99. package/packages/dd-trace/src/util.js +17 -0
  100. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  101. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -3,7 +3,12 @@
3
3
  // Capture real timers at module load time, before any test can install fake timers.
4
4
  const realSetTimeout = setTimeout
5
5
 
6
- const { getTestSuitePath, DYNAMIC_NAME_RE } = require('../../../dd-trace/src/plugins/util/test')
6
+ const {
7
+ getTestSuitePath,
8
+ DYNAMIC_NAME_RE,
9
+ recordAttemptToFixExecution,
10
+ logAttemptToFixTestExecution,
11
+ } = require('../../../dd-trace/src/plugins/util/test')
7
12
  const { channel } = require('../helpers/instrument')
8
13
  const shimmer = require('../../../datadog-shimmer')
9
14
 
@@ -30,6 +35,8 @@ const newTestsWithDynamicNames = new Set()
30
35
  const testsAttemptToFix = new Set()
31
36
  const testsQuarantined = new Set()
32
37
  const testsStatuses = new Map()
38
+ const attemptToFixExecutions = new Map()
39
+ const loggedAttemptToFixTests = new Set()
33
40
 
34
41
  function getAfterEachHooks (testOrHook) {
35
42
  const hooks = []
@@ -229,6 +236,13 @@ function getOnTestHandler (isMain) {
229
236
  if (testInfo.hasDynamicName) {
230
237
  newTestsWithDynamicNames.add(`${getTestSuitePath(test.file, process.cwd())} › ${test.fullTitle()}`)
231
238
  }
239
+ if (isAttemptToFix) {
240
+ logAttemptToFixTestExecution(
241
+ getTestSuitePath(test.file, process.cwd()),
242
+ test.fullTitle(),
243
+ loggedAttemptToFixTests
244
+ )
245
+ }
232
246
  // We want to store the result of the new tests
233
247
  if (isNew) {
234
248
  const testFullName = getTestFullName(test)
@@ -272,9 +286,8 @@ function getFinalStatus ({
272
286
  return
273
287
  }
274
288
 
275
- // If the test is quarantined or disabled, regardless of its actual execution result or active retry features,
276
- // the final status of its last execution should be reported as 'skip'.
277
- if (isQuarantined || isDisabled) {
289
+ // If the test is quarantined or disabled, its final status is skip unless attempt-to-fix takes precedence.
290
+ if (!isAttemptToFix && (isQuarantined || isDisabled)) {
278
291
  return 'skip'
279
292
  }
280
293
 
@@ -295,116 +308,126 @@ function getFinalStatus ({
295
308
  }
296
309
  }
297
310
 
298
- function getOnTestEndHandler (config) {
299
- return async function (test) {
300
- const ctx = getTestContext(test)
301
- const status = getTestStatus(test)
311
+ function getTestFinishInfo (test, status, config, error) {
312
+ let hasFailedAllRetries = false
313
+ let attemptToFixPassed = false
314
+ let attemptToFixFailed = false
302
315
 
303
- // After finishing it might take a bit for the snapshot to be handled.
304
- // This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
305
- if (test._ddShouldWaitForHitProbe || test._retriedTest?._ddShouldWaitForHitProbe) {
306
- await new Promise((resolve) => {
307
- realSetTimeout(() => {
308
- resolve()
309
- }, BREAKPOINT_HIT_GRACE_PERIOD_MS)
310
- })
311
- }
312
-
313
- let hasFailedAllRetries = false
314
- let attemptToFixPassed = false
315
- let attemptToFixFailed = false
316
-
317
- const testName = getTestFullName(test)
316
+ const testName = getTestFullName(test)
318
317
 
319
- if (testsStatuses.get(testName)) {
320
- testsStatuses.get(testName).push(status)
321
- } else {
322
- testsStatuses.set(testName, [status])
323
- }
324
- const testStatuses = testsStatuses.get(testName)
318
+ if (testsStatuses.get(testName)) {
319
+ testsStatuses.get(testName).push(status)
320
+ } else {
321
+ testsStatuses.set(testName, [status])
322
+ }
323
+ const testStatuses = testsStatuses.get(testName)
325
324
 
326
- const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
327
- const isLastEfdRetry = testStatuses.length === config.earlyFlakeDetectionNumRetries + 1
328
- const isLastAtrAttempt = getIsLastRetry(test) || (config.isFlakyTestRetriesEnabled && status === 'pass')
325
+ const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
326
+ const isLastEfdRetry = testStatuses.length === config.earlyFlakeDetectionNumRetries + 1
327
+ const isLastAtrAttempt = getIsLastRetry(test) || (config.isFlakyTestRetriesEnabled && status === 'pass')
329
328
 
330
- // Needed for the getFinalStatus call. This is because EFD does NOT tag as
331
- // EFD retry the first run of the test. It only tags as retries the clones
332
- const isEfdRetry = test._ddIsEfdRetry || (test._ddIsNew && config.isEarlyFlakeDetectionEnabled)
329
+ // Needed for the getFinalStatus call. This is because EFD does NOT tag as
330
+ // EFD retry the first run of the test. It only tags as retries the clones
331
+ const isEfdRetry = test._ddIsEfdRetry || (test._ddIsNew && config.isEarlyFlakeDetectionEnabled)
333
332
 
334
- if (test._ddIsAttemptToFix && isLastAttempt) {
335
- if (testStatuses.includes('fail')) {
336
- attemptToFixFailed = true
337
- }
338
- if (testStatuses.every(status => status === 'fail')) {
339
- hasFailedAllRetries = true
340
- } else if (testStatuses.every(status => status === 'pass')) {
341
- attemptToFixPassed = true
342
- }
333
+ if (test._ddIsAttemptToFix && isLastAttempt) {
334
+ if (testStatuses.includes('fail')) {
335
+ attemptToFixFailed = true
343
336
  }
344
-
345
- if (test._ddIsEfdRetry && isLastEfdRetry &&
346
- testStatuses.every(status => status === 'fail')) {
337
+ if (testStatuses.every(status => status === 'fail')) {
347
338
  hasFailedAllRetries = true
339
+ } else if (testStatuses.every(status => status === 'pass')) {
340
+ attemptToFixPassed = true
348
341
  }
342
+ }
349
343
 
350
- // ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
351
- if (config.isFlakyTestRetriesEnabled && !test._ddIsAttemptToFix && !test._ddIsEfdRetry &&
352
- getIsLastRetry(test) && testStatuses.every(status => status === 'fail')) {
353
- hasFailedAllRetries = true
354
- }
344
+ if (test._ddIsEfdRetry && isLastEfdRetry &&
345
+ testStatuses.every(status => status === 'fail')) {
346
+ hasFailedAllRetries = true
347
+ }
355
348
 
356
- const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
357
- const isAtrRetry = config.isFlakyTestRetriesEnabled &&
358
- !test._ddIsAttemptToFix &&
359
- !test._ddIsEfdRetry
349
+ // ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
350
+ if (config.isFlakyTestRetriesEnabled && !test._ddIsAttemptToFix && !test._ddIsEfdRetry &&
351
+ getIsLastRetry(test) && testStatuses.every(status => status === 'fail')) {
352
+ hasFailedAllRetries = true
353
+ }
360
354
 
361
- const { isFlakyTestRetriesEnabled } = config
362
- const { _ddIsAttemptToFix, _ddIsQuarantined, _ddIsDisabled } = test
355
+ const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
356
+ const isAtrRetry = config.isFlakyTestRetriesEnabled &&
357
+ !test._ddIsAttemptToFix &&
358
+ !test._ddIsEfdRetry
359
+
360
+ const { isFlakyTestRetriesEnabled } = config
361
+ const { _ddIsAttemptToFix, _ddIsQuarantined, _ddIsDisabled } = test
362
+
363
+ const finalStatus = getFinalStatus({
364
+ status,
365
+ hasFailedAllRetries,
366
+ isFlakyTestRetriesEnabled,
367
+ isLastAtrAttempt,
368
+ isEfdRetry,
369
+ isLastEfdRetry,
370
+ isAttemptToFix: _ddIsAttemptToFix,
371
+ isLastAttemptToFix: isLastAttempt,
372
+ attemptToFixPassed,
373
+ isQuarantined: _ddIsQuarantined,
374
+ isDisabled: _ddIsDisabled,
375
+ })
363
376
 
364
- const finalStatus = getFinalStatus({
377
+ if (_ddIsAttemptToFix) {
378
+ recordAttemptToFixExecution(attemptToFixExecutions, {
379
+ testSuite: getTestSuitePath(test.file, process.cwd()),
380
+ testName: test.fullTitle(),
365
381
  status,
366
- hasFailedAllRetries,
367
- isFlakyTestRetriesEnabled,
368
- isLastAtrAttempt,
369
- isEfdRetry,
370
- isLastEfdRetry,
371
- isAttemptToFix: _ddIsAttemptToFix,
372
- isLastAttemptToFix: isLastAttempt,
373
- attemptToFixPassed,
374
- isQuarantined: _ddIsQuarantined,
375
382
  isDisabled: _ddIsDisabled,
383
+ isQuarantined: _ddIsQuarantined,
376
384
  })
385
+ }
386
+
387
+ return {
388
+ hasFailedAllRetries,
389
+ attemptToFixPassed,
390
+ attemptToFixFailed,
391
+ isAttemptToFixRetry,
392
+ isAtrRetry,
393
+ finalStatus,
394
+ }
395
+ }
396
+
397
+ function getOnTestEndHandler (config) {
398
+ return async function (test) {
399
+ const ctx = getTestContext(test)
400
+ const status = getTestStatus(test)
401
+
402
+ // After finishing it might take a bit for the snapshot to be handled.
403
+ // This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
404
+ if (test._ddShouldWaitForHitProbe || test._retriedTest?._ddShouldWaitForHitProbe) {
405
+ await new Promise((resolve) => {
406
+ realSetTimeout(() => {
407
+ resolve()
408
+ }, BREAKPOINT_HIT_GRACE_PERIOD_MS)
409
+ })
410
+ }
377
411
 
378
412
  // If there are afterEach to be run, we don't finish the test yet.
379
413
  // Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
380
414
  // In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
381
415
  // getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
382
416
  // directly sets finalStatus without waiting for hooks
383
- if (ctx && (!getAfterEachHooks(test).length || test._ddIsDisabled)) {
417
+ if (ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))) {
418
+ const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
384
419
  testFinishCh.publish({
385
420
  status,
386
421
  hasBeenRetried: isMochaRetry(test),
387
422
  isLastRetry: getIsLastRetry(test),
388
- hasFailedAllRetries,
389
- attemptToFixPassed,
390
- attemptToFixFailed,
391
- isAttemptToFixRetry,
392
- isAtrRetry,
423
+ ...testFinishInfo,
393
424
  ...ctx.currentStore,
394
- finalStatus,
395
425
  })
396
- } else if (ctx) { // if there is an afterEach to run, let's store the finalStatus for getOnHookEndHandler
397
- ctx.finalStatus = finalStatus
398
- ctx.hasFailedAllRetries = hasFailedAllRetries
399
- ctx.attemptToFixPassed = attemptToFixPassed
400
- ctx.attemptToFixFailed = attemptToFixFailed
401
- ctx.isAttemptToFixRetry = isAttemptToFixRetry
402
- ctx.isAtrRetry = isAtrRetry
403
426
  }
404
427
  }
405
428
  }
406
429
 
407
- function getOnHookEndHandler () {
430
+ function getOnHookEndHandler (config) {
408
431
  return function (hook) {
409
432
  const test = hook.ctx.currentTest
410
433
  const afterEachHooks = getAfterEachHooks(hook)
@@ -415,18 +438,14 @@ function getOnHookEndHandler () {
415
438
  const ctx = getTestContext(test)
416
439
  // Disabled tests are already finished in getOnTestEndHandler,
417
440
  // skip to avoid double-publishing
418
- if (ctx && !test._ddIsDisabled) {
441
+ if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
442
+ const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
419
443
  testFinishCh.publish({
420
444
  status,
421
445
  hasBeenRetried: isMochaRetry(test),
422
446
  isLastRetry: getIsLastRetry(test),
423
- hasFailedAllRetries: ctx.hasFailedAllRetries,
424
- attemptToFixPassed: ctx.attemptToFixPassed,
425
- attemptToFixFailed: ctx.attemptToFixFailed,
426
- isAttemptToFixRetry: ctx.isAttemptToFixRetry,
427
- isAtrRetry: ctx.isAtrRetry,
447
+ ...testFinishInfo,
428
448
  ...ctx.currentStore,
429
- finalStatus: ctx.finalStatus,
430
449
  })
431
450
  }
432
451
  }
@@ -434,7 +453,7 @@ function getOnHookEndHandler () {
434
453
  }
435
454
  }
436
455
 
437
- function getOnFailHandler (isMain) {
456
+ function getOnFailHandler (isMain, config) {
438
457
  return function (testOrHook, err) {
439
458
  const testFile = testOrHook.file
440
459
  let test = testOrHook
@@ -451,15 +470,12 @@ function getOnFailHandler (isMain) {
451
470
  err.message = `${testOrHook.fullTitle()}: ${err.message}`
452
471
  testContext.err = err
453
472
  errorCh.runStores(testContext, () => {})
454
- // if it's a hook and it has failed, 'test end' will not be called
455
- // quarantined and disabled tests always report 'skip'
456
- // as final status, even when hooks fail
457
- const isSkippedByManagement = test._ddIsQuarantined || test._ddIsDisabled
473
+ const testFinishInfo = getTestFinishInfo(test, 'fail', config, err)
458
474
  testFinishCh.publish({
459
475
  status: 'fail',
460
476
  hasBeenRetried: isMochaRetry(test),
477
+ ...testFinishInfo,
461
478
  ...testContext.currentStore,
462
- finalStatus: isSkippedByManagement ? 'skip' : 'fail',
463
479
  })
464
480
  } else {
465
481
  testContext.err = err
@@ -627,4 +643,6 @@ module.exports = {
627
643
  testsQuarantined,
628
644
  testsAttemptToFix,
629
645
  testsStatuses,
646
+ attemptToFixExecutions,
647
+ loggedAttemptToFixTests,
630
648
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { addHook, channel } = require('../helpers/instrument')
4
4
  const shimmer = require('../../../datadog-shimmer')
5
+ const { DD_MAJOR } = require('../../../../version')
5
6
 
6
7
  const {
7
8
  runnableWrapper,
@@ -15,6 +16,8 @@ const {
15
16
  } = require('./utils')
16
17
  require('./common')
17
18
 
19
+ const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
20
+
18
21
  const workerFinishCh = channel('ci:mocha:worker:finish')
19
22
 
20
23
  const config = {}
@@ -64,7 +67,7 @@ addHook({
64
67
  // Runner is also hooked in mocha/main.js, but in here we only generate test events.
65
68
  addHook({
66
69
  name: 'mocha',
67
- versions: ['>=5.2.0'],
70
+ versions: [MINIMUM_MOCHA_VERSION],
68
71
  file: 'lib/runner.js',
69
72
  }, function (Runner) {
70
73
  shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
@@ -84,9 +87,9 @@ addHook({
84
87
  this.on('retry', getOnTestRetryHandler(config))
85
88
 
86
89
  // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
87
- this.on('hook end', getOnHookEndHandler())
90
+ this.on('hook end', getOnHookEndHandler(config))
88
91
 
89
- this.on('fail', getOnFailHandler(false))
92
+ this.on('fail', getOnFailHandler(false, config))
90
93
 
91
94
  this.on('pending', getOnPendingHandler())
92
95
 
@@ -99,6 +102,6 @@ addHook({
99
102
  // Used to set the correct async resource to the test.
100
103
  addHook({
101
104
  name: 'mocha',
102
- versions: ['>=5.2.0'],
105
+ versions: [MINIMUM_MOCHA_VERSION],
103
106
  file: 'lib/runnable.js',
104
107
  }, (runnablePackage) => runnableWrapper(runnablePackage, config))
@@ -3,14 +3,10 @@
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const tracer = require('../../dd-trace')
5
5
  const { getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
6
+ const { isFalse, isTrue } = require('../../dd-trace/src/util')
6
7
  const { addHook } = require('./helpers/instrument')
7
8
 
8
- const otelSdkEnabled = getValueFromEnvSources('DD_TRACE_OTEL_ENABLED') ||
9
- getValueFromEnvSources('OTEL_SDK_DISABLED')
10
- ? !getValueFromEnvSources('OTEL_SDK_DISABLED')
11
- : undefined
12
-
13
- if (otelSdkEnabled) {
9
+ if (isOtelSdkEnabled()) {
14
10
  addHook({
15
11
  name: '@opentelemetry/sdk-trace-node',
16
12
  file: 'build/src/NodeTracerProvider.js',
@@ -22,3 +18,12 @@ if (otelSdkEnabled) {
22
18
  return mod
23
19
  })
24
20
  }
21
+
22
+ function isOtelSdkEnabled () {
23
+ // Datadog explicit opt-out wins over every OTel signal; check it first.
24
+ const ddTraceOtelEnabled = getValueFromEnvSources('DD_TRACE_OTEL_ENABLED')
25
+ if (isFalse(ddTraceOtelEnabled)) return false
26
+ const otelSdkDisabled = getValueFromEnvSources('OTEL_SDK_DISABLED')
27
+ if (isTrue(otelSdkDisabled)) return false
28
+ return isTrue(ddTraceOtelEnabled) || isFalse(otelSdkDisabled)
29
+ }
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const { addHook } = require('./helpers/instrument')
4
+
5
+ /** @type {((pattern: string | RegExp) => RegExp | undefined) | undefined} */
6
+ let compileToRegexp
7
+
8
+ addHook({ name: 'path-to-regexp', versions: ['*'] }, moduleExports => {
9
+ // 0.1.x and 6.x: `module.exports = (path, ...) => RegExp`.
10
+ // 7.x: `module.exports = { pathToRegexp(path, ...) => RegExp }`.
11
+ // 8.x: `module.exports = { pathToRegexp(path, ...) => { regexp, keys } }`.
12
+ const compile = typeof moduleExports === 'function'
13
+ ? moduleExports
14
+ : (typeof moduleExports?.pathToRegexp === 'function' ? moduleExports.pathToRegexp : undefined)
15
+
16
+ if (compile !== undefined) {
17
+ compileToRegexp = pattern => {
18
+ let result
19
+ try {
20
+ result = compile(pattern)
21
+ } catch {
22
+ return
23
+ }
24
+ const regex = result?.regexp ?? result
25
+ if (regex instanceof RegExp) return regex
26
+ }
27
+ }
28
+
29
+ return moduleExports
30
+ })
31
+
32
+ /**
33
+ * Returns whatever path-to-regexp compile adapter the host most recently
34
+ * loaded. Capture this once at addHook fire time so each express/router
35
+ * instance keeps the dialect that was current when its routes were wrapped;
36
+ * a later host load that swaps the global compile won't retroactively change
37
+ * already-wrapped routers. `undefined` when the host has not loaded
38
+ * path-to-regexp yet, or never if it does not depend on it.
39
+ */
40
+ function getCompileToRegexp () {
41
+ return compileToRegexp
42
+ }
43
+
44
+ module.exports = { getCompileToRegexp }
@@ -12,7 +12,9 @@ const {
12
12
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
13
13
  getIsFaultyEarlyFlakeDetection,
14
14
  DYNAMIC_NAME_RE,
15
- logDynamicNamesWarning,
15
+ recordAttemptToFixExecution,
16
+ logAttemptToFixTestExecution,
17
+ logTestOptimizationSummary,
16
18
  } = require('../../dd-trace/src/plugins/util/test')
17
19
  const log = require('../../dd-trace/src/log')
18
20
  const {
@@ -76,9 +78,10 @@ let testManagementAttemptToFixRetries = 0
76
78
  let testManagementTests = {}
77
79
  let isImpactedTestsEnabled = false
78
80
  let modifiedFiles = {}
79
- const quarantinedOrDisabledTestsAttemptToFix = []
80
81
  let quarantinedButNotAttemptToFixFqns = new Set()
81
82
  const newTestsWithDynamicNames = new Set()
83
+ const attemptToFixExecutions = new Map()
84
+ const loggedAttemptToFixTests = new Set()
82
85
  let rootDir = ''
83
86
  let sessionProjects = []
84
87
 
@@ -290,6 +293,32 @@ function testWillRetry (test, testStatus) {
290
293
  return testStatus === 'fail' && test.results.length <= test.retries
291
294
  }
292
295
 
296
+ function getFinalStatus ({
297
+ isFinalExecution,
298
+ isDisabled,
299
+ isQuarantined,
300
+ isAtrRetry,
301
+ isEfdManagedTest,
302
+ isAttemptToFix,
303
+ hasFailedAllRetries,
304
+ hasFailedAttemptToFixRetries,
305
+ testStatus,
306
+ }) {
307
+ if (!isFinalExecution) {
308
+ return
309
+ }
310
+ if (isDisabled || isQuarantined || testStatus === 'skip') {
311
+ return 'skip'
312
+ }
313
+ if (isAtrRetry || isEfdManagedTest) {
314
+ return hasFailedAllRetries ? 'fail' : 'pass'
315
+ }
316
+ if (isAttemptToFix) {
317
+ return hasFailedAttemptToFixRetries ? 'fail' : 'pass'
318
+ }
319
+ return testStatus
320
+ }
321
+
293
322
  function getTestFullname (test) {
294
323
  let parent = test.parent
295
324
  const names = [test.title]
@@ -337,6 +366,11 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
337
366
  // We disable retries by default if attemptToFix is true
338
367
  if (getTestProperties(test).attemptToFix) {
339
368
  test.retries = 0
369
+ logAttemptToFixTestExecution(
370
+ getTestSuitePath(testSuiteAbsolutePath, rootDir),
371
+ getTestFullname(test),
372
+ loggedAttemptToFixTests
373
+ )
340
374
  }
341
375
 
342
376
  // this handles tests that do not go through the worker process (because they're skipped)
@@ -399,6 +433,20 @@ function testEndHandler ({
399
433
 
400
434
  const testProperties = getTestProperties(test)
401
435
 
436
+ if (testProperties.attemptToFix) {
437
+ test._ddHasFailedAttemptToFixRetries = false
438
+ test._ddHasFailedAllRetries = false
439
+ test._ddHasPassedAttemptToFixRetries = false
440
+
441
+ recordAttemptToFixExecution(attemptToFixExecutions, {
442
+ testSuite: getTestSuitePath(test._requireFile, rootDir),
443
+ testName: getTestFullname(test),
444
+ status: testStatus,
445
+ isDisabled: testProperties.disabled,
446
+ isQuarantined: testProperties.quarantined,
447
+ })
448
+ }
449
+
402
450
  if (testStatuses.length === testManagementAttemptToFixRetries + 1 && testProperties.attemptToFix) {
403
451
  if (testStatuses.includes('fail')) {
404
452
  test._ddHasFailedAttemptToFixRetries = true
@@ -431,10 +479,26 @@ function testEndHandler ({
431
479
  if (shouldCreateTestSpan) {
432
480
  const testResult = results.at(-1)
433
481
  const testCtx = testToCtx.get(test)
482
+ const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
483
+ !test._ddIsAttemptToFix &&
484
+ isEarlyFlakeDetectionEnabled
434
485
  const isAtrRetry = testResult?.retry > 0 &&
435
486
  isFlakyTestRetriesEnabled &&
436
487
  !test._ddIsAttemptToFix &&
437
488
  !test._ddIsEfdRetry
489
+
490
+ const finalStatus = getFinalStatus({
491
+ isFinalExecution: !testWillRetry(test, testStatus),
492
+ isDisabled: test._ddIsDisabled,
493
+ isQuarantined: test._ddIsQuarantined,
494
+ isAtrRetry,
495
+ isEfdManagedTest,
496
+ isAttemptToFix: test._ddIsAttemptToFix,
497
+ hasFailedAllRetries: test._ddHasFailedAllRetries,
498
+ hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
499
+ testStatus,
500
+ })
501
+
438
502
  // if there is no testCtx, the skipped test will be created later
439
503
  if (testCtx) {
440
504
  testFinishCh.publish({
@@ -454,6 +518,7 @@ function testEndHandler ({
454
518
  hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
455
519
  isAtrRetry,
456
520
  isModified: test._ddIsModified,
521
+ finalStatus,
457
522
  ...testCtx.currentStore,
458
523
  })
459
524
  }
@@ -597,11 +662,12 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
597
662
 
598
663
  const isTimeout = status === 'timedOut'
599
664
  const shouldCreateTestSpan = test.expectedStatus === 'skipped'
665
+ const testStatus = STATUS_TO_TEST_STATUS[status]
600
666
  testEndHandler(
601
667
  {
602
668
  test,
603
669
  annotations,
604
- testStatus: STATUS_TO_TEST_STATUS[status],
670
+ testStatus,
605
671
  error: errors && errors[0],
606
672
  isTimeout,
607
673
  shouldCreateTestSpan,
@@ -613,6 +679,27 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
613
679
  isFlakyTestRetriesEnabled &&
614
680
  !test._ddIsAttemptToFix &&
615
681
  !test._ddIsEfdRetry
682
+
683
+ // EFD retries (new or modified tests) are implemented as clones with retries=0,
684
+ // so testWillRetry always returns false for them. Instead, we track how many
685
+ // executions have been reported via testsToTestStatuses (updated by testEndHandler
686
+ // above) and mark the execution final once the count reaches the expected total.
687
+ // This mirrors how ATF finality is detected and centralizes the decision in the
688
+ // main process, so workers only need to act on the _ddIsFinalExecution flag.
689
+ const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
690
+ !test._ddIsAttemptToFix &&
691
+ isEarlyFlakeDetectionEnabled
692
+ let isFinalExecution
693
+ if (isEfdManagedTest) {
694
+ const testFqn = getTestFullyQualifiedName(test)
695
+ const efdTestStatuses = testsToTestStatuses.get(testFqn) || []
696
+ isFinalExecution = efdTestStatuses.length === earlyFlakeDetectionNumRetries + 1
697
+ } else if (test._ddIsAttemptToFix) {
698
+ isFinalExecution = !!(test._ddHasPassedAttemptToFixRetries || test._ddHasFailedAttemptToFixRetries)
699
+ } else {
700
+ isFinalExecution = !testWillRetry(test, testStatus)
701
+ }
702
+
616
703
  // We want to send the ddProperties to the worker
617
704
  worker.process.send({
618
705
  type: 'ddProperties',
@@ -629,6 +716,8 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
629
716
  _ddHasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
630
717
  _ddIsAtrRetry: isAtrRetry,
631
718
  _ddIsModified: test._ddIsModified,
719
+ _ddIsFinalExecution: isFinalExecution,
720
+ _ddIsEfdManagedTest: isEfdManagedTest,
632
721
  },
633
722
  })
634
723
  })
@@ -766,7 +855,6 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
766
855
 
767
856
  if (isTestManagementTestsEnabled && sessionStatus === 'failed') {
768
857
  let totalFailedTestCount = 0
769
- let totalAttemptToFixFailedTestCount = 0
770
858
  let totalPureQuarantinedFailedTestCount = 0
771
859
 
772
860
  for (const [fqn, testStatuses] of testsToTestStatuses.entries()) {
@@ -780,16 +868,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
780
868
  }
781
869
  }
782
870
 
783
- for (const test of quarantinedOrDisabledTestsAttemptToFix) {
784
- const testFqn = getTestFullyQualifiedName(test)
785
- const testStatuses = testsToTestStatuses.get(testFqn)
786
- // Only count as failed if the final status (after retries) is 'fail'
787
- if (testStatuses && testStatuses[testStatuses.length - 1] === 'fail') {
788
- totalAttemptToFixFailedTestCount += 1
789
- }
790
- }
791
-
792
- const totalIgnorableFailures = totalAttemptToFixFailedTestCount + totalPureQuarantinedFailedTestCount
871
+ const totalIgnorableFailures = totalPureQuarantinedFailedTestCount
793
872
 
794
873
  if (totalFailedTestCount > 0 && totalFailedTestCount === totalIgnorableFailures) {
795
874
  runAllTestsReturn = 'passed'
@@ -797,7 +876,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
797
876
  }
798
877
  }
799
878
 
800
- logDynamicNamesWarning(newTestsWithDynamicNames)
879
+ logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
880
+ loggedAttemptToFixTests.clear()
801
881
 
802
882
  const flushWait = new Promise(resolve => {
803
883
  onDone = resolve
@@ -975,9 +1055,6 @@ addHook({
975
1055
  if (!fileSuitesWithManagedTestsToProjects.has(fileSuite)) {
976
1056
  fileSuitesWithManagedTestsToProjects.set(fileSuite, getSuiteType(test, 'project'))
977
1057
  }
978
- if (testProperties.disabled || testProperties.quarantined) {
979
- quarantinedOrDisabledTestsAttemptToFix.push(test)
980
- }
981
1058
  }
982
1059
  }
983
1060
  applyRetriesToTests(
@@ -1275,6 +1352,18 @@ addHook({
1275
1352
  // Wait for the properties to be received
1276
1353
  await ddPropertiesPromise
1277
1354
 
1355
+ const finalStatus = getFinalStatus({
1356
+ isFinalExecution: test._ddIsFinalExecution,
1357
+ isDisabled: test._ddIsDisabled,
1358
+ isQuarantined: test._ddIsQuarantined,
1359
+ isAtrRetry: test._ddIsAtrRetry,
1360
+ isEfdManagedTest: test._ddIsEfdManagedTest,
1361
+ isAttemptToFix: test._ddIsAttemptToFix,
1362
+ hasFailedAllRetries: test._ddHasFailedAllRetries,
1363
+ hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
1364
+ testStatus: STATUS_TO_TEST_STATUS[status],
1365
+ })
1366
+
1278
1367
  testFinishCh.publish({
1279
1368
  testStatus: STATUS_TO_TEST_STATUS[status],
1280
1369
  steps: steps.filter(step => step.testId === testId),
@@ -1294,6 +1383,7 @@ addHook({
1294
1383
  isAtrRetry: test._ddIsAtrRetry,
1295
1384
  isModified: test._ddIsModified,
1296
1385
  onDone,
1386
+ finalStatus,
1297
1387
  ...testCtx.currentStore,
1298
1388
  })
1299
1389