dd-trace 4.11.1 → 4.12.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 (29) hide show
  1. package/index.d.ts +43 -0
  2. package/package.json +2 -1
  3. package/packages/datadog-esbuild/index.js +10 -4
  4. package/packages/datadog-instrumentations/src/cucumber.js +30 -11
  5. package/packages/datadog-instrumentations/src/jest.js +22 -11
  6. package/packages/datadog-instrumentations/src/mocha.js +30 -8
  7. package/packages/datadog-instrumentations/src/next.js +16 -14
  8. package/packages/datadog-instrumentations/src/pg.js +46 -0
  9. package/packages/datadog-plugin-cucumber/src/index.js +14 -2
  10. package/packages/datadog-plugin-cypress/src/plugin.js +17 -8
  11. package/packages/datadog-plugin-jest/src/index.js +10 -2
  12. package/packages/datadog-plugin-jest/src/util.js +10 -4
  13. package/packages/datadog-plugin-mocha/src/index.js +14 -2
  14. package/packages/datadog-plugin-next/src/index.js +11 -3
  15. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +19 -4
  16. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
  17. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
  18. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +120 -10
  19. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
  20. package/packages/dd-trace/src/dogstatsd.js +65 -5
  21. package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
  22. package/packages/dd-trace/src/opentracing/span.js +13 -13
  23. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  24. package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
  25. package/packages/dd-trace/src/plugins/util/test.js +18 -1
  26. package/packages/dd-trace/src/profiling/profilers/wall.js +7 -5
  27. package/packages/dd-trace/src/proxy.js +23 -2
  28. package/packages/dd-trace/src/ritm.js +10 -2
  29. /package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +0 -0
package/index.d.ts CHANGED
@@ -121,6 +121,8 @@ export declare interface Tracer extends opentracing.Tracer {
121
121
  appsec: Appsec;
122
122
 
123
123
  TracerProvider: opentelemetry.TracerProvider;
124
+
125
+ dogstatsd: DogStatsD;
124
126
  }
125
127
 
126
128
  export declare interface TraceOptions extends Analyzable {
@@ -642,6 +644,47 @@ export declare interface User {
642
644
  [key: string]: string | undefined
643
645
  }
644
646
 
647
+ export declare interface DogStatsD {
648
+ /**
649
+ * Increments a metric by the specified value, optionally specifying tags.
650
+ * @param {string} stat The dot-separated metric name.
651
+ * @param {number} value The amount to increment the stat by.
652
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
653
+ */
654
+ increment(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
655
+
656
+ /**
657
+ * Decrements a metric by the specified value, optionally specifying tags.
658
+ * @param {string} stat The dot-separated metric name.
659
+ * @param {number} value The amount to decrement the stat by.
660
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
661
+ */
662
+ decrement(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
663
+
664
+ /**
665
+ * Sets a distribution value, optionally specifying tags.
666
+ * @param {string} stat The dot-separated metric name.
667
+ * @param {number} value The amount to increment the stat by.
668
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
669
+ */
670
+ distribution(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
671
+
672
+ /**
673
+ * Sets a gauge value, optionally specifying tags.
674
+ * @param {string} stat The dot-separated metric name.
675
+ * @param {number} value The amount to increment the stat by.
676
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
677
+ */
678
+ gauge(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
679
+
680
+ /**
681
+ * Forces any unsent metrics to be sent
682
+ *
683
+ * @beta This method is experimental and could be removed in future versions.
684
+ */
685
+ flush(): void
686
+ }
687
+
645
688
  export declare interface Appsec {
646
689
  /**
647
690
  * Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.11.1",
3
+ "version": "4.12.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -37,6 +37,7 @@
37
37
  "test:integration:cypress": "mocha --colors --timeout 30000 \"integration-tests/cypress/*.spec.js\"",
38
38
  "test:integration:playwright": "mocha --colors --timeout 30000 \"integration-tests/playwright/*.spec.js\"",
39
39
  "test:integration:serverless": "mocha --colors --timeout 30000 \"integration-tests/serverless/*.spec.js\"",
40
+ "test:integration:plugins": "mocha --colors --timeout 30000 \"packages/datadog-plugin-*/test/integration-test/*.spec.js\"",
40
41
  "test:shimmer": "mocha --colors 'packages/datadog-shimmer/test/**/*.spec.js'",
41
42
  "test:shimmer:ci": "nyc --no-clean --include 'packages/datadog-shimmer/src/**/*.js' -- npm run test:shimmer",
42
43
  "leak:core": "node ./scripts/install_plugin_modules && (cd packages/memwatch && yarn) && NODE_PATH=./packages/memwatch/node_modules node --no-warnings ./node_modules/.bin/tape 'packages/dd-trace/test/leak/**/*.js'",
@@ -50,12 +50,18 @@ for (const pkg of INSTRUMENTED) {
50
50
  module.exports.name = 'datadog-esbuild'
51
51
 
52
52
  module.exports.setup = function (build) {
53
+ const externalModules = new Set(build.initialOptions.external || [])
53
54
  build.onResolve({ filter: /.*/ }, args => {
55
+ if (externalModules.has(args.path)) {
56
+ if (DEBUG) console.log(`EXTERNAL: ${args.path}`)
57
+ return
58
+ }
59
+
54
60
  let fullPathToModule
55
61
  try {
56
62
  fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir)
57
63
  } catch (err) {
58
- console.warn(`Unable to find "${args.path}". Is the package dead code?`)
64
+ console.warn(`MISSING: Unable to find "${args.path}". Is the package dead code?`)
59
65
  return
60
66
  }
61
67
  const extracted = extractPackageAndModulePath(fullPathToModule)
@@ -74,7 +80,7 @@ module.exports.setup = function (build) {
74
80
  } catch (err) {
75
81
  if (err.code === 'MODULE_NOT_FOUND') {
76
82
  if (!internal) {
77
- console.warn(`Unable to find "${extracted.pkg}/package.json". Is the package dead code?`)
83
+ console.warn(`MISSING: Unable to find "${extracted.pkg}/package.json". Is the package dead code?`)
78
84
  }
79
85
  return
80
86
  } else {
@@ -84,7 +90,7 @@ module.exports.setup = function (build) {
84
90
 
85
91
  const packageJson = require(pathToPackageJson)
86
92
 
87
- if (DEBUG) console.log(`RESOLVE ${packageName}@${packageJson.version}`)
93
+ if (DEBUG) console.log(`RESOLVE: ${packageName}@${packageJson.version}`)
88
94
 
89
95
  // https://esbuild.github.io/plugins/#on-resolve-arguments
90
96
  return {
@@ -114,7 +120,7 @@ module.exports.setup = function (build) {
114
120
  build.onLoad({ filter: /.*/, namespace: NAMESPACE }, args => {
115
121
  const data = args.pluginData
116
122
 
117
- if (DEBUG) console.log(`LOAD ${data.pkg}@${data.version}, pkg "${data.path}"`)
123
+ if (DEBUG) console.log(`LOAD: ${data.pkg}@${data.version}, pkg "${data.path}"`)
118
124
 
119
125
  const path = data.raw !== data.pkg
120
126
  ? `${data.pkg}/${data.path}`
@@ -21,6 +21,8 @@ const skippableSuitesCh = channel('ci:cucumber:test-suite:skippable')
21
21
  const sessionStartCh = channel('ci:cucumber:session:start')
22
22
  const sessionFinishCh = channel('ci:cucumber:session:finish')
23
23
 
24
+ const itrSkippedSuitesCh = channel('ci:cucumber:itr:skipped-suites')
25
+
24
26
  const {
25
27
  getCoveredFilenamesFromCoverage,
26
28
  resetCoverage,
@@ -37,7 +39,6 @@ const patched = new WeakSet()
37
39
 
38
40
  let pickleByFile = {}
39
41
  const pickleResultByFile = {}
40
- let isSuitesSkipped = false
41
42
 
42
43
  function getSuiteStatusFromTestStatuses (testStatuses) {
43
44
  if (testStatuses.some(status => status === 'fail')) {
@@ -216,11 +217,18 @@ addHook({
216
217
  file: 'lib/runtime/test_case_runner.js'
217
218
  }, testCaseHook)
218
219
 
219
- function getPicklesToRun (runtime, suitesToSkip) {
220
- return runtime.pickleIds.filter((pickleId) => {
220
+ function getFilteredPickles (runtime, suitesToSkip) {
221
+ return runtime.pickleIds.reduce((acc, pickleId) => {
221
222
  const test = runtime.eventDataCollector.getPickle(pickleId)
222
- return !suitesToSkip.includes(getTestSuitePath(test.uri, process.cwd()))
223
- }, {})
223
+ const testSuitePath = getTestSuitePath(test.uri, process.cwd())
224
+ const isSkipped = suitesToSkip.includes(testSuitePath)
225
+ if (isSkipped) {
226
+ acc.skippedSuites.add(testSuitePath)
227
+ } else {
228
+ acc.picklesToRun.push(pickleId)
229
+ }
230
+ return acc
231
+ }, { skippedSuites: new Set(), picklesToRun: [] })
224
232
  }
225
233
 
226
234
  function getPickleByFile (runtime) {
@@ -239,7 +247,7 @@ addHook({
239
247
  name: '@cucumber/cucumber',
240
248
  versions: ['>=7.0.0'],
241
249
  file: 'lib/runtime/index.js'
242
- }, (runtimePackage, cucumberVersion) => {
250
+ }, (runtimePackage, frameworkVersion) => {
243
251
  shimmer.wrap(runtimePackage.default.prototype, 'start', start => async function () {
244
252
  const asyncResource = new AsyncResource('bound-anonymous-fn')
245
253
  let onDone
@@ -263,11 +271,16 @@ addHook({
263
271
  })
264
272
 
265
273
  const { err, skippableSuites } = await skippableSuitesPromise
274
+ let skippedSuites = []
275
+ let isSuitesSkipped = false
266
276
 
267
277
  if (!err) {
268
- const newPickleIds = getPicklesToRun(this, skippableSuites)
269
- isSuitesSkipped = newPickleIds.length !== this.pickleIds.length
270
- this.pickleIds = newPickleIds
278
+ const filteredPickles = getFilteredPickles(this, skippableSuites)
279
+ const { picklesToRun } = filteredPickles
280
+ isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
281
+ this.pickleIds = picklesToRun
282
+
283
+ skippedSuites = Array.from(filteredPickles.skippedSuites)
271
284
  }
272
285
 
273
286
  pickleByFile = getPickleByFile(this)
@@ -276,8 +289,13 @@ addHook({
276
289
  const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}`
277
290
 
278
291
  asyncResource.runInAsyncScope(() => {
279
- sessionStartCh.publish({ command, frameworkVersion: cucumberVersion })
292
+ sessionStartCh.publish({ command, frameworkVersion })
280
293
  })
294
+
295
+ if (!err && skippedSuites.length) {
296
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
297
+ }
298
+
281
299
  const success = await start.apply(this, arguments)
282
300
 
283
301
  let testCodeCoverageLinesTotal
@@ -296,7 +314,8 @@ addHook({
296
314
  sessionFinishCh.publish({
297
315
  status: success ? 'pass' : 'fail',
298
316
  isSuitesSkipped,
299
- testCodeCoverageLinesTotal
317
+ testCodeCoverageLinesTotal,
318
+ numSkippedSuites: skippedSuites.length
300
319
  })
301
320
  })
302
321
  return success
@@ -39,10 +39,13 @@ const testErrCh = channel('ci:jest:test:err')
39
39
  const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
40
40
  const jestItrConfigurationCh = channel('ci:jest:itr-configuration')
41
41
 
42
+ const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
43
+
42
44
  let skippableSuites = []
43
45
  let isCodeCoverageEnabled = false
44
46
  let isSuitesSkippingEnabled = false
45
47
  let isSuitesSkipped = false
48
+ let numSkippedSuites = 0
46
49
 
47
50
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
48
51
 
@@ -102,7 +105,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
102
105
  await super.handleTestEvent(event, state)
103
106
  }
104
107
 
105
- const setNameToParams = (name, params) => { this.nameToParams[name] = params }
108
+ const setNameToParams = (name, params) => { this.nameToParams[name] = [...params] }
106
109
 
107
110
  if (event.name === 'setup') {
108
111
  if (this.global.test) {
@@ -191,7 +194,7 @@ addHook({
191
194
  addHook({
192
195
  name: '@jest/test-sequencer',
193
196
  versions: ['>=24.8.0']
194
- }, sequencerPackage => {
197
+ }, (sequencerPackage, frameworkVersion) => {
195
198
  shimmer.wrap(sequencerPackage.default.prototype, 'shard', shard => function () {
196
199
  const shardedTests = shard.apply(this, arguments)
197
200
 
@@ -202,13 +205,15 @@ addHook({
202
205
  const [test] = shardedTests
203
206
  const rootDir = test && test.context && test.context.config && test.context.config.rootDir
204
207
 
205
- const filteredTests = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
208
+ const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
206
209
 
207
- isSuitesSkipped = filteredTests.length !== shardedTests.length
210
+ isSuitesSkipped = suitesToRun.length !== shardedTests.length
211
+ numSkippedSuites = skippedSuites.length
208
212
 
209
- skippableSuites = []
213
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
210
214
 
211
- return filteredTests
215
+ skippableSuites = []
216
+ return suitesToRun
212
217
  })
213
218
  return sequencerPackage
214
219
  })
@@ -279,10 +284,13 @@ function cliWrapper (cli, jestVersion) {
279
284
  isSuitesSkipped,
280
285
  isSuitesSkippingEnabled,
281
286
  isCodeCoverageEnabled,
282
- testCodeCoverageLinesTotal
287
+ testCodeCoverageLinesTotal,
288
+ numSkippedSuites
283
289
  })
284
290
  })
285
291
 
292
+ numSkippedSuites = 0
293
+
286
294
  return result
287
295
  })
288
296
 
@@ -468,7 +476,7 @@ addHook({
468
476
  name: '@jest/core',
469
477
  versions: ['>=24.8.0'],
470
478
  file: 'build/SearchSource.js'
471
- }, searchSourcePackage => {
479
+ }, (searchSourcePackage, frameworkVersion) => {
472
480
  const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
473
481
 
474
482
  shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
@@ -492,13 +500,16 @@ addHook({
492
500
  const testPaths = await getTestPaths.apply(this, arguments)
493
501
  const { tests } = testPaths
494
502
 
495
- const filteredTests = getJestSuitesToRun(skippableSuites, tests, rootDir)
503
+ const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, tests, rootDir)
504
+
505
+ isSuitesSkipped = suitesToRun.length !== tests.length
506
+ numSkippedSuites = skippedSuites.length
496
507
 
497
- isSuitesSkipped = filteredTests.length !== tests.length
508
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
498
509
 
499
510
  skippableSuites = []
500
511
 
501
- return { ...testPaths, tests: filteredTests }
512
+ return { ...testPaths, tests: suitesToRun }
502
513
  })
503
514
 
504
515
  return searchSourcePackage
@@ -30,6 +30,8 @@ const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
30
30
  const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
31
31
  const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
32
32
 
33
+ const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
34
+
33
35
  // TODO: remove when root hooks and fixtures are implemented
34
36
  const patched = new WeakSet()
35
37
 
@@ -47,6 +49,7 @@ const originalCoverageMap = createCoverageMap()
47
49
  let suitesToSkip = []
48
50
  let frameworkVersion
49
51
  let isSuitesSkipped = false
52
+ let skippedSuites = []
50
53
 
51
54
  function getSuitesByTestFile (root) {
52
55
  const suitesByTestFile = {}
@@ -97,10 +100,17 @@ function getTestAsyncResource (test) {
97
100
  return testToAr.get(originalFn)
98
101
  }
99
102
 
100
- function getSuitesToRun (originalSuites) {
101
- return originalSuites.filter(suite =>
102
- !suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
103
- )
103
+ function getFilteredSuites (originalSuites) {
104
+ return originalSuites.reduce((acc, suite) => {
105
+ const testPath = getTestSuitePath(suite.file, process.cwd())
106
+ const shouldSkip = suitesToSkip.includes(testPath)
107
+ if (shouldSkip) {
108
+ acc.skippedSuites.add(testPath)
109
+ } else {
110
+ acc.suitesToRun.push(suite)
111
+ }
112
+ return acc
113
+ }, { suitesToRun: [], skippedSuites: new Set() })
104
114
  }
105
115
 
106
116
  function mochaHook (Runner) {
@@ -137,13 +147,21 @@ function mochaHook (Runner) {
137
147
  global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
138
148
  }
139
149
 
140
- testSessionFinishCh.publish({ status, isSuitesSkipped, testCodeCoverageLinesTotal })
150
+ testSessionFinishCh.publish({
151
+ status,
152
+ isSuitesSkipped,
153
+ testCodeCoverageLinesTotal,
154
+ numSkippedSuites: skippedSuites.length
155
+ })
141
156
  }))
142
157
 
143
158
  this.once('start', testRunAsyncResource.bind(function () {
144
159
  const processArgv = process.argv.slice(2).join(' ')
145
160
  const command = `mocha ${processArgv}`
146
161
  testSessionStartCh.publish({ command, frameworkVersion })
162
+ if (skippedSuites.length) {
163
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
164
+ }
147
165
  }))
148
166
 
149
167
  this.on('suite', function (suite) {
@@ -359,9 +377,13 @@ addHook({
359
377
  suitesToSkip = skippableSuites
360
378
  }
361
379
  // We remove the suites that we skip through ITR
362
- const newSuites = getSuitesToRun(runner.suite.suites)
363
- isSuitesSkipped = newSuites.length !== runner.suite.suites.length
364
- runner.suite.suites = newSuites
380
+ const filteredSuites = getFilteredSuites(runner.suite.suites)
381
+ const { suitesToRun } = filteredSuites
382
+
383
+ isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
384
+ runner.suite.suites = suitesToRun
385
+
386
+ skippedSuites = Array.from(filteredSuites.skippedSuites)
365
387
 
366
388
  global.run()
367
389
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  // TODO: either instrument all or none of the render functions
4
4
 
5
- const { channel, addHook, AsyncResource } = require('./helpers/instrument')
5
+ const { channel, addHook } = require('./helpers/instrument')
6
6
  const shimmer = require('../../datadog-shimmer')
7
7
  const { DD_MAJOR } = require('../../../version')
8
8
 
@@ -11,7 +11,7 @@ const finishChannel = channel('apm:next:request:finish')
11
11
  const errorChannel = channel('apm:next:request:error')
12
12
  const pageLoadChannel = channel('apm:next:page:load')
13
13
 
14
- const requestResources = new WeakMap()
14
+ const requests = new WeakSet()
15
15
 
16
16
  function wrapHandleRequest (handleRequest) {
17
17
  return function (req, res, pathname, query) {
@@ -105,38 +105,40 @@ function getPageFromPath (page, dynamicRoutes = []) {
105
105
  }
106
106
 
107
107
  function instrument (req, res, handler) {
108
- if (requestResources.has(req)) return handler()
108
+ req = req.originalRequest || req
109
+ res = res.originalResponse || res
109
110
 
110
- const requestResource = new AsyncResource('bound-anonymous-fn')
111
+ if (requests.has(req)) return handler()
111
112
 
112
- requestResources.set(req, requestResource)
113
+ requests.add(req)
113
114
 
114
- return requestResource.runInAsyncScope(() => {
115
- startChannel.publish({ req, res })
115
+ const ctx = { req, res }
116
116
 
117
+ return startChannel.runStores(ctx, () => {
117
118
  try {
118
- const promise = handler()
119
+ const promise = handler(ctx)
119
120
 
120
121
  // promise should only reject when propagateError is true:
121
122
  // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L547
122
123
  return promise.then(
123
- result => finish(req, res, result),
124
- err => finish(req, res, null, err)
124
+ result => finish(ctx, result),
125
+ err => finish(ctx, null, err)
125
126
  )
126
127
  } catch (e) {
127
128
  // this will probably never happen as the handler caller is an async function:
128
129
  // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L420
129
- return finish(req, res, null, e)
130
+ return finish(ctx, null, e)
130
131
  }
131
132
  })
132
133
  }
133
134
 
134
- function finish (req, res, result, err) {
135
+ function finish (ctx, result, err) {
135
136
  if (err) {
136
- errorChannel.publish(err)
137
+ ctx.error = err
138
+ errorChannel.publish(ctx)
137
139
  }
138
140
 
139
- finishChannel.publish({ req, res })
141
+ finishChannel.publish(ctx)
140
142
 
141
143
  if (err) {
142
144
  throw err
@@ -11,8 +11,12 @@ const startCh = channel('apm:pg:query:start')
11
11
  const finishCh = channel('apm:pg:query:finish')
12
12
  const errorCh = channel('apm:pg:query:error')
13
13
 
14
+ const startPoolQueryCh = channel('datadog:pg:pool:query:start')
15
+ const finishPoolQueryCh = channel('datadog:pg:pool:query:finish')
16
+
14
17
  addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
15
18
  shimmer.wrap(pg.Client.prototype, 'query', query => wrapQuery(query))
19
+ shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(query))
16
20
  return pg
17
21
  })
18
22
 
@@ -97,3 +101,45 @@ function wrapQuery (query) {
97
101
  })
98
102
  }
99
103
  }
104
+
105
+ function wrapPoolQuery (query) {
106
+ return function () {
107
+ if (!startPoolQueryCh.hasSubscribers) {
108
+ return query.apply(this, arguments)
109
+ }
110
+
111
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
112
+
113
+ const pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
114
+
115
+ return asyncResource.runInAsyncScope(() => {
116
+ startPoolQueryCh.publish({
117
+ query: pgQuery
118
+ })
119
+
120
+ const finish = asyncResource.bind(function () {
121
+ finishPoolQueryCh.publish()
122
+ })
123
+
124
+ const cb = arguments[arguments.length - 1]
125
+ if (typeof cb === 'function') {
126
+ arguments[arguments.length - 1] = shimmer.wrap(cb, function () {
127
+ finish()
128
+ return cb.apply(this, arguments)
129
+ })
130
+ }
131
+
132
+ const retval = query.apply(this, arguments)
133
+
134
+ if (retval && retval.then) {
135
+ retval.then(() => {
136
+ finish()
137
+ }).catch(() => {
138
+ finish()
139
+ })
140
+ }
141
+
142
+ return retval
143
+ })
144
+ }
145
+ }
@@ -25,12 +25,24 @@ class CucumberPlugin extends CiPlugin {
25
25
 
26
26
  this.sourceRoot = process.cwd()
27
27
 
28
- this.addSub('ci:cucumber:session:finish', ({ status, isSuitesSkipped, testCodeCoverageLinesTotal }) => {
28
+ this.addSub('ci:cucumber:session:finish', ({
29
+ status,
30
+ isSuitesSkipped,
31
+ numSkippedSuites,
32
+ testCodeCoverageLinesTotal
33
+ }) => {
29
34
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
30
35
  addIntelligentTestRunnerSpanTags(
31
36
  this.testSessionSpan,
32
37
  this.testModuleSpan,
33
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
38
+ {
39
+ isSuitesSkipped,
40
+ isSuitesSkippingEnabled,
41
+ isCodeCoverageEnabled,
42
+ testCodeCoverageLinesTotal,
43
+ skippingCount: numSkippedSuites,
44
+ skippingType: 'suite'
45
+ }
34
46
  )
35
47
 
36
48
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -20,7 +20,8 @@ const {
20
20
  finishAllTraceSpans,
21
21
  getCoveredFilenamesFromCoverage,
22
22
  getTestSuitePath,
23
- addIntelligentTestRunnerSpanTags
23
+ addIntelligentTestRunnerSpanTags,
24
+ TEST_SKIPPED_BY_ITR
24
25
  } = require('../../dd-trace/src/plugins/util/test')
25
26
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
26
27
  const log = require('../../dd-trace/src/log')
@@ -120,6 +121,7 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
120
121
 
121
122
  module.exports = (on, config) => {
122
123
  let isTestsSkipped = false
124
+ const skippedTests = []
123
125
  const tracer = require('../../dd-trace')
124
126
  const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
125
127
 
@@ -248,19 +250,23 @@ module.exports = (on, config) => {
248
250
  const cypressTests = tests || []
249
251
  const finishedTests = finishedTestsByFile[spec.relative] || []
250
252
 
251
- // Get tests that didn't go through `dd:afterEach` and haven't been skipped by ITR
253
+ // Get tests that didn't go through `dd:afterEach`
252
254
  // and create a skipped test span for each of them
253
255
  cypressTests.filter(({ title }) => {
254
256
  const cypressTestName = title.join(' ')
255
- const isSkippedByItr = testsToSkip.find(test =>
256
- cypressTestName === test.name && spec.relative === test.suite
257
- )
258
257
  const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
259
258
 
260
- return !isSkippedByItr && !isTestFinished
259
+ return !isTestFinished
261
260
  }).forEach(({ title }) => {
262
- const skippedTestSpan = getTestSpan(title.join(' '), spec.relative)
261
+ const cypressTestName = title.join(' ')
262
+ const isSkippedByItr = testsToSkip.find(test =>
263
+ cypressTestName === test.name && spec.relative === test.suite
264
+ )
265
+ const skippedTestSpan = getTestSpan(cypressTestName, spec.relative)
263
266
  skippedTestSpan.setTag(TEST_STATUS, 'skip')
267
+ if (isSkippedByItr) {
268
+ skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
269
+ }
264
270
  skippedTestSpan.finish()
265
271
  })
266
272
 
@@ -309,7 +315,9 @@ module.exports = (on, config) => {
309
315
  {
310
316
  isSuitesSkipped: isTestsSkipped,
311
317
  isSuitesSkippingEnabled,
312
- isCodeCoverageEnabled
318
+ isCodeCoverageEnabled,
319
+ skippingType: 'test',
320
+ skippingCount: skippedTests.length
313
321
  }
314
322
  )
315
323
 
@@ -353,6 +361,7 @@ module.exports = (on, config) => {
353
361
  if (testsToSkip.find(test => {
354
362
  return testName === test.name && testSuite === test.suite
355
363
  })) {
364
+ skippedTests.push(test)
356
365
  isTestsSkipped = true
357
366
  return { shouldSkip: true }
358
367
  }
@@ -49,7 +49,8 @@ class JestPlugin extends CiPlugin {
49
49
  isSuitesSkipped,
50
50
  isSuitesSkippingEnabled,
51
51
  isCodeCoverageEnabled,
52
- testCodeCoverageLinesTotal
52
+ testCodeCoverageLinesTotal,
53
+ numSkippedSuites
53
54
  }) => {
54
55
  this.testSessionSpan.setTag(TEST_STATUS, status)
55
56
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -57,7 +58,14 @@ class JestPlugin extends CiPlugin {
57
58
  addIntelligentTestRunnerSpanTags(
58
59
  this.testSessionSpan,
59
60
  this.testModuleSpan,
60
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
61
+ {
62
+ isSuitesSkipped,
63
+ isSuitesSkippingEnabled,
64
+ isCodeCoverageEnabled,
65
+ testCodeCoverageLinesTotal,
66
+ skippingType: 'suite',
67
+ skippingCount: numSkippedSuites
68
+ }
61
69
  )
62
70
 
63
71
  this.testModuleSpan.finish()
@@ -48,10 +48,16 @@ function getJestTestName (test) {
48
48
  }
49
49
 
50
50
  function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
51
- return originalTests.filter(({ path: testPath }) => {
52
- const relativePath = getTestSuitePath(testPath, rootDir)
53
- return !skippableSuites.includes(relativePath)
54
- })
51
+ return originalTests.reduce((acc, test) => {
52
+ const relativePath = getTestSuitePath(test.path, rootDir)
53
+ const shouldBeSkipped = skippableSuites.includes(relativePath)
54
+ if (shouldBeSkipped) {
55
+ acc.skippedSuites.push(relativePath)
56
+ } else {
57
+ acc.suitesToRun.push(test)
58
+ }
59
+ return acc
60
+ }, { skippedSuites: [], suitesToRun: [] })
55
61
  }
56
62
 
57
63
  module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun }