dd-trace 3.21.0 → 3.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/package.json +3 -2
  3. package/packages/datadog-esbuild/index.js +13 -1
  4. package/packages/datadog-instrumentations/src/cucumber.js +13 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  6. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  7. package/packages/datadog-instrumentations/src/http/server.js +14 -0
  8. package/packages/datadog-instrumentations/src/http2/client.js +4 -0
  9. package/packages/datadog-instrumentations/src/pg.js +14 -11
  10. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  11. package/packages/datadog-instrumentations/src/sequelize.js +51 -0
  12. package/packages/datadog-plugin-amqp10/src/consumer.js +1 -3
  13. package/packages/datadog-plugin-amqp10/src/producer.js +1 -3
  14. package/packages/datadog-plugin-amqplib/src/client.js +4 -3
  15. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  16. package/packages/datadog-plugin-amqplib/src/producer.js +1 -3
  17. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  18. package/packages/datadog-plugin-cypress/src/plugin.js +150 -30
  19. package/packages/datadog-plugin-cypress/src/support.js +6 -3
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -3
  21. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -3
  22. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -3
  23. package/packages/datadog-plugin-http/src/client.js +70 -68
  24. package/packages/datadog-plugin-http2/src/client.js +50 -47
  25. package/packages/datadog-plugin-jest/src/index.js +5 -4
  26. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -4
  27. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -3
  28. package/packages/datadog-plugin-memcached/src/index.js +2 -3
  29. package/packages/datadog-plugin-mocha/src/index.js +4 -2
  30. package/packages/datadog-plugin-pg/src/index.js +1 -1
  31. package/packages/datadog-plugin-redis/src/index.js +2 -13
  32. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  33. package/packages/datadog-plugin-rhea/src/producer.js +1 -5
  34. package/packages/datadog-plugin-router/src/index.js +12 -1
  35. package/packages/dd-trace/src/appsec/blocked_templates.js +2 -101
  36. package/packages/dd-trace/src/appsec/blocking.js +60 -11
  37. package/packages/dd-trace/src/appsec/channels.js +3 -2
  38. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +7 -5
  39. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -0
  40. package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +31 -0
  41. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -0
  42. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +47 -0
  43. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +30 -5
  44. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +26 -0
  45. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +35 -3
  46. package/packages/dd-trace/src/appsec/iast/path-line.js +14 -7
  47. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +19 -4
  48. package/packages/dd-trace/src/appsec/iast/telemetry/logs.js +1 -1
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +25 -2
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +49 -0
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -1
  52. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +7 -5
  53. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +0 -33
  54. package/packages/dd-trace/src/appsec/recommended.json +45 -46
  55. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +3 -1
  56. package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
  57. package/packages/dd-trace/src/appsec/rule_manager.js +49 -6
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -7
  59. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  60. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -6
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +10 -4
  62. package/packages/dd-trace/src/config.js +36 -5
  63. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +11 -3
  64. package/packages/dd-trace/src/exporters/common/util.js +9 -0
  65. package/packages/dd-trace/src/exporters/common/writer.js +3 -2
  66. package/packages/dd-trace/src/plugin_manager.js +2 -0
  67. package/packages/dd-trace/src/plugins/cache.js +7 -0
  68. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  69. package/packages/dd-trace/src/plugins/client.js +3 -2
  70. package/packages/dd-trace/src/plugins/consumer.js +14 -2
  71. package/packages/dd-trace/src/plugins/database.js +2 -2
  72. package/packages/dd-trace/src/plugins/inbound.js +7 -0
  73. package/packages/dd-trace/src/plugins/{outgoing.js → outbound.js} +2 -2
  74. package/packages/dd-trace/src/plugins/producer.js +19 -2
  75. package/packages/dd-trace/src/plugins/server.js +2 -2
  76. package/packages/dd-trace/src/plugins/storage.js +2 -0
  77. package/packages/dd-trace/src/plugins/tracing.js +11 -0
  78. package/packages/dd-trace/src/plugins/util/ci.js +1 -1
  79. package/packages/dd-trace/src/profiling/config.js +4 -2
  80. package/packages/dd-trace/src/service-naming/index.js +30 -0
  81. package/packages/dd-trace/src/service-naming/schemas/definition.js +24 -0
  82. package/packages/dd-trace/src/service-naming/schemas/index.js +6 -0
  83. package/packages/dd-trace/src/service-naming/schemas/util.js +5 -0
  84. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +5 -0
  85. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +64 -0
  86. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +33 -0
  87. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +5 -0
  88. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +52 -0
  89. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +21 -0
  90. package/packages/dd-trace/src/telemetry/index.js +5 -6
  91. package/packages/dd-trace/src/telemetry/send-data.js +17 -5
  92. package/packages/dd-trace/src/plugins/incoming.js +0 -7
@@ -51,6 +51,7 @@ dev,glob,ISC,Copyright Isaac Z. Schlueter and Contributors
51
51
  dev,graphql,MIT,Copyright 2015 Facebook Inc.
52
52
  dev,int64-buffer,MIT,Copyright 2015-2016 Yusuke Kawasaki
53
53
  dev,jszip,MIT,Copyright 2015-2016 Stuart Knightley and contributors
54
+ dev,knex,MIT,Copyright (c) 2013-present Tim Griesser
54
55
  dev,mkdirp,MIT,Copyright 2010 James Halliday
55
56
  dev,mocha,MIT,Copyright 2011-2018 JS Foundation and contributors https://js.foundation
56
57
  dev,multer,MIT,Copyright 2014 Hage Yaapa
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "3.21.0",
3
+ "version": "3.22.1",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -66,7 +66,7 @@
66
66
  "node": ">=14"
67
67
  },
68
68
  "dependencies": {
69
- "@datadog/native-appsec": "^3.1.0",
69
+ "@datadog/native-appsec": "^3.2.0",
70
70
  "@datadog/native-iast-rewriter": "2.0.1",
71
71
  "@datadog/native-iast-taint-tracking": "^1.4.1",
72
72
  "@datadog/native-metrics": "^2.0.0",
@@ -120,6 +120,7 @@
120
120
  "graphql": "0.13.2",
121
121
  "int64-buffer": "^0.1.9",
122
122
  "jszip": "^3.5.0",
123
+ "knex": "^2.4.2",
123
124
  "mkdirp": "^0.5.1",
124
125
  "mocha": "8",
125
126
  "msgpack-lite": "^0.1.26",
@@ -39,7 +39,19 @@ module.exports.setup = function (build) {
39
39
 
40
40
  if (args.namespace === 'file' && packagesOfInterest.has(packageName)) {
41
41
  // The file namespace is used when requiring files from disk in userland
42
- const pathToPackageJson = require.resolve(`${packageName}/package.json`, { paths: [ args.resolveDir ] })
42
+
43
+ let pathToPackageJson
44
+ try {
45
+ pathToPackageJson = require.resolve(`${packageName}/package.json`, { paths: [ args.resolveDir ] })
46
+ } catch (err) {
47
+ if (err.code === 'MODULE_NOT_FOUND') {
48
+ console.warn(`Unable to open "${packageName}/package.json". Is the "${packageName}" package dead code?`)
49
+ return
50
+ } else {
51
+ throw err
52
+ }
53
+ }
54
+
43
55
  const pkg = require(pathToPackageJson)
44
56
 
45
57
  if (DEBUG) {
@@ -3,6 +3,7 @@ const { createCoverageMap } = require('istanbul-lib-coverage')
3
3
 
4
4
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
5
5
  const shimmer = require('../../datadog-shimmer')
6
+ const log = require('../../dd-trace/src/log')
6
7
 
7
8
  const testStartCh = channel('ci:cucumber:test:start')
8
9
  const testFinishCh = channel('ci:cucumber:test:finish') // used for test steps too
@@ -175,6 +176,12 @@ function wrapRun (pl, isLatestVersion) {
175
176
  }
176
177
 
177
178
  function pickleHook (PickleRunner) {
179
+ if (process.env.CUCUMBER_WORKER_ID) {
180
+ // Parallel mode is not supported
181
+ log.warn('Unable to initialize CI Visibility because Cucumber is running in parallel mode.')
182
+ return PickleRunner
183
+ }
184
+
178
185
  const pl = PickleRunner.default
179
186
 
180
187
  wrapRun(pl, false)
@@ -183,6 +190,12 @@ function pickleHook (PickleRunner) {
183
190
  }
184
191
 
185
192
  function testCaseHook (TestCaseRunner) {
193
+ if (process.env.CUCUMBER_WORKER_ID) {
194
+ // Parallel mode is not supported
195
+ log.warn('Unable to initialize CI Visibility because Cucumber is running in parallel mode.')
196
+ return TestCaseRunner
197
+ }
198
+
186
199
  const pl = TestCaseRunner.default
187
200
 
188
201
  wrapRun(pl, true)
@@ -80,6 +80,7 @@ module.exports = {
80
80
  'rhea': () => require('../rhea'),
81
81
  'router': () => require('../router'),
82
82
  'sharedb': () => require('../sharedb'),
83
+ 'sequelize': () => require('../sequelize'),
83
84
  'tedious': () => require('../tedious'),
84
85
  'when': () => require('../when'),
85
86
  'winston': () => require('../winston')
@@ -101,6 +101,7 @@ function patch (http, methodName) {
101
101
  }
102
102
 
103
103
  function normalizeArgs (inputURL, inputOptions, cb) {
104
+ const originalUrl = inputURL
104
105
  inputURL = normalizeOptions(inputURL)
105
106
 
106
107
  const [callback, inputOptionsNormalized] = normalizeCallback(inputOptions, cb, inputURL)
@@ -108,7 +109,7 @@ function patch (http, methodName) {
108
109
  normalizeHeaders(options)
109
110
  const uri = url.format(options)
110
111
 
111
- return { uri, options, callback }
112
+ return { uri, options, callback, originalUrl }
112
113
  }
113
114
 
114
115
  function combineOptions (inputURL, inputOptions) {
@@ -11,6 +11,7 @@ const startServerCh = channel('apm:http:server:request:start')
11
11
  const exitServerCh = channel('apm:http:server:request:exit')
12
12
  const errorServerCh = channel('apm:http:server:request:error')
13
13
  const finishServerCh = channel('apm:http:server:request:finish')
14
+ const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finish')
14
15
 
15
16
  const requestFinishedSet = new WeakSet()
16
17
 
@@ -58,6 +59,9 @@ function wrapEmit (emit) {
58
59
  // TODO: should this always return true ?
59
60
  return this.listenerCount(eventName) > 0
60
61
  }
62
+ if (finishSetHeaderCh.hasSubscribers) {
63
+ wrapSetHeader(res)
64
+ }
61
65
  return emit.apply(this, arguments)
62
66
  } catch (err) {
63
67
  errorServerCh.publish(err)
@@ -70,3 +74,13 @@ function wrapEmit (emit) {
70
74
  return emit.apply(this, arguments)
71
75
  }
72
76
  }
77
+
78
+ function wrapSetHeader (res) {
79
+ shimmer.wrap(res, 'setHeader', setHeader => {
80
+ return function (name, value) {
81
+ const setHeaderResult = setHeader.apply(this, arguments)
82
+ finishSetHeaderCh.publish({ name, value, res })
83
+ return setHeaderResult
84
+ }
85
+ })
86
+ }
@@ -3,6 +3,7 @@
3
3
  const shimmer = require('../../../datadog-shimmer')
4
4
  const { addHook, channel, AsyncResource } = require('../helpers/instrument')
5
5
 
6
+ const connectChannel = channel('apm:http2:client:connect:start')
6
7
  const startChannel = channel('apm:http2:client:request:start')
7
8
  const finishChannel = channel('apm:http2:client:request:finish')
8
9
  const errorChannel = channel('apm:http2:client:request:error')
@@ -52,6 +53,9 @@ function createWrapRequest (authority, options) {
52
53
 
53
54
  function wrapConnect (connect) {
54
55
  return function (authority, options) {
56
+ if (connectChannel.hasSubscribers) {
57
+ connectChannel.publish({ authority })
58
+ }
55
59
  const session = connect.apply(this, arguments)
56
60
 
57
61
  shimmer.wrap(session, 'request', createWrapRequest(authority, options))
@@ -31,16 +31,19 @@ function wrapQuery (query) {
31
31
  const asyncResource = new AsyncResource('bound-anonymous-fn')
32
32
  const processId = this.processID
33
33
 
34
- let pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
34
+ const pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
35
+
36
+ // shallow clone the existing query to swap out .text field
37
+ let newQuery = { ...pgQuery }
35
38
 
36
39
  return asyncResource.runInAsyncScope(() => {
37
40
  startCh.publish({
38
41
  params: this.connectionParameters,
39
- query: pgQuery,
42
+ query: newQuery,
40
43
  processId
41
44
  })
42
45
 
43
- arguments[0] = pgQuery
46
+ arguments[0] = newQuery
44
47
 
45
48
  const finish = asyncResource.bind(function (error) {
46
49
  if (error) {
@@ -53,24 +56,24 @@ function wrapQuery (query) {
53
56
  const queryQueue = this.queryQueue || this._queryQueue
54
57
  const activeQuery = this.activeQuery || this._activeQuery
55
58
 
56
- pgQuery = queryQueue[queryQueue.length - 1] || activeQuery
59
+ newQuery = queryQueue[queryQueue.length - 1] || activeQuery
57
60
 
58
- if (!pgQuery) {
61
+ if (!newQuery) {
59
62
  return retval
60
63
  }
61
64
 
62
- if (pgQuery.callback) {
63
- const originalCallback = callbackResource.bind(pgQuery.callback)
64
- pgQuery.callback = function (err, res) {
65
+ if (newQuery.callback) {
66
+ const originalCallback = callbackResource.bind(newQuery.callback)
67
+ newQuery.callback = function (err, res) {
65
68
  finish(err)
66
69
  return originalCallback.apply(this, arguments)
67
70
  }
68
- } else if (pgQuery.once) {
69
- pgQuery
71
+ } else if (newQuery.once) {
72
+ newQuery
70
73
  .once('error', finish)
71
74
  .once('end', () => finish())
72
75
  } else {
73
- pgQuery.then(() => finish(), finish)
76
+ newQuery.then(() => finish(), finish)
74
77
  }
75
78
 
76
79
  try {
@@ -248,7 +248,7 @@ function runnerHook (runnerExport, playwrightVersion) {
248
248
  addHook({
249
249
  name: '@playwright/test',
250
250
  file: 'lib/runner.js',
251
- versions: ['>=1.18.0 <1.30.0']
251
+ versions: ['>=1.18.0 <=1.30.0']
252
252
  }, runnerHook)
253
253
 
254
254
  addHook({
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook,
6
+ AsyncResource
7
+ } = require('./helpers/instrument')
8
+
9
+ const shimmer = require('../../datadog-shimmer')
10
+
11
+ addHook({ name: 'sequelize', versions: ['>=4'] }, Sequelize => {
12
+ const startCh = channel('datadog:sequelize:query:start')
13
+ const finishCh = channel('datadog:sequelize:query:finish')
14
+
15
+ shimmer.wrap(Sequelize.prototype, 'query', query => {
16
+ return function (sql) {
17
+ if (!startCh.hasSubscribers) {
18
+ return query.apply(this, arguments)
19
+ }
20
+
21
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
22
+
23
+ let dialect
24
+ if (this.options && this.options.dialect) {
25
+ dialect = this.options.dialect
26
+ } else if (this.dialect && this.dialect.name) {
27
+ dialect = this.dialect.name
28
+ }
29
+
30
+ function onFinish () {
31
+ asyncResource.bind(function () {
32
+ finishCh.publish()
33
+ }, this).apply(this)
34
+ }
35
+
36
+ return asyncResource.bind(function () {
37
+ startCh.publish({
38
+ sql,
39
+ dialect
40
+ })
41
+
42
+ const promise = query.apply(this, arguments)
43
+ promise.then(onFinish, onFinish)
44
+
45
+ return promise
46
+ }, this).apply(this, arguments)
47
+ }
48
+ })
49
+
50
+ return Sequelize
51
+ })
@@ -11,11 +11,9 @@ class Amqp10ConsumerPlugin extends ConsumerPlugin {
11
11
  const source = getShortName(link)
12
12
  const address = getAddress(link)
13
13
 
14
- this.startSpan('amqp.receive', {
15
- service: this.config.service || `${this.tracer._service}-amqp`,
14
+ this.startSpan({
16
15
  resource: ['receive', source].filter(v => v).join(' '),
17
16
  type: 'worker',
18
- kind: 'consumer',
19
17
  meta: {
20
18
  'amqp.link.source.address': source,
21
19
  'amqp.link.role': 'receiver',
@@ -13,10 +13,8 @@ class Amqp10ProducerPlugin extends ProducerPlugin {
13
13
  const address = getAddress(link)
14
14
  const target = getShortName(link)
15
15
 
16
- this.startSpan('amqp.send', {
17
- service: this.config.service || `${this.tracer._service}-amqp`,
16
+ this.startSpan({
18
17
  resource: ['send', target].filter(v => v).join(' '),
19
- kind: 'producer',
20
18
  meta: {
21
19
  'amqp.link.target.address': target,
22
20
  'amqp.link.role': 'sender',
@@ -7,6 +7,7 @@ const { getResourceName } = require('./util')
7
7
 
8
8
  class AmqplibClientPlugin extends ClientPlugin {
9
9
  static get id () { return 'amqplib' }
10
+ static get type () { return 'messaging' }
10
11
  static get operation () { return 'command' }
11
12
 
12
13
  start ({ channel = {}, method, fields }) {
@@ -14,10 +15,10 @@ class AmqplibClientPlugin extends ClientPlugin {
14
15
  if (method === 'basic.publish') return
15
16
 
16
17
  const stream = (channel.connection && channel.connection.stream) || {}
17
- const span = this.startSpan('amqp.command', {
18
- service: this.config.service || `${this.tracer._service}-amqp`,
18
+ const span = this.startSpan(this.operationName(), {
19
+ service: this.config.service || this.serviceName(),
19
20
  resource: getResourceName(method, fields),
20
- kind: 'client',
21
+ kind: this.constructor.kind,
21
22
  meta: {
22
23
  'out.host': stream._host,
23
24
  [CLIENT_PORT_KEY]: stream.remotePort,
@@ -13,11 +13,9 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
13
13
 
14
14
  const childOf = extract(this.tracer, message)
15
15
 
16
- this.startSpan('amqp.command', {
16
+ this.startSpan({
17
17
  childOf,
18
- service: this.config.service || `${this.tracer._service}-amqp`,
19
18
  resource: getResourceName(method, fields),
20
- kind: 'consumer',
21
19
  type: 'worker',
22
20
  meta: {
23
21
  'amqp.queue': fields.queue,
@@ -13,10 +13,8 @@ class AmqplibProducerPlugin extends ProducerPlugin {
13
13
  if (method !== 'basic.publish') return
14
14
 
15
15
  const stream = (channel.connection && channel.connection.stream) || {}
16
- const span = this.startSpan('amqp.command', {
17
- service: this.config.service || `${this.tracer._service}-amqp`,
16
+ const span = this.startSpan({
18
17
  resource: getResourceName(method, fields),
19
- kind: 'producer',
20
18
  meta: {
21
19
  'out.host': stream._host,
22
20
  [CLIENT_PORT_KEY]: stream.remotePort,
@@ -73,8 +73,8 @@ class CucumberPlugin extends CiPlugin {
73
73
  .map(filename => getTestSuitePath(filename, this.sourceRoot))
74
74
 
75
75
  const formattedCoverage = {
76
- traceId: this.testSuiteSpan.context()._traceId,
77
- spanId: this.testSuiteSpan.context()._spanId,
76
+ sessionId: this.testSuiteSpan.context()._traceId,
77
+ suiteId: this.testSuiteSpan.context()._spanId,
78
78
  files: relativeCoverageFiles
79
79
  }
80
80
 
@@ -17,13 +17,16 @@ const {
17
17
  TEST_COMMAND,
18
18
  TEST_MODULE,
19
19
  TEST_SOURCE_START,
20
- finishAllTraceSpans
20
+ finishAllTraceSpans,
21
+ getCoveredFilenamesFromCoverage,
22
+ getTestSuitePath,
23
+ addIntelligentTestRunnerSpanTags
21
24
  } = require('../../dd-trace/src/plugins/util/test')
25
+ const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
26
+ const log = require('../../dd-trace/src/log')
22
27
 
23
28
  const TEST_FRAMEWORK_NAME = 'cypress'
24
29
 
25
- const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
26
-
27
30
  const CYPRESS_STATUS_TO_TEST_STATUS = {
28
31
  passed: 'pass',
29
32
  failed: 'fail',
@@ -52,6 +55,13 @@ function getCypressVersion (details) {
52
55
  return ''
53
56
  }
54
57
 
58
+ function getRootDir (details) {
59
+ if (details && details.config) {
60
+ return details.config.projectRoot || details.config.repoRoot || process.cwd()
61
+ }
62
+ return process.cwd()
63
+ }
64
+
55
65
  function getCypressCommand (details) {
56
66
  if (!details) {
57
67
  return TEST_FRAMEWORK_NAME
@@ -79,10 +89,62 @@ function getSuiteStatus (suiteStats) {
79
89
  return 'pass'
80
90
  }
81
91
 
92
+ function getItrConfig (tracer, testConfiguration) {
93
+ return new Promise(resolve => {
94
+ if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) {
95
+ return resolve({ err: new Error('CI Visibility was not initialized correctly') })
96
+ }
97
+
98
+ tracer._tracer._exporter.getItrConfiguration(testConfiguration, (err, itrConfig) => {
99
+ resolve({ err, itrConfig })
100
+ })
101
+ })
102
+ }
103
+
104
+ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration) {
105
+ if (!isSuitesSkippingEnabled) {
106
+ return Promise.resolve({ skippableTests: [] })
107
+ }
108
+ return new Promise(resolve => {
109
+ if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) {
110
+ return resolve({ err: new Error('CI Visibility was not initialized correctly') })
111
+ }
112
+ tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests) => {
113
+ resolve({
114
+ err,
115
+ skippableTests
116
+ })
117
+ })
118
+ })
119
+ }
120
+
82
121
  module.exports = (on, config) => {
83
122
  const tracer = require('../../dd-trace')
84
123
  const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
85
124
 
125
+ const {
126
+ 'git.repository_url': repositoryUrl,
127
+ 'git.commit.sha': sha,
128
+ 'os.version': osVersion,
129
+ 'os.platform': osPlatform,
130
+ 'os.architecture': osArchitecture,
131
+ 'runtime.name': runtimeName,
132
+ 'runtime.version': runtimeVersion,
133
+ 'git.branch': branch
134
+ } = testEnvironmentMetadata
135
+
136
+ const testConfiguration = {
137
+ repositoryUrl,
138
+ sha,
139
+ osVersion,
140
+ osPlatform,
141
+ osArchitecture,
142
+ runtimeName,
143
+ runtimeVersion,
144
+ branch,
145
+ testLevel: 'test'
146
+ }
147
+
86
148
  const codeOwnersEntries = getCodeOwnersFileEntries()
87
149
 
88
150
  let activeSpan = null
@@ -91,31 +153,54 @@ module.exports = (on, config) => {
91
153
  let testSuiteSpan = null
92
154
  let command = null
93
155
  let frameworkVersion
156
+ let rootDir
157
+ let isSuitesSkippingEnabled = false
158
+ let isCodeCoverageEnabled = false
159
+ let testsToSkip = []
94
160
 
95
161
  on('before:run', (details) => {
96
- const childOf = getTestParentSpan(tracer)
162
+ return getItrConfig(tracer, testConfiguration).then(({ err, itrConfig }) => {
163
+ if (err) {
164
+ log.error(err)
165
+ } else {
166
+ isSuitesSkippingEnabled = itrConfig.isSuitesSkippingEnabled
167
+ isCodeCoverageEnabled = itrConfig.isCodeCoverageEnabled
168
+ }
97
169
 
98
- command = getCypressCommand(details)
99
- frameworkVersion = getCypressVersion(details)
170
+ getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration).then(({ err, skippableTests }) => {
171
+ if (err) {
172
+ log.error(err)
173
+ } else {
174
+ testsToSkip = skippableTests || []
175
+ }
100
176
 
101
- const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME)
102
- const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME)
177
+ const childOf = getTestParentSpan(tracer)
178
+ rootDir = getRootDir(details)
103
179
 
104
- testSessionSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
105
- childOf,
106
- tags: {
107
- [COMPONENT]: TEST_FRAMEWORK_NAME,
108
- ...testEnvironmentMetadata,
109
- ...testSessionSpanMetadata
110
- }
111
- })
112
- testModuleSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, {
113
- childOf: testSessionSpan,
114
- tags: {
115
- [COMPONENT]: TEST_FRAMEWORK_NAME,
116
- ...testEnvironmentMetadata,
117
- ...testModuleSpanMetadata
118
- }
180
+ command = getCypressCommand(details)
181
+ frameworkVersion = getCypressVersion(details)
182
+
183
+ const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME)
184
+ const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME)
185
+
186
+ testSessionSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
187
+ childOf,
188
+ tags: {
189
+ [COMPONENT]: TEST_FRAMEWORK_NAME,
190
+ ...testEnvironmentMetadata,
191
+ ...testSessionSpanMetadata
192
+ }
193
+ })
194
+ testModuleSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, {
195
+ childOf: testSessionSpan,
196
+ tags: {
197
+ [COMPONENT]: TEST_FRAMEWORK_NAME,
198
+ ...testEnvironmentMetadata,
199
+ ...testModuleSpanMetadata
200
+ }
201
+ })
202
+ return details
203
+ })
119
204
  })
120
205
  })
121
206
 
@@ -125,6 +210,16 @@ module.exports = (on, config) => {
125
210
  testModuleSpan.setTag(TEST_STATUS, testStatus)
126
211
  testSessionSpan.setTag(TEST_STATUS, testStatus)
127
212
 
213
+ addIntelligentTestRunnerSpanTags(
214
+ testSessionSpan,
215
+ testModuleSpan,
216
+ {
217
+ isSuitesSkipped: !!testsToSkip.length,
218
+ isSuitesSkippingEnabled,
219
+ isCodeCoverageEnabled
220
+ }
221
+ )
222
+
128
223
  testModuleSpan.finish()
129
224
  testSessionSpan.finish()
130
225
 
@@ -132,9 +227,15 @@ module.exports = (on, config) => {
132
227
  }
133
228
 
134
229
  return new Promise(resolve => {
135
- tracer._tracer._exporter._writer.flush(() => {
136
- resolve(null)
137
- })
230
+ if (tracer._tracer._exporter.flush) {
231
+ tracer._tracer._exporter.flush(() => {
232
+ resolve(null)
233
+ })
234
+ } else {
235
+ tracer._tracer._exporter._writer.flush(() => {
236
+ resolve(null)
237
+ })
238
+ }
138
239
  })
139
240
  })
140
241
  on('task', {
@@ -153,9 +254,9 @@ module.exports = (on, config) => {
153
254
  })
154
255
  return null
155
256
  },
156
- 'dd:testSuiteFinish': (suiteStats) => {
257
+ 'dd:testSuiteFinish': (stats) => {
157
258
  if (testSuiteSpan) {
158
- const status = getSuiteStatus(suiteStats)
259
+ const status = getSuiteStatus(stats)
159
260
  testSuiteSpan.setTag(TEST_STATUS, status)
160
261
  testSuiteSpan.finish()
161
262
  testSuiteSpan = null
@@ -164,6 +265,12 @@ module.exports = (on, config) => {
164
265
  },
165
266
  'dd:beforeEach': (test) => {
166
267
  const { testName, testSuite } = test
268
+ // skip test
269
+ if (testsToSkip.find(test => {
270
+ return testName === test.name && testSuite === test.suite
271
+ })) {
272
+ return { shouldSkip: true }
273
+ }
167
274
 
168
275
  const testSuiteTags = {
169
276
  [TEST_COMMAND]: command,
@@ -202,11 +309,24 @@ module.exports = (on, config) => {
202
309
  }
203
310
  })
204
311
  }
205
- return activeSpan ? activeSpan.context().toTraceId() : null
312
+ return activeSpan ? { traceId: activeSpan.context().toTraceId() } : {}
206
313
  },
207
- 'dd:afterEach': (test) => {
314
+ 'dd:afterEach': ({ test, coverage }) => {
208
315
  const { state, error, isRUMActive, testSourceLine } = test
209
316
  if (activeSpan) {
317
+ if (coverage && tracer._tracer._exporter.exportCoverage && isCodeCoverageEnabled) {
318
+ const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
319
+ const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir))
320
+ const { _traceId, _spanId } = testSuiteSpan.context()
321
+ const formattedCoverage = {
322
+ sessionId: _traceId,
323
+ suiteId: _spanId,
324
+ testId: activeSpan.context()._spanId,
325
+ files: relativeCoverageFiles
326
+ }
327
+ tracer._tracer._exporter.exportCoverage(formattedCoverage)
328
+ }
329
+
210
330
  activeSpan.setTag(TEST_STATUS, CYPRESS_STATUS_TO_TEST_STATUS[state])
211
331
  if (error) {
212
332
  activeSpan.setTag('error', error)
@@ -1,10 +1,13 @@
1
1
  /* eslint-disable */
2
- beforeEach(() => {
2
+ beforeEach(function () {
3
3
  cy.task('dd:beforeEach', {
4
4
  testName: Cypress.mocha.getRunner().suite.ctx.currentTest.fullTitle(),
5
5
  testSuite: Cypress.mocha.getRootSuite().file
6
- }).then(traceId => {
6
+ }).then(({ traceId, shouldSkip }) => {
7
7
  Cypress.env('traceId', traceId)
8
+ if (shouldSkip) {
9
+ this.skip()
10
+ }
8
11
  })
9
12
  })
10
13
 
@@ -36,6 +39,6 @@ afterEach(() => {
36
39
  if (win.DD_RUM) {
37
40
  testInfo.isRUMActive = true
38
41
  }
39
- cy.task('dd:afterEach', testInfo)
42
+ cy.task('dd:afterEach', { test: testInfo, coverage: win.__coverage__ })
40
43
  })
41
44
  })