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