dd-trace 5.11.0 → 5.13.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 (40) hide show
  1. package/ci/init.js +7 -0
  2. package/ext/exporters.d.ts +2 -1
  3. package/ext/exporters.js +2 -1
  4. package/index.d.ts +14 -6
  5. package/package.json +4 -4
  6. package/packages/datadog-esbuild/index.js +8 -2
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
  8. package/packages/datadog-instrumentations/src/cucumber.js +182 -105
  9. package/packages/datadog-instrumentations/src/helpers/fetch.js +9 -4
  10. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  11. package/packages/datadog-instrumentations/src/lodash.js +31 -0
  12. package/packages/datadog-instrumentations/src/openai.js +149 -0
  13. package/packages/datadog-instrumentations/src/playwright.js +6 -1
  14. package/packages/datadog-plugin-aws-sdk/src/services/index.js +3 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/sfn.js +7 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/states.js +7 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +64 -0
  18. package/packages/datadog-plugin-cucumber/src/index.js +83 -11
  19. package/packages/datadog-plugin-fetch/src/index.js +5 -2
  20. package/packages/datadog-plugin-openai/src/index.js +159 -32
  21. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +4 -0
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +3 -0
  24. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +1 -9
  25. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -1
  26. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -2
  27. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +55 -1
  28. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/hardcoded-password-analyzer.js +13 -0
  29. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +8 -2
  30. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +6 -6
  31. package/packages/dd-trace/src/appsec/remote_config/index.js +5 -5
  32. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +56 -0
  33. package/packages/dd-trace/src/ci-visibility/exporters/{jest-worker → test-worker}/writer.js +7 -0
  34. package/packages/dd-trace/src/config.js +7 -0
  35. package/packages/dd-trace/src/exporter.js +2 -1
  36. package/packages/dd-trace/src/plugins/database.js +20 -5
  37. package/packages/dd-trace/src/plugins/util/test.js +7 -0
  38. package/packages/dd-trace/src/proxy.js +26 -6
  39. package/packages/dd-trace/src/telemetry/index.js +9 -3
  40. package/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js +0 -33
package/ci/init.js CHANGED
@@ -3,6 +3,7 @@ const tracer = require('../packages/dd-trace')
3
3
  const { isTrue } = require('../packages/dd-trace/src/util')
4
4
 
5
5
  const isJestWorker = !!process.env.JEST_WORKER_ID
6
+ const isCucumberWorker = !!process.env.CUCUMBER_WORKER_ID
6
7
 
7
8
  const options = {
8
9
  startupLogs: false,
@@ -37,6 +38,12 @@ if (isJestWorker) {
37
38
  }
38
39
  }
39
40
 
41
+ if (isCucumberWorker) {
42
+ options.experimental = {
43
+ exporter: 'cucumber_worker'
44
+ }
45
+ }
46
+
40
47
  if (shouldInit) {
41
48
  tracer.init(options)
42
49
  tracer.use('fs', false)
@@ -3,7 +3,8 @@ declare const exporters: {
3
3
  AGENT: 'agent',
4
4
  DATADOG: 'datadog',
5
5
  AGENT_PROXY: 'agent_proxy',
6
- JEST_WORKER: 'jest_worker'
6
+ JEST_WORKER: 'jest_worker',
7
+ CUCUMBER_WORKER: 'cucumber_worker'
7
8
  }
8
9
 
9
10
  export = exporters
package/ext/exporters.js CHANGED
@@ -4,5 +4,6 @@ module.exports = {
4
4
  AGENT: 'agent',
5
5
  DATADOG: 'datadog',
6
6
  AGENT_PROXY: 'agent_proxy',
7
- JEST_WORKER: 'jest_worker'
7
+ JEST_WORKER: 'jest_worker',
8
+ CUCUMBER_WORKER: 'cucumber_worker'
8
9
  }
package/index.d.ts CHANGED
@@ -940,11 +940,15 @@ declare namespace tracer {
940
940
  /** @hidden */
941
941
  interface Http extends Instrumentation {
942
942
  /**
943
- * List of URLs that should be instrumented.
943
+ * List of URLs/paths that should be instrumented.
944
+ *
945
+ * Note that when used for an http client the entry represents a full
946
+ * outbound URL (`https://example.org/api/foo`) but when used as a
947
+ * server the entry represents an inbound path (`/api/foo`).
944
948
  *
945
949
  * @default /^.*$/
946
950
  */
947
- allowlist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[];
951
+ allowlist?: string | RegExp | ((urlOrPath: string) => boolean) | (string | RegExp | ((urlOrPath: string) => boolean))[];
948
952
 
949
953
  /**
950
954
  * Deprecated in favor of `allowlist`.
@@ -952,15 +956,19 @@ declare namespace tracer {
952
956
  * @deprecated
953
957
  * @hidden
954
958
  */
955
- whitelist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[];
959
+ whitelist?: string | RegExp | ((urlOrPath: string) => boolean) | (string | RegExp | ((urlOrPath: string) => boolean))[];
956
960
 
957
961
  /**
958
- * List of URLs that should not be instrumented. Takes precedence over
962
+ * List of URLs/paths that should not be instrumented. Takes precedence over
959
963
  * allowlist if a URL matches an entry in both.
960
964
  *
965
+ * Note that when used for an http client the entry represents a full
966
+ * outbound URL (`https://example.org/api/foo`) but when used as a
967
+ * server the entry represents an inbound path (`/api/foo`).
968
+ *
961
969
  * @default []
962
970
  */
963
- blocklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[];
971
+ blocklist?: string | RegExp | ((urlOrPath: string) => boolean) | (string | RegExp | ((urlOrPath: string) => boolean))[];
964
972
 
965
973
  /**
966
974
  * Deprecated in favor of `blocklist`.
@@ -968,7 +976,7 @@ declare namespace tracer {
968
976
  * @deprecated
969
977
  * @hidden
970
978
  */
971
- blacklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[];
979
+ blacklist?: string | RegExp | ((urlOrPath: string) => boolean) | (string | RegExp | ((urlOrPath: string) => boolean))[];
972
980
 
973
981
  /**
974
982
  * An array of headers to include in the span metadata.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.11.0",
3
+ "version": "5.13.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -71,8 +71,8 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "@datadog/native-appsec": "7.1.1",
74
- "@datadog/native-iast-rewriter": "2.3.0",
75
- "@datadog/native-iast-taint-tracking": "1.7.0",
74
+ "@datadog/native-iast-rewriter": "2.3.1",
75
+ "@datadog/native-iast-taint-tracking": "2.1.0",
76
76
  "@datadog/native-metrics": "^2.0.0",
77
77
  "@datadog/pprof": "5.2.0",
78
78
  "@datadog/sketches-js": "^2.1.0",
@@ -81,7 +81,7 @@
81
81
  "crypto-randomuuid": "^1.0.0",
82
82
  "dc-polyfill": "^0.1.4",
83
83
  "ignore": "^5.2.4",
84
- "import-in-the-middle": "^1.7.3",
84
+ "import-in-the-middle": "^1.7.4",
85
85
  "int64-buffer": "^0.1.9",
86
86
  "ipaddr.js": "^2.1.0",
87
87
  "istanbul-lib-coverage": "3.2.0",
@@ -75,7 +75,10 @@ module.exports.setup = function (build) {
75
75
  try {
76
76
  fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir)
77
77
  } catch (err) {
78
- console.warn(`MISSING: Unable to find "${args.path}". Is the package dead code?`)
78
+ if (DEBUG) {
79
+ console.warn(`Warning: Unable to find "${args.path}".` +
80
+ "Unless it's dead code this could cause a problem at runtime.")
81
+ }
79
82
  return
80
83
  }
81
84
  const extracted = extractPackageAndModulePath(fullPathToModule)
@@ -93,7 +96,10 @@ module.exports.setup = function (build) {
93
96
  } catch (err) {
94
97
  if (err.code === 'MODULE_NOT_FOUND') {
95
98
  if (!internal) {
96
- console.warn(`MISSING: Unable to find "${extracted.pkg}/package.json". Is the package dead code?`)
99
+ if (DEBUG) {
100
+ console.warn(`Warning: Unable to find "${extracted.pkg}/package.json".` +
101
+ "Unless it's dead code this could cause a problem at runtime.")
102
+ }
97
103
  }
98
104
  return
99
105
  } else {
@@ -162,8 +162,11 @@ function getChannelSuffix (name) {
162
162
  'lambda',
163
163
  'redshift',
164
164
  's3',
165
+ 'sfn',
165
166
  'sns',
166
- 'sqs'
167
+ 'sqs',
168
+ 'states',
169
+ 'stepfunctions'
167
170
  ].includes(name)
168
171
  ? name
169
172
  : 'default'
@@ -22,6 +22,8 @@ const skippableSuitesCh = channel('ci:cucumber:test-suite:skippable')
22
22
  const sessionStartCh = channel('ci:cucumber:session:start')
23
23
  const sessionFinishCh = channel('ci:cucumber:session:finish')
24
24
 
25
+ const workerReportTraceCh = channel('ci:cucumber:worker-report:trace')
26
+
25
27
  const itrSkippedSuitesCh = channel('ci:cucumber:itr:skipped-suites')
26
28
 
27
29
  const {
@@ -29,7 +31,8 @@ const {
29
31
  resetCoverage,
30
32
  mergeCoverage,
31
33
  fromCoverageMapToCoverage,
32
- getTestSuitePath
34
+ getTestSuitePath,
35
+ CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
33
36
  } = require('../../dd-trace/src/plugins/util/test')
34
37
 
35
38
  const isMarkedAsUnskippable = (pickle) => {
@@ -47,13 +50,19 @@ const numRetriesByPickleId = new Map()
47
50
 
48
51
  let pickleByFile = {}
49
52
  const pickleResultByFile = {}
53
+
54
+ const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
55
+
50
56
  let skippableSuites = []
51
57
  let itrCorrelationId = ''
52
58
  let isForcedToRun = false
53
59
  let isUnskippable = false
60
+ let isSuitesSkippingEnabled = false
54
61
  let isEarlyFlakeDetectionEnabled = false
55
62
  let earlyFlakeDetectionNumRetries = 0
56
63
  let knownTests = []
64
+ let skippedSuites = []
65
+ let isSuitesSkipped = false
57
66
 
58
67
  function getSuiteStatusFromTestStatuses (testStatuses) {
59
68
  if (testStatuses.some(status => status === 'fail')) {
@@ -106,6 +115,43 @@ function getTestStatusFromRetries (testStatuses) {
106
115
  return 'pass'
107
116
  }
108
117
 
118
+ function getChannelPromise (channelToPublishTo) {
119
+ return new Promise(resolve => {
120
+ sessionAsyncResource.runInAsyncScope(() => {
121
+ channelToPublishTo.publish({ onDone: resolve })
122
+ })
123
+ })
124
+ }
125
+
126
+ function getFilteredPickles (runtime, suitesToSkip) {
127
+ return runtime.pickleIds.reduce((acc, pickleId) => {
128
+ const test = runtime.eventDataCollector.getPickle(pickleId)
129
+ const testSuitePath = getTestSuitePath(test.uri, process.cwd())
130
+
131
+ const isUnskippable = isMarkedAsUnskippable(test)
132
+ const isSkipped = suitesToSkip.includes(testSuitePath)
133
+
134
+ if (isSkipped && !isUnskippable) {
135
+ acc.skippedSuites.add(testSuitePath)
136
+ } else {
137
+ acc.picklesToRun.push(pickleId)
138
+ }
139
+ return acc
140
+ }, { skippedSuites: new Set(), picklesToRun: [] })
141
+ }
142
+
143
+ function getPickleByFile (runtime) {
144
+ return runtime.pickleIds.reduce((acc, pickleId) => {
145
+ const test = runtime.eventDataCollector.getPickle(pickleId)
146
+ if (acc[test.uri]) {
147
+ acc[test.uri].push(test)
148
+ } else {
149
+ acc[test.uri] = [test]
150
+ }
151
+ return acc
152
+ }, {})
153
+ }
154
+
109
155
  function wrapRun (pl, isLatestVersion) {
110
156
  if (patched.has(pl)) return
111
157
 
@@ -125,7 +171,8 @@ function wrapRun (pl, isLatestVersion) {
125
171
  testStartCh.publish({
126
172
  testName: this.pickle.name,
127
173
  testFileAbsolutePath,
128
- testSourceLine
174
+ testSourceLine,
175
+ isParallel: !!process.env.CUCUMBER_WORKER_ID
129
176
  })
130
177
  try {
131
178
  const promise = run.apply(this, arguments)
@@ -193,12 +240,6 @@ function wrapRun (pl, isLatestVersion) {
193
240
  }
194
241
 
195
242
  function pickleHook (PickleRunner) {
196
- if (process.env.CUCUMBER_WORKER_ID) {
197
- // Parallel mode is not supported
198
- log.warn('Unable to initialize CI Visibility because Cucumber is running in parallel mode.')
199
- return PickleRunner
200
- }
201
-
202
243
  const pl = PickleRunner.default
203
244
 
204
245
  wrapRun(pl, false)
@@ -207,12 +248,6 @@ function pickleHook (PickleRunner) {
207
248
  }
208
249
 
209
250
  function testCaseHook (TestCaseRunner) {
210
- if (process.env.CUCUMBER_WORKER_ID) {
211
- // Parallel mode is not supported
212
- log.warn('Unable to initialize CI Visibility because Cucumber is running in parallel mode.')
213
- return TestCaseRunner
214
- }
215
-
216
251
  const pl = TestCaseRunner.default
217
252
 
218
253
  wrapRun(pl, true)
@@ -220,76 +255,21 @@ function testCaseHook (TestCaseRunner) {
220
255
  return TestCaseRunner
221
256
  }
222
257
 
223
- addHook({
224
- name: '@cucumber/cucumber',
225
- versions: ['7.0.0 - 7.2.1'],
226
- file: 'lib/runtime/pickle_runner.js'
227
- }, pickleHook)
228
-
229
- addHook({
230
- name: '@cucumber/cucumber',
231
- versions: ['>=7.3.0'],
232
- file: 'lib/runtime/test_case_runner.js'
233
- }, testCaseHook)
234
-
235
- function getFilteredPickles (runtime, suitesToSkip) {
236
- return runtime.pickleIds.reduce((acc, pickleId) => {
237
- const test = runtime.eventDataCollector.getPickle(pickleId)
238
- const testSuitePath = getTestSuitePath(test.uri, process.cwd())
239
-
240
- const isUnskippable = isMarkedAsUnskippable(test)
241
- const isSkipped = suitesToSkip.includes(testSuitePath)
242
-
243
- if (isSkipped && !isUnskippable) {
244
- acc.skippedSuites.add(testSuitePath)
245
- } else {
246
- acc.picklesToRun.push(pickleId)
247
- }
248
- return acc
249
- }, { skippedSuites: new Set(), picklesToRun: [] })
250
- }
251
-
252
- function getPickleByFile (runtime) {
253
- return runtime.pickleIds.reduce((acc, pickleId) => {
254
- const test = runtime.eventDataCollector.getPickle(pickleId)
255
- if (acc[test.uri]) {
256
- acc[test.uri].push(test)
257
- } else {
258
- acc[test.uri] = [test]
259
- }
260
- return acc
261
- }, {})
262
- }
263
-
264
- function getWrappedStart (start, frameworkVersion) {
258
+ function getWrappedStart (start, frameworkVersion, isParallel = false) {
265
259
  return async function () {
266
260
  if (!libraryConfigurationCh.hasSubscribers) {
267
261
  return start.apply(this, arguments)
268
262
  }
269
- const asyncResource = new AsyncResource('bound-anonymous-fn')
270
- let onDone
271
-
272
- const configPromise = new Promise(resolve => {
273
- onDone = resolve
274
- })
275
-
276
- asyncResource.runInAsyncScope(() => {
277
- libraryConfigurationCh.publish({ onDone })
278
- })
263
+ let errorSkippableRequest
279
264
 
280
- const configurationResponse = await configPromise
265
+ const configurationResponse = await getChannelPromise(libraryConfigurationCh)
281
266
 
282
267
  isEarlyFlakeDetectionEnabled = configurationResponse.libraryConfig?.isEarlyFlakeDetectionEnabled
283
268
  earlyFlakeDetectionNumRetries = configurationResponse.libraryConfig?.earlyFlakeDetectionNumRetries
269
+ isSuitesSkippingEnabled = configurationResponse.libraryConfig?.isSuitesSkippingEnabled
284
270
 
285
271
  if (isEarlyFlakeDetectionEnabled) {
286
- const knownTestsPromise = new Promise(resolve => {
287
- onDone = resolve
288
- })
289
- asyncResource.runInAsyncScope(() => {
290
- knownTestsCh.publish({ onDone })
291
- })
292
- const knownTestsResponse = await knownTestsPromise
272
+ const knownTestsResponse = await getChannelPromise(knownTestsCh)
293
273
  if (!knownTestsResponse.err) {
294
274
  knownTests = knownTestsResponse.knownTests
295
275
  } else {
@@ -297,35 +277,26 @@ function getWrappedStart (start, frameworkVersion) {
297
277
  }
298
278
  }
299
279
 
300
- const skippableSuitesPromise = new Promise(resolve => {
301
- onDone = resolve
302
- })
280
+ if (isSuitesSkippingEnabled) {
281
+ const skippableResponse = await getChannelPromise(skippableSuitesCh)
303
282
 
304
- asyncResource.runInAsyncScope(() => {
305
- skippableSuitesCh.publish({ onDone })
306
- })
283
+ errorSkippableRequest = skippableResponse.err
284
+ skippableSuites = skippableResponse.skippableSuites
307
285
 
308
- const skippableResponse = await skippableSuitesPromise
286
+ if (!errorSkippableRequest) {
287
+ const filteredPickles = getFilteredPickles(this, skippableSuites)
288
+ const { picklesToRun } = filteredPickles
289
+ isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
309
290
 
310
- const err = skippableResponse.err
311
- skippableSuites = skippableResponse.skippableSuites
291
+ log.debug(
292
+ () => `${picklesToRun.length} out of ${this.pickleIds.length} suites are going to run.`
293
+ )
312
294
 
313
- let skippedSuites = []
314
- let isSuitesSkipped = false
295
+ this.pickleIds = picklesToRun
315
296
 
316
- if (!err) {
317
- const filteredPickles = getFilteredPickles(this, skippableSuites)
318
- const { picklesToRun } = filteredPickles
319
- isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
320
-
321
- log.debug(
322
- () => `${picklesToRun.length} out of ${this.pickleIds.length} suites are going to run.`
323
- )
324
-
325
- this.pickleIds = picklesToRun
326
-
327
- skippedSuites = Array.from(filteredPickles.skippedSuites)
328
- itrCorrelationId = skippableResponse.itrCorrelationId
297
+ skippedSuites = Array.from(filteredPickles.skippedSuites)
298
+ itrCorrelationId = skippableResponse.itrCorrelationId
299
+ }
329
300
  }
330
301
 
331
302
  pickleByFile = getPickleByFile(this)
@@ -333,11 +304,11 @@ function getWrappedStart (start, frameworkVersion) {
333
304
  const processArgv = process.argv.slice(2).join(' ')
334
305
  const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}`
335
306
 
336
- asyncResource.runInAsyncScope(() => {
307
+ sessionAsyncResource.runInAsyncScope(() => {
337
308
  sessionStartCh.publish({ command, frameworkVersion })
338
309
  })
339
310
 
340
- if (!err && skippedSuites.length) {
311
+ if (!errorSkippableRequest && skippedSuites.length) {
341
312
  itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
342
313
  }
343
314
 
@@ -355,7 +326,7 @@ function getWrappedStart (start, frameworkVersion) {
355
326
  global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
356
327
  }
357
328
 
358
- asyncResource.runInAsyncScope(() => {
329
+ sessionAsyncResource.runInAsyncScope(() => {
359
330
  sessionFinishCh.publish({
360
331
  status: success ? 'pass' : 'fail',
361
332
  isSuitesSkipped,
@@ -363,7 +334,8 @@ function getWrappedStart (start, frameworkVersion) {
363
334
  numSkippedSuites: skippedSuites.length,
364
335
  hasUnskippableSuites: isUnskippable,
365
336
  hasForcedToRunSuites: isForcedToRun,
366
- isEarlyFlakeDetectionEnabled
337
+ isEarlyFlakeDetectionEnabled,
338
+ isParallel
367
339
  })
368
340
  })
369
341
  return success
@@ -432,7 +404,8 @@ function getWrappedRunTest (runTestFunction) {
432
404
 
433
405
  testSuiteCodeCoverageCh.publish({
434
406
  coverageFiles,
435
- suiteFile: testFileAbsolutePath
407
+ suiteFile: testFileAbsolutePath,
408
+ testSuitePath
436
409
  })
437
410
  // We need to reset coverage to get a code coverage per suite
438
411
  // Before that, we preserve the original coverage
@@ -440,14 +413,97 @@ function getWrappedRunTest (runTestFunction) {
440
413
  resetCoverage(global.__coverage__)
441
414
  }
442
415
 
443
- testSuiteFinishCh.publish(testSuiteStatus)
416
+ testSuiteFinishCh.publish({ status: testSuiteStatus, testSuitePath })
444
417
  }
445
418
 
446
419
  return runTestCaseResult
447
420
  }
448
421
  }
449
422
 
450
- // From 7.3.0 onwards, runPickle becomes runTestCase
423
+ function getWrappedParseWorkerMessage (parseWorkerMessageFunction) {
424
+ return function (worker, message) {
425
+ // If the message is an array, it's a dd-trace message, so we need to stop cucumber processing,
426
+ // or cucumber will throw an error
427
+ // TODO: identify the message better
428
+ if (Array.isArray(message)) {
429
+ const [messageCode, payload] = message
430
+ if (messageCode === CUCUMBER_WORKER_TRACE_PAYLOAD_CODE) {
431
+ sessionAsyncResource.runInAsyncScope(() => {
432
+ workerReportTraceCh.publish(payload)
433
+ })
434
+ return
435
+ }
436
+ }
437
+
438
+ const { jsonEnvelope } = message
439
+ if (!jsonEnvelope) {
440
+ return parseWorkerMessageFunction.apply(this, arguments)
441
+ }
442
+ let parsed = jsonEnvelope
443
+
444
+ if (typeof parsed === 'string') {
445
+ try {
446
+ parsed = JSON.parse(jsonEnvelope)
447
+ } catch (e) {
448
+ // ignore errors and continue
449
+ return parseWorkerMessageFunction.apply(this, arguments)
450
+ }
451
+ }
452
+ if (parsed.testCaseStarted) {
453
+ const { pickleId } = this.eventDataCollector.testCaseMap[parsed.testCaseStarted.testCaseId]
454
+ const pickle = this.eventDataCollector.getPickle(pickleId)
455
+ const testFileAbsolutePath = pickle.uri
456
+ // First test in suite
457
+ if (!pickleResultByFile[testFileAbsolutePath]) {
458
+ pickleResultByFile[testFileAbsolutePath] = []
459
+ testSuiteStartCh.publish({
460
+ testSuitePath: getTestSuitePath(testFileAbsolutePath, process.cwd())
461
+ })
462
+ }
463
+ }
464
+
465
+ const parseWorkerResponse = parseWorkerMessageFunction.apply(this, arguments)
466
+
467
+ // after calling `parseWorkerMessageFunction`, the test status can already be read
468
+ if (parsed.testCaseFinished) {
469
+ const { pickle, worstTestStepResult } =
470
+ this.eventDataCollector.getTestCaseAttempt(parsed.testCaseFinished.testCaseStartedId)
471
+
472
+ const { status } = getStatusFromResultLatest(worstTestStepResult)
473
+
474
+ const testFileAbsolutePath = pickle.uri
475
+ const finished = pickleResultByFile[testFileAbsolutePath]
476
+ finished.push(status)
477
+
478
+ if (finished.length === pickleByFile[testFileAbsolutePath].length) {
479
+ testSuiteFinishCh.publish({
480
+ status: getSuiteStatusFromTestStatuses(finished),
481
+ testSuitePath: getTestSuitePath(testFileAbsolutePath, process.cwd())
482
+ })
483
+ }
484
+ }
485
+
486
+ return parseWorkerResponse
487
+ }
488
+ }
489
+
490
+ // Test start / finish for older versions. The only hook executed in workers when in parallel mode
491
+ addHook({
492
+ name: '@cucumber/cucumber',
493
+ versions: ['7.0.0 - 7.2.1'],
494
+ file: 'lib/runtime/pickle_runner.js'
495
+ }, pickleHook)
496
+
497
+ // Test start / finish for newer versions. The only hook executed in workers when in parallel mode
498
+ addHook({
499
+ name: '@cucumber/cucumber',
500
+ versions: ['>=7.3.0'],
501
+ file: 'lib/runtime/test_case_runner.js'
502
+ }, testCaseHook)
503
+
504
+ // From 7.3.0 onwards, runPickle becomes runTestCase. Not executed in parallel mode.
505
+ // `getWrappedStart` generates session start and finish events
506
+ // `getWrappedRunTest` generates suite start and finish events
451
507
  addHook({
452
508
  name: '@cucumber/cucumber',
453
509
  versions: ['>=7.3.0'],
@@ -459,6 +515,9 @@ addHook({
459
515
  return runtimePackage
460
516
  })
461
517
 
518
+ // Not executed in parallel mode.
519
+ // `getWrappedStart` generates session start and finish events
520
+ // `getWrappedRunTest` generates suite start and finish events
462
521
  addHook({
463
522
  name: '@cucumber/cucumber',
464
523
  versions: ['>=7.0.0 <7.3.0'],
@@ -469,3 +528,21 @@ addHook({
469
528
 
470
529
  return runtimePackage
471
530
  })
531
+
532
+ // Only executed in parallel mode.
533
+ // `getWrappedStart` generates session start and finish events
534
+ // `getWrappedGiveWork` generates suite start events and sets pickleResultByFile (used by suite finish events)
535
+ // `getWrappedParseWorkerMessage` generates suite finish events
536
+ addHook({
537
+ name: '@cucumber/cucumber',
538
+ versions: ['>=8.0.0'],
539
+ file: 'lib/runtime/parallel/coordinator.js'
540
+ }, (coordinatorPackage, frameworkVersion) => {
541
+ shimmer.wrap(coordinatorPackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion, true))
542
+ shimmer.wrap(
543
+ coordinatorPackage.default.prototype,
544
+ 'parseWorkerMessage',
545
+ parseWorkerMessage => getWrappedParseWorkerMessage(parseWorkerMessage)
546
+ )
547
+ return coordinatorPackage
548
+ })
@@ -7,11 +7,16 @@ exports.createWrapFetch = function createWrapFetch (Request, ch) {
7
7
  return function (input, init) {
8
8
  if (!ch.start.hasSubscribers) return fetch.apply(this, arguments)
9
9
 
10
- const req = new Request(input, init)
11
- const headers = req.headers
12
- const ctx = { req, headers }
10
+ if (input instanceof Request) {
11
+ const ctx = { req: input }
13
12
 
14
- return ch.tracePromise(() => fetch.call(this, req, { headers: ctx.headers }), ctx)
13
+ return ch.tracePromise(() => fetch.call(this, input, init), ctx)
14
+ } else {
15
+ const req = new Request(input, init)
16
+ const ctx = { req }
17
+
18
+ return ch.tracePromise(() => fetch.call(this, req), ctx)
19
+ }
15
20
  }
16
21
  }
17
22
  }
@@ -66,6 +66,7 @@ module.exports = {
66
66
  kafkajs: () => require('../kafkajs'),
67
67
  ldapjs: () => require('../ldapjs'),
68
68
  'limitd-client': () => require('../limitd-client'),
69
+ lodash: () => require('../lodash'),
69
70
  mariadb: () => require('../mariadb'),
70
71
  memcached: () => require('../memcached'),
71
72
  'microgateway-core': () => require('../microgateway-core'),
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ const { channel, addHook } = require('./helpers/instrument')
4
+
5
+ const shimmer = require('../../datadog-shimmer')
6
+
7
+ addHook({ name: 'lodash', versions: ['>=4'] }, lodash => {
8
+ const lodashOperationCh = channel('datadog:lodash:operation')
9
+
10
+ const instrumentedLodashFn = ['trim', 'trimStart', 'trimEnd', 'toLower', 'toUpper', 'join']
11
+
12
+ shimmer.massWrap(
13
+ lodash,
14
+ instrumentedLodashFn,
15
+ lodashFn => {
16
+ return function () {
17
+ if (!lodashOperationCh.hasSubscribers) {
18
+ return lodashFn.apply(this, arguments)
19
+ }
20
+
21
+ const result = lodashFn.apply(this, arguments)
22
+ const message = { operation: lodashFn.name, arguments, result }
23
+ lodashOperationCh.publish(message)
24
+
25
+ return message.result
26
+ }
27
+ }
28
+ )
29
+
30
+ return lodash
31
+ })