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
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { addHook } = require('./helpers/instrument')
3
+ const { addHook, channel } = require('./helpers/instrument')
4
4
  const { wrapThen } = require('./helpers/promise')
5
5
  const { AsyncResource } = require('./helpers/instrument')
6
6
  const shimmer = require('../../datadog-shimmer')
@@ -26,5 +26,144 @@ addHook({
26
26
  }
27
27
 
28
28
  shimmer.wrap(mongoose.Collection.prototype, 'addQueue', wrapAddQueue)
29
+
29
30
  return mongoose
30
31
  })
32
+
33
+ const startCh = channel('datadog:mongoose:model:filter:start')
34
+ const finishCh = channel('datadog:mongoose:model:filter:finish')
35
+
36
+ const collectionMethodsWithFilter = [
37
+ 'count',
38
+ 'countDocuments',
39
+ 'deleteMany',
40
+ 'deleteOne',
41
+ 'find',
42
+ 'findOne',
43
+ 'findOneAndDelete',
44
+ 'findOneAndReplace',
45
+ 'replaceOne',
46
+ 'remove'
47
+ ]
48
+
49
+ const collectionMethodsWithTwoFilters = [
50
+ 'findOneAndUpdate',
51
+ 'updateMany',
52
+ 'updateOne'
53
+ ]
54
+
55
+ addHook({
56
+ name: 'mongoose',
57
+ versions: ['>=4.6.4 <5', '5', '6', '>=7'],
58
+ file: 'lib/model.js'
59
+ }, Model => {
60
+ [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters].forEach(methodName => {
61
+ const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
62
+ if (!(methodName in Model)) return
63
+
64
+ shimmer.wrap(Model, methodName, method => {
65
+ return function wrappedModelMethod () {
66
+ if (!startCh.hasSubscribers) {
67
+ return method.apply(this, arguments)
68
+ }
69
+
70
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
71
+
72
+ const filters = [arguments[0]]
73
+ if (useTwoArguments) {
74
+ filters.push(arguments[1])
75
+ }
76
+
77
+ const finish = asyncResource.bind(function () {
78
+ finishCh.publish()
79
+ })
80
+
81
+ let callbackWrapped = false
82
+ const lastArgumentIndex = arguments.length - 1
83
+
84
+ if (typeof arguments[lastArgumentIndex] === 'function') {
85
+ // is a callback, wrap it to execute finish()
86
+ shimmer.wrap(arguments, lastArgumentIndex, originalCb => {
87
+ return function () {
88
+ finish()
89
+
90
+ return originalCb.apply(this, arguments)
91
+ }
92
+ })
93
+
94
+ callbackWrapped = true
95
+ }
96
+
97
+ return asyncResource.runInAsyncScope(() => {
98
+ startCh.publish({
99
+ filters,
100
+ methodName
101
+ })
102
+
103
+ const res = method.apply(this, arguments)
104
+
105
+ // if it is not callback, wrap exec method and its then
106
+ if (!callbackWrapped) {
107
+ shimmer.wrap(res, 'exec', originalExec => {
108
+ return function wrappedExec () {
109
+ const execResult = originalExec.apply(this, arguments)
110
+
111
+ // wrap them method, wrap resolve and reject methods
112
+ shimmer.wrap(execResult, 'then', originalThen => {
113
+ return function wrappedThen () {
114
+ const resolve = arguments[0]
115
+ const reject = arguments[1]
116
+
117
+ // not using shimmer here because resolve/reject could be empty
118
+ arguments[0] = function wrappedResolve () {
119
+ finish()
120
+
121
+ if (resolve) {
122
+ return resolve.apply(this, arguments)
123
+ }
124
+ }
125
+
126
+ arguments[1] = function wrappedReject () {
127
+ finish()
128
+
129
+ if (reject) {
130
+ return reject.apply(this, arguments)
131
+ }
132
+ }
133
+
134
+ return originalThen.apply(this, arguments)
135
+ }
136
+ })
137
+
138
+ return execResult
139
+ }
140
+ })
141
+ }
142
+ return res
143
+ })
144
+ }
145
+ })
146
+ })
147
+
148
+ return Model
149
+ })
150
+
151
+ const sanitizeFilterFinishCh = channel('datadog:mongoose:sanitize-filter:finish')
152
+
153
+ addHook({
154
+ name: 'mongoose',
155
+ versions: ['6', '>=7'],
156
+ file: 'lib/helpers/query/sanitizeFilter.js'
157
+ }, sanitizeFilter => {
158
+ return shimmer.wrap(sanitizeFilter, function wrappedSanitizeFilter () {
159
+ const sanitizedObject = sanitizeFilter.apply(this, arguments)
160
+
161
+ if (sanitizeFilterFinishCh.hasSubscribers) {
162
+ sanitizeFilterFinishCh.publish({
163
+ sanitizedObject
164
+ })
165
+ }
166
+
167
+ return sanitizedObject
168
+ })
169
+ })
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- // TODO: either instrument all or none of the render functions
4
-
5
3
  const { channel, addHook } = require('./helpers/instrument')
6
4
  const shimmer = require('../../datadog-shimmer')
7
5
  const { DD_MAJOR } = require('../../../version')
@@ -10,8 +8,13 @@ const startChannel = channel('apm:next:request:start')
10
8
  const finishChannel = channel('apm:next:request:finish')
11
9
  const errorChannel = channel('apm:next:request:error')
12
10
  const pageLoadChannel = channel('apm:next:page:load')
11
+ const bodyParsedChannel = channel('apm:next:body-parsed')
12
+ const queryParsedChannel = channel('apm:next:query-parsed')
13
13
 
14
14
  const requests = new WeakSet()
15
+ const nodeNextRequestsToNextRequests = new WeakMap()
16
+
17
+ const MIDDLEWARE_HEADER = 'x-middleware-invoke'
15
18
 
16
19
  function wrapHandleRequest (handleRequest) {
17
20
  return function (req, res, pathname, query) {
@@ -54,18 +57,6 @@ function wrapHandleApiRequestWithMatch (handleApiRequest) {
54
57
  }
55
58
  }
56
59
 
57
- function wrapRenderToResponse (renderToResponse) {
58
- return function (ctx) {
59
- return instrument(ctx.req, ctx.res, () => renderToResponse.apply(this, arguments))
60
- }
61
- }
62
-
63
- function wrapRenderErrorToResponse (renderErrorToResponse) {
64
- return function (ctx) {
65
- return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
66
- }
67
- }
68
-
69
60
  function wrapRenderToHTML (renderToHTML) {
70
61
  return function (req, res, pathname, query, parsedUrl) {
71
62
  return instrument(req, res, () => renderToHTML.apply(this, arguments))
@@ -78,6 +69,18 @@ function wrapRenderErrorToHTML (renderErrorToHTML) {
78
69
  }
79
70
  }
80
71
 
72
+ function wrapRenderToResponse (renderToResponse) {
73
+ return function (ctx) {
74
+ return instrument(ctx.req, ctx.res, () => renderToResponse.apply(this, arguments))
75
+ }
76
+ }
77
+
78
+ function wrapRenderErrorToResponse (renderErrorToResponse) {
79
+ return function (ctx) {
80
+ return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
81
+ }
82
+ }
83
+
81
84
  function wrapFindPageComponents (findPageComponents) {
82
85
  return function (pathname, query) {
83
86
  const result = findPageComponents.apply(this, arguments)
@@ -112,7 +115,9 @@ function instrument (req, res, handler) {
112
115
  req = req.originalRequest || req
113
116
  res = res.originalResponse || res
114
117
 
115
- if (requests.has(req)) return handler()
118
+ // TODO support middleware properly in the future?
119
+ const isMiddleware = req.headers[MIDDLEWARE_HEADER]
120
+ if (isMiddleware || requests.has(req)) return handler()
116
121
 
117
122
  requests.add(req)
118
123
 
@@ -152,6 +157,11 @@ function finish (ctx, result, err) {
152
157
  errorChannel.publish(ctx)
153
158
  }
154
159
 
160
+ const maybeNextRequest = nodeNextRequestsToNextRequests.get(ctx.req)
161
+ if (maybeNextRequest) {
162
+ ctx.nextRequest = maybeNextRequest
163
+ }
164
+
155
165
  finishChannel.publish(ctx)
156
166
 
157
167
  if (err) {
@@ -161,6 +171,24 @@ function finish (ctx, result, err) {
161
171
  return result
162
172
  }
163
173
 
174
+ // also wrapped in dist/server/future/route-handlers/app-route-route-handler.js
175
+ // in versions below 13.3.0 that support middleware,
176
+ // however, it is not provided as a class function or exported property
177
+ addHook({
178
+ name: 'next',
179
+ versions: ['>=13.3.0'],
180
+ file: 'dist/server/web/spec-extension/adapters/next-request.js'
181
+ }, NextRequestAdapter => {
182
+ shimmer.wrap(NextRequestAdapter.NextRequestAdapter, 'fromNodeNextRequest', fromNodeNextRequest => {
183
+ return function (nodeNextRequest) {
184
+ const nextRequest = fromNodeNextRequest.apply(this, arguments)
185
+ nodeNextRequestsToNextRequests.set(nodeNextRequest.originalRequest, nextRequest)
186
+ return nextRequest
187
+ }
188
+ })
189
+ return NextRequestAdapter
190
+ })
191
+
164
192
  addHook({
165
193
  name: 'next',
166
194
  versions: ['>=11.1'],
@@ -173,27 +201,32 @@ addHook({
173
201
  file: 'dist/next-server/server/serve-static.js'
174
202
  }, serveStatic => shimmer.wrap(serveStatic, 'serveStatic', wrapServeStatic))
175
203
 
176
- addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
204
+ addHook({ name: 'next', versions: ['>=11.1'], file: 'dist/server/next-server.js' }, nextServer => {
177
205
  const Server = nextServer.default
178
206
 
179
207
  shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
180
- shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
208
+
209
+ // Wrapping these makes sure any public API render methods called in a custom server
210
+ // are traced properly
211
+ // (instead of wrapping the top-level API methods, just wrapping these covers them all)
181
212
  shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
182
213
  shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
214
+
183
215
  shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
184
216
 
185
217
  return nextServer
186
218
  })
187
219
 
188
- addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
220
+ // `handleApiRequest` changes parameters/implementation at 13.2.0
221
+ addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
189
222
  const Server = nextServer.default
223
+ shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
224
+ return nextServer
225
+ })
190
226
 
191
- shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
227
+ addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
228
+ const Server = nextServer.default
192
229
  shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequest)
193
- shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
194
- shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
195
- shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
196
-
197
230
  return nextServer
198
231
  })
199
232
 
@@ -206,9 +239,51 @@ addHook({
206
239
 
207
240
  shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
208
241
  shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequest)
242
+
243
+ // Likewise with newer versions, these correlate to public API render methods for custom servers
244
+ // all public ones use these methods somewhere in their code path
209
245
  shimmer.wrap(Server.prototype, 'renderToHTML', wrapRenderToHTML)
210
246
  shimmer.wrap(Server.prototype, 'renderErrorToHTML', wrapRenderErrorToHTML)
247
+
211
248
  shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
212
249
 
213
250
  return nextServer
214
251
  })
252
+
253
+ addHook({
254
+ name: 'next',
255
+ versions: ['>=13'],
256
+ file: 'dist/server/web/spec-extension/request.js'
257
+ }, request => {
258
+ const nextUrlDescriptor = Object.getOwnPropertyDescriptor(request.NextRequest.prototype, 'nextUrl')
259
+ shimmer.wrap(nextUrlDescriptor, 'get', function (originalGet) {
260
+ return function wrappedGet () {
261
+ const nextUrl = originalGet.apply(this, arguments)
262
+ if (queryParsedChannel.hasSubscribers) {
263
+ const query = {}
264
+ for (const key of nextUrl.searchParams.keys()) {
265
+ if (!query[key]) {
266
+ query[key] = nextUrl.searchParams.getAll(key)
267
+ }
268
+ }
269
+
270
+ queryParsedChannel.publish({ query })
271
+ }
272
+ return nextUrl
273
+ }
274
+ })
275
+
276
+ Object.defineProperty(request.NextRequest.prototype, 'nextUrl', nextUrlDescriptor)
277
+
278
+ shimmer.massWrap(request.NextRequest.prototype, ['text', 'json'], function (originalMethod) {
279
+ return async function wrappedJson () {
280
+ const body = await originalMethod.apply(this, arguments)
281
+ bodyParsedChannel.publish({
282
+ body
283
+ })
284
+ return body
285
+ }
286
+ })
287
+
288
+ return request
289
+ })
@@ -1,5 +1,6 @@
1
1
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
2
  const shimmer = require('../../datadog-shimmer')
3
+ const { parseAnnotations } = require('../../dd-trace/src/plugins/util/test')
3
4
 
4
5
  const testStartCh = channel('ci:playwright:test:start')
5
6
  const testFinishCh = channel('ci:playwright:test:finish')
@@ -103,7 +104,11 @@ function testBeginHandler (test) {
103
104
  })
104
105
  }
105
106
 
106
- function testEndHandler (test, testStatus, error) {
107
+ function testEndHandler (test, annotations, testStatus, error) {
108
+ let annotationTags
109
+ if (annotations.length) {
110
+ annotationTags = parseAnnotations(annotations)
111
+ }
107
112
  const { _requireFile: testSuiteAbsolutePath, results, _type } = test
108
113
 
109
114
  if (_type === 'beforeAll' || _type === 'afterAll') {
@@ -113,7 +118,7 @@ function testEndHandler (test, testStatus, error) {
113
118
  const testResult = results[results.length - 1]
114
119
  const testAsyncResource = testToAr.get(test)
115
120
  testAsyncResource.runInAsyncScope(() => {
116
- testFinishCh.publish({ testStatus, steps: testResult.steps, error })
121
+ testFinishCh.publish({ testStatus, steps: testResult.steps, error, extraTags: annotationTags })
117
122
  })
118
123
 
119
124
  if (!testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
@@ -172,7 +177,7 @@ function dispatcherHook (dispatcherExport) {
172
177
  const { results } = test
173
178
  const testResult = results[results.length - 1]
174
179
 
175
- testEndHandler(test, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
180
+ testEndHandler(test, params.annotations, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
176
181
  }
177
182
  })
178
183
 
@@ -181,6 +186,15 @@ function dispatcherHook (dispatcherExport) {
181
186
  return dispatcherExport
182
187
  }
183
188
 
189
+ function getTestByTestId (dispatcher, testId) {
190
+ if (dispatcher._testById) {
191
+ return dispatcher._testById.get(testId)?.test
192
+ }
193
+ if (dispatcher._allTests) {
194
+ return dispatcher._allTests.find(({ id }) => id === testId)
195
+ }
196
+ }
197
+
184
198
  function dispatcherHookNew (dispatcherExport, runWrapper) {
185
199
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper)
186
200
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
@@ -188,13 +202,13 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
188
202
  const worker = createWorker.apply(this, arguments)
189
203
 
190
204
  worker.on('testBegin', ({ testId }) => {
191
- const { test } = dispatcher._testById.get(testId)
205
+ const test = getTestByTestId(dispatcher, testId)
192
206
  testBeginHandler(test)
193
207
  })
194
- worker.on('testEnd', ({ testId, status, errors }) => {
195
- const { test } = dispatcher._testById.get(testId)
208
+ worker.on('testEnd', ({ testId, status, errors, annotations }) => {
209
+ const test = getTestByTestId(dispatcher, testId)
196
210
 
197
- testEndHandler(test, STATUS_TO_TEST_STATUS[status], errors && errors[0])
211
+ testEndHandler(test, annotations, STATUS_TO_TEST_STATUS[status], errors && errors[0])
198
212
  })
199
213
 
200
214
  return worker
@@ -221,7 +235,7 @@ function runnerHook (runnerExport, playwrightVersion) {
221
235
  // because they were skipped
222
236
  tests.forEach(test => {
223
237
  testBeginHandler(test)
224
- testEndHandler(test, 'skip')
238
+ testEndHandler(test, [], 'skip')
225
239
  })
226
240
  })
227
241
 
@@ -10,7 +10,9 @@ const {
10
10
  finishAllTraceSpans,
11
11
  getTestSuitePath,
12
12
  getTestSuiteCommonTags,
13
- addIntelligentTestRunnerSpanTags
13
+ addIntelligentTestRunnerSpanTags,
14
+ TEST_ITR_UNSKIPPABLE,
15
+ TEST_ITR_FORCED_RUN
14
16
  } = require('../../dd-trace/src/plugins/util/test')
15
17
  const { RESOURCE_NAME } = require('../../../ext/tags')
16
18
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -29,7 +31,9 @@ class CucumberPlugin extends CiPlugin {
29
31
  status,
30
32
  isSuitesSkipped,
31
33
  numSkippedSuites,
32
- testCodeCoverageLinesTotal
34
+ testCodeCoverageLinesTotal,
35
+ hasUnskippableSuites,
36
+ hasForcedToRunSuites
33
37
  }) => {
34
38
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
35
39
  addIntelligentTestRunnerSpanTags(
@@ -41,7 +45,9 @@ class CucumberPlugin extends CiPlugin {
41
45
  isCodeCoverageEnabled,
42
46
  testCodeCoverageLinesTotal,
43
47
  skippingCount: numSkippedSuites,
44
- skippingType: 'suite'
48
+ skippingType: 'suite',
49
+ hasUnskippableSuites,
50
+ hasForcedToRunSuites
45
51
  }
46
52
  )
47
53
 
@@ -55,13 +61,19 @@ class CucumberPlugin extends CiPlugin {
55
61
  this.tracer._exporter.flush()
56
62
  })
57
63
 
58
- this.addSub('ci:cucumber:test-suite:start', (testSuiteFullPath) => {
64
+ this.addSub('ci:cucumber:test-suite:start', ({ testSuitePath, isUnskippable, isForcedToRun }) => {
59
65
  const testSuiteMetadata = getTestSuiteCommonTags(
60
66
  this.command,
61
67
  this.frameworkVersion,
62
- getTestSuitePath(testSuiteFullPath, this.sourceRoot),
68
+ testSuitePath,
63
69
  'cucumber'
64
70
  )
71
+ if (isUnskippable) {
72
+ testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
73
+ }
74
+ if (isForcedToRun) {
75
+ testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
76
+ }
65
77
  this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
66
78
  childOf: this.testModuleSpan,
67
79
  tags: {
@@ -21,11 +21,14 @@ const {
21
21
  getCoveredFilenamesFromCoverage,
22
22
  getTestSuitePath,
23
23
  addIntelligentTestRunnerSpanTags,
24
- TEST_SKIPPED_BY_ITR
24
+ TEST_SKIPPED_BY_ITR,
25
+ TEST_ITR_UNSKIPPABLE,
26
+ TEST_ITR_FORCED_RUN
25
27
  } = require('../../dd-trace/src/plugins/util/test')
26
28
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
27
29
  const log = require('../../dd-trace/src/log')
28
30
  const NoopTracer = require('../../dd-trace/src/noop/tracer')
31
+ const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
29
32
 
30
33
  const TEST_FRAMEWORK_NAME = 'cypress'
31
34
 
@@ -185,8 +188,11 @@ module.exports = (on, config) => {
185
188
  let isSuitesSkippingEnabled = false
186
189
  let isCodeCoverageEnabled = false
187
190
  let testsToSkip = []
191
+ const unskippableSuites = []
192
+ let hasForcedToRunSuites = false
193
+ let hasUnskippableSuites = false
188
194
 
189
- function getTestSpan (testName, testSuite) {
195
+ function getTestSpan (testName, testSuite, isUnskippable, isForcedToRun) {
190
196
  const testSuiteTags = {
191
197
  [TEST_COMMAND]: command,
192
198
  [TEST_COMMAND]: command,
@@ -212,6 +218,16 @@ module.exports = (on, config) => {
212
218
  testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
213
219
  }
214
220
 
221
+ if (isUnskippable) {
222
+ hasUnskippableSuites = true
223
+ testSpanMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
224
+ }
225
+
226
+ if (isForcedToRun) {
227
+ hasForcedToRunSuites = true
228
+ testSpanMetadata[TEST_ITR_FORCED_RUN] = 'true'
229
+ }
230
+
215
231
  return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
216
232
  childOf,
217
233
  tags: {
@@ -233,13 +249,21 @@ module.exports = (on, config) => {
233
249
  isCodeCoverageEnabled = itrConfig.isCodeCoverageEnabled
234
250
  }
235
251
 
236
- getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration).then(({ err, skippableTests }) => {
252
+ return getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration).then(({ err, skippableTests }) => {
237
253
  if (err) {
238
254
  log.error(err)
239
255
  } else {
240
256
  testsToSkip = skippableTests || []
241
257
  }
242
258
 
259
+ // `details.specs` are test files
260
+ details.specs.forEach(({ absolute, relative }) => {
261
+ const isUnskippableSuite = isMarkedAsUnskippable({ path: absolute })
262
+ if (isUnskippableSuite) {
263
+ unskippableSuites.push(relative)
264
+ }
265
+ })
266
+
243
267
  const childOf = getTestParentSpan(tracer)
244
268
  rootDir = getRootDir(details)
245
269
 
@@ -340,7 +364,9 @@ module.exports = (on, config) => {
340
364
  isSuitesSkippingEnabled,
341
365
  isCodeCoverageEnabled,
342
366
  skippingType: 'test',
343
- skippingCount: skippedTests.length
367
+ skippingCount: skippedTests.length,
368
+ hasForcedToRunSuites,
369
+ hasUnskippableSuites
344
370
  }
345
371
  )
346
372
 
@@ -384,17 +410,21 @@ module.exports = (on, config) => {
384
410
  },
385
411
  'dd:beforeEach': (test) => {
386
412
  const { testName, testSuite } = test
387
- // skip test
388
- if (testsToSkip.find(test => {
413
+ const shouldSkip = !!testsToSkip.find(test => {
389
414
  return testName === test.name && testSuite === test.suite
390
- })) {
415
+ })
416
+ const isUnskippable = unskippableSuites.includes(testSuite)
417
+ const isForcedToRun = shouldSkip && isUnskippable
418
+
419
+ // skip test
420
+ if (shouldSkip && !isUnskippable) {
391
421
  skippedTests.push(test)
392
422
  isTestsSkipped = true
393
423
  return { shouldSkip: true }
394
424
  }
395
425
 
396
426
  if (!activeSpan) {
397
- activeSpan = getTestSpan(testName, testSuite)
427
+ activeSpan = getTestSpan(testName, testSuite, isUnskippable, isForcedToRun)
398
428
  }
399
429
 
400
430
  return activeSpan ? { traceId: activeSpan.context().toTraceId() } : {}
@@ -76,6 +76,7 @@ class HttpClientPlugin extends ClientPlugin {
76
76
  }
77
77
 
78
78
  finish ({ req, res, span }) {
79
+ if (!span) return
79
80
  if (res) {
80
81
  const status = res.status || res.statusCode
81
82
 
@@ -98,6 +99,7 @@ class HttpClientPlugin extends ClientPlugin {
98
99
  }
99
100
 
100
101
  error ({ span, error }) {
102
+ if (!span) return
101
103
  if (error) {
102
104
  span.addTags({
103
105
  [ERROR_TYPE]: error.name,
@@ -10,7 +10,9 @@ const {
10
10
  TEST_PARAMETERS,
11
11
  TEST_COMMAND,
12
12
  TEST_FRAMEWORK_VERSION,
13
- TEST_SOURCE_START
13
+ TEST_SOURCE_START,
14
+ TEST_ITR_UNSKIPPABLE,
15
+ TEST_ITR_FORCED_RUN
14
16
  } = require('../../dd-trace/src/plugins/util/test')
15
17
  const { COMPONENT } = require('../../dd-trace/src/constants')
16
18
  const id = require('../../dd-trace/src/id')
@@ -50,11 +52,19 @@ class JestPlugin extends CiPlugin {
50
52
  isSuitesSkippingEnabled,
51
53
  isCodeCoverageEnabled,
52
54
  testCodeCoverageLinesTotal,
53
- numSkippedSuites
55
+ numSkippedSuites,
56
+ hasUnskippableSuites,
57
+ hasForcedToRunSuites,
58
+ error
54
59
  }) => {
55
60
  this.testSessionSpan.setTag(TEST_STATUS, status)
56
61
  this.testModuleSpan.setTag(TEST_STATUS, status)
57
62
 
63
+ if (error) {
64
+ this.testSessionSpan.setTag('error', error)
65
+ this.testModuleSpan.setTag('error', error)
66
+ }
67
+
58
68
  addIntelligentTestRunnerSpanTags(
59
69
  this.testSessionSpan,
60
70
  this.testModuleSpan,
@@ -64,7 +74,9 @@ class JestPlugin extends CiPlugin {
64
74
  isCodeCoverageEnabled,
65
75
  testCodeCoverageLinesTotal,
66
76
  skippingType: 'suite',
67
- skippingCount: numSkippedSuites
77
+ skippingCount: numSkippedSuites,
78
+ hasUnskippableSuites,
79
+ hasForcedToRunSuites
68
80
  }
69
81
  )
70
82
 
@@ -89,7 +101,9 @@ class JestPlugin extends CiPlugin {
89
101
  const {
90
102
  _ddTestSessionId: testSessionId,
91
103
  _ddTestCommand: testCommand,
92
- _ddTestModuleId: testModuleId
104
+ _ddTestModuleId: testModuleId,
105
+ _ddForcedToRun,
106
+ _ddUnskippable
93
107
  } = testEnvironmentOptions
94
108
 
95
109
  const testSessionSpanContext = this.tracer.extract('text_map', {
@@ -99,6 +113,13 @@ class JestPlugin extends CiPlugin {
99
113
 
100
114
  const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, 'jest')
101
115
 
116
+ if (_ddUnskippable) {
117
+ testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
118
+ if (_ddForcedToRun) {
119
+ testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
120
+ }
121
+ }
122
+
102
123
  this.testSuiteSpan = this.tracer.startSpan('jest.test_suite', {
103
124
  childOf: testSessionSpanContext,
104
125
  tags: {
@@ -135,9 +156,11 @@ class JestPlugin extends CiPlugin {
135
156
  })
136
157
  })
137
158
 
138
- this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage }) => {
159
+ this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
139
160
  this.testSuiteSpan.setTag(TEST_STATUS, status)
140
- if (errorMessage) {
161
+ if (error) {
162
+ this.testSuiteSpan.setTag('error', error)
163
+ } else if (errorMessage) {
141
164
  this.testSuiteSpan.setTag('error', new Error(errorMessage))
142
165
  }
143
166
  this.testSuiteSpan.finish()