dd-trace 4.46.0 → 4.47.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
@@ -404,7 +404,7 @@ addHook({
404
404
 
405
405
  addHook({
406
406
  name: '@jest/test-sequencer',
407
- versions: ['>=24.8.0']
407
+ versions: ['>=28']
408
408
  }, (sequencerPackage, frameworkVersion) => {
409
409
  shimmer.wrap(sequencerPackage.default.prototype, 'shard', shard => function () {
410
410
  const shardedTests = shard.apply(this, arguments)
@@ -648,6 +648,7 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
648
648
  testSuiteStartCh.publish({
649
649
  testSuite: environment.testSuite,
650
650
  testEnvironmentOptions: environment.testEnvironmentOptions,
651
+ testSourceFile: environment.testSourceFile,
651
652
  displayName: environment.displayName,
652
653
  frameworkVersion: jestVersion
653
654
  })
@@ -765,6 +766,7 @@ addHook({
765
766
  _ddTestModuleId,
766
767
  _ddTestSessionId,
767
768
  _ddTestCommand,
769
+ _ddTestSessionName,
768
770
  _ddForcedToRun,
769
771
  _ddUnskippable,
770
772
  _ddItrCorrelationId,
@@ -17,6 +17,10 @@ const consumerCommitCh = channel('apm:kafkajs:consume:commit')
17
17
  const consumerFinishCh = channel('apm:kafkajs:consume:finish')
18
18
  const consumerErrorCh = channel('apm:kafkajs:consume:error')
19
19
 
20
+ const batchConsumerStartCh = channel('apm:kafkajs:consume-batch:start')
21
+ const batchConsumerFinishCh = channel('apm:kafkajs:consume-batch:finish')
22
+ const batchConsumerErrorCh = channel('apm:kafkajs:consume-batch:error')
23
+
20
24
  function commitsFromEvent (event) {
21
25
  const { payload: { groupId, topics } } = event
22
26
  const commitList = []
@@ -96,6 +100,17 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
96
100
  return createConsumer.apply(this, arguments)
97
101
  }
98
102
 
103
+ const eachMessageExtractor = (args) => {
104
+ const { topic, partition, message } = args[0]
105
+ return { topic, partition, message, groupId }
106
+ }
107
+
108
+ const eachBatchExtractor = (args) => {
109
+ const { batch } = args[0]
110
+ const { topic, partition, messages } = batch
111
+ return { topic, partition, messages, groupId }
112
+ }
113
+
99
114
  const consumer = createConsumer.apply(this, arguments)
100
115
 
101
116
  consumer.on(consumer.events.COMMIT_OFFSETS, commitsFromEvent)
@@ -103,43 +118,64 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
103
118
  const run = consumer.run
104
119
 
105
120
  const groupId = arguments[0].groupId
106
- consumer.run = function ({ eachMessage, ...runArgs }) {
107
- if (typeof eachMessage !== 'function') return run({ eachMessage, ...runArgs })
121
+ consumer.run = function ({ eachMessage, eachBatch, ...runArgs }) {
122
+ eachMessage = wrapFunction(
123
+ eachMessage,
124
+ consumerStartCh,
125
+ consumerFinishCh,
126
+ consumerErrorCh,
127
+ eachMessageExtractor
128
+ )
129
+
130
+ eachBatch = wrapFunction(
131
+ eachBatch,
132
+ batchConsumerStartCh,
133
+ batchConsumerFinishCh,
134
+ batchConsumerErrorCh,
135
+ eachBatchExtractor
136
+ )
108
137
 
109
138
  return run({
110
- eachMessage: function (...eachMessageArgs) {
111
- const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
112
- return innerAsyncResource.runInAsyncScope(() => {
113
- const { topic, partition, message } = eachMessageArgs[0]
114
- consumerStartCh.publish({ topic, partition, message, groupId })
115
- try {
116
- const result = eachMessage.apply(this, eachMessageArgs)
117
- if (result && typeof result.then === 'function') {
118
- result.then(
119
- innerAsyncResource.bind(() => consumerFinishCh.publish(undefined)),
120
- innerAsyncResource.bind(err => {
121
- if (err) {
122
- consumerErrorCh.publish(err)
123
- }
124
- consumerFinishCh.publish(undefined)
125
- })
126
- )
127
- } else {
128
- consumerFinishCh.publish(undefined)
129
- }
130
-
131
- return result
132
- } catch (e) {
133
- consumerErrorCh.publish(e)
134
- consumerFinishCh.publish(undefined)
135
- throw e
136
- }
137
- })
138
- },
139
+ eachMessage,
140
+ eachBatch,
139
141
  ...runArgs
140
142
  })
141
143
  }
144
+
142
145
  return consumer
143
146
  })
144
147
  return Kafka
145
148
  })
149
+
150
+ const wrapFunction = (fn, startCh, finishCh, errorCh, extractArgs) => {
151
+ return typeof fn === 'function'
152
+ ? function (...args) {
153
+ const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
154
+ return innerAsyncResource.runInAsyncScope(() => {
155
+ const extractedArgs = extractArgs(args)
156
+ startCh.publish(extractedArgs)
157
+ try {
158
+ const result = fn.apply(this, args)
159
+ if (result && typeof result.then === 'function') {
160
+ result.then(
161
+ innerAsyncResource.bind(() => finishCh.publish(undefined)),
162
+ innerAsyncResource.bind(err => {
163
+ if (err) {
164
+ errorCh.publish(err)
165
+ }
166
+ finishCh.publish(undefined)
167
+ })
168
+ )
169
+ } else {
170
+ finishCh.publish(undefined)
171
+ }
172
+ return result
173
+ } catch (e) {
174
+ errorCh.publish(e)
175
+ finishCh.publish(undefined)
176
+ throw e
177
+ }
178
+ })
179
+ }
180
+ : fn
181
+ }
@@ -8,7 +8,9 @@ const routeChannel = channel('apm:microgateway-core:request:route')
8
8
  const errorChannel = channel('apm:microgateway-core:request:error')
9
9
 
10
10
  const name = 'microgateway-core'
11
- const versions = ['>=2.1']
11
+
12
+ // TODO Remove " <=3.0.0" when "volos-util-apigee" module is fixed
13
+ const versions = ['>=2.1 <=3.0.0']
12
14
  const requestResources = new WeakMap()
13
15
 
14
16
  function wrapConfigProxyFactory (configProxyFactory) {
@@ -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,22 +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
220
- config.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
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
221
229
 
222
- if (isEarlyFlakeDetectionEnabled) {
230
+ if (config.isEarlyFlakeDetectionEnabled) {
223
231
  knownTestsCh.publish({
224
232
  onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
225
233
  })
226
- } else if (isSuitesSkippingEnabled) {
234
+ } else if (config.isSuitesSkippingEnabled) {
227
235
  skippableSuitesCh.publish({
228
236
  onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
229
237
  })
@@ -251,8 +259,8 @@ addHook({
251
259
  return run.apply(this, arguments)
252
260
  }
253
261
 
254
- // `options.delay` does not work in parallel mode, so ITR and EFD can't work.
255
- // 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.
256
264
  this.options.delay = true
257
265
 
258
266
  const runner = run.apply(this, arguments)
@@ -264,7 +272,19 @@ addHook({
264
272
  }
265
273
  })
266
274
 
267
- 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
+ }
268
288
  if (getCodeCoverageCh.hasSubscribers) {
269
289
  getCodeCoverageCh.publish({
270
290
  onDone: (receivedCodeCoverage) => {
@@ -282,9 +302,6 @@ addHook({
282
302
  return Mocha
283
303
  })
284
304
 
285
- // Only used to set `mocha.options.delay` to true in serial mode. When the mocha CLI is used,
286
- // setting options.delay in Mocha#run is not enough to delay the execution.
287
- // TODO: modify this hook to grab the data in parallel mode, so that ITR and EFD can work.
288
305
  addHook({
289
306
  name: 'mocha',
290
307
  versions: ['>=5.2.0'],
@@ -294,15 +311,20 @@ addHook({
294
311
  if (!testStartCh.hasSubscribers) {
295
312
  return runMocha.apply(this, arguments)
296
313
  }
297
-
298
314
  const mocha = arguments[0]
315
+
299
316
  /**
300
317
  * This attaches `run` to the global context, which we'll call after
301
- * 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.
302
323
  */
303
324
  if (!mocha.options.parallel) {
304
325
  mocha.options.delay = true
305
326
  }
327
+
306
328
  return runMocha.apply(this, arguments)
307
329
  })
308
330
  return run
@@ -319,18 +341,7 @@ addHook({
319
341
 
320
342
  patched.add(Runner)
321
343
 
322
- shimmer.wrap(Runner.prototype, 'runTests', runTests => function (suite, fn) {
323
- if (isEarlyFlakeDetectionEnabled) {
324
- // by the time we reach `this.on('test')`, it is too late. We need to add retries here
325
- suite.tests.forEach(test => {
326
- if (!test.isPending() && isNewTest(test, knownTests)) {
327
- test._ddIsNew = true
328
- retryTest(test, earlyFlakeDetectionNumRetries)
329
- }
330
- })
331
- }
332
- return runTests.apply(this, arguments)
333
- })
344
+ shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
334
345
 
335
346
  shimmer.wrap(Runner.prototype, 'run', run => function () {
336
347
  if (!testStartCh.hasSubscribers) {
@@ -514,10 +525,10 @@ addHook({
514
525
  // Used to start and finish test session and test module
515
526
  addHook({
516
527
  name: 'mocha',
517
- versions: ['>=5.2.0'],
528
+ versions: ['>=8.0.0'],
518
529
  file: 'lib/nodejs/parallel-buffered-runner.js'
519
530
  }, (ParallelBufferedRunner, frameworkVersion) => {
520
- shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function () {
531
+ shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function (cb, { files }) {
521
532
  if (!testStartCh.hasSubscribers) {
522
533
  return run.apply(this, arguments)
523
534
  }
@@ -525,8 +536,82 @@ addHook({
525
536
  this.once('start', getOnStartHandler(true, frameworkVersion))
526
537
  this.once('end', getOnEndHandler(true))
527
538
 
528
- 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
529
556
  })
530
557
 
531
558
  return ParallelBufferedRunner
532
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
+ })
@@ -24,6 +24,7 @@ const originalFns = new WeakMap()
24
24
  const testToStartLine = new WeakMap()
25
25
  const testFileToSuiteAr = new Map()
26
26
  const wrappedFunctions = new WeakSet()
27
+ const newTests = {}
27
28
 
28
29
  function isNewTest (test, knownTests) {
29
30
  const testSuite = getTestSuitePath(test.file, process.cwd())
@@ -151,7 +152,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
151
152
  return RunnablePackage
152
153
  }
153
154
 
154
- function getOnTestHandler (isMain, newTests) {
155
+ function getOnTestHandler (isMain) {
155
156
  return function (test) {
156
157
  const testStartLine = testToStartLine.get(test)
157
158
  const asyncResource = new AsyncResource('bound-anonymous-fn')
@@ -179,22 +180,22 @@ function getOnTestHandler (isMain, newTests) {
179
180
  testStartLine
180
181
  }
181
182
 
182
- if (isMain) {
183
- testInfo.isNew = isNew
184
- testInfo.isEfdRetry = isEfdRetry
185
- // We want to store the result of the new tests
186
- if (isNew) {
187
- const testFullName = getTestFullName(test)
188
- if (newTests[testFullName]) {
189
- newTests[testFullName].push(test)
190
- } else {
191
- newTests[testFullName] = [test]
192
- }
193
- }
194
- } else {
183
+ if (!isMain) {
195
184
  testInfo.isParallel = true
196
185
  }
197
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
+
198
199
  asyncResource.runInAsyncScope(() => {
199
200
  testStartCh.publish(testInfo)
200
201
  })
@@ -327,6 +328,23 @@ function getOnPendingHandler () {
327
328
  }
328
329
  }
329
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
+
330
348
  module.exports = {
331
349
  isNewTest,
332
350
  retryTest,
@@ -345,5 +363,7 @@ module.exports = {
345
363
  getOnHookEndHandler,
346
364
  getOnFailHandler,
347
365
  getOnPendingHandler,
348
- testFileToSuiteAr
366
+ testFileToSuiteAr,
367
+ getRunTestsWrapper,
368
+ newTests
349
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()
@@ -165,10 +165,12 @@ function addStreamedChunk (content, chunk) {
165
165
 
166
166
  if (tools) {
167
167
  oldChoice.delta.tool_calls = tools.map((newTool, toolIdx) => {
168
- const oldTool = oldChoice.delta.tool_calls[toolIdx]
168
+ const oldTool = oldChoice.delta.tool_calls?.[toolIdx]
169
169
 
170
170
  if (oldTool) {
171
171
  oldTool.function.arguments += newTool.function.arguments
172
+ } else {
173
+ return newTool
172
174
  }
173
175
 
174
176
  return oldTool
@@ -247,7 +249,7 @@ function wrapStreamIterator (response, options, n, ctx) {
247
249
  return res
248
250
  })
249
251
  .catch(err => {
250
- finish(undefined, err)
252
+ finish(ctx, undefined, err)
251
253
 
252
254
  throw err
253
255
  })