UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

431 lines (374 loc) 13.9 kB
import { realpathSync } from 'node:fs' import { relative } from 'node:path' import { performance } from 'node:perf_hooks' import { afterEach, beforeAll, beforeEach } from 'vitest' // Instrumentation-less worker setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT. // It applies Test Optimization execution changes without initializing dd-trace in Vitest workers. const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE' const isNoWorkerInitActive = getIsNoWorkerInitActive() const providedContext = isNoWorkerInitActive ? getProvidedContext() : {} const attemptToFixTests = providedContext.attemptToFixTests || {} const attemptToFixRetries = providedContext.attemptToFixRetries || 0 const disabledTests = providedContext.disabledTests || {} const earlyFlakeDetectionRetries = providedContext.earlyFlakeDetectionRetries || 0 const earlyFlakeDetectionRetryThresholds = Array.isArray(providedContext.earlyFlakeDetectionRetryThresholds) ? providedContext.earlyFlakeDetectionRetryThresholds : [] const earlyFlakeDetectionSlowRetries = providedContext.earlyFlakeDetectionSlowRetries || {} const hasEarlyFlakeDetectionSlowRetries = Object.keys(earlyFlakeDetectionSlowRetries).length > 0 const isEarlyFlakeDetectionEnabled = providedContext.isEarlyFlakeDetectionEnabled === true const knownTests = providedContext.knownTests || {} const modifiedFiles = providedContext.modifiedFiles || {} const quarantinedTests = providedContext.quarantinedTests || {} const repositoryRoot = realpath(providedContext.repositoryRoot || process.cwd()) let setVitestTaskFn if (isNoWorkerInitActive) { try { // Vitest does not expose setFn from the public setup API; keep this optional for strict installers. const vitestRunner = await import('@vitest/runner') setVitestTaskFn = vitestRunner.setFn } catch {} } const earlyFlakeDetectionRetriesByTask = new WeakMap() const earlyFlakeDetectionSkippedResults = new WeakMap() const earlyFlakeDetectionStartByTask = new WeakMap() const nextAttemptIndexByTask = new WeakMap() const realpaths = new Map() if (isNoWorkerInitActive) { // eslint-disable-next-line no-empty-pattern beforeAll(function ({}, suite) { suite ||= arguments[0] applyExecutionChanges(suite) }) beforeEach(function ({ onTestFinished, task, skip }) { const testSuite = getTestSuite(task) const testName = getTestName(task) const isAttemptToFixTest = attemptToFixTests[testSuite]?.[testName] const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTest(testSuite, testName) const isQuarantinedTest = quarantinedTests[testSuite]?.[testName] && !isAttemptToFixTest const attemptIndex = getNextAttemptIndex(task) if (attemptIndex > 0) { recordTestOptimizationStatus(task, attemptIndex - 1) } if (disabledTests[testSuite]?.[testName] && !isAttemptToFixTest) { skip('Skipped by Datadog Test Optimization') } else if (isAttemptToFixTest && attemptIndex > 0) { task.result.state = 'run' } else if (isEarlyFlakeDetectionTestAttempt) { const isSkippedRepeat = prepareEarlyFlakeDetectionAttempt(task, attemptIndex) if (!isSkippedRepeat && attemptIndex > 0) { task.result.state = 'run' } } if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) { onTestFinished(() => { if (attemptIndex === getFinalAttemptIndex(task)) { if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) { recordTestOptimizationStatus(task, attemptIndex, true) } switchQuarantinedFinalFailure(task, attemptIndex) } }) } }) afterEach(function ({ task }) { const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex if (restoreEarlyFlakeDetectionSkippedResult(task)) { return } if (attemptIndex === getFinalAttemptIndex(task)) { recordTestOptimizationStatus(task, attemptIndex) } switchQuarantinedFinalFailure(task, attemptIndex) }) } function applyExecutionChanges (suite) { for (const task of suite?.tasks || []) { if (task.type === 'suite') { applyExecutionChanges(task) continue } const testSuite = getTestSuite(task) const testName = getTestName(task) if (attemptToFixTests[testSuite]?.[testName]) { task.retry = 0 task.repeats = attemptToFixRetries task.meta.__ddTestOptAtfRetries = attemptToFixRetries } else if (disabledTests[testSuite]?.[testName]) { task.mode = 'skip' } else if (isEarlyFlakeDetectionTest(testSuite, testName)) { task.retry = 0 task.repeats = earlyFlakeDetectionRetries task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries } } } function getNextAttemptIndex (task) { const attemptIndex = nextAttemptIndexByTask.get(task) || 0 nextAttemptIndexByTask.set(task, attemptIndex + 1) task.meta.__ddTestOptCurrentAttemptIndex = attemptIndex return attemptIndex } function recordTestOptimizationStatus (task, attemptIndex = task.result?.repeatCount || 0, onlyIfNewErrors = false) { const testSuite = getTestSuite(task) const testName = getTestName(task) if (attemptToFixTests[testSuite]?.[testName]) { recordAttemptToFixStatus(task, attemptIndex, onlyIfNewErrors) } else if (isEarlyFlakeDetectionTest(testSuite, testName)) { recordEarlyFlakeDetectionStatus(task, attemptIndex, onlyIfNewErrors) } else if (task.repeats > 0) { recordManualRepeatStatus(task, attemptIndex) } } function recordAttemptToFixStatus (task, attemptIndex, onlyIfNewErrors) { if (onlyIfNewErrors && !hasNewErrors(task.meta.__ddTestOptAtfErrorCounts, attemptIndex, task)) { return } task.meta.__ddTestOptAtfStatuses ||= [] task.meta.__ddTestOptAtfErrorCounts ||= [] task.meta.__ddTestOptAtfStatuses[attemptIndex] = getAttemptStatus( task, task.meta.__ddTestOptAtfErrorCounts, attemptIndex ) task.meta.__ddTestOptAtfErrorCounts[attemptIndex] = task.result?.errors?.length || 0 if ( attemptIndex === getAttemptToFixRetryCount(task) && task.meta.__ddTestOptAtfStatuses.includes('fail') && task.result?.state === 'pass' ) { task.result.state = 'fail' } } function recordEarlyFlakeDetectionStatus (task, attemptIndex, onlyIfNewErrors) { if (restoreEarlyFlakeDetectionSkippedResult(task)) { return } const retryCount = earlyFlakeDetectionRetriesByTask.get(task) if (retryCount !== undefined && attemptIndex > retryCount) { return } if (onlyIfNewErrors && !hasNewErrors(task.meta.__ddTestOptEfdErrorCounts, attemptIndex, task)) { return } if (!earlyFlakeDetectionRetriesByTask.has(task)) { const retryCount = getEarlyFlakeDetectionRetryCount(task) earlyFlakeDetectionRetriesByTask.set(task, retryCount) task.repeats = retryCount task.meta.__ddTestOptEfdRetries = retryCount if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) { task.meta.__ddTestOptEfdAbortReason = 'slow' } } task.meta.__ddTestOptEfdStatuses ||= [] task.meta.__ddTestOptEfdErrorCounts ||= [] task.meta.__ddTestOptEfdStatuses[attemptIndex] = getAttemptStatus( task, task.meta.__ddTestOptEfdErrorCounts, attemptIndex ) task.meta.__ddTestOptEfdErrorCounts[attemptIndex] = task.result?.errors?.length || 0 if (attemptIndex === getEarlyFlakeDetectionRetryCountForTask(task) && task.meta.__ddTestOptEfdStatuses.includes('pass')) { task.result.state = 'pass' } } function recordManualRepeatStatus (task, attemptIndex) { task.meta.__ddTestOptRepeatStatuses ||= [] task.meta.__ddTestOptRepeatErrorCounts ||= [] task.meta.__ddTestOptRepeatStatuses[attemptIndex] = getManualRepeatStatus( task, task.meta.__ddTestOptRepeatErrorCounts, attemptIndex ) task.meta.__ddTestOptRepeatErrorCounts[attemptIndex] = task.result?.errors?.length || 0 } function restoreEarlyFlakeDetectionSkippedResult (task) { if (!task.meta.__ddTestOptEfdSkipCurrentAttempt) { return false } delete task.meta.__ddTestOptEfdSkipCurrentAttempt const skippedResult = earlyFlakeDetectionSkippedResults.get(task) if (skippedResult) { task.result = skippedResult earlyFlakeDetectionSkippedResults.delete(task) } return true } function hasNewErrors (errorCounts, attemptIndex, task) { const recordedErrorCount = errorCounts?.[attemptIndex] const previousErrorCount = recordedErrorCount ?? getPreviousErrorCount(errorCounts, attemptIndex) return (task.result?.errors?.length || 0) > previousErrorCount } function getFinalAttemptIndex (task) { const testSuite = getTestSuite(task) const testName = getTestName(task) if (attemptToFixTests[testSuite]?.[testName]) { return getAttemptToFixRetryCount(task) } if (isEarlyFlakeDetectionTest(testSuite, testName)) { return getEarlyFlakeDetectionRetryCountForTask(task) } return task.repeats } function switchQuarantinedFinalFailure (task, attemptIndex) { const testSuite = getTestSuite(task) const testName = getTestName(task) if ( !quarantinedTests[testSuite]?.[testName] || attemptToFixTests[testSuite]?.[testName] || task.result?.state !== 'fail' ) { return } const retryCount = task.result?.retryCount || 0 const retryLimit = task.retry || 0 if (retryCount < retryLimit || attemptIndex < getFinalAttemptIndex(task)) { return } task.meta.__ddTestOptQuarantinedFailed = true task.result.state = 'pass' } function getAttemptToFixRetryCount (task) { return task.meta.__ddTestOptAtfRetries ?? task.repeats } function getEarlyFlakeDetectionRetryCountForTask (task) { return earlyFlakeDetectionRetriesByTask.get(task) ?? task.meta.__ddTestOptEfdRetries ?? task.repeats } function getAttemptStatus (task, errorCounts, repeatCount) { const errorCount = task.result?.errors?.length || 0 if (errorCount > getPreviousErrorCount(errorCounts, repeatCount)) { return 'fail' } return task.result?.state === 'fail' ? 'fail' : 'pass' } function getManualRepeatStatus (task, errorCounts, repeatCount) { const errorCount = task.result?.errors?.length || 0 return errorCount > getPreviousErrorCount(errorCounts, repeatCount) ? 'fail' : 'pass' } function getPreviousErrorCount (errorCounts, repeatCount) { for (let index = repeatCount - 1; index >= 0; index--) { if (errorCounts[index] !== undefined) { return errorCounts[index] } } return 0 } function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) { if (attemptIndex === 0) { earlyFlakeDetectionStartByTask.set(task, performance.now()) return false } let retryCount = earlyFlakeDetectionRetriesByTask.get(task) if (retryCount === undefined) { retryCount = getEarlyFlakeDetectionRetryCount(task) earlyFlakeDetectionRetriesByTask.set(task, retryCount) task.repeats = retryCount task.meta.__ddTestOptEfdRetries = retryCount if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) { task.meta.__ddTestOptEfdAbortReason = 'slow' } } if (attemptIndex <= retryCount) { earlyFlakeDetectionStartByTask.set(task, performance.now()) return false } if (!canReplaceVitestTaskFn()) { earlyFlakeDetectionStartByTask.set(task, performance.now()) return false } if (task.result) { earlyFlakeDetectionSkippedResults.set(task, { ...task.result, errors: task.result.errors?.slice(), }) } task.meta.__ddTestOptEfdSkipCurrentAttempt = true replaceVitestTaskFn(task, noopTest) return true } function getEarlyFlakeDetectionRetryCount (task) { if (!hasEarlyFlakeDetectionSlowRetries) { return earlyFlakeDetectionRetries } const executionStart = earlyFlakeDetectionStartByTask.get(task) const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart for (const { key, limitMs } of earlyFlakeDetectionRetryThresholds) { if (duration < limitMs) { return earlyFlakeDetectionSlowRetries[key] ?? 0 } } return 0 } function noopTest () {} /** * Returns whether Vitest's private task function setter is available. * * @returns {boolean} */ function canReplaceVitestTaskFn () { return typeof setVitestTaskFn === 'function' } /** * Replaces the function Vitest runs for a task. * * @param {object} task * @param {(...args: unknown[]) => unknown} testFn */ function replaceVitestTaskFn (task, testFn) { setVitestTaskFn(task, testFn) } function isEarlyFlakeDetectionTest (testSuite, testName) { if (!isEarlyFlakeDetectionEnabled || earlyFlakeDetectionRetries <= 0) return false if (isModifiedTest(testSuite)) return true const testsForSuite = knownTests[testSuite] || [] return !testsForSuite.includes(testName) } function isModifiedTest (testSuite) { return modifiedFiles[testSuite]?.length > 0 } function getTestSuite (task) { let filepath = realpaths.get(task.file.filepath) if (filepath === undefined) { filepath = realpath(task.file.filepath) realpaths.set(task.file.filepath, filepath) } return normalizePath(relative(repositoryRoot, filepath)) } function realpath (filepath) { try { return realpathSync(filepath) } catch { return filepath } } function getTestName (task) { let testName = task.name let currentTask = task.suite while (currentTask) { if (currentTask.name) { testName = `${currentTask.name} ${testName}` } currentTask = currentTask.suite } return testName } function normalizePath (filepath) { return filepath.replaceAll('\\', '/') } function getIsNoWorkerInitActive () { try { // eslint-disable-next-line eslint-rules/eslint-process-env const value = process.env[VITEST_NO_WORKER_INIT_ACTIVE_ENV] return value === '1' || value === 'true' } catch { return false } } function getProvidedContext () { try { return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {} } catch { return {} } }