Skip to content

Commit 91205a5

Browse files
[test optimization] suppress quarantined test errors to prevent Jest --bail from stopping execution (#8015)
1 parent 639037f commit 91205a5

4 files changed

Lines changed: 379 additions & 15 deletions

File tree

integration-tests/ci-visibility/run-jest.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ if (process.env.WORKER_IDLE_MEMORY_LIMIT) {
5959
options.workerIdleMemoryLimit = Number(process.env.WORKER_IDLE_MEMORY_LIMIT)
6060
}
6161

62+
if (process.env.JEST_BAIL) {
63+
options.bail = true
64+
}
65+
6266
jest.runCLI(
6367
options,
6468
options.projects

integration-tests/jest/jest.spec.js

Lines changed: 269 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6370,7 +6370,10 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
63706370
}
63716371
)
63726372

6373-
// jest uses stderr to output logs
6373+
// jest uses stderr to output logs, stdout for console.log from tests
6374+
childProcess.stdout?.on('data', (chunk) => {
6375+
stdout += chunk.toString()
6376+
})
63746377
childProcess.stderr?.on('data', (chunk) => {
63756378
stdout += chunk.toString()
63766379
})
@@ -6382,9 +6385,9 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
63826385
if (isQuarantining) {
63836386
// even though a test fails, the exit code is 0 because the test is quarantined
63846387
assert.strictEqual(exitCode, 0)
6385-
// Verify Datadog Test Optimization message is shown when exit code is flipped
6388+
// Verify Datadog Test Optimization message is shown for suppressed quarantine failures
63866389
assert.match(stdout, /Datadog Test Optimization/)
6387-
assert.match(stdout, /\d+ test failure\(s\) were ignored\. Exit code set to 0\./)
6390+
assert.match(stdout, /\d+ test failure\(s\) were ignored/)
63886391
assert.match(stdout, /Quarantine/)
63896392
assert.match(stdout, /test-quarantine-1.*.*quarantine tests can quarantine a test/)
63906393
} else {
@@ -6769,6 +6772,269 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
67696772
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), testAssertionsPromise])
67706773
assert.strictEqual(exitCode, 1, 'exit code 1 when suite fails (resolution error)')
67716774
})
6775+
6776+
it('does not bail on quarantined test failures', async () => {
6777+
receiver.setSettings({ test_management: { enabled: true } })
6778+
6779+
const eventsPromise = receiver
6780+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
6781+
const events = payloads.flatMap(({ payload }) => payload.events)
6782+
const tests = events.filter(event => event.type === 'test').map(event => event.content)
6783+
const testSession = events.find(event => event.type === 'test_session_end').content
6784+
6785+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
6786+
assert.strictEqual(testSession.meta[TEST_MANAGEMENT_ENABLED], 'true')
6787+
6788+
const resourceNames = tests.map(span => span.resource)
6789+
6790+
// Both suites must have run — bail should not have stopped execution
6791+
assertObjectContains(resourceNames, [
6792+
'ci-visibility/test-management/test-quarantine-1.js.quarantine tests can quarantine a test',
6793+
'ci-visibility/test-management/test-quarantine-1.js.quarantine tests can pass normally',
6794+
'ci-visibility/test-management/test-quarantine-2.js.quarantine tests 2 can quarantine a test',
6795+
'ci-visibility/test-management/test-quarantine-2.js.quarantine tests 2 can pass normally',
6796+
])
6797+
6798+
// The quarantined test should still report as failed with quarantine tag
6799+
const failedTest = tests.find(
6800+
test => test.meta[TEST_NAME] === 'quarantine tests can quarantine a test' &&
6801+
test.resource.includes('test-quarantine-1')
6802+
)
6803+
assert.strictEqual(failedTest.meta[TEST_STATUS], 'fail')
6804+
assert.strictEqual(failedTest.meta[TEST_MANAGEMENT_IS_QUARANTINED], 'true')
6805+
6806+
// The test suite containing the quarantined test should be reported as passed
6807+
const suites = events.filter(event => event.type === 'test_suite_end').map(event => event.content)
6808+
const quarantineSuite = suites.find(
6809+
s => s.meta[TEST_SUITE]?.includes('test-quarantine-1')
6810+
)
6811+
assert.strictEqual(quarantineSuite.meta[TEST_STATUS], 'pass')
6812+
})
6813+
6814+
let stderr = ''
6815+
childProcess = exec(
6816+
runTestsCommand,
6817+
{
6818+
cwd,
6819+
env: {
6820+
...getCiVisAgentlessConfig(receiver.port),
6821+
TESTS_TO_RUN: 'test-management/test-quarantine',
6822+
JEST_BAIL: '1',
6823+
SHOULD_CHECK_RESULTS: '1',
6824+
},
6825+
}
6826+
)
6827+
childProcess.stderr?.on('data', (chunk) => {
6828+
stderr += chunk.toString()
6829+
})
6830+
6831+
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), eventsPromise])
6832+
6833+
// With quarantine suppressing errors, Jest should not see failures,
6834+
// so bail should not stop the second suite from running
6835+
assert.match(stderr, /Test Suites:.*2 passed/)
6836+
assert.strictEqual(exitCode, 0)
6837+
})
6838+
6839+
it('does not bail on quarantined test failures when ATR is enabled', async () => {
6840+
receiver.setSettings({
6841+
test_management: { enabled: true },
6842+
flaky_test_retries_enabled: true,
6843+
})
6844+
6845+
const eventsPromise = receiver
6846+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
6847+
const events = payloads.flatMap(({ payload }) => payload.events)
6848+
const tests = events.filter(event => event.type === 'test').map(event => event.content)
6849+
const testSession = events.find(event => event.type === 'test_session_end').content
6850+
6851+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
6852+
6853+
const resourceNames = tests.map(span => span.resource)
6854+
6855+
// Both suites must have run — bail should not have stopped execution
6856+
assertObjectContains(resourceNames, [
6857+
'ci-visibility/test-management/test-quarantine-1.js.quarantine tests can quarantine a test',
6858+
'ci-visibility/test-management/test-quarantine-2.js.quarantine tests 2 can quarantine a test',
6859+
])
6860+
6861+
// ATR should have retried the quarantined test
6862+
const quarantinedTests = tests.filter(
6863+
test => test.meta[TEST_NAME] === 'quarantine tests can quarantine a test' &&
6864+
test.resource.includes('test-quarantine-1')
6865+
)
6866+
assert.ok(quarantinedTests.length > 1, 'quarantined test should have been retried by ATR')
6867+
for (const test of quarantinedTests) {
6868+
assert.strictEqual(test.meta[TEST_MANAGEMENT_IS_QUARANTINED], 'true')
6869+
}
6870+
})
6871+
6872+
let stderr = ''
6873+
childProcess = exec(
6874+
runTestsCommand,
6875+
{
6876+
cwd,
6877+
env: {
6878+
...getCiVisAgentlessConfig(receiver.port),
6879+
TESTS_TO_RUN: 'test-management/test-quarantine',
6880+
JEST_BAIL: '1',
6881+
SHOULD_CHECK_RESULTS: '1',
6882+
},
6883+
}
6884+
)
6885+
childProcess.stderr?.on('data', (chunk) => {
6886+
stderr += chunk.toString()
6887+
})
6888+
6889+
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), eventsPromise])
6890+
6891+
assert.match(stderr, /Test Suites:.*2/)
6892+
assert.strictEqual(exitCode, 0)
6893+
})
6894+
6895+
it('does not bail on quarantined + attempt to fix test failures', async () => {
6896+
receiver.setSettings({
6897+
test_management: { enabled: true, attempt_to_fix_retries: 2 },
6898+
})
6899+
6900+
receiver.setTestManagementTests({
6901+
jest: {
6902+
suites: {
6903+
'ci-visibility/test-management/test-quarantine-1.js': {
6904+
tests: {
6905+
'quarantine tests can quarantine a test': {
6906+
properties: {
6907+
quarantined: true,
6908+
attempt_to_fix: true,
6909+
},
6910+
},
6911+
},
6912+
},
6913+
},
6914+
},
6915+
})
6916+
6917+
const eventsPromise = receiver
6918+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
6919+
const events = payloads.flatMap(({ payload }) => payload.events)
6920+
const tests = events.filter(event => event.type === 'test').map(event => event.content)
6921+
const testSession = events.find(event => event.type === 'test_session_end').content
6922+
6923+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
6924+
6925+
const resourceNames = tests.map(span => span.resource)
6926+
6927+
// Both suites must have run — bail should not have stopped execution
6928+
assertObjectContains(resourceNames, [
6929+
'ci-visibility/test-management/test-quarantine-1.js.quarantine tests can quarantine a test',
6930+
'ci-visibility/test-management/test-quarantine-2.js.quarantine tests 2 can quarantine a test',
6931+
])
6932+
6933+
// ATF should have retried the test
6934+
const quarantinedTests = tests.filter(
6935+
test => test.meta[TEST_NAME] === 'quarantine tests can quarantine a test' &&
6936+
test.resource.includes('test-quarantine-1')
6937+
)
6938+
assert.ok(quarantinedTests.length > 1, 'quarantined test should have been retried by ATF')
6939+
6940+
const atfRetries = quarantinedTests.filter(
6941+
t => t.meta[TEST_RETRY_REASON] === TEST_RETRY_REASON_TYPES.atf
6942+
)
6943+
assert.ok(atfRetries.length > 0, 'should have ATF retries')
6944+
})
6945+
6946+
let stderr = ''
6947+
childProcess = exec(
6948+
runTestsCommand,
6949+
{
6950+
cwd,
6951+
env: {
6952+
...getCiVisAgentlessConfig(receiver.port),
6953+
TESTS_TO_RUN: 'test-management/test-quarantine',
6954+
JEST_BAIL: '1',
6955+
SHOULD_CHECK_RESULTS: '1',
6956+
},
6957+
}
6958+
)
6959+
childProcess.stderr?.on('data', (chunk) => {
6960+
stderr += chunk.toString()
6961+
})
6962+
6963+
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), eventsPromise])
6964+
6965+
assert.match(stderr, /Test Suites:.*2/)
6966+
assert.strictEqual(exitCode, 0)
6967+
})
6968+
6969+
it('does not bail when quarantine, ATR, and attempt to fix are all enabled', async () => {
6970+
receiver.setSettings({
6971+
test_management: { enabled: true, attempt_to_fix_retries: 2 },
6972+
flaky_test_retries_enabled: true,
6973+
})
6974+
6975+
receiver.setTestManagementTests({
6976+
jest: {
6977+
suites: {
6978+
'ci-visibility/test-management/test-quarantine-1.js': {
6979+
tests: {
6980+
'quarantine tests can quarantine a test': {
6981+
properties: {
6982+
quarantined: true,
6983+
attempt_to_fix: true,
6984+
},
6985+
},
6986+
},
6987+
},
6988+
},
6989+
},
6990+
})
6991+
6992+
const eventsPromise = receiver
6993+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
6994+
const events = payloads.flatMap(({ payload }) => payload.events)
6995+
const tests = events.filter(event => event.type === 'test').map(event => event.content)
6996+
const testSession = events.find(event => event.type === 'test_session_end').content
6997+
6998+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
6999+
7000+
const resourceNames = tests.map(span => span.resource)
7001+
7002+
// Both suites must have run — bail should not have stopped execution
7003+
assertObjectContains(resourceNames, [
7004+
'ci-visibility/test-management/test-quarantine-1.js.quarantine tests can quarantine a test',
7005+
'ci-visibility/test-management/test-quarantine-2.js.quarantine tests 2 can quarantine a test',
7006+
])
7007+
7008+
// The test should have been retried (by ATF since it takes precedence over ATR)
7009+
const quarantinedTests = tests.filter(
7010+
test => test.meta[TEST_NAME] === 'quarantine tests can quarantine a test' &&
7011+
test.resource.includes('test-quarantine-1')
7012+
)
7013+
assert.ok(quarantinedTests.length > 1, 'quarantined test should have been retried')
7014+
})
7015+
7016+
let stderr = ''
7017+
childProcess = exec(
7018+
runTestsCommand,
7019+
{
7020+
cwd,
7021+
env: {
7022+
...getCiVisAgentlessConfig(receiver.port),
7023+
TESTS_TO_RUN: 'test-management/test-quarantine',
7024+
JEST_BAIL: '1',
7025+
SHOULD_CHECK_RESULTS: '1',
7026+
},
7027+
}
7028+
)
7029+
childProcess.stderr?.on('data', (chunk) => {
7030+
stderr += chunk.toString()
7031+
})
7032+
7033+
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), eventsPromise])
7034+
7035+
assert.match(stderr, /Test Suites:.*2/)
7036+
assert.strictEqual(exitCode, 0)
7037+
})
67727038
})
67737039

67747040
it('does not crash if the request to get test management tests fails', async () => {

0 commit comments

Comments
 (0)