dd-trace 6.7.0 → 6.8.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
|
@@ -3734,29 +3734,74 @@ declare namespace tracer {
|
|
|
3734
3734
|
flush (): void
|
|
3735
3735
|
}
|
|
3736
3736
|
|
|
3737
|
+
/** JSON-serializable value accepted by LLMObs Experiments. */
|
|
3738
|
+
type JSONType = string | number | boolean | null | JSONType[] | { [key: string]: JSONType }
|
|
3739
|
+
|
|
3737
3740
|
/**
|
|
3738
3741
|
* A task run over each dataset record during an experiment.
|
|
3739
3742
|
*/
|
|
3740
|
-
type ExperimentTask = (
|
|
3743
|
+
type ExperimentTask = (
|
|
3744
|
+
input: JSONType,
|
|
3745
|
+
config: Record<string, JSONType>,
|
|
3746
|
+
metadata?: Record<string, JSONType>
|
|
3747
|
+
) => JSONType | Promise<JSONType>
|
|
3741
3748
|
|
|
3742
3749
|
/**
|
|
3743
3750
|
* Scores a single task output. The return type selects the metric:
|
|
3744
|
-
* `boolean` -> boolean, `number` -> score, anything else ->
|
|
3751
|
+
* `boolean` -> boolean, `number` -> score, `string` -> categorical, anything else -> json.
|
|
3752
|
+
*/
|
|
3753
|
+
type ExperimentEvaluator = (
|
|
3754
|
+
input: JSONType,
|
|
3755
|
+
output: JSONType,
|
|
3756
|
+
expectedOutput: JSONType
|
|
3757
|
+
) => JSONType | Promise<JSONType>
|
|
3758
|
+
|
|
3759
|
+
/**
|
|
3760
|
+
* Scores all rows in an experiment run and emits a summary metric.
|
|
3745
3761
|
*/
|
|
3746
|
-
type
|
|
3762
|
+
type ExperimentSummaryEvaluator = (
|
|
3763
|
+
inputs: any[],
|
|
3764
|
+
outputs: any[],
|
|
3765
|
+
expectedOutputs: any[],
|
|
3766
|
+
evaluatorResults: Record<string, any[]>,
|
|
3767
|
+
metadata?: Array<Record<string, any>>
|
|
3768
|
+
) => any | Promise<any>
|
|
3769
|
+
|
|
3770
|
+
interface CreateDatasetOptions {
|
|
3771
|
+
description?: string
|
|
3772
|
+
records?: Array<{
|
|
3773
|
+
id?: string,
|
|
3774
|
+
inputData: JSONType,
|
|
3775
|
+
expectedOutput?: JSONType,
|
|
3776
|
+
metadata?: Record<string, JSONType>
|
|
3777
|
+
}>
|
|
3778
|
+
}
|
|
3747
3779
|
|
|
3748
3780
|
interface ExperimentOptions {
|
|
3749
3781
|
name: string
|
|
3750
3782
|
dataset: Dataset
|
|
3751
3783
|
task: ExperimentTask
|
|
3752
|
-
/** Evaluators keyed by metric label. */
|
|
3753
|
-
evaluators?: Record<string, ExperimentEvaluator>
|
|
3784
|
+
/** Evaluators keyed by metric label, or named functions. */
|
|
3785
|
+
evaluators?: Record<string, ExperimentEvaluator> | ExperimentEvaluator[]
|
|
3786
|
+
/** Summary evaluators keyed by metric label, or named functions. */
|
|
3787
|
+
summaryEvaluators?: Record<string, ExperimentSummaryEvaluator> | ExperimentSummaryEvaluator[]
|
|
3754
3788
|
description?: string
|
|
3755
|
-
config?: Record<string,
|
|
3789
|
+
config?: Record<string, JSONType>
|
|
3756
3790
|
tags?: Record<string, string>
|
|
3757
3791
|
}
|
|
3758
3792
|
|
|
3793
|
+
interface ExperimentRunOptions {
|
|
3794
|
+
/** Maximum retries for task and evaluator failures. Default 0. */
|
|
3795
|
+
maxRetries?: number
|
|
3796
|
+
/** Delay before a retry, in milliseconds. Default 100 * (attempt + 1). */
|
|
3797
|
+
retryDelay?: (attempt: number) => number
|
|
3798
|
+
/** Reject on the first task/evaluator error instead of capturing it. Default false. */
|
|
3799
|
+
throwOnErrors?: boolean
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3759
3802
|
interface PullDatasetOptions {
|
|
3803
|
+
/** Dataset version to pull. Defaults to latest. */
|
|
3804
|
+
version?: number
|
|
3760
3805
|
/** Wait until at least this many records are readable (absorbs write lag). */
|
|
3761
3806
|
expectedRecordCount?: number
|
|
3762
3807
|
/** Maximum total time to wait, in ms. Default 30000. */
|
|
@@ -3769,19 +3814,30 @@ declare namespace tracer {
|
|
|
3769
3814
|
traceId: string
|
|
3770
3815
|
startNs: number
|
|
3771
3816
|
durationNs: number
|
|
3772
|
-
input:
|
|
3773
|
-
output:
|
|
3774
|
-
expectedOutput:
|
|
3817
|
+
input: JSONType
|
|
3818
|
+
output: JSONType
|
|
3819
|
+
expectedOutput: JSONType
|
|
3775
3820
|
readonly isError: boolean
|
|
3776
3821
|
errorType: string | null
|
|
3777
3822
|
errorMessage: string | null
|
|
3778
|
-
evaluations: Record<string,
|
|
3823
|
+
evaluations: Record<string, JSONType>
|
|
3779
3824
|
evaluationErrors: Record<string, string>
|
|
3780
3825
|
}
|
|
3781
3826
|
|
|
3827
|
+
interface ExperimentRun {
|
|
3828
|
+
runId: string
|
|
3829
|
+
runIteration: number
|
|
3830
|
+
rows: ExperimentResultRow[]
|
|
3831
|
+
summaryEvaluations: Record<string, { value: any, error: string | null }>
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3782
3834
|
interface ExperimentResult {
|
|
3783
3835
|
experimentId: string
|
|
3784
3836
|
rows: ExperimentResultRow[]
|
|
3837
|
+
/** Single-run summary evaluator results. */
|
|
3838
|
+
summaryEvaluations: Record<string, { value: any, error: string | null }>
|
|
3839
|
+
/** Experiment runs. P0 Node experiments currently return one run. */
|
|
3840
|
+
runs: ExperimentRun[]
|
|
3785
3841
|
/** Dashboard URL for the experiment. */
|
|
3786
3842
|
url: string
|
|
3787
3843
|
}
|
|
@@ -3794,13 +3850,20 @@ declare namespace tracer {
|
|
|
3794
3850
|
}
|
|
3795
3851
|
|
|
3796
3852
|
interface Dataset {
|
|
3797
|
-
addRecord (input:
|
|
3853
|
+
addRecord (input: JSONType, expectedOutput?: JSONType, metadata?: Record<string, JSONType>): Dataset
|
|
3798
3854
|
/** Creates the dataset remotely if needed and pushes any unpushed records. */
|
|
3799
3855
|
push (): Promise<DatasetPushResult>
|
|
3800
3856
|
name (): string
|
|
3801
3857
|
id (): string | null
|
|
3802
3858
|
projectId (): string | null
|
|
3803
|
-
|
|
3859
|
+
version (): number | null
|
|
3860
|
+
latestVersion (): number | null
|
|
3861
|
+
records (): Array<{
|
|
3862
|
+
id: string | null,
|
|
3863
|
+
input: JSONType,
|
|
3864
|
+
expectedOutput: JSONType,
|
|
3865
|
+
metadata: Record<string, JSONType>
|
|
3866
|
+
}>
|
|
3804
3867
|
/** Dashboard URL for the dataset, or null until pushed. */
|
|
3805
3868
|
url (): string | null
|
|
3806
3869
|
}
|
|
@@ -3809,12 +3872,13 @@ declare namespace tracer {
|
|
|
3809
3872
|
name (): string
|
|
3810
3873
|
experimentId (): string | null
|
|
3811
3874
|
url (): string | null
|
|
3812
|
-
run (): Promise<ExperimentResult>
|
|
3875
|
+
run (options?: ExperimentRunOptions): Promise<ExperimentResult>
|
|
3813
3876
|
}
|
|
3814
3877
|
|
|
3815
3878
|
interface Experiments {
|
|
3816
3879
|
/** Create a local dataset buffer; pushed on the first experiment run. */
|
|
3817
3880
|
createDataset (name: string, description?: string): Dataset
|
|
3881
|
+
createDataset (name: string, options?: CreateDatasetOptions): Dataset
|
|
3818
3882
|
/** Pull an existing dataset (with records) by name. */
|
|
3819
3883
|
pullDataset (name: string, options?: PullDatasetOptions): Promise<Dataset>
|
|
3820
3884
|
/** Build an experiment to run over a dataset. */
|