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.
- package/LICENSE-3rdparty.csv +1 -2
- package/index.d.ts +21 -1
- package/package.json +32 -29
- package/packages/datadog-instrumentations/src/ai.js +45 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
- package/packages/datadog-instrumentations/src/child_process.js +3 -3
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
- package/packages/datadog-instrumentations/src/cucumber.js +102 -43
- package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
- package/packages/datadog-instrumentations/src/fastify.js +27 -8
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/jest.js +713 -63
- package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
- package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
- package/packages/datadog-instrumentations/src/mongodb.js +74 -0
- package/packages/datadog-instrumentations/src/mongoose.js +4 -5
- package/packages/datadog-instrumentations/src/playwright.js +13 -9
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +28 -1
- package/packages/datadog-instrumentations/src/vitest-util.js +5 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +104 -34
- package/packages/datadog-plugin-ai/src/index.js +1 -1
- package/packages/datadog-plugin-ai/src/tracing.js +66 -3
- package/packages/datadog-plugin-ai/src/utils.js +17 -4
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
- package/packages/datadog-plugin-child_process/src/index.js +13 -2
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
- package/packages/datadog-plugin-cucumber/src/index.js +14 -23
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
- package/packages/datadog-plugin-cypress/src/index.js +47 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
- package/packages/datadog-plugin-graphql/src/execute.js +21 -10
- package/packages/datadog-plugin-graphql/src/index.js +17 -1
- package/packages/datadog-plugin-graphql/src/utils.js +9 -2
- package/packages/datadog-plugin-graphql/src/validate.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +30 -14
- package/packages/datadog-plugin-mocha/src/index.js +39 -14
- package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
- package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
- package/packages/datadog-plugin-playwright/src/index.js +3 -2
- package/packages/datadog-plugin-vitest/src/index.js +51 -9
- package/packages/dd-trace/src/aiguard/index.js +9 -14
- package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
- package/packages/dd-trace/src/appsec/graphql.js +9 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +37 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugin_manager.js +16 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +4 -0
- 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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
682
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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.
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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: !
|
|
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 (
|
|
892
|
-
testSuiteToTestStatuses.
|
|
893
|
-
|
|
894
|
-
|
|
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
|
-
|
|
898
|
-
|
|
900
|
+
if (error) {
|
|
901
|
+
addErrorToTestSuite(testSuiteAbsolutePath, error)
|
|
902
|
+
}
|
|
899
903
|
}
|
|
900
904
|
|
|
901
|
-
if (!
|
|
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 {
|
|
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,
|