dd-trace 5.133.0 → 5.134.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/ci/vitest-no-worker-init-runner-setup.mjs +27 -0
- package/index.d.ts +16 -5
- package/package.json +17 -16
- package/packages/datadog-instrumentations/index.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +17 -8
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +27 -8
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/jest.js +12 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/vitest.js +8 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +5 -0
- package/packages/datadog-instrumentations/src/jest.js +69 -57
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/nova-sonic/session.js +25 -16
- package/packages/datadog-instrumentations/src/playwright.js +2 -0
- package/packages/datadog-instrumentations/src/stripe.js +23 -12
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +7 -3
- package/packages/datadog-instrumentations/src/vitest-util.js +5 -2
- package/packages/datadog-instrumentations/src/vitest.js +8 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -0
- package/packages/datadog-plugin-cypress/src/support.js +2 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +143 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
- package/packages/dd-trace/src/appsec/stack_trace.js +3 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +3 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/index.js +45 -0
- package/packages/dd-trace/src/config/supported-configurations.json +9 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +6 -5
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -5
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +4 -6
- package/packages/dd-trace/src/debugger/unsupported-runtime.js +16 -0
- package/packages/dd-trace/src/llmobs/agent-manifest.js +19 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +9 -1
- package/packages/dd-trace/src/llmobs/sdk.js +15 -4
- package/packages/dd-trace/src/llmobs/span_processor.js +12 -0
- package/packages/dd-trace/src/llmobs/tagger.js +18 -8
- package/packages/dd-trace/src/llmobs/util.js +10 -4
- package/packages/dd-trace/src/openfeature/index.js +6 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +4 -4
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +18 -1
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/tracing.js +0 -1
- package/packages/dd-trace/src/plugins/util/git.js +91 -35
- package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -1
- package/packages/dd-trace/src/plugins/util/test.js +2 -0
- package/packages/dd-trace/src/plugins/util/web.js +90 -20
- package/packages/dd-trace/src/span_stats.js +4 -1
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +1 -1
- package/packages/dd-trace/src/util.js +8 -4
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +8 -8
|
@@ -64,7 +64,7 @@ const {
|
|
|
64
64
|
getChannelPromise,
|
|
65
65
|
publishWithCompletion,
|
|
66
66
|
} = require('./helpers/channel')
|
|
67
|
-
const { addHook, channel } = require('./helpers/instrument')
|
|
67
|
+
const { addHook, channel, getHooks, tracingChannel } = require('./helpers/instrument')
|
|
68
68
|
|
|
69
69
|
const testSessionStartCh = channel('ci:jest:session:start')
|
|
70
70
|
const testSessionFinishCh = channel('ci:jest:session:finish')
|
|
@@ -125,6 +125,7 @@ const jestSessionState = (globalThis[JEST_SESSION_STATE] ||= {})
|
|
|
125
125
|
|
|
126
126
|
// https://github.com/jestjs/jest/blob/41f842a46bb2691f828c3a5f27fc1d6290495b82/packages/jest-circus/src/types.ts#L9C8-L9C54
|
|
127
127
|
const RETRY_TIMES = Symbol.for('RETRY_TIMES')
|
|
128
|
+
const RETRY_TIMES_SETTER = Symbol.for('RETRY_TIMES_SETTER')
|
|
128
129
|
|
|
129
130
|
let skippableSuites = []
|
|
130
131
|
let skippableSuitesCoverage
|
|
@@ -195,6 +196,7 @@ const dynamicAtrResultHandlerRegistrations = new WeakMap()
|
|
|
195
196
|
// Tests that are genuinely new (not in known tests list).
|
|
196
197
|
const newTests = new Set()
|
|
197
198
|
const testSuiteJestObjects = new Map()
|
|
199
|
+
const deferredTestSuiteFinishes = new Map()
|
|
198
200
|
const testSuiteDatadogEnvironments = new Map()
|
|
199
201
|
const wrappedJestGlobals = new WeakSet()
|
|
200
202
|
const wrappedJestEsmLoaders = new WeakSet()
|
|
@@ -741,9 +743,11 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
741
743
|
}
|
|
742
744
|
}
|
|
743
745
|
|
|
746
|
+
this.hasAutomaticRetries = false
|
|
744
747
|
if (this.isFlakyTestRetriesEnabled) {
|
|
745
748
|
const currentNumRetries = this.global[RETRY_TIMES]
|
|
746
749
|
if (!currentNumRetries) {
|
|
750
|
+
this.hasAutomaticRetries = true
|
|
747
751
|
// When dynamic ATR is enabled, use the max bucket value as the initial count.
|
|
748
752
|
// The actual duration-based count is computed per test after the first attempt.
|
|
749
753
|
if (this.isDynamicAtrEnabled) {
|
|
@@ -2445,7 +2449,16 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
2445
2449
|
}
|
|
2446
2450
|
|
|
2447
2451
|
let isAtrRetry = false
|
|
2448
|
-
|
|
2452
|
+
let hasAutomaticRetries = this.hasAutomaticRetries || dynamicAtrRetryCountByTest.has(event.test)
|
|
2453
|
+
// Describe retries suppress per-test retries throughout the block, including nested describes.
|
|
2454
|
+
for (let block = event.test.parent; block; block = block.parent) {
|
|
2455
|
+
if (state.describeRetryOptions?.has(block)) {
|
|
2456
|
+
hasAutomaticRetries = false
|
|
2457
|
+
break
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
if (this.isFlakyTestRetriesEnabled && hasAutomaticRetries &&
|
|
2461
|
+
event.test?.invocations > 1 && !isAttemptToFix && !isEfdRetry) {
|
|
2449
2462
|
isAtrRetry = true
|
|
2450
2463
|
}
|
|
2451
2464
|
|
|
@@ -2652,19 +2665,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
2652
2665
|
}
|
|
2653
2666
|
}
|
|
2654
2667
|
}
|
|
2655
|
-
|
|
2656
|
-
realSetTimeout(() => {
|
|
2657
|
-
if (activeTestSuiteAbsolutePath === this.testSuiteAbsolutePath) {
|
|
2658
|
-
activeTestSuiteAbsolutePath = undefined
|
|
2659
|
-
}
|
|
2660
|
-
}, 0)
|
|
2661
|
-
}
|
|
2662
|
-
const result = super.teardown()
|
|
2663
|
-
if (result?.then) {
|
|
2664
|
-
return result.finally(clearActiveTestSuite)
|
|
2665
|
-
}
|
|
2666
|
-
clearActiveTestSuite()
|
|
2667
|
-
return result
|
|
2668
|
+
return super.teardown()
|
|
2668
2669
|
}
|
|
2669
2670
|
}
|
|
2670
2671
|
}
|
|
@@ -3492,22 +3493,48 @@ function shouldWaitForTestSuiteFinish (environment) {
|
|
|
3492
3493
|
return isJestWorker && environment.globalConfig?.workerIdleMemoryLimit !== undefined
|
|
3493
3494
|
}
|
|
3494
3495
|
|
|
3496
|
+
/**
|
|
3497
|
+
* @param {string} testSuiteAbsolutePath
|
|
3498
|
+
*/
|
|
3499
|
+
function finishTestSuiteAfterRun (testSuiteAbsolutePath) {
|
|
3500
|
+
const clearActiveTestSuite = () => {
|
|
3501
|
+
realSetTimeout(() => {
|
|
3502
|
+
if (activeTestSuiteAbsolutePath === testSuiteAbsolutePath) {
|
|
3503
|
+
activeTestSuiteAbsolutePath = undefined
|
|
3504
|
+
}
|
|
3505
|
+
}, 0)
|
|
3506
|
+
}
|
|
3507
|
+
const payload = deferredTestSuiteFinishes.get(testSuiteAbsolutePath)
|
|
3508
|
+
deferredTestSuiteFinishes.delete(testSuiteAbsolutePath)
|
|
3509
|
+
if (payload) {
|
|
3510
|
+
return getChannelPromise(testSuiteFinishCh, { ...payload, waitForFinish: true })
|
|
3511
|
+
.finally(clearActiveTestSuite)
|
|
3512
|
+
}
|
|
3513
|
+
return clearActiveTestSuite()
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
tracingChannel('orchestrion:jest-runner:runTestInternal').end.subscribe((message) => {
|
|
3517
|
+
const ctx = /** @type {{ arguments: [string, ...unknown[]], result: Promise<unknown> }} */ (message)
|
|
3518
|
+
const [testSuiteAbsolutePath] = ctx.arguments
|
|
3519
|
+
// Jest's runner awaits the complete custom teardown before this promise settles.
|
|
3520
|
+
ctx.result = ctx.result.finally(() => finishTestSuiteAfterRun(testSuiteAbsolutePath))
|
|
3521
|
+
})
|
|
3522
|
+
|
|
3523
|
+
for (const hook of getHooks('jest-runner').values()) addHook(hook, moduleExports => moduleExports)
|
|
3524
|
+
|
|
3495
3525
|
/**
|
|
3496
3526
|
* @param {Record<string, unknown>} payload
|
|
3497
|
-
* @param {
|
|
3527
|
+
* @param {InstanceType<ReturnType<typeof getWrappedEnvironment>>} environment
|
|
3498
3528
|
*/
|
|
3499
|
-
function publishTestSuiteFinish (payload,
|
|
3529
|
+
function publishTestSuiteFinish (payload, environment) {
|
|
3500
3530
|
if (!testSuiteFinishCh.hasSubscribers) return
|
|
3501
3531
|
|
|
3502
|
-
if (
|
|
3503
|
-
|
|
3532
|
+
if (shouldWaitForTestSuiteFinish(environment)) {
|
|
3533
|
+
deferredTestSuiteFinishes.set(environment.testSuiteAbsolutePath, payload)
|
|
3504
3534
|
return
|
|
3505
3535
|
}
|
|
3506
3536
|
|
|
3507
|
-
|
|
3508
|
-
...payload,
|
|
3509
|
-
waitForFinish,
|
|
3510
|
-
})
|
|
3537
|
+
testSuiteFinishCh.publish(payload)
|
|
3511
3538
|
}
|
|
3512
3539
|
|
|
3513
3540
|
function cleanupTestSuiteState (testSuiteAbsolutePath) {
|
|
@@ -3684,48 +3711,24 @@ function jestAdapterWrapper (jestAdapter, jestVersion, isIitm, hookMeta) {
|
|
|
3684
3711
|
testSuiteAbsolutePath: environment.testSuiteAbsolutePath,
|
|
3685
3712
|
})
|
|
3686
3713
|
}
|
|
3687
|
-
const waitForFinish = shouldWaitForTestSuiteFinish(environment)
|
|
3688
3714
|
const finishPayload = {
|
|
3689
3715
|
status,
|
|
3690
3716
|
errorMessage,
|
|
3691
3717
|
testSuiteAbsolutePath: environment.testSuiteAbsolutePath,
|
|
3692
3718
|
}
|
|
3693
|
-
|
|
3694
|
-
const finishPromise = publishTestSuiteFinish(finishPayload, waitForFinish)
|
|
3695
|
-
if (finishPromise) {
|
|
3696
|
-
return finishPromise.then(() => {
|
|
3697
|
-
// Cleanup per-suite state to avoid memory leaks
|
|
3698
|
-
cleanupTestSuiteState(environment.testSuiteAbsolutePath)
|
|
3699
|
-
|
|
3700
|
-
return suiteResults
|
|
3701
|
-
})
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
|
-
publishTestSuiteFinish(finishPayload, waitForFinish)
|
|
3719
|
+
publishTestSuiteFinish(finishPayload, environment)
|
|
3705
3720
|
|
|
3706
3721
|
// Cleanup per-suite state to avoid memory leaks
|
|
3707
3722
|
cleanupTestSuiteState(environment.testSuiteAbsolutePath)
|
|
3708
3723
|
|
|
3709
3724
|
return suiteResults
|
|
3710
3725
|
}).catch(error => {
|
|
3711
|
-
const waitForFinish = shouldWaitForTestSuiteFinish(environment)
|
|
3712
3726
|
const finishPayload = {
|
|
3713
3727
|
status: 'fail',
|
|
3714
3728
|
error,
|
|
3715
3729
|
testSuiteAbsolutePath: environment.testSuiteAbsolutePath,
|
|
3716
3730
|
}
|
|
3717
|
-
|
|
3718
|
-
const finishPromise = publishTestSuiteFinish(finishPayload, waitForFinish)
|
|
3719
|
-
if (finishPromise) {
|
|
3720
|
-
return finishPromise.then(() => {
|
|
3721
|
-
// Cleanup per-suite state to avoid memory leaks
|
|
3722
|
-
cleanupTestSuiteState(environment.testSuiteAbsolutePath)
|
|
3723
|
-
|
|
3724
|
-
throw error
|
|
3725
|
-
})
|
|
3726
|
-
}
|
|
3727
|
-
}
|
|
3728
|
-
publishTestSuiteFinish(finishPayload, waitForFinish)
|
|
3731
|
+
publishTestSuiteFinish(finishPayload, environment)
|
|
3729
3732
|
|
|
3730
3733
|
// Cleanup per-suite state to avoid memory leaks
|
|
3731
3734
|
cleanupTestSuiteState(environment.testSuiteAbsolutePath)
|
|
@@ -3978,12 +3981,11 @@ const LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE = new Set([
|
|
|
3978
3981
|
'selenium-webdriver/chromium',
|
|
3979
3982
|
])
|
|
3980
3983
|
|
|
3981
|
-
function recordMockedFile (suiteFilePath, moduleName) {
|
|
3984
|
+
function recordMockedFile (suiteFilePath, moduleName, from = suiteFilePath) {
|
|
3982
3985
|
if (!suiteFilePath || typeof moduleName !== 'string') return
|
|
3983
3986
|
|
|
3984
3987
|
const existingMockedFiles = testSuiteMockedFiles.get(suiteFilePath) || []
|
|
3985
|
-
const
|
|
3986
|
-
const mockPath = path.resolve(suiteDir, moduleName)
|
|
3988
|
+
const mockPath = path.resolve(path.dirname(from), moduleName)
|
|
3987
3989
|
existingMockedFiles.push(mockPath)
|
|
3988
3990
|
testSuiteMockedFiles.set(suiteFilePath, existingMockedFiles)
|
|
3989
3991
|
}
|
|
@@ -4018,14 +4020,24 @@ function getMockedFiles (suiteFilePath) {
|
|
|
4018
4020
|
return staticMockedFiles
|
|
4019
4021
|
}
|
|
4020
4022
|
|
|
4021
|
-
function wrapJestObject (jestObject, suiteFilePath) {
|
|
4023
|
+
function wrapJestObject (jestObject, suiteFilePath, from = suiteFilePath) {
|
|
4022
4024
|
if (!jestObject || !suiteFilePath || wrappedJestObjects.has(jestObject)) return
|
|
4023
4025
|
|
|
4024
4026
|
testSuiteJestObjects.set(suiteFilePath, jestObject)
|
|
4025
4027
|
wrappedJestObjects.add(jestObject)
|
|
4026
4028
|
|
|
4029
|
+
// retryTimes is created per runtime, so wrap the returned public API object.
|
|
4030
|
+
shimmer.wrap(jestObject, 'retryTimes', retryTimes => function () {
|
|
4031
|
+
const environment = testSuiteDatadogEnvironments.get(suiteFilePath)
|
|
4032
|
+
// Older Jest versions ignore entireDescribe and still replace the global budget.
|
|
4033
|
+
if (environment && !(arguments[1]?.entireDescribe && environment.global[RETRY_TIMES_SETTER])) {
|
|
4034
|
+
environment.hasAutomaticRetries = false
|
|
4035
|
+
}
|
|
4036
|
+
return retryTimes.apply(this, arguments)
|
|
4037
|
+
})
|
|
4038
|
+
|
|
4027
4039
|
shimmer.wrap(jestObject, 'mock', mock => function (moduleName) {
|
|
4028
|
-
recordMockedFile(suiteFilePath, moduleName)
|
|
4040
|
+
recordMockedFile(suiteFilePath, moduleName, from)
|
|
4029
4041
|
return mock.apply(this, arguments)
|
|
4030
4042
|
})
|
|
4031
4043
|
}
|
|
@@ -4039,7 +4051,7 @@ function wrapJestGlobalsForRuntime (runtime) {
|
|
|
4039
4051
|
wrappedJestGlobals.add(jestGlobals)
|
|
4040
4052
|
shimmer.wrap(jestGlobals, 'jestObjectFor', jestObjectFor => function (from) {
|
|
4041
4053
|
const jestObject = jestObjectFor.apply(this, arguments)
|
|
4042
|
-
wrapJestObject(jestObject, from)
|
|
4054
|
+
wrapJestObject(jestObject, runtime._testPath || from, from)
|
|
4043
4055
|
return jestObject
|
|
4044
4056
|
})
|
|
4045
4057
|
}
|
|
@@ -4334,9 +4346,9 @@ addHook({
|
|
|
4334
4346
|
if (typeof Runtime.prototype._createJestObjectFor === 'function') {
|
|
4335
4347
|
shimmer.wrap(Runtime.prototype, '_createJestObjectFor', _createJestObjectFor => function (from) {
|
|
4336
4348
|
const result = _createJestObjectFor.apply(this, arguments)
|
|
4337
|
-
const suiteFilePath = this._testPath || from
|
|
4349
|
+
const suiteFilePath = this._testPath || this._environment?.testSuiteAbsolutePath || from
|
|
4338
4350
|
|
|
4339
|
-
wrapJestObject(result, suiteFilePath)
|
|
4351
|
+
wrapJestObject(result, suiteFilePath, from)
|
|
4340
4352
|
return result
|
|
4341
4353
|
})
|
|
4342
4354
|
}
|
|
@@ -43,6 +43,9 @@ const originalEfdFns = new WeakMap()
|
|
|
43
43
|
const originalFns = new WeakMap()
|
|
44
44
|
const originalPendingByTest = new WeakMap()
|
|
45
45
|
const originalRetriesByTest = new WeakMap()
|
|
46
|
+
let automaticRetryTests = new WeakSet()
|
|
47
|
+
let automaticRetryAttempts = new WeakMap()
|
|
48
|
+
let nativeRetryTests = new WeakSet()
|
|
46
49
|
const testToStartLine = new WeakMap()
|
|
47
50
|
const testFileToSuiteCtx = new Map()
|
|
48
51
|
const datadogRetryOriginals = new WeakMap()
|
|
@@ -346,6 +349,9 @@ function resetRunState (rootSuite) {
|
|
|
346
349
|
efdRetryCountByTestFullName.clear()
|
|
347
350
|
efdSlowAbortedTests.clear()
|
|
348
351
|
suiteHookFailureSuites.clear()
|
|
352
|
+
automaticRetryTests = new WeakSet()
|
|
353
|
+
automaticRetryAttempts = new WeakMap()
|
|
354
|
+
nativeRetryTests = new WeakSet()
|
|
349
355
|
dynamicAtrRetryCountByTest = new WeakMap()
|
|
350
356
|
attemptToFixExecutions.clear()
|
|
351
357
|
loggedAttemptToFixTests.clear()
|
|
@@ -423,7 +429,7 @@ function isMochaRetry (test) {
|
|
|
423
429
|
}
|
|
424
430
|
|
|
425
431
|
function getIsLastRetry (test) {
|
|
426
|
-
return test._currentRetry
|
|
432
|
+
return test._currentRetry >= test._retries
|
|
427
433
|
}
|
|
428
434
|
|
|
429
435
|
function getTestFullName (test) {
|
|
@@ -534,6 +540,18 @@ function inheritDatadogPropertiesFromRetriedTest (test) {
|
|
|
534
540
|
|
|
535
541
|
function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
536
542
|
const Runnable = RunnablePackage.Runnable ?? RunnablePackage
|
|
543
|
+
// Mocha 12 can load its ESM class through require() without the ESM rewriter.
|
|
544
|
+
// Intercept the exported prototype to observe setters in both injection modes.
|
|
545
|
+
shimmer.wrap(Runnable.prototype, 'retries', retries => function () {
|
|
546
|
+
if (arguments.length && automaticRetryTests.delete(this)) {
|
|
547
|
+
const originalTest = this._retriedTest || this
|
|
548
|
+
// afterEach can change this runnable after Mocha has already copied its budget into a queued clone.
|
|
549
|
+
if (!automaticRetryAttempts.get(originalTest)?.has(this._currentRetry + 1)) {
|
|
550
|
+
nativeRetryTests.add(originalTest)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return retries.apply(this, arguments)
|
|
554
|
+
})
|
|
537
555
|
shimmer.wrap(Runnable.prototype, 'run', run => function (...args) {
|
|
538
556
|
if (!testFinishCh.hasSubscribers) {
|
|
539
557
|
return run.apply(this, args)
|
|
@@ -559,11 +577,12 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
559
577
|
return onRunnableFinished.apply(this, arguments)
|
|
560
578
|
}
|
|
561
579
|
}
|
|
562
|
-
} else if (libraryConfig?.isFlakyTestRetriesEnabled) {
|
|
580
|
+
} else if (libraryConfig?.isFlakyTestRetriesEnabled && !nativeRetryTests.has(test._retriedTest || test)) {
|
|
563
581
|
if (!originalRetriesByTest.has(test)) {
|
|
564
582
|
const originalRetries = originalRetriesByTest.get(test._retriedTest) ?? test.retries()
|
|
565
583
|
originalRetriesByTest.set(test, originalRetries)
|
|
566
584
|
}
|
|
585
|
+
automaticRetryTests.delete(test)
|
|
567
586
|
if (libraryConfig.isDynamicAtrEnabled) {
|
|
568
587
|
// Dynamic ATR: set the max possible retries initially.
|
|
569
588
|
// The actual duration-based count is computed after the first attempt.
|
|
@@ -581,6 +600,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
581
600
|
} else {
|
|
582
601
|
test.retries(libraryConfig.flakyTestRetriesCount)
|
|
583
602
|
}
|
|
603
|
+
automaticRetryTests.add(test)
|
|
584
604
|
}
|
|
585
605
|
|
|
586
606
|
if (isTestHook || this.type === 'test') {
|
|
@@ -833,6 +853,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
833
853
|
|
|
834
854
|
const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
|
|
835
855
|
const isAtrRetry = config.isFlakyTestRetriesEnabled &&
|
|
856
|
+
automaticRetryAttempts.get(test._retriedTest || test)?.has(test._currentRetry) &&
|
|
836
857
|
!test._ddIsAttemptToFix &&
|
|
837
858
|
!test._ddIsEfdRetry
|
|
838
859
|
const isFinalAttempt = isTerminalHookFailure || status !== 'fail' || test._currentRetry >= test._retries
|
|
@@ -1178,9 +1199,20 @@ function getOnFailHandler (isMain, config) {
|
|
|
1178
1199
|
|
|
1179
1200
|
function getOnTestRetryHandler (config) {
|
|
1180
1201
|
return function (test, err) {
|
|
1202
|
+
if (automaticRetryTests.has(test)) {
|
|
1203
|
+
// Mocha has already queued the clone; later budget changes cannot change its scheduling owner.
|
|
1204
|
+
const originalTest = test._retriedTest || test
|
|
1205
|
+
let attempts = automaticRetryAttempts.get(originalTest)
|
|
1206
|
+
if (!attempts) {
|
|
1207
|
+
attempts = new Set()
|
|
1208
|
+
automaticRetryAttempts.set(originalTest, attempts)
|
|
1209
|
+
}
|
|
1210
|
+
attempts.add(test._currentRetry + 1)
|
|
1211
|
+
}
|
|
1181
1212
|
const isFirstAttempt = test._currentRetry === 0
|
|
1182
1213
|
const isDynamicAtrTest = config.isDynamicAtrEnabled &&
|
|
1183
1214
|
config.isFlakyTestRetriesEnabled &&
|
|
1215
|
+
automaticRetryTests.has(test) &&
|
|
1184
1216
|
!test._ddIsAttemptToFix &&
|
|
1185
1217
|
!isEarlyFlakeDetectionTest(test, config)
|
|
1186
1218
|
if (isDynamicAtrTest && isFirstAttempt) {
|
|
@@ -1217,6 +1249,7 @@ function publishTestRetry (test, err, config) {
|
|
|
1217
1249
|
const willBeRetried = test._currentRetry < test._retries
|
|
1218
1250
|
const isAtrRetry = !isFirstAttempt &&
|
|
1219
1251
|
config.isFlakyTestRetriesEnabled &&
|
|
1252
|
+
automaticRetryAttempts.get(test._retriedTest || test)?.has(test._currentRetry) &&
|
|
1220
1253
|
!test._ddIsAttemptToFix &&
|
|
1221
1254
|
!test._ddIsEfdRetry
|
|
1222
1255
|
const promises = {}
|
|
@@ -33,7 +33,7 @@ class Turn {
|
|
|
33
33
|
textEnd
|
|
34
34
|
completionId
|
|
35
35
|
metrics = { input_tokens: 0, output_tokens: 0, total_tokens: 0 }
|
|
36
|
-
|
|
36
|
+
missingModalityMetrics = new Set()
|
|
37
37
|
emitted = false
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -86,7 +86,9 @@ class SonicSession {
|
|
|
86
86
|
#sessionId = randomUUID()
|
|
87
87
|
#providerSessionId
|
|
88
88
|
#totals = { input_tokens: 0, output_tokens: 0 }
|
|
89
|
-
#
|
|
89
|
+
#modalityTotals = {
|
|
90
|
+
input_audio_tokens: 0, output_audio_tokens: 0, input_text_tokens: 0, output_text_tokens: 0,
|
|
91
|
+
}
|
|
90
92
|
|
|
91
93
|
/** @param {(descriptor: object) => void} emit */
|
|
92
94
|
constructor (emit) {
|
|
@@ -389,21 +391,28 @@ class SonicSession {
|
|
|
389
391
|
target.metrics[key] += delta
|
|
390
392
|
target.metrics.total_tokens += delta
|
|
391
393
|
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
394
|
+
// Each modality is a subset of the aggregate and has its own cumulative baseline.
|
|
395
|
+
// Missing text must not discard valid speech or charge a previous turn's gap.
|
|
396
|
+
let remaining = delta
|
|
397
|
+
for (const [modality, field] of [['audio', 'speechTokens'], ['text', 'textTokens']]) {
|
|
398
|
+
const modalityKey = `${direction}_${modality}_tokens`
|
|
399
|
+
const modalityTotal = data.details?.total?.[direction]?.[field]
|
|
400
|
+
const previous = this.#modalityTotals[modalityKey]
|
|
401
|
+
const valid = Number.isSafeInteger(modalityTotal) && modalityTotal >= 0 && modalityTotal <= total &&
|
|
402
|
+
(previous === undefined || modalityTotal >= previous)
|
|
403
|
+
const modalityDelta = valid && previous !== undefined ? modalityTotal - previous : undefined
|
|
404
|
+
this.#modalityTotals[modalityKey] = valid ? modalityTotal : delta ? undefined : previous
|
|
405
|
+
if (modalityDelta === undefined || modalityDelta > remaining) {
|
|
406
|
+
if (delta || modalityDelta > remaining) {
|
|
407
|
+
target.missingModalityMetrics.add(modalityKey)
|
|
408
|
+
delete target.metrics[modalityKey]
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
remaining -= modalityDelta
|
|
412
|
+
if (!target.missingModalityMetrics.has(modalityKey)) {
|
|
413
|
+
target.metrics[modalityKey] = (target.metrics[modalityKey] ?? 0) + modalityDelta
|
|
414
|
+
}
|
|
404
415
|
}
|
|
405
|
-
} else if (!target.missingAudioMetrics.has(audioKey)) {
|
|
406
|
-
target.metrics[audioKey] = (target.metrics[audioKey] ?? 0) + audioDelta
|
|
407
416
|
}
|
|
408
417
|
}
|
|
409
418
|
}
|
|
@@ -1077,6 +1077,7 @@ function testEndHandler ({
|
|
|
1077
1077
|
: undefined
|
|
1078
1078
|
const isAtrRetry = testResult?.retry > 0 &&
|
|
1079
1079
|
isFlakyTestRetriesEnabled &&
|
|
1080
|
+
hasAutomaticRetries(test, projects) &&
|
|
1080
1081
|
!test._ddIsAttemptToFix &&
|
|
1081
1082
|
!test._ddIsEfdRetry
|
|
1082
1083
|
|
|
@@ -1340,6 +1341,7 @@ function onDispatcherCreateWorker (dispatcher, worker) {
|
|
|
1340
1341
|
}
|
|
1341
1342
|
const isAtrRetry = testResult?.retry > 0 &&
|
|
1342
1343
|
isFlakyTestRetriesEnabled &&
|
|
1344
|
+
hasAutomaticRetries(test, projects) &&
|
|
1343
1345
|
!test._ddIsAttemptToFix &&
|
|
1344
1346
|
!test._ddIsEfdRetry
|
|
1345
1347
|
|
|
@@ -58,19 +58,30 @@ function wrapConstructEventAsync (constructEventAsync) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
const wrappedMethods = new WeakSet()
|
|
62
|
+
|
|
63
|
+
// Stripe >=9 <11.10 and >=18 share `webhooks` across clients. Stripe >=11.10 <18
|
|
64
|
+
// creates it per client. checkout.sessions and paymentIntents are separate for each client.
|
|
65
|
+
// Rewrapping shared webhook methods adds one layer per client, causing duplicate
|
|
66
|
+
// publications and eventually a stack overflow. Track installed functions so replacements
|
|
67
|
+
// still get wrapped and restored wrappers are reused.
|
|
68
|
+
/**
|
|
69
|
+
* @param {object|undefined} target
|
|
70
|
+
* @param {string} name
|
|
71
|
+
* @param {Function} wrapper
|
|
72
|
+
*/
|
|
73
|
+
function wrapOnce (target, name, wrapper) {
|
|
74
|
+
if (typeof target?.[name] !== 'function' || wrappedMethods.has(target[name])) return
|
|
75
|
+
|
|
76
|
+
shimmer.wrap(target, name, wrapper)
|
|
77
|
+
wrappedMethods.add(target[name])
|
|
78
|
+
}
|
|
79
|
+
|
|
61
80
|
function instrumentStripeInstance (stripe) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
shimmer.wrap(stripe.paymentIntents, 'create', wrapPaymentIntentCreate)
|
|
67
|
-
}
|
|
68
|
-
if (typeof stripe.webhooks?.constructEvent === 'function') {
|
|
69
|
-
shimmer.wrap(stripe.webhooks, 'constructEvent', wrapConstructEvent)
|
|
70
|
-
}
|
|
71
|
-
if (typeof stripe.webhooks?.constructEventAsync === 'function') {
|
|
72
|
-
shimmer.wrap(stripe.webhooks, 'constructEventAsync', wrapConstructEventAsync)
|
|
73
|
-
}
|
|
81
|
+
wrapOnce(stripe.checkout?.sessions, 'create', wrapSessionCreate)
|
|
82
|
+
wrapOnce(stripe.paymentIntents, 'create', wrapPaymentIntentCreate)
|
|
83
|
+
wrapOnce(stripe.webhooks, 'constructEvent', wrapConstructEvent)
|
|
84
|
+
wrapOnce(stripe.webhooks, 'constructEventAsync', wrapConstructEventAsync)
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
// stripe <22: the constructor mutates this (when invoked with 'new') and
|
|
@@ -934,6 +934,7 @@ function createMainProcessReporter (reporterState) {
|
|
|
934
934
|
isDynamicAtrEnabled: state.isDynamicAtrEnabled,
|
|
935
935
|
flakyTestRetriesIncludesUnnamedProject: flakyTestRetriesConfiguration.includesUnnamedProject,
|
|
936
936
|
flakyTestRetriesProjectNames: flakyTestRetriesConfiguration.projectNames,
|
|
937
|
+
flakyTestRetriesUseRetryMarker: flakyTestRetriesConfiguration.usesRetryMarker,
|
|
937
938
|
}, task)
|
|
938
939
|
|
|
939
940
|
return {
|
|
@@ -680,6 +680,7 @@ function resetMainProcessProvidedContext (ctx) {
|
|
|
680
680
|
_ddIsEfdSuiteAdmissionEnabled: false,
|
|
681
681
|
_ddIsFlakyTestRetriesEnabled: false,
|
|
682
682
|
_ddFlakyTestRetriesCount: 0,
|
|
683
|
+
_ddFlakyTestRetriesUseRetryMarker: false,
|
|
683
684
|
_ddIsDynamicAtrEnabled: false,
|
|
684
685
|
_ddDynamicAtrBuckets: undefined,
|
|
685
686
|
_ddFlakyTestRetriesIncludesUnnamedProject: false,
|
|
@@ -823,7 +824,7 @@ async function runMainProcessSetup (
|
|
|
823
824
|
}
|
|
824
825
|
}
|
|
825
826
|
|
|
826
|
-
const flakyTestRetriesConfiguration = configureFlakyTestRetries(ctx, testSpecifications)
|
|
827
|
+
const flakyTestRetriesConfiguration = configureFlakyTestRetries(ctx, testSpecifications, frameworkVersion)
|
|
827
828
|
if (flakyTestRetriesConfiguration) {
|
|
828
829
|
setProvidedContext(ctx, {
|
|
829
830
|
_ddIsFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled,
|
|
@@ -832,6 +833,7 @@ async function runMainProcessSetup (
|
|
|
832
833
|
_ddDynamicAtrBuckets: dynamicAtrBuckets,
|
|
833
834
|
_ddFlakyTestRetriesIncludesUnnamedProject: flakyTestRetriesConfiguration.includesUnnamedProject,
|
|
834
835
|
_ddFlakyTestRetriesProjectNames: flakyTestRetriesConfiguration.projectNames,
|
|
836
|
+
_ddFlakyTestRetriesUseRetryMarker: flakyTestRetriesConfiguration.usesRetryMarker,
|
|
835
837
|
}, 'Could not send library configuration to workers.')
|
|
836
838
|
}
|
|
837
839
|
|
|
@@ -1038,7 +1040,7 @@ function shouldUseBrowserReporter (frameworkVersion, testSpecifications) {
|
|
|
1038
1040
|
testSpecifications.some(isBrowserTestSpecification)
|
|
1039
1041
|
}
|
|
1040
1042
|
|
|
1041
|
-
function configureFlakyTestRetries (ctx, testSpecifications) {
|
|
1043
|
+
function configureFlakyTestRetries (ctx, testSpecifications, frameworkVersion) {
|
|
1042
1044
|
if (!isFlakyTestRetriesEnabled || (!isDynamicAtrEnabled && flakyTestRetriesCount <= 0)) return
|
|
1043
1045
|
|
|
1044
1046
|
const maximumDynamicAtrRetries = dynamicAtrBuckets
|
|
@@ -1047,13 +1049,14 @@ function configureFlakyTestRetries (ctx, testSpecifications) {
|
|
|
1047
1049
|
const retryCount = isDynamicAtrEnabled
|
|
1048
1050
|
? Math.max(1, maximumDynamicAtrRetries)
|
|
1049
1051
|
: flakyTestRetriesCount
|
|
1052
|
+
const usesRetryMarker = isDynamicAtrEnabled || satisfies(frameworkVersion, '>=4.1.0')
|
|
1050
1053
|
let configured = false
|
|
1051
1054
|
let includesUnnamedProject = false
|
|
1052
1055
|
const projectNames = []
|
|
1053
1056
|
for (const { config, projectName } of getVitestProjectConfigs(ctx, testSpecifications)) {
|
|
1054
1057
|
if (!config.retry || config.retry.__ddTestOptAtr) {
|
|
1055
1058
|
// The serializable marker survives task inheritance and setup refreshes, unlike numeric retry counts.
|
|
1056
|
-
config.retry =
|
|
1059
|
+
config.retry = usesRetryMarker ? { count: retryCount, __ddTestOptAtr: true } : retryCount
|
|
1057
1060
|
configured = true
|
|
1058
1061
|
if (projectName) {
|
|
1059
1062
|
projectNames.push(projectName)
|
|
@@ -1068,6 +1071,7 @@ function configureFlakyTestRetries (ctx, testSpecifications) {
|
|
|
1068
1071
|
return {
|
|
1069
1072
|
includesUnnamedProject,
|
|
1070
1073
|
projectNames,
|
|
1074
|
+
usesRetryMarker,
|
|
1071
1075
|
}
|
|
1072
1076
|
}
|
|
1073
1077
|
|
|
@@ -182,6 +182,7 @@ function getProvidedContext () {
|
|
|
182
182
|
_ddDynamicAtrBuckets: dynamicAtrBuckets,
|
|
183
183
|
_ddFlakyTestRetriesIncludesUnnamedProject: flakyTestRetriesIncludesUnnamedProject,
|
|
184
184
|
_ddFlakyTestRetriesProjectNames: flakyTestRetriesProjectNames,
|
|
185
|
+
_ddFlakyTestRetriesUseRetryMarker: flakyTestRetriesUseRetryMarker,
|
|
185
186
|
_ddIsImpactedTestsEnabled: isImpactedTestsEnabled,
|
|
186
187
|
_ddTestSessionId: testSessionId,
|
|
187
188
|
_ddTestModuleId: testModuleId,
|
|
@@ -211,6 +212,7 @@ function getProvidedContext () {
|
|
|
211
212
|
dynamicAtrBuckets,
|
|
212
213
|
flakyTestRetriesIncludesUnnamedProject,
|
|
213
214
|
flakyTestRetriesProjectNames,
|
|
215
|
+
flakyTestRetriesUseRetryMarker,
|
|
214
216
|
isImpactedTestsEnabled,
|
|
215
217
|
testSessionId,
|
|
216
218
|
testModuleId,
|
|
@@ -254,8 +256,9 @@ function getProvidedContext () {
|
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
function isFlakyTestRetriesEnabledForTask (providedContext, task) {
|
|
257
|
-
if (!providedContext.isFlakyTestRetriesEnabled) return false
|
|
258
|
-
if (providedContext.isDynamicAtrEnabled
|
|
259
|
+
if (!providedContext.isFlakyTestRetriesEnabled || task.meta?.__ddTestOptNativeRetry) return false
|
|
260
|
+
if ((providedContext.isDynamicAtrEnabled || providedContext.flakyTestRetriesUseRetryMarker) &&
|
|
261
|
+
!task.retry?.__ddTestOptAtr) return false
|
|
259
262
|
|
|
260
263
|
const { flakyTestRetriesProjectNames } = providedContext
|
|
261
264
|
if (!Array.isArray(flakyTestRetriesProjectNames)) return true
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
|
|
4
4
|
const { isTrue } = require('../../dd-trace/src/util')
|
|
5
|
+
const { channel } = require('./helpers/instrument')
|
|
6
|
+
|
|
7
|
+
channel('tracing:orchestrion:@vitest/runner:createTestTask:end').subscribe(({ arguments: args, result: task }) => {
|
|
8
|
+
// Suite options have already been merged here, including overrides equal to ATR's count.
|
|
9
|
+
if (args[1]?.retry !== undefined && task?.meta) {
|
|
10
|
+
task.meta = { ...task.meta, __ddTestOptNativeRetry: true }
|
|
11
|
+
}
|
|
12
|
+
})
|
|
5
13
|
|
|
6
14
|
if (getEnvironmentVariable('DD_VITEST_WORKER') === '1') {
|
|
7
15
|
const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
|
|
@@ -1664,6 +1664,7 @@ class CypressPlugin {
|
|
|
1664
1664
|
cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state]
|
|
1665
1665
|
const isAtrRetry = attemptIndex > 0 &&
|
|
1666
1666
|
this.isFlakyTestRetriesEnabled &&
|
|
1667
|
+
(!finishedTest.hasNativeRetries || this.isDynamicAtrEnabled) &&
|
|
1667
1668
|
!finishedTest.isAttemptToFix &&
|
|
1668
1669
|
!finishedTest.isEfdRetry
|
|
1669
1670
|
if (attemptIndex > 0) {
|
|
@@ -2154,6 +2155,7 @@ class CypressPlugin {
|
|
|
2154
2155
|
isEfdRetry,
|
|
2155
2156
|
isAttemptToFix,
|
|
2156
2157
|
isModified,
|
|
2158
|
+
hasNativeRetries,
|
|
2157
2159
|
duration,
|
|
2158
2160
|
isQuarantined: isQuarantinedFromSupport,
|
|
2159
2161
|
isDisabled: isDisabledFromSupport,
|
|
@@ -2354,6 +2356,7 @@ class CypressPlugin {
|
|
|
2354
2356
|
isEfdRetry,
|
|
2355
2357
|
isEfdManagedTest,
|
|
2356
2358
|
isAttemptToFix,
|
|
2359
|
+
hasNativeRetries,
|
|
2357
2360
|
}
|
|
2358
2361
|
if (this.finishedTestsByFile[testSuite]) {
|
|
2359
2362
|
this.finishedTestsByFile[testSuite].push(finishedTest)
|
|
@@ -523,6 +523,8 @@ afterEach(function () {
|
|
|
523
523
|
isEfdRetry: currentTest._ddIsEfdRetry,
|
|
524
524
|
isAttemptToFix: currentTest._ddIsAttemptToFix,
|
|
525
525
|
isModified: currentTest._ddIsModified,
|
|
526
|
+
hasNativeRetries: !!(currentTest._testConfig && currentTest._testConfig.unverifiedTestConfig &&
|
|
527
|
+
currentTest._testConfig.unverifiedTestConfig.retries !== undefined),
|
|
526
528
|
duration: currentTest.duration,
|
|
527
529
|
// Mark suppressed tests so the plugin can tag them with the correct test management reason.
|
|
528
530
|
isQuarantined: isTestManagementTestThatFailed && suppressedTestFailure.isQuarantined,
|