dd-trace 5.109.0 → 5.111.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 (208) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +25 -5
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +79 -24
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +154 -47
  8. package/package.json +20 -16
  9. package/packages/datadog-esbuild/index.js +26 -0
  10. package/packages/datadog-esbuild/src/utils.js +46 -3
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  13. package/packages/datadog-instrumentations/src/fs.js +8 -6
  14. package/packages/datadog-instrumentations/src/graphql.js +26 -484
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  17. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  19. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  23. package/packages/datadog-instrumentations/src/koa.js +3 -2
  24. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  25. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  26. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  27. package/packages/datadog-instrumentations/src/next.js +10 -10
  28. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  29. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  30. package/packages/datadog-instrumentations/src/restify.js +4 -3
  31. package/packages/datadog-instrumentations/src/router.js +18 -5
  32. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  33. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  34. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  35. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  36. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  37. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  38. package/packages/datadog-instrumentations/src/vitest.js +11 -1598
  39. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  45. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  46. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -5
  48. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  49. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  50. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  52. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  53. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  54. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  55. package/packages/datadog-plugin-http/src/client.js +8 -3
  56. package/packages/datadog-plugin-http/src/index.js +6 -8
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-jest/src/index.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  60. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  61. package/packages/datadog-plugin-openai/src/services.js +2 -2
  62. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  63. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  64. package/packages/datadog-plugin-undici/src/index.js +5 -2
  65. package/packages/datadog-plugin-vitest/src/index.js +76 -64
  66. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  67. package/packages/datadog-webpack/index.js +17 -1
  68. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  69. package/packages/dd-trace/src/aiguard/sdk.js +15 -3
  70. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  71. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  72. package/packages/dd-trace/src/appsec/channels.js +3 -1
  73. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  74. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  75. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  76. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  77. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  78. package/packages/dd-trace/src/appsec/index.js +20 -7
  79. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  80. package/packages/dd-trace/src/appsec/reporter.js +50 -10
  81. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  82. package/packages/dd-trace/src/appsec/telemetry/index.js +27 -1
  83. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  84. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  85. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  86. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  87. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  90. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  91. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  92. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  94. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  95. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  96. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  97. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  98. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  99. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  100. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  101. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  102. package/packages/dd-trace/src/config/defaults.js +25 -5
  103. package/packages/dd-trace/src/config/generated-config-types.d.ts +596 -58
  104. package/packages/dd-trace/src/config/helper.js +134 -33
  105. package/packages/dd-trace/src/config/index.js +39 -23
  106. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  107. package/packages/dd-trace/src/config/parsers.js +19 -1
  108. package/packages/dd-trace/src/config/remote_config.js +1 -1
  109. package/packages/dd-trace/src/config/supported-configurations.json +109 -57
  110. package/packages/dd-trace/src/constants.js +7 -0
  111. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  112. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  113. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  114. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  116. package/packages/dd-trace/src/debugger/index.js +5 -0
  117. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  118. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  119. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  120. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  122. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  123. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  124. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  125. package/packages/dd-trace/src/guardrails/index.js +8 -5
  126. package/packages/dd-trace/src/index.js +6 -4
  127. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  128. package/packages/dd-trace/src/llmobs/index.js +17 -6
  129. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  130. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  131. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/sdk.js +8 -6
  134. package/packages/dd-trace/src/llmobs/span_processor.js +5 -1
  135. package/packages/dd-trace/src/llmobs/tagger.js +58 -3
  136. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  137. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  138. package/packages/dd-trace/src/log/index.js +18 -20
  139. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  140. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  141. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  142. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  143. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  145. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  146. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  147. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  148. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  149. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  150. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  151. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  152. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +44 -38
  153. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  154. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  155. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  156. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  157. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  158. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  159. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  160. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  161. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  162. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  163. package/packages/dd-trace/src/plugin_manager.js +14 -8
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -4
  165. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  166. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  167. package/packages/dd-trace/src/plugins/util/test.js +91 -9
  168. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  169. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  170. package/packages/dd-trace/src/priority_sampler.js +1 -13
  171. package/packages/dd-trace/src/profiling/config.js +129 -198
  172. package/packages/dd-trace/src/profiling/constants.js +4 -7
  173. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  174. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  175. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  176. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  177. package/packages/dd-trace/src/profiling/oom.js +71 -0
  178. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  179. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  180. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  181. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  182. package/packages/dd-trace/src/proxy.js +8 -8
  183. package/packages/dd-trace/src/ritm.js +4 -2
  184. package/packages/dd-trace/src/serverless.js +1 -2
  185. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  186. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  187. package/packages/dd-trace/src/span_format.js +2 -1
  188. package/packages/dd-trace/src/span_processor.js +11 -6
  189. package/packages/dd-trace/src/span_stats.js +96 -78
  190. package/packages/dd-trace/src/startup-log.js +2 -1
  191. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  192. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  193. package/packages/dd-trace/src/telemetry/index.js +2 -2
  194. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  195. package/packages/dd-trace/src/telemetry/send-data.js +20 -18
  196. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  197. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  198. package/packages/dd-trace/src/tracer.js +15 -1
  199. package/packages/dd-trace/src/util.js +14 -0
  200. package/register.js +58 -1
  201. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  202. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  203. package/vendor/dist/pprof-format/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-plugin-graphql/src/resolve.js +0 -170
  208. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -34,9 +34,9 @@ function uploadCoverageReport (
34
34
  { filePath, format, testEnvironmentMetadata, url, isEvpProxy, evpProxyPrefix },
35
35
  callback
36
36
  ) {
37
- const apiKey = getConfig().apiKey
37
+ const { DD_API_KEY } = getConfig()
38
38
 
39
- if (!apiKey && !isEvpProxy) {
39
+ if (!DD_API_KEY && !isEvpProxy) {
40
40
  return callback(new Error('DD_API_KEY is required for coverage report upload'))
41
41
  }
42
42
 
@@ -82,7 +82,7 @@ function uploadCoverageReport (
82
82
  options.headers['X-Datadog-EVP-Subdomain'] = 'ci-intake'
83
83
  } else {
84
84
  options.path = '/api/v2/cicovreprt'
85
- options.headers['dd-api-key'] = apiKey
85
+ options.headers['dd-api-key'] = DD_API_KEY
86
86
  }
87
87
 
88
88
  log.debug('Uploading coverage report %s to %s%s', filePath, url, options.path)
@@ -16,6 +16,7 @@ const {
16
16
  } = require('../telemetry')
17
17
 
18
18
  const { buildCacheKey, writeToCache, withCache } = require('../requests/fs-cache')
19
+ const { validateTestManagementTestsResponse } = require('../test-optimization-http-cache-schema')
19
20
 
20
21
  // Calculate the number of tests from the test management tests response, which has a shape like:
21
22
  // { module: { suites: { suite: { tests: { testName: { properties: {...} } } } } } }
@@ -39,6 +40,19 @@ function getNumFromTestManagementTests (testManagementTests) {
39
40
  return totalNumTests
40
41
  }
41
42
 
43
+ function parseJsonResponse (rawJson) {
44
+ return typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson
45
+ }
46
+
47
+ function parseTestManagementTestsResponse (rawJson, options = {}) {
48
+ const parsedResponse = parseJsonResponse(rawJson)
49
+ if (options.validateRequiredFields) {
50
+ validateTestManagementTestsResponse(parsedResponse)
51
+ }
52
+ const { data: { attributes: { modules: testManagementTests } } } = parsedResponse
53
+ return testManagementTests
54
+ }
55
+
42
56
  function getTestManagementTests ({
43
57
  url,
44
58
  isEvpProxy,
@@ -121,12 +135,12 @@ function fetchFromApi ({
121
135
  options.path = `${evpProxyPrefix}/api/v2/test/libraries/test-management/tests`
122
136
  options.headers['X-Datadog-EVP-Subdomain'] = 'api'
123
137
  } else {
124
- const { apiKey } = getConfig()
125
- if (!apiKey) {
138
+ const { DD_API_KEY } = getConfig()
139
+ if (!DD_API_KEY) {
126
140
  return done(new Error('Test management tests were not fetched because Datadog API key is not defined.'))
127
141
  }
128
142
 
129
- options.headers['dd-api-key'] = apiKey
143
+ options.headers['dd-api-key'] = DD_API_KEY
130
144
  }
131
145
 
132
146
  const data = JSON.stringify({
@@ -155,7 +169,7 @@ function fetchFromApi ({
155
169
  done(err)
156
170
  } else {
157
171
  try {
158
- const { data: { attributes: { modules: testManagementTests } } } = JSON.parse(res)
172
+ const testManagementTests = parseTestManagementTestsResponse(res)
159
173
 
160
174
  const numTests = getNumFromTestManagementTests(testManagementTests)
161
175
 
@@ -174,4 +188,8 @@ function fetchFromApi ({
174
188
  })
175
189
  }
176
190
 
177
- module.exports = { getTestManagementTests }
191
+ module.exports = {
192
+ getNumFromTestManagementTests, // Exported for later use in the cache
193
+ getTestManagementTests,
194
+ parseTestManagementTestsResponse,
195
+ }
@@ -0,0 +1,128 @@
1
+ 'use strict'
2
+
3
+ const REQUIRED_SETTINGS_KEYS = [
4
+ 'code_coverage',
5
+ 'tests_skipping',
6
+ 'itr_enabled',
7
+ 'require_git',
8
+ 'early_flake_detection',
9
+ 'flaky_test_retries_enabled',
10
+ 'di_enabled',
11
+ 'known_tests_enabled',
12
+ 'test_management',
13
+ 'impacted_tests_enabled',
14
+ 'coverage_report_upload_enabled',
15
+ ]
16
+
17
+ function isObject (value) {
18
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
19
+ }
20
+
21
+ function assertObject (value, message) {
22
+ if (!isObject(value)) {
23
+ throw new Error(message)
24
+ }
25
+ }
26
+
27
+ function assertHasKeys (value, keys, endpoint) {
28
+ for (const key of keys) {
29
+ if (!Object.hasOwn(value, key)) {
30
+ throw new Error(`Invalid ${endpoint} response: missing ${key}`)
31
+ }
32
+ }
33
+ }
34
+
35
+ function getAttributes (response, endpoint) {
36
+ const attributes = response?.data?.attributes
37
+ assertObject(attributes, `Invalid ${endpoint} response: attributes must be an object`)
38
+ return attributes
39
+ }
40
+
41
+ function validateSettingsResponse (response) {
42
+ const attributes = response?.data?.attributes ?? response
43
+ assertObject(attributes, 'Invalid settings response: attributes must be an object')
44
+ assertHasKeys(attributes, REQUIRED_SETTINGS_KEYS, 'settings')
45
+ assertObject(attributes.early_flake_detection, 'Invalid settings response: early_flake_detection must be an object')
46
+ assertObject(attributes.test_management, 'Invalid settings response: test_management must be an object')
47
+ assertHasKeys(attributes.early_flake_detection, ['enabled'], 'settings early_flake_detection')
48
+ assertHasKeys(attributes.test_management, ['enabled'], 'settings test_management')
49
+ }
50
+
51
+ function validateKnownTestsResponse (response) {
52
+ const attributes = getAttributes(response, 'known tests')
53
+ assertHasKeys(attributes, ['tests'], 'known tests')
54
+
55
+ const { tests } = attributes
56
+ if (tests === null) return
57
+ assertObject(tests, 'Invalid known tests response: tests must be an object or null')
58
+
59
+ for (const suites of Object.values(tests)) {
60
+ assertObject(suites, 'Invalid known tests response: module suites must be objects')
61
+ for (const testNames of Object.values(suites)) {
62
+ if (!Array.isArray(testNames)) {
63
+ throw new TypeError('Invalid known tests response: suite tests must be arrays')
64
+ }
65
+ for (const testName of testNames) {
66
+ if (typeof testName !== 'string') {
67
+ throw new TypeError('Invalid known tests response: test names must be strings')
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ function validateSkippableTestsResponse (response) {
75
+ assertObject(response, 'Invalid skippable tests response: response must be an object')
76
+ if (!Array.isArray(response.data)) {
77
+ throw new TypeError('Invalid skippable tests response: data must be an array')
78
+ }
79
+ if (response.meta !== undefined) {
80
+ assertObject(response.meta, 'Invalid skippable tests response: meta must be an object')
81
+ }
82
+ if (response.meta?.coverage !== undefined) {
83
+ assertObject(response.meta.coverage, 'Invalid skippable tests response: meta.coverage must be an object')
84
+ }
85
+ if (response.meta?.correlation_id !== undefined && typeof response.meta.correlation_id !== 'string') {
86
+ throw new Error('Invalid skippable tests response: meta.correlation_id must be a string')
87
+ }
88
+
89
+ for (const item of response.data) {
90
+ assertObject(item, 'Invalid skippable tests response: data entries must be objects')
91
+ if (typeof item.type !== 'string') {
92
+ throw new TypeError('Invalid skippable tests response: data entry type must be a string')
93
+ }
94
+ assertObject(item.attributes, 'Invalid skippable tests response: data entry attributes must be an object')
95
+ if ((item.type === 'suite' || item.type === 'test') && typeof item.attributes.suite !== 'string') {
96
+ throw new Error('Invalid skippable tests response: data entry suite must be a string')
97
+ }
98
+ if (item.type === 'test' && typeof item.attributes.name !== 'string') {
99
+ throw new Error('Invalid skippable tests response: data entry name must be a string')
100
+ }
101
+ }
102
+ }
103
+
104
+ function validateTestManagementTestsResponse (response) {
105
+ const attributes = getAttributes(response, 'test management tests')
106
+ assertHasKeys(attributes, ['modules'], 'test management tests')
107
+ assertObject(attributes.modules, 'Invalid test management tests response: modules must be an object')
108
+
109
+ for (const testModule of Object.values(attributes.modules)) {
110
+ assertObject(testModule, 'Invalid test management tests response: modules entries must be objects')
111
+ assertObject(testModule.suites, 'Invalid test management tests response: module suites must be objects')
112
+ for (const suite of Object.values(testModule.suites)) {
113
+ assertObject(suite, 'Invalid test management tests response: suites entries must be objects')
114
+ assertObject(suite.tests, 'Invalid test management tests response: suite tests must be objects')
115
+ for (const test of Object.values(suite.tests)) {
116
+ assertObject(test, 'Invalid test management tests response: tests entries must be objects')
117
+ assertObject(test.properties, 'Invalid test management tests response: test properties must be objects')
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ module.exports = {
124
+ validateKnownTestsResponse,
125
+ validateSettingsResponse,
126
+ validateSkippableTestsResponse,
127
+ validateTestManagementTestsResponse,
128
+ }
@@ -0,0 +1,287 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+
6
+ const log = require('../log')
7
+ const { getNumFromKnownTests } = require('../plugins/util/test')
8
+ const { parseKnownTestsResponse } = require('./early-flake-detection/get-known-tests')
9
+ const { parseSkippableSuitesResponse } = require('./intelligent-test-runner/get-skippable-suites')
10
+ const { parseLibraryConfigurationResponse } = require('./requests/get-library-configuration')
11
+ const {
12
+ getNumFromTestManagementTests,
13
+ parseTestManagementTestsResponse,
14
+ } = require('./test-management/get-test-management-tests')
15
+ const {
16
+ incrementCountMetric,
17
+ distributionMetric,
18
+ TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE,
19
+ TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS,
20
+ TELEMETRY_KNOWN_TESTS_RESPONSE_BYTES,
21
+ TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_SUITES,
22
+ TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_TESTS,
23
+ TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES,
24
+ TELEMETRY_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
25
+ TELEMETRY_TEST_MANAGEMENT_TESTS_RESPONSE_BYTES,
26
+ } = require('./telemetry')
27
+
28
+ const CACHE_MISS = Symbol('test optimization http cache miss')
29
+
30
+ const PLAN_FOLDER = '.testoptimization'
31
+ const MANIFEST_FILE_NAME = 'manifest.txt'
32
+ const SUPPORTED_MANIFEST_VERSION = '1'
33
+ const CACHE_FOLDER_NAME = 'cache'
34
+ const HTTP_CACHE_FOLDER_NAME = 'http'
35
+
36
+ const ENV_MANIFEST_FILE = 'TEST_OPTIMIZATION_MANIFEST_FILE'
37
+ const ENV_MANIFEST_FILE_ALIAS = 'DD_TEST_OPTIMIZATION_MANIFEST_FILE'
38
+ const ENV_RUNFILES_DIR = 'RUNFILES_DIR'
39
+ const ENV_RUNFILES_MANIFEST_FILE = 'RUNFILES_MANIFEST_FILE'
40
+ const ENV_TEST_SRCDIR = 'TEST_SRCDIR'
41
+
42
+ const SETTINGS_FILE_NAME = 'settings.json'
43
+ const KNOWN_TESTS_FILE_NAME = 'known_tests.json'
44
+ const SKIPPABLE_TESTS_FILE_NAME = 'skippable_tests.json'
45
+ const TEST_MANAGEMENT_FILE_NAME = 'test_management.json'
46
+
47
+ const RUNFILES_MANIFEST_SEPARATOR = ' '
48
+
49
+ function parseManifestVersion (content) {
50
+ // Supported just the number version or 'version=x'
51
+ const version = content.replace(/^\uFEFF/, '').trim()
52
+ const match = version.match(/^version=(.+)$/)
53
+ return match ? match[1].trim() : version
54
+ }
55
+
56
+ class TestOptimizationHttpCache {
57
+ constructor ({ cwd = process.cwd(), env } = {}) {
58
+ this._cwd = cwd
59
+ // This cache intentionally consumes env vars that are not tracer config keys. (??)
60
+ // eslint-disable-next-line eslint-rules/eslint-process-env
61
+ this._env = env ?? process.env
62
+ this._manifestPath = this._resolveManifestPath()
63
+ this._testOptimizationPath = undefined
64
+ this._httpCachePath = undefined
65
+ this._available = false
66
+
67
+ this._buildReader()
68
+ }
69
+
70
+ isAvailable () {
71
+ return this._available
72
+ }
73
+
74
+ readSettings () {
75
+ const payload = this._readFile(SETTINGS_FILE_NAME)
76
+ if (payload === CACHE_MISS) {
77
+ this._disable()
78
+ return CACHE_MISS
79
+ }
80
+
81
+ try {
82
+ const settings = parseLibraryConfigurationResponse(payload, undefined, { validateRequiredFields: true })
83
+ incrementCountMetric(TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE, settings)
84
+ return settings
85
+ } catch (err) {
86
+ this._logInvalidCacheFile(SETTINGS_FILE_NAME, err)
87
+ this._disable()
88
+ return CACHE_MISS
89
+ }
90
+ }
91
+
92
+ readKnownTests () {
93
+ const payload = this._readFile(KNOWN_TESTS_FILE_NAME)
94
+ if (payload === CACHE_MISS) return CACHE_MISS
95
+
96
+ try {
97
+ const knownTests = parseKnownTestsResponse(payload, { validateRequiredFields: true })
98
+ distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_TESTS, {}, getNumFromKnownTests(knownTests))
99
+ distributionMetric(TELEMETRY_KNOWN_TESTS_RESPONSE_BYTES, {}, payload.length)
100
+ return knownTests
101
+ } catch (err) {
102
+ this._logInvalidCacheFile(KNOWN_TESTS_FILE_NAME, err)
103
+ return CACHE_MISS
104
+ }
105
+ }
106
+
107
+ readSkippableSuites (options = {}) {
108
+ const payload = this._readFile(SKIPPABLE_TESTS_FILE_NAME)
109
+ if (payload === CACHE_MISS) return CACHE_MISS
110
+
111
+ try {
112
+ const parsedResponse = JSON.parse(payload)
113
+ const result = parseSkippableSuitesResponse(parsedResponse, { ...options, validateRequiredFields: true })
114
+ const testLevel = options.testLevel || 'suite'
115
+ const skippableItems = parsedResponse.data.filter(({ type }) => type === testLevel)
116
+ incrementCountMetric(
117
+ testLevel === 'test'
118
+ ? TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_TESTS
119
+ : TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_SUITES,
120
+ {},
121
+ skippableItems.length
122
+ )
123
+ distributionMetric(TELEMETRY_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES, {}, payload.length)
124
+ return result
125
+ } catch (err) {
126
+ this._logInvalidCacheFile(SKIPPABLE_TESTS_FILE_NAME, err)
127
+ return CACHE_MISS
128
+ }
129
+ }
130
+
131
+ hasValidSkippableSuites (options = {}) {
132
+ const payload = this._readFile(SKIPPABLE_TESTS_FILE_NAME)
133
+ if (payload === CACHE_MISS) return false
134
+
135
+ try {
136
+ parseSkippableSuitesResponse(JSON.parse(payload), { ...options, validateRequiredFields: true })
137
+ return true
138
+ } catch {
139
+ return false
140
+ }
141
+ }
142
+
143
+ readTestManagementTests () {
144
+ const payload = this._readFile(TEST_MANAGEMENT_FILE_NAME)
145
+ if (payload === CACHE_MISS) return CACHE_MISS
146
+
147
+ try {
148
+ const testManagementTests = parseTestManagementTestsResponse(payload, { validateRequiredFields: true })
149
+ distributionMetric(
150
+ TELEMETRY_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
151
+ {},
152
+ getNumFromTestManagementTests(testManagementTests)
153
+ )
154
+ distributionMetric(TELEMETRY_TEST_MANAGEMENT_TESTS_RESPONSE_BYTES, {}, payload.length)
155
+ return testManagementTests
156
+ } catch (err) {
157
+ this._logInvalidCacheFile(TEST_MANAGEMENT_FILE_NAME, err)
158
+ return CACHE_MISS
159
+ }
160
+ }
161
+
162
+ _buildReader () {
163
+ if (!this._manifestPath) {
164
+ log.debug('Test Optimization HTTP cache manifest not found')
165
+ return
166
+ }
167
+
168
+ const version = this._readManifestVersion(this._manifestPath)
169
+ if (version !== SUPPORTED_MANIFEST_VERSION) {
170
+ log.debug('Unsupported Test Optimization HTTP cache manifest version %j at %s', version, this._manifestPath)
171
+ return
172
+ }
173
+
174
+ this._testOptimizationPath = path.dirname(this._manifestPath)
175
+ this._httpCachePath = path.join(
176
+ this._testOptimizationPath,
177
+ CACHE_FOLDER_NAME,
178
+ HTTP_CACHE_FOLDER_NAME
179
+ )
180
+
181
+ const settingsPath = path.join(this._httpCachePath, SETTINGS_FILE_NAME)
182
+ if (!fs.existsSync(settingsPath)) {
183
+ log.debug('Test Optimization HTTP cache settings file not found at %s', settingsPath)
184
+ return
185
+ }
186
+
187
+ log.debug('Test Optimization HTTP cache found at %s', this._httpCachePath)
188
+ this._available = true
189
+ }
190
+
191
+ _resolveManifestPath () {
192
+ const localManifest = path.join(this._cwd, PLAN_FOLDER, MANIFEST_FILE_NAME)
193
+ if (fs.existsSync(localManifest)) {
194
+ return localManifest
195
+ }
196
+
197
+ const manifestFile = this._env[ENV_MANIFEST_FILE] || this._env[ENV_MANIFEST_FILE_ALIAS]
198
+ if (!manifestFile) return
199
+
200
+ const resolvedManifestPath = this._resolveRunfilePath(manifestFile)
201
+ if (fs.existsSync(resolvedManifestPath)) {
202
+ return resolvedManifestPath
203
+ }
204
+ }
205
+
206
+ _resolveRunfilePath (manifestFile) {
207
+ if (fs.existsSync(manifestFile)) {
208
+ return manifestFile
209
+ }
210
+
211
+ const runfilesDir = this._env[ENV_RUNFILES_DIR]
212
+ if (runfilesDir) {
213
+ const candidate = path.join(runfilesDir, manifestFile)
214
+ if (fs.existsSync(candidate)) {
215
+ return candidate
216
+ }
217
+ }
218
+
219
+ const runfilesManifestCandidate = this._resolveRunfilePathFromManifest(manifestFile)
220
+ if (runfilesManifestCandidate) {
221
+ return runfilesManifestCandidate
222
+ }
223
+
224
+ const testSrcdir = this._env[ENV_TEST_SRCDIR]
225
+ if (testSrcdir) {
226
+ const candidate = path.join(testSrcdir, manifestFile)
227
+ if (fs.existsSync(candidate)) {
228
+ return candidate
229
+ }
230
+ }
231
+
232
+ return manifestFile
233
+ }
234
+
235
+ _resolveRunfilePathFromManifest (manifestFile) {
236
+ const runfilesManifest = this._env[ENV_RUNFILES_MANIFEST_FILE]
237
+ if (!runfilesManifest || !fs.existsSync(runfilesManifest)) return
238
+
239
+ try {
240
+ const lines = fs.readFileSync(runfilesManifest, 'utf8').split('\n')
241
+ for (const line of lines) {
242
+ const separatorIndex = line.indexOf(RUNFILES_MANIFEST_SEPARATOR)
243
+ if (separatorIndex <= 0) continue
244
+ if (line.slice(0, separatorIndex) !== manifestFile) continue
245
+
246
+ const resolvedPath = line.slice(separatorIndex + 1).trim()
247
+ if (resolvedPath) return resolvedPath
248
+ }
249
+ } catch (err) {
250
+ log.debug('Failed to resolve Test Optimization HTTP cache manifest from %s: %s', runfilesManifest, err.message)
251
+ }
252
+ }
253
+
254
+ _readManifestVersion (manifestPath) {
255
+ try {
256
+ return parseManifestVersion(fs.readFileSync(manifestPath, 'utf8'))
257
+ } catch (err) {
258
+ log.debug('Failed to read Test Optimization HTTP cache manifest %s: %s', manifestPath, err.message)
259
+ }
260
+ }
261
+
262
+ _readFile (fileName) {
263
+ if (!this.isAvailable()) return CACHE_MISS
264
+
265
+ const filePath = path.join(this._httpCachePath, fileName)
266
+ try {
267
+ log.debug('Reading Test Optimization HTTP cache file %s', filePath)
268
+ return fs.readFileSync(filePath, 'utf8')
269
+ } catch (err) {
270
+ log.debug('Test Optimization HTTP cache file %s could not be read: %s', filePath, err.message)
271
+ return CACHE_MISS
272
+ }
273
+ }
274
+
275
+ _logInvalidCacheFile (fileName, err) {
276
+ log.debug('Test Optimization HTTP cache file %s could not be parsed: %s', fileName, err.message)
277
+ }
278
+
279
+ _disable () {
280
+ this._available = false
281
+ }
282
+ }
283
+
284
+ module.exports = {
285
+ CACHE_MISS,
286
+ TestOptimizationHttpCache,
287
+ }
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const dns = require('dns')
4
3
  const util = require('util')
5
4
 
6
5
  const { DD_MAJOR } = require('../../../../version')
@@ -51,7 +50,6 @@ const defaults = {
51
50
  isServiceUserProvided: false,
52
51
  plugins: true,
53
52
  isCiVisibility: false,
54
- lookup: dns.lookup,
55
53
  logger: undefined,
56
54
  }
57
55
 
@@ -218,11 +216,20 @@ for (const [canonicalName, entries] of Object.entries(supportedConfigurations))
218
216
  )
219
217
  }
220
218
  for (const entry of entries) {
219
+ // A deprecated entry that only aliases a canonical option must not surface as
220
+ // its own Config property/default: the canonical entry already owns the value
221
+ // and the alias is resolved by helper.js. helper.js drops these from the shared
222
+ // supported-configurations object (after registering the deprecation), but that
223
+ // mutation is order-dependent on which module loads first. Skip here too so
224
+ // `defaults` is identical regardless of load order (it differed in v5, where
225
+ // major-overrides keeps these entries instead of deleting them outright).
226
+ if (entry.deprecated && entry.aliases) continue
221
227
  if (entry.sensitive) {
222
228
  sensitiveConfigurations.add(canonicalName)
223
229
  }
224
- const configurationNames = entry.internalPropertyName ? [entry.internalPropertyName] : entry.configurationNames
225
- const fullPropertyName = configurationNames?.[0] ?? canonicalName
230
+ const fullPropertyName = entry.namespace
231
+ ? `${entry.namespace}.${canonicalName}`
232
+ : (entry.internalPropertyName ?? entry.configurationNames?.[0] ?? canonicalName)
226
233
  const type = entry.type.toUpperCase()
227
234
 
228
235
  let transformer = transformers[entry.transform]
@@ -285,7 +292,7 @@ for (const [canonicalName, entries] of Object.entries(supportedConfigurations))
285
292
 
286
293
  // Replace the alias with the canonical property name.
287
294
  for (const [fullPropertyName, alias] of fallbackConfigurations) {
288
- if (configurationsTable[alias].property) {
295
+ if (configurationsTable[alias]?.property) {
289
296
  fallbackConfigurations.set(fullPropertyName, configurationsTable[alias].property)
290
297
  }
291
298
  }
@@ -347,3 +354,16 @@ module.exports = {
347
354
 
348
355
  generateTelemetry,
349
356
  }
357
+
358
+ // `dns` is instrumented, so requiring it pulls in the dns plugin, which loads
359
+ // `log`, whose bootstrap calls back into `require('./defaults')`. Resolve the
360
+ // `lookup` default last — after the exports above are fully built — so that the
361
+ // re-entrant require during that cascade sees the complete module instead of a
362
+ // half-initialized one.
363
+ defaults.lookup = require('dns').lookup
364
+ configWithOrigin.set('lookupdefault', {
365
+ name: 'lookup',
366
+ value: defaults.lookup,
367
+ origin: 'default',
368
+ seq_id: seqId++,
369
+ })