dd-trace 6.8.0 → 6.10.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 (209) 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 +140 -42
  44. package/index.d.ts +140 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +8 -5
  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/confluentinc-kafka-javascript.js +111 -6
  55. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  56. package/packages/datadog-instrumentations/src/dns.js +21 -2
  57. package/packages/datadog-instrumentations/src/electron.js +1 -0
  58. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  59. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  60. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  61. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  62. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +199 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +62 -0
  68. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  69. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  70. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  71. package/packages/datadog-instrumentations/src/jest.js +980 -234
  72. package/packages/datadog-instrumentations/src/mocha/main.js +108 -10
  73. package/packages/datadog-instrumentations/src/mocha/utils.js +66 -50
  74. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  75. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  76. package/packages/datadog-instrumentations/src/mysql.js +46 -16
  77. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  78. package/packages/datadog-instrumentations/src/next.js +155 -23
  79. package/packages/datadog-instrumentations/src/openai.js +14 -0
  80. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  81. package/packages/datadog-instrumentations/src/pg.js +209 -11
  82. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  83. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  84. package/packages/datadog-instrumentations/src/promise.js +3 -3
  85. package/packages/datadog-instrumentations/src/router.js +195 -19
  86. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +85 -13
  87. package/packages/datadog-instrumentations/src/vitest-main.js +528 -72
  88. package/packages/datadog-instrumentations/src/vitest-util.js +106 -12
  89. package/packages/datadog-instrumentations/src/vitest-worker.js +382 -40
  90. package/packages/datadog-instrumentations/src/webdriverio.js +212 -22
  91. package/packages/datadog-instrumentations/src/when.js +3 -3
  92. package/packages/datadog-instrumentations/src/ws.js +5 -1
  93. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  94. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  95. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
  96. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  97. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  98. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  99. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -47
  100. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  101. package/packages/datadog-plugin-electron/src/net.js +10 -4
  102. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  103. package/packages/datadog-plugin-http/src/client.js +1 -1
  104. package/packages/datadog-plugin-http2/src/client.js +1 -1
  105. package/packages/datadog-plugin-jest/src/index.js +6 -22
  106. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  107. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  108. package/packages/datadog-plugin-kafkajs/src/producer.js +5 -3
  109. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  110. package/packages/datadog-plugin-mocha/src/index.js +832 -4
  111. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  112. package/packages/datadog-plugin-next/src/index.js +51 -21
  113. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  114. package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
  115. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  116. package/packages/datadog-plugin-pg/src/index.js +66 -1
  117. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  118. package/packages/datadog-plugin-vitest/src/index.js +103 -27
  119. package/packages/datadog-plugin-ws/src/util.js +2 -1
  120. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  121. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  123. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  124. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +228 -0
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  127. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  128. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  129. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  130. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  131. package/packages/dd-trace/src/appsec/index.js +48 -476
  132. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  133. package/packages/dd-trace/src/carrier.js +356 -0
  134. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  135. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  136. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  137. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  139. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  140. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  141. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  142. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  143. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  144. package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
  145. package/packages/dd-trace/src/config/helper.js +2 -0
  146. package/packages/dd-trace/src/config/index.js +16 -5
  147. package/packages/dd-trace/src/config/remote_config.js +1 -1
  148. package/packages/dd-trace/src/config/supported-configurations.json +18 -0
  149. package/packages/dd-trace/src/datastreams/context.js +6 -1
  150. package/packages/dd-trace/src/datastreams/manager.js +5 -1
  151. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  152. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  153. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  154. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  155. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  156. package/packages/dd-trace/src/guardrails/log.js +1 -10
  157. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  158. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  159. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  160. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  161. package/packages/dd-trace/src/llmobs/index.js +34 -2
  162. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  163. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  164. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  165. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  166. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  167. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  168. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  169. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  170. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  171. package/packages/dd-trace/src/llmobs/util.js +78 -0
  172. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  173. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  174. package/packages/dd-trace/src/log/channels.js +1 -9
  175. package/packages/dd-trace/src/log/levels.js +12 -0
  176. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  177. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  178. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  179. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  180. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  181. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  182. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  183. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  184. package/packages/dd-trace/src/plugin_manager.js +2 -0
  185. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  186. package/packages/dd-trace/src/plugins/index.js +1 -0
  187. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  188. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  189. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  190. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  191. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +3 -62
  192. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  193. package/packages/dd-trace/src/plugins/util/test.js +397 -74
  194. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  195. package/packages/dd-trace/src/plugins/util/web.js +76 -3
  196. package/packages/dd-trace/src/profiler.js +1 -1
  197. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  198. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  199. package/packages/dd-trace/src/proxy.js +1 -0
  200. package/packages/dd-trace/src/ritm.js +5 -0
  201. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  202. package/packages/dd-trace/src/sampling_rule.js +4 -2
  203. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  204. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  205. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  206. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  207. package/vendor/dist/pprof-format/index.js +1 -1
  208. package/version.js +10 -8
  209. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -3,21 +3,23 @@
3
3
  const path = require('path')
4
4
  const fs = require('fs')
5
5
  const { URL } = require('url')
6
+ const { stripVTControlCharacters } = require('node:util')
6
7
  const { getLageTestSessionName } = require('../../ci-visibility/lage')
7
8
  const log = require('../../log')
8
- const { getEnvironmentVariable } = require('../../config/helper')
9
+ const { getEnvironmentVariable, getValueFromEnvSources } = require('../../config/helper')
9
10
  const { getSegment } = require('../../util')
11
+ const { parse } = require('../../../../../vendor/dist/jest-docblock')
10
12
  const satisfies = require('../../../../../vendor/dist/semifies')
11
13
 
12
14
  const istanbul = require('../../../../../vendor/dist/istanbul-lib-coverage')
13
15
 
16
+ const { writeDatadogParentId, writeDatadogTraceId } = require('../../carrier')
14
17
  const id = require('../../id')
15
18
  const {
16
19
  incrementCountMetric,
17
20
  TELEMETRY_GIT_COMMIT_SHA_DISCREPANCY,
18
21
  TELEMETRY_GIT_SHA_MATCH,
19
22
  } = require('../../ci-visibility/telemetry')
20
-
21
23
  const { SPAN_TYPE, RESOURCE_NAME, SAMPLING_PRIORITY } = require('../../../../../ext/tags')
22
24
  const { SAMPLING_RULE_DECISION } = require('../../constants')
23
25
  const { AUTO_KEEP } = require('../../../../../ext/priority')
@@ -123,13 +125,10 @@ const TEST_RETRY_REASON = 'test.retry_reason'
123
125
  const TEST_HAS_FAILED_ALL_RETRIES = 'test.has_failed_all_retries'
124
126
  const TEST_IS_MODIFIED = 'test.is_modified'
125
127
  const TEST_HAS_DYNAMIC_NAME = '_dd.has_dynamic_name'
126
- const EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS = [
127
- { limitMs: 5 * 1000, key: '5s' },
128
- { limitMs: 10 * 1000, key: '10s' },
129
- { limitMs: 30 * 1000, key: '30s' },
130
- { limitMs: 5 * 60 * 1000, key: '5m' },
131
- ]
132
128
  const CI_APP_ORIGIN = 'ciapp-test'
129
+ // eslint-disable-next-line no-control-regex
130
+ const TEST_OPTIMIZATION_NAME_CONTROL_RE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g
131
+ const TEST_OPTIMIZATION_NAME_WHITESPACE_RE = /\s+/g
133
132
 
134
133
  // Matches patterns that are almost certainly runtime-generated values in test names:
135
134
  // - Unix timestamps in ms (13 digits, years ~2020-2090) or s (10 digits)
@@ -162,6 +161,7 @@ const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
162
161
  const TEST_ITR_UNSKIPPABLE = 'test.itr.unskippable'
163
162
  const TEST_ITR_FORCED_RUN = 'test.itr.forced_run'
164
163
  const ITR_CORRELATION_ID = 'itr_correlation_id'
164
+ const TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE = 'Test Impact Analysis skipped all tests.'
165
165
 
166
166
  const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
167
167
 
@@ -180,17 +180,24 @@ const JEST_WORKER_QUARANTINE_PAYLOAD_CODE = 64
180
180
 
181
181
  // cucumber worker variables
182
182
  const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
183
+ const CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE = 71
183
184
 
184
185
  // mocha worker variables
185
186
  const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
186
187
  const MOCHA_WORKER_LOGS_PAYLOAD_CODE = 81
188
+ const MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE = 82
187
189
 
188
190
  // playwright worker variables
189
191
  const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
192
+ const PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE = 91
190
193
 
191
194
  // vitest worker variables
192
195
  const VITEST_WORKER_TRACE_PAYLOAD_CODE = 100
196
+ const VITEST_WORKER_COVERAGE_PAYLOAD_CODE = 101
193
197
  const VITEST_WORKER_LOGS_PAYLOAD_CODE = 102
198
+ const VITEST_WORKER_TELEMETRY_PAYLOAD_CODE = 103
199
+ const VITEST_WORKER_EFD_SUITE_ADMISSION_REQUEST_CODE = 104
200
+ const VITEST_WORKER_EFD_SUITE_ADMISSION_RESPONSE_CODE = 105
194
201
 
195
202
  const TEST_IS_TEST_FRAMEWORK_WORKER = 'test.is_test_framework_worker'
196
203
 
@@ -211,7 +218,7 @@ const DD_CI_LIBRARY_CONFIGURATION_ERROR_KNOWN_TESTS = '_dd.ci.library_configurat
211
218
  const DD_CI_LIBRARY_CONFIGURATION_ERROR_TEST_MANAGEMENT_TESTS =
212
219
  '_dd.ci.library_configuration_error.test_management_tests'
213
220
 
214
- const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'vitest', 'webdriverio'])
221
+ const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'webdriverio'])
215
222
  const MINIMUM_FRAMEWORK_VERSION_FOR_EFD = {
216
223
  playwright: '>=1.38.0',
217
224
  }
@@ -300,7 +307,10 @@ const TEST_MANAGEMENT_IS_QUARANTINED = 'test.test_management.is_quarantined'
300
307
  const TEST_MANAGEMENT_ENABLED = 'test.test_management.enabled'
301
308
  const TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED = 'test.test_management.attempt_to_fix_passed'
302
309
 
310
+ const MAX_TEST_MANAGEMENT_REPORT_ITEMS = 5
311
+ const MAX_TEST_OPTIMIZATION_NAME_LENGTH = 160
303
312
  const MAX_TEST_OPTIMIZATION_SUMMARY_ITEMS = 10
313
+ const testManagementExecutions = new Map()
304
314
 
305
315
  // Impacted tests
306
316
  const POSSIBLE_BASE_BRANCHES = ['main', 'master', 'preprod', 'prod', 'dev', 'development', 'trunk']
@@ -472,11 +482,18 @@ module.exports = {
472
482
  JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
473
483
  JEST_WORKER_QUARANTINE_PAYLOAD_CODE,
474
484
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
485
+ CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE,
475
486
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
476
487
  MOCHA_WORKER_LOGS_PAYLOAD_CODE,
488
+ MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE,
477
489
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
490
+ PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE,
478
491
  VITEST_WORKER_TRACE_PAYLOAD_CODE,
492
+ VITEST_WORKER_COVERAGE_PAYLOAD_CODE,
479
493
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
494
+ VITEST_WORKER_TELEMETRY_PAYLOAD_CODE,
495
+ VITEST_WORKER_EFD_SUITE_ADMISSION_REQUEST_CODE,
496
+ VITEST_WORKER_EFD_SUITE_ADMISSION_RESPONSE_CODE,
480
497
  TEST_IS_TEST_FRAMEWORK_WORKER,
481
498
  TEST_SOURCE_START,
482
499
  TEST_SKIPPED_BY_ITR,
@@ -492,6 +509,7 @@ module.exports = {
492
509
  getTestParametersString,
493
510
  finishAllTraceSpans,
494
511
  getTestParentSpan,
512
+ isMarkedAsUnskippable,
495
513
  getTestSuitePath,
496
514
  getCodeOwnersFileEntries,
497
515
  getCodeOwnersForFilename,
@@ -515,6 +533,7 @@ module.exports = {
515
533
  TEST_ITR_UNSKIPPABLE,
516
534
  TEST_ITR_FORCED_RUN,
517
535
  ITR_CORRELATION_ID,
536
+ TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE,
518
537
  addIntelligentTestRunnerSpanTags,
519
538
  getCoveredFilenamesFromCoverage,
520
539
  getCoveredFilesFromCoverage,
@@ -531,9 +550,7 @@ module.exports = {
531
550
  removeInvalidMetadata,
532
551
  parseAnnotations,
533
552
  getIsFaultyEarlyFlakeDetection,
534
- EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
535
- getEfdRetryCount,
536
- getMaxEfdRetryCount,
553
+ getFailedTestReplayPromise,
537
554
  TEST_BROWSER_DRIVER,
538
555
  TEST_BROWSER_DRIVER_VERSION,
539
556
  TEST_BROWSER_NAME,
@@ -584,8 +601,10 @@ module.exports = {
584
601
  collectDynamicNamesFromTraces,
585
602
  collectTestOptimizationSummariesFromTraces,
586
603
  logDynamicNamesWarning,
604
+ recordTestManagementExecution,
587
605
  recordAttemptToFixExecution,
588
606
  collectAttemptToFixExecutionsFromTraces,
607
+ formatTestManagementSummary,
589
608
  formatAttemptToFixSummary,
590
609
  logAttemptToFixTestExecution,
591
610
  formatDynamicNamesSummary,
@@ -732,6 +751,7 @@ function checkShaDiscrepancies (ciMetadata, userProvidedGitMetadata) {
732
751
  *
733
752
  * @param {string=} testFramework
734
753
  * @param {TestEnvironmentConfig=} config
754
+ * @param {boolean=} shouldSkipGitMetadataExtraction
735
755
  * @returns {TestEnvironmentMetadata}
736
756
  */
737
757
  function getTestEnvironmentMetadata (testFramework, config, shouldSkipGitMetadataExtraction = false) {
@@ -888,10 +908,10 @@ function setRumTestCorrelation (context, activeSpan) {
888
908
  * @returns {import('../../opentracing/span_context')}
889
909
  */
890
910
  function getTestParentSpan (tracer, testExecutionId) {
891
- return tracer.extract('text_map', {
892
- 'x-datadog-trace-id': testExecutionId || id().toString(10),
893
- 'x-datadog-parent-id': '0000000000000000',
894
- })
911
+ const carrier = /** @type {Record<string, unknown>} */ ({})
912
+ writeDatadogTraceId(carrier, testExecutionId || id().toString(10))
913
+ writeDatadogParentId(carrier, '0000000000000000')
914
+ return tracer.extract('text_map', carrier)
895
915
  }
896
916
 
897
917
  function getTestCommonTags (name, suite, version, testFramework) {
@@ -912,6 +932,10 @@ function getTestCommonTags (name, suite, version, testFramework) {
912
932
  /**
913
933
  * We want to make sure that test suites are reported the same way for
914
934
  * every OS, so we replace `path.sep` by `/`
935
+ *
936
+ * @param {string | undefined} testSuiteAbsolutePath
937
+ * @param {string} sourceRoot
938
+ * @returns {string}
915
939
  */
916
940
  function getTestSuitePath (testSuiteAbsolutePath, sourceRoot) {
917
941
  if (!testSuiteAbsolutePath) {
@@ -924,6 +948,62 @@ function getTestSuitePath (testSuiteAbsolutePath, sourceRoot) {
924
948
  return testSuitePath.replaceAll(path.sep, '/')
925
949
  }
926
950
 
951
+ const globalDocblockRegExp = /^\s*(\/\*\*?(.|\r?\n)*?\*\/)/
952
+ const MAX_COMMENTS_CHECKED = 10
953
+
954
+ function isMarkedAsUnskippable (test) {
955
+ let testSource
956
+
957
+ try {
958
+ testSource = fs.readFileSync(test.path, 'utf8')
959
+ } catch {
960
+ return false
961
+ }
962
+
963
+ const re = globalDocblockRegExp
964
+ re.lastIndex = 0
965
+ let commentsChecked = 0
966
+
967
+ while (testSource.length) {
968
+ const match = re.exec(testSource)
969
+ if (!match) break
970
+ const comment = match[1]
971
+
972
+ let docblocks
973
+ try {
974
+ docblocks = parse(comment)
975
+ } catch {
976
+ // Skip unparsable comment and continue scanning
977
+ if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
978
+ return false
979
+ }
980
+ continue
981
+ }
982
+
983
+ if (docblocks?.datadog) {
984
+ try {
985
+ // @ts-expect-error The datadog type is defined by us and may only be a string.
986
+ return JSON.parse(docblocks.datadog).unskippable
987
+ } catch {
988
+ // If the @datadog block comment is present but malformed, we'll run the suite
989
+ log.warn('@datadog block comment is malformed.')
990
+ return true
991
+ }
992
+ }
993
+
994
+ if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
995
+ return false
996
+ }
997
+
998
+ // To stop as soon as no doc blocks are found, slice the source. That way the
999
+ // regexp works by using the `^` anchor. Without it, it would continue
1000
+ // scanning the rest of the file.
1001
+ testSource = testSource.slice(match[0].length)
1002
+ }
1003
+
1004
+ return false
1005
+ }
1006
+
927
1007
  const POSSIBLE_CODEOWNERS_LOCATIONS = [
928
1008
  'CODEOWNERS',
929
1009
  '.github/CODEOWNERS',
@@ -1526,43 +1606,19 @@ function parseAnnotations (annotations) {
1526
1606
  }
1527
1607
 
1528
1608
  /**
1529
- * Given a test's first-execution duration (ms) and the slow_test_retries map
1530
- * from the backend, return how many EFD retries to run.
1609
+ * Combines the optional Failed Test Replay operations that must settle before a retry.
1531
1610
  *
1532
- * Returns 0 when the test is too slow to retry (≥ 5 min).
1533
- *
1534
- * @param {number} durationMs
1535
- * @param {Record<string, number>} slowTestRetries e.g. { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
1536
- * @returns {number}
1537
- */
1538
- function getEfdRetryCount (durationMs, slowTestRetries) {
1539
- for (const { limitMs, key } of EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS) {
1540
- if (durationMs < limitMs) {
1541
- return slowTestRetries[key] ?? 0
1542
- }
1543
- }
1544
- return 0 // ≥ 5 min — abort
1545
- }
1546
-
1547
- /**
1548
- * Returns the maximum retry count configured by the backend for EFD.
1549
- *
1550
- * @param {Record<string, number> | undefined} slowTestRetries
1551
- * @returns {number | undefined}
1611
+ * @param {{setProbePromise?: Promise<void>, finishTestPromise?: Promise<void>}} promises
1612
+ * @returns {Promise<void>|undefined}
1552
1613
  */
1553
- function getMaxEfdRetryCount (slowTestRetries) {
1554
- if (slowTestRetries === undefined) return
1555
-
1556
- const retryCounts = Object.values(slowTestRetries)
1557
- if (retryCounts.length === 0) return
1558
-
1559
- let maxRetries = 0
1560
- for (const retryCount of retryCounts) {
1561
- if (retryCount > maxRetries) {
1562
- maxRetries = retryCount
1563
- }
1614
+ function getFailedTestReplayPromise (promises) {
1615
+ if (promises.setProbePromise && promises.finishTestPromise) {
1616
+ return Promise.all([
1617
+ promises.setProbePromise,
1618
+ promises.finishTestPromise,
1619
+ ]).then(() => {})
1564
1620
  }
1565
- return maxRetries
1621
+ return promises.setProbePromise || promises.finishTestPromise
1566
1622
  }
1567
1623
 
1568
1624
  function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) {
@@ -1706,7 +1762,8 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
1706
1762
 
1707
1763
  function getLibraryCapabilitiesTags (testFramework, frameworkVersion, options = {}) {
1708
1764
  return {
1709
- [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
1765
+ [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: !options.omitTestImpactAnalysis &&
1766
+ isTiaSupported(testFramework)
1710
1767
  ? '1'
1711
1768
  : undefined,
1712
1769
  [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionSupported(testFramework, frameworkVersion)
@@ -1866,6 +1923,17 @@ function getModifiedFilesFromDiff (diff) {
1866
1923
  * @typedef {Map<string, AttemptToFixExecutionResult>} AttemptToFixExecutions
1867
1924
  */
1868
1925
 
1926
+ /**
1927
+ * Returns a collision-free identity for a test's suite and name.
1928
+ *
1929
+ * @param {string | undefined} testSuite
1930
+ * @param {string} testName
1931
+ * @returns {string}
1932
+ */
1933
+ function getTestOptimizationIdentity (testSuite, testName) {
1934
+ return JSON.stringify([testSuite, testName])
1935
+ }
1936
+
1869
1937
  /**
1870
1938
  * Formats a test name for user-facing Test Optimization summaries.
1871
1939
  *
@@ -1877,6 +1945,79 @@ function formatTestOptimizationName (testSuite, testName) {
1877
1945
  return testSuite ? `${testSuite} › ${testName}` : testName
1878
1946
  }
1879
1947
 
1948
+ /**
1949
+ * Replaces characters that could make one test occupy multiple CI log lines.
1950
+ *
1951
+ * @param {string} value
1952
+ * @returns {string}
1953
+ */
1954
+ function sanitizeTestOptimizationName (value) {
1955
+ return stripVTControlCharacters(value)
1956
+ .replaceAll(TEST_OPTIMIZATION_NAME_CONTROL_RE, '')
1957
+ .replaceAll(TEST_OPTIMIZATION_NAME_WHITESPACE_RE, ' ')
1958
+ .trim()
1959
+ }
1960
+
1961
+ /**
1962
+ * Truncates the beginning of a value while preserving its distinguishing suffix.
1963
+ *
1964
+ * @param {string} value
1965
+ * @param {number} maxLength
1966
+ * @returns {string}
1967
+ */
1968
+ function truncateTestOptimizationNameStart (value, maxLength) {
1969
+ if (value.length <= maxLength) return value
1970
+
1971
+ let start = value.length - maxLength + 1
1972
+ const code = value.charCodeAt(start)
1973
+ if (code >= 0xDC_00 && code <= 0xDF_FF) start++
1974
+ return `…${value.slice(start)}`
1975
+ }
1976
+
1977
+ /**
1978
+ * Truncates the middle of a value while preserving both its context and suffix.
1979
+ *
1980
+ * @param {string} value
1981
+ * @param {number} maxLength
1982
+ * @returns {string}
1983
+ */
1984
+ function truncateTestOptimizationNameMiddle (value, maxLength) {
1985
+ if (value.length <= maxLength) return value
1986
+
1987
+ let prefixLength = Math.ceil((maxLength - 1) / 2)
1988
+ let suffixLength = maxLength - prefixLength - 1
1989
+ const prefixCode = value.charCodeAt(prefixLength - 1)
1990
+ if (prefixCode >= 0xD8_00 && prefixCode <= 0xDB_FF) prefixLength--
1991
+ const suffixCode = value.charCodeAt(value.length - suffixLength)
1992
+ if (suffixCode >= 0xDC_00 && suffixCode <= 0xDF_FF) suffixLength--
1993
+ return `${value.slice(0, prefixLength)}…${value.slice(-suffixLength)}`
1994
+ }
1995
+
1996
+ /**
1997
+ * Formats a bounded, single-line test name for CI output.
1998
+ *
1999
+ * @param {string | undefined} testSuite
2000
+ * @param {string} testName
2001
+ * @returns {string}
2002
+ */
2003
+ function formatTestOptimizationDisplayName (testSuite, testName) {
2004
+ const sanitizedSuite = testSuite ? sanitizeTestOptimizationName(testSuite) : ''
2005
+ const sanitizedName = sanitizeTestOptimizationName(testName)
2006
+ if (!sanitizedSuite) {
2007
+ return truncateTestOptimizationNameMiddle(sanitizedName, MAX_TEST_OPTIMIZATION_NAME_LENGTH)
2008
+ }
2009
+
2010
+ const separator = ' › '
2011
+ if (sanitizedSuite.length + separator.length + sanitizedName.length <= MAX_TEST_OPTIMIZATION_NAME_LENGTH) {
2012
+ return `${sanitizedSuite}${separator}${sanitizedName}`
2013
+ }
2014
+
2015
+ const suiteLength = Math.min(60, sanitizedSuite.length)
2016
+ const testNameLength = MAX_TEST_OPTIMIZATION_NAME_LENGTH - separator.length - suiteLength
2017
+ return truncateTestOptimizationNameStart(sanitizedSuite, suiteLength) +
2018
+ `${separator}${truncateTestOptimizationNameMiddle(sanitizedName, testNameLength)}`
2019
+ }
2020
+
1880
2021
  /**
1881
2022
  * Renders a bounded bullet list for Test Optimization summaries.
1882
2023
  *
@@ -1888,7 +2029,12 @@ function formatTestOptimizationList (items) {
1888
2029
  const more = items.length - shown.length
1889
2030
  const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
1890
2031
 
1891
- return shown.map(({ text, suffix }) => ` • ${text}${suffix ? ` (${suffix})` : ''}`).join('\n') + moreSuffix
2032
+ return shown
2033
+ .map(({ text, suffix }) => ` • ${truncateTestOptimizationNameMiddle(
2034
+ sanitizeTestOptimizationName(text),
2035
+ MAX_TEST_OPTIMIZATION_NAME_LENGTH
2036
+ )}${suffix ? ` (${suffix})` : ''}`)
2037
+ .join('\n') + moreSuffix
1892
2038
  }
1893
2039
 
1894
2040
  /**
@@ -1901,15 +2047,67 @@ function formatTestOptimizationList (items) {
1901
2047
  function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTests) {
1902
2048
  if (!testName) return
1903
2049
 
1904
- const name = formatTestOptimizationName(testSuite, testName)
1905
2050
  if (loggedAttemptToFixTests) {
1906
- if (loggedAttemptToFixTests.has(name)) return
2051
+ const identity = getTestOptimizationIdentity(testSuite, testName)
2052
+ if (loggedAttemptToFixTests.has(identity)) return
1907
2053
 
1908
- loggedAttemptToFixTests.add(name)
2054
+ loggedAttemptToFixTests.add(identity)
1909
2055
  }
1910
2056
 
1911
2057
  // eslint-disable-next-line no-console -- Intentional user-facing attempt-to-fix progress report
1912
- console.warn(`Datadog Test Optimization: attempting to fix ${name}`)
2058
+ console.warn(`Datadog Test Optimization: attempting to fix ${formatTestOptimizationDisplayName(testSuite, testName)}`)
2059
+ }
2060
+
2061
+ /**
2062
+ * @typedef {object} TestManagementExecutionResult
2063
+ * @property {string} name
2064
+ * @property {'disabled'|'quarantined'} action
2065
+ * @property {boolean} failed
2066
+ */
2067
+
2068
+ /**
2069
+ * @typedef {Map<string, TestManagementExecutionResult>} TestManagementExecutions
2070
+ */
2071
+
2072
+ /**
2073
+ * Records the effective non-Attempt-to-Fix Test Management action for one execution.
2074
+ *
2075
+ * @param {{
2076
+ * testSuite?: string,
2077
+ * testName: string,
2078
+ * status?: string,
2079
+ * isAttemptToFix?: boolean,
2080
+ * isDisabled?: boolean,
2081
+ * isQuarantined?: boolean
2082
+ * }} execution
2083
+ * @param {TestManagementExecutions} [executions]
2084
+ */
2085
+ function recordTestManagementExecution (execution, executions = testManagementExecutions) {
2086
+ if (!execution?.testName || execution.isAttemptToFix) return
2087
+
2088
+ const action = execution.isDisabled
2089
+ ? 'disabled'
2090
+ : execution.isQuarantined ? 'quarantined' : undefined
2091
+ if (!action) return
2092
+ if (action === 'quarantined' && execution.status !== 'pass' && execution.status !== 'fail') return
2093
+ if (getValueFromEnvSources('DD_TEST_MANAGEMENT_REPORT_ENABLED') === false) return
2094
+
2095
+ const identity = getTestOptimizationIdentity(execution.testSuite, execution.testName)
2096
+ const name = formatTestOptimizationName(execution.testSuite, execution.testName)
2097
+ let result = executions.get(identity)
2098
+
2099
+ if (!result) {
2100
+ result = {
2101
+ name,
2102
+ action,
2103
+ failed: false,
2104
+ }
2105
+ executions.set(identity, result)
2106
+ } else if (action === 'disabled') {
2107
+ result.action = action
2108
+ }
2109
+
2110
+ result.failed ||= execution.status === 'fail'
1913
2111
  }
1914
2112
 
1915
2113
  /**
@@ -1926,10 +2124,12 @@ function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTe
1926
2124
  */
1927
2125
  function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
1928
2126
  if (!execution?.testName) return
2127
+ if (getValueFromEnvSources('DD_TEST_MANAGEMENT_REPORT_ENABLED') === false) return
1929
2128
 
1930
2129
  const { testSuite, testName, status, isDisabled, isQuarantined } = execution
2130
+ const identity = getTestOptimizationIdentity(testSuite, testName)
1931
2131
  const name = formatTestOptimizationName(testSuite, testName)
1932
- let result = attemptToFixExecutions.get(name)
2132
+ let result = attemptToFixExecutions.get(identity)
1933
2133
 
1934
2134
  if (!result) {
1935
2135
  result = {
@@ -1939,7 +2139,7 @@ function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
1939
2139
  isDisabled: false,
1940
2140
  isQuarantined: false,
1941
2141
  }
1942
- attemptToFixExecutions.set(name, result)
2142
+ attemptToFixExecutions.set(identity, result)
1943
2143
  }
1944
2144
 
1945
2145
  result.executions++
@@ -1975,6 +2175,27 @@ function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions
1975
2175
  })
1976
2176
  }
1977
2177
 
2178
+ /**
2179
+ * Records a Test Management execution from a serialized worker trace span.
2180
+ *
2181
+ * @param {{ meta?: Record<string, string> }} span
2182
+ * @param {TestManagementExecutions} executions
2183
+ * @returns {void}
2184
+ */
2185
+ function collectTestManagementExecutionFromTraceSpan (span, executions) {
2186
+ const meta = span.meta
2187
+ if (!meta) return
2188
+
2189
+ recordTestManagementExecution({
2190
+ testSuite: meta[TEST_SUITE],
2191
+ testName: meta[TEST_NAME],
2192
+ status: meta[TEST_STATUS],
2193
+ isAttemptToFix: meta[TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX] === 'true',
2194
+ isDisabled: meta[TEST_MANAGEMENT_IS_DISABLED] === 'true',
2195
+ isQuarantined: meta[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
2196
+ }, executions)
2197
+ }
2198
+
1978
2199
  /**
1979
2200
  * Scans serialized worker trace payloads and populates Test Optimization summary data.
1980
2201
  * Silently ignores parse errors.
@@ -1982,11 +2203,16 @@ function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions
1982
2203
  * @param {string} data - JSON-serialized traces from a worker
1983
2204
  * @param {{
1984
2205
  * newTestsWithDynamicNames?: Set<string>,
1985
- * attemptToFixExecutions?: AttemptToFixExecutions
2206
+ * attemptToFixExecutions?: AttemptToFixExecutions,
2207
+ * testManagementExecutions?: TestManagementExecutions
1986
2208
  * }} summaries
1987
2209
  */
1988
2210
  function collectTestOptimizationSummariesFromTraces (data, summaries) {
1989
- const { newTestsWithDynamicNames, attemptToFixExecutions } = summaries
2211
+ const {
2212
+ newTestsWithDynamicNames,
2213
+ attemptToFixExecutions,
2214
+ testManagementExecutions: executions = testManagementExecutions,
2215
+ } = summaries
1990
2216
 
1991
2217
  try {
1992
2218
  const traces = JSON.parse(data)
@@ -1998,6 +2224,7 @@ function collectTestOptimizationSummariesFromTraces (data, summaries) {
1998
2224
  if (attemptToFixExecutions) {
1999
2225
  collectAttemptToFixExecutionFromTraceSpan(span, attemptToFixExecutions)
2000
2226
  }
2227
+ collectTestManagementExecutionFromTraceSpan(span, executions)
2001
2228
  }
2002
2229
  }
2003
2230
  } catch {
@@ -2044,7 +2271,10 @@ function hasAttemptToFixManagementNotes (result) {
2044
2271
  }
2045
2272
 
2046
2273
  function addAttemptToFixResultLine (lines, result) {
2047
- lines.push(` • ${result.name}`)
2274
+ lines.push(` • ${truncateTestOptimizationNameMiddle(
2275
+ sanitizeTestOptimizationName(result.name),
2276
+ MAX_TEST_OPTIMIZATION_NAME_LENGTH
2277
+ )}`)
2048
2278
 
2049
2279
  for (const note of getAttemptToFixManagementNotes(result)) {
2050
2280
  lines.push(` ${note}`)
@@ -2055,9 +2285,10 @@ function addAttemptToFixResultLine (lines, result) {
2055
2285
  * Formats the attempt-to-fix end-of-session summary.
2056
2286
  *
2057
2287
  * @param {AttemptToFixExecutions} attemptToFixExecutions
2288
+ * @param {boolean} [includeTestNames]
2058
2289
  * @returns {string}
2059
2290
  */
2060
- function formatAttemptToFixSummary (attemptToFixExecutions) {
2291
+ function formatAttemptToFixSummary (attemptToFixExecutions, includeTestNames = true) {
2061
2292
  if (attemptToFixExecutions.size === 0) return ''
2062
2293
 
2063
2294
  const results = [...attemptToFixExecutions.values()]
@@ -2069,9 +2300,11 @@ function formatAttemptToFixSummary (attemptToFixExecutions) {
2069
2300
  `Attempt to fix passed: all ${totalExecutions} execution(s) passed for ${results.length} test(s).`,
2070
2301
  ]
2071
2302
 
2072
- for (const result of results) {
2073
- if (hasAttemptToFixManagementNotes(result)) {
2074
- addAttemptToFixResultLine(lines, result)
2303
+ if (includeTestNames) {
2304
+ for (const result of results) {
2305
+ if (hasAttemptToFixManagementNotes(result)) {
2306
+ addAttemptToFixResultLine(lines, result)
2307
+ }
2075
2308
  }
2076
2309
  }
2077
2310
 
@@ -2087,13 +2320,67 @@ function formatAttemptToFixSummary (attemptToFixExecutions) {
2087
2320
  `across ${failedResults.length} of ${results.length} test(s).`,
2088
2321
  ]
2089
2322
 
2090
- for (const result of failedResults) {
2091
- addAttemptToFixResultLine(lines, result)
2092
- }
2093
- for (const result of results) {
2094
- if (result.failedCount === 0 && hasAttemptToFixManagementNotes(result)) {
2323
+ if (includeTestNames) {
2324
+ for (const result of failedResults) {
2095
2325
  addAttemptToFixResultLine(lines, result)
2096
2326
  }
2327
+ for (const result of results) {
2328
+ if (result.failedCount === 0 && hasAttemptToFixManagementNotes(result)) {
2329
+ addAttemptToFixResultLine(lines, result)
2330
+ }
2331
+ }
2332
+ }
2333
+
2334
+ return lines.join('\n')
2335
+ }
2336
+
2337
+ /**
2338
+ * Formats the disabled and quarantined Test Management summary.
2339
+ *
2340
+ * @param {TestManagementExecutions} executions
2341
+ * @param {boolean} [includeTestNames]
2342
+ * @returns {string}
2343
+ */
2344
+ function formatTestManagementSummary (executions, includeTestNames = true) {
2345
+ if (executions.size === 0) return ''
2346
+
2347
+ const results = [...executions.values()]
2348
+ const disabledResults = results.filter(result => result.action === 'disabled')
2349
+ const quarantinedResults = results.filter(result => result.action === 'quarantined')
2350
+ const failedQuarantinedResults = quarantinedResults.filter(result => result.failed)
2351
+ const lines = ['Test Management']
2352
+
2353
+ if (disabledResults.length > 0) {
2354
+ const testLabel = disabledResults.length === 1 ? 'test' : 'tests'
2355
+ lines.push(` Disabled: ${disabledResults.length} ${testLabel} skipped.`)
2356
+ }
2357
+ if (quarantinedResults.length > 0) {
2358
+ const testLabel = quarantinedResults.length === 1 ? 'test' : 'tests'
2359
+ if (failedQuarantinedResults.length > 0) {
2360
+ const failureLabel = failedQuarantinedResults.length === 1 ? 'failure' : 'failures'
2361
+ lines.push(
2362
+ ` Quarantined: ${quarantinedResults.length} ${testLabel} run; ` +
2363
+ `${failedQuarantinedResults.length} ${failureLabel} did not affect the test session.`
2364
+ )
2365
+ } else {
2366
+ lines.push(` Quarantined: ${quarantinedResults.length} ${testLabel} run; all passed.`)
2367
+ }
2368
+ }
2369
+
2370
+ if (includeTestNames) {
2371
+ lines.push('', ' Affected tests:')
2372
+ results.sort((a, b) => a.name.localeCompare(b.name))
2373
+ for (const result of results) {
2374
+ const status = result.action === 'disabled'
2375
+ ? 'disabled'
2376
+ : result.failed ? 'quarantined, failed' : 'quarantined, passed'
2377
+ lines.push(
2378
+ ` • [${status}] ${truncateTestOptimizationNameMiddle(
2379
+ sanitizeTestOptimizationName(result.name),
2380
+ MAX_TEST_OPTIMIZATION_NAME_LENGTH
2381
+ )}`
2382
+ )
2383
+ }
2097
2384
  }
2098
2385
 
2099
2386
  return lines.join('\n')
@@ -2124,25 +2411,57 @@ function formatDynamicNamesSummary (newTestsWithDynamicNames) {
2124
2411
  *
2125
2412
  * @param {{
2126
2413
  * attemptToFixExecutions?: AttemptToFixExecutions,
2414
+ * testManagementExecutions?: TestManagementExecutions,
2127
2415
  * newTestsWithDynamicNames?: Set<string>,
2128
2416
  * extraSections?: string[]
2129
2417
  * }} summary
2130
2418
  */
2131
2419
  function logTestOptimizationSummary (summary) {
2132
- const { attemptToFixExecutions, newTestsWithDynamicNames, extraSections = [] } = summary
2420
+ const {
2421
+ attemptToFixExecutions,
2422
+ testManagementExecutions: executions = testManagementExecutions,
2423
+ newTestsWithDynamicNames,
2424
+ extraSections = [],
2425
+ } = summary
2133
2426
  const sections = []
2134
- const attemptToFixSummary = attemptToFixExecutions
2135
- ? formatAttemptToFixSummary(attemptToFixExecutions)
2427
+ const isTestManagementReportEnabled =
2428
+ getValueFromEnvSources('DD_TEST_MANAGEMENT_REPORT_ENABLED') !== false
2429
+ const testManagementTestCount = (attemptToFixExecutions?.size || 0) + executions.size
2430
+ const includeTestNames = testManagementTestCount <= MAX_TEST_MANAGEMENT_REPORT_ITEMS
2431
+ const attemptToFixSummary = isTestManagementReportEnabled && attemptToFixExecutions
2432
+ ? formatAttemptToFixSummary(attemptToFixExecutions, includeTestNames)
2433
+ : ''
2434
+ const testManagementSummary = isTestManagementReportEnabled
2435
+ ? formatTestManagementSummary(executions, includeTestNames)
2136
2436
  : ''
2137
2437
  const dynamicNamesSummary = newTestsWithDynamicNames
2138
2438
  ? formatDynamicNamesSummary(newTestsWithDynamicNames)
2139
2439
  : ''
2140
2440
 
2141
2441
  if (attemptToFixSummary) sections.push(attemptToFixSummary)
2442
+ if (testManagementSummary) sections.push(testManagementSummary)
2443
+ if (attemptToFixSummary || testManagementSummary) {
2444
+ if (!includeTestNames) {
2445
+ sections.push(`Individual test names omitted because ${testManagementTestCount} tests were affected.`)
2446
+ }
2447
+ sections.push('Hide this report: DD_TEST_MANAGEMENT_REPORT_ENABLED=false')
2448
+ }
2142
2449
  sections.push(...extraSections.filter(Boolean))
2143
2450
  if (dynamicNamesSummary) sections.push(dynamicNamesSummary)
2144
2451
 
2145
- if (sections.length === 0) return
2452
+ if (sections.length === 0) {
2453
+ executions.clear()
2454
+ if (executions !== testManagementExecutions) {
2455
+ testManagementExecutions.clear()
2456
+ }
2457
+ if (attemptToFixExecutions) {
2458
+ attemptToFixExecutions.clear()
2459
+ }
2460
+ if (newTestsWithDynamicNames) {
2461
+ newTestsWithDynamicNames.clear()
2462
+ }
2463
+ return
2464
+ }
2146
2465
 
2147
2466
  const line = '-'.repeat(50)
2148
2467
  // eslint-disable-next-line no-console -- Intentional user-facing session summary
@@ -2151,6 +2470,10 @@ function logTestOptimizationSummary (summary) {
2151
2470
  if (attemptToFixExecutions) {
2152
2471
  attemptToFixExecutions.clear()
2153
2472
  }
2473
+ executions.clear()
2474
+ if (executions !== testManagementExecutions) {
2475
+ testManagementExecutions.clear()
2476
+ }
2154
2477
  if (newTestsWithDynamicNames) {
2155
2478
  newTestsWithDynamicNames.clear()
2156
2479
  }