dd-trace 4.28.0 → 4.30.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 (58) hide show
  1. package/CONTRIBUTING.md +98 -0
  2. package/README.md +8 -99
  3. package/ci/cypress/after-run.js +1 -0
  4. package/ci/cypress/after-spec.js +1 -0
  5. package/index.d.ts +1499 -1486
  6. package/package.json +3 -3
  7. package/packages/datadog-core/src/utils/src/get.js +11 -0
  8. package/packages/datadog-core/src/utils/src/has.js +14 -0
  9. package/packages/datadog-core/src/utils/src/set.js +16 -0
  10. package/packages/datadog-instrumentations/src/amqplib.js +1 -1
  11. package/packages/datadog-instrumentations/src/cucumber.js +157 -42
  12. package/packages/datadog-instrumentations/src/grpc/server.js +3 -1
  13. package/packages/datadog-instrumentations/src/jest.js +80 -40
  14. package/packages/datadog-instrumentations/src/mocha.js +4 -1
  15. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -3
  16. package/packages/datadog-instrumentations/src/playwright.js +78 -16
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +8 -4
  18. package/packages/datadog-plugin-amqplib/src/producer.js +3 -4
  19. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -2
  20. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +60 -57
  21. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +42 -22
  22. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +64 -30
  23. package/packages/datadog-plugin-cucumber/src/index.js +25 -9
  24. package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
  25. package/packages/datadog-plugin-cypress/src/after-spec.js +3 -0
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +625 -0
  27. package/packages/datadog-plugin-cypress/src/plugin.js +6 -549
  28. package/packages/datadog-plugin-cypress/src/support.js +50 -3
  29. package/packages/datadog-plugin-graphql/src/index.js +1 -1
  30. package/packages/datadog-plugin-graphql/src/resolve.js +10 -8
  31. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  32. package/packages/datadog-plugin-jest/src/index.js +11 -2
  33. package/packages/datadog-plugin-kafkajs/src/consumer.js +4 -3
  34. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -5
  35. package/packages/datadog-plugin-playwright/src/index.js +34 -3
  36. package/packages/datadog-plugin-rhea/src/consumer.js +8 -3
  37. package/packages/datadog-plugin-rhea/src/producer.js +3 -4
  38. package/packages/dd-trace/src/appsec/iast/index.js +10 -0
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +18 -5
  40. package/packages/dd-trace/src/appsec/recommended.json +67 -27
  41. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -1
  42. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -3
  43. package/packages/dd-trace/src/config.js +451 -459
  44. package/packages/dd-trace/src/data_streams_context.js +1 -1
  45. package/packages/dd-trace/src/datastreams/pathway.js +58 -1
  46. package/packages/dd-trace/src/datastreams/processor.js +3 -5
  47. package/packages/dd-trace/src/format.js +0 -1
  48. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  49. package/packages/dd-trace/src/opentracing/span.js +4 -4
  50. package/packages/dd-trace/src/plugins/util/test.js +2 -0
  51. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  52. package/packages/dd-trace/src/profiling/exporters/agent.js +77 -32
  53. package/packages/dd-trace/src/telemetry/index.js +22 -34
  54. package/packages/dd-trace/src/tracer.js +3 -3
  55. package/register.js +4 -0
  56. /package/packages/{utils → datadog-core/src/utils}/src/kebabcase.js +0 -0
  57. /package/packages/{utils → datadog-core/src/utils}/src/pick.js +0 -0
  58. /package/packages/{utils → datadog-core/src/utils}/src/uniq.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.28.0",
3
+ "version": "4.30.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -69,11 +69,11 @@
69
69
  "node": ">=16"
70
70
  },
71
71
  "dependencies": {
72
- "@datadog/native-appsec": "7.0.0",
72
+ "@datadog/native-appsec": "7.1.0",
73
73
  "@datadog/native-iast-rewriter": "2.2.3",
74
74
  "@datadog/native-iast-taint-tracking": "1.7.0",
75
75
  "@datadog/native-metrics": "^2.0.0",
76
- "@datadog/pprof": "5.0.0",
76
+ "@datadog/pprof": "5.1.0",
77
77
  "@datadog/sketches-js": "^2.1.0",
78
78
  "@opentelemetry/api": "^1.0.0",
79
79
  "@opentelemetry/core": "^1.14.0",
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ module.exports = (object, path) => {
4
+ const pathArr = path.split('.')
5
+ let val = object
6
+ for (const p of pathArr) {
7
+ if (val === undefined) return val
8
+ val = val[p]
9
+ }
10
+ return val
11
+ }
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ module.exports = (object, path) => {
4
+ const pathArr = path.split('.')
5
+ let property = object
6
+ for (const n of pathArr) {
7
+ if (property.hasOwnProperty(n)) {
8
+ property = property[n]
9
+ } else {
10
+ return false
11
+ }
12
+ }
13
+ return true
14
+ }
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ module.exports = (object, path, value) => {
4
+ const pathArr = path.split('.')
5
+ let property = object
6
+ let i
7
+ for (i = 0; i < pathArr.length - 1; i++) {
8
+ const n = pathArr[i]
9
+ if (property.hasOwnProperty(n)) {
10
+ property = property[n]
11
+ } else {
12
+ property[n] = property = {}
13
+ }
14
+ }
15
+ property[pathArr[i]] = value
16
+ }
@@ -5,7 +5,7 @@ const {
5
5
  addHook,
6
6
  AsyncResource
7
7
  } = require('./helpers/instrument')
8
- const kebabCase = require('../../utils/src/kebabcase')
8
+ const kebabCase = require('../../datadog-core/src/utils/src/kebabcase')
9
9
  const shimmer = require('../../datadog-shimmer')
10
10
 
11
11
  const startCh = channel('apm:amqplib:command:start')
@@ -17,6 +17,7 @@ const testSuiteFinishCh = channel('ci:cucumber:test-suite:finish')
17
17
  const testSuiteCodeCoverageCh = channel('ci:cucumber:test-suite:code-coverage')
18
18
 
19
19
  const libraryConfigurationCh = channel('ci:cucumber:library-configuration')
20
+ const knownTestsCh = channel('ci:cucumber:known-tests')
20
21
  const skippableSuitesCh = channel('ci:cucumber:test-suite:skippable')
21
22
  const sessionStartCh = channel('ci:cucumber:session:start')
22
23
  const sessionFinishCh = channel('ci:cucumber:session:finish')
@@ -41,12 +42,18 @@ const originalCoverageMap = createCoverageMap()
41
42
  // TODO: remove in a later major version
42
43
  const patched = new WeakSet()
43
44
 
45
+ const lastStatusByPickleId = new Map()
46
+ const numRetriesByPickleId = new Map()
47
+
44
48
  let pickleByFile = {}
45
49
  const pickleResultByFile = {}
46
50
  let skippableSuites = []
47
51
  let itrCorrelationId = ''
48
52
  let isForcedToRun = false
49
53
  let isUnskippable = false
54
+ let isEarlyFlakeDetectionEnabled = false
55
+ let earlyFlakeDetectionNumRetries = 0
56
+ let knownTests = []
50
57
 
51
58
  function getSuiteStatusFromTestStatuses (testStatuses) {
52
59
  if (testStatuses.some(status => status === 'fail')) {
@@ -84,6 +91,21 @@ function getStatusFromResultLatest (result) {
84
91
  return { status: 'fail', errorMessage: result.message }
85
92
  }
86
93
 
94
+ function isNewTest (testSuite, testName) {
95
+ const testsForSuite = knownTests.cucumber?.[testSuite] || []
96
+ return !testsForSuite.includes(testName)
97
+ }
98
+
99
+ function getTestStatusFromRetries (testStatuses) {
100
+ if (testStatuses.every(status => status === 'fail')) {
101
+ return 'fail'
102
+ }
103
+ if (testStatuses.some(status => status === 'pass')) {
104
+ return 'pass'
105
+ }
106
+ return 'pass'
107
+ }
108
+
87
109
  function wrapRun (pl, isLatestVersion) {
88
110
  if (patched.has(pl)) return
89
111
 
@@ -98,18 +120,7 @@ function wrapRun (pl, isLatestVersion) {
98
120
  return asyncResource.runInAsyncScope(() => {
99
121
  const testFileAbsolutePath = this.pickle.uri
100
122
 
101
- if (!pickleResultByFile[testFileAbsolutePath]) { // first test in suite
102
- isUnskippable = isMarkedAsUnskippable(this.pickle)
103
- const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
104
- isForcedToRun = isUnskippable && skippableSuites.includes(testSuitePath)
105
-
106
- testSuiteStartCh.publish({ testSuitePath, isUnskippable, isForcedToRun, itrCorrelationId })
107
- }
108
-
109
- const testSourceLine = this.gherkinDocument &&
110
- this.gherkinDocument.feature &&
111
- this.gherkinDocument.feature.location &&
112
- this.gherkinDocument.feature.location.line
123
+ const testSourceLine = this.gherkinDocument?.feature?.location?.line
113
124
 
114
125
  testStartCh.publish({
115
126
  testName: this.pickle.name,
@@ -123,30 +134,20 @@ function wrapRun (pl, isLatestVersion) {
123
134
  const { status, skipReason, errorMessage } = isLatestVersion
124
135
  ? getStatusFromResultLatest(result) : getStatusFromResult(result)
125
136
 
126
- if (!pickleResultByFile[testFileAbsolutePath]) {
127
- pickleResultByFile[testFileAbsolutePath] = [status]
137
+ if (lastStatusByPickleId.has(this.pickle.id)) {
138
+ lastStatusByPickleId.get(this.pickle.id).push(status)
128
139
  } else {
129
- pickleResultByFile[testFileAbsolutePath].push(status)
140
+ lastStatusByPickleId.set(this.pickle.id, [status])
130
141
  }
131
- testFinishCh.publish({ status, skipReason, errorMessage })
132
- // last test in suite
133
- if (pickleResultByFile[testFileAbsolutePath].length === pickleByFile[testFileAbsolutePath].length) {
134
- const testSuiteStatus = getSuiteStatusFromTestStatuses(pickleResultByFile[testFileAbsolutePath])
135
- if (global.__coverage__) {
136
- const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
137
-
138
- testSuiteCodeCoverageCh.publish({
139
- coverageFiles,
140
- suiteFile: testFileAbsolutePath
141
- })
142
- // We need to reset coverage to get a code coverage per suite
143
- // Before that, we preserve the original coverage
144
- mergeCoverage(global.__coverage__, originalCoverageMap)
145
- resetCoverage(global.__coverage__)
146
- }
147
-
148
- testSuiteFinishCh.publish(testSuiteStatus)
142
+ let isNew = false
143
+ let isEfdRetry = false
144
+ if (isEarlyFlakeDetectionEnabled && status !== 'skip') {
145
+ const numRetries = numRetriesByPickleId.get(this.pickle.id)
146
+
147
+ isNew = numRetries !== undefined
148
+ isEfdRetry = numRetries > 0
149
149
  }
150
+ testFinishCh.publish({ status, skipReason, errorMessage, isNew, isEfdRetry })
150
151
  })
151
152
  return promise
152
153
  } catch (err) {
@@ -258,12 +259,11 @@ function getPickleByFile (runtime) {
258
259
  }, {})
259
260
  }
260
261
 
261
- addHook({
262
- name: '@cucumber/cucumber',
263
- versions: ['>=7.0.0'],
264
- file: 'lib/runtime/index.js'
265
- }, (runtimePackage, frameworkVersion) => {
266
- shimmer.wrap(runtimePackage.default.prototype, 'start', start => async function () {
262
+ function getWrappedStart (start, frameworkVersion) {
263
+ return async function () {
264
+ if (!libraryConfigurationCh.hasSubscribers) {
265
+ return start.apply(this, arguments)
266
+ }
267
267
  const asyncResource = new AsyncResource('bound-anonymous-fn')
268
268
  let onDone
269
269
 
@@ -275,7 +275,23 @@ addHook({
275
275
  libraryConfigurationCh.publish({ onDone })
276
276
  })
277
277
 
278
- await configPromise
278
+ const configurationResponse = await configPromise
279
+
280
+ isEarlyFlakeDetectionEnabled = configurationResponse.libraryConfig?.isEarlyFlakeDetectionEnabled
281
+ earlyFlakeDetectionNumRetries = configurationResponse.libraryConfig?.earlyFlakeDetectionNumRetries
282
+
283
+ if (isEarlyFlakeDetectionEnabled) {
284
+ const knownTestsPromise = new Promise(resolve => {
285
+ onDone = resolve
286
+ })
287
+ asyncResource.runInAsyncScope(() => {
288
+ knownTestsCh.publish({ onDone })
289
+ })
290
+ const knownTestsResponse = await knownTestsPromise
291
+ if (!knownTestsResponse.err) {
292
+ knownTests = knownTestsResponse.knownTests
293
+ }
294
+ }
279
295
 
280
296
  const skippableSuitesPromise = new Promise(resolve => {
281
297
  onDone = resolve
@@ -342,11 +358,110 @@ addHook({
342
358
  testCodeCoverageLinesTotal,
343
359
  numSkippedSuites: skippedSuites.length,
344
360
  hasUnskippableSuites: isUnskippable,
345
- hasForcedToRunSuites: isForcedToRun
361
+ hasForcedToRunSuites: isForcedToRun,
362
+ isEarlyFlakeDetectionEnabled
346
363
  })
347
364
  })
348
365
  return success
349
- })
366
+ }
367
+ }
368
+
369
+ function getWrappedRunTest (runTestFunction) {
370
+ return async function (pickleId) {
371
+ const test = this.eventDataCollector.getPickle(pickleId)
372
+
373
+ const testFileAbsolutePath = test.uri
374
+ const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
375
+
376
+ if (!pickleResultByFile[testFileAbsolutePath]) { // first test in suite
377
+ isUnskippable = isMarkedAsUnskippable(test)
378
+ isForcedToRun = isUnskippable && skippableSuites.includes(testSuitePath)
379
+
380
+ testSuiteStartCh.publish({ testSuitePath, isUnskippable, isForcedToRun, itrCorrelationId })
381
+ }
382
+
383
+ let isNew = false
384
+
385
+ if (isEarlyFlakeDetectionEnabled) {
386
+ isNew = isNewTest(testSuitePath, test.name)
387
+ if (isNew) {
388
+ numRetriesByPickleId.set(pickleId, 0)
389
+ }
390
+ }
391
+ const runTestCaseResult = await runTestFunction.apply(this, arguments)
392
+
393
+ const testStatuses = lastStatusByPickleId.get(pickleId)
394
+ const lastTestStatus = testStatuses[testStatuses.length - 1]
395
+ // If it's a new test and it hasn't been skipped, we run it again
396
+ if (isEarlyFlakeDetectionEnabled && lastTestStatus !== 'skip' && isNew) {
397
+ for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
398
+ numRetriesByPickleId.set(pickleId, retryIndex + 1)
399
+ await runTestFunction.apply(this, arguments)
400
+ }
401
+ }
402
+ let testStatus = lastTestStatus
403
+ if (isEarlyFlakeDetectionEnabled) {
404
+ /**
405
+ * If Early Flake Detection (EFD) is enabled the logic is as follows:
406
+ * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
407
+ * - If just a single attempt passes, we will prevent the test process from failing.
408
+ * The rationale behind is the following: you may still be able to block your CI pipeline by gating
409
+ * on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
410
+ */
411
+ testStatus = getTestStatusFromRetries(testStatuses)
412
+ if (testStatus === 'pass') {
413
+ this.success = true
414
+ }
415
+ }
416
+
417
+ if (!pickleResultByFile[testFileAbsolutePath]) {
418
+ pickleResultByFile[testFileAbsolutePath] = [testStatus]
419
+ } else {
420
+ pickleResultByFile[testFileAbsolutePath].push(testStatus)
421
+ }
422
+
423
+ // last test in suite
424
+ if (pickleResultByFile[testFileAbsolutePath].length === pickleByFile[testFileAbsolutePath].length) {
425
+ const testSuiteStatus = getSuiteStatusFromTestStatuses(pickleResultByFile[testFileAbsolutePath])
426
+ if (global.__coverage__) {
427
+ const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
428
+
429
+ testSuiteCodeCoverageCh.publish({
430
+ coverageFiles,
431
+ suiteFile: testFileAbsolutePath
432
+ })
433
+ // We need to reset coverage to get a code coverage per suite
434
+ // Before that, we preserve the original coverage
435
+ mergeCoverage(global.__coverage__, originalCoverageMap)
436
+ resetCoverage(global.__coverage__)
437
+ }
438
+
439
+ testSuiteFinishCh.publish(testSuiteStatus)
440
+ }
441
+
442
+ return runTestCaseResult
443
+ }
444
+ }
445
+
446
+ // From 7.3.0 onwards, runPickle becomes runTestCase
447
+ addHook({
448
+ name: '@cucumber/cucumber',
449
+ versions: ['>=7.3.0'],
450
+ file: 'lib/runtime/index.js'
451
+ }, (runtimePackage, frameworkVersion) => {
452
+ shimmer.wrap(runtimePackage.default.prototype, 'runTestCase', runTestCase => getWrappedRunTest(runTestCase))
453
+ shimmer.wrap(runtimePackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion))
454
+
455
+ return runtimePackage
456
+ })
457
+
458
+ addHook({
459
+ name: '@cucumber/cucumber',
460
+ versions: ['>=7.0.0 <7.3.0'],
461
+ file: 'lib/runtime/index.js'
462
+ }, (runtimePackage, frameworkVersion) => {
463
+ shimmer.wrap(runtimePackage.default.prototype, 'runPickle', runPickle => getWrappedRunTest(runPickle))
464
+ shimmer.wrap(runtimePackage.default.prototype, 'start', start => getWrappedStart(start, frameworkVersion))
350
465
 
351
466
  return runtimePackage
352
467
  })
@@ -92,7 +92,9 @@ function createWrapEmit (call, ctx, onCancel) {
92
92
  finishChannel.publish(ctx)
93
93
  call.removeListener('cancelled', onCancel)
94
94
  break
95
- case 'finish':
95
+ // Streams are always cancelled before `finish` since 1.10.0 so we have
96
+ // to use `prefinish` instead to avoid cancellation false positives.
97
+ case 'prefinish':
96
98
  if (call.status) {
97
99
  updateChannel.publish(call.status)
98
100
  }
@@ -58,6 +58,7 @@ let hasUnskippableSuites = false
58
58
  let hasForcedToRunSuites = false
59
59
  let isEarlyFlakeDetectionEnabled = false
60
60
  let earlyFlakeDetectionNumRetries = 0
61
+ let hasFilteredSkippableSuites = false
61
62
 
62
63
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
63
64
 
@@ -114,6 +115,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
114
115
  this.nameToParams = {}
115
116
  this.global._ddtrace = global._ddtrace
116
117
 
118
+ this.displayName = config.projectConfig?.displayName?.name
117
119
  this.testEnvironmentOptions = getTestEnvironmentOptions(config)
118
120
 
119
121
  const repositoryRoot = this.testEnvironmentOptions._ddRepositoryRoot
@@ -138,14 +140,16 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
138
140
  // Function that receives a list of known tests for a test service and
139
141
  // returns the ones that belong to the current suite
140
142
  getKnownTestsForSuite (knownTests) {
143
+ if (this.knownTestsForThisSuite) {
144
+ return this.knownTestsForThisSuite
145
+ }
141
146
  let knownTestsForSuite = knownTests
142
- // If jest runs in band, the known tests are not serialized, so they're an array.
143
- if (!Array.isArray(knownTests)) {
147
+ // If jest is using workers, known tests are serialized to json.
148
+ // If jest runs in band, they are not.
149
+ if (typeof knownTestsForSuite === 'string') {
144
150
  knownTestsForSuite = JSON.parse(knownTestsForSuite)
145
151
  }
146
- return knownTestsForSuite
147
- .filter(test => test.includes(this.testSuite))
148
- .map(test => test.replace(`jest.${this.testSuite}.`, '').trim())
152
+ return knownTestsForSuite.jest?.[this.testSuite] || []
149
153
  }
150
154
 
151
155
  // Add the `add_test` event we don't have the test object yet, so
@@ -200,6 +204,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
200
204
  suite: this.testSuite,
201
205
  testSourceFile: this.testSourceFile,
202
206
  runner: 'jest-circus',
207
+ displayName: this.displayName,
203
208
  testParameters,
204
209
  frameworkVersion: jestVersion,
205
210
  isNew: isNewTest,
@@ -251,6 +256,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
251
256
  suite: this.testSuite,
252
257
  testSourceFile: this.testSourceFile,
253
258
  runner: 'jest-circus',
259
+ displayName: this.displayName,
254
260
  frameworkVersion: jestVersion,
255
261
  testStartLine: getTestLineStart(event.test.asyncError, this.testSuite)
256
262
  })
@@ -270,6 +276,23 @@ function getTestEnvironment (pkg, jestVersion) {
270
276
  return getWrappedEnvironment(pkg, jestVersion)
271
277
  }
272
278
 
279
+ function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
280
+ const jestSuitesToRun = getJestSuitesToRun(skippableSuites, originalTests, rootDir || process.cwd())
281
+ hasFilteredSkippableSuites = true
282
+ log.debug(
283
+ () => `${jestSuitesToRun.suitesToRun.length} out of ${originalTests.length} suites are going to run.`
284
+ )
285
+ hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
286
+ hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
287
+
288
+ isSuitesSkipped = jestSuitesToRun.suitesToRun.length !== originalTests.length
289
+ numSkippedSuites = jestSuitesToRun.skippedSuites.length
290
+
291
+ itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
292
+ skippableSuites = []
293
+ return jestSuitesToRun.suitesToRun
294
+ }
295
+
273
296
  addHook({
274
297
  name: 'jest-environment-node',
275
298
  versions: ['>=24.8.0']
@@ -280,6 +303,51 @@ addHook({
280
303
  versions: ['>=24.8.0']
281
304
  }, getTestEnvironment)
282
305
 
306
+ function getWrappedScheduleTests (scheduleTests, frameworkVersion) {
307
+ return async function (tests) {
308
+ if (!isSuitesSkippingEnabled || hasFilteredSkippableSuites) {
309
+ return scheduleTests.apply(this, arguments)
310
+ }
311
+ const [test] = tests
312
+ const rootDir = test?.context?.config?.rootDir
313
+
314
+ arguments[0] = applySuiteSkipping(tests, rootDir, frameworkVersion)
315
+
316
+ return scheduleTests.apply(this, arguments)
317
+ }
318
+ }
319
+
320
+ addHook({
321
+ name: '@jest/core',
322
+ file: 'build/TestScheduler.js',
323
+ versions: ['>=27.0.0']
324
+ }, (testSchedulerPackage, frameworkVersion) => {
325
+ const oldCreateTestScheduler = testSchedulerPackage.createTestScheduler
326
+ const newCreateTestScheduler = async function () {
327
+ if (!isSuitesSkippingEnabled || hasFilteredSkippableSuites) {
328
+ return oldCreateTestScheduler.apply(this, arguments)
329
+ }
330
+ // If suite skipping is enabled and has not filtered skippable suites yet, we'll attempt to do it
331
+ const scheduler = await oldCreateTestScheduler.apply(this, arguments)
332
+ shimmer.wrap(scheduler, 'scheduleTests', scheduleTests => getWrappedScheduleTests(scheduleTests, frameworkVersion))
333
+ return scheduler
334
+ }
335
+ testSchedulerPackage.createTestScheduler = newCreateTestScheduler
336
+ return testSchedulerPackage
337
+ })
338
+
339
+ addHook({
340
+ name: '@jest/core',
341
+ file: 'build/TestScheduler.js',
342
+ versions: ['>=24.8.0 <27.0.0']
343
+ }, (testSchedulerPackage, frameworkVersion) => {
344
+ shimmer.wrap(
345
+ testSchedulerPackage.default.prototype,
346
+ 'scheduleTests', scheduleTests => getWrappedScheduleTests(scheduleTests, frameworkVersion)
347
+ )
348
+ return testSchedulerPackage
349
+ })
350
+
283
351
  addHook({
284
352
  name: '@jest/test-sequencer',
285
353
  versions: ['>=24.8.0']
@@ -287,29 +355,13 @@ addHook({
287
355
  shimmer.wrap(sequencerPackage.default.prototype, 'shard', shard => function () {
288
356
  const shardedTests = shard.apply(this, arguments)
289
357
 
290
- if (!shardedTests.length) {
358
+ if (!shardedTests.length || !isSuitesSkippingEnabled || !skippableSuites.length) {
291
359
  return shardedTests
292
360
  }
293
- // TODO: could we get the rootDir from each test?
294
361
  const [test] = shardedTests
295
362
  const rootDir = test?.context?.config?.rootDir
296
363
 
297
- const jestSuitesToRun = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
298
-
299
- log.debug(
300
- () => `${jestSuitesToRun.suitesToRun.length} out of ${shardedTests.length} suites are going to run.`
301
- )
302
-
303
- hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
304
- hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
305
-
306
- isSuitesSkipped = jestSuitesToRun.suitesToRun.length !== shardedTests.length
307
- numSkippedSuites = jestSuitesToRun.skippedSuites.length
308
-
309
- itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
310
-
311
- skippableSuites = []
312
- return jestSuitesToRun.suitesToRun
364
+ return applySuiteSkipping(shardedTests, rootDir, frameworkVersion)
313
365
  })
314
366
  return sequencerPackage
315
367
  })
@@ -512,6 +564,7 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
512
564
  testSuiteStartCh.publish({
513
565
  testSuite: environment.testSuite,
514
566
  testEnvironmentOptions: environment.testEnvironmentOptions,
567
+ displayName: environment.displayName,
515
568
  frameworkVersion: jestVersion
516
569
  })
517
570
  return adapter.apply(this, arguments).then(suiteResults => {
@@ -660,13 +713,13 @@ addHook({
660
713
  const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
661
714
 
662
715
  shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
663
- if (!skippableSuites.length) {
716
+ if (!isSuitesSkippingEnabled || !skippableSuites.length) {
664
717
  return getTestPaths.apply(this, arguments)
665
718
  }
666
719
 
667
720
  const [{ rootDir, shard }] = arguments
668
721
 
669
- if (shard && shard.shardIndex) {
722
+ if (shard?.shardCount > 1) {
670
723
  // If the user is using jest sharding, we want to apply the filtering of tests in the shard process.
671
724
  // The reason for this is the following:
672
725
  // The tests for different shards are likely being run in different CI jobs so
@@ -680,21 +733,8 @@ addHook({
680
733
  const testPaths = await getTestPaths.apply(this, arguments)
681
734
  const { tests } = testPaths
682
735
 
683
- const jestSuitesToRun = getJestSuitesToRun(skippableSuites, tests, rootDir)
684
-
685
- log.debug(() => `${jestSuitesToRun.suitesToRun.length} out of ${tests.length} suites are going to run.`)
686
-
687
- hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
688
- hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
689
-
690
- isSuitesSkipped = jestSuitesToRun.suitesToRun.length !== tests.length
691
- numSkippedSuites = jestSuitesToRun.skippedSuites.length
692
-
693
- itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
694
-
695
- skippableSuites = []
696
-
697
- return { ...testPaths, tests: jestSuitesToRun.suitesToRun }
736
+ const suitesToRun = applySuiteSkipping(tests, rootDir, frameworkVersion)
737
+ return { ...testPaths, tests: suitesToRun }
698
738
  })
699
739
 
700
740
  return searchSourcePackage
@@ -106,7 +106,10 @@ function getTestFullName (test) {
106
106
  }
107
107
 
108
108
  function isNewTest (test) {
109
- return !knownTests.includes(getTestFullName(test))
109
+ const testSuite = getTestSuitePath(test.file, process.cwd())
110
+ const testName = removeEfdStringFromTestName(test.fullTitle())
111
+ const testsForSuite = knownTests.mocha?.[testSuite] || []
112
+ return !testsForSuite.includes(testName)
110
113
  }
111
114
 
112
115
  function retryTest (test) {
@@ -32,12 +32,18 @@ addHook({ name: 'mongodb', versions: ['>=4 <4.6.0'], file: 'lib/cmap/connection.
32
32
  return Connection
33
33
  })
34
34
 
35
- addHook({ name: 'mongodb', versions: ['>=4.6.0'], file: 'lib/cmap/connection.js' }, Connection => {
35
+ addHook({ name: 'mongodb', versions: ['>=4.6.0 <6.4.0'], file: 'lib/cmap/connection.js' }, Connection => {
36
36
  const proto = Connection.Connection.prototype
37
37
  shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command'))
38
38
  return Connection
39
39
  })
40
40
 
41
+ addHook({ name: 'mongodb', versions: ['>=6.4.0'], file: 'lib/cmap/connection.js' }, Connection => {
42
+ const proto = Connection.Connection.prototype
43
+ shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command', undefined, instrumentPromise))
44
+ return Connection
45
+ })
46
+
41
47
  addHook({ name: 'mongodb', versions: ['>=3.3 <4'], file: 'lib/core/wireprotocol/index.js' }, wp => wrapWp(wp))
42
48
 
43
49
  addHook({ name: 'mongodb-core', versions: ['>=3.2'], file: 'lib/wireprotocol/index.js' }, wp => wrapWp(wp))
@@ -89,7 +95,7 @@ function wrapUnifiedCommand (command, operation, name) {
89
95
  return shimmer.wrap(command, wrapped)
90
96
  }
91
97
 
92
- function wrapConnectionCommand (command, operation, name) {
98
+ function wrapConnectionCommand (command, operation, name, instrumentFn = instrument) {
93
99
  const wrapped = function (ns, ops) {
94
100
  if (!startCh.hasSubscribers) {
95
101
  return command.apply(this, arguments)
@@ -101,7 +107,7 @@ function wrapConnectionCommand (command, operation, name) {
101
107
  const topology = { s: { options } }
102
108
 
103
109
  ns = `${ns.db}.${ns.collection}`
104
- return instrument(operation, command, this, arguments, topology, ns, ops, { name })
110
+ return instrumentFn(operation, command, this, arguments, topology, ns, ops, { name })
105
111
  }
106
112
  return shimmer.wrap(command, wrapped)
107
113
  }
@@ -179,3 +185,28 @@ function instrument (operation, command, ctx, args, server, ns, ops, options = {
179
185
  }
180
186
  })
181
187
  }
188
+
189
+ function instrumentPromise (operation, command, ctx, args, server, ns, ops, options = {}) {
190
+ const name = options.name || (ops && Object.keys(ops)[0])
191
+
192
+ const serverInfo = server && server.s && server.s.options
193
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
194
+
195
+ return asyncResource.runInAsyncScope(() => {
196
+ startCh.publish({ ns, ops, options: serverInfo, name })
197
+
198
+ const promise = command.apply(ctx, args)
199
+
200
+ promise.then(function (res) {
201
+ finishCh.publish()
202
+ return res
203
+ }, function (err) {
204
+ errorCh.publish(err)
205
+ finishCh.publish()
206
+
207
+ return Promise.reject(err)
208
+ })
209
+
210
+ return promise
211
+ })
212
+ }