dd-trace
Version:
Datadog APM tracing client for JavaScript
820 lines (727 loc) • 28 kB
JavaScript
'use strict'
const { performance } = require('node:perf_hooks')
const shimmer = require('../../datadog-shimmer')
const {
DYNAMIC_NAME_RE,
getEfdRetryCount,
recordAttemptToFixExecution,
logAttemptToFixTestExecution,
} = require('../../dd-trace/src/plugins/util/test')
const { getChannelPromise } = require('./helpers/channel')
const { addHook } = require('./helpers/instrument')
const {
testStartCh,
testFinishTimeCh,
testPassCh,
testErrorCh,
testDiWaitCh,
testSkipCh,
testFnCh,
testSuiteStartCh,
testSuiteFinishCh,
testSuiteErrorCh,
findExportByName,
getTestRunnerExport,
getTypeTasks,
getTestName,
getProvidedContext,
isFlakyTestRetriesEnabledForTask,
getVitestTestProperties,
} = require('./vitest-util')
const taskToCtx = new WeakMap()
const taskToTestProperties = new WeakMap()
const taskToStatuses = new WeakMap()
const taskToReportedErrorCount = new WeakMap()
const attemptToFixTaskToStatuses = new WeakMap()
const fileToHasConcurrentTests = new WeakMap()
const originalHookFns = new WeakMap()
const newTasks = new WeakSet()
const dynamicNameTasks = new WeakSet()
const disabledTasks = new WeakSet()
const quarantinedTasks = new WeakSet()
const attemptToFixTasks = new WeakSet()
const attemptToFixRetryTasks = new WeakSet()
const modifiedTasks = new WeakSet()
const efdRetryTasks = new WeakSet()
const efdDeterminedRetries = new WeakMap()
const efdSlowAbortedTasks = new WeakSet()
const efdExecutionStartByTask = new WeakMap()
const efdSkippedRetryResults = new WeakMap()
const attemptToFixExecutions = new Map()
const loggedAttemptToFixTests = new Set()
const switchedStatuses = new WeakSet()
let vitestGetFn = null
let vitestSetFn = null
let vitestGetHooks = null
function waitForHitProbe () {
const promises = {}
testDiWaitCh.publish({ promises })
return promises.hitBreakpointPromise
}
function getVitestTestStatus (test, retryCount) {
if (test.result.state !== 'fail' && (!test.repeats || (test.retry ?? 0) === retryCount)) {
return 'pass'
}
return 'fail'
}
function getFinalAttemptToFixStatus (task, state, isSwitchedStatus, testCtx) {
if (isSwitchedStatus && attemptToFixTasks.has(task) && testCtx?.status) {
return testCtx.status
}
return state === 'fail' ? 'fail' : 'pass'
}
/**
* Return the normalized test suite path prepared by the main process for a Vitest task.
*
* @param {{ file: { filepath: string } }} task
* @returns {string}
*/
function getTaskTestSuite (task) {
return taskToTestProperties.get(task)?.testSuite || task.file.filepath
}
function recordFinalAttemptToFixExecution (task, status, providedContext) {
const statuses = attemptToFixTaskToStatuses.get(task)
if (statuses && statuses.length <= providedContext.testManagementAttemptToFixRetries) {
statuses.push(status)
}
recordAttemptToFixExecution(attemptToFixExecutions, {
testSuite: getTaskTestSuite(task),
testName: getTestName(task),
status,
isDisabled: disabledTasks.has(task),
isQuarantined: quarantinedTasks.has(task),
})
}
function disableFrameworkRetries (task) {
task.retry = 0
}
function getCurrentAttemptTestError (task, errors) {
if (!errors?.length) return
const previousErrorCount = taskToReportedErrorCount.get(task) ?? 0
const testError = errors[previousErrorCount] ?? errors[0]
taskToReportedErrorCount.set(task, errors.length)
return testError
}
function wrapTestScopedFn (task, fn) {
return shimmer.wrapFunction(fn, fn => function (...args) {
return testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
})
}
function wrapBeforeEachCleanupResult (task, result) {
if (typeof result === 'function') {
return wrapTestScopedFn(task, result)
}
if (result && typeof result.then === 'function') {
return result.then(cleanupFn => wrapBeforeEachCleanupResult(task, cleanupFn))
}
return result
}
/**
* Returns whether a Vitest task tree includes any concurrent task.
*
* @param {Array<{ type?: string, concurrent?: boolean, tasks?: object[] }>|undefined} tasks
* @returns {boolean}
*/
function hasConcurrentTask (tasks) {
if (!tasks) return false
for (const task of tasks) {
if (task.concurrent === true) return true
if (hasConcurrentTask(task.tasks)) return true
}
return false
}
/**
* Returns whether a Vitest file includes any concurrent test.
*
* @param {{ tasks?: object[] }} file
* @returns {boolean}
*/
function hasConcurrentTests (file) {
const cached = fileToHasConcurrentTests.get(file)
if (cached !== undefined) return cached
const hasConcurrent = hasConcurrentTask(file.tasks)
fileToHasConcurrentTests.set(file, hasConcurrent)
return hasConcurrent
}
/**
* Gets the task associated with a Vitest hook invocation.
*
* @param {unknown[]} args
* @param {object} fallbackTask
* @returns {object}
*/
function getTaskFromHookArgs (args, fallbackTask) {
return args[0]?.task || fallbackTask
}
/**
* Wraps a Vitest hook so it runs inside the span context for the current test.
*
* @param {'beforeEach'|'afterEach'} hookType
* @param {Function} fn
* @param {object} fallbackTask
* @returns {Function}
*/
function wrapSuiteHookFn (hookType, fn, fallbackTask) {
return shimmer.wrapFunction(fn, fn => function (...args) {
const task = getTaskFromHookArgs(args, fallbackTask)
const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
if (hookType === 'beforeEach') {
return wrapBeforeEachCleanupResult(task, result)
}
return result
})
}
function wrapVitestTestRunner (VitestTestRunner) {
// `onBeforeRunTask` is run before any repetition or attempt is run
// `onBeforeRunTask` is an async function
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeRunTask', onBeforeRunTask => function (task) {
const testName = getTestName(task)
const providedContext = getProvidedContext()
const {
isEarlyFlakeDetectionEnabled,
isKnownTestsEnabled,
numRepeats,
isTestManagementTestsEnabled,
testManagementAttemptToFixRetries,
isImpactedTestsEnabled,
} = providedContext
const testProperties = getVitestTestProperties(providedContext, task.file.filepath, testName)
taskToTestProperties.set(task, testProperties)
if (isTestManagementTestsEnabled) {
const {
isAttemptToFix,
isDisabled,
isQuarantined,
} = testProperties
if (isAttemptToFix) {
if (task.repeats !== testManagementAttemptToFixRetries) {
attemptToFixRetryTasks.add(task)
}
disableFrameworkRetries(task)
task.repeats = testManagementAttemptToFixRetries
attemptToFixTasks.add(task)
attemptToFixTaskToStatuses.set(task, [])
}
if (isQuarantined) {
quarantinedTasks.add(task)
}
if (isDisabled) {
disabledTasks.add(task)
if (!attemptToFixTasks.has(task)) {
// we only actually skip if the test is not being attempted to be fixed
task.mode = 'skip'
}
}
}
if (isImpactedTestsEnabled && testProperties.isModified) {
if (isEarlyFlakeDetectionEnabled) {
efdRetryTasks.add(task)
disableFrameworkRetries(task)
task.repeats = numRepeats
}
modifiedTasks.add(task)
taskToStatuses.set(task, [])
}
if (isKnownTestsEnabled && testProperties.isNew && !attemptToFixTasks.has(task)) {
if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
efdRetryTasks.add(task)
disableFrameworkRetries(task)
task.repeats = numRepeats
}
newTasks.add(task)
taskToStatuses.set(task, [])
if (DYNAMIC_NAME_RE.test(testName)) {
dynamicNameTasks.add(task)
}
}
return onBeforeRunTask.apply(this, arguments)
})
// `onAfterRunTask` is run after all repetitions or attempts are run
// `onAfterRunTask` is an async function
shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => function (task) {
const { isEarlyFlakeDetectionEnabled, isTestManagementTestsEnabled } = getProvidedContext()
if (isTestManagementTestsEnabled) {
const isAttemptingToFix = attemptToFixTasks.has(task)
const isQuarantined = quarantinedTasks.has(task)
if (isAttemptingToFix) {
const statuses = attemptToFixTaskToStatuses.get(task)
if (task.result.state === 'pass' && statuses?.includes('fail')) {
switchedStatuses.add(task)
task.result.state = 'fail'
}
}
if (!isAttemptingToFix && isQuarantined) {
if (task.result.state === 'fail') {
switchedStatuses.add(task)
}
task.result.state = 'pass'
}
}
if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)) {
const statuses = taskToStatuses.get(task)
// If the test has passed at least once, we consider it passed
if (statuses.includes('pass')) {
if (task.result.state === 'fail') {
switchedStatuses.add(task)
}
task.result.state = 'pass'
}
}
return onAfterRunTask.apply(this, arguments)
})
// test start (only tests that are not marked as skip or todo)
// `onBeforeTryTask` is run for every repetition and attempt of the test
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeTryTask', onBeforeTryTask => async function (task, retryInfo) {
if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
return onBeforeTryTask.apply(this, arguments)
}
const testName = getTestName(task)
let isNew = false
const providedContext = getProvidedContext()
const {
isKnownTestsEnabled,
isEarlyFlakeDetectionEnabled,
isDiEnabled,
slowTestRetries,
} = providedContext
if (isKnownTestsEnabled) {
isNew = newTasks.has(task)
}
const { retry: numAttempt, repeats: numRepetition } = retryInfo
const isFailedTestReplayAllowed = !hasConcurrentTests(task.file)
const isEfdManagedTask = isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)
if (isEfdManagedTask && numRepetition > 0 && !efdDeterminedRetries.has(task)) {
const previousExecutionStart = efdExecutionStartByTask.get(task)
const duration = previousExecutionStart === undefined
? task.result?.duration ?? 0
: performance.now() - previousExecutionStart
const retryCount = getEfdRetryCount(duration, slowTestRetries)
efdDeterminedRetries.set(task, retryCount)
task.repeats = retryCount
if (retryCount === 0) {
efdSlowAbortedTasks.add(task)
}
}
const efdRetryCount = efdDeterminedRetries.get(task)
if (isEfdManagedTask && efdRetryCount !== undefined && numRepetition > efdRetryCount) {
if (task.result) {
efdSkippedRetryResults.set(task, {
...task.result,
errors: task.result.errors?.slice(),
})
}
if (vitestSetFn) {
const noop = function () {}
noop.__ddTraceWrapped = true
vitestSetFn(task, noop)
}
return onBeforeTryTask.apply(this, arguments)
}
if (isEfdManagedTask) {
efdExecutionStartByTask.set(task, performance.now())
}
// We finish the previous test here because we know it has failed already
if (numAttempt > 0) {
const shouldWaitForHitProbe = isDiEnabled && isFailedTestReplayAllowed && numAttempt > 1
if (shouldWaitForHitProbe) {
await waitForHitProbe()
}
const promises = {}
const shouldSetProbe = isDiEnabled && isFailedTestReplayAllowed && numAttempt === 1
const ctx = taskToCtx.get(task)
const testError = getCurrentAttemptTestError(task, task.result?.errors)
if (ctx) {
testErrorCh.publish({
error: testError,
shouldSetProbe,
shouldWaitForHitProbe,
promises,
...ctx.currentStore,
})
// We wait for the probe to be set
if (promises.setProbePromise) {
await promises.setProbePromise
}
}
}
const lastExecutionStatus = task.result.state
const isAtf = attemptToFixTasks.has(task)
const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isAtf
const statuses = isAtf ? attemptToFixTaskToStatuses.get(task) : taskToStatuses.get(task)
// These clauses handle task.repeats, whether EFD is enabled or not
// The only thing that EFD does is to forcefully pass the test if it has passed at least once
if (numRepetition > 0 && numRepetition < task.repeats) { // it may or may have not failed
// Here we finish the earlier iteration,
// as long as it's not the _last_ iteration (which will be finished normally)
const ctx = taskToCtx.get(task)
if (ctx) {
if (lastExecutionStatus === 'fail') {
const testError = getCurrentAttemptTestError(task, task.result?.errors)
testErrorCh.publish({ error: testError, ...ctx.currentStore })
} else {
testPassCh.publish({ task, ...ctx.currentStore })
}
if (shouldTrackStatuses && statuses) {
statuses.push(lastExecutionStatus)
}
if (shouldFlipStatus) {
// If we don't "reset" the result.state to "pass", once a repetition fails,
// vitest will always consider the test as failed, so we can't read the actual status
// This means that we change vitest's behavior:
// if the last attempt passes, vitest would consider the test as failed
// but after this change, it will consider the test as passed
task.result.state = 'pass'
}
}
} else if (numRepetition === task.repeats) {
if (shouldTrackStatuses && statuses) {
statuses.push(lastExecutionStatus)
}
const ctx = taskToCtx.get(task)
if (lastExecutionStatus === 'fail') {
const testError = getCurrentAttemptTestError(task, task.result?.errors)
testErrorCh.publish({ error: testError, ...ctx.currentStore })
} else {
testPassCh.publish({ task, ...ctx.currentStore })
}
if (shouldFlipStatus) {
task.result.state = 'pass'
}
}
const isRetryReasonAtr = numAttempt > 0 &&
isFlakyTestRetriesEnabledForTask(providedContext, task) &&
!attemptToFixRetryTasks.has(task) &&
!efdRetryTasks.has(task)
const ctx = {
testName,
testSuiteAbsolutePath: task.file.filepath,
isRetry: numAttempt > 0 || numRepetition > 0,
isRetryReasonEfd: efdRetryTasks.has(task),
isRetryReasonAttemptToFix: attemptToFixRetryTasks.has(task) && numRepetition > 0,
isNew,
hasDynamicName: dynamicNameTasks.has(task),
mightHitProbe: isDiEnabled && isFailedTestReplayAllowed && numAttempt > 0,
isAttemptToFix: attemptToFixTasks.has(task),
isDisabled: disabledTasks.has(task),
isQuarantined: quarantinedTasks.has(task),
isRetryReasonAtr,
isModified: modifiedTasks.has(task),
}
taskToCtx.set(task, ctx)
if (attemptToFixTasks.has(task)) {
logAttemptToFixTestExecution(
getTaskTestSuite(task),
testName,
loggedAttemptToFixTests
)
}
testStartCh.runStores(ctx, () => {})
// Wrap the test function so it runs inside the test span context.
// Without this, HTTP requests during test execution become orphaned root spans.
if (vitestGetFn && vitestSetFn) {
const originalFn = vitestGetFn(task)
if (originalFn && !originalFn.__ddTraceWrapped) {
const wrappedFn = wrapTestScopedFn(task, originalFn)
wrappedFn.__ddTraceWrapped = true
vitestSetFn(task, wrappedFn)
}
}
// Wrap beforeEach/afterEach hooks so they also run inside the test span context.
// In vitest 4+, hooks are in a WeakMap accessed via getHooks(). In older versions, they're on suite.hooks.
let currentSuite = task.suite
while (currentSuite) {
const hooks = vitestGetHooks ? vitestGetHooks(currentSuite) : currentSuite.hooks
if (hooks) {
for (const hookType of ['beforeEach', 'afterEach']) {
const hookArray = hooks[hookType]
if (!hookArray) continue
for (let i = 0; i < hookArray.length; i++) {
const currentFn = hookArray[i]
if (originalHookFns.has(currentFn)) continue
const wrappedFn = wrapSuiteHookFn(hookType, currentFn, task)
originalHookFns.set(wrappedFn, currentFn)
hookArray[i] = wrappedFn
}
}
}
currentSuite = currentSuite.suite
}
return onBeforeTryTask.apply(this, arguments)
})
// test finish (only passed tests)
shimmer.wrap(VitestTestRunner.prototype, 'onAfterTryTask', onAfterTryTask =>
async function (task, retryInfo) {
if (!testPassCh.hasSubscribers && !testErrorCh.hasSubscribers && !testSkipCh.hasSubscribers) {
return onAfterTryTask.apply(this, arguments)
}
const result = await onAfterTryTask.apply(this, arguments)
const {
isEarlyFlakeDetectionEnabled,
testManagementAttemptToFixRetries,
slowTestRetries,
} = getProvidedContext()
const status = getVitestTestStatus(task, retryInfo.retry)
const ctx = taskToCtx.get(task)
const { isDiEnabled } = getProvidedContext()
const isFailedTestReplayAllowed = !hasConcurrentTests(task.file)
if (efdSkippedRetryResults.has(task)) {
task.result = efdSkippedRetryResults.get(task)
efdSkippedRetryResults.delete(task)
return result
}
if (isDiEnabled && isFailedTestReplayAllowed && retryInfo.retry > 1) {
await waitForHitProbe()
}
if (
isEarlyFlakeDetectionEnabled &&
(retryInfo.repeats ?? 0) === 0 &&
taskToStatuses.has(task) &&
!attemptToFixTasks.has(task) &&
!efdDeterminedRetries.has(task)
) {
const executionStart = efdExecutionStartByTask.get(task)
const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
const retryCount = getEfdRetryCount(duration, slowTestRetries)
efdDeterminedRetries.set(task, retryCount)
task.repeats = retryCount
if (retryCount === 0) {
efdSlowAbortedTasks.add(task)
}
}
let attemptToFixPassed = false
let attemptToFixFailed = false
if (attemptToFixTasks.has(task)) {
const statuses = attemptToFixTaskToStatuses.get(task)
if (statuses.length === testManagementAttemptToFixRetries) {
if (status === 'pass' && statuses.every(status => status === 'pass')) {
attemptToFixPassed = true
} else if (status === 'fail' || statuses.includes('fail')) {
attemptToFixFailed = true
}
}
}
if (ctx) {
// We don't finish here because the test might fail in a later hook (afterEach)
ctx.status = status
ctx.task = task
ctx.attemptToFixPassed = attemptToFixPassed
ctx.attemptToFixFailed = attemptToFixFailed
testFinishTimeCh.runStores(ctx, () => {})
}
return result
})
}
function captureRunnerFunctions (pkg) {
if (vitestGetFn) return
const getFnExport = findExportByName(pkg, 'getFn')
const setFnExport = findExportByName(pkg, 'setFn')
if (getFnExport && setFnExport) {
vitestGetFn = getFnExport.value
vitestSetFn = setFnExport.value
}
const getHooksExport = findExportByName(pkg, 'getHooks')
if (getHooksExport) {
vitestGetHooks = getHooksExport.value
}
}
addHook({
name: 'vitest',
versions: ['>=4.0.0'],
filePattern: 'dist/chunks/test.*',
}, (testPackage) => {
const testRunner = getTestRunnerExport(testPackage)
if (!testRunner) {
return testPackage
}
captureRunnerFunctions(testPackage)
wrapVitestTestRunner(testRunner.value)
return testPackage
})
addHook({
name: '@vitest/runner',
versions: ['>=1.6.0'],
}, (runnerModule) => {
if (!vitestGetFn && runnerModule.getFn && runnerModule.setFn) {
vitestGetFn = runnerModule.getFn
vitestSetFn = runnerModule.setFn
}
if (!vitestGetHooks && runnerModule.getHooks) {
vitestGetHooks = runnerModule.getHooks
}
return runnerModule
})
addHook({
name: 'vitest',
versions: ['>=1.6.0 <4.0.0'],
file: 'dist/runners.js',
}, (vitestPackage) => {
const { VitestTestRunner } = vitestPackage
wrapVitestTestRunner(VitestTestRunner)
return vitestPackage
})
// test suite start and finish
// only relevant for workers
addHook({
name: '@vitest/runner',
versions: ['>=1.6.0'],
}, (vitestPackage, frameworkVersion) => {
shimmer.wrap(vitestPackage, 'startTests', startTests => async function (testPaths) {
let testSuiteError = null
if (!testSuiteFinishCh.hasSubscribers) {
return startTests.apply(this, arguments)
}
// From >=3.0.1, the first arguments changes from a string to an object containing the filepath
const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
const providedContext = getProvidedContext()
const testSuiteCtx = {
testSuiteAbsolutePath,
frameworkVersion,
testSessionId: providedContext.testSessionId,
testModuleId: providedContext.testModuleId,
testCommand: providedContext.testCommand,
repositoryRoot: providedContext.repositoryRoot,
codeOwnersEntries: providedContext.codeOwnersEntries,
}
testSuiteStartCh.runStores(testSuiteCtx, () => {})
const startTestsResponse = await startTests.apply(this, arguments)
const testTasks = getTypeTasks(startTestsResponse[0].tasks)
const testEventPromises = []
// Only one test task per test, even if there are retries
for (const task of testTasks) {
const testCtx = taskToCtx.get(task)
const { result } = task
// We have to trick vitest into thinking that the test has passed
// but we want to report it as failed if it did fail
const isSwitchedStatus = switchedStatuses.has(task)
if (result) {
const { state, duration, errors } = result
const testError = getCurrentAttemptTestError(task, errors)
if (attemptToFixTasks.has(task)) {
const status = getFinalAttemptToFixStatus(task, state, isSwitchedStatus, testCtx)
recordFinalAttemptToFixExecution(task, status, providedContext)
}
if (state === 'skip') { // programmatic skip
testSkipCh.publish({
testName: getTestName(task),
testSuiteAbsolutePath: task.file.filepath,
isNew: newTasks.has(task),
isDisabled: disabledTasks.has(task),
})
} else if (state === 'pass' && !isSwitchedStatus) {
if (testCtx) {
const isSkippedByTestManagement =
!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
const promises = {}
testPassCh.publish({
task,
finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
promises,
...testCtx.currentStore,
})
if (promises.hitBreakpointPromise) {
testEventPromises.push(promises.hitBreakpointPromise)
}
}
} else if (state === 'fail' || isSwitchedStatus) {
let hasFailedAllRetries = false
let attemptToFixFailed = false
if (attemptToFixTasks.has(task)) {
const statuses = attemptToFixTaskToStatuses.get(task)
if (statuses.includes('fail')) {
attemptToFixFailed = true
}
if (statuses.every(status => status === 'fail')) {
hasFailedAllRetries = true
}
}
// Check if all EFD retries failed
const isEfdRetry =
providedContext.isEarlyFlakeDetectionEnabled && (newTasks.has(task) || modifiedTasks.has(task))
if (isEfdRetry) {
const statuses = taskToStatuses.get(task)
const efdRetryCount = efdDeterminedRetries.get(task) ?? providedContext.numRepeats
// statuses only includes repetitions (not the initial run), so we check against retry count (not +1)
if (efdRetryCount > 0 && statuses && statuses.length === efdRetryCount &&
statuses.every(status => status === 'fail')) {
hasFailedAllRetries = true
}
}
// ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
const isAtrRetry = isFlakyTestRetriesEnabledForTask(providedContext, task) && !attemptToFixTasks.has(task) &&
!newTasks.has(task) && !modifiedTasks.has(task)
if (isAtrRetry) {
const maxRetries = providedContext.flakyTestRetriesCount ?? 0
if (maxRetries > 0 && task.result?.retryCount === maxRetries) {
hasFailedAllRetries = true
}
}
if (testCtx) {
const isRetry = task.result?.retryCount > 0
const promises = {}
// `duration` is the duration of all the retries, so it can't be used if there are retries
let finalStatus
if (isSwitchedStatus) {
if (!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))) {
finalStatus = 'skip'
} else if (isAtrRetry || isEfdRetry) {
finalStatus = hasFailedAllRetries ? 'fail' : 'pass'
} else if (attemptToFixTasks.has(task)) {
finalStatus = attemptToFixFailed ? 'fail' : 'pass'
} else {
finalStatus = undefined
}
} else {
finalStatus = 'fail'
}
testErrorCh.publish({
duration: isRetry ? undefined : duration,
error: testError,
hasFailedAllRetries,
attemptToFixFailed,
finalStatus,
earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
promises,
...testCtx.currentStore,
})
if (promises.hitBreakpointPromise) {
testEventPromises.push(promises.hitBreakpointPromise)
}
}
if (errors?.length) {
testSuiteError = testError // we store the error to bubble it up to the suite
}
}
} else { // test.skip or test.todo
testSkipCh.publish({
testName: getTestName(task),
testSuiteAbsolutePath: task.file.filepath,
isNew: newTasks.has(task),
isDisabled: disabledTasks.has(task),
})
}
}
await Promise.all(testEventPromises)
const testSuiteResult = startTestsResponse[0].result
if (testSuiteResult.errors?.length) { // Errors from root level hooks
testSuiteError = testSuiteResult.errors[0]
} else if (testSuiteResult.state === 'fail') { // Errors from `describe` level hooks
const suiteTasks = getTypeTasks(startTestsResponse[0].tasks, 'suite')
const failedSuites = suiteTasks.filter(task => task.result?.state === 'fail')
if (failedSuites.length && failedSuites[0].result?.errors?.length) {
testSuiteError = failedSuites[0].result.errors[0]
}
}
if (testSuiteError) {
testSuiteCtx.error = testSuiteError
testSuiteErrorCh.runStores(testSuiteCtx, () => {})
}
await getChannelPromise(testSuiteFinishCh, {
status: testSuiteResult.state,
...testSuiteCtx.currentStore,
})
return startTestsResponse
})
return vitestPackage
})