dd-trace 6.4.0 → 6.6.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 (185) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +11 -8
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +31 -19
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
  65. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  66. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  68. package/packages/datadog-instrumentations/src/jest.js +44 -17
  69. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  70. package/packages/datadog-instrumentations/src/mocha/main.js +79 -32
  71. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  72. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  73. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  74. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  75. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  76. package/packages/datadog-instrumentations/src/next.js +24 -0
  77. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  78. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  79. package/packages/datadog-instrumentations/src/pg.js +2 -2
  80. package/packages/datadog-instrumentations/src/playwright.js +162 -46
  81. package/packages/datadog-instrumentations/src/process.js +3 -0
  82. package/packages/datadog-instrumentations/src/router.js +18 -8
  83. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  84. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +3 -3
  85. package/packages/datadog-instrumentations/src/vitest-main.js +14 -28
  86. package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
  87. package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
  88. package/packages/datadog-instrumentations/src/ws.js +2 -1
  89. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  90. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  91. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  92. package/packages/datadog-plugin-cucumber/src/index.js +3 -2
  93. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  94. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  95. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  96. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  97. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  98. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  99. package/packages/datadog-plugin-http2/src/client.js +1 -1
  100. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  101. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  102. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  103. package/packages/datadog-plugin-mocha/src/index.js +2 -1
  104. package/packages/datadog-plugin-playwright/src/index.js +181 -65
  105. package/packages/datadog-plugin-router/src/index.js +11 -2
  106. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  107. package/packages/datadog-plugin-vitest/src/index.js +5 -5
  108. package/packages/datadog-plugin-ws/src/close.js +2 -1
  109. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  110. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  111. package/packages/datadog-plugin-ws/src/server.js +2 -1
  112. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  114. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  115. package/packages/dd-trace/src/azure_metadata.js +2 -1
  116. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  117. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  122. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  124. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  127. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  128. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  129. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  131. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  132. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  133. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  134. package/packages/dd-trace/src/config/helper.js +1 -0
  135. package/packages/dd-trace/src/config/index.js +10 -0
  136. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  137. package/packages/dd-trace/src/exporter.js +2 -0
  138. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  140. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  141. package/packages/dd-trace/src/feature-registry.js +10 -3
  142. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  143. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  144. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  145. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  146. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  147. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  148. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  149. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  150. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  151. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  152. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  153. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  154. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  155. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  156. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  157. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  158. package/packages/dd-trace/src/llmobs/util.js +32 -0
  159. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  160. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  161. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  162. package/packages/dd-trace/src/openfeature/index.js +0 -2
  163. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  164. package/packages/dd-trace/src/openfeature/register.js +16 -27
  165. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  166. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  167. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  168. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  169. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  170. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  171. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  172. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  173. package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
  174. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
  175. package/packages/dd-trace/src/proxy.js +84 -7
  176. package/packages/dd-trace/src/span_processor.js +5 -0
  177. package/packages/dd-trace/src/standalone/index.js +0 -14
  178. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  179. package/packages/dd-trace/src/util.js +26 -0
  180. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  181. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  182. package/vendor/dist/protobufjs/index.js +1 -1
  183. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  184. package/vendor/dist/shell-quote/index.js +1 -1
  185. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -4,27 +4,12 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
 
6
6
  const {
7
- TEST_IS_RUM_ACTIVE,
7
+ setRumTestCorrelation,
8
8
  TEST_BROWSER_DRIVER,
9
9
  TEST_BROWSER_DRIVER_VERSION,
10
10
  TEST_BROWSER_NAME,
11
- TEST_BROWSER_VERSION,
12
11
  TEST_TYPE,
13
12
  } = require('../../dd-trace/src/plugins/util/test')
14
- const { SPAN_TYPE } = require('../../../ext/tags')
15
-
16
- function isTestSpan (span) {
17
- return span.context().getTag(SPAN_TYPE) === 'test'
18
- }
19
-
20
- function getTestSpanFromTrace (trace) {
21
- for (const span of trace.started) {
22
- if (isTestSpan(span)) {
23
- return span
24
- }
25
- }
26
- return null
27
- }
28
13
 
29
14
  class SeleniumPlugin extends CiPlugin {
30
15
  static id = 'selenium'
@@ -32,32 +17,16 @@ class SeleniumPlugin extends CiPlugin {
32
17
  constructor (...args) {
33
18
  super(...args)
34
19
 
35
- this.addSub('ci:selenium:driver:get', ({
36
- setTraceId,
37
- seleniumVersion,
38
- browserName,
39
- browserVersion,
40
- isRumActive,
41
- }) => {
42
- const store = storage('legacy').getStore()
43
- const span = store?.span
44
- if (!span) {
45
- return
46
- }
47
- const testSpan = isTestSpan(span) ? span : getTestSpanFromTrace(span.context()._trace)
20
+ this.addSub('ci:selenium:driver:get', (ctx) => {
21
+ const { seleniumVersion, browserName } = ctx
22
+ const activeSpan = storage('legacy').getStore()?.span
23
+ const testSpan = setRumTestCorrelation(ctx, activeSpan)
48
24
  if (!testSpan) {
49
25
  return
50
26
  }
51
- if (setTraceId) {
52
- setTraceId(testSpan.context().toTraceId())
53
- }
54
- if (isRumActive) {
55
- testSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
56
- }
57
27
  testSpan.setTag(TEST_BROWSER_DRIVER, 'selenium')
58
28
  testSpan.setTag(TEST_BROWSER_DRIVER_VERSION, seleniumVersion)
59
29
  testSpan.setTag(TEST_BROWSER_NAME, browserName)
60
- testSpan.setTag(TEST_BROWSER_VERSION, browserVersion)
61
30
  testSpan.setTag(TEST_TYPE, 'browser')
62
31
  })
63
32
  }
@@ -400,7 +400,7 @@ class VitestPlugin extends CiPlugin {
400
400
  return ctx.currentStore
401
401
  })
402
402
 
403
- this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, deferFlush, onFinish }) => {
403
+ this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, deferFlush, onDone }) => {
404
404
  if (testSuiteSpan) {
405
405
  testSuiteSpan.setTag(TEST_STATUS, status)
406
406
  testSuiteSpan.finish()
@@ -408,10 +408,10 @@ class VitestPlugin extends CiPlugin {
408
408
  }
409
409
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
410
410
  if (deferFlush) {
411
- onFinish()
411
+ onDone()
412
412
  return
413
413
  }
414
- this.tracer._exporter.flush(onFinish)
414
+ this.tracer._exporter.flush(onDone)
415
415
  if (this.runningTestProbe) {
416
416
  this.removeDiProbe(this.runningTestProbe)
417
417
  }
@@ -442,7 +442,7 @@ class VitestPlugin extends CiPlugin {
442
442
  requestErrorTags,
443
443
  vitestPool,
444
444
  isVitestNoWorkerInitActive,
445
- onFinish,
445
+ onDone,
446
446
  }) => {
447
447
  for (const [tag, value] of Object.entries(requestErrorTags || {})) {
448
448
  this.testSessionSpan.setTag(tag, value)
@@ -481,7 +481,7 @@ class VitestPlugin extends CiPlugin {
481
481
  provider: this.ciProviderName,
482
482
  autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
483
483
  })
484
- this.tracer._exporter.flush(onFinish)
484
+ this.tracer._exporter.flush(onDone)
485
485
  })
486
486
 
487
487
  this.addSub('ci:vitest:coverage-report', ({ rootDir, onDone }) => {
@@ -6,6 +6,7 @@ const {
6
6
  SPAN_POINTER_DIRECTION,
7
7
  SPAN_POINTER_DIRECTION_NAME,
8
8
  } = require('../../dd-trace/src/constants')
9
+ const { getSegment } = require('../../dd-trace/src/util')
9
10
  const {
10
11
  incrementWebSocketCounter,
11
12
  buildWebSocketSpanPointerHash,
@@ -29,7 +30,7 @@ class WSClosePlugin extends TracingPlugin {
29
30
 
30
31
  const spanKind = isPeerClose ? 'consumer' : 'producer'
31
32
  const spanTags = socket.spanTags
32
- const path = spanTags['resource.name'].split(' ')[1]
33
+ const path = getSegment(spanTags['resource.name'], ' ', 1)
33
34
  const service = this.serviceName({ pluginConfig: this.config })
34
35
  const span = this.startSpan(this.operationName(), {
35
36
  service,
@@ -6,6 +6,7 @@ const {
6
6
  SPAN_POINTER_DIRECTION,
7
7
  SPAN_POINTER_DIRECTION_NAME,
8
8
  } = require('../../dd-trace/src/constants')
9
+ const { getSegment } = require('../../dd-trace/src/util')
9
10
  const {
10
11
  incrementWebSocketCounter,
11
12
  buildWebSocketSpanPointerHash,
@@ -23,7 +24,7 @@ class WSProducerPlugin extends TracingPlugin {
23
24
  if (!socket.spanContext) return
24
25
 
25
26
  const spanTags = socket.spanTags
26
- const path = spanTags['resource.name'].split(' ')[1]
27
+ const path = getSegment(spanTags['resource.name'], ' ', 1)
27
28
  const opCode = binary ? 'binary' : 'text'
28
29
  const service = this.serviceName({ pluginConfig: this.config })
29
30
  const span = this.startSpan(this.operationName(), {
@@ -6,6 +6,7 @@ const {
6
6
  SPAN_POINTER_DIRECTION,
7
7
  SPAN_POINTER_DIRECTION_NAME,
8
8
  } = require('../../dd-trace/src/constants')
9
+ const { getSegment } = require('../../dd-trace/src/util')
9
10
  const {
10
11
  incrementWebSocketCounter,
11
12
  buildWebSocketSpanPointerHash,
@@ -28,7 +29,7 @@ class WSReceiverPlugin extends TracingPlugin {
28
29
  if (!socket.spanContext) return
29
30
 
30
31
  const spanTags = socket.spanTags
31
- const path = spanTags['resource.name'].split(' ')[1]
32
+ const path = getSegment(spanTags['resource.name'], ' ', 1)
32
33
  const opCode = binary ? 'binary' : 'text'
33
34
 
34
35
  const service = this.serviceName({ pluginConfig: this.config })
@@ -3,6 +3,7 @@
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing.js')
4
4
  const tags = require('../../../ext/tags.js')
5
5
  const { HTTP_HEADERS } = require('../../../ext/formats')
6
+ const { getSegment } = require('../../dd-trace/src/util')
6
7
  const {
7
8
  createWebSocketSpanContext,
8
9
  hasTraceHeaders,
@@ -94,7 +95,7 @@ function getRequestProtocol (req, fallback = 'ws') {
94
95
 
95
96
  // 2. Check for a trusted header set by a proxy
96
97
  if (req.headers && req.headers['x-forwarded-proto']) {
97
- const proto = req.headers['x-forwarded-proto'].split(',')[0].trim()
98
+ const proto = getSegment(req.headers['x-forwarded-proto'], ',', 0).trim()
98
99
  if (proto === 'https') return 'wss'
99
100
  if (proto === 'http') return 'ws'
100
101
  }
@@ -230,7 +230,7 @@ class AIGuard extends NoopAIGuard {
230
230
  // still applies for SDK callers that don't supply an explicit parent.
231
231
  const traceOpts = childOf ? { childOf } : {}
232
232
  return this.#tracer.trace(TAGS.RESOURCE, traceOpts, async (span) => {
233
- const last = messages[messages.length - 1]
233
+ const last = messages.at(-1)
234
234
  const target = this.#isToolCall(last) ? 'tool' : 'prompt'
235
235
  span.setTag(TAGS.TARGET_TAG_KEY, target)
236
236
  if (target === 'tool') {
@@ -190,7 +190,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
190
190
  }
191
191
 
192
192
  addURLParsingSubscriptions () {
193
- const urlResultTaintedProperties = ['host', 'origin', 'hostname']
193
+ const urlResultTaintedProperties = new Set(['host', 'origin', 'hostname'])
194
194
  this.addSub(
195
195
  { channelName: 'datadog:url:parse:finish' },
196
196
  ({ input, base, parsed, isURL }) => {
@@ -212,7 +212,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
212
212
  this.addSub(
213
213
  { channelName: 'datadog:url:getter:finish' },
214
214
  (context) => {
215
- if (!urlResultTaintedProperties.includes(context.property)) return
215
+ if (!urlResultTaintedProperties.has(context.property)) return
216
216
 
217
217
  const origRange = this._taintedURLs.get(context.urlObject)
218
218
  if (!origRange) return
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { getSegment } = require('../../../util')
4
+
3
5
  function addMetricsToSpan (rootSpan, metrics, tagPrefix) {
4
6
  if (!rootSpan?.addTags || !metrics) return
5
7
 
@@ -44,7 +46,7 @@ function filterTags (tags) {
44
46
  }
45
47
 
46
48
  function processTagValue (tags) {
47
- return tags.map(tag => tag.includes(':') ? tag.split(':')[1] : tag)
49
+ return tags.map(tag => tag.includes(':') ? getSegment(tag, ':', 1) : tag)
48
50
  .join('_').replaceAll('.', '_')
49
51
  }
50
52
 
@@ -6,10 +6,11 @@ const os = require('os')
6
6
  const getConfig = require('./config')
7
7
  const { getEnvironmentVariable } = require('./config/helper')
8
8
  const { getIsAzureFunction } = require('./serverless')
9
+ const { getSegment } = require('./util')
9
10
 
10
11
  function extractSubscriptionID (ownerName) {
11
12
  if (ownerName !== undefined) {
12
- const subId = ownerName.split('+')[0].trim()
13
+ const subId = getSegment(ownerName, '+', 0).trim()
13
14
  if (subId.length > 0) {
14
15
  return subId
15
16
  }
@@ -7,11 +7,25 @@ const log = require('../../log')
7
7
  const { getEnvironmentVariables } = require('../../config/helper')
8
8
  const getDebuggerConfig = require('../../debugger/config')
9
9
 
10
- const probeIdToResolveBreakpointSet = new Map()
11
- const probeIdToResolveBreakpointRemove = new Map()
12
10
  const drainRequestIdToResolveBreakpointHit = new Map()
13
11
 
12
+ /**
13
+ * @typedef {object} ProbeState
14
+ * @property {string} locationKey
15
+ * @property {(breakpoint: object) => void} onHitBreakpoint
16
+ * @property {Promise<void>|undefined} removePromise
17
+ * @property {(() => void)|undefined} resolveRemove
18
+ * @property {(() => void)|undefined} resolveSet
19
+ * @property {Promise<void>} setPromise
20
+ * @property {boolean} setPosted
21
+ */
22
+
14
23
  class TestVisDynamicInstrumentation {
24
+ /** @type {Map<string, Promise<void>>} */
25
+ #pendingProbeRemovalByLocation = new Map()
26
+ /** @type {Map<string, ProbeState>} */
27
+ #probeStateById = new Map()
28
+
15
29
  /**
16
30
  * @param {import('../../config/config-base')} config - Tracer configuration
17
31
  */
@@ -24,37 +38,88 @@ class TestVisDynamicInstrumentation {
24
38
  this.breakpointSetChannel = new MessageChannel()
25
39
  this.breakpointHitChannel = new MessageChannel()
26
40
  this.breakpointRemoveChannel = new MessageChannel()
27
- this.onHitBreakpointByProbeId = new Map()
28
41
  }
29
42
 
43
+ /**
44
+ * @param {string|undefined} probeId
45
+ */
30
46
  removeProbe (probeId) {
31
- return new Promise(resolve => {
32
- this.breakpointRemoveChannel.port2.postMessage(probeId)
47
+ const probeState = probeId === undefined ? undefined : this.#probeStateById.get(probeId)
48
+ if (!probeState) return Promise.resolve()
49
+ if (probeState.removePromise) return probeState.removePromise
50
+
51
+ if (!probeState.setPosted) {
52
+ this.#probeStateById.delete(probeId)
53
+ probeState.resolveSet?.()
54
+ probeState.resolveSet = undefined
55
+ return Promise.resolve()
56
+ }
33
57
 
34
- probeIdToResolveBreakpointRemove.set(probeId, resolve)
58
+ const postRemoval = () => new Promise(resolve => {
59
+ probeState.resolveRemove = resolve
60
+ this.breakpointRemoveChannel.port2.postMessage(probeId)
61
+ })
62
+ const removeAcknowledgedPromise = probeState.setPromise.then(postRemoval)
63
+ const removePromise = removeAcknowledgedPromise.then(() => this.waitForInFlightBreakpointHits())
64
+ probeState.removePromise = removePromise
65
+ this.#pendingProbeRemovalByLocation.set(probeState.locationKey, removeAcknowledgedPromise)
66
+ removeAcknowledgedPromise.then(() => {
67
+ if (this.#pendingProbeRemovalByLocation.get(probeState.locationKey) === removeAcknowledgedPromise) {
68
+ this.#pendingProbeRemovalByLocation.delete(probeState.locationKey)
69
+ }
35
70
  })
71
+ removePromise.then(() => {
72
+ if (this.#probeStateById.get(probeId) === probeState) {
73
+ this.#probeStateById.delete(probeId)
74
+ }
75
+ })
76
+ return removePromise
36
77
  }
37
78
 
38
- // Return 2 elements:
39
- // 1. Probe ID
40
- // 2. Promise that's resolved when the breakpoint is set
79
+ /**
80
+ * @param {{ file: string, line: number }} location
81
+ * @param {(breakpoint: object) => void} onHitBreakpoint
82
+ */
41
83
  addLineProbe ({ file, line }, onHitBreakpoint) {
42
84
  if (!this.worker) { // not init yet
43
85
  this.start()
44
86
  }
45
87
  const probeId = randomUUID()
88
+ const locationKey = `${file}:${line}`
89
+ const pendingRemoval = this.#pendingProbeRemovalByLocation.get(locationKey)
46
90
 
47
- this.breakpointSetChannel.port2.postMessage(
48
- { id: probeId, file, line }
49
- )
50
-
51
- this.onHitBreakpointByProbeId.set(probeId, onHitBreakpoint)
91
+ let resolveSet
92
+ const setProbePromise = new Promise(resolve => {
93
+ resolveSet = resolve
94
+ })
95
+ const probeState = {
96
+ locationKey,
97
+ onHitBreakpoint,
98
+ removePromise: undefined,
99
+ resolveRemove: undefined,
100
+ resolveSet,
101
+ setPromise: setProbePromise,
102
+ setPosted: false,
103
+ }
104
+ this.#probeStateById.set(probeId, probeState)
105
+
106
+ const setProbe = () => {
107
+ if (this.#probeStateById.get(probeId) === probeState) {
108
+ probeState.setPosted = true
109
+ this.breakpointSetChannel.port2.postMessage(
110
+ { id: probeId, file, line }
111
+ )
112
+ }
113
+ }
114
+ if (pendingRemoval) {
115
+ pendingRemoval.then(setProbe)
116
+ } else {
117
+ setProbe()
118
+ }
52
119
 
53
120
  return [
54
121
  probeId,
55
- new Promise(resolve => {
56
- probeIdToResolveBreakpointSet.set(probeId, resolve)
57
- }),
122
+ setProbePromise,
58
123
  ]
59
124
  }
60
125
 
@@ -139,10 +204,10 @@ class TestVisDynamicInstrumentation {
139
204
  this.worker.unref?.()
140
205
 
141
206
  this.breakpointSetChannel.port2.on('message', (probeId) => {
142
- const resolve = probeIdToResolveBreakpointSet.get(probeId)
143
- if (resolve) {
144
- resolve()
145
- probeIdToResolveBreakpointSet.delete(probeId)
207
+ const probeState = this.#probeStateById.get(probeId)
208
+ if (probeState?.resolveSet) {
209
+ probeState.resolveSet()
210
+ probeState.resolveSet = undefined
146
211
  }
147
212
  }).unref?.()
148
213
 
@@ -157,19 +222,19 @@ class TestVisDynamicInstrumentation {
157
222
  }
158
223
 
159
224
  const { probe: { id: probeId } } = snapshot
160
- const onHit = this.onHitBreakpointByProbeId.get(probeId)
161
- if (onHit) {
162
- onHit({ snapshot })
225
+ const probeState = this.#probeStateById.get(probeId)
226
+ if (probeState) {
227
+ probeState.onHitBreakpoint({ snapshot })
163
228
  } else {
164
229
  log.warn('Received a breakpoint hit for an unknown probe')
165
230
  }
166
231
  }).unref?.()
167
232
 
168
233
  this.breakpointRemoveChannel.port2.on('message', (probeId) => {
169
- const resolve = probeIdToResolveBreakpointRemove.get(probeId)
170
- if (resolve) {
171
- resolve()
172
- probeIdToResolveBreakpointRemove.delete(probeId)
234
+ const probeState = this.#probeStateById.get(probeId)
235
+ if (probeState?.resolveRemove) {
236
+ probeState.resolveRemove()
237
+ probeState.resolveRemove = undefined
173
238
  }
174
239
  }).unref?.()
175
240
  }
@@ -60,7 +60,7 @@ function parseJsonResponse (rawJson) {
60
60
  function parseKnownTestsResponse (rawJson, options = {}) {
61
61
  const parsedResponse = parseJsonResponse(rawJson)
62
62
  if (options.validateRequiredFields) {
63
- validateKnownTestsResponse(parsedResponse)
63
+ validateKnownTestsResponse(parsedResponse, options)
64
64
  }
65
65
  const { data: { attributes: { tests } } } = parsedResponse
66
66
  return tests
@@ -0,0 +1,160 @@
1
+ 'use strict'
2
+
3
+ /* eslint-disable eslint-rules/eslint-process-env */
4
+
5
+ const TestOptimizationHttpCache = require('../../test-optimization-http-cache').TestOptimizationHttpCache
6
+ const CiVisibilityExporter = require('../ci-visibility-exporter')
7
+ const { CiValidationSink } = require('./sink')
8
+ const CiValidationWriter = require('./writer')
9
+
10
+ const VALIDATION_MANIFEST_ENV = '_DD_TEST_OPTIMIZATION_VALIDATION_MANIFEST_FILE'
11
+ const VALIDATION_OUTPUT_ENV = '_DD_TEST_OPTIMIZATION_VALIDATION_OUTPUT_DIR'
12
+ const VALIDATION_CAPTURE_MODE_ENV = '_DD_TEST_OPTIMIZATION_VALIDATION_CAPTURE_MODE'
13
+
14
+ class CiValidationExporter extends CiVisibilityExporter {
15
+ /**
16
+ * Creates an immediately available cache-only Test Optimization exporter.
17
+ *
18
+ * @param {object} config tracer configuration
19
+ */
20
+ constructor (config) {
21
+ const validationManifestPath = process.env[VALIDATION_MANIFEST_ENV]
22
+ const validationOutputRoot = process.env[VALIDATION_OUTPUT_ENV]
23
+ if (!validationManifestPath) {
24
+ throw new Error('Offline Test Optimization validation requires an explicit private manifest path.')
25
+ }
26
+ if (!validationOutputRoot) {
27
+ throw new Error('Offline Test Optimization validation requires an explicit private output root.')
28
+ }
29
+ const cache = new TestOptimizationHttpCache({
30
+ validationManifestPath,
31
+ })
32
+ super(config, { cacheOnly: true, testOptimizationHttpCache: cache })
33
+
34
+ this._sink = new CiValidationSink(validationOutputRoot, {
35
+ captureMode: process.env[VALIDATION_CAPTURE_MODE_ENV] || 'strict',
36
+ })
37
+ this._writer = new CiValidationWriter({ sink: this._sink, tags: config.tags })
38
+ this._isInitialized = true
39
+ this._isGzipCompatible = false
40
+ this._resolveCanUseCiVisProtocol(true)
41
+ this._resolveGit()
42
+ this.exportUncodedTraces()
43
+
44
+ this._finalizeValidation = () => this.flush(() => this._sink.writeSummary())
45
+ globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(this._finalizeValidation)
46
+ process.once('exit', this._finalizeValidation)
47
+ }
48
+
49
+ /**
50
+ * Loads library settings from the validator-controlled cache.
51
+ *
52
+ * @param {object} testConfiguration test configuration identity
53
+ * @param {Function} callback completion callback
54
+ */
55
+ getLibraryConfiguration (testConfiguration, callback) {
56
+ super.getLibraryConfiguration(testConfiguration, (err, configuration) => {
57
+ this._sink.writeInputResult('settings', err)
58
+ if (err) process.exitCode = 1
59
+ callback(err, configuration)
60
+ })
61
+ }
62
+
63
+ /**
64
+ * Loads known tests from the validator-controlled cache.
65
+ *
66
+ * @param {object} testConfiguration test configuration identity
67
+ * @param {Function} callback completion callback
68
+ */
69
+ getKnownTests (testConfiguration, callback) {
70
+ super.getKnownTests(testConfiguration, (err, tests) => {
71
+ this._sink.writeInputResult('known_tests', err)
72
+ if (err) process.exitCode = 1
73
+ callback(err, tests)
74
+ })
75
+ }
76
+
77
+ /**
78
+ * Loads skippable suites from the validator-controlled cache.
79
+ *
80
+ * @param {object} testConfiguration test configuration identity
81
+ * @param {Function} callback completion callback
82
+ */
83
+ getSkippableSuites (testConfiguration, callback) {
84
+ super.getSkippableSuites(testConfiguration, (err, suites, correlationId, coverage) => {
85
+ this._sink.writeInputResult('skippable_tests', err)
86
+ if (err) process.exitCode = 1
87
+ callback(err, suites, correlationId, coverage)
88
+ })
89
+ }
90
+
91
+ /**
92
+ * Loads managed tests from the validator-controlled cache.
93
+ *
94
+ * @param {object} testConfiguration test configuration identity
95
+ * @param {Function} callback completion callback
96
+ */
97
+ getTestManagementTests (testConfiguration, callback) {
98
+ super.getTestManagementTests(testConfiguration, (err, tests) => {
99
+ this._sink.writeInputResult('test_management', err)
100
+ if (err) process.exitCode = 1
101
+ callback(err, tests)
102
+ })
103
+ }
104
+
105
+ /**
106
+ * Resolves the inherited git-upload gate without performing an upload.
107
+ *
108
+ * @returns {void}
109
+ */
110
+ sendGitMetadata () {
111
+ this._resolveGit()
112
+ }
113
+
114
+ /**
115
+ * Drops debugger logs in offline validation mode.
116
+ *
117
+ * @returns {void}
118
+ */
119
+ exportDiLogs () {}
120
+
121
+ /**
122
+ * Reports that code coverage is outside the offline validator's scope.
123
+ *
124
+ * @returns {boolean} always false
125
+ */
126
+ canReportCodeCoverage () {
127
+ return false
128
+ }
129
+
130
+ /**
131
+ * Rejects coverage report upload in offline validation mode.
132
+ *
133
+ * @param {object} options ignored upload options
134
+ * @param {Function} callback completion callback
135
+ */
136
+ uploadCoverageReport (options, callback) {
137
+ callback(new Error('Coverage report upload is disabled during offline Test Optimization validation.'))
138
+ }
139
+
140
+ /**
141
+ * Reports that screenshot upload is unavailable.
142
+ *
143
+ * @returns {boolean} always false
144
+ */
145
+ canUploadTestScreenshots () {
146
+ return false
147
+ }
148
+
149
+ /**
150
+ * Rejects screenshot upload in offline validation mode.
151
+ *
152
+ * @param {object} options ignored upload options
153
+ * @param {Function} callback completion callback
154
+ */
155
+ uploadTestScreenshot (options, callback) {
156
+ callback(new Error('Screenshot upload is disabled during offline Test Optimization validation.'))
157
+ }
158
+ }
159
+
160
+ module.exports = CiValidationExporter