dd-trace 5.21.0 → 5.23.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 (153) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +20 -8
  3. package/package.json +11 -5
  4. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  5. package/packages/datadog-instrumentations/src/apollo-server.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -4
  7. package/packages/datadog-instrumentations/src/body-parser.js +4 -4
  8. package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
  9. package/packages/datadog-instrumentations/src/child_process.js +2 -2
  10. package/packages/datadog-instrumentations/src/connect.js +4 -4
  11. package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
  12. package/packages/datadog-instrumentations/src/couchbase.js +12 -12
  13. package/packages/datadog-instrumentations/src/cucumber.js +294 -56
  14. package/packages/datadog-instrumentations/src/dns.js +10 -10
  15. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  16. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +3 -3
  17. package/packages/datadog-instrumentations/src/express.js +4 -4
  18. package/packages/datadog-instrumentations/src/fastify.js +6 -6
  19. package/packages/datadog-instrumentations/src/fetch.js +1 -1
  20. package/packages/datadog-instrumentations/src/find-my-way.js +2 -2
  21. package/packages/datadog-instrumentations/src/fs.js +2 -2
  22. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +2 -2
  23. package/packages/datadog-instrumentations/src/grpc/client.js +4 -6
  24. package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
  25. package/packages/datadog-instrumentations/src/hapi.js +10 -13
  26. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  27. package/packages/datadog-instrumentations/src/http/client.js +3 -3
  28. package/packages/datadog-instrumentations/src/jest.js +8 -5
  29. package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
  30. package/packages/datadog-instrumentations/src/knex.js +2 -2
  31. package/packages/datadog-instrumentations/src/koa.js +5 -5
  32. package/packages/datadog-instrumentations/src/ldapjs.js +1 -1
  33. package/packages/datadog-instrumentations/src/mariadb.js +8 -8
  34. package/packages/datadog-instrumentations/src/memcached.js +2 -2
  35. package/packages/datadog-instrumentations/src/microgateway-core.js +7 -5
  36. package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
  37. package/packages/datadog-instrumentations/src/mocha/main.js +139 -53
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +37 -18
  39. package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
  40. package/packages/datadog-instrumentations/src/mocha.js +4 -0
  41. package/packages/datadog-instrumentations/src/moleculer/server.js +2 -2
  42. package/packages/datadog-instrumentations/src/mongodb-core.js +7 -7
  43. package/packages/datadog-instrumentations/src/mongoose.js +5 -6
  44. package/packages/datadog-instrumentations/src/mysql.js +3 -3
  45. package/packages/datadog-instrumentations/src/mysql2.js +6 -6
  46. package/packages/datadog-instrumentations/src/net.js +2 -2
  47. package/packages/datadog-instrumentations/src/next.js +5 -5
  48. package/packages/datadog-instrumentations/src/openai.js +62 -71
  49. package/packages/datadog-instrumentations/src/oracledb.js +8 -8
  50. package/packages/datadog-instrumentations/src/passport-http.js +1 -1
  51. package/packages/datadog-instrumentations/src/passport-local.js +1 -1
  52. package/packages/datadog-instrumentations/src/passport-utils.js +1 -1
  53. package/packages/datadog-instrumentations/src/pg.js +60 -5
  54. package/packages/datadog-instrumentations/src/pino.js +4 -4
  55. package/packages/datadog-instrumentations/src/playwright.js +6 -4
  56. package/packages/datadog-instrumentations/src/redis.js +2 -2
  57. package/packages/datadog-instrumentations/src/restify.js +4 -4
  58. package/packages/datadog-instrumentations/src/rhea.js +4 -4
  59. package/packages/datadog-instrumentations/src/router.js +5 -5
  60. package/packages/datadog-instrumentations/src/sharedb.js +2 -2
  61. package/packages/datadog-instrumentations/src/vitest.js +188 -12
  62. package/packages/datadog-instrumentations/src/winston.js +2 -3
  63. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  64. package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
  65. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  66. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
  67. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  68. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  69. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +39 -10
  70. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  71. package/packages/datadog-plugin-hapi/src/index.js +2 -2
  72. package/packages/datadog-plugin-http/src/client.js +1 -42
  73. package/packages/datadog-plugin-http2/src/client.js +1 -26
  74. package/packages/datadog-plugin-jest/src/index.js +18 -1
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
  76. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
  77. package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +18 -0
  79. package/packages/datadog-plugin-openai/src/index.js +85 -65
  80. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  81. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  82. package/packages/datadog-plugin-vitest/src/index.js +68 -3
  83. package/packages/datadog-shimmer/src/shimmer.js +144 -10
  84. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  85. package/packages/dd-trace/src/appsec/blocking.js +23 -17
  86. package/packages/dd-trace/src/appsec/channels.js +4 -2
  87. package/packages/dd-trace/src/appsec/graphql.js +3 -1
  88. package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -1
  89. package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
  90. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
  91. package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
  92. package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
  93. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  94. package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
  95. package/packages/dd-trace/src/appsec/remote_config/manager.js +93 -52
  96. package/packages/dd-trace/src/appsec/rule_manager.js +8 -0
  97. package/packages/dd-trace/src/appsec/telemetry.js +3 -3
  98. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
  99. package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -1
  102. package/packages/dd-trace/src/config.js +100 -40
  103. package/packages/dd-trace/src/constants.js +11 -1
  104. package/packages/dd-trace/src/data_streams_context.js +3 -0
  105. package/packages/dd-trace/src/datastreams/fnv.js +23 -0
  106. package/packages/dd-trace/src/datastreams/pathway.js +12 -5
  107. package/packages/dd-trace/src/datastreams/processor.js +35 -0
  108. package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
  109. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
  110. package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
  111. package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
  112. package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
  113. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
  114. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
  116. package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
  117. package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
  118. package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
  119. package/packages/dd-trace/src/debugger/index.js +92 -0
  120. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
  121. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  122. package/packages/dd-trace/src/lambda/handler.js +1 -0
  123. package/packages/dd-trace/src/lambda/index.js +12 -1
  124. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -6
  125. package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
  126. package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
  127. package/packages/dd-trace/src/payload-tagging/index.js +93 -0
  128. package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
  129. package/packages/dd-trace/src/plugin_manager.js +11 -10
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
  131. package/packages/dd-trace/src/plugins/util/env.js +5 -2
  132. package/packages/dd-trace/src/plugins/util/test.js +24 -4
  133. package/packages/dd-trace/src/profiler.js +15 -5
  134. package/packages/dd-trace/src/profiling/config.js +7 -4
  135. package/packages/dd-trace/src/profiling/exporter_cli.js +13 -1
  136. package/packages/dd-trace/src/profiling/exporters/agent.js +8 -2
  137. package/packages/dd-trace/src/profiling/profiler.js +0 -9
  138. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
  139. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
  140. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
  141. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
  142. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
  143. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
  144. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
  145. package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
  146. package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  148. package/packages/dd-trace/src/profiling/ssi-heuristics.js +59 -60
  149. package/packages/dd-trace/src/proxy.js +31 -24
  150. package/packages/dd-trace/src/span_stats.js +4 -2
  151. package/packages/dd-trace/src/telemetry/index.js +23 -6
  152. package/packages/dd-trace/src/telemetry/logs/index.js +20 -0
  153. package/packages/dd-trace/src/appsec/rasp.js +0 -176
@@ -11,12 +11,12 @@ const {
11
11
  fromCoverageMapToCoverage,
12
12
  getCoveredFilenamesFromCoverage,
13
13
  mergeCoverage,
14
- resetCoverage
14
+ resetCoverage,
15
+ getIsFaultyEarlyFlakeDetection
15
16
  } = require('../../../dd-trace/src/plugins/util/test')
16
17
 
17
18
  const {
18
19
  isNewTest,
19
- retryTest,
20
20
  getSuitesByTestFile,
21
21
  runnableWrapper,
22
22
  getOnTestHandler,
@@ -25,22 +25,21 @@ const {
25
25
  getOnHookEndHandler,
26
26
  getOnFailHandler,
27
27
  getOnPendingHandler,
28
- testFileToSuiteAr
28
+ testFileToSuiteAr,
29
+ newTests,
30
+ getTestFullName,
31
+ getRunTestsWrapper
29
32
  } = require('./utils')
33
+
30
34
  require('./common')
31
35
 
32
36
  const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
33
37
  const patched = new WeakSet()
34
- const newTests = {}
35
- let suitesToSkip = []
38
+
36
39
  const unskippableSuites = []
40
+ let suitesToSkip = []
37
41
  let isSuitesSkipped = false
38
42
  let skippedSuites = []
39
- let isEarlyFlakeDetectionEnabled = false
40
- let isSuitesSkippingEnabled = false
41
- let isFlakyTestRetriesEnabled = false
42
- let earlyFlakeDetectionNumRetries = 0
43
- let knownTests = []
44
43
  let itrCorrelationId = ''
45
44
  let isForcedToRun = false
46
45
  const config = {}
@@ -69,6 +68,17 @@ const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
69
68
 
70
69
  const getCodeCoverageCh = channel('ci:nyc:get-coverage')
71
70
 
71
+ // Tests from workers do not come with `isFailed` method
72
+ function isTestFailed (test) {
73
+ if (test.isFailed) {
74
+ return test.isFailed()
75
+ }
76
+ if (test.isPending) {
77
+ return !test.isPending() && test.state !== 'failed'
78
+ }
79
+ return false
80
+ }
81
+
72
82
  function getFilteredSuites (originalSuites) {
73
83
  return originalSuites.reduce((acc, suite) => {
74
84
  const testPath = getTestSuitePath(suite.file, process.cwd())
@@ -107,7 +117,7 @@ function getOnEndHandler (isParallel) {
107
117
  status = 'fail'
108
118
  }
109
119
 
110
- if (!isParallel && isEarlyFlakeDetectionEnabled) {
120
+ if (config.isEarlyFlakeDetectionEnabled) {
111
121
  /**
112
122
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
113
123
  * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
@@ -116,7 +126,7 @@ function getOnEndHandler (isParallel) {
116
126
  * on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
117
127
  */
118
128
  for (const tests of Object.values(newTests)) {
119
- const failingNewTests = tests.filter(test => test.isFailed())
129
+ const failingNewTests = tests.filter(test => isTestFailed(test))
120
130
  const areAllNewTestsFailing = failingNewTests.length === tests.length
121
131
  if (failingNewTests.length && !areAllNewTestsFailing) {
122
132
  this.stats.failures -= failingNewTests.length
@@ -153,13 +163,14 @@ function getOnEndHandler (isParallel) {
153
163
  hasForcedToRunSuites: isForcedToRun,
154
164
  hasUnskippableSuites: !!unskippableSuites.length,
155
165
  error,
156
- isEarlyFlakeDetectionEnabled,
166
+ isEarlyFlakeDetectionEnabled: config.isEarlyFlakeDetectionEnabled,
167
+ isEarlyFlakeDetectionFaulty: config.isEarlyFlakeDetectionFaulty,
157
168
  isParallel
158
169
  })
159
170
  })
160
171
  }
161
172
 
162
- function getExecutionConfiguration (runner, onFinishRequest) {
173
+ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
163
174
  const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
164
175
 
165
176
  const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
@@ -186,15 +197,15 @@ function getExecutionConfiguration (runner, onFinishRequest) {
186
197
  onFinishRequest()
187
198
  }
188
199
 
189
- const onReceivedKnownTests = ({ err, knownTests: receivedKnownTests }) => {
200
+ const onReceivedKnownTests = ({ err, knownTests }) => {
190
201
  if (err) {
191
- knownTests = []
192
- isEarlyFlakeDetectionEnabled = false
202
+ config.knownTests = []
203
+ config.isEarlyFlakeDetectionEnabled = false
193
204
  } else {
194
- knownTests = receivedKnownTests
205
+ config.knownTests = knownTests
195
206
  }
196
207
 
197
- if (isSuitesSkippingEnabled) {
208
+ if (config.isSuitesSkippingEnabled) {
198
209
  skippableSuitesCh.publish({
199
210
  onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
200
211
  })
@@ -208,21 +219,19 @@ function getExecutionConfiguration (runner, onFinishRequest) {
208
219
  return onFinishRequest()
209
220
  }
210
221
 
211
- isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
212
- isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
213
- earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
214
- isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
215
-
216
- config.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
217
- config.isSuitesSkippingEnabled = isSuitesSkippingEnabled
218
- config.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
219
- config.isFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
222
+ config.isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
223
+ config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
224
+ config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
225
+ // ITR and auto test retries are not supported in parallel mode yet
226
+ config.isSuitesSkippingEnabled = !isParallel && libraryConfig.isSuitesSkippingEnabled
227
+ config.isFlakyTestRetriesEnabled = !isParallel && libraryConfig.isFlakyTestRetriesEnabled
228
+ config.flakyTestRetriesCount = !isParallel && libraryConfig.flakyTestRetriesCount
220
229
 
221
- if (isEarlyFlakeDetectionEnabled) {
230
+ if (config.isEarlyFlakeDetectionEnabled) {
222
231
  knownTestsCh.publish({
223
232
  onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
224
233
  })
225
- } else if (isSuitesSkippingEnabled) {
234
+ } else if (config.isSuitesSkippingEnabled) {
226
235
  skippableSuitesCh.publish({
227
236
  onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
228
237
  })
@@ -250,8 +259,8 @@ addHook({
250
259
  return run.apply(this, arguments)
251
260
  }
252
261
 
253
- // `options.delay` does not work in parallel mode, so ITR and EFD can't work.
254
- // TODO: use `lib/cli/run-helpers.js#runMocha` to get the data in parallel mode.
262
+ // `options.delay` does not work in parallel mode, so we can't delay the execution this way
263
+ // This needs to be both here and in `runMocha` hook. Read the comment in `runMocha` hook for more info.
255
264
  this.options.delay = true
256
265
 
257
266
  const runner = run.apply(this, arguments)
@@ -263,7 +272,19 @@ addHook({
263
272
  }
264
273
  })
265
274
 
266
- getExecutionConfiguration(runner, () => {
275
+ getExecutionConfiguration(runner, false, () => {
276
+ if (config.isEarlyFlakeDetectionEnabled) {
277
+ const testSuites = this.files.map(file => getTestSuitePath(file, process.cwd()))
278
+ const isFaulty = getIsFaultyEarlyFlakeDetection(
279
+ testSuites,
280
+ config.knownTests?.mocha || {},
281
+ config.earlyFlakeDetectionFaultyThreshold
282
+ )
283
+ if (isFaulty) {
284
+ config.isEarlyFlakeDetectionEnabled = false
285
+ config.isEarlyFlakeDetectionFaulty = true
286
+ }
287
+ }
267
288
  if (getCodeCoverageCh.hasSubscribers) {
268
289
  getCodeCoverageCh.publish({
269
290
  onDone: (receivedCodeCoverage) => {
@@ -281,9 +302,6 @@ addHook({
281
302
  return Mocha
282
303
  })
283
304
 
284
- // Only used to set `mocha.options.delay` to true in serial mode. When the mocha CLI is used,
285
- // setting options.delay in Mocha#run is not enough to delay the execution.
286
- // TODO: modify this hook to grab the data in parallel mode, so that ITR and EFD can work.
287
305
  addHook({
288
306
  name: 'mocha',
289
307
  versions: ['>=5.2.0'],
@@ -293,15 +311,20 @@ addHook({
293
311
  if (!testStartCh.hasSubscribers) {
294
312
  return runMocha.apply(this, arguments)
295
313
  }
296
-
297
314
  const mocha = arguments[0]
315
+
298
316
  /**
299
317
  * This attaches `run` to the global context, which we'll call after
300
- * our configuration and skippable suites requests
318
+ * our configuration and skippable suites requests.
319
+ * You need this both here and in Mocha#run hook: the programmatic API
320
+ * does not call `runMocha`, so it needs to be in Mocha#run. When using
321
+ * the CLI, modifying `options.delay` in Mocha#run is not enough (it's too late),
322
+ * so it also needs to be here.
301
323
  */
302
324
  if (!mocha.options.parallel) {
303
325
  mocha.options.delay = true
304
326
  }
327
+
305
328
  return runMocha.apply(this, arguments)
306
329
  })
307
330
  return run
@@ -318,18 +341,7 @@ addHook({
318
341
 
319
342
  patched.add(Runner)
320
343
 
321
- shimmer.wrap(Runner.prototype, 'runTests', runTests => function (suite, fn) {
322
- if (isEarlyFlakeDetectionEnabled) {
323
- // by the time we reach `this.on('test')`, it is too late. We need to add retries here
324
- suite.tests.forEach(test => {
325
- if (!test.isPending() && isNewTest(test, knownTests)) {
326
- test._ddIsNew = true
327
- retryTest(test, earlyFlakeDetectionNumRetries)
328
- }
329
- })
330
- }
331
- return runTests.apply(this, arguments)
332
- })
344
+ shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
333
345
 
334
346
  shimmer.wrap(Runner.prototype, 'run', run => function () {
335
347
  if (!testStartCh.hasSubscribers) {
@@ -513,10 +525,10 @@ addHook({
513
525
  // Used to start and finish test session and test module
514
526
  addHook({
515
527
  name: 'mocha',
516
- versions: ['>=5.2.0'],
528
+ versions: ['>=8.0.0'],
517
529
  file: 'lib/nodejs/parallel-buffered-runner.js'
518
530
  }, (ParallelBufferedRunner, frameworkVersion) => {
519
- shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function () {
531
+ shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function (cb, { files }) {
520
532
  if (!testStartCh.hasSubscribers) {
521
533
  return run.apply(this, arguments)
522
534
  }
@@ -524,8 +536,82 @@ addHook({
524
536
  this.once('start', getOnStartHandler(true, frameworkVersion))
525
537
  this.once('end', getOnEndHandler(true))
526
538
 
527
- return run.apply(this, arguments)
539
+ getExecutionConfiguration(this, true, () => {
540
+ if (config.isEarlyFlakeDetectionEnabled) {
541
+ const testSuites = files.map(file => getTestSuitePath(file, process.cwd()))
542
+ const isFaulty = getIsFaultyEarlyFlakeDetection(
543
+ testSuites,
544
+ config.knownTests?.mocha || {},
545
+ config.earlyFlakeDetectionFaultyThreshold
546
+ )
547
+ if (isFaulty) {
548
+ config.isEarlyFlakeDetectionEnabled = false
549
+ config.isEarlyFlakeDetectionFaulty = true
550
+ }
551
+ }
552
+ run.apply(this, arguments)
553
+ })
554
+
555
+ return this
528
556
  })
529
557
 
530
558
  return ParallelBufferedRunner
531
559
  })
560
+
561
+ // Only in parallel mode: BufferedWorkerPool#run is used to run a test file in a worker
562
+ // If Early Flake Detection is enabled,
563
+ // In this hook we pass the known tests to the worker and collect the new tests that run
564
+ addHook({
565
+ name: 'mocha',
566
+ versions: ['>=8.0.0'],
567
+ file: 'lib/nodejs/buffered-worker-pool.js'
568
+ }, (BufferedWorkerPoolPackage) => {
569
+ const { BufferedWorkerPool } = BufferedWorkerPoolPackage
570
+
571
+ shimmer.wrap(BufferedWorkerPool.prototype, 'run', run => async function (testSuiteAbsolutePath, workerArgs) {
572
+ if (!testStartCh.hasSubscribers || !config.isEarlyFlakeDetectionEnabled) {
573
+ return run.apply(this, arguments)
574
+ }
575
+
576
+ const testPath = getTestSuitePath(testSuiteAbsolutePath, process.cwd())
577
+ const testSuiteKnownTests = config.knownTests.mocha?.[testPath] || []
578
+
579
+ // We pass the known tests for the test file to the worker
580
+ const testFileResult = await run.apply(
581
+ this,
582
+ [
583
+ testSuiteAbsolutePath,
584
+ {
585
+ ...workerArgs,
586
+ _ddEfdNumRetries: config.earlyFlakeDetectionNumRetries,
587
+ _ddKnownTests: {
588
+ mocha: {
589
+ [testPath]: testSuiteKnownTests
590
+ }
591
+ }
592
+ }
593
+ ]
594
+ )
595
+ const tests = testFileResult
596
+ .events
597
+ .filter(event => event.eventName === 'test end')
598
+ .map(event => event.data)
599
+
600
+ // `newTests` is filled in the worker process, so we need to use the test results to fill it here too.
601
+ for (const test of tests) {
602
+ if (isNewTest(test, config.knownTests)) {
603
+ const testFullName = getTestFullName(test)
604
+ const tests = newTests[testFullName]
605
+
606
+ if (!tests) {
607
+ newTests[testFullName] = [test]
608
+ } else {
609
+ tests.push(test)
610
+ }
611
+ }
612
+ }
613
+ return testFileResult
614
+ })
615
+
616
+ return BufferedWorkerPoolPackage
617
+ })
@@ -3,8 +3,7 @@
3
3
  const {
4
4
  getTestSuitePath,
5
5
  removeEfdStringFromTestName,
6
- addEfdStringToTestName,
7
- NUM_FAILED_TEST_RETRIES
6
+ addEfdStringToTestName
8
7
  } = require('../../../dd-trace/src/plugins/util/test')
9
8
  const { channel, AsyncResource } = require('../helpers/instrument')
10
9
  const shimmer = require('../../../datadog-shimmer')
@@ -25,6 +24,7 @@ const originalFns = new WeakMap()
25
24
  const testToStartLine = new WeakMap()
26
25
  const testFileToSuiteAr = new Map()
27
26
  const wrappedFunctions = new WeakSet()
27
+ const newTests = {}
28
28
 
29
29
  function isNewTest (test, knownTests) {
30
30
  const testSuite = getTestSuitePath(test.file, process.cwd())
@@ -114,7 +114,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
114
114
  }
115
115
  // Flaky test retries does not work in parallel mode
116
116
  if (libraryConfig?.isFlakyTestRetriesEnabled) {
117
- this.retries(NUM_FAILED_TEST_RETRIES)
117
+ this.retries(libraryConfig?.flakyTestRetriesCount)
118
118
  }
119
119
  // The reason why the wrapping logic is here is because we need to cover
120
120
  // `afterEach` and `beforeEach` hooks as well.
@@ -152,7 +152,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
152
152
  return RunnablePackage
153
153
  }
154
154
 
155
- function getOnTestHandler (isMain, newTests) {
155
+ function getOnTestHandler (isMain) {
156
156
  return function (test) {
157
157
  const testStartLine = testToStartLine.get(test)
158
158
  const asyncResource = new AsyncResource('bound-anonymous-fn')
@@ -180,22 +180,22 @@ function getOnTestHandler (isMain, newTests) {
180
180
  testStartLine
181
181
  }
182
182
 
183
- if (isMain) {
184
- testInfo.isNew = isNew
185
- testInfo.isEfdRetry = isEfdRetry
186
- // We want to store the result of the new tests
187
- if (isNew) {
188
- const testFullName = getTestFullName(test)
189
- if (newTests[testFullName]) {
190
- newTests[testFullName].push(test)
191
- } else {
192
- newTests[testFullName] = [test]
193
- }
194
- }
195
- } else {
183
+ if (!isMain) {
196
184
  testInfo.isParallel = true
197
185
  }
198
186
 
187
+ testInfo.isNew = isNew
188
+ testInfo.isEfdRetry = isEfdRetry
189
+ // We want to store the result of the new tests
190
+ if (isNew) {
191
+ const testFullName = getTestFullName(test)
192
+ if (newTests[testFullName]) {
193
+ newTests[testFullName].push(test)
194
+ } else {
195
+ newTests[testFullName] = [test]
196
+ }
197
+ }
198
+
199
199
  asyncResource.runInAsyncScope(() => {
200
200
  testStartCh.publish(testInfo)
201
201
  })
@@ -328,6 +328,23 @@ function getOnPendingHandler () {
328
328
  }
329
329
  }
330
330
  }
331
+
332
+ // Hook to add retries to tests if EFD is enabled
333
+ function getRunTestsWrapper (runTests, config) {
334
+ return function (suite, fn) {
335
+ if (config.isEarlyFlakeDetectionEnabled) {
336
+ // by the time we reach `this.on('test')`, it is too late. We need to add retries here
337
+ suite.tests.forEach(test => {
338
+ if (!test.isPending() && isNewTest(test, config.knownTests)) {
339
+ test._ddIsNew = true
340
+ retryTest(test, config.earlyFlakeDetectionNumRetries)
341
+ }
342
+ })
343
+ }
344
+ return runTests.apply(this, arguments)
345
+ }
346
+ }
347
+
331
348
  module.exports = {
332
349
  isNewTest,
333
350
  retryTest,
@@ -346,5 +363,7 @@ module.exports = {
346
363
  getOnHookEndHandler,
347
364
  getOnFailHandler,
348
365
  getOnPendingHandler,
349
- testFileToSuiteAr
366
+ testFileToSuiteAr,
367
+ getRunTestsWrapper,
368
+ newTests
350
369
  }
@@ -9,19 +9,47 @@ const {
9
9
  getOnTestEndHandler,
10
10
  getOnHookEndHandler,
11
11
  getOnFailHandler,
12
- getOnPendingHandler
12
+ getOnPendingHandler,
13
+ getRunTestsWrapper
13
14
  } = require('./utils')
14
15
  require('./common')
15
16
 
16
17
  const workerFinishCh = channel('ci:mocha:worker:finish')
17
18
 
19
+ const config = {}
20
+
21
+ addHook({
22
+ name: 'mocha',
23
+ versions: ['>=8.0.0'],
24
+ file: 'lib/mocha.js'
25
+ }, (Mocha) => {
26
+ shimmer.wrap(Mocha.prototype, 'run', run => function () {
27
+ if (this.options._ddKnownTests) {
28
+ // EFD is enabled if there's a list of known tests
29
+ config.isEarlyFlakeDetectionEnabled = true
30
+ config.knownTests = this.options._ddKnownTests
31
+ config.earlyFlakeDetectionNumRetries = this.options._ddEfdNumRetries
32
+ delete this.options._ddKnownTests
33
+ delete this.options._ddEfdNumRetries
34
+ }
35
+ return run.apply(this, arguments)
36
+ })
37
+
38
+ return Mocha
39
+ })
40
+
18
41
  // Runner is also hooked in mocha/main.js, but in here we only generate test events.
19
42
  addHook({
20
43
  name: 'mocha',
21
44
  versions: ['>=5.2.0'],
22
45
  file: 'lib/runner.js'
23
46
  }, function (Runner) {
47
+ shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
48
+
24
49
  shimmer.wrap(Runner.prototype, 'run', run => function () {
50
+ if (!workerFinishCh.hasSubscribers) {
51
+ return run.apply(this, arguments)
52
+ }
25
53
  // We flush when the worker ends with its test file (a mocha instance in a worker runs a single test file)
26
54
  this.on('end', () => {
27
55
  workerFinishCh.publish()
@@ -3,3 +3,7 @@ if (process.env.MOCHA_WORKER_ID) {
3
3
  } else {
4
4
  require('./mocha/main')
5
5
  }
6
+
7
+ // TODO add appropriate calls to wrapFunction whenever we're adding a callback
8
+ // wrapper. Right now this is less of an issue since that only has effect in
9
+ // SSI, where CI Vis isn't supported.
@@ -24,7 +24,7 @@ function createMiddleware () {
24
24
  localAction (next, action) {
25
25
  const broker = this
26
26
 
27
- return function datadogMiddleware (ctx) {
27
+ return shimmer.wrapFunction(next, next => function datadogMiddleware (ctx) {
28
28
  const actionResource = new AsyncResource('bound-anonymous-fn')
29
29
 
30
30
  return actionResource.runInAsyncScope(() => {
@@ -47,7 +47,7 @@ function createMiddleware () {
47
47
  finishChannel.publish()
48
48
  }
49
49
  })
50
- }
50
+ })
51
51
  }
52
52
  }
53
53
  }
@@ -92,7 +92,7 @@ function wrapUnifiedCommand (command, operation, name) {
92
92
  }
93
93
  return instrument(operation, command, this, arguments, server, ns, ops, { name })
94
94
  }
95
- return shimmer.wrap(command, wrapped)
95
+ return wrapped
96
96
  }
97
97
 
98
98
  function wrapConnectionCommand (command, operation, name, instrumentFn = instrument) {
@@ -109,7 +109,7 @@ function wrapConnectionCommand (command, operation, name, instrumentFn = instrum
109
109
  ns = `${ns.db}.${ns.collection}`
110
110
  return instrumentFn(operation, command, this, arguments, topology, ns, ops, { name })
111
111
  }
112
- return shimmer.wrap(command, wrapped)
112
+ return wrapped
113
113
  }
114
114
 
115
115
  function wrapQuery (query, operation, name) {
@@ -123,7 +123,7 @@ function wrapQuery (query, operation, name) {
123
123
  return instrument(operation, query, this, arguments, pool, ns, ops)
124
124
  }
125
125
 
126
- return shimmer.wrap(query, wrapped)
126
+ return wrapped
127
127
  }
128
128
 
129
129
  function wrapCursor (cursor, operation, name) {
@@ -135,7 +135,7 @@ function wrapCursor (cursor, operation, name) {
135
135
  const ns = this.ns
136
136
  return instrument(operation, cursor, this, arguments, pool, ns, {}, { name })
137
137
  }
138
- return shimmer.wrap(cursor, wrapped)
138
+ return wrapped
139
139
  }
140
140
 
141
141
  function wrapCommand (command, operation, name) {
@@ -145,7 +145,7 @@ function wrapCommand (command, operation, name) {
145
145
  }
146
146
  return instrument(operation, command, this, arguments, this, ns, ops, { name })
147
147
  }
148
- return shimmer.wrap(command, wrapped)
148
+ return wrapped
149
149
  }
150
150
 
151
151
  function instrument (operation, command, ctx, args, server, ns, ops, options = {}) {
@@ -164,7 +164,7 @@ function instrument (operation, command, ctx, args, server, ns, ops, options = {
164
164
  return asyncResource.runInAsyncScope(() => {
165
165
  startCh.publish({ ns, ops, options: serverInfo, name })
166
166
 
167
- args[index] = asyncResource.bind(function (err, res) {
167
+ args[index] = shimmer.wrapFunction(callback, callback => asyncResource.bind(function (err, res) {
168
168
  if (err) {
169
169
  errorCh.publish(err)
170
170
  }
@@ -174,7 +174,7 @@ function instrument (operation, command, ctx, args, server, ns, ops, options = {
174
174
  if (callback) {
175
175
  return callback.apply(this, arguments)
176
176
  }
177
- })
177
+ }))
178
178
 
179
179
  try {
180
180
  return command.apply(ctx, args)
@@ -128,22 +128,21 @@ addHook({
128
128
  const resolve = arguments[0]
129
129
  const reject = arguments[1]
130
130
 
131
- // not using shimmer here because resolve/reject could be empty
132
- arguments[0] = function wrappedResolve () {
131
+ arguments[0] = shimmer.wrapFunction(resolve, resolve => function wrappedResolve () {
133
132
  finish()
134
133
 
135
134
  if (resolve) {
136
135
  return resolve.apply(this, arguments)
137
136
  }
138
- }
137
+ })
139
138
 
140
- arguments[1] = function wrappedReject () {
139
+ arguments[1] = shimmer.wrapFunction(reject, reject => function wrappedReject () {
141
140
  finish()
142
141
 
143
142
  if (reject) {
144
143
  return reject.apply(this, arguments)
145
144
  }
146
- }
145
+ })
147
146
 
148
147
  return originalThen.apply(this, arguments)
149
148
  }
@@ -169,7 +168,7 @@ addHook({
169
168
  versions: ['6', '>=7'],
170
169
  file: 'lib/helpers/query/sanitizeFilter.js'
171
170
  }, sanitizeFilter => {
172
- return shimmer.wrap(sanitizeFilter, function wrappedSanitizeFilter () {
171
+ return shimmer.wrapFunction(sanitizeFilter, sanitizeFilter => function wrappedSanitizeFilter () {
173
172
  const sanitizedObject = sanitizeFilter.apply(this, arguments)
174
173
 
175
174
  if (sanitizeFilterFinishCh.hasSubscribers) {
@@ -37,14 +37,14 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
37
37
 
38
38
  if (res._callback) {
39
39
  const cb = callbackResource.bind(res._callback)
40
- res._callback = asyncResource.bind(function (error, result) {
40
+ res._callback = shimmer.wrapFunction(cb, cb => asyncResource.bind(function (error, result) {
41
41
  if (error) {
42
42
  errorCh.publish(error)
43
43
  }
44
44
  finishCh.publish(result)
45
45
 
46
46
  return cb.apply(this, arguments)
47
- })
47
+ }))
48
48
  } else {
49
49
  const cb = asyncResource.bind(function () {
50
50
  finishCh.publish(undefined)
@@ -92,7 +92,7 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
92
92
 
93
93
  const cb = arguments[arguments.length - 1]
94
94
  if (typeof cb === 'function') {
95
- arguments[arguments.length - 1] = shimmer.wrap(cb, function () {
95
+ arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function () {
96
96
  finish()
97
97
  return cb.apply(this, arguments)
98
98
  })
@@ -31,19 +31,19 @@ addHook({ name: 'mysql2', file: 'lib/connection.js', versions: ['>=1'] }, Connec
31
31
  return Connection
32
32
 
33
33
  function bindExecute (cmd, execute, asyncResource) {
34
- return asyncResource.bind(function executeWithTrace (packet, connection) {
34
+ return shimmer.wrapFunction(execute, execute => asyncResource.bind(function executeWithTrace (packet, connection) {
35
35
  if (this.onResult) {
36
36
  this.onResult = asyncResource.bind(this.onResult)
37
37
  }
38
38
 
39
39
  return execute.apply(this, arguments)
40
- }, cmd)
40
+ }, cmd))
41
41
  }
42
42
 
43
43
  function wrapExecute (cmd, execute, asyncResource, config) {
44
44
  const callbackResource = new AsyncResource('bound-anonymous-fn')
45
45
 
46
- return asyncResource.bind(function executeWithTrace (packet, connection) {
46
+ return shimmer.wrapFunction(execute, execute => asyncResource.bind(function executeWithTrace (packet, connection) {
47
47
  const sql = cmd.statement ? cmd.statement.query : cmd.sql
48
48
  const payload = { sql, conf: config }
49
49
  startCh.publish(payload)
@@ -57,13 +57,13 @@ addHook({ name: 'mysql2', file: 'lib/connection.js', versions: ['>=1'] }, Connec
57
57
  if (this.onResult) {
58
58
  const onResult = callbackResource.bind(this.onResult)
59
59
 
60
- this.onResult = asyncResource.bind(function (error) {
60
+ this.onResult = shimmer.wrapFunction(onResult, onResult => asyncResource.bind(function (error) {
61
61
  if (error) {
62
62
  errorCh.publish(error)
63
63
  }
64
64
  finishCh.publish(undefined)
65
65
  onResult.apply(this, arguments)
66
- }, 'bound-anonymous-fn', this)
66
+ }, 'bound-anonymous-fn', this))
67
67
  } else {
68
68
  this.on('error', asyncResource.bind(error => errorCh.publish(error)))
69
69
  this.on('end', asyncResource.bind(() => finishCh.publish(undefined)))
@@ -76,6 +76,6 @@ addHook({ name: 'mysql2', file: 'lib/connection.js', versions: ['>=1'] }, Connec
76
76
  } catch (err) {
77
77
  errorCh.publish(err)
78
78
  }
79
- }, cmd)
79
+ }, cmd))
80
80
  }
81
81
  })