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,1297 @@
1
+ 'use strict'
2
+
3
+ const path = require('node:path')
4
+
5
+ const satisfies = require('../../../vendor/dist/semifies')
6
+
7
+ const { getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
8
+ const log = require('../../dd-trace/src/log')
9
+ const {
10
+ DYNAMIC_NAME_RE,
11
+ EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
12
+ getTestSuitePath,
13
+ logAttemptToFixTestExecution,
14
+ recordAttemptToFixExecution,
15
+ } = require('../../dd-trace/src/plugins/util/test')
16
+ const {
17
+ getTestName,
18
+ getTypeTasks,
19
+ getWorkspaceProject,
20
+ isFlakyTestRetriesEnabledForTask,
21
+ setProvidedContext,
22
+ testErrorCh,
23
+ testFinishTimeCh,
24
+ testPassCh,
25
+ testSkipCh,
26
+ testStartCh,
27
+ testSuiteErrorCh,
28
+ testSuiteFinishCh,
29
+ testSuiteStartCh,
30
+ } = require('./vitest-util')
31
+
32
+ // No-worker-init instrumentation for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
33
+ // When enabled, Vitest workers do not initialize dd-trace, so this main-process instrumentation
34
+ // takes over some worker responsibilities, including test span creation and lifecycle reporting.
35
+ const mainProcessReporterStates = new WeakMap()
36
+ const loggedAttemptToFixTests = new Set()
37
+
38
+ const DATADOG_TEST_OPTIMIZATION_BOOTSTRAPS = new Set([
39
+ 'dd-trace/register.js',
40
+ 'dd-trace/ci/init',
41
+ 'dd-trace/ci/init.js',
42
+ ])
43
+ const DATADOG_TEST_OPTIMIZATION_NODE_OPTION_FLAGS = new Set(['--import', '--require', '-r'])
44
+ const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
45
+ const VITEST_NO_WORKER_INIT_REQUEST_ENV = 'DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT'
46
+ const VITEST_NO_WORKER_INIT_MINIMUM_VERSION = '3.2.6'
47
+ const VITEST_DEFAULT_POOL = 'forks'
48
+ const VITEST_NO_WORKER_INIT_SETUP_FILE = path.join(
49
+ __dirname,
50
+ '..',
51
+ '..',
52
+ '..',
53
+ 'ci',
54
+ 'vitest-no-worker-init-setup.mjs'
55
+ )
56
+ const VITEST_NO_WORKER_INIT_ISOLATE_WARNING =
57
+ `${VITEST_NO_WORKER_INIT_REQUEST_ENV} is ignored because Vitest isolate is disabled. ` +
58
+ 'The lighter Vitest worker path only works when each test file runs in an isolated worker.'
59
+ const NODE_OPTIONS_QUOTE_RE = /[\s"\\]/
60
+ let hasWarnedDisabledIsolate = false
61
+ let hasWarnedUnsupportedVersion = false
62
+ let nodeOptionsBeforeNoWorkerInit
63
+
64
+ function noop () {}
65
+
66
+ function isRequested () {
67
+ return getValueFromEnvSources(VITEST_NO_WORKER_INIT_REQUEST_ENV) === true
68
+ }
69
+
70
+ function shouldUse (ctx, frameworkVersion, testSpecifications, options) {
71
+ if (!isRequested()) return false
72
+ if (!isSupportedVersion(frameworkVersion)) {
73
+ warnUnsupportedVersion(frameworkVersion)
74
+ return false
75
+ }
76
+
77
+ const config = ctx?.config
78
+ if (!config) return false
79
+
80
+ const { isVitestWorkerPool } = options
81
+ if (Array.isArray(testSpecifications)) {
82
+ return shouldUseForTestSpecifications(config, testSpecifications, isVitestWorkerPool)
83
+ }
84
+
85
+ const pool = getEffectiveConfigPool(config)
86
+ if (!isNoWorkerInitPool(pool, isVitestWorkerPool)) return false
87
+ if (getEffectiveConfigIsolate(config, pool) === false) {
88
+ warnDisabledIsolate()
89
+ return false
90
+ }
91
+
92
+ return true
93
+ }
94
+
95
+ function isSupportedVersion (frameworkVersion) {
96
+ return !!frameworkVersion && satisfies(frameworkVersion, `>=${VITEST_NO_WORKER_INIT_MINIMUM_VERSION}`)
97
+ }
98
+
99
+ function warnUnsupportedVersion (frameworkVersion) {
100
+ if (hasWarnedUnsupportedVersion) return
101
+
102
+ hasWarnedUnsupportedVersion = true
103
+ log.warn(
104
+ '%s is only supported for vitest >=%s. Falling back to normal Vitest worker instrumentation for vitest %s.',
105
+ VITEST_NO_WORKER_INIT_REQUEST_ENV,
106
+ VITEST_NO_WORKER_INIT_MINIMUM_VERSION,
107
+ frameworkVersion || 'unknown'
108
+ )
109
+ }
110
+
111
+ function warnDisabledIsolate () {
112
+ if (hasWarnedDisabledIsolate) return
113
+
114
+ hasWarnedDisabledIsolate = true
115
+ log.warn(VITEST_NO_WORKER_INIT_ISOLATE_WARNING)
116
+ }
117
+
118
+ function shouldUseForTestSpecifications (config, testSpecifications, isVitestWorkerPool) {
119
+ const defaultPool = getEffectiveConfigPool(config)
120
+ let hasNoWorkerInitSpecification = false
121
+ let hasNonWorkerSpecification = false
122
+
123
+ for (const testSpecification of testSpecifications) {
124
+ const pool = getEffectiveTestSpecificationPool(testSpecification, defaultPool)
125
+ if (!isNoWorkerInitPool(pool, isVitestWorkerPool)) {
126
+ hasNonWorkerSpecification = true
127
+ continue
128
+ }
129
+
130
+ const defaultIsolate = getEffectiveConfigIsolate(config, pool)
131
+ if (getEffectiveTestSpecificationIsolate(testSpecification, pool, defaultIsolate) === false) {
132
+ warnDisabledIsolate()
133
+ return false
134
+ }
135
+
136
+ hasNoWorkerInitSpecification = true
137
+ }
138
+
139
+ return hasNoWorkerInitSpecification && !hasNonWorkerSpecification
140
+ }
141
+
142
+ function isNoWorkerInitPool (pool, isVitestWorkerPool) {
143
+ return pool === undefined || isVitestWorkerPool(pool)
144
+ }
145
+
146
+ function configure (ctx, frameworkVersion, testSpecifications, setupData, options) {
147
+ const { getConfiguredEfdRetryCount, state } = options
148
+ addSetupFileToVitestConfigs(ctx, VITEST_NO_WORKER_INIT_SETUP_FILE, testSpecifications)
149
+
150
+ const {
151
+ knownTestsBySuite,
152
+ modifiedFiles,
153
+ repositoryRoot,
154
+ testManagementTestsBySuite,
155
+ testSessionConfiguration,
156
+ } = setupData
157
+
158
+ setProvidedContext(ctx, {
159
+ _ddVitestWorkerSetup: {
160
+ attemptToFixRetries: state.testManagementAttemptToFixRetries,
161
+ attemptToFixTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isAttemptToFix'),
162
+ disabledTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isDisabled'),
163
+ earlyFlakeDetectionRetries: getConfiguredEfdRetryCount(
164
+ state.earlyFlakeDetectionSlowTestRetries,
165
+ state.earlyFlakeDetectionNumRetries
166
+ ),
167
+ earlyFlakeDetectionRetryThresholds: EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
168
+ earlyFlakeDetectionSlowRetries: state.earlyFlakeDetectionSlowTestRetries,
169
+ isEarlyFlakeDetectionEnabled: state.isEarlyFlakeDetectionEnabled && !state.isEarlyFlakeDetectionFaulty,
170
+ knownTests: knownTestsBySuite || {},
171
+ modifiedFiles: modifiedFiles || {},
172
+ quarantinedTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isQuarantined'),
173
+ repositoryRoot: repositoryRoot || process.cwd(),
174
+ },
175
+ }, 'Could not send Vitest worker setup context, so no-worker execution changes will not work.')
176
+
177
+ installMainProcessReporter(ctx, frameworkVersion, testSessionConfiguration || {}, setupData, state)
178
+ }
179
+
180
+ function deactivate (ctx) {
181
+ const installedReporterState = mainProcessReporterStates.get(ctx)
182
+ if (installedReporterState) {
183
+ installedReporterState.isActive = false
184
+ }
185
+ }
186
+
187
+ function configureWorkerEnv (workerEnv, shouldSkipWorkerInit = false) {
188
+ if (!shouldSkipWorkerInit) {
189
+ delete workerEnv[VITEST_NO_WORKER_INIT_ACTIVE_ENV]
190
+ restoreDatadogTestOptimizationNodeOptions(workerEnv)
191
+ return workerEnv
192
+ }
193
+
194
+ workerEnv[VITEST_NO_WORKER_INIT_ACTIVE_ENV] = '1'
195
+ rememberDatadogTestOptimizationNodeOptions(workerEnv.NODE_OPTIONS)
196
+
197
+ const nodeOptions = removeDatadogTestOptimizationNodeOptions(workerEnv.NODE_OPTIONS)
198
+ if (nodeOptions) {
199
+ workerEnv.NODE_OPTIONS = nodeOptions
200
+ } else {
201
+ delete workerEnv.NODE_OPTIONS
202
+ }
203
+ return workerEnv
204
+ }
205
+
206
+ function rememberDatadogTestOptimizationNodeOptions (nodeOptions) {
207
+ if (hasDatadogTestOptimizationNodeOptions(nodeOptions)) {
208
+ nodeOptionsBeforeNoWorkerInit = nodeOptions
209
+ }
210
+ }
211
+
212
+ function restoreDatadogTestOptimizationNodeOptions (workerEnv) {
213
+ if (!nodeOptionsBeforeNoWorkerInit || hasDatadogTestOptimizationNodeOptions(workerEnv.NODE_OPTIONS)) return
214
+
215
+ const strippedNodeOptions = removeDatadogTestOptimizationNodeOptions(nodeOptionsBeforeNoWorkerInit)
216
+ if (workerEnv.NODE_OPTIONS === strippedNodeOptions || (!workerEnv.NODE_OPTIONS && !strippedNodeOptions)) {
217
+ workerEnv.NODE_OPTIONS = nodeOptionsBeforeNoWorkerInit
218
+ }
219
+ }
220
+
221
+ function addSetupFileToVitestConfigs (ctx, setupFile, testSpecifications) {
222
+ const configs = getNoWorkerInitVitestConfigs(ctx, testSpecifications)
223
+
224
+ for (const config of configs) {
225
+ if (!config) continue
226
+
227
+ if (config.setupFiles === undefined) {
228
+ config.setupFiles = []
229
+ } else if (typeof config.setupFiles === 'string') {
230
+ config.setupFiles = [config.setupFiles]
231
+ }
232
+
233
+ if (!config.setupFiles.includes(setupFile)) {
234
+ config.setupFiles.push(setupFile)
235
+ }
236
+ }
237
+ }
238
+
239
+ function getNoWorkerInitVitestConfigs (ctx, testSpecifications) {
240
+ const configs = new Set()
241
+ if (Array.isArray(testSpecifications)) {
242
+ let shouldAddRootConfig = false
243
+ for (const testSpecification of testSpecifications) {
244
+ const project = getTestSpecificationProject(testSpecification)
245
+ const config = safeConfig(project)
246
+ if (config) {
247
+ addNoWorkerInitConfig(configs, config)
248
+ } else {
249
+ shouldAddRootConfig = true
250
+ }
251
+ }
252
+
253
+ if (!shouldAddRootConfig && configs.size > 0) {
254
+ return configs
255
+ }
256
+ }
257
+
258
+ addNoWorkerInitConfig(configs, safeConfig(ctx))
259
+ addNoWorkerInitConfig(configs, safeConfig(safeWorkspaceProject(ctx)))
260
+ return configs
261
+ }
262
+
263
+ function addNoWorkerInitConfig (configs, config) {
264
+ if (config) configs.add(config)
265
+ }
266
+
267
+ function getSelectedTestManagementTests (testManagementTestsBySuite, propertyName) {
268
+ if (!testManagementTestsBySuite) return {}
269
+
270
+ const selectedTests = {}
271
+ for (const [testSuite, tests] of Object.entries(testManagementTestsBySuite)) {
272
+ for (const [testName, properties] of Object.entries(tests)) {
273
+ if (properties?.[propertyName]) {
274
+ selectedTests[testSuite] ||= {}
275
+ selectedTests[testSuite][testName] = true
276
+ }
277
+ }
278
+ }
279
+ return selectedTests
280
+ }
281
+
282
+ function installMainProcessReporter (ctx, frameworkVersion, testSessionConfiguration, testOptimizationData, state) {
283
+ if (!ctx?.reporters) return
284
+
285
+ const reporterState = {
286
+ frameworkVersion,
287
+ isActive: true,
288
+ state,
289
+ testSessionConfiguration,
290
+ testOptimizationData,
291
+ }
292
+ const installedReporterState = mainProcessReporterStates.get(ctx)
293
+ if (installedReporterState) {
294
+ Object.assign(installedReporterState, reporterState)
295
+ return
296
+ }
297
+
298
+ mainProcessReporterStates.set(ctx, reporterState)
299
+ ctx.reporters.push(createMainProcessReporter(reporterState))
300
+ }
301
+
302
+ function createMainProcessReporter (reporterState) {
303
+ const testSuiteContexts = new Map()
304
+ const finishedTestModules = new Set()
305
+ const taskAttemptStatuses = new Map()
306
+ const tasksWithRecordedFinalAttempt = new Set()
307
+
308
+ return {
309
+ onTestModuleStart (testModule) {
310
+ if (!isActive()) return
311
+
312
+ startTestSuite(testModule)
313
+ },
314
+
315
+ onTestModuleEnd (testModule) {
316
+ if (!isActive()) return
317
+
318
+ return reportTestModule(testModule)
319
+ },
320
+
321
+ onTestCaseResult (testCase) {
322
+ if (!isActive()) return
323
+
324
+ const task = getTestCaseTask(testCase)
325
+ recordFinalTaskAttemptResult(task)
326
+ },
327
+
328
+ onTaskUpdate (packs, events) {
329
+ if (!isActive()) return
330
+ if (!events) return
331
+
332
+ for (const event of events) {
333
+ if (event[1] === 'test-retried') {
334
+ recordTaskAttemptStatus(event[0], 'fail', getTaskUpdateAttemptCount(event[0], packs))
335
+ }
336
+ }
337
+ },
338
+
339
+ onFinished (files) {
340
+ if (!isActive()) return
341
+ if (!files) return
342
+
343
+ for (const file of files) {
344
+ const testModule = createTestModuleFromFile(file)
345
+ if (!finishedTestModules.has(file.id)) {
346
+ reportTestModule(testModule)
347
+ }
348
+ }
349
+ },
350
+ }
351
+
352
+ function isActive () {
353
+ return reporterState.isActive === true
354
+ }
355
+
356
+ function recordTaskAttemptStatus (taskId, status, attemptCount) {
357
+ let statuses = taskAttemptStatuses.get(taskId)
358
+ if (!statuses) {
359
+ statuses = []
360
+ taskAttemptStatuses.set(taskId, statuses)
361
+ }
362
+ if (attemptCount !== undefined && statuses.length >= attemptCount) return
363
+
364
+ if (tasksWithRecordedFinalAttempt.has(taskId)) {
365
+ statuses.splice(-1, 0, status)
366
+ return
367
+ }
368
+ statuses.push(status)
369
+ }
370
+
371
+ function recordFinalTaskAttemptResult (task) {
372
+ const statuses = taskAttemptStatuses.get(task.id)
373
+ if (!statuses) return
374
+
375
+ const attemptCount = getRepeatedAttemptCount(task, statuses)
376
+ if (statuses.length < attemptCount) {
377
+ statuses.push(getFinalRepeatedTaskStatus(task))
378
+ tasksWithRecordedFinalAttempt.add(task.id)
379
+ }
380
+ }
381
+
382
+ function reportTestModule (testModule) {
383
+ const testModuleId = getTestModuleId(testModule)
384
+ const testModuleTask = getTestModuleTask(testModule)
385
+ finishedTestModules.add(testModuleId)
386
+ const testSuiteCtx = testSuiteContexts.get(testModuleId) || startTestSuite(testModule)
387
+ const testTasks = getTypeTasks(testModuleTask.tasks)
388
+ const testReports = []
389
+ let testSuiteError
390
+
391
+ for (const task of testTasks) {
392
+ const testReport = getTestReport(task, testSuiteCtx.currentStore)
393
+ testReports.push(testReport)
394
+
395
+ for (const attempt of testReport.nonFinalAttempts) {
396
+ reportTestAttempt(testReport, attempt)
397
+ }
398
+ }
399
+
400
+ for (const testReport of testReports) {
401
+ const error = reportFinalTestAttempt(testReport)
402
+ testSuiteError ||= error
403
+ }
404
+
405
+ const suiteTaskError = getSuiteTaskError(testModuleTask)
406
+ recomputeTaskState(testModuleTask)
407
+ const testSuiteResult = testModuleTask.result
408
+ if (testSuiteResult?.errors?.length) {
409
+ testSuiteError = testSuiteResult.errors[0]
410
+ } else if (suiteTaskError) {
411
+ testSuiteError = suiteTaskError
412
+ }
413
+
414
+ if (testSuiteError) {
415
+ testSuiteCtx.error = testSuiteError
416
+ testSuiteErrorCh.runStores(testSuiteCtx, () => {})
417
+ }
418
+
419
+ testSuiteFinishCh.publish({
420
+ status: getDatadogStatus(testSuiteResult),
421
+ deferFlush: true,
422
+ onFinish: noop,
423
+ ...testSuiteCtx.currentStore,
424
+ })
425
+ testSuiteContexts.delete(testModuleId)
426
+ }
427
+
428
+ function startTestSuite (testModule) {
429
+ const { frameworkVersion, testSessionConfiguration } = reporterState
430
+ const testModuleId = getTestModuleId(testModule)
431
+ const testSuiteCtx = {
432
+ testSuiteAbsolutePath: getTestModuleFilepath(testModule),
433
+ frameworkVersion,
434
+ testSessionId: testSessionConfiguration.testSessionId,
435
+ testModuleId: testSessionConfiguration.testModuleId,
436
+ testCommand: testSessionConfiguration.testCommand,
437
+ repositoryRoot: testSessionConfiguration.repositoryRoot,
438
+ codeOwnersEntries: testSessionConfiguration.codeOwnersEntries,
439
+ requestErrorTags: reporterState.state.requestErrorTags,
440
+ isTestFrameworkWorker: true,
441
+ isVitestNoWorkerInitActive: true,
442
+ }
443
+ testSuiteStartCh.runStores(testSuiteCtx, () => {})
444
+ testSuiteContexts.set(testModuleId, testSuiteCtx)
445
+ return testSuiteCtx
446
+ }
447
+
448
+ function getTestReport (task, testSuiteStore) {
449
+ const result = task.result
450
+ const testSuiteAbsolutePath = task.file?.filepath
451
+ const testName = getTestName(task)
452
+ const testProperties = getMainProcessTestProperties(task, testSuiteAbsolutePath, testName)
453
+ const { state } = reporterState
454
+ let status = getDatadogStatus(result)
455
+
456
+ if (task.meta?.__ddTestOptQuarantinedFailed && testProperties.isQuarantined && !testProperties.isAttemptToFix) {
457
+ status = 'fail'
458
+ }
459
+
460
+ if (testProperties.isAttemptToFix && task.meta?.__ddTestOptAtfStatuses?.length) {
461
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
462
+ errorCounts: task.meta.__ddTestOptAtfErrorCounts,
463
+ finalStatus: getAttemptToFixFinalStatus,
464
+ state,
465
+ statuses: task.meta.__ddTestOptAtfStatuses,
466
+ testSuiteStore,
467
+ type: 'attempt_to_fix',
468
+ })
469
+ }
470
+
471
+ if (testProperties.isEarlyFlakeDetection && task.meta?.__ddTestOptEfdStatuses?.length) {
472
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
473
+ errorCounts: task.meta.__ddTestOptEfdErrorCounts,
474
+ finalStatus: getEarlyFlakeDetectionFinalStatus,
475
+ state,
476
+ statuses: task.meta.__ddTestOptEfdStatuses,
477
+ testSuiteStore,
478
+ type: 'early_flake_detection',
479
+ })
480
+ }
481
+
482
+ if (!testProperties.isAttemptToFix && task.meta?.__ddTestOptRepeatStatuses?.length) {
483
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
484
+ errorCounts: task.meta.__ddTestOptRepeatErrorCounts,
485
+ finalStatus: () => status,
486
+ state,
487
+ statuses: task.meta.__ddTestOptRepeatStatuses,
488
+ testSuiteStore,
489
+ type: 'external',
490
+ })
491
+ }
492
+
493
+ const attemptStatuses = taskAttemptStatuses.get(task.id)
494
+ if (!testProperties.isAttemptToFix && !testProperties.isEarlyFlakeDetection && attemptStatuses?.length > 1) {
495
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
496
+ finalStatus: () => status,
497
+ state,
498
+ statuses: attemptStatuses,
499
+ testSuiteStore,
500
+ type: 'external',
501
+ })
502
+ }
503
+
504
+ if (!testProperties.isAttemptToFix && task.result?.repeatCount > 0) {
505
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
506
+ finalStatus: () => status,
507
+ state,
508
+ statuses: getRepeatedTaskStatuses(task, status),
509
+ testSuiteStore,
510
+ type: 'external',
511
+ })
512
+ }
513
+
514
+ if (testProperties.isAttemptToFix) {
515
+ logAttemptToFixTestExecution(testProperties.testSuite, testName, loggedAttemptToFixTests)
516
+ }
517
+
518
+ if (testProperties.isDisabled && !testProperties.isAttemptToFix) {
519
+ status = 'skip'
520
+ if (result) result.state = 'skip'
521
+ } else if (testProperties.isQuarantined && status === 'fail' && !testProperties.isAttemptToFix) {
522
+ result.state = 'pass'
523
+ }
524
+
525
+ const errors = result?.errors || []
526
+ const finalErrorIndex = status === 'fail' ? errors.length - 1 : -1
527
+ const nonFinalAttempts = []
528
+
529
+ if (status !== 'skip') {
530
+ for (let index = 0; index < errors.length; index++) {
531
+ if (index === finalErrorIndex) continue
532
+ nonFinalAttempts.push({
533
+ error: errors[index],
534
+ isRetry: index > 0,
535
+ status: 'fail',
536
+ })
537
+ }
538
+ }
539
+
540
+ return {
541
+ errors,
542
+ nonFinalAttempts,
543
+ state,
544
+ status,
545
+ task,
546
+ testName,
547
+ testProperties,
548
+ testSuiteAbsolutePath,
549
+ testSuiteStore,
550
+ }
551
+ }
552
+
553
+ function getMainProcessTestProperties (task, testSuiteAbsolutePath, testName) {
554
+ const { state, testOptimizationData, testSessionConfiguration } = reporterState
555
+ const testProperties = testOptimizationData.testPropertiesByFilepath?.[testSuiteAbsolutePath]
556
+ const testSuite = testProperties?.testSuite || getTestSuitePath(
557
+ testSuiteAbsolutePath,
558
+ testSessionConfiguration.repositoryRoot || process.cwd()
559
+ )
560
+ const knownTests = testProperties?.knownTests
561
+ const testManagementProperties = testProperties?.testManagementTests?.[testName] || {}
562
+ const isAttemptToFix = testManagementProperties.isAttemptToFix
563
+ const isNew = !!(
564
+ !isAttemptToFix &&
565
+ state.isKnownTestsEnabled &&
566
+ knownTests &&
567
+ !state.isEarlyFlakeDetectionFaulty &&
568
+ !knownTests.includes(testName)
569
+ )
570
+ const isModified = testProperties?.isModified === true
571
+ const { flakyTestRetriesConfiguration } = testOptimizationData
572
+ const isFlakyTestRetries = !!flakyTestRetriesConfiguration && isFlakyTestRetriesEnabledForTask({
573
+ isFlakyTestRetriesEnabled: state.isFlakyTestRetriesEnabled,
574
+ flakyTestRetriesIncludesUnnamedProject: flakyTestRetriesConfiguration.includesUnnamedProject,
575
+ flakyTestRetriesProjectNames: flakyTestRetriesConfiguration.projectNames,
576
+ }, task)
577
+
578
+ return {
579
+ isAttemptToFix,
580
+ isDisabled: testManagementProperties.isDisabled,
581
+ isEarlyFlakeDetection: (isNew || isModified) && state.isEarlyFlakeDetectionEnabled,
582
+ isFlakyTestRetries,
583
+ isQuarantined: testManagementProperties.isQuarantined,
584
+ isModified,
585
+ isNew,
586
+ testSuite,
587
+ hasDynamicName: isNew && DYNAMIC_NAME_RE.test(testName),
588
+ }
589
+ }
590
+ }
591
+
592
+ function getTaskUpdateAttemptCount (taskId, packs) {
593
+ if (!packs) return
594
+
595
+ for (const pack of packs) {
596
+ if (pack[0] === taskId) {
597
+ return getRepeatedAttemptCount({
598
+ id: taskId,
599
+ result: pack[1],
600
+ meta: pack[2],
601
+ }, [])
602
+ }
603
+ }
604
+ }
605
+
606
+ function createTestModuleFromFile (file) {
607
+ return {
608
+ id: file.id,
609
+ moduleId: file.filepath,
610
+ project: file.project,
611
+ projectName: file.projectName,
612
+ task: file,
613
+ }
614
+ }
615
+
616
+ function getTestModuleId (testModule) {
617
+ return testModule.id || getTestModuleFilepath(testModule)
618
+ }
619
+
620
+ function getTestModuleTask (testModule) {
621
+ if (testModule.task?.tasks) {
622
+ return testModule.task
623
+ }
624
+
625
+ const task = createTaskFromReportedTask(testModule)
626
+ task.file = task
627
+ return task
628
+ }
629
+
630
+ function getTestCaseTask (testCase) {
631
+ if (testCase.task) {
632
+ return testCase.task
633
+ }
634
+
635
+ return createTaskFromReportedTask(testCase, {
636
+ filepath: getTestModuleFilepath(testCase.module),
637
+ projectName: getTestModuleProjectName(testCase.module),
638
+ })
639
+ }
640
+
641
+ function createTaskFromReportedTask (reportedTask, file, suite) {
642
+ const task = {
643
+ id: reportedTask.id,
644
+ type: reportedTask.type,
645
+ name: reportedTask.name || reportedTask.relativeModuleId || reportedTask.moduleId,
646
+ mode: reportedTask.options?.mode,
647
+ meta: getReportedTaskMeta(reportedTask),
648
+ result: getReportedTaskResult(reportedTask),
649
+ file,
650
+ suite,
651
+ }
652
+
653
+ if (reportedTask.type === 'module') {
654
+ task.filepath = reportedTask.moduleId
655
+ task.projectName = getTestModuleProjectName(reportedTask)
656
+ }
657
+
658
+ const children = getReportedTaskChildren(reportedTask)
659
+ if (children) {
660
+ task.tasks = []
661
+ const taskFile = file || task
662
+ const childSuite = task.type === 'suite' ? task : undefined
663
+ for (const child of children) {
664
+ task.tasks.push(createTaskFromReportedTask(child, taskFile, childSuite))
665
+ }
666
+ }
667
+
668
+ return task
669
+ }
670
+
671
+ function getReportedTaskChildren (reportedTask) {
672
+ const children = reportedTask.children
673
+ if (!children) return
674
+
675
+ if (typeof children.array === 'function') {
676
+ return children.array()
677
+ }
678
+
679
+ if (Array.isArray(children)) {
680
+ return children
681
+ }
682
+
683
+ return [...children]
684
+ }
685
+
686
+ function getReportedTaskMeta (reportedTask) {
687
+ if (typeof reportedTask.meta === 'function') {
688
+ return reportedTask.meta()
689
+ }
690
+
691
+ return reportedTask.meta || {}
692
+ }
693
+
694
+ function getReportedTaskResult (reportedTask) {
695
+ let result
696
+ if (typeof reportedTask.result === 'function') {
697
+ result = reportedTask.result()
698
+ } else if (typeof reportedTask.state === 'function') {
699
+ result = {
700
+ errors: typeof reportedTask.errors === 'function' ? reportedTask.errors() : undefined,
701
+ state: reportedTask.state(),
702
+ }
703
+ } else {
704
+ result = reportedTask.result
705
+ }
706
+
707
+ const diagnostic = typeof reportedTask.diagnostic === 'function'
708
+ ? reportedTask.diagnostic()
709
+ : undefined
710
+
711
+ return {
712
+ duration: diagnostic?.duration,
713
+ errors: result?.errors,
714
+ note: result?.note,
715
+ repeatCount: diagnostic?.repeatCount,
716
+ retryCount: diagnostic?.retryCount,
717
+ state: result?.state,
718
+ }
719
+ }
720
+
721
+ function getTestModuleFilepath (testModule) {
722
+ return testModule.moduleId || testModule.task?.filepath || testModule.filepath
723
+ }
724
+
725
+ function getTestModuleProjectName (testModule) {
726
+ return normalizeProjectName(
727
+ testModule.projectName || getProjectName(testModule.project) || testModule.task?.projectName ||
728
+ testModule.task?.file?.projectName
729
+ )
730
+ }
731
+
732
+ function getRepeatedTestReport (task, testName, testSuiteAbsolutePath, testProperties, status, options) {
733
+ const result = task.result
734
+ const errors = result?.errors || []
735
+ const { errorCounts, finalStatus, state, statuses, testSuiteStore, type } = options
736
+ const finalAttemptStatus = finalStatus(statuses)
737
+ const hasFailure = finalAttemptStatus === 'fail'
738
+ const attempts = []
739
+ const attemptCount = getRepeatedAttemptCount(task, statuses)
740
+ let errorIndex = 0
741
+ let previousErrorCount = 0
742
+
743
+ if (type === 'attempt_to_fix') {
744
+ logAttemptToFixTestExecution(testProperties.testSuite, testName, loggedAttemptToFixTests)
745
+ }
746
+
747
+ for (let index = 0; index < attemptCount; index++) {
748
+ const isFinalAttempt = index === attemptCount - 1
749
+ const attemptStatus = statuses[index] || 'pass'
750
+ const nextErrorCount = errorCounts?.[index]
751
+ const error = attemptStatus === 'fail'
752
+ ? errors[nextErrorCount === undefined ? errorIndex : previousErrorCount] || errors[errorIndex] || errors[0]
753
+ : undefined
754
+ if (nextErrorCount !== undefined) {
755
+ previousErrorCount = nextErrorCount
756
+ } else if (attemptStatus === 'fail') {
757
+ errorIndex++
758
+ }
759
+ const attempt = {
760
+ attemptToFixFailed: type === 'attempt_to_fix' && isFinalAttempt && hasFailure,
761
+ earlyFlakeAbortReason: type === 'early_flake_detection' && isFinalAttempt
762
+ ? task.meta?.__ddTestOptEfdAbortReason
763
+ : undefined,
764
+ error,
765
+ finalStatus: isFinalAttempt ? finalAttemptStatus : undefined,
766
+ hasFailedAllRetries: hasFailedAllManagedRetries(
767
+ task,
768
+ testProperties,
769
+ type,
770
+ statuses,
771
+ isFinalAttempt,
772
+ hasFailure
773
+ ),
774
+ isRetry: index > 0,
775
+ status: attemptStatus,
776
+ }
777
+ if (type === 'attempt_to_fix') {
778
+ attempt.attemptToFixPassed = isFinalAttempt && !hasFailure
779
+ } else if (type === 'early_flake_detection') {
780
+ attempt.isRetryReasonEfd = index > 0
781
+ }
782
+ attempts.push(attempt)
783
+ }
784
+
785
+ return {
786
+ errors,
787
+ finalAttempt: attempts[attempts.length - 1],
788
+ nonFinalAttempts: attempts.slice(0, -1),
789
+ state,
790
+ status,
791
+ task,
792
+ testName,
793
+ testProperties,
794
+ testSuiteAbsolutePath,
795
+ testSuiteStore,
796
+ }
797
+ }
798
+
799
+ /**
800
+ * Returns whether a final failed attempt exhausted Datadog-managed retries.
801
+ *
802
+ * @param {{
803
+ * meta?: { __ddTestOptEfdRetries?: number },
804
+ * repeats?: number,
805
+ * result?: { retryCount?: number }
806
+ * }} task
807
+ * @param {{ isFlakyTestRetries?: boolean }} testProperties
808
+ * @param {'attempt_to_fix'|'early_flake_detection'|'external'} type
809
+ * @param {string[]} statuses
810
+ * @param {boolean} isFinalAttempt
811
+ * @param {boolean} hasFailure
812
+ * @returns {boolean}
813
+ */
814
+ function hasFailedAllManagedRetries (task, testProperties, type, statuses, isFinalAttempt, hasFailure) {
815
+ if (!isFinalAttempt || !hasFailure || statuses.length === 0 || !statuses.every(status => status === 'fail')) {
816
+ return false
817
+ }
818
+
819
+ if (type === 'attempt_to_fix') {
820
+ return true
821
+ }
822
+
823
+ if (type === 'early_flake_detection') {
824
+ return (task.meta?.__ddTestOptEfdRetries || 0) > 0
825
+ }
826
+
827
+ return testProperties.isFlakyTestRetries === true && (task.result?.retryCount || 0) > 0
828
+ }
829
+
830
+ function getRepeatedAttemptCount (task, statuses) {
831
+ const retries = task.meta?.__ddTestOptAtfRetries ?? task.meta?.__ddTestOptEfdRetries ??
832
+ Math.max(task.result?.retryCount || 0, task.repeats || 0)
833
+ return Math.max(statuses.length, retries + 1)
834
+ }
835
+
836
+ function getAttemptToFixFinalStatus (statuses) {
837
+ return statuses.includes('fail') ? 'fail' : 'pass'
838
+ }
839
+
840
+ function getEarlyFlakeDetectionFinalStatus (statuses) {
841
+ return statuses.includes('pass') ? 'pass' : 'fail'
842
+ }
843
+
844
+ function getRepeatedTaskStatuses (task, status) {
845
+ const repeatedStatuses = []
846
+ const repeatCount = task.result?.repeatCount || 0
847
+ for (let index = 0; index <= repeatCount; index++) {
848
+ repeatedStatuses.push(status)
849
+ }
850
+ return repeatedStatuses
851
+ }
852
+
853
+ function reportFinalTestAttempt (testReport) {
854
+ const {
855
+ errors,
856
+ state,
857
+ status,
858
+ task,
859
+ testName,
860
+ testProperties,
861
+ testSuiteAbsolutePath,
862
+ testSuiteStore,
863
+ } = testReport
864
+
865
+ if (status === 'skip') {
866
+ testSkipCh.publish({
867
+ testName,
868
+ testSuiteAbsolutePath,
869
+ isNew: testProperties.isNew,
870
+ isDisabled: testProperties.isDisabled,
871
+ isTestFrameworkWorker: true,
872
+ requestErrorTags: state.requestErrorTags,
873
+ ...testSuiteStore,
874
+ })
875
+ return
876
+ }
877
+
878
+ const result = task.result
879
+ const finalStatus = getFinalTestStatus(testReport)
880
+ const finalAttempt = testReport.finalAttempt
881
+
882
+ if (status === 'fail') {
883
+ const error = errors[errors.length - 1] || errors[0]
884
+ reportTestAttempt(testReport, finalAttempt || {
885
+ error,
886
+ finalStatus,
887
+ hasFailedAllRetries: hasFailedAllManagedRetries(
888
+ task,
889
+ testProperties,
890
+ 'external',
891
+ ['fail'],
892
+ true,
893
+ true
894
+ ),
895
+ isRetry: errors.length > 1 || (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
896
+ status: 'fail',
897
+ })
898
+ return finalStatus === 'skip' ? undefined : error
899
+ }
900
+
901
+ reportTestAttempt(testReport, finalAttempt || {
902
+ finalStatus,
903
+ isRetry: errors.length > 0 || (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
904
+ status: 'pass',
905
+ })
906
+ }
907
+
908
+ function reportTestAttempt (testReport, attempt) {
909
+ const {
910
+ state,
911
+ task,
912
+ testName,
913
+ testProperties,
914
+ testSuiteAbsolutePath,
915
+ testSuiteStore,
916
+ } = testReport
917
+ const result = task.result
918
+ const status = attempt.status
919
+ const testCtx = {
920
+ currentStore: testSuiteStore,
921
+ testName,
922
+ testSuiteAbsolutePath,
923
+ isRetry: attempt.isRetry,
924
+ isNew: testProperties.isNew,
925
+ hasDynamicName: testProperties.hasDynamicName,
926
+ isAttemptToFix: testProperties.isAttemptToFix,
927
+ isDisabled: testProperties.isDisabled,
928
+ isQuarantined: testProperties.isQuarantined,
929
+ isModified: testProperties.isModified,
930
+ isRetryReasonEfd: attempt.isRetryReasonEfd,
931
+ isRetryReasonAttemptToFix: testProperties.isAttemptToFix && attempt.isRetry,
932
+ isRetryReasonAtr: !testProperties.isAttemptToFix && !testProperties.isEarlyFlakeDetection &&
933
+ testProperties.isFlakyTestRetries,
934
+ isTestFrameworkWorker: true,
935
+ requestErrorTags: state.requestErrorTags,
936
+ }
937
+ if (testProperties.isAttemptToFix) {
938
+ recordAttemptToFixExecution(state.attemptToFixExecutions, {
939
+ testSuite: testProperties.testSuite,
940
+ testName,
941
+ status,
942
+ isDisabled: testProperties.isDisabled,
943
+ isQuarantined: testProperties.isQuarantined,
944
+ })
945
+ }
946
+ if (testProperties.hasDynamicName) {
947
+ state.newTestsWithDynamicNames.add(`${testProperties.testSuite} › ${testName}`)
948
+ }
949
+ if (attempt.attemptToFixPassed) {
950
+ testCtx.attemptToFixPassed = true
951
+ } else if (attempt.attemptToFixFailed) {
952
+ testCtx.attemptToFixFailed = true
953
+ }
954
+ testStartCh.runStores(testCtx, () => {})
955
+ testCtx.status = status
956
+ testCtx.task = task
957
+ if (
958
+ status === 'pass' &&
959
+ !attempt.isRetry &&
960
+ isFinalTestAttempt(testReport, attempt) &&
961
+ result?.duration !== undefined
962
+ ) {
963
+ testCtx.duration = result.duration
964
+ }
965
+ testFinishTimeCh.runStores(testCtx, () => {})
966
+
967
+ if (status === 'pass') {
968
+ testPassCh.publish({
969
+ task,
970
+ earlyFlakeAbortReason: attempt.earlyFlakeAbortReason,
971
+ finalStatus: attempt.finalStatus,
972
+ ...testCtx.currentStore,
973
+ })
974
+ return
975
+ }
976
+
977
+ testErrorCh.publish({
978
+ duration: shouldUseTaskDurationForFailure(testReport, attempt) ? result?.duration : undefined,
979
+ error: attempt.error,
980
+ earlyFlakeAbortReason: attempt.earlyFlakeAbortReason,
981
+ finalStatus: attempt.finalStatus,
982
+ hasFailedAllRetries: attempt.hasFailedAllRetries,
983
+ attemptToFixFailed: attempt.attemptToFixFailed,
984
+ ...testCtx.currentStore,
985
+ })
986
+ }
987
+
988
+ function isFinalTestAttempt (testReport, attempt) {
989
+ return attempt.finalStatus !== undefined || testReport.finalAttempt === undefined
990
+ }
991
+
992
+ function shouldUseTaskDurationForFailure (testReport, attempt) {
993
+ return !attempt.isRetry && attempt.finalStatus !== undefined && testReport.finalAttempt === undefined
994
+ }
995
+
996
+ function getFinalTestStatus (testReport) {
997
+ const testProperties = testReport.testProperties
998
+ if (testProperties.isAttemptToFix) {
999
+ return testReport.finalAttempt?.finalStatus
1000
+ }
1001
+ if (testProperties.isDisabled || testProperties.isQuarantined) {
1002
+ return 'skip'
1003
+ }
1004
+ return testReport.status
1005
+ }
1006
+
1007
+ function getDatadogStatus (result) {
1008
+ const state = result?.state
1009
+ if (state === 'pass' || state === 'passed') return 'pass'
1010
+ if (state === 'fail' || state === 'failed') return 'fail'
1011
+ return 'skip'
1012
+ }
1013
+
1014
+ function getFinalRepeatedTaskStatus (task) {
1015
+ return task.meta?.__ddTestOptQuarantinedFailed ? 'fail' : getDatadogStatus(task.result)
1016
+ }
1017
+
1018
+ function getSuiteTaskError (task) {
1019
+ const suiteTasks = getTypeTasks(task.tasks || [], 'suite')
1020
+ for (const suiteTask of suiteTasks) {
1021
+ if (suiteTask.result?.state === 'fail' && suiteTask.result?.errors?.length) {
1022
+ return suiteTask.result.errors[0]
1023
+ }
1024
+ }
1025
+ }
1026
+
1027
+ function recomputeTaskState (task) {
1028
+ if (!task.tasks) {
1029
+ return getDatadogStatus(task.result)
1030
+ }
1031
+
1032
+ const hasErrors = task.result?.errors?.length > 0
1033
+ let hasFailed = false
1034
+ let hasPassed = false
1035
+
1036
+ for (const child of task.tasks) {
1037
+ const childStatus = recomputeTaskState(child)
1038
+ hasFailed ||= childStatus === 'fail'
1039
+ hasPassed ||= childStatus === 'pass'
1040
+ }
1041
+
1042
+ const status = hasErrors || hasFailed ? 'fail' : hasPassed ? 'pass' : 'skip'
1043
+ task.result ||= {}
1044
+ task.result.state = status
1045
+ return status
1046
+ }
1047
+
1048
+ function removeDatadogTestOptimizationNodeOptions (nodeOptions) {
1049
+ if (!nodeOptions) return nodeOptions
1050
+
1051
+ const tokens = splitNodeOptions(nodeOptions)
1052
+ const filteredTokens = []
1053
+ for (let index = 0; index < tokens.length; index++) {
1054
+ const token = tokens[index]
1055
+ const valueSeparator = token.indexOf('=')
1056
+ if (valueSeparator !== -1) {
1057
+ const flag = token.slice(0, valueSeparator)
1058
+ const value = token.slice(valueSeparator + 1)
1059
+ if (shouldRemoveNodeOption(flag, value)) {
1060
+ continue
1061
+ }
1062
+ }
1063
+
1064
+ if (token.startsWith('-r') && token.length > 2 && isDatadogTestOptimizationBootstrap(token.slice(2))) {
1065
+ continue
1066
+ }
1067
+
1068
+ if (
1069
+ DATADOG_TEST_OPTIMIZATION_NODE_OPTION_FLAGS.has(token) &&
1070
+ isDatadogTestOptimizationBootstrap(tokens[index + 1])
1071
+ ) {
1072
+ index++
1073
+ continue
1074
+ }
1075
+
1076
+ filteredTokens.push(token)
1077
+ }
1078
+
1079
+ return serializeNodeOptions(filteredTokens)
1080
+ }
1081
+
1082
+ function hasDatadogTestOptimizationNodeOptions (nodeOptions) {
1083
+ if (!nodeOptions) return false
1084
+
1085
+ const tokens = splitNodeOptions(nodeOptions)
1086
+ for (let index = 0; index < tokens.length; index++) {
1087
+ const token = tokens[index]
1088
+ const valueSeparator = token.indexOf('=')
1089
+ if (valueSeparator !== -1) {
1090
+ const flag = token.slice(0, valueSeparator)
1091
+ const value = token.slice(valueSeparator + 1)
1092
+ if (shouldRemoveNodeOption(flag, value)) {
1093
+ return true
1094
+ }
1095
+ }
1096
+
1097
+ if (token.startsWith('-r') && token.length > 2 && isDatadogTestOptimizationBootstrap(token.slice(2))) {
1098
+ return true
1099
+ }
1100
+
1101
+ if (
1102
+ DATADOG_TEST_OPTIMIZATION_NODE_OPTION_FLAGS.has(token) &&
1103
+ isDatadogTestOptimizationBootstrap(tokens[index + 1])
1104
+ ) {
1105
+ return true
1106
+ }
1107
+ }
1108
+ return false
1109
+ }
1110
+
1111
+ function shouldRemoveNodeOption (flag, value) {
1112
+ return DATADOG_TEST_OPTIMIZATION_NODE_OPTION_FLAGS.has(flag) &&
1113
+ isDatadogTestOptimizationBootstrap(value)
1114
+ }
1115
+
1116
+ function isDatadogTestOptimizationBootstrap (value) {
1117
+ if (!value) return false
1118
+
1119
+ const normalizedValue = value.replaceAll('\\', '/')
1120
+ if (DATADOG_TEST_OPTIMIZATION_BOOTSTRAPS.has(normalizedValue)) return true
1121
+
1122
+ for (const bootstrap of DATADOG_TEST_OPTIMIZATION_BOOTSTRAPS) {
1123
+ if (normalizedValue.endsWith(`/node_modules/${bootstrap}`)) return true
1124
+ }
1125
+ return false
1126
+ }
1127
+
1128
+ function splitNodeOptions (nodeOptions) {
1129
+ const tokens = []
1130
+ let token = ''
1131
+ let quote
1132
+
1133
+ for (let index = 0; index < nodeOptions.length; index++) {
1134
+ const char = nodeOptions[index]
1135
+
1136
+ if (quote) {
1137
+ if (char === '\\' && nodeOptions[index + 1] === quote) {
1138
+ token += quote
1139
+ index++
1140
+ continue
1141
+ }
1142
+
1143
+ if (char === quote) {
1144
+ quote = undefined
1145
+ } else {
1146
+ token += char
1147
+ }
1148
+ continue
1149
+ }
1150
+
1151
+ if (char === '"' || char === "'") {
1152
+ quote = char
1153
+ continue
1154
+ }
1155
+
1156
+ if (char === ' ' || char === '\n' || char === '\t') {
1157
+ if (token) {
1158
+ tokens.push(token)
1159
+ token = ''
1160
+ }
1161
+ continue
1162
+ }
1163
+
1164
+ token += char
1165
+ }
1166
+
1167
+ if (token) {
1168
+ tokens.push(token)
1169
+ }
1170
+ return tokens
1171
+ }
1172
+
1173
+ function serializeNodeOptions (tokens) {
1174
+ const serializedTokens = []
1175
+ for (const token of tokens) {
1176
+ if (NODE_OPTIONS_QUOTE_RE.test(token)) {
1177
+ serializedTokens.push(JSON.stringify(token))
1178
+ } else {
1179
+ serializedTokens.push(token)
1180
+ }
1181
+ }
1182
+ return serializedTokens.join(' ')
1183
+ }
1184
+
1185
+ function getTestSpecificationProject (testSpecification) {
1186
+ if (Array.isArray(testSpecification)) {
1187
+ return testSpecification[0]
1188
+ }
1189
+ return testSpecification?.project
1190
+ }
1191
+
1192
+ function getTestSpecificationFile (testSpecification) {
1193
+ if (Array.isArray(testSpecification)) {
1194
+ return testSpecification[1]
1195
+ }
1196
+ return testSpecification
1197
+ }
1198
+
1199
+ function getTestSpecificationOptions (testSpecification) {
1200
+ if (Array.isArray(testSpecification)) {
1201
+ return testSpecification[2]
1202
+ }
1203
+ return testSpecification
1204
+ }
1205
+
1206
+ function getTestSpecificationPool (testSpecification) {
1207
+ const options = getTestSpecificationOptions(testSpecification)
1208
+ const file = getTestSpecificationFile(testSpecification)
1209
+ const project = getTestSpecificationProject(testSpecification)
1210
+ return options?.pool ||
1211
+ file?.pool ||
1212
+ testSpecification?.pool ||
1213
+ project?.config?.pool ||
1214
+ project?.serializedConfig?.pool ||
1215
+ project?.pool
1216
+ }
1217
+
1218
+ function getEffectiveTestSpecificationPool (testSpecification, defaultPool) {
1219
+ return getTestSpecificationPool(testSpecification) || defaultPool
1220
+ }
1221
+
1222
+ /**
1223
+ * Resolves Vitest's default worker pool when the user did not configure one.
1224
+ *
1225
+ * @param {{ pool?: string }|undefined} config
1226
+ * @returns {string}
1227
+ */
1228
+ function getEffectiveConfigPool (config) {
1229
+ return config?.pool || VITEST_DEFAULT_POOL
1230
+ }
1231
+
1232
+ function getPoolOptionsIsolate (config, pool) {
1233
+ if (!pool) return
1234
+
1235
+ return config?.poolOptions?.[pool]?.isolate
1236
+ }
1237
+
1238
+ function getEffectiveConfigIsolate (config, pool) {
1239
+ return getPoolOptionsIsolate(config, pool) ?? config?.isolate
1240
+ }
1241
+
1242
+ function getTestSpecificationIsolate (testSpecification, pool) {
1243
+ const options = getTestSpecificationOptions(testSpecification)
1244
+ const file = getTestSpecificationFile(testSpecification)
1245
+ const project = getTestSpecificationProject(testSpecification)
1246
+ return getPoolOptionsIsolate(options, pool) ??
1247
+ getPoolOptionsIsolate(file, pool) ??
1248
+ getPoolOptionsIsolate(project?.config, pool) ??
1249
+ getPoolOptionsIsolate(project?.serializedConfig, pool) ??
1250
+ getPoolOptionsIsolate(project, pool) ??
1251
+ getPoolOptionsIsolate(testSpecification, pool) ??
1252
+ options?.isolate ??
1253
+ file?.isolate ??
1254
+ project?.config?.isolate ??
1255
+ project?.serializedConfig?.isolate ??
1256
+ project?.isolate ??
1257
+ testSpecification?.isolate
1258
+ }
1259
+
1260
+ function getEffectiveTestSpecificationIsolate (testSpecification, pool, defaultIsolate) {
1261
+ const isolate = getTestSpecificationIsolate(testSpecification, pool)
1262
+ return isolate === undefined ? defaultIsolate : isolate
1263
+ }
1264
+
1265
+ function getProjectName (project) {
1266
+ return normalizeProjectName(project?.name || project?.config?.name || project?.test?.name)
1267
+ }
1268
+
1269
+ function normalizeProjectName (name) {
1270
+ if (typeof name === 'string') return name
1271
+
1272
+ const label = name?.label
1273
+ return typeof label === 'string' ? label : undefined
1274
+ }
1275
+
1276
+ function safeConfig (project) {
1277
+ let config
1278
+ try {
1279
+ config = project?.config
1280
+ } catch {}
1281
+ return config
1282
+ }
1283
+
1284
+ function safeWorkspaceProject (ctx) {
1285
+ let project
1286
+ try {
1287
+ project = getWorkspaceProject(ctx)
1288
+ } catch {}
1289
+ return project
1290
+ }
1291
+
1292
+ module.exports = {
1293
+ configure,
1294
+ deactivate,
1295
+ configureWorkerEnv,
1296
+ shouldUse,
1297
+ }