dd-trace 6.8.0 → 6.9.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 (179) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +30 -34
  44. package/index.d.ts +98 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +5 -3
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  55. package/packages/datadog-instrumentations/src/dns.js +21 -2
  56. package/packages/datadog-instrumentations/src/electron.js +1 -0
  57. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  58. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  59. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  60. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
  62. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  66. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  67. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  68. package/packages/datadog-instrumentations/src/jest.js +979 -233
  69. package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
  71. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  72. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  73. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  74. package/packages/datadog-instrumentations/src/next.js +155 -23
  75. package/packages/datadog-instrumentations/src/openai.js +14 -0
  76. package/packages/datadog-instrumentations/src/pg.js +180 -6
  77. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  78. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  79. package/packages/datadog-instrumentations/src/promise.js +3 -3
  80. package/packages/datadog-instrumentations/src/router.js +195 -19
  81. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
  82. package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
  83. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  84. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  85. package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
  86. package/packages/datadog-instrumentations/src/when.js +3 -3
  87. package/packages/datadog-instrumentations/src/ws.js +5 -1
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  89. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  90. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  91. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  92. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  93. package/packages/datadog-plugin-electron/src/net.js +10 -4
  94. package/packages/datadog-plugin-http/src/client.js +1 -1
  95. package/packages/datadog-plugin-http2/src/client.js +1 -1
  96. package/packages/datadog-plugin-jest/src/index.js +1 -18
  97. package/packages/datadog-plugin-jest/src/util.js +2 -0
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  99. package/packages/datadog-plugin-mocha/src/index.js +318 -1
  100. package/packages/datadog-plugin-next/src/index.js +57 -9
  101. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  102. package/packages/datadog-plugin-pg/src/index.js +65 -1
  103. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  104. package/packages/datadog-plugin-vitest/src/index.js +95 -7
  105. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  106. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  107. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  108. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  109. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  110. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  111. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  112. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  113. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  115. package/packages/dd-trace/src/appsec/index.js +48 -476
  116. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  117. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  118. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  120. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  122. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  123. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  124. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  127. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  128. package/packages/dd-trace/src/config/helper.js +2 -0
  129. package/packages/dd-trace/src/config/index.js +16 -5
  130. package/packages/dd-trace/src/config/remote_config.js +1 -1
  131. package/packages/dd-trace/src/config/supported-configurations.json +8 -0
  132. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  133. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  134. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  135. package/packages/dd-trace/src/guardrails/log.js +1 -10
  136. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  137. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  138. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  139. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  140. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  141. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  142. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  143. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  144. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  145. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  146. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  147. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  148. package/packages/dd-trace/src/log/channels.js +1 -9
  149. package/packages/dd-trace/src/log/levels.js +12 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  151. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  153. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  154. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  155. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  156. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  157. package/packages/dd-trace/src/plugins/index.js +1 -0
  158. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  159. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  160. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  161. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  162. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  163. package/packages/dd-trace/src/plugins/util/test.js +319 -75
  164. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  165. package/packages/dd-trace/src/plugins/util/web.js +23 -1
  166. package/packages/dd-trace/src/profiler.js +1 -1
  167. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  168. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  169. package/packages/dd-trace/src/proxy.js +1 -0
  170. package/packages/dd-trace/src/ritm.js +5 -0
  171. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  172. package/packages/dd-trace/src/sampling_rule.js +4 -2
  173. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  174. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  175. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  176. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  177. package/vendor/dist/pprof-format/index.js +1 -1
  178. package/version.js +10 -8
  179. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -1,6 +1,12 @@
1
1
  'use strict'
2
2
 
3
+ const fs = require('node:fs')
4
+
3
5
  const log = require('../../dd-trace/src/log')
6
+ const {
7
+ EMPTY_EFD_RETRY_POLICY,
8
+ hasEfdRetries,
9
+ } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
4
10
  const { channel } = require('./helpers/instrument')
5
11
 
6
12
  // test hooks
@@ -28,9 +34,25 @@ const testManagementTestsCh = channel('ci:vitest:test-management-tests')
28
34
  const modifiedFilesCh = channel('ci:vitest:modified-files')
29
35
 
30
36
  const workerReportTraceCh = channel('ci:vitest:worker-report:trace')
37
+ const workerReportCoverageCh = channel('ci:vitest:worker-report:coverage')
31
38
  const workerReportLogsCh = channel('ci:vitest:worker-report:logs')
39
+ const workerReportTelemetryCh = channel('ci:vitest:worker-report:telemetry')
32
40
  const codeCoverageReportCh = channel('ci:vitest:coverage-report')
33
41
 
42
+ /**
43
+ * Resolves a path without failing Test Optimization when the path is unavailable.
44
+ *
45
+ * @param {string} filepath
46
+ * @returns {string}
47
+ */
48
+ function realpath (filepath) {
49
+ try {
50
+ return fs.realpathSync(filepath)
51
+ } catch {
52
+ return filepath
53
+ }
54
+ }
55
+
34
56
  function findExportByName (pkg, name) {
35
57
  for (const [key, value] of Object.entries(pkg)) {
36
58
  if (value?.name === name) {
@@ -95,8 +117,7 @@ function getProvidedContext () {
95
117
  _ddIsEarlyFlakeDetectionEnabled,
96
118
  _ddIsDiEnabled,
97
119
  _ddTestPropertiesByFilepath: testPropertiesByFilepath,
98
- _ddEarlyFlakeDetectionNumRetries: numRepeats,
99
- _ddEarlyFlakeDetectionSlowTestRetries: slowTestRetries,
120
+ _ddEarlyFlakeDetectionRetryPolicy: earlyFlakeDetectionRetryPolicy,
100
121
  _ddIsKnownTestsEnabled: isKnownTestsEnabled,
101
122
  _ddIsTestManagementTestsEnabled: isTestManagementTestsEnabled,
102
123
  _ddTestManagementAttemptToFixRetries: testManagementAttemptToFixRetries,
@@ -110,14 +131,18 @@ function getProvidedContext () {
110
131
  _ddTestCommand: testCommand,
111
132
  _ddRepositoryRoot: repositoryRoot,
112
133
  _ddCodeOwnersEntries: codeOwnersEntries,
134
+ _ddIsCodeCoverageEnabled: isCodeCoverageEnabled,
135
+ _ddItrCorrelationId: itrCorrelationId,
136
+ _ddUnskippableSuites: unskippableSuites,
137
+ _ddForcedToRunSuites: forcedToRunSuites,
113
138
  } = globalThis.__vitest_worker__.providedContext
139
+ const retryPolicy = earlyFlakeDetectionRetryPolicy ?? EMPTY_EFD_RETRY_POLICY
114
140
 
115
141
  return {
116
142
  isDiEnabled: _ddIsDiEnabled,
117
- isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled,
143
+ isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled && hasEfdRetries(retryPolicy),
118
144
  testPropertiesByFilepath,
119
- numRepeats,
120
- slowTestRetries: slowTestRetries ?? {},
145
+ earlyFlakeDetectionRetryPolicy: retryPolicy,
121
146
  isKnownTestsEnabled,
122
147
  isTestManagementTestsEnabled,
123
148
  testManagementAttemptToFixRetries,
@@ -131,6 +156,10 @@ function getProvidedContext () {
131
156
  testCommand,
132
157
  repositoryRoot,
133
158
  codeOwnersEntries,
159
+ isCodeCoverageEnabled,
160
+ itrCorrelationId,
161
+ unskippableSuites,
162
+ forcedToRunSuites,
134
163
  }
135
164
  } catch {
136
165
  log.error('Vitest workers could not parse provided context, so some features will not work.')
@@ -138,8 +167,7 @@ function getProvidedContext () {
138
167
  isDiEnabled: false,
139
168
  isEarlyFlakeDetectionEnabled: false,
140
169
  testPropertiesByFilepath: {},
141
- numRepeats: 0,
142
- slowTestRetries: {},
170
+ earlyFlakeDetectionRetryPolicy: EMPTY_EFD_RETRY_POLICY,
143
171
  isKnownTestsEnabled: false,
144
172
  isTestManagementTestsEnabled: false,
145
173
  testManagementAttemptToFixRetries: 0,
@@ -153,6 +181,10 @@ function getProvidedContext () {
153
181
  testCommand: undefined,
154
182
  repositoryRoot: undefined,
155
183
  codeOwnersEntries: undefined,
184
+ isCodeCoverageEnabled: false,
185
+ itrCorrelationId: undefined,
186
+ unskippableSuites: {},
187
+ forcedToRunSuites: {},
156
188
  }
157
189
  }
158
190
  }
@@ -230,8 +262,11 @@ module.exports = {
230
262
  testManagementTestsCh,
231
263
  modifiedFilesCh,
232
264
  workerReportTraceCh,
265
+ workerReportCoverageCh,
233
266
  workerReportLogsCh,
267
+ workerReportTelemetryCh,
234
268
  codeCoverageReportCh,
269
+ realpath,
235
270
  findExportByName,
236
271
  getTestRunnerExport,
237
272
  getTypeTasks,
@@ -1,11 +1,15 @@
1
1
  'use strict'
2
2
 
3
+ const path = require('node:path')
3
4
  const { performance } = require('node:perf_hooks')
5
+ const { fileURLToPath } = require('node:url')
4
6
 
5
7
  const shimmer = require('../../datadog-shimmer')
8
+ const log = require('../../dd-trace/src/log')
9
+ const { getEfdRetryCountForDuration } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
6
10
  const {
7
11
  DYNAMIC_NAME_RE,
8
- getEfdRetryCount,
12
+ getTestSuitePath,
9
13
  recordAttemptToFixExecution,
10
14
  logAttemptToFixTestExecution,
11
15
  } = require('../../dd-trace/src/plugins/util/test')
@@ -27,6 +31,7 @@ const {
27
31
  getTypeTasks,
28
32
  getTestName,
29
33
  getProvidedContext,
34
+ realpath,
30
35
  isFlakyTestRetriesEnabledForTask,
31
36
  getVitestTestProperties,
32
37
  } = require('./vitest-util')
@@ -52,10 +57,187 @@ const efdExecutionStartByTask = new WeakMap()
52
57
  const efdSkippedRetryResults = new WeakMap()
53
58
  const attemptToFixExecutions = new Map()
54
59
  const loggedAttemptToFixTests = new Set()
55
- const switchedStatuses = new WeakSet()
60
+ const switchedStatuses = new WeakMap()
56
61
  let vitestGetFn = null
57
62
  let vitestSetFn = null
58
63
  let vitestGetHooks = null
64
+ let preciseCoverageSession
65
+ let isPreciseCoverageUnavailable = false
66
+ let vitestCoverageSnapshot
67
+ const wrappedCoverageWorkerStates = new WeakSet()
68
+ const nonIsolatedCoverageFiles = new Set()
69
+
70
+ /**
71
+ * Sends a command to a Node.js inspector session.
72
+ *
73
+ * @param {import('node:inspector').Session} session
74
+ * @param {string} method
75
+ * @param {object} [params]
76
+ * @returns {Promise<object>}
77
+ */
78
+ function postInspectorCommand (session, method, params) {
79
+ return new Promise((resolve, reject) => {
80
+ session.post(method, params, (error, result) => {
81
+ if (error) {
82
+ reject(error)
83
+ } else {
84
+ resolve(result || {})
85
+ }
86
+ })
87
+ })
88
+ }
89
+
90
+ async function startPreciseCoverage () {
91
+ if (preciseCoverageSession) return true
92
+ if (isPreciseCoverageUnavailable) return false
93
+
94
+ let session
95
+ try {
96
+ const inspector = require('node:inspector')
97
+ session = new inspector.Session()
98
+ session.connect()
99
+ await postInspectorCommand(session, 'Profiler.enable')
100
+ await postInspectorCommand(session, 'Profiler.startPreciseCoverage', {
101
+ callCount: false,
102
+ detailed: false,
103
+ })
104
+ preciseCoverageSession = session
105
+ return true
106
+ } catch (error) {
107
+ isPreciseCoverageUnavailable = true
108
+ try {
109
+ session?.disconnect()
110
+ } catch {}
111
+ log.warn('Could not start Vitest TIA code coverage: %s', error?.message)
112
+ return false
113
+ }
114
+ }
115
+
116
+ function getCoverageFilename (url) {
117
+ if (!url) return
118
+
119
+ if (url.startsWith('file://')) {
120
+ try {
121
+ return fileURLToPath(url)
122
+ } catch {
123
+ return
124
+ }
125
+ }
126
+
127
+ if (path.isAbsolute(url)) return url
128
+ }
129
+
130
+ function isFileInRepository (filename, repositoryRoot) {
131
+ const relativeFilename = path.relative(repositoryRoot, filename)
132
+ return relativeFilename !== '..' &&
133
+ !relativeFilename.startsWith(`..${path.sep}`) &&
134
+ !path.isAbsolute(relativeFilename) &&
135
+ !relativeFilename.startsWith(`node_modules${path.sep}`) &&
136
+ !relativeFilename.includes(`${path.sep}node_modules${path.sep}`)
137
+ }
138
+
139
+ function isV8ScriptCovered (scriptCoverage) {
140
+ for (const functionCoverage of scriptCoverage.functions) {
141
+ for (const range of functionCoverage.ranges) {
142
+ if (range.count > 0) return true
143
+ }
144
+ }
145
+ return false
146
+ }
147
+
148
+ function getCoveredFilesFromV8Result (coverage, repositoryRoot) {
149
+ const coveredFiles = []
150
+ const scriptCoverageResults = coverage?.result
151
+ if (scriptCoverageResults) {
152
+ for (const scriptCoverage of scriptCoverageResults) {
153
+ if (!isV8ScriptCovered(scriptCoverage)) continue
154
+
155
+ const coverageFilename = getCoverageFilename(scriptCoverage.url)
156
+ if (!coverageFilename) continue
157
+
158
+ const filename = realpath(coverageFilename)
159
+ if (isFileInRepository(filename, repositoryRoot)) {
160
+ coveredFiles.push(filename)
161
+ }
162
+ }
163
+ }
164
+ return coveredFiles
165
+ }
166
+
167
+ function getVitestCoverageOptions () {
168
+ return globalThis.__vitest_worker__?.config?.coverage
169
+ }
170
+
171
+ /**
172
+ * Check whether the current Vitest worker reuses its module cache across test suites.
173
+ *
174
+ * @returns {boolean}
175
+ */
176
+ function isNonIsolatedRun () {
177
+ const config = globalThis.__vitest_worker__?.config
178
+ if (config?.isolate === false) return true
179
+
180
+ return config?.poolOptions?.[config.pool]?.isolate === false
181
+ }
182
+
183
+ /**
184
+ * Conservatively include files covered by earlier suites when Vitest reuses its module cache.
185
+ *
186
+ * @param {string[] | undefined} coverageFiles
187
+ * @returns {string[] | undefined}
188
+ */
189
+ function includePreviouslyCoveredFiles (coverageFiles) {
190
+ if (!coverageFiles || !isNonIsolatedRun()) return coverageFiles
191
+
192
+ // TODO: Track module cache hits per suite instead; cumulative coverage can under-skip with isolate:false.
193
+ for (const filename of coverageFiles) {
194
+ nonIsolatedCoverageFiles.add(filename)
195
+ }
196
+ return [...nonIsolatedCoverageFiles]
197
+ }
198
+
199
+ function usesVitestV8CoverageSnapshot (coverageOptions) {
200
+ return coverageOptions?.enabled === true &&
201
+ coverageOptions.provider === 'v8'
202
+ }
203
+
204
+ async function getPreciseCoverageFiles (repositoryRoot) {
205
+ if (!await startPreciseCoverage()) return
206
+
207
+ try {
208
+ const coverage = await postInspectorCommand(preciseCoverageSession, 'Profiler.takePreciseCoverage')
209
+ return getCoveredFilesFromV8Result(coverage, repositoryRoot)
210
+ } catch (error) {
211
+ isPreciseCoverageUnavailable = true
212
+ try {
213
+ preciseCoverageSession.disconnect()
214
+ } catch {}
215
+ preciseCoverageSession = undefined
216
+ log.warn('Could not collect Vitest TIA code coverage: %s', error?.message)
217
+ }
218
+ }
219
+
220
+ function getVitestCoverageFiles (repositoryRoot) {
221
+ return getCoveredFilesFromV8Result(vitestCoverageSnapshot, repositoryRoot)
222
+ }
223
+
224
+ function wrapVitestCoverageRpc () {
225
+ const workerState = globalThis.__vitest_worker__
226
+ if (!workerState?.rpc || wrappedCoverageWorkerStates.has(workerState)) return
227
+
228
+ wrappedCoverageWorkerStates.add(workerState)
229
+ workerState.rpc = new Proxy(workerState.rpc, {
230
+ get (target, property, receiver) {
231
+ const value = Reflect.get(target, property, receiver)
232
+ if (property !== 'onAfterSuiteRun' || typeof value !== 'function') return value
233
+
234
+ return function (metadata) {
235
+ vitestCoverageSnapshot = metadata?.coverage
236
+ return value.apply(this, arguments)
237
+ }
238
+ },
239
+ })
240
+ }
59
241
 
60
242
  function waitForHitProbe () {
61
243
  const promises = {}
@@ -208,7 +390,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
208
390
  const {
209
391
  isEarlyFlakeDetectionEnabled,
210
392
  isKnownTestsEnabled,
211
- numRepeats,
393
+ earlyFlakeDetectionRetryPolicy,
212
394
  isTestManagementTestsEnabled,
213
395
  testManagementAttemptToFixRetries,
214
396
  isImpactedTestsEnabled,
@@ -247,7 +429,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
247
429
  if (isEarlyFlakeDetectionEnabled) {
248
430
  efdRetryTasks.add(task)
249
431
  disableFrameworkRetries(task)
250
- task.repeats = numRepeats
432
+ task.repeats = earlyFlakeDetectionRetryPolicy.schedulingRetryCount
251
433
  }
252
434
  modifiedTasks.add(task)
253
435
  taskToStatuses.set(task, [])
@@ -257,7 +439,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
257
439
  if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
258
440
  efdRetryTasks.add(task)
259
441
  disableFrameworkRetries(task)
260
- task.repeats = numRepeats
442
+ task.repeats = earlyFlakeDetectionRetryPolicy.schedulingRetryCount
261
443
  }
262
444
  newTasks.add(task)
263
445
  taskToStatuses.set(task, [])
@@ -281,14 +463,14 @@ function wrapVitestTestRunner (VitestTestRunner) {
281
463
  if (isAttemptingToFix) {
282
464
  const statuses = attemptToFixTaskToStatuses.get(task)
283
465
  if (task.result.state === 'pass' && statuses?.includes('fail')) {
284
- switchedStatuses.add(task)
466
+ switchedStatuses.set(task, task.result.state)
285
467
  task.result.state = 'fail'
286
468
  }
287
469
  }
288
470
 
289
471
  if (!isAttemptingToFix && isQuarantined) {
290
472
  if (task.result.state === 'fail') {
291
- switchedStatuses.add(task)
473
+ switchedStatuses.set(task, task.result.state)
292
474
  }
293
475
  task.result.state = 'pass'
294
476
  }
@@ -299,7 +481,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
299
481
  // If the test has passed at least once, we consider it passed
300
482
  if (statuses.includes('pass')) {
301
483
  if (task.result.state === 'fail') {
302
- switchedStatuses.add(task)
484
+ switchedStatuses.set(task, task.result.state)
303
485
  }
304
486
  task.result.state = 'pass'
305
487
  }
@@ -321,7 +503,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
321
503
  isKnownTestsEnabled,
322
504
  isEarlyFlakeDetectionEnabled,
323
505
  isDiEnabled,
324
- slowTestRetries,
506
+ earlyFlakeDetectionRetryPolicy,
325
507
  } = providedContext
326
508
 
327
509
  if (isKnownTestsEnabled) {
@@ -337,7 +519,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
337
519
  const duration = previousExecutionStart === undefined
338
520
  ? task.result?.duration ?? 0
339
521
  : performance.now() - previousExecutionStart
340
- const retryCount = getEfdRetryCount(duration, slowTestRetries)
522
+ const retryCount = getEfdRetryCountForDuration(duration, earlyFlakeDetectionRetryPolicy)
341
523
  efdDeterminedRetries.set(task, retryCount)
342
524
  task.repeats = retryCount
343
525
  if (retryCount === 0) {
@@ -517,7 +699,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
517
699
  const {
518
700
  isEarlyFlakeDetectionEnabled,
519
701
  testManagementAttemptToFixRetries,
520
- slowTestRetries,
702
+ earlyFlakeDetectionRetryPolicy,
521
703
  } = getProvidedContext()
522
704
 
523
705
  const status = getVitestTestStatus(task, retryInfo.retry)
@@ -545,7 +727,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
545
727
  ) {
546
728
  const executionStart = efdExecutionStartByTask.get(task)
547
729
  const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
548
- const retryCount = getEfdRetryCount(duration, slowTestRetries)
730
+ const retryCount = getEfdRetryCountForDuration(duration, earlyFlakeDetectionRetryPolicy)
549
731
  efdDeterminedRetries.set(task, retryCount)
550
732
  task.repeats = retryCount
551
733
  if (retryCount === 0) {
@@ -649,6 +831,19 @@ addHook({
649
831
  // From >=3.0.1, the first arguments changes from a string to an object containing the filepath
650
832
  const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
651
833
  const providedContext = getProvidedContext()
834
+ const repositoryRoot = providedContext.repositoryRoot || process.cwd()
835
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, repositoryRoot)
836
+ const coverageOptions = getVitestCoverageOptions()
837
+ const shouldUseVitestCoverage = usesVitestV8CoverageSnapshot(coverageOptions)
838
+ const coverageLibrary = 'v8'
839
+ vitestCoverageSnapshot = undefined
840
+ if (providedContext.isCodeCoverageEnabled) {
841
+ if (shouldUseVitestCoverage) {
842
+ wrapVitestCoverageRpc()
843
+ } else {
844
+ await startPreciseCoverage()
845
+ }
846
+ }
652
847
 
653
848
  const testSuiteCtx = {
654
849
  testSuiteAbsolutePath,
@@ -656,8 +851,13 @@ addHook({
656
851
  testSessionId: providedContext.testSessionId,
657
852
  testModuleId: providedContext.testModuleId,
658
853
  testCommand: providedContext.testCommand,
659
- repositoryRoot: providedContext.repositoryRoot,
854
+ repositoryRoot,
660
855
  codeOwnersEntries: providedContext.codeOwnersEntries,
856
+ isCodeCoverageEnabled: providedContext.isCodeCoverageEnabled,
857
+ coverageLibrary,
858
+ itrCorrelationId: providedContext.itrCorrelationId,
859
+ isUnskippable: providedContext.unskippableSuites?.[testSuite] === true,
860
+ isForcedToRun: providedContext.forcedToRunSuites?.[testSuite] === true,
661
861
  }
662
862
  testSuiteStartCh.runStores(testSuiteCtx, () => {})
663
863
  const startTestsResponse = await startTests.apply(this, arguments)
@@ -671,14 +871,15 @@ addHook({
671
871
  const { result } = task
672
872
  // We have to trick vitest into thinking that the test has passed
673
873
  // but we want to report it as failed if it did fail
674
- const isSwitchedStatus = switchedStatuses.has(task)
874
+ const switchedStatus = switchedStatuses.get(task)
875
+ const isSwitchedStatus = switchedStatus !== undefined
675
876
 
676
877
  if (result) {
677
878
  const { state, duration, errors } = result
678
879
  const testError = getCurrentAttemptTestError(task, errors)
679
880
  if (attemptToFixTasks.has(task)) {
680
- const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
681
- recordFinalAttemptToFixExecution(task, status, providedContext)
881
+ const attemptToFixStatus = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
882
+ recordFinalAttemptToFixExecution(task, attemptToFixStatus, providedContext)
682
883
  }
683
884
 
684
885
  if (state === 'skip') { // programmatic skip
@@ -686,7 +887,9 @@ addHook({
686
887
  testName: getTestName(task),
687
888
  testSuiteAbsolutePath: task.file.filepath,
688
889
  isNew: newTasks.has(task),
890
+ isAttemptToFix: attemptToFixTasks.has(task),
689
891
  isDisabled: disabledTasks.has(task),
892
+ isQuarantined: quarantinedTasks.has(task),
690
893
  })
691
894
  } else if (state === 'pass' && !isSwitchedStatus) {
692
895
  if (testCtx) {
@@ -722,7 +925,8 @@ addHook({
722
925
  providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
723
926
  if (isEfdRetry) {
724
927
  const statuses = taskToStatuses.get(task)
725
- const efdRetryCount = efdDeterminedRetries.get(task) ?? providedContext.numRepeats
928
+ const efdRetryCount = efdDeterminedRetries.get(task) ??
929
+ providedContext.earlyFlakeDetectionRetryPolicy.schedulingRetryCount
726
930
  // statuses only includes repetitions (not the initial run), so we check against retry count (not +1)
727
931
  if (efdRetryCount > 0 && statuses && statuses.length === efdRetryCount &&
728
932
  statuses.every(status => status === 'fail')) {
@@ -783,7 +987,9 @@ addHook({
783
987
  testName: getTestName(task),
784
988
  testSuiteAbsolutePath: task.file.filepath,
785
989
  isNew: newTasks.has(task),
990
+ isAttemptToFix: attemptToFixTasks.has(task),
786
991
  isDisabled: disabledTasks.has(task),
992
+ isQuarantined: quarantinedTasks.has(task),
787
993
  })
788
994
  }
789
995
  }
@@ -807,8 +1013,19 @@ addHook({
807
1013
  testSuiteErrorCh.runStores(testSuiteCtx, () => {})
808
1014
  }
809
1015
 
1016
+ let coverageFiles
1017
+ if (providedContext.isCodeCoverageEnabled) {
1018
+ const currentCoverageFiles = shouldUseVitestCoverage
1019
+ ? getVitestCoverageFiles(repositoryRoot)
1020
+ : await getPreciseCoverageFiles(repositoryRoot)
1021
+ coverageFiles = includePreviouslyCoveredFiles(currentCoverageFiles)
1022
+ }
1023
+
810
1024
  await getChannelPromise(testSuiteFinishCh, {
811
1025
  status: testSuiteResult.state,
1026
+ coverageFiles,
1027
+ coverageLibrary,
1028
+ testSuiteAbsolutePath,
812
1029
  ...testSuiteCtx.currentStore,
813
1030
  })
814
1031