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
@@ -0,0 +1,1293 @@
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
+ isTestFrameworkWorker: true,
440
+ isVitestNoWorkerInitActive: true,
441
+ }
442
+ testSuiteStartCh.runStores(testSuiteCtx, () => {})
443
+ testSuiteContexts.set(testModuleId, testSuiteCtx)
444
+ return testSuiteCtx
445
+ }
446
+
447
+ function getTestReport (task, testSuiteStore) {
448
+ const result = task.result
449
+ const testSuiteAbsolutePath = task.file?.filepath
450
+ const testName = getTestName(task)
451
+ const testProperties = getMainProcessTestProperties(task, testSuiteAbsolutePath, testName)
452
+ const { state } = reporterState
453
+ let status = getDatadogStatus(result)
454
+
455
+ if (task.meta?.__ddTestOptQuarantinedFailed && testProperties.isQuarantined && !testProperties.isAttemptToFix) {
456
+ status = 'fail'
457
+ }
458
+
459
+ if (testProperties.isAttemptToFix && task.meta?.__ddTestOptAtfStatuses?.length) {
460
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
461
+ errorCounts: task.meta.__ddTestOptAtfErrorCounts,
462
+ finalStatus: getAttemptToFixFinalStatus,
463
+ state,
464
+ statuses: task.meta.__ddTestOptAtfStatuses,
465
+ testSuiteStore,
466
+ type: 'attempt_to_fix',
467
+ })
468
+ }
469
+
470
+ if (testProperties.isEarlyFlakeDetection && task.meta?.__ddTestOptEfdStatuses?.length) {
471
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
472
+ errorCounts: task.meta.__ddTestOptEfdErrorCounts,
473
+ finalStatus: getEarlyFlakeDetectionFinalStatus,
474
+ state,
475
+ statuses: task.meta.__ddTestOptEfdStatuses,
476
+ testSuiteStore,
477
+ type: 'early_flake_detection',
478
+ })
479
+ }
480
+
481
+ if (!testProperties.isAttemptToFix && task.meta?.__ddTestOptRepeatStatuses?.length) {
482
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
483
+ errorCounts: task.meta.__ddTestOptRepeatErrorCounts,
484
+ finalStatus: () => status,
485
+ state,
486
+ statuses: task.meta.__ddTestOptRepeatStatuses,
487
+ testSuiteStore,
488
+ type: 'external',
489
+ })
490
+ }
491
+
492
+ const attemptStatuses = taskAttemptStatuses.get(task.id)
493
+ if (!testProperties.isAttemptToFix && !testProperties.isEarlyFlakeDetection && attemptStatuses?.length > 1) {
494
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
495
+ finalStatus: () => status,
496
+ state,
497
+ statuses: attemptStatuses,
498
+ testSuiteStore,
499
+ type: 'external',
500
+ })
501
+ }
502
+
503
+ if (!testProperties.isAttemptToFix && task.result?.repeatCount > 0) {
504
+ return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
505
+ finalStatus: () => status,
506
+ state,
507
+ statuses: getRepeatedTaskStatuses(task, status),
508
+ testSuiteStore,
509
+ type: 'external',
510
+ })
511
+ }
512
+
513
+ if (testProperties.isAttemptToFix) {
514
+ logAttemptToFixTestExecution(testProperties.testSuite, testName, loggedAttemptToFixTests)
515
+ }
516
+
517
+ if (testProperties.isDisabled && !testProperties.isAttemptToFix) {
518
+ status = 'skip'
519
+ if (result) result.state = 'skip'
520
+ } else if (testProperties.isQuarantined && status === 'fail' && !testProperties.isAttemptToFix) {
521
+ result.state = 'pass'
522
+ }
523
+
524
+ const errors = result?.errors || []
525
+ const finalErrorIndex = status === 'fail' ? errors.length - 1 : -1
526
+ const nonFinalAttempts = []
527
+
528
+ if (status !== 'skip') {
529
+ for (let index = 0; index < errors.length; index++) {
530
+ if (index === finalErrorIndex) continue
531
+ nonFinalAttempts.push({
532
+ error: errors[index],
533
+ isRetry: index > 0,
534
+ status: 'fail',
535
+ })
536
+ }
537
+ }
538
+
539
+ return {
540
+ errors,
541
+ nonFinalAttempts,
542
+ state,
543
+ status,
544
+ task,
545
+ testName,
546
+ testProperties,
547
+ testSuiteAbsolutePath,
548
+ testSuiteStore,
549
+ }
550
+ }
551
+
552
+ function getMainProcessTestProperties (task, testSuiteAbsolutePath, testName) {
553
+ const { state, testOptimizationData, testSessionConfiguration } = reporterState
554
+ const testProperties = testOptimizationData.testPropertiesByFilepath?.[testSuiteAbsolutePath]
555
+ const testSuite = testProperties?.testSuite || getTestSuitePath(
556
+ testSuiteAbsolutePath,
557
+ testSessionConfiguration.repositoryRoot || process.cwd()
558
+ )
559
+ const knownTests = testProperties?.knownTests
560
+ const testManagementProperties = testProperties?.testManagementTests?.[testName] || {}
561
+ const isAttemptToFix = testManagementProperties.isAttemptToFix
562
+ const isNew = !!(
563
+ !isAttemptToFix &&
564
+ state.isKnownTestsEnabled &&
565
+ knownTests &&
566
+ !state.isEarlyFlakeDetectionFaulty &&
567
+ !knownTests.includes(testName)
568
+ )
569
+ const isModified = testProperties?.isModified === true
570
+ const { flakyTestRetriesConfiguration } = testOptimizationData
571
+ const isFlakyTestRetries = !!flakyTestRetriesConfiguration && isFlakyTestRetriesEnabledForTask({
572
+ isFlakyTestRetriesEnabled: state.isFlakyTestRetriesEnabled,
573
+ flakyTestRetriesIncludesUnnamedProject: flakyTestRetriesConfiguration.includesUnnamedProject,
574
+ flakyTestRetriesProjectNames: flakyTestRetriesConfiguration.projectNames,
575
+ }, task)
576
+
577
+ return {
578
+ isAttemptToFix,
579
+ isDisabled: testManagementProperties.isDisabled,
580
+ isEarlyFlakeDetection: (isNew || isModified) && state.isEarlyFlakeDetectionEnabled,
581
+ isFlakyTestRetries,
582
+ isQuarantined: testManagementProperties.isQuarantined,
583
+ isModified,
584
+ isNew,
585
+ testSuite,
586
+ hasDynamicName: isNew && DYNAMIC_NAME_RE.test(testName),
587
+ }
588
+ }
589
+ }
590
+
591
+ function getTaskUpdateAttemptCount (taskId, packs) {
592
+ if (!packs) return
593
+
594
+ for (const pack of packs) {
595
+ if (pack[0] === taskId) {
596
+ return getRepeatedAttemptCount({
597
+ id: taskId,
598
+ result: pack[1],
599
+ meta: pack[2],
600
+ }, [])
601
+ }
602
+ }
603
+ }
604
+
605
+ function createTestModuleFromFile (file) {
606
+ return {
607
+ id: file.id,
608
+ moduleId: file.filepath,
609
+ project: file.project,
610
+ projectName: file.projectName,
611
+ task: file,
612
+ }
613
+ }
614
+
615
+ function getTestModuleId (testModule) {
616
+ return testModule.id || getTestModuleFilepath(testModule)
617
+ }
618
+
619
+ function getTestModuleTask (testModule) {
620
+ if (testModule.task?.tasks) {
621
+ return testModule.task
622
+ }
623
+
624
+ const task = createTaskFromReportedTask(testModule)
625
+ task.file = task
626
+ return task
627
+ }
628
+
629
+ function getTestCaseTask (testCase) {
630
+ if (testCase.task) {
631
+ return testCase.task
632
+ }
633
+
634
+ return createTaskFromReportedTask(testCase, {
635
+ filepath: getTestModuleFilepath(testCase.module),
636
+ projectName: getTestModuleProjectName(testCase.module),
637
+ })
638
+ }
639
+
640
+ function createTaskFromReportedTask (reportedTask, file, suite) {
641
+ const task = {
642
+ id: reportedTask.id,
643
+ type: reportedTask.type,
644
+ name: reportedTask.name || reportedTask.relativeModuleId || reportedTask.moduleId,
645
+ mode: reportedTask.options?.mode,
646
+ meta: getReportedTaskMeta(reportedTask),
647
+ result: getReportedTaskResult(reportedTask),
648
+ file,
649
+ suite,
650
+ }
651
+
652
+ if (reportedTask.type === 'module') {
653
+ task.filepath = reportedTask.moduleId
654
+ task.projectName = getTestModuleProjectName(reportedTask)
655
+ }
656
+
657
+ const children = getReportedTaskChildren(reportedTask)
658
+ if (children) {
659
+ task.tasks = []
660
+ const taskFile = file || task
661
+ const childSuite = task.type === 'suite' ? task : undefined
662
+ for (const child of children) {
663
+ task.tasks.push(createTaskFromReportedTask(child, taskFile, childSuite))
664
+ }
665
+ }
666
+
667
+ return task
668
+ }
669
+
670
+ function getReportedTaskChildren (reportedTask) {
671
+ const children = reportedTask.children
672
+ if (!children) return
673
+
674
+ if (typeof children.array === 'function') {
675
+ return children.array()
676
+ }
677
+
678
+ if (Array.isArray(children)) {
679
+ return children
680
+ }
681
+
682
+ return [...children]
683
+ }
684
+
685
+ function getReportedTaskMeta (reportedTask) {
686
+ if (typeof reportedTask.meta === 'function') {
687
+ return reportedTask.meta()
688
+ }
689
+
690
+ return reportedTask.meta || {}
691
+ }
692
+
693
+ function getReportedTaskResult (reportedTask) {
694
+ let result
695
+ if (typeof reportedTask.result === 'function') {
696
+ result = reportedTask.result()
697
+ } else if (typeof reportedTask.state === 'function') {
698
+ result = {
699
+ errors: typeof reportedTask.errors === 'function' ? reportedTask.errors() : undefined,
700
+ state: reportedTask.state(),
701
+ }
702
+ } else {
703
+ result = reportedTask.result
704
+ }
705
+
706
+ const diagnostic = typeof reportedTask.diagnostic === 'function'
707
+ ? reportedTask.diagnostic()
708
+ : undefined
709
+
710
+ return {
711
+ duration: diagnostic?.duration,
712
+ errors: result?.errors,
713
+ note: result?.note,
714
+ repeatCount: diagnostic?.repeatCount,
715
+ retryCount: diagnostic?.retryCount,
716
+ state: result?.state,
717
+ }
718
+ }
719
+
720
+ function getTestModuleFilepath (testModule) {
721
+ return testModule.moduleId || testModule.task?.filepath || testModule.filepath
722
+ }
723
+
724
+ function getTestModuleProjectName (testModule) {
725
+ return normalizeProjectName(
726
+ testModule.projectName || getProjectName(testModule.project) || testModule.task?.projectName ||
727
+ testModule.task?.file?.projectName
728
+ )
729
+ }
730
+
731
+ function getRepeatedTestReport (task, testName, testSuiteAbsolutePath, testProperties, status, options) {
732
+ const result = task.result
733
+ const errors = result?.errors || []
734
+ const { errorCounts, finalStatus, state, statuses, testSuiteStore, type } = options
735
+ const finalAttemptStatus = finalStatus(statuses)
736
+ const hasFailure = finalAttemptStatus === 'fail'
737
+ const attempts = []
738
+ const attemptCount = getRepeatedAttemptCount(task, statuses)
739
+ let errorIndex = 0
740
+ let previousErrorCount = 0
741
+
742
+ if (type === 'attempt_to_fix') {
743
+ logAttemptToFixTestExecution(testProperties.testSuite, testName, loggedAttemptToFixTests)
744
+ }
745
+
746
+ for (let index = 0; index < attemptCount; index++) {
747
+ const isFinalAttempt = index === attemptCount - 1
748
+ const attemptStatus = statuses[index] || 'pass'
749
+ const nextErrorCount = errorCounts?.[index]
750
+ const error = attemptStatus === 'fail'
751
+ ? errors[nextErrorCount === undefined ? errorIndex : previousErrorCount] || errors[errorIndex] || errors[0]
752
+ : undefined
753
+ if (nextErrorCount !== undefined) {
754
+ previousErrorCount = nextErrorCount
755
+ } else if (attemptStatus === 'fail') {
756
+ errorIndex++
757
+ }
758
+ const attempt = {
759
+ attemptToFixFailed: type === 'attempt_to_fix' && isFinalAttempt && hasFailure,
760
+ earlyFlakeAbortReason: type === 'early_flake_detection' && isFinalAttempt
761
+ ? task.meta?.__ddTestOptEfdAbortReason
762
+ : undefined,
763
+ error,
764
+ finalStatus: isFinalAttempt ? finalAttemptStatus : undefined,
765
+ hasFailedAllRetries: hasFailedAllManagedRetries(
766
+ task,
767
+ testProperties,
768
+ type,
769
+ statuses,
770
+ isFinalAttempt,
771
+ hasFailure
772
+ ),
773
+ isRetry: index > 0,
774
+ status: attemptStatus,
775
+ }
776
+ if (type === 'attempt_to_fix') {
777
+ attempt.attemptToFixPassed = isFinalAttempt && !hasFailure
778
+ } else if (type === 'early_flake_detection') {
779
+ attempt.isRetryReasonEfd = index > 0
780
+ }
781
+ attempts.push(attempt)
782
+ }
783
+
784
+ return {
785
+ errors,
786
+ finalAttempt: attempts[attempts.length - 1],
787
+ nonFinalAttempts: attempts.slice(0, -1),
788
+ state,
789
+ status,
790
+ task,
791
+ testName,
792
+ testProperties,
793
+ testSuiteAbsolutePath,
794
+ testSuiteStore,
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Returns whether a final failed attempt exhausted Datadog-managed retries.
800
+ *
801
+ * @param {{
802
+ * meta?: { __ddTestOptEfdRetries?: number },
803
+ * repeats?: number,
804
+ * result?: { retryCount?: number }
805
+ * }} task
806
+ * @param {{ isFlakyTestRetries?: boolean }} testProperties
807
+ * @param {'attempt_to_fix'|'early_flake_detection'|'external'} type
808
+ * @param {string[]} statuses
809
+ * @param {boolean} isFinalAttempt
810
+ * @param {boolean} hasFailure
811
+ * @returns {boolean}
812
+ */
813
+ function hasFailedAllManagedRetries (task, testProperties, type, statuses, isFinalAttempt, hasFailure) {
814
+ if (!isFinalAttempt || !hasFailure || statuses.length === 0 || !statuses.every(status => status === 'fail')) {
815
+ return false
816
+ }
817
+
818
+ if (type === 'attempt_to_fix') {
819
+ return true
820
+ }
821
+
822
+ if (type === 'early_flake_detection') {
823
+ return (task.meta?.__ddTestOptEfdRetries || 0) > 0
824
+ }
825
+
826
+ return testProperties.isFlakyTestRetries === true && (task.result?.retryCount || 0) > 0
827
+ }
828
+
829
+ function getRepeatedAttemptCount (task, statuses) {
830
+ const retries = task.meta?.__ddTestOptAtfRetries ?? task.meta?.__ddTestOptEfdRetries ??
831
+ Math.max(task.result?.retryCount || 0, task.repeats || 0)
832
+ return Math.max(statuses.length, retries + 1)
833
+ }
834
+
835
+ function getAttemptToFixFinalStatus (statuses) {
836
+ return statuses.includes('fail') ? 'fail' : 'pass'
837
+ }
838
+
839
+ function getEarlyFlakeDetectionFinalStatus (statuses) {
840
+ return statuses.includes('pass') ? 'pass' : 'fail'
841
+ }
842
+
843
+ function getRepeatedTaskStatuses (task, status) {
844
+ const repeatedStatuses = []
845
+ const repeatCount = task.result?.repeatCount || 0
846
+ for (let index = 0; index <= repeatCount; index++) {
847
+ repeatedStatuses.push(status)
848
+ }
849
+ return repeatedStatuses
850
+ }
851
+
852
+ function reportFinalTestAttempt (testReport) {
853
+ const {
854
+ errors,
855
+ status,
856
+ task,
857
+ testName,
858
+ testProperties,
859
+ testSuiteAbsolutePath,
860
+ testSuiteStore,
861
+ } = testReport
862
+
863
+ if (status === 'skip') {
864
+ testSkipCh.publish({
865
+ testName,
866
+ testSuiteAbsolutePath,
867
+ isNew: testProperties.isNew,
868
+ isDisabled: testProperties.isDisabled,
869
+ isTestFrameworkWorker: true,
870
+ ...testSuiteStore,
871
+ })
872
+ return
873
+ }
874
+
875
+ const result = task.result
876
+ const finalStatus = getFinalTestStatus(testReport)
877
+ const finalAttempt = testReport.finalAttempt
878
+
879
+ if (status === 'fail') {
880
+ const error = errors[errors.length - 1] || errors[0]
881
+ reportTestAttempt(testReport, finalAttempt || {
882
+ error,
883
+ finalStatus,
884
+ hasFailedAllRetries: hasFailedAllManagedRetries(
885
+ task,
886
+ testProperties,
887
+ 'external',
888
+ ['fail'],
889
+ true,
890
+ true
891
+ ),
892
+ isRetry: errors.length > 1 || (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
893
+ status: 'fail',
894
+ })
895
+ return finalStatus === 'skip' ? undefined : error
896
+ }
897
+
898
+ reportTestAttempt(testReport, finalAttempt || {
899
+ finalStatus,
900
+ isRetry: errors.length > 0 || (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
901
+ status: 'pass',
902
+ })
903
+ }
904
+
905
+ function reportTestAttempt (testReport, attempt) {
906
+ const {
907
+ state,
908
+ task,
909
+ testName,
910
+ testProperties,
911
+ testSuiteAbsolutePath,
912
+ testSuiteStore,
913
+ } = testReport
914
+ const result = task.result
915
+ const status = attempt.status
916
+ const testCtx = {
917
+ currentStore: testSuiteStore,
918
+ testName,
919
+ testSuiteAbsolutePath,
920
+ isRetry: attempt.isRetry,
921
+ isNew: testProperties.isNew,
922
+ hasDynamicName: testProperties.hasDynamicName,
923
+ isAttemptToFix: testProperties.isAttemptToFix,
924
+ isDisabled: testProperties.isDisabled,
925
+ isQuarantined: testProperties.isQuarantined,
926
+ isModified: testProperties.isModified,
927
+ isRetryReasonEfd: attempt.isRetryReasonEfd,
928
+ isRetryReasonAttemptToFix: testProperties.isAttemptToFix && attempt.isRetry,
929
+ isRetryReasonAtr: !testProperties.isAttemptToFix && !testProperties.isEarlyFlakeDetection &&
930
+ testProperties.isFlakyTestRetries,
931
+ isTestFrameworkWorker: true,
932
+ }
933
+ if (testProperties.isAttemptToFix) {
934
+ recordAttemptToFixExecution(state.attemptToFixExecutions, {
935
+ testSuite: testProperties.testSuite,
936
+ testName,
937
+ status,
938
+ isDisabled: testProperties.isDisabled,
939
+ isQuarantined: testProperties.isQuarantined,
940
+ })
941
+ }
942
+ if (testProperties.hasDynamicName) {
943
+ state.newTestsWithDynamicNames.add(`${testProperties.testSuite} › ${testName}`)
944
+ }
945
+ if (attempt.attemptToFixPassed) {
946
+ testCtx.attemptToFixPassed = true
947
+ } else if (attempt.attemptToFixFailed) {
948
+ testCtx.attemptToFixFailed = true
949
+ }
950
+ testStartCh.runStores(testCtx, () => {})
951
+ testCtx.status = status
952
+ testCtx.task = task
953
+ if (
954
+ status === 'pass' &&
955
+ !attempt.isRetry &&
956
+ isFinalTestAttempt(testReport, attempt) &&
957
+ result?.duration !== undefined
958
+ ) {
959
+ testCtx.duration = result.duration
960
+ }
961
+ testFinishTimeCh.runStores(testCtx, () => {})
962
+
963
+ if (status === 'pass') {
964
+ testPassCh.publish({
965
+ task,
966
+ earlyFlakeAbortReason: attempt.earlyFlakeAbortReason,
967
+ finalStatus: attempt.finalStatus,
968
+ ...testCtx.currentStore,
969
+ })
970
+ return
971
+ }
972
+
973
+ testErrorCh.publish({
974
+ duration: shouldUseTaskDurationForFailure(testReport, attempt) ? result?.duration : undefined,
975
+ error: attempt.error,
976
+ earlyFlakeAbortReason: attempt.earlyFlakeAbortReason,
977
+ finalStatus: attempt.finalStatus,
978
+ hasFailedAllRetries: attempt.hasFailedAllRetries,
979
+ attemptToFixFailed: attempt.attemptToFixFailed,
980
+ ...testCtx.currentStore,
981
+ })
982
+ }
983
+
984
+ function isFinalTestAttempt (testReport, attempt) {
985
+ return attempt.finalStatus !== undefined || testReport.finalAttempt === undefined
986
+ }
987
+
988
+ function shouldUseTaskDurationForFailure (testReport, attempt) {
989
+ return !attempt.isRetry && attempt.finalStatus !== undefined && testReport.finalAttempt === undefined
990
+ }
991
+
992
+ function getFinalTestStatus (testReport) {
993
+ const testProperties = testReport.testProperties
994
+ if (testProperties.isAttemptToFix) {
995
+ return testReport.finalAttempt?.finalStatus
996
+ }
997
+ if (testProperties.isDisabled || testProperties.isQuarantined) {
998
+ return 'skip'
999
+ }
1000
+ return testReport.status
1001
+ }
1002
+
1003
+ function getDatadogStatus (result) {
1004
+ const state = result?.state
1005
+ if (state === 'pass' || state === 'passed') return 'pass'
1006
+ if (state === 'fail' || state === 'failed') return 'fail'
1007
+ return 'skip'
1008
+ }
1009
+
1010
+ function getFinalRepeatedTaskStatus (task) {
1011
+ return task.meta?.__ddTestOptQuarantinedFailed ? 'fail' : getDatadogStatus(task.result)
1012
+ }
1013
+
1014
+ function getSuiteTaskError (task) {
1015
+ const suiteTasks = getTypeTasks(task.tasks || [], 'suite')
1016
+ for (const suiteTask of suiteTasks) {
1017
+ if (suiteTask.result?.state === 'fail' && suiteTask.result?.errors?.length) {
1018
+ return suiteTask.result.errors[0]
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ function recomputeTaskState (task) {
1024
+ if (!task.tasks) {
1025
+ return getDatadogStatus(task.result)
1026
+ }
1027
+
1028
+ const hasErrors = task.result?.errors?.length > 0
1029
+ let hasFailed = false
1030
+ let hasPassed = false
1031
+
1032
+ for (const child of task.tasks) {
1033
+ const childStatus = recomputeTaskState(child)
1034
+ hasFailed ||= childStatus === 'fail'
1035
+ hasPassed ||= childStatus === 'pass'
1036
+ }
1037
+
1038
+ const status = hasErrors || hasFailed ? 'fail' : hasPassed ? 'pass' : 'skip'
1039
+ task.result ||= {}
1040
+ task.result.state = status
1041
+ return status
1042
+ }
1043
+
1044
+ function removeDatadogTestOptimizationNodeOptions (nodeOptions) {
1045
+ if (!nodeOptions) return nodeOptions
1046
+
1047
+ const tokens = splitNodeOptions(nodeOptions)
1048
+ const filteredTokens = []
1049
+ for (let index = 0; index < tokens.length; index++) {
1050
+ const token = tokens[index]
1051
+ const valueSeparator = token.indexOf('=')
1052
+ if (valueSeparator !== -1) {
1053
+ const flag = token.slice(0, valueSeparator)
1054
+ const value = token.slice(valueSeparator + 1)
1055
+ if (shouldRemoveNodeOption(flag, value)) {
1056
+ continue
1057
+ }
1058
+ }
1059
+
1060
+ if (token.startsWith('-r') && token.length > 2 && isDatadogTestOptimizationBootstrap(token.slice(2))) {
1061
+ continue
1062
+ }
1063
+
1064
+ if (
1065
+ DATADOG_TEST_OPTIMIZATION_NODE_OPTION_FLAGS.has(token) &&
1066
+ isDatadogTestOptimizationBootstrap(tokens[index + 1])
1067
+ ) {
1068
+ index++
1069
+ continue
1070
+ }
1071
+
1072
+ filteredTokens.push(token)
1073
+ }
1074
+
1075
+ return serializeNodeOptions(filteredTokens)
1076
+ }
1077
+
1078
+ function hasDatadogTestOptimizationNodeOptions (nodeOptions) {
1079
+ if (!nodeOptions) return false
1080
+
1081
+ const tokens = splitNodeOptions(nodeOptions)
1082
+ for (let index = 0; index < tokens.length; index++) {
1083
+ const token = tokens[index]
1084
+ const valueSeparator = token.indexOf('=')
1085
+ if (valueSeparator !== -1) {
1086
+ const flag = token.slice(0, valueSeparator)
1087
+ const value = token.slice(valueSeparator + 1)
1088
+ if (shouldRemoveNodeOption(flag, value)) {
1089
+ return true
1090
+ }
1091
+ }
1092
+
1093
+ if (token.startsWith('-r') && token.length > 2 && isDatadogTestOptimizationBootstrap(token.slice(2))) {
1094
+ return true
1095
+ }
1096
+
1097
+ if (
1098
+ DATADOG_TEST_OPTIMIZATION_NODE_OPTION_FLAGS.has(token) &&
1099
+ isDatadogTestOptimizationBootstrap(tokens[index + 1])
1100
+ ) {
1101
+ return true
1102
+ }
1103
+ }
1104
+ return false
1105
+ }
1106
+
1107
+ function shouldRemoveNodeOption (flag, value) {
1108
+ return DATADOG_TEST_OPTIMIZATION_NODE_OPTION_FLAGS.has(flag) &&
1109
+ isDatadogTestOptimizationBootstrap(value)
1110
+ }
1111
+
1112
+ function isDatadogTestOptimizationBootstrap (value) {
1113
+ if (!value) return false
1114
+
1115
+ const normalizedValue = value.replaceAll('\\', '/')
1116
+ if (DATADOG_TEST_OPTIMIZATION_BOOTSTRAPS.has(normalizedValue)) return true
1117
+
1118
+ for (const bootstrap of DATADOG_TEST_OPTIMIZATION_BOOTSTRAPS) {
1119
+ if (normalizedValue.endsWith(`/node_modules/${bootstrap}`)) return true
1120
+ }
1121
+ return false
1122
+ }
1123
+
1124
+ function splitNodeOptions (nodeOptions) {
1125
+ const tokens = []
1126
+ let token = ''
1127
+ let quote
1128
+
1129
+ for (let index = 0; index < nodeOptions.length; index++) {
1130
+ const char = nodeOptions[index]
1131
+
1132
+ if (quote) {
1133
+ if (char === '\\' && nodeOptions[index + 1] === quote) {
1134
+ token += quote
1135
+ index++
1136
+ continue
1137
+ }
1138
+
1139
+ if (char === quote) {
1140
+ quote = undefined
1141
+ } else {
1142
+ token += char
1143
+ }
1144
+ continue
1145
+ }
1146
+
1147
+ if (char === '"' || char === "'") {
1148
+ quote = char
1149
+ continue
1150
+ }
1151
+
1152
+ if (char === ' ' || char === '\n' || char === '\t') {
1153
+ if (token) {
1154
+ tokens.push(token)
1155
+ token = ''
1156
+ }
1157
+ continue
1158
+ }
1159
+
1160
+ token += char
1161
+ }
1162
+
1163
+ if (token) {
1164
+ tokens.push(token)
1165
+ }
1166
+ return tokens
1167
+ }
1168
+
1169
+ function serializeNodeOptions (tokens) {
1170
+ const serializedTokens = []
1171
+ for (const token of tokens) {
1172
+ if (NODE_OPTIONS_QUOTE_RE.test(token)) {
1173
+ serializedTokens.push(JSON.stringify(token))
1174
+ } else {
1175
+ serializedTokens.push(token)
1176
+ }
1177
+ }
1178
+ return serializedTokens.join(' ')
1179
+ }
1180
+
1181
+ function getTestSpecificationProject (testSpecification) {
1182
+ if (Array.isArray(testSpecification)) {
1183
+ return testSpecification[0]
1184
+ }
1185
+ return testSpecification?.project
1186
+ }
1187
+
1188
+ function getTestSpecificationFile (testSpecification) {
1189
+ if (Array.isArray(testSpecification)) {
1190
+ return testSpecification[1]
1191
+ }
1192
+ return testSpecification
1193
+ }
1194
+
1195
+ function getTestSpecificationOptions (testSpecification) {
1196
+ if (Array.isArray(testSpecification)) {
1197
+ return testSpecification[2]
1198
+ }
1199
+ return testSpecification
1200
+ }
1201
+
1202
+ function getTestSpecificationPool (testSpecification) {
1203
+ const options = getTestSpecificationOptions(testSpecification)
1204
+ const file = getTestSpecificationFile(testSpecification)
1205
+ const project = getTestSpecificationProject(testSpecification)
1206
+ return options?.pool ||
1207
+ file?.pool ||
1208
+ testSpecification?.pool ||
1209
+ project?.config?.pool ||
1210
+ project?.serializedConfig?.pool ||
1211
+ project?.pool
1212
+ }
1213
+
1214
+ function getEffectiveTestSpecificationPool (testSpecification, defaultPool) {
1215
+ return getTestSpecificationPool(testSpecification) || defaultPool
1216
+ }
1217
+
1218
+ /**
1219
+ * Resolves Vitest's default worker pool when the user did not configure one.
1220
+ *
1221
+ * @param {{ pool?: string }|undefined} config
1222
+ * @returns {string}
1223
+ */
1224
+ function getEffectiveConfigPool (config) {
1225
+ return config?.pool || VITEST_DEFAULT_POOL
1226
+ }
1227
+
1228
+ function getPoolOptionsIsolate (config, pool) {
1229
+ if (!pool) return
1230
+
1231
+ return config?.poolOptions?.[pool]?.isolate
1232
+ }
1233
+
1234
+ function getEffectiveConfigIsolate (config, pool) {
1235
+ return getPoolOptionsIsolate(config, pool) ?? config?.isolate
1236
+ }
1237
+
1238
+ function getTestSpecificationIsolate (testSpecification, pool) {
1239
+ const options = getTestSpecificationOptions(testSpecification)
1240
+ const file = getTestSpecificationFile(testSpecification)
1241
+ const project = getTestSpecificationProject(testSpecification)
1242
+ return getPoolOptionsIsolate(options, pool) ??
1243
+ getPoolOptionsIsolate(file, pool) ??
1244
+ getPoolOptionsIsolate(project?.config, pool) ??
1245
+ getPoolOptionsIsolate(project?.serializedConfig, pool) ??
1246
+ getPoolOptionsIsolate(project, pool) ??
1247
+ getPoolOptionsIsolate(testSpecification, pool) ??
1248
+ options?.isolate ??
1249
+ file?.isolate ??
1250
+ project?.config?.isolate ??
1251
+ project?.serializedConfig?.isolate ??
1252
+ project?.isolate ??
1253
+ testSpecification?.isolate
1254
+ }
1255
+
1256
+ function getEffectiveTestSpecificationIsolate (testSpecification, pool, defaultIsolate) {
1257
+ const isolate = getTestSpecificationIsolate(testSpecification, pool)
1258
+ return isolate === undefined ? defaultIsolate : isolate
1259
+ }
1260
+
1261
+ function getProjectName (project) {
1262
+ return normalizeProjectName(project?.name || project?.config?.name || project?.test?.name)
1263
+ }
1264
+
1265
+ function normalizeProjectName (name) {
1266
+ if (typeof name === 'string') return name
1267
+
1268
+ const label = name?.label
1269
+ return typeof label === 'string' ? label : undefined
1270
+ }
1271
+
1272
+ function safeConfig (project) {
1273
+ let config
1274
+ try {
1275
+ config = project?.config
1276
+ } catch {}
1277
+ return config
1278
+ }
1279
+
1280
+ function safeWorkspaceProject (ctx) {
1281
+ let project
1282
+ try {
1283
+ project = getWorkspaceProject(ctx)
1284
+ } catch {}
1285
+ return project
1286
+ }
1287
+
1288
+ module.exports = {
1289
+ configure,
1290
+ deactivate,
1291
+ configureWorkerEnv,
1292
+ shouldUse,
1293
+ }