dd-trace 5.104.0 → 5.106.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 (159) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +16 -2
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +390 -157
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  14. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  15. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  19. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  27. package/packages/datadog-instrumentations/src/hono.js +54 -3
  28. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  29. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  30. package/packages/datadog-instrumentations/src/jest.js +360 -150
  31. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  32. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  33. package/packages/datadog-instrumentations/src/nats.js +182 -0
  34. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  35. package/packages/datadog-instrumentations/src/openai.js +33 -18
  36. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  37. package/packages/datadog-instrumentations/src/pino.js +17 -5
  38. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  39. package/packages/datadog-instrumentations/src/router.js +76 -32
  40. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  41. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  42. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  45. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  46. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  47. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  48. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  49. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  50. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  51. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  52. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  53. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  54. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  55. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  56. package/packages/datadog-plugin-http/src/server.js +40 -15
  57. package/packages/datadog-plugin-jest/src/index.js +11 -3
  58. package/packages/datadog-plugin-jest/src/util.js +15 -8
  59. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  60. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  61. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  62. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  63. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  64. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  65. package/packages/datadog-plugin-nats/src/index.js +20 -0
  66. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  67. package/packages/datadog-plugin-nats/src/util.js +33 -0
  68. package/packages/datadog-plugin-next/src/index.js +5 -3
  69. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  70. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  71. package/packages/datadog-plugin-pino/src/index.js +42 -0
  72. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  73. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  74. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  75. package/packages/datadog-plugin-router/src/index.js +33 -44
  76. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  77. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  78. package/packages/datadog-plugin-winston/src/index.js +30 -0
  79. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  80. package/packages/dd-trace/src/aiguard/index.js +1 -1
  81. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  82. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  83. package/packages/dd-trace/src/appsec/index.js +1 -1
  84. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  85. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  86. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  87. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  88. package/packages/dd-trace/src/baggage.js +7 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  90. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  91. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  92. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  93. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  94. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  96. package/packages/dd-trace/src/encode/0.4.js +124 -108
  97. package/packages/dd-trace/src/encode/0.5.js +114 -26
  98. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  99. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  100. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  101. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  102. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  103. package/packages/dd-trace/src/id.js +15 -0
  104. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +92 -6
  105. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  106. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  107. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  108. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  109. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  110. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  111. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  112. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  113. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  114. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  115. package/packages/dd-trace/src/llmobs/util.js +66 -3
  116. package/packages/dd-trace/src/log/index.js +1 -1
  117. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  118. package/packages/dd-trace/src/msgpack/index.js +96 -2
  119. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  120. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  121. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  122. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  123. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  124. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  125. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +22 -3
  126. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  127. package/packages/dd-trace/src/opentracing/propagation/text_map.js +64 -77
  128. package/packages/dd-trace/src/opentracing/span.js +59 -19
  129. package/packages/dd-trace/src/opentracing/span_context.js +50 -3
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  131. package/packages/dd-trace/src/plugins/database.js +7 -6
  132. package/packages/dd-trace/src/plugins/index.js +4 -0
  133. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  134. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  135. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  136. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  137. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  138. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  139. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  140. package/packages/dd-trace/src/priority_sampler.js +2 -2
  141. package/packages/dd-trace/src/profiling/config.js +10 -23
  142. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  143. package/packages/dd-trace/src/profiling/profiler.js +21 -11
  144. package/packages/dd-trace/src/profiling/profilers/wall.js +12 -7
  145. package/packages/dd-trace/src/sampling_rule.js +7 -7
  146. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  147. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  148. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  149. package/packages/dd-trace/src/span_format.js +190 -58
  150. package/packages/dd-trace/src/spanleak.js +1 -1
  151. package/packages/dd-trace/src/standalone/index.js +3 -3
  152. package/packages/dd-trace/src/tagger.js +0 -2
  153. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  154. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  155. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  156. package/vendor/dist/protobufjs/index.js +1 -1
  157. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  158. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  159. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -62,6 +62,7 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
62
62
  shimmer.wrap(Kafka.prototype, 'producer', createProducer => function () {
63
63
  const producer = createProducer.apply(this, arguments)
64
64
  const originalSend = producer.send
65
+ const originalSendBatch = producer.sendBatch
65
66
  const bootstrapServers = this._brokers
66
67
  const cluster = clientToCluster.get(producer)
67
68
 
@@ -75,35 +76,46 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
75
76
  }
76
77
  }
77
78
 
78
- producer.send = function (...args) {
79
- if (!producerStartCh.hasSubscribers) {
80
- return originalSend.apply(this, args)
81
- }
82
-
83
- // Fast path: kafkajs has fetched metadata, so versions and clusterId
84
- // are already on the broker pool.
79
+ /**
80
+ * Resolve the negotiated clusterId once and hand it to `call`. Fast path reads
81
+ * `cluster.brokerPool.metadata` synchronously when kafkajs already fetched it.
82
+ * Slow path primes `refreshMetadataIfNecessary`, which `sharedPromiseTo`
83
+ * deduplicates with kafkajs's own internal fetch so total latency is unchanged.
84
+ *
85
+ * @param {(clusterId: string | undefined) => Promise<unknown>} call
86
+ */
87
+ const withClusterId = (call) => {
85
88
  const metadata = cluster?.brokerPool?.metadata
86
89
  if (metadata) {
87
90
  refreshHeaderSupport()
88
- return runSend.call(this, args, metadata.clusterId)
91
+ return call(metadata.clusterId)
89
92
  }
90
-
91
- // Slow path, taken at most once per producer connect cycle. Prime the
92
- // metadata fetch kafkajs's send would do internally a few stack frames
93
- // later. `sharedPromiseTo` collapses our call and kafkajs's call into a
94
- // single round trip, so total latency is unchanged.
95
93
  if (typeof cluster?.refreshMetadataIfNecessary !== 'function') {
96
- return runSend.call(this, args)
94
+ return call()
97
95
  }
98
96
  return cluster.refreshMetadataIfNecessary().then(
99
97
  () => {
100
98
  refreshHeaderSupport()
101
- return runSend.call(this, args, cluster.brokerPool?.metadata?.clusterId)
99
+ return call(cluster.brokerPool?.metadata?.clusterId)
102
100
  },
103
- () => runSend.call(this, args)
101
+ () => call()
104
102
  )
105
103
  }
106
104
 
105
+ producer.send = function (...args) {
106
+ if (!producerStartCh.hasSubscribers) {
107
+ return originalSend.apply(this, args)
108
+ }
109
+ return withClusterId((clusterId) => runSend.call(this, args, clusterId))
110
+ }
111
+
112
+ producer.sendBatch = function (...args) {
113
+ if (!producerStartCh.hasSubscribers) {
114
+ return originalSendBatch.apply(this, args)
115
+ }
116
+ return withClusterId((clusterId) => runSendBatch.call(this, args, clusterId))
117
+ }
118
+
107
119
  function runSend (args, clusterId) {
108
120
  const arg0 = args[0]
109
121
  const topic = arg0?.topic
@@ -166,6 +178,98 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
166
178
  })
167
179
  }
168
180
 
181
+ function runSendBatch (args, clusterId) {
182
+ const arg0 = args[0]
183
+ const inputTopicMessages = Array.isArray(arg0?.topicMessages) ? arg0.topicMessages : []
184
+ if (inputTopicMessages.length === 0) {
185
+ return originalSendBatch.apply(this, args)
186
+ }
187
+
188
+ // One ctx per topicMessages entry — kafkajs implements `send` as a single-entry
189
+ // `sendBatch` (`producer/messageProducer.js`), so one span per entry is the same
190
+ // unit `send` already produces. Cloning only happens for valid arrays so kafkajs
191
+ // still sees and rejects a caller's malformed `messages` field.
192
+ const outputEntries = new Array(inputTopicMessages.length)
193
+ const ctxList = []
194
+ let cloned = false
195
+ for (let i = 0; i < inputTopicMessages.length; i++) {
196
+ const entry = inputTopicMessages[i]
197
+ const topic = entry?.topic
198
+ const rawMessages = entry?.messages
199
+ let entryMessages = rawMessages
200
+ if (Array.isArray(rawMessages) && rawMessages.length > 0) {
201
+ entryMessages = cloneMessages(rawMessages, !disableHeaderInjection)
202
+ outputEntries[i] = { ...entry, messages: entryMessages }
203
+ cloned = true
204
+ } else {
205
+ outputEntries[i] = entry
206
+ }
207
+ ctxList.push({
208
+ bootstrapServers,
209
+ clusterId,
210
+ disableHeaderInjection,
211
+ messages: Array.isArray(entryMessages) ? entryMessages : [],
212
+ topic,
213
+ })
214
+ }
215
+ if (cloned) {
216
+ args[0] = { ...arg0, topicMessages: outputEntries }
217
+ }
218
+
219
+ for (const ctx of ctxList) {
220
+ producerStartCh.runStores(ctx, noop)
221
+ }
222
+
223
+ let result
224
+ try {
225
+ result = originalSendBatch.apply(this, args)
226
+ } catch (error) {
227
+ failProduceBatch(ctxList, error)
228
+ throw error
229
+ }
230
+
231
+ result.then(
232
+ (res) => {
233
+ for (const ctx of ctxList) {
234
+ ctx.result = res
235
+ producerFinishCh.publish(ctx)
236
+ }
237
+ // kafkajs returns a single aggregated response covering every topic;
238
+ // commit fires once so the plugin's `setOffset` loop runs once per
239
+ // entry of the response, not once per span.
240
+ producerCommitCh.publish(ctxList[0])
241
+ },
242
+ (error) => failProduceBatch(ctxList, error)
243
+ )
244
+
245
+ return result
246
+ }
247
+
248
+ /**
249
+ * Tag every open ctx with the shared error, then publish error + finish so the
250
+ * plugin closes each span. The mixed-version safety net (broker advertised
251
+ * Produce v3+ but the leader rejected the headers) fires at most once per
252
+ * failed batch and short-circuits subsequent sends to the disabled path.
253
+ *
254
+ * @param {Array<object>} ctxList
255
+ * @param {Error} error
256
+ */
257
+ function failProduceBatch (ctxList, error) {
258
+ if (error?.name === 'KafkaJSProtocolError' && error.type === 'UNKNOWN') {
259
+ disableHeaderInjection = true
260
+ refreshHeaderSupport = noop
261
+ log.error(
262
+ // eslint-disable-next-line @stylistic/max-len
263
+ 'Kafka Broker responded with UNKNOWN_SERVER_ERROR (-1). Please look at broker logs for more information. Tracer message header injection for Kafka is disabled.'
264
+ )
265
+ }
266
+ for (const ctx of ctxList) {
267
+ ctx.error = error
268
+ producerErrorCh.publish(ctx)
269
+ producerFinishCh.publish(ctx)
270
+ }
271
+ }
272
+
169
273
  return producer
170
274
  })
171
275
 
@@ -6,16 +6,21 @@ const { DD_MAJOR } = require('../../../../version')
6
6
  const { addHook, channel } = require('../helpers/instrument')
7
7
  const shimmer = require('../../../datadog-shimmer')
8
8
  const { isMarkedAsUnskippable } = require('../../../datadog-plugin-jest/src/util')
9
+ const { writeCoverageBackfillToCache } = require('../../../dd-trace/src/ci-visibility/test-optimization-cache')
9
10
  const log = require('../../../dd-trace/src/log')
10
11
  const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper')
11
12
  const {
12
13
  getTestSuitePath,
13
14
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
14
15
  fromCoverageMapToCoverage,
15
- getCoveredFilenamesFromCoverage,
16
+ getCoveredFilesFromCoverage,
17
+ getExecutableFilesFromCoverage,
18
+ applySkippedCoverageToCoverage,
16
19
  mergeCoverage,
17
20
  resetCoverage,
18
21
  getIsFaultyEarlyFlakeDetection,
22
+ getRelativeCoverageFiles,
23
+ getTestCoverageLinesPercentage,
19
24
  collectTestOptimizationSummariesFromTraces,
20
25
  logTestOptimizationSummary,
21
26
  getTestOptimizationRequestResults,
@@ -53,6 +58,8 @@ const unskippableSuites = []
53
58
  let suitesToSkip = []
54
59
  let isSuitesSkipped = false
55
60
  let skippedSuites = []
61
+ let skippableSuitesCoverage = {}
62
+ let skippedSuitesCoverage = {}
56
63
  let itrCorrelationId = ''
57
64
  let isForcedToRun = false
58
65
  const config = {}
@@ -133,10 +140,33 @@ function haveRootTestsFinished (rootTests) {
133
140
  return true
134
141
  }
135
142
 
143
+ function getSuitePath (suite) {
144
+ return getTestSuitePath(suite.file, process.cwd())
145
+ }
146
+
147
+ function getSuitesToSkip (originalSuites) {
148
+ return getSuitesToSkipFromPaths(originalSuites.map(getSuitePath))
149
+ }
150
+
151
+ function getSuitesToSkipFromPaths (localSuites) {
152
+ const localSuitesSet = new Set(localSuites)
153
+ const suitesToSkipForRun = []
154
+
155
+ for (const suite of suitesToSkip) {
156
+ if (localSuitesSet.has(suite)) {
157
+ suitesToSkipForRun.push(suite)
158
+ }
159
+ }
160
+
161
+ return suitesToSkipForRun
162
+ }
163
+
136
164
  function getFilteredSuites (originalSuites) {
165
+ const suitesToSkipForRun = getSuitesToSkip(originalSuites)
166
+
137
167
  return originalSuites.reduce((acc, suite) => {
138
- const testPath = getTestSuitePath(suite.file, process.cwd())
139
- const shouldSkip = suitesToSkip.includes(testPath)
168
+ const testPath = getSuitePath(suite)
169
+ const shouldSkip = suitesToSkipForRun.includes(testPath)
140
170
  const isUnskippable = unskippableSuites.includes(suite.file)
141
171
  if (shouldSkip && !isUnskippable) {
142
172
  acc.skippedSuites.add(testPath)
@@ -144,7 +174,50 @@ function getFilteredSuites (originalSuites) {
144
174
  acc.suitesToRun.push(suite)
145
175
  }
146
176
  return acc
147
- }, { suitesToRun: [], skippedSuites: new Set() })
177
+ }, { suitesToRun: [], skippedSuites: new Set(), suitesToSkipForRun })
178
+ }
179
+
180
+ function hasSkippableSuitesCoverage () {
181
+ return skippableSuitesCoverage &&
182
+ typeof skippableSuitesCoverage === 'object' &&
183
+ Object.keys(skippableSuitesCoverage).length > 0
184
+ }
185
+
186
+ function isTiaCoverageBackfillEnabled () {
187
+ return config.isItrEnabled && config.isCoverageReportUploadEnabled
188
+ }
189
+
190
+ function getCoverageRootDir () {
191
+ return config.repositoryRoot || process.cwd()
192
+ }
193
+
194
+ function shouldReportCodeCoverageLinesPct (hasBackfilledCoverage) {
195
+ return !isSuitesSkipped || hasBackfilledCoverage
196
+ }
197
+
198
+ function getSkippedSuitesCoverageForRun () {
199
+ return isSuitesSkipped && isTiaCoverageBackfillEnabled() && hasSkippableSuitesCoverage()
200
+ ? skippableSuitesCoverage
201
+ : {}
202
+ }
203
+
204
+ function applySkippedCoverageToMochaCoverageMap () {
205
+ if (!isTiaCoverageBackfillEnabled()) return false
206
+ return applySkippedCoverageToCoverage(originalCoverageMap, skippedSuitesCoverage, getCoverageRootDir())
207
+ }
208
+
209
+ function getMochaTestSessionCoverageFiles () {
210
+ return getRelativeCoverageFiles(getExecutableFilesFromCoverage(originalCoverageMap), getCoverageRootDir())
211
+ }
212
+
213
+ function resetSuiteSkippingRunState () {
214
+ isSuitesSkipped = false
215
+ skippedSuites = []
216
+ skippableSuitesCoverage = {}
217
+ skippedSuitesCoverage = {}
218
+ untestedCoverage = undefined
219
+ config.repositoryRoot = undefined
220
+ writeCoverageBackfillToCache({})
148
221
  }
149
222
 
150
223
  function getOnStartHandler (frameworkVersion) {
@@ -218,12 +291,24 @@ function getOnEndHandler (isParallel) {
218
291
  testFileToSuiteCtx.clear()
219
292
 
220
293
  let testCodeCoverageLinesTotal
221
- if (global.__coverage__) {
294
+ let testSessionCoverageFiles
295
+ if (global.__coverage__ || untestedCoverage) {
222
296
  try {
297
+ let hasBackfilledCoverage = false
223
298
  if (untestedCoverage) {
224
299
  originalCoverageMap.merge(fromCoverageMapToCoverage(untestedCoverage))
225
300
  }
226
- testCodeCoverageLinesTotal = originalCoverageMap.getCoverageSummary().lines.pct
301
+ hasBackfilledCoverage = applySkippedCoverageToMochaCoverageMap()
302
+ if (shouldReportCodeCoverageLinesPct(hasBackfilledCoverage)) {
303
+ testCodeCoverageLinesTotal = getTestCoverageLinesPercentage(
304
+ originalCoverageMap,
305
+ undefined,
306
+ getCoverageRootDir()
307
+ )
308
+ }
309
+ if (isTiaCoverageBackfillEnabled()) {
310
+ testSessionCoverageFiles = getMochaTestSessionCoverageFiles()
311
+ }
227
312
  } catch {
228
313
  // ignore errors
229
314
  }
@@ -235,6 +320,7 @@ function getOnEndHandler (isParallel) {
235
320
  status,
236
321
  isSuitesSkipped,
237
322
  testCodeCoverageLinesTotal,
323
+ testSessionCoverageFiles,
238
324
  numSkippedSuites: skippedSuites.length,
239
325
  hasForcedToRunSuites: isForcedToRun,
240
326
  hasUnskippableSuites: !!unskippableSuites.length,
@@ -276,23 +362,39 @@ function applyTestManagementTestsResponse ({ err, testManagementTests: receivedT
276
362
  }
277
363
  }
278
364
 
279
- function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFinishRequest) {
365
+ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFinishRequest, localSuites) {
280
366
  const ctx = {
281
367
  isParallel,
282
368
  frameworkVersion,
283
369
  }
284
370
  let skippableSuitesResponse
285
-
286
- const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
371
+ resetSuiteSkippingRunState()
372
+
373
+ const onReceivedSkippableSuites = ({
374
+ err,
375
+ skippableSuites,
376
+ itrCorrelationId: responseItrCorrelationId,
377
+ skippableSuitesCoverage: responseSkippableSuitesCoverage,
378
+ }) => {
287
379
  if (err) {
288
380
  suitesToSkip = []
381
+ skippableSuitesCoverage = {}
289
382
  } else {
290
383
  suitesToSkip = skippableSuites
291
384
  itrCorrelationId = responseItrCorrelationId
385
+ skippableSuitesCoverage = responseSkippableSuitesCoverage || {}
292
386
  }
387
+ if (localSuites) {
388
+ suitesToSkip = getSuitesToSkipFromPaths(localSuites)
389
+ mochaGlobalRunCh.runStores(ctx, () => {
390
+ onFinishRequest()
391
+ })
392
+ return
393
+ }
394
+
293
395
  // We remove the suites that we skip through ITR
294
396
  const filteredSuites = getFilteredSuites(runner.suite.suites)
295
- const { suitesToRun } = filteredSuites
397
+ const { suitesToRun, suitesToSkipForRun } = filteredSuites
296
398
 
297
399
  isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
298
400
 
@@ -301,6 +403,9 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
301
403
  runner.suite.suites = suitesToRun
302
404
 
303
405
  skippedSuites = [...filteredSuites.skippedSuites]
406
+ suitesToSkip = suitesToSkipForRun
407
+ skippedSuitesCoverage = getSkippedSuitesCoverageForRun()
408
+ writeCoverageBackfillToCache(skippedSuitesCoverage, getCoverageRootDir())
304
409
 
305
410
  mochaGlobalRunCh.runStores(ctx, () => {
306
411
  onFinishRequest()
@@ -346,12 +451,13 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
346
451
  }
347
452
  }
348
453
 
349
- const onReceivedConfiguration = ({ err, libraryConfig }) => {
454
+ const onReceivedConfiguration = ({ err, libraryConfig, repositoryRoot }) => {
350
455
  if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) {
351
456
  return mochaGlobalRunCh.runStores(ctx, () => {
352
457
  onFinishRequest()
353
458
  })
354
459
  }
460
+ config.repositoryRoot = repositoryRoot
355
461
  config.isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
356
462
  config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
357
463
  config.earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
@@ -360,7 +466,10 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
360
466
  config.isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
361
467
  config.testManagementAttemptToFixRetries = libraryConfig.testManagementAttemptToFixRetries
362
468
  config.isImpactedTestsEnabled = libraryConfig.isImpactedTestsEnabled
363
- config.isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
469
+ config.isItrEnabled = libraryConfig.isItrEnabled
470
+ config.isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
471
+ config.isCoverageReportUploadEnabled = libraryConfig.isCoverageReportUploadEnabled
472
+ config.isSuitesSkippingEnabled = config.isItrEnabled && libraryConfig.isSuitesSkippingEnabled
364
473
  config.isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
365
474
  config.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
366
475
 
@@ -588,7 +697,7 @@ addHook({
588
697
  const status = getRootSuiteStatus(rootTests)
589
698
 
590
699
  if (global.__coverage__) {
591
- const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
700
+ const coverageFiles = getCoveredFilesFromCoverage(global.__coverage__)
592
701
  testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
593
702
  mergeCoverage(global.__coverage__, originalCoverageMap)
594
703
  resetCoverage(global.__coverage__)
@@ -786,7 +895,7 @@ addHook({
786
895
  }
787
896
 
788
897
  if (global.__coverage__) {
789
- const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
898
+ const coverageFiles = getCoveredFilesFromCoverage(global.__coverage__)
790
899
 
791
900
  testSuiteCodeCoverageCh.publish({
792
901
  coverageFiles,
@@ -908,11 +1017,11 @@ addHook({
908
1017
  }
909
1018
  }
910
1019
 
1020
+ const localSuites = files.map(file => getTestSuitePath(file, process.cwd()))
911
1021
  getExecutionConfiguration(this, true, frameworkVersion, () => {
912
1022
  if (config.isKnownTestsEnabled) {
913
- const testSuites = files.map(file => getTestSuitePath(file, process.cwd()))
914
1023
  const isFaulty = getIsFaultyEarlyFlakeDetection(
915
- testSuites,
1024
+ localSuites,
916
1025
  config.knownTests?.mocha || {},
917
1026
  config.earlyFlakeDetectionFaultyThreshold
918
1027
  )
@@ -937,11 +1046,13 @@ addHook({
937
1046
  }
938
1047
  isSuitesSkipped = skippedFiles.length > 0
939
1048
  skippedSuites = skippedFiles
1049
+ skippedSuitesCoverage = getSkippedSuitesCoverageForRun()
1050
+ writeCoverageBackfillToCache(skippedSuitesCoverage, getCoverageRootDir())
940
1051
  run.apply(this, [cb, { files: filteredFiles }])
941
1052
  } else {
942
1053
  run.apply(this, arguments)
943
1054
  }
944
- })
1055
+ }, localSuites)
945
1056
 
946
1057
  return this
947
1058
  })
@@ -0,0 +1,182 @@
1
+ 'use strict'
2
+
3
+ // Shimmer required: NATS consumer paths need argument modification — the user's
4
+ // `opts.callback` is wrapped before being handed to SubscriptionImpl, and the
5
+ // returned subscription's async iterator is wrapped so iterator-style consumers
6
+ // get receive events. Orchestrion can only wrap method calls, not arguments
7
+ // or returned iterables.
8
+
9
+ const shimmer = require('../../datadog-shimmer')
10
+ const { addHook, channel } = require('./helpers/instrument')
11
+
12
+ const publishStartCh = channel('apm:nats:publish:start')
13
+ const publishFinishCh = channel('apm:nats:publish:finish')
14
+ const publishErrorCh = channel('apm:nats:publish:error')
15
+
16
+ const consumeStartCh = channel('apm:nats:consume:start')
17
+ const consumeFinishCh = channel('apm:nats:consume:finish')
18
+ const consumeErrorCh = channel('apm:nats:consume:error')
19
+
20
+ // Tracks connections that are currently inside a `request`/`requestMany` call
21
+ // so the nested `this.publish(...)` they issue short-circuits without creating
22
+ // a second producer span (the outer request wrap already created one and
23
+ // injected headers — the inner publish would double-count it). A WeakSet avoids
24
+ // changing the shape of the user's connection object.
25
+ const requestsInFlight = new WeakSet()
26
+
27
+ // Captured from the `lib/headers.js` hook below. The nats-core package always
28
+ // imports `./headers` from `lib/nats.js`, so by the time we wrap `publish` the
29
+ // reference is set. No defensive checks needed at call sites.
30
+ let createHeaders
31
+
32
+ addHook({ name: '@nats-io/nats-core', versions: ['>=3.0.0'], file: 'lib/headers.js' }, exports => {
33
+ createHeaders = exports.headers
34
+ return exports
35
+ })
36
+
37
+ // transport-node re-exports nats-core internals — the passthrough hook ensures
38
+ // the package name is registered so `withVersions('nats', '@nats-io/transport-node', ...)`
39
+ // can resolve it in plugin tests.
40
+ addHook({ name: '@nats-io/transport-node', versions: ['>=3.0.0'] }, exports => exports)
41
+
42
+ function wrapSyncProducer (original, type) {
43
+ return function (subject, data, options) {
44
+ if (!publishStartCh.hasSubscribers) {
45
+ return original.apply(this, arguments)
46
+ }
47
+ const opts = { ...options }
48
+ const ctx = { type, subject, data, options: opts, connection: this, createHeaders }
49
+ return publishStartCh.runStores(ctx, () => {
50
+ try {
51
+ return original.call(this, subject, data, opts)
52
+ } catch (err) {
53
+ ctx.error = err
54
+ publishErrorCh.publish(ctx)
55
+ throw err
56
+ } finally {
57
+ publishFinishCh.publish(ctx)
58
+ }
59
+ })
60
+ }
61
+ }
62
+
63
+ // publish is also wrapped by `wrapSyncProducer`, but request/requestMany call
64
+ // `this.publish(...)` internally. Set a marker on the connection so the inner
65
+ // publish wrap short-circuits — see `wrapPublish`.
66
+ function wrapAsyncProducer (original, type) {
67
+ return function (subject, data, options) {
68
+ if (!publishStartCh.hasSubscribers) {
69
+ return original.apply(this, arguments)
70
+ }
71
+ const opts = { ...options }
72
+ const ctx = { type, subject, data, options: opts, connection: this, createHeaders }
73
+ return publishStartCh.runStores(ctx, () => {
74
+ requestsInFlight.add(this)
75
+ let promise
76
+ try {
77
+ // `request`/`requestMany` never throw synchronously — they wrap their own
78
+ // input validation in a try/catch that returns `Promise.reject`.
79
+ promise = original.call(this, subject, data, opts)
80
+ } finally {
81
+ // The nested `this.publish(...)` runs during the synchronous body of
82
+ // request/requestMany, so clearing the marker as soon as the call
83
+ // returns is sufficient — the promise resolution happens later.
84
+ requestsInFlight.delete(this)
85
+ }
86
+ return Promise.resolve(promise).then(
87
+ result => {
88
+ ctx.result = result
89
+ publishFinishCh.publish(ctx)
90
+ return result
91
+ },
92
+ err => {
93
+ ctx.error = err
94
+ publishErrorCh.publish(ctx)
95
+ publishFinishCh.publish(ctx)
96
+ throw err
97
+ }
98
+ )
99
+ })
100
+ }
101
+ }
102
+
103
+ function wrapPublish (original) {
104
+ const wrapped = wrapSyncProducer(original, 'publish')
105
+ return function (subject, data, options) {
106
+ // Called from inside request/requestMany — the outer wrap already produced
107
+ // a span and injected headers; running the inner wrap would double-count.
108
+ if (requestsInFlight.has(this)) {
109
+ return original.apply(this, arguments)
110
+ }
111
+ return wrapped.apply(this, arguments)
112
+ }
113
+ }
114
+
115
+ function wrapSubscribeCallback (userCallback, subject, connection) {
116
+ return function (err, message) {
117
+ if (!message || err) {
118
+ return userCallback.call(this, err, message)
119
+ }
120
+ const ctx = { subject, message, connection }
121
+ return consumeStartCh.runStores(ctx, () => {
122
+ try {
123
+ return userCallback.call(this, err, message)
124
+ } catch (e) {
125
+ ctx.error = e
126
+ consumeErrorCh.publish(ctx)
127
+ throw e
128
+ } finally {
129
+ consumeFinishCh.publish(ctx)
130
+ }
131
+ })
132
+ }
133
+ }
134
+
135
+ // Iterator-style consumers don't expose a delivery callback we can wrap, so
136
+ // the consume span represents the moment of receipt only — it starts and
137
+ // finishes before the value is yielded to user code, and the user's loop
138
+ // body is not parented under the span.
139
+ function wrapAsyncIteratorFactory (asyncIterator, subject, connection) {
140
+ return function () {
141
+ const iterator = asyncIterator.apply(this, arguments)
142
+ iterator.next = shimmer.wrapCallback(iterator.next, next => function () {
143
+ return next.apply(this, arguments).then(result => {
144
+ if (result && !result.done && result.value) {
145
+ const ctx = { subject, message: result.value, connection }
146
+ consumeStartCh.runStores(ctx, () => {
147
+ consumeFinishCh.publish(ctx)
148
+ })
149
+ }
150
+ return result
151
+ })
152
+ })
153
+ return iterator
154
+ }
155
+ }
156
+
157
+ addHook({ name: '@nats-io/nats-core', versions: ['>=3.0.0'], file: 'lib/nats.js' }, exports => {
158
+ const proto = exports.NatsConnectionImpl.prototype
159
+
160
+ shimmer.wrap(proto, 'publish', wrapPublish)
161
+ shimmer.wrap(proto, 'request', request => wrapAsyncProducer(request, 'request'))
162
+ shimmer.wrap(proto, 'requestMany', requestMany => wrapAsyncProducer(requestMany, 'requestMany'))
163
+
164
+ shimmer.wrap(proto, 'subscribe', subscribe => function (subject, opts) {
165
+ if (!consumeStartCh.hasSubscribers) {
166
+ return subscribe.apply(this, arguments)
167
+ }
168
+
169
+ const userOpts = opts ?? {}
170
+ if (typeof userOpts.callback === 'function') {
171
+ arguments[1] = { ...userOpts, callback: wrapSubscribeCallback(userOpts.callback, subject, this) }
172
+ return subscribe.apply(this, arguments)
173
+ }
174
+
175
+ const sub = subscribe.apply(this, arguments)
176
+ shimmer.wrap(sub, Symbol.asyncIterator, asyncIterator =>
177
+ wrapAsyncIteratorFactory(asyncIterator, subject, this))
178
+ return sub
179
+ })
180
+
181
+ return exports
182
+ })
@@ -2,7 +2,12 @@
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
5
- const { setupSettingsCachePath } = require('../../dd-trace/src/ci-visibility/test-optimization-cache')
5
+ const {
6
+ readCoverageBackfillFromCache,
7
+ readCoverageBackfillRootDirFromCache,
8
+ setupSettingsCachePath,
9
+ } = require('../../dd-trace/src/ci-visibility/test-optimization-cache')
10
+ const { applySkippedCoverageToCoverage } = require('../../dd-trace/src/plugins/util/test')
6
11
  const { addHook, channel } = require('./helpers/instrument')
7
12
 
8
13
  const codeCoverageWrapCh = channel('ci:nyc:wrap')
@@ -16,6 +21,38 @@ addHook({
16
21
  // when dd-trace fetches library configuration
17
22
  setupSettingsCachePath()
18
23
 
24
+ if (nycPackage.prototype.getCoverageMapFromAllCoverageFiles) {
25
+ // Some test frameworks receive skipped-suite coverage in the test process, but nyc merges reports later in the nyc
26
+ // process. Reuse the settings cache path as the process handoff so nyc can backfill skipped files before reporting.
27
+ shimmer.wrap(
28
+ nycPackage.prototype,
29
+ 'getCoverageMapFromAllCoverageFiles',
30
+ getCoverageMapFromAllCoverageFiles => function (...args) {
31
+ const coverageMap = getCoverageMapFromAllCoverageFiles.apply(this, args)
32
+ const applyCoverageBackfill = (resolvedCoverageMap) => {
33
+ try {
34
+ if (!resolvedCoverageMap) {
35
+ return resolvedCoverageMap
36
+ }
37
+ applySkippedCoverageToCoverage(
38
+ resolvedCoverageMap,
39
+ readCoverageBackfillFromCache(),
40
+ readCoverageBackfillRootDirFromCache() || this.cwd
41
+ )
42
+ } catch {
43
+ // Do not break nyc's report generation if the cached backfill is stale or malformed.
44
+ }
45
+ return resolvedCoverageMap
46
+ }
47
+
48
+ if (coverageMap && typeof coverageMap.then === 'function') {
49
+ return coverageMap.then(applyCoverageBackfill)
50
+ }
51
+ return applyCoverageBackfill(coverageMap)
52
+ }
53
+ )
54
+ }
55
+
19
56
  // `wrap` is an async function
20
57
  shimmer.wrap(nycPackage.prototype, 'wrap', wrap => function (...args) {
21
58
  // Only relevant if the config `all` is set to true (for untested code coverage)