dd-trace 3.7.1 → 3.9.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 (67) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +11 -0
  3. package/package.json +4 -2
  4. package/packages/datadog-instrumentations/src/body-parser.js +26 -0
  5. package/packages/datadog-instrumentations/src/child-process.js +30 -0
  6. package/packages/datadog-instrumentations/src/cucumber.js +1 -1
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -0
  8. package/packages/datadog-instrumentations/src/jest.js +110 -40
  9. package/packages/datadog-instrumentations/src/mocha.js +87 -6
  10. package/packages/datadog-instrumentations/src/pg.js +1 -2
  11. package/packages/datadog-instrumentations/src/qs.js +24 -0
  12. package/packages/datadog-plugin-cucumber/src/index.js +13 -32
  13. package/packages/datadog-plugin-cypress/src/plugin.js +2 -1
  14. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +0 -1
  15. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +0 -1
  16. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +0 -1
  17. package/packages/datadog-plugin-http/src/client.js +14 -4
  18. package/packages/datadog-plugin-http/src/server.js +3 -0
  19. package/packages/datadog-plugin-http2/src/client.js +20 -1
  20. package/packages/datadog-plugin-http2/src/server.js +3 -0
  21. package/packages/datadog-plugin-jest/src/index.js +11 -129
  22. package/packages/datadog-plugin-mocha/src/index.js +33 -46
  23. package/packages/datadog-plugin-net/src/index.js +4 -0
  24. package/packages/datadog-plugin-next/src/index.js +3 -0
  25. package/packages/datadog-plugin-oracledb/src/index.js +0 -1
  26. package/packages/datadog-plugin-pg/src/index.js +5 -2
  27. package/packages/datadog-plugin-router/src/index.js +2 -0
  28. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +3 -5
  29. package/packages/dd-trace/src/appsec/gateway/engine/index.js +1 -1
  30. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -1
  31. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +11 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +19 -0
  33. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +13 -0
  34. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -1
  35. package/packages/dd-trace/src/appsec/iast/index.js +6 -0
  36. package/packages/dd-trace/src/appsec/iast/path-line.js +8 -1
  37. package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +16 -0
  38. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +18 -0
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +125 -0
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/origin-types.js +4 -0
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +38 -0
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +66 -0
  43. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +52 -6
  44. package/packages/dd-trace/src/appsec/index.js +8 -0
  45. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +7 -0
  46. package/packages/dd-trace/src/appsec/remote_config/index.js +34 -0
  47. package/packages/dd-trace/src/appsec/remote_config/manager.js +264 -0
  48. package/packages/dd-trace/src/{exporters → appsec/remote_config}/scheduler.js +9 -9
  49. package/packages/dd-trace/src/appsec/rule_manager.js +3 -0
  50. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +5 -7
  51. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +3 -4
  52. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +3 -4
  53. package/packages/dd-trace/src/config.js +41 -20
  54. package/packages/dd-trace/src/constants.js +6 -1
  55. package/packages/dd-trace/src/exporters/common/request.js +7 -1
  56. package/packages/dd-trace/src/format.js +12 -10
  57. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -5
  58. package/packages/dd-trace/src/opentracing/span_context.js +9 -0
  59. package/packages/dd-trace/src/plugin_manager.js +6 -1
  60. package/packages/dd-trace/src/plugins/ci_plugin.js +132 -0
  61. package/packages/dd-trace/src/plugins/database.js +46 -0
  62. package/packages/dd-trace/src/plugins/tracing.js +2 -0
  63. package/packages/dd-trace/src/plugins/util/test.js +61 -8
  64. package/packages/dd-trace/src/plugins/util/web.js +9 -8
  65. package/packages/dd-trace/src/proxy.js +4 -3
  66. package/packages/dd-trace/src/span_processor.js +1 -0
  67. package/packages/dd-trace/src/tracer.js +4 -3
@@ -1,6 +1,8 @@
1
1
  Component,Origin,License,Copyright
2
2
  require,@datadog/native-appsec,Apache license 2.0,Copyright 2018 Datadog Inc.
3
3
  require,@datadog/native-metrics,Apache license 2.0,Copyright 2018 Datadog Inc.
4
+ require,@datadog/native-iast-rewriter,Apache license 2.0,Copyright 2018 Datadog Inc.
5
+ require,@datadog/native-iast-taint-tracking,Apache license 2.0,Copyright 2018 Datadog Inc.
4
6
  require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
5
7
  require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
6
8
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
package/index.d.ts CHANGED
@@ -167,6 +167,11 @@ export declare interface SpanContext extends opentracing.SpanContext {
167
167
  * Returns the string representation of the internal span ID.
168
168
  */
169
169
  toSpanId(): string;
170
+
171
+ /**
172
+ * Returns the string representation used for DBM integration.
173
+ */
174
+ toTraceparent(): string;
170
175
  }
171
176
 
172
177
  /**
@@ -463,6 +468,12 @@ export declare interface TracerOptions {
463
468
  */
464
469
  orphanable?: boolean
465
470
 
471
+ /**
472
+ * Enables DBM to APM link using tag injection.
473
+ * @default 'disabled'
474
+ */
475
+ dbmPropagationMode?: 'disabled' | 'service' | 'full'
476
+
466
477
  /**
467
478
  * Configuration of the AppSec protection. Can be a boolean as an alias to `appsec.enabled`.
468
479
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "3.7.1",
3
+ "version": "3.9.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -58,7 +58,9 @@
58
58
  "node": ">=14"
59
59
  },
60
60
  "dependencies": {
61
- "@datadog/native-appsec": "^1.2.1",
61
+ "@datadog/native-appsec": "2.0.0",
62
+ "@datadog/native-iast-rewriter": "1.0.0",
63
+ "@datadog/native-iast-taint-tracking": "1.0.0",
62
64
  "@datadog/native-metrics": "^1.5.0",
63
65
  "@datadog/pprof": "^1.1.1",
64
66
  "@datadog/sketches-js": "^2.1.0",
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const { channel, addHook, AsyncResource } = require('./helpers/instrument')
4
+
5
+ const bodyParserReadCh = channel('datadog:body-parser:read:finish')
6
+
7
+ function publishRequestBodyAndNext (request, next) {
8
+ return function () {
9
+ if (bodyParserReadCh.hasSubscribers && request) {
10
+ bodyParserReadCh.publish({ request })
11
+ }
12
+ next.apply(this, arguments)
13
+ }
14
+ }
15
+
16
+ addHook({
17
+ name: 'body-parser',
18
+ file: 'lib/read.js',
19
+ versions: ['>=1']
20
+ }, read => {
21
+ return function (req, res, next) {
22
+ const nextResource = new AsyncResource('bound-anonymous-fn')
23
+ arguments[2] = nextResource.bind(publishRequestBodyAndNext(req, next))
24
+ read.apply(this, arguments)
25
+ }
26
+ })
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook
6
+ } = require('./helpers/instrument')
7
+ const shimmer = require('../../datadog-shimmer')
8
+
9
+ const childProcessChannel = channel('datadog:child_process:execution:start')
10
+ const execMethods = ['exec', 'execFile', 'fork', 'spawn', 'execFileSync', 'execSync', 'spawnSync']
11
+ const names = ['child_process', 'node:child_process']
12
+ names.forEach(name => {
13
+ addHook({ name }, childProcess => {
14
+ shimmer.massWrap(childProcess, execMethods, wrapChildProcessMethod())
15
+ return childProcess
16
+ })
17
+ })
18
+
19
+ function wrapChildProcessMethod () {
20
+ function wrapMethod (childProcessMethod) {
21
+ return function () {
22
+ if (childProcessChannel.hasSubscribers && arguments.length > 0) {
23
+ const command = arguments[0]
24
+ childProcessChannel.publish({ command })
25
+ }
26
+ return childProcessMethod.apply(this, arguments)
27
+ }
28
+ }
29
+ return wrapMethod
30
+ }
@@ -50,7 +50,7 @@ function wrapRun (pl, isLatestVersion) {
50
50
 
51
51
  const asyncResource = new AsyncResource('bound-anonymous-fn')
52
52
  return asyncResource.runInAsyncScope(() => {
53
- runStartCh.publish({ pickleName: this.pickle.name, pickleUri: this.pickle.uri })
53
+ runStartCh.publish({ testName: this.pickle.name, fullTestSuite: this.pickle.uri })
54
54
  try {
55
55
  const promise = run.apply(this, arguments)
56
56
  promise.finally(() => {
@@ -8,6 +8,7 @@ module.exports = {
8
8
  '@grpc/grpc-js': () => require('../grpc'),
9
9
  '@hapi/hapi': () => require('../hapi'),
10
10
  '@jest/core': () => require('../jest'),
11
+ '@jest/reporters': () => require('../jest'),
11
12
  '@koa/router': () => require('../koa'),
12
13
  '@node-redis/client': () => require('../redis'),
13
14
  '@opensearch-project/opensearch': () => require('../opensearch'),
@@ -16,8 +17,11 @@ module.exports = {
16
17
  'amqplib': () => require('../amqplib'),
17
18
  'aws-sdk': () => require('../aws-sdk'),
18
19
  'bluebird': () => require('../bluebird'),
20
+ 'body-parser': () => require('../body-parser'),
19
21
  'bunyan': () => require('../bunyan'),
20
22
  'cassandra-driver': () => require('../cassandra-driver'),
23
+ 'child_process': () => require('../child-process'),
24
+ 'node:child_process': () => require('../child-process'),
21
25
  'connect': () => require('../connect'),
22
26
  'couchbase': () => require('../couchbase'),
23
27
  'crypto': () => require('../crypto'),
@@ -64,6 +68,7 @@ module.exports = {
64
68
  'promise-js': () => require('../promise-js'),
65
69
  'promise': () => require('../promise'),
66
70
  'q': () => require('../q'),
71
+ 'qs': () => require('../qs'),
67
72
  'redis': () => require('../redis'),
68
73
  'restify': () => require('../restify'),
69
74
  'rhea': () => require('../rhea'),
@@ -1,8 +1,8 @@
1
1
  'use strict'
2
- const istanbul = require('istanbul-lib-coverage')
3
2
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
3
  const shimmer = require('../../datadog-shimmer')
5
4
  const log = require('../../dd-trace/src/log')
5
+ const { getCoveredFilenamesFromCoverage } = require('../../dd-trace/src/plugins/util/test')
6
6
 
7
7
  const testSessionStartCh = channel('ci:jest:session:start')
8
8
  const testSessionFinishCh = channel('ci:jest:session:finish')
@@ -23,6 +23,7 @@ const jestConfigurationCh = channel('ci:jest:configuration')
23
23
 
24
24
  let skippableSuites = []
25
25
  let isCodeCoverageEnabled = false
26
+ let isSuitesSkippingEnabled = false
26
27
 
27
28
  const {
28
29
  getTestSuitePath,
@@ -33,21 +34,6 @@ const { getFormattedJestTestParameters, getJestTestName } = require('../../datad
33
34
 
34
35
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
35
36
 
36
- function extractCoverageInformation (coverage, rootDir) {
37
- const coverageMap = istanbul.createCoverageMap(coverage)
38
-
39
- return coverageMap
40
- .files()
41
- .filter(filename => {
42
- const fileCoverage = coverageMap.fileCoverageFor(filename)
43
- const lineCoverage = fileCoverage.getLineCoverage()
44
- const isAnyLineExecuted = Object.entries(lineCoverage).some(([, numExecutions]) => !!numExecutions)
45
-
46
- return isAnyLineExecuted
47
- })
48
- .map(filename => filename.replace(`${rootDir}/`, ''))
49
- }
50
-
51
37
  const specStatusToTestStatus = {
52
38
  'pending': 'skip',
53
39
  'disabled': 'skip',
@@ -186,38 +172,42 @@ addHook({
186
172
 
187
173
  function cliWrapper (cli) {
188
174
  const wrapped = shimmer.wrap(cli, 'runCLI', runCLI => async function () {
189
- let onResponse, onError
190
- const configurationPromise = new Promise((resolve, reject) => {
191
- onResponse = resolve
192
- onError = reject
175
+ let onDone
176
+ const configurationPromise = new Promise((resolve) => {
177
+ onDone = resolve
193
178
  })
194
179
 
195
180
  sessionAsyncResource.runInAsyncScope(() => {
196
- jestConfigurationCh.publish({ onResponse, onError })
181
+ jestConfigurationCh.publish({ onDone })
197
182
  })
198
183
 
199
- let isSuitesSkippingEnabled = false
200
-
201
184
  try {
202
- const config = await configurationPromise
185
+ const { err, config } = await configurationPromise
186
+ if (err) {
187
+ log.error(err)
188
+ }
203
189
  isCodeCoverageEnabled = config.isCodeCoverageEnabled
204
190
  isSuitesSkippingEnabled = config.isSuitesSkippingEnabled
205
191
  } catch (e) {
206
- // ignore error
192
+ log.error(e)
207
193
  }
208
194
 
209
195
  if (isSuitesSkippingEnabled) {
210
- const skippableSuitesPromise = new Promise((resolve, reject) => {
211
- onResponse = resolve
212
- onError = reject
196
+ const skippableSuitesPromise = new Promise((resolve) => {
197
+ onDone = resolve
213
198
  })
214
199
 
215
200
  sessionAsyncResource.runInAsyncScope(() => {
216
- skippableSuitesCh.publish({ onResponse, onError })
201
+ skippableSuitesCh.publish({ onDone })
217
202
  })
218
203
 
219
204
  try {
220
- skippableSuites = await skippableSuitesPromise
205
+ const { err, skippableSuites: receivedSkippableSuites } = await skippableSuitesPromise
206
+ if (err) {
207
+ log.error(err)
208
+ } else {
209
+ skippableSuites = receivedSkippableSuites
210
+ }
221
211
  } catch (e) {
222
212
  log.error(e)
223
213
  }
@@ -253,6 +243,35 @@ function cliWrapper (cli) {
253
243
  return cli
254
244
  }
255
245
 
246
+ function coverageReporterWrapper (coverageReporter) {
247
+ const CoverageReporter = coverageReporter.default ? coverageReporter.default : coverageReporter
248
+
249
+ /**
250
+ * If ITR is active, we're running fewer tests, so of course the total code coverage is reduced.
251
+ * This calculation adds no value, so we'll skip it.
252
+ */
253
+ shimmer.wrap(CoverageReporter.prototype, '_addUntestedFiles', addUntestedFiles => async function () {
254
+ if (isSuitesSkippingEnabled) {
255
+ return Promise.resolve()
256
+ }
257
+ return addUntestedFiles.apply(this, arguments)
258
+ })
259
+
260
+ return coverageReporter
261
+ }
262
+
263
+ addHook({
264
+ name: '@jest/reporters',
265
+ file: 'build/coverage_reporter.js',
266
+ versions: ['>=24.8.0 <26.6.2']
267
+ }, coverageReporterWrapper)
268
+
269
+ addHook({
270
+ name: '@jest/reporters',
271
+ file: 'build/CoverageReporter.js',
272
+ versions: ['>=26.6.2']
273
+ }, coverageReporterWrapper)
274
+
256
275
  addHook({
257
276
  name: '@jest/core',
258
277
  file: 'build/cli/index.js',
@@ -263,6 +282,9 @@ function jestAdapterWrapper (jestAdapter) {
263
282
  const adapter = jestAdapter.default ? jestAdapter.default : jestAdapter
264
283
  const newAdapter = shimmer.wrap(adapter, function () {
265
284
  const environment = arguments[2]
285
+ if (!environment) {
286
+ return adapter.apply(this, arguments)
287
+ }
266
288
  const asyncResource = new AsyncResource('bound-anonymous-fn')
267
289
  return asyncResource.runInAsyncScope(() => {
268
290
  testSuiteStartCh.publish({
@@ -278,11 +300,16 @@ function jestAdapterWrapper (jestAdapter) {
278
300
  status = 'fail'
279
301
  }
280
302
  testSuiteFinishCh.publish({ status, errorMessage })
281
- if (environment.global.__coverage__) {
282
- const coverageFiles = extractCoverageInformation(environment.global.__coverage__, environment.rootDir)
283
- if (coverageFiles.length) {
303
+
304
+ const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
305
+ .map(filename => getTestSuitePath(filename, environment.rootDir))
306
+
307
+ if (coverageFiles &&
308
+ environment.testEnvironmentOptions &&
309
+ environment.testEnvironmentOptions._ddTestCodeCoverageEnabled) {
310
+ asyncResource.runInAsyncScope(() => {
284
311
  testSuiteCodeCoverageCh.publish([...coverageFiles, environment.testSuite])
285
- }
312
+ })
286
313
  }
287
314
  return suiteResults
288
315
  })
@@ -305,15 +332,15 @@ addHook({
305
332
 
306
333
  function configureTestEnvironment (readConfigsResult) {
307
334
  const { configs } = readConfigsResult
308
- configs.forEach(config => {
309
- skippableSuites.forEach((suite) => {
310
- config.testMatch.push(`!**/${suite}`)
311
- })
312
- skippableSuites = []
313
- })
314
335
  sessionAsyncResource.runInAsyncScope(() => {
315
336
  testSessionConfigurationCh.publish(configs.map(config => config.testEnvironmentOptions))
316
337
  })
338
+ // We can't directly use isCodeCoverageEnabled when reporting coverage in `jestAdapterWrapper`
339
+ // because `jestAdapterWrapper` runs in a different process. We have to go through `testEnvironmentOptions`
340
+ configs.forEach(config => {
341
+ config.testEnvironmentOptions._ddTestCodeCoverageEnabled = isCodeCoverageEnabled
342
+ })
343
+
317
344
  if (isCodeCoverageEnabled) {
318
345
  const globalConfig = {
319
346
  ...readConfigsResult.globalConfig,
@@ -321,6 +348,16 @@ function configureTestEnvironment (readConfigsResult) {
321
348
  }
322
349
  readConfigsResult.globalConfig = globalConfig
323
350
  }
351
+ if (isSuitesSkippingEnabled) {
352
+ // If suite skipping is enabled, the code coverage results are not going to be relevant,
353
+ // so we do not show them.
354
+ const globalConfig = {
355
+ ...readConfigsResult.globalConfig,
356
+ coverageReporters: ['none']
357
+ }
358
+ readConfigsResult.globalConfig = globalConfig
359
+ }
360
+
324
361
  return readConfigsResult
325
362
  }
326
363
 
@@ -342,6 +379,39 @@ function jestConfigSyncWrapper (jestConfig) {
342
379
  return jestConfig
343
380
  }
344
381
 
382
+ /**
383
+ * Hook to remove the test paths (test suite) that are part of `skippableSuites`
384
+ */
385
+ addHook({
386
+ name: '@jest/core',
387
+ versions: ['>=24.8.0'],
388
+ file: 'build/SearchSource.js'
389
+ }, searchSourcePackage => {
390
+ const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
391
+
392
+ shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
393
+ if (!skippableSuites.length) {
394
+ return getTestPaths.apply(this, arguments)
395
+ }
396
+
397
+ const [{ rootDir }] = arguments
398
+
399
+ const testPaths = await getTestPaths.apply(this, arguments)
400
+ const { tests } = testPaths
401
+
402
+ const filteredTests = tests.filter(({ path: testPath }) => {
403
+ const relativePath = testPath.replace(`${rootDir}/`, '')
404
+ return !skippableSuites.includes(relativePath)
405
+ })
406
+
407
+ skippableSuites = []
408
+
409
+ return { ...testPaths, tests: filteredTests }
410
+ })
411
+
412
+ return searchSourcePackage
413
+ })
414
+
345
415
  // from 25.1.0 on, readConfigs becomes async
346
416
  addHook({
347
417
  name: 'jest-config',
@@ -1,5 +1,15 @@
1
+ const { createCoverageMap } = require('istanbul-lib-coverage')
2
+
1
3
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
4
  const shimmer = require('../../datadog-shimmer')
5
+ const log = require('../../dd-trace/src/log')
6
+ const {
7
+ getCoveredFilenamesFromCoverage,
8
+ resetCoverage,
9
+ mergeCoverage,
10
+ getTestSuitePath,
11
+ fromCoverageMapToCoverage
12
+ } = require('../../dd-trace/src/plugins/util/test')
3
13
 
4
14
  const testStartCh = channel('ci:mocha:test:start')
5
15
  const errorCh = channel('ci:mocha:test:error')
@@ -7,12 +17,16 @@ const skipCh = channel('ci:mocha:test:skip')
7
17
  const testFinishCh = channel('ci:mocha:test:finish')
8
18
  const parameterizedTestCh = channel('ci:mocha:test:parameterize')
9
19
 
20
+ const configurationCh = channel('ci:mocha:configuration')
21
+ const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
22
+
10
23
  const testSessionStartCh = channel('ci:mocha:session:start')
11
24
  const testSessionFinishCh = channel('ci:mocha:session:finish')
12
25
 
13
26
  const testSuiteStartCh = channel('ci:mocha:test-suite:start')
14
27
  const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
15
28
  const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
29
+ const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
16
30
 
17
31
  // TODO: remove when root hooks and fixtures are implemented
18
32
  const patched = new WeakSet()
@@ -21,6 +35,11 @@ const testToAr = new WeakMap()
21
35
  const originalFns = new WeakMap()
22
36
  const testFileToSuiteAr = new Map()
23
37
 
38
+ // We'll preserve the original coverage here
39
+ const originalCoverageMap = createCoverageMap()
40
+
41
+ let suitesToSkip = []
42
+
24
43
  function getSuitesByTestFile (root) {
25
44
  const suitesByTestFile = {}
26
45
  function getSuites (suite) {
@@ -46,13 +65,13 @@ function getSuitesByTestFile (root) {
46
65
  }
47
66
 
48
67
  function getTestStatus (test) {
49
- if (test.pending) {
68
+ if (test.isPending()) {
50
69
  return 'skip'
51
70
  }
52
- if (test.state !== 'failed' && !test.timedOut) {
53
- return 'pass'
71
+ if (test.isFailed() || test.timedOut) {
72
+ return 'fail'
54
73
  }
55
- return 'fail'
74
+ return 'pass'
56
75
  }
57
76
 
58
77
  function isRetry (test) {
@@ -93,6 +112,8 @@ function mochaHook (Runner) {
93
112
  }
94
113
  testFileToSuiteAr.clear()
95
114
  testSessionFinishCh.publish(status)
115
+ // restore the original coverage
116
+ global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
96
117
  }))
97
118
 
98
119
  this.once('start', testRunAsyncResource.bind(function () {
@@ -140,9 +161,21 @@ function mochaHook (Runner) {
140
161
  })
141
162
  }
142
163
 
164
+ if (global.__coverage__) {
165
+ const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
166
+
167
+ testSuiteCodeCoverageCh.publish({
168
+ coverageFiles,
169
+ suiteFile: suite.file
170
+ })
171
+ // We need to reset coverage to get a code coverage per suite
172
+ // Before that, we preserve the original coverage
173
+ mergeCoverage(global.__coverage__, originalCoverageMap)
174
+ resetCoverage(global.__coverage__)
175
+ }
176
+
143
177
  const asyncResource = testFileToSuiteAr.get(suite.file)
144
178
  asyncResource.runInAsyncScope(() => {
145
- // get suite status
146
179
  testSuiteFinishCh.publish(status)
147
180
  })
148
181
  })
@@ -229,7 +262,8 @@ function mochaHook (Runner) {
229
262
  skipCh.publish(test)
230
263
  })
231
264
  } else {
232
- // if there is no async resource, the test has been skipped through `test.skip``
265
+ // if there is no async resource, the test has been skipped through `test.skip`
266
+ // or the parent suite is skipped
233
267
  const skippedTestAsyncResource = new AsyncResource('bound-anonymous-fn')
234
268
  if (test.fn) {
235
269
  testToAr.set(test.fn, skippedTestAsyncResource)
@@ -242,6 +276,11 @@ function mochaHook (Runner) {
242
276
  }
243
277
  })
244
278
 
279
+ // We remove the suites that we skip through ITR
280
+ this.suite.suites = this.suite.suites.filter(suite =>
281
+ !suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
282
+ )
283
+
245
284
  return run.apply(this, arguments)
246
285
  })
247
286
 
@@ -266,6 +305,48 @@ function mochaEachHook (mochaEach) {
266
305
  })
267
306
  }
268
307
 
308
+ addHook({
309
+ name: 'mocha',
310
+ versions: ['>=5.2.0'],
311
+ file: 'lib/mocha.js'
312
+ }, (Mocha) => {
313
+ const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
314
+
315
+ /**
316
+ * Get ITR configuration and skippable suites
317
+ * If ITR is disabled, `onDone` is called immediately on the subscriber
318
+ */
319
+ shimmer.wrap(Mocha.prototype, 'run', run => function () {
320
+ const onReceivedSkippableSuites = ({ err, skippableSuites }) => {
321
+ if (err) {
322
+ log.error(err)
323
+ suitesToSkip = []
324
+ } else {
325
+ suitesToSkip = skippableSuites
326
+ }
327
+ run.apply(this, arguments)
328
+ }
329
+
330
+ const onReceivedConfiguration = ({ err }) => {
331
+ if (err) {
332
+ log.error(err)
333
+ return run.apply(this, arguments)
334
+ }
335
+
336
+ skippableSuitesCh.publish({
337
+ onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
338
+ })
339
+ }
340
+
341
+ mochaRunAsyncResource.runInAsyncScope(() => {
342
+ configurationCh.publish({
343
+ onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
344
+ })
345
+ })
346
+ })
347
+ return Mocha
348
+ })
349
+
269
350
  addHook({
270
351
  name: 'mocha',
271
352
  versions: ['>=5.2.0'],
@@ -37,13 +37,12 @@ function wrapQuery (query) {
37
37
  return retval
38
38
  }
39
39
 
40
- const statement = pgQuery.text
41
40
  const callbackResource = new AsyncResource('bound-anonymous-fn')
42
41
  const asyncResource = new AsyncResource('bound-anonymous-fn')
43
42
  const processId = this.processID
44
43
 
45
44
  return asyncResource.runInAsyncScope(() => {
46
- startCh.publish({ params: this.connectionParameters, statement, processId })
45
+ startCh.publish({ params: this.connectionParameters, query: pgQuery, processId })
47
46
 
48
47
  const finish = asyncResource.bind(function (error) {
49
48
  if (error) {
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const { addHook, channel } = require('./helpers/instrument')
4
+ const shimmer = require('../../datadog-shimmer')
5
+
6
+ const qsParseCh = channel('datadog:qs:parse:finish')
7
+
8
+ function wrapParse (originalParse) {
9
+ return function () {
10
+ const qsParsedObj = originalParse.apply(this, arguments)
11
+ if (qsParseCh.hasSubscribers && qsParsedObj) {
12
+ qsParseCh.publish({ qs: qsParsedObj })
13
+ }
14
+ return qsParsedObj
15
+ }
16
+ }
17
+
18
+ addHook({
19
+ name: 'qs',
20
+ versions: ['>=1']
21
+ }, qs => {
22
+ shimmer.wrap(qs, 'parse', wrapParse)
23
+ return qs
24
+ })
@@ -1,24 +1,18 @@
1
1
  'use strict'
2
2
 
3
- const Plugin = require('../../dd-trace/src/plugins/plugin')
3
+ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
 
6
6
  const {
7
- CI_APP_ORIGIN,
8
7
  TEST_SKIP_REASON,
9
- ERROR_MESSAGE,
10
8
  TEST_STATUS,
11
- TEST_CODE_OWNERS,
12
9
  finishAllTraceSpans,
13
- getTestEnvironmentMetadata,
14
- getTestSuitePath,
15
- getCodeOwnersFileEntries,
16
- getCodeOwnersForFilename,
17
- getTestCommonTags
10
+ getTestSuitePath
18
11
  } = require('../../dd-trace/src/plugins/util/test')
19
12
  const { RESOURCE_NAME } = require('../../../ext/tags')
13
+ const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
20
14
 
21
- class CucumberPlugin extends Plugin {
15
+ class CucumberPlugin extends CiPlugin {
22
16
  static get name () {
23
17
  return 'cucumber'
24
18
  }
@@ -26,36 +20,18 @@ class CucumberPlugin extends Plugin {
26
20
  constructor (...args) {
27
21
  super(...args)
28
22
 
29
- const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber', this.config)
30
- const sourceRoot = process.cwd()
31
- const codeOwnersEntries = getCodeOwnersFileEntries(sourceRoot)
32
-
33
23
  this.addSub('ci:cucumber:session:finish', () => {
34
24
  this.tracer._exporter._writer.flush()
35
25
  })
36
26
 
37
- this.addSub('ci:cucumber:run:start', ({ pickleName, pickleUri }) => {
27
+ this.addSub('ci:cucumber:run:start', ({ testName, fullTestSuite }) => {
38
28
  const store = storage.getStore()
39
29
  const childOf = store ? store.span : store
40
- const testSuite = getTestSuitePath(pickleUri, sourceRoot)
41
-
42
- const commonTags = getTestCommonTags(pickleName, testSuite, this.tracer._version)
30
+ const testSuite = getTestSuitePath(fullTestSuite, process.cwd())
43
31
 
44
- const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
45
- if (codeOwners) {
46
- commonTags[TEST_CODE_OWNERS] = codeOwners
47
- }
32
+ const testSpan = this.startTestSpan(testName, testSuite, childOf)
48
33
 
49
- const span = this.tracer.startSpan('cucumber.test', {
50
- childOf,
51
- tags: {
52
- ...commonTags,
53
- ...testEnvironmentMetadata
54
- }
55
- })
56
-
57
- span.context()._trace.origin = CI_APP_ORIGIN
58
- this.enter(span, store)
34
+ this.enter(testSpan, store)
59
35
  })
60
36
 
61
37
  this.addSub('ci:cucumber:run-step:start', ({ resource }) => {
@@ -64,6 +40,7 @@ class CucumberPlugin extends Plugin {
64
40
  const span = this.tracer.startSpan('cucumber.step', {
65
41
  childOf,
66
42
  tags: {
43
+ [COMPONENT]: this.constructor.name,
67
44
  'cucumber.step': resource,
68
45
  [RESOURCE_NAME]: resource
69
46
  }
@@ -98,6 +75,10 @@ class CucumberPlugin extends Plugin {
98
75
  }
99
76
  })
100
77
  }
78
+
79
+ startTestSpan (testName, testSuite, childOf) {
80
+ return super.startTestSpan(testName, testSuite, {}, childOf)
81
+ }
101
82
  }
102
83
 
103
84
  module.exports = CucumberPlugin