dd-trace 6.20.1 → 6.22.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 (225) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  16. package/index.d.ts +116 -17
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +13 -11
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  48. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  49. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  50. package/packages/datadog-instrumentations/src/jest.js +122 -11
  51. package/packages/datadog-instrumentations/src/knex.js +9 -5
  52. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  53. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  54. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  55. package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
  56. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
  57. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  58. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  59. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  60. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  61. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  62. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  63. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  64. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  65. package/packages/datadog-instrumentations/src/openai.js +7 -0
  66. package/packages/datadog-instrumentations/src/pino.js +5 -6
  67. package/packages/datadog-instrumentations/src/playwright.js +3 -0
  68. package/packages/datadog-instrumentations/src/redis.js +27 -13
  69. package/packages/datadog-instrumentations/src/router.js +30 -0
  70. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  71. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  72. package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
  73. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  74. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  75. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  76. package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
  77. package/packages/datadog-instrumentations/src/ws.js +1 -4
  78. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  79. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  80. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  81. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  82. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  83. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  84. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  85. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  86. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  87. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  88. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  89. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
  90. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  91. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  92. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  93. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  94. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  95. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  96. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  97. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +223 -39
  100. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
  101. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  102. package/packages/datadog-plugin-next/src/index.js +1 -2
  103. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  104. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  105. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  106. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  107. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  108. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  109. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  110. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  111. package/packages/datadog-webpack/index.js +55 -43
  112. package/packages/datadog-webpack/src/loader.js +56 -8
  113. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  114. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  115. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  116. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  117. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  118. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  119. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  120. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  121. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  122. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  123. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  125. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  126. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  127. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  128. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  129. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  130. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  131. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  132. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  135. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  137. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  138. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  139. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  140. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  141. package/packages/dd-trace/src/config/defaults.js +5 -5
  142. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  143. package/packages/dd-trace/src/config/helper.js +34 -3
  144. package/packages/dd-trace/src/config/index.js +10 -9
  145. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  146. package/packages/dd-trace/src/config/parsers.js +2 -2
  147. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  148. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  149. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  150. package/packages/dd-trace/src/debugger/constants.js +1 -0
  151. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
  152. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
  153. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
  154. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  155. package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
  156. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  157. package/packages/dd-trace/src/debugger/index.js +24 -2
  158. package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
  159. package/packages/dd-trace/src/dogstatsd.js +1 -1
  160. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  161. package/packages/dd-trace/src/exporters/common/request.js +2 -5
  162. package/packages/dd-trace/src/exporters/common/url.js +1 -1
  163. package/packages/dd-trace/src/id.js +15 -5
  164. package/packages/dd-trace/src/lambda/handler.js +179 -19
  165. package/packages/dd-trace/src/lambda/index.js +15 -1
  166. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  167. package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
  168. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  169. package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
  170. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  171. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  172. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  173. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  174. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  175. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  176. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  177. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  178. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  179. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  180. package/packages/dd-trace/src/llmobs/sdk.js +23 -6
  181. package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
  182. package/packages/dd-trace/src/llmobs/tagger.js +56 -3
  183. package/packages/dd-trace/src/llmobs/util.js +1 -2
  184. package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
  185. package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
  186. package/packages/dd-trace/src/log/log.js +1 -5
  187. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
  188. package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
  189. package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
  190. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  191. package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
  192. package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
  193. package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
  194. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  195. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  196. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  197. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  198. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
  199. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  201. package/packages/dd-trace/src/otel-sampling.js +4 -5
  202. package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
  203. package/packages/dd-trace/src/plugins/database.js +7 -16
  204. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  205. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  206. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  207. package/packages/dd-trace/src/priority_sampler.js +1 -2
  208. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  209. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
  210. package/packages/dd-trace/src/proxy.js +3 -2
  211. package/packages/dd-trace/src/ritm.js +4 -2
  212. package/packages/dd-trace/src/serverless.js +10 -0
  213. package/packages/dd-trace/src/span_processor.js +13 -17
  214. package/packages/dd-trace/src/storage-channels.js +112 -46
  215. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  216. package/packages/dd-trace/src/util.js +29 -0
  217. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  218. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  219. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  220. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  221. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  222. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  223. package/vendor/dist/protobufjs/index.js +1 -1
  224. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  225. package/vendor/dist/shell-quote/index.js +1 -1
@@ -12,11 +12,37 @@ const POSTGRES_SIMPLE_DBM_QUERIES = '__ddTracePostgresSimpleDbmQueries'
12
12
  const POSTGRES_STATEMENT = 'this.string ?? (this.tagged && this.strings?.length !== 1 ? undefined : this.strings?.[0])'
13
13
 
14
14
  module.exports = {
15
+ postgresQueryAcquire,
15
16
  postgresQueryHandlers,
16
17
  postgresQueryLifecycle,
17
18
  postgresQueryPreparation,
18
19
  }
19
20
 
21
+ /**
22
+ * @param {object} state
23
+ * @param {import('estree').Program} program
24
+ */
25
+ function postgresQueryAcquire (state, program) {
26
+ const channelVariable = injectPostgresTracingChannel(state, program)
27
+ const executeFunctions = query(program, 'FunctionDeclaration[id.name="execute"]')
28
+ assert(executeFunctions.length === 1, 'postgresQueryAcquire: unexpected execute function count')
29
+
30
+ const execute = executeFunctions[0]
31
+ const queryParameter = execute.params[0]
32
+ assert(queryParameter?.type === 'Identifier', 'postgresQueryAcquire: execute query parameter changed')
33
+ assert(execute.body.type === 'BlockStatement', 'postgresQueryAcquire: execute body changed')
34
+
35
+ const body = execute.body.body
36
+ const executeIndex = body.findIndex(statement => statement.type === 'TryStatement')
37
+ assert(executeIndex !== -1, 'postgresQueryAcquire: execute try block changed')
38
+
39
+ body.splice(executeIndex, 0, ...parse(`
40
+ if (${channelVariable}.start.hasSubscribers) {
41
+ ${channelVariable}.start.publish({ query: ${queryParameter.name} });
42
+ }
43
+ `).body)
44
+ }
45
+
20
46
  /**
21
47
  * Starts a Postgres.js query at the pool handler that owns its connection options.
22
48
  *
@@ -154,6 +180,7 @@ function wrapPostgresHandler (handler, queryParameter, channelVariable) {
154
180
  assert(handler.body.type === 'BlockStatement', 'postgresQueryHandlers: handler body changed')
155
181
  assert(!handler.async && !handler.generator, 'postgresQueryHandlers: unsupported handler kind')
156
182
 
183
+ // Direct handoffs publish before the handler returns, so they record zero pool wait.
157
184
  const originalBody = handler.body.body
158
185
  const wrapperBody = parse(`
159
186
  function wrapper () {
@@ -167,12 +194,18 @@ function wrapPostgresHandler (handler, queryParameter, channelVariable) {
167
194
  __ddTraceContext.host = ${POSTGRES_OPTIONS}.host[0];
168
195
  __ddTraceContext.port = ${POSTGRES_OPTIONS}.port[0];
169
196
  }
170
- return ${channelVariable}.start.runStores(__ddTraceContext, () => {});
197
+ const __ddTraceResult = ${channelVariable}.start.runStores(__ddTraceContext, () => {});
198
+ if (!__ddTraceContext.poolWaitFinished) {
199
+ __ddTraceContext.poolWaitStartMs = performance.now();
200
+ }
201
+ return __ddTraceResult;
171
202
  }
172
203
  }
173
204
  `).body[0].body.body
174
205
 
175
- wrapperBody[0].consequent.body.at(-1).argument.arguments[1].body.body = originalBody
206
+ const tracingCalls = query(wrapperBody[0], 'CallExpression[callee.property.name="runStores"]')
207
+ assert(tracingCalls.length === 1, 'postgresQueryHandlers: runStores call changed')
208
+ tracingCalls[0].arguments[1].body.body = originalBody
176
209
  handler.body.body = [...wrapperBody, ...originalBody]
177
210
  }
178
211
 
@@ -272,8 +305,13 @@ function findPostgresQueryIdentifier (program) {
272
305
 
273
306
  const declarations = query(program, 'VariableDeclarator[id.type="ObjectPattern"]')
274
307
  for (const declaration of declarations) {
275
- if (declaration.init?.type !== 'CallExpression' || declaration.init.callee.name !== 'require') continue
276
- if (declaration.init.arguments[0]?.value !== './query.js') continue
308
+ if (
309
+ declaration.init?.type !== 'CallExpression' ||
310
+ declaration.init.callee.name !== 'require' ||
311
+ declaration.init.arguments[0]?.value !== './query.js'
312
+ ) {
313
+ continue
314
+ }
277
315
 
278
316
  for (const property of declaration.id.properties) {
279
317
  if (property.type === 'Property' && property.key.name === 'Query' && property.value.type === 'Identifier') {
@@ -140,8 +140,7 @@ function awaitContextCallback (state, node, _parent, ancestry) {
140
140
  const generatedCallback = createAwaitedContextCallback(
141
141
  state,
142
142
  insertionTarget,
143
- callbackAncestry,
144
- 'awaitContextCallback'
143
+ callbackAncestry
145
144
  )
146
145
  if (!generatedCallback) return
147
146
 
@@ -158,8 +157,7 @@ function awaitContextCallback (state, node, _parent, ancestry) {
158
157
  const generatedCallback = createAwaitedContextCallback(
159
158
  state,
160
159
  node.consequent,
161
- ancestry,
162
- 'awaitContextCallback'
160
+ ancestry
163
161
  )
164
162
  if (!generatedCallback) return
165
163
 
@@ -181,18 +179,17 @@ function awaitContextCallback (state, node, _parent, ancestry) {
181
179
  * @param {Parameters<typeof awaitContextCallback>[0]} state
182
180
  * @param {import('estree').BlockStatement} insertionTarget
183
181
  * @param {import('estree').Node[]} ancestry
184
- * @param {string} transformName
185
182
  * @returns {{
186
183
  * callbackStatements: import('estree').Statement[],
187
184
  * callbackVariable: string
188
185
  * }|undefined}
189
186
  */
190
- function createAwaitedContextCallback (state, insertionTarget, ancestry, transformName) {
187
+ function createAwaitedContextCallback (state, insertionTarget, ancestry) {
191
188
  const { callbackArgumentNames = [], callbackName, callbackThis = false } = state.transformOptions ?? {}
192
189
 
193
- assert(identifierPattern.test(callbackName), `${transformName}: callbackName must be an identifier`)
190
+ assert(identifierPattern.test(callbackName), 'awaitContextCallback: callbackName must be an identifier')
194
191
  assert(Array.isArray(callbackArgumentNames) && callbackArgumentNames.every(name => identifierPattern.test(name)),
195
- `${transformName}: callbackArgumentNames must be identifiers`)
192
+ 'awaitContextCallback: callbackArgumentNames must be identifiers')
196
193
 
197
194
  let enclosingFunction
198
195
  let hasTraceWrapper = false
@@ -215,8 +212,8 @@ function createAwaitedContextCallback (state, insertionTarget, ancestry, transfo
215
212
  hasTraceWrapper ||= hasContextBinding && hasTracedBinding
216
213
  }
217
214
 
218
- assert(enclosingFunction?.async, `${transformName}: expected an enclosing async function`)
219
- assert(hasTraceWrapper, `${transformName}: expected an enclosing trace wrapper`)
215
+ assert(enclosingFunction?.async, 'awaitContextCallback: expected an enclosing async function')
216
+ assert(hasTraceWrapper, 'awaitContextCallback: expected an enclosing trace wrapper')
220
217
 
221
218
  const callbackVariable = `__apm$${callbackName}`
222
219
  if (query(insertionTarget, `[id.name="${callbackVariable}"]`).length > 0) return
@@ -14,9 +14,7 @@ const connectionOptionsCache = new WeakMap()
14
14
 
15
15
  function wrapRedis (Redis) {
16
16
  shimmer.wrap(Redis.prototype, 'sendCommand', sendCommand => function (command, stream) {
17
- if (!startCh.hasSubscribers) return sendCommand.call(this, command, stream)
18
-
19
- if (!command?.promise) return sendCommand.call(this, command, stream)
17
+ if (!startCh.hasSubscribers || !command?.promise) return sendCommand.call(this, command, stream)
20
18
 
21
19
  const options = this.options || {}
22
20
  let connectionOptions = connectionOptionsCache.get(this)
@@ -32,7 +30,7 @@ function wrapRedis (Redis) {
32
30
  connectionName: options.connectionName,
33
31
  }
34
32
  return startCh.runStores(ctx, () => {
35
- command.promise.then(() => finish(finishCh, errorCh, ctx), err => finish(finishCh, errorCh, ctx, err))
33
+ command.promise.then(() => finish(ctx), err => finish(ctx, err))
36
34
 
37
35
  return sendCommand.call(this, command, stream)
38
36
  })
@@ -51,7 +49,11 @@ addHook({ name: 'ioredis', versions: ['>=4.11.0 <5'], file: 'built/redis/index.j
51
49
 
52
50
  addHook({ name: 'ioredis', versions: ['>=5'] }, wrapRedis)
53
51
 
54
- function finish (finishCh, errorCh, ctx, error) {
52
+ /**
53
+ * @param {Record<string, unknown> & { error?: Error }} ctx
54
+ * @param {Error} [error]
55
+ */
56
+ function finish (ctx, error) {
55
57
  if (error) {
56
58
  ctx.error = error
57
59
  errorCh.publish(ctx)
@@ -12,9 +12,7 @@ const errorCh = channel('apm:iovalkey:command:error')
12
12
 
13
13
  addHook({ name: 'iovalkey', versions: ['>=0.0.1'] }, Valkey => {
14
14
  shimmer.wrap(Valkey.prototype, 'sendCommand', sendCommand => function (command, stream) {
15
- if (!startCh.hasSubscribers) return sendCommand.apply(this, arguments)
16
-
17
- if (!command?.promise) return sendCommand.apply(this, arguments)
15
+ if (!startCh.hasSubscribers || !command?.promise) return sendCommand.apply(this, arguments)
18
16
 
19
17
  const options = this.options || {}
20
18
  const connectionName = options.connectionName
@@ -22,7 +20,7 @@ addHook({ name: 'iovalkey', versions: ['>=0.0.1'] }, Valkey => {
22
20
 
23
21
  const ctx = { command: command.name, args: command.args, connectionOptions, connectionName }
24
22
  return startCh.runStores(ctx, () => {
25
- command.promise.then(() => finish(finishCh, errorCh, ctx), err => finish(finishCh, errorCh, ctx, err))
23
+ command.promise.then(() => finish(ctx), err => finish(ctx, err))
26
24
 
27
25
  try {
28
26
  return sendCommand.apply(this, arguments)
@@ -37,7 +35,11 @@ addHook({ name: 'iovalkey', versions: ['>=0.0.1'] }, Valkey => {
37
35
  return Valkey
38
36
  })
39
37
 
40
- function finish (finishCh, errorCh, ctx, error) {
38
+ /**
39
+ * @param {Record<string, unknown> & { error?: Error }} ctx
40
+ * @param {Error} [error]
41
+ */
42
+ function finish (ctx, error) {
41
43
  if (error) {
42
44
  ctx.error = error
43
45
  errorCh.publish(ctx)
@@ -10,6 +10,7 @@ const { performance } = require('node:perf_hooks')
10
10
  const path = require('path')
11
11
  const satisfies = require('../../../vendor/dist/semifies')
12
12
  const { DD_MAJOR } = require('../../../version')
13
+ const { storage } = require('../../datadog-core')
13
14
  const shimmer = require('../../datadog-shimmer')
14
15
  const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
15
16
  const log = require('../../dd-trace/src/log')
@@ -88,6 +89,8 @@ const testSkippedCh = channel('ci:jest:test:skip')
88
89
  const testFinishCh = channel('ci:jest:test:finish')
89
90
  const testErrCh = channel('ci:jest:test:err')
90
91
  const testFnCh = channel('ci:jest:test:fn')
92
+ const snapshotStorage = storage('ci:jest:snapshots')
93
+ let activeSnapshotTrackers = 0
91
94
  const testSuiteHookFnCh = channel('ci:jest:test-suite:hook:fn')
92
95
  const consoleLogSubmissionCh = channel('ci:log-submission:console')
93
96
  const logSubmissionFlushCh = channel('ci:log-submission:flush')
@@ -666,7 +669,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
666
669
  #discardedEfdRetryTests
667
670
  #earlyFlakeDetectionRetryPolicy
668
671
  #efdRetryGatesByName
672
+ #expect
669
673
  #pendingPre30ConcurrentTests
674
+ /** @type {(() => void)|undefined} */
675
+ #restoreSnapshotState
676
+ /** @type {Set<string>|undefined} */
677
+ #snapshotTestNames
670
678
 
671
679
  constructor (config, context) {
672
680
  super(config, context)
@@ -883,8 +891,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
883
891
  */
884
892
  wrapConcurrentTestFunction (target, methodName, state) {
885
893
  let concurrentTest = target?.[methodName]
886
- if (typeof concurrentTest !== 'function') return
887
- if (this.wrappedConcurrentTestFunctions.has(concurrentTest)) return
894
+ if (typeof concurrentTest !== 'function' || this.wrappedConcurrentTestFunctions.has(concurrentTest)) return
888
895
 
889
896
  const originalConcurrentTest = getOriginalConcurrentTest(concurrentTest)
890
897
  if (originalConcurrentTest !== concurrentTest) {
@@ -1445,6 +1452,98 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1445
1452
  }
1446
1453
  }
1447
1454
 
1455
+ getSnapshotState () {
1456
+ return this.#expect?.getState?.()?.snapshotState
1457
+ }
1458
+
1459
+ /**
1460
+ * Attributes snapshot increments to the executing test when serial and concurrent bodies can overlap.
1461
+ * Both Jest and image snapshot matchers write this shared counter directly.
1462
+ */
1463
+ #trackQuarantinedSnapshots () {
1464
+ if (!this.hasConcurrentTests || !this.testManagementTestsForThisSuite?.quarantined?.length) return
1465
+
1466
+ const snapshotState = this.getSnapshotState()
1467
+ const descriptor = snapshotState && Object.getOwnPropertyDescriptor(snapshotState, 'unmatched')
1468
+ if (!descriptor?.configurable || typeof descriptor.value !== 'number') return
1469
+
1470
+ let unmatched = descriptor.value
1471
+ try {
1472
+ Object.defineProperty(snapshotState, 'unmatched', {
1473
+ configurable: true,
1474
+ enumerable: descriptor.enumerable,
1475
+ get () { return unmatched },
1476
+ /** @param {number} value */
1477
+ set (value) {
1478
+ const ctx = /** @type {{ unmatchedSnapshots?: number }|undefined} */ (snapshotStorage.getStore())
1479
+ if (ctx?.unmatchedSnapshots !== undefined && value > unmatched) {
1480
+ ctx.unmatchedSnapshots += value - unmatched
1481
+ }
1482
+ unmatched = value
1483
+ },
1484
+ })
1485
+ } catch (error) {
1486
+ log.error('Could not track quarantined Jest snapshots: %s', error)
1487
+ return
1488
+ }
1489
+
1490
+ if (activeSnapshotTrackers++ === 0) testFnCh.bindStore(snapshotStorage)
1491
+ this.#restoreSnapshotState = () => {
1492
+ try {
1493
+ Object.defineProperty(snapshotState, 'unmatched', { ...descriptor, value: unmatched })
1494
+ } catch (error) {
1495
+ log.error('Could not restore Jest snapshot state: %s', error)
1496
+ } finally {
1497
+ if (--activeSnapshotTrackers === 0) testFnCh.unbindStore(snapshotStorage)
1498
+ }
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Keeps the snapshot results of a quarantined test from failing the run.
1504
+ *
1505
+ * Jest fails the run on unmatched or obsolete snapshots even when no test failed.
1506
+ *
1507
+ * @param {object} test
1508
+ */
1509
+ ignoreQuarantinedSnapshotResults (test) {
1510
+ const ctx = testContexts.get(test)
1511
+ const unmatchedBeforeTest = ctx?.unmatchedSnapshotsBeforeTest
1512
+ const unmatchedSnapshots = ctx?.unmatchedSnapshots
1513
+ // Jest retries the attempts that still have errors and resets their snapshot results itself.
1514
+ if ((unmatchedBeforeTest === undefined && unmatchedSnapshots === undefined) || test.errors?.length) return
1515
+
1516
+ const snapshotState = this.getSnapshotState()
1517
+ if (!snapshotState) return
1518
+
1519
+ if (unmatchedSnapshots !== undefined) {
1520
+ snapshotState.unmatched -= unmatchedSnapshots
1521
+ ctx.unmatchedSnapshots = 0
1522
+ } else if (snapshotState.unmatched > unmatchedBeforeTest) {
1523
+ snapshotState.unmatched = unmatchedBeforeTest
1524
+ }
1525
+ // Jest's name-based marker can consume other tests' keys, so delete only keys owned by this test.
1526
+ const keys = snapshotState._uncheckedKeys
1527
+ const testNames = this.#snapshotTestNames
1528
+ if (!keys?.delete || !testNames) return
1529
+ const testName = getRawJestTestName(test)
1530
+ const hintPrefix = `${testName}: `
1531
+ for (const key of keys) {
1532
+ const snapshotName = key.slice(0, key.lastIndexOf(' '))
1533
+ if (snapshotName !== testName && !snapshotName.startsWith(hintPrefix)) continue
1534
+ let belongsToAnotherTest = false
1535
+ // Snapshot names do not identify their owner when another registered name matches the same key.
1536
+ for (const name of testNames) {
1537
+ if (name !== testName &&
1538
+ (snapshotName === name || snapshotName.startsWith(`${name}: `))) {
1539
+ belongsToAnotherTest = true
1540
+ break
1541
+ }
1542
+ }
1543
+ if (!belongsToAnotherTest) keys.delete(key)
1544
+ }
1545
+ }
1546
+
1448
1547
  // This function returns an array if the known tests are valid and null otherwise.
1449
1548
  getKnownTestsForSuite (suiteKnownTests) {
1450
1549
  // `suiteKnownTests` is `this.testEnvironmentOptions._ddKnownTests`,
@@ -1819,6 +1918,11 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1819
1918
  if (event.concurrent) {
1820
1919
  this.hasConcurrentTests = true
1821
1920
  }
1921
+ const registeredTest = getTestEntries(state.currentDescribeBlock)?.at(-1)
1922
+ if (registeredTest && this.testManagementTestsForThisSuite?.quarantined?.length) {
1923
+ this.#snapshotTestNames ??= new Set()
1924
+ this.#snapshotTestNames.add(getRawJestTestName(registeredTest))
1925
+ }
1822
1926
  if (event.failing) {
1823
1927
  return
1824
1928
  }
@@ -1827,7 +1931,6 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1827
1931
  const concurrentTestContexts = this.concurrentTestContexts.get(testFullName)
1828
1932
  const concurrentTestState = this.concurrentTestStates.get(event.fn) ||
1829
1933
  concurrentTestContexts?.at(-1)?.concurrentTestState
1830
- const registeredTest = getTestEntries(state.currentDescribeBlock)?.at(-1)
1831
1934
  if (concurrentTestState && registeredTest?.fn === event.fn) {
1832
1935
  testContexts.set(registeredTest, concurrentTestState.ctx)
1833
1936
  }
@@ -1925,10 +2028,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1925
2028
  }
1926
2029
 
1927
2030
  if (event.name === 'run_start') {
2031
+ this.#trackQuarantinedSnapshots()
1928
2032
  const registerResultHandler = dynamicAtrResultHandlerRegistrations.get(this)
1929
2033
  dynamicAtrResultHandlerRegistrations.delete(this)
1930
2034
  registerResultHandler?.()
1931
2035
  } else if (event.name === 'setup') {
2036
+ this.#expect = event.runtimeGlobals?.expect ?? this.global.expect
1932
2037
  this.wrapConcurrentTest(state)
1933
2038
  this.bindTestEach(this.global.test)
1934
2039
  }
@@ -2044,6 +2149,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
2044
2149
  ctx.concurrentTestState = concurrentTestState
2045
2150
  concurrentTestState.ctx = ctx
2046
2151
  }
2152
+ if (isQuarantined && !isAttemptToFix && !event.test.concurrent && !ctx.concurrentTestState) {
2153
+ if (this.#restoreSnapshotState) {
2154
+ ctx.unmatchedSnapshots = 0
2155
+ } else if (!this.hasConcurrentTests) {
2156
+ ctx.unmatchedSnapshotsBeforeTest = this.getSnapshotState()?.unmatched
2157
+ }
2158
+ }
2047
2159
  testContexts.set(event.test, ctx)
2048
2160
 
2049
2161
  if (isAttemptToFix) {
@@ -2282,6 +2394,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
2282
2394
  event.test.errors = []
2283
2395
  }
2284
2396
  }
2397
+ this.ignoreQuarantinedSnapshotResults(event.test)
2285
2398
 
2286
2399
  // Quarantine must consume terminal failures before we save errors for Jest's final result.
2287
2400
  if (dynamicAtrCount !== undefined && failedAllTests && event.test.errors?.length) {
@@ -2359,6 +2472,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
2359
2472
  ctx.currentStore = undefined
2360
2473
  }
2361
2474
  } else if (event.name === 'run_finish') {
2475
+ this.#restoreSnapshotState?.()
2476
+ this.#restoreSnapshotState = undefined
2362
2477
  this.removeDiscardedEfdRetries()
2363
2478
  for (const [test, errors] of atrSuppressedErrors) {
2364
2479
  // Do not restore errors for non-ATF quarantined tests — they should stay suppressed
@@ -2528,6 +2643,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
2528
2643
  }
2529
2644
 
2530
2645
  teardown () {
2646
+ this.#restoreSnapshotState?.()
2647
+ this.#restoreSnapshotState = undefined
2531
2648
  if (this._globalProxy?.propertyToValue) {
2532
2649
  for (const [key] of this._globalProxy.propertyToValue) {
2533
2650
  if (typeof key === 'string' && key.startsWith('_dd')) {
@@ -2557,10 +2674,7 @@ function getTestEnvironment (pkg, jestVersion) {
2557
2674
  const wrappedTestEnvironment = getWrappedEnvironment(pkg.default, jestVersion)
2558
2675
  return new Proxy(pkg, {
2559
2676
  get (target, prop) {
2560
- if (prop === 'default') {
2561
- return wrappedTestEnvironment
2562
- }
2563
- if (prop === 'TestEnvironment') {
2677
+ if (prop === 'default' || prop === 'TestEnvironment') {
2564
2678
  return wrappedTestEnvironment
2565
2679
  }
2566
2680
  return target[prop]
@@ -4377,10 +4491,7 @@ function sendWrapper (send) {
4377
4491
  // In here we modify the `config.testEnvironmentOptions` to include the known tests for the suite.
4378
4492
  // This way the suite only knows about the tests that are part of it.
4379
4493
  const args = request.at(-1)
4380
- if (args.length > 1) {
4381
- return send.apply(this, arguments)
4382
- }
4383
- if (!args[0]?.config) {
4494
+ if (args.length > 1 || !args[0]?.config) {
4384
4495
  return send.apply(this, arguments)
4385
4496
  }
4386
4497
  const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
@@ -62,13 +62,13 @@ addHook({
62
62
  const rawResult = raw.apply(this, args)
63
63
  shimmer.wrap(rawResult, 'then', originalThen => function (...args) {
64
64
  return rawQuerySubscribes.runStores(context, () => {
65
- args[0] = wrapCallbackWithFinish(args[0], finish, context)
66
- if (args[1]) args[1] = wrapCallbackWithFinish(args[1], finish, context)
65
+ args[0] = wrapCallbackWithFinish(args[0], context)
66
+ if (args[1]) args[1] = wrapCallbackWithFinish(args[1], context)
67
67
 
68
68
  const originalThenResult = originalThen.apply(this, args)
69
69
 
70
70
  shimmer.wrap(originalThenResult, 'catch', originalCatch => function (...args) {
71
- args[0] = wrapCallbackWithFinish(args[0], finish, context)
71
+ args[0] = wrapCallbackWithFinish(args[0], context)
72
72
  return originalCatch.apply(this, args)
73
73
  })
74
74
 
@@ -78,7 +78,7 @@ addHook({
78
78
 
79
79
  shimmer.wrap(rawResult, 'asCallback', originalAsCallback => function (...args) {
80
80
  return rawQuerySubscribes.runStores(context, () => {
81
- args[0] = wrapCallbackWithFinish(args[0], finish, context)
81
+ args[0] = wrapCallbackWithFinish(args[0], context)
82
82
  return originalAsCallback.apply(this, args)
83
83
  })
84
84
  })
@@ -118,7 +118,11 @@ addHook({
118
118
  return Transaction
119
119
  })
120
120
 
121
- function wrapCallbackWithFinish (callback, finish, context) {
121
+ /**
122
+ * @param {unknown} callback
123
+ * @param {object} context
124
+ */
125
+ function wrapCallbackWithFinish (callback, context) {
122
126
  if (typeof callback !== 'function') return callback
123
127
 
124
128
  return shimmer.wrapFunction(callback, callback => function (...args) {
@@ -1055,9 +1055,9 @@ function createWrapCallbackGetConnection (options) {
1055
1055
  return function wrapGetConnection (getConnection) {
1056
1056
  return function () {
1057
1057
  const callback = arguments[arguments.length - 1]
1058
- if (typeof callback !== 'function') return getConnection.apply(this, arguments)
1059
-
1060
- if (!connectionStartCh.hasSubscribers) return getConnection.apply(this, arguments)
1058
+ if (typeof callback !== 'function' || !connectionStartCh.hasSubscribers) {
1059
+ return getConnection.apply(this, arguments)
1060
+ }
1061
1061
 
1062
1062
  if (!acquireStartCh.hasSubscribers) {
1063
1063
  const ctx = {}
@@ -11,33 +11,45 @@ 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
+ /** @typedef {{ moduleBaseDir?: string }} MochaHookMeta */
15
+
14
16
  /**
15
17
  * Registers every Mocha package view that can expose the constructor.
16
18
  *
17
19
  * @param {string[]} versions
18
- * @param {(Mocha: Function, frameworkVersion: string) => Function} wrapMochaRun
20
+ * @param {(Mocha: Function, frameworkVersion: string, hookMeta: MochaHookMeta) => Function} wrapMochaRun
19
21
  */
20
22
  function addMochaRunHooks (versions, wrapMochaRun) {
21
- const patchedMochaConstructors = new WeakSet()
23
+ const patchedMochaConstructors = new WeakMap()
22
24
 
23
25
  /**
24
26
  * @param {Function} Mocha
25
27
  * @param {string} frameworkVersion
28
+ * @param {boolean | undefined} isIitm
29
+ * @param {{ moduleBaseDir: string } | undefined} hookMeta
26
30
  * @returns {Function}
27
31
  */
28
- function wrapMochaOnce (Mocha, frameworkVersion) {
29
- if (patchedMochaConstructors.has(Mocha)) return Mocha
30
- patchedMochaConstructors.add(Mocha)
31
- return wrapMochaRun(Mocha, frameworkVersion)
32
+ function wrapMochaOnce (Mocha, frameworkVersion, isIitm, hookMeta) {
33
+ let metadata = patchedMochaConstructors.get(Mocha)
34
+ if (metadata) {
35
+ // A bundler can expose the constructor before an ordinary loader supplies its package directory.
36
+ metadata.moduleBaseDir ??= hookMeta?.moduleBaseDir
37
+ return Mocha
38
+ }
39
+ metadata = { moduleBaseDir: hookMeta?.moduleBaseDir }
40
+ patchedMochaConstructors.set(Mocha, metadata)
41
+ return wrapMochaRun(Mocha, frameworkVersion, metadata)
32
42
  }
33
43
 
34
44
  /**
35
45
  * @param {object|Function} MochaPackage
36
46
  * @param {string} frameworkVersion
47
+ * @param {boolean | undefined} isIitm
48
+ * @param {{ moduleBaseDir: string } | undefined} hookMeta
37
49
  * @returns {object|Function}
38
50
  */
39
- function wrapMochaPackage (MochaPackage, frameworkVersion) {
40
- wrapMochaOnce(MochaPackage.default ?? MochaPackage, frameworkVersion)
51
+ function wrapMochaPackage (MochaPackage, frameworkVersion, isIitm, hookMeta) {
52
+ wrapMochaOnce(MochaPackage.default ?? MochaPackage, frameworkVersion, isIitm, hookMeta)
41
53
  return MochaPackage
42
54
  }
43
55