dd-trace 5.18.0 → 5.19.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 (43) hide show
  1. package/LICENSE-3rdparty.csv +0 -2
  2. package/index.d.ts +61 -39
  3. package/package.json +7 -11
  4. package/packages/datadog-instrumentations/src/child_process.js +2 -2
  5. package/packages/datadog-instrumentations/src/fs.js +1 -1
  6. package/packages/datadog-instrumentations/src/hapi.js +1 -1
  7. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  8. package/packages/datadog-instrumentations/src/jest.js +17 -2
  9. package/packages/datadog-instrumentations/src/kafkajs.js +1 -1
  10. package/packages/datadog-instrumentations/src/ldapjs.js +2 -2
  11. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  12. package/packages/datadog-instrumentations/src/next.js +1 -1
  13. package/packages/datadog-instrumentations/src/pg.js +2 -2
  14. package/packages/datadog-instrumentations/src/playwright.js +46 -32
  15. package/packages/datadog-instrumentations/src/restify.js +1 -1
  16. package/packages/datadog-instrumentations/src/vitest.js +51 -5
  17. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  18. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
  20. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -4
  21. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +79 -42
  22. package/packages/datadog-plugin-cypress/src/plugin.js +4 -3
  23. package/packages/datadog-plugin-fs/src/index.js +1 -1
  24. package/packages/datadog-plugin-jest/src/index.js +7 -1
  25. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -1
  26. package/packages/datadog-plugin-mongodb-core/src/index.js +1 -1
  27. package/packages/datadog-plugin-openai/src/index.js +5 -5
  28. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  29. package/packages/datadog-plugin-sharedb/src/index.js +1 -1
  30. package/packages/datadog-plugin-vitest/src/index.js +17 -6
  31. package/packages/dd-trace/src/analytics_sampler.js +1 -1
  32. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +1 -1
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  34. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +3 -1
  36. package/packages/dd-trace/src/appsec/index.js +3 -3
  37. package/packages/dd-trace/src/appsec/passport.js +1 -1
  38. package/packages/dd-trace/src/appsec/reporter.js +0 -4
  39. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
  40. package/packages/dd-trace/src/config.js +27 -24
  41. package/packages/dd-trace/src/datastreams/processor.js +1 -1
  42. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  43. package/packages/dd-trace/src/opentelemetry/tracer.js +6 -0
@@ -46,7 +46,6 @@ dev,eslint-plugin-import,MIT,Copyright 2015 Ben Mosher
46
46
  dev,eslint-plugin-mocha,MIT,Copyright 2014 Mathias Schreck
47
47
  dev,eslint-plugin-n,MIT,Copyright 2015 Toru Nagashima
48
48
  dev,eslint-plugin-promise,ISC,jden and other contributors
49
- dev,eslint-plugin-standard,MIT,Copyright 2015 Jamund Ferguson
50
49
  dev,express,MIT,Copyright 2009-2014 TJ Holowaychuk 2013-2014 Roman Shtylman 2014-2015 Douglas Christopher Wilson
51
50
  dev,get-port,MIT,Copyright Sindre Sorhus
52
51
  dev,glob,ISC,Copyright Isaac Z. Schlueter and Contributors
@@ -63,7 +62,6 @@ dev,rimraf,ISC,Copyright Isaac Z. Schlueter and Contributors
63
62
  dev,sinon,BSD-3-Clause,Copyright 2010-2017 Christian Johansen
64
63
  dev,sinon-chai,WTFPL and BSD-2-Clause,Copyright 2004 Sam Hocevar 2012–2017 Domenic Denicola
65
64
  dev,tap,ISC,Copyright 2011-2022 Isaac Z. Schlueter and Contributors
66
- dev,tape,MIT,Copyright James Halliday
67
65
  dev,tiktoken,MIT,Copyright (c) 2022 OpenAI, Shantanu Jain
68
66
  file,aws-lambda-nodejs-runtime-interface-client,Apache 2.0,Copyright 2019 Amazon.com Inc. or its affiliates. All Rights Reserved.
69
67
  file,profile.proto,Apache license 2.0,Copyright 2016 Google Inc.
package/index.d.ts CHANGED
@@ -518,45 +518,7 @@ declare namespace tracer {
518
518
  /**
519
519
  * Configuration of the IAST. Can be a boolean as an alias to `iast.enabled`.
520
520
  */
521
- iast?: boolean | {
522
- /**
523
- * Whether to enable IAST.
524
- * @default false
525
- */
526
- enabled?: boolean,
527
- /**
528
- * Controls the percentage of requests that iast will analyze
529
- * @default 30
530
- */
531
- requestSampling?: number,
532
- /**
533
- * Controls how many request can be analyzing code vulnerabilities at the same time
534
- * @default 2
535
- */
536
- maxConcurrentRequests?: number,
537
- /**
538
- * Controls how many code vulnerabilities can be detected in the same request
539
- * @default 2
540
- */
541
- maxContextOperations?: number,
542
- /**
543
- * Whether to enable vulnerability deduplication
544
- */
545
- deduplicationEnabled?: boolean,
546
- /**
547
- * Whether to enable vulnerability redaction
548
- * @default true
549
- */
550
- redactionEnabled?: boolean,
551
- /**
552
- * Specifies a regex that will redact sensitive source names in vulnerability reports.
553
- */
554
- redactionNamePattern?: string,
555
- /**
556
- * Specifies a regex that will redact sensitive source values in vulnerability reports.
557
- */
558
- redactionValuePattern?: string
559
- }
521
+ iast?: boolean | IastOptions
560
522
 
561
523
  appsec?: {
562
524
  /**
@@ -736,6 +698,11 @@ declare namespace tracer {
736
698
  }
737
699
  };
738
700
 
701
+ /**
702
+ * Configuration of the IAST. Can be a boolean as an alias to `iast.enabled`.
703
+ */
704
+ iast?: boolean | IastOptions
705
+
739
706
  /**
740
707
  * Configuration of ASM Remote Configuration
741
708
  */
@@ -2137,6 +2104,61 @@ declare namespace tracer {
2137
2104
  export type TimeInput = otel.TimeInput;
2138
2105
  export type TraceState = otel.TraceState;
2139
2106
  }
2107
+
2108
+ /**
2109
+ * Iast configuration used in `tracer` and `tracer.experimental` options
2110
+ */
2111
+ interface IastOptions {
2112
+ /**
2113
+ * Whether to enable IAST.
2114
+ * @default false
2115
+ */
2116
+ enabled?: boolean,
2117
+
2118
+ /**
2119
+ * Controls the percentage of requests that iast will analyze
2120
+ * @default 30
2121
+ */
2122
+ requestSampling?: number,
2123
+
2124
+ /**
2125
+ * Controls how many request can be analyzing code vulnerabilities at the same time
2126
+ * @default 2
2127
+ */
2128
+ maxConcurrentRequests?: number,
2129
+
2130
+ /**
2131
+ * Controls how many code vulnerabilities can be detected in the same request
2132
+ * @default 2
2133
+ */
2134
+ maxContextOperations?: number,
2135
+
2136
+ /**
2137
+ * Whether to enable vulnerability deduplication
2138
+ */
2139
+ deduplicationEnabled?: boolean,
2140
+
2141
+ /**
2142
+ * Whether to enable vulnerability redaction
2143
+ * @default true
2144
+ */
2145
+ redactionEnabled?: boolean,
2146
+
2147
+ /**
2148
+ * Specifies a regex that will redact sensitive source names in vulnerability reports.
2149
+ */
2150
+ redactionNamePattern?: string,
2151
+
2152
+ /**
2153
+ * Specifies a regex that will redact sensitive source values in vulnerability reports.
2154
+ */
2155
+ redactionValuePattern?: string,
2156
+
2157
+ /**
2158
+ * Specifies the verbosity of the sent telemetry. Default 'INFORMATION'
2159
+ */
2160
+ telemetryVerbosity?: string
2161
+ }
2140
2162
  }
2141
2163
 
2142
2164
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.18.0",
3
+ "version": "5.19.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -45,9 +45,7 @@
45
45
  "test:integration:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"",
46
46
  "test:unit:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\" --exclude \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"",
47
47
  "test:shimmer": "mocha 'packages/datadog-shimmer/test/**/*.spec.js'",
48
- "test:shimmer:ci": "nyc --no-clean --include 'packages/datadog-shimmer/src/**/*.js' -- npm run test:shimmer",
49
- "leak:core": "node ./scripts/install_plugin_modules && (cd packages/memwatch && yarn) && NODE_PATH=./packages/memwatch/node_modules node --no-warnings ./node_modules/.bin/tape 'packages/dd-trace/test/leak/**/*.js'",
50
- "leak:plugins": "yarn services && (cd packages/memwatch && yarn) && NODE_PATH=./packages/memwatch/node_modules node --no-warnings ./node_modules/.bin/tape \"packages/datadog-plugin-@($(echo $PLUGINS))/test/leak.js\""
48
+ "test:shimmer:ci": "nyc --no-clean --include 'packages/datadog-shimmer/src/**/*.js' -- npm run test:shimmer"
51
49
  },
52
50
  "repository": {
53
51
  "type": "git",
@@ -116,13 +114,12 @@
116
114
  "cli-table3": "^0.6.3",
117
115
  "dotenv": "16.3.1",
118
116
  "esbuild": "0.16.12",
119
- "eslint": "^8.23.0",
117
+ "eslint": "^8.57.0",
120
118
  "eslint-config-standard": "^17.1.0",
121
- "eslint-plugin-import": "^2.8.0",
122
- "eslint-plugin-mocha": "^10.1.0",
123
- "eslint-plugin-n": "^15.7.0",
124
- "eslint-plugin-promise": "^3.6.0",
125
- "eslint-plugin-standard": "^3.0.1",
119
+ "eslint-plugin-import": "^2.29.1",
120
+ "eslint-plugin-mocha": "^10.4.3",
121
+ "eslint-plugin-n": "^16.6.2",
122
+ "eslint-plugin-promise": "^6.4.0",
126
123
  "express": "^4.18.2",
127
124
  "get-port": "^3.2.0",
128
125
  "glob": "^7.1.6",
@@ -139,7 +136,6 @@
139
136
  "sinon": "^15.2.0",
140
137
  "sinon-chai": "^3.7.0",
141
138
  "tap": "^16.3.7",
142
- "tape": "^5.6.5",
143
139
  "tiktoken": "^1.0.15"
144
140
  }
145
141
  }
@@ -39,10 +39,10 @@ function normalizeArgs (args, shell) {
39
39
 
40
40
  if (Array.isArray(args[1])) {
41
41
  childProcessInfo.command = childProcessInfo.command + ' ' + args[1].join(' ')
42
- if (args[2] != null && typeof args[2] === 'object') {
42
+ if (args[2] !== null && typeof args[2] === 'object') {
43
43
  childProcessInfo.options = args[2]
44
44
  }
45
- } else if (args[1] != null && typeof args[1] === 'object') {
45
+ } else if (args[1] !== null && typeof args[1] === 'object') {
46
46
  childProcessInfo.options = args[1]
47
47
  }
48
48
  childProcessInfo.shell = shell ||
@@ -272,7 +272,7 @@ function createWrapFunction (prefix = '', override = '') {
272
272
  const outerResource = new AsyncResource('bound-anonymous-fn')
273
273
 
274
274
  arguments[lastIndex] = innerResource.bind(function (e) {
275
- if (typeof e === 'object') { // fs.exists receives a boolean
275
+ if (e !== null && typeof e === 'object') { // fs.exists receives a boolean
276
276
  errorChannel.publish(e)
277
277
  }
278
278
 
@@ -38,7 +38,7 @@ function wrapStart (start) {
38
38
 
39
39
  function wrapExt (ext) {
40
40
  return function (events, method, options) {
41
- if (typeof events === 'object') {
41
+ if (events !== null && typeof events === 'object') {
42
42
  arguments[0] = wrapEvents(events)
43
43
  } else {
44
44
  arguments[1] = wrapExtension(method)
@@ -132,7 +132,7 @@ function patch (http, methodName) {
132
132
  }
133
133
 
134
134
  function combineOptions (inputURL, inputOptions) {
135
- if (typeof inputOptions === 'object') {
135
+ if (inputOptions !== null && typeof inputOptions === 'object') {
136
136
  return Object.assign(inputURL || {}, inputOptions)
137
137
  } else {
138
138
  return inputURL
@@ -12,7 +12,8 @@ const {
12
12
  getTestParametersString,
13
13
  addEfdStringToTestName,
14
14
  removeEfdStringFromTestName,
15
- getIsFaultyEarlyFlakeDetection
15
+ getIsFaultyEarlyFlakeDetection,
16
+ NUM_FAILED_TEST_RETRIES
16
17
  } = require('../../dd-trace/src/plugins/util/test')
17
18
  const {
18
19
  getFormattedJestTestParameters,
@@ -49,6 +50,9 @@ const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
49
50
  const CHILD_MESSAGE_CALL = 1
50
51
  // Maximum time we'll wait for the tracer to flush
51
52
  const FLUSH_TIMEOUT = 10000
53
+ // eslint-disable-next-line
54
+ // https://github.com/jestjs/jest/blob/41f842a46bb2691f828c3a5f27fc1d6290495b82/packages/jest-circus/src/types.ts#L9C8-L9C54
55
+ const RETRY_TIMES = Symbol.for('RETRY_TIMES')
52
56
 
53
57
  let skippableSuites = []
54
58
  let knownTests = {}
@@ -127,6 +131,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
127
131
  }
128
132
 
129
133
  this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
134
+ this.isFlakyTestRetriesEnabled = this.testEnvironmentOptions._ddIsFlakyTestRetriesEnabled
130
135
 
131
136
  if (this.isEarlyFlakeDetectionEnabled) {
132
137
  const hasKnownTests = !!knownTests.jest
@@ -140,6 +145,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
140
145
  this.isEarlyFlakeDetectionEnabled = false
141
146
  }
142
147
  }
148
+
149
+ if (this.isFlakyTestRetriesEnabled) {
150
+ const currentNumRetries = this.global[RETRY_TIMES]
151
+ if (!currentNumRetries) {
152
+ this.global[RETRY_TIMES] = NUM_FAILED_TEST_RETRIES
153
+ }
154
+ }
143
155
  }
144
156
 
145
157
  getHasSnapshotTests () {
@@ -218,6 +230,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
218
230
  retriedTestsToNumAttempts.set(originalTestName, numEfdRetry + 1)
219
231
  }
220
232
  }
233
+ const isJestRetry = event.test?.invocations > 1
221
234
  asyncResource.runInAsyncScope(() => {
222
235
  testStartCh.publish({
223
236
  name: removeEfdStringFromTestName(testName),
@@ -228,7 +241,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
228
241
  testParameters,
229
242
  frameworkVersion: jestVersion,
230
243
  isNew: isNewTest,
231
- isEfdRetry: numEfdRetry > 0
244
+ isEfdRetry: numEfdRetry > 0,
245
+ isJestRetry
232
246
  })
233
247
  originalTestFns.set(event.test, event.test.fn)
234
248
  event.test.fn = asyncResource.bind(event.test.fn)
@@ -758,6 +772,7 @@ addHook({
758
772
  _ddIsEarlyFlakeDetectionEnabled,
759
773
  _ddEarlyFlakeDetectionNumRetries,
760
774
  _ddRepositoryRoot,
775
+ _ddIsFlakyTestRetriesEnabled,
761
776
  ...restOfTestEnvironmentOptions
762
777
  } = testEnvironmentOptions
763
778
 
@@ -59,7 +59,7 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
59
59
  try {
60
60
  const { topic, messages = [] } = arguments[0]
61
61
  for (const message of messages) {
62
- if (typeof message === 'object') {
62
+ if (message !== null && typeof message === 'object') {
63
63
  message.headers = message.headers || {}
64
64
  }
65
65
  }
@@ -61,7 +61,7 @@ addHook({ name: 'ldapjs', versions: ['>=2'] }, ldapjs => {
61
61
  let filter
62
62
  if (isString(options)) {
63
63
  filter = options
64
- } else if (typeof options === 'object' && options.filter) {
64
+ } else if (options !== null && typeof options === 'object' && options.filter) {
65
65
  if (isString(options.filter)) {
66
66
  filter = options.filter
67
67
  }
@@ -78,7 +78,7 @@ addHook({ name: 'ldapjs', versions: ['>=2'] }, ldapjs => {
78
78
  const callback = arguments[callbackIndex]
79
79
  // eslint-disable-next-line n/handle-callback-err
80
80
  arguments[callbackIndex] = shimmer.wrap(callback, function (err, corkedEmitter) {
81
- if (typeof corkedEmitter === 'object' && typeof corkedEmitter.on === 'function') {
81
+ if (corkedEmitter !== null && typeof corkedEmitter === 'object' && typeof corkedEmitter.on === 'function') {
82
82
  wrapEmitter(corkedEmitter)
83
83
  }
84
84
  callback.apply(this, arguments)
@@ -25,9 +25,9 @@ const methodsOptionalArgs = ['findOneAndUpdate']
25
25
  function getFilters (args, methodName) {
26
26
  const [arg0, arg1] = args
27
27
 
28
- const filters = arg0 && typeof arg0 === 'object' ? [arg0] : []
28
+ const filters = arg0 !== null && typeof arg0 === 'object' ? [arg0] : []
29
29
 
30
- if (arg1 && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) {
30
+ if (arg1 !== null && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) {
31
31
  filters.push(arg1)
32
32
  }
33
33
 
@@ -46,7 +46,7 @@ function wrapHandleApiRequest (handleApiRequest) {
46
46
  function wrapHandleApiRequestWithMatch (handleApiRequest) {
47
47
  return function (req, res, query, match) {
48
48
  return instrument(req, res, () => {
49
- const page = (typeof match === 'object' && typeof match.definition === 'object')
49
+ const page = (match !== null && typeof match === 'object' && typeof match.definition === 'object')
50
50
  ? match.definition.pathname
51
51
  : undefined
52
52
 
@@ -35,7 +35,7 @@ function wrapQuery (query) {
35
35
  const asyncResource = new AsyncResource('bound-anonymous-fn')
36
36
  const processId = this.processID
37
37
 
38
- const pgQuery = arguments[0] && typeof arguments[0] === 'object'
38
+ const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object'
39
39
  ? arguments[0]
40
40
  : { text: arguments[0] }
41
41
 
@@ -109,7 +109,7 @@ function wrapPoolQuery (query) {
109
109
 
110
110
  const asyncResource = new AsyncResource('bound-anonymous-fn')
111
111
 
112
- const pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
112
+ const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
113
113
 
114
114
  return asyncResource.runInAsyncScope(() => {
115
115
  startPoolQueryCh.publish({
@@ -2,7 +2,7 @@ const semver = require('semver')
2
2
 
3
3
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { parseAnnotations, getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
5
+ const { parseAnnotations, getTestSuitePath, NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test')
6
6
  const log = require('../../dd-trace/src/log')
7
7
 
8
8
  const testStartCh = channel('ci:playwright:test:start')
@@ -21,6 +21,7 @@ const testToAr = new WeakMap()
21
21
  const testSuiteToAr = new Map()
22
22
  const testSuiteToTestStatuses = new Map()
23
23
  const testSuiteToErrors = new Map()
24
+ const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
24
25
 
25
26
  let applyRepeatEachIndex = null
26
27
 
@@ -35,6 +36,7 @@ const STATUS_TO_TEST_STATUS = {
35
36
 
36
37
  let remainingTestsByFile = {}
37
38
  let isEarlyFlakeDetectionEnabled = false
39
+ let isFlakyTestRetriesEnabled = false
38
40
  let earlyFlakeDetectionNumRetries = 0
39
41
  let knownTests = {}
40
42
  let rootDir = ''
@@ -196,6 +198,31 @@ function getTestSuiteError (testSuiteAbsolutePath) {
196
198
  return new Error(`${errors.length} errors in this test suite:\n${errors.map(e => e.message).join('\n------\n')}`)
197
199
  }
198
200
 
201
+ function getTestByTestId (dispatcher, testId) {
202
+ if (dispatcher._testById) {
203
+ return dispatcher._testById.get(testId)?.test
204
+ }
205
+ const allTests = dispatcher._allTests || dispatcher._ddAllTests
206
+ if (allTests) {
207
+ return allTests.find(({ id }) => id === testId)
208
+ }
209
+ }
210
+
211
+ function getChannelPromise (channelToPublishTo) {
212
+ return new Promise(resolve => {
213
+ testSessionAsyncResource.runInAsyncScope(() => {
214
+ channelToPublishTo.publish({ onDone: resolve })
215
+ })
216
+ })
217
+ }
218
+ // eslint-disable-next-line
219
+ // Inspired by https://github.com/microsoft/playwright/blob/2b77ed4d7aafa85a600caa0b0d101b72c8437eeb/packages/playwright/src/reporters/base.ts#L293
220
+ // We can't use test.outcome() directly because it's set on follow up handlers:
221
+ // our `testEndHandler` is called before the outcome is set.
222
+ function testWillRetry (test, testStatus) {
223
+ return testStatus === 'fail' && test.results.length <= test.retries
224
+ }
225
+
199
226
  function testBeginHandler (test, browserName) {
200
227
  const {
201
228
  _requireFile: testSuiteAbsolutePath,
@@ -250,6 +277,7 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
250
277
  testFinishCh.publish({
251
278
  testStatus,
252
279
  steps: testResult?.steps || [],
280
+ isRetry: testResult?.retry > 0,
253
281
  error,
254
282
  extraTags: annotationTags,
255
283
  isNew: test._ddIsNew,
@@ -267,8 +295,10 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
267
295
  addErrorToTestSuite(testSuiteAbsolutePath, error)
268
296
  }
269
297
 
270
- remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
271
- .filter(currentTest => currentTest !== test)
298
+ if (!testWillRetry(test, testStatus)) {
299
+ remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
300
+ .filter(currentTest => currentTest !== test)
301
+ }
272
302
 
273
303
  // Last test, we finish the suite
274
304
  if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
@@ -335,16 +365,6 @@ function dispatcherHook (dispatcherExport) {
335
365
  return dispatcherExport
336
366
  }
337
367
 
338
- function getTestByTestId (dispatcher, testId) {
339
- if (dispatcher._testById) {
340
- return dispatcher._testById.get(testId)?.test
341
- }
342
- const allTests = dispatcher._allTests || dispatcher._ddAllTests
343
- if (allTests) {
344
- return allTests.find(({ id }) => id === testId)
345
- }
346
- }
347
-
348
368
  function dispatcherHookNew (dispatcherExport, runWrapper) {
349
369
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper)
350
370
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
@@ -373,8 +393,6 @@ function runnerHook (runnerExport, playwrightVersion) {
373
393
  shimmer.wrap(runnerExport.Runner.prototype, 'runAllTests', runAllTests => async function () {
374
394
  let onDone
375
395
 
376
- const testSessionAsyncResource = new AsyncResource('bound-anonymous-fn')
377
-
378
396
  rootDir = getRootDir(this)
379
397
 
380
398
  const processArgv = process.argv.slice(2).join(' ')
@@ -383,46 +401,42 @@ function runnerHook (runnerExport, playwrightVersion) {
383
401
  testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
384
402
  })
385
403
 
386
- const configurationPromise = new Promise((resolve) => {
387
- onDone = resolve
388
- })
389
-
390
- testSessionAsyncResource.runInAsyncScope(() => {
391
- libraryConfigurationCh.publish({ onDone })
392
- })
393
-
394
404
  try {
395
- const { err, libraryConfig } = await configurationPromise
405
+ const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
396
406
  if (!err) {
397
407
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
398
408
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
409
+ isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
399
410
  }
400
411
  } catch (e) {
412
+ isEarlyFlakeDetectionEnabled = false
401
413
  log.error(e)
402
414
  }
403
415
 
404
416
  if (isEarlyFlakeDetectionEnabled && semver.gte(playwrightVersion, MINIMUM_SUPPORTED_VERSION_EFD)) {
405
- const knownTestsPromise = new Promise((resolve) => {
406
- onDone = resolve
407
- })
408
- testSessionAsyncResource.runInAsyncScope(() => {
409
- knownTestsCh.publish({ onDone })
410
- })
411
-
412
417
  try {
413
- const { err, knownTests: receivedKnownTests } = await knownTestsPromise
418
+ const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh)
414
419
  if (!err) {
415
420
  knownTests = receivedKnownTests
416
421
  } else {
417
422
  isEarlyFlakeDetectionEnabled = false
418
423
  }
419
424
  } catch (err) {
425
+ isEarlyFlakeDetectionEnabled = false
420
426
  log.error(err)
421
427
  }
422
428
  }
423
429
 
424
430
  const projects = getProjectsFromRunner(this)
425
431
 
432
+ if (isFlakyTestRetriesEnabled) {
433
+ projects.forEach(project => {
434
+ if (project.retries === 0) { // Only if it hasn't been set by the user
435
+ project.retries = NUM_FAILED_TEST_RETRIES
436
+ }
437
+ })
438
+ }
439
+
426
440
  const runAllTestsReturn = await runAllTests.apply(this, arguments)
427
441
 
428
442
  Object.values(remainingTestsByFile).forEach(tests => {
@@ -51,7 +51,7 @@ function wrapFn (fn) {
51
51
 
52
52
  try {
53
53
  const result = fn.apply(this, arguments)
54
- if (result && typeof result === 'object' && typeof result.then === 'function') {
54
+ if (result !== null && typeof result === 'object' && typeof result.then === 'function') {
55
55
  return result.then(function () {
56
56
  nextChannel.publish({ req })
57
57
  finishChannel.publish({ req })
@@ -1,5 +1,6 @@
1
1
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
2
  const shimmer = require('../../datadog-shimmer')
3
+ const { NUM_FAILED_TEST_RETRIES } = require('../../dd-trace/src/plugins/util/test')
3
4
 
4
5
  // test hooks
5
6
  const testStartCh = channel('ci:vitest:test:start')
@@ -16,6 +17,7 @@ const testSuiteErrorCh = channel('ci:vitest:test-suite:error')
16
17
  // test session hooks
17
18
  const testSessionStartCh = channel('ci:vitest:session:start')
18
19
  const testSessionFinishCh = channel('ci:vitest:session:finish')
20
+ const libraryConfigurationCh = channel('ci:vitest:library-configuration')
19
21
 
20
22
  const taskToAsync = new WeakMap()
21
23
 
@@ -30,6 +32,14 @@ function isReporterPackageNew (vitestPackage) {
30
32
  return vitestPackage.e?.name === 'BaseSequencer'
31
33
  }
32
34
 
35
+ function getChannelPromise (channelToPublishTo) {
36
+ return new Promise(resolve => {
37
+ sessionAsyncResource.runInAsyncScope(() => {
38
+ channelToPublishTo.publish({ onDone: resolve })
39
+ })
40
+ })
41
+ }
42
+
33
43
  function getSessionStatus (state) {
34
44
  if (state.getCountOfFailedTests() > 0) {
35
45
  return 'fail'
@@ -90,6 +100,23 @@ function getSortWrapper (sort) {
90
100
  if (!testSessionFinishCh.hasSubscribers) {
91
101
  return sort.apply(this, arguments)
92
102
  }
103
+ // There isn't any other async function that we seem to be able to hook into
104
+ // So we will use the sort from BaseSequencer. This means that a custom sequencer
105
+ // will not work. This will be a known limitation.
106
+ let isFlakyTestRetriesEnabled = false
107
+
108
+ try {
109
+ const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh)
110
+ if (!err) {
111
+ isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
112
+ }
113
+ } catch (e) {
114
+ isFlakyTestRetriesEnabled = false
115
+ }
116
+ if (isFlakyTestRetriesEnabled && !this.ctx.config.retry) {
117
+ this.ctx.config.retry = NUM_FAILED_TEST_RETRIES
118
+ }
119
+
93
120
  shimmer.wrap(this.ctx, 'exit', exit => async function () {
94
121
  let onFinish
95
122
 
@@ -126,15 +153,31 @@ addHook({
126
153
  }, (vitestPackage) => {
127
154
  const { VitestTestRunner } = vitestPackage
128
155
  // test start (only tests that are not marked as skip or todo)
129
- shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task) {
156
+ shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
130
157
  if (!testStartCh.hasSubscribers) {
131
158
  return onBeforeTryTask.apply(this, arguments)
132
159
  }
160
+ const { retry: numAttempt } = retryInfo
161
+ // We finish the previous test here because we know it has failed already
162
+ if (numAttempt > 0) {
163
+ const asyncResource = taskToAsync.get(task)
164
+ const testError = task.result?.errors?.[0]
165
+ if (asyncResource) {
166
+ asyncResource.runInAsyncScope(() => {
167
+ testErrorCh.publish({ error: testError })
168
+ })
169
+ }
170
+ }
171
+
133
172
  const asyncResource = new AsyncResource('bound-anonymous-fn')
134
173
  taskToAsync.set(task, asyncResource)
135
174
 
136
175
  asyncResource.runInAsyncScope(() => {
137
- testStartCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath })
176
+ testStartCh.publish({
177
+ testName: getTestName(task),
178
+ testSuiteAbsolutePath: task.file.filepath,
179
+ isRetry: numAttempt > 0
180
+ })
138
181
  })
139
182
  return onBeforeTryTask.apply(this, arguments)
140
183
  })
@@ -235,6 +278,7 @@ addHook({
235
278
 
236
279
  const testTasks = getTypeTasks(startTestsResponse[0].tasks)
237
280
 
281
+ // Only one test task per test, even if there are retries
238
282
  testTasks.forEach(task => {
239
283
  const testAsyncResource = taskToAsync.get(task)
240
284
  const { result } = task
@@ -242,7 +286,7 @@ addHook({
242
286
  if (result) {
243
287
  const { state, duration, errors } = result
244
288
  if (state === 'skip') { // programmatic skip
245
- testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath })
289
+ testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
246
290
  } else if (state === 'pass') {
247
291
  if (testAsyncResource) {
248
292
  testAsyncResource.runInAsyncScope(() => {
@@ -258,8 +302,10 @@ addHook({
258
302
  }
259
303
 
260
304
  if (testAsyncResource) {
305
+ const isRetry = task.result?.retryCount > 0
306
+ // `duration` is the duration of all the retries, so it can't be used if there are retries
261
307
  testAsyncResource.runInAsyncScope(() => {
262
- testErrorCh.publish({ duration, error: testError })
308
+ testErrorCh.publish({ duration: !isRetry ? duration : undefined, error: testError })
263
309
  })
264
310
  }
265
311
  if (errors?.length) {
@@ -267,7 +313,7 @@ addHook({
267
313
  }
268
314
  }
269
315
  } else { // test.skip or test.todo
270
- testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.suite.file.filepath })
316
+ testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
271
317
  }
272
318
  })
273
319
 
@@ -73,7 +73,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
73
73
  if (!cbExists && this.serviceIdentifier === 'sqs') {
74
74
  const params = response.request.params
75
75
  const operation = response.request.operation
76
- this.responseExtractDSMContext(operation, params, response.data, span)
76
+ this.responseExtractDSMContext(operation, params, response.data ?? response, span)
77
77
  }
78
78
  this.addResponseTags(span, response)
79
79
  this.finish(span, response, response.error)