dd-trace 5.35.0 → 5.37.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 (125) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +8 -7
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +15 -14
  5. package/packages/datadog-core/index.js +1 -1
  6. package/packages/datadog-core/src/storage.js +76 -31
  7. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  8. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  10. package/packages/datadog-instrumentations/src/jest.js +105 -11
  11. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  13. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  14. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  15. package/packages/datadog-instrumentations/src/openai.js +8 -0
  16. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  17. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  18. package/packages/datadog-plugin-aerospike/src/index.js +1 -1
  19. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +1 -1
  20. package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
  21. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -1
  22. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -3
  23. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -4
  24. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -2
  25. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  26. package/packages/datadog-plugin-couchbase/src/index.js +2 -2
  27. package/packages/datadog-plugin-cucumber/src/index.js +31 -14
  28. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +72 -7
  29. package/packages/datadog-plugin-cypress/src/support.js +36 -29
  30. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  31. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  32. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  33. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  34. package/packages/datadog-plugin-hapi/src/index.js +1 -1
  35. package/packages/datadog-plugin-http/src/client.js +1 -1
  36. package/packages/datadog-plugin-http/src/server.js +1 -1
  37. package/packages/datadog-plugin-http2/src/client.js +3 -3
  38. package/packages/datadog-plugin-http2/src/server.js +1 -1
  39. package/packages/datadog-plugin-jest/src/index.js +17 -12
  40. package/packages/datadog-plugin-langchain/src/tracing.js +1 -1
  41. package/packages/datadog-plugin-mariadb/src/index.js +3 -3
  42. package/packages/datadog-plugin-mocha/src/index.js +35 -16
  43. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  44. package/packages/datadog-plugin-next/src/index.js +4 -4
  45. package/packages/datadog-plugin-openai/src/tracing.js +2 -3
  46. package/packages/datadog-plugin-playwright/src/index.js +35 -9
  47. package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
  48. package/packages/datadog-plugin-router/src/index.js +2 -2
  49. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  50. package/packages/datadog-plugin-vitest/src/index.js +36 -12
  51. package/packages/dd-trace/src/appsec/graphql.js +6 -6
  52. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +3 -7
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  54. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +17 -30
  55. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +2 -2
  56. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -11
  57. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  58. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  59. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +24 -4
  60. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +2 -2
  62. package/packages/dd-trace/src/appsec/iast/index.js +4 -2
  63. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  64. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  65. package/packages/dd-trace/src/appsec/iast/taint-tracking/constants.js +6 -0
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  67. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +8 -8
  68. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +1 -1
  69. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +65 -0
  70. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +14 -5
  71. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +80 -2
  72. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
  75. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  76. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  78. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  79. package/packages/dd-trace/src/appsec/index.js +4 -4
  80. package/packages/dd-trace/src/appsec/rasp/command_injection.js +5 -5
  81. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +5 -5
  82. package/packages/dd-trace/src/appsec/rasp/lfi.js +3 -3
  83. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +4 -4
  84. package/packages/dd-trace/src/appsec/rasp/ssrf.js +3 -3
  85. package/packages/dd-trace/src/appsec/reporter.js +3 -3
  86. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  87. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +2 -0
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +31 -56
  90. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  91. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  92. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  93. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +3 -3
  94. package/packages/dd-trace/src/config.js +18 -3
  95. package/packages/dd-trace/src/data_streams_context.js +2 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  97. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  98. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  99. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  100. package/packages/dd-trace/src/exporters/common/request.js +3 -3
  101. package/packages/dd-trace/src/iitm.js +2 -2
  102. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +1 -1
  103. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  104. package/packages/dd-trace/src/log/writer.js +3 -3
  105. package/packages/dd-trace/src/noop/span.js +1 -1
  106. package/packages/dd-trace/src/opentracing/propagation/text_map.js +5 -4
  107. package/packages/dd-trace/src/opentracing/span.js +3 -3
  108. package/packages/dd-trace/src/plugin_manager.js +3 -1
  109. package/packages/dd-trace/src/plugins/apollo.js +1 -1
  110. package/packages/dd-trace/src/plugins/ci_plugin.js +51 -4
  111. package/packages/dd-trace/src/plugins/database.js +14 -4
  112. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  113. package/packages/dd-trace/src/plugins/plugin.js +8 -8
  114. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  115. package/packages/dd-trace/src/plugins/util/git.js +3 -3
  116. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  117. package/packages/dd-trace/src/plugins/util/test.js +10 -4
  118. package/packages/dd-trace/src/profiling/exporters/agent.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  120. package/packages/dd-trace/src/proxy.js +5 -1
  121. package/packages/dd-trace/src/ritm.js +2 -1
  122. package/packages/dd-trace/src/scope.js +5 -5
  123. package/packages/dd-trace/src/spanleak.js +0 -1
  124. package/packages/dd-trace/src/tracer.js +0 -14
  125. package/packages/memwatch/package.json +0 -9
@@ -9,6 +9,7 @@ const testPassCh = channel('ci:vitest:test:pass')
9
9
  const testErrorCh = channel('ci:vitest:test:error')
10
10
  const testSkipCh = channel('ci:vitest:test:skip')
11
11
  const isNewTestCh = channel('ci:vitest:test:is-new')
12
+ const isQuarantinedCh = channel('ci:vitest:test:is-quarantined')
12
13
 
13
14
  // test suite hooks
14
15
  const testSuiteStartCh = channel('ci:vitest:test-suite:start')
@@ -21,10 +22,12 @@ const testSessionFinishCh = channel('ci:vitest:session:finish')
21
22
  const libraryConfigurationCh = channel('ci:vitest:library-configuration')
22
23
  const knownTestsCh = channel('ci:vitest:known-tests')
23
24
  const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detection-faulty')
25
+ const quarantinedTestsCh = channel('ci:vitest:quarantined-tests')
24
26
 
25
27
  const taskToAsync = new WeakMap()
26
28
  const taskToStatuses = new WeakMap()
27
29
  const newTasks = new WeakSet()
30
+ const quarantinedTasks = new WeakSet()
28
31
  let isRetryReasonEfd = false
29
32
  const switchedStatuses = new WeakSet()
30
33
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
@@ -46,7 +49,9 @@ function getProvidedContext () {
46
49
  _ddIsDiEnabled,
47
50
  _ddKnownTests: knownTests,
48
51
  _ddEarlyFlakeDetectionNumRetries: numRepeats,
49
- _ddIsKnownTestsEnabled: isKnownTestsEnabled
52
+ _ddIsKnownTestsEnabled: isKnownTestsEnabled,
53
+ _ddIsQuarantinedTestsEnabled: isQuarantinedTestsEnabled,
54
+ _ddQuarantinedTests: quarantinedTests
50
55
  } = globalThis.__vitest_worker__.providedContext
51
56
 
52
57
  return {
@@ -54,7 +59,9 @@ function getProvidedContext () {
54
59
  isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled,
55
60
  knownTests,
56
61
  numRepeats,
57
- isKnownTestsEnabled
62
+ isKnownTestsEnabled,
63
+ isQuarantinedTestsEnabled,
64
+ quarantinedTests
58
65
  }
59
66
  } catch (e) {
60
67
  log.error('Vitest workers could not parse provided context, so some features will not work.')
@@ -63,7 +70,9 @@ function getProvidedContext () {
63
70
  isEarlyFlakeDetectionEnabled: false,
64
71
  knownTests: {},
65
72
  numRepeats: 0,
66
- isKnownTestsEnabled: false
73
+ isKnownTestsEnabled: false,
74
+ isQuarantinedTestsEnabled: false,
75
+ quarantinedTests: {}
67
76
  }
68
77
  }
69
78
  }
@@ -158,8 +167,10 @@ function getSortWrapper (sort) {
158
167
  let earlyFlakeDetectionNumRetries = 0
159
168
  let isEarlyFlakeDetectionFaulty = false
160
169
  let isKnownTestsEnabled = false
170
+ let isQuarantinedTestsEnabled = false
161
171
  let isDiEnabled = false
162
172
  let knownTests = {}
173
+ let quarantinedTests = {}
163
174
 
164
175
  try {
165
176
  const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
@@ -170,6 +181,7 @@ function getSortWrapper (sort) {
170
181
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
171
182
  isDiEnabled = libraryConfig.isDiEnabled
172
183
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
184
+ isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
173
185
  }
174
186
  } catch (e) {
175
187
  isFlakyTestRetriesEnabled = false
@@ -229,6 +241,23 @@ function getSortWrapper (sort) {
229
241
  }
230
242
  }
231
243
 
244
+ if (isQuarantinedTestsEnabled) {
245
+ const { err, quarantinedTests: receivedQuarantinedTests } = await getChannelPromise(quarantinedTestsCh)
246
+ if (!err) {
247
+ quarantinedTests = receivedQuarantinedTests
248
+ try {
249
+ const workspaceProject = this.ctx.getCoreWorkspaceProject()
250
+ workspaceProject._provided._ddIsQuarantinedTestsEnabled = isQuarantinedTestsEnabled
251
+ workspaceProject._provided._ddQuarantinedTests = quarantinedTests
252
+ } catch (e) {
253
+ log.warn('Could not send quarantined tests to workers so Quarantine will not work.')
254
+ }
255
+ } else {
256
+ isQuarantinedTestsEnabled = false
257
+ log.error('Could not get quarantined tests.')
258
+ }
259
+ }
260
+
232
261
  let testCodeCoverageLinesTotal
233
262
 
234
263
  if (this.ctx.coverageProvider?.generateCoverage) {
@@ -263,6 +292,7 @@ function getSortWrapper (sort) {
263
292
  error,
264
293
  isEarlyFlakeDetectionEnabled,
265
294
  isEarlyFlakeDetectionFaulty,
295
+ isQuarantinedTestsEnabled,
266
296
  onFinish
267
297
  })
268
298
  })
@@ -332,7 +362,7 @@ addHook({
332
362
 
333
363
  // `onAfterRunTask` is run after all repetitions or attempts are run
334
364
  shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => async function (task) {
335
- const { isEarlyFlakeDetectionEnabled } = getProvidedContext()
365
+ const { isEarlyFlakeDetectionEnabled, isQuarantinedTestsEnabled } = getProvidedContext()
336
366
 
337
367
  if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task)) {
338
368
  const statuses = taskToStatuses.get(task)
@@ -345,6 +375,12 @@ addHook({
345
375
  }
346
376
  }
347
377
 
378
+ if (isQuarantinedTestsEnabled) {
379
+ if (quarantinedTasks.has(task)) {
380
+ task.result.state = 'pass'
381
+ }
382
+ }
383
+
348
384
  return onAfterRunTask.apply(this, arguments)
349
385
  })
350
386
 
@@ -356,17 +392,34 @@ addHook({
356
392
  }
357
393
  const testName = getTestName(task)
358
394
  let isNew = false
395
+ let isQuarantined = false
359
396
 
360
397
  const {
361
398
  isKnownTestsEnabled,
362
399
  isEarlyFlakeDetectionEnabled,
363
- isDiEnabled
400
+ isDiEnabled,
401
+ isQuarantinedTestsEnabled,
402
+ quarantinedTests
364
403
  } = getProvidedContext()
365
404
 
366
405
  if (isKnownTestsEnabled) {
367
406
  isNew = newTasks.has(task)
368
407
  }
369
408
 
409
+ if (isQuarantinedTestsEnabled) {
410
+ isQuarantinedCh.publish({
411
+ quarantinedTests,
412
+ testSuiteAbsolutePath: task.file.filepath,
413
+ testName,
414
+ onDone: (isTestQuarantined) => {
415
+ isQuarantined = isTestQuarantined
416
+ if (isTestQuarantined) {
417
+ quarantinedTasks.add(task)
418
+ }
419
+ }
420
+ })
421
+ }
422
+
370
423
  const { retry: numAttempt, repeats: numRepetition } = retryInfo
371
424
 
372
425
  // We finish the previous test here because we know it has failed already
@@ -448,7 +501,8 @@ addHook({
448
501
  isRetry: numAttempt > 0 || numRepetition > 0,
449
502
  isRetryReasonEfd,
450
503
  isNew,
451
- mightHitProbe: isDiEnabled && numAttempt > 0
504
+ mightHitProbe: isDiEnabled && numAttempt > 0,
505
+ isQuarantined
452
506
  })
453
507
  })
454
508
  return onBeforeTryTask.apply(this, arguments)
@@ -20,7 +20,7 @@ class AerospikePlugin extends DatabasePlugin {
20
20
  bindStart (ctx) {
21
21
  const { commandName, commandArgs } = ctx
22
22
  const resourceName = commandName.slice(0, commandName.indexOf('Command'))
23
- const store = storage.getStore()
23
+ const store = storage('legacy').getStore()
24
24
  const childOf = store ? store.span : null
25
25
  const meta = getMeta(resourceName, commandArgs)
26
26
 
@@ -10,7 +10,7 @@ class ApolloGatewayFetchPlugin extends ApolloBasePlugin {
10
10
  }
11
11
 
12
12
  bindStart (ctx) {
13
- const store = storage.getStore()
13
+ const store = storage('legacy').getStore()
14
14
  const childOf = store ? store.span : null
15
15
 
16
16
  const spanData = {
@@ -25,7 +25,7 @@ class ApolloGatewayPlugin extends CompositePlugin {
25
25
  constructor (...args) {
26
26
  super(...args)
27
27
  this.addSub('apm:apollo:gateway:general:error', (ctx) => {
28
- const store = storage.getStore()
28
+ const store = storage('legacy').getStore()
29
29
  const span = store?.span
30
30
  if (!span) return
31
31
  span.setTag('error', ctx.error)
@@ -15,7 +15,7 @@ class ApolloGatewayRequestPlugin extends ApolloBasePlugin {
15
15
  }
16
16
 
17
17
  bindStart (ctx) {
18
- const store = storage.getStore()
18
+ const store = storage('legacy').getStore()
19
19
  const childOf = store ? store.span : null
20
20
  const spanData = {
21
21
  childOf,
@@ -67,13 +67,13 @@ class BaseAwsSdkPlugin extends ClientPlugin {
67
67
  span.addTags(requestTags)
68
68
  }
69
69
 
70
- const store = storage.getStore()
70
+ const store = storage('legacy').getStore()
71
71
 
72
72
  this.enter(span, store)
73
73
  })
74
74
 
75
75
  this.addSub(`apm:aws:request:region:${this.serviceIdentifier}`, region => {
76
- const store = storage.getStore()
76
+ const store = storage('legacy').getStore()
77
77
  if (!store) return
78
78
  const { span } = store
79
79
  if (!span) return
@@ -82,7 +82,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
82
82
  })
83
83
 
84
84
  this.addSub(`apm:aws:request:complete:${this.serviceIdentifier}`, ({ response, cbExists = false }) => {
85
- const store = storage.getStore()
85
+ const store = storage('legacy').getStore()
86
86
  if (!store) return
87
87
  const { span } = store
88
88
  if (!span) return
@@ -21,7 +21,7 @@ class Kinesis extends BaseAwsSdkPlugin {
21
21
 
22
22
  this.addSub('apm:aws:response:start:kinesis', obj => {
23
23
  const { request, response } = obj
24
- const store = storage.getStore()
24
+ const store = storage('legacy').getStore()
25
25
  const plugin = this
26
26
 
27
27
  // if we have either of these operations, we want to store the streamName param
@@ -49,7 +49,7 @@ class Kinesis extends BaseAwsSdkPlugin {
49
49
  }
50
50
 
51
51
  // get the stream name that should have been stored previously
52
- const { streamName } = storage.getStore()
52
+ const { streamName } = storage('legacy').getStore()
53
53
 
54
54
  // extract DSM context after as we might not have a parent-child but may have a DSM context
55
55
  this.responseExtractDSMContext(
@@ -59,7 +59,7 @@ class Kinesis extends BaseAwsSdkPlugin {
59
59
  })
60
60
 
61
61
  this.addSub('apm:aws:response:finish:kinesis', err => {
62
- const { span } = storage.getStore()
62
+ const { span } = storage('legacy').getStore()
63
63
  this.finish(span, null, err)
64
64
  })
65
65
  }
@@ -79,7 +79,7 @@ class Kinesis extends BaseAwsSdkPlugin {
79
79
  if (!params || !params.StreamName) return
80
80
 
81
81
  const streamName = params.StreamName
82
- storage.enterWith({ ...store, streamName })
82
+ storage('legacy').enterWith({ ...store, streamName })
83
83
  }
84
84
 
85
85
  responseExtract (params, operation, response) {
@@ -20,7 +20,7 @@ class Sqs extends BaseAwsSdkPlugin {
20
20
 
21
21
  this.addSub('apm:aws:response:start:sqs', obj => {
22
22
  const { request, response } = obj
23
- const store = storage.getStore()
23
+ const store = storage('legacy').getStore()
24
24
  const plugin = this
25
25
  const contextExtraction = this.responseExtract(request.params, request.operation, response)
26
26
  let span
@@ -47,7 +47,7 @@ class Sqs extends BaseAwsSdkPlugin {
47
47
  })
48
48
 
49
49
  this.addSub('apm:aws:response:finish:sqs', err => {
50
- const { span } = storage.getStore()
50
+ const { span } = storage('legacy').getStore()
51
51
  this.finish(span, null, err)
52
52
  })
53
53
  }
@@ -24,7 +24,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
24
24
 
25
25
  bindStart (ctx) {
26
26
  const { functionName, methodName } = ctx
27
- const store = storage.getStore()
27
+ const store = storage('legacy').getStore()
28
28
 
29
29
  const span = this.startSpan(this.operationName(), {
30
30
  service: this.serviceName(),
@@ -42,7 +42,7 @@ class CouchBasePlugin extends StoragePlugin {
42
42
  super(...args)
43
43
 
44
44
  this.addSubs('query', ({ resource, bucket, seedNodes }) => {
45
- const store = storage.getStore()
45
+ const store = storage('legacy').getStore()
46
46
  const span = this.startSpan(
47
47
  'query', {
48
48
  'span.type': 'sql',
@@ -64,7 +64,7 @@ class CouchBasePlugin extends StoragePlugin {
64
64
 
65
65
  _addCommandSubs (name) {
66
66
  this.addSubs(name, ({ bucket, collection, seedNodes }) => {
67
- const store = storage.getStore()
67
+ const store = storage('legacy').getStore()
68
68
  const span = this.startSpan(name, {}, store, { bucket, collection, seedNodes })
69
69
  this.enter(span, store)
70
70
  })
@@ -27,7 +27,9 @@ const {
27
27
  TEST_MODULE_ID,
28
28
  TEST_SUITE,
29
29
  CUCUMBER_IS_PARALLEL,
30
- TEST_RETRY_REASON
30
+ TEST_RETRY_REASON,
31
+ TEST_MANAGEMENT_ENABLED,
32
+ TEST_MANAGEMENT_IS_QUARANTINED
31
33
  } = require('../../dd-trace/src/plugins/util/test')
32
34
  const { RESOURCE_NAME } = require('../../../ext/tags')
33
35
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -47,6 +49,7 @@ const {
47
49
  const id = require('../../dd-trace/src/id')
48
50
 
49
51
  const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
52
+ const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
50
53
  const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID
51
54
 
52
55
  function getTestSuiteTags (testSuiteSpan) {
@@ -83,6 +86,7 @@ class CucumberPlugin extends CiPlugin {
83
86
  hasForcedToRunSuites,
84
87
  isEarlyFlakeDetectionEnabled,
85
88
  isEarlyFlakeDetectionFaulty,
89
+ isQuarantinedTestsEnabled,
86
90
  isParallel
87
91
  }) => {
88
92
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
@@ -109,6 +113,9 @@ class CucumberPlugin extends CiPlugin {
109
113
  if (isParallel) {
110
114
  this.testSessionSpan.setTag(CUCUMBER_IS_PARALLEL, 'true')
111
115
  }
116
+ if (isQuarantinedTestsEnabled) {
117
+ this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
118
+ }
112
119
 
113
120
  this.testSessionSpan.setTag(TEST_STATUS, status)
114
121
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -213,7 +220,7 @@ class CucumberPlugin extends CiPlugin {
213
220
  isParallel,
214
221
  promises
215
222
  }) => {
216
- const store = storage.getStore()
223
+ const store = storage('legacy').getStore()
217
224
  const testSuite = getTestSuitePath(testFileAbsolutePath, this.sourceRoot)
218
225
  const testSourceFile = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot)
219
226
 
@@ -231,7 +238,7 @@ class CucumberPlugin extends CiPlugin {
231
238
 
232
239
  this.activeTestSpan = testSpan
233
240
  // Time we give the breakpoint to be hit
234
- if (promises && this.runningTestProbeId) {
241
+ if (promises && this.runningTestProbe) {
235
242
  promises.hitBreakpointPromise = new Promise((resolve) => {
236
243
  setTimeout(resolve, BREAKPOINT_HIT_GRACE_PERIOD_MS)
237
244
  })
@@ -239,7 +246,7 @@ class CucumberPlugin extends CiPlugin {
239
246
  })
240
247
 
241
248
  this.addSub('ci:cucumber:test:retry', ({ isFirstAttempt, error }) => {
242
- const store = storage.getStore()
249
+ const store = storage('legacy').getStore()
243
250
  const span = store.span
244
251
  if (!isFirstAttempt) {
245
252
  span.setTag(TEST_IS_RETRY, 'true')
@@ -248,10 +255,15 @@ class CucumberPlugin extends CiPlugin {
248
255
  if (isFirstAttempt && this.di && error && this.libraryConfig?.isDiEnabled) {
249
256
  const probeInformation = this.addDiProbe(error)
250
257
  if (probeInformation) {
251
- const { probeId, stackIndex } = probeInformation
252
- this.runningTestProbeId = probeId
258
+ const { file, line, stackIndex } = probeInformation
259
+ this.runningTestProbe = { file, line }
253
260
  this.testErrorStackIndex = stackIndex
254
- // TODO: we're not waiting for setProbePromise to be resolved, so there might be race conditions
261
+ const waitUntil = Date.now() + BREAKPOINT_SET_GRACE_PERIOD_MS
262
+ while (Date.now() < waitUntil) {
263
+ // TODO: To avoid a race condition, we should wait until `probeInformation.setProbePromise` has resolved.
264
+ // However, Cucumber doesn't have a mechanism for waiting asyncrounously here, so for now, we'll have to
265
+ // fall back to a fixed syncronous delay.
266
+ }
255
267
  }
256
268
  }
257
269
  span.setTag(TEST_STATUS, 'fail')
@@ -260,7 +272,7 @@ class CucumberPlugin extends CiPlugin {
260
272
  })
261
273
 
262
274
  this.addSub('ci:cucumber:test-step:start', ({ resource }) => {
263
- const store = storage.getStore()
275
+ const store = storage('legacy').getStore()
264
276
  const childOf = store ? store.span : store
265
277
  const span = this.tracer.startSpan('cucumber.step', {
266
278
  childOf,
@@ -311,9 +323,10 @@ class CucumberPlugin extends CiPlugin {
311
323
  errorMessage,
312
324
  isNew,
313
325
  isEfdRetry,
314
- isFlakyRetry
326
+ isFlakyRetry,
327
+ isQuarantined
315
328
  }) => {
316
- const span = storage.getStore().span
329
+ const span = storage('legacy').getStore().span
317
330
  const statusTag = isStep ? 'step.status' : TEST_STATUS
318
331
 
319
332
  span.setTag(statusTag, status)
@@ -340,6 +353,10 @@ class CucumberPlugin extends CiPlugin {
340
353
  span.setTag(TEST_IS_RETRY, 'true')
341
354
  }
342
355
 
356
+ if (isQuarantined) {
357
+ span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
358
+ }
359
+
343
360
  span.finish()
344
361
  if (!isStep) {
345
362
  const spanTags = span.context()._tags
@@ -359,16 +376,16 @@ class CucumberPlugin extends CiPlugin {
359
376
  this.tracer._exporter.flush()
360
377
  }
361
378
  this.activeTestSpan = null
362
- if (this.runningTestProbeId) {
363
- this.removeDiProbe(this.runningTestProbeId)
364
- this.runningTestProbeId = null
379
+ if (this.runningTestProbe) {
380
+ this.removeDiProbe(this.runningTestProbe)
381
+ this.runningTestProbe = null
365
382
  }
366
383
  }
367
384
  })
368
385
 
369
386
  this.addSub('ci:cucumber:error', (err) => {
370
387
  if (err) {
371
- const span = storage.getStore().span
388
+ const span = storage('legacy').getStore().span
372
389
  span.setTag('error', err)
373
390
  }
374
391
  })
@@ -32,7 +32,10 @@ const {
32
32
  getTestSessionName,
33
33
  TEST_SESSION_NAME,
34
34
  TEST_LEVEL_EVENT_TYPES,
35
- TEST_RETRY_REASON
35
+ TEST_RETRY_REASON,
36
+ DD_TEST_IS_USER_PROVIDED_SERVICE,
37
+ TEST_MANAGEMENT_IS_QUARANTINED,
38
+ TEST_MANAGEMENT_ENABLED
36
39
  } = require('../../dd-trace/src/plugins/util/test')
37
40
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
38
41
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -151,6 +154,20 @@ function getKnownTests (tracer, testConfiguration) {
151
154
  })
152
155
  }
153
156
 
157
+ function getQuarantinedTests (tracer, testConfiguration) {
158
+ return new Promise(resolve => {
159
+ if (!tracer._tracer._exporter?.getQuarantinedTests) {
160
+ return resolve({ err: new Error('Test Optimization was not initialized correctly') })
161
+ }
162
+ tracer._tracer._exporter.getQuarantinedTests(testConfiguration, (err, quarantinedTests) => {
163
+ resolve({
164
+ err,
165
+ quarantinedTests
166
+ })
167
+ })
168
+ })
169
+ }
170
+
154
171
  function getSuiteStatus (suiteStats) {
155
172
  if (!suiteStats) {
156
173
  return 'skip'
@@ -222,6 +239,10 @@ class CypressPlugin {
222
239
  this.tracer = tracer
223
240
  this.cypressConfig = cypressConfig
224
241
 
242
+ // we have to do it here because the tracer is not initialized in the constructor
243
+ this.testEnvironmentMetadata[DD_TEST_IS_USER_PROVIDED_SERVICE] =
244
+ tracer._tracer._config.isServiceUserProvided ? 'true' : 'false'
245
+
225
246
  this.libraryConfigurationPromise = getLibraryConfiguration(this.tracer, this.testConfiguration)
226
247
  .then((libraryConfigurationResponse) => {
227
248
  if (libraryConfigurationResponse.err) {
@@ -235,7 +256,8 @@ class CypressPlugin {
235
256
  earlyFlakeDetectionNumRetries,
236
257
  isFlakyTestRetriesEnabled,
237
258
  flakyTestRetriesCount,
238
- isKnownTestsEnabled
259
+ isKnownTestsEnabled,
260
+ isQuarantinedTestsEnabled
239
261
  }
240
262
  } = libraryConfigurationResponse
241
263
  this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
@@ -246,12 +268,24 @@ class CypressPlugin {
246
268
  if (isFlakyTestRetriesEnabled) {
247
269
  this.cypressConfig.retries.runMode = flakyTestRetriesCount
248
270
  }
271
+ this.isQuarantinedTestsEnabled = isQuarantinedTestsEnabled
249
272
  }
250
273
  return this.cypressConfig
251
274
  })
252
275
  return this.libraryConfigurationPromise
253
276
  }
254
277
 
278
+ getIsQuarantinedTest (testSuite, testName) {
279
+ return this.quarantinedTests
280
+ ?.cypress
281
+ ?.suites
282
+ ?.[testSuite]
283
+ ?.tests
284
+ ?.[testName]
285
+ ?.properties
286
+ ?.quarantined
287
+ }
288
+
255
289
  getTestSuiteSpan ({ testSuite, testSuiteAbsolutePath }) {
256
290
  const testSuiteSpanMetadata =
257
291
  getTestSuiteCommonTags(this.command, this.frameworkVersion, testSuite, TEST_FRAMEWORK_NAME)
@@ -346,10 +380,6 @@ class CypressPlugin {
346
380
  })
347
381
  }
348
382
 
349
- isNewTest (testName, testSuite) {
350
- return !this.knownTestsByTestSuite?.[testSuite]?.includes(testName)
351
- }
352
-
353
383
  async beforeRun (details) {
354
384
  // We need to make sure that the plugin is initialized before running the tests
355
385
  // This is for the case where the user has not returned the promise from the init function
@@ -388,6 +418,19 @@ class CypressPlugin {
388
418
  }
389
419
  }
390
420
 
421
+ if (this.isQuarantinedTestsEnabled) {
422
+ const quarantinedTestsResponse = await getQuarantinedTests(
423
+ this.tracer,
424
+ this.testConfiguration
425
+ )
426
+ if (quarantinedTestsResponse.err) {
427
+ log.error('Cypress quarantined tests response error', quarantinedTestsResponse.err)
428
+ this.isQuarantinedTestsEnabled = false
429
+ } else {
430
+ this.quarantinedTests = quarantinedTestsResponse.quarantinedTests
431
+ }
432
+ }
433
+
391
434
  // `details.specs` are test files
392
435
  details.specs?.forEach(({ absolute, relative }) => {
393
436
  const isUnskippableSuite = isMarkedAsUnskippable({ path: absolute })
@@ -466,6 +509,10 @@ class CypressPlugin {
466
509
  }
467
510
  )
468
511
 
512
+ if (this.isQuarantinedTestsEnabled) {
513
+ this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
514
+ }
515
+
469
516
  this.testModuleSpan.finish()
470
517
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
471
518
  this.testSessionSpan.finish()
@@ -540,6 +587,13 @@ class CypressPlugin {
540
587
  if (this.itrCorrelationId) {
541
588
  skippedTestSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
542
589
  }
590
+
591
+ const isQuarantined = this.getIsQuarantinedTest(spec.relative, cypressTestName)
592
+
593
+ if (isQuarantined) {
594
+ skippedTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
595
+ }
596
+
543
597
  skippedTestSpan.finish()
544
598
  })
545
599
 
@@ -643,6 +697,7 @@ class CypressPlugin {
643
697
  })
644
698
  const isUnskippable = this.unskippableSuites.includes(testSuite)
645
699
  const isForcedToRun = shouldSkip && isUnskippable
700
+ const isQuarantined = this.getIsQuarantinedTest(testSuite, testName)
646
701
 
647
702
  // skip test
648
703
  if (shouldSkip && !isUnskippable) {
@@ -651,6 +706,12 @@ class CypressPlugin {
651
706
  return { shouldSkip: true }
652
707
  }
653
708
 
709
+ // TODO: I haven't found a way to trick cypress into ignoring a test
710
+ // The way we'll implement quarantine in cypress is by skipping the test altogether
711
+ if (isQuarantined) {
712
+ return { shouldSkip: true }
713
+ }
714
+
654
715
  if (!this.activeTestSpan) {
655
716
  this.activeTestSpan = this.getTestSpan({
656
717
  testName,
@@ -676,7 +737,8 @@ class CypressPlugin {
676
737
  testSuiteAbsolutePath,
677
738
  testName,
678
739
  isNew,
679
- isEfdRetry
740
+ isEfdRetry,
741
+ isQuarantined
680
742
  } = test
681
743
  if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
682
744
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
@@ -715,6 +777,9 @@ class CypressPlugin {
715
777
  this.activeTestSpan.setTag(TEST_RETRY_REASON, 'efd')
716
778
  }
717
779
  }
780
+ if (isQuarantined) {
781
+ this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
782
+ }
718
783
  const finishedTest = {
719
784
  testName,
720
785
  testStatus,