dd-trace 5.36.0 → 5.37.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 (65) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +5 -0
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +14 -13
  5. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  6. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  7. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  8. package/packages/datadog-instrumentations/src/jest.js +103 -5
  9. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  11. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  12. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  13. package/packages/datadog-instrumentations/src/openai.js +8 -0
  14. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  15. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  16. package/packages/datadog-plugin-cucumber/src/index.js +20 -3
  17. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
  18. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  19. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  20. package/packages/datadog-plugin-jest/src/index.js +12 -2
  21. package/packages/datadog-plugin-mocha/src/index.js +22 -3
  22. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  23. package/packages/datadog-plugin-openai/src/tracing.js +1 -2
  24. package/packages/datadog-plugin-playwright/src/index.js +31 -5
  25. package/packages/datadog-plugin-vitest/src/index.js +25 -1
  26. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
  27. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  28. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
  29. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
  30. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  31. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  32. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
  33. package/packages/dd-trace/src/appsec/iast/index.js +2 -0
  34. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  35. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  36. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  37. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  39. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  40. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  41. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  42. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  43. package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
  44. package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
  45. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
  46. package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
  47. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  49. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  50. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  51. package/packages/dd-trace/src/config.js +16 -3
  52. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  53. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  54. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  55. package/packages/dd-trace/src/iitm.js +2 -2
  56. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  57. package/packages/dd-trace/src/opentracing/span.js +2 -2
  58. package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
  59. package/packages/dd-trace/src/plugins/database.js +14 -4
  60. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  61. package/packages/dd-trace/src/plugins/util/test.js +6 -4
  62. package/packages/dd-trace/src/proxy.js +5 -1
  63. package/packages/dd-trace/src/ritm.js +2 -1
  64. package/packages/dd-trace/src/spanleak.js +0 -1
  65. package/packages/memwatch/package.json +0 -9
@@ -27,7 +27,7 @@ require,protobufjs,BSD-3-Clause,Copyright 2016 Daniel Wirtz
27
27
  require,tlhunter-sorted-set,MIT,Copyright (c) 2023 Datadog Inc.
28
28
  require,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer
29
29
  require,rfdc,MIT,Copyright 2019 David Mark Clements
30
- require,semver,ISC,Copyright Isaac Z. Schlueter and Contributors
30
+ require,semifies,Apache license 2.0,Copyright Authors
31
31
  require,shell-quote,mit,Copyright (c) 2013 James Halliday
32
32
  require,source-map,BSD-3-Clause,Copyright (c) 2009-2011, Mozilla Foundation and contributors
33
33
  require,ttl-set,MIT,Copyright (c) 2024 Thomas Watson
@@ -68,6 +68,7 @@ dev,nock,MIT,Copyright 2017 Pedro Teixeira and other contributors
68
68
  dev,nyc,ISC,Copyright 2015 Contributors
69
69
  dev,proxyquire,MIT,Copyright 2013 Thorsten Lorenz
70
70
  dev,rimraf,ISC,Copyright Isaac Z. Schlueter and Contributors
71
+ dev,semver,ISC,Copyright Isaac Z. Schlueter and Contributors
71
72
  dev,sinon,BSD-3-Clause,Copyright 2010-2017 Christian Johansen
72
73
  dev,sinon-chai,WTFPL and BSD-2-Clause,Copyright 2004 Sam Hocevar 2012–2017 Domenic Denicola
73
74
  dev,tap,ISC,Copyright 2011-2022 Isaac Z. Schlueter and Contributors
package/index.d.ts CHANGED
@@ -2226,6 +2226,11 @@ declare namespace tracer {
2226
2226
  */
2227
2227
  redactionValuePattern?: string,
2228
2228
 
2229
+ /**
2230
+ * Allows to enable security controls.
2231
+ */
2232
+ securityControlsConfiguration?: string,
2233
+
2229
2234
  /**
2230
2235
  * Specifies the verbosity of the sent telemetry. Default 'INFORMATION'
2231
2236
  */
package/loader-hook.mjs CHANGED
@@ -1,5 +1 @@
1
- // TODO(bengl): Not sure why `import/export` fails on this line, but it's just
2
- // a passthrough to another module so it should be fine. Disabling for now.
3
-
4
- // eslint-disable-next-line import/export
5
1
  export * from 'import-in-the-middle/hook.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.36.0",
3
+ "version": "5.37.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -12,8 +12,8 @@
12
12
  "bench:e2e:ci-visibility": "node benchmark/e2e-ci/benchmark-run.js",
13
13
  "type:doc": "cd docs && yarn && yarn build",
14
14
  "type:test": "cd docs && yarn && yarn test",
15
- "lint": "node scripts/check_licenses.js && eslint . && yarn audit",
16
- "lint:fix": "node scripts/check_licenses.js && eslint . --fix && yarn audit",
15
+ "lint": "node scripts/check_licenses.js && eslint . --max-warnings 0 && yarn audit",
16
+ "lint:fix": "node scripts/check_licenses.js && eslint . --max-warnings 0 --fix && yarn audit",
17
17
  "release:proposal": "node scripts/release/proposal",
18
18
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
19
19
  "test": "SERVICES=* yarn services && mocha --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
@@ -84,7 +84,7 @@
84
84
  "@datadog/libdatadog": "^0.4.0",
85
85
  "@datadog/native-appsec": "8.4.0",
86
86
  "@datadog/native-iast-rewriter": "2.8.0",
87
- "@datadog/native-iast-taint-tracking": "3.2.0",
87
+ "@datadog/native-iast-taint-tracking": "3.3.0",
88
88
  "@datadog/native-metrics": "^3.1.0",
89
89
  "@datadog/pprof": "5.5.1",
90
90
  "@datadog/sketches-js": "^2.1.0",
@@ -108,7 +108,7 @@
108
108
  "protobufjs": "^7.2.5",
109
109
  "retry": "^0.13.1",
110
110
  "rfdc": "^1.3.1",
111
- "semver": "^7.5.4",
111
+ "semifies": "^1.0.0",
112
112
  "shell-quote": "^1.8.1",
113
113
  "source-map": "^0.7.4",
114
114
  "tlhunter-sorted-set": "^0.1.0",
@@ -116,10 +116,10 @@
116
116
  },
117
117
  "devDependencies": {
118
118
  "@apollo/server": "^4.11.0",
119
- "@eslint/eslintrc": "^3.1.0",
120
- "@eslint/js": "^8.57.1",
119
+ "@eslint/eslintrc": "^3.2.0",
120
+ "@eslint/js": "^9.19.0",
121
121
  "@msgpack/msgpack": "^3.0.0-beta3",
122
- "@stylistic/eslint-plugin-js": "^2.8.0",
122
+ "@stylistic/eslint-plugin-js": "^3.0.1",
123
123
  "@types/node": "^16.0.0",
124
124
  "autocannon": "^4.5.2",
125
125
  "aws-sdk": "^2.1446.0",
@@ -132,12 +132,12 @@
132
132
  "cli-table3": "^0.6.3",
133
133
  "dotenv": "16.3.1",
134
134
  "esbuild": "0.16.12",
135
- "eslint": "^8.57.0",
135
+ "eslint": "^9.19.0",
136
136
  "eslint-config-standard": "^17.1.0",
137
- "eslint-plugin-import": "^2.29.1",
138
- "eslint-plugin-mocha": "^10.4.3",
139
- "eslint-plugin-n": "^16.6.2",
140
- "eslint-plugin-promise": "^6.4.0",
137
+ "eslint-plugin-import": "^2.31.0",
138
+ "eslint-plugin-mocha": "^10.5.0",
139
+ "eslint-plugin-n": "^17.15.1",
140
+ "eslint-plugin-promise": "^7.2.1",
141
141
  "express": "^4.21.2",
142
142
  "get-port": "^3.2.0",
143
143
  "glob": "^7.1.6",
@@ -152,6 +152,7 @@
152
152
  "nyc": "^15.1.0",
153
153
  "proxyquire": "^1.8.0",
154
154
  "rimraf": "^3.0.0",
155
+ "semver": "^7.5.4",
155
156
  "sinon": "^16.1.3",
156
157
  "sinon-chai": "^3.7.0",
157
158
  "tap": "^16.3.7",
@@ -22,6 +22,7 @@ const knownTestsCh = channel('ci:cucumber:known-tests')
22
22
  const skippableSuitesCh = channel('ci:cucumber:test-suite:skippable')
23
23
  const sessionStartCh = channel('ci:cucumber:session:start')
24
24
  const sessionFinishCh = channel('ci:cucumber:session:finish')
25
+ const quarantinedTestsCh = channel('ci:cucumber:quarantined-tests')
25
26
 
26
27
  const workerReportTraceCh = channel('ci:cucumber:worker-report:trace')
27
28
 
@@ -71,6 +72,8 @@ let earlyFlakeDetectionFaultyThreshold = 0
71
72
  let isEarlyFlakeDetectionFaulty = false
72
73
  let isFlakyTestRetriesEnabled = false
73
74
  let isKnownTestsEnabled = false
75
+ let isQuarantinedTestsEnabled = false
76
+ let quarantinedTests = {}
74
77
  let numTestRetries = 0
75
78
  let knownTests = []
76
79
  let skippedSuites = []
@@ -117,6 +120,17 @@ function isNewTest (testSuite, testName) {
117
120
  return !testsForSuite.includes(testName)
118
121
  }
119
122
 
123
+ function isQuarantinedTest (testSuite, testName) {
124
+ return quarantinedTests
125
+ ?.cucumber
126
+ ?.suites
127
+ ?.[testSuite]
128
+ ?.tests
129
+ ?.[testName]
130
+ ?.properties
131
+ ?.quarantined
132
+ }
133
+
120
134
  function getTestStatusFromRetries (testStatuses) {
121
135
  if (testStatuses.every(status => status === 'fail')) {
122
136
  return 'fail'
@@ -293,12 +307,17 @@ function wrapRun (pl, isLatestVersion) {
293
307
  }
294
308
  let isNew = false
295
309
  let isEfdRetry = false
310
+ let isQuarantined = false
296
311
  if (isKnownTestsEnabled && status !== 'skip') {
297
312
  const numRetries = numRetriesByPickleId.get(this.pickle.id)
298
313
 
299
314
  isNew = numRetries !== undefined
300
315
  isEfdRetry = numRetries > 0
301
316
  }
317
+ if (isQuarantinedTestsEnabled) {
318
+ const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
319
+ isQuarantined = isQuarantinedTest(testSuitePath, this.pickle.name)
320
+ }
302
321
  const attemptAsyncResource = numAttemptToAsyncResource.get(numAttempt)
303
322
 
304
323
  const error = getErrorFromCucumberResult(result)
@@ -307,7 +326,15 @@ function wrapRun (pl, isLatestVersion) {
307
326
  await promises.hitBreakpointPromise
308
327
  }
309
328
  attemptAsyncResource.runInAsyncScope(() => {
310
- testFinishCh.publish({ status, skipReason, error, isNew, isEfdRetry, isFlakyRetry: numAttempt > 0 })
329
+ testFinishCh.publish({
330
+ status,
331
+ skipReason,
332
+ error,
333
+ isNew,
334
+ isEfdRetry,
335
+ isFlakyRetry: numAttempt > 0,
336
+ isQuarantined
337
+ })
311
338
  })
312
339
  })
313
340
  return promise
@@ -396,6 +423,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
396
423
  isFlakyTestRetriesEnabled = configurationResponse.libraryConfig?.isFlakyTestRetriesEnabled
397
424
  numTestRetries = configurationResponse.libraryConfig?.flakyTestRetriesCount
398
425
  isKnownTestsEnabled = configurationResponse.libraryConfig?.isKnownTestsEnabled
426
+ isQuarantinedTestsEnabled = configurationResponse.libraryConfig?.isQuarantinedTestsEnabled
399
427
 
400
428
  if (isKnownTestsEnabled) {
401
429
  const knownTestsResponse = await getChannelPromise(knownTestsCh)
@@ -453,6 +481,15 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
453
481
  }
454
482
  }
455
483
 
484
+ if (isQuarantinedTestsEnabled) {
485
+ const quarantinedTestsResponse = await getChannelPromise(quarantinedTestsCh)
486
+ if (!quarantinedTestsResponse.err) {
487
+ quarantinedTests = quarantinedTestsResponse.quarantinedTests
488
+ } else {
489
+ isQuarantinedTestsEnabled = false
490
+ }
491
+ }
492
+
456
493
  const processArgv = process.argv.slice(2).join(' ')
457
494
  const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}`
458
495
 
@@ -500,6 +537,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
500
537
  hasForcedToRunSuites: isForcedToRun,
501
538
  isEarlyFlakeDetectionEnabled,
502
539
  isEarlyFlakeDetectionFaulty,
540
+ isQuarantinedTestsEnabled,
503
541
  isParallel
504
542
  })
505
543
  })
@@ -536,6 +574,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
536
574
  }
537
575
 
538
576
  let isNew = false
577
+ let isQuarantined = false
539
578
 
540
579
  if (isKnownTestsEnabled) {
541
580
  isNew = isNewTest(testSuitePath, pickle.name)
@@ -543,6 +582,10 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
543
582
  numRetriesByPickleId.set(pickle.id, 0)
544
583
  }
545
584
  }
585
+ if (isQuarantinedTestsEnabled) {
586
+ isQuarantined = isQuarantinedTest(testSuitePath, pickle.name)
587
+ }
588
+
546
589
  // TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
547
590
  let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
548
591
 
@@ -557,6 +600,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
557
600
  }
558
601
  let testStatus = lastTestStatus
559
602
  let shouldBePassedByEFD = false
603
+ let shouldBePassedByQuarantine = false
560
604
  if (isNew && isEarlyFlakeDetectionEnabled) {
561
605
  /**
562
606
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
@@ -573,6 +617,11 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
573
617
  }
574
618
  }
575
619
 
620
+ if (isQuarantinedTestsEnabled && isQuarantined) {
621
+ this.success = true
622
+ shouldBePassedByQuarantine = true
623
+ }
624
+
576
625
  if (!pickleResultByFile[testFileAbsolutePath]) {
577
626
  pickleResultByFile[testFileAbsolutePath] = [testStatus]
578
627
  } else {
@@ -604,6 +653,10 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
604
653
  return shouldBePassedByEFD
605
654
  }
606
655
 
656
+ if (isNewerCucumberVersion && isQuarantinedTestsEnabled && isQuarantined) {
657
+ return shouldBePassedByQuarantine
658
+ }
659
+
607
660
  return runTestCaseResult
608
661
  }
609
662
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const dc = require('dc-polyfill')
4
- const semver = require('semver')
4
+ const satisfies = require('semifies')
5
5
  const instrumentations = require('./instrumentations')
6
6
  const { AsyncResource } = require('async_hooks')
7
7
 
@@ -36,7 +36,7 @@ exports.addHook = function addHook ({ name, versions, file, filePattern }, hook)
36
36
 
37
37
  // AsyncResource.bind exists and binds `this` properly only from 17.8.0 and up.
38
38
  // https://nodejs.org/api/async_context.html#asyncresourcebindfn-thisarg
39
- if (semver.satisfies(process.versions.node, '>=17.8.0')) {
39
+ if (satisfies(process.versions.node, '>=17.8.0')) {
40
40
  exports.AsyncResource = AsyncResource
41
41
  } else {
42
42
  exports.AsyncResource = class extends AsyncResource {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
4
  const path = require('path')
5
- const semver = require('semver')
5
+ const satisfies = require('semifies')
6
6
  const Hook = require('./hook')
7
7
  const requirePackageJson = require('../../../dd-trace/src/require-package-json')
8
8
  const log = require('../../../dd-trace/src/log')
@@ -155,7 +155,7 @@ for (const packageName of names) {
155
155
  }
156
156
 
157
157
  function matchVersion (version, ranges) {
158
- return !version || (ranges && ranges.some(range => semver.satisfies(semver.coerce(version), range)))
158
+ return !version || (ranges && ranges.some(range => satisfies(version, range)))
159
159
  }
160
160
 
161
161
  function getVersion (moduleBaseDir) {
@@ -43,6 +43,7 @@ const testErrCh = channel('ci:jest:test:err')
43
43
  const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
44
44
  const libraryConfigurationCh = channel('ci:jest:library-configuration')
45
45
  const knownTestsCh = channel('ci:jest:known-tests')
46
+ const quarantinedTestsCh = channel('ci:jest:quarantined-tests')
46
47
 
47
48
  const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
48
49
 
@@ -70,6 +71,8 @@ let earlyFlakeDetectionFaultyThreshold = 30
70
71
  let isEarlyFlakeDetectionFaulty = false
71
72
  let hasFilteredSkippableSuites = false
72
73
  let isKnownTestsEnabled = false
74
+ let isQuarantinedTestsEnabled = false
75
+ let quarantinedTests = {}
73
76
 
74
77
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
75
78
 
@@ -140,6 +143,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
140
143
  this.flakyTestRetriesCount = this.testEnvironmentOptions._ddFlakyTestRetriesCount
141
144
  this.isDiEnabled = this.testEnvironmentOptions._ddIsDiEnabled
142
145
  this.isKnownTestsEnabled = this.testEnvironmentOptions._ddIsKnownTestsEnabled
146
+ this.isQuarantinedTestsEnabled = this.testEnvironmentOptions._ddIsQuarantinedTestsEnabled
143
147
 
144
148
  if (this.isKnownTestsEnabled) {
145
149
  try {
@@ -161,6 +165,18 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
161
165
  this.global[RETRY_TIMES] = this.flakyTestRetriesCount
162
166
  }
163
167
  }
168
+
169
+ if (this.isQuarantinedTestsEnabled) {
170
+ try {
171
+ const hasQuarantinedTests = !!quarantinedTests.jest
172
+ this.quarantinedTestsForThisSuite = hasQuarantinedTests
173
+ ? this.getQuarantinedTestsForSuite(quarantinedTests.jest.suites[this.testSuite].tests)
174
+ : this.getQuarantinedTestsForSuite(this.testEnvironmentOptions._ddQuarantinedTests)
175
+ } catch (e) {
176
+ log.error('Error parsing quarantined tests', e)
177
+ this.isQuarantinedTestsEnabled = false
178
+ }
179
+ }
164
180
  }
165
181
 
166
182
  getHasSnapshotTests () {
@@ -193,8 +209,25 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
193
209
  return knownTestsForSuite
194
210
  }
195
211
 
212
+ getQuarantinedTestsForSuite (quaratinedTests) {
213
+ if (this.quarantinedTestsForThisSuite) {
214
+ return this.quarantinedTestsForThisSuite
215
+ }
216
+ let quarantinedTestsForSuite = quaratinedTests
217
+ // If jest is using workers, quarantined tests are serialized to json.
218
+ // If jest runs in band, they are not.
219
+ if (typeof quarantinedTestsForSuite === 'string') {
220
+ quarantinedTestsForSuite = JSON.parse(quarantinedTestsForSuite)
221
+ }
222
+ return Object.entries(quarantinedTestsForSuite).reduce((acc, [testName, { properties }]) => {
223
+ if (properties?.quarantined) {
224
+ acc.push(testName)
225
+ }
226
+ return acc
227
+ }, [])
228
+ }
229
+
196
230
  // Add the `add_test` event we don't have the test object yet, so
197
- // we use its describe block to get the full name
198
231
  getTestNameFromAddTestEvent (event, state) {
199
232
  const describeSuffix = getJestTestName(state.currentDescribeBlock)
200
233
  const fullTestName = describeSuffix ? `${describeSuffix} ${event.testName}` : event.testName
@@ -303,6 +336,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
303
336
  }
304
337
  }
305
338
  }
339
+ let isQuarantined = false
340
+
341
+ if (this.isQuarantinedTestsEnabled) {
342
+ const testName = getJestTestName(event.test)
343
+ isQuarantined = this.quarantinedTestsForThisSuite?.includes(testName)
344
+ }
306
345
 
307
346
  const promises = {}
308
347
  const numRetries = this.global[RETRY_TIMES]
@@ -337,7 +376,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
337
376
  testFinishCh.publish({
338
377
  status,
339
378
  testStartLine: getTestLineStart(event.test.asyncError, this.testSuite),
340
- promises
379
+ isQuarantined
341
380
  })
342
381
  })
343
382
 
@@ -485,6 +524,7 @@ function cliWrapper (cli, jestVersion) {
485
524
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
486
525
  earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
487
526
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
527
+ isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
488
528
  }
489
529
  } catch (err) {
490
530
  log.error('Jest library configuration error', err)
@@ -532,6 +572,25 @@ function cliWrapper (cli, jestVersion) {
532
572
  }
533
573
  }
534
574
 
575
+ if (isQuarantinedTestsEnabled) {
576
+ const quarantinedTestsPromise = new Promise((resolve) => {
577
+ onDone = resolve
578
+ })
579
+
580
+ sessionAsyncResource.runInAsyncScope(() => {
581
+ quarantinedTestsCh.publish({ onDone })
582
+ })
583
+
584
+ try {
585
+ const { err, quarantinedTests: receivedQuarantinedTests } = await quarantinedTestsPromise
586
+ if (!err) {
587
+ quarantinedTests = receivedQuarantinedTests
588
+ }
589
+ } catch (err) {
590
+ log.error('Jest quarantined tests error', err)
591
+ }
592
+ }
593
+
535
594
  const processArgv = process.argv.slice(2).join(' ')
536
595
  sessionAsyncResource.runInAsyncScope(() => {
537
596
  testSessionStartCh.publish({ command: `jest ${processArgv}`, frameworkVersion: jestVersion })
@@ -601,6 +660,7 @@ function cliWrapper (cli, jestVersion) {
601
660
  error,
602
661
  isEarlyFlakeDetectionEnabled,
603
662
  isEarlyFlakeDetectionFaulty,
663
+ isQuarantinedTestsEnabled,
604
664
  onDone
605
665
  })
606
666
  })
@@ -634,6 +694,37 @@ function cliWrapper (cli, jestVersion) {
634
694
  }
635
695
  }
636
696
 
697
+ if (isQuarantinedTestsEnabled) {
698
+ const failedTests = result
699
+ .results
700
+ .testResults.flatMap(({ testResults, testFilePath: testSuiteAbsolutePath }) => (
701
+ testResults.map(({ fullName: testName, status }) => ({ testName, testSuiteAbsolutePath, status }))
702
+ ))
703
+ .filter(({ status }) => status === 'failed')
704
+
705
+ let numFailedQuarantinedTests = 0
706
+
707
+ for (const { testName, testSuiteAbsolutePath } of failedTests) {
708
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, result.globalConfig.rootDir)
709
+ const isQuarantined = quarantinedTests
710
+ ?.jest
711
+ ?.suites
712
+ ?.[testSuite]
713
+ ?.tests
714
+ ?.[testName]
715
+ ?.properties
716
+ ?.quarantined
717
+ if (isQuarantined) {
718
+ numFailedQuarantinedTests++
719
+ }
720
+ }
721
+
722
+ // If every test that failed was quarantined, we'll consider the suite passed
723
+ if (numFailedQuarantinedTests !== 0 && result.results.numFailedTests === numFailedQuarantinedTests) {
724
+ result.results.success = true
725
+ }
726
+ }
727
+
637
728
  return result
638
729
  })
639
730
 
@@ -825,6 +916,8 @@ addHook({
825
916
  _ddFlakyTestRetriesCount,
826
917
  _ddIsDiEnabled,
827
918
  _ddIsKnownTestsEnabled,
919
+ _ddIsQuarantinedTestsEnabled,
920
+ _ddQuarantinedTests,
828
921
  ...restOfTestEnvironmentOptions
829
922
  } = testEnvironmentOptions
830
923
 
@@ -936,8 +1029,9 @@ addHook({
936
1029
  })
937
1030
 
938
1031
  /*
939
- * This hook does two things:
1032
+ * This hook does three things:
940
1033
  * - Pass known tests to the workers.
1034
+ * - Pass quarantined tests to the workers.
941
1035
  * - Receive trace, coverage and logs payloads from the workers.
942
1036
  */
943
1037
  addHook({
@@ -947,7 +1041,7 @@ addHook({
947
1041
  }, (childProcessWorker) => {
948
1042
  const ChildProcessWorker = childProcessWorker.default
949
1043
  shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
950
- if (!isKnownTestsEnabled) {
1044
+ if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
951
1045
  return send.apply(this, arguments)
952
1046
  }
953
1047
  const [type] = request
@@ -967,11 +1061,15 @@ addHook({
967
1061
  const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
968
1062
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
969
1063
  const suiteKnownTests = knownTests.jest?.[testSuite] || []
1064
+
1065
+ const suiteQuarantinedTests = quarantinedTests.jest?.suites?.[testSuite]?.tests || {}
1066
+
970
1067
  args[0].config = {
971
1068
  ...config,
972
1069
  testEnvironmentOptions: {
973
1070
  ...config.testEnvironmentOptions,
974
- _ddKnownTests: suiteKnownTests
1071
+ _ddKnownTests: suiteKnownTests,
1072
+ _ddQuarantinedTests: suiteQuarantinedTests
975
1073
  }
976
1074
  }
977
1075
  }
@@ -27,6 +27,7 @@ const {
27
27
  getOnPendingHandler,
28
28
  testFileToSuiteAr,
29
29
  newTests,
30
+ testsQuarantined,
30
31
  getTestFullName,
31
32
  getRunTestsWrapper
32
33
  } = require('./utils')
@@ -61,6 +62,7 @@ const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
61
62
  const libraryConfigurationCh = channel('ci:mocha:library-configuration')
62
63
  const knownTestsCh = channel('ci:mocha:known-tests')
63
64
  const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
65
+ const quarantinedTestsCh = channel('ci:mocha:quarantined-tests')
64
66
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
65
67
  const testSessionStartCh = channel('ci:mocha:session:start')
66
68
  const testSessionFinishCh = channel('ci:mocha:session:finish')
@@ -135,6 +137,18 @@ function getOnEndHandler (isParallel) {
135
137
  }
136
138
  }
137
139
 
140
+ // We subtract the errors from quarantined tests from the total number of failures
141
+ if (config.isQuarantinedTestsEnabled) {
142
+ let numFailedQuarantinedTests = 0
143
+ for (const test of testsQuarantined) {
144
+ if (isTestFailed(test)) {
145
+ numFailedQuarantinedTests++
146
+ }
147
+ }
148
+ this.stats.failures -= numFailedQuarantinedTests
149
+ this.failures -= numFailedQuarantinedTests
150
+ }
151
+
138
152
  if (status === 'fail') {
139
153
  error = new Error(`Failed tests: ${this.failures}.`)
140
154
  }
@@ -165,6 +179,7 @@ function getOnEndHandler (isParallel) {
165
179
  error,
166
180
  isEarlyFlakeDetectionEnabled: config.isEarlyFlakeDetectionEnabled,
167
181
  isEarlyFlakeDetectionFaulty: config.isEarlyFlakeDetectionFaulty,
182
+ isQuarantinedTestsEnabled: config.isQuarantinedTestsEnabled,
168
183
  isParallel
169
184
  })
170
185
  })
@@ -173,6 +188,22 @@ function getOnEndHandler (isParallel) {
173
188
  function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
174
189
  const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
175
190
 
191
+ const onReceivedQuarantinedTests = ({ err, quarantinedTests: receivedQuarantinedTests }) => {
192
+ if (err) {
193
+ config.quarantinedTests = {}
194
+ config.isQuarantinedTestsEnabled = false
195
+ } else {
196
+ config.quarantinedTests = receivedQuarantinedTests
197
+ }
198
+ if (config.isSuitesSkippingEnabled) {
199
+ skippableSuitesCh.publish({
200
+ onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
201
+ })
202
+ } else {
203
+ onFinishRequest()
204
+ }
205
+ }
206
+
176
207
  const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
177
208
  if (err) {
178
209
  suitesToSkip = []
@@ -205,8 +236,11 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
205
236
  } else {
206
237
  config.knownTests = knownTests
207
238
  }
208
-
209
- if (config.isSuitesSkippingEnabled) {
239
+ if (config.isQuarantinedTestsEnabled) {
240
+ quarantinedTestsCh.publish({
241
+ onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
242
+ })
243
+ } else if (config.isSuitesSkippingEnabled) {
210
244
  skippableSuitesCh.publish({
211
245
  onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
212
246
  })
@@ -224,15 +258,20 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
224
258
  config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
225
259
  config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
226
260
  config.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
227
- // ITR and auto test retries are not supported in parallel mode yet
261
+ // ITR, auto test retries and quarantine are not supported in parallel mode yet
228
262
  config.isSuitesSkippingEnabled = !isParallel && libraryConfig.isSuitesSkippingEnabled
229
263
  config.isFlakyTestRetriesEnabled = !isParallel && libraryConfig.isFlakyTestRetriesEnabled
230
264
  config.flakyTestRetriesCount = !isParallel && libraryConfig.flakyTestRetriesCount
265
+ config.isQuarantinedTestsEnabled = !isParallel && libraryConfig.isQuarantinedTestsEnabled
231
266
 
232
267
  if (config.isKnownTestsEnabled) {
233
268
  knownTestsCh.publish({
234
269
  onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
235
270
  })
271
+ } else if (config.isQuarantinedTestsEnabled) {
272
+ quarantinedTestsCh.publish({
273
+ onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
274
+ })
236
275
  } else if (config.isSuitesSkippingEnabled) {
237
276
  skippableSuitesCh.publish({
238
277
  onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
@@ -357,7 +396,7 @@ addHook({
357
396
 
358
397
  this.once('end', getOnEndHandler(false))
359
398
 
360
- this.on('test', getOnTestHandler(true, newTests))
399
+ this.on('test', getOnTestHandler(true))
361
400
 
362
401
  this.on('test end', getOnTestEndHandler())
363
402
 
@@ -579,6 +618,7 @@ addHook({
579
618
 
580
619
  const testPath = getTestSuitePath(testSuiteAbsolutePath, process.cwd())
581
620
  const testSuiteKnownTests = config.knownTests.mocha?.[testPath] || []
621
+ const testSuiteQuarantinedTests = config.quarantinedTests?.modules?.mocha?.suites?.[testPath] || []
582
622
 
583
623
  // We pass the known tests for the test file to the worker
584
624
  const testFileResult = await run.apply(
@@ -589,6 +629,8 @@ addHook({
589
629
  ...workerArgs,
590
630
  _ddEfdNumRetries: config.earlyFlakeDetectionNumRetries,
591
631
  _ddIsEfdEnabled: config.isEarlyFlakeDetectionEnabled,
632
+ _ddIsQuarantinedEnabled: config.isQuarantinedTestsEnabled,
633
+ _ddQuarantinedTests: testSuiteQuarantinedTests,
592
634
  _ddKnownTests: {
593
635
  mocha: {
594
636
  [testPath]: testSuiteKnownTests