dd-trace 6.4.0 → 6.5.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 (178) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +10 -7
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +20 -7
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  65. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  67. package/packages/datadog-instrumentations/src/jest.js +26 -0
  68. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  69. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  71. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  72. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  73. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  74. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  75. package/packages/datadog-instrumentations/src/next.js +24 -0
  76. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  77. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  78. package/packages/datadog-instrumentations/src/pg.js +2 -2
  79. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  80. package/packages/datadog-instrumentations/src/process.js +3 -0
  81. package/packages/datadog-instrumentations/src/router.js +18 -8
  82. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  83. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  84. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  85. package/packages/datadog-instrumentations/src/ws.js +2 -1
  86. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  87. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  88. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  89. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  90. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  91. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  92. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  93. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  94. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  95. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  96. package/packages/datadog-plugin-http2/src/client.js +1 -1
  97. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  99. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  100. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  101. package/packages/datadog-plugin-router/src/index.js +11 -2
  102. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  103. package/packages/datadog-plugin-ws/src/close.js +2 -1
  104. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  105. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  106. package/packages/datadog-plugin-ws/src/server.js +2 -1
  107. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  109. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  110. package/packages/dd-trace/src/azure_metadata.js +2 -1
  111. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  112. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  113. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  114. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  115. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  116. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  117. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  119. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  120. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  121. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  122. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  123. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  124. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  127. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  128. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  129. package/packages/dd-trace/src/config/helper.js +1 -0
  130. package/packages/dd-trace/src/config/index.js +10 -0
  131. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  132. package/packages/dd-trace/src/exporter.js +2 -0
  133. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  134. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  135. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  136. package/packages/dd-trace/src/feature-registry.js +10 -3
  137. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  138. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  139. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  140. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  141. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  142. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  143. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  144. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  145. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  146. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  147. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  148. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  149. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  150. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  151. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  152. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  153. package/packages/dd-trace/src/llmobs/util.js +32 -0
  154. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  155. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  156. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  157. package/packages/dd-trace/src/openfeature/index.js +0 -2
  158. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  159. package/packages/dd-trace/src/openfeature/register.js +16 -27
  160. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  161. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  162. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  163. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  165. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  166. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  167. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  168. package/packages/dd-trace/src/proxy.js +84 -7
  169. package/packages/dd-trace/src/span_processor.js +5 -0
  170. package/packages/dd-trace/src/standalone/index.js +0 -14
  171. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  172. package/packages/dd-trace/src/util.js +26 -0
  173. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  174. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  175. package/vendor/dist/protobufjs/index.js +1 -1
  176. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  177. package/vendor/dist/shell-quote/index.js +1 -1
  178. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -0,0 +1,657 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ const { runCommand } = require('../command-runner')
7
+ const { getDatadogCleanCommand, getLocalValidationCommand } = require('../local-command')
8
+
9
+ const {
10
+ basicEventEvidence,
11
+ error,
12
+ failWithDebugRerun,
13
+ findInterestingLines,
14
+ frameworkOutDir,
15
+ hasAllBasicEventTypes,
16
+ inconclusive,
17
+ pass,
18
+ runInstrumentedCommand,
19
+ tailInterestingLines,
20
+ } = require('./helpers')
21
+
22
+ async function runBasicReporting ({ framework, out, options }) {
23
+ const scenarioName = 'basic-reporting'
24
+ try {
25
+ const command = getBasicReportingCommand(framework)
26
+ const { result, events, offline, outDir } = await runInstrumentedCommand({
27
+ framework,
28
+ out,
29
+ scenarioName,
30
+ command,
31
+ options,
32
+ allowMissingInitialization: true,
33
+ })
34
+
35
+ const evidence = {
36
+ commandExitCode: result.exitCode,
37
+ commandTimedOut: result.timedOut,
38
+ commandDescription: command?.description,
39
+ commandOutputSummary: summarizeTestOutput(result.stdout, result.stderr),
40
+ manifestNotes: Array.isArray(framework.notes) ? framework.notes : [],
41
+ preflight: summarizePreflight(framework.preflight),
42
+ offlineExporterInitialized: offline.initialized,
43
+ settingsLoadedFromCache: offline.inputs.settings?.status === 'loaded',
44
+ offlineExporterSummary: offline.summary,
45
+ ...basicEventEvidence(events),
46
+ }
47
+
48
+ if (evidence.offlineExporterInitialized && !evidence.settingsLoadedFromCache) {
49
+ return error(
50
+ framework,
51
+ scenarioName,
52
+ new Error('Basic Reporting settings were not loaded from the authoritative offline cache fixture.'),
53
+ outDir
54
+ )
55
+ }
56
+
57
+ if (!hasAllBasicEventTypes(events)) {
58
+ if (result.exitCode !== 0) {
59
+ evidence.commandFailure = summarizeCommandFailure(result, evidence)
60
+ }
61
+ const eventLevelFailure = getMissingEventDiagnosis({ framework, result, evidence })
62
+ evidence.eventLevelFailure = eventLevelFailure
63
+
64
+ return failBasicReportingWithDebugRerun({
65
+ command,
66
+ diagnosis: eventLevelFailure.summary,
67
+ evidence,
68
+ framework,
69
+ options,
70
+ out,
71
+ outDir,
72
+ scenarioName,
73
+ skipDebug: evidence.commandFailure?.buildErrors?.length > 0 ||
74
+ !shouldRunDebugRerun(eventLevelFailure, result),
75
+ })
76
+ }
77
+
78
+ if (result.exitCode === 0) {
79
+ return pass(
80
+ framework,
81
+ scenarioName,
82
+ 'Basic reporting emitted session, module, suite, and test events.',
83
+ evidence,
84
+ outDir
85
+ )
86
+ }
87
+
88
+ if (matchesPreflightExitCode(framework.preflight, result.exitCode)) {
89
+ evidence.commandFailure = summarizeCommandFailure(result, evidence)
90
+ evidence.commandExitMatchesPreflight = true
91
+ return pass(
92
+ framework,
93
+ scenarioName,
94
+ 'Basic reporting emitted session, module, suite, and test events. ' +
95
+ `The command exited ${result.exitCode}, matching the dd-trace-less preflight run.`,
96
+ evidence,
97
+ outDir
98
+ )
99
+ }
100
+
101
+ evidence.commandFailure = summarizeCommandFailure(result, evidence)
102
+ evidence.commandExitMatchesPreflight = false
103
+ const cleanConfirmation = await runCleanConfirmation({ command, framework, options, out, scenarioName })
104
+ evidence.cleanConfirmation = cleanConfirmation.evidence
105
+
106
+ if (!cleanConfirmation.evidence.exitMatchesPreflight) {
107
+ return inconclusive(
108
+ framework,
109
+ scenarioName,
110
+ getUnstableBaselineDiagnosis(framework.preflight, result, cleanConfirmation.result),
111
+ evidence,
112
+ outDir,
113
+ cleanConfirmation.artifacts
114
+ )
115
+ }
116
+
117
+ const failure = await failBasicReportingWithDebugRerun({
118
+ command,
119
+ diagnosis: getPossibleCompatibilityDiagnosis(framework.preflight, result),
120
+ evidence,
121
+ framework,
122
+ options,
123
+ out,
124
+ outDir,
125
+ scenarioName,
126
+ })
127
+ failure.artifacts.push(...cleanConfirmation.artifacts)
128
+ return failure
129
+ } catch (err) {
130
+ return error(framework, scenarioName, err)
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Repeats the selected command without Datadog after an instrumented exit-code mismatch.
136
+ *
137
+ * @param {object} input confirmation inputs
138
+ * @param {object} input.command selected project command
139
+ * @param {object} input.framework manifest framework entry
140
+ * @param {object} input.options validator options
141
+ * @param {string} input.out validation output directory
142
+ * @param {string} input.scenarioName scenario identifier
143
+ * @returns {Promise<{artifacts: string[], evidence: object, result: object}>} clean confirmation outcome
144
+ */
145
+ async function runCleanConfirmation ({ command, framework, options, out, scenarioName }) {
146
+ const outDir = frameworkOutDir(out, framework, `${scenarioName}-clean-confirmation`)
147
+ const result = await runCommand(getDatadogCleanCommand(command), {
148
+ artifactRoot: out,
149
+ envMode: 'clean',
150
+ label: `${framework.id}:${scenarioName}:clean-confirmation`,
151
+ outDir,
152
+ repositoryRoot: options.repositoryRoot,
153
+ requireExecutableApproval: options.requireExecutableApproval,
154
+ verbose: options.verbose,
155
+ })
156
+ const exitMatchesPreflight = !result.timedOut && matchesPreflightExitCode(framework.preflight, result.exitCode)
157
+ return {
158
+ artifacts: Object.values(result.artifacts),
159
+ result,
160
+ evidence: {
161
+ ran: true,
162
+ exitCode: result.exitCode,
163
+ timedOut: result.timedOut,
164
+ exitMatchesPreflight,
165
+ commandOutputSummary: summarizeTestOutput(result.stdout, result.stderr),
166
+ },
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Describes an exit mismatch that cannot be attributed because clean executions disagreed.
172
+ *
173
+ * @param {object} preflight initial clean preflight evidence
174
+ * @param {object} instrumented instrumented command result
175
+ * @param {object} cleanConfirmation repeated clean command result
176
+ * @returns {string} customer-facing diagnosis
177
+ */
178
+ function getUnstableBaselineDiagnosis (preflight, instrumented, cleanConfirmation) {
179
+ const confirmation = cleanConfirmation.timedOut
180
+ ? 'timed out'
181
+ : `exited ${cleanConfirmation.exitCode}`
182
+ return `The selected command exited ${preflight.exitCode} during the initial clean preflight, exited ` +
183
+ `${instrumented.exitCode} with Datadog initialized, and ${confirmation} during a second clean run. The ` +
184
+ 'non-Datadog baseline was not stable, so no Test Optimization compatibility conclusion was reached.'
185
+ }
186
+
187
+ /**
188
+ * Describes an instrumented-only exit mismatch after the clean baseline was reproduced.
189
+ *
190
+ * @param {object} preflight initial clean preflight evidence
191
+ * @param {object} instrumented instrumented command result
192
+ * @returns {string} customer-facing diagnosis
193
+ */
194
+ function getPossibleCompatibilityDiagnosis (preflight, instrumented) {
195
+ return `The selected command exited ${preflight.exitCode} in both runs without Datadog, but exited ` +
196
+ `${instrumented.exitCode} with Datadog initialized after reporting Test Optimization events. This may indicate ` +
197
+ 'a dd-trace compatibility issue rather than a project baseline failure.'
198
+ }
199
+
200
+ function getBasicReportingCommand (framework) {
201
+ return getLocalValidationCommand(framework, framework.existingTestCommand)
202
+ }
203
+
204
+ async function failBasicReportingWithDebugRerun (options) {
205
+ const failure = await failWithDebugRerun(options)
206
+ return refineBasicReportingFailure(failure)
207
+ }
208
+
209
+ function refineBasicReportingFailure (failure) {
210
+ const evidence = failure.evidence || {}
211
+ if (['dd-trace-preload-failed', 'command-setup-failed'].includes(evidence.eventLevelFailure?.kind)) {
212
+ failure.status = 'error'
213
+ evidence.localDiagnosis = evidence.eventLevelFailure
214
+ return failure
215
+ }
216
+
217
+ const diagnosis = getDebugAwareDiagnosis(failure.diagnosis, evidence)
218
+ if (!diagnosis) return failure
219
+
220
+ failure.diagnosis = diagnosis.summary
221
+ evidence.localDiagnosis = diagnosis
222
+
223
+ if (evidence.eventLevelFailure) {
224
+ evidence.eventLevelFailure = {
225
+ ...evidence.eventLevelFailure,
226
+ summary: diagnosis.summary,
227
+ recommendation: diagnosis.recommendation,
228
+ signals: diagnosis.signals,
229
+ }
230
+ }
231
+
232
+ return failure
233
+ }
234
+
235
+ function getDebugAwareDiagnosis (currentDiagnosis, evidence) {
236
+ if (evidence.eventLevelFailure?.kind !== 'no-test-optimization-events') return null
237
+
238
+ const debugRerun = evidence.debugRerun
239
+ if (!debugRerun || debugRerun.ran !== true) return null
240
+
241
+ const testOutputSummary = summarizeTestOutput(
242
+ (evidence.commandOutputSummary || []).join('\n'),
243
+ (debugRerun.stdoutExcerpt || []).join('\n')
244
+ )
245
+ const testsRan = commandOutputShowsTestsRan(testOutputSummary)
246
+ const debugLine = findDebugLine(debugRerun, /dd-trace is not initialized in a package manager/i)
247
+ const noDebugEvents = !hasAnyTestOptimizationEvent(debugRerun)
248
+
249
+ if (testsRan && debugLine && noDebugEvents) {
250
+ return {
251
+ kind: 'tests-ran-tracer-not-initialized',
252
+ summary: 'The selected command ran tests, but no Test Optimization events reached the offline event artifact. ' +
253
+ `The debug rerun printed "${debugLine}", which means the preload executed in the package-manager ` +
254
+ 'wrapper without producing Test Optimization events from the test process.',
255
+ recommendation: 'Try a direct test-runner command, or verify NODE_OPTIONS with dd-trace/ci/init reaches the ' +
256
+ 'final test process rather than only the package-manager wrapper.',
257
+ signals: getDebugSignals({
258
+ debugLine,
259
+ debugRerun,
260
+ testOutputSummary,
261
+ }),
262
+ }
263
+ }
264
+
265
+ if (testsRan && noDebugEvents) {
266
+ return {
267
+ kind: 'tests-ran-no-test-optimization-events',
268
+ summary: 'The selected command ran tests, but no Test Optimization events reached the offline event artifact. ' +
269
+ 'The debug rerun did not emit Test Optimization events either.',
270
+ recommendation: 'Inspect the debug rerun excerpt for tracer initialization or offline exporter errors, then ' +
271
+ 'verify NODE_OPTIONS with dd-trace/ci/init reaches the final test process.',
272
+ signals: getDebugSignals({
273
+ debugRerun,
274
+ testOutputSummary,
275
+ }),
276
+ }
277
+ }
278
+
279
+ if (debugLine && noDebugEvents) {
280
+ return {
281
+ kind: 'tracer-not-initialized',
282
+ summary: `${currentDiagnosis} The debug rerun printed "${debugLine}".`,
283
+ recommendation: 'Verify NODE_OPTIONS with dd-trace/ci/init reaches the final test process.',
284
+ signals: getDebugSignals({
285
+ debugLine,
286
+ debugRerun,
287
+ testOutputSummary,
288
+ }),
289
+ }
290
+ }
291
+ }
292
+
293
+ function shouldRunDebugRerun (eventLevelFailure, result) {
294
+ return result.timedOut !== true &&
295
+ eventLevelFailure.kind !== 'vitest-benchmark' &&
296
+ eventLevelFailure.kind !== 'custom-jest-runner'
297
+ }
298
+
299
+ function matchesPreflightExitCode (preflight, exitCode) {
300
+ return preflight?.ran === true &&
301
+ Number.isInteger(preflight.exitCode) &&
302
+ preflight.exitCode === exitCode
303
+ }
304
+
305
+ function summarizePreflight (preflight) {
306
+ if (!preflight || preflight.ran !== true) {
307
+ return {
308
+ ran: false,
309
+ reason: 'No dd-trace-less preflight result was recorded in the manifest.',
310
+ }
311
+ }
312
+
313
+ return {
314
+ ran: true,
315
+ exitCode: preflight.exitCode,
316
+ observedTestCount: preflight.observedTestCount,
317
+ stdoutSummary: preflight.stdoutSummary,
318
+ stderrSummary: preflight.stderrSummary,
319
+ }
320
+ }
321
+
322
+ function summarizeTestOutput (stdout = '', stderr = '') {
323
+ return findInterestingLines(`${stdout}\n${stderr}`, [
324
+ /\b\d+\s+passing\b/i,
325
+ /\b\d+\s+pending\b/i,
326
+ /\b\d+\s+failing\b/i,
327
+ /\b\d+\s+passed\b/i,
328
+ /\b\d+\s+failed\b/i,
329
+ /\btests?\b.*\bpassed\b/i,
330
+ /\btests?\b.*\bfailed\b/i,
331
+ /\bSuccessfully ran target\b.*\btest\b/i,
332
+ /\bsuccess:\s*\d+\b/i,
333
+ /\bTasks:\s*\d+\s+successful\b/i,
334
+ ], 8)
335
+ }
336
+
337
+ function commandOutputShowsTestsRan (lines) {
338
+ return lines.some(line => {
339
+ return /\b\d+\s+(?:passing|passed)\b/i.test(line) ||
340
+ /\btests?\b.*\bpassed\b/i.test(line) ||
341
+ /\bSuccessfully ran target\b.*\btest\b/i.test(line) ||
342
+ /\bsuccess:\s*[1-9]\d*\b/i.test(line) ||
343
+ /\bfailed:\s*[1-9]\d*\b/i.test(line) ||
344
+ /\bTasks:\s*[1-9]\d*\s+successful\b/i.test(line)
345
+ })
346
+ }
347
+
348
+ function findDebugLine (debugRerun, pattern) {
349
+ const lines = [
350
+ ...(debugRerun.debugLines || []),
351
+ ...(debugRerun.stdoutExcerpt || []),
352
+ ...(debugRerun.stderrExcerpt || []),
353
+ ]
354
+ return lines.find(line => pattern.test(line))
355
+ }
356
+
357
+ function getDebugSignals ({ debugLine, debugRerun, testOutputSummary }) {
358
+ return {
359
+ debugLine,
360
+ debugLines: debugRerun.debugLines || [],
361
+ stdoutExcerpt: debugRerun.stdoutExcerpt || [],
362
+ stderrExcerpt: debugRerun.stderrExcerpt || [],
363
+ testOutputSummary,
364
+ }
365
+ }
366
+
367
+ function summarizeCommandFailure (result, evidence) {
368
+ const output = `${result.stdout}\n${result.stderr}`
369
+ const buildErrors = findInterestingLines(output, [
370
+ /Could not resolve /,
371
+ /Cannot find module/,
372
+ /Module not found/,
373
+ /Error \[ERR_MODULE_NOT_FOUND\]/,
374
+ ])
375
+ const assertionFailures = findInterestingLines(output, [
376
+ /AssertionError/,
377
+ /Timed out retrying/,
378
+ /^\s+\d+\) /,
379
+ ])
380
+ const testEventsWereReported = evidence.testSessionEvents > 0 &&
381
+ evidence.testModuleEvents > 0 &&
382
+ evidence.testSuiteEvents > 0 &&
383
+ evidence.testEvents > 0
384
+ const summary = getFailureSummary({
385
+ buildErrors,
386
+ assertionFailures,
387
+ exitCode: result.exitCode,
388
+ testEventsWereReported,
389
+ timedOut: result.timedOut,
390
+ })
391
+
392
+ return {
393
+ assertionFailures,
394
+ buildErrors,
395
+ stderrExcerpt: tailInterestingLines(result.stderr),
396
+ stdoutExcerpt: tailInterestingLines(result.stdout),
397
+ summary,
398
+ testEventsWereReported,
399
+ }
400
+ }
401
+
402
+ function getMissingEventDiagnosis ({ framework, result, evidence }) {
403
+ const missingLevels = getMissingLevels(evidence)
404
+ const commandFailure = evidence.commandFailure
405
+ const vitestBenchmark = detectVitestBenchmark(framework, result)
406
+ const frameworkSourceTreeRunner = detectFrameworkSourceTreeRunner(framework, result)
407
+ const customJestRunner = detectCustomJestRunner(framework)
408
+
409
+ if (commandFailure?.buildErrors?.length > 0 && !hasAnyTestOptimizationEvent(evidence)) {
410
+ const preloadFailed = /(?:ci\/init\.js|internal\/preload)/.test(`${result.stdout}\n${result.stderr}`)
411
+ return {
412
+ kind: preloadFailed ? 'dd-trace-preload-failed' : 'command-setup-failed',
413
+ missingLevels,
414
+ signals: commandFailure.buildErrors,
415
+ summary: preloadFailed
416
+ ? `The Test Optimization preload failed before tests started: ${commandFailure.buildErrors[0]}. ` +
417
+ 'No Test Optimization conclusion was reached.'
418
+ : `${commandFailure.summary} No Test Optimization conclusion was reached.`,
419
+ recommendation: preloadFailed
420
+ ? 'Repair or reinstall dd-trace so its declared dependencies resolve, then rerun validation.'
421
+ : 'Fix the selected project command or its setup, then rerun validation.',
422
+ }
423
+ }
424
+
425
+ if (vitestBenchmark) {
426
+ return {
427
+ kind: 'vitest-benchmark',
428
+ missingLevels,
429
+ signals: vitestBenchmark.signals,
430
+ summary: 'The selected Vitest command appears to run benchmark mode, not normal tests. ' +
431
+ 'Test Optimization reported session/module/suite events, but Vitest benchmark mode did not emit ' +
432
+ 'per-test events. Choose a normal Vitest test command such as "vitest run <test-file>" for validation.',
433
+ recommendation: 'Replace the selected command with a normal Vitest test command; do not use `vitest bench` ' +
434
+ 'or benchmark-only `*.bench.*` files for Test Optimization validation.',
435
+ }
436
+ }
437
+
438
+ if (frameworkSourceTreeRunner && !hasAnyTestOptimizationEvent(evidence)) {
439
+ return {
440
+ kind: 'framework-source-tree-runner',
441
+ missingLevels,
442
+ signals: frameworkSourceTreeRunner.signals,
443
+ summary: 'The selected command ran tests from the test framework source tree, but no Test Optimization ' +
444
+ 'events reached the offline event artifact. This command is not equivalent to a customer project running an ' +
445
+ 'installed test-runner package.',
446
+ recommendation: 'Choose a project test command that uses an installed supported framework package. If this ' +
447
+ 'repository is the framework itself, treat this entry as not runnable for Test Optimization validation.',
448
+ }
449
+ }
450
+
451
+ if (!hasAnyTestOptimizationEvent(evidence)) {
452
+ return {
453
+ kind: 'no-test-optimization-events',
454
+ missingLevels,
455
+ summary: 'No Test Optimization test events reached the offline event artifact. The tracer may not have ' +
456
+ 'initialized in the test process, or the selected command may not have executed tests.',
457
+ recommendation: 'Check the debug rerun output for tracer initialization or offline exporter errors.',
458
+ }
459
+ }
460
+
461
+ if (evidence.testEvents === 0) {
462
+ if (customJestRunner) {
463
+ return {
464
+ kind: 'custom-jest-runner',
465
+ missingLevels,
466
+ customTestRunner: customJestRunner,
467
+ signals: customJestRunner.signals,
468
+ summary: `We detected a custom Jest-compatible runner: \`${customJestRunner.name}\`. The test command ` +
469
+ 'executes tests, but it does not use a currently supported Jest entrypoint. Test Optimization ' +
470
+ 'initialized, but this runner did not emit the Jest lifecycle events needed to report individual ' +
471
+ 'suites and tests.',
472
+ recommendation: 'Try a standard Jest runner command for validation, or choose a test command that does not ' +
473
+ 'use the custom runner. If this project must use the custom runner, dd-trace may need explicit support ' +
474
+ 'for that runner before per-test reporting and advanced Test Optimization features can work.',
475
+ }
476
+ }
477
+
478
+ return {
479
+ kind: 'missing-test-events',
480
+ missingLevels,
481
+ summary: 'Test Optimization initialized and emitted higher-level events, but per-test events were missing. ' +
482
+ 'This usually points to an unsupported runner mode, unsupported framework configuration, or per-test hooks ' +
483
+ 'not firing for the selected command.',
484
+ recommendation: 'Choose a smaller standard test command, then inspect the debug rerun output for hook or ' +
485
+ 'exporter errors.',
486
+ }
487
+ }
488
+
489
+ return {
490
+ kind: 'missing-event-levels',
491
+ missingLevels,
492
+ summary: `The command ran, but these required Test Optimization event levels were missing: ${
493
+ missingLevels.join(', ')
494
+ }.`,
495
+ recommendation: 'Inspect the debug rerun output for tracer initialization, hook, or exporter errors.',
496
+ }
497
+ }
498
+
499
+ function getMissingLevels (evidence) {
500
+ const missing = []
501
+ if (evidence.testSessionEvents === 0) missing.push('test_session_end')
502
+ if (evidence.testModuleEvents === 0) missing.push('test_module_end')
503
+ if (evidence.testSuiteEvents === 0) missing.push('test_suite_end')
504
+ if (evidence.testEvents === 0) missing.push('test')
505
+ return missing
506
+ }
507
+
508
+ function hasAnyTestOptimizationEvent (evidence) {
509
+ return evidence.testSessionEvents > 0 ||
510
+ evidence.testModuleEvents > 0 ||
511
+ evidence.testSuiteEvents > 0 ||
512
+ evidence.testEvents > 0
513
+ }
514
+
515
+ function detectVitestBenchmark (framework, result) {
516
+ if (framework.framework !== 'vitest') return null
517
+
518
+ const command = result.command || ''
519
+ const output = `${result.stdout}\n${result.stderr}`
520
+ const signals = []
521
+
522
+ if (/\bvitest\s+bench\b/.test(command)) signals.push('command contains `vitest bench`')
523
+ if (/\.bench\.[cm]?[jt]sx?\b/.test(command)) signals.push('command targets a `*.bench.*` file')
524
+ if (/^\s*BENCH\s+Summary\b/m.test(output)) signals.push('stdout contains a Vitest BENCH summary')
525
+ if (/Benchmarking is an experimental feature/.test(output)) {
526
+ signals.push('stderr says Vitest benchmarking is experimental')
527
+ }
528
+
529
+ return signals.length > 0 ? { signals } : null
530
+ }
531
+
532
+ function detectCustomJestRunner (framework) {
533
+ if (framework.framework !== 'jest') return null
534
+
535
+ const configRunner = findJestRunnerInConfigFiles(framework.project?.configFiles || [])
536
+ if (configRunner && isCustomJestRunner(configRunner.name)) return configRunner
537
+
538
+ const packageRunner = findJestRunnerInPackageJson(framework.project?.packageJson)
539
+ if (packageRunner && isCustomJestRunner(packageRunner.name)) return packageRunner
540
+ }
541
+
542
+ function findJestRunnerInConfigFiles (configFiles) {
543
+ for (const configFile of configFiles) {
544
+ const content = readFile(configFile)
545
+ if (!content) continue
546
+
547
+ const match = /(?:^|[,{]\s*)runner\s*:\s*['"]([^'"]+)['"]/.exec(content)
548
+ if (!match) continue
549
+
550
+ return {
551
+ name: match[1],
552
+ source: configFile,
553
+ sourceType: 'config',
554
+ signals: [
555
+ `Jest config ${configFile} sets runner: ${match[1]}`,
556
+ ],
557
+ }
558
+ }
559
+ }
560
+
561
+ function findJestRunnerInPackageJson (packageJsonPath) {
562
+ if (!packageJsonPath) return
563
+
564
+ try {
565
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
566
+ const runner = packageJson.jest?.runner
567
+ if (typeof runner !== 'string' || runner.length === 0) return
568
+
569
+ return {
570
+ name: runner,
571
+ source: packageJsonPath,
572
+ sourceType: 'package.json',
573
+ signals: [
574
+ `package.json jest.runner is ${runner}`,
575
+ ],
576
+ }
577
+ } catch {}
578
+ }
579
+
580
+ function isCustomJestRunner (runner) {
581
+ return runner !== 'jest-runner'
582
+ }
583
+
584
+ function detectFrameworkSourceTreeRunner (framework, result) {
585
+ if (framework.framework !== 'mocha') return null
586
+
587
+ const projectName = framework.project?.name || ''
588
+ const commandAndOutput = [
589
+ result.command || '',
590
+ result.stdout || '',
591
+ result.stderr || '',
592
+ ].join('\n')
593
+ const signals = []
594
+
595
+ if (projectName === 'mocha') {
596
+ signals.push('repository package name is `mocha`')
597
+ }
598
+ if (/\bnode\s+\.\/bin\/mocha\.js\b/.test(commandAndOutput) ||
599
+ /\bnode\s+bin\/mocha\.js\b/.test(commandAndOutput)) {
600
+ signals.push('selected command invokes the repository-local `bin/mocha.js` source-tree runner')
601
+ }
602
+ if (fileExists(framework.project?.root, 'lib/mocha.cjs') && fileExists(framework.project?.root, 'lib/runner.cjs')) {
603
+ signals.push('repository contains Mocha source files `lib/mocha.cjs` and `lib/runner.cjs`')
604
+ }
605
+
606
+ return signals.length >= 2 ? { signals } : null
607
+ }
608
+
609
+ function fileExists (root, filename) {
610
+ if (!root) return false
611
+
612
+ try {
613
+ return fs.existsSync(path.join(root, filename))
614
+ } catch {
615
+ return false
616
+ }
617
+ }
618
+
619
+ function readFile (filename) {
620
+ try {
621
+ return fs.readFileSync(filename, 'utf8')
622
+ } catch {}
623
+ }
624
+
625
+ function getFailureSummary ({ buildErrors, assertionFailures, exitCode, testEventsWereReported, timedOut }) {
626
+ if (timedOut) {
627
+ return 'The selected test command timed out before payload validation could pass.'
628
+ }
629
+
630
+ if (buildErrors.length > 0) {
631
+ if (testEventsWereReported) {
632
+ return 'The selected test command reported Datadog test events, but project setup/build errors made it fail.'
633
+ }
634
+
635
+ return 'The selected test command failed during project setup/build before payload validation could pass.'
636
+ }
637
+
638
+ if (assertionFailures.length > 0 && testEventsWereReported) {
639
+ return 'The selected test command reported Datadog test events, but the tests failed.'
640
+ }
641
+
642
+ if (testEventsWereReported) {
643
+ return `The selected test command reported Datadog test events, but exited ${exitCode}.`
644
+ }
645
+
646
+ return `The selected test command exited ${exitCode} before payload validation could pass.`
647
+ }
648
+
649
+ module.exports = {
650
+ getDebugAwareDiagnosis,
651
+ getBasicReportingCommand,
652
+ getMissingEventDiagnosis,
653
+ refineBasicReportingFailure,
654
+ runBasicReporting,
655
+ shouldRunDebugRerun,
656
+ summarizeTestOutput,
657
+ }