dd-trace 5.22.0 → 5.23.1

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 (89) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +20 -8
  3. package/package.json +9 -3
  4. package/packages/datadog-instrumentations/src/cucumber.js +290 -53
  5. package/packages/datadog-instrumentations/src/jest.js +3 -1
  6. package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
  7. package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
  8. package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
  9. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
  10. package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
  11. package/packages/datadog-instrumentations/src/openai.js +4 -2
  12. package/packages/datadog-instrumentations/src/pg.js +59 -4
  13. package/packages/datadog-instrumentations/src/vitest.js +184 -9
  14. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  17. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
  18. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  19. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
  21. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  22. package/packages/datadog-plugin-http/src/client.js +1 -42
  23. package/packages/datadog-plugin-http2/src/client.js +1 -26
  24. package/packages/datadog-plugin-jest/src/index.js +17 -1
  25. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
  26. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
  27. package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
  28. package/packages/datadog-plugin-mocha/src/index.js +18 -0
  29. package/packages/datadog-plugin-openai/src/index.js +27 -18
  30. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  31. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  32. package/packages/datadog-plugin-vitest/src/index.js +68 -3
  33. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  34. package/packages/dd-trace/src/appsec/channels.js +4 -2
  35. package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
  36. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
  37. package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
  39. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  40. package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
  41. package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
  43. package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
  44. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
  46. package/packages/dd-trace/src/config.js +61 -10
  47. package/packages/dd-trace/src/constants.js +11 -1
  48. package/packages/dd-trace/src/data_streams_context.js +3 -0
  49. package/packages/dd-trace/src/datastreams/fnv.js +23 -0
  50. package/packages/dd-trace/src/datastreams/pathway.js +12 -5
  51. package/packages/dd-trace/src/datastreams/processor.js +35 -0
  52. package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
  53. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
  54. package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
  59. package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
  60. package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
  62. package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
  63. package/packages/dd-trace/src/debugger/index.js +92 -0
  64. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
  65. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  66. package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
  67. package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
  68. package/packages/dd-trace/src/payload-tagging/index.js +93 -0
  69. package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
  70. package/packages/dd-trace/src/plugin_manager.js +11 -10
  71. package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
  72. package/packages/dd-trace/src/plugins/util/env.js +5 -2
  73. package/packages/dd-trace/src/plugins/util/test.js +26 -2
  74. package/packages/dd-trace/src/profiling/config.js +5 -0
  75. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  76. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
  77. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
  78. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
  79. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
  80. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
  81. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
  82. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
  83. package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
  84. package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
  85. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  86. package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
  87. package/packages/dd-trace/src/proxy.js +10 -3
  88. package/packages/dd-trace/src/span_stats.js +4 -2
  89. package/packages/dd-trace/src/appsec/rasp.js +0 -176
@@ -14,6 +14,7 @@ require,import-in-the-middle,Apache license 2.0,Copyright 2021 Datadog Inc.
14
14
  require,int64-buffer,MIT,Copyright 2015-2016 Yusuke Kawasaki
15
15
  require,istanbul-lib-coverage,BSD-3-Clause,Copyright 2012-2015 Yahoo! Inc.
16
16
  require,jest-docblock,MIT,Copyright Meta Platforms, Inc. and affiliates.
17
+ require,jsonpath-plus,MIT,Copyright (c) 2011-2019 Stefan Goessner, Subbu Allamaraju, Mike Brevoort, Robert Krahn, Brett Zamir, Richard Schneider
17
18
  require,koalas,MIT,Copyright 2013-2017 Brian Woodward
18
19
  require,limiter,MIT,Copyright 2011 John Hurliman
19
20
  require,lodash.sortby,MIT,Copyright JS Foundation and other contributors
@@ -26,6 +27,7 @@ require,pprof-format,MIT,Copyright 2022 Stephen Belanger
26
27
  require,protobufjs,BSD-3-Clause,Copyright 2016 Daniel Wirtz
27
28
  require,tlhunter-sorted-set,MIT,Copyright (c) 2023 Datadog Inc.
28
29
  require,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer
30
+ require,rfdc,MIT,Copyright 2019 David Mark Clements
29
31
  require,semver,ISC,Copyright Isaac Z. Schlueter and Contributors
30
32
  require,shell-quote,mit,Copyright (c) 2013 James Halliday
31
33
  dev,@types/node,MIT,Copyright Authors
package/index.d.ts CHANGED
@@ -729,6 +729,26 @@ declare namespace tracer {
729
729
  * The selection and priority order of context propagation injection and extraction mechanisms.
730
730
  */
731
731
  propagationStyle?: string[] | PropagationStyle
732
+
733
+ /**
734
+ * Cloud payload report as tags
735
+ */
736
+ cloudPayloadTagging?: {
737
+ /**
738
+ * Additional JSONPath queries to replace with `redacted` in request payloads
739
+ * Undefined or invalid JSONPath queries disable the feature for requests.
740
+ */
741
+ request?: string,
742
+ /**
743
+ * Additional JSONPath queries to replace with `redacted` in response payloads
744
+ * Undefined or invalid JSONPath queries disable the feature for responses.
745
+ */
746
+ response?: string,
747
+ /**
748
+ * Maximum depth of payload traversal for tags
749
+ */
750
+ maxDepth?: number
751
+ }
732
752
  }
733
753
 
734
754
  /**
@@ -1010,14 +1030,6 @@ declare namespace tracer {
1010
1030
  * @default code => code < 500
1011
1031
  */
1012
1032
  validateStatus?: (code: number) => boolean;
1013
-
1014
- /**
1015
- * Enable injection of tracing headers into requests signed with AWS IAM headers.
1016
- * Disable this if you get AWS signature errors (HTTP 403).
1017
- *
1018
- * @default false
1019
- */
1020
- enablePropagationWithAmazonHeaders?: boolean;
1021
1033
  }
1022
1034
 
1023
1035
  /** @hidden */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.22.0",
3
+ "version": "5.23.1",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -13,12 +13,15 @@
13
13
  "type:doc": "cd docs && yarn && yarn build",
14
14
  "type:test": "cd docs && yarn && yarn test",
15
15
  "lint": "node scripts/check_licenses.js && eslint . && yarn audit --groups dependencies",
16
+ "lint-fix": "node scripts/check_licenses.js && eslint . --fix && yarn audit --groups dependencies",
16
17
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
17
18
  "test": "SERVICES=* yarn services && mocha --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
18
19
  "test:appsec": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"",
19
20
  "test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec",
20
21
  "test:appsec:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
21
22
  "test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" -- npm run test:appsec:plugins",
23
+ "test:debugger": "tap packages/dd-trace/test/debugger/**/*.spec.js",
24
+ "test:debugger:ci": "npm run test:debugger -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/debugger/**/*.js\"",
22
25
  "test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,datastreams,encode,exporters,opentelemetry,opentracing,plugins,service-naming,telemetry}/**/*.spec.js\"",
23
26
  "test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
24
27
  "test:instrumentations": "mocha -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
@@ -36,6 +39,7 @@
36
39
  "test:integration:appsec": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/appsec/*.spec.js\"",
37
40
  "test:integration:cucumber": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cucumber/*.spec.js\"",
38
41
  "test:integration:cypress": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cypress/*.spec.js\"",
42
+ "test:integration:debugger": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/debugger/*.spec.js\"",
39
43
  "test:integration:jest": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/jest/*.spec.js\"",
40
44
  "test:integration:mocha": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/mocha/*.spec.js\"",
41
45
  "test:integration:playwright": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/playwright/*.spec.js\"",
@@ -83,10 +87,11 @@
83
87
  "crypto-randomuuid": "^1.0.0",
84
88
  "dc-polyfill": "^0.1.4",
85
89
  "ignore": "^5.2.4",
86
- "import-in-the-middle": "^1.8.1",
90
+ "import-in-the-middle": "1.11.2",
87
91
  "int64-buffer": "^0.1.9",
88
92
  "istanbul-lib-coverage": "3.2.0",
89
93
  "jest-docblock": "^29.7.0",
94
+ "jsonpath-plus": "^9.0.0",
90
95
  "koalas": "^1.0.2",
91
96
  "limiter": "1.1.5",
92
97
  "lodash.sortby": "^4.7.0",
@@ -94,10 +99,11 @@
94
99
  "module-details-from-path": "^1.0.3",
95
100
  "msgpack-lite": "^0.1.26",
96
101
  "opentracing": ">=0.12.1",
97
- "path-to-regexp": "^0.1.2",
102
+ "path-to-regexp": "^0.1.10",
98
103
  "pprof-format": "^2.1.0",
99
104
  "protobufjs": "^7.2.5",
100
105
  "retry": "^0.13.1",
106
+ "rfdc": "^1.3.1",
101
107
  "semver": "^7.5.4",
102
108
  "shell-quote": "^1.8.1",
103
109
  "tlhunter-sorted-set": "^0.1.0"
@@ -35,7 +35,8 @@ const {
35
35
  mergeCoverage,
36
36
  fromCoverageMapToCoverage,
37
37
  getTestSuitePath,
38
- CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
38
+ CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
39
+ getIsFaultyEarlyFlakeDetection
39
40
  } = require('../../dd-trace/src/plugins/util/test')
40
41
 
41
42
  const isMarkedAsUnskippable = (pickle) => {
@@ -51,7 +52,9 @@ const patched = new WeakSet()
51
52
  const lastStatusByPickleId = new Map()
52
53
  const numRetriesByPickleId = new Map()
53
54
  const numAttemptToAsyncResource = new Map()
55
+ const newTestsByTestFullname = new Map()
54
56
 
57
+ let eventDataCollector = null
55
58
  let pickleByFile = {}
56
59
  const pickleResultByFile = {}
57
60
 
@@ -64,6 +67,8 @@ let isUnskippable = false
64
67
  let isSuitesSkippingEnabled = false
65
68
  let isEarlyFlakeDetectionEnabled = false
66
69
  let earlyFlakeDetectionNumRetries = 0
70
+ let earlyFlakeDetectionFaultyThreshold = 0
71
+ let isEarlyFlakeDetectionFaulty = false
67
72
  let isFlakyTestRetriesEnabled = false
68
73
  let numTestRetries = 0
69
74
  let knownTests = []
@@ -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 {
@@ -294,17 +331,31 @@ 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
310
361
  numTestRetries = configurationResponse.libraryConfig?.flakyTestRetriesCount
@@ -325,28 +376,49 @@ function getWrappedStart (start, frameworkVersion, isParallel = false) {
325
376
  skippableSuites = skippableResponse.skippableSuites
326
377
 
327
378
  if (!errorSkippableRequest) {
328
- const filteredPickles = getFilteredPickles(this, skippableSuites)
379
+ const filteredPickles = isCoordinator
380
+ ? getFilteredPicklesNew(this, skippableSuites)
381
+ : getFilteredPickles(this, skippableSuites)
382
+
329
383
  const { picklesToRun } = filteredPickles
330
- isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
384
+ const oldPickles = isCoordinator ? this.sourcedPickles : this.pickleIds
385
+
386
+ isSuitesSkipped = picklesToRun.length !== oldPickles.length
331
387
 
332
388
  log.debug(
333
- () => `${picklesToRun.length} out of ${this.pickleIds.length} suites are going to run.`
389
+ () => `${picklesToRun.length} out of ${oldPickles.length} suites are going to run.`
334
390
  )
335
391
 
336
- this.pickleIds = picklesToRun
392
+ if (isCoordinator) {
393
+ this.sourcedPickles = picklesToRun
394
+ } else {
395
+ this.pickleIds = picklesToRun
396
+ }
337
397
 
338
398
  skippedSuites = Array.from(filteredPickles.skippedSuites)
339
399
  itrCorrelationId = skippableResponse.itrCorrelationId
340
400
  }
341
401
  }
342
402
 
343
- 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
+ }
344
416
 
345
417
  const processArgv = process.argv.slice(2).join(' ')
346
418
  const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}`
347
419
 
348
- if (isFlakyTestRetriesEnabled && !this.options.retry && numTestRetries > 0) {
349
- this.options.retry = numTestRetries
420
+ if (isFlakyTestRetriesEnabled && !options.retry && numTestRetries > 0) {
421
+ options.retry = numTestRetries
350
422
  }
351
423
 
352
424
  sessionAsyncResource.runInAsyncScope(() => {
@@ -388,48 +460,65 @@ function getWrappedStart (start, frameworkVersion, isParallel = false) {
388
460
  hasUnskippableSuites: isUnskippable,
389
461
  hasForcedToRunSuites: isForcedToRun,
390
462
  isEarlyFlakeDetectionEnabled,
463
+ isEarlyFlakeDetectionFaulty,
391
464
  isParallel
392
465
  })
393
466
  })
467
+ eventDataCollector = null
394
468
  return success
395
469
  }
396
470
  }
397
471
 
398
- function getWrappedRunTest (runTestFunction) {
399
- return async function (pickleId) {
400
- 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
+ }
401
482
 
402
- const testFileAbsolutePath = test.uri
483
+ const testFileAbsolutePath = pickle.uri
403
484
  const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
404
485
 
405
- if (!pickleResultByFile[testFileAbsolutePath]) { // first test in suite
406
- 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)
407
489
  isForcedToRun = isUnskippable && skippableSuites.includes(testSuitePath)
408
490
 
409
- testSuiteStartCh.publish({ testSuitePath, isUnskippable, isForcedToRun, itrCorrelationId })
491
+ testSuiteStartCh.publish({
492
+ testFileAbsolutePath,
493
+ isUnskippable,
494
+ isForcedToRun,
495
+ itrCorrelationId
496
+ })
410
497
  }
411
498
 
412
499
  let isNew = false
413
500
 
414
501
  if (isEarlyFlakeDetectionEnabled) {
415
- isNew = isNewTest(testSuitePath, test.name)
502
+ isNew = isNewTest(testSuitePath, pickle.name)
416
503
  if (isNew) {
417
- numRetriesByPickleId.set(pickleId, 0)
504
+ numRetriesByPickleId.set(pickle.id, 0)
418
505
  }
419
506
  }
420
- 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)
421
509
 
422
- const testStatuses = lastStatusByPickleId.get(pickleId)
510
+ const testStatuses = lastStatusByPickleId.get(pickle.id)
423
511
  const lastTestStatus = testStatuses[testStatuses.length - 1]
424
512
  // If it's a new test and it hasn't been skipped, we run it again
425
513
  if (isEarlyFlakeDetectionEnabled && lastTestStatus !== 'skip' && isNew) {
426
514
  for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
427
- numRetriesByPickleId.set(pickleId, retryIndex + 1)
428
- await runTestFunction.apply(this, arguments)
515
+ numRetriesByPickleId.set(pickle.id, retryIndex + 1)
516
+ runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
429
517
  }
430
518
  }
431
519
  let testStatus = lastTestStatus
432
- if (isEarlyFlakeDetectionEnabled) {
520
+ let shouldBePassedByEFD = false
521
+ if (isNew && isEarlyFlakeDetectionEnabled) {
433
522
  /**
434
523
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
435
524
  * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
@@ -439,6 +528,8 @@ function getWrappedRunTest (runTestFunction) {
439
528
  */
440
529
  testStatus = getTestStatusFromRetries(testStatuses)
441
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
442
533
  this.success = true
443
534
  }
444
535
  }
@@ -449,8 +540,9 @@ function getWrappedRunTest (runTestFunction) {
449
540
  pickleResultByFile[testFileAbsolutePath].push(testStatus)
450
541
  }
451
542
 
452
- // last test in suite
453
- 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
454
546
  const testSuiteStatus = getSuiteStatusFromTestStatuses(pickleResultByFile[testFileAbsolutePath])
455
547
  if (global.__coverage__) {
456
548
  const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
@@ -469,11 +561,15 @@ function getWrappedRunTest (runTestFunction) {
469
561
  testSuiteFinishCh.publish({ status: testSuiteStatus, testSuitePath })
470
562
  }
471
563
 
564
+ if (isNewerCucumberVersion && isEarlyFlakeDetectionEnabled && isNew) {
565
+ return shouldBePassedByEFD
566
+ }
567
+
472
568
  return runTestCaseResult
473
569
  }
474
570
  }
475
571
 
476
- function getWrappedParseWorkerMessage (parseWorkerMessageFunction) {
572
+ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion) {
477
573
  return function (worker, message) {
478
574
  // If the message is an array, it's a dd-trace message, so we need to stop cucumber processing,
479
575
  // or cucumber will throw an error
@@ -488,29 +584,43 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction) {
488
584
  }
489
585
  }
490
586
 
491
- const { jsonEnvelope } = message
492
- if (!jsonEnvelope) {
587
+ let envelope
588
+
589
+ if (isNewVersion) {
590
+ envelope = message.envelope
591
+ } else {
592
+ envelope = message.jsonEnvelope
593
+ }
594
+
595
+ if (!envelope) {
493
596
  return parseWorkerMessageFunction.apply(this, arguments)
494
597
  }
495
- let parsed = jsonEnvelope
598
+ let parsed = envelope
496
599
 
497
600
  if (typeof parsed === 'string') {
498
601
  try {
499
- parsed = JSON.parse(jsonEnvelope)
602
+ parsed = JSON.parse(envelope)
500
603
  } catch (e) {
501
604
  // ignore errors and continue
502
605
  return parseWorkerMessageFunction.apply(this, arguments)
503
606
  }
504
607
  }
608
+ let pickle
609
+
505
610
  if (parsed.testCaseStarted) {
506
- const { pickleId } = this.eventDataCollector.testCaseMap[parsed.testCaseStarted.testCaseId]
507
- 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
508
618
  const testFileAbsolutePath = pickle.uri
509
619
  // First test in suite
510
620
  if (!pickleResultByFile[testFileAbsolutePath]) {
511
621
  pickleResultByFile[testFileAbsolutePath] = []
512
622
  testSuiteStartCh.publish({
513
- testSuitePath: getTestSuitePath(testFileAbsolutePath, process.cwd())
623
+ testFileAbsolutePath
514
624
  })
515
625
  }
516
626
  }
@@ -519,14 +629,47 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction) {
519
629
 
520
630
  // after calling `parseWorkerMessageFunction`, the test status can already be read
521
631
  if (parsed.testCaseFinished) {
522
- const { pickle, worstTestStepResult } =
523
- 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
+ }
524
642
 
525
643
  const { status } = getStatusFromResultLatest(worstTestStepResult)
644
+ let isNew = false
645
+
646
+ if (isEarlyFlakeDetectionEnabled) {
647
+ isNew = isNewTest(pickle.uri, pickle.name)
648
+ }
526
649
 
527
650
  const testFileAbsolutePath = pickle.uri
528
651
  const finished = pickleResultByFile[testFileAbsolutePath]
529
- 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
+ }
530
673
 
531
674
  if (finished.length === pickleByFile[testFileAbsolutePath].length) {
532
675
  testSuiteFinishCh.publish({
@@ -556,13 +699,16 @@ addHook({
556
699
 
557
700
  // From 7.3.0 onwards, runPickle becomes runTestCase. Not executed in parallel mode.
558
701
  // `getWrappedStart` generates session start and finish events
559
- // `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".
560
705
  addHook({
561
706
  name: '@cucumber/cucumber',
562
- versions: ['>=7.3.0'],
707
+ versions: ['>=7.3.0 <11.0.0'],
563
708
  file: 'lib/runtime/index.js'
564
709
  }, (runtimePackage, frameworkVersion) => {
565
- shimmer.wrap(runtimePackage.default.prototype, 'runTestCase', runTestCase => getWrappedRunTest(runTestCase))
710
+ shimmer.wrap(runtimePackage.default.prototype, 'runTestCase', runTestCase => getWrappedRunTestCase(runTestCase))
711
+
566
712
  shimmer.wrap(runtimePackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion))
567
713
 
568
714
  return runtimePackage
@@ -570,13 +716,13 @@ addHook({
570
716
 
571
717
  // Not executed in parallel mode.
572
718
  // `getWrappedStart` generates session start and finish events
573
- // `getWrappedRunTest` generates suite start and finish events
719
+ // `getWrappedRunTestCase` generates suite start and finish events and handles EFD.
574
720
  addHook({
575
721
  name: '@cucumber/cucumber',
576
722
  versions: ['>=7.0.0 <7.3.0'],
577
723
  file: 'lib/runtime/index.js'
578
724
  }, (runtimePackage, frameworkVersion) => {
579
- shimmer.wrap(runtimePackage.default.prototype, 'runPickle', runPickle => getWrappedRunTest(runPickle))
725
+ shimmer.wrap(runtimePackage.default.prototype, 'runPickle', runPickle => getWrappedRunTestCase(runPickle))
580
726
  shimmer.wrap(runtimePackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion))
581
727
 
582
728
  return runtimePackage
@@ -584,11 +730,10 @@ addHook({
584
730
 
585
731
  // Only executed in parallel mode.
586
732
  // `getWrappedStart` generates session start and finish events
587
- // `getWrappedGiveWork` generates suite start events and sets pickleResultByFile (used by suite finish events)
588
- // `getWrappedParseWorkerMessage` generates suite finish events
733
+ // `getWrappedParseWorkerMessage` generates suite start and finish events
589
734
  addHook({
590
735
  name: '@cucumber/cucumber',
591
- versions: ['>=8.0.0'],
736
+ versions: ['>=8.0.0 <11.0.0'],
592
737
  file: 'lib/runtime/parallel/coordinator.js'
593
738
  }, (coordinatorPackage, frameworkVersion) => {
594
739
  shimmer.wrap(coordinatorPackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion, true))
@@ -599,3 +744,95 @@ addHook({
599
744
  )
600
745
  return coordinatorPackage
601
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
+ })