dd-trace 5.99.0 → 5.100.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 (74) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/package.json +24 -5
  3. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  4. package/packages/datadog-instrumentations/src/express.js +3 -2
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  6. package/packages/datadog-instrumentations/src/hono.js +15 -4
  7. package/packages/datadog-instrumentations/src/jest.js +89 -63
  8. package/packages/datadog-instrumentations/src/mocha/main.js +18 -22
  9. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  10. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  11. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  12. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  13. package/packages/datadog-instrumentations/src/router.js +53 -33
  14. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  16. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  17. package/packages/datadog-plugin-bullmq/src/consumer.js +3 -2
  18. package/packages/datadog-plugin-bullmq/src/producer.js +25 -11
  19. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +32 -9
  20. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  21. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -1
  22. package/packages/datadog-plugin-graphql/src/utils.js +2 -2
  23. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  24. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  25. package/packages/datadog-plugin-memcached/src/index.js +1 -1
  26. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  27. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  28. package/packages/datadog-plugin-router/src/index.js +13 -0
  29. package/packages/dd-trace/index.js +4 -3
  30. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  31. package/packages/dd-trace/src/appsec/blocking.js +18 -6
  32. package/packages/dd-trace/src/appsec/graphql.js +1 -1
  33. package/packages/dd-trace/src/baggage.js +26 -13
  34. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -1
  35. package/packages/dd-trace/src/config/generated-config-types.d.ts +45 -69
  36. package/packages/dd-trace/src/config/index.js +13 -12
  37. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  38. package/packages/dd-trace/src/config/supported-configurations.json +31 -76
  39. package/packages/dd-trace/src/debugger/config.js +1 -1
  40. package/packages/dd-trace/src/dogstatsd.js +5 -8
  41. package/packages/dd-trace/src/encode/0.4.js +1 -1
  42. package/packages/dd-trace/src/encode/tags-processors.js +3 -3
  43. package/packages/dd-trace/src/exporter.js +1 -1
  44. package/packages/dd-trace/src/git_metadata_tagger.js +1 -1
  45. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  46. package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
  47. package/packages/dd-trace/src/llmobs/sdk.js +21 -1
  48. package/packages/dd-trace/src/llmobs/span_processor.js +14 -1
  49. package/packages/dd-trace/src/llmobs/writers/base.js +7 -1
  50. package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
  51. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  52. package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -8
  53. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -5
  54. package/packages/dd-trace/src/opentelemetry/metrics/index.js +6 -6
  55. package/packages/dd-trace/src/opentelemetry/span-helpers.js +170 -0
  56. package/packages/dd-trace/src/opentelemetry/span.js +14 -42
  57. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +1 -1
  58. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  59. package/packages/dd-trace/src/opentracing/propagation/text_map.js +44 -23
  60. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +42 -12
  61. package/packages/dd-trace/src/opentracing/span.js +4 -3
  62. package/packages/dd-trace/src/plugin_manager.js +6 -6
  63. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  64. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  65. package/packages/dd-trace/src/plugins/util/test.js +295 -29
  66. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  67. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  68. package/packages/dd-trace/src/proxy.js +9 -9
  69. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  70. package/packages/dd-trace/src/span_processor.js +1 -1
  71. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  72. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  73. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  74. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -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
 
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
4
- const pathToRegExp = require('../../../vendor/dist/path-to-regexp')
5
4
  const shimmer = require('../../datadog-shimmer')
6
5
  const { addHook, channel } = require('./helpers/instrument')
6
+ const { getCompileToRegexp } = require('./path-to-regexp')
7
7
 
8
8
  const {
9
9
  getRouterMountPaths,
@@ -19,15 +19,22 @@ const {
19
19
  } = require('./helpers/router-helper')
20
20
 
21
21
  function isFastStar (layer, matchers) {
22
- return layer.regexp?.fast_star ?? matchers.some(matcher => matcher.path === '*')
22
+ return layer.regexp?.fast_star ?? matchers.hasStarPath
23
23
  }
24
24
 
25
25
  function isFastSlash (layer, matchers) {
26
- return layer.regexp?.fast_slash ?? matchers.some(matcher => matcher.path === '/')
26
+ return layer.regexp?.fast_slash ?? matchers.hasSlashPath
27
27
  }
28
28
 
29
29
  // TODO: Move this function to a shared file between Express and Router
30
- function createWrapRouterMethod (name) {
30
+ /**
31
+ * @param {string} name Channel namespace (`apm:<name>:middleware:*`).
32
+ * @param {((pattern: string | RegExp) => RegExp | undefined) | undefined} compile
33
+ * Host-resolved path-to-regexp compile adapter, or undefined when the host
34
+ * instance ships no path-to-regexp. Captured here so each express/router
35
+ * instance keeps the dialect it actually loaded.
36
+ */
37
+ function createWrapRouterMethod (name, compile) {
31
38
  const enterChannel = channel(`apm:${name}:middleware:enter`)
32
39
  const exitChannel = channel(`apm:${name}:middleware:exit`)
33
40
  const finishChannel = channel(`apm:${name}:middleware:finish`)
@@ -35,8 +42,6 @@ function createWrapRouterMethod (name) {
35
42
  const nextChannel = channel(`apm:${name}:middleware:next`)
36
43
  const routeAddedChannel = channel(`apm:${name}:route:added`)
37
44
 
38
- const regexpCache = Object.create(null)
39
-
40
45
  function wrapLayerHandle (layer, original) {
41
46
  original._name = original._name || layer.name
42
47
 
@@ -55,13 +60,16 @@ function createWrapRouterMethod (name) {
55
60
 
56
61
  let route
57
62
 
58
- if (matchers) {
59
- // Try to guess which path actually matched
60
- for (const matcher of matchers) {
61
- if (matcher.test(layer)) {
62
- route = matcher.path
63
-
64
- break
63
+ if (matchers?.length && !isFastStar(layer, matchers) && !isFastSlash(layer, matchers)) {
64
+ if (matchers.length === 1) {
65
+ // The host already matched this layer; the lone pattern is the route.
66
+ route = matchers[0].path
67
+ } else {
68
+ for (const matcher of matchers) {
69
+ if (matcher.regex?.test(layer.path)) {
70
+ route = matcher.path
71
+ break
72
+ }
65
73
  }
66
74
  }
67
75
  }
@@ -124,25 +132,35 @@ function createWrapRouterMethod (name) {
124
132
  return []
125
133
  }
126
134
 
127
- return arg.map(pattern => ({
128
- path: pattern instanceof RegExp ? `(${pattern})` : pattern,
129
- test: layer => {
130
- const matchers = getLayerMatchers(layer)
131
- return !isFastStar(layer, matchers) &&
132
- !isFastSlash(layer, matchers) &&
133
- cachedPathToRegExp(pattern).test(layer.path)
134
- },
135
- }))
136
- }
137
-
138
- function cachedPathToRegExp (pattern) {
139
- const maybeCached = regexpCache[pattern]
140
- if (maybeCached) {
141
- return maybeCached
135
+ if (arg.length === 1) {
136
+ const pattern = arg[0]
137
+ const path = pattern instanceof RegExp ? `(${pattern})` : pattern
138
+ const matchers = [{ path }]
139
+ matchers.hasStarPath = path === '*'
140
+ matchers.hasSlashPath = path === '/'
141
+ return matchers
142
142
  }
143
- const regexp = pathToRegExp(pattern)
144
- regexpCache[pattern] = regexp
145
- return regexp
143
+
144
+ // hasStarPath/hasSlashPath cache the lookups isFastStar/isFastSlash
145
+ // would otherwise re-run on every request.
146
+ let hasStarPath = false
147
+ let hasSlashPath = false
148
+ const matchers = arg.map(pattern => {
149
+ const isRegExp = pattern instanceof RegExp
150
+ const path = isRegExp ? `(${pattern})` : pattern
151
+ if (path === '*') {
152
+ hasStarPath = true
153
+ } else if (path === '/') {
154
+ hasSlashPath = true
155
+ }
156
+ return {
157
+ path,
158
+ regex: isRegExp ? pattern : compile?.(pattern),
159
+ }
160
+ })
161
+ matchers.hasStarPath = hasStarPath
162
+ matchers.hasSlashPath = hasSlashPath
163
+ return matchers
146
164
  }
147
165
 
148
166
  function wrapMethod (original) {
@@ -218,9 +236,9 @@ function createWrapRouterMethod (name) {
218
236
  return wrapMethod
219
237
  }
220
238
 
221
- const wrapRouterMethod = createWrapRouterMethod('router')
222
-
223
239
  addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
240
+ const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
241
+
224
242
  shimmer.wrap(Router.prototype, 'use', wrapRouterMethod)
225
243
  shimmer.wrap(Router.prototype, 'route', wrapRouterMethod)
226
244
 
@@ -230,6 +248,8 @@ addHook({ name: 'router', versions: ['>=1 <2'] }, Router => {
230
248
  const queryParserReadCh = channel('datadog:query:read:finish')
231
249
 
232
250
  addHook({ name: 'router', versions: ['>=2'] }, Router => {
251
+ const wrapRouterMethod = createWrapRouterMethod('router', getCompileToRegexp())
252
+
233
253
  const WrappedRouter = shimmer.wrapFunction(Router, function (originalRouter) {
234
254
  return function wrappedMethod () {
235
255
  const router = originalRouter.apply(this, arguments)
@@ -11,8 +11,11 @@ const {
11
11
  VITEST_WORKER_TRACE_PAYLOAD_CODE,
12
12
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
13
13
  DYNAMIC_NAME_RE,
14
- collectDynamicNamesFromTraces,
15
- logDynamicNamesWarning,
14
+ getTestSuitePath,
15
+ recordAttemptToFixExecution,
16
+ collectTestOptimizationSummariesFromTraces,
17
+ logAttemptToFixTestExecution,
18
+ logTestOptimizationSummary,
16
19
  } = require('../../dd-trace/src/plugins/util/test')
17
20
  const { addHook, channel } = require('./helpers/instrument')
18
21
 
@@ -49,6 +52,7 @@ const codeCoverageReportCh = channel('ci:vitest:coverage-report')
49
52
 
50
53
  const taskToCtx = new WeakMap()
51
54
  const taskToStatuses = new WeakMap()
55
+ const attemptToFixTaskToStatuses = new WeakMap()
52
56
  const originalHookFns = new WeakMap()
53
57
  const newTasks = new WeakSet()
54
58
  const dynamicNameTasks = new WeakSet()
@@ -57,6 +61,8 @@ const disabledTasks = new WeakSet()
57
61
  const quarantinedTasks = new WeakSet()
58
62
  const attemptToFixTasks = new WeakSet()
59
63
  const modifiedTasks = new WeakSet()
64
+ const attemptToFixExecutions = new Map()
65
+ const loggedAttemptToFixTests = new Set()
60
66
  let isRetryReasonEfd = false
61
67
  let isRetryReasonAttemptToFix = false
62
68
  const switchedStatuses = new WeakSet()
@@ -255,6 +261,29 @@ function getTestName (task) {
255
261
  return testName
256
262
  }
257
263
 
264
+ function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
265
+ if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
266
+ return testCtx.status
267
+ }
268
+
269
+ return state === 'fail' ? 'fail' : 'pass'
270
+ }
271
+
272
+ function recordFinalAttemptToFixExecution (task, status, providedContext) {
273
+ const statuses = attemptToFixTaskToStatuses.get(task)
274
+ if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
275
+ statuses.push(status)
276
+ }
277
+
278
+ recordAttemptToFixExecution(attemptToFixExecutions, {
279
+ testSuite: getTestSuitePath(task.file.filepath, process.cwd()),
280
+ testName: getTestName(task),
281
+ status,
282
+ isDisabled: disabledTasks.has(task),
283
+ isQuarantined: quarantinedTasks.has(task),
284
+ })
285
+ }
286
+
258
287
  /**
259
288
  * Wraps a function so it runs inside the current test span context.
260
289
  * @param {object} task
@@ -480,7 +509,8 @@ function getFinishWrapper (exitOrClose) {
480
509
  onFinish,
481
510
  })
482
511
 
483
- logDynamicNamesWarning(newTestsWithDynamicNames)
512
+ logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
513
+ loggedAttemptToFixTests.clear()
484
514
 
485
515
  await flushPromise
486
516
 
@@ -542,7 +572,10 @@ function threadHandler (thread) {
542
572
  workerProcess.on('message', (message) => {
543
573
  if (message.__tinypool_worker_message__ && message.data) {
544
574
  if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
545
- collectDynamicNamesFromTraces(message.data, newTestsWithDynamicNames)
575
+ collectTestOptimizationSummariesFromTraces(message.data, {
576
+ newTestsWithDynamicNames,
577
+ attemptToFixExecutions,
578
+ })
546
579
  workerReportTraceCh.publish(message.data)
547
580
  } else if (message.interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
548
581
  workerReportLogsCh.publish(message.data)
@@ -584,7 +617,10 @@ function getWrappedOn (on) {
584
617
  if (message.type !== 'Buffer' && Array.isArray(message)) {
585
618
  const [interprocessCode, data] = message
586
619
  if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
587
- collectDynamicNamesFromTraces(data, newTestsWithDynamicNames)
620
+ collectTestOptimizationSummariesFromTraces(data, {
621
+ newTestsWithDynamicNames,
622
+ attemptToFixExecutions,
623
+ })
588
624
  workerReportTraceCh.publish(data)
589
625
  } else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
590
626
  workerReportLogsCh.publish(data)
@@ -658,7 +694,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
658
694
  isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
659
695
  task.repeats = testManagementAttemptToFixRetries
660
696
  attemptToFixTasks.add(task)
661
- taskToStatuses.set(task, [])
697
+ attemptToFixTaskToStatuses.set(task, [])
662
698
  }
663
699
  },
664
700
  })
@@ -726,15 +762,17 @@ function wrapVitestTestRunner (VitestTestRunner) {
726
762
 
727
763
  if (isTestManagementTestsEnabled) {
728
764
  const isAttemptingToFix = attemptToFixTasks.has(task)
729
- const isDisabled = disabledTasks.has(task)
730
765
  const isQuarantined = quarantinedTasks.has(task)
731
766
 
732
- if (isAttemptingToFix && (isDisabled || isQuarantined)) {
733
- if (task.result.state === 'fail') {
767
+ if (isAttemptingToFix) {
768
+ const statuses = attemptToFixTaskToStatuses.get(task)
769
+ if (task.result.state === 'pass' && statuses?.includes('fail')) {
734
770
  switchedStatuses.add(task)
771
+ task.result.state = 'fail'
735
772
  }
736
- task.result.state = 'pass'
737
- } else if (isQuarantined) {
773
+ }
774
+
775
+ if (!isAttemptingToFix && isQuarantined) {
738
776
  if (task.result.state === 'fail') {
739
777
  switchedStatuses.add(task)
740
778
  }
@@ -822,10 +860,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
822
860
 
823
861
  const lastExecutionStatus = task.result.state
824
862
  const isAtf = attemptToFixTasks.has(task)
825
- const isQuarantinedOrDisabledAtf = isAtf && (quarantinedTasks.has(task) || disabledTasks.has(task))
826
863
  const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
827
- const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isQuarantinedOrDisabledAtf
828
- const statuses = taskToStatuses.get(task)
864
+ const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
865
+ const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
829
866
 
830
867
  // These clauses handle task.repeats, whether EFD is enabled or not
831
868
  // The only thing that EFD does is to forcefully pass the test if it has passed at least once
@@ -842,7 +879,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
842
879
  } else {
843
880
  testPassCh.publish({ task, ...ctx.currentStore })
844
881
  }
845
- if (shouldTrackStatuses) {
882
+ if (shouldTrackStatuses && statuses) {
846
883
  statuses.push(lastExecutionStatus)
847
884
  }
848
885
  if (shouldFlipStatus) {
@@ -855,7 +892,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
855
892
  }
856
893
  }
857
894
  } else if (numRepetition === task.repeats) {
858
- if (shouldTrackStatuses) {
895
+ if (shouldTrackStatuses && statuses) {
859
896
  statuses.push(lastExecutionStatus)
860
897
  }
861
898
 
@@ -893,6 +930,14 @@ function wrapVitestTestRunner (VitestTestRunner) {
893
930
  }
894
931
  taskToCtx.set(task, ctx)
895
932
 
933
+ if (attemptToFixTasks.has(task)) {
934
+ logAttemptToFixTestExecution(
935
+ getTestSuitePath(task.file.filepath, process.cwd()),
936
+ testName,
937
+ loggedAttemptToFixTests
938
+ )
939
+ }
940
+
896
941
  testStartCh.runStores(ctx, () => {})
897
942
 
898
943
  // Wrap the test function so it runs inside the test span context.
@@ -960,11 +1005,11 @@ function wrapVitestTestRunner (VitestTestRunner) {
960
1005
  let attemptToFixPassed = false
961
1006
  let attemptToFixFailed = false
962
1007
  if (attemptToFixTasks.has(task)) {
963
- const statuses = taskToStatuses.get(task)
1008
+ const statuses = attemptToFixTaskToStatuses.get(task)
964
1009
  if (statuses.length === testManagementAttemptToFixRetries) {
965
- if (statuses.every(status => status === 'pass')) {
1010
+ if (status === 'pass' && statuses.every(status => status === 'pass')) {
966
1011
  attemptToFixPassed = true
967
- } else if (statuses.includes('fail')) {
1012
+ } else if (status === 'fail' || statuses.includes('fail')) {
968
1013
  attemptToFixFailed = true
969
1014
  }
970
1015
  }
@@ -1165,9 +1210,16 @@ addHook({
1165
1210
  // We have to trick vitest into thinking that the test has passed
1166
1211
  // but we want to report it as failed if it did fail
1167
1212
  const isSwitchedStatus = switchedStatuses.has(task)
1213
+ const providedContext = getProvidedContext()
1168
1214
 
1169
1215
  if (result) {
1170
1216
  const { state, duration, errors } = result
1217
+ const testError = errors?.[0]
1218
+ if (attemptToFixTasks.has(task)) {
1219
+ const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
1220
+ recordFinalAttemptToFixExecution(task, status, providedContext)
1221
+ }
1222
+
1171
1223
  if (state === 'skip') { // programmatic skip
1172
1224
  testSkipCh.publish({
1173
1225
  testName: getTestName(task),
@@ -1177,24 +1229,19 @@ addHook({
1177
1229
  })
1178
1230
  } else if (state === 'pass' && !isSwitchedStatus) {
1179
1231
  if (testCtx) {
1232
+ const isSkippedByTestManagement =
1233
+ !attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
1180
1234
  testPassCh.publish({
1181
1235
  task,
1182
- finalStatus:
1183
- disabledTasks.has(task) || quarantinedTasks.has(task) ? 'skip' : 'pass',
1236
+ finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
1184
1237
  ...testCtx.currentStore,
1185
1238
  })
1186
1239
  }
1187
1240
  } else if (state === 'fail' || isSwitchedStatus) {
1188
- let testError
1189
-
1190
- if (errors?.length) {
1191
- testError = errors[0]
1192
- }
1193
-
1194
1241
  let hasFailedAllRetries = false
1195
1242
  let attemptToFixFailed = false
1196
1243
  if (attemptToFixTasks.has(task)) {
1197
- const statuses = taskToStatuses.get(task)
1244
+ const statuses = attemptToFixTaskToStatuses.get(task)
1198
1245
  if (statuses.includes('fail')) {
1199
1246
  attemptToFixFailed = true
1200
1247
  }
@@ -1204,7 +1251,6 @@ addHook({
1204
1251
  }
1205
1252
 
1206
1253
  // Check if all EFD retries failed
1207
- const providedContext = getProvidedContext()
1208
1254
  const isEfdRetry =
1209
1255
  providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
1210
1256
  if (isEfdRetry) {
@@ -1232,7 +1278,7 @@ addHook({
1232
1278
 
1233
1279
  let finalStatus
1234
1280
  if (isSwitchedStatus) {
1235
- if (disabledTasks.has(task) || quarantinedTasks.has(task)) {
1281
+ if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
1236
1282
  finalStatus = 'skip'
1237
1283
  } else if (isAtrRetry || isEfdRetry) {
1238
1284
  finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
@@ -149,7 +149,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
149
149
  }
150
150
  this.addResponseTags(span, response)
151
151
 
152
- if (this._tracerConfig?.trace?.aws?.addSpanPointers) {
152
+ if (this._tracerConfig?.DD_TRACE_AWS_ADD_SPAN_POINTERS) {
153
153
  this.addSpanPointers(span, response)
154
154
  }
155
155
  })
@@ -118,7 +118,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
118
118
  return this.dynamoPrimaryKeyConfig
119
119
  }
120
120
 
121
- const configStr = this._tracerConfig?.trace?.dynamoDb?.tablePrimaryKeys
121
+ const configStr = this._tracerConfig?.DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS
122
122
  if (!configStr) {
123
123
  log.warn(
124
124
  // eslint-disable-next-line @stylistic/max-len
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('../../dd-trace/src/log')
3
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
5
  const { getMessageSize } = require('../../dd-trace/src/datastreams')
5
6
 
@@ -73,8 +74,8 @@ class BullmqConsumerPlugin extends ConsumerPlugin {
73
74
  job.opts.telemetry.metadata = JSON.stringify(metadata)
74
75
 
75
76
  return ddCarrier
76
- } catch {
77
- // Ignore malformed metadata
77
+ } catch (error) {
78
+ log.warn('bullmq: skipping _datadog extract on malformed telemetry.metadata: %s', error.message)
78
79
  }
79
80
  }
80
81
  }