dd-trace 5.110.0 → 5.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -2
- package/README.md +13 -8
- package/ci/init.js +21 -2
- package/ci/vitest-no-worker-init-setup.mjs +430 -0
- package/ext/tags.js +2 -0
- package/index.d.ts +34 -1
- package/initialize.mjs +5 -6
- package/loader-hook.mjs +76 -55
- package/package.json +37 -34
- package/packages/datadog-instrumentations/src/ai.js +45 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
- package/packages/datadog-instrumentations/src/child_process.js +3 -3
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
- package/packages/datadog-instrumentations/src/cucumber.js +102 -43
- package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
- package/packages/datadog-instrumentations/src/fastify.js +27 -8
- package/packages/datadog-instrumentations/src/fs.js +8 -6
- package/packages/datadog-instrumentations/src/graphql.js +26 -495
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/jest.js +713 -63
- package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
- package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
- package/packages/datadog-instrumentations/src/mongodb.js +74 -0
- package/packages/datadog-instrumentations/src/mongoose.js +13 -2
- package/packages/datadog-instrumentations/src/playwright.js +13 -9
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
- package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
- package/packages/datadog-instrumentations/src/vitest.js +11 -1855
- package/packages/datadog-plugin-ai/src/index.js +1 -1
- package/packages/datadog-plugin-ai/src/tracing.js +66 -3
- package/packages/datadog-plugin-ai/src/utils.js +17 -4
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
- package/packages/datadog-plugin-child_process/src/index.js +13 -2
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
- package/packages/datadog-plugin-cucumber/src/index.js +15 -24
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
- package/packages/datadog-plugin-cypress/src/index.js +47 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
- package/packages/datadog-plugin-cypress/src/support.js +45 -1
- package/packages/datadog-plugin-express/src/code_origin.js +1 -1
- package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +639 -12
- package/packages/datadog-plugin-graphql/src/index.js +37 -9
- package/packages/datadog-plugin-graphql/src/parse.js +24 -4
- package/packages/datadog-plugin-graphql/src/utils.js +9 -2
- package/packages/datadog-plugin-graphql/src/validate.js +17 -6
- package/packages/datadog-plugin-http/src/index.js +6 -8
- package/packages/datadog-plugin-jest/src/index.js +31 -15
- package/packages/datadog-plugin-mocha/src/index.js +40 -15
- package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
- package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
- package/packages/datadog-plugin-openai/src/services.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +4 -3
- package/packages/datadog-plugin-vitest/src/index.js +120 -72
- package/packages/datadog-shimmer/src/shimmer.js +37 -16
- package/packages/dd-trace/src/aiguard/index.js +9 -14
- package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
- package/packages/dd-trace/src/appsec/graphql.js +14 -11
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
- package/packages/dd-trace/src/appsec/index.js +10 -1
- package/packages/dd-trace/src/appsec/lambda.js +135 -0
- package/packages/dd-trace/src/appsec/reporter.js +49 -10
- package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/index.js +16 -4
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
- package/packages/dd-trace/src/config/defaults.js +3 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +35 -22
- package/packages/dd-trace/src/config/major-overrides.js +4 -2
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +127 -56
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/processor.js +11 -3
- package/packages/dd-trace/src/exporters/agent/index.js +1 -1
- package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
- package/packages/dd-trace/src/llmobs/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +3 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +3 -3
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
- package/packages/dd-trace/src/opentracing/tracer.js +6 -1
- package/packages/dd-trace/src/plugin_manager.js +23 -7
- package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/llm.js +6 -1
- package/packages/dd-trace/src/plugins/util/test.js +15 -9
- package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
- package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
- package/packages/dd-trace/src/proxy.js +8 -8
- package/packages/dd-trace/src/span_processor.js +5 -6
- package/packages/dd-trace/src/span_stats.js +96 -78
- package/packages/dd-trace/src/startup-log.js +2 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
- package/packages/dd-trace/src/telemetry/index.js +2 -2
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +8 -8
- package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { performance } = require('node:perf_hooks')
|
|
4
|
+
|
|
5
|
+
const shimmer = require('../../datadog-shimmer')
|
|
6
|
+
const {
|
|
7
|
+
DYNAMIC_NAME_RE,
|
|
8
|
+
getEfdRetryCount,
|
|
9
|
+
recordAttemptToFixExecution,
|
|
10
|
+
logAttemptToFixTestExecution,
|
|
11
|
+
} = require('../../dd-trace/src/plugins/util/test')
|
|
12
|
+
const { addHook } = require('./helpers/instrument')
|
|
13
|
+
const {
|
|
14
|
+
testStartCh,
|
|
15
|
+
testFinishTimeCh,
|
|
16
|
+
testPassCh,
|
|
17
|
+
testErrorCh,
|
|
18
|
+
testDiWaitCh,
|
|
19
|
+
testSkipCh,
|
|
20
|
+
testFnCh,
|
|
21
|
+
testSuiteStartCh,
|
|
22
|
+
testSuiteFinishCh,
|
|
23
|
+
testSuiteErrorCh,
|
|
24
|
+
findExportByName,
|
|
25
|
+
getTestRunnerExport,
|
|
26
|
+
getTypeTasks,
|
|
27
|
+
getTestName,
|
|
28
|
+
getProvidedContext,
|
|
29
|
+
isFlakyTestRetriesEnabledForTask,
|
|
30
|
+
getVitestTestProperties,
|
|
31
|
+
} = require('./vitest-util')
|
|
32
|
+
|
|
33
|
+
const taskToCtx = new WeakMap()
|
|
34
|
+
const taskToTestProperties = new WeakMap()
|
|
35
|
+
const taskToStatuses = new WeakMap()
|
|
36
|
+
const taskToReportedErrorCount = new WeakMap()
|
|
37
|
+
const attemptToFixTaskToStatuses = new WeakMap()
|
|
38
|
+
const fileToHasConcurrentTests = new WeakMap()
|
|
39
|
+
const originalHookFns = new WeakMap()
|
|
40
|
+
const newTasks = new WeakSet()
|
|
41
|
+
const dynamicNameTasks = new WeakSet()
|
|
42
|
+
const disabledTasks = new WeakSet()
|
|
43
|
+
const quarantinedTasks = new WeakSet()
|
|
44
|
+
const attemptToFixTasks = new WeakSet()
|
|
45
|
+
const attemptToFixRetryTasks = new WeakSet()
|
|
46
|
+
const modifiedTasks = new WeakSet()
|
|
47
|
+
const efdRetryTasks = new WeakSet()
|
|
48
|
+
const efdDeterminedRetries = new WeakMap()
|
|
49
|
+
const efdSlowAbortedTasks = new WeakSet()
|
|
50
|
+
const efdExecutionStartByTask = new WeakMap()
|
|
51
|
+
const efdSkippedRetryResults = new WeakMap()
|
|
52
|
+
const attemptToFixExecutions = new Map()
|
|
53
|
+
const loggedAttemptToFixTests = new Set()
|
|
54
|
+
const switchedStatuses = new WeakSet()
|
|
55
|
+
let vitestGetFn = null
|
|
56
|
+
let vitestSetFn = null
|
|
57
|
+
let vitestGetHooks = null
|
|
58
|
+
|
|
59
|
+
function waitForHitProbe () {
|
|
60
|
+
const promises = {}
|
|
61
|
+
testDiWaitCh.publish({ promises })
|
|
62
|
+
return promises.hitBreakpointPromise
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getVitestTestStatus (test, retryCount) {
|
|
66
|
+
if (test.result.state !== 'fail' && (!test.repeats || (test.retry ?? 0) === retryCount)) {
|
|
67
|
+
return 'pass'
|
|
68
|
+
}
|
|
69
|
+
return 'fail'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
|
|
73
|
+
if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
|
|
74
|
+
return testCtx.status
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return state === 'fail' ? 'fail' : 'pass'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Return the normalized test suite path prepared by the main process for a Vitest task.
|
|
82
|
+
*
|
|
83
|
+
* @param {{ file: { filepath: string } }} task
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
function getTaskTestSuite (task) {
|
|
87
|
+
return taskToTestProperties.get(task)?.testSuite || task.file.filepath
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function recordFinalAttemptToFixExecution (task, status, providedContext) {
|
|
91
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
92
|
+
if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
|
|
93
|
+
statuses.push(status)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
recordAttemptToFixExecution(attemptToFixExecutions, {
|
|
97
|
+
testSuite: getTaskTestSuite(task),
|
|
98
|
+
testName: getTestName(task),
|
|
99
|
+
status,
|
|
100
|
+
isDisabled: disabledTasks.has(task),
|
|
101
|
+
isQuarantined: quarantinedTasks.has(task),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function disableFrameworkRetries (task) {
|
|
106
|
+
task.retry = 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getCurrentAttemptTestError (task, errors) {
|
|
110
|
+
if (!errors?.length) return
|
|
111
|
+
|
|
112
|
+
const previousErrorCount = taskToReportedErrorCount.get(task) ?? 0
|
|
113
|
+
const testError = errors[previousErrorCount] ?? errors[0]
|
|
114
|
+
taskToReportedErrorCount.set(task, errors.length)
|
|
115
|
+
return testError
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function wrapTestScopedFn (task, fn) {
|
|
119
|
+
return shimmer.wrapFunction(fn, fn => function (...args) {
|
|
120
|
+
return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function wrapBeforeEachCleanupResult (task, result) {
|
|
125
|
+
if (typeof result === 'function') {
|
|
126
|
+
return wrapTestScopedFn(task, result)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result && typeof result.then === 'function') {
|
|
130
|
+
return result.then(cleanupFn => wrapBeforeEachCleanupResult(task, cleanupFn))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Returns whether a Vitest task tree includes any concurrent task.
|
|
138
|
+
*
|
|
139
|
+
* @param {Array<{ type?: string, concurrent?: boolean, tasks?: object[] }>|undefined} tasks
|
|
140
|
+
* @returns {boolean}
|
|
141
|
+
*/
|
|
142
|
+
function hasConcurrentTask (tasks) {
|
|
143
|
+
if (!tasks) return false
|
|
144
|
+
|
|
145
|
+
for (const task of tasks) {
|
|
146
|
+
if (task.concurrent === true) return true
|
|
147
|
+
if (hasConcurrentTask(task.tasks)) return true
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Returns whether a Vitest file includes any concurrent test.
|
|
155
|
+
*
|
|
156
|
+
* @param {{ tasks?: object[] }} file
|
|
157
|
+
* @returns {boolean}
|
|
158
|
+
*/
|
|
159
|
+
function hasConcurrentTests (file) {
|
|
160
|
+
const cached = fileToHasConcurrentTests.get(file)
|
|
161
|
+
if (cached !== undefined) return cached
|
|
162
|
+
|
|
163
|
+
const hasConcurrent = hasConcurrentTask(file.tasks)
|
|
164
|
+
fileToHasConcurrentTests.set(file, hasConcurrent)
|
|
165
|
+
return hasConcurrent
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Gets the task associated with a Vitest hook invocation.
|
|
170
|
+
*
|
|
171
|
+
* @param {unknown[]} args
|
|
172
|
+
* @param {object} fallbackTask
|
|
173
|
+
* @returns {object}
|
|
174
|
+
*/
|
|
175
|
+
function getTaskFromHookArgs (args, fallbackTask) {
|
|
176
|
+
return args[0]?.task || fallbackTask
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Wraps a Vitest hook so it runs inside the span context for the current test.
|
|
181
|
+
*
|
|
182
|
+
* @param {'beforeEach'|'afterEach'} hookType
|
|
183
|
+
* @param {Function} fn
|
|
184
|
+
* @param {object} fallbackTask
|
|
185
|
+
* @returns {Function}
|
|
186
|
+
*/
|
|
187
|
+
function wrapSuiteHookFn (hookType, fn, fallbackTask) {
|
|
188
|
+
return shimmer.wrapFunction(fn, fn => function (...args) {
|
|
189
|
+
const task = getTaskFromHookArgs(args, fallbackTask)
|
|
190
|
+
const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
|
|
191
|
+
|
|
192
|
+
if (hookType === 'beforeEach') {
|
|
193
|
+
return wrapBeforeEachCleanupResult(task, result)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return result
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function wrapVitestTestRunner (VitestTestRunner) {
|
|
201
|
+
// `onBeforeRunTask` is run before any repetition or attempt is run
|
|
202
|
+
// `onBeforeRunTask` is an async function
|
|
203
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeRunTask', onBeforeRunTask => function (task) {
|
|
204
|
+
const testName = getTestName(task)
|
|
205
|
+
|
|
206
|
+
const providedContext = getProvidedContext()
|
|
207
|
+
const {
|
|
208
|
+
isEarlyFlakeDetectionEnabled,
|
|
209
|
+
isKnownTestsEnabled,
|
|
210
|
+
numRepeats,
|
|
211
|
+
isTestManagementTestsEnabled,
|
|
212
|
+
testManagementAttemptToFixRetries,
|
|
213
|
+
isImpactedTestsEnabled,
|
|
214
|
+
} = providedContext
|
|
215
|
+
const testProperties = getVitestTestProperties(providedContext, task.file.filepath, testName)
|
|
216
|
+
taskToTestProperties.set(task, testProperties)
|
|
217
|
+
|
|
218
|
+
if (isTestManagementTestsEnabled) {
|
|
219
|
+
const {
|
|
220
|
+
isAttemptToFix,
|
|
221
|
+
isDisabled,
|
|
222
|
+
isQuarantined,
|
|
223
|
+
} = testProperties
|
|
224
|
+
if (isAttemptToFix) {
|
|
225
|
+
if (task.repeats !== testManagementAttemptToFixRetries) {
|
|
226
|
+
attemptToFixRetryTasks.add(task)
|
|
227
|
+
}
|
|
228
|
+
disableFrameworkRetries(task)
|
|
229
|
+
task.repeats = testManagementAttemptToFixRetries
|
|
230
|
+
attemptToFixTasks.add(task)
|
|
231
|
+
attemptToFixTaskToStatuses.set(task, [])
|
|
232
|
+
}
|
|
233
|
+
if (isQuarantined) {
|
|
234
|
+
quarantinedTasks.add(task)
|
|
235
|
+
}
|
|
236
|
+
if (isDisabled) {
|
|
237
|
+
disabledTasks.add(task)
|
|
238
|
+
if (!attemptToFixTasks.has(task)) {
|
|
239
|
+
// we only actually skip if the test is not being attempted to be fixed
|
|
240
|
+
task.mode = 'skip'
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (isImpactedTestsEnabled && testProperties.isModified) {
|
|
246
|
+
if (isEarlyFlakeDetectionEnabled) {
|
|
247
|
+
efdRetryTasks.add(task)
|
|
248
|
+
disableFrameworkRetries(task)
|
|
249
|
+
task.repeats = numRepeats
|
|
250
|
+
}
|
|
251
|
+
modifiedTasks.add(task)
|
|
252
|
+
taskToStatuses.set(task, [])
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (isKnownTestsEnabled && testProperties.isNew && !attemptToFixTasks.has(task)) {
|
|
256
|
+
if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
|
|
257
|
+
efdRetryTasks.add(task)
|
|
258
|
+
disableFrameworkRetries(task)
|
|
259
|
+
task.repeats = numRepeats
|
|
260
|
+
}
|
|
261
|
+
newTasks.add(task)
|
|
262
|
+
taskToStatuses.set(task, [])
|
|
263
|
+
if (DYNAMIC_NAME_RE.test(testName)) {
|
|
264
|
+
dynamicNameTasks.add(task)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return onBeforeRunTask.apply(this, arguments)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
// `onAfterRunTask` is run after all repetitions or attempts are run
|
|
272
|
+
// `onAfterRunTask` is an async function
|
|
273
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => function (task) {
|
|
274
|
+
const { isEarlyFlakeDetectionEnabled, isTestManagementTestsEnabled } = getProvidedContext()
|
|
275
|
+
|
|
276
|
+
if (isTestManagementTestsEnabled) {
|
|
277
|
+
const isAttemptingToFix = attemptToFixTasks.has(task)
|
|
278
|
+
const isQuarantined = quarantinedTasks.has(task)
|
|
279
|
+
|
|
280
|
+
if (isAttemptingToFix) {
|
|
281
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
282
|
+
if (task.result.state === 'pass' && statuses?.includes('fail')) {
|
|
283
|
+
switchedStatuses.add(task)
|
|
284
|
+
task.result.state = 'fail'
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!isAttemptingToFix && isQuarantined) {
|
|
289
|
+
if (task.result.state === 'fail') {
|
|
290
|
+
switchedStatuses.add(task)
|
|
291
|
+
}
|
|
292
|
+
task.result.state = 'pass'
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)) {
|
|
297
|
+
const statuses = taskToStatuses.get(task)
|
|
298
|
+
// If the test has passed at least once, we consider it passed
|
|
299
|
+
if (statuses.includes('pass')) {
|
|
300
|
+
if (task.result.state === 'fail') {
|
|
301
|
+
switchedStatuses.add(task)
|
|
302
|
+
}
|
|
303
|
+
task.result.state = 'pass'
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return onAfterRunTask.apply(this, arguments)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// test start (only tests that are not marked as skip or todo)
|
|
311
|
+
// `onBeforeTryTask` is run for every repetition and attempt of the test
|
|
312
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
|
|
313
|
+
if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
|
|
314
|
+
return onBeforeTryTask.apply(this, arguments)
|
|
315
|
+
}
|
|
316
|
+
const testName = getTestName(task)
|
|
317
|
+
let isNew = false
|
|
318
|
+
const providedContext = getProvidedContext()
|
|
319
|
+
const {
|
|
320
|
+
isKnownTestsEnabled,
|
|
321
|
+
isEarlyFlakeDetectionEnabled,
|
|
322
|
+
isDiEnabled,
|
|
323
|
+
slowTestRetries,
|
|
324
|
+
} = providedContext
|
|
325
|
+
|
|
326
|
+
if (isKnownTestsEnabled) {
|
|
327
|
+
isNew = newTasks.has(task)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const { retry: numAttempt, repeats: numRepetition } = retryInfo
|
|
331
|
+
const isFailedTestReplayAllowed = !hasConcurrentTests(task.file)
|
|
332
|
+
const isEfdManagedTask = isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)
|
|
333
|
+
|
|
334
|
+
if (isEfdManagedTask && numRepetition > 0 && !efdDeterminedRetries.has(task)) {
|
|
335
|
+
const previousExecutionStart = efdExecutionStartByTask.get(task)
|
|
336
|
+
const duration = previousExecutionStart === undefined
|
|
337
|
+
? task.result?.duration ?? 0
|
|
338
|
+
: performance.now() - previousExecutionStart
|
|
339
|
+
const retryCount = getEfdRetryCount(duration, slowTestRetries)
|
|
340
|
+
efdDeterminedRetries.set(task, retryCount)
|
|
341
|
+
task.repeats = retryCount
|
|
342
|
+
if (retryCount === 0) {
|
|
343
|
+
efdSlowAbortedTasks.add(task)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const efdRetryCount = efdDeterminedRetries.get(task)
|
|
348
|
+
if (isEfdManagedTask && efdRetryCount !== undefined && numRepetition > efdRetryCount) {
|
|
349
|
+
if (task.result) {
|
|
350
|
+
efdSkippedRetryResults.set(task, {
|
|
351
|
+
...task.result,
|
|
352
|
+
errors: task.result.errors?.slice(),
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
if (vitestSetFn) {
|
|
356
|
+
const noop = function () {}
|
|
357
|
+
noop.__ddTraceWrapped = true
|
|
358
|
+
vitestSetFn(task, noop)
|
|
359
|
+
}
|
|
360
|
+
return onBeforeTryTask.apply(this, arguments)
|
|
361
|
+
}
|
|
362
|
+
if (isEfdManagedTask) {
|
|
363
|
+
efdExecutionStartByTask.set(task, performance.now())
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// We finish the previous test here because we know it has failed already
|
|
367
|
+
if (numAttempt > 0) {
|
|
368
|
+
const shouldWaitForHitProbe = isDiEnabled && isFailedTestReplayAllowed && numAttempt > 1
|
|
369
|
+
if (shouldWaitForHitProbe) {
|
|
370
|
+
await waitForHitProbe()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const promises = {}
|
|
374
|
+
const shouldSetProbe = isDiEnabled && isFailedTestReplayAllowed && numAttempt === 1
|
|
375
|
+
const ctx = taskToCtx.get(task)
|
|
376
|
+
const testError = getCurrentAttemptTestError(task, task.result?.errors)
|
|
377
|
+
if (ctx) {
|
|
378
|
+
testErrorCh.publish({
|
|
379
|
+
error: testError,
|
|
380
|
+
shouldSetProbe,
|
|
381
|
+
shouldWaitForHitProbe,
|
|
382
|
+
promises,
|
|
383
|
+
...ctx.currentStore,
|
|
384
|
+
})
|
|
385
|
+
// We wait for the probe to be set
|
|
386
|
+
if (promises.setProbePromise) {
|
|
387
|
+
await promises.setProbePromise
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const lastExecutionStatus = task.result.state
|
|
393
|
+
const isAtf = attemptToFixTasks.has(task)
|
|
394
|
+
const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
|
|
395
|
+
const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
|
|
396
|
+
const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
|
|
397
|
+
|
|
398
|
+
// These clauses handle task.repeats, whether EFD is enabled or not
|
|
399
|
+
// The only thing that EFD does is to forcefully pass the test if it has passed at least once
|
|
400
|
+
if (numRepetition > 0 && numRepetition < task.repeats) { // it may or may have not failed
|
|
401
|
+
// Here we finish the earlier iteration,
|
|
402
|
+
// as long as it's not the _last_ iteration (which will be finished normally)
|
|
403
|
+
|
|
404
|
+
const ctx = taskToCtx.get(task)
|
|
405
|
+
if (ctx) {
|
|
406
|
+
if (lastExecutionStatus === 'fail') {
|
|
407
|
+
const testError = getCurrentAttemptTestError(task, task.result?.errors)
|
|
408
|
+
testErrorCh.publish({ error: testError, ...ctx.currentStore })
|
|
409
|
+
} else {
|
|
410
|
+
testPassCh.publish({ task, ...ctx.currentStore })
|
|
411
|
+
}
|
|
412
|
+
if (shouldTrackStatuses && statuses) {
|
|
413
|
+
statuses.push(lastExecutionStatus)
|
|
414
|
+
}
|
|
415
|
+
if (shouldFlipStatus) {
|
|
416
|
+
// If we don't "reset" the result.state to "pass", once a repetition fails,
|
|
417
|
+
// vitest will always consider the test as failed, so we can't read the actual status
|
|
418
|
+
// This means that we change vitest's behavior:
|
|
419
|
+
// if the last attempt passes, vitest would consider the test as failed
|
|
420
|
+
// but after this change, it will consider the test as passed
|
|
421
|
+
task.result.state = 'pass'
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} else if (numRepetition === task.repeats) {
|
|
425
|
+
if (shouldTrackStatuses && statuses) {
|
|
426
|
+
statuses.push(lastExecutionStatus)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const ctx = taskToCtx.get(task)
|
|
430
|
+
if (lastExecutionStatus === 'fail') {
|
|
431
|
+
const testError = getCurrentAttemptTestError(task, task.result?.errors)
|
|
432
|
+
testErrorCh.publish({ error: testError, ...ctx.currentStore })
|
|
433
|
+
} else {
|
|
434
|
+
testPassCh.publish({ task, ...ctx.currentStore })
|
|
435
|
+
}
|
|
436
|
+
if (shouldFlipStatus) {
|
|
437
|
+
task.result.state = 'pass'
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const isRetryReasonAtr = numAttempt > 0 &&
|
|
442
|
+
isFlakyTestRetriesEnabledForTask(providedContext, task) &&
|
|
443
|
+
!attemptToFixRetryTasks.has(task) &&
|
|
444
|
+
!efdRetryTasks.has(task)
|
|
445
|
+
|
|
446
|
+
const ctx = {
|
|
447
|
+
testName,
|
|
448
|
+
testSuiteAbsolutePath: task.file.filepath,
|
|
449
|
+
isRetry: numAttempt > 0 || numRepetition > 0,
|
|
450
|
+
isRetryReasonEfd: efdRetryTasks.has(task),
|
|
451
|
+
isRetryReasonAttemptToFix: attemptToFixRetryTasks.has(task) && numRepetition > 0,
|
|
452
|
+
isNew,
|
|
453
|
+
hasDynamicName: dynamicNameTasks.has(task),
|
|
454
|
+
mightHitProbe: isDiEnabled && isFailedTestReplayAllowed && numAttempt > 0,
|
|
455
|
+
isAttemptToFix: attemptToFixTasks.has(task),
|
|
456
|
+
isDisabled: disabledTasks.has(task),
|
|
457
|
+
isQuarantined: quarantinedTasks.has(task),
|
|
458
|
+
isRetryReasonAtr,
|
|
459
|
+
isModified: modifiedTasks.has(task),
|
|
460
|
+
}
|
|
461
|
+
taskToCtx.set(task, ctx)
|
|
462
|
+
|
|
463
|
+
if (attemptToFixTasks.has(task)) {
|
|
464
|
+
logAttemptToFixTestExecution(
|
|
465
|
+
getTaskTestSuite(task),
|
|
466
|
+
testName,
|
|
467
|
+
loggedAttemptToFixTests
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
testStartCh.runStores(ctx, () => {})
|
|
472
|
+
|
|
473
|
+
// Wrap the test function so it runs inside the test span context.
|
|
474
|
+
// Without this, HTTP requests during test execution become orphaned root spans.
|
|
475
|
+
if (vitestGetFn && vitestSetFn) {
|
|
476
|
+
const originalFn = vitestGetFn(task)
|
|
477
|
+
if (originalFn && !originalFn.__ddTraceWrapped) {
|
|
478
|
+
const wrappedFn = wrapTestScopedFn(task, originalFn)
|
|
479
|
+
wrappedFn.__ddTraceWrapped = true
|
|
480
|
+
vitestSetFn(task, wrappedFn)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Wrap beforeEach/afterEach hooks so they also run inside the test span context.
|
|
485
|
+
// In vitest 4+, hooks are in a WeakMap accessed via getHooks(). In older versions, they're on suite.hooks.
|
|
486
|
+
let currentSuite = task.suite
|
|
487
|
+
while (currentSuite) {
|
|
488
|
+
const hooks = vitestGetHooks ? vitestGetHooks(currentSuite) : currentSuite.hooks
|
|
489
|
+
if (hooks) {
|
|
490
|
+
for (const hookType of ['beforeEach', 'afterEach']) {
|
|
491
|
+
const hookArray = hooks[hookType]
|
|
492
|
+
if (!hookArray) continue
|
|
493
|
+
for (let i = 0; i < hookArray.length; i++) {
|
|
494
|
+
const currentFn = hookArray[i]
|
|
495
|
+
if (originalHookFns.has(currentFn)) continue
|
|
496
|
+
const wrappedFn = wrapSuiteHookFn(hookType, currentFn, task)
|
|
497
|
+
originalHookFns.set(wrappedFn, currentFn)
|
|
498
|
+
hookArray[i] = wrappedFn
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
currentSuite = currentSuite.suite
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return onBeforeTryTask.apply(this, arguments)
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
// test finish (only passed tests)
|
|
509
|
+
shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
|
|
510
|
+
async function (task, retryInfo) {
|
|
511
|
+
if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
|
|
512
|
+
return onAfterTryTask.apply(this, arguments)
|
|
513
|
+
}
|
|
514
|
+
const result = await onAfterTryTask.apply(this, arguments)
|
|
515
|
+
|
|
516
|
+
const {
|
|
517
|
+
isEarlyFlakeDetectionEnabled,
|
|
518
|
+
testManagementAttemptToFixRetries,
|
|
519
|
+
slowTestRetries,
|
|
520
|
+
} = getProvidedContext()
|
|
521
|
+
|
|
522
|
+
const status = getVitestTestStatus(task, retryInfo.retry)
|
|
523
|
+
const ctx = taskToCtx.get(task)
|
|
524
|
+
|
|
525
|
+
const { isDiEnabled } = getProvidedContext()
|
|
526
|
+
const isFailedTestReplayAllowed = !hasConcurrentTests(task.file)
|
|
527
|
+
|
|
528
|
+
if (efdSkippedRetryResults.has(task)) {
|
|
529
|
+
task.result = efdSkippedRetryResults.get(task)
|
|
530
|
+
efdSkippedRetryResults.delete(task)
|
|
531
|
+
return result
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (isDiEnabled && isFailedTestReplayAllowed && retryInfo.retry > 1) {
|
|
535
|
+
await waitForHitProbe()
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (
|
|
539
|
+
isEarlyFlakeDetectionEnabled &&
|
|
540
|
+
(retryInfo.repeats ?? 0) === 0 &&
|
|
541
|
+
taskToStatuses.has(task) &&
|
|
542
|
+
!attemptToFixTasks.has(task) &&
|
|
543
|
+
!efdDeterminedRetries.has(task)
|
|
544
|
+
) {
|
|
545
|
+
const executionStart = efdExecutionStartByTask.get(task)
|
|
546
|
+
const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
|
|
547
|
+
const retryCount = getEfdRetryCount(duration, slowTestRetries)
|
|
548
|
+
efdDeterminedRetries.set(task, retryCount)
|
|
549
|
+
task.repeats = retryCount
|
|
550
|
+
if (retryCount === 0) {
|
|
551
|
+
efdSlowAbortedTasks.add(task)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
let attemptToFixPassed = false
|
|
556
|
+
let attemptToFixFailed = false
|
|
557
|
+
if (attemptToFixTasks.has(task)) {
|
|
558
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
559
|
+
if (statuses.length === testManagementAttemptToFixRetries) {
|
|
560
|
+
if (status === 'pass' && statuses.every(status => status === 'pass')) {
|
|
561
|
+
attemptToFixPassed = true
|
|
562
|
+
} else if (status === 'fail' || statuses.includes('fail')) {
|
|
563
|
+
attemptToFixFailed = true
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (ctx) {
|
|
569
|
+
// We don't finish here because the test might fail in a later hook (afterEach)
|
|
570
|
+
ctx.status = status
|
|
571
|
+
ctx.task = task
|
|
572
|
+
ctx.attemptToFixPassed = attemptToFixPassed
|
|
573
|
+
ctx.attemptToFixFailed = attemptToFixFailed
|
|
574
|
+
testFinishTimeCh.runStores(ctx, () => {})
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return result
|
|
578
|
+
})
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function captureRunnerFunctions (pkg) {
|
|
582
|
+
if (vitestGetFn) return
|
|
583
|
+
const getFnExport = findExportByName(pkg, 'getFn')
|
|
584
|
+
const setFnExport = findExportByName(pkg, 'setFn')
|
|
585
|
+
if (getFnExport && setFnExport) {
|
|
586
|
+
vitestGetFn = getFnExport.value
|
|
587
|
+
vitestSetFn = setFnExport.value
|
|
588
|
+
}
|
|
589
|
+
const getHooksExport = findExportByName(pkg, 'getHooks')
|
|
590
|
+
if (getHooksExport) {
|
|
591
|
+
vitestGetHooks = getHooksExport.value
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
addHook({
|
|
596
|
+
name: 'vitest',
|
|
597
|
+
versions: ['>=4.0.0'],
|
|
598
|
+
filePattern: 'dist/chunks/test.*',
|
|
599
|
+
}, (testPackage) => {
|
|
600
|
+
const testRunner = getTestRunnerExport(testPackage)
|
|
601
|
+
if (!testRunner) {
|
|
602
|
+
return testPackage
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
captureRunnerFunctions(testPackage)
|
|
606
|
+
wrapVitestTestRunner(testRunner.value)
|
|
607
|
+
|
|
608
|
+
return testPackage
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
addHook({
|
|
612
|
+
name: '@vitest/runner',
|
|
613
|
+
versions: ['>=1.6.0'],
|
|
614
|
+
}, (runnerModule) => {
|
|
615
|
+
if (!vitestGetFn && runnerModule.getFn && runnerModule.setFn) {
|
|
616
|
+
vitestGetFn = runnerModule.getFn
|
|
617
|
+
vitestSetFn = runnerModule.setFn
|
|
618
|
+
}
|
|
619
|
+
if (!vitestGetHooks && runnerModule.getHooks) {
|
|
620
|
+
vitestGetHooks = runnerModule.getHooks
|
|
621
|
+
}
|
|
622
|
+
return runnerModule
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
addHook({
|
|
626
|
+
name: 'vitest',
|
|
627
|
+
versions: ['>=1.6.0 <4.0.0'],
|
|
628
|
+
file: 'dist/runners.js',
|
|
629
|
+
}, (vitestPackage) => {
|
|
630
|
+
const { VitestTestRunner } = vitestPackage
|
|
631
|
+
|
|
632
|
+
wrapVitestTestRunner(VitestTestRunner)
|
|
633
|
+
|
|
634
|
+
return vitestPackage
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
// test suite start and finish
|
|
638
|
+
// only relevant for workers
|
|
639
|
+
addHook({
|
|
640
|
+
name: '@vitest/runner',
|
|
641
|
+
versions: ['>=1.6.0'],
|
|
642
|
+
}, (vitestPackage, frameworkVersion) => {
|
|
643
|
+
shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPaths) {
|
|
644
|
+
let testSuiteError = null
|
|
645
|
+
if (!testSuiteFinishCh.hasSubscribers) {
|
|
646
|
+
return startTests.apply(this, arguments)
|
|
647
|
+
}
|
|
648
|
+
// From >=3.0.1, the first arguments changes from a string to an object containing the filepath
|
|
649
|
+
const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
|
|
650
|
+
const providedContext = getProvidedContext()
|
|
651
|
+
|
|
652
|
+
const testSuiteCtx = {
|
|
653
|
+
testSuiteAbsolutePath,
|
|
654
|
+
frameworkVersion,
|
|
655
|
+
testSessionId: providedContext.testSessionId,
|
|
656
|
+
testModuleId: providedContext.testModuleId,
|
|
657
|
+
testCommand: providedContext.testCommand,
|
|
658
|
+
repositoryRoot: providedContext.repositoryRoot,
|
|
659
|
+
codeOwnersEntries: providedContext.codeOwnersEntries,
|
|
660
|
+
testEnvironmentMetadata: providedContext.testEnvironmentMetadata,
|
|
661
|
+
}
|
|
662
|
+
testSuiteStartCh.runStores(testSuiteCtx, () => {})
|
|
663
|
+
const startTestsResponse = await startTests.apply(this, arguments)
|
|
664
|
+
|
|
665
|
+
let onFinish = null
|
|
666
|
+
const onFinishPromise = new Promise(resolve => {
|
|
667
|
+
onFinish = resolve
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
const testTasks = getTypeTasks(startTestsResponse[0].tasks)
|
|
671
|
+
const testEventPromises = []
|
|
672
|
+
|
|
673
|
+
// Only one test task per test, even if there are retries
|
|
674
|
+
for (const task of testTasks) {
|
|
675
|
+
const testCtx = taskToCtx.get(task)
|
|
676
|
+
const { result } = task
|
|
677
|
+
// We have to trick vitest into thinking that the test has passed
|
|
678
|
+
// but we want to report it as failed if it did fail
|
|
679
|
+
const isSwitchedStatus = switchedStatuses.has(task)
|
|
680
|
+
|
|
681
|
+
if (result) {
|
|
682
|
+
const { state, duration, errors } = result
|
|
683
|
+
const testError = getCurrentAttemptTestError(task, errors)
|
|
684
|
+
if (attemptToFixTasks.has(task)) {
|
|
685
|
+
const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
|
|
686
|
+
recordFinalAttemptToFixExecution(task, status, providedContext)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (state === 'skip') { // programmatic skip
|
|
690
|
+
testSkipCh.publish({
|
|
691
|
+
testName: getTestName(task),
|
|
692
|
+
testSuiteAbsolutePath: task.file.filepath,
|
|
693
|
+
isNew: newTasks.has(task),
|
|
694
|
+
isDisabled: disabledTasks.has(task),
|
|
695
|
+
})
|
|
696
|
+
} else if (state === 'pass' && !isSwitchedStatus) {
|
|
697
|
+
if (testCtx) {
|
|
698
|
+
const isSkippedByTestManagement =
|
|
699
|
+
!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
|
|
700
|
+
const promises = {}
|
|
701
|
+
testPassCh.publish({
|
|
702
|
+
task,
|
|
703
|
+
finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
|
|
704
|
+
earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
|
|
705
|
+
promises,
|
|
706
|
+
...testCtx.currentStore,
|
|
707
|
+
})
|
|
708
|
+
if (promises.hitBreakpointPromise) {
|
|
709
|
+
testEventPromises.push(promises.hitBreakpointPromise)
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
} else if (state === 'fail' || isSwitchedStatus) {
|
|
713
|
+
let hasFailedAllRetries = false
|
|
714
|
+
let attemptToFixFailed = false
|
|
715
|
+
if (attemptToFixTasks.has(task)) {
|
|
716
|
+
const statuses = attemptToFixTaskToStatuses.get(task)
|
|
717
|
+
if (statuses.includes('fail')) {
|
|
718
|
+
attemptToFixFailed = true
|
|
719
|
+
}
|
|
720
|
+
if (statuses.every(status => status === 'fail')) {
|
|
721
|
+
hasFailedAllRetries = true
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Check if all EFD retries failed
|
|
726
|
+
const isEfdRetry =
|
|
727
|
+
providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
|
|
728
|
+
if (isEfdRetry) {
|
|
729
|
+
const statuses = taskToStatuses.get(task)
|
|
730
|
+
const efdRetryCount = efdDeterminedRetries.get(task) ?? providedContext.numRepeats
|
|
731
|
+
// statuses only includes repetitions (not the initial run), so we check against retry count (not +1)
|
|
732
|
+
if (efdRetryCount > 0 && statuses && statuses.length === efdRetryCount &&
|
|
733
|
+
statuses.every(status => status === 'fail')) {
|
|
734
|
+
hasFailedAllRetries = true
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
|
|
739
|
+
const isAtrRetry = isFlakyTestRetriesEnabledForTask(providedContext, task) && !attemptToFixTasks.has(task) &&
|
|
740
|
+
!newTasks.has(task) && !modifiedTasks.has(task)
|
|
741
|
+
if (isAtrRetry) {
|
|
742
|
+
const maxRetries = providedContext.flakyTestRetriesCount ?? 0
|
|
743
|
+
if (maxRetries > 0 && task.result?.retryCount === maxRetries) {
|
|
744
|
+
hasFailedAllRetries = true
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (testCtx) {
|
|
749
|
+
const isRetry = task.result?.retryCount > 0
|
|
750
|
+
const promises = {}
|
|
751
|
+
// `duration` is the duration of all the retries, so it can't be used if there are retries
|
|
752
|
+
|
|
753
|
+
let finalStatus
|
|
754
|
+
if (isSwitchedStatus) {
|
|
755
|
+
if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
|
|
756
|
+
finalStatus = 'skip'
|
|
757
|
+
} else if (isAtrRetry || isEfdRetry) {
|
|
758
|
+
finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
|
|
759
|
+
} else if (attemptToFixTasks.has(task)) {
|
|
760
|
+
finalStatus = attemptToFixFailed ? 'fail' : 'pass'
|
|
761
|
+
} else {
|
|
762
|
+
finalStatus = undefined
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
finalStatus = 'fail'
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
testErrorCh.publish({
|
|
769
|
+
duration: isRetry ? undefined : duration,
|
|
770
|
+
error: testError,
|
|
771
|
+
hasFailedAllRetries,
|
|
772
|
+
attemptToFixFailed,
|
|
773
|
+
finalStatus,
|
|
774
|
+
earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
|
|
775
|
+
promises,
|
|
776
|
+
...testCtx.currentStore,
|
|
777
|
+
})
|
|
778
|
+
if (promises.hitBreakpointPromise) {
|
|
779
|
+
testEventPromises.push(promises.hitBreakpointPromise)
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (errors?.length) {
|
|
783
|
+
testSuiteError = testError // we store the error to bubble it up to the suite
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
} else { // test.skip or test.todo
|
|
787
|
+
testSkipCh.publish({
|
|
788
|
+
testName: getTestName(task),
|
|
789
|
+
testSuiteAbsolutePath: task.file.filepath,
|
|
790
|
+
isNew: newTasks.has(task),
|
|
791
|
+
isDisabled: disabledTasks.has(task),
|
|
792
|
+
})
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
await Promise.all(testEventPromises)
|
|
797
|
+
|
|
798
|
+
const testSuiteResult = startTestsResponse[0].result
|
|
799
|
+
|
|
800
|
+
if (testSuiteResult.errors?.length) { // Errors from root level hooks
|
|
801
|
+
testSuiteError = testSuiteResult.errors[0]
|
|
802
|
+
} else if (testSuiteResult.state === 'fail') { // Errors from `describe` level hooks
|
|
803
|
+
const suiteTasks = getTypeTasks(startTestsResponse[0].tasks, 'suite')
|
|
804
|
+
const failedSuites = suiteTasks.filter(task => task.result?.state === 'fail')
|
|
805
|
+
if (failedSuites.length && failedSuites[0].result?.errors?.length) {
|
|
806
|
+
testSuiteError = failedSuites[0].result.errors[0]
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (testSuiteError) {
|
|
811
|
+
testSuiteCtx.error = testSuiteError
|
|
812
|
+
testSuiteErrorCh.runStores(testSuiteCtx, () => {})
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish, ...testSuiteCtx.currentStore })
|
|
816
|
+
|
|
817
|
+
await onFinishPromise
|
|
818
|
+
|
|
819
|
+
return startTestsResponse
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
return vitestPackage
|
|
823
|
+
})
|