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
@@ -22,18 +22,27 @@ function parseJsonResponse (rawJson) {
22
22
 
23
23
  function parseSkippableSuitesResponse (
24
24
  rawJson,
25
- { testLevel = 'suite', isCoverageReportUploadEnabled = false, validateRequiredFields = false } = {}
25
+ {
26
+ testLevel = 'suite',
27
+ isCoverageReportUploadEnabled = false,
28
+ validateRequiredFields = false,
29
+ validationMode = false,
30
+ } = {}
26
31
  ) {
27
32
  const parsedResponse = parseJsonResponse(rawJson)
28
33
  if (validateRequiredFields) {
29
- validateSkippableTestsResponse(parsedResponse)
34
+ validateSkippableTestsResponse(parsedResponse, { validationMode })
35
+ }
36
+ const coverage = {}
37
+ for (const [filename, bitmap] of Object.entries(parsedResponse.meta?.coverage || {})) {
38
+ coverage[filename.replaceAll('\\', '/')] = bitmap
30
39
  }
31
- const coverage = parsedResponse.meta?.coverage || {}
32
40
 
33
41
  const skippableItems = parsedResponse
34
42
  .data
35
43
  .filter(({ type }) => type === testLevel)
36
44
  const skippableSuites = []
45
+ let numExcludedByMissingLineCoverage = 0
37
46
  for (const {
38
47
  attributes: {
39
48
  suite,
@@ -42,13 +51,58 @@ function parseSkippableSuitesResponse (
42
51
  },
43
52
  } of skippableItems) {
44
53
  // Only reject candidates without backend line coverage when we need that coverage to backfill reports.
45
- if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage) continue
54
+ if (isCoverageReportUploadEnabled && isMissingLineCodeCoverage) {
55
+ numExcludedByMissingLineCoverage++
56
+ continue
57
+ }
46
58
 
47
59
  skippableSuites.push(testLevel === 'suite' ? suite : { suite, name })
48
60
  }
49
61
  const correlationId = parsedResponse.meta?.correlation_id
50
62
 
51
- return { skippableSuites, correlationId, coverage }
63
+ return {
64
+ skippableSuites,
65
+ correlationId,
66
+ coverage,
67
+ numReceivedSkippableItems: skippableItems.length,
68
+ numExcludedByMissingLineCoverage,
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Logs how missing line coverage affected the skippable candidates without logging their contents.
74
+ *
75
+ * @param {{
76
+ * skippableSuites: Array<string|{suite: string, name: string}>,
77
+ * numReceivedSkippableItems?: number,
78
+ * numExcludedByMissingLineCoverage?: number
79
+ * }} result - Parsed skippable response.
80
+ * @param {'suite'|'test'} testLevel - Test optimization skipping level.
81
+ * @returns {void}
82
+ */
83
+ function logSkippableSuitesResponse (result, testLevel) {
84
+ if (result.numReceivedSkippableItems === undefined) {
85
+ log.debug('Number of received skippable %ss: %d', testLevel, result.skippableSuites.length)
86
+ return
87
+ }
88
+
89
+ log.debug(
90
+ 'Received %d skippable %s candidates; excluded %d because line coverage is missing; %d remain.',
91
+ result.numReceivedSkippableItems,
92
+ testLevel,
93
+ result.numExcludedByMissingLineCoverage,
94
+ result.skippableSuites.length
95
+ )
96
+ if (
97
+ result.numReceivedSkippableItems > 0 &&
98
+ result.numExcludedByMissingLineCoverage === result.numReceivedSkippableItems
99
+ ) {
100
+ log.warn(
101
+ 'All %d skippable %s candidates were excluded: coverage upload is enabled but line coverage is missing.',
102
+ result.numReceivedSkippableItems,
103
+ testLevel
104
+ )
105
+ }
52
106
  }
53
107
 
54
108
  function getSkippableSuites ({
@@ -96,6 +150,7 @@ function getSkippableSuites ({
96
150
  }, cb)
97
151
  }, (err, data) => {
98
152
  if (err) return done(err)
153
+ logSkippableSuitesResponse(data, testLevel)
99
154
  done(null, data.skippableSuites, data.correlationId, data.coverage)
100
155
  })
101
156
  }
@@ -203,6 +258,7 @@ function fetchFromApi ({
203
258
  const result = parseSkippableSuitesResponse(parsedResponse, {
204
259
  testLevel,
205
260
  isCoverageReportUploadEnabled,
261
+ validateRequiredFields: true,
206
262
  })
207
263
  const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
208
264
  incrementCountMetric(
@@ -213,7 +269,6 @@ function fetchFromApi ({
213
269
  skippableItems.length
214
270
  )
215
271
  distributionMetric(TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES, {}, res.length)
216
- log.debug('Number of received skippable %ss:', testLevel, result.skippableSuites.length)
217
272
 
218
273
  writeToCache(cacheKey, result)
219
274
 
@@ -225,4 +280,4 @@ function fetchFromApi ({
225
280
  })
226
281
  }
227
282
 
228
- module.exports = { getSkippableSuites, parseSkippableSuitesResponse }
283
+ module.exports = { getSkippableSuites, logSkippableSuitesResponse, parseSkippableSuitesResponse }
@@ -26,7 +26,7 @@ function parseJsonResponse (rawJson) {
26
26
  function parseLibraryConfigurationResponse (rawJson, config = getConfig(), options = {}) {
27
27
  const parsedResponse = parseJsonResponse(rawJson)
28
28
  if (options.validateRequiredFields) {
29
- validateSettingsResponse(parsedResponse)
29
+ validateSettingsResponse(parsedResponse, options)
30
30
  }
31
31
  const {
32
32
  code_coverage: isCodeCoverageEnabled,
@@ -49,7 +49,7 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
49
49
  requireGit,
50
50
  isEarlyFlakeDetectionEnabled: isKnownTestsEnabled && (earlyFlakeDetectionConfig?.enabled ?? false),
51
51
  earlyFlakeDetectionNumRetries:
52
- earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] || DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
52
+ earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] ?? DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
53
53
  earlyFlakeDetectionSlowTestRetries:
54
54
  earlyFlakeDetectionConfig?.slow_test_retries ?? DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
55
55
  earlyFlakeDetectionFaultyThreshold:
@@ -74,6 +74,13 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
74
74
  settings.isSuitesSkippingEnabled = true
75
75
  log.debug('Dangerously set test skipping to true')
76
76
  }
77
+ if (
78
+ settings.isCoverageReportUploadEnabled &&
79
+ !config.testOptimization.DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED
80
+ ) {
81
+ settings.isCoverageReportUploadEnabled = false
82
+ log.debug('Code coverage report upload was disabled by the environment variable')
83
+ }
77
84
 
78
85
  return settings
79
86
  }
@@ -24,14 +24,15 @@ const UPLOAD_TIMEOUT_MS = 30_000
24
24
  * @param {object} options - Upload options
25
25
  * @param {string} options.filePath - Path to the coverage report file
26
26
  * @param {string} options.format - Format of the coverage report (e.g., 'lcov', 'cobertura')
27
+ * @param {string[]} [options.flags] - Optional coverage report grouping flags
27
28
  * @param {object} options.testEnvironmentMetadata - Test environment metadata containing git/CI tags
28
29
  * @param {URL} options.url - The base URL for the coverage report upload
29
30
  * @param {boolean} [options.isEvpProxy] - Whether to use EVP proxy for the upload
30
31
  * @param {string} [options.evpProxyPrefix] - The EVP proxy prefix (e.g., '/evp_proxy/v4')
31
- * @param {Function} callback - Callback function (err)
32
+ * @param {(error: Error|null) => void} callback - Callback function
32
33
  */
33
34
  function uploadCoverageReport (
34
- { filePath, format, testEnvironmentMetadata, url, isEvpProxy, evpProxyPrefix },
35
+ { filePath, format, flags, testEnvironmentMetadata, url, isEvpProxy, evpProxyPrefix },
35
36
  callback
36
37
  ) {
37
38
  const { DD_API_KEY } = getConfig()
@@ -54,6 +55,9 @@ function uploadCoverageReport (
54
55
  format,
55
56
  ...testEnvironmentMetadata,
56
57
  }
58
+ if (flags?.length) {
59
+ eventPayload['report.flags'] = flags
60
+ }
57
61
 
58
62
  // Create multipart form
59
63
  const form = new FormData()
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const RUM_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
4
+
5
+ module.exports = {
6
+ RUM_TEST_EXECUTION_ID_COOKIE_NAME,
7
+ }
@@ -28,12 +28,16 @@ class TestApiManualPlugin extends CiPlugin {
28
28
  const store = legacyStorage.getStore()
29
29
  const testSpan = store && store.span
30
30
  if (testSpan) {
31
+ const previousStore = testSpan._store === undefined
32
+ ? undefined
33
+ : legacyStorage.getStore(testSpan._store)
31
34
  testSpan.setTag(TEST_STATUS, status)
32
35
  if (error) {
33
36
  testSpan.setTag('error', error)
34
37
  }
35
38
  testSpan.finish()
36
39
  finishAllTraceSpans(testSpan)
40
+ legacyStorage.enterWith(previousStore)
37
41
  }
38
42
  })
39
43
  this.unconfiguredAddSub('dd-trace:ci:manual:test:addTags', (tags) => {
@@ -47,7 +47,7 @@ function parseJsonResponse (rawJson) {
47
47
  function parseTestManagementTestsResponse (rawJson, options = {}) {
48
48
  const parsedResponse = parseJsonResponse(rawJson)
49
49
  if (options.validateRequiredFields) {
50
- validateTestManagementTestsResponse(parsedResponse)
50
+ validateTestManagementTestsResponse(parsedResponse, options)
51
51
  }
52
52
  const { data: { attributes: { modules: testManagementTests } } } = parsedResponse
53
53
  return testManagementTests
@@ -13,6 +13,25 @@ const REQUIRED_SETTINGS_KEYS = [
13
13
  'impacted_tests_enabled',
14
14
  'coverage_report_upload_enabled',
15
15
  ]
16
+ const SETTINGS_BOOLEAN_KEYS = [
17
+ 'code_coverage',
18
+ 'tests_skipping',
19
+ 'itr_enabled',
20
+ 'require_git',
21
+ 'flaky_test_retries_enabled',
22
+ 'di_enabled',
23
+ 'known_tests_enabled',
24
+ 'impacted_tests_enabled',
25
+ 'coverage_report_upload_enabled',
26
+ ]
27
+ const EARLY_FLAKE_DETECTION_KEYS = ['enabled', 'faulty_session_threshold', 'slow_test_retries']
28
+ const TEST_MANAGEMENT_KEYS = ['attempt_to_fix_retries', 'enabled']
29
+ const RETRY_THRESHOLD_PATTERN = /^\d+(?:ms|s|m|h)$/
30
+ const MAX_VALIDATION_MODULES = 1000
31
+ const MAX_VALIDATION_SUITES = 10_000
32
+ const MAX_VALIDATION_TESTS = 100_000
33
+ const MAX_VALIDATION_RETRIES = 100
34
+ const MAX_VALIDATION_STRING_BYTES = 4096
16
35
 
17
36
  function isObject (value) {
18
37
  return value !== null && typeof value === 'object' && !Array.isArray(value)
@@ -38,7 +57,7 @@ function getAttributes (response, endpoint) {
38
57
  return attributes
39
58
  }
40
59
 
41
- function validateSettingsResponse (response) {
60
+ function validateSettingsResponse (response, options = {}) {
42
61
  const attributes = response?.data?.attributes ?? response
43
62
  assertObject(attributes, 'Invalid settings response: attributes must be an object')
44
63
  assertHasKeys(attributes, REQUIRED_SETTINGS_KEYS, 'settings')
@@ -46,9 +65,27 @@ function validateSettingsResponse (response) {
46
65
  assertObject(attributes.test_management, 'Invalid settings response: test_management must be an object')
47
66
  assertHasKeys(attributes.early_flake_detection, ['enabled'], 'settings early_flake_detection')
48
67
  assertHasKeys(attributes.test_management, ['enabled'], 'settings test_management')
68
+ if (!options.validationMode) return
69
+
70
+ assertOnlyKeys(attributes, REQUIRED_SETTINGS_KEYS, 'settings')
71
+ assertOnlyKeys(attributes.early_flake_detection, EARLY_FLAKE_DETECTION_KEYS, 'settings early_flake_detection')
72
+ assertOnlyKeys(attributes.test_management, TEST_MANAGEMENT_KEYS, 'settings test_management')
73
+ assertBooleanFields(attributes, SETTINGS_BOOLEAN_KEYS, 'settings')
74
+ assertBooleanFields(attributes.early_flake_detection, ['enabled'], 'settings early_flake_detection')
75
+ assertBooleanFields(attributes.test_management, ['enabled'], 'settings test_management')
76
+ assertOptionalBoundedNumber(
77
+ attributes.early_flake_detection.faulty_session_threshold,
78
+ 'settings early_flake_detection faulty_session_threshold'
79
+ )
80
+ assertRetryMap(attributes.early_flake_detection.slow_test_retries)
81
+ assertOptionalBoundedNumber(
82
+ attributes.test_management.attempt_to_fix_retries,
83
+ 'settings test_management attempt_to_fix_retries',
84
+ { integer: true }
85
+ )
49
86
  }
50
87
 
51
- function validateKnownTestsResponse (response) {
88
+ function validateKnownTestsResponse (response, options = {}) {
52
89
  const attributes = getAttributes(response, 'known tests')
53
90
  assertHasKeys(attributes, ['tests'], 'known tests')
54
91
 
@@ -56,26 +93,46 @@ function validateKnownTestsResponse (response) {
56
93
  if (tests === null) return
57
94
  assertObject(tests, 'Invalid known tests response: tests must be an object or null')
58
95
 
59
- for (const suites of Object.values(tests)) {
96
+ let suiteCount = 0
97
+ let testCount = 0
98
+ const modules = Object.entries(tests)
99
+ if (options.validationMode && modules.length > MAX_VALIDATION_MODULES) {
100
+ throw new Error('Invalid known tests response: too many modules')
101
+ }
102
+ for (const [moduleName, suites] of modules) {
103
+ if (options.validationMode) assertValidationString(moduleName, 'known tests module')
60
104
  assertObject(suites, 'Invalid known tests response: module suites must be objects')
61
- for (const testNames of Object.values(suites)) {
105
+ for (const [suiteName, testNames] of Object.entries(suites)) {
106
+ suiteCount++
107
+ if (options.validationMode) {
108
+ if (suiteCount > MAX_VALIDATION_SUITES) throw new Error('Invalid known tests response: too many suites')
109
+ assertValidationString(suiteName, 'known tests suite')
110
+ }
62
111
  if (!Array.isArray(testNames)) {
63
112
  throw new TypeError('Invalid known tests response: suite tests must be arrays')
64
113
  }
65
114
  for (const testName of testNames) {
115
+ testCount++
66
116
  if (typeof testName !== 'string') {
67
117
  throw new TypeError('Invalid known tests response: test names must be strings')
68
118
  }
119
+ if (options.validationMode) {
120
+ if (testCount > MAX_VALIDATION_TESTS) throw new Error('Invalid known tests response: too many tests')
121
+ assertValidationString(testName, 'known test name')
122
+ }
69
123
  }
70
124
  }
71
125
  }
72
126
  }
73
127
 
74
- function validateSkippableTestsResponse (response) {
128
+ function validateSkippableTestsResponse (response, options = {}) {
75
129
  assertObject(response, 'Invalid skippable tests response: response must be an object')
76
130
  if (!Array.isArray(response.data)) {
77
131
  throw new TypeError('Invalid skippable tests response: data must be an array')
78
132
  }
133
+ if (options.validationMode && response.data.length > MAX_VALIDATION_TESTS) {
134
+ throw new Error('Invalid skippable tests response: too many items')
135
+ }
79
136
  if (response.meta !== undefined) {
80
137
  assertObject(response.meta, 'Invalid skippable tests response: meta must be an object')
81
138
  }
@@ -91,28 +148,56 @@ function validateSkippableTestsResponse (response) {
91
148
  if (typeof item.type !== 'string') {
92
149
  throw new TypeError('Invalid skippable tests response: data entry type must be a string')
93
150
  }
151
+ if (options.validationMode) assertValidationString(item.type, 'skippable test type')
94
152
  assertObject(item.attributes, 'Invalid skippable tests response: data entry attributes must be an object')
95
153
  if ((item.type === 'suite' || item.type === 'test') && typeof item.attributes.suite !== 'string') {
96
154
  throw new Error('Invalid skippable tests response: data entry suite must be a string')
97
155
  }
156
+ if (options.validationMode && item.attributes.suite !== undefined) {
157
+ assertValidationString(item.attributes.suite, 'skippable test suite')
158
+ }
98
159
  if (item.type === 'test' && typeof item.attributes.name !== 'string') {
99
160
  throw new Error('Invalid skippable tests response: data entry name must be a string')
100
161
  }
162
+ if (options.validationMode && item.attributes.name !== undefined) {
163
+ assertValidationString(item.attributes.name, 'skippable test name')
164
+ }
101
165
  }
102
166
  }
103
167
 
104
- function validateTestManagementTestsResponse (response) {
168
+ function validateTestManagementTestsResponse (response, options = {}) {
105
169
  const attributes = getAttributes(response, 'test management tests')
106
170
  assertHasKeys(attributes, ['modules'], 'test management tests')
107
171
  assertObject(attributes.modules, 'Invalid test management tests response: modules must be an object')
108
172
 
109
- for (const testModule of Object.values(attributes.modules)) {
173
+ let suiteCount = 0
174
+ let testCount = 0
175
+ const modules = Object.entries(attributes.modules)
176
+ if (options.validationMode && modules.length > MAX_VALIDATION_MODULES) {
177
+ throw new Error('Invalid test management tests response: too many modules')
178
+ }
179
+ for (const [moduleName, testModule] of modules) {
180
+ if (options.validationMode) assertValidationString(moduleName, 'test management module')
110
181
  assertObject(testModule, 'Invalid test management tests response: modules entries must be objects')
111
182
  assertObject(testModule.suites, 'Invalid test management tests response: module suites must be objects')
112
- for (const suite of Object.values(testModule.suites)) {
183
+ for (const [suiteName, suite] of Object.entries(testModule.suites)) {
184
+ suiteCount++
185
+ if (options.validationMode) {
186
+ if (suiteCount > MAX_VALIDATION_SUITES) {
187
+ throw new Error('Invalid test management tests response: too many suites')
188
+ }
189
+ assertValidationString(suiteName, 'test management suite')
190
+ }
113
191
  assertObject(suite, 'Invalid test management tests response: suites entries must be objects')
114
192
  assertObject(suite.tests, 'Invalid test management tests response: suite tests must be objects')
115
- for (const test of Object.values(suite.tests)) {
193
+ for (const [testName, test] of Object.entries(suite.tests)) {
194
+ testCount++
195
+ if (options.validationMode) {
196
+ if (testCount > MAX_VALIDATION_TESTS) {
197
+ throw new Error('Invalid test management tests response: too many tests')
198
+ }
199
+ assertValidationString(testName, 'test management test')
200
+ }
116
201
  assertObject(test, 'Invalid test management tests response: tests entries must be objects')
117
202
  assertObject(test.properties, 'Invalid test management tests response: test properties must be objects')
118
203
  }
@@ -120,6 +205,49 @@ function validateTestManagementTestsResponse (response) {
120
205
  }
121
206
  }
122
207
 
208
+ function assertBooleanFields (object, keys, endpoint) {
209
+ for (const key of keys) {
210
+ if (typeof object[key] !== 'boolean') {
211
+ throw new TypeError(`Invalid ${endpoint} response: ${key} must be a boolean`)
212
+ }
213
+ }
214
+ }
215
+
216
+ function assertOptionalBoundedNumber (value, endpoint, { integer = false } = {}) {
217
+ if (value === undefined) return
218
+ if (!Number.isFinite(value) || (integer && !Number.isInteger(value)) ||
219
+ value < 0 || value > MAX_VALIDATION_RETRIES) {
220
+ throw new TypeError(`Invalid ${endpoint}: value must be between 0 and ${MAX_VALIDATION_RETRIES}`)
221
+ }
222
+ }
223
+
224
+ function assertRetryMap (retries) {
225
+ if (retries === undefined) return
226
+ assertObject(retries, 'Invalid settings response: early_flake_detection slow_test_retries must be an object')
227
+ for (const [threshold, count] of Object.entries(retries)) {
228
+ assertValidationString(threshold, 'early flake detection retry threshold')
229
+ if (!RETRY_THRESHOLD_PATTERN.test(threshold)) {
230
+ throw new TypeError('Invalid early flake detection retry threshold: expected a duration such as 5s')
231
+ }
232
+ assertOptionalBoundedNumber(count, `early flake detection retry count ${threshold}`, { integer: true })
233
+ }
234
+ }
235
+
236
+ function assertOnlyKeys (object, keys, endpoint) {
237
+ const allowed = new Set(keys)
238
+ for (const key of Object.keys(object)) {
239
+ if (!allowed.has(key)) {
240
+ throw new Error(`Invalid ${endpoint} response: unexpected ${key}`)
241
+ }
242
+ }
243
+ }
244
+
245
+ function assertValidationString (value, field) {
246
+ if (typeof value !== 'string' || Buffer.byteLength(value) > MAX_VALIDATION_STRING_BYTES) {
247
+ throw new TypeError(`Invalid ${field}: value must be a bounded string`)
248
+ }
249
+ }
250
+
123
251
  module.exports = {
124
252
  validateKnownTestsResponse,
125
253
  validateSettingsResponse,