dd-trace 6.9.0 → 6.11.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 (175) hide show
  1. package/README.md +0 -17
  2. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  3. package/index.d.ts +150 -3
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +14 -10
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  13. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  14. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  15. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  16. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  17. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  20. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  26. package/packages/datadog-instrumentations/src/jest.js +26 -18
  27. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  28. package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
  30. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  31. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  32. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  33. package/packages/datadog-instrumentations/src/pg.js +116 -92
  34. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  35. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  36. package/packages/datadog-instrumentations/src/router.js +2 -0
  37. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  38. package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
  39. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  40. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  41. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  45. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
  46. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  47. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  48. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  49. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  50. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  51. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  52. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  53. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
  54. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  55. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  56. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  57. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  58. package/packages/datadog-plugin-electron/src/index.js +2 -0
  59. package/packages/datadog-plugin-fs/src/index.js +1 -1
  60. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  61. package/packages/datadog-plugin-jest/src/index.js +11 -4
  62. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  63. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  64. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  65. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  66. package/packages/datadog-plugin-mocha/src/index.js +606 -40
  67. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  68. package/packages/datadog-plugin-nats/src/index.js +1 -1
  69. package/packages/datadog-plugin-next/src/index.js +1 -19
  70. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  71. package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
  72. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  73. package/packages/datadog-plugin-pg/src/index.js +1 -0
  74. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  75. package/packages/datadog-plugin-vitest/src/index.js +14 -20
  76. package/packages/datadog-plugin-ws/src/util.js +2 -1
  77. package/packages/datadog-webpack/index.js +0 -11
  78. package/packages/dd-trace/index.js +0 -1
  79. package/packages/dd-trace/src/agent/info.js +6 -5
  80. package/packages/dd-trace/src/aiguard/client.js +100 -1
  81. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  82. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  83. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  84. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  85. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  86. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  87. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  88. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  89. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  90. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  91. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  92. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  93. package/packages/dd-trace/src/carrier.js +356 -0
  94. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  101. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  102. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  103. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  104. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  105. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  106. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  107. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
  109. package/packages/dd-trace/src/config/index.js +13 -2
  110. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  111. package/packages/dd-trace/src/config/supported-configurations.json +24 -0
  112. package/packages/dd-trace/src/datastreams/context.js +6 -1
  113. package/packages/dd-trace/src/datastreams/index.js +2 -0
  114. package/packages/dd-trace/src/datastreams/manager.js +14 -3
  115. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  116. package/packages/dd-trace/src/datastreams/processor.js +16 -13
  117. package/packages/dd-trace/src/datastreams/size.js +5 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  119. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  120. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  121. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  122. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  123. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  124. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  125. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  126. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  127. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  128. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  129. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  130. package/packages/dd-trace/src/llmobs/index.js +34 -2
  131. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  132. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  133. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  134. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  135. package/packages/dd-trace/src/llmobs/util.js +78 -0
  136. package/packages/dd-trace/src/noop/proxy.js +3 -4
  137. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  138. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  139. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  140. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  141. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  142. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  143. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  145. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  146. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  147. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  148. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  149. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  150. package/packages/dd-trace/src/plugin_manager.js +5 -3
  151. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  152. package/packages/dd-trace/src/plugins/index.js +1 -0
  153. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
  154. package/packages/dd-trace/src/plugins/util/test.js +93 -14
  155. package/packages/dd-trace/src/plugins/util/web.js +56 -2
  156. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  157. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  158. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  159. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  160. package/packages/dd-trace/src/proxy.js +38 -29
  161. package/packages/dd-trace/src/ritm.js +9 -2
  162. package/packages/dd-trace/src/serverless.js +37 -0
  163. package/packages/dd-trace/src/span_format.js +1 -0
  164. package/packages/dd-trace/src/span_stats.js +30 -3
  165. package/packages/dd-trace/src/storage-channels.js +86 -0
  166. package/packages/dd-trace/src/tracer.js +9 -2
  167. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  168. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  169. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  170. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  171. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  172. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  173. package/packages/dd-trace/src/feature-registry.js +0 -29
  174. package/packages/dd-trace/src/openfeature/register.js +0 -35
  175. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -47,6 +47,7 @@ const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTI
47
47
  const VITEST_NO_WORKER_INIT_REQUEST_ENV = 'DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT'
48
48
  const VITEST_NO_WORKER_INIT_MINIMUM_VERSION = '3.2.6'
49
49
  const VITEST_DEFAULT_POOL = 'forks'
50
+ const VITEST_BROWSER_EFD_SUITE_ADMISSION_COMMAND = '__dd_vitest_efd_suite_admission'
50
51
  const VITEST_NO_WORKER_INIT_SETUP_FILE = path.join(
51
52
  __dirname,
52
53
  '..',
@@ -69,6 +70,7 @@ const VITEST_BROWSER_URL_RE = /https?:\/\/[^\s)"'>\]]+/g
69
70
  let hasWarnedDisabledIsolate = false
70
71
  let hasWarnedUnsupportedVersion = false
71
72
  let nodeOptionsBeforeNoWorkerInit
73
+ let reserveEarlyFlakeDetectionSuite
72
74
 
73
75
  function noop () {}
74
76
 
@@ -322,6 +324,7 @@ function isEarlyFlakeDetectionActive (state) {
322
324
 
323
325
  function configure (ctx, frameworkVersion, testSpecifications, setupData, options) {
324
326
  const { shouldReportTestModule, state } = options
327
+ reserveEarlyFlakeDetectionSuite = options.reserveEarlyFlakeDetectionSuite
325
328
  addSetupFileToVitestConfigs(ctx, VITEST_NO_WORKER_INIT_SETUP_FILE, testSpecifications)
326
329
  addVitestBrowserSetupFileAccess(testSpecifications)
327
330
 
@@ -341,6 +344,8 @@ function configure (ctx, frameworkVersion, testSpecifications, setupData, option
341
344
  attemptToFixTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isAttemptToFix'),
342
345
  disabledTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isDisabled'),
343
346
  earlyFlakeDetectionRetryPolicy: state.earlyFlakeDetectionRetryPolicy,
347
+ efdSuiteAdmissionBrowserCommand: VITEST_BROWSER_EFD_SUITE_ADMISSION_COMMAND,
348
+ isEfdSuiteAdmissionEnabled: state.isEfdSuiteAdmissionEnabled,
344
349
  isEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionActive(state),
345
350
  isRumCorrelationEnabled: !canRaceRumCorrelation(ctx, testSpecifications),
346
351
  knownTests: knownTestsBySuite || {},
@@ -443,6 +448,18 @@ function configureVitestBrowserSetupFile (viteConfig) {
443
448
  }
444
449
  }
445
450
 
451
+ /**
452
+ * Reserves EFD retries for a suite executing in Vitest Browser Mode.
453
+ *
454
+ * @param {unknown} _context
455
+ * @param {string} testSuite
456
+ * @param {unknown} hasNewTest
457
+ * @returns {boolean}
458
+ */
459
+ function handleBrowserEfdSuiteAdmission (_context, testSuite, hasNewTest) {
460
+ return reserveEarlyFlakeDetectionSuite?.(testSuite, hasNewTest === true) === true
461
+ }
462
+
446
463
  /**
447
464
  * Allows Vite to serve the Datadog setup file after its default workspace detection has run.
448
465
  *
@@ -451,7 +468,9 @@ function configureVitestBrowserSetupFile (viteConfig) {
451
468
  */
452
469
  function allowVitestBrowserSetupFile (viteConfig) {
453
470
  const allow = viteConfig.server?.fs?.allow
454
- if (allow && !allow.includes(VITEST_NO_WORKER_INIT_SETUP_FILE)) {
471
+ if (!allow) return
472
+
473
+ if (!allow.includes(VITEST_NO_WORKER_INIT_SETUP_FILE)) {
455
474
  allow.push(VITEST_NO_WORKER_INIT_SETUP_FILE)
456
475
  }
457
476
  }
@@ -471,10 +490,16 @@ function addVitestBrowserSetupFileAccess (testSpecifications) {
471
490
  if (!isBrowserTestSpecification(testSpecification)) continue
472
491
 
473
492
  const browserServerProject = project?._parent || project
474
- if (!browserServerProject || configuredProjects.has(browserServerProject)) continue
493
+ if (!browserServerProject) continue
494
+
495
+ addVitestBrowserCommand(safeConfig(project))
496
+ if (configuredProjects.has(browserServerProject)) continue
475
497
 
476
498
  configuredProjects.add(browserServerProject)
499
+ addVitestBrowserCommand(safeConfig(browserServerProject))
477
500
  browserServerProject.options ||= {}
501
+ browserServerProject.options.browser ||= {}
502
+ addVitestBrowserCommand(browserServerProject.options)
478
503
  browserServerProject.options.plugins ||= []
479
504
  if (!browserServerProject.options.plugins.includes(VITEST_BROWSER_SETUP_FILE_PLUGIN)) {
480
505
  browserServerProject.options.plugins.push(VITEST_BROWSER_SETUP_FILE_PLUGIN)
@@ -482,6 +507,20 @@ function addVitestBrowserSetupFileAccess (testSpecifications) {
482
507
  }
483
508
  }
484
509
 
510
+ /**
511
+ * Adds the private EFD admission command to a Vitest Browser Mode configuration.
512
+ *
513
+ * @param {object|undefined} config
514
+ * @returns {void}
515
+ */
516
+ function addVitestBrowserCommand (config) {
517
+ const browserConfig = config?.browser
518
+ if (!browserConfig) return
519
+
520
+ browserConfig.commands ||= {}
521
+ browserConfig.commands[VITEST_BROWSER_EFD_SUITE_ADMISSION_COMMAND] = handleBrowserEfdSuiteAdmission
522
+ }
523
+
485
524
  function getNoWorkerInitVitestConfigs (ctx, testSpecifications) {
486
525
  const configs = new Set()
487
526
  if (Array.isArray(testSpecifications)) {
@@ -847,7 +886,7 @@ function createMainProcessReporter (reporterState) {
847
886
  !isAttemptToFix &&
848
887
  state.isKnownTestsEnabled &&
849
888
  knownTests &&
850
- !state.isEarlyFlakeDetectionFaulty &&
889
+ (state.isEfdSuiteAdmissionEnabled || !state.isEarlyFlakeDetectionFaulty) &&
851
890
  !knownTests.includes(testName)
852
891
  )
853
892
  const isModified = testProperties?.isModified === true
@@ -861,7 +900,7 @@ function createMainProcessReporter (reporterState) {
861
900
  return {
862
901
  isAttemptToFix,
863
902
  isDisabled: testManagementProperties.isDisabled,
864
- isEarlyFlakeDetection: (isNew || isModified) && isEarlyFlakeDetectionActive(state),
903
+ isEarlyFlakeDetection: task.meta?.__ddTestOptEfdRetries !== undefined,
865
904
  isFlakyTestRetries,
866
905
  isQuarantined: testManagementProperties.isQuarantined,
867
906
  isModified,
@@ -4,6 +4,8 @@ const path = require('node:path')
4
4
  const { fileURLToPath } = require('node:url')
5
5
  const { MessagePort } = require('node:worker_threads')
6
6
 
7
+ const satisfies = require('../../../vendor/dist/semifies')
8
+
7
9
  const shimmer = require('../../datadog-shimmer')
8
10
  const log = require('../../dd-trace/src/log')
9
11
  const { EMPTY_EFD_RETRY_POLICY } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
@@ -12,16 +14,19 @@ const {
12
14
  VITEST_WORKER_COVERAGE_PAYLOAD_CODE,
13
15
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
14
16
  VITEST_WORKER_TELEMETRY_PAYLOAD_CODE,
17
+ VITEST_WORKER_EFD_SUITE_ADMISSION_REQUEST_CODE,
18
+ VITEST_WORKER_EFD_SUITE_ADMISSION_RESPONSE_CODE,
15
19
  collectTestOptimizationSummariesFromTraces,
20
+ getIsFaultyEarlyFlakeDetection,
16
21
  logTestOptimizationSummary,
17
22
  TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE,
18
23
  getTestOptimizationRequestResults,
19
24
  getTestSuitePath,
20
25
  isModifiedTest,
26
+ isMarkedAsUnskippable,
21
27
  recordTestManagementExecution,
22
28
  recordAttemptToFixExecution,
23
29
  } = require('../../dd-trace/src/plugins/util/test')
24
- const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
25
30
  const { getChannelPromise } = require('./helpers/channel')
26
31
  const { addHook, channel } = require('./helpers/instrument')
27
32
  const noWorkerInit = require('./vitest-main-no-worker-init')
@@ -38,7 +43,6 @@ const {
38
43
  testSessionConfigurationCh,
39
44
  libraryConfigurationCh,
40
45
  knownTestsCh,
41
- isEarlyFlakeDetectionFaultyCh,
42
46
  testManagementTestsCh,
43
47
  modifiedFilesCh,
44
48
  workerReportTraceCh,
@@ -50,6 +54,7 @@ const {
50
54
  findExportByName,
51
55
  getTypeTasks,
52
56
  getWorkspaceProject,
57
+ parseProvidedContextValue,
53
58
  setProvidedContext,
54
59
  getVitestTestProperties,
55
60
  } = require('./vitest-util')
@@ -62,10 +67,12 @@ const coverageWrappedProviders = new WeakSet()
62
67
  const finishWrappedContexts = new WeakSet()
63
68
  const runFilesWrappedPrototypes = new WeakSet()
64
69
  const activeRunFilesContexts = new WeakSet()
70
+ const runErrorsByContext = new WeakMap()
65
71
  let isFlakyTestRetriesEnabled = false
66
72
  let flakyTestRetriesCount = 0
67
73
  let isEarlyFlakeDetectionEnabled = false
68
74
  let earlyFlakeDetectionRetryPolicy = EMPTY_EFD_RETRY_POLICY
75
+ let earlyFlakeDetectionFaultyThreshold = 0
69
76
  let isEarlyFlakeDetectionFaulty = false
70
77
  let isKnownTestsEnabled = false
71
78
  let isTestManagementTestsEnabled = false
@@ -97,6 +104,10 @@ let hasRunnableSuites = false
97
104
  let hasSelectedSuites = false
98
105
  let itrCorrelationId
99
106
  let tiaRepositoryRoot = process.cwd()
107
+ let activeNoWorkerInitState
108
+ let isEfdSuiteAdmissionEnabled = false
109
+ let maximumSuitesWithNewTests = 0
110
+ const suitesWithNewTests = new Set()
100
111
  const tinyPoolClassWrappers = new WeakMap()
101
112
  const itrSkippedSuitesCh = channel('ci:vitest:itr:skipped-suites')
102
113
  const skippableSuitesCh = channel('ci:vitest:test-suite:skippable')
@@ -223,6 +234,100 @@ function getNormalizedTestSuitePath (testFilepath, repositoryRoot) {
223
234
  return getTestSuitePath(realpath(testSuiteAbsolutePath), realpath(repositoryRoot))
224
235
  }
225
236
 
237
+ /**
238
+ * Resets suite-level EFD admission state between Vitest runs.
239
+ *
240
+ * @returns {void}
241
+ */
242
+ function resetEfdSuiteTracker () {
243
+ activeNoWorkerInitState = undefined
244
+ isEfdSuiteAdmissionEnabled = false
245
+ maximumSuitesWithNewTests = 0
246
+ suitesWithNewTests.clear()
247
+ }
248
+
249
+ /**
250
+ * Returns whether a Vitest pool has a transport for runtime EFD suite admission.
251
+ *
252
+ * @param {string|undefined} pool
253
+ * @returns {boolean}
254
+ */
255
+ function isEfdSuiteAdmissionPool (pool) {
256
+ return pool === undefined || pool === 'forks' || pool === 'threads' || pool === 'browser'
257
+ }
258
+
259
+ /**
260
+ * Returns whether Vitest exposes the worker transports used for runtime EFD suite admission.
261
+ *
262
+ * @param {string} frameworkVersion
263
+ * @param {object[]|undefined} testSpecifications
264
+ * @param {object} ctx
265
+ * @returns {boolean}
266
+ */
267
+ function supportsEfdSuiteAdmission (frameworkVersion, testSpecifications, ctx) {
268
+ if (!satisfies(frameworkVersion, '>=4.0.0')) return false
269
+ const defaultPool = ctx?.config?.pool
270
+ if (!Array.isArray(testSpecifications) || testSpecifications.length === 0) {
271
+ return isEfdSuiteAdmissionPool(defaultPool)
272
+ }
273
+
274
+ for (const testSpecification of testSpecifications) {
275
+ const pool = getTestSpecificationPool(testSpecification) || defaultPool
276
+ if (!isEfdSuiteAdmissionPool(pool)) return false
277
+ }
278
+ return true
279
+ }
280
+
281
+ /**
282
+ * Configures the EFD threshold against the unique suites selected by Vitest.
283
+ *
284
+ * @param {string[]} testFilepaths
285
+ * @param {string} repositoryRoot
286
+ * @returns {void}
287
+ */
288
+ function configureEfdSuiteTracker (testFilepaths, repositoryRoot) {
289
+ const testSuites = new Set()
290
+ for (const testFilepath of testFilepaths) {
291
+ testSuites.add(getNormalizedTestSuitePath(testFilepath, repositoryRoot))
292
+ }
293
+
294
+ maximumSuitesWithNewTests = Math.floor(Math.max(
295
+ earlyFlakeDetectionFaultyThreshold,
296
+ testSuites.size * earlyFlakeDetectionFaultyThreshold / 100
297
+ ))
298
+ isEfdSuiteAdmissionEnabled = true
299
+ }
300
+
301
+ /**
302
+ * Returns whether a suite may schedule EFD retries and records runnable suites with new tests.
303
+ *
304
+ * @param {string} testSuite
305
+ * @param {boolean} hasNewTest
306
+ * @returns {boolean}
307
+ */
308
+ function reserveEarlyFlakeDetectionSuite (testSuite, hasNewTest) {
309
+ if (!isEfdSuiteAdmissionEnabled || typeof testSuite !== 'string') return false
310
+ if (isEarlyFlakeDetectionFaulty) return false
311
+
312
+ if (hasNewTest) {
313
+ suitesWithNewTests.add(testSuite)
314
+ if (suitesWithNewTests.size > maximumSuitesWithNewTests) {
315
+ isEarlyFlakeDetectionEnabled = false
316
+ isEarlyFlakeDetectionFaulty = true
317
+ if (activeNoWorkerInitState) {
318
+ activeNoWorkerInitState.isEarlyFlakeDetectionEnabled = false
319
+ activeNoWorkerInitState.isEarlyFlakeDetectionFaulty = true
320
+ }
321
+ log.warn(
322
+ 'Early Flake Detection retries are disabled because the number of suites with new tests is too high.'
323
+ )
324
+ return false
325
+ }
326
+ }
327
+
328
+ return true
329
+ }
330
+
226
331
  function resetSuiteSkippingRunState () {
227
332
  skippableSuites = []
228
333
  resetAppliedSuiteSkippingState()
@@ -543,6 +648,7 @@ function resetLibraryConfig () {
543
648
  flakyTestRetriesCount = 0
544
649
  isEarlyFlakeDetectionEnabled = false
545
650
  earlyFlakeDetectionRetryPolicy = EMPTY_EFD_RETRY_POLICY
651
+ earlyFlakeDetectionFaultyThreshold = 0
546
652
  isEarlyFlakeDetectionFaulty = false
547
653
  isDiEnabled = false
548
654
  isKnownTestsEnabled = false
@@ -558,6 +664,7 @@ function applyLibraryConfig (libraryConfig) {
558
664
  flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
559
665
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
560
666
  earlyFlakeDetectionRetryPolicy = libraryConfig.earlyFlakeDetectionRetryPolicy ?? EMPTY_EFD_RETRY_POLICY
667
+ earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold ?? 0
561
668
  isEarlyFlakeDetectionFaulty = false
562
669
  isDiEnabled = libraryConfig.isDiEnabled
563
670
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
@@ -573,6 +680,7 @@ function resetMainProcessProvidedContext (ctx) {
573
680
  _ddIsDiEnabled: false,
574
681
  _ddIsEarlyFlakeDetectionEnabled: false,
575
682
  _ddEarlyFlakeDetectionRetryPolicy: EMPTY_EFD_RETRY_POLICY,
683
+ _ddIsEfdSuiteAdmissionEnabled: false,
576
684
  _ddIsFlakyTestRetriesEnabled: false,
577
685
  _ddFlakyTestRetriesCount: 0,
578
686
  _ddFlakyTestRetriesIncludesUnnamedProject: false,
@@ -616,6 +724,7 @@ async function runMainProcessSetup (
616
724
  }
617
725
 
618
726
  resetSuiteSkippingRunState()
727
+ resetEfdSuiteTracker()
619
728
  isVitestBrowserModeActive ||= shouldInstallBrowserReporter
620
729
  isTestImpactAnalysisDisabled =
621
730
  disableTestImpactAnalysis || shouldInstallNoWorkerInit || isVitestBrowserModeActive
@@ -732,21 +841,27 @@ async function runMainProcessSetup (
732
841
  const currentTestFilepaths = await getCurrentTestFilepaths()
733
842
 
734
843
  if (isValidKnownTests(knownTests)) {
735
- isEarlyFlakeDetectionFaultyCh.publish({
736
- knownTests: knownTests.vitest,
737
- testFilepaths: currentTestFilepaths,
738
- onDone: (isFaulty) => {
739
- isEarlyFlakeDetectionFaulty = isFaulty
740
- },
741
- })
742
- if (isEarlyFlakeDetectionFaulty) {
743
- isEarlyFlakeDetectionEnabled = false
744
- log.warn('New test detection is disabled because the number of new tests is too high.')
844
+ if (!shouldInstallNoWorkerInit && supportsEfdSuiteAdmission(frameworkVersion, testSpecifications, ctx)) {
845
+ configureEfdSuiteTracker(currentTestFilepaths, repositoryRoot)
745
846
  } else {
847
+ const projectSuites = currentTestFilepaths.map(testFilepath => getTestSuitePath(testFilepath, repositoryRoot))
848
+ isEarlyFlakeDetectionFaulty = getIsFaultyEarlyFlakeDetection(
849
+ projectSuites,
850
+ knownTests.vitest,
851
+ earlyFlakeDetectionFaultyThreshold
852
+ )
853
+ if (isEarlyFlakeDetectionFaulty) {
854
+ isEarlyFlakeDetectionEnabled = false
855
+ log.warn('New test detection is disabled because the number of new tests is too high.')
856
+ }
857
+ }
858
+
859
+ if (!isEarlyFlakeDetectionFaulty) {
746
860
  knownTestsBySuite = knownTests.vitest
747
861
  shouldSendTestProperties = true
748
862
  if (!shouldInstallNoWorkerInit) {
749
863
  setProvidedContext(ctx, {
864
+ _ddIsEfdSuiteAdmissionEnabled: isEfdSuiteAdmissionEnabled,
750
865
  _ddIsKnownTestsEnabled: isKnownTestsEnabled,
751
866
  _ddIsEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled,
752
867
  _ddEarlyFlakeDetectionRetryPolicy: earlyFlakeDetectionRetryPolicy,
@@ -821,6 +936,7 @@ async function runMainProcessSetup (
821
936
  const reporterTestSpecifications = shouldInstallNoWorkerInit
822
937
  ? testSpecifications
823
938
  : getBrowserTestSpecifications(testSpecifications)
939
+ activeNoWorkerInitState = getNoWorkerInitState()
824
940
  noWorkerInit.configure(ctx, frameworkVersion, reporterTestSpecifications, {
825
941
  knownTests,
826
942
  knownTestsBySuite,
@@ -832,8 +948,9 @@ async function runMainProcessSetup (
832
948
  testPropertiesByFilepath: testPropertiesByFilepath || {},
833
949
  testSessionConfiguration,
834
950
  }, {
951
+ reserveEarlyFlakeDetectionSuite,
835
952
  shouldReportTestModule: shouldInstallBrowserReporter ? isBrowserTestModule : undefined,
836
- state: getNoWorkerInitState(),
953
+ state: activeNoWorkerInitState,
837
954
  })
838
955
  }
839
956
 
@@ -846,6 +963,7 @@ function getNoWorkerInitState () {
846
963
  return {
847
964
  attemptToFixExecutions,
848
965
  earlyFlakeDetectionRetryPolicy,
966
+ isEfdSuiteAdmissionEnabled,
849
967
  isEarlyFlakeDetectionEnabled,
850
968
  isEarlyFlakeDetectionFaulty,
851
969
  isFlakyTestRetriesEnabled,
@@ -1078,15 +1196,18 @@ function getFinishWrapper (exitOrClose) {
1078
1196
  }
1079
1197
 
1080
1198
  const failedSuites = this.state.getFailedFilepaths()
1081
- let error
1082
- if (failedSuites.length) {
1199
+ const runError = runErrorsByContext.get(this)
1200
+ runErrorsByContext.delete(this)
1201
+ let error = runError
1202
+ if (!error && failedSuites.length) {
1083
1203
  error = new Error(`Test suites failed: ${failedSuites.length}.`)
1084
1204
  }
1085
1205
 
1086
1206
  const flushPromise = getChannelPromise(testSessionFinishCh, {
1087
- status: areAllSuitesSkipped ? 'skip' : getSessionStatus(this.state),
1207
+ status: runError ? 'fail' : (areAllSuitesSkipped ? 'skip' : getSessionStatus(this.state)),
1088
1208
  testCodeCoverageLinesTotal,
1089
1209
  error,
1210
+ isTestSessionFinalizationError: Boolean(runError),
1090
1211
  isEarlyFlakeDetectionEnabled,
1091
1212
  isEarlyFlakeDetectionFaulty,
1092
1213
  isTestManagementTestsEnabled,
@@ -1172,8 +1293,9 @@ function getTestSpecificationOptions (testSpecification) {
1172
1293
  function getTestSpecificationPool (testSpecification) {
1173
1294
  const options = getTestSpecificationOptions(testSpecification)
1174
1295
  const project = getTestSpecificationProject(testSpecification)
1175
- return options?.pool || project?.config?.pool || project?.serializedConfig?.pool || project?.pool ||
1176
- testSpecification?.pool
1296
+ const filePool = Array.isArray(testSpecification) ? testSpecification[1]?.pool : undefined
1297
+ return options?.pool || filePool || project?.config?.pool || project?.serializedConfig?.pool ||
1298
+ project?.pool || testSpecification?.pool
1177
1299
  }
1178
1300
 
1179
1301
  /**
@@ -1258,6 +1380,9 @@ function wrapVitestRunFiles (Vitest, frameworkVersion) {
1258
1380
  activeRunFilesContexts.add(this)
1259
1381
  try {
1260
1382
  return await runFiles.apply(this, arguments)
1383
+ } catch (error) {
1384
+ runErrorsByContext.set(this, error)
1385
+ throw error
1261
1386
  } finally {
1262
1387
  activeRunFilesContexts.delete(this)
1263
1388
  }
@@ -1328,7 +1453,7 @@ function getMainProcessProvidedContext (ctx) {
1328
1453
  const providedContext = workspaceProject.getProvidedContext?.() || workspaceProject._provided || {}
1329
1454
 
1330
1455
  return {
1331
- testPropertiesByFilepath: providedContext._ddTestPropertiesByFilepath || {},
1456
+ testPropertiesByFilepath: parseProvidedContextValue(providedContext._ddTestPropertiesByFilepath) || {},
1332
1457
  }
1333
1458
  } catch {
1334
1459
  return {
@@ -1559,6 +1684,7 @@ async function reportTypecheckFile (file, sessionConfiguration, frameworkVersion
1559
1684
  }
1560
1685
 
1561
1686
  await getChannelPromise(testSuiteFinishCh, {
1687
+ deferFlush: true,
1562
1688
  frameworkVersion,
1563
1689
  status: getTypecheckTaskStatus(file),
1564
1690
  ...testSuiteCtx.currentStore,
@@ -1750,7 +1876,10 @@ function getWrappedOn (on) {
1750
1876
  arguments[1] = shimmer.wrapFunction(callback, callback => function (message) {
1751
1877
  if (message.type !== 'Buffer' && Array.isArray(message)) {
1752
1878
  const [interprocessCode, data] = message
1753
- if (handleWorkerReport(interprocessCode, data)) {
1879
+ if (
1880
+ handleEfdAdmissionMessage(this, interprocessCode, data) ||
1881
+ handleWorkerReport(interprocessCode, data)
1882
+ ) {
1754
1883
  // If we execute the callback vitest crashes, as the message is not supported
1755
1884
  return
1756
1885
  }
@@ -1761,6 +1890,37 @@ function getWrappedOn (on) {
1761
1890
  }
1762
1891
  }
1763
1892
 
1893
+ /**
1894
+ * Handles an EFD suite admission request sent through a Vitest worker transport.
1895
+ *
1896
+ * @param {object} workerProcess
1897
+ * @param {number} interprocessCode
1898
+ * @param {object} data
1899
+ * @returns {boolean}
1900
+ */
1901
+ function handleEfdAdmissionMessage (workerProcess, interprocessCode, data) {
1902
+ if (interprocessCode !== VITEST_WORKER_EFD_SUITE_ADMISSION_REQUEST_CODE) return false
1903
+
1904
+ const response = [VITEST_WORKER_EFD_SUITE_ADMISSION_RESPONSE_CODE, {
1905
+ allowed: reserveEarlyFlakeDetectionSuite(data?.testSuite, data?.hasNewTest === true),
1906
+ requestId: data?.requestId,
1907
+ }]
1908
+ try {
1909
+ if (typeof workerProcess.send === 'function') {
1910
+ workerProcess.send(response, (error) => {
1911
+ if (error) {
1912
+ log.error('Could not send Vitest EFD suite admission response: %s', error.message)
1913
+ }
1914
+ })
1915
+ } else {
1916
+ workerProcess.postMessage(response)
1917
+ }
1918
+ } catch (error) {
1919
+ log.error('Could not send Vitest EFD suite admission response: %s', error?.message)
1920
+ }
1921
+ return true
1922
+ }
1923
+
1764
1924
  function handleWorkerReport (interprocessCode, data) {
1765
1925
  if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
1766
1926
  collectTestOptimizationSummariesFromTraces(data, {
@@ -29,10 +29,12 @@ const testSessionFinishCh = channel('ci:vitest:session:finish')
29
29
  const testSessionConfigurationCh = channel('ci:vitest:session:configuration')
30
30
  const libraryConfigurationCh = channel('ci:vitest:library-configuration')
31
31
  const knownTestsCh = channel('ci:vitest:known-tests')
32
- const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detection-faulty')
33
32
  const testManagementTestsCh = channel('ci:vitest:test-management-tests')
34
33
  const modifiedFilesCh = channel('ci:vitest:modified-files')
35
34
 
35
+ const CLOSING_SCRIPT_TAG_RE = /<\/script/i
36
+ const SERIALIZED_CONTEXT_PREFIX = '\u0000dd-vitest-context:'
37
+
36
38
  const workerReportTraceCh = channel('ci:vitest:worker-report:trace')
37
39
  const workerReportCoverageCh = channel('ci:vitest:worker-report:coverage')
38
40
  const workerReportLogsCh = channel('ci:vitest:worker-report:logs')
@@ -105,18 +107,72 @@ function getWorkspaceProject (ctx) {
105
107
 
106
108
  function setProvidedContext (ctx, values, warningMessage) {
107
109
  try {
108
- Object.assign(getWorkspaceProject(ctx)._provided, values)
110
+ const providedContext = getWorkspaceProject(ctx)._provided
111
+ for (const key of Object.keys(values)) {
112
+ providedContext[key] = key.startsWith('_dd')
113
+ ? makeProvidedContextBrowserSafe(values[key])
114
+ : values[key]
115
+ }
109
116
  } catch {
110
117
  log.warn(warningMessage)
111
118
  }
112
119
  }
113
120
 
121
+ /**
122
+ * Prevent Vitest Browser Mode from terminating its inline bootstrap script with Datadog metadata.
123
+ *
124
+ * @param {unknown} value
125
+ * @returns {unknown}
126
+ */
127
+ function makeProvidedContextBrowserSafe (value) {
128
+ if (typeof value !== 'string' && (typeof value !== 'object' || value === null)) return value
129
+ if (typeof value === 'string' && !CLOSING_SCRIPT_TAG_RE.test(value)) return value
130
+
131
+ const serializedValue = JSON.stringify(value)
132
+ return CLOSING_SCRIPT_TAG_RE.test(serializedValue)
133
+ ? SERIALIZED_CONTEXT_PREFIX + serializedValue.replaceAll('<', String.raw`\u003c`)
134
+ : value
135
+ }
136
+
137
+ /**
138
+ * Restore context serialized by makeProvidedContextBrowserSafe.
139
+ *
140
+ * @param {unknown} value
141
+ * @returns {unknown}
142
+ */
143
+ function parseProvidedContextValue (value) {
144
+ if (typeof value !== 'string' || !value.startsWith(SERIALIZED_CONTEXT_PREFIX)) return value
145
+
146
+ let parsedValue
147
+ try {
148
+ parsedValue = JSON.parse(value.slice(SERIALIZED_CONTEXT_PREFIX.length))
149
+ } catch {}
150
+ return parsedValue
151
+ }
152
+
153
+ /**
154
+ * Restores Datadog context values serialized for Vitest Browser Mode.
155
+ *
156
+ * @param {Record<string, unknown>} values
157
+ * @returns {Record<string, unknown>}
158
+ */
159
+ function parseProvidedContextValues (values) {
160
+ const parsedValues = {}
161
+ for (const key of Object.keys(values)) {
162
+ if (key.startsWith('_dd')) {
163
+ parsedValues[key] = parseProvidedContextValue(values[key])
164
+ }
165
+ }
166
+ return parsedValues
167
+ }
168
+
114
169
  function getProvidedContext () {
115
170
  try {
116
171
  const {
117
172
  _ddIsEarlyFlakeDetectionEnabled,
173
+ _ddIsEfdSuiteAdmissionEnabled: isEfdSuiteAdmissionEnabled,
118
174
  _ddIsDiEnabled,
119
- _ddTestPropertiesByFilepath: testPropertiesByFilepath,
175
+ _ddTestPropertiesByFilepath,
120
176
  _ddEarlyFlakeDetectionRetryPolicy: earlyFlakeDetectionRetryPolicy,
121
177
  _ddIsKnownTestsEnabled: isKnownTestsEnabled,
122
178
  _ddIsTestManagementTestsEnabled: isTestManagementTestsEnabled,
@@ -135,11 +191,13 @@ function getProvidedContext () {
135
191
  _ddItrCorrelationId: itrCorrelationId,
136
192
  _ddUnskippableSuites: unskippableSuites,
137
193
  _ddForcedToRunSuites: forcedToRunSuites,
138
- } = globalThis.__vitest_worker__.providedContext
194
+ } = parseProvidedContextValues(globalThis.__vitest_worker__.providedContext)
139
195
  const retryPolicy = earlyFlakeDetectionRetryPolicy ?? EMPTY_EFD_RETRY_POLICY
196
+ const testPropertiesByFilepath = _ddTestPropertiesByFilepath || {}
140
197
 
141
198
  return {
142
199
  isDiEnabled: _ddIsDiEnabled,
200
+ isEfdSuiteAdmissionEnabled,
143
201
  isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled && hasEfdRetries(retryPolicy),
144
202
  testPropertiesByFilepath,
145
203
  earlyFlakeDetectionRetryPolicy: retryPolicy,
@@ -165,6 +223,7 @@ function getProvidedContext () {
165
223
  log.error('Vitest workers could not parse provided context, so some features will not work.')
166
224
  return {
167
225
  isDiEnabled: false,
226
+ isEfdSuiteAdmissionEnabled: false,
168
227
  isEarlyFlakeDetectionEnabled: false,
169
228
  testPropertiesByFilepath: {},
170
229
  earlyFlakeDetectionRetryPolicy: EMPTY_EFD_RETRY_POLICY,
@@ -258,7 +317,6 @@ module.exports = {
258
317
  testSessionConfigurationCh,
259
318
  libraryConfigurationCh,
260
319
  knownTestsCh,
261
- isEarlyFlakeDetectionFaultyCh,
262
320
  testManagementTestsCh,
263
321
  modifiedFilesCh,
264
322
  workerReportTraceCh,
@@ -273,6 +331,7 @@ module.exports = {
273
331
  getTestName,
274
332
  getWorkspaceProject,
275
333
  setProvidedContext,
334
+ parseProvidedContextValue,
276
335
  getProvidedContext,
277
336
  isFlakyTestRetriesEnabledForTask,
278
337
  getVitestTestProperties,