dd-trace 6.13.0 → 6.14.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 (201) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/package.json +13 -10
  14. package/packages/datadog-esbuild/index.js +3 -1
  15. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  16. package/packages/datadog-instrumentations/src/fs.js +24 -20
  17. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  18. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  19. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  21. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  33. package/packages/datadog-instrumentations/src/jest.js +25 -20
  34. package/packages/datadog-instrumentations/src/knex.js +76 -0
  35. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  36. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  37. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  39. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  40. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  41. package/packages/datadog-instrumentations/src/net.js +14 -17
  42. package/packages/datadog-instrumentations/src/next.js +212 -62
  43. package/packages/datadog-instrumentations/src/openai.js +42 -40
  44. package/packages/datadog-instrumentations/src/pg.js +15 -0
  45. package/packages/datadog-instrumentations/src/playwright.js +109 -13
  46. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  47. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  48. package/packages/datadog-instrumentations/src/undici.js +53 -4
  49. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  50. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  51. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  52. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  53. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  54. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  56. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  57. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  58. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  59. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  60. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  62. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  63. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  64. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  65. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  66. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  67. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  68. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  69. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  70. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  71. package/packages/datadog-plugin-jest/src/index.js +0 -6
  72. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  73. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  74. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  75. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  76. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  77. package/packages/datadog-plugin-next/src/index.js +63 -29
  78. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  79. package/packages/datadog-plugin-undici/src/index.js +27 -41
  80. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  81. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  82. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  83. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  85. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  86. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  87. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  88. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  90. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  91. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  94. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  102. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  103. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  104. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  105. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  106. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  107. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  108. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  109. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  110. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  111. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  112. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  114. package/packages/dd-trace/src/config/index.js +73 -13
  115. package/packages/dd-trace/src/config/parsers.js +11 -1
  116. package/packages/dd-trace/src/config/stable.js +3 -3
  117. package/packages/dd-trace/src/config/supported-configurations.json +20 -2
  118. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  119. package/packages/dd-trace/src/debugger/config.js +12 -1
  120. package/packages/dd-trace/src/debugger/constants.js +1 -0
  121. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  122. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  123. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  124. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  125. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  126. package/packages/dd-trace/src/debugger/index.js +23 -3
  127. package/packages/dd-trace/src/dogstatsd.js +510 -76
  128. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  129. package/packages/dd-trace/src/encode/0.4.js +5 -5
  130. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  131. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  132. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  133. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  134. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  135. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  136. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  137. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  138. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  140. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  141. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  142. package/packages/dd-trace/src/flush.js +6 -3
  143. package/packages/dd-trace/src/id.js +14 -0
  144. package/packages/dd-trace/src/index.js +5 -2
  145. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  146. package/packages/dd-trace/src/llmobs/index.js +32 -32
  147. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  148. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  149. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  150. package/packages/dd-trace/src/llmobs/util.js +0 -33
  151. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  152. package/packages/dd-trace/src/log/index.js +7 -1
  153. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  154. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  155. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  156. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  157. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  158. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  159. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  160. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  161. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  162. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  163. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  164. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
  166. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  167. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  168. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  169. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  170. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  171. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  172. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  173. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  174. package/packages/dd-trace/src/profiling/config.js +1 -0
  175. package/packages/dd-trace/src/profiling/oom.js +7 -2
  176. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  177. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  178. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  179. package/packages/dd-trace/src/proxy.js +75 -5
  180. package/packages/dd-trace/src/remote_config/index.js +319 -101
  181. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  182. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  183. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  184. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  185. package/packages/dd-trace/src/sampling_rule.js +36 -9
  186. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  187. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  188. package/packages/dd-trace/src/serverless.js +10 -3
  189. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  190. package/packages/dd-trace/src/span_stats.js +24 -28
  191. package/packages/dd-trace/src/startup-log.js +3 -2
  192. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  193. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  194. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  195. package/packages/dd-trace/src/tracer.js +4 -5
  196. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  197. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  198. package/vendor/dist/protobufjs/index.js +1 -1
  199. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  200. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  201. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -18,6 +18,8 @@ const {
18
18
  getEfdRetryCountForDuration,
19
19
  hasEfdRetries,
20
20
  } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
21
+ const { FINAL_FLUSH_FALLBACK_DELAY, FINAL_FLUSH_TIMEOUT } =
22
+ require('../../dd-trace/src/ci-visibility/final-flush')
21
23
  const {
22
24
  getCoveredFilesFromCoverage,
23
25
  JEST_WORKER_TRACE_PAYLOAD_CODE,
@@ -95,10 +97,10 @@ const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
95
97
  // https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/types.ts#L37
96
98
  const CHILD_MESSAGE_CALL = 1
97
99
 
98
- // Maximum time we'll wait for the tracer to flush
99
- // The exporter has a 10-second bounded final-flush deadline. Leave enough time
100
- // for its completion callback before Jest's --forceExit fallback takes over.
101
- const FLUSH_TIMEOUT = 12_000
100
+ // Maximum time we'll wait for the tracer to flush.
101
+ // Let the exporter enforce its hard deadline and invoke its completion callback
102
+ // before Jest's --forceExit fallback takes over.
103
+ const FLUSH_TIMEOUT = FINAL_FLUSH_TIMEOUT + FINAL_FLUSH_FALLBACK_DELAY + 5000
102
104
  const JEST_SESSION_STATE = Symbol.for('dd-trace:jest:session')
103
105
  const JEST_BAIL_REPORTER_PATH = require.resolve('./jest/bail-reporter')
104
106
  const DD_JEST_HANDLE_TEST_EVENT_WRAPPED = Symbol('dd-trace:jest:handle-test-event-wrapped')
@@ -342,16 +344,17 @@ function getTestStats (testStatuses) {
342
344
  function formatIgnoredFailuresSummary (ignoredFailures) {
343
345
  if (!ignoredFailures?.efdFailureCount) return ''
344
346
 
345
- const items = ignoredFailures.efdNames.map(text => ({ text, suffix: 'Early Flake Detection' }))
346
-
347
- if (items.length === 0) return ''
348
-
349
- const shown = items.slice(0, MAX_IGNORED_TEST_NAMES)
350
- const more = items.length - shown.length
347
+ const shown = ignoredFailures.efdNames.slice(0, MAX_IGNORED_TEST_NAMES)
348
+ const more = ignoredFailures.efdNames.length - shown.length
351
349
  const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
352
- const formattedItems = shown
353
- .map(({ text, suffix }) => ` ${text}${suffix ? ` (${suffix})` : ''}`)
354
- .join('\n') + moreSuffix
350
+ let formattedItems = ''
351
+ let isFirstItem = true
352
+ for (const text of shown) {
353
+ if (!isFirstItem) formattedItems += '\n'
354
+ formattedItems += ` • ${text} (Early Flake Detection)`
355
+ isFirstItem = false
356
+ }
357
+ formattedItems += moreSuffix
355
358
 
356
359
  return `${ignoredFailures.efdFailureCount} test failure(s) were ignored. Exit code set to 0.\n\n${formattedItems}`
357
360
  }
@@ -764,7 +767,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
764
767
  }
765
768
  return test.call(this, testName, testFn, ...callArgs)
766
769
  }
767
- const eachBind = bind(eachTest).apply(this, eachArgs)
770
+ const boundTest = bind(eachTest)
771
+ const eachBind = boundTest.apply(this, eachArgs)
768
772
  return eachBind.apply(this, testArgs)
769
773
  }
770
774
  }
@@ -877,7 +881,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
877
881
  }
878
882
  return concurrentTest.call(this, testName, testFn, ...callArgs)
879
883
  }
880
- const eachBind = bind(concurrentEachTest, false, needsEachError).apply(this, eachArgs)
884
+ const boundTest = bind(concurrentEachTest, false, needsEachError)
885
+ const eachBind = boundTest.apply(this, eachArgs)
881
886
  return eachBind.apply(this, testArgs)
882
887
  }
883
888
  }
@@ -926,10 +931,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
926
931
 
927
932
  let jestEach
928
933
  try {
929
- jestEach = createRequire(path.join(this.rootDir, 'package.json'))('jest-each')
934
+ const requireFromPath = createRequire(path.join(this.rootDir, 'package.json'))
935
+ jestEach = requireFromPath('jest-each')
930
936
  } catch {
931
937
  try {
932
- jestEach = createRequire(path.join(process.cwd(), 'package.json'))('jest-each')
938
+ const requireFromPath = createRequire(path.join(process.cwd(), 'package.json'))
939
+ jestEach = requireFromPath('jest-each')
933
940
  } catch {
934
941
  jestEach = undefined
935
942
  }
@@ -3067,9 +3074,7 @@ function getCliWrapper (isNewJestVersion) {
3067
3074
  result = await runCLI.apply(this, arguments)
3068
3075
  } catch (error) {
3069
3076
  try {
3070
- await waitForTestSessionFinish(getTestSessionFinishPayload('fail', error, {
3071
- isTestSessionFinalizationError: true,
3072
- }))
3077
+ await waitForTestSessionFinish(getTestSessionFinishPayload('fail', error))
3073
3078
  } catch (finalizationError) {
3074
3079
  log.error('Jest test session finalization error: %s', finalizationError)
3075
3080
  }
@@ -3,6 +3,11 @@
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { addHook, channel } = require('./helpers/instrument')
5
5
  const { wrapThen } = require('./helpers/promise')
6
+ const {
7
+ wrapPoolRelease,
8
+ wrapPromisePoolAcquire,
9
+ wrapPromisePoolQueryMethod,
10
+ } = require('./helpers/pool-acquire')
6
11
 
7
12
  const startRawQueryCh = channel('datadog:knex:raw:start')
8
13
  const rawQuerySubscribes = channel('datadog:knex:raw:subscribes')
@@ -32,6 +37,14 @@ addHook({
32
37
  versions: ['>=2'],
33
38
  file: 'lib/knex-builder/Knex.js',
34
39
  }, Knex => {
40
+ shimmer.wrap(Knex.Client.prototype, 'acquireConnection', acquireConnection => wrapPromisePoolAcquire(
41
+ acquireConnection,
42
+ resolveKnexDriver,
43
+ resolveKnexConfig,
44
+ knexHasIdleConnection
45
+ ))
46
+ shimmer.wrap(Knex.Client.prototype, 'releaseConnection', wrapPoolRelease)
47
+
35
48
  shimmer.wrap(Knex.Client.prototype, 'raw', raw => function (...args) {
36
49
  if (!startRawQueryCh.hasSubscribers) {
37
50
  return raw.apply(this, args)
@@ -77,6 +90,34 @@ addHook({
77
90
  return Knex
78
91
  })
79
92
 
93
+ addHook({
94
+ name: 'knex',
95
+ versions: ['>=2'],
96
+ file: 'lib/execution/runner.js',
97
+ }, Runner => {
98
+ shimmer.wrap(Runner.prototype, 'ensureConnection', ensureConnection => wrapPromisePoolQueryMethod(
99
+ ensureConnection,
100
+ runner => runner.connection || runner.builder?._connection ? undefined : runner.client,
101
+ runner => resolveKnexDriver(runner.client)
102
+ ))
103
+
104
+ return Runner
105
+ })
106
+
107
+ addHook({
108
+ name: 'knex',
109
+ versions: ['>=2'],
110
+ file: 'lib/execution/transaction.js',
111
+ }, Transaction => {
112
+ shimmer.wrap(Transaction.prototype, 'acquireConnection', acquireConnection => wrapPromisePoolQueryMethod(
113
+ acquireConnection,
114
+ (transaction, args) => args[0]?.connection ? undefined : transaction.client,
115
+ transaction => resolveKnexDriver(transaction.client)
116
+ ))
117
+
118
+ return Transaction
119
+ })
120
+
80
121
  function wrapCallbackWithFinish (callback, finish, context) {
81
122
  if (typeof callback !== 'function') return callback
82
123
 
@@ -84,3 +125,38 @@ function wrapCallbackWithFinish (callback, finish, context) {
84
125
  finish(context, () => callback.apply(this, args))
85
126
  })
86
127
  }
128
+
129
+ /**
130
+ * @param {{ pool?: { numFree?: () => number, numPendingAcquires?: () => number } }} client
131
+ * @returns {boolean}
132
+ */
133
+ function knexHasIdleConnection (client) {
134
+ const pool = client.pool
135
+ if (typeof pool?.numFree !== 'function') return false
136
+ return pool.numFree() > pool.numPendingAcquires()
137
+ }
138
+
139
+ /**
140
+ * @param {{ driverName?: string }|undefined} client
141
+ * @returns {'mysql'|'mysql2'|'pg'|undefined}
142
+ */
143
+ function resolveKnexDriver (client) {
144
+ switch (client?.driverName) {
145
+ case 'mysql':
146
+ return 'mysql'
147
+ case 'mysql2':
148
+ return 'mysql2'
149
+ case 'pg':
150
+ case 'cockroachdb':
151
+ case 'pg-redshift':
152
+ return 'pg'
153
+ }
154
+ }
155
+
156
+ /**
157
+ * @param {{ connectionSettings?: Record<string, unknown> }} client
158
+ * @returns {Record<string, unknown>}
159
+ */
160
+ function resolveKnexConfig (client) {
161
+ return client.connectionSettings ?? {}
162
+ }
@@ -11,6 +11,7 @@ const {
11
11
  runOutsidePoolQueryAcquire,
12
12
  runPoolAcquireError,
13
13
  runWithPoolWait,
14
+ setPromisePoolConnectionConfig,
14
15
  takePoolWaitTime,
15
16
  wrapPoolQueryMethod,
16
17
  } = require('./helpers/pool-acquire')
@@ -97,6 +98,8 @@ function createWrapQuery (options) {
97
98
  if (!startCh.hasSubscribers) return query.apply(this, arguments)
98
99
 
99
100
  const ctx = { sql, conf: options }
101
+ const poolWaitTime = takePoolWaitTime(this)
102
+ if (poolWaitTime !== undefined) ctx.poolWaitTime = poolWaitTime
100
103
 
101
104
  return startCh.runStores(ctx, query, this, ...arguments)
102
105
  .then(result => {
@@ -146,8 +149,11 @@ function createWrapQueryCallback (options) {
146
149
  function wrapConnection (promiseMethod, Connection) {
147
150
  return function (options) {
148
151
  Connection.apply(this, arguments)
152
+ setPromisePoolConnectionConfig(this, options)
149
153
 
154
+ const query = this[promiseMethod]
150
155
  shimmer.wrap(this, promiseMethod, createWrapQuery(options))
156
+ if (this.query === query) this.query = this[promiseMethod]
151
157
  shimmer.wrap(this, '_queryCallback', createWrapQueryCallback(options))
152
158
  }
153
159
  }
@@ -11,6 +11,60 @@ const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
11
11
  const parameterizedTestCh = channel('ci:mocha:test:parameterize')
12
12
  const patched = new WeakSet()
13
13
 
14
+ /**
15
+ * Registers every Mocha package view that can expose the constructor.
16
+ *
17
+ * @param {string[]} versions
18
+ * @param {(Mocha: Function, frameworkVersion: string) => Function} wrapMochaRun
19
+ * @returns {void}
20
+ */
21
+ function addMochaRunHooks (versions, wrapMochaRun) {
22
+ const patchedMochaConstructors = new WeakSet()
23
+
24
+ /**
25
+ * @param {Function} Mocha
26
+ * @param {string} frameworkVersion
27
+ * @returns {Function}
28
+ */
29
+ function wrapMochaOnce (Mocha, frameworkVersion) {
30
+ if (patchedMochaConstructors.has(Mocha)) return Mocha
31
+ patchedMochaConstructors.add(Mocha)
32
+ return wrapMochaRun(Mocha, frameworkVersion)
33
+ }
34
+
35
+ /**
36
+ * @param {object|Function} MochaPackage
37
+ * @param {string} frameworkVersion
38
+ * @returns {object|Function}
39
+ */
40
+ function wrapMochaPackage (MochaPackage, frameworkVersion) {
41
+ wrapMochaOnce(MochaPackage.default ?? MochaPackage, frameworkVersion)
42
+ return MochaPackage
43
+ }
44
+
45
+ addHook({
46
+ name: 'mocha',
47
+ versions,
48
+ filePattern: String.raw`lib/mocha\.(?:c?js)$`,
49
+ }, wrapMochaOnce)
50
+
51
+ // Mocha 12's ESM package root can load lib/mocha.cjs before the CJS hook observes it. ESM imports report the
52
+ // package root while synchronous require reports index.js, so hook both views. Keeping the namespace intact makes
53
+ // the ESM hook skip its separate default-export callback, and wrapMochaOnce deduplicates loaders exposing both.
54
+ addHook({
55
+ name: 'mocha',
56
+ versions: ['>=12.0.0'],
57
+ patchDefault: false,
58
+ }, wrapMochaPackage)
59
+
60
+ addHook({
61
+ name: 'mocha',
62
+ versions: ['>=12.0.0'],
63
+ file: 'index.js',
64
+ patchDefault: false,
65
+ }, wrapMochaPackage)
66
+ }
67
+
14
68
  // mocha-each support
15
69
  addHook({
16
70
  name: 'mocha-each',
@@ -38,7 +92,8 @@ addHook({
38
92
  name: 'mocha',
39
93
  versions: [MINIMUM_MOCHA_VERSION],
40
94
  file: 'lib/suite.js',
41
- }, (Suite) => {
95
+ }, (SuitePackage) => {
96
+ const Suite = SuitePackage.Suite ?? SuitePackage
42
97
  shimmer.wrap(Suite.prototype, 'addTest', addTest => function (test) {
43
98
  const callSites = getCallSites()
44
99
  const testCallSite = callSites.find(site => site.getFileName() === test.file)
@@ -47,5 +102,7 @@ addHook({
47
102
  }
48
103
  return addTest.apply(this, arguments)
49
104
  })
50
- return Suite
105
+ return SuitePackage
51
106
  })
107
+
108
+ module.exports = { addMochaRunHooks }
@@ -35,6 +35,7 @@ const {
35
35
  isMarkedAsUnskippable,
36
36
  } = require('../../../dd-trace/src/plugins/util/test')
37
37
 
38
+ const { addMochaRunHooks } = require('./common')
38
39
  const {
39
40
  isNewTest,
40
41
  getTestProperties,
@@ -60,8 +61,6 @@ const {
60
61
  adjustRunnerFailuresForTestOptimization,
61
62
  } = require('./utils')
62
63
 
63
- require('./common')
64
-
65
64
  const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
66
65
 
67
66
  /**
@@ -536,7 +535,10 @@ function stopCurrentHook (runner, hook) {
536
535
  if (test) {
537
536
  if (hook.parent?._afterEach?.includes(hook) || hook.parent?._afterAll?.includes(hook)) {
538
537
  markTestTerminal(runner, test)
539
- if (!test._ddTestFinishStarted) runnerTestEndHandlers.get(runner)?.(test)
538
+ if (!test._ddTestFinishStarted) {
539
+ const onTestEnd = runnerTestEndHandlers.get(runner)
540
+ onTestEnd?.(test)
541
+ }
540
542
  } else {
541
543
  markTestPending(runner, test)
542
544
  }
@@ -763,14 +765,18 @@ function wrapRunnerEmit (Runner) {
763
765
  const test = hook.ctx?.currentTest
764
766
  if (test && hook.parent?._afterEach?.includes(hook)) {
765
767
  markTestTerminal(this, test)
766
- if (!test._ddTestFinishStarted) runnerTestEndHandlers.get(this)?.(test)
768
+ if (!test._ddTestFinishStarted) {
769
+ const onTestEnd = runnerTestEndHandlers.get(this)
770
+ onTestEnd?.(test)
771
+ }
767
772
  }
768
773
  } else if (event === 'pending' || event === 'pass' || event === 'fail' || event === 'retry' ||
769
774
  event === 'test end') {
770
775
  const test = arguments[1]
771
776
  stopAfterEachHooks(this, test)
772
777
  if (event === 'test end' && !test._ddTestFinishStarted) {
773
- runnerTestEndHandlers.get(this)?.(test)
778
+ const onTestEnd = runnerTestEndHandlers.get(this)
779
+ onTestEnd?.(test)
774
780
  }
775
781
  }
776
782
  }
@@ -1063,13 +1069,15 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
1063
1069
  // In this hook we delay the execution with options.delay to grab library configuration,
1064
1070
  // skippable and known tests.
1065
1071
  // It is called but skipped in parallel mode.
1066
- addHook({
1067
- name: 'mocha',
1068
- versions: [MINIMUM_MOCHA_VERSION],
1069
- file: 'lib/mocha.js',
1070
- }, (Mocha, frameworkVersion) => {
1072
+ /**
1073
+ * @param {Function} Mocha
1074
+ * @param {string} frameworkVersion
1075
+ * @returns {Function}
1076
+ */
1077
+ function wrapMochaRun (Mocha, frameworkVersion) {
1071
1078
  warnDeprecatedMochaVersion(frameworkVersion)
1072
1079
 
1080
+ // Shimmer is required because run must return its Runner while execution is paused and resumed after configuration.
1073
1081
  shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
1074
1082
  // Workers do not need to request any data, just run the tests
1075
1083
  if (!testFinishCh.hasSubscribers || getEnvironmentVariable('MOCHA_WORKER_ID') || this.options.parallel) {
@@ -1121,12 +1129,14 @@ addHook({
1121
1129
  return runner
1122
1130
  })
1123
1131
  return Mocha
1124
- })
1132
+ }
1133
+
1134
+ addMochaRunHooks([MINIMUM_MOCHA_VERSION], wrapMochaRun)
1125
1135
 
1126
1136
  addHook({
1127
1137
  name: 'mocha',
1128
1138
  versions: [MINIMUM_MOCHA_VERSION],
1129
- file: 'lib/cli/run-helpers.js',
1139
+ filePattern: String.raw`lib/cli/run-helpers\.(?:c?js)$`,
1130
1140
  }, (run) => {
1131
1141
  // `runMocha` is an async function
1132
1142
  shimmer.wrap(run, 'runMocha', runMocha => function (...args) {
@@ -1157,7 +1167,7 @@ addHook({
1157
1167
  addHook({
1158
1168
  name: 'mocha',
1159
1169
  versions: [MINIMUM_MOCHA_VERSION],
1160
- file: 'lib/runner.js',
1170
+ filePattern: String.raw`lib/runner\.(?:c?js)$`,
1161
1171
  }, function (Runner, frameworkVersion) {
1162
1172
  if (patched.has(Runner)) return Runner
1163
1173
 
@@ -1588,7 +1598,7 @@ addHook({
1588
1598
  addHook({
1589
1599
  name: 'mocha',
1590
1600
  versions: ['>=8.0.0'],
1591
- file: 'lib/nodejs/parallel-buffered-runner.js',
1601
+ filePattern: String.raw`lib/nodejs/parallel-buffered-runner\.(?:c?js)$`,
1592
1602
  }, (ParallelBufferedRunner, frameworkVersion) => {
1593
1603
  shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function (cb, { files, options = {} }) {
1594
1604
  if (!testFinishCh.hasSubscribers) {
@@ -1667,7 +1677,7 @@ addHook({
1667
1677
  addHook({
1668
1678
  name: 'mocha',
1669
1679
  versions: ['>=8.0.0'],
1670
- file: 'lib/nodejs/buffered-worker-pool.js',
1680
+ filePattern: String.raw`lib/nodejs/buffered-worker-pool\.(?:c?js)$`,
1671
1681
  }, (BufferedWorkerPoolPackage, frameworkVersion) => {
1672
1682
  const { BufferedWorkerPool } = BufferedWorkerPoolPackage
1673
1683
 
@@ -507,7 +507,8 @@ function inheritDatadogPropertiesFromRetriedTest (test) {
507
507
  }
508
508
 
509
509
  function runnableWrapper (RunnablePackage, libraryConfig) {
510
- shimmer.wrap(RunnablePackage.prototype, 'run', run => function (...args) {
510
+ const Runnable = RunnablePackage.Runnable ?? RunnablePackage
511
+ shimmer.wrap(Runnable.prototype, 'run', run => function (...args) {
511
512
  if (!testFinishCh.hasSubscribers) {
512
513
  return run.apply(this, args)
513
514
  }
@@ -6,6 +6,11 @@ const {
6
6
  WEBDRIVERIO_WORKER_EVENT,
7
7
  WEBDRIVERIO_WORKER_ORIGIN,
8
8
  } = require('../../../dd-trace/src/ci-visibility/exporters/test-worker/webdriverio')
9
+ const { FINAL_FLUSH_TIMEOUT } = require('../../../dd-trace/src/ci-visibility/final-flush')
10
+
11
+ const SCREENSHOT_UPLOAD = 'dd:test-optimization:webdriverio:screenshot:upload'
12
+ const SCREENSHOT_UPLOAD_RESPONSE = 'dd:test-optimization:webdriverio:screenshot:upload:response'
13
+ const SCREENSHOT_UPLOAD_TIMEOUT_MS = FINAL_FLUSH_TIMEOUT + 5000
9
14
 
10
15
  /**
11
16
  * Sends a message over WebdriverIO's worker IPC envelope.
@@ -30,10 +35,103 @@ function sendWebdriverioWorkerMessage (message, onError, onDone) {
30
35
  })
31
36
  }
32
37
 
38
+ let screenshotUploadRequestId = 0
39
+ const screenshotUploadRequests = new Map()
40
+
41
+ /**
42
+ * Removes shared screenshot response listeners when there are no pending requests.
43
+ *
44
+ * @returns {void}
45
+ */
46
+ function removeScreenshotUploadListeners () {
47
+ if (screenshotUploadRequests.size !== 0) return
48
+
49
+ process.off('message', onScreenshotUploadResponse)
50
+ process.off('disconnect', onScreenshotUploadDisconnect)
51
+ }
52
+
53
+ /**
54
+ * Completes one pending screenshot upload request.
55
+ *
56
+ * @param {string} requestId
57
+ * @param {Error} [error]
58
+ * @returns {void}
59
+ */
60
+ function finishScreenshotUploadRequest (requestId, error) {
61
+ const request = screenshotUploadRequests.get(requestId)
62
+ if (!request) return
63
+
64
+ screenshotUploadRequests.delete(requestId)
65
+ clearTimeout(request.timeout)
66
+ removeScreenshotUploadListeners()
67
+ request.onDone(error)
68
+ }
69
+
70
+ /**
71
+ * Dispatches one coordinator screenshot response to its pending request.
72
+ *
73
+ * @param {object} message
74
+ * @returns {void}
75
+ */
76
+ function onScreenshotUploadResponse (message) {
77
+ if (message?.name !== SCREENSHOT_UPLOAD_RESPONSE) return
78
+
79
+ const { error: errorMessage, requestId } = message.content || {}
80
+ if (!requestId) return
81
+
82
+ finishScreenshotUploadRequest(requestId, errorMessage ? new Error(errorMessage) : undefined)
83
+ }
84
+
85
+ /**
86
+ * Fails every pending screenshot upload after coordinator disconnect.
87
+ *
88
+ * @returns {void}
89
+ */
90
+ function onScreenshotUploadDisconnect () {
91
+ for (const requestId of screenshotUploadRequests.keys()) {
92
+ finishScreenshotUploadRequest(
93
+ requestId,
94
+ new Error('WebdriverIO coordinator disconnected during screenshot upload')
95
+ )
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Requests one screenshot upload from the WebdriverIO coordinator.
101
+ *
102
+ * @param {object} content - Screenshot upload metadata
103
+ * @param {(error?: Error) => void} onDone - Upload completion callback
104
+ * @returns {void}
105
+ */
106
+ function requestWebdriverioScreenshotUpload (content, onDone) {
107
+ const requestId = `${process.pid}-${++screenshotUploadRequestId}`
108
+ const timeout = setTimeout(() => {
109
+ finishScreenshotUploadRequest(requestId, new Error('WebdriverIO screenshot upload timed out'))
110
+ }, SCREENSHOT_UPLOAD_TIMEOUT_MS)
111
+ timeout.unref?.()
112
+ if (screenshotUploadRequests.size === 0) {
113
+ process.on('message', onScreenshotUploadResponse)
114
+ process.once('disconnect', onScreenshotUploadDisconnect)
115
+ }
116
+ screenshotUploadRequests.set(requestId, { onDone, timeout })
117
+ sendWebdriverioWorkerMessage({
118
+ origin: 'datadog',
119
+ name: SCREENSHOT_UPLOAD,
120
+ content: { ...content, requestId },
121
+ }, error => finishScreenshotUploadRequest(
122
+ requestId,
123
+ error || new Error('WebdriverIO screenshot upload IPC failed')
124
+ ))
125
+ }
126
+
33
127
  module.exports = {
34
128
  CONFIGURATION_REQUEST: 'dd:test-optimization:webdriverio:configuration:request',
35
129
  CONFIGURATION_RESPONSE: 'dd:test-optimization:webdriverio:configuration:response',
36
130
  createWebdriverioWorkerMessage,
131
+ requestWebdriverioScreenshotUpload,
132
+ SCREENSHOT_UPLOAD,
133
+ SCREENSHOT_UPLOAD_RESPONSE,
134
+ SCREENSHOT_UPLOAD_TIMEOUT_MS,
37
135
  sendWebdriverioWorkerMessage,
38
136
  SUITE_FINISH: 'dd:test-optimization:webdriverio:test-suite:finish',
39
137
  WORKER_READY: 'dd:test-optimization:webdriverio:worker:ready',
@@ -7,6 +7,7 @@ const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper'
7
7
  const log = require('../../../dd-trace/src/log')
8
8
  const { DD_MAJOR } = require('../../../../version')
9
9
 
10
+ const { addMochaRunHooks } = require('./common')
10
11
  const {
11
12
  runnableWrapper,
12
13
  getOnTestHandler,
@@ -29,7 +30,6 @@ const {
29
30
  WEBDRIVERIO_WORKER_ENV,
30
31
  WORKER_READY,
31
32
  } = require('./webdriverio-protocol')
32
- require('./common')
33
33
 
34
34
  const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
35
35
 
@@ -360,13 +360,15 @@ function isFailedTestReplayEnabled () {
360
360
  return config.isTestDynamicInstrumentationEnabled && config.isDiEnabled
361
361
  }
362
362
 
363
- addHook({
364
- name: 'mocha',
365
- versions: ['>=8.0.0'],
366
- file: 'lib/mocha.js',
367
- }, (Mocha, frameworkVersion) => {
363
+ /**
364
+ * @param {Function} Mocha
365
+ * @param {string} frameworkVersion
366
+ * @returns {Function}
367
+ */
368
+ function wrapMochaRun (Mocha, frameworkVersion) {
368
369
  reportWebdriverioWorkerReady(frameworkVersion)
369
370
 
371
+ // Shimmer is required because run must return its Runner while execution is paused and resumed after configuration.
370
372
  shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
371
373
  applyMochaOptions(this.options)
372
374
  if (!isWebdriverioWorker || !workerFinishCh.hasSubscribers) {
@@ -438,13 +440,15 @@ addHook({
438
440
  })
439
441
 
440
442
  return Mocha
441
- })
443
+ }
444
+
445
+ addMochaRunHooks(['>=8.0.0'], wrapMochaRun)
442
446
 
443
447
  // Runner is also hooked in mocha/main.js, but in here we only generate test events.
444
448
  addHook({
445
449
  name: 'mocha',
446
450
  versions: [MINIMUM_MOCHA_VERSION],
447
- file: 'lib/runner.js',
451
+ filePattern: String.raw`lib/runner\.(?:c?js)$`,
448
452
  }, function (Runner) {
449
453
  shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
450
454
 
@@ -27,8 +27,6 @@ addHook({ name: 'net' }, (net) => {
27
27
  }
28
28
 
29
29
  const options = getOptions(args)
30
- const lastIndex = args.length - 1
31
- const callback = args[lastIndex]
32
30
 
33
31
  if (!options) return connect.apply(this, args)
34
32
 
@@ -38,42 +36,39 @@ addHook({ name: 'net' }, (net) => {
38
36
  const errorCh = protocol === 'ipc' ? errorICPCh : errorTCPCh
39
37
  const ctx = { options }
40
38
 
41
- if (typeof callback === 'function') {
42
- args[lastIndex] = function (...args) {
43
- return finishCh.runStores(ctx, callback, this, ...args)
44
- }
45
- }
46
-
47
39
  return startCh.runStores(ctx, () => {
48
- setupListeners(this, protocol, ctx, finishCh, errorCh)
40
+ const cleanup = setupListeners(this, protocol, ctx, finishCh, errorCh)
49
41
 
50
- const emit = this.emit
42
+ const originalEmit = this.emit
51
43
  let pendingReadyEvents = 2
52
44
  // Named `emit`/arity-1 mirrors the socket method so the per-socket wrap
53
45
  // skips its name/length rewrite.
54
- this.emit = shimmer.wrapFunction(emit, originalEmit => function emit (eventName) {
46
+ this.emit = shimmer.wrapFunction(originalEmit, wrappedEmit => function emit (eventName) {
55
47
  switch (eventName) {
56
48
  case 'ready':
57
49
  case 'connect':
58
50
  if (--pendingReadyEvents === 0) this.emit = originalEmit
59
51
  return readyCh.runStores(ctx, () => {
60
- return Reflect.apply(originalEmit, this, arguments)
52
+ return Reflect.apply(wrappedEmit, this, arguments)
61
53
  })
62
54
  case 'error':
63
55
  case 'close':
64
56
  this.emit = originalEmit
65
- return Reflect.apply(originalEmit, this, arguments)
57
+ return Reflect.apply(wrappedEmit, this, arguments)
66
58
  default:
67
- return Reflect.apply(originalEmit, this, arguments)
59
+ return Reflect.apply(wrappedEmit, this, arguments)
68
60
  }
69
61
  })
70
62
 
71
63
  try {
72
64
  return connect.apply(this, args)
73
- } catch (err) {
74
- errorCh.publish(err)
65
+ } catch (error) {
66
+ cleanup()
67
+ this.emit = originalEmit
68
+ errorCh.publish(error)
69
+ finishCh.runStores(ctx, () => {})
75
70
 
76
- throw err
71
+ throw error
77
72
  }
78
73
  })
79
74
  })
@@ -142,4 +137,6 @@ function setupListeners (socket, protocol, ctx, finishCh, errorCh) {
142
137
  for (const event of events) {
143
138
  socket.once(event, wrapListener)
144
139
  }
140
+
141
+ return cleanupOtherListeners
145
142
  }