dd-trace 6.0.0 → 6.1.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 (77) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +21 -1
  3. package/package.json +32 -29
  4. package/packages/datadog-instrumentations/src/ai.js +45 -0
  5. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  6. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  7. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  9. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  10. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  16. package/packages/datadog-instrumentations/src/jest.js +713 -63
  17. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  19. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  20. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  21. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  22. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  23. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  24. package/packages/datadog-instrumentations/src/vitest-main.js +28 -1
  25. package/packages/datadog-instrumentations/src/vitest-util.js +5 -0
  26. package/packages/datadog-instrumentations/src/vitest-worker.js +104 -34
  27. package/packages/datadog-plugin-ai/src/index.js +1 -1
  28. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  29. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  30. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  31. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  32. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  33. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  34. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  35. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  36. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  38. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  39. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  40. package/packages/datadog-plugin-graphql/src/execute.js +21 -10
  41. package/packages/datadog-plugin-graphql/src/index.js +17 -1
  42. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  43. package/packages/datadog-plugin-graphql/src/validate.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +30 -14
  45. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  46. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  47. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  48. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  50. package/packages/datadog-plugin-vitest/src/index.js +51 -9
  51. package/packages/dd-trace/src/aiguard/index.js +9 -14
  52. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  53. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  54. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  55. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  56. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  57. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  58. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  59. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  60. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  61. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  62. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  63. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  64. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  65. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  66. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  67. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  68. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  69. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  70. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  71. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  72. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  73. package/packages/dd-trace/src/plugin_manager.js +16 -0
  74. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  75. package/packages/dd-trace/src/plugins/index.js +2 -0
  76. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  77. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
@@ -36,6 +36,7 @@ const {
36
36
  getOnTestEndHandler,
37
37
  getOnTestRetryHandler,
38
38
  getOnHookEndHandler,
39
+ patchFailedTestReplayHookUp,
39
40
  getOnFailHandler,
40
41
  getOnPendingHandler,
41
42
  testFileToSuiteCtx,
@@ -382,6 +383,10 @@ function applyTestManagementTestsResponse ({ err, testManagementTests: receivedT
382
383
  }
383
384
  }
384
385
 
386
+ function isFailedTestReplayEnabled () {
387
+ return config.isTestDynamicInstrumentationEnabled && config.isDiEnabled
388
+ }
389
+
385
390
  function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFinishRequest, localSuites) {
386
391
  const ctx = {
387
392
  isParallel,
@@ -471,7 +476,7 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
471
476
  }
472
477
  }
473
478
 
474
- const onReceivedConfiguration = ({ err, libraryConfig, repositoryRoot }) => {
479
+ const onReceivedConfiguration = ({ err, isTestDynamicInstrumentationEnabled, libraryConfig, repositoryRoot }) => {
475
480
  if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) {
476
481
  return mochaGlobalRunCh.runStores(ctx, () => {
477
482
  onFinishRequest()
@@ -492,6 +497,8 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
492
497
  config.isSuitesSkippingEnabled = config.isItrEnabled && libraryConfig.isSuitesSkippingEnabled
493
498
  config.isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
494
499
  config.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
500
+ config.isDiEnabled = libraryConfig.isDiEnabled
501
+ config.isTestDynamicInstrumentationEnabled = isTestDynamicInstrumentationEnabled
495
502
 
496
503
  getTestOptimizationRequestResults({
497
504
  isKnownTestsEnabled: config.isKnownTestsEnabled,
@@ -555,6 +562,9 @@ addHook({
555
562
  })
556
563
 
557
564
  getExecutionConfiguration(runner, false, frameworkVersion, () => {
565
+ if (isFailedTestReplayEnabled()) {
566
+ patchFailedTestReplayHookUp(runner.constructor)
567
+ }
558
568
  if (config.isKnownTestsEnabled) {
559
569
  const testSuites = this.files.map(file => getTestSuitePath(file, process.cwd()))
560
570
  const isFaulty = getIsFaultyEarlyFlakeDetection(
@@ -780,7 +790,13 @@ addHook({
780
790
  this.on('retry', getOnTestRetryHandler(config))
781
791
 
782
792
  // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
783
- this.on('hook end', getOnHookEndHandler(config))
793
+ this.on('hook end', getOnHookEndHandler(config, {
794
+ onStart: incrementPendingRootFinalization,
795
+ onFinish: function (test) {
796
+ finishRootSuiteAfterFinalAttempt(test)
797
+ decrementPendingRootFinalization(test)
798
+ },
799
+ }))
784
800
 
785
801
  this.on('hook end', function (hook) {
786
802
  const test = hook.ctx?.currentTest
@@ -1095,7 +1111,8 @@ addHook({
1095
1111
  (!config.isKnownTestsEnabled &&
1096
1112
  !config.isTestManagementTestsEnabled &&
1097
1113
  !config.isImpactedTestsEnabled &&
1098
- !config.isFlakyTestRetriesEnabled)) {
1114
+ !config.isFlakyTestRetriesEnabled &&
1115
+ !isFailedTestReplayEnabled())) {
1099
1116
  return run.apply(this, arguments)
1100
1117
  }
1101
1118
 
@@ -1146,6 +1163,10 @@ addHook({
1146
1163
  newWorkerArgs._ddFlakyTestRetriesCount = config.flakyTestRetriesCount
1147
1164
  }
1148
1165
 
1166
+ if (isFailedTestReplayEnabled()) {
1167
+ newWorkerArgs._ddIsFailedTestReplayEnabled = true
1168
+ }
1169
+
1149
1170
  // We pass the known tests for the test file to the worker
1150
1171
  const testFileResult = await run.apply(
1151
1172
  this,
@@ -2,9 +2,6 @@
2
2
 
3
3
  const { performance } = require('node:perf_hooks')
4
4
 
5
- // Capture real timers at module load time, before any test can install fake timers.
6
- const realSetTimeout = setTimeout
7
-
8
5
  const {
9
6
  getTestSuitePath,
10
7
  DYNAMIC_NAME_RE,
@@ -19,6 +16,7 @@ const shimmer = require('../../../datadog-shimmer')
19
16
  // test channels
20
17
  const testStartCh = channel('ci:mocha:test:start')
21
18
  const testFinishCh = channel('ci:mocha:test:finish')
19
+ const testDiWaitCh = channel('ci:mocha:test:di:wait')
22
20
  // after a test has failed, we'll publish to this channel
23
21
  const testRetryCh = channel('ci:mocha:test:retry')
24
22
  const errorCh = channel('ci:mocha:test:error')
@@ -28,7 +26,6 @@ const isModifiedCh = channel('ci:mocha:test:is-modified')
28
26
  // suite channels
29
27
  const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
30
28
 
31
- const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
32
29
  const testToContext = new WeakMap()
33
30
  const originalFns = new WeakMap()
34
31
  const testToStartLine = new WeakMap()
@@ -43,6 +40,12 @@ const testsStatuses = new Map()
43
40
  const efdRetryCountByTestFullName = new Map()
44
41
  const efdSlowAbortedTests = new Set()
45
42
  const attemptToFixExecutions = new Map()
43
+
44
+ function waitForHitProbe () {
45
+ const promises = {}
46
+ testDiWaitCh.publish({ promises })
47
+ return promises.hitBreakpointPromise
48
+ }
46
49
  const loggedAttemptToFixTests = new Set()
47
50
 
48
51
  function getAfterEachHooks (testOrHook) {
@@ -639,14 +642,8 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
639
642
  finalAttemptHandlers?.onStart?.(test)
640
643
  }
641
644
 
642
- // After finishing it might take a bit for the snapshot to be handled.
643
- // This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
644
- if (test._ddShouldWaitForHitProbe || test._retriedTest?._ddShouldWaitForHitProbe) {
645
- await new Promise((resolve) => {
646
- realSetTimeout(() => {
647
- resolve()
648
- }, BREAKPOINT_HIT_GRACE_PERIOD_MS)
649
- })
645
+ if (test._retriedTest?._ddShouldWaitForHitProbe) {
646
+ await waitForHitProbe()
650
647
  }
651
648
 
652
649
  if (shouldFinishTest) {
@@ -665,7 +662,7 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
665
662
  }
666
663
  }
667
664
 
668
- function getOnHookEndHandler (config) {
665
+ function getOnHookEndHandler (config, finalAttemptHandlers) {
669
666
  return function (hook) {
670
667
  const test = hook.ctx.currentTest
671
668
  const afterEachHooks = getAfterEachHooks(hook)
@@ -678,22 +675,118 @@ function getOnHookEndHandler (config) {
678
675
  // skip to avoid double-publishing
679
676
  if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
680
677
  const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
681
- if (testFinishInfo.finalStatus !== undefined) {
682
- test._ddIsFinalAttempt = true
678
+ const isFinalAttempt = testFinishInfo.finalStatus !== undefined
679
+ const publishTestFinish = () => {
680
+ testFinishCh.publish({
681
+ status,
682
+ hasBeenRetried: isMochaRetry(test),
683
+ isLastRetry: getIsLastRetry(test),
684
+ ...testFinishInfo,
685
+ ...ctx.currentStore,
686
+ })
687
+ if (isFinalAttempt) {
688
+ test._ddIsFinalAttempt = true
689
+ }
683
690
  }
684
- testFinishCh.publish({
685
- status,
686
- hasBeenRetried: isMochaRetry(test),
687
- isLastRetry: getIsLastRetry(test),
688
- ...testFinishInfo,
689
- ...ctx.currentStore,
690
- })
691
+ if (test._retriedTest?._ddShouldWaitForHitProbe) {
692
+ if (isFinalAttempt) {
693
+ finalAttemptHandlers?.onStart?.(test)
694
+ }
695
+ test._ddDeferredHookEnd = {
696
+ waitForHitProbePromise: waitForHitProbe(),
697
+ publishTestFinish,
698
+ onFinish: isFinalAttempt ? () => finalAttemptHandlers?.onFinish?.(test) : undefined,
699
+ }
700
+ return
701
+ }
702
+ publishTestFinish()
691
703
  }
692
704
  }
693
705
  }
694
706
  }
695
707
  }
696
708
 
709
+ function finishDeferredHookEnd (test) {
710
+ const deferredHookEnd = test?._ddDeferredHookEnd
711
+ if (!deferredHookEnd) return
712
+
713
+ const finish = () => {
714
+ try {
715
+ return deferredHookEnd.publishTestFinish()
716
+ } finally {
717
+ deferredHookEnd.onFinish?.()
718
+ }
719
+ }
720
+
721
+ delete test._ddDeferredHookEnd
722
+ if (!deferredHookEnd.waitForHitProbePromise) return finish()
723
+
724
+ return deferredHookEnd.waitForHitProbePromise.then(
725
+ finish,
726
+ finish
727
+ )
728
+ }
729
+
730
+ /**
731
+ * Runs a Failed Test Replay hookUp callback after pending DI operations that must happen first.
732
+ *
733
+ * @param {(...args: unknown[]) => unknown} fn - Original hookUp completion callback.
734
+ * @param {object} test - Mocha test currently owning the hook.
735
+ * @param {Promise<void>|undefined} failedTestReplayPromise - Pending Failed Test Replay wait, if any.
736
+ * @param {unknown} hookThis - Callback receiver.
737
+ * @param {IArguments} args - Arguments passed by Mocha.
738
+ * @returns {unknown}
739
+ */
740
+ function runFailedTestReplayHookUpCallback (fn, test, failedTestReplayPromise, hookThis, args) {
741
+ const continueAfterProbe = () => {
742
+ const deferredHookEndPromise = finishDeferredHookEnd(test)
743
+ if (deferredHookEndPromise) {
744
+ return deferredHookEndPromise.then(() => fn.apply(hookThis, args), () => fn.apply(hookThis, args))
745
+ }
746
+ return fn.apply(hookThis, args)
747
+ }
748
+
749
+ if (failedTestReplayPromise) {
750
+ return failedTestReplayPromise.then(continueAfterProbe, continueAfterProbe)
751
+ }
752
+ return continueAfterProbe()
753
+ }
754
+
755
+ /**
756
+ * Wraps Mocha's hookUp completion callback so retries wait for DI before continuing.
757
+ *
758
+ * @param {(...args: unknown[]) => unknown} fn - Original hookUp completion callback.
759
+ * @param {object} test - Mocha test currently owning the hook.
760
+ * @param {Promise<void>|undefined} failedTestReplayPromise - Pending Failed Test Replay wait, if any.
761
+ * @returns {(...args: unknown[]) => unknown}
762
+ */
763
+ function wrapFailedTestReplayHookUpCallback (fn, test, failedTestReplayPromise) {
764
+ return shimmer.wrapCallback(fn, fn => function () {
765
+ return runFailedTestReplayHookUpCallback(fn, test, failedTestReplayPromise, this, arguments)
766
+ })
767
+ }
768
+
769
+ const patchedFailedTestReplayHookUp = new WeakSet()
770
+
771
+ function patchFailedTestReplayHookUp (Runner) {
772
+ if (patchedFailedTestReplayHookUp.has(Runner)) return
773
+
774
+ patchedFailedTestReplayHookUp.add(Runner)
775
+ shimmer.wrap(Runner.prototype, 'hookUp', hookUp => function (name, fn) {
776
+ const test = name === 'afterEach' && this.test
777
+ if (!test) {
778
+ return hookUp.apply(this, arguments)
779
+ }
780
+
781
+ const failedTestReplayPromise = test._ddFailedTestReplayPromise
782
+ if (failedTestReplayPromise) {
783
+ delete test._ddFailedTestReplayPromise
784
+ }
785
+
786
+ return hookUp.call(this, name, wrapFailedTestReplayHookUpCallback(fn, test, failedTestReplayPromise))
787
+ })
788
+ }
789
+
697
790
  function getOnFailHandler (isMain, config) {
698
791
  return function (testOrHook, err) {
699
792
  const testFile = testOrHook.file
@@ -764,14 +857,24 @@ function getOnTestRetryHandler (config) {
764
857
  config.isFlakyTestRetriesEnabled &&
765
858
  !test._ddIsAttemptToFix &&
766
859
  !test._ddIsEfdRetry
860
+ const promises = {}
767
861
  testRetryCh.publish({
768
862
  isFirstAttempt,
769
863
  err,
770
864
  willBeRetried,
771
865
  test,
772
866
  isAtrRetry,
867
+ promises,
773
868
  ...ctx.currentStore,
774
869
  })
870
+ if (promises.setProbePromise && promises.finishTestPromise) {
871
+ test._ddFailedTestReplayPromise = Promise.all([
872
+ promises.setProbePromise,
873
+ promises.finishTestPromise,
874
+ ]).then(() => {})
875
+ } else if (promises.setProbePromise || promises.finishTestPromise) {
876
+ test._ddFailedTestReplayPromise = promises.setProbePromise || promises.finishTestPromise
877
+ }
775
878
  }
776
879
  const key = getTestToContextKey(test)
777
880
  testToContext.delete(key)
@@ -901,6 +1004,9 @@ module.exports = {
901
1004
  getOnTestEndHandler,
902
1005
  getOnTestRetryHandler,
903
1006
  getOnHookEndHandler,
1007
+ finishDeferredHookEnd,
1008
+ wrapFailedTestReplayHookUpCallback,
1009
+ patchFailedTestReplayHookUp,
904
1010
  getOnFailHandler,
905
1011
  getOnPendingHandler,
906
1012
  testFileToSuiteCtx,
@@ -13,6 +13,7 @@ const {
13
13
  getOnPendingHandler,
14
14
  getOnTestRetryHandler,
15
15
  getRunTestsWrapper,
16
+ patchFailedTestReplayHookUp,
16
17
  } = require('./utils')
17
18
  require('./common')
18
19
 
@@ -22,6 +23,10 @@ const workerFinishCh = channel('ci:mocha:worker:finish')
22
23
 
23
24
  const config = {}
24
25
 
26
+ function isFailedTestReplayEnabled () {
27
+ return config.isTestDynamicInstrumentationEnabled && config.isDiEnabled
28
+ }
29
+
25
30
  addHook({
26
31
  name: 'mocha',
27
32
  versions: ['>=8.0.0'],
@@ -60,6 +65,11 @@ addHook({
60
65
  delete this.options._ddIsFlakyTestRetriesEnabled
61
66
  delete this.options._ddFlakyTestRetriesCount
62
67
  }
68
+ if (this.options._ddIsFailedTestReplayEnabled) {
69
+ config.isTestDynamicInstrumentationEnabled = true
70
+ config.isDiEnabled = true
71
+ delete this.options._ddIsFailedTestReplayEnabled
72
+ }
63
73
  return run.apply(this, args)
64
74
  })
65
75
 
@@ -78,6 +88,9 @@ addHook({
78
88
  if (!workerFinishCh.hasSubscribers) {
79
89
  return run.apply(this, args)
80
90
  }
91
+ if (isFailedTestReplayEnabled()) {
92
+ patchFailedTestReplayHookUp(Runner)
93
+ }
81
94
  // We flush when the worker ends with its test file (a mocha instance in a worker runs a single test file)
82
95
  this.once('end', () => {
83
96
  workerFinishCh.publish()
@@ -28,6 +28,10 @@ const collectionMethodsWithTwoFilters = [
28
28
 
29
29
  const startCh = channel('datadog:mongodb:collection:filter:start')
30
30
 
31
+ const bulkWriteStartCh = channel('apm:mongodb:bulkwrite:start')
32
+ const bulkWriteFinishCh = channel('apm:mongodb:bulkwrite:finish')
33
+ const bulkWriteErrorCh = channel('apm:mongodb:bulkwrite:error')
34
+
31
35
  addHook({ name: 'mongodb', versions: ['>=3.3 <5', '5', '>=6'] }, mongodb => {
32
36
  for (const methodName of [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters]) {
33
37
  if (!(methodName in mongodb.Collection.prototype)) continue
@@ -55,5 +59,75 @@ addHook({ name: 'mongodb', versions: ['>=3.3 <5', '5', '>=6'] }, mongodb => {
55
59
  }
56
60
  })
57
61
  }
62
+
63
+ // `bulkWrite` fans out into separate `insert`/`update`/`delete` wire commands, so wrap
64
+ // it to open one parent span those per-type commands nest under as children.
65
+ if ('bulkWrite' in mongodb.Collection.prototype) {
66
+ shimmer.wrap(mongodb.Collection.prototype, 'bulkWrite', wrapBulkWrite)
67
+ }
68
+
58
69
  return mongodb
59
70
  })
71
+
72
+ /**
73
+ * @param {Function} bulkWrite
74
+ * @returns {Function}
75
+ */
76
+ function wrapBulkWrite (bulkWrite) {
77
+ return function (...args) {
78
+ /* istanbul ignore if: plugin stays subscribed for the whole suite; the disabled fast path is unreachable */
79
+ if (!bulkWriteStartCh.hasSubscribers) {
80
+ return bulkWrite.apply(this, args)
81
+ }
82
+
83
+ const ctx = { ns: this.namespace }
84
+
85
+ return bulkWriteStartCh.runStores(ctx, () => {
86
+ // Pre-v5 drivers take a trailing callback and return `undefined`; v5+ ignore any
87
+ // extra argument and always return a promise. Wrap a trailing callback for the
88
+ // legacy path and finish on the returned promise when there is one, so both APIs are
89
+ // covered without sniffing the version. Pre-v5 also validates arguments synchronously,
90
+ // so guard the call to finish the span instead of leaking it on a throw.
91
+ const lastIndex = args.length - 1
92
+ const callback = args[lastIndex]
93
+ if (typeof callback === 'function') {
94
+ args[lastIndex] = shimmer.wrapCallback(callback, callback => function (error) {
95
+ if (error) {
96
+ ctx.error = error
97
+ bulkWriteErrorCh.publish(ctx)
98
+ }
99
+ return bulkWriteFinishCh.runStores(ctx, callback, this, ...arguments)
100
+ })
101
+ }
102
+
103
+ let result
104
+ try {
105
+ result = bulkWrite.apply(this, args)
106
+ } catch (error) {
107
+ finishBulkWriteError(ctx, error)
108
+ throw error
109
+ }
110
+
111
+ if (result !== undefined && typeof result.then === 'function') {
112
+ result.then(function (value) {
113
+ ctx.result = value
114
+ bulkWriteFinishCh.publish(ctx)
115
+ }, function (error) {
116
+ finishBulkWriteError(ctx, error)
117
+ })
118
+ }
119
+
120
+ return result
121
+ })
122
+ }
123
+ }
124
+
125
+ /**
126
+ * @param {{ error?: unknown }} ctx
127
+ * @param {unknown} error
128
+ */
129
+ function finishBulkWriteError (ctx, error) {
130
+ ctx.error = error
131
+ bulkWriteErrorCh.publish(ctx)
132
+ bulkWriteFinishCh.publish(ctx)
133
+ }
@@ -6,11 +6,10 @@ const { wrapThen } = require('./helpers/promise')
6
6
 
7
7
  const startCh = channel('datadog:mongoose:model:filter:start')
8
8
  const finishCh = channel('datadog:mongoose:model:filter:finish')
9
- // Bound around the deferred query execution. A subscriber returns the store that
10
- // stays active for the whole async scope that reaches the mongodb driver, so the
11
- // nested driver query can see the parent's analysis marker without it leaking
12
- // past the query. `runStores` enters the store only for that scope and restores
13
- // the parent on its own.
9
+ // Bound around the deferred query execution. The `bindStore` transform returns a
10
+ // child store with the analysis marker set, covering the whole async scope that
11
+ // reaches the mongodb driver. `runStores` enters the child only for that scope
12
+ // and restores the parent on its own.
14
13
  const execCh = channel('datadog:mongoose:model:filter:exec')
15
14
  // this channel is for wrapping the callback of exec methods and handling store context
16
15
  const execStartCh = channel('apm:mongoose:exec:start')
@@ -840,6 +840,8 @@ function testEndHandler ({
840
840
  test._ddHasFailedAllRetries = true
841
841
  }
842
842
 
843
+ const willRetry = testWillRetry(test, testStatus)
844
+
843
845
  // this handles tests that do not go through the worker process (because they're skipped)
844
846
  if (shouldCreateTestSpan) {
845
847
  const testResult = results.at(-1)
@@ -850,7 +852,7 @@ function testEndHandler ({
850
852
  !test._ddIsEfdRetry
851
853
 
852
854
  const finalStatus = getFinalStatus({
853
- isFinalExecution: !testWillRetry(test, testStatus),
855
+ isFinalExecution: !willRetry,
854
856
  isDisabled: test._ddIsDisabled,
855
857
  isQuarantined: test._ddIsQuarantined,
856
858
  isAtrRetry,
@@ -888,17 +890,19 @@ function testEndHandler ({
888
890
  }
889
891
  }
890
892
 
891
- if (testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
892
- testSuiteToTestStatuses.get(testSuiteAbsolutePath).push(testStatus)
893
- } else {
894
- testSuiteToTestStatuses.set(testSuiteAbsolutePath, [testStatus])
895
- }
893
+ if (!willRetry) {
894
+ if (testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
895
+ testSuiteToTestStatuses.get(testSuiteAbsolutePath).push(testStatus)
896
+ } else {
897
+ testSuiteToTestStatuses.set(testSuiteAbsolutePath, [testStatus])
898
+ }
896
899
 
897
- if (error) {
898
- addErrorToTestSuite(testSuiteAbsolutePath, error)
900
+ if (error) {
901
+ addErrorToTestSuite(testSuiteAbsolutePath, error)
902
+ }
899
903
  }
900
904
 
901
- if (!testWillRetry(test, testStatus)) {
905
+ if (!willRetry) {
902
906
  remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
903
907
  .filter(currentTest => currentTest !== test)
904
908
  }
@@ -436,6 +436,7 @@ function createMainProcessReporter (reporterState) {
436
436
  testCommand: testSessionConfiguration.testCommand,
437
437
  repositoryRoot: testSessionConfiguration.repositoryRoot,
438
438
  codeOwnersEntries: testSessionConfiguration.codeOwnersEntries,
439
+ requestErrorTags: reporterState.state.requestErrorTags,
439
440
  isTestFrameworkWorker: true,
440
441
  isVitestNoWorkerInitActive: true,
441
442
  }
@@ -852,6 +853,7 @@ function getRepeatedTaskStatuses (task, status) {
852
853
  function reportFinalTestAttempt (testReport) {
853
854
  const {
854
855
  errors,
856
+ state,
855
857
  status,
856
858
  task,
857
859
  testName,
@@ -867,6 +869,7 @@ function reportFinalTestAttempt (testReport) {
867
869
  isNew: testProperties.isNew,
868
870
  isDisabled: testProperties.isDisabled,
869
871
  isTestFrameworkWorker: true,
872
+ requestErrorTags: state.requestErrorTags,
870
873
  ...testSuiteStore,
871
874
  })
872
875
  return
@@ -929,6 +932,7 @@ function reportTestAttempt (testReport, attempt) {
929
932
  isRetryReasonAtr: !testProperties.isAttemptToFix && !testProperties.isEarlyFlakeDetection &&
930
933
  testProperties.isFlakyTestRetries,
931
934
  isTestFrameworkWorker: true,
935
+ requestErrorTags: state.requestErrorTags,
932
936
  }
933
937
  if (testProperties.isAttemptToFix) {
934
938
  recordAttemptToFixExecution(state.attemptToFixExecutions, {
@@ -64,6 +64,7 @@ let testManagementAttemptToFixRetries = 0
64
64
  let isDiEnabled = false
65
65
  let testCodeCoverageLinesTotal
66
66
  let coverageRootDir
67
+ let requestErrorTags = {}
67
68
  let isSessionStarted = false
68
69
  let isVitestNoWorkerInitActive = false
69
70
  let vitestPool = null
@@ -390,6 +391,20 @@ function resetMainProcessProvidedContext (ctx) {
390
391
  }, 'Could not reset Test Optimization context for workers.')
391
392
  }
392
393
 
394
+ /**
395
+ * Merges request error tags from a Test Optimization request response into the tags propagated by no-worker mode.
396
+ *
397
+ * @param {{ requestErrorTags?: Record<string, string> }|undefined} requestResponse - Request response.
398
+ */
399
+ function mergeRequestErrorTags (requestResponse) {
400
+ if (requestResponse?.requestErrorTags) {
401
+ requestErrorTags = {
402
+ ...requestErrorTags,
403
+ ...requestResponse.requestErrorTags,
404
+ }
405
+ }
406
+ }
407
+
393
408
  async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, shouldInstallNoWorkerInit) {
394
409
  if (!testSessionFinishCh.hasSubscribers) {
395
410
  return
@@ -414,15 +429,21 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
414
429
  }
415
430
 
416
431
  try {
417
- const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh, frameworkVersion, {
432
+ const {
433
+ err,
434
+ libraryConfig,
435
+ requestErrorTags: receivedRequestErrorTags = {},
436
+ } = await getChannelPromise(libraryConfigurationCh, frameworkVersion, {
418
437
  isVitestNoWorkerInitActive: shouldInstallNoWorkerInit,
419
438
  })
439
+ requestErrorTags = receivedRequestErrorTags
420
440
  if (err) {
421
441
  resetLibraryConfig()
422
442
  } else {
423
443
  applyLibraryConfig(libraryConfig)
424
444
  }
425
445
  } catch {
446
+ requestErrorTags = {}
426
447
  resetLibraryConfig()
427
448
  }
428
449
 
@@ -436,6 +457,7 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
436
457
  testCommand,
437
458
  repositoryRoot: receivedRepositoryRoot,
438
459
  codeOwnersEntries,
460
+ testEnvironmentMetadata,
439
461
  } = testSessionConfiguration
440
462
  repositoryRoot = receivedRepositoryRoot || repositoryRoot
441
463
  if (!shouldInstallNoWorkerInit) {
@@ -445,6 +467,7 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
445
467
  _ddTestCommand: testCommand,
446
468
  _ddRepositoryRoot: repositoryRoot,
447
469
  _ddCodeOwnersEntries: codeOwnersEntries,
470
+ _ddTestEnvironmentMetadata: testEnvironmentMetadata,
448
471
  }, 'Could not send test session configuration to workers.')
449
472
  }
450
473
  }
@@ -458,6 +481,8 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
458
481
  getKnownTests: () => getChannelPromise(knownTestsCh),
459
482
  getTestManagementTests: () => getChannelPromise(testManagementTestsCh),
460
483
  })
484
+ mergeRequestErrorTags(knownTestsResponse)
485
+ mergeRequestErrorTags(testManagementTestsResponse)
461
486
 
462
487
  const flakyTestRetriesConfiguration = configureFlakyTestRetries(ctx, testSpecifications)
463
488
  if (flakyTestRetriesConfiguration) {
@@ -597,6 +622,7 @@ function getNoWorkerInitState () {
597
622
  isFlakyTestRetriesEnabled,
598
623
  isKnownTestsEnabled,
599
624
  newTestsWithDynamicNames,
625
+ requestErrorTags,
600
626
  testManagementAttemptToFixRetries,
601
627
  }
602
628
  }
@@ -806,6 +832,7 @@ function getFinishWrapper (exitOrClose) {
806
832
  isEarlyFlakeDetectionEnabled,
807
833
  isEarlyFlakeDetectionFaulty,
808
834
  isTestManagementTestsEnabled,
835
+ requestErrorTags,
809
836
  vitestPool,
810
837
  isVitestNoWorkerInitActive,
811
838
  onFinish,
@@ -8,6 +8,7 @@ const testStartCh = channel('ci:vitest:test:start')
8
8
  const testFinishTimeCh = channel('ci:vitest:test:finish-time')
9
9
  const testPassCh = channel('ci:vitest:test:pass')
10
10
  const testErrorCh = channel('ci:vitest:test:error')
11
+ const testDiWaitCh = channel('ci:vitest:test:di:wait')
11
12
  const testSkipCh = channel('ci:vitest:test:skip')
12
13
  const testFnCh = channel('ci:vitest:test:fn')
13
14
 
@@ -115,6 +116,7 @@ function getProvidedContext () {
115
116
  _ddTestCommand: testCommand,
116
117
  _ddRepositoryRoot: repositoryRoot,
117
118
  _ddCodeOwnersEntries: codeOwnersEntries,
119
+ _ddTestEnvironmentMetadata: testEnvironmentMetadata,
118
120
  } = globalThis.__vitest_worker__.providedContext
119
121
 
120
122
  return {
@@ -136,6 +138,7 @@ function getProvidedContext () {
136
138
  testCommand,
137
139
  repositoryRoot,
138
140
  codeOwnersEntries,
141
+ testEnvironmentMetadata,
139
142
  }
140
143
  } catch {
141
144
  log.error('Vitest workers could not parse provided context, so some features will not work.')
@@ -158,6 +161,7 @@ function getProvidedContext () {
158
161
  testCommand: undefined,
159
162
  repositoryRoot: undefined,
160
163
  codeOwnersEntries: undefined,
164
+ testEnvironmentMetadata: undefined,
161
165
  }
162
166
  }
163
167
  }
@@ -220,6 +224,7 @@ module.exports = {
220
224
  testFinishTimeCh,
221
225
  testPassCh,
222
226
  testErrorCh,
227
+ testDiWaitCh,
223
228
  testSkipCh,
224
229
  testFnCh,
225
230
  testSuiteStartCh,