dd-trace 5.101.0 → 5.103.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 (235) hide show
  1. package/ext/exporters.js +1 -0
  2. package/package.json +20 -17
  3. package/packages/datadog-esbuild/src/utils.js +2 -2
  4. package/packages/datadog-instrumentations/src/aerospike.js +2 -2
  5. package/packages/datadog-instrumentations/src/ai.js +9 -9
  6. package/packages/datadog-instrumentations/src/amqplib.js +6 -7
  7. package/packages/datadog-instrumentations/src/anthropic.js +10 -10
  8. package/packages/datadog-instrumentations/src/apollo-server-core.js +3 -3
  9. package/packages/datadog-instrumentations/src/apollo-server.js +5 -5
  10. package/packages/datadog-instrumentations/src/avsc.js +6 -6
  11. package/packages/datadog-instrumentations/src/aws-sdk.js +151 -67
  12. package/packages/datadog-instrumentations/src/azure-durable-functions.js +8 -8
  13. package/packages/datadog-instrumentations/src/bluebird.js +2 -2
  14. package/packages/datadog-instrumentations/src/body-parser.js +2 -2
  15. package/packages/datadog-instrumentations/src/cassandra-driver.js +7 -7
  16. package/packages/datadog-instrumentations/src/child_process.js +12 -12
  17. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +41 -24
  18. package/packages/datadog-instrumentations/src/connect.js +7 -7
  19. package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
  20. package/packages/datadog-instrumentations/src/cookie.js +2 -2
  21. package/packages/datadog-instrumentations/src/couchbase.js +73 -238
  22. package/packages/datadog-instrumentations/src/crypto.js +4 -4
  23. package/packages/datadog-instrumentations/src/cucumber.js +78 -17
  24. package/packages/datadog-instrumentations/src/dns.js +0 -3
  25. package/packages/datadog-instrumentations/src/elasticsearch.js +8 -11
  26. package/packages/datadog-instrumentations/src/electron/preload.js +42 -0
  27. package/packages/datadog-instrumentations/src/electron.js +240 -0
  28. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +6 -6
  29. package/packages/datadog-instrumentations/src/express-session.js +4 -4
  30. package/packages/datadog-instrumentations/src/express.js +10 -11
  31. package/packages/datadog-instrumentations/src/fastify.js +2 -2
  32. package/packages/datadog-instrumentations/src/fetch.js +5 -5
  33. package/packages/datadog-instrumentations/src/fs.js +14 -14
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +5 -7
  35. package/packages/datadog-instrumentations/src/google-genai.js +4 -4
  36. package/packages/datadog-instrumentations/src/graphql.js +13 -12
  37. package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
  38. package/packages/datadog-instrumentations/src/hapi.js +2 -2
  39. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +9 -9
  40. package/packages/datadog-instrumentations/src/helpers/hook.js +4 -1
  41. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  42. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  43. package/packages/datadog-instrumentations/src/helpers/kafka.js +41 -0
  44. package/packages/datadog-instrumentations/src/helpers/promise.js +2 -2
  45. package/packages/datadog-instrumentations/src/hono.js +2 -2
  46. package/packages/datadog-instrumentations/src/http/client.js +6 -6
  47. package/packages/datadog-instrumentations/src/http/server.js +9 -9
  48. package/packages/datadog-instrumentations/src/ioredis.js +16 -12
  49. package/packages/datadog-instrumentations/src/jest.js +382 -81
  50. package/packages/datadog-instrumentations/src/kafkajs.js +165 -174
  51. package/packages/datadog-instrumentations/src/knex.js +17 -17
  52. package/packages/datadog-instrumentations/src/koa.js +12 -12
  53. package/packages/datadog-instrumentations/src/ldapjs.js +5 -5
  54. package/packages/datadog-instrumentations/src/light-my-request.js +2 -2
  55. package/packages/datadog-instrumentations/src/limitd-client.js +4 -4
  56. package/packages/datadog-instrumentations/src/lodash.js +4 -4
  57. package/packages/datadog-instrumentations/src/mariadb.js +13 -13
  58. package/packages/datadog-instrumentations/src/memcached.js +2 -2
  59. package/packages/datadog-instrumentations/src/microgateway-core.js +2 -2
  60. package/packages/datadog-instrumentations/src/mocha/common.js +3 -3
  61. package/packages/datadog-instrumentations/src/mocha/main.js +85 -11
  62. package/packages/datadog-instrumentations/src/mocha/utils.js +133 -16
  63. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  64. package/packages/datadog-instrumentations/src/mongodb-core.js +42 -30
  65. package/packages/datadog-instrumentations/src/mongodb.js +5 -5
  66. package/packages/datadog-instrumentations/src/mongoose.js +21 -21
  67. package/packages/datadog-instrumentations/src/mquery.js +5 -5
  68. package/packages/datadog-instrumentations/src/multer.js +4 -4
  69. package/packages/datadog-instrumentations/src/mysql.js +16 -16
  70. package/packages/datadog-instrumentations/src/mysql2.js +4 -4
  71. package/packages/datadog-instrumentations/src/net.js +14 -8
  72. package/packages/datadog-instrumentations/src/nyc.js +5 -5
  73. package/packages/datadog-instrumentations/src/openai.js +19 -19
  74. package/packages/datadog-instrumentations/src/oracledb.js +6 -6
  75. package/packages/datadog-instrumentations/src/passport-utils.js +5 -5
  76. package/packages/datadog-instrumentations/src/pg.js +39 -25
  77. package/packages/datadog-instrumentations/src/pino.js +6 -10
  78. package/packages/datadog-instrumentations/src/playwright.js +445 -68
  79. package/packages/datadog-instrumentations/src/protobufjs.js +16 -16
  80. package/packages/datadog-instrumentations/src/redis.js +20 -12
  81. package/packages/datadog-instrumentations/src/restify.js +2 -2
  82. package/packages/datadog-instrumentations/src/router.js +12 -12
  83. package/packages/datadog-instrumentations/src/stripe.js +12 -12
  84. package/packages/datadog-instrumentations/src/vitest.js +107 -26
  85. package/packages/datadog-instrumentations/src/winston.js +4 -4
  86. package/packages/datadog-instrumentations/src/ws.js +7 -7
  87. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -21
  88. package/packages/datadog-plugin-aws-sdk/src/base.js +70 -28
  89. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +1 -1
  90. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +20 -13
  91. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +46 -36
  92. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +34 -23
  93. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -1
  94. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  95. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +14 -15
  96. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +74 -55
  97. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +20 -18
  98. package/packages/datadog-plugin-aws-sdk/src/util.js +22 -0
  99. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -6
  100. package/packages/datadog-plugin-couchbase/src/index.js +58 -52
  101. package/packages/datadog-plugin-cucumber/src/index.js +5 -0
  102. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +215 -26
  103. package/packages/datadog-plugin-cypress/src/support.js +13 -1
  104. package/packages/datadog-plugin-electron/src/index.js +17 -0
  105. package/packages/datadog-plugin-electron/src/ipc.js +143 -0
  106. package/packages/datadog-plugin-electron/src/net.js +82 -0
  107. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -5
  108. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +27 -18
  109. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +3 -1
  110. package/packages/datadog-plugin-graphql/src/execute.js +6 -28
  111. package/packages/datadog-plugin-graphql/src/resolve.js +30 -35
  112. package/packages/datadog-plugin-graphql/src/tools/signature.js +32 -7
  113. package/packages/datadog-plugin-graphql/src/tools/transforms.js +118 -100
  114. package/packages/datadog-plugin-graphql/src/utils.js +29 -0
  115. package/packages/datadog-plugin-grpc/src/client.js +6 -7
  116. package/packages/datadog-plugin-grpc/src/util.js +57 -22
  117. package/packages/datadog-plugin-http/src/client.js +3 -7
  118. package/packages/datadog-plugin-jest/src/index.js +92 -50
  119. package/packages/datadog-plugin-jest/src/util.js +1 -2
  120. package/packages/datadog-plugin-mocha/src/index.js +5 -0
  121. package/packages/datadog-plugin-mongodb-core/src/index.js +36 -69
  122. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  123. package/packages/datadog-plugin-openai/src/services.js +2 -1
  124. package/packages/datadog-plugin-openai/src/tracing.js +12 -23
  125. package/packages/datadog-plugin-pg/src/index.js +3 -3
  126. package/packages/datadog-plugin-playwright/src/index.js +5 -1
  127. package/packages/datadog-plugin-redis/src/index.js +18 -23
  128. package/packages/datadog-plugin-vitest/src/index.js +8 -1
  129. package/packages/datadog-shimmer/src/shimmer.js +7 -1
  130. package/packages/dd-trace/src/aiguard/index.js +3 -1
  131. package/packages/dd-trace/src/aiguard/sdk.js +36 -30
  132. package/packages/dd-trace/src/aiguard/tags.js +20 -11
  133. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  134. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +81 -81
  135. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  136. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
  137. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +4 -4
  138. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -2
  139. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
  140. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -3
  141. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +83 -48
  142. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  143. package/packages/dd-trace/src/appsec/index.js +21 -24
  144. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  145. package/packages/dd-trace/src/appsec/rule_manager.js +4 -2
  146. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +31 -16
  147. package/packages/dd-trace/src/azure_metadata.js +17 -6
  148. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +4 -4
  149. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  150. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -4
  151. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +1 -1
  152. package/packages/dd-trace/src/config/defaults.js +3 -14
  153. package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -1
  154. package/packages/dd-trace/src/config/git_properties.js +2 -2
  155. package/packages/dd-trace/src/config/helper.js +4 -0
  156. package/packages/dd-trace/src/config/index.js +2 -2
  157. package/packages/dd-trace/src/config/major-overrides.js +98 -0
  158. package/packages/dd-trace/src/config/parsers.js +7 -1
  159. package/packages/dd-trace/src/config/supported-configurations.json +51 -38
  160. package/packages/dd-trace/src/datastreams/checkpointer.js +2 -2
  161. package/packages/dd-trace/src/datastreams/index.js +2 -1
  162. package/packages/dd-trace/src/datastreams/manager.js +1 -1
  163. package/packages/dd-trace/src/datastreams/processor.js +3 -4
  164. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +2 -2
  165. package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +1 -0
  166. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
  167. package/packages/dd-trace/src/debugger/devtools_client/state.js +2 -1
  168. package/packages/dd-trace/src/debugger/index.js +7 -7
  169. package/packages/dd-trace/src/dogstatsd.js +2 -2
  170. package/packages/dd-trace/src/encode/0.4.js +748 -232
  171. package/packages/dd-trace/src/encode/0.5.js +47 -10
  172. package/packages/dd-trace/src/encode/agentless-json.js +1 -1
  173. package/packages/dd-trace/src/exporter.js +2 -0
  174. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  175. package/packages/dd-trace/src/exporters/agentless/index.js +3 -2
  176. package/packages/dd-trace/src/exporters/agentless/writer.js +2 -2
  177. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -1
  178. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  179. package/packages/dd-trace/src/exporters/electron/index.js +49 -0
  180. package/packages/dd-trace/src/external-logger/src/index.js +2 -1
  181. package/packages/dd-trace/src/git_metadata.js +10 -8
  182. package/packages/dd-trace/src/lambda/handler-paths.js +52 -0
  183. package/packages/dd-trace/src/lambda/index.js +62 -14
  184. package/packages/dd-trace/src/lambda/runtime/patch.js +21 -46
  185. package/packages/dd-trace/src/llmobs/index.js +13 -2
  186. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -2
  187. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +45 -15
  188. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +6 -3
  189. package/packages/dd-trace/src/llmobs/sdk.js +24 -26
  190. package/packages/dd-trace/src/llmobs/span_processor.js +25 -5
  191. package/packages/dd-trace/src/llmobs/util.js +1 -0
  192. package/packages/dd-trace/src/llmobs/writers/base.js +2 -1
  193. package/packages/dd-trace/src/msgpack/chunk.js +6 -3
  194. package/packages/dd-trace/src/openfeature/noop.js +40 -36
  195. package/packages/dd-trace/src/openfeature/writers/base.js +2 -1
  196. package/packages/dd-trace/src/openfeature/writers/exposures.js +33 -52
  197. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  198. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +1 -2
  199. package/packages/dd-trace/src/opentelemetry/tracer.js +0 -22
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +20 -9
  201. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +2 -11
  202. package/packages/dd-trace/src/payload-tagging/config/index.js +2 -2
  203. package/packages/dd-trace/src/plugins/ci_plugin.js +49 -4
  204. package/packages/dd-trace/src/plugins/database.js +54 -12
  205. package/packages/dd-trace/src/plugins/index.js +1 -0
  206. package/packages/dd-trace/src/plugins/plugin.js +2 -4
  207. package/packages/dd-trace/src/plugins/util/ci.js +9 -9
  208. package/packages/dd-trace/src/plugins/util/git-cache.js +23 -23
  209. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -2
  210. package/packages/dd-trace/src/plugins/util/test.js +56 -12
  211. package/packages/dd-trace/src/plugins/util/url.js +1 -3
  212. package/packages/dd-trace/src/plugins/util/user-provided-git.js +18 -16
  213. package/packages/dd-trace/src/plugins/util/web.js +5 -7
  214. package/packages/dd-trace/src/priority_sampler.js +1 -1
  215. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  216. package/packages/dd-trace/src/profiling/profilers/events.js +3 -23
  217. package/packages/dd-trace/src/profiling/profilers/wall.js +5 -6
  218. package/packages/dd-trace/src/profiling/ssi-heuristics.js +1 -1
  219. package/packages/dd-trace/src/rate_limiter.js +1 -1
  220. package/packages/dd-trace/src/remote_config/scheduler.js +1 -1
  221. package/packages/dd-trace/src/ritm.js +2 -1
  222. package/packages/dd-trace/src/runtime_metrics/index.js +2 -2
  223. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -8
  224. package/packages/dd-trace/src/scope.js +3 -10
  225. package/packages/dd-trace/src/serverless.js +6 -6
  226. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +27 -1
  227. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  228. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +24 -0
  229. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  230. package/packages/dd-trace/src/span_stats.js +1 -1
  231. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  232. package/packages/dd-trace/src/telemetry/endpoints.js +1 -1
  233. package/packages/dd-trace/src/telemetry/telemetry.js +2 -2
  234. package/packages/dd-trace/src/tracer.js +7 -7
  235. package/packages/dd-trace/src/lambda/runtime/ritm.js +0 -133
@@ -18,8 +18,8 @@ addHook({ name: 'memcached', versions: ['>=2.2'] }, Memcached => {
18
18
 
19
19
  const client = this
20
20
 
21
- const wrappedQueryCompiler = function () {
22
- const query = queryCompiler.apply(this, arguments)
21
+ const wrappedQueryCompiler = function (...args) {
22
+ const query = queryCompiler.apply(this, args)
23
23
 
24
24
  const ctx = { client, server, query }
25
25
  startCh.runStores(ctx, () => {
@@ -14,8 +14,8 @@ const versions = ['>=2.1 <=3.0.0']
14
14
  const requestContexts = new WeakMap()
15
15
 
16
16
  function wrapConfigProxyFactory (configProxyFactory) {
17
- return function () {
18
- const configProxy = configProxyFactory.apply(this, arguments)
17
+ return function (...args) {
18
+ const configProxy = configProxyFactory.apply(this, args)
19
19
 
20
20
  return function (req, res, next) {
21
21
  const ctx = { req, res }
@@ -20,9 +20,9 @@ addHook({
20
20
 
21
21
  patched.add(mochaEach)
22
22
 
23
- return shimmer.wrapFunction(mochaEach, mochaEach => function () {
24
- const [params] = arguments
25
- const { it, ...rest } = mochaEach.apply(this, arguments)
23
+ return shimmer.wrapFunction(mochaEach, mochaEach => function (...args) {
24
+ const [params] = args
25
+ const { it, ...rest } = mochaEach.apply(this, args)
26
26
  return {
27
27
  it: function (title) {
28
28
  parameterizedTestCh.publish({ title, params })
@@ -326,6 +326,7 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
326
326
  }
327
327
  config.isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
328
328
  config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
329
+ config.earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
329
330
  config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
330
331
  config.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
331
332
  config.isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
@@ -369,17 +370,17 @@ addHook({
369
370
  }, (Mocha, frameworkVersion) => {
370
371
  warnDeprecatedMochaVersion(frameworkVersion)
371
372
 
372
- shimmer.wrap(Mocha.prototype, 'run', run => function () {
373
+ shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
373
374
  // Workers do not need to request any data, just run the tests
374
375
  if (!testFinishCh.hasSubscribers || getEnvironmentVariable('MOCHA_WORKER_ID') || this.options.parallel) {
375
- return run.apply(this, arguments)
376
+ return run.apply(this, args)
376
377
  }
377
378
 
378
379
  // `options.delay` does not work in parallel mode, so we can't delay the execution this way
379
380
  // This needs to be both here and in `runMocha` hook. Read the comment in `runMocha` hook for more info.
380
381
  this.options.delay = true
381
382
 
382
- const runner = run.apply(this, arguments)
383
+ const runner = run.apply(this, args)
383
384
 
384
385
  // eslint-disable-next-line unicorn/no-array-for-each
385
386
  this.files.forEach((path) => {
@@ -426,11 +427,11 @@ addHook({
426
427
  file: 'lib/cli/run-helpers.js',
427
428
  }, (run) => {
428
429
  // `runMocha` is an async function
429
- shimmer.wrap(run, 'runMocha', runMocha => function () {
430
+ shimmer.wrap(run, 'runMocha', runMocha => function (...args) {
430
431
  if (!testFinishCh.hasSubscribers) {
431
- return runMocha.apply(this, arguments)
432
+ return runMocha.apply(this, args)
432
433
  }
433
- const mocha = arguments[0]
434
+ const mocha = args[0]
434
435
 
435
436
  /**
436
437
  * This attaches `run` to the global context, which we'll call after
@@ -444,7 +445,7 @@ addHook({
444
445
  mocha.options.delay = true
445
446
  }
446
447
 
447
- return runMocha.apply(this, arguments)
448
+ return runMocha.apply(this, args)
448
449
  })
449
450
  return run
450
451
  })
@@ -462,12 +463,16 @@ addHook({
462
463
 
463
464
  shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
464
465
 
465
- shimmer.wrap(Runner.prototype, 'run', run => function () {
466
+ shimmer.wrap(Runner.prototype, 'run', run => function (...args) {
466
467
  if (!testFinishCh.hasSubscribers) {
467
- return run.apply(this, arguments)
468
+ return run.apply(this, args)
468
469
  }
469
470
 
470
471
  const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
472
+ // Root-level tests (direct children of root, no describe wrapper) keyed by file.
473
+ // Populated during the root 'suite' event so the normal finish path can include them
474
+ // in mixed-file status calculation.
475
+ const rootTestsByFile = new Map()
471
476
 
472
477
  this.once('start', getOnStartHandler(frameworkVersion))
473
478
 
@@ -488,6 +493,30 @@ addHook({
488
493
 
489
494
  this.on('suite', function (suite) {
490
495
  if (suite.root || !suite.tests.length) {
496
+ // This branch can be triggered when we have top level it(...) inside test files.
497
+ // In that case, they all (even if they are from different files) are going to be
498
+ // children of the root suite.
499
+ // Note: We could have suites that contain top level it(...) and also it(...) nested
500
+ // inside describe(...) ("mixed case"). Duplication is avoided by the context guard
501
+ // below. Since 'suite' fires for root first, in the mixed case the ctx is created
502
+ // here and the describe-based handler finds it already set.
503
+ if (suite.root && suite.tests.length > 0) {
504
+ const files = new Set(suite.tests.map(test => test.file).filter(Boolean))
505
+ for (const file of files) {
506
+ rootTestsByFile.set(file, suite.tests.filter(t => t.file === file))
507
+ if (testFileToSuiteCtx.get(file)) continue
508
+ const isUnskippable = unskippableSuites.includes(file)
509
+ isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(file, process.cwd()))
510
+ const ctx = {
511
+ testSuiteAbsolutePath: file,
512
+ isUnskippable,
513
+ isForcedToRun,
514
+ itrCorrelationId,
515
+ }
516
+ testFileToSuiteCtx.set(file, ctx)
517
+ testSuiteStartCh.runStores(ctx, () => {})
518
+ }
519
+ }
491
520
  return
492
521
  }
493
522
  let ctx = testFileToSuiteCtx.get(suite.file)
@@ -507,6 +536,44 @@ addHook({
507
536
 
508
537
  this.on('suite end', function (suite) {
509
538
  if (suite.root) {
539
+ // Symmetric to the suite start fix
540
+ const fileToTests = new Map()
541
+ for (const test of suite.tests) {
542
+ if (!test.file) continue
543
+ if (!fileToTests.has(test.file)) fileToTests.set(test.file, [])
544
+ fileToTests.get(test.file).push(test)
545
+ }
546
+ for (const [file, tests] of fileToTests) {
547
+ // Mixed case: if a file appears in suitesByTestFile (pre-populated before the run),
548
+ // its numSuitesByTestFile counter hits zero when its last describe-based suite ends
549
+ // and the normal path below fires testSuiteFinishCh. Since root is last when
550
+ // 'suite end' fires, any such file has already been handled — skipping it here
551
+ // avoids duplication.
552
+ if (suitesByTestFile[file]) continue
553
+ let status = 'pass'
554
+ if (tests.every(test => test.isPending())) {
555
+ status = 'skip'
556
+ } else {
557
+ for (const test of tests) {
558
+ if (test.state === 'failed' || test.timedOut) {
559
+ status = 'fail'
560
+ break
561
+ }
562
+ }
563
+ }
564
+ if (global.__coverage__) {
565
+ const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
566
+ testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
567
+ mergeCoverage(global.__coverage__, originalCoverageMap)
568
+ resetCoverage(global.__coverage__)
569
+ }
570
+ const ctx = testFileToSuiteCtx.get(file)
571
+ if (ctx) {
572
+ testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
573
+ } else {
574
+ log.warn('No ctx found for suite', file)
575
+ }
576
+ }
510
577
  return
511
578
  }
512
579
  const suitesInTestFile = suitesByTestFile[suite.file]
@@ -516,8 +583,9 @@ addHook({
516
583
  return
517
584
  }
518
585
 
586
+ const rootTests = rootTestsByFile.get(suite.file) || []
519
587
  let status = 'pass'
520
- if (suitesInTestFile.every(suite => suite.pending)) {
588
+ if (suitesInTestFile.every(suite => suite.pending) && rootTests.every(test => test.isPending())) {
521
589
  status = 'skip'
522
590
  } else {
523
591
  // has to check every test in the test file
@@ -529,6 +597,11 @@ addHook({
529
597
  }
530
598
  })
531
599
  })
600
+ for (const test of rootTests) {
601
+ if (test.state === 'failed' || test.timedOut) {
602
+ status = 'fail'
603
+ }
604
+ }
532
605
  }
533
606
 
534
607
  if (global.__coverage__) {
@@ -552,7 +625,7 @@ addHook({
552
625
  }
553
626
  })
554
627
 
555
- return run.apply(this, arguments)
628
+ return run.apply(this, args)
556
629
  })
557
630
 
558
631
  return Runner
@@ -722,6 +795,7 @@ addHook({
722
795
  if (config.knownTests?.mocha) {
723
796
  const testSuiteKnownTests = config.knownTests.mocha[testPath] || []
724
797
  newWorkerArgs._ddEfdNumRetries = config.earlyFlakeDetectionNumRetries
798
+ newWorkerArgs._ddEfdSlowTestRetries = config.earlyFlakeDetectionSlowTestRetries
725
799
  newWorkerArgs._ddIsEfdEnabled = config.isEarlyFlakeDetectionEnabled
726
800
  newWorkerArgs._ddIsKnownTestsEnabled = true
727
801
  newWorkerArgs._ddKnownTests = {
@@ -1,11 +1,15 @@
1
1
  'use strict'
2
2
 
3
+ const { performance } = require('node:perf_hooks')
4
+
3
5
  // Capture real timers at module load time, before any test can install fake timers.
4
6
  const realSetTimeout = setTimeout
5
7
 
6
8
  const {
7
9
  getTestSuitePath,
8
10
  DYNAMIC_NAME_RE,
11
+ getEfdRetryCount,
12
+ getMaxEfdRetryCount,
9
13
  recordAttemptToFixExecution,
10
14
  logAttemptToFixTestExecution,
11
15
  } = require('../../../dd-trace/src/plugins/util/test')
@@ -35,6 +39,8 @@ const newTestsWithDynamicNames = new Set()
35
39
  const testsAttemptToFix = new Set()
36
40
  const testsQuarantined = new Set()
37
41
  const testsStatuses = new Map()
42
+ const efdRetryCountByTestFullName = new Map()
43
+ const efdSlowAbortedTests = new Set()
38
44
  const attemptToFixExecutions = new Map()
39
45
  const loggedAttemptToFixTests = new Set()
40
46
 
@@ -70,11 +76,82 @@ function isNewTest (test, knownTests) {
70
76
  return !testsForSuite.includes(testName)
71
77
  }
72
78
 
73
- function retryTest (test, numRetries, tags) {
79
+ function setEfdRetryCountForTest (test, duration, slowTestRetries) {
80
+ const testName = getTestFullName(test)
81
+ if (efdRetryCountByTestFullName.has(testName)) {
82
+ return
83
+ }
84
+ const retryCount = getEfdRetryCount(duration, slowTestRetries || {})
85
+ efdRetryCountByTestFullName.set(testName, retryCount)
86
+ if (retryCount === 0) {
87
+ efdSlowAbortedTests.add(testName)
88
+ }
89
+ }
90
+
91
+ function wrapOriginalEfdTest (test, slowTestRetries) {
92
+ if (test._ddEfdDurationWrapped || typeof test.fn !== 'function') {
93
+ return
94
+ }
95
+ test._ddEfdDurationWrapped = true
96
+ const originalFn = test.fn
97
+ test.fn = shimmer.wrapFunction(originalFn, originalFn => function () {
98
+ const start = performance.now()
99
+ const recordDuration = () => {
100
+ setEfdRetryCountForTest(test, performance.now() - start, slowTestRetries)
101
+ }
102
+
103
+ if (originalFn.length > 0) {
104
+ const args = Array.prototype.slice.call(arguments)
105
+ args[0] = shimmer.wrapFunction(args[0], done => function (...args) {
106
+ recordDuration()
107
+ return done.apply(this, args)
108
+ })
109
+ return originalFn.apply(this, args)
110
+ }
111
+
112
+ try {
113
+ const result = originalFn.apply(this, arguments)
114
+ if (result?.then) {
115
+ return result.then(value => {
116
+ recordDuration()
117
+ return value
118
+ }, error => {
119
+ recordDuration()
120
+ throw error
121
+ })
122
+ }
123
+ recordDuration()
124
+ return result
125
+ } catch (error) {
126
+ recordDuration()
127
+ throw error
128
+ }
129
+ })
130
+ }
131
+
132
+ function retryTest (test, numRetries, tags, slowTestRetries) {
74
133
  const suite = test.parent
134
+ const isEfdRetry = tags.includes('_ddIsEfdRetry')
135
+ if (isEfdRetry) {
136
+ wrapOriginalEfdTest(test, slowTestRetries)
137
+ }
75
138
  for (let retryIndex = 0; retryIndex < numRetries; retryIndex++) {
76
139
  const clonedTest = test.clone()
77
140
  suite.addTest(clonedTest)
141
+ if (isEfdRetry) {
142
+ clonedTest._ddEfdRetryIndex = retryIndex + 1
143
+ const originalFn = clonedTest.fn
144
+ if (typeof originalFn === 'function') {
145
+ clonedTest.fn = shimmer.wrapFunction(originalFn, originalFn => function (...args) {
146
+ const efdRetryCount = efdRetryCountByTestFullName.get(getTestFullName(clonedTest))
147
+ if (efdRetryCount !== undefined && clonedTest._ddEfdRetryIndex > efdRetryCount) {
148
+ clonedTest._ddShouldSkipEfdRetry = true
149
+ this.skip()
150
+ }
151
+ return originalFn.apply(this, args)
152
+ })
153
+ }
154
+ }
78
155
  for (const tag of tags) {
79
156
  if (tag) {
80
157
  clonedTest[tag] = true
@@ -83,6 +160,14 @@ function retryTest (test, numRetries, tags) {
83
160
  }
84
161
  }
85
162
 
163
+ function getConfiguredEfdRetryCount (config) {
164
+ const { earlyFlakeDetectionSlowTestRetries } = config
165
+ if (!earlyFlakeDetectionSlowTestRetries || !Object.keys(earlyFlakeDetectionSlowTestRetries).length) {
166
+ return config.earlyFlakeDetectionNumRetries
167
+ }
168
+ return getMaxEfdRetryCount(earlyFlakeDetectionSlowTestRetries)
169
+ }
170
+
86
171
  function getSuitesByTestFile (root) {
87
172
  const suitesByTestFile = {}
88
173
  function getSuites (suite) {
@@ -137,8 +222,7 @@ function getTestToContextKey (test) {
137
222
  if (!wrappedFunctions.has(test.fn)) {
138
223
  return test.fn
139
224
  }
140
- const originalFn = originalFns.get(test.fn)
141
- return originalFn
225
+ return originalFns.get(test.fn)
142
226
  }
143
227
 
144
228
  function getTestContext (test) {
@@ -147,9 +231,9 @@ function getTestContext (test) {
147
231
  }
148
232
 
149
233
  function runnableWrapper (RunnablePackage, libraryConfig) {
150
- shimmer.wrap(RunnablePackage.prototype, 'run', run => function () {
234
+ shimmer.wrap(RunnablePackage.prototype, 'run', run => function (...args) {
151
235
  if (!testFinishCh.hasSubscribers) {
152
- return run.apply(this, arguments)
236
+ return run.apply(this, args)
153
237
  }
154
238
  if (libraryConfig?.isFlakyTestRetriesEnabled) {
155
239
  this.retries(libraryConfig?.flakyTestRetriesCount)
@@ -175,8 +259,8 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
175
259
 
176
260
  if (ctx) {
177
261
  // we bind the test fn to the correct context
178
- const newFn = shimmer.wrapFunction(this.fn, originalFn => function () {
179
- return testFnCh.runStores(ctx, () => originalFn.apply(this, arguments))
262
+ const newFn = shimmer.wrapFunction(this.fn, originalFn => function (...args) {
263
+ return testFnCh.runStores(ctx, () => originalFn.apply(this, args))
180
264
  })
181
265
 
182
266
  // we store the original function, not to lose it
@@ -187,7 +271,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
187
271
  }
188
272
  }
189
273
 
190
- return run.apply(this, arguments)
274
+ return run.apply(this, args)
191
275
  })
192
276
  return RunnablePackage
193
277
  }
@@ -215,6 +299,17 @@ function getOnTestHandler (isMain) {
215
299
  _ddIsModified: isModified,
216
300
  } = test
217
301
 
302
+ test._ddStartTime = performance.now()
303
+
304
+ if (isEfdRetry) {
305
+ const efdRetryCount = efdRetryCountByTestFullName.get(getTestFullName(test))
306
+ if (efdRetryCount !== undefined && test._ddEfdRetryIndex > efdRetryCount) {
307
+ test.pending = true
308
+ test._ddShouldSkipEfdRetry = true
309
+ return
310
+ }
311
+ }
312
+
218
313
  const testInfo = {
219
314
  testName: test.fullTitle(),
220
315
  testSuiteAbsolutePath,
@@ -273,6 +368,7 @@ function getFinalStatus ({
273
368
  isAttemptToFix,
274
369
  isLastAttemptToFix,
275
370
  attemptToFixPassed,
371
+ hasPassedAnyEfdAttempt,
276
372
  isQuarantined,
277
373
  isDisabled,
278
374
  }) {
@@ -301,7 +397,7 @@ function getFinalStatus ({
301
397
  return hasFailedAllRetries ? 'fail' : 'pass'
302
398
  }
303
399
  if (isEfdRetry && isLastEfdRetry) {
304
- return hasFailedAllRetries ? 'fail' : 'pass'
400
+ return hasPassedAnyEfdAttempt ? 'pass' : 'fail'
305
401
  }
306
402
  if (isAttemptToFix && isLastAttemptToFix) {
307
403
  return attemptToFixPassed ? 'pass' : 'fail'
@@ -314,6 +410,15 @@ function getTestFinishInfo (test, status, config, error) {
314
410
  let attemptToFixFailed = false
315
411
 
316
412
  const testName = getTestFullName(test)
413
+ if (
414
+ config.isEarlyFlakeDetectionEnabled &&
415
+ (test._ddIsNew || test._ddIsModified) &&
416
+ !test._ddIsEfdRetry &&
417
+ !efdRetryCountByTestFullName.has(testName)
418
+ ) {
419
+ const duration = test.duration > 0 ? test.duration : performance.now() - test._ddStartTime
420
+ setEfdRetryCountForTest(test, duration, config.earlyFlakeDetectionSlowTestRetries)
421
+ }
317
422
 
318
423
  if (testsStatuses.get(testName)) {
319
424
  testsStatuses.get(testName).push(status)
@@ -323,12 +428,14 @@ function getTestFinishInfo (test, status, config, error) {
323
428
  const testStatuses = testsStatuses.get(testName)
324
429
 
325
430
  const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
326
- const isLastEfdRetry = testStatuses.length === config.earlyFlakeDetectionNumRetries + 1
431
+ const efdRetryCount = efdRetryCountByTestFullName.get(testName) ?? getConfiguredEfdRetryCount(config)
432
+ const isLastEfdRetry = testStatuses.length === efdRetryCount + 1
327
433
  const isLastAtrAttempt = getIsLastRetry(test) || (config.isFlakyTestRetriesEnabled && status === 'pass')
328
434
 
329
435
  // Needed for the getFinalStatus call. This is because EFD does NOT tag as
330
436
  // EFD retry the first run of the test. It only tags as retries the clones
331
- const isEfdRetry = test._ddIsEfdRetry || (test._ddIsNew && config.isEarlyFlakeDetectionEnabled)
437
+ const isEfdRetry =
438
+ test._ddIsEfdRetry || ((test._ddIsNew || test._ddIsModified) && config.isEarlyFlakeDetectionEnabled)
332
439
 
333
440
  if (test._ddIsAttemptToFix && isLastAttempt) {
334
441
  if (testStatuses.includes('fail')) {
@@ -341,7 +448,7 @@ function getTestFinishInfo (test, status, config, error) {
341
448
  }
342
449
  }
343
450
 
344
- if (test._ddIsEfdRetry && isLastEfdRetry &&
451
+ if (test._ddIsEfdRetry && efdRetryCount > 0 && isLastEfdRetry &&
345
452
  testStatuses.every(status => status === 'fail')) {
346
453
  hasFailedAllRetries = true
347
454
  }
@@ -370,6 +477,7 @@ function getTestFinishInfo (test, status, config, error) {
370
477
  isAttemptToFix: _ddIsAttemptToFix,
371
478
  isLastAttemptToFix: isLastAttempt,
372
479
  attemptToFixPassed,
480
+ hasPassedAnyEfdAttempt: testStatuses.includes('pass'),
373
481
  isQuarantined: _ddIsQuarantined,
374
482
  isDisabled: _ddIsDisabled,
375
483
  })
@@ -391,11 +499,15 @@ function getTestFinishInfo (test, status, config, error) {
391
499
  isAttemptToFixRetry,
392
500
  isAtrRetry,
393
501
  finalStatus,
502
+ earlyFlakeAbortReason: efdSlowAbortedTests.has(testName) ? 'slow' : undefined,
394
503
  }
395
504
  }
396
505
 
397
506
  function getOnTestEndHandler (config) {
398
507
  return async function (test) {
508
+ if (test._ddShouldSkipEfdRetry) {
509
+ return
510
+ }
399
511
  const ctx = getTestContext(test)
400
512
  const status = getTestStatus(test)
401
513
 
@@ -518,6 +630,9 @@ function getOnTestRetryHandler (config) {
518
630
 
519
631
  function getOnPendingHandler () {
520
632
  return function (test) {
633
+ if (test._ddShouldSkipEfdRetry) {
634
+ return
635
+ }
521
636
  const testStartLine = testToStartLine.get(test)
522
637
  const {
523
638
  file: testSuiteAbsolutePath,
@@ -587,8 +702,9 @@ function getRunTestsWrapper (runTests, config) {
587
702
  if (!test.isPending() && !test._ddIsAttemptToFix && config.isEarlyFlakeDetectionEnabled) {
588
703
  retryTest(
589
704
  test,
590
- config.earlyFlakeDetectionNumRetries,
591
- ['_ddIsModified', '_ddIsEfdRetry']
705
+ getConfiguredEfdRetryCount(config),
706
+ ['_ddIsModified', '_ddIsEfdRetry'],
707
+ config.earlyFlakeDetectionSlowTestRetries
592
708
  )
593
709
  }
594
710
  }
@@ -606,8 +722,9 @@ function getRunTestsWrapper (runTests, config) {
606
722
  if (config.isEarlyFlakeDetectionEnabled && !test._ddIsAttemptToFix && !test._ddIsModified) {
607
723
  retryTest(
608
724
  test,
609
- config.earlyFlakeDetectionNumRetries,
610
- ['_ddIsNew', '_ddIsEfdRetry']
725
+ getConfiguredEfdRetryCount(config),
726
+ ['_ddIsNew', '_ddIsEfdRetry'],
727
+ config.earlyFlakeDetectionSlowTestRetries
611
728
  )
612
729
  }
613
730
  }
@@ -27,15 +27,17 @@ addHook({
27
27
  versions: ['>=8.0.0'],
28
28
  file: 'lib/mocha.js',
29
29
  }, (Mocha) => {
30
- shimmer.wrap(Mocha.prototype, 'run', run => function () {
30
+ shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
31
31
  if (this.options._ddIsKnownTestsEnabled) {
32
32
  config.isKnownTestsEnabled = true
33
33
  config.isEarlyFlakeDetectionEnabled = this.options._ddIsEfdEnabled
34
34
  config.knownTests = this.options._ddKnownTests
35
35
  config.earlyFlakeDetectionNumRetries = this.options._ddEfdNumRetries
36
+ config.earlyFlakeDetectionSlowTestRetries = this.options._ddEfdSlowTestRetries ?? {}
36
37
  delete this.options._ddIsEfdEnabled
37
38
  delete this.options._ddKnownTests
38
39
  delete this.options._ddEfdNumRetries
40
+ delete this.options._ddEfdSlowTestRetries
39
41
  delete this.options._ddIsKnownTestsEnabled
40
42
  }
41
43
  if (this.options._ddIsImpactedTestsEnabled) {
@@ -58,7 +60,7 @@ addHook({
58
60
  delete this.options._ddIsFlakyTestRetriesEnabled
59
61
  delete this.options._ddFlakyTestRetriesCount
60
62
  }
61
- return run.apply(this, arguments)
63
+ return run.apply(this, args)
62
64
  })
63
65
 
64
66
  return Mocha
@@ -72,9 +74,9 @@ addHook({
72
74
  }, function (Runner) {
73
75
  shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
74
76
 
75
- shimmer.wrap(Runner.prototype, 'run', run => function () {
77
+ shimmer.wrap(Runner.prototype, 'run', run => function (...args) {
76
78
  if (!workerFinishCh.hasSubscribers) {
77
- return run.apply(this, arguments)
79
+ return run.apply(this, args)
78
80
  }
79
81
  // We flush when the worker ends with its test file (a mocha instance in a worker runs a single test file)
80
82
  this.once('end', () => {
@@ -93,7 +95,7 @@ addHook({
93
95
 
94
96
  this.on('pending', getOnPendingHandler())
95
97
 
96
- return run.apply(this, arguments)
98
+ return run.apply(this, args)
97
99
  })
98
100
  return Runner
99
101
  })