dd-trace 4.45.0 → 4.47.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
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
  const { createCoverageMap } = require('istanbul-lib-coverage')
3
3
 
4
- const { NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test')
5
4
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
6
5
  const shimmer = require('../../datadog-shimmer')
7
6
  const log = require('../../dd-trace/src/log')
@@ -36,7 +35,8 @@ const {
36
35
  mergeCoverage,
37
36
  fromCoverageMapToCoverage,
38
37
  getTestSuitePath,
39
- CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
38
+ CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
39
+ getIsFaultyEarlyFlakeDetection
40
40
  } = require('../../dd-trace/src/plugins/util/test')
41
41
 
42
42
  const isMarkedAsUnskippable = (pickle) => {
@@ -52,7 +52,9 @@ const patched = new WeakSet()
52
52
  const lastStatusByPickleId = new Map()
53
53
  const numRetriesByPickleId = new Map()
54
54
  const numAttemptToAsyncResource = new Map()
55
+ const newTestsByTestFullname = new Map()
55
56
 
57
+ let eventDataCollector = null
56
58
  let pickleByFile = {}
57
59
  const pickleResultByFile = {}
58
60
 
@@ -65,7 +67,10 @@ let isUnskippable = false
65
67
  let isSuitesSkippingEnabled = false
66
68
  let isEarlyFlakeDetectionEnabled = false
67
69
  let earlyFlakeDetectionNumRetries = 0
70
+ let earlyFlakeDetectionFaultyThreshold = 0
71
+ let isEarlyFlakeDetectionFaulty = false
68
72
  let isFlakyTestRetriesEnabled = false
73
+ let numTestRetries = 0
69
74
  let knownTests = []
70
75
  let skippedSuites = []
71
76
  let isSuitesSkipped = false
@@ -129,15 +134,35 @@ function getChannelPromise (channelToPublishTo) {
129
134
  })
130
135
  }
131
136
 
137
+ function getShouldBeSkippedSuite (pickle, suitesToSkip) {
138
+ const testSuitePath = getTestSuitePath(pickle.uri, process.cwd())
139
+ const isUnskippable = isMarkedAsUnskippable(pickle)
140
+ const isSkipped = suitesToSkip.includes(testSuitePath)
141
+
142
+ return [isSkipped && !isUnskippable, testSuitePath]
143
+ }
144
+
145
+ // From cucumber@>=11
146
+ function getFilteredPicklesNew (coordinator, suitesToSkip) {
147
+ return coordinator.sourcedPickles.reduce((acc, sourcedPickle) => {
148
+ const { pickle } = sourcedPickle
149
+ const [shouldBeSkipped, testSuitePath] = getShouldBeSkippedSuite(pickle, suitesToSkip)
150
+
151
+ if (shouldBeSkipped) {
152
+ acc.skippedSuites.add(testSuitePath)
153
+ } else {
154
+ acc.picklesToRun.push(sourcedPickle)
155
+ }
156
+ return acc
157
+ }, { skippedSuites: new Set(), picklesToRun: [] })
158
+ }
159
+
132
160
  function getFilteredPickles (runtime, suitesToSkip) {
133
161
  return runtime.pickleIds.reduce((acc, pickleId) => {
134
- const test = runtime.eventDataCollector.getPickle(pickleId)
135
- const testSuitePath = getTestSuitePath(test.uri, process.cwd())
136
-
137
- const isUnskippable = isMarkedAsUnskippable(test)
138
- const isSkipped = suitesToSkip.includes(testSuitePath)
162
+ const pickle = runtime.eventDataCollector.getPickle(pickleId)
163
+ const [shouldBeSkipped, testSuitePath] = getShouldBeSkippedSuite(pickle, suitesToSkip)
139
164
 
140
- if (isSkipped && !isUnskippable) {
165
+ if (shouldBeSkipped) {
141
166
  acc.skippedSuites.add(testSuitePath)
142
167
  } else {
143
168
  acc.picklesToRun.push(pickleId)
@@ -146,9 +171,21 @@ function getFilteredPickles (runtime, suitesToSkip) {
146
171
  }, { skippedSuites: new Set(), picklesToRun: [] })
147
172
  }
148
173
 
149
- function getPickleByFile (runtime) {
150
- return runtime.pickleIds.reduce((acc, pickleId) => {
151
- const test = runtime.eventDataCollector.getPickle(pickleId)
174
+ // From cucumber@>=11
175
+ function getPickleByFileNew (coordinator) {
176
+ return coordinator.sourcedPickles.reduce((acc, { pickle }) => {
177
+ if (acc[pickle.uri]) {
178
+ acc[pickle.uri].push(pickle)
179
+ } else {
180
+ acc[pickle.uri] = [pickle]
181
+ }
182
+ return acc
183
+ }, {})
184
+ }
185
+
186
+ function getPickleByFile (runtimeOrCoodinator) {
187
+ return runtimeOrCoodinator.pickleIds.reduce((acc, pickleId) => {
188
+ const test = runtimeOrCoodinator.eventDataCollector.getPickle(pickleId)
152
189
  if (acc[test.uri]) {
153
190
  acc[test.uri].push(test)
154
191
  } else {
@@ -188,7 +225,7 @@ function wrapRun (pl, isLatestVersion) {
188
225
  testStartCh.publish(testStartPayload)
189
226
  })
190
227
  try {
191
- this.eventBroadcaster.on('envelope', (testCase) => {
228
+ this.eventBroadcaster.on('envelope', shimmer.wrapFunction(null, () => (testCase) => {
192
229
  // Only supported from >=8.0.0
193
230
  if (testCase?.testCaseFinished) {
194
231
  const { testCaseFinished: { willBeRetried } } = testCase
@@ -206,7 +243,7 @@ function wrapRun (pl, isLatestVersion) {
206
243
  })
207
244
  }
208
245
  }
209
- })
246
+ }))
210
247
  let promise
211
248
 
212
249
  asyncResource.runInAsyncScope(() => {
@@ -294,19 +331,34 @@ function testCaseHook (TestCaseRunner) {
294
331
  return TestCaseRunner
295
332
  }
296
333
 
297
- function getWrappedStart (start, frameworkVersion, isParallel = false) {
334
+ // Valid for old and new cucumber versions
335
+ function getCucumberOptions (adapterOrCoordinator) {
336
+ if (adapterOrCoordinator.adapter) {
337
+ return adapterOrCoordinator.adapter.worker?.options || adapterOrCoordinator.adapter.options
338
+ }
339
+ return adapterOrCoordinator.options
340
+ }
341
+
342
+ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordinator = false) {
298
343
  return async function () {
299
344
  if (!libraryConfigurationCh.hasSubscribers) {
300
345
  return start.apply(this, arguments)
301
346
  }
347
+ const options = getCucumberOptions(this)
348
+
349
+ if (!isParallel && this.adapter?.options) {
350
+ isParallel = options.parallel > 0
351
+ }
302
352
  let errorSkippableRequest
303
353
 
304
354
  const configurationResponse = await getChannelPromise(libraryConfigurationCh)
305
355
 
306
356
  isEarlyFlakeDetectionEnabled = configurationResponse.libraryConfig?.isEarlyFlakeDetectionEnabled
307
357
  earlyFlakeDetectionNumRetries = configurationResponse.libraryConfig?.earlyFlakeDetectionNumRetries
358
+ earlyFlakeDetectionFaultyThreshold = configurationResponse.libraryConfig?.earlyFlakeDetectionFaultyThreshold
308
359
  isSuitesSkippingEnabled = configurationResponse.libraryConfig?.isSuitesSkippingEnabled
309
360
  isFlakyTestRetriesEnabled = configurationResponse.libraryConfig?.isFlakyTestRetriesEnabled
361
+ numTestRetries = configurationResponse.libraryConfig?.flakyTestRetriesCount
310
362
 
311
363
  if (isEarlyFlakeDetectionEnabled) {
312
364
  const knownTestsResponse = await getChannelPromise(knownTestsCh)
@@ -324,28 +376,49 @@ function getWrappedStart (start, frameworkVersion, isParallel = false) {
324
376
  skippableSuites = skippableResponse.skippableSuites
325
377
 
326
378
  if (!errorSkippableRequest) {
327
- const filteredPickles = getFilteredPickles(this, skippableSuites)
379
+ const filteredPickles = isCoordinator
380
+ ? getFilteredPicklesNew(this, skippableSuites)
381
+ : getFilteredPickles(this, skippableSuites)
382
+
328
383
  const { picklesToRun } = filteredPickles
329
- isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
384
+ const oldPickles = isCoordinator ? this.sourcedPickles : this.pickleIds
385
+
386
+ isSuitesSkipped = picklesToRun.length !== oldPickles.length
330
387
 
331
388
  log.debug(
332
- () => `${picklesToRun.length} out of ${this.pickleIds.length} suites are going to run.`
389
+ () => `${picklesToRun.length} out of ${oldPickles.length} suites are going to run.`
333
390
  )
334
391
 
335
- this.pickleIds = picklesToRun
392
+ if (isCoordinator) {
393
+ this.sourcedPickles = picklesToRun
394
+ } else {
395
+ this.pickleIds = picklesToRun
396
+ }
336
397
 
337
398
  skippedSuites = Array.from(filteredPickles.skippedSuites)
338
399
  itrCorrelationId = skippableResponse.itrCorrelationId
339
400
  }
340
401
  }
341
402
 
342
- pickleByFile = getPickleByFile(this)
403
+ pickleByFile = isCoordinator ? getPickleByFileNew(this) : getPickleByFile(this)
404
+
405
+ if (isEarlyFlakeDetectionEnabled) {
406
+ const isFaulty = getIsFaultyEarlyFlakeDetection(
407
+ Object.keys(pickleByFile),
408
+ knownTests.cucumber || {},
409
+ earlyFlakeDetectionFaultyThreshold
410
+ )
411
+ if (isFaulty) {
412
+ isEarlyFlakeDetectionEnabled = false
413
+ isEarlyFlakeDetectionFaulty = true
414
+ }
415
+ }
343
416
 
344
417
  const processArgv = process.argv.slice(2).join(' ')
345
418
  const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}`
346
419
 
347
- if (isFlakyTestRetriesEnabled && !this.options.retry) {
348
- this.options.retry = NUM_FAILED_TEST_RETRIES
420
+ if (isFlakyTestRetriesEnabled && !options.retry && numTestRetries > 0) {
421
+ options.retry = numTestRetries
349
422
  }
350
423
 
351
424
  sessionAsyncResource.runInAsyncScope(() => {
@@ -387,48 +460,65 @@ function getWrappedStart (start, frameworkVersion, isParallel = false) {
387
460
  hasUnskippableSuites: isUnskippable,
388
461
  hasForcedToRunSuites: isForcedToRun,
389
462
  isEarlyFlakeDetectionEnabled,
463
+ isEarlyFlakeDetectionFaulty,
390
464
  isParallel
391
465
  })
392
466
  })
467
+ eventDataCollector = null
393
468
  return success
394
469
  }
395
470
  }
396
471
 
397
- function getWrappedRunTest (runTestFunction) {
398
- return async function (pickleId) {
399
- const test = this.eventDataCollector.getPickle(pickleId)
472
+ // Generates suite start and finish events in the main process.
473
+ // Handles EFD in both the main process and the worker process.
474
+ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = false, isWorker = false) {
475
+ return async function () {
476
+ let pickle
477
+ if (isNewerCucumberVersion) {
478
+ pickle = arguments[0].pickle
479
+ } else {
480
+ pickle = this.eventDataCollector.getPickle(arguments[0])
481
+ }
400
482
 
401
- const testFileAbsolutePath = test.uri
483
+ const testFileAbsolutePath = pickle.uri
402
484
  const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
403
485
 
404
- if (!pickleResultByFile[testFileAbsolutePath]) { // first test in suite
405
- isUnskippable = isMarkedAsUnskippable(test)
486
+ // If it's a worker, suite events are handled in `getWrappedParseWorkerMessage`
487
+ if (!isWorker && !pickleResultByFile[testFileAbsolutePath]) { // first test in suite
488
+ isUnskippable = isMarkedAsUnskippable(pickle)
406
489
  isForcedToRun = isUnskippable && skippableSuites.includes(testSuitePath)
407
490
 
408
- testSuiteStartCh.publish({ testSuitePath, isUnskippable, isForcedToRun, itrCorrelationId })
491
+ testSuiteStartCh.publish({
492
+ testFileAbsolutePath,
493
+ isUnskippable,
494
+ isForcedToRun,
495
+ itrCorrelationId
496
+ })
409
497
  }
410
498
 
411
499
  let isNew = false
412
500
 
413
501
  if (isEarlyFlakeDetectionEnabled) {
414
- isNew = isNewTest(testSuitePath, test.name)
502
+ isNew = isNewTest(testSuitePath, pickle.name)
415
503
  if (isNew) {
416
- numRetriesByPickleId.set(pickleId, 0)
504
+ numRetriesByPickleId.set(pickle.id, 0)
417
505
  }
418
506
  }
419
- const runTestCaseResult = await runTestFunction.apply(this, arguments)
507
+ // TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
508
+ let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
420
509
 
421
- const testStatuses = lastStatusByPickleId.get(pickleId)
510
+ const testStatuses = lastStatusByPickleId.get(pickle.id)
422
511
  const lastTestStatus = testStatuses[testStatuses.length - 1]
423
512
  // If it's a new test and it hasn't been skipped, we run it again
424
513
  if (isEarlyFlakeDetectionEnabled && lastTestStatus !== 'skip' && isNew) {
425
514
  for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
426
- numRetriesByPickleId.set(pickleId, retryIndex + 1)
427
- await runTestFunction.apply(this, arguments)
515
+ numRetriesByPickleId.set(pickle.id, retryIndex + 1)
516
+ runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
428
517
  }
429
518
  }
430
519
  let testStatus = lastTestStatus
431
- if (isEarlyFlakeDetectionEnabled) {
520
+ let shouldBePassedByEFD = false
521
+ if (isNew && isEarlyFlakeDetectionEnabled) {
432
522
  /**
433
523
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
434
524
  * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
@@ -438,6 +528,8 @@ function getWrappedRunTest (runTestFunction) {
438
528
  */
439
529
  testStatus = getTestStatusFromRetries(testStatuses)
440
530
  if (testStatus === 'pass') {
531
+ // for cucumber@>=11, setting `this.success` does not work, so we have to change the returned value
532
+ shouldBePassedByEFD = true
441
533
  this.success = true
442
534
  }
443
535
  }
@@ -448,8 +540,9 @@ function getWrappedRunTest (runTestFunction) {
448
540
  pickleResultByFile[testFileAbsolutePath].push(testStatus)
449
541
  }
450
542
 
451
- // last test in suite
452
- if (pickleResultByFile[testFileAbsolutePath].length === pickleByFile[testFileAbsolutePath].length) {
543
+ // If it's a worker, suite events are handled in `getWrappedParseWorkerMessage`
544
+ if (!isWorker && pickleResultByFile[testFileAbsolutePath].length === pickleByFile[testFileAbsolutePath].length) {
545
+ // last test in suite
453
546
  const testSuiteStatus = getSuiteStatusFromTestStatuses(pickleResultByFile[testFileAbsolutePath])
454
547
  if (global.__coverage__) {
455
548
  const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
@@ -468,11 +561,15 @@ function getWrappedRunTest (runTestFunction) {
468
561
  testSuiteFinishCh.publish({ status: testSuiteStatus, testSuitePath })
469
562
  }
470
563
 
564
+ if (isNewerCucumberVersion && isEarlyFlakeDetectionEnabled && isNew) {
565
+ return shouldBePassedByEFD
566
+ }
567
+
471
568
  return runTestCaseResult
472
569
  }
473
570
  }
474
571
 
475
- function getWrappedParseWorkerMessage (parseWorkerMessageFunction) {
572
+ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion) {
476
573
  return function (worker, message) {
477
574
  // If the message is an array, it's a dd-trace message, so we need to stop cucumber processing,
478
575
  // or cucumber will throw an error
@@ -487,29 +584,43 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction) {
487
584
  }
488
585
  }
489
586
 
490
- const { jsonEnvelope } = message
491
- if (!jsonEnvelope) {
587
+ let envelope
588
+
589
+ if (isNewVersion) {
590
+ envelope = message.envelope
591
+ } else {
592
+ envelope = message.jsonEnvelope
593
+ }
594
+
595
+ if (!envelope) {
492
596
  return parseWorkerMessageFunction.apply(this, arguments)
493
597
  }
494
- let parsed = jsonEnvelope
598
+ let parsed = envelope
495
599
 
496
600
  if (typeof parsed === 'string') {
497
601
  try {
498
- parsed = JSON.parse(jsonEnvelope)
602
+ parsed = JSON.parse(envelope)
499
603
  } catch (e) {
500
604
  // ignore errors and continue
501
605
  return parseWorkerMessageFunction.apply(this, arguments)
502
606
  }
503
607
  }
608
+ let pickle
609
+
504
610
  if (parsed.testCaseStarted) {
505
- const { pickleId } = this.eventDataCollector.testCaseMap[parsed.testCaseStarted.testCaseId]
506
- const pickle = this.eventDataCollector.getPickle(pickleId)
611
+ if (isNewVersion) {
612
+ pickle = this.inProgress[worker.id].pickle
613
+ } else {
614
+ const { pickleId } = this.eventDataCollector.testCaseMap[parsed.testCaseStarted.testCaseId]
615
+ pickle = this.eventDataCollector.getPickle(pickleId)
616
+ }
617
+ // THIS FAILS IN PARALLEL MODE
507
618
  const testFileAbsolutePath = pickle.uri
508
619
  // First test in suite
509
620
  if (!pickleResultByFile[testFileAbsolutePath]) {
510
621
  pickleResultByFile[testFileAbsolutePath] = []
511
622
  testSuiteStartCh.publish({
512
- testSuitePath: getTestSuitePath(testFileAbsolutePath, process.cwd())
623
+ testFileAbsolutePath
513
624
  })
514
625
  }
515
626
  }
@@ -518,14 +629,47 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction) {
518
629
 
519
630
  // after calling `parseWorkerMessageFunction`, the test status can already be read
520
631
  if (parsed.testCaseFinished) {
521
- const { pickle, worstTestStepResult } =
522
- this.eventDataCollector.getTestCaseAttempt(parsed.testCaseFinished.testCaseStartedId)
632
+ let worstTestStepResult
633
+ if (isNewVersion && eventDataCollector) {
634
+ pickle = this.inProgress[worker.id].pickle
635
+ worstTestStepResult =
636
+ eventDataCollector.getTestCaseAttempt(parsed.testCaseFinished.testCaseStartedId).worstTestStepResult
637
+ } else {
638
+ const testCase = this.eventDataCollector.getTestCaseAttempt(parsed.testCaseFinished.testCaseStartedId)
639
+ worstTestStepResult = testCase.worstTestStepResult
640
+ pickle = testCase.pickle
641
+ }
523
642
 
524
643
  const { status } = getStatusFromResultLatest(worstTestStepResult)
644
+ let isNew = false
645
+
646
+ if (isEarlyFlakeDetectionEnabled) {
647
+ isNew = isNewTest(pickle.uri, pickle.name)
648
+ }
525
649
 
526
650
  const testFileAbsolutePath = pickle.uri
527
651
  const finished = pickleResultByFile[testFileAbsolutePath]
528
- finished.push(status)
652
+
653
+ if (isNew) {
654
+ const testFullname = `${pickle.uri}:${pickle.name}`
655
+ let testStatuses = newTestsByTestFullname.get(testFullname)
656
+ if (!testStatuses) {
657
+ testStatuses = [status]
658
+ newTestsByTestFullname.set(testFullname, testStatuses)
659
+ } else {
660
+ testStatuses.push(status)
661
+ }
662
+ // We have finished all retries
663
+ if (testStatuses.length === earlyFlakeDetectionNumRetries + 1) {
664
+ const newTestFinalStatus = getTestStatusFromRetries(testStatuses)
665
+ // we only push to `finished` if the retries have finished
666
+ finished.push(newTestFinalStatus)
667
+ }
668
+ } else {
669
+ // TODO: can we get error message?
670
+ const finished = pickleResultByFile[testFileAbsolutePath]
671
+ finished.push(status)
672
+ }
529
673
 
530
674
  if (finished.length === pickleByFile[testFileAbsolutePath].length) {
531
675
  testSuiteFinishCh.publish({
@@ -555,13 +699,16 @@ addHook({
555
699
 
556
700
  // From 7.3.0 onwards, runPickle becomes runTestCase. Not executed in parallel mode.
557
701
  // `getWrappedStart` generates session start and finish events
558
- // `getWrappedRunTest` generates suite start and finish events
702
+ // `getWrappedRunTestCase` generates suite start and finish events and handles EFD.
703
+ // TODO (fix): there is a lib/runtime/index in >=11.0.0, but we don't instrument it because it's not useful for us
704
+ // This causes a info log saying "Found incompatible integration version".
559
705
  addHook({
560
706
  name: '@cucumber/cucumber',
561
- versions: ['>=7.3.0'],
707
+ versions: ['>=7.3.0 <11.0.0'],
562
708
  file: 'lib/runtime/index.js'
563
709
  }, (runtimePackage, frameworkVersion) => {
564
- shimmer.wrap(runtimePackage.default.prototype, 'runTestCase', runTestCase => getWrappedRunTest(runTestCase))
710
+ shimmer.wrap(runtimePackage.default.prototype, 'runTestCase', runTestCase => getWrappedRunTestCase(runTestCase))
711
+
565
712
  shimmer.wrap(runtimePackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion))
566
713
 
567
714
  return runtimePackage
@@ -569,13 +716,13 @@ addHook({
569
716
 
570
717
  // Not executed in parallel mode.
571
718
  // `getWrappedStart` generates session start and finish events
572
- // `getWrappedRunTest` generates suite start and finish events
719
+ // `getWrappedRunTestCase` generates suite start and finish events and handles EFD.
573
720
  addHook({
574
721
  name: '@cucumber/cucumber',
575
722
  versions: ['>=7.0.0 <7.3.0'],
576
723
  file: 'lib/runtime/index.js'
577
724
  }, (runtimePackage, frameworkVersion) => {
578
- shimmer.wrap(runtimePackage.default.prototype, 'runPickle', runPickle => getWrappedRunTest(runPickle))
725
+ shimmer.wrap(runtimePackage.default.prototype, 'runPickle', runPickle => getWrappedRunTestCase(runPickle))
579
726
  shimmer.wrap(runtimePackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion))
580
727
 
581
728
  return runtimePackage
@@ -583,11 +730,10 @@ addHook({
583
730
 
584
731
  // Only executed in parallel mode.
585
732
  // `getWrappedStart` generates session start and finish events
586
- // `getWrappedGiveWork` generates suite start events and sets pickleResultByFile (used by suite finish events)
587
- // `getWrappedParseWorkerMessage` generates suite finish events
733
+ // `getWrappedParseWorkerMessage` generates suite start and finish events
588
734
  addHook({
589
735
  name: '@cucumber/cucumber',
590
- versions: ['>=8.0.0'],
736
+ versions: ['>=8.0.0 <11.0.0'],
591
737
  file: 'lib/runtime/parallel/coordinator.js'
592
738
  }, (coordinatorPackage, frameworkVersion) => {
593
739
  shimmer.wrap(coordinatorPackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion, true))
@@ -598,3 +744,95 @@ addHook({
598
744
  )
599
745
  return coordinatorPackage
600
746
  })
747
+
748
+ // >=11.0.0 hooks
749
+ // `getWrappedRunTestCase` does two things:
750
+ // - generates suite start and finish events in the main process,
751
+ // - handles EFD in both the main process and the worker process.
752
+ addHook({
753
+ name: '@cucumber/cucumber',
754
+ versions: ['>=11.0.0'],
755
+ file: 'lib/runtime/worker.js'
756
+ }, (workerPackage) => {
757
+ shimmer.wrap(
758
+ workerPackage.Worker.prototype,
759
+ 'runTestCase',
760
+ runTestCase => getWrappedRunTestCase(runTestCase, true, !!process.env.CUCUMBER_WORKER_ID)
761
+ )
762
+ return workerPackage
763
+ })
764
+
765
+ // `getWrappedStart` generates session start and finish events
766
+ addHook({
767
+ name: '@cucumber/cucumber',
768
+ versions: ['>=11.0.0'],
769
+ file: 'lib/runtime/coordinator.js'
770
+ }, (coordinatorPackage, frameworkVersion) => {
771
+ shimmer.wrap(
772
+ coordinatorPackage.Coordinator.prototype,
773
+ 'run',
774
+ run => getWrappedStart(run, frameworkVersion, false, true)
775
+ )
776
+ return coordinatorPackage
777
+ })
778
+
779
+ // Necessary because `eventDataCollector` is no longer available in the runtime instance
780
+ addHook({
781
+ name: '@cucumber/cucumber',
782
+ versions: ['>=11.0.0'],
783
+ file: 'lib/formatter/helpers/event_data_collector.js'
784
+ }, (eventDataCollectorPackage) => {
785
+ shimmer.wrap(eventDataCollectorPackage.default.prototype, 'parseEnvelope', parseEnvelope => function () {
786
+ eventDataCollector = this
787
+ return parseEnvelope.apply(this, arguments)
788
+ })
789
+ return eventDataCollectorPackage
790
+ })
791
+
792
+ // Only executed in parallel mode for >=11, in the main process.
793
+ // `getWrappedParseWorkerMessage` generates suite start and finish events
794
+ // In `startWorker` we pass early flake detection info to the worker.
795
+ addHook({
796
+ name: '@cucumber/cucumber',
797
+ versions: ['>=11.0.0'],
798
+ file: 'lib/runtime/parallel/adapter.js'
799
+ }, (adapterPackage) => {
800
+ shimmer.wrap(
801
+ adapterPackage.ChildProcessAdapter.prototype,
802
+ 'parseWorkerMessage',
803
+ parseWorkerMessage => getWrappedParseWorkerMessage(parseWorkerMessage, true)
804
+ )
805
+ // EFD in parallel mode only supported in >=11.0.0
806
+ shimmer.wrap(adapterPackage.ChildProcessAdapter.prototype, 'startWorker', startWorker => function () {
807
+ if (isEarlyFlakeDetectionEnabled) {
808
+ this.options.worldParameters._ddKnownTests = knownTests
809
+ this.options.worldParameters._ddEarlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
810
+ }
811
+
812
+ return startWorker.apply(this, arguments)
813
+ })
814
+ return adapterPackage
815
+ })
816
+
817
+ // Hook executed in the worker process when in parallel mode.
818
+ // In this hook we read the information passed in `worldParameters` and make it available for
819
+ // `getWrappedRunTestCase`.
820
+ addHook({
821
+ name: '@cucumber/cucumber',
822
+ versions: ['>=11.0.0'],
823
+ file: 'lib/runtime/parallel/worker.js'
824
+ }, (workerPackage) => {
825
+ shimmer.wrap(
826
+ workerPackage.ChildProcessWorker.prototype,
827
+ 'initialize',
828
+ initialize => async function () {
829
+ await initialize.apply(this, arguments)
830
+ isEarlyFlakeDetectionEnabled = !!this.options.worldParameters._ddKnownTests
831
+ if (isEarlyFlakeDetectionEnabled) {
832
+ knownTests = this.options.worldParameters._ddKnownTests
833
+ earlyFlakeDetectionNumRetries = this.options.worldParameters._ddEarlyFlakeDetectionNumRetries
834
+ }
835
+ }
836
+ )
837
+ return workerPackage
838
+ })
@@ -21,16 +21,16 @@ const rrtypeMap = new WeakMap()
21
21
  const names = ['dns', 'node:dns']
22
22
 
23
23
  addHook({ name: names }, dns => {
24
- dns.lookup = wrap('apm:dns:lookup', dns.lookup, 2)
25
- dns.lookupService = wrap('apm:dns:lookup_service', dns.lookupService, 3)
26
- dns.resolve = wrap('apm:dns:resolve', dns.resolve, 2)
27
- dns.reverse = wrap('apm:dns:reverse', dns.reverse, 2)
24
+ shimmer.wrap(dns, 'lookup', fn => wrap('apm:dns:lookup', fn, 2))
25
+ shimmer.wrap(dns, 'lookupService', fn => wrap('apm:dns:lookup_service', fn, 2))
26
+ shimmer.wrap(dns, 'resolve', fn => wrap('apm:dns:resolve', fn, 2))
27
+ shimmer.wrap(dns, 'reverse', fn => wrap('apm:dns:reverse', fn, 2))
28
28
 
29
29
  patchResolveShorthands(dns)
30
30
 
31
31
  if (dns.Resolver) {
32
- dns.Resolver.prototype.resolve = wrap('apm:dns:resolve', dns.Resolver.prototype.resolve, 2)
33
- dns.Resolver.prototype.reverse = wrap('apm:dns:reverse', dns.Resolver.prototype.reverse, 2)
32
+ shimmer.wrap(dns.Resolver.prototype, 'resolve', fn => wrap('apm:dns:resolve', fn, 2))
33
+ shimmer.wrap(dns.Resolver.prototype, 'reverse', fn => wrap('apm:dns:reverse', fn, 2))
34
34
 
35
35
  patchResolveShorthands(dns.Resolver.prototype)
36
36
  }
@@ -43,7 +43,7 @@ function patchResolveShorthands (prototype) {
43
43
  .filter(method => !!prototype[method])
44
44
  .forEach(method => {
45
45
  rrtypeMap.set(prototype[method], rrtypes[method])
46
- prototype[method] = wrap('apm:dns:resolve', prototype[method], 2, rrtypes[method])
46
+ shimmer.wrap(prototype, method, fn => wrap('apm:dns:resolve', fn, 2, rrtypes[method]))
47
47
  })
48
48
  }
49
49
 
@@ -72,13 +72,13 @@ function wrap (prefix, fn, expectedArgs, rrtype) {
72
72
  return asyncResource.runInAsyncScope(() => {
73
73
  startCh.publish(startArgs)
74
74
 
75
- arguments[arguments.length - 1] = asyncResource.bind(function (error, result) {
75
+ arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => asyncResource.bind(function (error, result) {
76
76
  if (error) {
77
77
  errorCh.publish(error)
78
78
  }
79
79
  finishCh.publish(result)
80
80
  cb.apply(this, arguments)
81
- })
81
+ }))
82
82
 
83
83
  try {
84
84
  return fn.apply(this, arguments)
@@ -92,5 +92,5 @@ function wrap (prefix, fn, expectedArgs, rrtype) {
92
92
  })
93
93
  }
94
94
 
95
- return shimmer.wrap(fn, wrapped)
95
+ return wrapped
96
96
  }
@@ -48,12 +48,12 @@ function createWrapSelect () {
48
48
  return function () {
49
49
  if (arguments.length === 1) {
50
50
  const cb = arguments[0]
51
- arguments[0] = function (err, connection) {
51
+ arguments[0] = shimmer.wrapFunction(cb, cb => function (err, connection) {
52
52
  if (connectCh.hasSubscribers && connection && connection.host) {
53
53
  connectCh.publish({ hostname: connection.host.host, port: connection.host.port })
54
54
  }
55
55
  cb(err, connection)
56
- }
56
+ })
57
57
  }
58
58
  return request.apply(this, arguments)
59
59
  }
@@ -86,10 +86,10 @@ function createWrapRequest (name) {
86
86
  if (typeof cb === 'function') {
87
87
  cb = parentResource.bind(cb)
88
88
 
89
- arguments[lastIndex] = asyncResource.bind(function (error) {
89
+ arguments[lastIndex] = shimmer.wrapFunction(cb, cb => asyncResource.bind(function (error) {
90
90
  finish(params, error)
91
91
  return cb.apply(null, arguments)
92
- })
92
+ }))
93
93
  return request.apply(this, arguments)
94
94
  } else {
95
95
  const promise = request.apply(this, arguments)
@@ -22,15 +22,15 @@ addHook({ name: 'express-mongo-sanitize', versions: ['>=1.0.0'] }, expressMongoS
22
22
  return sanitizedObject
23
23
  })
24
24
 
25
- return shimmer.wrap(expressMongoSanitize, function () {
25
+ return shimmer.wrapFunction(expressMongoSanitize, expressMongoSanitize => function () {
26
26
  const middleware = expressMongoSanitize.apply(this, arguments)
27
27
 
28
- return shimmer.wrap(middleware, function (req, res, next) {
28
+ return shimmer.wrapFunction(middleware, middleware => function (req, res, next) {
29
29
  if (!sanitizeMiddlewareFinished.hasSubscribers) {
30
30
  return middleware.apply(this, arguments)
31
31
  }
32
32
 
33
- const wrappedNext = shimmer.wrap(next, function () {
33
+ const wrappedNext = shimmer.wrapFunction(next, next => function () {
34
34
  sanitizeMiddlewareFinished.publish({
35
35
  sanitizedProperties: propertiesToSanitize,
36
36
  req