dd-trace 5.109.0 → 5.111.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 (208) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +25 -5
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +79 -24
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +154 -47
  8. package/package.json +20 -16
  9. package/packages/datadog-esbuild/index.js +26 -0
  10. package/packages/datadog-esbuild/src/utils.js +46 -3
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  13. package/packages/datadog-instrumentations/src/fs.js +8 -6
  14. package/packages/datadog-instrumentations/src/graphql.js +26 -484
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  17. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  19. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  23. package/packages/datadog-instrumentations/src/koa.js +3 -2
  24. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  25. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  26. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  27. package/packages/datadog-instrumentations/src/next.js +10 -10
  28. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  29. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  30. package/packages/datadog-instrumentations/src/restify.js +4 -3
  31. package/packages/datadog-instrumentations/src/router.js +18 -5
  32. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  33. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  34. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  35. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  36. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  37. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  38. package/packages/datadog-instrumentations/src/vitest.js +11 -1598
  39. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  45. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  46. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -5
  48. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  49. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  50. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  52. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  53. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  54. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  55. package/packages/datadog-plugin-http/src/client.js +8 -3
  56. package/packages/datadog-plugin-http/src/index.js +6 -8
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-jest/src/index.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  60. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  61. package/packages/datadog-plugin-openai/src/services.js +2 -2
  62. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  63. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  64. package/packages/datadog-plugin-undici/src/index.js +5 -2
  65. package/packages/datadog-plugin-vitest/src/index.js +76 -64
  66. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  67. package/packages/datadog-webpack/index.js +17 -1
  68. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  69. package/packages/dd-trace/src/aiguard/sdk.js +15 -3
  70. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  71. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  72. package/packages/dd-trace/src/appsec/channels.js +3 -1
  73. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  74. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  75. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  76. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  77. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  78. package/packages/dd-trace/src/appsec/index.js +20 -7
  79. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  80. package/packages/dd-trace/src/appsec/reporter.js +50 -10
  81. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  82. package/packages/dd-trace/src/appsec/telemetry/index.js +27 -1
  83. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  84. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  85. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  86. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  87. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  90. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  91. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  92. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  94. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  95. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  96. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  97. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  98. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  99. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  100. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  101. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  102. package/packages/dd-trace/src/config/defaults.js +25 -5
  103. package/packages/dd-trace/src/config/generated-config-types.d.ts +596 -58
  104. package/packages/dd-trace/src/config/helper.js +134 -33
  105. package/packages/dd-trace/src/config/index.js +39 -23
  106. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  107. package/packages/dd-trace/src/config/parsers.js +19 -1
  108. package/packages/dd-trace/src/config/remote_config.js +1 -1
  109. package/packages/dd-trace/src/config/supported-configurations.json +109 -57
  110. package/packages/dd-trace/src/constants.js +7 -0
  111. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  112. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  113. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  114. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  116. package/packages/dd-trace/src/debugger/index.js +5 -0
  117. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  118. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  119. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  120. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  122. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  123. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  124. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  125. package/packages/dd-trace/src/guardrails/index.js +8 -5
  126. package/packages/dd-trace/src/index.js +6 -4
  127. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  128. package/packages/dd-trace/src/llmobs/index.js +17 -6
  129. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  130. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  131. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/sdk.js +8 -6
  134. package/packages/dd-trace/src/llmobs/span_processor.js +5 -1
  135. package/packages/dd-trace/src/llmobs/tagger.js +58 -3
  136. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  137. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  138. package/packages/dd-trace/src/log/index.js +18 -20
  139. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  140. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  141. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  142. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  143. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  145. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  146. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  147. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  148. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  149. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  150. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  151. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  152. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +44 -38
  153. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  154. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  155. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  156. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  157. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  158. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  159. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  160. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  161. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  162. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  163. package/packages/dd-trace/src/plugin_manager.js +14 -8
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -4
  165. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  166. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  167. package/packages/dd-trace/src/plugins/util/test.js +91 -9
  168. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  169. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  170. package/packages/dd-trace/src/priority_sampler.js +1 -13
  171. package/packages/dd-trace/src/profiling/config.js +129 -198
  172. package/packages/dd-trace/src/profiling/constants.js +4 -7
  173. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  174. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  175. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  176. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  177. package/packages/dd-trace/src/profiling/oom.js +71 -0
  178. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  179. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  180. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  181. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  182. package/packages/dd-trace/src/proxy.js +8 -8
  183. package/packages/dd-trace/src/ritm.js +4 -2
  184. package/packages/dd-trace/src/serverless.js +1 -2
  185. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  186. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  187. package/packages/dd-trace/src/span_format.js +2 -1
  188. package/packages/dd-trace/src/span_processor.js +11 -6
  189. package/packages/dd-trace/src/span_stats.js +96 -78
  190. package/packages/dd-trace/src/startup-log.js +2 -1
  191. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  192. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  193. package/packages/dd-trace/src/telemetry/index.js +2 -2
  194. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  195. package/packages/dd-trace/src/telemetry/send-data.js +20 -18
  196. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  197. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  198. package/packages/dd-trace/src/tracer.js +15 -1
  199. package/packages/dd-trace/src/util.js +14 -0
  200. package/register.js +58 -1
  201. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  202. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  203. package/vendor/dist/pprof-format/index.js +1 -1
  204. package/vendor/dist/protobufjs/index.js +1 -1
  205. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  206. package/vendor/dist/shell-quote/index.js +1 -1
  207. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
  208. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -0,0 +1,753 @@
1
+ 'use strict'
2
+
3
+ // Capture real timers at module load time, before any test can install fake timers.
4
+ const realSetTimeout = setTimeout
5
+
6
+ const { performance } = require('node:perf_hooks')
7
+
8
+ const shimmer = require('../../datadog-shimmer')
9
+ const {
10
+ DYNAMIC_NAME_RE,
11
+ getEfdRetryCount,
12
+ recordAttemptToFixExecution,
13
+ logAttemptToFixTestExecution,
14
+ } = require('../../dd-trace/src/plugins/util/test')
15
+ const { addHook } = require('./helpers/instrument')
16
+ const {
17
+ testStartCh,
18
+ testFinishTimeCh,
19
+ testPassCh,
20
+ testErrorCh,
21
+ testSkipCh,
22
+ testFnCh,
23
+ testSuiteStartCh,
24
+ testSuiteFinishCh,
25
+ testSuiteErrorCh,
26
+ findExportByName,
27
+ getTestRunnerExport,
28
+ getTypeTasks,
29
+ getTestName,
30
+ getProvidedContext,
31
+ isFlakyTestRetriesEnabledForTask,
32
+ getVitestTestProperties,
33
+ } = require('./vitest-util')
34
+
35
+ const BREAKPOINT_HIT_GRACE_PERIOD_MS = 400
36
+
37
+ const taskToCtx = new WeakMap()
38
+ const taskToTestProperties = new WeakMap()
39
+ const taskToStatuses = new WeakMap()
40
+ const taskToReportedErrorCount = new WeakMap()
41
+ const attemptToFixTaskToStatuses = new WeakMap()
42
+ const originalHookFns = new WeakMap()
43
+ const newTasks = new WeakSet()
44
+ const dynamicNameTasks = new WeakSet()
45
+ const disabledTasks = new WeakSet()
46
+ const quarantinedTasks = new WeakSet()
47
+ const attemptToFixTasks = new WeakSet()
48
+ const modifiedTasks = new WeakSet()
49
+ const efdDeterminedRetries = new WeakMap()
50
+ const efdSlowAbortedTasks = new WeakSet()
51
+ const efdExecutionStartByTask = new WeakMap()
52
+ const efdSkippedRetryResults = new WeakMap()
53
+ const attemptToFixExecutions = new Map()
54
+ const loggedAttemptToFixTests = new Set()
55
+ let isRetryReasonEfd = false
56
+ let isRetryReasonAttemptToFix = false
57
+ const switchedStatuses = new WeakSet()
58
+ let vitestGetFn = null
59
+ let vitestSetFn = null
60
+ let vitestGetHooks = null
61
+
62
+ function waitForHitProbe () {
63
+ return new Promise(resolve => {
64
+ realSetTimeout(() => {
65
+ resolve()
66
+ }, BREAKPOINT_HIT_GRACE_PERIOD_MS)
67
+ })
68
+ }
69
+
70
+ function getVitestTestStatus (test, retryCount) {
71
+ if (test.result.state !== 'fail' && (!test.repeats || (test.retry ?? 0) === retryCount)) {
72
+ return 'pass'
73
+ }
74
+ return 'fail'
75
+ }
76
+
77
+ function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
78
+ if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
79
+ return testCtx.status
80
+ }
81
+
82
+ return state === 'fail' ? 'fail' : 'pass'
83
+ }
84
+
85
+ /**
86
+ * Return the normalized test suite path prepared by the main process for a Vitest task.
87
+ *
88
+ * @param {{ file: { filepath: string } }} task
89
+ * @returns {string}
90
+ */
91
+ function getTaskTestSuite (task) {
92
+ return taskToTestProperties.get(task)?.testSuite || task.file.filepath
93
+ }
94
+
95
+ function recordFinalAttemptToFixExecution (task, status, providedContext) {
96
+ const statuses = attemptToFixTaskToStatuses.get(task)
97
+ if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
98
+ statuses.push(status)
99
+ }
100
+
101
+ recordAttemptToFixExecution(attemptToFixExecutions, {
102
+ testSuite: getTaskTestSuite(task),
103
+ testName: getTestName(task),
104
+ status,
105
+ isDisabled: disabledTasks.has(task),
106
+ isQuarantined: quarantinedTasks.has(task),
107
+ })
108
+ }
109
+
110
+ function disableFrameworkRetries (task) {
111
+ task.retry = 0
112
+ }
113
+
114
+ function getCurrentAttemptTestError (task, errors) {
115
+ if (!errors?.length) return
116
+
117
+ const previousErrorCount = taskToReportedErrorCount.get(task) ?? 0
118
+ const testError = errors[previousErrorCount] ?? errors[0]
119
+ taskToReportedErrorCount.set(task, errors.length)
120
+ return testError
121
+ }
122
+
123
+ function wrapTestScopedFn (task, fn) {
124
+ return shimmer.wrapFunction(fn, fn => function (...args) {
125
+ return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
126
+ })
127
+ }
128
+
129
+ function wrapBeforeEachCleanupResult (task, result) {
130
+ if (typeof result === 'function') {
131
+ return wrapTestScopedFn(task, result)
132
+ }
133
+
134
+ if (result && typeof result.then === 'function') {
135
+ return result.then(cleanupFn => wrapBeforeEachCleanupResult(task, cleanupFn))
136
+ }
137
+
138
+ return result
139
+ }
140
+
141
+ function wrapVitestTestRunner (VitestTestRunner) {
142
+ // `onBeforeRunTask` is run before any repetition or attempt is run
143
+ // `onBeforeRunTask` is an async function
144
+ shimmer.wrap(VitestTestRunner.prototype, 'onBeforeRunTask', onBeforeRunTask => function (task) {
145
+ const testName = getTestName(task)
146
+
147
+ const providedContext = getProvidedContext()
148
+ const {
149
+ isEarlyFlakeDetectionEnabled,
150
+ isKnownTestsEnabled,
151
+ numRepeats,
152
+ isTestManagementTestsEnabled,
153
+ testManagementAttemptToFixRetries,
154
+ isImpactedTestsEnabled,
155
+ } = providedContext
156
+ const testProperties = getVitestTestProperties(providedContext, task.file.filepath, testName)
157
+ taskToTestProperties.set(task, testProperties)
158
+
159
+ if (isTestManagementTestsEnabled) {
160
+ const {
161
+ isAttemptToFix,
162
+ isDisabled,
163
+ isQuarantined,
164
+ } = testProperties
165
+ if (isAttemptToFix) {
166
+ isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
167
+ disableFrameworkRetries(task)
168
+ task.repeats = testManagementAttemptToFixRetries
169
+ attemptToFixTasks.add(task)
170
+ attemptToFixTaskToStatuses.set(task, [])
171
+ }
172
+ if (isQuarantined) {
173
+ quarantinedTasks.add(task)
174
+ }
175
+ if (isDisabled) {
176
+ disabledTasks.add(task)
177
+ if (!attemptToFixTasks.has(task)) {
178
+ // we only actually skip if the test is not being attempted to be fixed
179
+ task.mode = 'skip'
180
+ }
181
+ }
182
+ }
183
+
184
+ if (isImpactedTestsEnabled && testProperties.isModified) {
185
+ if (isEarlyFlakeDetectionEnabled) {
186
+ isRetryReasonEfd = true
187
+ disableFrameworkRetries(task)
188
+ task.repeats = numRepeats
189
+ }
190
+ modifiedTasks.add(task)
191
+ taskToStatuses.set(task, [])
192
+ }
193
+
194
+ if (isKnownTestsEnabled && testProperties.isNew && !attemptToFixTasks.has(task)) {
195
+ if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
196
+ isRetryReasonEfd = true
197
+ disableFrameworkRetries(task)
198
+ task.repeats = numRepeats
199
+ }
200
+ newTasks.add(task)
201
+ taskToStatuses.set(task, [])
202
+ if (DYNAMIC_NAME_RE.test(testName)) {
203
+ dynamicNameTasks.add(task)
204
+ }
205
+ }
206
+
207
+ return onBeforeRunTask.apply(this, arguments)
208
+ })
209
+
210
+ // `onAfterRunTask` is run after all repetitions or attempts are run
211
+ // `onAfterRunTask` is an async function
212
+ shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => function (task) {
213
+ const { isEarlyFlakeDetectionEnabled, isTestManagementTestsEnabled } = getProvidedContext()
214
+
215
+ if (isTestManagementTestsEnabled) {
216
+ const isAttemptingToFix = attemptToFixTasks.has(task)
217
+ const isQuarantined = quarantinedTasks.has(task)
218
+
219
+ if (isAttemptingToFix) {
220
+ const statuses = attemptToFixTaskToStatuses.get(task)
221
+ if (task.result.state === 'pass' && statuses?.includes('fail')) {
222
+ switchedStatuses.add(task)
223
+ task.result.state = 'fail'
224
+ }
225
+ }
226
+
227
+ if (!isAttemptingToFix && isQuarantined) {
228
+ if (task.result.state === 'fail') {
229
+ switchedStatuses.add(task)
230
+ }
231
+ task.result.state = 'pass'
232
+ }
233
+ }
234
+
235
+ if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)) {
236
+ const statuses = taskToStatuses.get(task)
237
+ // If the test has passed at least once, we consider it passed
238
+ if (statuses.includes('pass')) {
239
+ if (task.result.state === 'fail') {
240
+ switchedStatuses.add(task)
241
+ }
242
+ task.result.state = 'pass'
243
+ }
244
+ }
245
+
246
+ return onAfterRunTask.apply(this, arguments)
247
+ })
248
+
249
+ // test start (only tests that are not marked as skip or todo)
250
+ // `onBeforeTryTask` is run for every repetition and attempt of the test
251
+ shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
252
+ if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
253
+ return onBeforeTryTask.apply(this, arguments)
254
+ }
255
+ const testName = getTestName(task)
256
+ let isNew = false
257
+ const providedContext = getProvidedContext()
258
+ const {
259
+ isKnownTestsEnabled,
260
+ isEarlyFlakeDetectionEnabled,
261
+ isDiEnabled,
262
+ slowTestRetries,
263
+ } = providedContext
264
+
265
+ if (isKnownTestsEnabled) {
266
+ isNew = newTasks.has(task)
267
+ }
268
+
269
+ const { retry: numAttempt, repeats: numRepetition } = retryInfo
270
+ const isEfdManagedTask = isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)
271
+
272
+ if (isEfdManagedTask && numRepetition > 0 && !efdDeterminedRetries.has(task)) {
273
+ const previousExecutionStart = efdExecutionStartByTask.get(task)
274
+ const duration = previousExecutionStart === undefined
275
+ ? task.result?.duration ?? 0
276
+ : performance.now() - previousExecutionStart
277
+ const retryCount = getEfdRetryCount(duration, slowTestRetries)
278
+ efdDeterminedRetries.set(task, retryCount)
279
+ task.repeats = retryCount
280
+ if (retryCount === 0) {
281
+ efdSlowAbortedTasks.add(task)
282
+ }
283
+ }
284
+
285
+ const efdRetryCount = efdDeterminedRetries.get(task)
286
+ if (isEfdManagedTask && efdRetryCount !== undefined && numRepetition > efdRetryCount) {
287
+ if (task.result) {
288
+ efdSkippedRetryResults.set(task, {
289
+ ...task.result,
290
+ errors: task.result.errors?.slice(),
291
+ })
292
+ }
293
+ if (vitestSetFn) {
294
+ const noop = function () {}
295
+ noop.__ddTraceWrapped = true
296
+ vitestSetFn(task, noop)
297
+ }
298
+ return onBeforeTryTask.apply(this, arguments)
299
+ }
300
+ if (isEfdManagedTask) {
301
+ efdExecutionStartByTask.set(task, performance.now())
302
+ }
303
+
304
+ // We finish the previous test here because we know it has failed already
305
+ if (numAttempt > 0) {
306
+ const shouldWaitForHitProbe = isDiEnabled && numAttempt > 1
307
+ if (shouldWaitForHitProbe) {
308
+ await waitForHitProbe()
309
+ }
310
+
311
+ const promises = {}
312
+ const shouldSetProbe = isDiEnabled && numAttempt === 1
313
+ const ctx = taskToCtx.get(task)
314
+ const testError = getCurrentAttemptTestError(task, task.result?.errors)
315
+ if (ctx) {
316
+ testErrorCh.publish({
317
+ error: testError,
318
+ shouldSetProbe,
319
+ promises,
320
+ ...ctx.currentStore,
321
+ })
322
+ // We wait for the probe to be set
323
+ if (promises.setProbePromise) {
324
+ await promises.setProbePromise
325
+ }
326
+ }
327
+ }
328
+
329
+ const lastExecutionStatus = task.result.state
330
+ const isAtf = attemptToFixTasks.has(task)
331
+ const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
332
+ const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
333
+ const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
334
+
335
+ // These clauses handle task.repeats, whether EFD is enabled or not
336
+ // The only thing that EFD does is to forcefully pass the test if it has passed at least once
337
+ if (numRepetition > 0 && numRepetition < task.repeats) { // it may or may have not failed
338
+ // Here we finish the earlier iteration,
339
+ // as long as it's not the _last_ iteration (which will be finished normally)
340
+
341
+ const ctx = taskToCtx.get(task)
342
+ if (ctx) {
343
+ if (lastExecutionStatus === 'fail') {
344
+ const testError = getCurrentAttemptTestError(task, task.result?.errors)
345
+ testErrorCh.publish({ error: testError, ...ctx.currentStore })
346
+ } else {
347
+ testPassCh.publish({ task, ...ctx.currentStore })
348
+ }
349
+ if (shouldTrackStatuses && statuses) {
350
+ statuses.push(lastExecutionStatus)
351
+ }
352
+ if (shouldFlipStatus) {
353
+ // If we don't "reset" the result.state to "pass", once a repetition fails,
354
+ // vitest will always consider the test as failed, so we can't read the actual status
355
+ // This means that we change vitest's behavior:
356
+ // if the last attempt passes, vitest would consider the test as failed
357
+ // but after this change, it will consider the test as passed
358
+ task.result.state = 'pass'
359
+ }
360
+ }
361
+ } else if (numRepetition === task.repeats) {
362
+ if (shouldTrackStatuses && statuses) {
363
+ statuses.push(lastExecutionStatus)
364
+ }
365
+
366
+ const ctx = taskToCtx.get(task)
367
+ if (lastExecutionStatus === 'fail') {
368
+ const testError = getCurrentAttemptTestError(task, task.result?.errors)
369
+ testErrorCh.publish({ error: testError, ...ctx.currentStore })
370
+ } else {
371
+ testPassCh.publish({ task, ...ctx.currentStore })
372
+ }
373
+ if (shouldFlipStatus) {
374
+ task.result.state = 'pass'
375
+ }
376
+ }
377
+
378
+ const isRetryReasonAtr = numAttempt > 0 &&
379
+ isFlakyTestRetriesEnabledForTask(providedContext, task) &&
380
+ !isRetryReasonAttemptToFix &&
381
+ !isRetryReasonEfd
382
+
383
+ const ctx = {
384
+ testName,
385
+ testSuiteAbsolutePath: task.file.filepath,
386
+ isRetry: numAttempt > 0 || numRepetition > 0,
387
+ isRetryReasonEfd,
388
+ isRetryReasonAttemptToFix: isRetryReasonAttemptToFix && numRepetition > 0,
389
+ isNew,
390
+ hasDynamicName: dynamicNameTasks.has(task),
391
+ mightHitProbe: isDiEnabled && numAttempt > 0,
392
+ isAttemptToFix: attemptToFixTasks.has(task),
393
+ isDisabled: disabledTasks.has(task),
394
+ isQuarantined: quarantinedTasks.has(task),
395
+ isRetryReasonAtr,
396
+ isModified: modifiedTasks.has(task),
397
+ }
398
+ taskToCtx.set(task, ctx)
399
+
400
+ if (attemptToFixTasks.has(task)) {
401
+ logAttemptToFixTestExecution(
402
+ getTaskTestSuite(task),
403
+ testName,
404
+ loggedAttemptToFixTests
405
+ )
406
+ }
407
+
408
+ testStartCh.runStores(ctx, () => {})
409
+
410
+ // Wrap the test function so it runs inside the test span context.
411
+ // Without this, HTTP requests during test execution become orphaned root spans.
412
+ if (vitestGetFn && vitestSetFn) {
413
+ const originalFn = vitestGetFn(task)
414
+ if (originalFn && !originalFn.__ddTraceWrapped) {
415
+ const wrappedFn = wrapTestScopedFn(task, originalFn)
416
+ wrappedFn.__ddTraceWrapped = true
417
+ vitestSetFn(task, wrappedFn)
418
+ }
419
+ }
420
+
421
+ // Wrap beforeEach/afterEach hooks so they also run inside the test span context.
422
+ // In vitest 4+, hooks are in a WeakMap accessed via getHooks(). In older versions, they're on suite.hooks.
423
+ let currentSuite = task.suite
424
+ while (currentSuite) {
425
+ const hooks = vitestGetHooks ? vitestGetHooks(currentSuite) : currentSuite.hooks
426
+ if (hooks) {
427
+ for (const hookType of ['beforeEach', 'afterEach']) {
428
+ const hookArray = hooks[hookType]
429
+ if (!hookArray) continue
430
+ for (let i = 0; i < hookArray.length; i++) {
431
+ const currentFn = hookArray[i]
432
+ const originalFn = originalHookFns.get(currentFn) || currentFn
433
+ const wrappedFn = shimmer.wrapFunction(originalFn, fn => function (...args) {
434
+ const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
435
+
436
+ if (hookType === 'beforeEach') {
437
+ return wrapBeforeEachCleanupResult(task, result)
438
+ }
439
+
440
+ return result
441
+ })
442
+ originalHookFns.set(wrappedFn, originalFn)
443
+ hookArray[i] = wrappedFn
444
+ }
445
+ }
446
+ }
447
+ currentSuite = currentSuite.suite
448
+ }
449
+
450
+ return onBeforeTryTask.apply(this, arguments)
451
+ })
452
+
453
+ // test finish (only passed tests)
454
+ shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
455
+ async function (task, retryInfo) {
456
+ if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
457
+ return onAfterTryTask.apply(this, arguments)
458
+ }
459
+ const result = await onAfterTryTask.apply(this, arguments)
460
+
461
+ const {
462
+ isEarlyFlakeDetectionEnabled,
463
+ testManagementAttemptToFixRetries,
464
+ slowTestRetries,
465
+ } = getProvidedContext()
466
+
467
+ const status = getVitestTestStatus(task, retryInfo.retry)
468
+ const ctx = taskToCtx.get(task)
469
+
470
+ const { isDiEnabled } = getProvidedContext()
471
+
472
+ if (efdSkippedRetryResults.has(task)) {
473
+ task.result = efdSkippedRetryResults.get(task)
474
+ efdSkippedRetryResults.delete(task)
475
+ return result
476
+ }
477
+
478
+ if (isDiEnabled && retryInfo.retry > 1) {
479
+ await waitForHitProbe()
480
+ }
481
+
482
+ if (
483
+ isEarlyFlakeDetectionEnabled &&
484
+ (retryInfo.repeats ?? 0) === 0 &&
485
+ taskToStatuses.has(task) &&
486
+ !attemptToFixTasks.has(task) &&
487
+ !efdDeterminedRetries.has(task)
488
+ ) {
489
+ const executionStart = efdExecutionStartByTask.get(task)
490
+ const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
491
+ const retryCount = getEfdRetryCount(duration, slowTestRetries)
492
+ efdDeterminedRetries.set(task, retryCount)
493
+ task.repeats = retryCount
494
+ if (retryCount === 0) {
495
+ efdSlowAbortedTasks.add(task)
496
+ }
497
+ }
498
+
499
+ let attemptToFixPassed = false
500
+ let attemptToFixFailed = false
501
+ if (attemptToFixTasks.has(task)) {
502
+ const statuses = attemptToFixTaskToStatuses.get(task)
503
+ if (statuses.length === testManagementAttemptToFixRetries) {
504
+ if (status === 'pass' && statuses.every(status => status === 'pass')) {
505
+ attemptToFixPassed = true
506
+ } else if (status === 'fail' || statuses.includes('fail')) {
507
+ attemptToFixFailed = true
508
+ }
509
+ }
510
+ }
511
+
512
+ if (ctx) {
513
+ // We don't finish here because the test might fail in a later hook (afterEach)
514
+ ctx.status = status
515
+ ctx.task = task
516
+ ctx.attemptToFixPassed = attemptToFixPassed
517
+ ctx.attemptToFixFailed = attemptToFixFailed
518
+ testFinishTimeCh.runStores(ctx, () => {})
519
+ }
520
+
521
+ return result
522
+ })
523
+ }
524
+
525
+ function captureRunnerFunctions (pkg) {
526
+ if (vitestGetFn) return
527
+ const getFnExport = findExportByName(pkg, 'getFn')
528
+ const setFnExport = findExportByName(pkg, 'setFn')
529
+ if (getFnExport && setFnExport) {
530
+ vitestGetFn = getFnExport.value
531
+ vitestSetFn = setFnExport.value
532
+ }
533
+ const getHooksExport = findExportByName(pkg, 'getHooks')
534
+ if (getHooksExport) {
535
+ vitestGetHooks = getHooksExport.value
536
+ }
537
+ }
538
+
539
+ addHook({
540
+ name: 'vitest',
541
+ versions: ['>=4.0.0'],
542
+ filePattern: 'dist/chunks/test.*',
543
+ }, (testPackage) => {
544
+ const testRunner = getTestRunnerExport(testPackage)
545
+ if (!testRunner) {
546
+ return testPackage
547
+ }
548
+
549
+ captureRunnerFunctions(testPackage)
550
+ wrapVitestTestRunner(testRunner.value)
551
+
552
+ return testPackage
553
+ })
554
+
555
+ addHook({
556
+ name: '@vitest/runner',
557
+ versions: ['>=1.6.0'],
558
+ }, (runnerModule) => {
559
+ if (!vitestGetFn && runnerModule.getFn && runnerModule.setFn) {
560
+ vitestGetFn = runnerModule.getFn
561
+ vitestSetFn = runnerModule.setFn
562
+ }
563
+ if (!vitestGetHooks && runnerModule.getHooks) {
564
+ vitestGetHooks = runnerModule.getHooks
565
+ }
566
+ return runnerModule
567
+ })
568
+
569
+ addHook({
570
+ name: 'vitest',
571
+ versions: ['>=1.6.0 <4.0.0'],
572
+ file: 'dist/runners.js',
573
+ }, (vitestPackage) => {
574
+ const { VitestTestRunner } = vitestPackage
575
+
576
+ wrapVitestTestRunner(VitestTestRunner)
577
+
578
+ return vitestPackage
579
+ })
580
+
581
+ // test suite start and finish
582
+ // only relevant for workers
583
+ addHook({
584
+ name: '@vitest/runner',
585
+ versions: ['>=1.6.0'],
586
+ }, (vitestPackage, frameworkVersion) => {
587
+ shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPaths) {
588
+ let testSuiteError = null
589
+ if (!testSuiteFinishCh.hasSubscribers) {
590
+ return startTests.apply(this, arguments)
591
+ }
592
+ // From >=3.0.1, the first arguments changes from a string to an object containing the filepath
593
+ const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
594
+ const providedContext = getProvidedContext()
595
+
596
+ const testSuiteCtx = {
597
+ testSuiteAbsolutePath,
598
+ frameworkVersion,
599
+ testSessionId: providedContext.testSessionId,
600
+ testModuleId: providedContext.testModuleId,
601
+ testCommand: providedContext.testCommand,
602
+ repositoryRoot: providedContext.repositoryRoot,
603
+ codeOwnersEntries: providedContext.codeOwnersEntries,
604
+ }
605
+ testSuiteStartCh.runStores(testSuiteCtx, () => {})
606
+ const startTestsResponse = await startTests.apply(this, arguments)
607
+
608
+ let onFinish = null
609
+ const onFinishPromise = new Promise(resolve => {
610
+ onFinish = resolve
611
+ })
612
+
613
+ const testTasks = getTypeTasks(startTestsResponse[0].tasks)
614
+
615
+ // Only one test task per test, even if there are retries
616
+ for (const task of testTasks) {
617
+ const testCtx = taskToCtx.get(task)
618
+ const { result } = task
619
+ // We have to trick vitest into thinking that the test has passed
620
+ // but we want to report it as failed if it did fail
621
+ const isSwitchedStatus = switchedStatuses.has(task)
622
+
623
+ if (result) {
624
+ const { state, duration, errors } = result
625
+ const testError = getCurrentAttemptTestError(task, errors)
626
+ if (attemptToFixTasks.has(task)) {
627
+ const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
628
+ recordFinalAttemptToFixExecution(task, status, providedContext)
629
+ }
630
+
631
+ if (state === 'skip') { // programmatic skip
632
+ testSkipCh.publish({
633
+ testName: getTestName(task),
634
+ testSuiteAbsolutePath: task.file.filepath,
635
+ isNew: newTasks.has(task),
636
+ isDisabled: disabledTasks.has(task),
637
+ })
638
+ } else if (state === 'pass' && !isSwitchedStatus) {
639
+ if (testCtx) {
640
+ const isSkippedByTestManagement =
641
+ !attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
642
+ testPassCh.publish({
643
+ task,
644
+ finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
645
+ earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
646
+ ...testCtx.currentStore,
647
+ })
648
+ }
649
+ } else if (state === 'fail' || isSwitchedStatus) {
650
+ let hasFailedAllRetries = false
651
+ let attemptToFixFailed = false
652
+ if (attemptToFixTasks.has(task)) {
653
+ const statuses = attemptToFixTaskToStatuses.get(task)
654
+ if (statuses.includes('fail')) {
655
+ attemptToFixFailed = true
656
+ }
657
+ if (statuses.every(status => status === 'fail')) {
658
+ hasFailedAllRetries = true
659
+ }
660
+ }
661
+
662
+ // Check if all EFD retries failed
663
+ const isEfdRetry =
664
+ providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
665
+ if (isEfdRetry) {
666
+ const statuses = taskToStatuses.get(task)
667
+ const efdRetryCount = efdDeterminedRetries.get(task) ?? providedContext.numRepeats
668
+ // statuses only includes repetitions (not the initial run), so we check against retry count (not +1)
669
+ if (efdRetryCount > 0 && statuses && statuses.length === efdRetryCount &&
670
+ statuses.every(status => status === 'fail')) {
671
+ hasFailedAllRetries = true
672
+ }
673
+ }
674
+
675
+ // ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
676
+ const isAtrRetry = isFlakyTestRetriesEnabledForTask(providedContext, task) && !attemptToFixTasks.has(task) &&
677
+ !newTasks.has(task) && !modifiedTasks.has(task)
678
+ if (isAtrRetry) {
679
+ const maxRetries = providedContext.flakyTestRetriesCount ?? 0
680
+ if (maxRetries > 0 && task.result?.retryCount === maxRetries) {
681
+ hasFailedAllRetries = true
682
+ }
683
+ }
684
+
685
+ if (testCtx) {
686
+ const isRetry = task.result?.retryCount > 0
687
+ // `duration` is the duration of all the retries, so it can't be used if there are retries
688
+
689
+ let finalStatus
690
+ if (isSwitchedStatus) {
691
+ if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
692
+ finalStatus = 'skip'
693
+ } else if (isAtrRetry || isEfdRetry) {
694
+ finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
695
+ } else if (attemptToFixTasks.has(task)) {
696
+ finalStatus = attemptToFixFailed ? 'fail' : 'pass'
697
+ } else {
698
+ finalStatus = undefined
699
+ }
700
+ } else {
701
+ finalStatus = 'fail'
702
+ }
703
+
704
+ testErrorCh.publish({
705
+ duration: isRetry ? undefined : duration,
706
+ error: testError,
707
+ hasFailedAllRetries,
708
+ attemptToFixFailed,
709
+ finalStatus,
710
+ earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
711
+ ...testCtx.currentStore,
712
+ })
713
+ }
714
+ if (errors?.length) {
715
+ testSuiteError = testError // we store the error to bubble it up to the suite
716
+ }
717
+ }
718
+ } else { // test.skip or test.todo
719
+ testSkipCh.publish({
720
+ testName: getTestName(task),
721
+ testSuiteAbsolutePath: task.file.filepath,
722
+ isNew: newTasks.has(task),
723
+ isDisabled: disabledTasks.has(task),
724
+ })
725
+ }
726
+ }
727
+
728
+ const testSuiteResult = startTestsResponse[0].result
729
+
730
+ if (testSuiteResult.errors?.length) { // Errors from root level hooks
731
+ testSuiteError = testSuiteResult.errors[0]
732
+ } else if (testSuiteResult.state === 'fail') { // Errors from `describe` level hooks
733
+ const suiteTasks = getTypeTasks(startTestsResponse[0].tasks, 'suite')
734
+ const failedSuites = suiteTasks.filter(task => task.result?.state === 'fail')
735
+ if (failedSuites.length && failedSuites[0].result?.errors?.length) {
736
+ testSuiteError = failedSuites[0].result.errors[0]
737
+ }
738
+ }
739
+
740
+ if (testSuiteError) {
741
+ testSuiteCtx.error = testSuiteError
742
+ testSuiteErrorCh.runStores(testSuiteCtx, () => {})
743
+ }
744
+
745
+ testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish, ...testSuiteCtx.currentStore })
746
+
747
+ await onFinishPromise
748
+
749
+ return startTestsResponse
750
+ })
751
+
752
+ return vitestPackage
753
+ })