dd-trace 5.97.0 → 5.98.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 (81) hide show
  1. package/index.d.ts +26 -2
  2. package/package.json +1 -1
  3. package/packages/datadog-instrumentations/src/cucumber.js +65 -3
  4. package/packages/datadog-instrumentations/src/cypress-config.js +31 -37
  5. package/packages/datadog-instrumentations/src/jest.js +104 -12
  6. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -0
  7. package/packages/datadog-instrumentations/src/redis.js +12 -6
  8. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  9. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -0
  10. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  11. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  12. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -0
  13. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -0
  14. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  15. package/packages/datadog-plugin-cucumber/src/index.js +6 -0
  16. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +109 -1
  17. package/packages/datadog-plugin-cypress/src/index.js +59 -2
  18. package/packages/datadog-plugin-fs/src/index.js +1 -1
  19. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -7
  21. package/packages/datadog-plugin-http/src/client.js +1 -1
  22. package/packages/datadog-plugin-http/src/server.js +10 -2
  23. package/packages/datadog-plugin-http2/src/client.js +1 -1
  24. package/packages/datadog-plugin-http2/src/server.js +10 -2
  25. package/packages/datadog-plugin-mongodb-core/src/index.js +3 -3
  26. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  27. package/packages/datadog-plugin-next/src/index.js +8 -2
  28. package/packages/datadog-plugin-pg/src/index.js +1 -1
  29. package/packages/datadog-plugin-tedious/src/index.js +1 -1
  30. package/packages/datadog-plugin-ws/src/close.js +1 -1
  31. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  32. package/packages/dd-trace/src/aiguard/sdk.js +22 -22
  33. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -3
  34. package/packages/dd-trace/src/appsec/blocking.js +62 -34
  35. package/packages/dd-trace/src/appsec/sdk/set_user.js +1 -1
  36. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -5
  37. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  38. package/packages/dd-trace/src/appsec/sdk/utils.js +4 -2
  39. package/packages/dd-trace/src/config/defaults.js +0 -1
  40. package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
  41. package/packages/dd-trace/src/config/index.js +55 -28
  42. package/packages/dd-trace/src/config/supported-configurations.json +61 -4
  43. package/packages/dd-trace/src/constants.js +1 -0
  44. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +5 -2
  45. package/packages/dd-trace/src/encode/0.4.js +7 -6
  46. package/packages/dd-trace/src/encode/span-stats.js +4 -1
  47. package/packages/dd-trace/src/log/index.js +0 -10
  48. package/packages/dd-trace/src/openfeature/remote_config.js +6 -1
  49. package/packages/dd-trace/src/opentelemetry/context_manager.js +6 -4
  50. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +17 -2
  51. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +14 -2
  52. package/packages/dd-trace/src/opentelemetry/otlp/trace.proto +358 -0
  53. package/packages/dd-trace/src/opentelemetry/otlp/trace_service.proto +78 -0
  54. package/packages/dd-trace/src/opentelemetry/trace/index.js +75 -0
  55. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +66 -0
  56. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +332 -0
  57. package/packages/dd-trace/src/opentracing/tracer.js +9 -4
  58. package/packages/dd-trace/src/plugins/log_plugin.js +3 -0
  59. package/packages/dd-trace/src/plugins/plugin.js +6 -11
  60. package/packages/dd-trace/src/plugins/storage.js +2 -2
  61. package/packages/dd-trace/src/plugins/tracing.js +22 -5
  62. package/packages/dd-trace/src/plugins/util/test.js +2 -0
  63. package/packages/dd-trace/src/plugins/util/web.js +6 -88
  64. package/packages/dd-trace/src/profiling/profiler.js +34 -77
  65. package/packages/dd-trace/src/proxy.js +8 -3
  66. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +15 -11
  67. package/packages/dd-trace/src/service-naming/index.js +1 -1
  68. package/packages/dd-trace/src/service-naming/schemas/definition.js +4 -1
  69. package/packages/dd-trace/src/service-naming/schemas/util.js +15 -1
  70. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +24 -1
  71. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +60 -0
  72. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +17 -1
  73. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +5 -0
  74. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +17 -0
  75. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +11 -1
  76. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +6 -0
  77. package/packages/dd-trace/src/span_stats.js +5 -1
  78. package/packages/dd-trace/src/tracer.js +2 -2
  79. package/vendor/dist/@apm-js-collab/code-transformer/index.js +28 -6
  80. package/vendor/dist/protobufjs/index.js +1 -1
  81. package/packages/dd-trace/src/log/utils.js +0 -16
package/index.d.ts CHANGED
@@ -665,7 +665,15 @@ declare namespace tracer {
665
665
  * @env DD_RUNTIME_METRICS_EVENT_LOOP_ENABLED
666
666
  * Programmatic configuration takes precedence over the environment variables listed above.
667
667
  */
668
- eventLoop?: boolean
668
+ eventLoop?: boolean,
669
+
670
+ /**
671
+ * Whether to use native metrics. When set to false, forces the JS implementation
672
+ * @default true
673
+ * @env DD_RUNTIME_METRICS_NATIVE
674
+ * Programmatic configuration takes precedence over the environment variables listed above.
675
+ */
676
+ native?: boolean
669
677
  }
670
678
 
671
679
  /**
@@ -795,7 +803,7 @@ declare namespace tracer {
795
803
  * Whether to request blocking mode when evaluating prompts via auto-instrumentation.
796
804
  * When `true`, AI Guard will block requests that violate security policies.
797
805
  * When `false`, AI Guard evaluates but never blocks (monitor-only mode).
798
- * @default false
806
+ * @default true
799
807
  * @env DD_AI_GUARD_BLOCK
800
808
  * Programmatic configuration takes precedence over the environment variables listed above.
801
809
  */
@@ -1847,6 +1855,10 @@ declare namespace tracer {
1847
1855
  * List of tags associated with the evaluation (e.g. indirect-prompt-injection)
1848
1856
  */
1849
1857
  tags: string[];
1858
+ /**
1859
+ * Dictionary of tag probabilities (e.g. { indirect-prompt-injection: 0.2, jailbreak-attempt: 0.8 })
1860
+ */
1861
+ tagProbabilities: { [key: string]: number }
1850
1862
  /**
1851
1863
  * Sensitive Data Scanner findings from the evaluation.
1852
1864
  */
@@ -1866,6 +1878,10 @@ declare namespace tracer {
1866
1878
  * List of tags associated with the evaluation (e.g. indirect-prompt-injection)
1867
1879
  */
1868
1880
  tags: string[];
1881
+ /**
1882
+ * Dictionary of tag probabilities (e.g. { indirect-prompt-injection: 0.2, jailbreak-attempt: 0.8 })
1883
+ */
1884
+ tagProbabilities: { [key: string]: number }
1869
1885
  /**
1870
1886
  * Sensitive Data Scanner findings from the evaluation.
1871
1887
  */
@@ -3048,6 +3064,14 @@ declare namespace tracer {
3048
3064
  * @returns true to instrument the command, false to skip it
3049
3065
  */
3050
3066
  filter?: (command: string) => boolean;
3067
+
3068
+ /**
3069
+ * Whether to use a different service name for each Redis instance based
3070
+ * on the configured connection name of the client.
3071
+ *
3072
+ * @default false
3073
+ */
3074
+ splitByInstance?: boolean;
3051
3075
  }
3052
3076
 
3053
3077
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.97.0",
3
+ "version": "5.98.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -235,6 +235,40 @@ function getPickleByFile (runtimeOrCoodinator) {
235
235
  }, {})
236
236
  }
237
237
 
238
+ function getFinalStatus ({
239
+ status,
240
+ hasFailedAllRetries,
241
+ isLastAtrRetry,
242
+ isLastEfdRetry,
243
+ isLastAttemptToFix,
244
+ hasPassedAllRetries,
245
+ isQuarantined,
246
+ isDisabled,
247
+ }) {
248
+ // Note that intermediate executions DO NOT report a final status tag
249
+
250
+ // If the test is quarantined or disabled, regardless of its actual execution result or active retry features,
251
+ // the final status of its last execution should be reported as 'skip'.
252
+ if (isQuarantined || isDisabled || status === 'skip') {
253
+ return 'skip'
254
+ }
255
+
256
+ // When no retry feature is active, every execution is final
257
+ if (!isLastAtrRetry && !isLastEfdRetry && !isLastAttemptToFix) {
258
+ return status
259
+ }
260
+
261
+ // ATR and EFD: pass unless every attempt failed
262
+ if (isLastAtrRetry || isLastEfdRetry) {
263
+ return hasFailedAllRetries ? 'fail' : 'pass'
264
+ }
265
+
266
+ // Branch for ATF (We need to check hasPassedAllRetries)
267
+ if (isLastAttemptToFix) {
268
+ return hasPassedAllRetries ? 'pass' : 'fail'
269
+ }
270
+ }
271
+
238
272
  function wrapRun (pl, isLatestVersion, version) {
239
273
  if (patched.has(pl)) return
240
274
 
@@ -262,7 +296,7 @@ function wrapRun (pl, isLatestVersion, version) {
262
296
  testStartCh.runStores(ctx, () => {})
263
297
  const promises = {}
264
298
  try {
265
- this.eventBroadcaster.on('envelope', async (testCase) => {
299
+ const onEnvelope = async (testCase) => {
266
300
  // Only supported from >=8.0.0
267
301
  if (testCase?.testCaseFinished) {
268
302
  const { testCaseFinished: { willBeRetried } } = testCase
@@ -280,7 +314,7 @@ function wrapRun (pl, isLatestVersion, version) {
280
314
  const isAtrRetry = !isFirstAttempt && isFlakyTestRetriesEnabled
281
315
 
282
316
  // ATR: record this attempt as failed so when run().finally runs (after retry) we have all statuses
283
- if (isFlakyTestRetriesEnabled && isAtrRetry === false) {
317
+ if (isFlakyTestRetriesEnabled) {
284
318
  const nameForKey = this.pickle.name.replace(/\s*\(attempt \d+(?:, retried)?\)\s*$/, '')
285
319
  const atrKey = `${this.pickle.uri}:${nameForKey}`
286
320
  if (atrStatusesByScenarioKey.has(atrKey)) {
@@ -303,13 +337,15 @@ function wrapRun (pl, isLatestVersion, version) {
303
337
  testStartCh.runStores(newCtx, () => {})
304
338
  }
305
339
  }
306
- })
340
+ }
341
+ this.eventBroadcaster.on('envelope', onEnvelope)
307
342
  let promise
308
343
 
309
344
  testFnCh.runStores(ctx, () => {
310
345
  promise = run.apply(this, arguments)
311
346
  })
312
347
  promise.finally(async () => {
348
+ this.eventBroadcaster.removeListener('envelope', onEnvelope)
313
349
  const result = this.getWorstStepResult()
314
350
  const { status, skipReason } = isLatestVersion
315
351
  ? getStatusFromResultLatest(result)
@@ -408,6 +444,31 @@ function wrapRun (pl, isLatestVersion, version) {
408
444
  if (promises.hitBreakpointPromise) {
409
445
  await promises.hitBreakpointPromise
410
446
  }
447
+
448
+ // Notice that ATR is handled using cucumber native retries features.
449
+ // Therefore, if we reach this point, we are certain that it's the last ATR execution
450
+ const isLastAtrRetry = isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry && numTestRetries > 0
451
+
452
+ const statuses = lastStatusByPickleId.get(this.pickle.id)
453
+ const isLastEfdRetry = isEfdRetry && statuses?.length === earlyFlakeDetectionNumRetries + 1
454
+ const isLastAttemptToFixRetry = isAttemptToFix && statuses?.length === testManagementAttemptToFixRetries + 1
455
+
456
+ // Intermediate (non-last EFD or ATF retries) executions do not report a final status
457
+ const isIntermediateExecution = (isEfdRetry && !isLastEfdRetry) || (isAttemptToFix && !isLastAttemptToFixRetry)
458
+
459
+ const finalStatus = isIntermediateExecution
460
+ ? undefined
461
+ : getFinalStatus({
462
+ status,
463
+ hasFailedAllRetries,
464
+ isLastAtrRetry,
465
+ isLastEfdRetry,
466
+ isLastAttemptToFix: isLastAttemptToFixRetry,
467
+ hasPassedAllRetries,
468
+ isQuarantined,
469
+ isDisabled,
470
+ })
471
+
411
472
  testFinishCh.publish({
412
473
  status,
413
474
  skipReason,
@@ -424,6 +485,7 @@ function wrapRun (pl, isLatestVersion, version) {
424
485
  isQuarantined,
425
486
  isModified,
426
487
  ...attemptCtx.currentStore,
488
+ finalStatus,
427
489
  })
428
490
  })
429
491
  return promise
@@ -4,9 +4,21 @@ const fs = require('fs')
4
4
  const os = require('os')
5
5
  const path = require('path')
6
6
  const { pathToFileURL } = require('url')
7
+ const { channel } = require('./helpers/instrument')
7
8
 
8
9
  const DD_CONFIG_WRAPPED = Symbol('dd-trace.cypress.config.wrapped')
9
10
 
11
+ const setupNodeEventsCh = channel('ci:cypress:setup-node-events')
12
+
13
+ // Ensure the cypress plugin is loaded so it can subscribe to our channel.
14
+ // Normally, plugins are loaded when their npm module is required (via addHook),
15
+ // but plain-object configs don't require('cypress'), so the plugin would never
16
+ // be instantiated in the Cypress Config Manager child process.
17
+ const loadCh = channel('dd-trace:instrumentation:load')
18
+ if (loadCh.hasSubscribers) {
19
+ loadCh.publish({ name: 'cypress' })
20
+ }
21
+
10
22
  const noopTask = {
11
23
  'dd:testSuiteStart': () => null,
12
24
  'dd:beforeEach': () => ({}),
@@ -93,8 +105,9 @@ function injectSupportFile (config) {
93
105
 
94
106
  /**
95
107
  * Registers dd-trace's Cypress hooks (before:run, after:spec, after:run, tasks)
96
- * and injects the support file. Handles chaining with user-registered handlers
97
- * for after:spec/after:run so both the user's code and dd-trace's run in sequence.
108
+ * and injects the support file. Communicates with the plugin layer via
109
+ * the `ci:cypress:setup-node-events` diagnostic channel, avoiding direct
110
+ * tracer references in the instrumentation layer.
98
111
  *
99
112
  * @param {Function} on Cypress event registration function
100
113
  * @param {object} config Cypress resolved config object
@@ -111,8 +124,6 @@ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHa
111
124
  }
112
125
  }
113
126
 
114
- const tracer = global._ddtrace
115
-
116
127
  const registerAfterRunWithCleanup = () => {
117
128
  on('after:run', (results) => {
118
129
  const chain = userAfterRunHandlers.reduce(
@@ -129,49 +140,32 @@ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHa
129
140
  on('task', noopTask)
130
141
  }
131
142
 
132
- if (!tracer || !tracer._initialized) {
143
+ if (!setupNodeEventsCh.hasSubscribers) {
133
144
  registerNoopHandlers()
134
145
  return config
135
146
  }
136
147
 
137
- const NoopTracer = require('../../../packages/dd-trace/src/noop/tracer')
138
-
139
- if (tracer._tracer instanceof NoopTracer) {
140
- registerNoopHandlers()
141
- return config
148
+ // Publish to the plugin layer via diagnostic channel.
149
+ // The subscriber sets `payload.registered = true` and optionally
150
+ // `payload.configPromise` when it handles the event.
151
+ const payload = {
152
+ on,
153
+ config,
154
+ userAfterSpecHandlers,
155
+ userAfterRunHandlers,
156
+ cleanupWrapper,
157
+ registered: false,
158
+ configPromise: undefined,
142
159
  }
143
160
 
144
- const cypressPlugin = require('../../../packages/datadog-plugin-cypress/src/cypress-plugin')
161
+ setupNodeEventsCh.publish(payload)
145
162
 
146
- if (cypressPlugin._isInit) {
147
- for (const h of userAfterSpecHandlers) on('after:spec', h)
148
- registerAfterRunWithCleanup()
163
+ if (!payload.registered) {
164
+ registerNoopHandlers()
149
165
  return config
150
166
  }
151
167
 
152
- on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
153
-
154
- on('after:spec', (spec, results) => {
155
- const chain = userAfterSpecHandlers.reduce(
156
- (p, h) => p.then(() => h(spec, results)),
157
- Promise.resolve()
158
- )
159
- return chain.then(() => cypressPlugin.afterSpec(spec, results))
160
- })
161
-
162
- on('after:run', (results) => {
163
- const chain = userAfterRunHandlers.reduce(
164
- (p, h) => p.then(() => h(results)),
165
- Promise.resolve()
166
- )
167
- return chain
168
- .then(() => cypressPlugin.afterRun(results))
169
- .finally(cleanupWrapper)
170
- })
171
-
172
- on('task', cypressPlugin.getTasks())
173
-
174
- return Promise.resolve(cypressPlugin.init(tracer, config)).then(() => config)
168
+ return payload.configPromise || config
175
169
  }
176
170
 
177
171
  /**
@@ -11,6 +11,7 @@ const {
11
11
  JEST_WORKER_TRACE_PAYLOAD_CODE,
12
12
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
13
13
  JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
14
+ JEST_WORKER_QUARANTINE_PAYLOAD_CODE,
14
15
  getTestLineStart,
15
16
  getTestSuitePath,
16
17
  getTestParametersString,
@@ -123,6 +124,38 @@ const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
123
124
  const ATR_RETRY_SUPPRESSION_FLAG = '_ddDisableAtrRetry'
124
125
  const atrSuppressedErrors = new Map()
125
126
 
127
+ // Track quarantined tests whose errors were suppressed, keyed by "suite › testName"
128
+ const quarantinedFailingTests = new Set()
129
+
130
+ /**
131
+ * Sends suppressed quarantine test names from a worker process to the main process.
132
+ * Supports both child_process (process.send) and worker_threads (parentPort.postMessage).
133
+ * Returns true if the data was sent (worker mode), false if in main process (runInBand).
134
+ *
135
+ * @param {string[]} testNames
136
+ * @returns {boolean}
137
+ */
138
+ function sendQuarantineInfoToMainProcess (testNames) {
139
+ const payload = [JEST_WORKER_QUARANTINE_PAYLOAD_CODE, JSON.stringify(testNames)]
140
+
141
+ if (process.send) {
142
+ process.send(payload)
143
+ return true
144
+ }
145
+
146
+ try {
147
+ const { isMainThread, parentPort } = require('node:worker_threads')
148
+ if (!isMainThread && parentPort) {
149
+ parentPort.postMessage(payload)
150
+ return true
151
+ }
152
+ } catch {
153
+ // Not in a worker context
154
+ }
155
+
156
+ return false
157
+ }
158
+
126
159
  // based on https://github.com/facebook/jest/blob/main/packages/jest-circus/src/formatNodeAssertErrors.ts#L41
127
160
  function formatJestError (errors) {
128
161
  let error
@@ -756,6 +789,18 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
756
789
  const willBeRetriedByFailedTestReplay = numRetries > 0 && numTestExecutions - 1 < numRetries
757
790
  const mightHitBreakpoint = this.isDiEnabled && numTestExecutions >= 2
758
791
 
792
+ // For quarantined tests, suppress errors so Jest doesn't count them as failures.
793
+ // This prevents --bail from stopping the test run on quarantined test failures.
794
+ // The actual status ('fail') is already captured above for dd-trace reporting.
795
+ // Only suppress on the final execution — not when ATR/EFD/ATF will retry the test.
796
+ if (!event.test?.[ATR_RETRY_SUPPRESSION_FLAG] && !willBeRetriedByFailedTestReplay) {
797
+ const quarantineCtx = testContexts.get(event.test)
798
+ if (quarantineCtx?.isQuarantined && event.test.errors?.length) {
799
+ quarantinedFailingTests.add(`${quarantineCtx.suite} › ${quarantineCtx.name}`)
800
+ event.test.errors = []
801
+ }
802
+ }
803
+
759
804
  const ctx = testContexts.get(event.test)
760
805
  if (!ctx) {
761
806
  log.warn('"ci:jest:test_done": no context found for test "%s"', testName)
@@ -813,9 +858,25 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
813
858
  }
814
859
  if (event.name === 'run_finish') {
815
860
  for (const [test, errors] of atrSuppressedErrors) {
816
- test.errors = errors
861
+ // Do not restore errors for quarantined tests — they should stay suppressed
862
+ // so Jest doesn't see the failure (prevents --bail from stopping the run).
863
+ const ctx = testContexts.get(test)
864
+ if (ctx?.isQuarantined) {
865
+ const testName = getJestTestName(test, this.getShouldStripSeedFromTestName())
866
+ quarantinedFailingTests.add(`${ctx.suite} › ${testName}`)
867
+ } else {
868
+ test.errors = errors
869
+ }
817
870
  }
818
871
  atrSuppressedErrors.clear()
872
+
873
+ // In parallel mode, send suppressed quarantine info to the main process
874
+ // so it can include them in the session summary.
875
+ // In runInBand mode, keep the set — it will be consumed by the session-level code directly.
876
+ if (quarantinedFailingTests.size > 0 && sendQuarantineInfoToMainProcess([...quarantinedFailingTests])) {
877
+ quarantinedFailingTests.clear()
878
+ }
879
+
819
880
  efdDeterminedRetries.clear()
820
881
  efdSlowAbortedTests.clear()
821
882
  efdNewTestCandidates.clear()
@@ -1255,6 +1316,7 @@ function getCliWrapper (isNewJestVersion) {
1255
1316
 
1256
1317
  let numFailedQuarantinedTests = 0
1257
1318
  let numFailedQuarantinedOrDisabledAttemptedToFixTests = 0
1319
+ let numSuppressedQuarantinedTests = 0
1258
1320
  if (isTestManagementTestsEnabled) {
1259
1321
  const failedTests = result
1260
1322
  .results
@@ -1291,45 +1353,69 @@ function getCliWrapper (isNewJestVersion) {
1291
1353
  }
1292
1354
  }
1293
1355
 
1356
+ // Include quarantined tests whose errors were suppressed at test_done time.
1357
+ // These tests don't appear as failed in Jest's results because their errors were cleared
1358
+ // to prevent --bail from stopping the run, but they should still be counted for the summary.
1359
+ for (const name of quarantinedFailingTests) {
1360
+ if (!quarantineIgnoredNames.includes(name)) {
1361
+ numSuppressedQuarantinedTests++
1362
+ quarantineIgnoredNames.push(name)
1363
+ }
1364
+ }
1365
+ quarantinedFailingTests.clear()
1366
+
1294
1367
  // If every test that failed was quarantined, we'll consider the suite passed
1295
1368
  // Note that if a test is attempted to fix,
1296
1369
  // it's considered quarantined both if it's disabled and if it's quarantined
1297
1370
  // (it'll run but its status is ignored)
1298
1371
  // Skip if EFD block already flipped (to avoid logging twice)
1372
+ // Only use visible failures (from Jest results) for the flip check.
1373
+ // Suppressed quarantine failures are not in numFailedTests.
1374
+ const visibleQuarantineFailures = numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
1299
1375
  if (
1300
1376
  !result.results.success &&
1301
1377
  !mustNotFlipSuccess &&
1302
- (numFailedQuarantinedOrDisabledAttemptedToFixTests !== 0 || numFailedQuarantinedTests !== 0) &&
1303
- result.results.numFailedTests ===
1304
- numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
1378
+ visibleQuarantineFailures !== 0 &&
1379
+ result.results.numFailedTests === visibleQuarantineFailures
1305
1380
  ) {
1306
1381
  result.results.success = true
1307
- ignoredFailuresSummary = {
1308
- efdNames: [],
1309
- quarantineNames: quarantineIgnoredNames,
1310
- totalCount: numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests,
1382
+ }
1383
+
1384
+ const totalQuarantineFailures = visibleQuarantineFailures + numSuppressedQuarantinedTests
1385
+ if (totalQuarantineFailures > 0) {
1386
+ if (ignoredFailuresSummary) {
1387
+ ignoredFailuresSummary.quarantineNames = quarantineIgnoredNames
1388
+ ignoredFailuresSummary.totalCount += totalQuarantineFailures
1389
+ } else {
1390
+ ignoredFailuresSummary = {
1391
+ efdNames: [],
1392
+ quarantineNames: quarantineIgnoredNames,
1393
+ totalCount: totalQuarantineFailures,
1394
+ }
1311
1395
  }
1312
1396
  }
1313
1397
  }
1314
1398
 
1315
1399
  // Combined check: if all failed tests are accounted for by EFD (flaky retries) and/or quarantine,
1316
1400
  // we should consider the suite passed even when neither check alone covers all failures.
1401
+ // Only visible failures (in Jest results) are compared — suppressed quarantine failures
1402
+ // are already removed from numFailedTests at test_done time.
1317
1403
  if (
1318
1404
  !result.results.success &&
1319
1405
  !mustNotFlipSuccess &&
1320
1406
  (isEarlyFlakeDetectionEnabled || isTestManagementTestsEnabled)
1321
1407
  ) {
1322
- const totalIgnoredFailures =
1408
+ const visibleIgnoredFailures =
1323
1409
  numEfdFailedTestsToIgnore + numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
1324
1410
  if (
1325
- totalIgnoredFailures !== 0 &&
1326
- result.results.numFailedTests === totalIgnoredFailures
1411
+ visibleIgnoredFailures !== 0 &&
1412
+ result.results.numFailedTests === visibleIgnoredFailures
1327
1413
  ) {
1328
1414
  result.results.success = true
1329
1415
  ignoredFailuresSummary = {
1330
1416
  efdNames: efdIgnoredNames,
1331
1417
  quarantineNames: quarantineIgnoredNames,
1332
- totalCount: totalIgnoredFailures,
1418
+ totalCount: visibleIgnoredFailures + numSuppressedQuarantinedTests,
1333
1419
  }
1334
1420
  }
1335
1421
  }
@@ -1865,6 +1951,12 @@ function onMessageWrapper (onMessage) {
1865
1951
  workerReportTelemetryCh.publish(data)
1866
1952
  return
1867
1953
  }
1954
+ if (code === JEST_WORKER_QUARANTINE_PAYLOAD_CODE) { // quarantined test failures suppressed in worker
1955
+ for (const name of JSON.parse(data)) {
1956
+ quarantinedFailingTests.add(name)
1957
+ }
1958
+ return
1959
+ }
1868
1960
  return onMessage.apply(this, arguments)
1869
1961
  }
1870
1962
  }
@@ -264,6 +264,14 @@ function getFinalStatus ({
264
264
  }) {
265
265
  // Note that intermediate executions DO NOT report a final status tag
266
266
 
267
+ // Intermediate EFD and ATF executions must not carry a final status, regardless of quarantine/disabled state
268
+ const isIntermediateExecution =
269
+ (isEfdRetry && !isLastEfdRetry) ||
270
+ (isAttemptToFix && !isLastAttemptToFix)
271
+ if (isIntermediateExecution) {
272
+ return
273
+ }
274
+
267
275
  // If the test is quarantined or disabled, regardless of its actual execution result or active retry features,
268
276
  // the final status of its last execution should be reported as 'skip'.
269
277
  if (isQuarantined || isDisabled) {
@@ -11,6 +11,8 @@ const finishCh = channel('apm:redis:command:finish')
11
11
  const errorCh = channel('apm:redis:command:error')
12
12
 
13
13
  let createClientUrl
14
+ let createClientName
15
+ const instanceInfo = new WeakMap()
14
16
 
15
17
  function wrapAddCommand (addCommand) {
16
18
  return function (command) {
@@ -21,7 +23,7 @@ function wrapAddCommand (addCommand) {
21
23
  const name = command[0]
22
24
  const args = command.slice(1)
23
25
 
24
- const ctx = getStartCtx(this, name, args, this._url)
26
+ const ctx = getStartCtx(this, name, args)
25
27
  return startCh.runStores(ctx, () => {
26
28
  const res = addCommand.apply(this, arguments)
27
29
 
@@ -36,17 +38,16 @@ function wrapCommandQueueClass (cls) {
36
38
  const ret = class RedisCommandQueue extends cls {
37
39
  constructor (...args) {
38
40
  super(...args)
41
+ let url = { host: 'localhost', port: 6379 }
39
42
  if (createClientUrl) {
40
43
  try {
41
44
  const parsed = new URL(createClientUrl)
42
- if (parsed) {
43
- this._url = { host: parsed.hostname, port: Number(parsed.port) || 6379 }
44
- }
45
+ url = { host: parsed.hostname, port: Number(parsed.port) || 6379 }
45
46
  } catch {
46
47
  // ignore
47
48
  }
48
49
  }
49
- this._url = this._url || { host: 'localhost', port: 6379 }
50
+ instanceInfo.set(this, { connectionName: createClientName, url })
50
51
  }
51
52
  }
52
53
  return ret
@@ -55,8 +56,10 @@ function wrapCommandQueueClass (cls) {
55
56
  function wrapCreateClient (request) {
56
57
  return function (opts) {
57
58
  createClientUrl = opts && opts.url
59
+ createClientName = opts && opts.name
58
60
  const ret = request.apply(this, arguments)
59
61
  createClientUrl = undefined
62
+ createClientName = undefined
60
63
  return ret
61
64
  }
62
65
  }
@@ -134,12 +137,15 @@ addHook({ name: 'redis', versions: ['>=0.12 <2.6'] }, redis => {
134
137
  return redis
135
138
  })
136
139
 
137
- function getStartCtx (client, command, args, url = {}) {
140
+ function getStartCtx (client, command, args) {
141
+ const { url, connectionName } = instanceInfo.get(client) || {}
142
+
138
143
  return {
139
144
  db: client.selected_db,
140
145
  command,
141
146
  args,
142
147
  connectionOptions: client.connection_options || client.connection_option || client.connectionOption || url,
148
+ connectionName,
143
149
  }
144
150
  }
145
151
 
@@ -56,7 +56,6 @@ class BaseAwsSdkPlugin extends ClientPlugin {
56
56
 
57
57
  const meta = {
58
58
  'span.kind': 'client',
59
- 'service.name': this.serviceName(),
60
59
  'aws.operation': operation,
61
60
  'aws.region': awsRegion,
62
61
  region: awsRegion,
@@ -70,6 +69,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
70
69
  const span = this.startSpan(this.operationFromRequest(request), {
71
70
  childOf,
72
71
  meta,
72
+ service: this.serviceName(),
73
73
  integrationName: 'aws-sdk',
74
74
  }, ctx)
75
75
 
@@ -40,6 +40,7 @@ class DynamoDb extends BaseAwsSdkPlugin {
40
40
 
41
41
  // Also add span type to match serverless convention
42
42
  tags['span.type'] = 'dynamodb'
43
+ tags['db.system'] = 'aws.dynamodb'
43
44
 
44
45
  return tags
45
46
  }
@@ -12,6 +12,7 @@ class EventBridge extends BaseAwsSdkPlugin {
12
12
  return {
13
13
  'resource.name': operation ? `${operation} ${params.source}` : params.source,
14
14
  'aws.eventbridge.source': `${params.source}`,
15
+ 'messaging.system': 'aws_eventbridge',
15
16
  rulename: `${rulename}`,
16
17
  }
17
18
  }
@@ -68,6 +68,7 @@ class Kinesis extends BaseAwsSdkPlugin {
68
68
  return {
69
69
  'resource.name': `${operation} ${params.StreamName}`,
70
70
  'aws.kinesis.stream_name': params.StreamName,
71
+ 'messaging.system': 'aws_kinesis',
71
72
  streamname: params.StreamName,
72
73
  }
73
74
  }
@@ -11,6 +11,7 @@ class Redshift extends BaseAwsSdkPlugin {
11
11
  return {
12
12
  'resource.name': `${operation} ${params.ClusterIdentifier}`,
13
13
  'aws.redshift.cluster_identifier': params.ClusterIdentifier,
14
+ 'db.system': 'aws.redshift',
14
15
  clusteridentifier: params.ClusterIdentifier,
15
16
  }
16
17
  }
@@ -24,6 +24,7 @@ class Sns extends BaseAwsSdkPlugin {
24
24
  return {
25
25
  'resource.name': `${operation} ${params.TopicArn || response.data.TopicArn}`,
26
26
  'aws.sns.topic_arn': TopicArn,
27
+ 'messaging.system': 'aws.sns',
27
28
  topicname: topicName,
28
29
  }
29
30
 
@@ -100,6 +100,7 @@ class Sqs extends BaseAwsSdkPlugin {
100
100
  const tags = {
101
101
  'resource.name': `${operation} ${params.QueueName || params.QueueUrl}`,
102
102
  'aws.sqs.queue_name': params.QueueName || params.QueueUrl,
103
+ 'messaging.system': 'aws_sqs',
103
104
  queuename: queueName,
104
105
  }
105
106
 
@@ -37,6 +37,7 @@ const {
37
37
  TEST_SOURCE_FILE,
38
38
  TEST_SOURCE_START,
39
39
  TEST_STATUS,
40
+ TEST_FINAL_STATUS,
40
41
  } = require('../../dd-trace/src/plugins/util/test')
41
42
  const { RESOURCE_NAME } = require('../../../ext/tags')
42
43
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -307,11 +308,16 @@ class CucumberPlugin extends CiPlugin {
307
308
  isDisabled,
308
309
  isQuarantined,
309
310
  isModified,
311
+ finalStatus,
310
312
  }) => {
311
313
  const statusTag = isStep ? 'step.status' : TEST_STATUS
312
314
 
313
315
  span.setTag(statusTag, status)
314
316
 
317
+ if (finalStatus) {
318
+ span.setTag(TEST_FINAL_STATUS, finalStatus)
319
+ }
320
+
315
321
  if (isNew) {
316
322
  span.setTag(TEST_IS_NEW, 'true')
317
323
  if (isEfdRetry) {