dd-trace 5.110.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 (115) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +21 -2
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +13 -0
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +76 -55
  8. package/package.json +9 -9
  9. package/packages/datadog-instrumentations/src/fs.js +8 -6
  10. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  13. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  14. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  15. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  16. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  17. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  18. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  19. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  20. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  21. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  22. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +10 -4
  24. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  25. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  26. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  27. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  28. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  29. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  30. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  31. package/packages/datadog-plugin-http/src/index.js +6 -8
  32. package/packages/datadog-plugin-jest/src/index.js +1 -1
  33. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  34. package/packages/datadog-plugin-openai/src/services.js +2 -2
  35. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  36. package/packages/datadog-plugin-vitest/src/index.js +69 -63
  37. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  38. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  39. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  40. package/packages/dd-trace/src/appsec/channels.js +3 -1
  41. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  42. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  43. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  44. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  45. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  46. package/packages/dd-trace/src/appsec/index.js +10 -1
  47. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  48. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  49. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  50. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  51. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  52. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  53. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  54. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  55. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  56. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  57. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  58. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  59. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  60. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  62. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  63. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  64. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  65. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  66. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  67. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  68. package/packages/dd-trace/src/config/defaults.js +3 -2
  69. package/packages/dd-trace/src/config/generated-config-types.d.ts +61 -57
  70. package/packages/dd-trace/src/config/helper.js +1 -0
  71. package/packages/dd-trace/src/config/index.js +35 -22
  72. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  73. package/packages/dd-trace/src/config/remote_config.js +1 -1
  74. package/packages/dd-trace/src/config/supported-configurations.json +90 -56
  75. package/packages/dd-trace/src/constants.js +7 -0
  76. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  77. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  78. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  79. package/packages/dd-trace/src/llmobs/index.js +1 -1
  80. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  81. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  82. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  83. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  84. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  85. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  87. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  88. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  89. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  90. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  91. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  92. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  93. package/packages/dd-trace/src/plugin_manager.js +7 -7
  94. package/packages/dd-trace/src/plugins/ci_plugin.js +12 -3
  95. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  96. package/packages/dd-trace/src/plugins/util/test.js +11 -9
  97. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  98. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  99. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  100. package/packages/dd-trace/src/proxy.js +8 -8
  101. package/packages/dd-trace/src/span_processor.js +5 -6
  102. package/packages/dd-trace/src/span_stats.js +96 -78
  103. package/packages/dd-trace/src/startup-log.js +2 -1
  104. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  105. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  106. package/packages/dd-trace/src/telemetry/index.js +2 -2
  107. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  108. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  109. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  110. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  111. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  112. package/vendor/dist/protobufjs/index.js +1 -1
  113. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  114. package/vendor/dist/shell-quote/index.js +1 -1
  115. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -1,1861 +1,17 @@
1
1
  'use strict'
2
2
 
3
- // Capture real timers at module load time, before any test can install fake timers.
4
- const realSetTimeout = setTimeout
3
+ const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
4
+ const { isTrue } = require('../../dd-trace/src/util')
5
5
 
6
- const path = require('node:path')
7
- const { performance } = require('node:perf_hooks')
6
+ const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
8
7
 
9
- const shimmer = require('../../datadog-shimmer')
10
- const log = require('../../dd-trace/src/log')
11
- const {
12
- VITEST_WORKER_TRACE_PAYLOAD_CODE,
13
- VITEST_WORKER_LOGS_PAYLOAD_CODE,
14
- DYNAMIC_NAME_RE,
15
- getTestSuitePath,
16
- getEfdRetryCount,
17
- getMaxEfdRetryCount,
18
- recordAttemptToFixExecution,
19
- collectTestOptimizationSummariesFromTraces,
20
- logAttemptToFixTestExecution,
21
- logTestOptimizationSummary,
22
- getTestOptimizationRequestResults,
23
- } = require('../../dd-trace/src/plugins/util/test')
24
- const { addHook, channel } = require('./helpers/instrument')
8
+ if (getEnvironmentVariable('DD_VITEST_WORKER') === '1') {
9
+ // In no-worker-init mode the main process reports Vitest events from reporter hooks.
10
+ // Loading worker instrumentation here would reintroduce the worker tracer path.
11
+ // eslint-disable-next-line eslint-rules/eslint-process-env
12
+ if (isTrue(process.env[VITEST_NO_WORKER_INIT_ACTIVE_ENV])) return
25
13
 
26
- // test hooks
27
- const testStartCh = channel('ci:vitest:test:start')
28
- const testFinishTimeCh = channel('ci:vitest:test:finish-time')
29
- const testPassCh = channel('ci:vitest:test:pass')
30
- const testErrorCh = channel('ci:vitest:test:error')
31
- const testSkipCh = channel('ci:vitest:test:skip')
32
- const isNewTestCh = channel('ci:vitest:test:is-new')
33
- const isAttemptToFixCh = channel('ci:vitest:test:is-attempt-to-fix')
34
- const isDisabledCh = channel('ci:vitest:test:is-disabled')
35
- const isQuarantinedCh = channel('ci:vitest:test:is-quarantined')
36
- const isModifiedCh = channel('ci:vitest:test:is-modified')
37
- const testFnCh = channel('ci:vitest:test:fn')
38
-
39
- // test suite hooks
40
- const testSuiteStartCh = channel('ci:vitest:test-suite:start')
41
- const testSuiteFinishCh = channel('ci:vitest:test-suite:finish')
42
- const testSuiteErrorCh = channel('ci:vitest:test-suite:error')
43
-
44
- // test session hooks
45
- const testSessionStartCh = channel('ci:vitest:session:start')
46
- const testSessionFinishCh = channel('ci:vitest:session:finish')
47
- const testSessionConfigurationCh = channel('ci:vitest:session:configuration')
48
- const libraryConfigurationCh = channel('ci:vitest:library-configuration')
49
- const knownTestsCh = channel('ci:vitest:known-tests')
50
- const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detection-faulty')
51
- const testManagementTestsCh = channel('ci:vitest:test-management-tests')
52
- const modifiedFilesCh = channel('ci:vitest:modified-files')
53
-
54
- const workerReportTraceCh = channel('ci:vitest:worker-report:trace')
55
- const workerReportLogsCh = channel('ci:vitest:worker-report:logs')
56
- const codeCoverageReportCh = channel('ci:vitest:coverage-report')
57
-
58
- const taskToCtx = new WeakMap()
59
- const taskToStatuses = new WeakMap()
60
- const taskToReportedErrorCount = new WeakMap()
61
- const attemptToFixTaskToStatuses = new WeakMap()
62
- const originalHookFns = new WeakMap()
63
- const newTasks = new WeakSet()
64
- const dynamicNameTasks = new WeakSet()
65
- const newTestsWithDynamicNames = new Set()
66
- const disabledTasks = new WeakSet()
67
- const quarantinedTasks = new WeakSet()
68
- const attemptToFixTasks = new WeakSet()
69
- const modifiedTasks = new WeakSet()
70
- const efdDeterminedRetries = new WeakMap()
71
- const efdSlowAbortedTasks = new WeakSet()
72
- const efdExecutionStartByTask = new WeakMap()
73
- const efdSkippedRetryResults = new WeakMap()
74
- const attemptToFixExecutions = new Map()
75
- const loggedAttemptToFixTests = new Set()
76
- let isRetryReasonEfd = false
77
- let isRetryReasonAttemptToFix = false
78
- const switchedStatuses = new WeakSet()
79
- const workerProcesses = new WeakSet()
80
- const mainProcessSetupPromises = new WeakMap()
81
- const coverageWrappedProviders = new WeakSet()
82
- const finishWrappedContexts = new WeakSet()
83
- let isFlakyTestRetriesEnabled = false
84
- let flakyTestRetriesCount = 0
85
- let isEarlyFlakeDetectionEnabled = false
86
- let earlyFlakeDetectionNumRetries = 0
87
- let earlyFlakeDetectionSlowTestRetries = {}
88
- let isEarlyFlakeDetectionFaulty = false
89
- let isKnownTestsEnabled = false
90
- let isTestManagementTestsEnabled = false
91
- let isImpactedTestsEnabled = false
92
- let vitestGetFn = null
93
- let vitestSetFn = null
94
- let vitestGetHooks = null
95
- let testManagementAttemptToFixRetries = 0
96
- let isDiEnabled = false
97
- let testCodeCoverageLinesTotal
98
- let coverageRootDir
99
- let isSessionStarted = false
100
- let vitestPool = null
101
-
102
- const BREAKPOINT_HIT_GRACE_PERIOD_MS = 400
103
-
104
- function getConfiguredEfdRetryCount (slowTestRetries, fallbackRetryCount) {
105
- if (!slowTestRetries || !Object.keys(slowTestRetries).length) {
106
- return fallbackRetryCount
107
- }
108
- return getMaxEfdRetryCount(slowTestRetries)
109
- }
110
-
111
- function getTestCommand () {
112
- return `vitest ${process.argv.slice(2).join(' ')}`
113
- }
114
-
115
- function waitForHitProbe () {
116
- return new Promise(resolve => {
117
- realSetTimeout(() => {
118
- resolve()
119
- }, BREAKPOINT_HIT_GRACE_PERIOD_MS)
120
- })
121
- }
122
-
123
- function isValidKnownTests (receivedKnownTests) {
124
- return !!receivedKnownTests.vitest
125
- }
126
-
127
- function getProvidedContext () {
128
- try {
129
- const {
130
- _ddIsEarlyFlakeDetectionEnabled,
131
- _ddIsDiEnabled,
132
- _ddKnownTests: knownTests,
133
- _ddEarlyFlakeDetectionNumRetries: numRepeats,
134
- _ddEarlyFlakeDetectionSlowTestRetries: slowTestRetries,
135
- _ddIsKnownTestsEnabled: isKnownTestsEnabled,
136
- _ddIsTestManagementTestsEnabled: isTestManagementTestsEnabled,
137
- _ddTestManagementAttemptToFixRetries: testManagementAttemptToFixRetries,
138
- _ddTestManagementTests: testManagementTests,
139
- _ddIsFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled,
140
- _ddFlakyTestRetriesCount: flakyTestRetriesCount,
141
- _ddFlakyTestRetriesIncludesUnnamedProject: flakyTestRetriesIncludesUnnamedProject,
142
- _ddFlakyTestRetriesProjectNames: flakyTestRetriesProjectNames,
143
- _ddIsImpactedTestsEnabled: isImpactedTestsEnabled,
144
- _ddModifiedFiles: modifiedFiles,
145
- _ddTestSessionId: testSessionId,
146
- _ddTestModuleId: testModuleId,
147
- _ddTestCommand: testCommand,
148
- _ddRepositoryRoot: repositoryRoot,
149
- _ddCodeOwnersEntries: codeOwnersEntries,
150
- } = globalThis.__vitest_worker__.providedContext
151
-
152
- return {
153
- isDiEnabled: _ddIsDiEnabled,
154
- isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled,
155
- knownTests,
156
- numRepeats,
157
- slowTestRetries: slowTestRetries ?? {},
158
- isKnownTestsEnabled,
159
- isTestManagementTestsEnabled,
160
- testManagementAttemptToFixRetries,
161
- testManagementTests,
162
- isFlakyTestRetriesEnabled,
163
- flakyTestRetriesCount: flakyTestRetriesCount ?? 0,
164
- flakyTestRetriesIncludesUnnamedProject,
165
- flakyTestRetriesProjectNames,
166
- isImpactedTestsEnabled,
167
- modifiedFiles,
168
- testSessionId,
169
- testModuleId,
170
- testCommand,
171
- repositoryRoot,
172
- codeOwnersEntries,
173
- }
174
- } catch {
175
- log.error('Vitest workers could not parse provided context, so some features will not work.')
176
- return {
177
- isDiEnabled: false,
178
- isEarlyFlakeDetectionEnabled: false,
179
- knownTests: {},
180
- numRepeats: 0,
181
- slowTestRetries: {},
182
- isKnownTestsEnabled: false,
183
- isTestManagementTestsEnabled: false,
184
- testManagementAttemptToFixRetries: 0,
185
- testManagementTests: {},
186
- isFlakyTestRetriesEnabled: false,
187
- flakyTestRetriesCount: 0,
188
- flakyTestRetriesIncludesUnnamedProject: false,
189
- flakyTestRetriesProjectNames: undefined,
190
- isImpactedTestsEnabled: false,
191
- modifiedFiles: {},
192
- testSessionId: undefined,
193
- testModuleId: undefined,
194
- testCommand: undefined,
195
- repositoryRoot: undefined,
196
- codeOwnersEntries: undefined,
197
- }
198
- }
199
- }
200
-
201
- function isReporterPackage (vitestPackage) {
202
- return vitestPackage.B?.name === 'BaseSequencer'
203
- }
204
-
205
- // from 2.0.0
206
- function isReporterPackageNew (vitestPackage) {
207
- return vitestPackage.e?.name === 'BaseSequencer'
208
- }
209
-
210
- function isReporterPackageNewest (vitestPackage) {
211
- return vitestPackage.h?.name === 'BaseSequencer'
212
- }
213
-
214
- /**
215
- * Finds an export by its `.name` property in a minified vitest chunk.
216
- * Minified export keys change across versions, so we search by function/class name.
217
- * @param {object} pkg - The module exports object
218
- * @param {string} name - The `.name` value to look for
219
- * @returns {{ key: string, value: Function } | undefined}
220
- */
221
- function findExportByName (pkg, name) {
222
- for (const [key, value] of Object.entries(pkg)) {
223
- if (value?.name === name) {
224
- return { key, value }
225
- }
226
- }
227
- }
228
-
229
- function getBaseSequencerExport (vitestPackage) {
230
- return findExportByName(vitestPackage, 'BaseSequencer')
231
- }
232
-
233
- function getChannelPromise (channelToPublishTo, frameworkVersion) {
234
- return new Promise(resolve => {
235
- channelToPublishTo.publish({ onDone: resolve, frameworkVersion })
236
- })
237
- }
238
-
239
- function isCliApiPackage (vitestPackage) {
240
- return !!findExportByName(vitestPackage, 'startVitest')
241
- }
242
-
243
- function getTestRunnerExport (testPackage) {
244
- return findExportByName(testPackage, 'VitestTestRunner') || findExportByName(testPackage, 'TestRunner')
245
- }
246
-
247
- function getVitestExport (vitestPackage) {
248
- return findExportByName(vitestPackage, 'Vitest')
249
- }
250
-
251
- function getForksPoolWorkerExport (vitestPackage) {
252
- return findExportByName(vitestPackage, 'ForksPoolWorker')
14
+ require('./vitest-worker')
15
+ } else {
16
+ require('./vitest-main')
253
17
  }
254
-
255
- function getThreadsPoolWorkerExport (vitestPackage) {
256
- return findExportByName(vitestPackage, 'ThreadsPoolWorker')
257
- }
258
-
259
- function getSessionStatus (state) {
260
- if (state.getCountOfFailedTests() > 0) {
261
- return 'fail'
262
- }
263
- if (state.pathsSet.size === 0) {
264
- return 'skip'
265
- }
266
- return 'pass'
267
- }
268
-
269
- // From https://github.com/vitest-dev/vitest/blob/51c04e2f44d91322b334f8ccbcdb368facc3f8ec/packages/runner/src/run.ts#L243-L250
270
- function getVitestTestStatus (test, retryCount) {
271
- if (test.result.state !== 'fail' && (!test.repeats || (test.retry ?? 0) === retryCount)) {
272
- return 'pass'
273
- }
274
- return 'fail'
275
- }
276
-
277
- function getTypeTasks (fileTasks, type = 'test') {
278
- const typeTasks = []
279
-
280
- function getTasks (tasks) {
281
- for (const task of tasks) {
282
- if (task.type === type) {
283
- typeTasks.push(task)
284
- } else if (task.tasks) {
285
- getTasks(task.tasks)
286
- }
287
- }
288
- }
289
-
290
- getTasks(fileTasks)
291
-
292
- return typeTasks
293
- }
294
-
295
- function getTestName (task) {
296
- let testName = task.name
297
- let currentTask = task.suite
298
-
299
- while (currentTask) {
300
- if (currentTask.name) {
301
- testName = `${currentTask.name} ${testName}`
302
- }
303
- currentTask = currentTask.suite
304
- }
305
-
306
- return testName
307
- }
308
-
309
- function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
310
- if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
311
- return testCtx.status
312
- }
313
-
314
- return state === 'fail' ? 'fail' : 'pass'
315
- }
316
-
317
- function recordFinalAttemptToFixExecution (task, status, providedContext) {
318
- const statuses = attemptToFixTaskToStatuses.get(task)
319
- if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
320
- statuses.push(status)
321
- }
322
-
323
- recordAttemptToFixExecution(attemptToFixExecutions, {
324
- testSuite: getTestSuitePath(task.file.filepath, process.cwd()),
325
- testName: getTestName(task),
326
- status,
327
- isDisabled: disabledTasks.has(task),
328
- isQuarantined: quarantinedTasks.has(task),
329
- })
330
- }
331
-
332
- function disableFrameworkRetries (task) {
333
- task.retry = 0
334
- }
335
-
336
- /**
337
- * Vitest accumulates retry and repeat errors on one task result. The first error added since
338
- * the last reported attempt is the primary error for the failed attempt currently being reported.
339
- *
340
- * @param {object} task
341
- * @param {Array<object> | undefined} errors
342
- * @returns {object | undefined}
343
- */
344
- function getCurrentAttemptTestError (task, errors) {
345
- if (!errors?.length) return
346
-
347
- const previousErrorCount = taskToReportedErrorCount.get(task) ?? 0
348
- const testError = errors[previousErrorCount] ?? errors[0]
349
- taskToReportedErrorCount.set(task, errors.length)
350
- return testError
351
- }
352
-
353
- /**
354
- * Wraps a function so it runs inside the current test span context.
355
- * @param {object} task
356
- * @param {Function} fn
357
- * @returns {Function}
358
- */
359
- function wrapTestScopedFn (task, fn) {
360
- return shimmer.wrapFunction(fn, fn => function (...args) {
361
- return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
362
- })
363
- }
364
-
365
- /**
366
- * Wraps a `beforeEach` cleanup callback so it inherits the test span context.
367
- * Vitest allows `beforeEach` to return a cleanup function, including via a promise.
368
- * @param {object} task
369
- * @param {unknown} result
370
- * @returns {unknown}
371
- */
372
- function wrapBeforeEachCleanupResult (task, result) {
373
- if (typeof result === 'function') {
374
- return wrapTestScopedFn(task, result)
375
- }
376
-
377
- if (result && typeof result.then === 'function') {
378
- return result.then(cleanupFn => wrapBeforeEachCleanupResult(task, cleanupFn))
379
- }
380
-
381
- return result
382
- }
383
-
384
- function getWorkspaceProject (ctx) {
385
- return ctx.getCoreWorkspaceProject
386
- ? ctx.getCoreWorkspaceProject()
387
- : ctx.getRootProject()
388
- }
389
-
390
- function setProvidedContext (ctx, values, warningMessage) {
391
- try {
392
- Object.assign(getWorkspaceProject(ctx)._provided, values)
393
- } catch {
394
- log.warn(warningMessage)
395
- }
396
- }
397
-
398
- function getTestFilepathsFromSpecifications (testSpecifications) {
399
- if (!Array.isArray(testSpecifications) || !testSpecifications.length) {
400
- return
401
- }
402
-
403
- return testSpecifications.map(testSpecification => {
404
- const testFile = Array.isArray(testSpecification) ? testSpecification[1] : testSpecification
405
- return testFile?.moduleId || testFile?.filepath || testFile
406
- })
407
- }
408
-
409
- function getTestFilepaths (ctx, testSpecifications) {
410
- const testFilepaths = getTestFilepathsFromSpecifications(testSpecifications)
411
- if (testFilepaths) {
412
- return testFilepaths
413
- }
414
-
415
- const getFilePaths = ctx.getTestFilepaths || ctx._globTestFilepaths
416
- return getFilePaths.call(ctx)
417
- }
418
-
419
- function wrapCoverageProvider (ctx) {
420
- const { coverageProvider } = ctx
421
- if (!coverageProvider?.generateCoverage || coverageWrappedProviders.has(coverageProvider)) {
422
- return
423
- }
424
- coverageWrappedProviders.add(coverageProvider)
425
-
426
- // Capture coverage root directory from config (default is 'coverage' in cwd)
427
- try {
428
- const coverageConfig = ctx.config?.coverage
429
- const reportsDirectory = coverageConfig?.reportsDirectory || 'coverage'
430
- const rootDir = ctx.config?.root || process.cwd()
431
- coverageRootDir = path.isAbsolute(reportsDirectory) ? reportsDirectory : path.join(rootDir, reportsDirectory)
432
- } catch {
433
- // Fallback to cwd if we can't get config
434
- coverageRootDir = process.cwd()
435
- }
436
-
437
- shimmer.wrap(coverageProvider, 'generateCoverage', generateCoverage => async function () {
438
- const totalCodeCoverage = await generateCoverage.apply(this, arguments)
439
-
440
- try {
441
- testCodeCoverageLinesTotal = totalCodeCoverage.getCoverageSummary().lines.pct
442
- } catch {
443
- // ignore errors
444
- }
445
- return totalCodeCoverage
446
- })
447
- }
448
-
449
- function wrapSessionFinish (ctx) {
450
- if (finishWrappedContexts.has(ctx)) {
451
- return
452
- }
453
- finishWrappedContexts.add(ctx)
454
-
455
- shimmer.wrap(ctx, 'exit', getFinishWrapper)
456
- shimmer.wrap(ctx, 'close', getFinishWrapper)
457
- }
458
-
459
- async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications) {
460
- if (!testSessionFinishCh.hasSubscribers) {
461
- return
462
- }
463
-
464
- try {
465
- const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh, frameworkVersion)
466
- if (!err) {
467
- isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
468
- flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
469
- isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
470
- earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
471
- earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
472
- isDiEnabled = libraryConfig.isDiEnabled
473
- isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
474
- isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
475
- testManagementAttemptToFixRetries = libraryConfig.testManagementAttemptToFixRetries
476
- isImpactedTestsEnabled = libraryConfig.isImpactedTestsEnabled
477
- }
478
- } catch {
479
- isFlakyTestRetriesEnabled = false
480
- isEarlyFlakeDetectionEnabled = false
481
- isDiEnabled = false
482
- isKnownTestsEnabled = false
483
- isImpactedTestsEnabled = false
484
- }
485
-
486
- if (testSessionConfigurationCh.hasSubscribers) {
487
- const { testSessionId, testModuleId, testCommand, repositoryRoot, codeOwnersEntries } = await getChannelPromise(
488
- testSessionConfigurationCh,
489
- frameworkVersion
490
- )
491
- setProvidedContext(ctx, {
492
- _ddTestSessionId: testSessionId,
493
- _ddTestModuleId: testModuleId,
494
- _ddTestCommand: testCommand,
495
- _ddRepositoryRoot: repositoryRoot,
496
- _ddCodeOwnersEntries: codeOwnersEntries,
497
- }, 'Could not send test session configuration to workers.')
498
- }
499
-
500
- const {
501
- knownTestsResponse,
502
- testManagementTestsResponse,
503
- } = await getTestOptimizationRequestResults({
504
- isKnownTestsEnabled,
505
- isTestManagementTestsEnabled,
506
- getKnownTests: () => getChannelPromise(knownTestsCh),
507
- getTestManagementTests: () => getChannelPromise(testManagementTestsCh),
508
- })
509
-
510
- const flakyTestRetriesConfiguration = configureFlakyTestRetries(ctx, testSpecifications)
511
- if (flakyTestRetriesConfiguration) {
512
- setProvidedContext(ctx, {
513
- _ddIsFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled,
514
- _ddFlakyTestRetriesCount: flakyTestRetriesCount,
515
- _ddFlakyTestRetriesIncludesUnnamedProject: flakyTestRetriesConfiguration.includesUnnamedProject,
516
- _ddFlakyTestRetriesProjectNames: flakyTestRetriesConfiguration.projectNames,
517
- }, 'Could not send library configuration to workers.')
518
- }
519
-
520
- if (isKnownTestsEnabled) {
521
- const currentKnownTestsResponse = knownTestsResponse || await getChannelPromise(knownTestsCh)
522
- if (currentKnownTestsResponse.err) {
523
- isEarlyFlakeDetectionEnabled = false
524
- } else {
525
- const knownTests = currentKnownTestsResponse.knownTests
526
- const testFilepaths = await getTestFilepaths(ctx, testSpecifications)
527
-
528
- if (isValidKnownTests(knownTests)) {
529
- isEarlyFlakeDetectionFaultyCh.publish({
530
- knownTests: knownTests.vitest,
531
- testFilepaths,
532
- onDone: (isFaulty) => {
533
- isEarlyFlakeDetectionFaulty = isFaulty
534
- },
535
- })
536
- if (isEarlyFlakeDetectionFaulty) {
537
- isEarlyFlakeDetectionEnabled = false
538
- log.warn('New test detection is disabled because the number of new tests is too high.')
539
- } else {
540
- setProvidedContext(ctx, {
541
- _ddIsKnownTestsEnabled: isKnownTestsEnabled,
542
- _ddKnownTests: knownTests,
543
- _ddIsEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionEnabled,
544
- _ddEarlyFlakeDetectionNumRetries:
545
- getConfiguredEfdRetryCount(earlyFlakeDetectionSlowTestRetries, earlyFlakeDetectionNumRetries),
546
- _ddEarlyFlakeDetectionSlowTestRetries: earlyFlakeDetectionSlowTestRetries,
547
- }, 'Could not send known tests to workers so Early Flake Detection will not work.')
548
- }
549
- } else {
550
- isEarlyFlakeDetectionFaulty = true
551
- isEarlyFlakeDetectionEnabled = false
552
- }
553
- }
554
- }
555
-
556
- if (isDiEnabled) {
557
- setProvidedContext(ctx, {
558
- _ddIsDiEnabled: isDiEnabled,
559
- }, 'Could not send Dynamic Instrumentation configuration to workers.')
560
- }
561
-
562
- if (isTestManagementTestsEnabled) {
563
- const { err, testManagementTests: receivedTestManagementTests } =
564
- testManagementTestsResponse || await getChannelPromise(testManagementTestsCh)
565
- if (err) {
566
- isTestManagementTestsEnabled = false
567
- log.error('Could not get test management tests.')
568
- } else {
569
- setProvidedContext(ctx, {
570
- _ddIsTestManagementTestsEnabled: isTestManagementTestsEnabled,
571
- _ddTestManagementAttemptToFixRetries: testManagementAttemptToFixRetries,
572
- _ddTestManagementTests: receivedTestManagementTests,
573
- }, 'Could not send test management tests to workers so Test Management will not work.')
574
- }
575
- }
576
-
577
- if (isImpactedTestsEnabled) {
578
- const { err, modifiedFiles } = await getChannelPromise(modifiedFilesCh)
579
- if (err) {
580
- log.error('Could not get modified tests.')
581
- } else {
582
- setProvidedContext(ctx, {
583
- _ddIsImpactedTestsEnabled: isImpactedTestsEnabled,
584
- _ddModifiedFiles: modifiedFiles,
585
- }, 'Could not send modified tests to workers so Impacted Tests will not work.')
586
- }
587
- }
588
-
589
- wrapCoverageProvider(ctx)
590
- wrapSessionFinish(ctx)
591
- }
592
-
593
- function ensureMainProcessSetup (ctx, frameworkVersion, testSpecifications) {
594
- let setupPromise = mainProcessSetupPromises.get(ctx)
595
- if (!setupPromise) {
596
- setupPromise = runMainProcessSetup(ctx, frameworkVersion, testSpecifications)
597
- mainProcessSetupPromises.set(ctx, setupPromise)
598
- }
599
- return setupPromise
600
- }
601
-
602
- /**
603
- * Configure Vitest retries for the root project and resolved workspace projects.
604
- *
605
- * @param {object} ctx
606
- * @param {unknown[]|undefined} testSpecifications
607
- * @returns {{ projectNames: string[], includesUnnamedProject: boolean }|undefined}
608
- */
609
- function configureFlakyTestRetries (ctx, testSpecifications) {
610
- if (!isFlakyTestRetriesEnabled || flakyTestRetriesCount <= 0) return
611
-
612
- let configured = false
613
- let includesUnnamedProject = false
614
- const projectNames = []
615
- for (const { config, projectName } of getVitestProjectConfigs(ctx, testSpecifications)) {
616
- if (!config.retry) {
617
- config.retry = flakyTestRetriesCount
618
- configured = true
619
- if (projectName) {
620
- projectNames.push(projectName)
621
- } else {
622
- includesUnnamedProject = true
623
- }
624
- }
625
- }
626
-
627
- if (!configured) return
628
-
629
- return {
630
- includesUnnamedProject,
631
- projectNames,
632
- }
633
- }
634
-
635
- /**
636
- * Return unique Vitest configs that can be used to run tests.
637
- *
638
- * @param {object} ctx
639
- * @param {unknown[]|undefined} testSpecifications
640
- * @returns {{ config: object, projectName?: string }[]}
641
- */
642
- function getVitestProjectConfigs (ctx, testSpecifications) {
643
- const entries = []
644
-
645
- addTestSpecificationConfigs(entries, testSpecifications)
646
- if (entries.length > 0) {
647
- return entries
648
- }
649
-
650
- const selectedProjectNames = getSelectedProjectNames()
651
- addSelectedInlineProjectConfigs(entries, safeConfig(ctx), selectedProjectNames)
652
- addSelectedRuntimeProjectConfigs(entries, ctx?.projects, selectedProjectNames)
653
- if (entries.length > 0) {
654
- return entries
655
- }
656
-
657
- if (Array.isArray(ctx?.projects)) {
658
- for (const project of ctx.projects) {
659
- addConfig(entries, safeConfig(project), getProjectName(project))
660
- }
661
- if (entries.length > 0) {
662
- return entries
663
- }
664
- }
665
-
666
- addConfig(entries, safeConfig(ctx))
667
- addConfig(entries, safeConfig(safeWorkspaceProject(ctx)))
668
-
669
- return entries
670
- }
671
-
672
- /**
673
- * Add configs from runnable test specifications once.
674
- *
675
- * @param {{ config: object, projectName?: string }[]} entries
676
- * @param {unknown[]|undefined} testSpecifications
677
- */
678
- function addTestSpecificationConfigs (entries, testSpecifications) {
679
- if (!Array.isArray(testSpecifications)) return
680
-
681
- for (const testSpecification of testSpecifications) {
682
- const project = getTestSpecificationProject(testSpecification)
683
- addConfig(entries, safeConfig(project), getProjectName(project))
684
- }
685
- }
686
-
687
- /**
688
- * Add selected inline project configs from the root Vitest config once.
689
- *
690
- * @param {{ config: object, projectName?: string }[]} entries
691
- * @param {object|undefined} rootConfig
692
- * @param {string[]} selectedProjectNames
693
- */
694
- function addSelectedInlineProjectConfigs (entries, rootConfig, selectedProjectNames) {
695
- if (selectedProjectNames.length === 0 || !Array.isArray(rootConfig?.projects)) return
696
-
697
- for (const project of rootConfig.projects) {
698
- const config = getInlineProjectConfig(project)
699
- const projectName = getProjectName(project)
700
- if (selectedProjectNames.includes(projectName)) {
701
- addConfig(entries, config, projectName)
702
- }
703
- }
704
- }
705
-
706
- /**
707
- * Add selected resolved project configs once.
708
- *
709
- * @param {{ config: object, projectName?: string }[]} entries
710
- * @param {unknown[]|undefined} projects
711
- * @param {string[]} selectedProjectNames
712
- */
713
- function addSelectedRuntimeProjectConfigs (entries, projects, selectedProjectNames) {
714
- if (selectedProjectNames.length === 0 || !Array.isArray(projects)) return
715
-
716
- for (const project of projects) {
717
- const projectName = getProjectName(project)
718
- if (selectedProjectNames.includes(projectName)) {
719
- addConfig(entries, safeConfig(project), projectName)
720
- }
721
- }
722
- }
723
-
724
- /**
725
- * Return selected project names from the Vitest CLI arguments.
726
- *
727
- * @returns {string[]}
728
- */
729
- function getSelectedProjectNames () {
730
- const names = []
731
- for (let index = 0; index < process.argv.length; index++) {
732
- const argument = process.argv[index]
733
- if (argument === '--project' && process.argv[index + 1]) {
734
- names.push(process.argv[index + 1])
735
- index++
736
- } else if (argument.startsWith('--project=')) {
737
- names.push(argument.slice('--project='.length))
738
- }
739
- }
740
- return names
741
- }
742
-
743
- /**
744
- * Return the test config from an inline Vitest project entry.
745
- *
746
- * @param {unknown} project
747
- * @returns {object|undefined}
748
- */
749
- function getInlineProjectConfig (project) {
750
- return project?.test || project
751
- }
752
-
753
- /**
754
- * Return a Vitest project name from runtime or inline project objects.
755
- *
756
- * @param {unknown} project
757
- * @returns {string|undefined}
758
- */
759
- function getProjectName (project) {
760
- return normalizeProjectName(project?.name || project?.config?.name || project?.test?.name)
761
- }
762
-
763
- /**
764
- * Return a normalized Vitest project name.
765
- *
766
- * @param {unknown} name
767
- * @returns {string|undefined}
768
- */
769
- function normalizeProjectName (name) {
770
- if (typeof name === 'string') return name
771
-
772
- const label = name?.label
773
- return typeof label === 'string' ? label : undefined
774
- }
775
-
776
- /**
777
- * Add a config object once.
778
- *
779
- * @param {{ config: object, projectName?: string }[]} entries
780
- * @param {object|undefined} config
781
- * @param {string|undefined} projectName
782
- */
783
- function addConfig (entries, config, projectName) {
784
- if (config && !entries.some(entry => entry.config === config || (projectName && entry.projectName === projectName))) {
785
- entries.push({ config, projectName })
786
- }
787
- }
788
-
789
- /**
790
- * Read a Vitest config object without assuming the project is initialized.
791
- *
792
- * @param {object|undefined} project
793
- * @returns {object|undefined}
794
- */
795
- function safeConfig (project) {
796
- let config
797
- try {
798
- config = project?.config
799
- } catch {}
800
- return config
801
- }
802
-
803
- /**
804
- * Read the workspace project without assuming the root server is initialized.
805
- *
806
- * @param {object} ctx
807
- * @returns {object|undefined}
808
- */
809
- function safeWorkspaceProject (ctx) {
810
- let project
811
- try {
812
- project = getWorkspaceProject(ctx)
813
- } catch {}
814
- return project
815
- }
816
-
817
- /**
818
- * Return whether Datadog configured ATR retries for a task.
819
- *
820
- * @param {object} providedContext
821
- * @param {object} task
822
- * @returns {boolean}
823
- */
824
- function isFlakyTestRetriesEnabledForTask (providedContext, task) {
825
- if (!providedContext.isFlakyTestRetriesEnabled) return false
826
-
827
- const { flakyTestRetriesProjectNames } = providedContext
828
- if (!Array.isArray(flakyTestRetriesProjectNames)) return true
829
-
830
- const projectName = task.file?.projectName
831
- if (!projectName) {
832
- return providedContext.flakyTestRetriesIncludesUnnamedProject === true
833
- }
834
-
835
- return flakyTestRetriesProjectNames.includes(projectName)
836
- }
837
-
838
- function getSortWrapper (sort, frameworkVersion) {
839
- return async function () {
840
- await ensureMainProcessSetup(this.ctx, frameworkVersion, arguments[0])
841
- return sort.apply(this, arguments)
842
- }
843
- }
844
-
845
- function getFinishWrapper (exitOrClose) {
846
- let isClosed = false
847
- return async function () {
848
- if (isClosed) { // needed because exit calls close
849
- return exitOrClose.apply(this, arguments)
850
- }
851
- isClosed = true
852
-
853
- if (!testSessionFinishCh.hasSubscribers) {
854
- return exitOrClose.apply(this, arguments)
855
- }
856
-
857
- let onFinish
858
-
859
- const flushPromise = new Promise(resolve => {
860
- onFinish = resolve
861
- })
862
- const failedSuites = this.state.getFailedFilepaths()
863
- let error
864
- if (failedSuites.length) {
865
- error = new Error(`Test suites failed: ${failedSuites.length}.`)
866
- }
867
-
868
- testSessionFinishCh.publish({
869
- status: getSessionStatus(this.state),
870
- testCodeCoverageLinesTotal,
871
- error,
872
- isEarlyFlakeDetectionEnabled,
873
- isEarlyFlakeDetectionFaulty,
874
- isTestManagementTestsEnabled,
875
- vitestPool,
876
- onFinish,
877
- })
878
-
879
- logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
880
- loggedAttemptToFixTests.clear()
881
-
882
- await flushPromise
883
-
884
- // If coverage was generated, publish coverage report channel for upload
885
- if (coverageRootDir && codeCoverageReportCh.hasSubscribers) {
886
- await new Promise((resolve) => {
887
- codeCoverageReportCh.publish({ rootDir: coverageRootDir, onDone: resolve })
888
- })
889
- }
890
-
891
- return exitOrClose.apply(this, arguments)
892
- }
893
- }
894
-
895
- function getCliOrStartVitestWrapper (frameworkVersion) {
896
- return function (oldCliOrStartVitest) {
897
- return function (...args) {
898
- if (!testSessionStartCh.hasSubscribers || isSessionStarted) {
899
- return oldCliOrStartVitest.apply(this, args)
900
- }
901
- isSessionStarted = true
902
- testSessionStartCh.publish({ command: getTestCommand(), frameworkVersion })
903
- return oldCliOrStartVitest.apply(this, args)
904
- }
905
- }
906
- }
907
-
908
- function isForkPool (pool) {
909
- return pool === 'forks' || pool === 'vmForks'
910
- }
911
-
912
- function isThreadPool (pool) {
913
- return pool === 'threads' || pool === 'vmThreads'
914
- }
915
-
916
- /**
917
- * Return the project object attached to a Vitest test specification.
918
- *
919
- * @param {unknown} testSpecification
920
- * @returns {object|undefined}
921
- */
922
- function getTestSpecificationProject (testSpecification) {
923
- if (Array.isArray(testSpecification)) {
924
- return testSpecification[0]
925
- }
926
- return testSpecification?.project
927
- }
928
-
929
- function getTestSpecificationPool (testSpecification) {
930
- const project = getTestSpecificationProject(testSpecification)
931
- return project?.config?.pool || project?.serializedConfig?.pool || project?.pool || testSpecification?.pool
932
- }
933
-
934
- function hasForkPoolTestSpecification (testSpecifications) {
935
- if (!Array.isArray(testSpecifications)) {
936
- return false
937
- }
938
-
939
- for (const testSpecification of testSpecifications) {
940
- if (isForkPool(getTestSpecificationPool(testSpecification))) {
941
- return true
942
- }
943
- }
944
-
945
- return false
946
- }
947
-
948
- function shouldMarkVitestWorkerEnv (pool, testSpecifications) {
949
- return isForkPool(pool) || hasForkPoolTestSpecification(testSpecifications) ||
950
- (!testSpecifications && !isThreadPool(pool))
951
- }
952
-
953
- function markVitestWorkerEnv (ctx, testSpecifications) {
954
- const config = ctx?.config
955
- if (!config || !shouldMarkVitestWorkerEnv(config.pool, testSpecifications)) {
956
- return
957
- }
958
- config.env = config.env || {}
959
- config.env.DD_VITEST_WORKER = '1'
960
- }
961
-
962
- function wrapVitestRunFiles (Vitest, frameworkVersion) {
963
- if (!Vitest?.prototype?.runFiles) {
964
- return
965
- }
966
-
967
- shimmer.wrap(Vitest.prototype, 'runFiles', runFiles => async function (testSpecifications) {
968
- markVitestWorkerEnv(this, testSpecifications)
969
- await ensureMainProcessSetup(this, frameworkVersion, testSpecifications)
970
- return runFiles.apply(this, arguments)
971
- })
972
-
973
- if (Vitest.prototype.collectTests) {
974
- shimmer.wrap(Vitest.prototype, 'collectTests', collectTests => function () {
975
- markVitestWorkerEnv(this)
976
- return collectTests.apply(this, arguments)
977
- })
978
- }
979
- }
980
-
981
- function getCreateCliWrapper (vitestPackage, frameworkVersion) {
982
- const createCliExport = findExportByName(vitestPackage, 'createCLI')
983
- if (!createCliExport) {
984
- return vitestPackage
985
- }
986
- shimmer.wrap(vitestPackage, createCliExport.key, getCliOrStartVitestWrapper(frameworkVersion))
987
-
988
- return vitestPackage
989
- }
990
-
991
- function threadHandler (thread) {
992
- const { runtime } = thread
993
- let workerProcess
994
- if (runtime === 'child_process') {
995
- vitestPool = 'child_process'
996
- workerProcess = thread.process
997
- } else if (runtime === 'worker_threads') {
998
- vitestPool = 'worker_threads'
999
- workerProcess = thread.thread
1000
- } else {
1001
- vitestPool = 'unknown'
1002
- }
1003
- if (!workerProcess) {
1004
- log.error('Vitest error: could not get process or thread from TinyPool#run')
1005
- return
1006
- }
1007
-
1008
- if (workerProcesses.has(workerProcess)) {
1009
- return
1010
- }
1011
- workerProcesses.add(workerProcess)
1012
- workerProcess.on('message', (message) => {
1013
- if (message.__tinypool_worker_message__ && message.data) {
1014
- if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
1015
- collectTestOptimizationSummariesFromTraces(message.data, {
1016
- newTestsWithDynamicNames,
1017
- attemptToFixExecutions,
1018
- })
1019
- workerReportTraceCh.publish(message.data)
1020
- } else if (message.interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
1021
- workerReportLogsCh.publish(message.data)
1022
- }
1023
- }
1024
- })
1025
- }
1026
-
1027
- function wrapTinyPoolRun (TinyPool) {
1028
- shimmer.wrap(TinyPool.prototype, 'run', run => async function () {
1029
- // We have to do this before and after because the threads list gets recycled, that is, the processes are re-created
1030
- // eslint-disable-next-line unicorn/no-array-for-each
1031
- this.threads.forEach(threadHandler)
1032
- const runResult = await run.apply(this, arguments)
1033
- // eslint-disable-next-line unicorn/no-array-for-each
1034
- this.threads.forEach(threadHandler)
1035
- return runResult
1036
- })
1037
- }
1038
-
1039
- addHook({
1040
- name: 'tinypool',
1041
- // version from tinypool@0.8 was used in vitest@1.6.0
1042
- versions: ['>=0.8.0'],
1043
- }, (TinyPool) => {
1044
- wrapTinyPoolRun(TinyPool)
1045
- return TinyPool
1046
- })
1047
-
1048
- function getWrappedOn (on) {
1049
- return function (event, callback) {
1050
- if (event !== 'message') {
1051
- return on.apply(this, arguments)
1052
- }
1053
- // `arguments[1]` is the callback function, which
1054
- // we modify to intercept our messages to not interfere
1055
- // with vitest's own messages
1056
- arguments[1] = shimmer.wrapFunction(callback, callback => function (message) {
1057
- if (message.type !== 'Buffer' && Array.isArray(message)) {
1058
- const [interprocessCode, data] = message
1059
- if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
1060
- collectTestOptimizationSummariesFromTraces(data, {
1061
- newTestsWithDynamicNames,
1062
- attemptToFixExecutions,
1063
- })
1064
- workerReportTraceCh.publish(data)
1065
- } else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
1066
- workerReportLogsCh.publish(data)
1067
- }
1068
- // If we execute the callback vitest crashes, as the message is not supported
1069
- return
1070
- }
1071
- return callback.apply(this, arguments)
1072
- })
1073
- return on.apply(this, arguments)
1074
- }
1075
- }
1076
-
1077
- function getStartVitestWrapper (cliApiPackage, frameworkVersion) {
1078
- if (!isCliApiPackage(cliApiPackage)) {
1079
- return cliApiPackage
1080
- }
1081
- const startVitestExport = findExportByName(cliApiPackage, 'startVitest')
1082
- shimmer.wrap(cliApiPackage, startVitestExport.key, getCliOrStartVitestWrapper(frameworkVersion))
1083
-
1084
- const vitest = getVitestExport(cliApiPackage)
1085
- if (vitest) {
1086
- wrapVitestRunFiles(vitest.value, frameworkVersion)
1087
- }
1088
-
1089
- const forksPoolWorker = getForksPoolWorkerExport(cliApiPackage)
1090
- if (forksPoolWorker) {
1091
- // function is async
1092
- shimmer.wrap(forksPoolWorker.value.prototype, 'start', start => function (...args) {
1093
- vitestPool = 'child_process'
1094
- this.env.DD_VITEST_WORKER = '1'
1095
-
1096
- return start.apply(this, args)
1097
- })
1098
- shimmer.wrap(forksPoolWorker.value.prototype, 'on', getWrappedOn)
1099
- }
1100
-
1101
- const threadsPoolWorker = getThreadsPoolWorkerExport(cliApiPackage)
1102
- if (threadsPoolWorker) {
1103
- // function is async
1104
- shimmer.wrap(threadsPoolWorker.value.prototype, 'start', start => function (...args) {
1105
- vitestPool = 'worker_threads'
1106
- this.env.DD_VITEST_WORKER = '1'
1107
- return start.apply(this, args)
1108
- })
1109
- shimmer.wrap(threadsPoolWorker.value.prototype, 'on', getWrappedOn)
1110
- }
1111
- return cliApiPackage
1112
- }
1113
-
1114
- function wrapVitestTestRunner (VitestTestRunner) {
1115
- // `onBeforeRunTask` is run before any repetition or attempt is run
1116
- // `onBeforeRunTask` is an async function
1117
- shimmer.wrap(VitestTestRunner.prototype, 'onBeforeRunTask', onBeforeRunTask => function (task) {
1118
- const testName = getTestName(task)
1119
-
1120
- const {
1121
- knownTests,
1122
- isEarlyFlakeDetectionEnabled,
1123
- isKnownTestsEnabled,
1124
- numRepeats,
1125
- isTestManagementTestsEnabled,
1126
- testManagementAttemptToFixRetries,
1127
- testManagementTests,
1128
- isImpactedTestsEnabled,
1129
- modifiedFiles,
1130
- } = getProvidedContext()
1131
-
1132
- if (isTestManagementTestsEnabled) {
1133
- isAttemptToFixCh.publish({
1134
- testManagementTests,
1135
- testSuiteAbsolutePath: task.file.filepath,
1136
- testName,
1137
- onDone: (isAttemptToFix) => {
1138
- if (isAttemptToFix) {
1139
- isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
1140
- disableFrameworkRetries(task)
1141
- task.repeats = testManagementAttemptToFixRetries
1142
- attemptToFixTasks.add(task)
1143
- attemptToFixTaskToStatuses.set(task, [])
1144
- }
1145
- },
1146
- })
1147
- isDisabledCh.publish({
1148
- testManagementTests,
1149
- testSuiteAbsolutePath: task.file.filepath,
1150
- testName,
1151
- onDone: (isTestDisabled) => {
1152
- if (isTestDisabled) {
1153
- disabledTasks.add(task)
1154
- if (!attemptToFixTasks.has(task)) {
1155
- // we only actually skip if the test is not being attempted to be fixed
1156
- task.mode = 'skip'
1157
- }
1158
- }
1159
- },
1160
- })
1161
- }
1162
-
1163
- if (isImpactedTestsEnabled) {
1164
- isModifiedCh.publish({
1165
- modifiedFiles,
1166
- testSuiteAbsolutePath: task.file.filepath,
1167
- onDone: (isImpacted) => {
1168
- if (isImpacted) {
1169
- if (isEarlyFlakeDetectionEnabled) {
1170
- isRetryReasonEfd = true
1171
- disableFrameworkRetries(task)
1172
- task.repeats = numRepeats
1173
- }
1174
- modifiedTasks.add(task)
1175
- taskToStatuses.set(task, [])
1176
- }
1177
- },
1178
- })
1179
- }
1180
-
1181
- if (isKnownTestsEnabled) {
1182
- isNewTestCh.publish({
1183
- knownTests,
1184
- testSuiteAbsolutePath: task.file.filepath,
1185
- testName,
1186
- onDone: (isNew) => {
1187
- if (isNew && !attemptToFixTasks.has(task)) {
1188
- if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
1189
- isRetryReasonEfd = true
1190
- disableFrameworkRetries(task)
1191
- task.repeats = numRepeats
1192
- }
1193
- newTasks.add(task)
1194
- taskToStatuses.set(task, [])
1195
- if (DYNAMIC_NAME_RE.test(testName)) {
1196
- dynamicNameTasks.add(task)
1197
- }
1198
- }
1199
- },
1200
- })
1201
- }
1202
-
1203
- return onBeforeRunTask.apply(this, arguments)
1204
- })
1205
-
1206
- // `onAfterRunTask` is run after all repetitions or attempts are run
1207
- // `onAfterRunTask` is an async function
1208
- shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => function (task) {
1209
- const { isEarlyFlakeDetectionEnabled, isTestManagementTestsEnabled } = getProvidedContext()
1210
-
1211
- if (isTestManagementTestsEnabled) {
1212
- const isAttemptingToFix = attemptToFixTasks.has(task)
1213
- const isQuarantined = quarantinedTasks.has(task)
1214
-
1215
- if (isAttemptingToFix) {
1216
- const statuses = attemptToFixTaskToStatuses.get(task)
1217
- if (task.result.state === 'pass' && statuses?.includes('fail')) {
1218
- switchedStatuses.add(task)
1219
- task.result.state = 'fail'
1220
- }
1221
- }
1222
-
1223
- if (!isAttemptingToFix && isQuarantined) {
1224
- if (task.result.state === 'fail') {
1225
- switchedStatuses.add(task)
1226
- }
1227
- task.result.state = 'pass'
1228
- }
1229
- }
1230
-
1231
- if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)) {
1232
- const statuses = taskToStatuses.get(task)
1233
- // If the test has passed at least once, we consider it passed
1234
- if (statuses.includes('pass')) {
1235
- if (task.result.state === 'fail') {
1236
- switchedStatuses.add(task)
1237
- }
1238
- task.result.state = 'pass'
1239
- }
1240
- }
1241
-
1242
- return onAfterRunTask.apply(this, arguments)
1243
- })
1244
-
1245
- // test start (only tests that are not marked as skip or todo)
1246
- // `onBeforeTryTask` is run for every repetition and attempt of the test
1247
- shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
1248
- if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
1249
- return onBeforeTryTask.apply(this, arguments)
1250
- }
1251
- const testName = getTestName(task)
1252
- let isNew = false
1253
- let isQuarantined = false
1254
-
1255
- const providedContext = getProvidedContext()
1256
- const {
1257
- isKnownTestsEnabled,
1258
- isEarlyFlakeDetectionEnabled,
1259
- isDiEnabled,
1260
- isTestManagementTestsEnabled,
1261
- testManagementTests,
1262
- slowTestRetries,
1263
- } = providedContext
1264
-
1265
- if (isKnownTestsEnabled) {
1266
- isNew = newTasks.has(task)
1267
- }
1268
-
1269
- if (isTestManagementTestsEnabled) {
1270
- isQuarantinedCh.publish({
1271
- testManagementTests,
1272
- testSuiteAbsolutePath: task.file.filepath,
1273
- testName,
1274
- onDone: (isTestQuarantined) => {
1275
- isQuarantined = isTestQuarantined
1276
- if (isTestQuarantined) {
1277
- quarantinedTasks.add(task)
1278
- }
1279
- },
1280
- })
1281
- }
1282
-
1283
- const { retry: numAttempt, repeats: numRepetition } = retryInfo
1284
- const isEfdManagedTask = isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)
1285
-
1286
- if (isEfdManagedTask && numRepetition > 0 && !efdDeterminedRetries.has(task)) {
1287
- const previousExecutionStart = efdExecutionStartByTask.get(task)
1288
- const duration = previousExecutionStart === undefined
1289
- ? task.result?.duration ?? 0
1290
- : performance.now() - previousExecutionStart
1291
- const retryCount = getEfdRetryCount(duration, slowTestRetries)
1292
- efdDeterminedRetries.set(task, retryCount)
1293
- task.repeats = retryCount
1294
- if (retryCount === 0) {
1295
- efdSlowAbortedTasks.add(task)
1296
- }
1297
- }
1298
-
1299
- const efdRetryCount = efdDeterminedRetries.get(task)
1300
- if (isEfdManagedTask && efdRetryCount !== undefined && numRepetition > efdRetryCount) {
1301
- if (task.result) {
1302
- efdSkippedRetryResults.set(task, {
1303
- ...task.result,
1304
- errors: task.result.errors?.slice(),
1305
- })
1306
- }
1307
- if (vitestSetFn) {
1308
- const noop = function () {}
1309
- noop.__ddTraceWrapped = true
1310
- vitestSetFn(task, noop)
1311
- }
1312
- return onBeforeTryTask.apply(this, arguments)
1313
- }
1314
- if (isEfdManagedTask) {
1315
- efdExecutionStartByTask.set(task, performance.now())
1316
- }
1317
-
1318
- // We finish the previous test here because we know it has failed already
1319
- if (numAttempt > 0) {
1320
- const shouldWaitForHitProbe = isDiEnabled && numAttempt > 1
1321
- if (shouldWaitForHitProbe) {
1322
- await waitForHitProbe()
1323
- }
1324
-
1325
- const promises = {}
1326
- const shouldSetProbe = isDiEnabled && numAttempt === 1
1327
- const ctx = taskToCtx.get(task)
1328
- const testError = getCurrentAttemptTestError(task, task.result?.errors)
1329
- if (ctx) {
1330
- testErrorCh.publish({
1331
- error: testError,
1332
- shouldSetProbe,
1333
- promises,
1334
- ...ctx.currentStore,
1335
- })
1336
- // We wait for the probe to be set
1337
- if (promises.setProbePromise) {
1338
- await promises.setProbePromise
1339
- }
1340
- }
1341
- }
1342
-
1343
- const lastExecutionStatus = task.result.state
1344
- const isAtf = attemptToFixTasks.has(task)
1345
- const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
1346
- const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
1347
- const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
1348
-
1349
- // These clauses handle task.repeats, whether EFD is enabled or not
1350
- // The only thing that EFD does is to forcefully pass the test if it has passed at least once
1351
- if (numRepetition > 0 && numRepetition < task.repeats) { // it may or may have not failed
1352
- // Here we finish the earlier iteration,
1353
- // as long as it's not the _last_ iteration (which will be finished normally)
1354
-
1355
- const ctx = taskToCtx.get(task)
1356
- if (ctx) {
1357
- if (lastExecutionStatus === 'fail') {
1358
- const testError = getCurrentAttemptTestError(task, task.result?.errors)
1359
- testErrorCh.publish({ error: testError, ...ctx.currentStore })
1360
- } else {
1361
- testPassCh.publish({ task, ...ctx.currentStore })
1362
- }
1363
- if (shouldTrackStatuses && statuses) {
1364
- statuses.push(lastExecutionStatus)
1365
- }
1366
- if (shouldFlipStatus) {
1367
- // If we don't "reset" the result.state to "pass", once a repetition fails,
1368
- // vitest will always consider the test as failed, so we can't read the actual status
1369
- // This means that we change vitest's behavior:
1370
- // if the last attempt passes, vitest would consider the test as failed
1371
- // but after this change, it will consider the test as passed
1372
- task.result.state = 'pass'
1373
- }
1374
- }
1375
- } else if (numRepetition === task.repeats) {
1376
- if (shouldTrackStatuses && statuses) {
1377
- statuses.push(lastExecutionStatus)
1378
- }
1379
-
1380
- const ctx = taskToCtx.get(task)
1381
- if (lastExecutionStatus === 'fail') {
1382
- const testError = getCurrentAttemptTestError(task, task.result?.errors)
1383
- testErrorCh.publish({ error: testError, ...ctx.currentStore })
1384
- } else {
1385
- testPassCh.publish({ task, ...ctx.currentStore })
1386
- }
1387
- if (shouldFlipStatus) {
1388
- task.result.state = 'pass'
1389
- }
1390
- }
1391
-
1392
- const isRetryReasonAtr = numAttempt > 0 &&
1393
- isFlakyTestRetriesEnabledForTask(providedContext, task) &&
1394
- !isRetryReasonAttemptToFix &&
1395
- !isRetryReasonEfd
1396
-
1397
- const ctx = {
1398
- testName,
1399
- testSuiteAbsolutePath: task.file.filepath,
1400
- isRetry: numAttempt > 0 || numRepetition > 0,
1401
- isRetryReasonEfd,
1402
- isRetryReasonAttemptToFix: isRetryReasonAttemptToFix && numRepetition > 0,
1403
- isNew,
1404
- hasDynamicName: dynamicNameTasks.has(task),
1405
- mightHitProbe: isDiEnabled && numAttempt > 0,
1406
- isAttemptToFix: attemptToFixTasks.has(task),
1407
- isDisabled: disabledTasks.has(task),
1408
- isQuarantined,
1409
- isRetryReasonAtr,
1410
- isModified: modifiedTasks.has(task),
1411
- }
1412
- taskToCtx.set(task, ctx)
1413
-
1414
- if (attemptToFixTasks.has(task)) {
1415
- logAttemptToFixTestExecution(
1416
- getTestSuitePath(task.file.filepath, process.cwd()),
1417
- testName,
1418
- loggedAttemptToFixTests
1419
- )
1420
- }
1421
-
1422
- testStartCh.runStores(ctx, () => {})
1423
-
1424
- // Wrap the test function so it runs inside the test span context.
1425
- // Without this, HTTP requests during test execution become orphaned root spans.
1426
- if (vitestGetFn && vitestSetFn) {
1427
- const originalFn = vitestGetFn(task)
1428
- if (originalFn && !originalFn.__ddTraceWrapped) {
1429
- const wrappedFn = wrapTestScopedFn(task, originalFn)
1430
- wrappedFn.__ddTraceWrapped = true
1431
- vitestSetFn(task, wrappedFn)
1432
- }
1433
- }
1434
-
1435
- // Wrap beforeEach/afterEach hooks so they also run inside the test span context.
1436
- // In vitest 4+, hooks are in a WeakMap accessed via getHooks(). In older versions, they're on suite.hooks.
1437
- let currentSuite = task.suite
1438
- while (currentSuite) {
1439
- const hooks = vitestGetHooks ? vitestGetHooks(currentSuite) : currentSuite.hooks
1440
- if (hooks) {
1441
- for (const hookType of ['beforeEach', 'afterEach']) {
1442
- const hookArray = hooks[hookType]
1443
- if (!hookArray) continue
1444
- for (let i = 0; i < hookArray.length; i++) {
1445
- const currentFn = hookArray[i]
1446
- const originalFn = originalHookFns.get(currentFn) || currentFn
1447
- const wrappedFn = shimmer.wrapFunction(originalFn, fn => function (...args) {
1448
- const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
1449
-
1450
- if (hookType === 'beforeEach') {
1451
- return wrapBeforeEachCleanupResult(task, result)
1452
- }
1453
-
1454
- return result
1455
- })
1456
- originalHookFns.set(wrappedFn, originalFn)
1457
- hookArray[i] = wrappedFn
1458
- }
1459
- }
1460
- }
1461
- currentSuite = currentSuite.suite
1462
- }
1463
-
1464
- return onBeforeTryTask.apply(this, arguments)
1465
- })
1466
-
1467
- // test finish (only passed tests)
1468
- shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
1469
- async function (task, retryInfo) {
1470
- if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
1471
- return onAfterTryTask.apply(this, arguments)
1472
- }
1473
- const result = await onAfterTryTask.apply(this, arguments)
1474
-
1475
- const {
1476
- isEarlyFlakeDetectionEnabled,
1477
- testManagementAttemptToFixRetries,
1478
- slowTestRetries,
1479
- } = getProvidedContext()
1480
-
1481
- const status = getVitestTestStatus(task, retryInfo.retry)
1482
- const ctx = taskToCtx.get(task)
1483
-
1484
- const { isDiEnabled } = getProvidedContext()
1485
-
1486
- if (efdSkippedRetryResults.has(task)) {
1487
- task.result = efdSkippedRetryResults.get(task)
1488
- efdSkippedRetryResults.delete(task)
1489
- return result
1490
- }
1491
-
1492
- if (isDiEnabled && retryInfo.retry > 1) {
1493
- await waitForHitProbe()
1494
- }
1495
-
1496
- if (
1497
- isEarlyFlakeDetectionEnabled &&
1498
- (retryInfo.repeats ?? 0) === 0 &&
1499
- taskToStatuses.has(task) &&
1500
- !attemptToFixTasks.has(task) &&
1501
- !efdDeterminedRetries.has(task)
1502
- ) {
1503
- const executionStart = efdExecutionStartByTask.get(task)
1504
- const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
1505
- const retryCount = getEfdRetryCount(duration, slowTestRetries)
1506
- efdDeterminedRetries.set(task, retryCount)
1507
- task.repeats = retryCount
1508
- if (retryCount === 0) {
1509
- efdSlowAbortedTasks.add(task)
1510
- }
1511
- }
1512
-
1513
- let attemptToFixPassed = false
1514
- let attemptToFixFailed = false
1515
- if (attemptToFixTasks.has(task)) {
1516
- const statuses = attemptToFixTaskToStatuses.get(task)
1517
- if (statuses.length === testManagementAttemptToFixRetries) {
1518
- if (status === 'pass' && statuses.every(status => status === 'pass')) {
1519
- attemptToFixPassed = true
1520
- } else if (status === 'fail' || statuses.includes('fail')) {
1521
- attemptToFixFailed = true
1522
- }
1523
- }
1524
- }
1525
-
1526
- if (ctx) {
1527
- // We don't finish here because the test might fail in a later hook (afterEach)
1528
- ctx.status = status
1529
- ctx.task = task
1530
- ctx.attemptToFixPassed = attemptToFixPassed
1531
- ctx.attemptToFixFailed = attemptToFixFailed
1532
- testFinishTimeCh.runStores(ctx, () => {})
1533
- }
1534
-
1535
- return result
1536
- })
1537
- }
1538
-
1539
- function captureRunnerFunctions (pkg) {
1540
- if (vitestGetFn) return
1541
- const getFnExport = findExportByName(pkg, 'getFn')
1542
- const setFnExport = findExportByName(pkg, 'setFn')
1543
- if (getFnExport && setFnExport) {
1544
- vitestGetFn = getFnExport.value
1545
- vitestSetFn = setFnExport.value
1546
- }
1547
- const getHooksExport = findExportByName(pkg, 'getHooks')
1548
- if (getHooksExport) {
1549
- vitestGetHooks = getHooksExport.value
1550
- }
1551
- }
1552
-
1553
- addHook({
1554
- name: 'vitest',
1555
- versions: ['>=4.0.0'],
1556
- filePattern: 'dist/chunks/test.*',
1557
- }, (testPackage) => {
1558
- const testRunner = getTestRunnerExport(testPackage)
1559
- if (!testRunner) {
1560
- return testPackage
1561
- }
1562
-
1563
- captureRunnerFunctions(testPackage)
1564
- wrapVitestTestRunner(testRunner.value)
1565
-
1566
- return testPackage
1567
- })
1568
-
1569
- addHook({
1570
- name: '@vitest/runner',
1571
- versions: ['>=1.6.0'],
1572
- }, (runnerModule) => {
1573
- if (!vitestGetFn && runnerModule.getFn && runnerModule.setFn) {
1574
- vitestGetFn = runnerModule.getFn
1575
- vitestSetFn = runnerModule.setFn
1576
- }
1577
- if (!vitestGetHooks && runnerModule.getHooks) {
1578
- vitestGetHooks = runnerModule.getHooks
1579
- }
1580
- return runnerModule
1581
- })
1582
-
1583
- addHook({
1584
- name: 'vitest',
1585
- versions: ['>=1.6.0 <4.0.0'],
1586
- file: 'dist/runners.js',
1587
- }, (vitestPackage) => {
1588
- const { VitestTestRunner } = vitestPackage
1589
-
1590
- wrapVitestTestRunner(VitestTestRunner)
1591
-
1592
- return vitestPackage
1593
- })
1594
-
1595
- // There are multiple index* files across different versions of vitest,
1596
- // so we check for the existence of BaseSequencer to determine if we are in the right file
1597
- addHook({
1598
- name: 'vitest',
1599
- versions: ['>=1.6.0 <2.0.0'],
1600
- filePattern: 'dist/vendor/index.*',
1601
- }, (vitestPackage) => {
1602
- if (isReporterPackage(vitestPackage)) {
1603
- shimmer.wrap(vitestPackage.B.prototype, 'sort', getSortWrapper)
1604
- }
1605
-
1606
- return vitestPackage
1607
- })
1608
-
1609
- addHook({
1610
- name: 'vitest',
1611
- versions: ['>=2.0.0 <2.0.5'],
1612
- filePattern: 'dist/vendor/index.*',
1613
- }, (vitestPackage) => {
1614
- if (isReporterPackageNew(vitestPackage)) {
1615
- shimmer.wrap(vitestPackage.e.prototype, 'sort', getSortWrapper)
1616
- }
1617
-
1618
- return vitestPackage
1619
- })
1620
-
1621
- addHook({
1622
- name: 'vitest',
1623
- versions: ['>=2.0.5 <2.1.0'],
1624
- filePattern: 'dist/chunks/index.*',
1625
- }, (vitestPackage) => {
1626
- if (isReporterPackageNewest(vitestPackage)) {
1627
- shimmer.wrap(vitestPackage.h.prototype, 'sort', getSortWrapper)
1628
- }
1629
-
1630
- return vitestPackage
1631
- })
1632
-
1633
- addHook({
1634
- name: 'vitest',
1635
- versions: ['>=2.1.0 <3.0.0'],
1636
- filePattern: 'dist/chunks/RandomSequencer.*',
1637
- }, (randomSequencerPackage) => {
1638
- shimmer.wrap(randomSequencerPackage.B.prototype, 'sort', getSortWrapper)
1639
- return randomSequencerPackage
1640
- })
1641
-
1642
- addHook({
1643
- name: 'vitest',
1644
- versions: ['>=3.0.9'],
1645
- filePattern: 'dist/chunks/coverage.*',
1646
- }, (coveragePackage) => {
1647
- const baseSequencer = getBaseSequencerExport(coveragePackage)
1648
- if (baseSequencer) {
1649
- shimmer.wrap(baseSequencer.value.prototype, 'sort', getSortWrapper)
1650
- }
1651
- return coveragePackage
1652
- })
1653
-
1654
- addHook({
1655
- name: 'vitest',
1656
- versions: ['>=3.0.0 <3.0.9'],
1657
- filePattern: 'dist/chunks/resolveConfig.*',
1658
- }, (resolveConfigPackage) => {
1659
- shimmer.wrap(resolveConfigPackage.B.prototype, 'sort', getSortWrapper)
1660
- return resolveConfigPackage
1661
- })
1662
-
1663
- // Can't specify file because compiled vitest includes hashes in their files
1664
- // Following 3 wrappers are for test session start
1665
- addHook({
1666
- name: 'vitest',
1667
- versions: ['>=1.6.0 <2.0.5'],
1668
- filePattern: 'dist/vendor/cac.*',
1669
- }, getCreateCliWrapper)
1670
-
1671
- addHook({
1672
- name: 'vitest',
1673
- versions: ['>=2.0.5'],
1674
- filePattern: 'dist/chunks/cac.*',
1675
- }, getCreateCliWrapper)
1676
-
1677
- addHook({
1678
- name: 'vitest',
1679
- versions: ['>=1.6.0 <2.0.5'],
1680
- filePattern: 'dist/vendor/cli-api.*',
1681
- }, getStartVitestWrapper)
1682
-
1683
- addHook({
1684
- name: 'vitest',
1685
- versions: ['>=2.0.5'],
1686
- filePattern: 'dist/chunks/cli-api.*',
1687
- }, getStartVitestWrapper)
1688
-
1689
- // test suite start and finish
1690
- // only relevant for workers
1691
- addHook({
1692
- name: '@vitest/runner',
1693
- versions: ['>=1.6.0'],
1694
- }, (vitestPackage, frameworkVersion) => {
1695
- shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPaths) {
1696
- let testSuiteError = null
1697
- if (!testSuiteFinishCh.hasSubscribers) {
1698
- return startTests.apply(this, arguments)
1699
- }
1700
- // From >=3.0.1, the first arguments changes from a string to an object containing the filepath
1701
- const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
1702
- const providedContext = getProvidedContext()
1703
-
1704
- const testSuiteCtx = {
1705
- testSuiteAbsolutePath,
1706
- frameworkVersion,
1707
- testSessionId: providedContext.testSessionId,
1708
- testModuleId: providedContext.testModuleId,
1709
- testCommand: providedContext.testCommand,
1710
- repositoryRoot: providedContext.repositoryRoot,
1711
- codeOwnersEntries: providedContext.codeOwnersEntries,
1712
- }
1713
- testSuiteStartCh.runStores(testSuiteCtx, () => {})
1714
- const startTestsResponse = await startTests.apply(this, arguments)
1715
-
1716
- let onFinish = null
1717
- const onFinishPromise = new Promise(resolve => {
1718
- onFinish = resolve
1719
- })
1720
-
1721
- const testTasks = getTypeTasks(startTestsResponse[0].tasks)
1722
-
1723
- // Only one test task per test, even if there are retries
1724
- for (const task of testTasks) {
1725
- const testCtx = taskToCtx.get(task)
1726
- const { result } = task
1727
- // We have to trick vitest into thinking that the test has passed
1728
- // but we want to report it as failed if it did fail
1729
- const isSwitchedStatus = switchedStatuses.has(task)
1730
-
1731
- if (result) {
1732
- const { state, duration, errors } = result
1733
- const testError = getCurrentAttemptTestError(task, errors)
1734
- if (attemptToFixTasks.has(task)) {
1735
- const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
1736
- recordFinalAttemptToFixExecution(task, status, providedContext)
1737
- }
1738
-
1739
- if (state === 'skip') { // programmatic skip
1740
- testSkipCh.publish({
1741
- testName: getTestName(task),
1742
- testSuiteAbsolutePath: task.file.filepath,
1743
- isNew: newTasks.has(task),
1744
- isDisabled: disabledTasks.has(task),
1745
- })
1746
- } else if (state === 'pass' && !isSwitchedStatus) {
1747
- if (testCtx) {
1748
- const isSkippedByTestManagement =
1749
- !attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
1750
- testPassCh.publish({
1751
- task,
1752
- finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
1753
- earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
1754
- ...testCtx.currentStore,
1755
- })
1756
- }
1757
- } else if (state === 'fail' || isSwitchedStatus) {
1758
- let hasFailedAllRetries = false
1759
- let attemptToFixFailed = false
1760
- if (attemptToFixTasks.has(task)) {
1761
- const statuses = attemptToFixTaskToStatuses.get(task)
1762
- if (statuses.includes('fail')) {
1763
- attemptToFixFailed = true
1764
- }
1765
- if (statuses.every(status => status === 'fail')) {
1766
- hasFailedAllRetries = true
1767
- }
1768
- }
1769
-
1770
- // Check if all EFD retries failed
1771
- const isEfdRetry =
1772
- providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
1773
- if (isEfdRetry) {
1774
- const statuses = taskToStatuses.get(task)
1775
- const efdRetryCount = efdDeterminedRetries.get(task) ?? providedContext.numRepeats
1776
- // statuses only includes repetitions (not the initial run), so we check against retry count (not +1)
1777
- if (efdRetryCount > 0 && statuses && statuses.length === efdRetryCount &&
1778
- statuses.every(status => status === 'fail')) {
1779
- hasFailedAllRetries = true
1780
- }
1781
- }
1782
-
1783
- // ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
1784
- const isAtrRetry = isFlakyTestRetriesEnabledForTask(providedContext, task) && !attemptToFixTasks.has(task) &&
1785
- !newTasks.has(task) && !modifiedTasks.has(task)
1786
- if (isAtrRetry) {
1787
- const maxRetries = providedContext.flakyTestRetriesCount ?? 0
1788
- if (maxRetries > 0 && task.result?.retryCount === maxRetries) {
1789
- hasFailedAllRetries = true
1790
- }
1791
- }
1792
-
1793
- if (testCtx) {
1794
- const isRetry = task.result?.retryCount > 0
1795
- // `duration` is the duration of all the retries, so it can't be used if there are retries
1796
-
1797
- let finalStatus
1798
- if (isSwitchedStatus) {
1799
- if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
1800
- finalStatus = 'skip'
1801
- } else if (isAtrRetry || isEfdRetry) {
1802
- finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
1803
- } else if (attemptToFixTasks.has(task)) {
1804
- finalStatus = attemptToFixFailed ? 'fail' : 'pass'
1805
- } else {
1806
- finalStatus = undefined
1807
- }
1808
- } else {
1809
- finalStatus = 'fail'
1810
- }
1811
-
1812
- testErrorCh.publish({
1813
- duration: isRetry ? undefined : duration,
1814
- error: testError,
1815
- hasFailedAllRetries,
1816
- attemptToFixFailed,
1817
- finalStatus,
1818
- earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
1819
- ...testCtx.currentStore,
1820
- })
1821
- }
1822
- if (errors?.length) {
1823
- testSuiteError = testError // we store the error to bubble it up to the suite
1824
- }
1825
- }
1826
- } else { // test.skip or test.todo
1827
- testSkipCh.publish({
1828
- testName: getTestName(task),
1829
- testSuiteAbsolutePath: task.file.filepath,
1830
- isNew: newTasks.has(task),
1831
- isDisabled: disabledTasks.has(task),
1832
- })
1833
- }
1834
- }
1835
-
1836
- const testSuiteResult = startTestsResponse[0].result
1837
-
1838
- if (testSuiteResult.errors?.length) { // Errors from root level hooks
1839
- testSuiteError = testSuiteResult.errors[0]
1840
- } else if (testSuiteResult.state === 'fail') { // Errors from `describe` level hooks
1841
- const suiteTasks = getTypeTasks(startTestsResponse[0].tasks, 'suite')
1842
- const failedSuites = suiteTasks.filter(task => task.result?.state === 'fail')
1843
- if (failedSuites.length && failedSuites[0].result?.errors?.length) {
1844
- testSuiteError = failedSuites[0].result.errors[0]
1845
- }
1846
- }
1847
-
1848
- if (testSuiteError) {
1849
- testSuiteCtx.error = testSuiteError
1850
- testSuiteErrorCh.runStores(testSuiteCtx, () => {})
1851
- }
1852
-
1853
- testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish, ...testSuiteCtx.currentStore })
1854
-
1855
- await onFinishPromise
1856
-
1857
- return startTestsResponse
1858
- })
1859
-
1860
- return vitestPackage
1861
- })