dd-trace 6.8.0 → 6.9.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 (179) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +30 -34
  44. package/index.d.ts +98 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +5 -3
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  55. package/packages/datadog-instrumentations/src/dns.js +21 -2
  56. package/packages/datadog-instrumentations/src/electron.js +1 -0
  57. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  58. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  59. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  60. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
  62. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  66. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  67. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  68. package/packages/datadog-instrumentations/src/jest.js +979 -233
  69. package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
  71. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  72. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  73. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  74. package/packages/datadog-instrumentations/src/next.js +155 -23
  75. package/packages/datadog-instrumentations/src/openai.js +14 -0
  76. package/packages/datadog-instrumentations/src/pg.js +180 -6
  77. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  78. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  79. package/packages/datadog-instrumentations/src/promise.js +3 -3
  80. package/packages/datadog-instrumentations/src/router.js +195 -19
  81. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
  82. package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
  83. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  84. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  85. package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
  86. package/packages/datadog-instrumentations/src/when.js +3 -3
  87. package/packages/datadog-instrumentations/src/ws.js +5 -1
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  89. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  90. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  91. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  92. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  93. package/packages/datadog-plugin-electron/src/net.js +10 -4
  94. package/packages/datadog-plugin-http/src/client.js +1 -1
  95. package/packages/datadog-plugin-http2/src/client.js +1 -1
  96. package/packages/datadog-plugin-jest/src/index.js +1 -18
  97. package/packages/datadog-plugin-jest/src/util.js +2 -0
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  99. package/packages/datadog-plugin-mocha/src/index.js +318 -1
  100. package/packages/datadog-plugin-next/src/index.js +57 -9
  101. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  102. package/packages/datadog-plugin-pg/src/index.js +65 -1
  103. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  104. package/packages/datadog-plugin-vitest/src/index.js +95 -7
  105. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  106. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  107. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  108. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  109. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  110. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  111. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  112. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  113. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  115. package/packages/dd-trace/src/appsec/index.js +48 -476
  116. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  117. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  118. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  120. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  122. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  123. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  124. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  127. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  128. package/packages/dd-trace/src/config/helper.js +2 -0
  129. package/packages/dd-trace/src/config/index.js +16 -5
  130. package/packages/dd-trace/src/config/remote_config.js +1 -1
  131. package/packages/dd-trace/src/config/supported-configurations.json +8 -0
  132. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  133. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  134. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  135. package/packages/dd-trace/src/guardrails/log.js +1 -10
  136. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  137. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  138. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  139. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  140. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  141. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  142. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  143. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  144. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  145. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  146. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  147. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  148. package/packages/dd-trace/src/log/channels.js +1 -9
  149. package/packages/dd-trace/src/log/levels.js +12 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  151. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  153. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  154. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  155. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  156. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  157. package/packages/dd-trace/src/plugins/index.js +1 -0
  158. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  159. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  160. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  161. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  162. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  163. package/packages/dd-trace/src/plugins/util/test.js +319 -75
  164. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  165. package/packages/dd-trace/src/plugins/util/web.js +23 -1
  166. package/packages/dd-trace/src/profiler.js +1 -1
  167. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  168. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  169. package/packages/dd-trace/src/proxy.js +1 -0
  170. package/packages/dd-trace/src/ritm.js +5 -0
  171. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  172. package/packages/dd-trace/src/sampling_rule.js +4 -2
  173. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  174. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  175. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  176. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  177. package/vendor/dist/pprof-format/index.js +1 -1
  178. package/version.js +10 -8
  179. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -11,11 +11,13 @@ const {
11
11
  const { addHook, channel } = require('../helpers/instrument')
12
12
  const shimmer = require('../../../datadog-shimmer')
13
13
  const { isMarkedAsUnskippable } = require('../../../datadog-plugin-jest/src/util')
14
+ const { EMPTY_EFD_RETRY_POLICY } = require('../../../dd-trace/src/ci-visibility/efd-retry-policy')
14
15
  const { writeCoverageBackfillToCache } = require('../../../dd-trace/src/ci-visibility/test-optimization-cache')
15
16
  const log = require('../../../dd-trace/src/log')
16
17
  const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper')
17
18
  const {
18
19
  getTestSuitePath,
20
+ MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE,
19
21
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
20
22
  fromCoverageMapToCoverage,
21
23
  getCoveredFilesFromCoverage,
@@ -28,6 +30,7 @@ const {
28
30
  getTestCoverageLinesPercentage,
29
31
  collectTestOptimizationSummariesFromTraces,
30
32
  logTestOptimizationSummary,
33
+ TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE,
31
34
  getTestOptimizationRequestResults,
32
35
  isModifiedTest,
33
36
  } = require('../../../dd-trace/src/plugins/util/test')
@@ -66,12 +69,15 @@ let hasWarnedDeprecatedMochaVersion = false
66
69
  const unskippableSuites = []
67
70
  let suitesToSkip = []
68
71
  let isSuitesSkipped = false
72
+ let areAllSuitesSkipped = false
69
73
  let skippedSuites = []
70
74
  let skippableSuitesCoverage = {}
71
75
  let skippedSuitesCoverage = {}
72
76
  let itrCorrelationId = ''
73
77
  let isForcedToRun = false
74
- const config = {}
78
+ const config = {
79
+ earlyFlakeDetectionRetryPolicy: EMPTY_EFD_RETRY_POLICY,
80
+ }
75
81
 
76
82
  // We'll preserve the original coverage here
77
83
  const originalCoverageMap = createCoverageMap()
@@ -95,6 +101,7 @@ const mochaGlobalRunCh = channel('ci:mocha:global:run')
95
101
  const testManagementTestsCh = channel('ci:mocha:test-management-tests')
96
102
  const modifiedFilesCh = channel('ci:mocha:modified-files')
97
103
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
104
+ const workerReportTelemetryCh = channel('ci:mocha:worker-report:telemetry')
98
105
  const testSessionStartCh = channel('ci:mocha:session:start')
99
106
  const testSessionFinishCh = channel('ci:mocha:session:finish')
100
107
  const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
@@ -227,6 +234,7 @@ function getMochaTestSessionCoverageFiles () {
227
234
 
228
235
  function resetSuiteSkippingRunState () {
229
236
  isSuitesSkipped = false
237
+ areAllSuitesSkipped = false
230
238
  skippedSuites = []
231
239
  skippableSuitesCoverage = {}
232
240
  skippedSuitesCoverage = {}
@@ -357,7 +365,11 @@ function getOnEndHandler (isParallel, onDone) {
357
365
  isParallel,
358
366
  }, onDone)
359
367
 
360
- logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
368
+ logTestOptimizationSummary({
369
+ attemptToFixExecutions,
370
+ newTestsWithDynamicNames,
371
+ extraSections: areAllSuitesSkipped ? [TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE] : [],
372
+ })
361
373
  loggedAttemptToFixTests.clear()
362
374
  }
363
375
  }
@@ -386,6 +398,57 @@ function isFailedTestReplayEnabled () {
386
398
  return config.isTestDynamicInstrumentationEnabled && config.isDiEnabled
387
399
  }
388
400
 
401
+ /**
402
+ * @typedef {object} MochaSuite
403
+ * @property {MochaSuite[]} suites
404
+ * @property {import('mocha').Test[]} tests
405
+ * @property {MochaSuite[]} _onlySuites
406
+ * @property {import('mocha').Test[]} _onlyTests
407
+ */
408
+
409
+ /**
410
+ * Mirrors Mocha 5's private exclusivity check.
411
+ *
412
+ * @param {MochaSuite} suite
413
+ * @returns {boolean}
414
+ */
415
+ function hasOnly (suite) {
416
+ if (suite._onlyTests.length || suite._onlySuites.length) return true
417
+
418
+ for (const childSuite of suite.suites) {
419
+ if (hasOnly(childSuite)) return true
420
+ }
421
+ return false
422
+ }
423
+
424
+ /**
425
+ * Mirrors Mocha 5's private exclusivity filter.
426
+ *
427
+ * @param {MochaSuite} suite
428
+ * @returns {boolean}
429
+ */
430
+ function filterOnly (suite) {
431
+ if (suite._onlyTests.length) {
432
+ suite.tests = suite._onlyTests
433
+ suite.suites = []
434
+ } else {
435
+ suite.tests = []
436
+
437
+ for (const onlySuite of suite._onlySuites) {
438
+ if (hasOnly(onlySuite)) filterOnly(onlySuite)
439
+ }
440
+
441
+ const filteredSuites = []
442
+ for (const childSuite of suite.suites) {
443
+ if (suite._onlySuites.includes(childSuite) || filterOnly(childSuite)) {
444
+ filteredSuites.push(childSuite)
445
+ }
446
+ }
447
+ suite.suites = filteredSuites
448
+ }
449
+ return suite.tests.length > 0 || suite.suites.length > 0
450
+ }
451
+
389
452
  function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFinishRequest, localSuites) {
390
453
  const ctx = {
391
454
  isParallel,
@@ -418,6 +481,13 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
418
481
  }
419
482
 
420
483
  // We remove the suites that we skip through ITR
484
+ // Mocha normally applies exclusivity after this asynchronous configuration step.
485
+ if (typeof runner.suite.hasOnly === 'function') {
486
+ if (runner.suite.hasOnly()) runner.suite.filterOnly()
487
+ } else if (hasOnly(runner.suite)) {
488
+ filterOnly(runner.suite)
489
+ }
490
+ const numTestsToRun = runner.grepTotal(runner.suite)
421
491
  const filteredSuites = getFilteredSuites(runner.suite.suites)
422
492
  const { suitesToRun, suitesToSkipForRun } = filteredSuites
423
493
 
@@ -426,6 +496,7 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
426
496
  log.debug('%d out of %d suites are going to run.', suitesToRun.length, runner.suite.suites.length)
427
497
 
428
498
  runner.suite.suites = suitesToRun
499
+ areAllSuitesSkipped = isSuitesSkipped && numTestsToRun > 0 && runner.grepTotal(runner.suite) === 0
429
500
 
430
501
  skippedSuites = [...filteredSuites.skippedSuites]
431
502
  suitesToSkip = suitesToSkipForRun
@@ -489,8 +560,8 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
489
560
  }
490
561
  config.repositoryRoot = repositoryRoot
491
562
  config.isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
492
- config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
493
- config.earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
563
+ config.earlyFlakeDetectionRetryPolicy =
564
+ libraryConfig.earlyFlakeDetectionRetryPolicy ?? EMPTY_EFD_RETRY_POLICY
494
565
  config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
495
566
  config.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
496
567
  config.isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
@@ -978,6 +1049,8 @@ function onMessage (message) {
978
1049
  attemptToFixExecutions,
979
1050
  })
980
1051
  workerReportTraceCh.publish(payload)
1052
+ } else if (messageCode === MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE) {
1053
+ workerReportTelemetryCh.publish(payload)
981
1054
  }
982
1055
  }
983
1056
  }
@@ -1042,7 +1115,7 @@ addHook({
1042
1115
  versions: ['>=8.0.0'],
1043
1116
  file: 'lib/nodejs/parallel-buffered-runner.js',
1044
1117
  }, (ParallelBufferedRunner, frameworkVersion) => {
1045
- shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function (cb, { files }) {
1118
+ shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function (cb, { files, options = {} }) {
1046
1119
  if (!testFinishCh.hasSubscribers) {
1047
1120
  return run.apply(this, arguments)
1048
1121
  }
@@ -1088,10 +1161,11 @@ addHook({
1088
1161
  }
1089
1162
  }
1090
1163
  isSuitesSkipped = skippedFiles.length > 0
1164
+ areAllSuitesSkipped = options.grep === undefined && files.length > 0 && filteredFiles.length === 0
1091
1165
  skippedSuites = skippedFiles
1092
1166
  skippedSuitesCoverage = getSkippedSuitesCoverageForRun()
1093
1167
  writeCoverageBackfillToCache(skippedSuitesCoverage, getCoverageRootDir())
1094
- run.apply(this, [onRunDone, { files: filteredFiles }])
1168
+ run.apply(this, [onRunDone, { files: filteredFiles, options }])
1095
1169
  } else {
1096
1170
  run.apply(this, arguments)
1097
1171
  }
@@ -1110,9 +1184,34 @@ addHook({
1110
1184
  name: 'mocha',
1111
1185
  versions: ['>=8.0.0'],
1112
1186
  file: 'lib/nodejs/buffered-worker-pool.js',
1113
- }, (BufferedWorkerPoolPackage) => {
1187
+ }, (BufferedWorkerPoolPackage, frameworkVersion) => {
1114
1188
  const { BufferedWorkerPool } = BufferedWorkerPoolPackage
1115
1189
 
1190
+ if (satisfies(frameworkVersion, '<9.2.0')) {
1191
+ // Shimmer is required because the worker environment must be changed before workerpool forks,
1192
+ // before any test lifecycle hook can run. Mocha added this worker ID itself in 9.2.0.
1193
+ shimmer.wrap(BufferedWorkerPool, 'create', create => function () {
1194
+ const pool = create.apply(this, arguments)
1195
+
1196
+ if (!testFinishCh.hasSubscribers) return pool
1197
+
1198
+ let workerId = 0
1199
+ shimmer.wrap(pool._pool, '_createWorkerHandler', createWorkerHandler => function () {
1200
+ this.forkOpts = {
1201
+ ...this.forkOpts,
1202
+ env: {
1203
+ // eslint-disable-next-line eslint-rules/eslint-process-env
1204
+ ...(this.forkOpts.env || process.env),
1205
+ MOCHA_WORKER_ID: String(workerId++),
1206
+ },
1207
+ }
1208
+ return createWorkerHandler.apply(this, arguments)
1209
+ })
1210
+
1211
+ return pool
1212
+ })
1213
+ }
1214
+
1116
1215
  shimmer.wrap(BufferedWorkerPool.prototype, 'run', run => async function (testSuiteAbsolutePath, workerArgs) {
1117
1216
  if (!testFinishCh.hasSubscribers ||
1118
1217
  (!config.isKnownTestsEnabled &&
@@ -1130,8 +1229,7 @@ addHook({
1130
1229
  if (config.isKnownTestsEnabled) {
1131
1230
  if (config.knownTests?.mocha) {
1132
1231
  const testSuiteKnownTests = config.knownTests.mocha[testPath] || []
1133
- newWorkerArgs._ddEfdNumRetries = config.earlyFlakeDetectionNumRetries
1134
- newWorkerArgs._ddEfdSlowTestRetries = config.earlyFlakeDetectionSlowTestRetries
1232
+ newWorkerArgs._ddEfdRetryPolicy = config.earlyFlakeDetectionRetryPolicy
1135
1233
  newWorkerArgs._ddIsEfdEnabled = config.isEarlyFlakeDetectionEnabled
1136
1234
  newWorkerArgs._ddIsKnownTestsEnabled = true
1137
1235
  newWorkerArgs._ddKnownTests = {
@@ -2,11 +2,16 @@
2
2
 
3
3
  const { performance } = require('node:perf_hooks')
4
4
 
5
+ const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper')
6
+ const {
7
+ getEfdRetryCountForDuration,
8
+ hasEfdRetries,
9
+ shouldSkipEfdRetry,
10
+ } = require('../../../dd-trace/src/ci-visibility/efd-retry-policy')
5
11
  const {
6
12
  getTestSuitePath,
7
13
  DYNAMIC_NAME_RE,
8
- getEfdRetryCount,
9
- getMaxEfdRetryCount,
14
+ recordTestManagementExecution,
10
15
  recordAttemptToFixExecution,
11
16
  logAttemptToFixTestExecution,
12
17
  } = require('../../../dd-trace/src/plugins/util/test')
@@ -26,6 +31,8 @@ const isModifiedCh = channel('ci:mocha:test:is-modified')
26
31
  // suite channels
27
32
  const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
28
33
 
34
+ /** @typedef {{ length: number, [index: number]: unknown } & Iterable<unknown>} ArgumentsLike */
35
+
29
36
  const testToContext = new WeakMap()
30
37
  const originalFns = new WeakMap()
31
38
  const testToStartLine = new WeakMap()
@@ -40,6 +47,7 @@ const testsStatuses = new Map()
40
47
  const efdRetryCountByTestFullName = new Map()
41
48
  const efdSlowAbortedTests = new Set()
42
49
  const attemptToFixExecutions = new Map()
50
+ const isMochaWorker = !!getEnvironmentVariable('MOCHA_WORKER_ID')
43
51
 
44
52
  function waitForHitProbe () {
45
53
  const promises = {}
@@ -133,19 +141,28 @@ function isNewTest (test, knownTests) {
133
141
  return !testsForSuite.includes(testName)
134
142
  }
135
143
 
136
- function setEfdRetryCountForTest (test, duration, slowTestRetries) {
144
+ /**
145
+ * @param {import('mocha').Test} test
146
+ * @param {number} duration
147
+ * @param {import('../../../dd-trace/src/ci-visibility/efd-retry-policy').EfdRetryPolicy} retryPolicy
148
+ */
149
+ function setEfdRetryCountForTest (test, duration, retryPolicy) {
137
150
  const testName = getTestFullName(test)
138
151
  if (efdRetryCountByTestFullName.has(testName)) {
139
152
  return
140
153
  }
141
- const retryCount = getEfdRetryCount(duration, slowTestRetries || {})
154
+ const retryCount = getEfdRetryCountForDuration(duration, retryPolicy)
142
155
  efdRetryCountByTestFullName.set(testName, retryCount)
143
156
  if (retryCount === 0) {
144
157
  efdSlowAbortedTests.add(testName)
145
158
  }
146
159
  }
147
160
 
148
- function wrapOriginalEfdTest (test, slowTestRetries) {
161
+ /**
162
+ * @param {import('mocha').Test} test
163
+ * @param {import('../../../dd-trace/src/ci-visibility/efd-retry-policy').EfdRetryPolicy} retryPolicy
164
+ */
165
+ function wrapOriginalEfdTest (test, retryPolicy) {
149
166
  if (test._ddEfdDurationWrapped || typeof test.fn !== 'function') {
150
167
  return
151
168
  }
@@ -154,7 +171,7 @@ function wrapOriginalEfdTest (test, slowTestRetries) {
154
171
  test.fn = shimmer.wrapFunction(originalFn, originalFn => function () {
155
172
  const start = performance.now()
156
173
  const recordDuration = () => {
157
- setEfdRetryCountForTest(test, performance.now() - start, slowTestRetries)
174
+ setEfdRetryCountForTest(test, performance.now() - start, retryPolicy)
158
175
  }
159
176
 
160
177
  if (originalFn.length > 0) {
@@ -205,13 +222,14 @@ function disableMochaRetries (test) {
205
222
  * _ddIsModified?: boolean,
206
223
  * _ddIsNew?: boolean
207
224
  * }} test
208
- * @param {{ isEarlyFlakeDetectionEnabled?: boolean }} config
225
+ * @param {{
226
+ * isEarlyFlakeDetectionEnabled?: boolean,
227
+ * earlyFlakeDetectionRetryPolicy?: import('../../../dd-trace/src/ci-visibility/efd-retry-policy').EfdRetryPolicy
228
+ * }} config
209
229
  * @returns {boolean}
210
230
  */
211
231
  function isDatadogManagedRetryTest (test, config) {
212
- return test._ddIsAttemptToFix ||
213
- test._ddIsEfdRetry ||
214
- (config.isEarlyFlakeDetectionEnabled && (test._ddIsNew || test._ddIsModified))
232
+ return test._ddIsAttemptToFix || isEarlyFlakeDetectionTest(test, config)
215
233
  }
216
234
 
217
235
  /**
@@ -223,22 +241,32 @@ function isDatadogManagedRetryTest (test, config) {
223
241
  * _ddIsModified?: boolean,
224
242
  * _ddIsNew?: boolean
225
243
  * }} test
226
- * @param {{ isEarlyFlakeDetectionEnabled?: boolean }} config
244
+ * @param {{
245
+ * isEarlyFlakeDetectionEnabled?: boolean,
246
+ * earlyFlakeDetectionRetryPolicy?: import('../../../dd-trace/src/ci-visibility/efd-retry-policy').EfdRetryPolicy
247
+ * }} config
227
248
  * @returns {boolean}
228
249
  */
229
250
  function isEarlyFlakeDetectionTest (test, config) {
230
251
  return !test._ddIsAttemptToFix &&
231
252
  !test._ddIsDisabled &&
232
253
  config.isEarlyFlakeDetectionEnabled &&
254
+ hasEfdRetries(config.earlyFlakeDetectionRetryPolicy) &&
233
255
  (test._ddIsEfdRetry || test._ddIsNew || test._ddIsModified)
234
256
  }
235
257
 
236
- function retryTest (test, numRetries, tags, slowTestRetries) {
258
+ /**
259
+ * @param {import('mocha').Test} test
260
+ * @param {number} numRetries
261
+ * @param {string[]} tags
262
+ * @param {import('../../../dd-trace/src/ci-visibility/efd-retry-policy').EfdRetryPolicy} retryPolicy
263
+ */
264
+ function retryTest (test, numRetries, tags, retryPolicy) {
237
265
  const suite = test.parent
238
266
  const isEfdRetry = tags.includes('_ddIsEfdRetry')
239
267
  disableMochaRetries(test)
240
268
  if (isEfdRetry) {
241
- wrapOriginalEfdTest(test, slowTestRetries)
269
+ wrapOriginalEfdTest(test, retryPolicy)
242
270
  }
243
271
  for (let retryIndex = 0; retryIndex < numRetries; retryIndex++) {
244
272
  const clonedTest = test.clone()
@@ -250,7 +278,7 @@ function retryTest (test, numRetries, tags, slowTestRetries) {
250
278
  if (typeof originalFn === 'function') {
251
279
  clonedTest.fn = shimmer.wrapFunction(originalFn, originalFn => function (...args) {
252
280
  const efdRetryCount = efdRetryCountByTestFullName.get(getTestFullName(clonedTest))
253
- if (efdRetryCount !== undefined && clonedTest._ddEfdRetryIndex > efdRetryCount) {
281
+ if (shouldSkipEfdRetry(clonedTest._ddEfdRetryIndex, efdRetryCount)) {
254
282
  clonedTest._ddShouldSkipEfdRetry = true
255
283
  this.skip()
256
284
  }
@@ -266,14 +294,6 @@ function retryTest (test, numRetries, tags, slowTestRetries) {
266
294
  }
267
295
  }
268
296
 
269
- function getConfiguredEfdRetryCount (config) {
270
- const { earlyFlakeDetectionSlowTestRetries } = config
271
- if (!earlyFlakeDetectionSlowTestRetries || !Object.keys(earlyFlakeDetectionSlowTestRetries).length) {
272
- return config.earlyFlakeDetectionNumRetries
273
- }
274
- return getMaxEfdRetryCount(earlyFlakeDetectionSlowTestRetries)
275
- }
276
-
277
297
  function getSuitesByTestFile (root) {
278
298
  const suitesByTestFile = {}
279
299
  function getSuites (suite) {
@@ -462,7 +482,7 @@ function getOnTestHandler (isMain) {
462
482
 
463
483
  if (isEfdRetry) {
464
484
  const efdRetryCount = efdRetryCountByTestFullName.get(getTestFullName(test))
465
- if (efdRetryCount !== undefined && test._ddEfdRetryIndex > efdRetryCount) {
485
+ if (shouldSkipEfdRetry(test._ddEfdRetryIndex, efdRetryCount)) {
466
486
  test.pending = true
467
487
  test._ddShouldSkipEfdRetry = true
468
488
  return
@@ -576,7 +596,7 @@ function getTestFinishInfo (test, status, config, error) {
576
596
  !efdRetryCountByTestFullName.has(testName)
577
597
  ) {
578
598
  const duration = test.duration > 0 ? test.duration : performance.now() - test._ddStartTime
579
- setEfdRetryCountForTest(test, duration, config.earlyFlakeDetectionSlowTestRetries)
599
+ setEfdRetryCountForTest(test, duration, config.earlyFlakeDetectionRetryPolicy)
580
600
  }
581
601
 
582
602
  if (testsStatuses.get(testName)) {
@@ -587,7 +607,8 @@ function getTestFinishInfo (test, status, config, error) {
587
607
  const testStatuses = testsStatuses.get(testName)
588
608
 
589
609
  const isLastAttempt = testStatuses.length === config.testManagementAttemptToFixRetries + 1
590
- const efdRetryCount = efdRetryCountByTestFullName.get(testName) ?? getConfiguredEfdRetryCount(config)
610
+ const efdRetryCount = efdRetryCountByTestFullName.get(testName) ??
611
+ config.earlyFlakeDetectionRetryPolicy.schedulingRetryCount
591
612
  const isLastEfdRetry = testStatuses.length === efdRetryCount + 1
592
613
  const isLastAtrAttempt = getIsLastRetry(test) || (config.isFlakyTestRetriesEnabled && status === 'pass')
593
614
 
@@ -642,6 +663,17 @@ function getTestFinishInfo (test, status, config, error) {
642
663
  isFinalAttempt,
643
664
  })
644
665
 
666
+ if (!isMochaWorker) {
667
+ recordTestManagementExecution({
668
+ testSuite: getTestSuitePath(test.file, process.cwd()),
669
+ testName: test.fullTitle(),
670
+ status,
671
+ isAttemptToFix: _ddIsAttemptToFix,
672
+ isDisabled: _ddIsDisabled,
673
+ isQuarantined: _ddIsQuarantined,
674
+ })
675
+ }
676
+
645
677
  if (_ddIsAttemptToFix) {
646
678
  recordAttemptToFixExecution(attemptToFixExecutions, {
647
679
  testSuite: getTestSuitePath(test.file, process.cwd()),
@@ -788,7 +820,7 @@ function finishDeferredHookEnd (test) {
788
820
  * @param {object} test - Mocha test currently owning the hook.
789
821
  * @param {Promise<void>|undefined} failedTestReplayPromise - Pending Failed Test Replay wait, if any.
790
822
  * @param {unknown} hookThis - Callback receiver.
791
- * @param {IArguments} args - Arguments passed by Mocha.
823
+ * @param {ArgumentsLike} args - Arguments passed by Mocha.
792
824
  * @returns {unknown}
793
825
  */
794
826
  function runFailedTestReplayHookUpCallback (fn, test, failedTestReplayPromise, hookThis, args) {
@@ -1004,17 +1036,12 @@ function getRunTestsWrapper (runTests, config) {
1004
1036
  onDone: (isModified) => {
1005
1037
  if (isModified) {
1006
1038
  test._ddIsModified = true
1007
- if (
1008
- !test.isPending() &&
1009
- !test._ddIsDisabled &&
1010
- !test._ddIsAttemptToFix &&
1011
- config.isEarlyFlakeDetectionEnabled
1012
- ) {
1039
+ if (!test.isPending() && isEarlyFlakeDetectionTest(test, config)) {
1013
1040
  retryTest(
1014
1041
  test,
1015
- getConfiguredEfdRetryCount(config),
1042
+ config.earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
1016
1043
  ['_ddIsModified', '_ddIsEfdRetry'],
1017
- config.earlyFlakeDetectionSlowTestRetries
1044
+ config.earlyFlakeDetectionRetryPolicy
1018
1045
  )
1019
1046
  }
1020
1047
  }
@@ -1028,17 +1055,12 @@ function getRunTestsWrapper (runTests, config) {
1028
1055
  suite.tests.forEach((test) => {
1029
1056
  if (!test.isPending() && isNewTest(test, config.knownTests)) {
1030
1057
  test._ddIsNew = true
1031
- if (
1032
- config.isEarlyFlakeDetectionEnabled &&
1033
- !test._ddIsDisabled &&
1034
- !test._ddIsAttemptToFix &&
1035
- !test._ddIsModified
1036
- ) {
1058
+ if (!test._ddIsModified && isEarlyFlakeDetectionTest(test, config)) {
1037
1059
  retryTest(
1038
1060
  test,
1039
- getConfiguredEfdRetryCount(config),
1061
+ config.earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
1040
1062
  ['_ddIsNew', '_ddIsEfdRetry'],
1041
- config.earlyFlakeDetectionSlowTestRetries
1063
+ config.earlyFlakeDetectionRetryPolicy
1042
1064
  )
1043
1065
  }
1044
1066
  }
@@ -7,12 +7,37 @@ const {
7
7
  WEBDRIVERIO_WORKER_ORIGIN,
8
8
  } = require('../../../dd-trace/src/ci-visibility/exporters/test-worker/webdriverio')
9
9
 
10
+ /**
11
+ * Sends a message over WebdriverIO's worker IPC envelope.
12
+ *
13
+ * @param {object} message
14
+ * @param {(error?: Error) => void} [onError]
15
+ * @param {() => void} [onDone]
16
+ * @returns {void}
17
+ */
18
+ function sendWebdriverioWorkerMessage (message, onError, onDone) {
19
+ if (!process.send || !process.connected) {
20
+ onError?.()
21
+ onDone?.()
22
+ return
23
+ }
24
+
25
+ process.send(createWebdriverioWorkerMessage(message), (error) => {
26
+ if (error) {
27
+ onError?.(error)
28
+ }
29
+ onDone?.()
30
+ })
31
+ }
32
+
10
33
  module.exports = {
11
34
  CONFIGURATION_REQUEST: 'dd:test-optimization:webdriverio:configuration:request',
12
35
  CONFIGURATION_RESPONSE: 'dd:test-optimization:webdriverio:configuration:response',
13
36
  createWebdriverioWorkerMessage,
37
+ sendWebdriverioWorkerMessage,
14
38
  SUITE_FINISH: 'dd:test-optimization:webdriverio:test-suite:finish',
15
39
  WORKER_READY: 'dd:test-optimization:webdriverio:worker:ready',
40
+ WORKER_READY_RESPONSE: 'dd:test-optimization:webdriverio:worker:ready:response',
16
41
  WEBDRIVERIO_WORKER_ENV,
17
42
  WEBDRIVERIO_WORKER_EVENT,
18
43
  WEBDRIVERIO_WORKER_ORIGIN,
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { addHook, channel } = require('../helpers/instrument')
4
4
  const shimmer = require('../../../datadog-shimmer')
5
+ const { EMPTY_EFD_RETRY_POLICY } = require('../../../dd-trace/src/ci-visibility/efd-retry-policy')
5
6
  const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper')
6
7
  const log = require('../../../dd-trace/src/log')
7
8
  const { DD_MAJOR } = require('../../../../version')
@@ -23,7 +24,7 @@ const {
23
24
  const {
24
25
  CONFIGURATION_REQUEST,
25
26
  CONFIGURATION_RESPONSE,
26
- createWebdriverioWorkerMessage,
27
+ sendWebdriverioWorkerMessage,
27
28
  SUITE_FINISH,
28
29
  WEBDRIVERIO_WORKER_ENV,
29
30
  WORKER_READY,
@@ -35,7 +36,9 @@ const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
35
36
  const workerFinishCh = channel('ci:mocha:worker:finish')
36
37
  const workerConfigurationCh = channel('ci:mocha:worker:configuration')
37
38
 
38
- const config = {}
39
+ const config = {
40
+ earlyFlakeDetectionRetryPolicy: EMPTY_EFD_RETRY_POLICY,
41
+ }
39
42
  const runnerToFiles = new WeakMap()
40
43
  const runnerToFailedHooks = new WeakMap()
41
44
  const isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
@@ -50,19 +53,12 @@ let configurationRequestId = 0
50
53
  * @returns {void}
51
54
  */
52
55
  function sendWebdriverioMessage (message, onError, onDone) {
53
- if (!process.send || !process.connected) {
54
- onError?.()
55
- onDone?.()
56
- return
57
- }
58
-
59
- process.send(createWebdriverioWorkerMessage(message), (error) => {
56
+ sendWebdriverioWorkerMessage(message, error => {
60
57
  if (error) {
61
58
  log.error('WebdriverIO Test Optimization IPC error', error)
62
- onError?.()
63
59
  }
64
- onDone?.()
65
- })
60
+ onError?.()
61
+ }, onDone)
66
62
  }
67
63
 
68
64
  /**
@@ -76,12 +72,10 @@ function applyMochaOptions (options) {
76
72
  config.isKnownTestsEnabled = true
77
73
  config.isEarlyFlakeDetectionEnabled = options._ddIsEfdEnabled
78
74
  config.knownTests = options._ddKnownTests
79
- config.earlyFlakeDetectionNumRetries = options._ddEfdNumRetries
80
- config.earlyFlakeDetectionSlowTestRetries = options._ddEfdSlowTestRetries ?? {}
75
+ config.earlyFlakeDetectionRetryPolicy = options._ddEfdRetryPolicy ?? EMPTY_EFD_RETRY_POLICY
81
76
  delete options._ddIsEfdEnabled
82
77
  delete options._ddKnownTests
83
- delete options._ddEfdNumRetries
84
- delete options._ddEfdSlowTestRetries
78
+ delete options._ddEfdRetryPolicy
85
79
  delete options._ddIsKnownTestsEnabled
86
80
  }
87
81
  if (options._ddIsImpactedTestsEnabled) {
@@ -245,7 +239,7 @@ function getWebdriverioHookTest (hook) {
245
239
  */
246
240
  function adjustWebdriverioHookFailures (runner) {
247
241
  let suppressedFailures = 0
248
- for (const { test } of runnerToFailedHooks.get(runner) || []) {
242
+ for (const { test } of runnerToFailedHooks.get(runner)) {
249
243
  if (isWebdriverioFailureSuppressed(test)) {
250
244
  suppressedFailures++
251
245
  }
@@ -287,7 +281,7 @@ function getWebdriverioSuiteResults (runner) {
287
281
  }
288
282
  })
289
283
 
290
- for (const { file, test } of runnerToFailedHooks.get(runner) || []) {
284
+ for (const { file, test } of runnerToFailedHooks.get(runner)) {
291
285
  const result = resultsByFile.get(file)
292
286
  if (result && !isWebdriverioFailureSuppressed(test)) {
293
287
  result.status = 'fail'
@@ -36,7 +36,7 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
36
36
  }
37
37
  ctx.result = result
38
38
 
39
- return finishCh.runStores(ctx, cb, this, error, result)
39
+ return finishCh.runStores(ctx, cb, this, ...arguments)
40
40
  })
41
41
  } else {
42
42
  res.once('end', () => finishCh.publish(ctx))