dd-trace 6.3.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 (207) 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/index.electron.js +3 -0
  52. package/init.js +18 -0
  53. package/initialize.mjs +1 -1
  54. package/loader-hook.mjs +28 -18
  55. package/openfeature.d.ts +1 -0
  56. package/openfeature.js +4 -0
  57. package/package.json +15 -8
  58. package/packages/datadog-instrumentations/src/ai.js +37 -26
  59. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  60. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  61. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  62. package/packages/datadog-instrumentations/src/cucumber.js +24 -10
  63. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  64. package/packages/datadog-instrumentations/src/express.js +20 -2
  65. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  66. package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  68. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  69. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  70. package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
  71. package/packages/datadog-instrumentations/src/http2/server.js +143 -17
  72. package/packages/datadog-instrumentations/src/jest.js +36 -4
  73. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  74. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  75. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  76. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  77. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  78. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  79. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  80. package/packages/datadog-instrumentations/src/next.js +24 -0
  81. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  82. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  83. package/packages/datadog-instrumentations/src/pg.js +2 -2
  84. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  85. package/packages/datadog-instrumentations/src/process.js +3 -0
  86. package/packages/datadog-instrumentations/src/router.js +191 -106
  87. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  88. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  89. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  90. package/packages/datadog-instrumentations/src/ws.js +2 -1
  91. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  92. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  93. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
  94. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
  95. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
  96. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -24
  97. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  98. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +24 -62
  99. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  100. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  101. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  102. package/packages/datadog-plugin-graphql/src/execute.js +177 -59
  103. package/packages/datadog-plugin-graphql/src/parse.js +22 -1
  104. package/packages/datadog-plugin-graphql/src/request.js +32 -1
  105. package/packages/datadog-plugin-graphql/src/utils.js +42 -0
  106. package/packages/datadog-plugin-graphql/src/validate.js +15 -3
  107. package/packages/datadog-plugin-http2/src/client.js +1 -1
  108. package/packages/datadog-plugin-http2/src/server.js +38 -6
  109. package/packages/datadog-plugin-jest/src/util.js +21 -6
  110. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  111. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  112. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  113. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  114. package/packages/datadog-plugin-router/src/index.js +11 -2
  115. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  116. package/packages/datadog-plugin-vitest/src/index.js +4 -2
  117. package/packages/datadog-plugin-ws/src/close.js +2 -1
  118. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  119. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  120. package/packages/datadog-plugin-ws/src/server.js +2 -1
  121. package/packages/dd-trace/index.electron.js +3 -0
  122. package/packages/dd-trace/index.js +2 -37
  123. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  125. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  126. package/packages/dd-trace/src/azure_metadata.js +2 -1
  127. package/packages/dd-trace/src/bootstrap.js +39 -0
  128. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  129. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  131. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  132. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  135. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -5
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
  137. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
  138. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +62 -7
  139. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -2
  140. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  141. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  142. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  143. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  144. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  145. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +173 -9
  146. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  147. package/packages/dd-trace/src/config/generated-config-types.d.ts +20 -0
  148. package/packages/dd-trace/src/config/helper.js +1 -0
  149. package/packages/dd-trace/src/config/index.js +10 -0
  150. package/packages/dd-trace/src/config/supported-configurations.json +83 -0
  151. package/packages/dd-trace/src/constants.js +7 -0
  152. package/packages/dd-trace/src/datastreams/pathway.js +6 -4
  153. package/packages/dd-trace/src/exporter.js +2 -0
  154. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  155. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  156. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  157. package/packages/dd-trace/src/feature-registry.js +29 -0
  158. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  159. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  160. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  161. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  162. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  163. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  164. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  165. package/packages/dd-trace/src/llmobs/index.js +10 -2
  166. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  167. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  168. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  169. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  170. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  171. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  172. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  173. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  174. package/packages/dd-trace/src/llmobs/tagger.js +79 -7
  175. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  176. package/packages/dd-trace/src/llmobs/util.js +32 -0
  177. package/packages/dd-trace/src/noop/proxy.js +5 -4
  178. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  179. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  180. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  181. package/packages/dd-trace/src/openfeature/index.js +0 -2
  182. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  183. package/packages/dd-trace/src/openfeature/register.js +35 -0
  184. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  185. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  186. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  187. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  188. package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
  189. package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
  190. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
  191. package/packages/dd-trace/src/opentracing/tracer.js +7 -1
  192. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  193. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  194. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  195. package/packages/dd-trace/src/plugins/util/test.js +65 -8
  196. package/packages/dd-trace/src/plugins/util/web.js +12 -0
  197. package/packages/dd-trace/src/proxy.js +99 -10
  198. package/packages/dd-trace/src/span_processor.js +5 -0
  199. package/packages/dd-trace/src/standalone/index.js +0 -36
  200. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  201. package/packages/dd-trace/src/util.js +26 -0
  202. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  203. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  204. package/vendor/dist/protobufjs/index.js +1 -1
  205. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  206. package/vendor/dist/shell-quote/index.js +1 -1
  207. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -0,0 +1,141 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ discoverScenarioTests,
5
+ discoveryEvidence,
6
+ error,
7
+ failWithDebugRerun,
8
+ pass,
9
+ prepareGeneratedScenario,
10
+ requireGeneratedScenario,
11
+ runInstrumentedCommand,
12
+ skip,
13
+ testEventSamples,
14
+ testsForDiscoveredScenario,
15
+ } = require('./helpers')
16
+
17
+ async function runEarlyFlakeDetection ({ framework, out, options }) {
18
+ const scenarioName = 'efd'
19
+ const skipResult = requireGeneratedScenario(framework, 'basic-pass', scenarioName)
20
+ if (skipResult) return skipResult
21
+
22
+ let outDir
23
+ try {
24
+ const { scenario } = await prepareGeneratedScenario(framework, 'basic-pass')
25
+ if (!scenario) {
26
+ return skip(framework, scenarioName, 'Generated scenario "basic-pass" is not present in the manifest.')
27
+ }
28
+
29
+ const discovery = await discoverScenarioTests({ framework, out, scenarioName, scenario, options })
30
+ if (discovery.tests.length === 0) {
31
+ return failWithDebugRerun({
32
+ command: scenario.runCommand,
33
+ diagnosis: 'The generated new-test candidate was not reported during baseline identity discovery.',
34
+ evidence: discoveryEvidence(discovery),
35
+ framework,
36
+ options,
37
+ out,
38
+ outDir: discovery.outDir,
39
+ scenarioName,
40
+ })
41
+ }
42
+
43
+ const fixtureConfig = {
44
+ settings: {
45
+ early_flake_detection: {
46
+ enabled: true,
47
+ slow_test_retries: {
48
+ '5s': 3,
49
+ },
50
+ faulty_session_threshold: 100,
51
+ },
52
+ known_tests_enabled: true,
53
+ },
54
+ knownTests: {
55
+ [framework.framework]: {},
56
+ },
57
+ }
58
+
59
+ const run = await runInstrumentedCommand({
60
+ framework,
61
+ out,
62
+ scenarioName,
63
+ command: scenario.runCommand,
64
+ options,
65
+ fixtureConfig,
66
+ })
67
+ outDir = run.outDir
68
+
69
+ const tests = testsForDiscoveredScenario(run.events, scenario, discovery)
70
+ const earlyFlakeRetryEvents = tests.filter(test => test.retryReason === 'early_flake_detection')
71
+ const externalRetryEvents = tests.filter(test => test.isRetry && test.retryReason !== 'early_flake_detection')
72
+ const evidence = {
73
+ ...discoveryEvidence(discovery),
74
+ commandExitCode: run.result.exitCode,
75
+ commandTimedOut: run.result.timedOut,
76
+ settingsLoadedFromCache: run.offline.inputs.settings?.status === 'loaded',
77
+ knownTestsLoadedFromCache: run.offline.inputs.known_tests?.status === 'loaded',
78
+ matchingTestEvents: tests.length,
79
+ earlyFlakeRetryEvents: earlyFlakeRetryEvents.length,
80
+ externalRetryEvents: externalRetryEvents.length,
81
+ earlyFlakeTaggedEvents: tests.filter(test => test.earlyFlakeEnabled).length,
82
+ samples: testEventSamples(tests),
83
+ }
84
+
85
+ if (run.result.exitCode !== 0) {
86
+ return failWithDebugRerun({
87
+ command: scenario.runCommand,
88
+ fixtureConfig,
89
+ diagnosis: 'The generated new-test command reported Early Flake Detection retry evidence, but the ' +
90
+ `command exited ${run.result.exitCode}. Early Flake Detection is only valid when the command ` +
91
+ 'completes successfully after retries.',
92
+ evidence,
93
+ framework,
94
+ options,
95
+ out,
96
+ outDir,
97
+ scenarioName,
98
+ })
99
+ }
100
+
101
+ if (!evidence.settingsLoadedFromCache || !evidence.knownTestsLoadedFromCache) {
102
+ return failWithDebugRerun({
103
+ command: scenario.runCommand,
104
+ fixtureConfig,
105
+ diagnosis: 'EFD settings or known-tests data were not loaded from the offline cache fixture.',
106
+ evidence,
107
+ framework,
108
+ options,
109
+ out,
110
+ outDir,
111
+ scenarioName,
112
+ })
113
+ }
114
+
115
+ if (tests.length < 2 || earlyFlakeRetryEvents.length === 0) {
116
+ return failWithDebugRerun({
117
+ command: scenario.runCommand,
118
+ fixtureConfig,
119
+ diagnosis: 'The generated new test did not appear to be retried for Early Flake Detection.',
120
+ evidence,
121
+ framework,
122
+ options,
123
+ out,
124
+ outDir,
125
+ scenarioName,
126
+ })
127
+ }
128
+
129
+ return pass(
130
+ framework,
131
+ scenarioName,
132
+ 'The generated new test was reported with retry evidence for Early Flake Detection.',
133
+ evidence,
134
+ outDir
135
+ )
136
+ } catch (err) {
137
+ return error(framework, scenarioName, err, outDir)
138
+ }
139
+ }
140
+
141
+ module.exports = { runEarlyFlakeDetection }
@@ -0,0 +1,539 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('path')
5
+
6
+ const { getArtifactId } = require('../artifact-id')
7
+ const { buildCiWiringEnv, buildDatadogEnv, runCommand } = require('../command-runner')
8
+ const {
9
+ cleanupGeneratedRuntimeFiles,
10
+ findGeneratedScenario,
11
+ writeGeneratedFiles,
12
+ } = require('../generated-files')
13
+ const {
14
+ eventsOfType,
15
+ findTestsByIdentity,
16
+ } = require('../payload-normalizer')
17
+ const { getLocalValidationCommand } = require('../local-command')
18
+ const { cleanupOfflineFixture, createOfflineFixture } = require('../offline-fixtures')
19
+ const { readOfflineOutput } = require('../offline-output')
20
+ const { sanitizeForReport, sanitizeString } = require('../redaction')
21
+ const { ensureSafeDirectory, writeFileSafely } = require('../safe-files')
22
+
23
+ const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*[ -/]*[@-~]`}`, 'g')
24
+
25
+ function frameworkOutDir (out, framework, scenario) {
26
+ return path.join(out, 'runs', getArtifactId(framework.id), scenario)
27
+ }
28
+
29
+ async function runInstrumentedCommand ({
30
+ framework,
31
+ out,
32
+ scenarioName,
33
+ command,
34
+ options,
35
+ extraEnv,
36
+ fixtureConfig,
37
+ ciWiring = false,
38
+ allowMissingInitialization = false,
39
+ }) {
40
+ const outDir = frameworkOutDir(out, framework, scenarioName)
41
+ const rawOutputRoot = path.join(outDir, '.offline-payloads')
42
+ ensureSafeDirectory(out, rawOutputRoot, 'offline validation payload output')
43
+ let fixture
44
+ let result
45
+ let offline
46
+ try {
47
+ fixture = createOfflineFixture({
48
+ approvedPlanSha256: options.approvedPlanSha256,
49
+ offlineFixtureNonce: options.offlineFixtureNonce,
50
+ framework,
51
+ repositoryRoot: options.repositoryRoot,
52
+ scenarioName,
53
+ ...fixtureConfig,
54
+ })
55
+ const validationEnv = ciWiring
56
+ ? buildCiWiringEnv({ fixture, outputRoot: rawOutputRoot })
57
+ : buildDatadogEnv({ fixture, outputRoot: rawOutputRoot, scenario: scenarioName, framework })
58
+ result = await runCommand(command, {
59
+ env: {
60
+ ...validationEnv,
61
+ ...extraEnv,
62
+ },
63
+ artifactRoot: out,
64
+ envMode: 'clean',
65
+ outDir,
66
+ label: `${framework.id}:${scenarioName}`,
67
+ repositoryRoot: options.repositoryRoot,
68
+ requireExecutableApproval: options.requireExecutableApproval,
69
+ verbose: options.verbose,
70
+ })
71
+ offline = readOfflineOutput(rawOutputRoot)
72
+ const sanitizedEvents = sanitizeForReport(offline.events)
73
+ writeFileSafely(
74
+ out,
75
+ path.join(outDir, 'events.ndjson'),
76
+ sanitizedEvents.map(event => JSON.stringify(event)).join('\n') + '\n',
77
+ 'scenario events artifact'
78
+ )
79
+ writeFileSafely(
80
+ out,
81
+ path.join(outDir, 'result.json'),
82
+ `${JSON.stringify(sanitizeForReport(result), null, 2)}\n`,
83
+ 'scenario result artifact'
84
+ )
85
+ if (!offline.initialized && !ciWiring && !allowMissingInitialization) {
86
+ const stderr = sanitizeString(result.stderr).trim().slice(-2000)
87
+ throw new Error(
88
+ 'Offline Test Optimization exporter did not initialize or write completion evidence. ' +
89
+ `The test command exited ${result.exitCode}${result.signal ? ` with signal ${result.signal}` : ''}` +
90
+ `${result.timedOut ? ' after timing out' : ''}.${stderr ? ` Sanitized stderr tail: ${stderr}` : ''}`
91
+ )
92
+ }
93
+ if (offline.summary?.errors.length > 0) {
94
+ throw new Error(`Offline Test Optimization exporter failed: ${offline.summary.errors.join(', ')}`)
95
+ }
96
+ } catch (err) {
97
+ err.artifactDirectory = outDir
98
+ throw err
99
+ } finally {
100
+ if (fixture) cleanupOfflineFixture(fixture.root)
101
+ fs.rmSync(rawOutputRoot, { force: true, recursive: true })
102
+ }
103
+
104
+ return { result, events: offline.events, offline, outDir }
105
+ }
106
+
107
+ async function failWithDebugRerun ({
108
+ command,
109
+ fixtureConfig,
110
+ diagnosis,
111
+ evidence,
112
+ framework,
113
+ options,
114
+ out,
115
+ outDir,
116
+ scenarioName,
117
+ skipDebug,
118
+ }) {
119
+ if (!skipDebug && command) {
120
+ const debugRerun = await runDebugInstrumentedCommand({
121
+ command,
122
+ fixtureConfig,
123
+ framework,
124
+ options,
125
+ out,
126
+ scenarioName,
127
+ })
128
+ evidence.debugRerun = debugRerun.summary
129
+
130
+ const failure = fail(framework, scenarioName, diagnosis, evidence, outDir)
131
+ if (debugRerun.artifacts) {
132
+ failure.artifacts.push(...debugRerun.artifacts)
133
+ }
134
+ return failure
135
+ }
136
+
137
+ return fail(framework, scenarioName, diagnosis, evidence, outDir)
138
+ }
139
+
140
+ async function runDebugInstrumentedCommand ({
141
+ command,
142
+ fixtureConfig,
143
+ framework,
144
+ options,
145
+ out,
146
+ scenarioName,
147
+ }) {
148
+ try {
149
+ cleanupGeneratedRuntimeFiles(framework)
150
+
151
+ const debug = await runInstrumentedCommand({
152
+ framework,
153
+ out,
154
+ scenarioName: `${scenarioName}-debug`,
155
+ command,
156
+ options,
157
+ fixtureConfig,
158
+ extraEnv: {
159
+ DD_TRACE_DEBUG: '1',
160
+ DD_TRACE_LOG_LEVEL: 'debug',
161
+ },
162
+ allowMissingInitialization: true,
163
+ })
164
+
165
+ return {
166
+ summary: summarizeDebugRerun(debug),
167
+ artifacts: getDebugArtifacts(debug.outDir),
168
+ }
169
+ } catch (err) {
170
+ return {
171
+ summary: {
172
+ ran: false,
173
+ error: err && err.message ? err.message : String(err),
174
+ },
175
+ }
176
+ }
177
+ }
178
+
179
+ async function prepareGeneratedScenario (framework, scenarioId) {
180
+ const scenario = findGeneratedScenario(framework, scenarioId)
181
+ if (!scenario) return { scenario: null, written: [] }
182
+ cleanupGeneratedRuntimeFiles(framework)
183
+ const written = await writeGeneratedFiles(framework)
184
+ return {
185
+ scenario: {
186
+ ...scenario,
187
+ runCommand: getLocalValidationCommand(framework, scenario.runCommand),
188
+ },
189
+ written,
190
+ }
191
+ }
192
+
193
+ function requireGeneratedScenario (framework, scenarioId, scenarioName) {
194
+ const strategy = framework.generatedTestStrategy
195
+ if (strategy?.status === 'not_possible') {
196
+ return skip(
197
+ framework,
198
+ scenarioName,
199
+ `Skipped because this advanced feature is not eligible: ${strategy.reason}`,
200
+ getGeneratedStrategySkipEvidence(framework, scenarioName, scenarioId)
201
+ )
202
+ }
203
+
204
+ if (!strategy || strategy.status !== 'verified') {
205
+ return incomplete(
206
+ framework,
207
+ scenarioName,
208
+ 'The validation manifest is incomplete because no verified generated test strategy is available. ' +
209
+ 'No conclusion was reached for this advanced feature.',
210
+ getGeneratedStrategySkipEvidence(framework, scenarioName, scenarioId)
211
+ )
212
+ }
213
+
214
+ const scenario = findGeneratedScenario(framework, scenarioId)
215
+ if (!scenario) {
216
+ return incomplete(
217
+ framework,
218
+ scenarioName,
219
+ `The validation manifest is incomplete because generated scenario "${scenarioId}" is missing. ` +
220
+ 'No conclusion was reached for this advanced feature.',
221
+ {
222
+ featureEligibility: {
223
+ eligible: false,
224
+ blockedBy: 'generated-scenario',
225
+ reasonCode: 'generated-scenario-missing',
226
+ scenario: scenarioName,
227
+ requiredGeneratedScenario: scenarioId,
228
+ },
229
+ }
230
+ )
231
+ }
232
+
233
+ return null
234
+ }
235
+
236
+ /**
237
+ * Builds stable evidence for advanced feature checks that cannot run without generated tests.
238
+ *
239
+ * @param {object} framework manifest framework entry
240
+ * @param {string} scenarioName advanced scenario name
241
+ * @param {string} scenarioId required generated scenario id
242
+ * @returns {object} skip evidence for reports and UI payloads
243
+ */
244
+ function getGeneratedStrategySkipEvidence (framework, scenarioName, scenarioId) {
245
+ const strategy = framework.generatedTestStrategy
246
+ const status = strategy?.status || 'missing'
247
+ let reasonCode = 'generated-test-strategy-missing'
248
+
249
+ if (status === 'proposed') {
250
+ reasonCode = 'generated-test-strategy-proposed-only'
251
+ } else if (status === 'not_possible') {
252
+ reasonCode = 'generated-test-strategy-not-possible'
253
+ } else if (status !== 'missing') {
254
+ reasonCode = 'generated-test-strategy-not-verified'
255
+ }
256
+
257
+ return {
258
+ featureEligibility: {
259
+ eligible: false,
260
+ blockedBy: 'generated-test-strategy',
261
+ reason: strategy?.reason,
262
+ reasonCode,
263
+ scenario: scenarioName,
264
+ strategyStatus: status,
265
+ requiredGeneratedScenario: scenarioId,
266
+ },
267
+ }
268
+ }
269
+
270
+ function basicEventEvidence (events) {
271
+ return {
272
+ testSessionEvents: eventsOfType(events, 'test_session_end').length,
273
+ testModuleEvents: eventsOfType(events, 'test_module_end').length,
274
+ testSuiteEvents: eventsOfType(events, 'test_suite_end').length,
275
+ testEvents: eventsOfType(events, 'test').length,
276
+ samples: basicEventSamples(events),
277
+ }
278
+ }
279
+
280
+ function hasAllBasicEventTypes (events) {
281
+ const evidence = basicEventEvidence(events)
282
+ return evidence.testSessionEvents > 0 &&
283
+ evidence.testModuleEvents > 0 &&
284
+ evidence.testSuiteEvents > 0 &&
285
+ evidence.testEvents > 0
286
+ }
287
+
288
+ function testsForScenario (events, scenario) {
289
+ return findTestsByIdentity(events, scenario.testIdentities || [])
290
+ }
291
+
292
+ async function discoverScenarioTests ({ framework, out, scenarioName, scenario, options }) {
293
+ const baseline = await runInstrumentedCommand({
294
+ framework,
295
+ out,
296
+ scenarioName: `${scenarioName}-baseline`,
297
+ command: scenario.runCommand,
298
+ options,
299
+ })
300
+ let tests = testsForScenario(baseline.events, scenario)
301
+ let identityMatch = 'manifest'
302
+ if (tests.length === 0) {
303
+ const nameAndFileIdentities = (scenario.testIdentities || [])
304
+ .filter(identity => identity.name && identity.file)
305
+ tests = findTestsByIdentity(baseline.events, nameAndFileIdentities, { ignoreSuite: true })
306
+ if (tests.length > 0) identityMatch = 'name-and-file-fallback'
307
+ }
308
+ cleanupGeneratedRuntimeFiles(framework)
309
+ return {
310
+ ...baseline,
311
+ identityMatch,
312
+ tests,
313
+ testIdentities: tests.map(testToIdentity),
314
+ }
315
+ }
316
+
317
+ function testsForDiscoveredScenario (events, scenario, discovery) {
318
+ if (discovery?.testIdentities?.length > 0) {
319
+ return findTestsByIdentity(events, discovery.testIdentities)
320
+ }
321
+ return testsForScenario(events, scenario)
322
+ }
323
+
324
+ function discoveryEvidence (discovery) {
325
+ return {
326
+ baselineCommandExitCode: discovery.result.exitCode,
327
+ baselineIdentityMatch: discovery.identityMatch,
328
+ baselineMatchingTestEvents: discovery.tests.length,
329
+ baselineSamples: testEventSamples(discovery.tests),
330
+ }
331
+ }
332
+
333
+ function testToIdentity (test) {
334
+ return {
335
+ discovered: true,
336
+ suite: test.testSuite,
337
+ name: test.testName,
338
+ file: test.testSourceFile,
339
+ }
340
+ }
341
+
342
+ function basicEventSamples (events) {
343
+ return [
344
+ sampleLevel(events, 'test_session_end', 'test session'),
345
+ sampleLevel(events, 'test_module_end', 'test module'),
346
+ sampleLevel(events, 'test_suite_end', 'test suite'),
347
+ sampleLevel(events, 'test', 'test'),
348
+ ].filter(Boolean)
349
+ }
350
+
351
+ function sampleLevel (events, type, level) {
352
+ const event = eventsOfType(events, type)[0]
353
+ if (!event) return null
354
+
355
+ const sample = { level }
356
+ copy(sample, event.meta, 'test.command')
357
+ copy(sample, event.meta, 'test.module')
358
+ copy(sample, event.meta, 'test.suite')
359
+ copy(sample, event.meta, 'test.name')
360
+ copy(sample, event.meta, 'test.status')
361
+ return sample
362
+ }
363
+
364
+ function testEventSamples (tests) {
365
+ return tests.slice(0, 3).map(test => {
366
+ const sample = {}
367
+ copy(sample, test.meta, 'test.name')
368
+ copy(sample, test.meta, 'test.status')
369
+ copy(sample, test.meta, 'test.is_new')
370
+ copy(sample, test.meta, 'test.is_retry')
371
+ copy(sample, test.meta, 'test.retry_reason')
372
+ copy(sample, test.meta, 'test.final_status')
373
+ copy(sample, test.meta, 'test.test_management.enabled')
374
+ copy(sample, test.meta, 'test.test_management.is_test_disabled')
375
+ copy(sample, test.meta, 'test.test_management.is_quarantined')
376
+ copy(sample, test.meta, 'test.test_management.is_attempt_to_fix')
377
+ copy(sample, test.meta, 'test.test_management.attempt_to_fix_passed')
378
+ return sample
379
+ })
380
+ }
381
+
382
+ function copy (target, source, key) {
383
+ if (source && source[key] !== undefined) target[key] = source[key]
384
+ }
385
+
386
+ function summarizeDebugRerun ({ result, events, offline, outDir }) {
387
+ const output = `${result.stdout}\n${result.stderr}`
388
+
389
+ return {
390
+ ran: true,
391
+ commandExitCode: result.exitCode,
392
+ commandTimedOut: result.timedOut,
393
+ debugCommandFailed: result.exitCode !== 0 || result.timedOut === true,
394
+ offlineExporterInitialized: offline.initialized,
395
+ artifactDirectory: outDir,
396
+ ...basicEventEvidence(events),
397
+ debugLines: findInterestingLines(output, [
398
+ /dd-trace/i,
399
+ /test optimization/i,
400
+ /ci visibility/i,
401
+ /citestcycle/i,
402
+ /auto.?test.?retr/i,
403
+ /flaky.?retr/i,
404
+ /\b(?:error|warn)\b/i,
405
+ ], 20),
406
+ stderrExcerpt: tailInterestingLines(result.stderr),
407
+ stdoutExcerpt: tailInterestingLines(result.stdout),
408
+ }
409
+ }
410
+
411
+ function getDebugArtifacts (outDir) {
412
+ return [
413
+ 'command.json',
414
+ 'stdout.txt',
415
+ 'stderr.txt',
416
+ 'events.ndjson',
417
+ 'result.json',
418
+ ].map(filename => path.join(outDir, filename))
419
+ }
420
+
421
+ function findInterestingLines (output, patterns, limit = 8) {
422
+ return uniqueLines(output.split(/\r?\n/).map(stripAnsi).filter(line => {
423
+ if (/^\s*Encoding payload:/.test(line)) return false
424
+ return patterns.some(pattern => pattern.test(line))
425
+ }).map(truncateLine)).slice(0, limit)
426
+ }
427
+
428
+ function truncateLine (line) {
429
+ const maxLength = 500
430
+ return line.length > maxLength ? `${line.slice(0, maxLength)}...` : line
431
+ }
432
+
433
+ function tailInterestingLines (output) {
434
+ return uniqueLines(output
435
+ .split(/\r?\n/)
436
+ .map(stripAnsi)
437
+ .map(line => line.trimEnd())
438
+ .filter(line => line.trim() !== '')
439
+ .filter(line => !/^\s*Encoding payload:/.test(line))
440
+ .map(truncateLine))
441
+ .slice(-12)
442
+ }
443
+
444
+ function stripAnsi (line) {
445
+ return line.replaceAll(ANSI_PATTERN, '')
446
+ }
447
+
448
+ function uniqueLines (lines) {
449
+ const seen = new Set()
450
+ const unique = []
451
+ for (const line of lines) {
452
+ const normalized = line.trim()
453
+ if (!normalized || seen.has(normalized)) continue
454
+ seen.add(normalized)
455
+ unique.push(line)
456
+ }
457
+ return unique
458
+ }
459
+
460
+ function pass (framework, scenario, diagnosis, evidence, outDir, extraArtifacts) {
461
+ return result(framework, scenario, 'pass', diagnosis, evidence, outDir, extraArtifacts)
462
+ }
463
+
464
+ function fail (framework, scenario, diagnosis, evidence, outDir, extraArtifacts) {
465
+ return result(framework, scenario, 'fail', diagnosis, evidence, outDir, extraArtifacts)
466
+ }
467
+
468
+ function skip (framework, scenario, diagnosis, evidence = {}) {
469
+ return result(framework, scenario, 'skip', diagnosis, evidence, null)
470
+ }
471
+
472
+ function incomplete (framework, scenario, diagnosis, evidence = {}) {
473
+ return result(framework, scenario, 'error', diagnosis, {
474
+ ...evidence,
475
+ manifestIncomplete: true,
476
+ }, null)
477
+ }
478
+
479
+ function inconclusive (framework, scenario, diagnosis, evidence = {}, outDir, extraArtifacts) {
480
+ return result(framework, scenario, 'error', diagnosis, {
481
+ ...evidence,
482
+ validationIncomplete: true,
483
+ }, outDir, extraArtifacts)
484
+ }
485
+
486
+ function error (framework, scenario, err, outDir = err?.artifactDirectory) {
487
+ return result(framework, scenario, 'error', err && err.stack ? err.stack : String(err), {}, outDir)
488
+ }
489
+
490
+ function result (framework, scenario, status, diagnosis, evidence, outDir, extraArtifacts) {
491
+ const artifacts = outDir
492
+ ? [
493
+ path.join(outDir, 'command.json'),
494
+ path.join(outDir, 'stdout.txt'),
495
+ path.join(outDir, 'stderr.txt'),
496
+ path.join(outDir, 'events.ndjson'),
497
+ path.join(outDir, 'result.json'),
498
+ ]
499
+ : []
500
+
501
+ if (Array.isArray(extraArtifacts)) {
502
+ artifacts.push(...extraArtifacts)
503
+ } else if (extraArtifacts && typeof extraArtifacts === 'object') {
504
+ artifacts.push(...Object.values(extraArtifacts).filter(Boolean))
505
+ }
506
+
507
+ return {
508
+ frameworkId: framework.id,
509
+ scenario,
510
+ status,
511
+ diagnosis,
512
+ evidence,
513
+ artifacts,
514
+ }
515
+ }
516
+
517
+ module.exports = {
518
+ basicEventEvidence,
519
+ discoverScenarioTests,
520
+ discoveryEvidence,
521
+ error,
522
+ fail,
523
+ failWithDebugRerun,
524
+ findInterestingLines,
525
+ frameworkOutDir,
526
+ hasAllBasicEventTypes,
527
+ incomplete,
528
+ inconclusive,
529
+ pass,
530
+ prepareGeneratedScenario,
531
+ requireGeneratedScenario,
532
+ runDebugInstrumentedCommand,
533
+ runInstrumentedCommand,
534
+ skip,
535
+ testEventSamples,
536
+ tailInterestingLines,
537
+ testsForDiscoveredScenario,
538
+ testsForScenario,
539
+ }