dd-trace 5.102.0 → 5.104.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/ext/exporters.js +1 -0
  2. package/index.d.ts +25 -3
  3. package/package.json +15 -13
  4. package/packages/datadog-esbuild/src/utils.js +2 -2
  5. package/packages/datadog-instrumentations/src/ai.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  7. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  8. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +32 -15
  9. package/packages/datadog-instrumentations/src/couchbase.js +69 -220
  10. package/packages/datadog-instrumentations/src/cucumber.js +104 -31
  11. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  12. package/packages/datadog-instrumentations/src/electron/preload.js +42 -0
  13. package/packages/datadog-instrumentations/src/electron.js +240 -0
  14. package/packages/datadog-instrumentations/src/fetch.js +5 -5
  15. package/packages/datadog-instrumentations/src/graphql.js +13 -17
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +2 -2
  18. package/packages/datadog-instrumentations/src/helpers/hook.js +4 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +58 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -5
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +14 -13
  25. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  26. package/packages/datadog-instrumentations/src/ioredis.js +18 -14
  27. package/packages/datadog-instrumentations/src/jest.js +382 -84
  28. package/packages/datadog-instrumentations/src/kafkajs.js +184 -174
  29. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  30. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  31. package/packages/datadog-instrumentations/src/mocha/main.js +309 -56
  32. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  33. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -9
  34. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  35. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  36. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  37. package/packages/datadog-instrumentations/src/pg.js +25 -11
  38. package/packages/datadog-instrumentations/src/playwright.js +449 -60
  39. package/packages/datadog-instrumentations/src/redis.js +19 -10
  40. package/packages/datadog-instrumentations/src/router.js +4 -2
  41. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  42. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -21
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +18 -24
  44. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +1 -1
  45. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  46. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  47. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  48. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -1
  49. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  50. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -2
  51. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  52. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
  53. package/packages/datadog-plugin-couchbase/src/index.js +58 -52
  54. package/packages/datadog-plugin-cucumber/src/index.js +1 -0
  55. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +239 -40
  56. package/packages/datadog-plugin-cypress/src/support.js +13 -1
  57. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  58. package/packages/datadog-plugin-electron/src/index.js +17 -0
  59. package/packages/datadog-plugin-electron/src/ipc.js +143 -0
  60. package/packages/datadog-plugin-electron/src/net.js +82 -0
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +27 -18
  62. package/packages/datadog-plugin-graphql/src/execute.js +6 -28
  63. package/packages/datadog-plugin-graphql/src/resolve.js +30 -35
  64. package/packages/datadog-plugin-graphql/src/tools/signature.js +32 -7
  65. package/packages/datadog-plugin-graphql/src/tools/transforms.js +118 -100
  66. package/packages/datadog-plugin-graphql/src/utils.js +33 -1
  67. package/packages/datadog-plugin-grpc/src/client.js +6 -7
  68. package/packages/datadog-plugin-grpc/src/util.js +57 -22
  69. package/packages/datadog-plugin-http/src/client.js +2 -2
  70. package/packages/datadog-plugin-jest/src/index.js +92 -50
  71. package/packages/datadog-plugin-kafkajs/src/producer.js +32 -0
  72. package/packages/datadog-plugin-mocha/src/index.js +1 -0
  73. package/packages/datadog-plugin-mongodb-core/src/index.js +70 -69
  74. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  75. package/packages/datadog-plugin-openai/src/services.js +2 -1
  76. package/packages/datadog-plugin-pg/src/index.js +3 -3
  77. package/packages/datadog-plugin-playwright/src/index.js +4 -0
  78. package/packages/datadog-plugin-redis/src/index.js +54 -24
  79. package/packages/datadog-plugin-undici/src/index.js +19 -0
  80. package/packages/datadog-plugin-vitest/src/index.js +19 -7
  81. package/packages/datadog-shimmer/src/shimmer.js +35 -0
  82. package/packages/dd-trace/src/aiguard/index.js +3 -1
  83. package/packages/dd-trace/src/aiguard/sdk.js +36 -30
  84. package/packages/dd-trace/src/aiguard/tags.js +20 -11
  85. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  86. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +2 -2
  87. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  88. package/packages/dd-trace/src/appsec/index.js +10 -3
  89. package/packages/dd-trace/src/appsec/reporter.js +19 -5
  90. package/packages/dd-trace/src/azure_metadata.js +17 -6
  91. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +4 -4
  92. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -4
  94. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +1 -1
  95. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  96. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  97. package/packages/dd-trace/src/config/defaults.js +3 -14
  98. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -1
  99. package/packages/dd-trace/src/config/helper.js +4 -0
  100. package/packages/dd-trace/src/config/index.js +2 -2
  101. package/packages/dd-trace/src/config/major-overrides.js +98 -0
  102. package/packages/dd-trace/src/config/parsers.js +7 -1
  103. package/packages/dd-trace/src/config/supported-configurations.json +60 -38
  104. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  105. package/packages/dd-trace/src/datastreams/checkpointer.js +2 -2
  106. package/packages/dd-trace/src/datastreams/context.js +4 -2
  107. package/packages/dd-trace/src/datastreams/manager.js +1 -1
  108. package/packages/dd-trace/src/datastreams/processor.js +2 -2
  109. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +2 -2
  110. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
  111. package/packages/dd-trace/src/debugger/devtools_client/state.js +2 -1
  112. package/packages/dd-trace/src/debugger/index.js +7 -7
  113. package/packages/dd-trace/src/dogstatsd.js +2 -2
  114. package/packages/dd-trace/src/encode/0.4.js +45 -54
  115. package/packages/dd-trace/src/encode/0.5.js +34 -3
  116. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +26 -19
  117. package/packages/dd-trace/src/encode/agentless-json.js +1 -1
  118. package/packages/dd-trace/src/exporter.js +2 -0
  119. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  120. package/packages/dd-trace/src/exporters/agentless/index.js +3 -2
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +2 -2
  122. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  123. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -1
  124. package/packages/dd-trace/src/exporters/common/request.js +4 -2
  125. package/packages/dd-trace/src/exporters/electron/index.js +49 -0
  126. package/packages/dd-trace/src/external-logger/src/index.js +2 -1
  127. package/packages/dd-trace/src/git_metadata.js +10 -8
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler-paths.js +52 -0
  130. package/packages/dd-trace/src/lambda/handler.js +2 -4
  131. package/packages/dd-trace/src/lambda/index.js +62 -14
  132. package/packages/dd-trace/src/lambda/runtime/patch.js +21 -46
  133. package/packages/dd-trace/src/llmobs/index.js +13 -2
  134. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +45 -15
  135. package/packages/dd-trace/src/llmobs/sdk.js +10 -0
  136. package/packages/dd-trace/src/llmobs/writers/base.js +2 -1
  137. package/packages/dd-trace/src/log/writer.js +3 -1
  138. package/packages/dd-trace/src/noop/span.js +3 -1
  139. package/packages/dd-trace/src/openfeature/writers/base.js +2 -1
  140. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  141. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  142. package/packages/dd-trace/src/opentracing/propagation/text_map.js +20 -9
  143. package/packages/dd-trace/src/payload-tagging/config/index.js +2 -2
  144. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  145. package/packages/dd-trace/src/plugins/ci_plugin.js +52 -17
  146. package/packages/dd-trace/src/plugins/database.js +54 -12
  147. package/packages/dd-trace/src/plugins/index.js +1 -0
  148. package/packages/dd-trace/src/plugins/log_plugin.js +3 -1
  149. package/packages/dd-trace/src/plugins/plugin.js +2 -4
  150. package/packages/dd-trace/src/plugins/tracing.js +5 -3
  151. package/packages/dd-trace/src/plugins/util/ci.js +8 -8
  152. package/packages/dd-trace/src/plugins/util/git-cache.js +20 -18
  153. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  154. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -2
  155. package/packages/dd-trace/src/plugins/util/test.js +119 -5
  156. package/packages/dd-trace/src/plugins/util/user-provided-git.js +17 -15
  157. package/packages/dd-trace/src/plugins/util/web.js +11 -0
  158. package/packages/dd-trace/src/priority_sampler.js +1 -1
  159. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  160. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  161. package/packages/dd-trace/src/profiling/ssi-heuristics.js +1 -1
  162. package/packages/dd-trace/src/rate_limiter.js +1 -1
  163. package/packages/dd-trace/src/remote_config/scheduler.js +1 -1
  164. package/packages/dd-trace/src/ritm.js +2 -1
  165. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -8
  166. package/packages/dd-trace/src/scope.js +7 -5
  167. package/packages/dd-trace/src/serverless.js +5 -2
  168. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  169. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +20 -0
  170. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  171. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +20 -0
  172. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  173. package/packages/dd-trace/src/span_stats.js +1 -1
  174. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  175. package/packages/dd-trace/src/telemetry/endpoints.js +1 -1
  176. package/packages/dd-trace/src/telemetry/telemetry.js +2 -2
  177. package/packages/dd-trace/src/lambda/runtime/ritm.js +0 -133
  178. package/vendor/dist/opentracing/LICENSE +0 -201
  179. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  180. package/vendor/dist/opentracing/constants.d.ts +0 -61
  181. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  182. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  183. package/vendor/dist/opentracing/functions.d.ts +0 -20
  184. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  185. package/vendor/dist/opentracing/index.d.ts +0 -12
  186. package/vendor/dist/opentracing/index.js +0 -1
  187. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  188. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  189. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  190. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  191. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  192. package/vendor/dist/opentracing/noop.d.ts +0 -8
  193. package/vendor/dist/opentracing/reference.d.ts +0 -33
  194. package/vendor/dist/opentracing/span.d.ts +0 -147
  195. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  196. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  197. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  198. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  199. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  200. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  201. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -18,6 +18,7 @@ const {
18
18
  getIsFaultyEarlyFlakeDetection,
19
19
  collectTestOptimizationSummariesFromTraces,
20
20
  logTestOptimizationSummary,
21
+ getTestOptimizationRequestResults,
21
22
  } = require('../../../dd-trace/src/plugins/util/test')
22
23
 
23
24
  const {
@@ -109,6 +110,29 @@ function isTestFailed (test) {
109
110
  return false
110
111
  }
111
112
 
113
+ function getRootSuiteStatus (rootTests) {
114
+ let status = 'pass'
115
+ if (rootTests.every(t => t.isPending())) {
116
+ status = 'skip'
117
+ } else {
118
+ for (const test of rootTests) {
119
+ if (test.state === 'failed' || test.timedOut || test._ddHookFailed) {
120
+ status = 'fail'
121
+ }
122
+ }
123
+ }
124
+ return status
125
+ }
126
+
127
+ function haveRootTestsFinished (rootTests) {
128
+ for (const test of rootTests) {
129
+ if (!test.isPending() && !test.state && !test.timedOut && !test._ddHookFailed) {
130
+ return false
131
+ }
132
+ }
133
+ return true
134
+ }
135
+
112
136
  function getFilteredSuites (originalSuites) {
113
137
  return originalSuites.reduce((acc, suite) => {
114
138
  const testPath = getTestSuitePath(suite.file, process.cwd())
@@ -226,11 +250,38 @@ function getOnEndHandler (isParallel) {
226
250
  }
227
251
  }
228
252
 
253
+ function getRunStoresPromise (channelToPublishTo, ctx) {
254
+ return new Promise(resolve => {
255
+ channelToPublishTo.runStores({ ...ctx, onDone: resolve }, () => {})
256
+ })
257
+ }
258
+
259
+ function applyKnownTestsResponse ({ err, knownTests }) {
260
+ if (err) {
261
+ config.knownTests = []
262
+ config.isEarlyFlakeDetectionEnabled = false
263
+ config.isKnownTestsEnabled = false
264
+ } else {
265
+ config.knownTests = knownTests
266
+ }
267
+ }
268
+
269
+ function applyTestManagementTestsResponse ({ err, testManagementTests: receivedTestManagementTests }) {
270
+ if (err) {
271
+ config.testManagementTests = {}
272
+ config.isTestManagementTestsEnabled = false
273
+ config.testManagementAttemptToFixRetries = 0
274
+ } else {
275
+ config.testManagementTests = receivedTestManagementTests
276
+ }
277
+ }
278
+
229
279
  function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFinishRequest) {
230
280
  const ctx = {
231
281
  isParallel,
232
282
  frameworkVersion,
233
283
  }
284
+ let skippableSuitesResponse
234
285
 
235
286
  const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
236
287
  if (err) {
@@ -256,6 +307,16 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
256
307
  })
257
308
  }
258
309
 
310
+ const requestSkippableSuites = () => {
311
+ if (skippableSuitesResponse) {
312
+ onReceivedSkippableSuites(skippableSuitesResponse)
313
+ return
314
+ }
315
+
316
+ ctx.onDone = onReceivedSkippableSuites
317
+ skippableSuitesCh.runStores(ctx, () => {})
318
+ }
319
+
259
320
  const onReceivedImpactedTests = ({ err, modifiedFiles: receivedModifiedFiles }) => {
260
321
  if (err) {
261
322
  config.modifiedFiles = []
@@ -264,8 +325,7 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
264
325
  config.modifiedFiles = receivedModifiedFiles
265
326
  }
266
327
  if (config.isSuitesSkippingEnabled) {
267
- ctx.onDone = onReceivedSkippableSuites
268
- skippableSuitesCh.runStores(ctx, () => {})
328
+ requestSkippableSuites()
269
329
  } else {
270
330
  mochaGlobalRunCh.runStores(ctx, () => {
271
331
  onFinishRequest()
@@ -273,44 +333,12 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
273
333
  }
274
334
  }
275
335
 
276
- const onReceivedTestManagementTests = ({ err, testManagementTests: receivedTestManagementTests }) => {
277
- if (err) {
278
- config.testManagementTests = {}
279
- config.isTestManagementTestsEnabled = false
280
- config.testManagementAttemptToFixRetries = 0
281
- } else {
282
- config.testManagementTests = receivedTestManagementTests
283
- }
336
+ const continueAfterTestRequests = () => {
284
337
  if (config.isImpactedTestsEnabled) {
285
338
  ctx.onDone = onReceivedImpactedTests
286
339
  modifiedFilesCh.runStores(ctx, () => {})
287
340
  } else if (config.isSuitesSkippingEnabled) {
288
- ctx.onDone = onReceivedSkippableSuites
289
- skippableSuitesCh.runStores(ctx, () => {})
290
- } else {
291
- mochaGlobalRunCh.runStores(ctx, () => {
292
- onFinishRequest()
293
- })
294
- }
295
- }
296
-
297
- const onReceivedKnownTests = ({ err, knownTests }) => {
298
- if (err) {
299
- config.knownTests = []
300
- config.isEarlyFlakeDetectionEnabled = false
301
- config.isKnownTestsEnabled = false
302
- } else {
303
- config.knownTests = knownTests
304
- }
305
- if (config.isTestManagementTestsEnabled) {
306
- ctx.onDone = onReceivedTestManagementTests
307
- testManagementTestsCh.runStores(ctx, () => {})
308
- } else if (config.isImpactedTestsEnabled) {
309
- ctx.onDone = onReceivedImpactedTests
310
- modifiedFilesCh.runStores(ctx, () => {})
311
- } else if (config.isSuitesSkippingEnabled) {
312
- ctx.onDone = onReceivedSkippableSuites
313
- skippableSuitesCh.runStores(ctx, () => {})
341
+ requestSkippableSuites()
314
342
  } else {
315
343
  mochaGlobalRunCh.runStores(ctx, () => {
316
344
  onFinishRequest()
@@ -336,23 +364,30 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
336
364
  config.isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
337
365
  config.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
338
366
 
339
- if (config.isKnownTestsEnabled) {
340
- ctx.onDone = onReceivedKnownTests
341
- knownTestsCh.runStores(ctx, () => {})
342
- } else if (config.isTestManagementTestsEnabled) {
343
- ctx.onDone = onReceivedTestManagementTests
344
- testManagementTestsCh.runStores(ctx, () => {})
345
- } else if (config.isImpactedTestsEnabled) {
346
- ctx.onDone = onReceivedImpactedTests
347
- modifiedFilesCh.runStores(ctx, () => {})
348
- } else if (config.isSuitesSkippingEnabled) {
349
- ctx.onDone = onReceivedSkippableSuites
350
- skippableSuitesCh.runStores(ctx, () => {})
351
- } else {
352
- mochaGlobalRunCh.runStores(ctx, () => {
353
- onFinishRequest()
354
- })
355
- }
367
+ getTestOptimizationRequestResults({
368
+ isKnownTestsEnabled: config.isKnownTestsEnabled,
369
+ isTestManagementTestsEnabled: config.isTestManagementTestsEnabled,
370
+ isSuitesSkippingEnabled: config.isSuitesSkippingEnabled,
371
+ getKnownTests: () => getRunStoresPromise(knownTestsCh, ctx),
372
+ getTestManagementTests: () => getRunStoresPromise(testManagementTestsCh, ctx),
373
+ getSkippableSuites: () => getRunStoresPromise(skippableSuitesCh, ctx),
374
+ }).then(requestResults => {
375
+ const {
376
+ knownTestsResponse,
377
+ testManagementTestsResponse,
378
+ skippableSuitesResponse: requestSkippableSuitesResponse,
379
+ } = requestResults
380
+
381
+ if (knownTestsResponse) {
382
+ applyKnownTestsResponse(knownTestsResponse)
383
+ }
384
+ if (testManagementTestsResponse) {
385
+ applyTestManagementTestsResponse(testManagementTestsResponse)
386
+ }
387
+ skippableSuitesResponse = requestSkippableSuitesResponse
388
+
389
+ continueAfterTestRequests()
390
+ })
356
391
  }
357
392
 
358
393
  ctx.onDone = onReceivedConfiguration
@@ -469,26 +504,204 @@ addHook({
469
504
  }
470
505
 
471
506
  const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
507
+ // Root-level tests (direct children of root, no describe wrapper) keyed by file.
508
+ // Populated during the root 'suite' event so the normal finish path can include them
509
+ // in mixed-file status calculation.
510
+ const rootTestsByFile = new Map()
511
+ // Counts how many original tests per pure-root file still need their final attempt.
512
+ // Hits zero when the last test's lifecycle completes, triggering the suite finish.
513
+ const rootPendingCountByFile = new Map()
514
+ const rootFinalizationPendingCountByFile = new Map()
515
+ const rootFallbackPendingFiles = new Set()
516
+ const rootFinalizationPendingTests = new WeakSet()
517
+ let pendingRootFinalizations = 0
518
+ let hasEnded = false
519
+ let hasFinishedRun = false
520
+ let endRunner
521
+
522
+ function updateRootTestForFinalAttempt (test) {
523
+ if (!test._retriedTest) return
524
+
525
+ const rootTests = rootTestsByFile.get(test.file)
526
+ if (!rootTests) return
527
+
528
+ const retriedTestIndex = rootTests.indexOf(test._retriedTest)
529
+ if (retriedTestIndex !== -1) {
530
+ rootTests[retriedTestIndex] = test
531
+ }
532
+ }
533
+
534
+ function finishRunIfReady () {
535
+ if (hasFinishedRun) return
536
+ if (hasEnded && pendingRootFinalizations === 0) {
537
+ hasFinishedRun = true
538
+ onEnd.call(endRunner)
539
+ }
540
+ }
541
+
542
+ function incrementPendingRootFinalization (test) {
543
+ if (!rootPendingCountByFile.has(test.file) || rootFinalizationPendingTests.has(test)) return
544
+
545
+ rootFinalizationPendingTests.add(test)
546
+ pendingRootFinalizations++
547
+ rootFinalizationPendingCountByFile.set(
548
+ test.file,
549
+ (rootFinalizationPendingCountByFile.get(test.file) || 0) + 1
550
+ )
551
+ }
552
+
553
+ function decrementPendingRootFinalization (test) {
554
+ if (!rootFinalizationPendingTests.has(test)) return
555
+
556
+ rootFinalizationPendingTests.delete(test)
557
+ pendingRootFinalizations--
558
+
559
+ const remaining = rootFinalizationPendingCountByFile.get(test.file) - 1
560
+ if (remaining > 0) {
561
+ rootFinalizationPendingCountByFile.set(test.file, remaining)
562
+ } else {
563
+ rootFinalizationPendingCountByFile.delete(test.file)
564
+ }
565
+
566
+ if (!rootFinalizationPendingCountByFile.has(test.file) && rootFallbackPendingFiles.delete(test.file)) {
567
+ finishRootSuiteFallbackForFile(test.file)
568
+ }
569
+
570
+ finishRunIfReady()
571
+ }
572
+
573
+ function finishRootSuiteForFile (file) {
574
+ const remaining = rootPendingCountByFile.get(file) - 1
575
+ if (remaining > 0) {
576
+ rootPendingCountByFile.set(file, remaining)
577
+ return
578
+ }
579
+ rootPendingCountByFile.delete(file)
580
+
581
+ const ctx = testFileToSuiteCtx.get(file)
582
+ if (!ctx) {
583
+ log.warn('No ctx found for suite', file)
584
+ return
585
+ }
586
+
587
+ const rootTests = rootTestsByFile.get(file) || []
588
+ const status = getRootSuiteStatus(rootTests)
589
+
590
+ if (global.__coverage__) {
591
+ const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
592
+ testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
593
+ mergeCoverage(global.__coverage__, originalCoverageMap)
594
+ resetCoverage(global.__coverage__)
595
+ }
596
+
597
+ testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
598
+ }
599
+
600
+ function finishRootSuiteFallbackForFile (file) {
601
+ const ctx = testFileToSuiteCtx.get(file)
602
+ if (!ctx || !rootPendingCountByFile.has(file)) return
603
+
604
+ const rootTests = rootTestsByFile.get(file) || []
605
+ const status = haveRootTestsFinished(rootTests) ? getRootSuiteStatus(rootTests) : 'fail'
606
+ rootPendingCountByFile.delete(file)
607
+ testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
608
+ }
609
+
610
+ function finishRootSuiteAfterFinalAttempt (test) {
611
+ if (!test._ddIsFinalAttempt || !rootPendingCountByFile.has(test.file)) return
612
+
613
+ updateRootTestForFinalAttempt(test)
614
+ finishRootSuiteForFile(test.file)
615
+ }
616
+
617
+ const onEnd = getOnEndHandler(false)
472
618
 
473
619
  this.once('start', getOnStartHandler(frameworkVersion))
474
620
 
475
- this.once('end', getOnEndHandler(false))
621
+ this.once('end', function () {
622
+ hasEnded = true
623
+ endRunner = this
624
+ finishRunIfReady()
625
+ })
626
+
627
+ // The job of this listener is to
628
+ // initialize the suite span tag in correct order
629
+ // (that is suiteA -> testA ... -> suiteB -> testB
630
+ // instead of suiteA -> suiteB -> testA -> ... -> testB)
631
+ // when the suite has tests that are in the top level
632
+ // (no describe(...))
633
+ this.on('test', function (test) {
634
+ const ctx = testFileToSuiteCtx.get(test.file)
635
+ if (ctx?._pendingRootStart) {
636
+ ctx._pendingRootStart = false
637
+ testSuiteStartCh.runStores(ctx, () => {})
638
+ }
639
+ })
476
640
 
477
641
  this.on('test', getOnTestHandler(true))
478
642
 
479
- this.on('test end', getOnTestEndHandler(config))
643
+ this.on('test end', getOnTestEndHandler(config, {
644
+ onStart: incrementPendingRootFinalization,
645
+ onFinish: function (test) {
646
+ finishRootSuiteAfterFinalAttempt(test)
647
+ decrementPendingRootFinalization(test)
648
+ },
649
+ }))
480
650
 
481
651
  this.on('retry', getOnTestRetryHandler(config))
482
652
 
483
653
  // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
484
654
  this.on('hook end', getOnHookEndHandler(config))
485
655
 
656
+ this.on('hook end', function (hook) {
657
+ const test = hook.ctx?.currentTest
658
+ if (!test) return
659
+ finishRootSuiteAfterFinalAttempt(test)
660
+ })
661
+
486
662
  this.on('fail', getOnFailHandler(true, config))
487
663
 
664
+ this.on('fail', function (testOrHook) {
665
+ if (testOrHook.type !== 'hook') return
666
+ const test = testOrHook.ctx?.currentTest
667
+ if (!test) return
668
+ finishRootSuiteAfterFinalAttempt(test)
669
+ })
670
+
488
671
  this.on('pending', getOnPendingHandler())
489
672
 
490
673
  this.on('suite', function (suite) {
491
674
  if (suite.root || !suite.tests.length) {
675
+ // This branch can be triggered when we have top level it(...) inside test files.
676
+ // In that case, they all (even if they are from different files) are going to be
677
+ // children of the root suite.
678
+ // Note: We could have suites that contain top level it(...) and also it(...) nested
679
+ // inside describe(...) ("mixed case"). Duplication is avoided by the context guard
680
+ // below. Since 'suite' fires for root first, in the mixed case the ctx is created
681
+ // here and the describe-based handler finds it already set.
682
+ if (suite.root && suite.tests.length > 0) {
683
+ const files = new Set(suite.tests.map(test => test.file).filter(Boolean))
684
+ for (const file of files) {
685
+ const testsForFile = suite.tests.filter(t => t.file === file)
686
+ rootTestsByFile.set(file, testsForFile)
687
+ // Only track the countdown for pure root-level files.
688
+ // Mixed files are finished by the normal 'suite end' path.
689
+ if (!suitesByTestFile[file]) {
690
+ rootPendingCountByFile.set(file, testsForFile.length)
691
+ }
692
+ if (testFileToSuiteCtx.get(file)) continue
693
+ const isUnskippable = unskippableSuites.includes(file)
694
+ isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(file, process.cwd()))
695
+ const ctx = {
696
+ testSuiteAbsolutePath: file,
697
+ isUnskippable,
698
+ isForcedToRun,
699
+ itrCorrelationId,
700
+ _pendingRootStart: true, // Now the suite start fires lazily on the first test event for this file
701
+ }
702
+ testFileToSuiteCtx.set(file, ctx)
703
+ }
704
+ }
492
705
  return
493
706
  }
494
707
  let ctx = testFileToSuiteCtx.get(suite.file)
@@ -508,6 +721,40 @@ addHook({
508
721
 
509
722
  this.on('suite end', function (suite) {
510
723
  if (suite.root) {
724
+ // Normal case: pure root-level files are finished by the 'test end' / 'hook end'
725
+ // listeners via finishRootSuiteForFile. Two edge cases remain here:
726
+ //
727
+ // 1. All-pending: no 'test' event fired, _pendingRootStart is still true.
728
+ // Start and immediately finish with 'skip'.
729
+ //
730
+ // 2. Aborted mid-run (e.g. a beforeEach hook failure): Mocha skips remaining
731
+ // tests and jumps straight to 'suite end'. rootPendingCountByFile still has
732
+ // a nonzero count for the file because the last tests never ran. Finish it
733
+ // as failed now.
734
+ //
735
+ // 3. Async finalization lagged behind Mocha's synchronous events (e.g. DI retry
736
+ // wait): all tests have Mocha terminal state, but the final-attempt callback
737
+ // did not run before root 'suite end'. Finish from the observed test states.
738
+ const processedFiles = new Set()
739
+ for (const test of suite.tests) {
740
+ if (!test.file || processedFiles.has(test.file)) continue
741
+ processedFiles.add(test.file)
742
+ if (suitesByTestFile[test.file]) continue // mixed: handled by normal path
743
+ const ctx = testFileToSuiteCtx.get(test.file)
744
+ if (!ctx) continue
745
+ if (ctx._pendingRootStart) {
746
+ ctx._pendingRootStart = false
747
+ testSuiteStartCh.runStores(ctx, () => {})
748
+ testSuiteFinishCh.publish({ status: 'skip', ...ctx.currentStore }, () => {})
749
+ } else if (rootPendingCountByFile.has(test.file)) {
750
+ if (rootFinalizationPendingCountByFile.has(test.file)) {
751
+ rootFallbackPendingFiles.add(test.file)
752
+ continue
753
+ }
754
+
755
+ finishRootSuiteFallbackForFile(test.file)
756
+ }
757
+ }
511
758
  return
512
759
  }
513
760
  const suitesInTestFile = suitesByTestFile[suite.file]
@@ -517,8 +764,9 @@ addHook({
517
764
  return
518
765
  }
519
766
 
767
+ const rootTests = rootTestsByFile.get(suite.file) || []
520
768
  let status = 'pass'
521
- if (suitesInTestFile.every(suite => suite.pending)) {
769
+ if (suitesInTestFile.every(suite => suite.pending) && rootTests.every(test => test.isPending())) {
522
770
  status = 'skip'
523
771
  } else {
524
772
  // has to check every test in the test file
@@ -530,6 +778,11 @@ addHook({
530
778
  }
531
779
  })
532
780
  })
781
+ for (const test of rootTests) {
782
+ if (test.state === 'failed' || test.timedOut) {
783
+ status = 'fail'
784
+ }
785
+ }
533
786
  }
534
787
 
535
788
  if (global.__coverage__) {
@@ -503,13 +503,38 @@ function getTestFinishInfo (test, status, config, error) {
503
503
  }
504
504
  }
505
505
 
506
- function getOnTestEndHandler (config) {
506
+ function getOnTestEndHandler (config, finalAttemptHandlers) {
507
507
  return async function (test) {
508
508
  if (test._ddShouldSkipEfdRetry) {
509
509
  return
510
510
  }
511
511
  const ctx = getTestContext(test)
512
512
  const status = getTestStatus(test)
513
+ const shouldFinishTest = ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))
514
+ let testFinishInfo
515
+ let isFinalAttempt = false
516
+
517
+ // If there are afterEach to be run, we don't finish the test yet.
518
+ // Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
519
+ // In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
520
+ // getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
521
+ // directly sets finalStatus without waiting for hooks
522
+ if (!ctx && test.isPending()) {
523
+ test._ddIsFinalAttempt = true
524
+ isFinalAttempt = true
525
+ }
526
+
527
+ if (shouldFinishTest) {
528
+ testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
529
+ if (testFinishInfo.finalStatus !== undefined) {
530
+ test._ddIsFinalAttempt = true
531
+ isFinalAttempt = true
532
+ }
533
+ }
534
+
535
+ if (isFinalAttempt) {
536
+ finalAttemptHandlers?.onStart?.(test)
537
+ }
513
538
 
514
539
  // After finishing it might take a bit for the snapshot to be handled.
515
540
  // This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
@@ -521,13 +546,7 @@ function getOnTestEndHandler (config) {
521
546
  })
522
547
  }
523
548
 
524
- // If there are afterEach to be run, we don't finish the test yet.
525
- // Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
526
- // In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
527
- // getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
528
- // directly sets finalStatus without waiting for hooks
529
- if (ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))) {
530
- const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
549
+ if (shouldFinishTest) {
531
550
  testFinishCh.publish({
532
551
  status,
533
552
  hasBeenRetried: isMochaRetry(test),
@@ -536,6 +555,10 @@ function getOnTestEndHandler (config) {
536
555
  ...ctx.currentStore,
537
556
  })
538
557
  }
558
+
559
+ if (isFinalAttempt) {
560
+ finalAttemptHandlers?.onFinish?.(test)
561
+ }
539
562
  }
540
563
  }
541
564
 
@@ -552,6 +575,9 @@ function getOnHookEndHandler (config) {
552
575
  // skip to avoid double-publishing
553
576
  if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
554
577
  const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
578
+ if (testFinishInfo.finalStatus !== undefined) {
579
+ test._ddIsFinalAttempt = true
580
+ }
555
581
  testFinishCh.publish({
556
582
  status,
557
583
  hasBeenRetried: isMochaRetry(test),
@@ -583,6 +609,20 @@ function getOnFailHandler (isMain, config) {
583
609
  testContext.err = err
584
610
  errorCh.runStores(testContext, () => {})
585
611
  const testFinishInfo = getTestFinishInfo(test, 'fail', config, err)
612
+ // ATR never retries hook failures: this.retries(N) is set in runnableWrapper
613
+ // which only runs when the test function executes — hooks bypass that path,
614
+ // so _retries stays at -1 and getIsLastRetry returns false, leaving finalStatus
615
+ // undefined. We must also mark the attempt final when no clone-based retry
616
+ // mechanism (EFD original, EFD clone, ATF) has queued further attempts.
617
+ const noCloneRetries = !test._ddIsEfdRetry &&
618
+ !((test._ddIsNew || test._ddIsModified) && config.isEarlyFlakeDetectionEnabled) &&
619
+ !test._ddIsAttemptToFix
620
+ if (testFinishInfo.finalStatus !== undefined || noCloneRetries) {
621
+ test._ddIsFinalAttempt = true
622
+ }
623
+ // test.state is never set to 'failed' for hook failures (Mocha marks the hook,
624
+ // not the test). Flag it so finishRootSuiteForFile can compute the correct status.
625
+ test._ddHookFailed = true
586
626
  testFinishCh.publish({
587
627
  status: 'fail',
588
628
  hasBeenRetried: isMochaRetry(test),
@@ -11,6 +11,14 @@ const startCh = channel('apm:mongodb:query:start')
11
11
  const finishCh = channel('apm:mongodb:query:finish')
12
12
  const errorCh = channel('apm:mongodb:query:error')
13
13
 
14
+ // Per-Connection cached topology shape (mongodb >= 4). The Connection's `address` is immutable
15
+ // for the lifetime of the connection, so we synthesize the `{ s: { options } }` envelope the
16
+ // plugin expects only once per connection. A WeakMap keeps the cache off the foreign Connection
17
+ // instance — no extra own-key visible to `Reflect.ownKeys`, `Object.freeze`, or another tracer's
18
+ // instrumentation walking the connection.
19
+ /** @type {WeakMap<object, { s: { options: { host?: string, port?: string } } }>} */
20
+ const topologyCache = new WeakMap()
21
+
14
22
  addHook({ name: 'mongodb-core', versions: ['2 - 3.1.9'] }, Server => {
15
23
  const serverProto = Server.Server.prototype
16
24
  shimmer.wrap(serverProto, 'command', command => wrapCommand(command, 'command'))
@@ -88,21 +96,36 @@ function wrapUnifiedCommand (command, operation, name) {
88
96
  }
89
97
 
90
98
  function wrapConnectionCommand (command, operation, name, instrumentFn = instrument) {
99
+ const opts = { name }
91
100
  return function (ns, ops) {
92
101
  if (!startCh.hasSubscribers) {
93
102
  return command.apply(this, arguments)
94
103
  }
95
- const hostParts = typeof this.address === 'string' ? this.address.split(':') : ''
96
- const options = hostParts.length === 2
97
- ? { host: hostParts[0], port: hostParts[1] }
98
- : {} // no port means the address is a random UUID so no host either
99
- const topology = { s: { options } }
100
-
101
- ns = `${ns.db}.${ns.collection}`
102
- return instrumentFn(operation, command, this, arguments, topology, ns, ops, { name })
104
+ let topology = topologyCache.get(this)
105
+ if (topology === undefined) {
106
+ topology = synthesizeTopology(this.address)
107
+ topologyCache.set(this, topology)
108
+ }
109
+ return instrumentFn(operation, command, this, arguments, topology, `${ns.db}.${ns.collection}`, ops, opts)
103
110
  }
104
111
  }
105
112
 
113
+ /**
114
+ * @param {string} address
115
+ * @returns {{ s: { options: { host?: string, port?: string } } }}
116
+ */
117
+ function synthesizeTopology (address) {
118
+ if (typeof address === 'string') {
119
+ const colon = address.indexOf(':')
120
+ // Match the previous `.split(':')` length-2 check: exactly one colon with non-empty parts on both sides.
121
+ if (colon > 0 && colon < address.length - 1 && !address.includes(':', colon + 1)) {
122
+ return { s: { options: { host: address.slice(0, colon), port: address.slice(colon + 1) } } }
123
+ }
124
+ }
125
+ // No port means the address is a random UUID, an IPv6 form, or otherwise unparseable, so no host either.
126
+ return { s: { options: {} } }
127
+ }
128
+
106
129
  function wrapQuery (query, operation, name) {
107
130
  return function (...args) {
108
131
  if (!startCh.hasSubscribers) {
@@ -151,7 +174,7 @@ function instrument (operation, command, instance, args, server, ns, ops, option
151
174
  name,
152
175
  }
153
176
  return startCh.runStores(ctx, () => {
154
- args[index] = shimmer.wrapFunction(callback, callback => function (err, res) {
177
+ args[index] = shimmer.wrapCallback(callback, callback => function (err, res) {
155
178
  if (err) {
156
179
  ctx.error = err
157
180
  errorCh.publish(ctx)
@@ -171,6 +194,8 @@ function instrument (operation, command, instance, args, server, ns, ops, option
171
194
  })
172
195
  }
173
196
 
197
+ module.exports = { synthesizeTopology }
198
+
174
199
  function instrumentPromise (operation, command, instance, args, server, ns, ops, options = {}) {
175
200
  const name = options.name || (ops && Object.keys(ops)[0])
176
201
 
@@ -125,21 +125,19 @@ addHook({
125
125
  const resolve = args[0]
126
126
  const reject = args[1]
127
127
 
128
- args[0] = shimmer.wrapFunction(resolve, resolve => function wrappedResolve (...args) {
129
- finishCh.publish(ctx)
130
-
131
- if (resolve) {
128
+ if (typeof resolve === 'function') {
129
+ args[0] = shimmer.wrapCallback(resolve, resolve => function wrappedResolve (...args) {
130
+ finishCh.publish(ctx)
132
131
  return resolve.apply(this, args)
133
- }
134
- })
135
-
136
- args[1] = shimmer.wrapFunction(reject, reject => function wrappedReject (...args) {
137
- finishCh.publish(ctx)
132
+ })
133
+ }
138
134
 
139
- if (reject) {
135
+ if (typeof reject === 'function') {
136
+ args[1] = shimmer.wrapCallback(reject, reject => function wrappedReject (...args) {
137
+ finishCh.publish(ctx)
140
138
  return reject.apply(this, args)
141
- }
142
- })
139
+ })
140
+ }
143
141
 
144
142
  return originalThen.apply(this, args)
145
143
  }