dd-trace 4.16.0 → 4.18.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 (71) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +9 -1
  3. package/package.json +5 -4
  4. package/packages/datadog-instrumentations/src/body-parser.js +2 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +29 -4
  6. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
  7. package/packages/datadog-instrumentations/src/express.js +2 -1
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  9. package/packages/datadog-instrumentations/src/jest.js +58 -20
  10. package/packages/datadog-instrumentations/src/knex.js +69 -1
  11. package/packages/datadog-instrumentations/src/mocha.js +34 -4
  12. package/packages/datadog-instrumentations/src/mongodb.js +63 -0
  13. package/packages/datadog-instrumentations/src/mongoose.js +140 -1
  14. package/packages/datadog-instrumentations/src/next.js +98 -23
  15. package/packages/datadog-instrumentations/src/playwright.js +22 -8
  16. package/packages/datadog-plugin-cucumber/src/index.js +17 -5
  17. package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
  18. package/packages/datadog-plugin-http/src/client.js +2 -0
  19. package/packages/datadog-plugin-jest/src/index.js +29 -6
  20. package/packages/datadog-plugin-jest/src/util.js +45 -2
  21. package/packages/datadog-plugin-memcached/src/index.js +10 -5
  22. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  23. package/packages/datadog-plugin-next/src/index.js +4 -3
  24. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  25. package/packages/dd-trace/src/appsec/channels.js +3 -1
  26. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
  27. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
  29. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
  30. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
  31. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +173 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
  33. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
  34. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
  35. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
  36. package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
  37. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
  38. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  44. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +22 -4
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +15 -2
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
  51. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
  52. package/packages/dd-trace/src/appsec/index.js +31 -13
  53. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
  54. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -1
  55. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
  56. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
  57. package/packages/dd-trace/src/config.js +37 -13
  58. package/packages/dd-trace/src/format.js +3 -0
  59. package/packages/dd-trace/src/git_properties.js +16 -15
  60. package/packages/dd-trace/src/plugin_manager.js +3 -1
  61. package/packages/dd-trace/src/plugins/util/ci.js +17 -0
  62. package/packages/dd-trace/src/plugins/util/git.js +26 -4
  63. package/packages/dd-trace/src/plugins/util/test.js +45 -2
  64. package/packages/dd-trace/src/profiling/config.js +20 -3
  65. package/packages/dd-trace/src/profiling/profilers/wall.js +51 -40
  66. package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
  67. package/packages/dd-trace/src/telemetry/index.js +4 -0
  68. package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
  69. package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
  70. package/packages/dd-trace/src/telemetry/metrics.js +0 -5
  71. package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
@@ -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,ipaddr.js,MIT,Copyright 2011-2017 whitequark
16
16
  require,istanbul-lib-coverage,BSD-3-Clause,Copyright 2012-2015 Yahoo! Inc.
17
+ require,jest-docblock,MIT,Copyright Meta Platforms, Inc. and affiliates.
17
18
  require,koalas,MIT,Copyright 2013-2017 Brian Woodward
18
19
  require,limiter,MIT,Copyright 2011 John Hurliman
19
20
  require,lodash.kebabcase,MIT,Copyright JS Foundation and other contributors
package/index.d.ts CHANGED
@@ -453,7 +453,15 @@ export declare interface TracerOptions {
453
453
  * Whether to enable vulnerability redaction
454
454
  * @default true
455
455
  */
456
- redactionEnabled?: boolean
456
+ redactionEnabled?: boolean,
457
+ /**
458
+ * Specifies a regex that will redact sensitive source names in vulnerability reports.
459
+ */
460
+ redactionNamePattern?: string,
461
+ /**
462
+ * Specifies a regex that will redact sensitive source values in vulnerability reports.
463
+ */
464
+ redactionValuePattern?: string
457
465
  }
458
466
  };
459
467
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.16.0",
3
+ "version": "4.18.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -69,10 +69,10 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "@datadog/native-appsec": "^4.0.0",
72
- "@datadog/native-iast-rewriter": "2.1.3",
73
- "@datadog/native-iast-taint-tracking": "1.5.0",
72
+ "@datadog/native-iast-rewriter": "2.2.1",
73
+ "@datadog/native-iast-taint-tracking": "1.6.3",
74
74
  "@datadog/native-metrics": "^2.0.0",
75
- "@datadog/pprof": "3.2.0",
75
+ "@datadog/pprof": "4.0.1",
76
76
  "@datadog/sketches-js": "^2.1.0",
77
77
  "@opentelemetry/api": "^1.0.0",
78
78
  "@opentelemetry/core": "^1.14.0",
@@ -83,6 +83,7 @@
83
83
  "int64-buffer": "^0.1.9",
84
84
  "ipaddr.js": "^2.1.0",
85
85
  "istanbul-lib-coverage": "3.2.0",
86
+ "jest-docblock": "^29.7.0",
86
87
  "koalas": "^1.0.2",
87
88
  "limiter": "^1.1.4",
88
89
  "lodash.kebabcase": "^4.1.1",
@@ -10,8 +10,9 @@ function publishRequestBodyAndNext (req, res, next) {
10
10
  return function () {
11
11
  if (bodyParserReadCh.hasSubscribers && req) {
12
12
  const abortController = new AbortController()
13
+ const body = req.body
13
14
 
14
- bodyParserReadCh.publish({ req, res, abortController })
15
+ bodyParserReadCh.publish({ req, res, body, abortController })
15
16
 
16
17
  if (abortController.signal.aborted) return
17
18
  }
@@ -31,6 +31,10 @@ const {
31
31
  getTestSuitePath
32
32
  } = require('../../dd-trace/src/plugins/util/test')
33
33
 
34
+ const isMarkedAsUnskippable = (pickle) => {
35
+ return !!pickle.tags.find(tag => tag.name === '@datadog:unskippable')
36
+ }
37
+
34
38
  // We'll preserve the original coverage here
35
39
  const originalCoverageMap = createCoverageMap()
36
40
 
@@ -39,6 +43,9 @@ const patched = new WeakSet()
39
43
 
40
44
  let pickleByFile = {}
41
45
  const pickleResultByFile = {}
46
+ let skippableSuites = []
47
+ let isForcedToRun = false
48
+ let isUnskippable = false
42
49
 
43
50
  function getSuiteStatusFromTestStatuses (testStatuses) {
44
51
  if (testStatuses.some(status => status === 'fail')) {
@@ -91,7 +98,11 @@ function wrapRun (pl, isLatestVersion) {
91
98
  const testSuiteFullPath = this.pickle.uri
92
99
 
93
100
  if (!pickleResultByFile[testSuiteFullPath]) { // first test in suite
94
- testSuiteStartCh.publish(testSuiteFullPath)
101
+ isUnskippable = isMarkedAsUnskippable(this.pickle)
102
+ const testSuitePath = getTestSuitePath(testSuiteFullPath, process.cwd())
103
+ isForcedToRun = isUnskippable && skippableSuites.includes(testSuitePath)
104
+
105
+ testSuiteStartCh.publish({ testSuitePath, isUnskippable, isForcedToRun })
95
106
  }
96
107
 
97
108
  const testSourceLine = this.gherkinDocument &&
@@ -221,8 +232,11 @@ function getFilteredPickles (runtime, suitesToSkip) {
221
232
  return runtime.pickleIds.reduce((acc, pickleId) => {
222
233
  const test = runtime.eventDataCollector.getPickle(pickleId)
223
234
  const testSuitePath = getTestSuitePath(test.uri, process.cwd())
235
+
236
+ const isUnskippable = isMarkedAsUnskippable(test)
224
237
  const isSkipped = suitesToSkip.includes(testSuitePath)
225
- if (isSkipped) {
238
+
239
+ if (isSkipped && !isUnskippable) {
226
240
  acc.skippedSuites.add(testSuitePath)
227
241
  } else {
228
242
  acc.picklesToRun.push(pickleId)
@@ -270,7 +284,11 @@ addHook({
270
284
  skippableSuitesCh.publish({ onDone })
271
285
  })
272
286
 
273
- const { err, skippableSuites } = await skippableSuitesPromise
287
+ const skippableResponse = await skippableSuitesPromise
288
+
289
+ const err = skippableResponse.err
290
+ skippableSuites = skippableResponse.skippableSuites
291
+
274
292
  let skippedSuites = []
275
293
  let isSuitesSkipped = false
276
294
 
@@ -278,6 +296,11 @@ addHook({
278
296
  const filteredPickles = getFilteredPickles(this, skippableSuites)
279
297
  const { picklesToRun } = filteredPickles
280
298
  isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
299
+
300
+ log.debug(
301
+ () => `${picklesToRun.length} out of ${this.pickleIds.length} suites are going to run.`
302
+ )
303
+
281
304
  this.pickleIds = picklesToRun
282
305
 
283
306
  skippedSuites = Array.from(filteredPickles.skippedSuites)
@@ -315,7 +338,9 @@ addHook({
315
338
  status: success ? 'pass' : 'fail',
316
339
  isSuitesSkipped,
317
340
  testCodeCoverageLinesTotal,
318
- numSkippedSuites: skippedSuites.length
341
+ numSkippedSuites: skippedSuites.length,
342
+ hasUnskippableSuites: isUnskippable,
343
+ hasForcedToRunSuites: isForcedToRun
319
344
  })
320
345
  })
321
346
  return success
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook
6
+ } = require('./helpers/instrument')
7
+ const shimmer = require('../../datadog-shimmer')
8
+
9
+ const sanitizeMethodFinished = channel('datadog:express-mongo-sanitize:sanitize:finish')
10
+ const sanitizeMiddlewareFinished = channel('datadog:express-mongo-sanitize:filter:finish')
11
+
12
+ const propertiesToSanitize = ['body', 'params', 'headers', 'query']
13
+
14
+ addHook({ name: 'express-mongo-sanitize', versions: ['>=1.0.0'] }, expressMongoSanitize => {
15
+ shimmer.wrap(expressMongoSanitize, 'sanitize', sanitize => function () {
16
+ const sanitizedObject = sanitize.apply(this, arguments)
17
+
18
+ if (sanitizeMethodFinished.hasSubscribers) {
19
+ sanitizeMethodFinished.publish({ sanitizedObject })
20
+ }
21
+
22
+ return sanitizedObject
23
+ })
24
+
25
+ return shimmer.wrap(expressMongoSanitize, function () {
26
+ const middleware = expressMongoSanitize.apply(this, arguments)
27
+
28
+ return shimmer.wrap(middleware, function (req, res, next) {
29
+ if (!sanitizeMiddlewareFinished.hasSubscribers) {
30
+ return middleware.apply(this, arguments)
31
+ }
32
+
33
+ const wrappedNext = shimmer.wrap(next, function () {
34
+ sanitizeMiddlewareFinished.publish({
35
+ sanitizedProperties: propertiesToSanitize,
36
+ req
37
+ })
38
+
39
+ return next.apply(this, arguments)
40
+ })
41
+
42
+ return middleware.call(this, req, res, wrappedNext)
43
+ })
44
+ })
45
+ })
@@ -33,8 +33,9 @@ function publishQueryParsedAndNext (req, res, next) {
33
33
  return function () {
34
34
  if (queryParserReadCh.hasSubscribers && req) {
35
35
  const abortController = new AbortController()
36
+ const query = req.query
36
37
 
37
- queryParserReadCh.publish({ req, res, abortController })
38
+ queryParserReadCh.publish({ req, res, query, abortController })
38
39
 
39
40
  if (abortController.signal.aborted) return
40
41
  }
@@ -38,6 +38,7 @@ module.exports = {
38
38
  'dns': () => require('../dns'),
39
39
  'elasticsearch': () => require('../elasticsearch'),
40
40
  'express': () => require('../express'),
41
+ 'express-mongo-sanitize': () => require('../express-mongo-sanitize'),
41
42
  'fastify': () => require('../fastify'),
42
43
  'find-my-way': () => require('../find-my-way'),
43
44
  'fs': () => require('../fs'),
@@ -68,7 +69,7 @@ module.exports = {
68
69
  'mocha': () => require('../mocha'),
69
70
  'mocha-each': () => require('../mocha'),
70
71
  'moleculer': () => require('../moleculer'),
71
- 'mongodb': () => require('../mongodb-core'),
72
+ 'mongodb': () => require('../mongodb'),
72
73
  'mongodb-core': () => require('../mongodb-core'),
73
74
  'mongoose': () => require('../mongoose'),
74
75
  'mysql': () => require('../mysql'),
@@ -46,6 +46,8 @@ let isCodeCoverageEnabled = false
46
46
  let isSuitesSkippingEnabled = false
47
47
  let isSuitesSkipped = false
48
48
  let numSkippedSuites = 0
49
+ let hasUnskippableSuites = false
50
+ let hasForcedToRunSuites = false
49
51
 
50
52
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
51
53
 
@@ -205,15 +207,22 @@ addHook({
205
207
  const [test] = shardedTests
206
208
  const rootDir = test && test.context && test.context.config && test.context.config.rootDir
207
209
 
208
- const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
210
+ const jestSuitesToRun = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
209
211
 
210
- isSuitesSkipped = suitesToRun.length !== shardedTests.length
211
- numSkippedSuites = skippedSuites.length
212
+ log.debug(
213
+ () => `${jestSuitesToRun.suitesToRun.length} out of ${shardedTests.length} suites are going to run.`
214
+ )
212
215
 
213
- itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
216
+ hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
217
+ hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
218
+
219
+ isSuitesSkipped = jestSuitesToRun.suitesToRun.length !== shardedTests.length
220
+ numSkippedSuites = jestSuitesToRun.skippedSuites.length
221
+
222
+ itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
214
223
 
215
224
  skippableSuites = []
216
- return suitesToRun
225
+ return jestSuitesToRun.suitesToRun
217
226
  })
218
227
  return sequencerPackage
219
228
  })
@@ -268,7 +277,16 @@ function cliWrapper (cli, jestVersion) {
268
277
 
269
278
  const result = await runCLI.apply(this, arguments)
270
279
 
271
- const { results: { success, coverageMap } } = result
280
+ const {
281
+ results: {
282
+ success,
283
+ coverageMap,
284
+ numFailedTestSuites,
285
+ numFailedTests,
286
+ numTotalTests,
287
+ numTotalTestSuites
288
+ }
289
+ } = result
272
290
 
273
291
  let testCodeCoverageLinesTotal
274
292
  try {
@@ -277,15 +295,30 @@ function cliWrapper (cli, jestVersion) {
277
295
  } catch (e) {
278
296
  // ignore errors
279
297
  }
298
+ let status, error
299
+
300
+ if (success) {
301
+ if (numTotalTests === 0 && numTotalTestSuites === 0) {
302
+ status = 'skip'
303
+ } else {
304
+ status = 'pass'
305
+ }
306
+ } else {
307
+ status = 'fail'
308
+ error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
309
+ }
280
310
 
281
311
  sessionAsyncResource.runInAsyncScope(() => {
282
312
  testSessionFinishCh.publish({
283
- status: success ? 'pass' : 'fail',
313
+ status,
284
314
  isSuitesSkipped,
285
315
  isSuitesSkippingEnabled,
286
316
  isCodeCoverageEnabled,
287
317
  testCodeCoverageLinesTotal,
288
- numSkippedSuites
318
+ numSkippedSuites,
319
+ hasUnskippableSuites,
320
+ hasForcedToRunSuites,
321
+ error
289
322
  })
290
323
  })
291
324
 
@@ -357,23 +390,23 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
357
390
  status = 'fail'
358
391
  }
359
392
 
360
- const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
361
- .map(filename => getTestSuitePath(filename, environment.rootDir))
362
-
363
393
  /**
364
394
  * Child processes do not each request ITR configuration, so the jest's parent process
365
395
  * needs to pass them the configuration. This is done via _ddTestCodeCoverageEnabled, which
366
396
  * controls whether coverage is reported.
367
- */
368
- if (coverageFiles &&
369
- environment.testEnvironmentOptions &&
370
- environment.testEnvironmentOptions._ddTestCodeCoverageEnabled) {
397
+ */
398
+ if (environment.testEnvironmentOptions?._ddTestCodeCoverageEnabled) {
399
+ const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
400
+ .map(filename => getTestSuitePath(filename, environment.rootDir))
371
401
  asyncResource.runInAsyncScope(() => {
372
402
  testSuiteCodeCoverageCh.publish([...coverageFiles, environment.testSuite])
373
403
  })
374
404
  }
375
405
  testSuiteFinishCh.publish({ status, errorMessage })
376
406
  return suiteResults
407
+ }).catch(error => {
408
+ testSuiteFinishCh.publish({ status: 'fail', error })
409
+ throw error
377
410
  })
378
411
  })
379
412
  })
@@ -500,16 +533,21 @@ addHook({
500
533
  const testPaths = await getTestPaths.apply(this, arguments)
501
534
  const { tests } = testPaths
502
535
 
503
- const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, tests, rootDir)
536
+ const jestSuitesToRun = getJestSuitesToRun(skippableSuites, tests, rootDir)
537
+
538
+ log.debug(() => `${jestSuitesToRun.suitesToRun.length} out of ${tests.length} suites are going to run.`)
539
+
540
+ hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
541
+ hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
504
542
 
505
- isSuitesSkipped = suitesToRun.length !== tests.length
506
- numSkippedSuites = skippedSuites.length
543
+ isSuitesSkipped = jestSuitesToRun.suitesToRun.length !== tests.length
544
+ numSkippedSuites = jestSuitesToRun.skippedSuites.length
507
545
 
508
- itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
546
+ itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
509
547
 
510
548
  skippableSuites = []
511
549
 
512
- return { ...testPaths, tests: suitesToRun }
550
+ return { ...testPaths, tests: jestSuitesToRun.suitesToRun }
513
551
  })
514
552
 
515
553
  return searchSourcePackage
@@ -1,9 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const { addHook } = require('./helpers/instrument')
3
+ const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
4
  const { wrapThen } = require('./helpers/promise')
5
5
  const shimmer = require('../../datadog-shimmer')
6
6
 
7
+ const startRawQueryCh = channel('datadog:knex:raw:start')
8
+ const finishRawQueryCh = channel('datadog:knex:raw:finish')
9
+
7
10
  patch('lib/query/builder.js')
8
11
  patch('lib/raw.js')
9
12
  patch('lib/schema/builder.js')
@@ -18,3 +21,68 @@ function patch (file) {
18
21
  return Builder
19
22
  })
20
23
  }
24
+
25
+ addHook({
26
+ name: 'knex',
27
+ versions: ['>=2'],
28
+ file: 'lib/knex-builder/Knex.js'
29
+ }, Knex => {
30
+ shimmer.wrap(Knex.Client.prototype, 'raw', raw => function () {
31
+ if (!startRawQueryCh.hasSubscribers) {
32
+ return raw.apply(this, arguments)
33
+ }
34
+
35
+ const sql = arguments[0]
36
+
37
+ // Skip query done by Knex to get the value used for undefined
38
+ if (sql === 'DEFAULT') {
39
+ return raw.apply(this, arguments)
40
+ }
41
+
42
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
43
+
44
+ function finish () {
45
+ finishRawQueryCh.publish()
46
+ }
47
+
48
+ return asyncResource.runInAsyncScope(() => {
49
+ startRawQueryCh.publish({ sql, dialect: this.dialect })
50
+
51
+ const rawResult = raw.apply(this, arguments)
52
+ shimmer.wrap(rawResult, 'then', originalThen => function () {
53
+ return asyncResource.runInAsyncScope(() => {
54
+ arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
55
+ if (arguments[1]) arguments[1] = wrapCallbackWithFinish(arguments[1], finish)
56
+
57
+ const originalThenResult = originalThen.apply(this, arguments)
58
+
59
+ shimmer.wrap(originalThenResult, 'catch', originalCatch => function () {
60
+ arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
61
+ return originalCatch.apply(this, arguments)
62
+ })
63
+
64
+ return originalThenResult
65
+ })
66
+ })
67
+
68
+ shimmer.wrap(rawResult, 'asCallback', originalAsCallback => function () {
69
+ return asyncResource.runInAsyncScope(() => {
70
+ arguments[0] = wrapCallbackWithFinish(arguments[0], finish)
71
+ return originalAsCallback.apply(this, arguments)
72
+ })
73
+ })
74
+
75
+ return rawResult
76
+ })
77
+ })
78
+ return Knex
79
+ })
80
+
81
+ function wrapCallbackWithFinish (callback, finish) {
82
+ if (typeof callback !== 'function') return callback
83
+
84
+ return function () {
85
+ finish()
86
+ callback.apply(this, arguments)
87
+ }
88
+ }
@@ -1,9 +1,10 @@
1
1
  const { createCoverageMap } = require('istanbul-lib-coverage')
2
2
 
3
+ const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
4
+
3
5
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
6
  const shimmer = require('../../datadog-shimmer')
5
7
  const log = require('../../dd-trace/src/log')
6
-
7
8
  const {
8
9
  getCoveredFilenamesFromCoverage,
9
10
  resetCoverage,
@@ -50,6 +51,8 @@ let suitesToSkip = []
50
51
  let frameworkVersion
51
52
  let isSuitesSkipped = false
52
53
  let skippedSuites = []
54
+ const unskippableSuites = []
55
+ let isForcedToRun = false
53
56
 
54
57
  function getSuitesByTestFile (root) {
55
58
  const suitesByTestFile = {}
@@ -104,7 +107,8 @@ function getFilteredSuites (originalSuites) {
104
107
  return originalSuites.reduce((acc, suite) => {
105
108
  const testPath = getTestSuitePath(suite.file, process.cwd())
106
109
  const shouldSkip = suitesToSkip.includes(testPath)
107
- if (shouldSkip) {
110
+ const isUnskippable = unskippableSuites.includes(suite.file)
111
+ if (shouldSkip && !isUnskippable) {
108
112
  acc.skippedSuites.add(testPath)
109
113
  } else {
110
114
  acc.suitesToRun.push(suite)
@@ -129,11 +133,20 @@ function mochaHook (Runner) {
129
133
 
130
134
  this.once('end', testRunAsyncResource.bind(function () {
131
135
  let status = 'pass'
136
+ let error
132
137
  if (this.stats) {
133
138
  status = this.stats.failures === 0 ? 'pass' : 'fail'
139
+ if (this.stats.tests === 0) {
140
+ status = 'skip'
141
+ }
134
142
  } else if (this.failures !== 0) {
135
143
  status = 'fail'
136
144
  }
145
+
146
+ if (status === 'fail') {
147
+ error = new Error(`Failed tests: ${this.failures}.`)
148
+ }
149
+
137
150
  testFileToSuiteAr.clear()
138
151
 
139
152
  let testCodeCoverageLinesTotal
@@ -151,7 +164,10 @@ function mochaHook (Runner) {
151
164
  status,
152
165
  isSuitesSkipped,
153
166
  testCodeCoverageLinesTotal,
154
- numSkippedSuites: skippedSuites.length
167
+ numSkippedSuites: skippedSuites.length,
168
+ hasForcedToRunSuites: isForcedToRun,
169
+ hasUnskippableSuites: !!unskippableSuites.length,
170
+ error
155
171
  })
156
172
  }))
157
173
 
@@ -172,8 +188,10 @@ function mochaHook (Runner) {
172
188
  if (!asyncResource) {
173
189
  asyncResource = new AsyncResource('bound-anonymous-fn')
174
190
  testFileToSuiteAr.set(suite.file, asyncResource)
191
+ const isUnskippable = unskippableSuites.includes(suite.file)
192
+ isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
175
193
  asyncResource.runInAsyncScope(() => {
176
- testSuiteStartCh.publish(suite)
194
+ testSuiteStartCh.publish({ testSuite: suite.file, isUnskippable, isForcedToRun })
177
195
  })
178
196
  }
179
197
  })
@@ -370,6 +388,13 @@ addHook({
370
388
 
371
389
  const runner = run.apply(this, arguments)
372
390
 
391
+ this.files.forEach(path => {
392
+ const isUnskippable = isMarkedAsUnskippable({ path })
393
+ if (isUnskippable) {
394
+ unskippableSuites.push(path)
395
+ }
396
+ })
397
+
373
398
  const onReceivedSkippableSuites = ({ err, skippableSuites }) => {
374
399
  if (err) {
375
400
  suitesToSkip = []
@@ -381,6 +406,11 @@ addHook({
381
406
  const { suitesToRun } = filteredSuites
382
407
 
383
408
  isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
409
+
410
+ log.debug(
411
+ () => `${suitesToRun.length} out of ${runner.suite.suites.length} suites are going to run.`
412
+ )
413
+
384
414
  runner.suite.suites = suitesToRun
385
415
 
386
416
  skippedSuites = Array.from(filteredSuites.skippedSuites)
@@ -0,0 +1,63 @@
1
+ 'use strict'
2
+
3
+ require('./mongodb-core')
4
+
5
+ const {
6
+ channel,
7
+ addHook,
8
+ AsyncResource
9
+ } = require('./helpers/instrument')
10
+ const shimmer = require('../../datadog-shimmer')
11
+
12
+ // collection methods with filter
13
+ const collectionMethodsWithFilter = [
14
+ 'count',
15
+ 'countDocuments',
16
+ 'deleteMany',
17
+ 'deleteOne',
18
+ 'find',
19
+ 'findOneAndDelete',
20
+ 'findOneAndReplace',
21
+ 'replaceOne'
22
+ ] // findOne is ignored because it calls to find
23
+
24
+ const collectionMethodsWithTwoFilters = [
25
+ 'findOneAndUpdate',
26
+ 'updateMany',
27
+ 'updateOne'
28
+ ]
29
+
30
+ const startCh = channel('datadog:mongodb:collection:filter:start')
31
+
32
+ addHook({ name: 'mongodb', versions: ['>=3.3 <5', '5', '>=6'] }, mongodb => {
33
+ [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters].forEach(methodName => {
34
+ if (!(methodName in mongodb.Collection.prototype)) return
35
+
36
+ const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
37
+
38
+ shimmer.wrap(mongodb.Collection.prototype, methodName, method => {
39
+ return function () {
40
+ if (!startCh.hasSubscribers) {
41
+ return method.apply(this, arguments)
42
+ }
43
+
44
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
45
+
46
+ return asyncResource.runInAsyncScope(() => {
47
+ const filters = [arguments[0]]
48
+ if (useTwoArguments) {
49
+ filters.push(arguments[1])
50
+ }
51
+
52
+ startCh.publish({
53
+ filters,
54
+ methodName
55
+ })
56
+
57
+ return method.apply(this, arguments)
58
+ })
59
+ }
60
+ })
61
+ })
62
+ return mongodb
63
+ })