dd-trace 5.86.0 → 5.88.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 (105) hide show
  1. package/LICENSE-3rdparty.csv +60 -32
  2. package/ext/exporters.d.ts +1 -0
  3. package/ext/exporters.js +1 -0
  4. package/index.d.ts +243 -7
  5. package/package.json +9 -6
  6. package/packages/datadog-instrumentations/src/ai.js +54 -90
  7. package/packages/datadog-instrumentations/src/cucumber.js +14 -0
  8. package/packages/datadog-instrumentations/src/helpers/hook.js +17 -11
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +55 -14
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +15 -13
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +103 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.js +108 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -1
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/transformer.js +21 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +138 -12
  17. package/packages/datadog-instrumentations/src/http/client.js +119 -1
  18. package/packages/datadog-instrumentations/src/jest.js +179 -15
  19. package/packages/datadog-instrumentations/src/kafkajs.js +20 -17
  20. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
  21. package/packages/datadog-instrumentations/src/mysql2.js +131 -64
  22. package/packages/datadog-instrumentations/src/playwright.js +9 -1
  23. package/packages/datadog-instrumentations/src/stripe.js +92 -0
  24. package/packages/datadog-instrumentations/src/vitest.js +11 -0
  25. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -10
  26. package/packages/datadog-plugin-amqplib/src/producer.js +23 -19
  27. package/packages/datadog-plugin-azure-functions/src/index.js +53 -37
  28. package/packages/datadog-plugin-bullmq/src/consumer.js +33 -11
  29. package/packages/datadog-plugin-bullmq/src/producer.js +60 -31
  30. package/packages/datadog-plugin-cucumber/src/index.js +9 -6
  31. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +33 -0
  32. package/packages/datadog-plugin-cypress/src/support.js +48 -8
  33. package/packages/datadog-plugin-jest/src/index.js +12 -2
  34. package/packages/datadog-plugin-jest/src/util.js +2 -1
  35. package/packages/datadog-plugin-kafkajs/src/consumer.js +22 -12
  36. package/packages/datadog-plugin-kafkajs/src/producer.js +33 -22
  37. package/packages/datadog-plugin-mocha/src/index.js +9 -6
  38. package/packages/datadog-plugin-playwright/src/index.js +10 -6
  39. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  40. package/packages/dd-trace/src/appsec/addresses.js +11 -0
  41. package/packages/dd-trace/src/appsec/channels.js +5 -1
  42. package/packages/dd-trace/src/appsec/downstream_requests.js +302 -0
  43. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -1
  44. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +1 -1
  46. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +4 -5
  47. package/packages/dd-trace/src/appsec/iast/path-line.js +36 -25
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +1 -1
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +3 -2
  51. package/packages/dd-trace/src/appsec/index.js +103 -0
  52. package/packages/dd-trace/src/appsec/rasp/ssrf.js +66 -4
  53. package/packages/dd-trace/src/azure_metadata.js +0 -2
  54. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +14 -1
  55. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  56. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -0
  57. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -1
  58. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  59. package/packages/dd-trace/src/ci-visibility/requests/request.js +236 -0
  60. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  61. package/packages/dd-trace/src/config/defaults.js +148 -195
  62. package/packages/dd-trace/src/config/helper.js +43 -1
  63. package/packages/dd-trace/src/config/index.js +42 -14
  64. package/packages/dd-trace/src/config/supported-configurations.json +4115 -510
  65. package/packages/dd-trace/src/constants.js +0 -2
  66. package/packages/dd-trace/src/crashtracking/crashtracker.js +10 -3
  67. package/packages/dd-trace/src/datastreams/pathway.js +22 -3
  68. package/packages/dd-trace/src/datastreams/processor.js +14 -1
  69. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -2
  70. package/packages/dd-trace/src/debugger/devtools_client/index.js +75 -23
  71. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +23 -1
  72. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +3 -3
  73. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +168 -36
  74. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +18 -0
  75. package/packages/dd-trace/src/encode/agentless-json.js +141 -0
  76. package/packages/dd-trace/src/exporter.js +2 -0
  77. package/packages/dd-trace/src/exporters/agent/writer.js +22 -8
  78. package/packages/dd-trace/src/exporters/agentless/index.js +89 -0
  79. package/packages/dd-trace/src/exporters/agentless/writer.js +184 -0
  80. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  81. package/packages/dd-trace/src/exporters/common/request.js +4 -4
  82. package/packages/dd-trace/src/llmobs/constants/writers.js +1 -1
  83. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -3
  84. package/packages/dd-trace/src/llmobs/sdk.js +34 -5
  85. package/packages/dd-trace/src/opentelemetry/context_manager.js +19 -46
  86. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +3 -4
  87. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -5
  88. package/packages/dd-trace/src/opentracing/span.js +6 -4
  89. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -5
  90. package/packages/dd-trace/src/plugins/database.js +57 -45
  91. package/packages/dd-trace/src/plugins/outbound.js +27 -2
  92. package/packages/dd-trace/src/plugins/tracing.js +39 -4
  93. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +7 -0
  94. package/packages/dd-trace/src/plugins/util/test.js +48 -0
  95. package/packages/dd-trace/src/plugins/util/web.js +8 -7
  96. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -0
  97. package/packages/dd-trace/src/propagation-hash/index.js +145 -0
  98. package/packages/dd-trace/src/proxy.js +4 -0
  99. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  100. package/packages/dd-trace/src/startup-log.js +3 -3
  101. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/bullmq.json +0 -106
  102. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +0 -741
  103. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +0 -11
  104. package/packages/dd-trace/src/plugins/util/serverless.js +0 -8
  105. package/packages/dd-trace/src/scope/noop/scope.js +0 -21
@@ -21,6 +21,7 @@ const {
21
21
  getFormattedJestTestParameters,
22
22
  getJestTestName,
23
23
  getJestSuitesToRun,
24
+ getEfdRetryCount,
24
25
  } = require('../../datadog-plugin-jest/src/util')
25
26
  const { addHook, channel } = require('./helpers/instrument')
26
27
 
@@ -76,6 +77,7 @@ let hasUnskippableSuites = false
76
77
  let hasForcedToRunSuites = false
77
78
  let isEarlyFlakeDetectionEnabled = false
78
79
  let earlyFlakeDetectionNumRetries = 0
80
+ let earlyFlakeDetectionSlowTestRetries = {}
79
81
  let earlyFlakeDetectionFaultyThreshold = 30
80
82
  let isEarlyFlakeDetectionFaulty = false
81
83
  let hasFilteredSkippableSuites = false
@@ -95,6 +97,12 @@ const attemptToFixRetriedTestsStatuses = new Map()
95
97
  const wrappedWorkers = new WeakSet()
96
98
  const testSuiteMockedFiles = new Map()
97
99
  const testsToBeRetried = new Set()
100
+ // Per-test: how many EFD retries were determined after the first execution.
101
+ const efdDeterminedRetries = new Map()
102
+ // Tests whose first run exceeded the 5-min threshold — tagged "slow".
103
+ const efdSlowAbortedTests = new Set()
104
+ // Tests added as EFD new-test candidates (not ATF, not impacted).
105
+ const efdNewTestCandidates = new Set()
98
106
  const testSuiteAbsolutePathsWithFastCheck = new Set()
99
107
  const testSuiteJestObjects = new Map()
100
108
 
@@ -129,6 +137,8 @@ function getTestEnvironmentOptions (config) {
129
137
  return {}
130
138
  }
131
139
 
140
+ const MAX_IGNORED_TEST_NAMES = 10
141
+
132
142
  function getTestStats (testStatuses) {
133
143
  return testStatuses.reduce((acc, testStatus) => {
134
144
  acc[testStatus]++
@@ -136,6 +146,32 @@ function getTestStats (testStatuses) {
136
146
  }, { pass: 0, fail: 0 })
137
147
  }
138
148
 
149
+ /**
150
+ * @param {string[]} efdNames
151
+ * @param {string[]} quarantineNames
152
+ * @param {number} totalCount
153
+ */
154
+ function logIgnoredFailuresSummary (efdNames, quarantineNames, totalCount) {
155
+ const names = []
156
+ for (const n of efdNames) {
157
+ names.push({ name: n, reason: 'Early Flake Detection' })
158
+ }
159
+ for (const n of quarantineNames) {
160
+ names.push({ name: n, reason: 'Quarantine' })
161
+ }
162
+ const shown = names.slice(0, MAX_IGNORED_TEST_NAMES)
163
+ const more = names.length - shown.length
164
+ const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
165
+ const list = shown.map(({ name, reason }) => ` • ${name} (${reason})`).join('\n')
166
+ const line = '-'.repeat(50)
167
+ // eslint-disable-next-line no-console -- Intentional user-facing message when exit code is flipped
168
+ console.warn(
169
+ `\n${line}\nDatadog Test Optimization\n${line}\n` +
170
+ `${totalCount} test failure(s) were ignored. Exit code set to 0.\n\n` +
171
+ `${list}${moreSuffix}\n`
172
+ )
173
+ }
174
+
139
175
  function getWrappedEnvironment (BaseEnvironment, jestVersion) {
140
176
  return class DatadogEnvironment extends BaseEnvironment {
141
177
  constructor (config, context) {
@@ -169,7 +205,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
169
205
  this.isImpactedTestsEnabled = this.testEnvironmentOptions._ddIsImpactedTestsEnabled
170
206
 
171
207
  if (this.isKnownTestsEnabled) {
172
- earlyFlakeDetectionNumRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionNumRetries
208
+ earlyFlakeDetectionSlowTestRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionSlowTestRetries ?? {}
173
209
  try {
174
210
  this.knownTestsForThisSuite = this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
175
211
 
@@ -438,7 +474,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
438
474
  testContexts.set(event.test, ctx)
439
475
 
440
476
  testStartCh.runStores(ctx, () => {
441
- for (const hook of event.test.parent.hooks) {
477
+ let p = event.test.parent
478
+ const hooks = []
479
+ while (p != null) {
480
+ hooks.push(...p.hooks)
481
+ p = p.parent
482
+ }
483
+ for (const hook of hooks) {
442
484
  let hookFn = hook.fn
443
485
  if (originalHookFns.has(hook)) {
444
486
  hookFn = originalHookFns.get(hook)
@@ -509,11 +551,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
509
551
  retriedTestsToNumAttempts.set(testFullName, 0)
510
552
  if (this.isEarlyFlakeDetectionEnabled) {
511
553
  testsToBeRetried.add(testFullName)
512
- this.retryTest({
513
- jestEvent: event,
514
- retryCount: earlyFlakeDetectionNumRetries,
515
- retryType: 'Early flake detection',
516
- })
554
+ efdNewTestCandidates.add(testFullName)
555
+ // Cloning is deferred to test_done after the first execution,
556
+ // when we know the duration and can choose the right retry count.
517
557
  }
518
558
  }
519
559
  }
@@ -538,8 +578,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
538
578
  let attemptToFixFailed = false
539
579
  let failedAllTests = false
540
580
  let isAttemptToFix = false
581
+ const testName = getJestTestName(event.test, this.getShouldStripSeedFromTestName())
541
582
  if (this.isTestManagementTestsEnabled) {
542
- const testName = getJestTestName(event.test, this.getShouldStripSeedFromTestName())
543
583
  isAttemptToFix = this.testManagementTestsForThisSuite?.attemptToFix?.includes(testName)
544
584
  if (isAttemptToFix) {
545
585
  if (attemptToFixRetriedTestsStatuses.has(testName)) {
@@ -564,9 +604,53 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
564
604
  }
565
605
  }
566
606
 
607
+ // EFD dynamic cloning: on first execution of a new EFD candidate,
608
+ // determine the retry count from the test's duration.
609
+ if (
610
+ this.isEarlyFlakeDetectionEnabled &&
611
+ this.isKnownTestsEnabled &&
612
+ efdNewTestCandidates.has(testName) &&
613
+ event.test.invocations === 1 &&
614
+ !efdDeterminedRetries.has(testName)
615
+ ) {
616
+ const durationMs = event.test.duration ?? 0
617
+ const retryCount = getEfdRetryCount(durationMs, earlyFlakeDetectionSlowTestRetries)
618
+ efdDeterminedRetries.set(testName, retryCount)
619
+ if (retryCount > 0) {
620
+ // Temporarily adjust jest-circus state so that retry tests are registered
621
+ // into the correct describe block and bypass the "tests have started" guard.
622
+ //
623
+ // Problem 1 (jest-circus ≤24): currentDescribeBlock points to ROOT during
624
+ // execution, and ROOT's tests loop already finished before children ran.
625
+ //
626
+ // Problem 2 (jest-circus ≥27): `hasStarted = true` causes `test()` to throw
627
+ // "Cannot add a test after tests have started running".
628
+ //
629
+ // Fix: temporarily point currentDescribeBlock to the test's parent (so retries
630
+ // land in the still-iterating children array) and set hasStarted = false (so the
631
+ // guard is bypassed). Both are restored immediately after scheduling the retries.
632
+ const originalDescribeBlock = state.currentDescribeBlock
633
+ const originalHasStarted = state.hasStarted
634
+ state.currentDescribeBlock = event.test.parent ?? originalDescribeBlock
635
+ state.hasStarted = false
636
+ this.retryTest({
637
+ jestEvent: {
638
+ testName: event.test.name,
639
+ fn: event.test.fn,
640
+ timeout: event.test.timeout,
641
+ },
642
+ retryCount,
643
+ retryType: 'Early flake detection',
644
+ })
645
+ state.currentDescribeBlock = originalDescribeBlock
646
+ state.hasStarted = originalHasStarted
647
+ } else {
648
+ efdSlowAbortedTests.add(testName)
649
+ }
650
+ }
651
+
567
652
  let isEfdRetry = false
568
653
  // We'll store the test statuses of the retries
569
- const testName = getJestTestName(event.test, this.getShouldStripSeedFromTestName())
570
654
  if (this.isKnownTestsEnabled) {
571
655
  const isNewTest = retriedTestsToNumAttempts.has(testName)
572
656
  if (isNewTest) {
@@ -576,6 +660,14 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
576
660
  } else {
577
661
  newTestsTestStatuses.set(testName, [status])
578
662
  }
663
+ const testStatuses = newTestsTestStatuses.get(testName)
664
+ // Check if this is the last EFD retry.
665
+ // If it is, we'll set the failedAllTests flag to true if all the tests failed
666
+ const efdRetryCount = efdDeterminedRetries.get(testName) ?? 0
667
+ if (efdRetryCount > 0 && testStatuses.length === efdRetryCount + 1 &&
668
+ testStatuses.every(status => status === 'fail')) {
669
+ failedAllTests = true
670
+ }
579
671
  }
580
672
  }
581
673
 
@@ -630,6 +722,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
630
722
  attemptToFixFailed,
631
723
  isAtrRetry,
632
724
  finalStatus,
725
+ earlyFlakeAbortReason: efdSlowAbortedTests.has(testName) ? 'slow' : undefined,
633
726
  })
634
727
 
635
728
  if (promises.isProbeReady) {
@@ -641,6 +734,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
641
734
  test.errors = errors
642
735
  }
643
736
  atrSuppressedErrors.clear()
737
+ efdDeterminedRetries.clear()
738
+ efdSlowAbortedTests.clear()
739
+ efdNewTestCandidates.clear()
644
740
  }
645
741
  if (event.name === 'test_skip' || event.name === 'test_todo') {
646
742
  const testName = getJestTestName(event.test, this.getShouldStripSeedFromTestName())
@@ -661,7 +757,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
661
757
  getEfdResult ({ testName, isNewTest, isModifiedTest, isEfdRetry, numberOfExecutedRetries }) {
662
758
  const isEfdEnabled = this.isEarlyFlakeDetectionEnabled
663
759
  const isEfdActive = isEfdEnabled && (isNewTest || isModifiedTest)
664
- const isLastEfdRetry = isEfdRetry && numberOfExecutedRetries >= (earlyFlakeDetectionNumRetries + 1)
760
+ const retryCount = efdDeterminedRetries.get(testName) ?? 0
761
+ const isSlowAbort = efdSlowAbortedTests.has(testName)
762
+ const isLastEfdRetry = (isEfdRetry && numberOfExecutedRetries >= (retryCount + 1)) || isSlowAbort
665
763
  const isFinalEfdTestExecution = isEfdActive && isLastEfdRetry
666
764
 
667
765
  let finalStatus
@@ -898,6 +996,7 @@ function getCliWrapper (isNewJestVersion) {
898
996
  isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
899
997
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
900
998
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
999
+ earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
901
1000
  earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
902
1001
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
903
1002
  isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
@@ -994,11 +1093,18 @@ function getCliWrapper (isNewJestVersion) {
994
1093
  coverageMap,
995
1094
  numFailedTestSuites,
996
1095
  numFailedTests,
1096
+ numRuntimeErrorTestSuites = 0,
997
1097
  numTotalTests,
998
1098
  numTotalTestSuites,
1099
+ runExecError,
1100
+ wasInterrupted,
999
1101
  },
1000
1102
  } = result
1001
1103
 
1104
+ const hasSuiteLevelFailures = numRuntimeErrorTestSuites > 0
1105
+ const hasRunLevelFailure = runExecError != null || wasInterrupted === true
1106
+ const mustNotFlipSuccess = hasSuiteLevelFailures || hasRunLevelFailure
1107
+
1002
1108
  let testCodeCoverageLinesTotal
1003
1109
 
1004
1110
  if (isUserCodeCoverageEnabled) {
@@ -1018,16 +1124,44 @@ function getCliWrapper (isNewJestVersion) {
1018
1124
  * on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
1019
1125
  */
1020
1126
  let numEfdFailedTestsToIgnore = 0
1127
+ const efdIgnoredNames = []
1128
+ const quarantineIgnoredNames = []
1129
+
1130
+ // Build fullName -> suite map from results (for EFD display)
1131
+ const fullNameToSuite = new Map()
1132
+ for (const { testResults, testFilePath } of result.results.testResults) {
1133
+ const suite = getTestSuitePath(testFilePath, result.globalConfig.rootDir)
1134
+ for (const { fullName } of testResults) {
1135
+ const name = testSuiteAbsolutePathsWithFastCheck.has(testFilePath)
1136
+ ? fullName.replace(SEED_SUFFIX_RE, '')
1137
+ : fullName
1138
+ fullNameToSuite.set(name, suite)
1139
+ }
1140
+ }
1141
+
1142
+ /** @type {{ efdNames: string[], quarantineNames: string[], totalCount: number } | undefined} */
1143
+ let ignoredFailuresSummary
1021
1144
  if (isEarlyFlakeDetectionEnabled) {
1022
- for (const testStatuses of newTestsTestStatuses.values()) {
1145
+ for (const [testName, testStatuses] of newTestsTestStatuses) {
1023
1146
  const { pass, fail } = getTestStats(testStatuses)
1024
1147
  if (pass > 0) { // as long as one passes, we'll consider the test passed
1025
1148
  numEfdFailedTestsToIgnore += fail
1149
+ const suite = fullNameToSuite.get(testName)
1150
+ efdIgnoredNames.push(suite ? `${suite} › ${testName}` : testName)
1026
1151
  }
1027
1152
  }
1028
1153
  // If every test that failed was an EFD retry, we'll consider the suite passed
1029
- if (numEfdFailedTestsToIgnore !== 0 && result.results.numFailedTests === numEfdFailedTestsToIgnore) {
1154
+ if (
1155
+ !mustNotFlipSuccess &&
1156
+ numEfdFailedTestsToIgnore !== 0 &&
1157
+ result.results.numFailedTests === numEfdFailedTestsToIgnore
1158
+ ) {
1030
1159
  result.results.success = true
1160
+ ignoredFailuresSummary = {
1161
+ efdNames: efdIgnoredNames,
1162
+ quarantineNames: [],
1163
+ totalCount: numEfdFailedTestsToIgnore,
1164
+ }
1031
1165
  }
1032
1166
  }
1033
1167
 
@@ -1062,8 +1196,10 @@ function getCliWrapper (isNewJestVersion) {
1062
1196
  // This uses `attempt_to_fix` because this is always the main process and it's not formatted in camelCase
1063
1197
  if (testManagementTest?.attempt_to_fix && (testManagementTest?.quarantined || testManagementTest?.disabled)) {
1064
1198
  numFailedQuarantinedOrDisabledAttemptedToFixTests++
1199
+ quarantineIgnoredNames.push(`${testSuite} › ${testName}`)
1065
1200
  } else if (testManagementTest?.quarantined) {
1066
1201
  numFailedQuarantinedTests++
1202
+ quarantineIgnoredNames.push(`${testSuite} › ${testName}`)
1067
1203
  }
1068
1204
  }
1069
1205
 
@@ -1071,22 +1207,42 @@ function getCliWrapper (isNewJestVersion) {
1071
1207
  // Note that if a test is attempted to fix,
1072
1208
  // it's considered quarantined both if it's disabled and if it's quarantined
1073
1209
  // (it'll run but its status is ignored)
1210
+ // Skip if EFD block already flipped (to avoid logging twice)
1074
1211
  if (
1212
+ !result.results.success &&
1213
+ !mustNotFlipSuccess &&
1075
1214
  (numFailedQuarantinedOrDisabledAttemptedToFixTests !== 0 || numFailedQuarantinedTests !== 0) &&
1076
1215
  result.results.numFailedTests ===
1077
1216
  numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
1078
1217
  ) {
1079
1218
  result.results.success = true
1219
+ ignoredFailuresSummary = {
1220
+ efdNames: [],
1221
+ quarantineNames: quarantineIgnoredNames,
1222
+ totalCount: numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests,
1223
+ }
1080
1224
  }
1081
1225
  }
1082
1226
 
1083
1227
  // Combined check: if all failed tests are accounted for by EFD (flaky retries) and/or quarantine,
1084
1228
  // we should consider the suite passed even when neither check alone covers all failures.
1085
- if (!result.results.success && (isEarlyFlakeDetectionEnabled || isTestManagementTestsEnabled)) {
1229
+ if (
1230
+ !result.results.success &&
1231
+ !mustNotFlipSuccess &&
1232
+ (isEarlyFlakeDetectionEnabled || isTestManagementTestsEnabled)
1233
+ ) {
1086
1234
  const totalIgnoredFailures =
1087
1235
  numEfdFailedTestsToIgnore + numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
1088
- if (totalIgnoredFailures !== 0 && result.results.numFailedTests === totalIgnoredFailures) {
1236
+ if (
1237
+ totalIgnoredFailures !== 0 &&
1238
+ result.results.numFailedTests === totalIgnoredFailures
1239
+ ) {
1089
1240
  result.results.success = true
1241
+ ignoredFailuresSummary = {
1242
+ efdNames: efdIgnoredNames,
1243
+ quarantineNames: quarantineIgnoredNames,
1244
+ totalCount: totalIgnoredFailures,
1245
+ }
1090
1246
  }
1091
1247
  }
1092
1248
 
@@ -1144,6 +1300,14 @@ function getCliWrapper (isNewJestVersion) {
1144
1300
  })
1145
1301
  }
1146
1302
 
1303
+ if (ignoredFailuresSummary) {
1304
+ logIgnoredFailuresSummary(
1305
+ ignoredFailuresSummary.efdNames,
1306
+ ignoredFailuresSummary.quarantineNames,
1307
+ ignoredFailuresSummary.totalCount
1308
+ )
1309
+ }
1310
+
1147
1311
  numSkippedSuites = 0
1148
1312
 
1149
1313
  return result
@@ -1408,7 +1572,7 @@ addHook({
1408
1572
  _ddItrCorrelationId,
1409
1573
  _ddKnownTests,
1410
1574
  _ddIsEarlyFlakeDetectionEnabled,
1411
- _ddEarlyFlakeDetectionNumRetries,
1575
+ _ddEarlyFlakeDetectionSlowTestRetries,
1412
1576
  _ddRepositoryRoot,
1413
1577
  _ddIsFlakyTestRetriesEnabled,
1414
1578
  _ddFlakyTestRetriesCount,
@@ -24,22 +24,6 @@ const batchConsumerErrorCh = channel('apm:kafkajs:consume-batch:error')
24
24
 
25
25
  const disabledHeaderWeakSet = new WeakSet()
26
26
 
27
- function commitsFromEvent (event) {
28
- const { payload: { groupId, topics } } = event
29
- const commitList = []
30
- for (const { topic, partitions } of topics) {
31
- for (const { partition, offset } of partitions) {
32
- commitList.push({
33
- groupId,
34
- partition,
35
- offset,
36
- topic,
37
- })
38
- }
39
- }
40
- consumerCommitCh.publish(commitList)
41
- }
42
-
43
27
  addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKafka) => {
44
28
  class Kafka extends BaseKafka {
45
29
  constructor (options) {
@@ -132,6 +116,7 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
132
116
  }
133
117
 
134
118
  const kafkaClusterIdPromise = getKafkaClusterId(this)
119
+ let resolvedClusterId = null
135
120
 
136
121
  const eachMessageExtractor = (args, clusterId) => {
137
122
  const { topic, partition, message } = args[0]
@@ -146,13 +131,31 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
146
131
 
147
132
  const consumer = createConsumer.apply(this, arguments)
148
133
 
149
- consumer.on(consumer.events.COMMIT_OFFSETS, commitsFromEvent)
134
+ consumer.on(consumer.events.COMMIT_OFFSETS, (event) => {
135
+ const { payload: { groupId: commitGroupId, topics } } = event
136
+ const commitList = []
137
+ for (const { topic, partitions } of topics) {
138
+ for (const { partition, offset } of partitions) {
139
+ commitList.push({
140
+ groupId: commitGroupId,
141
+ partition,
142
+ offset,
143
+ topic,
144
+ clusterId: resolvedClusterId,
145
+ })
146
+ }
147
+ }
148
+ consumerCommitCh.publish(commitList)
149
+ })
150
150
 
151
151
  const run = consumer.run
152
152
  const groupId = arguments[0].groupId
153
153
 
154
154
  consumer.run = function ({ eachMessage, eachBatch, ...runArgs }) {
155
155
  const wrapConsume = (clusterId) => {
156
+ // In kafkajs COMMIT_OFFSETS always happens in the context of one synchronous run
157
+ // So this will always reference a correct cluster id
158
+ resolvedClusterId = clusterId
156
159
  return run({
157
160
  eachMessage: wrappedCallback(
158
161
  eachMessage,
@@ -271,6 +271,7 @@ function getOnTestEndHandler (config) {
271
271
  const testStatuses = testsStatuses.get(testName)
272
272
 
273
273
  const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
274
+ const isLastEfdRetry = testStatuses.length === config.earlyFlakeDetectionNumRetries + 1
274
275
 
275
276
  if (test._ddIsAttemptToFix && isLastAttempt) {
276
277
  if (testStatuses.includes('fail')) {
@@ -283,6 +284,11 @@ function getOnTestEndHandler (config) {
283
284
  }
284
285
  }
285
286
 
287
+ if (test._ddIsEfdRetry && isLastEfdRetry &&
288
+ testStatuses.every(status => status === 'fail')) {
289
+ hasFailedAllRetries = true
290
+ }
291
+
286
292
  const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
287
293
  const isAtrRetry = config.isFlakyTestRetriesEnabled &&
288
294
  !test._ddIsAttemptToFix &&