dd-trace 6.9.0 → 6.11.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 (175) hide show
  1. package/README.md +0 -17
  2. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  3. package/index.d.ts +150 -3
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +14 -10
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  13. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  14. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  15. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  16. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  17. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  20. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  26. package/packages/datadog-instrumentations/src/jest.js +26 -18
  27. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  28. package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
  29. package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
  30. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  31. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  32. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  33. package/packages/datadog-instrumentations/src/pg.js +116 -92
  34. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  35. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  36. package/packages/datadog-instrumentations/src/router.js +2 -0
  37. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  38. package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
  39. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  40. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  41. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  45. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
  46. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  47. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  48. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  49. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  50. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  51. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  52. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  53. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
  54. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  55. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  56. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  57. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  58. package/packages/datadog-plugin-electron/src/index.js +2 -0
  59. package/packages/datadog-plugin-fs/src/index.js +1 -1
  60. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  61. package/packages/datadog-plugin-jest/src/index.js +11 -4
  62. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  63. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  64. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  65. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  66. package/packages/datadog-plugin-mocha/src/index.js +606 -40
  67. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  68. package/packages/datadog-plugin-nats/src/index.js +1 -1
  69. package/packages/datadog-plugin-next/src/index.js +1 -19
  70. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  71. package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
  72. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  73. package/packages/datadog-plugin-pg/src/index.js +1 -0
  74. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  75. package/packages/datadog-plugin-vitest/src/index.js +14 -20
  76. package/packages/datadog-plugin-ws/src/util.js +2 -1
  77. package/packages/datadog-webpack/index.js +0 -11
  78. package/packages/dd-trace/index.js +0 -1
  79. package/packages/dd-trace/src/agent/info.js +6 -5
  80. package/packages/dd-trace/src/aiguard/client.js +100 -1
  81. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  82. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  83. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  84. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  85. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  86. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  87. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  88. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  89. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  90. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  91. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  92. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  93. package/packages/dd-trace/src/carrier.js +356 -0
  94. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  101. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  102. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  103. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  104. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  105. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  106. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  107. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
  109. package/packages/dd-trace/src/config/index.js +13 -2
  110. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  111. package/packages/dd-trace/src/config/supported-configurations.json +24 -0
  112. package/packages/dd-trace/src/datastreams/context.js +6 -1
  113. package/packages/dd-trace/src/datastreams/index.js +2 -0
  114. package/packages/dd-trace/src/datastreams/manager.js +14 -3
  115. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  116. package/packages/dd-trace/src/datastreams/processor.js +16 -13
  117. package/packages/dd-trace/src/datastreams/size.js +5 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  119. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  120. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  121. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  122. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  123. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  124. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  125. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  126. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  127. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  128. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  129. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  130. package/packages/dd-trace/src/llmobs/index.js +34 -2
  131. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  132. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  133. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  134. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  135. package/packages/dd-trace/src/llmobs/util.js +78 -0
  136. package/packages/dd-trace/src/noop/proxy.js +3 -4
  137. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  138. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  139. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  140. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  141. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  142. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  143. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  145. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  146. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  147. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  148. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  149. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  150. package/packages/dd-trace/src/plugin_manager.js +5 -3
  151. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  152. package/packages/dd-trace/src/plugins/index.js +1 -0
  153. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
  154. package/packages/dd-trace/src/plugins/util/test.js +93 -14
  155. package/packages/dd-trace/src/plugins/util/web.js +56 -2
  156. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  157. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  158. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  159. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  160. package/packages/dd-trace/src/proxy.js +38 -29
  161. package/packages/dd-trace/src/ritm.js +9 -2
  162. package/packages/dd-trace/src/serverless.js +37 -0
  163. package/packages/dd-trace/src/span_format.js +1 -0
  164. package/packages/dd-trace/src/span_stats.js +30 -3
  165. package/packages/dd-trace/src/storage-channels.js +86 -0
  166. package/packages/dd-trace/src/tracer.js +9 -2
  167. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  168. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  169. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  170. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  171. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  172. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  173. package/packages/dd-trace/src/feature-registry.js +0 -29
  174. package/packages/dd-trace/src/openfeature/register.js +0 -35
  175. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -10,7 +10,6 @@ const {
10
10
  } = require('../helpers/channel')
11
11
  const { addHook, channel } = require('../helpers/instrument')
12
12
  const shimmer = require('../../../datadog-shimmer')
13
- const { isMarkedAsUnskippable } = require('../../../datadog-plugin-jest/src/util')
14
13
  const { EMPTY_EFD_RETRY_POLICY } = require('../../../dd-trace/src/ci-visibility/efd-retry-policy')
15
14
  const { writeCoverageBackfillToCache } = require('../../../dd-trace/src/ci-visibility/test-optimization-cache')
16
15
  const log = require('../../../dd-trace/src/log')
@@ -33,6 +32,7 @@ const {
33
32
  TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE,
34
33
  getTestOptimizationRequestResults,
35
34
  isModifiedTest,
35
+ isMarkedAsUnskippable,
36
36
  } = require('../../../dd-trace/src/plugins/util/test')
37
37
 
38
38
  const {
@@ -53,6 +53,7 @@ const {
53
53
  testsQuarantined,
54
54
  getTestFullName,
55
55
  getRunTestsWrapper,
56
+ resetRunState,
56
57
  newTestsWithDynamicNames,
57
58
  attemptToFixExecutions,
58
59
  loggedAttemptToFixTests,
@@ -63,7 +64,27 @@ require('./common')
63
64
 
64
65
  const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
65
66
 
67
+ /**
68
+ * @typedef {{ replacement: Function, run: Function }} ReporterReplacement
69
+ * @typedef {{
70
+ * createFileRunner?: ReporterReplacement,
71
+ * hookRuns: Map<object, ReporterReplacement>,
72
+ * pendingTests: Map<object, { hadPending: boolean, pending: unknown }>,
73
+ * tests: Set<object>
74
+ * }} RunnerRecoveryState
75
+ */
76
+
66
77
  const patched = new WeakSet()
78
+ const runnerEndHandlers = new WeakMap()
79
+ const runnerHookMethods = new WeakMap()
80
+ const runnerTestEndHandlers = new WeakMap()
81
+ const runnerFailuresAdjusted = new WeakSet()
82
+ const runnerFrameworkErrors = new WeakMap()
83
+ const runnerStarted = new WeakSet()
84
+ const runnerRecoveryStates = new WeakMap()
85
+ const runnersWithPendingCoverageReset = new WeakSet()
86
+ const parallelRunners = new WeakSet()
87
+ const wrappedRunnerEmitPrototypes = new WeakSet()
67
88
  let hasWarnedDeprecatedMochaVersion = false
68
89
 
69
90
  const unskippableSuites = []
@@ -71,7 +92,7 @@ let suitesToSkip = []
71
92
  let isSuitesSkipped = false
72
93
  let areAllSuitesSkipped = false
73
94
  let skippedSuites = []
74
- let skippableSuitesCoverage = {}
95
+ let skippableSuitesCoverage
75
96
  let skippedSuitesCoverage = {}
76
97
  let itrCorrelationId = ''
77
98
  let isForcedToRun = false
@@ -182,12 +203,6 @@ function getFilteredSuites (originalSuites) {
182
203
  }, { suitesToRun: [], skippedSuites: new Set(), suitesToSkipForRun })
183
204
  }
184
205
 
185
- function hasSkippableSuitesCoverage () {
186
- return skippableSuitesCoverage &&
187
- typeof skippableSuitesCoverage === 'object' &&
188
- Object.keys(skippableSuitesCoverage).length > 0
189
- }
190
-
191
206
  function isTiaCoverageBackfillEnabled () {
192
207
  return config.isItrEnabled && config.isCoverageReportUploadEnabled
193
208
  }
@@ -218,7 +233,7 @@ function shouldReportCodeCoverageLinesPct (hasBackfilledCoverage) {
218
233
  }
219
234
 
220
235
  function getSkippedSuitesCoverageForRun () {
221
- return isSuitesSkipped && isTiaCoverageBackfillEnabled() && hasSkippableSuitesCoverage()
236
+ return isSuitesSkipped && isTiaCoverageBackfillEnabled() && skippableSuitesCoverage !== undefined
222
237
  ? skippableSuitesCoverage
223
238
  : {}
224
239
  }
@@ -236,7 +251,7 @@ function resetSuiteSkippingRunState () {
236
251
  isSuitesSkipped = false
237
252
  areAllSuitesSkipped = false
238
253
  skippedSuites = []
239
- skippableSuitesCoverage = {}
254
+ skippableSuitesCoverage = undefined
240
255
  skippedSuitesCoverage = {}
241
256
  untestedCoverage = undefined
242
257
  config.repositoryRoot = undefined
@@ -292,12 +307,12 @@ function getOnStartHandler (frameworkVersion) {
292
307
  /**
293
308
  * @param {boolean} isParallel
294
309
  * @param {() => void} onDone
295
- * @returns {() => void}
310
+ * @returns {(frameworkError?: unknown, onFrameworkErrorDone?: () => void) => void}
296
311
  */
297
312
  function getOnEndHandler (isParallel, onDone) {
298
- return function () {
313
+ return function (frameworkError, onFrameworkErrorDone) {
299
314
  let status = 'pass'
300
- let error
315
+ let error = frameworkError
301
316
  if (this.stats) {
302
317
  status = this.stats.failures === 0 ? 'pass' : 'fail'
303
318
  if (this.stats.tests === 0) {
@@ -307,18 +322,9 @@ function getOnEndHandler (isParallel, onDone) {
307
322
  status = 'fail'
308
323
  }
309
324
 
310
- adjustRunnerFailuresForTestOptimization(this, config)
311
-
312
- // Recompute status after EFD and quarantine adjustments have reduced failure counts
313
- if (status === 'fail') {
314
- if (this.stats) {
315
- status = this.stats.failures === 0 ? 'pass' : 'fail'
316
- } else {
317
- status = this.failures === 0 ? 'pass' : 'fail'
318
- }
319
- }
320
-
321
- if (status === 'fail') {
325
+ if (arguments.length > 0) {
326
+ status = 'fail'
327
+ } else if (status === 'fail') {
322
328
  error = new Error(`Failed tests: ${this.failures}.`)
323
329
  }
324
330
 
@@ -363,7 +369,14 @@ function getOnEndHandler (isParallel, onDone) {
363
369
  isEarlyFlakeDetectionFaulty: config.isEarlyFlakeDetectionFaulty,
364
370
  isTestManagementEnabled: config.isTestManagementTestsEnabled,
365
371
  isParallel,
366
- }, onDone)
372
+ isFrameworkError: arguments.length > 0,
373
+ }, () => {
374
+ try {
375
+ onDone()
376
+ } finally {
377
+ onFrameworkErrorDone?.()
378
+ }
379
+ })
367
380
 
368
381
  logTestOptimizationSummary({
369
382
  attemptToFixExecutions,
@@ -374,6 +387,448 @@ function getOnEndHandler (isParallel, onDone) {
374
387
  }
375
388
  }
376
389
 
390
+ /**
391
+ * Applies Test Optimization failure suppression once per runner execution.
392
+ *
393
+ * @param {object} runner
394
+ * @returns {void}
395
+ */
396
+ function adjustRunnerFailuresOnce (runner) {
397
+ if (runnerFailuresAdjusted.has(runner)) return
398
+
399
+ runnerFailuresAdjusted.add(runner)
400
+ adjustRunnerFailuresForTestOptimization(runner, config)
401
+ }
402
+
403
+ /**
404
+ * Returns the mutations that must be restored after reporter-error recovery.
405
+ *
406
+ * @param {object} runner
407
+ * @returns {RunnerRecoveryState}
408
+ */
409
+ function getRunnerRecoveryState (runner) {
410
+ let state = runnerRecoveryStates.get(runner)
411
+ if (!state) {
412
+ state = {
413
+ hookRuns: new Map(),
414
+ pendingTests: new Map(),
415
+ tests: new Set(),
416
+ }
417
+ runnerRecoveryStates.set(runner, state)
418
+ }
419
+ return state
420
+ }
421
+
422
+ /**
423
+ * Marks a test pending for the aborted run while retaining its reusable state.
424
+ *
425
+ * @param {object} runner
426
+ * @param {object} test
427
+ * @returns {void}
428
+ */
429
+ function markTestPending (runner, test) {
430
+ const state = getRunnerRecoveryState(runner)
431
+ if (!state.pendingTests.has(test)) {
432
+ state.pendingTests.set(test, {
433
+ hadPending: Object.hasOwn(test, 'pending'),
434
+ pending: test.pending,
435
+ })
436
+ }
437
+ state.tests.add(test)
438
+ test.pending = true
439
+ test._ddReporterStartFailed = true
440
+ }
441
+
442
+ /**
443
+ * Marks a completed test for immediate reporter-error finalization.
444
+ *
445
+ * @param {object} runner
446
+ * @param {object} test
447
+ * @returns {void}
448
+ */
449
+ function markTestTerminal (runner, test) {
450
+ getRunnerRecoveryState(runner).tests.add(test)
451
+ test._ddReporterTerminalFailed = true
452
+ }
453
+
454
+ /**
455
+ * Restores test and hook state changed only to abort the current run.
456
+ *
457
+ * @param {object} runner
458
+ * @returns {void}
459
+ */
460
+ function restoreReporterMutations (runner) {
461
+ const state = runnerRecoveryStates.get(runner)
462
+ if (!state) return
463
+
464
+ runnerRecoveryStates.delete(runner)
465
+ for (const [test, { hadPending, pending }] of state.pendingTests) {
466
+ if (hadPending) test.pending = pending
467
+ else delete test.pending
468
+ }
469
+ for (const test of state.tests) {
470
+ delete test._ddTestFinishStarted
471
+ delete test._ddTestFinishPublished
472
+ delete test._ddIsFinalAttempt
473
+ delete test._ddHookFailed
474
+ delete test._ddReporterStartFailed
475
+ delete test._ddReporterTerminalFailed
476
+ }
477
+ for (const [hook, { replacement, run }] of state.hookRuns) {
478
+ if (hook.run === replacement) hook.run = run
479
+ }
480
+ if (state.createFileRunner && runner._createFileRunner === state.createFileRunner.replacement) {
481
+ runner._createFileRunner = state.createFileRunner.run
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Prevents Mocha from entering hooks or the test body after a test-start reporter error.
487
+ *
488
+ * @param {object} runner
489
+ * @param {object} test
490
+ * @returns {void}
491
+ */
492
+ function stopCurrentTest (runner, test) {
493
+ const hookDown = runner.hookDown
494
+ const hookUp = runner.hookUp
495
+
496
+ markTestPending(runner, test)
497
+ runner.hookDown = function (name, onDone) {
498
+ runner.hookDown = hookDown
499
+ onDone()
500
+ }
501
+ runner.hookUp = function (name, onDone) {
502
+ runner.hookUp = hookUp
503
+ onDone()
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Prevents Mocha from entering a hook after its hook-start reporter event fails.
509
+ *
510
+ * @param {object} runner
511
+ * @param {object} hook
512
+ * @returns {void}
513
+ */
514
+ function stopCurrentHook (runner, hook) {
515
+ const hookMethod = runner.hook
516
+ const hookDown = runner.hookDown
517
+ const hookUp = runner.hookUp
518
+ const run = hook.run
519
+
520
+ stopRemainingCurrentHooks(runner, hook)
521
+ runner.hook = function (name, onDone) {
522
+ runner.hook = hookMethod
523
+ onDone()
524
+ }
525
+ runner.hookDown = function (name, onDone) {
526
+ runner.hookDown = hookDown
527
+ onDone()
528
+ }
529
+ runner.hookUp = function (name, onDone) {
530
+ runner.hookUp = hookUp
531
+ onDone()
532
+ }
533
+ hook.run = function (onDone) {
534
+ hook.run = run
535
+ const test = hook.ctx?.currentTest
536
+ if (test) {
537
+ if (hook.parent?._afterEach?.includes(hook) || hook.parent?._afterAll?.includes(hook)) {
538
+ markTestTerminal(runner, test)
539
+ if (!test._ddTestFinishStarted) runnerTestEndHandlers.get(runner)?.(test)
540
+ } else {
541
+ markTestPending(runner, test)
542
+ }
543
+ }
544
+ onDone()
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Prevents Mocha from running after-each hooks after a terminal test reporter event fails.
550
+ *
551
+ * @param {object} runner
552
+ * @param {object} test
553
+ * @returns {void}
554
+ */
555
+ function stopAfterEachHooks (runner, test) {
556
+ const hookUp = runner.hookUp
557
+
558
+ markTestTerminal(runner, test)
559
+ runner.hookUp = function (name, onDone) {
560
+ runner.hookUp = hookUp
561
+ onDone()
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Prevents Mocha from entering any subsequent user hooks after a reporter error.
567
+ *
568
+ * @param {object} runner
569
+ * @returns {void}
570
+ */
571
+ function stopFutureHooks (runner) {
572
+ if (runnerHookMethods.has(runner)) return
573
+
574
+ runnerHookMethods.set(runner, runner.hook)
575
+ runner.hook = function (name, onDone) {
576
+ onDone()
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Restores the runner hook method after reporter-error finalization.
582
+ *
583
+ * @param {object} runner
584
+ * @returns {void}
585
+ */
586
+ function restoreFutureHooks (runner) {
587
+ const hook = runnerHookMethods.get(runner)
588
+ if (!hook) return
589
+
590
+ runnerHookMethods.delete(runner)
591
+ runner.hook = hook
592
+ }
593
+
594
+ /**
595
+ * Stops hooks remaining in the currently executing hook array.
596
+ *
597
+ * @param {object} runner
598
+ * @param {object} hook
599
+ * @returns {boolean} whether the completed hook was a before-each hook
600
+ */
601
+ function stopRemainingCurrentHooks (runner, hook) {
602
+ const hookLists = [hook.parent?._beforeAll, hook.parent?._beforeEach, hook.parent?._afterEach, hook.parent?._afterAll]
603
+
604
+ for (const hooks of hookLists) {
605
+ const hookIndex = hooks?.indexOf(hook) ?? -1
606
+ if (hookIndex === -1) continue
607
+
608
+ for (let index = hookIndex + 1; index < hooks.length; index++) {
609
+ const remainingHook = hooks[index]
610
+ const run = remainingHook.run
611
+ const replacement = function (onDone) {
612
+ remainingHook.run = run
613
+ onDone()
614
+ }
615
+ getRunnerRecoveryState(runner).hookRuns.set(remainingHook, { replacement, run })
616
+ remainingHook.run = replacement
617
+ }
618
+ return hooks === hook.parent._beforeEach
619
+ }
620
+ return false
621
+ }
622
+
623
+ /**
624
+ * Prevents a completed before-each hook from continuing into the test body.
625
+ *
626
+ * @param {object} runner
627
+ * @param {object} hook
628
+ * @returns {void}
629
+ */
630
+ function stopAfterHookEnd (runner, hook) {
631
+ if (!stopRemainingCurrentHooks(runner, hook) || !runner.test) return
632
+
633
+ markTestPending(runner, runner.test)
634
+ }
635
+
636
+ /**
637
+ * Prevents Mocha from entering the root suite after a run-start reporter error.
638
+ *
639
+ * @param {object} runner
640
+ * @returns {void}
641
+ */
642
+ function stopRootSuite (runner) {
643
+ const runSuite = runner.runSuite
644
+
645
+ runner.runSuite = function (suite, onDone) {
646
+ runner.runSuite = runSuite
647
+ onDone()
648
+ }
649
+ }
650
+
651
+ function skipParallelFile () {}
652
+
653
+ function createSkippedParallelFileRunner () {
654
+ return skipParallelFile
655
+ }
656
+
657
+ /**
658
+ * Prevents a parallel run-start reporter error from scheduling test workers.
659
+ *
660
+ * @param {object} runner
661
+ * @returns {void}
662
+ */
663
+ function stopParallelWorkers (runner) {
664
+ const state = getRunnerRecoveryState(runner)
665
+ if (state.createFileRunner) return
666
+
667
+ const run = runner._createFileRunner
668
+ const replacement = createSkippedParallelFileRunner
669
+ state.createFileRunner = { replacement, run }
670
+ runner._createFileRunner = replacement
671
+ }
672
+
673
+ /**
674
+ * Resets suite coverage after every reporter has observed the completed suite.
675
+ *
676
+ * @param {object} runner
677
+ * @returns {void}
678
+ */
679
+ function resetPendingSuiteCoverage (runner) {
680
+ if (!runnersWithPendingCoverageReset.delete(runner) || !global.__coverage__) return
681
+
682
+ resetCoverage(global.__coverage__)
683
+ }
684
+
685
+ /**
686
+ * Reads a string Error property without invoking user code outside this boundary.
687
+ *
688
+ * @param {Error} error
689
+ * @param {'message' | 'name' | 'stack'} property
690
+ * @returns {string | undefined}
691
+ */
692
+ function readErrorString (error, property) {
693
+ try {
694
+ const value = error[property]
695
+ if (typeof value === 'string') return value
696
+ } catch {
697
+ // Ignore user-defined accessors.
698
+ }
699
+ }
700
+
701
+ /**
702
+ * Creates an Error for Test Optimization tags without coercing a user-thrown value.
703
+ *
704
+ * @param {unknown} frameworkError
705
+ * @returns {Error}
706
+ */
707
+ function getFrameworkFinalizationError (frameworkError) {
708
+ if (typeof frameworkError === 'string') return new Error(frameworkError)
709
+ let isError = false
710
+ try {
711
+ isError = frameworkError instanceof Error
712
+ } catch {
713
+ // User-thrown proxies can fail the instanceof prototype lookup.
714
+ }
715
+ if (!isError) return new Error('Mocha reporter failed')
716
+
717
+ const message = readErrorString(frameworkError, 'message') || 'Mocha reporter failed'
718
+ const error = new Error(message)
719
+ const name = readErrorString(frameworkError, 'name')
720
+ const stack = readErrorString(frameworkError, 'stack')
721
+ if (name) error.name = name
722
+ if (stack) error.stack = stack
723
+ return error
724
+ }
725
+
726
+ /**
727
+ * Defers reporter errors until Mocha can emit its remaining lifecycle events, then
728
+ * runs Datadog's end handler and propagates the original error after finalization.
729
+ *
730
+ * @param {Function} Runner
731
+ * @returns {void}
732
+ */
733
+ function wrapRunnerEmit (Runner) {
734
+ if (wrappedRunnerEmitPrototypes.has(Runner.prototype)) return
735
+
736
+ wrappedRunnerEmitPrototypes.add(Runner.prototype)
737
+ shimmer.wrap(Runner.prototype, 'emit', emit => function (event) {
738
+ const endHandler = runnerEndHandlers.get(this)
739
+ if (!endHandler) return emit.apply(this, arguments)
740
+
741
+ if (event === 'start') runnerStarted.add(this)
742
+
743
+ if (!runnerStarted.has(this)) return emit.apply(this, arguments)
744
+
745
+ const hasPendingFrameworkError = runnerFrameworkErrors.has(this)
746
+ const pendingFrameworkError = runnerFrameworkErrors.get(this)
747
+ if (event !== 'end') {
748
+ try {
749
+ return emit.apply(this, arguments)
750
+ } catch (error) {
751
+ if (!hasPendingFrameworkError) {
752
+ runnerFrameworkErrors.set(this, error)
753
+ this.abort()
754
+ stopFutureHooks(this)
755
+ if (event === 'start') {
756
+ if (parallelRunners.has(this)) stopParallelWorkers(this)
757
+ else stopRootSuite(this)
758
+ } else if (event === 'test') stopCurrentTest(this, arguments[1])
759
+ else if (event === 'hook') stopCurrentHook(this, arguments[1])
760
+ else if (event === 'hook end') {
761
+ const hook = arguments[1]
762
+ stopAfterHookEnd(this, hook)
763
+ const test = hook.ctx?.currentTest
764
+ if (test && hook.parent?._afterEach?.includes(hook)) {
765
+ markTestTerminal(this, test)
766
+ if (!test._ddTestFinishStarted) runnerTestEndHandlers.get(this)?.(test)
767
+ }
768
+ } else if (event === 'pending' || event === 'pass' || event === 'fail' || event === 'retry' ||
769
+ event === 'test end') {
770
+ const test = arguments[1]
771
+ stopAfterEachHooks(this, test)
772
+ if (event === 'test end' && !test._ddTestFinishStarted) {
773
+ runnerTestEndHandlers.get(this)?.(test)
774
+ }
775
+ }
776
+ }
777
+ return
778
+ } finally {
779
+ if (event === 'suite end') resetPendingSuiteCoverage(this)
780
+ }
781
+ }
782
+
783
+ runnerEndHandlers.delete(this)
784
+ restoreFutureHooks(this)
785
+ parallelRunners.delete(this)
786
+ runnerTestEndHandlers.delete(this)
787
+ runnerStarted.delete(this)
788
+ let result
789
+ let hasFrameworkError = hasPendingFrameworkError
790
+ let frameworkError = pendingFrameworkError
791
+ try {
792
+ result = emit.apply(this, arguments)
793
+ } catch (error) {
794
+ if (!hasFrameworkError) {
795
+ hasFrameworkError = true
796
+ frameworkError = error
797
+ runnerFrameworkErrors.set(this, error)
798
+ }
799
+ } finally {
800
+ restoreReporterMutations(this)
801
+ }
802
+
803
+ adjustRunnerFailuresOnce(this)
804
+
805
+ if (hasFrameworkError) {
806
+ const finalizationError = getFrameworkFinalizationError(frameworkError)
807
+ let hasPropagatedFrameworkError = false
808
+ const propagateFrameworkError = () => {
809
+ if (hasPropagatedFrameworkError) return
810
+
811
+ hasPropagatedFrameworkError = true
812
+ if (typeof this.uncaught === 'function') process.removeListener('uncaughtException', this.uncaught)
813
+ if (typeof this.unhandled === 'function') process.removeListener('unhandledRejection', this.unhandled)
814
+ process.nextTick(() => { throw frameworkError })
815
+ }
816
+
817
+ try {
818
+ endHandler.call(this, finalizationError, propagateFrameworkError)
819
+ } catch (finalizerError) {
820
+ log.error('Datadog Mocha finalizer failed after a reporter error', finalizerError)
821
+ propagateFrameworkError()
822
+ }
823
+
824
+ return result
825
+ }
826
+
827
+ endHandler.call(this)
828
+ return result
829
+ })
830
+ }
831
+
377
832
  function applyKnownTestsResponse ({ err, knownTests }) {
378
833
  if (err) {
379
834
  config.knownTests = []
@@ -466,11 +921,11 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
466
921
  } = response || {}
467
922
  if (!response || err) {
468
923
  suitesToSkip = []
469
- skippableSuitesCoverage = {}
924
+ skippableSuitesCoverage = undefined
470
925
  } else {
471
926
  suitesToSkip = skippableSuites
472
927
  itrCorrelationId = responseItrCorrelationId
473
- skippableSuitesCoverage = responseSkippableSuitesCoverage || {}
928
+ skippableSuitesCoverage = responseSkippableSuitesCoverage
474
929
  }
475
930
  if (localSuites) {
476
931
  suitesToSkip = getSuitesToSkipFromPaths(localSuites)
@@ -707,6 +1162,7 @@ addHook({
707
1162
  if (patched.has(Runner)) return Runner
708
1163
 
709
1164
  patched.add(Runner)
1165
+ wrapRunnerEmit(Runner)
710
1166
 
711
1167
  shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
712
1168
 
@@ -716,7 +1172,16 @@ addHook({
716
1172
  }
717
1173
 
718
1174
  const { onRunDone, onFlushDone } = getRunCompletionCallbacks(args[0])
719
- args[0] = onRunDone
1175
+ resetRunState(this.suite)
1176
+ runnerFailuresAdjusted.delete(this)
1177
+ runnerFrameworkErrors.delete(this)
1178
+ runnerStarted.delete(this)
1179
+ args[0] = () => {
1180
+ adjustRunnerFailuresOnce(this)
1181
+ if (!this.failures && runnerFrameworkErrors.has(this)) this.failures = 1
1182
+ onRunDone(this.failures)
1183
+ runnerFrameworkErrors.delete(this)
1184
+ }
720
1185
 
721
1186
  const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
722
1187
  // Root-level tests (direct children of root, no describe wrapper) keyed by file.
@@ -733,6 +1198,8 @@ addHook({
733
1198
  let hasEnded = false
734
1199
  let hasFinishedRun = false
735
1200
  let endRunner
1201
+ let endError
1202
+ let onFrameworkErrorDone
736
1203
 
737
1204
  function updateRootTestForFinalAttempt (test) {
738
1205
  if (!test._retriedTest) return
@@ -750,7 +1217,8 @@ addHook({
750
1217
  if (hasFinishedRun) return
751
1218
  if (hasEnded && pendingRootFinalizations === 0) {
752
1219
  hasFinishedRun = true
753
- onEnd.call(endRunner)
1220
+ if (onFrameworkErrorDone) onEnd.call(endRunner, endError, onFrameworkErrorDone)
1221
+ else onEnd.call(endRunner)
754
1222
  }
755
1223
  }
756
1224
 
@@ -831,11 +1299,13 @@ addHook({
831
1299
 
832
1300
  const onEnd = getOnEndHandler(false, onFlushDone)
833
1301
 
834
- this.once('start', getOnStartHandler(frameworkVersion))
1302
+ this.prependOnceListener('start', getOnStartHandler(frameworkVersion))
835
1303
 
836
- this.once('end', function () {
1304
+ runnerEndHandlers.set(this, function (frameworkError, frameworkErrorDone) {
837
1305
  hasEnded = true
838
1306
  endRunner = this
1307
+ endError = frameworkError
1308
+ onFrameworkErrorDone = frameworkErrorDone
839
1309
  finishRunIfReady()
840
1310
  })
841
1311
 
@@ -845,7 +1315,10 @@ addHook({
845
1315
  // instead of suiteA -> suiteB -> testA -> ... -> testB)
846
1316
  // when the suite has tests that are in the top level
847
1317
  // (no describe(...))
848
- this.on('test', function (test) {
1318
+ this.prependListener('test', getOnTestHandler(true))
1319
+ // Reporters are registered before Runner#run, so prepend this after the test handler
1320
+ // to start the suite first and preserve the test event if a reporter throws.
1321
+ this.prependListener('test', function (test) {
849
1322
  const ctx = testFileToSuiteCtx.get(test.file)
850
1323
  if (ctx?._pendingRootStart) {
851
1324
  ctx._pendingRootStart = false
@@ -853,20 +1326,28 @@ addHook({
853
1326
  }
854
1327
  })
855
1328
 
856
- this.on('test', getOnTestHandler(true))
857
-
858
- this.on('test end', getOnTestEndHandler(config, {
1329
+ // Reporters are registered before this run wrapper. Prepend terminal handlers
1330
+ // so a reporter error cannot strand a test span that Mocha already completed.
1331
+ const onTestEnd = getOnTestEndHandler(config, {
859
1332
  onStart: incrementPendingRootFinalization,
860
1333
  onFinish: function (test) {
861
1334
  finishRootSuiteAfterFinalAttempt(test)
862
1335
  decrementPendingRootFinalization(test)
863
1336
  },
864
- }))
1337
+ })
1338
+ runnerTestEndHandlers.set(this, onTestEnd)
1339
+ this.prependListener('test end', onTestEnd)
865
1340
 
866
- this.on('retry', getOnTestRetryHandler(config))
1341
+ this.prependListener('retry', getOnTestRetryHandler(config))
867
1342
 
868
- // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
869
- this.on('hook end', getOnHookEndHandler(config, {
1343
+ this.prependListener('hook end', function (hook) {
1344
+ const test = hook.ctx?.currentTest
1345
+ if (!test) return
1346
+ finishRootSuiteAfterFinalAttempt(test)
1347
+ })
1348
+
1349
+ // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted.
1350
+ this.prependListener('hook end', getOnHookEndHandler(config, {
870
1351
  onStart: incrementPendingRootFinalization,
871
1352
  onFinish: function (test) {
872
1353
  finishRootSuiteAfterFinalAttempt(test)
@@ -874,24 +1355,17 @@ addHook({
874
1355
  },
875
1356
  }))
876
1357
 
877
- this.on('hook end', function (hook) {
878
- const test = hook.ctx?.currentTest
879
- if (!test) return
880
- finishRootSuiteAfterFinalAttempt(test)
881
- })
882
-
883
- this.on('fail', getOnFailHandler(true, config))
884
-
885
- this.on('fail', function (testOrHook) {
1358
+ this.prependListener('fail', function (testOrHook) {
886
1359
  if (testOrHook.type !== 'hook') return
887
1360
  const test = testOrHook.ctx?.currentTest
888
1361
  if (!test) return
889
1362
  finishRootSuiteAfterFinalAttempt(test)
890
1363
  })
1364
+ this.prependListener('fail', getOnFailHandler(true, config))
891
1365
 
892
- this.on('pending', getOnPendingHandler())
1366
+ this.prependListener('pending', getOnPendingHandler())
893
1367
 
894
- this.on('suite', function (suite) {
1368
+ this.prependListener('suite', function (suite) {
895
1369
  if (suite.root || !suite.tests.length) {
896
1370
  // This branch can be triggered when we have top level it(...) inside test files.
897
1371
  // In that case, they all (even if they are from different files) are going to be
@@ -940,7 +1414,8 @@ addHook({
940
1414
  }
941
1415
  })
942
1416
 
943
- this.on('suite end', function (suite) {
1417
+ // Reporters are registered before Runner#run, so this must run first when one throws.
1418
+ this.prependListener('suite end', function (suite) {
944
1419
  if (suite.root) {
945
1420
  // Normal case: pure root-level files are finished by the 'test end' / 'hook end'
946
1421
  // listeners via finishRootSuiteForFile. Two edge cases remain here:
@@ -1015,7 +1490,7 @@ addHook({
1015
1490
  // We need to reset coverage to get a code coverage per suite
1016
1491
  // Before that, we preserve the original coverage
1017
1492
  mergeCoverage(global.__coverage__, originalCoverageMap)
1018
- resetCoverage(global.__coverage__)
1493
+ runnersWithPendingCoverageReset.add(this)
1019
1494
  }
1020
1495
 
1021
1496
  const ctx = testFileToSuiteCtx.get(suite.file)
@@ -1121,10 +1596,19 @@ addHook({
1121
1596
  }
1122
1597
 
1123
1598
  const { onRunDone, onFlushDone } = getRunCompletionCallbacks(cb)
1124
- arguments[0] = onRunDone
1599
+ runnerFailuresAdjusted.delete(this)
1600
+ runnerFrameworkErrors.delete(this)
1601
+ runnerStarted.delete(this)
1602
+ parallelRunners.add(this)
1603
+ arguments[0] = () => {
1604
+ adjustRunnerFailuresOnce(this)
1605
+ if (!this.failures && runnerFrameworkErrors.has(this)) this.failures = 1
1606
+ onRunDone(this.failures)
1607
+ runnerFrameworkErrors.delete(this)
1608
+ }
1125
1609
 
1126
- this.once('start', getOnStartHandler(frameworkVersion))
1127
- this.once('end', getOnEndHandler(true, onFlushDone))
1610
+ this.prependOnceListener('start', getOnStartHandler(frameworkVersion))
1611
+ runnerEndHandlers.set(this, getOnEndHandler(true, onFlushDone))
1128
1612
 
1129
1613
  // Populate unskippable suites before config is fetched (matches serial mode at Mocha.prototype.run)
1130
1614
  for (const filePath of files) {