Skip to content

Commit 70b9dba

Browse files
[test optimization] Report suppressed errors (#7526)
1 parent bc2afc0 commit 70b9dba

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

integration-tests/jest/jest.spec.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,12 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
29112911
assert.match(testOutput, /2 failed, 2 passed/)
29122912
// Exit code is 0 because at least one retry of the new flaky test passes
29132913
assert.strictEqual(exitCode, 0)
2914+
2915+
// Verify Datadog Test Optimization message is shown when exit code is flipped
2916+
assert.match(testOutput, /Datadog Test Optimization/)
2917+
assert.match(testOutput, /\d+ test failure\(s\) were ignored\. Exit code set to 0\./)
2918+
assert.match(testOutput, /Early Flake Detection/)
2919+
assert.match(testOutput, /occasionally-failing-test.*.*fail occasionally fails/)
29142920
})
29152921

29162922
// resetting snapshot state logic only works in latest versions
@@ -5416,6 +5422,11 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
54165422
if (isQuarantining) {
54175423
// even though a test fails, the exit code is 0 because the test is quarantined
54185424
assert.strictEqual(exitCode, 0)
5425+
// Verify Datadog Test Optimization message is shown when exit code is flipped
5426+
assert.match(stdout, /Datadog Test Optimization/)
5427+
assert.match(stdout, /\d+ test failure\(s\) were ignored\. Exit code set to 0\./)
5428+
assert.match(stdout, /Quarantine/)
5429+
assert.match(stdout, /test-quarantine-1.*.*quarantine tests can quarantine a test/)
54195430
} else {
54205431
assert.strictEqual(exitCode, 1)
54215432
}

packages/datadog-instrumentations/src/jest.js

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,41 @@ function getTestEnvironmentOptions (config) {
129129
return {}
130130
}
131131

132+
const MAX_IGNORED_TEST_NAMES = 10
133+
132134
function getTestStats (testStatuses) {
133135
return testStatuses.reduce((acc, testStatus) => {
134136
acc[testStatus]++
135137
return acc
136138
}, { pass: 0, fail: 0 })
137139
}
138140

141+
/**
142+
* @param {string[]} efdNames
143+
* @param {string[]} quarantineNames
144+
* @param {number} totalCount
145+
*/
146+
function logIgnoredFailuresSummary (efdNames, quarantineNames, totalCount) {
147+
const names = []
148+
for (const n of efdNames) {
149+
names.push({ name: n, reason: 'Early Flake Detection' })
150+
}
151+
for (const n of quarantineNames) {
152+
names.push({ name: n, reason: 'Quarantine' })
153+
}
154+
const shown = names.slice(0, MAX_IGNORED_TEST_NAMES)
155+
const more = names.length - shown.length
156+
const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
157+
const list = shown.map(({ name, reason }) => ` • ${name} (${reason})`).join('\n')
158+
const line = '-'.repeat(50)
159+
// eslint-disable-next-line no-console -- Intentional user-facing message when exit code is flipped
160+
console.warn(
161+
`\n${line}\nDatadog Test Optimization\n${line}\n` +
162+
`${totalCount} test failure(s) were ignored. Exit code set to 0.\n\n` +
163+
`${list}${moreSuffix}\n`
164+
)
165+
}
166+
139167
function getWrappedEnvironment (BaseEnvironment, jestVersion) {
140168
return class DatadogEnvironment extends BaseEnvironment {
141169
constructor (config, context) {
@@ -1025,11 +1053,30 @@ function getCliWrapper (isNewJestVersion) {
10251053
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
10261054
*/
10271055
let numEfdFailedTestsToIgnore = 0
1056+
const efdIgnoredNames = []
1057+
const quarantineIgnoredNames = []
1058+
1059+
// Build fullName -> suite map from results (for EFD display)
1060+
const fullNameToSuite = new Map()
1061+
for (const { testResults, testFilePath } of result.results.testResults) {
1062+
const suite = getTestSuitePath(testFilePath, result.globalConfig.rootDir)
1063+
for (const { fullName } of testResults) {
1064+
const name = testSuiteAbsolutePathsWithFastCheck.has(testFilePath)
1065+
? fullName.replace(SEED_SUFFIX_RE, '')
1066+
: fullName
1067+
fullNameToSuite.set(name, suite)
1068+
}
1069+
}
1070+
1071+
/** @type {{ efdNames: string[], quarantineNames: string[], totalCount: number } | undefined} */
1072+
let ignoredFailuresSummary
10281073
if (isEarlyFlakeDetectionEnabled) {
1029-
for (const testStatuses of newTestsTestStatuses.values()) {
1074+
for (const [testName, testStatuses] of newTestsTestStatuses) {
10301075
const { pass, fail } = getTestStats(testStatuses)
10311076
if (pass > 0) { // as long as one passes, we'll consider the test passed
10321077
numEfdFailedTestsToIgnore += fail
1078+
const suite = fullNameToSuite.get(testName)
1079+
efdIgnoredNames.push(suite ? `${suite}${testName}` : testName)
10331080
}
10341081
}
10351082
// If every test that failed was an EFD retry, we'll consider the suite passed
@@ -1039,6 +1086,11 @@ function getCliWrapper (isNewJestVersion) {
10391086
result.results.numFailedTests === numEfdFailedTestsToIgnore
10401087
) {
10411088
result.results.success = true
1089+
ignoredFailuresSummary = {
1090+
efdNames: efdIgnoredNames,
1091+
quarantineNames: [],
1092+
totalCount: numEfdFailedTestsToIgnore,
1093+
}
10421094
}
10431095
}
10441096

@@ -1073,22 +1125,31 @@ function getCliWrapper (isNewJestVersion) {
10731125
// This uses `attempt_to_fix` because this is always the main process and it's not formatted in camelCase
10741126
if (testManagementTest?.attempt_to_fix && (testManagementTest?.quarantined || testManagementTest?.disabled)) {
10751127
numFailedQuarantinedOrDisabledAttemptedToFixTests++
1128+
quarantineIgnoredNames.push(`${testSuite}${testName}`)
10761129
} else if (testManagementTest?.quarantined) {
10771130
numFailedQuarantinedTests++
1131+
quarantineIgnoredNames.push(`${testSuite}${testName}`)
10781132
}
10791133
}
10801134

10811135
// If every test that failed was quarantined, we'll consider the suite passed
10821136
// Note that if a test is attempted to fix,
10831137
// it's considered quarantined both if it's disabled and if it's quarantined
10841138
// (it'll run but its status is ignored)
1139+
// Skip if EFD block already flipped (to avoid logging twice)
10851140
if (
1141+
!result.results.success &&
10861142
!mustNotFlipSuccess &&
10871143
(numFailedQuarantinedOrDisabledAttemptedToFixTests !== 0 || numFailedQuarantinedTests !== 0) &&
10881144
result.results.numFailedTests ===
10891145
numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
10901146
) {
10911147
result.results.success = true
1148+
ignoredFailuresSummary = {
1149+
efdNames: [],
1150+
quarantineNames: quarantineIgnoredNames,
1151+
totalCount: numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests,
1152+
}
10921153
}
10931154
}
10941155

@@ -1106,6 +1167,11 @@ function getCliWrapper (isNewJestVersion) {
11061167
result.results.numFailedTests === totalIgnoredFailures
11071168
) {
11081169
result.results.success = true
1170+
ignoredFailuresSummary = {
1171+
efdNames: efdIgnoredNames,
1172+
quarantineNames: quarantineIgnoredNames,
1173+
totalCount: totalIgnoredFailures,
1174+
}
11091175
}
11101176
}
11111177

@@ -1163,6 +1229,14 @@ function getCliWrapper (isNewJestVersion) {
11631229
})
11641230
}
11651231

1232+
if (ignoredFailuresSummary) {
1233+
logIgnoredFailuresSummary(
1234+
ignoredFailuresSummary.efdNames,
1235+
ignoredFailuresSummary.quarantineNames,
1236+
ignoredFailuresSummary.totalCount
1237+
)
1238+
}
1239+
11661240
numSkippedSuites = 0
11671241

11681242
return result

0 commit comments

Comments
 (0)