dd-trace 5.118.0 → 5.119.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/test-optimization-validation/command-output-policy.js +5 -5
- package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
- package/ci/test-optimization-validation/generated-files.js +9 -9
- package/ci/test-optimization-validation/generated-test-contract.js +1 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +1 -2
- package/ci/test-optimization-validation/offline-output.js +6 -6
- package/ci/test-optimization-validation/runner-command.js +1 -1
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +1 -1
- package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
- package/ci/vitest-no-worker-init-setup.mjs +348 -51
- package/index.d.ts +77 -13
- package/package.json +9 -3
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +9 -6
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest.js +8 -14
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
- package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +3 -2
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
- package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
- package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-jest/src/index.js +2 -3
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- package/packages/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +84 -11
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/index.js +15 -0
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +5 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +4 -11
- package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
- package/packages/dd-trace/src/llmobs/tagger.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +105 -20
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +7 -6
- package/packages/dd-trace/src/plugins/util/git.js +1 -1
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +38 -12
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/ritm.js +5 -20
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { relative } from 'node:path'
|
|
3
|
-
import { performance } from 'node:perf_hooks'
|
|
1
|
+
import { afterEach, beforeAll, beforeEach, inject } from 'vitest'
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// Instrumentation-less worker setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
|
|
8
|
-
// It applies Test Optimization execution changes without initializing dd-trace in Vitest workers.
|
|
3
|
+
// Instrumentation-less setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
|
|
4
|
+
// It applies Test Optimization execution changes without initializing dd-trace and also supports Browser Mode.
|
|
9
5
|
const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
|
|
10
|
-
const
|
|
11
|
-
const
|
|
6
|
+
const providedContext = getProvidedContext()
|
|
7
|
+
const isNoWorkerInitActive = providedContext.isActive ?? getIsNoWorkerInitActive()
|
|
12
8
|
const attemptToFixTests = providedContext.attemptToFixTests || {}
|
|
13
9
|
const attemptToFixRetries = providedContext.attemptToFixRetries || 0
|
|
14
10
|
const disabledTests = providedContext.disabledTests || {}
|
|
@@ -22,7 +18,9 @@ const isEarlyFlakeDetectionEnabled = providedContext.isEarlyFlakeDetectionEnable
|
|
|
22
18
|
const knownTests = providedContext.knownTests || {}
|
|
23
19
|
const modifiedFiles = providedContext.modifiedFiles || {}
|
|
24
20
|
const quarantinedTests = providedContext.quarantinedTests || {}
|
|
25
|
-
const
|
|
21
|
+
const isRumCorrelationEnabled = providedContext.isRumCorrelationEnabled !== false
|
|
22
|
+
const rumTestExecutionIdCookieName = providedContext.rumTestExecutionIdCookieName
|
|
23
|
+
const testPropertiesByFilepath = providedContext.testPropertiesByFilepath || {}
|
|
26
24
|
let setVitestTaskFn
|
|
27
25
|
if (isNoWorkerInitActive) {
|
|
28
26
|
try {
|
|
@@ -35,7 +33,21 @@ const earlyFlakeDetectionRetriesByTask = new WeakMap()
|
|
|
35
33
|
const earlyFlakeDetectionSkippedResults = new WeakMap()
|
|
36
34
|
const earlyFlakeDetectionStartByTask = new WeakMap()
|
|
37
35
|
const nextAttemptIndexByTask = new WeakMap()
|
|
38
|
-
const
|
|
36
|
+
const retryAttemptIndexByTask = new WeakMap()
|
|
37
|
+
const usedRumTestExecutionIds = new Set()
|
|
38
|
+
let now
|
|
39
|
+
let timeOrigin
|
|
40
|
+
// Use an unfaked monotonic clock in Node and Vitest's parent frame in Browser Mode.
|
|
41
|
+
if (typeof globalThis.process?.uptime === 'function') {
|
|
42
|
+
now = () => globalThis.process.uptime() * 1000
|
|
43
|
+
timeOrigin = Date.now() - now()
|
|
44
|
+
} else {
|
|
45
|
+
const clock = globalThis.window?.parent && globalThis.window.parent !== globalThis.window
|
|
46
|
+
? globalThis.window.parent.performance
|
|
47
|
+
: globalThis.performance
|
|
48
|
+
now = clock ? clock.now.bind(clock) : Date.now
|
|
49
|
+
timeOrigin = Number.isFinite(clock?.timeOrigin) ? clock.timeOrigin : Date.now() - now()
|
|
50
|
+
}
|
|
39
51
|
|
|
40
52
|
if (isNoWorkerInitActive) {
|
|
41
53
|
// eslint-disable-next-line no-empty-pattern
|
|
@@ -51,11 +63,12 @@ if (isNoWorkerInitActive) {
|
|
|
51
63
|
const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTest(testSuite, testName)
|
|
52
64
|
const isQuarantinedTest = quarantinedTests[testSuite]?.[testName] && !isAttemptToFixTest
|
|
53
65
|
const attemptIndex = getNextAttemptIndex(task)
|
|
66
|
+
const attemptStart = now()
|
|
54
67
|
if (attemptIndex > 0) {
|
|
55
68
|
recordTestOptimizationStatus(task, attemptIndex - 1)
|
|
56
69
|
}
|
|
57
70
|
|
|
58
|
-
if (disabledTests[testSuite]?.[testName]
|
|
71
|
+
if (!isAttemptToFixTest && disabledTests[testSuite]?.[testName]) {
|
|
59
72
|
skip('Skipped by Datadog Test Optimization')
|
|
60
73
|
} else if (isAttemptToFixTest && attemptIndex > 0) {
|
|
61
74
|
task.result.state = 'run'
|
|
@@ -66,27 +79,33 @@ if (isNoWorkerInitActive) {
|
|
|
66
79
|
}
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
prepareRumCorrelation(task, attemptIndex)
|
|
83
|
+
|
|
84
|
+
onTestFinished(() => {
|
|
85
|
+
recordTestAttemptTiming(task, attemptIndex, attemptStart)
|
|
86
|
+
recordRetryErrorCount(task)
|
|
87
|
+
if (
|
|
88
|
+
(isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) &&
|
|
89
|
+
attemptIndex === getFinalAttemptIndex(task)
|
|
90
|
+
) {
|
|
91
|
+
if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) {
|
|
92
|
+
recordTestOptimizationStatus(task, attemptIndex, true)
|
|
76
93
|
}
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
switchQuarantinedFinalFailure(task, attemptIndex)
|
|
95
|
+
}
|
|
96
|
+
finishRumCorrelation(task, attemptIndex)
|
|
97
|
+
})
|
|
79
98
|
})
|
|
80
99
|
|
|
81
100
|
afterEach(function ({ task }) {
|
|
82
101
|
const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
if (attemptIndex === getFinalAttemptIndex(task)) {
|
|
102
|
+
const restoredEarlyFlakeDetectionResult = restoreEarlyFlakeDetectionSkippedResult(task)
|
|
103
|
+
if (!restoredEarlyFlakeDetectionResult && attemptIndex === getFinalAttemptIndex(task)) {
|
|
87
104
|
recordTestOptimizationStatus(task, attemptIndex)
|
|
88
105
|
}
|
|
89
|
-
|
|
106
|
+
if (!restoredEarlyFlakeDetectionResult) {
|
|
107
|
+
switchQuarantinedFinalFailure(task, attemptIndex)
|
|
108
|
+
}
|
|
90
109
|
})
|
|
91
110
|
}
|
|
92
111
|
|
|
@@ -110,6 +129,7 @@ function applyExecutionChanges (suite) {
|
|
|
110
129
|
task.repeats = earlyFlakeDetectionRetries
|
|
111
130
|
task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
|
|
112
131
|
}
|
|
132
|
+
wrapRetryCondition(task)
|
|
113
133
|
}
|
|
114
134
|
}
|
|
115
135
|
|
|
@@ -117,9 +137,194 @@ function getNextAttemptIndex (task) {
|
|
|
117
137
|
const attemptIndex = nextAttemptIndexByTask.get(task) || 0
|
|
118
138
|
nextAttemptIndexByTask.set(task, attemptIndex + 1)
|
|
119
139
|
task.meta.__ddTestOptCurrentAttemptIndex = attemptIndex
|
|
140
|
+
|
|
141
|
+
const repeatCount = task.result?.repeatCount || 0
|
|
142
|
+
const retryAttempt = retryAttemptIndexByTask.get(task)
|
|
143
|
+
const retryAttemptIndex = retryAttempt?.repeatCount === repeatCount ? retryAttempt.index + 1 : 0
|
|
144
|
+
retryAttemptIndexByTask.set(task, {
|
|
145
|
+
index: retryAttemptIndex,
|
|
146
|
+
repeatCount,
|
|
147
|
+
})
|
|
148
|
+
|
|
120
149
|
return attemptIndex
|
|
121
150
|
}
|
|
122
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Sets the RUM correlation cookie for a sequential browser test attempt.
|
|
154
|
+
*
|
|
155
|
+
* @param {object} task
|
|
156
|
+
* @param {number} attemptIndex
|
|
157
|
+
* @returns {void}
|
|
158
|
+
*/
|
|
159
|
+
function prepareRumCorrelation (task, attemptIndex) {
|
|
160
|
+
// A per-origin cookie cannot identify overlapping attempts without racing.
|
|
161
|
+
if (!isRumCorrelationEnabled || isConcurrentTask(task)) return
|
|
162
|
+
|
|
163
|
+
if (
|
|
164
|
+
typeof window === 'undefined' ||
|
|
165
|
+
typeof document === 'undefined' ||
|
|
166
|
+
typeof rumTestExecutionIdCookieName !== 'string'
|
|
167
|
+
) {
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const testExecutionId = generateTestExecutionId()
|
|
172
|
+
if (
|
|
173
|
+
!testExecutionId ||
|
|
174
|
+
usedRumTestExecutionIds.has(testExecutionId) ||
|
|
175
|
+
!setRumCorrelationCookie(testExecutionId)
|
|
176
|
+
) {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
usedRumTestExecutionIds.add(testExecutionId)
|
|
181
|
+
task.meta.__ddTestOptRumTestExecutionIds ||= []
|
|
182
|
+
task.meta.__ddTestOptRumTestExecutionIds[attemptIndex] = testExecutionId
|
|
183
|
+
recordRumActivity(task, attemptIndex)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Records whether RUM was active and clears the attempt cookie.
|
|
188
|
+
*
|
|
189
|
+
* @param {object} task
|
|
190
|
+
* @param {number} attemptIndex
|
|
191
|
+
* @returns {void}
|
|
192
|
+
*/
|
|
193
|
+
function finishRumCorrelation (task, attemptIndex) {
|
|
194
|
+
const testExecutionId = task.meta.__ddTestOptRumTestExecutionIds?.[attemptIndex]
|
|
195
|
+
if (!testExecutionId) return
|
|
196
|
+
|
|
197
|
+
recordRumActivity(task, attemptIndex)
|
|
198
|
+
clearRumCorrelationCookie(testExecutionId)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Retains evidence that RUM was active at any point observed during the attempt.
|
|
203
|
+
*
|
|
204
|
+
* @param {object} task
|
|
205
|
+
* @param {number} attemptIndex
|
|
206
|
+
* @returns {void}
|
|
207
|
+
*/
|
|
208
|
+
function recordRumActivity (task, attemptIndex) {
|
|
209
|
+
if (!getIsRumActive(getRum())) return
|
|
210
|
+
|
|
211
|
+
task.meta.__ddTestOptRumActive ||= []
|
|
212
|
+
task.meta.__ddTestOptRumActive[attemptIndex] = true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Returns the RUM public API without letting application-defined accessors fail a test.
|
|
217
|
+
*
|
|
218
|
+
* @returns {object|undefined}
|
|
219
|
+
*/
|
|
220
|
+
function getRum () {
|
|
221
|
+
try {
|
|
222
|
+
return window.DD_RUM
|
|
223
|
+
} catch {}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Returns whether the current RUM session is active.
|
|
228
|
+
*
|
|
229
|
+
* @param {object|undefined} rum
|
|
230
|
+
* @returns {boolean}
|
|
231
|
+
*/
|
|
232
|
+
function getIsRumActive (rum) {
|
|
233
|
+
if (!rum) return false
|
|
234
|
+
if (typeof rum.getInternalContext !== 'function') return false
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
return !!rum.getInternalContext()
|
|
238
|
+
} catch {
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Returns whether a task or any containing suite is concurrent.
|
|
245
|
+
*
|
|
246
|
+
* @param {object} task
|
|
247
|
+
* @returns {boolean}
|
|
248
|
+
*/
|
|
249
|
+
function isConcurrentTask (task) {
|
|
250
|
+
let currentTask = task
|
|
251
|
+
while (currentTask) {
|
|
252
|
+
if (currentTask.concurrent === true) return true
|
|
253
|
+
currentTask = currentTask.suite
|
|
254
|
+
}
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Generates a positive 63-bit decimal test execution ID.
|
|
260
|
+
*
|
|
261
|
+
* @returns {string|undefined}
|
|
262
|
+
*/
|
|
263
|
+
function generateTestExecutionId () {
|
|
264
|
+
// This setup file executes in a browser even though lint validates it against the package's Node.js target.
|
|
265
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
266
|
+
if (typeof globalThis.crypto?.getRandomValues !== 'function') return
|
|
267
|
+
|
|
268
|
+
const words = new Uint32Array(2)
|
|
269
|
+
try {
|
|
270
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
271
|
+
globalThis.crypto.getRandomValues(words)
|
|
272
|
+
} catch {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
words[0] &= 0x7F_FF_FF_FF
|
|
277
|
+
if (words[0] === 0 && words[1] === 0) return
|
|
278
|
+
|
|
279
|
+
return ((BigInt(words[0]) << 32n) | BigInt(words[1])).toString(10)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Sets and verifies the RUM correlation cookie for the current origin.
|
|
284
|
+
*
|
|
285
|
+
* @param {string} testExecutionId
|
|
286
|
+
* @returns {boolean}
|
|
287
|
+
*/
|
|
288
|
+
function setRumCorrelationCookie (testExecutionId) {
|
|
289
|
+
try {
|
|
290
|
+
// eslint-disable-next-line unicorn/no-document-cookie
|
|
291
|
+
document.cookie = `${rumTestExecutionIdCookieName}=${testExecutionId}; path=/`
|
|
292
|
+
return getRumCorrelationCookie() === testExecutionId
|
|
293
|
+
} catch {
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Clears the RUM correlation cookie if it still belongs to this attempt.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} testExecutionId
|
|
302
|
+
* @returns {void}
|
|
303
|
+
*/
|
|
304
|
+
function clearRumCorrelationCookie (testExecutionId) {
|
|
305
|
+
try {
|
|
306
|
+
if (getRumCorrelationCookie() === testExecutionId) {
|
|
307
|
+
// eslint-disable-next-line unicorn/no-document-cookie
|
|
308
|
+
document.cookie = `${rumTestExecutionIdCookieName}=; path=/; max-age=0`
|
|
309
|
+
}
|
|
310
|
+
} catch {}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Returns the current RUM correlation cookie value.
|
|
315
|
+
*
|
|
316
|
+
* @returns {string|undefined}
|
|
317
|
+
*/
|
|
318
|
+
function getRumCorrelationCookie () {
|
|
319
|
+
const prefix = `${rumTestExecutionIdCookieName}=`
|
|
320
|
+
for (const cookie of document.cookie.split(';')) {
|
|
321
|
+
const normalizedCookie = cookie.trim()
|
|
322
|
+
if (normalizedCookie.startsWith(prefix)) {
|
|
323
|
+
return normalizedCookie.slice(prefix.length)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
123
328
|
function recordTestOptimizationStatus (task, attemptIndex = task.result?.repeatCount || 0, onlyIfNewErrors = false) {
|
|
124
329
|
const testSuite = getTestSuite(task)
|
|
125
330
|
const testName = getTestName(task)
|
|
@@ -206,6 +411,76 @@ function recordManualRepeatStatus (task, attemptIndex) {
|
|
|
206
411
|
task.meta.__ddTestOptRepeatErrorCounts[attemptIndex] = task.result?.errors?.length || 0
|
|
207
412
|
}
|
|
208
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Records cumulative errors at the end of each configured retry attempt.
|
|
416
|
+
*
|
|
417
|
+
* @param {object} task
|
|
418
|
+
* @returns {void}
|
|
419
|
+
*/
|
|
420
|
+
function recordRetryErrorCount (task) {
|
|
421
|
+
const retryLimit = getRetryLimit(task)
|
|
422
|
+
if (retryLimit <= 0) return
|
|
423
|
+
|
|
424
|
+
const retryCount = task.result?.retryCount || 0
|
|
425
|
+
task.meta.__ddTestOptRetryErrorCounts ||= []
|
|
426
|
+
task.meta.__ddTestOptRetryErrorCounts[retryCount] = task.result?.errors?.length || 0
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Records the wall-clock start and elapsed time for one retry or repeat execution.
|
|
431
|
+
*
|
|
432
|
+
* @param {object} task
|
|
433
|
+
* @param {number} attemptIndex
|
|
434
|
+
* @param {number} attemptStart
|
|
435
|
+
* @returns {void}
|
|
436
|
+
*/
|
|
437
|
+
function recordTestAttemptTiming (task, attemptIndex, attemptStart) {
|
|
438
|
+
task.meta.__ddTestOptAttemptStartTimes ||= []
|
|
439
|
+
task.meta.__ddTestOptAttemptDurations ||= []
|
|
440
|
+
task.meta.__ddTestOptAttemptStartTimes[attemptIndex] = timeOrigin + attemptStart
|
|
441
|
+
task.meta.__ddTestOptAttemptDurations[attemptIndex] = now() - attemptStart
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Returns the configured retry count for numeric and object-form retry options.
|
|
446
|
+
*
|
|
447
|
+
* @param {object} task
|
|
448
|
+
* @returns {number}
|
|
449
|
+
*/
|
|
450
|
+
function getRetryLimit (task) {
|
|
451
|
+
return typeof task.retry === 'number' ? task.retry : task.retry?.count || 0
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Wraps Vitest's retry condition so final-attempt handling follows the condition's actual result.
|
|
456
|
+
*
|
|
457
|
+
* @param {object} task
|
|
458
|
+
* @returns {void}
|
|
459
|
+
*/
|
|
460
|
+
function wrapRetryCondition (task) {
|
|
461
|
+
if (typeof task.retry !== 'object' || !task.retry?.condition || getRetryLimit(task) === 0) return
|
|
462
|
+
|
|
463
|
+
const condition = task.retry.condition
|
|
464
|
+
task.retry = {
|
|
465
|
+
...task.retry,
|
|
466
|
+
condition (error) {
|
|
467
|
+
let shouldRetry = false
|
|
468
|
+
if (condition instanceof RegExp) {
|
|
469
|
+
shouldRetry = condition.test(error?.message || '')
|
|
470
|
+
} else if (typeof condition === 'function') {
|
|
471
|
+
shouldRetry = condition(error)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (!shouldRetry && (task.result?.repeatCount || 0) >= (task.repeats || 0)) {
|
|
475
|
+
const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
|
|
476
|
+
recordTestOptimizationStatus(task, attemptIndex)
|
|
477
|
+
markQuarantinedFailure(task)
|
|
478
|
+
}
|
|
479
|
+
return shouldRetry
|
|
480
|
+
},
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
209
484
|
function restoreEarlyFlakeDetectionSkippedResult (task) {
|
|
210
485
|
if (!task.meta.__ddTestOptEfdSkipCurrentAttempt) {
|
|
211
486
|
return false
|
|
@@ -236,7 +511,18 @@ function getFinalAttemptIndex (task) {
|
|
|
236
511
|
if (isEarlyFlakeDetectionTest(testSuite, testName)) {
|
|
237
512
|
return getEarlyFlakeDetectionRetryCountForTask(task)
|
|
238
513
|
}
|
|
239
|
-
|
|
514
|
+
|
|
515
|
+
const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
|
|
516
|
+
const repeatCount = task.result?.repeatCount || 0
|
|
517
|
+
const repeatLimit = task.repeats || 0
|
|
518
|
+
const retryAttemptIndex = retryAttemptIndexByTask.get(task)?.index || 0
|
|
519
|
+
const retryLimit = getRetryLimit(task)
|
|
520
|
+
const retriesRemaining = task.result?.state === 'fail' || task.result?.state === 'run'
|
|
521
|
+
? Math.max(retryLimit - retryAttemptIndex, 0)
|
|
522
|
+
: 0
|
|
523
|
+
const repeatsRemaining = Math.max(repeatLimit - repeatCount, 0)
|
|
524
|
+
|
|
525
|
+
return attemptIndex + retriesRemaining + (repeatsRemaining * (retryLimit + 1))
|
|
240
526
|
}
|
|
241
527
|
|
|
242
528
|
function switchQuarantinedFinalFailure (task, attemptIndex) {
|
|
@@ -250,9 +536,27 @@ function switchQuarantinedFinalFailure (task, attemptIndex) {
|
|
|
250
536
|
return
|
|
251
537
|
}
|
|
252
538
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
539
|
+
if (attemptIndex < getFinalAttemptIndex(task)) {
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
markQuarantinedFailure(task)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Converts a quarantined failure into the passing runner state expected by Vitest.
|
|
548
|
+
*
|
|
549
|
+
* @param {object} task
|
|
550
|
+
* @returns {void}
|
|
551
|
+
*/
|
|
552
|
+
function markQuarantinedFailure (task) {
|
|
553
|
+
const testSuite = getTestSuite(task)
|
|
554
|
+
const testName = getTestName(task)
|
|
555
|
+
if (
|
|
556
|
+
!quarantinedTests[testSuite]?.[testName] ||
|
|
557
|
+
attemptToFixTests[testSuite]?.[testName] ||
|
|
558
|
+
task.result?.state !== 'fail'
|
|
559
|
+
) {
|
|
256
560
|
return
|
|
257
561
|
}
|
|
258
562
|
|
|
@@ -292,7 +596,7 @@ function getPreviousErrorCount (errorCounts, repeatCount) {
|
|
|
292
596
|
|
|
293
597
|
function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
|
|
294
598
|
if (attemptIndex === 0) {
|
|
295
|
-
earlyFlakeDetectionStartByTask.set(task,
|
|
599
|
+
earlyFlakeDetectionStartByTask.set(task, now())
|
|
296
600
|
return false
|
|
297
601
|
}
|
|
298
602
|
|
|
@@ -308,12 +612,12 @@ function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
|
|
|
308
612
|
}
|
|
309
613
|
|
|
310
614
|
if (attemptIndex <= retryCount) {
|
|
311
|
-
earlyFlakeDetectionStartByTask.set(task,
|
|
615
|
+
earlyFlakeDetectionStartByTask.set(task, now())
|
|
312
616
|
return false
|
|
313
617
|
}
|
|
314
618
|
|
|
315
619
|
if (!canReplaceVitestTaskFn()) {
|
|
316
|
-
earlyFlakeDetectionStartByTask.set(task,
|
|
620
|
+
earlyFlakeDetectionStartByTask.set(task, now())
|
|
317
621
|
return false
|
|
318
622
|
}
|
|
319
623
|
|
|
@@ -334,7 +638,7 @@ function getEarlyFlakeDetectionRetryCount (task) {
|
|
|
334
638
|
}
|
|
335
639
|
|
|
336
640
|
const executionStart = earlyFlakeDetectionStartByTask.get(task)
|
|
337
|
-
const duration = executionStart === undefined ? task.result?.duration ?? 0 :
|
|
641
|
+
const duration = executionStart === undefined ? task.result?.duration ?? 0 : now() - executionStart
|
|
338
642
|
for (const { key, limitMs } of earlyFlakeDetectionRetryThresholds) {
|
|
339
643
|
if (duration < limitMs) {
|
|
340
644
|
return earlyFlakeDetectionSlowRetries[key] ?? 0
|
|
@@ -376,21 +680,10 @@ function isModifiedTest (testSuite) {
|
|
|
376
680
|
}
|
|
377
681
|
|
|
378
682
|
function getTestSuite (task) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return normalizePath(relative(repositoryRoot, filepath))
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function realpath (filepath) {
|
|
389
|
-
try {
|
|
390
|
-
return realpathSync(filepath)
|
|
391
|
-
} catch {
|
|
392
|
-
return filepath
|
|
393
|
-
}
|
|
683
|
+
const filepath = task.file.filepath
|
|
684
|
+
return testPropertiesByFilepath[filepath]?.testSuite ||
|
|
685
|
+
testPropertiesByFilepath[normalizePath(filepath)]?.testSuite ||
|
|
686
|
+
normalizePath(filepath)
|
|
394
687
|
}
|
|
395
688
|
|
|
396
689
|
function getTestName (task) {
|
|
@@ -423,8 +716,12 @@ function getIsNoWorkerInitActive () {
|
|
|
423
716
|
|
|
424
717
|
function getProvidedContext () {
|
|
425
718
|
try {
|
|
426
|
-
return
|
|
719
|
+
return inject('_ddVitestWorkerSetup') || {}
|
|
427
720
|
} catch {
|
|
428
|
-
|
|
721
|
+
try {
|
|
722
|
+
return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
|
|
723
|
+
} catch {
|
|
724
|
+
return {}
|
|
725
|
+
}
|
|
429
726
|
}
|
|
430
727
|
}
|
package/index.d.ts
CHANGED
|
@@ -3931,29 +3931,74 @@ declare namespace tracer {
|
|
|
3931
3931
|
flush (): void
|
|
3932
3932
|
}
|
|
3933
3933
|
|
|
3934
|
+
/** JSON-serializable value accepted by LLMObs Experiments. */
|
|
3935
|
+
type JSONType = string | number | boolean | null | JSONType[] | { [key: string]: JSONType }
|
|
3936
|
+
|
|
3934
3937
|
/**
|
|
3935
3938
|
* A task run over each dataset record during an experiment.
|
|
3936
3939
|
*/
|
|
3937
|
-
type ExperimentTask = (
|
|
3940
|
+
type ExperimentTask = (
|
|
3941
|
+
input: JSONType,
|
|
3942
|
+
config: Record<string, JSONType>,
|
|
3943
|
+
metadata?: Record<string, JSONType>
|
|
3944
|
+
) => JSONType | Promise<JSONType>
|
|
3938
3945
|
|
|
3939
3946
|
/**
|
|
3940
3947
|
* Scores a single task output. The return type selects the metric:
|
|
3941
|
-
* `boolean` -> boolean, `number` -> score, anything else ->
|
|
3948
|
+
* `boolean` -> boolean, `number` -> score, `string` -> categorical, anything else -> json.
|
|
3949
|
+
*/
|
|
3950
|
+
type ExperimentEvaluator = (
|
|
3951
|
+
input: JSONType,
|
|
3952
|
+
output: JSONType,
|
|
3953
|
+
expectedOutput: JSONType
|
|
3954
|
+
) => JSONType | Promise<JSONType>
|
|
3955
|
+
|
|
3956
|
+
/**
|
|
3957
|
+
* Scores all rows in an experiment run and emits a summary metric.
|
|
3942
3958
|
*/
|
|
3943
|
-
type
|
|
3959
|
+
type ExperimentSummaryEvaluator = (
|
|
3960
|
+
inputs: any[],
|
|
3961
|
+
outputs: any[],
|
|
3962
|
+
expectedOutputs: any[],
|
|
3963
|
+
evaluatorResults: Record<string, any[]>,
|
|
3964
|
+
metadata?: Array<Record<string, any>>
|
|
3965
|
+
) => any | Promise<any>
|
|
3966
|
+
|
|
3967
|
+
interface CreateDatasetOptions {
|
|
3968
|
+
description?: string
|
|
3969
|
+
records?: Array<{
|
|
3970
|
+
id?: string,
|
|
3971
|
+
inputData: JSONType,
|
|
3972
|
+
expectedOutput?: JSONType,
|
|
3973
|
+
metadata?: Record<string, JSONType>
|
|
3974
|
+
}>
|
|
3975
|
+
}
|
|
3944
3976
|
|
|
3945
3977
|
interface ExperimentOptions {
|
|
3946
3978
|
name: string
|
|
3947
3979
|
dataset: Dataset
|
|
3948
3980
|
task: ExperimentTask
|
|
3949
|
-
/** Evaluators keyed by metric label. */
|
|
3950
|
-
evaluators?: Record<string, ExperimentEvaluator>
|
|
3981
|
+
/** Evaluators keyed by metric label, or named functions. */
|
|
3982
|
+
evaluators?: Record<string, ExperimentEvaluator> | ExperimentEvaluator[]
|
|
3983
|
+
/** Summary evaluators keyed by metric label, or named functions. */
|
|
3984
|
+
summaryEvaluators?: Record<string, ExperimentSummaryEvaluator> | ExperimentSummaryEvaluator[]
|
|
3951
3985
|
description?: string
|
|
3952
|
-
config?: Record<string,
|
|
3986
|
+
config?: Record<string, JSONType>
|
|
3953
3987
|
tags?: Record<string, string>
|
|
3954
3988
|
}
|
|
3955
3989
|
|
|
3990
|
+
interface ExperimentRunOptions {
|
|
3991
|
+
/** Maximum retries for task and evaluator failures. Default 0. */
|
|
3992
|
+
maxRetries?: number
|
|
3993
|
+
/** Delay before a retry, in milliseconds. Default 100 * (attempt + 1). */
|
|
3994
|
+
retryDelay?: (attempt: number) => number
|
|
3995
|
+
/** Reject on the first task/evaluator error instead of capturing it. Default false. */
|
|
3996
|
+
throwOnErrors?: boolean
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3956
3999
|
interface PullDatasetOptions {
|
|
4000
|
+
/** Dataset version to pull. Defaults to latest. */
|
|
4001
|
+
version?: number
|
|
3957
4002
|
/** Wait until at least this many records are readable (absorbs write lag). */
|
|
3958
4003
|
expectedRecordCount?: number
|
|
3959
4004
|
/** Maximum total time to wait, in ms. Default 30000. */
|
|
@@ -3966,19 +4011,30 @@ declare namespace tracer {
|
|
|
3966
4011
|
traceId: string
|
|
3967
4012
|
startNs: number
|
|
3968
4013
|
durationNs: number
|
|
3969
|
-
input:
|
|
3970
|
-
output:
|
|
3971
|
-
expectedOutput:
|
|
4014
|
+
input: JSONType
|
|
4015
|
+
output: JSONType
|
|
4016
|
+
expectedOutput: JSONType
|
|
3972
4017
|
readonly isError: boolean
|
|
3973
4018
|
errorType: string | null
|
|
3974
4019
|
errorMessage: string | null
|
|
3975
|
-
evaluations: Record<string,
|
|
4020
|
+
evaluations: Record<string, JSONType>
|
|
3976
4021
|
evaluationErrors: Record<string, string>
|
|
3977
4022
|
}
|
|
3978
4023
|
|
|
4024
|
+
interface ExperimentRun {
|
|
4025
|
+
runId: string
|
|
4026
|
+
runIteration: number
|
|
4027
|
+
rows: ExperimentResultRow[]
|
|
4028
|
+
summaryEvaluations: Record<string, { value: any, error: string | null }>
|
|
4029
|
+
}
|
|
4030
|
+
|
|
3979
4031
|
interface ExperimentResult {
|
|
3980
4032
|
experimentId: string
|
|
3981
4033
|
rows: ExperimentResultRow[]
|
|
4034
|
+
/** Single-run summary evaluator results. */
|
|
4035
|
+
summaryEvaluations: Record<string, { value: any, error: string | null }>
|
|
4036
|
+
/** Experiment runs. P0 Node experiments currently return one run. */
|
|
4037
|
+
runs: ExperimentRun[]
|
|
3982
4038
|
/** Dashboard URL for the experiment. */
|
|
3983
4039
|
url: string
|
|
3984
4040
|
}
|
|
@@ -3991,13 +4047,20 @@ declare namespace tracer {
|
|
|
3991
4047
|
}
|
|
3992
4048
|
|
|
3993
4049
|
interface Dataset {
|
|
3994
|
-
addRecord (input:
|
|
4050
|
+
addRecord (input: JSONType, expectedOutput?: JSONType, metadata?: Record<string, JSONType>): Dataset
|
|
3995
4051
|
/** Creates the dataset remotely if needed and pushes any unpushed records. */
|
|
3996
4052
|
push (): Promise<DatasetPushResult>
|
|
3997
4053
|
name (): string
|
|
3998
4054
|
id (): string | null
|
|
3999
4055
|
projectId (): string | null
|
|
4000
|
-
|
|
4056
|
+
version (): number | null
|
|
4057
|
+
latestVersion (): number | null
|
|
4058
|
+
records (): Array<{
|
|
4059
|
+
id: string | null,
|
|
4060
|
+
input: JSONType,
|
|
4061
|
+
expectedOutput: JSONType,
|
|
4062
|
+
metadata: Record<string, JSONType>
|
|
4063
|
+
}>
|
|
4001
4064
|
/** Dashboard URL for the dataset, or null until pushed. */
|
|
4002
4065
|
url (): string | null
|
|
4003
4066
|
}
|
|
@@ -4006,12 +4069,13 @@ declare namespace tracer {
|
|
|
4006
4069
|
name (): string
|
|
4007
4070
|
experimentId (): string | null
|
|
4008
4071
|
url (): string | null
|
|
4009
|
-
run (): Promise<ExperimentResult>
|
|
4072
|
+
run (options?: ExperimentRunOptions): Promise<ExperimentResult>
|
|
4010
4073
|
}
|
|
4011
4074
|
|
|
4012
4075
|
interface Experiments {
|
|
4013
4076
|
/** Create a local dataset buffer; pushed on the first experiment run. */
|
|
4014
4077
|
createDataset (name: string, description?: string): Dataset
|
|
4078
|
+
createDataset (name: string, options?: CreateDatasetOptions): Dataset
|
|
4015
4079
|
/** Pull an existing dataset (with records) by name. */
|
|
4016
4080
|
pullDataset (name: string, options?: PullDatasetOptions): Promise<Dataset>
|
|
4017
4081
|
/** Build an experiment to run over a dataset. */
|