Skip to content

Commit 5572149

Browse files
[test optimization] Fix test.status reported in test sessions including quarantined tests (#7353)
1 parent e67b47e commit 5572149

File tree

3 files changed

+181
-87
lines changed

3 files changed

+181
-87
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
const assert = require('assert')
4+
describe('partial quarantine tests', () => {
5+
it('quarantined failing test', () => {
6+
// This test fails but is quarantined, so it should not affect the session status
7+
assert.strictEqual(1 + 2, 4)
8+
})
9+
10+
it('non-quarantined failing test', () => {
11+
// This test fails and is NOT quarantined, so it should cause the session to fail
12+
assert.strictEqual(1 + 2, 5)
13+
})
14+
15+
it('passing test', () => {
16+
assert.strictEqual(1 + 2, 3)
17+
})
18+
})

integration-tests/jest/jest.spec.js

Lines changed: 114 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2594,7 +2594,7 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
25942594
})
25952595
})
25962596

2597-
it('retries flaky tests and sets exit code to 0 as long as one attempt passes', (done) => {
2597+
it('retries flaky tests and sets exit code to 0 as long as one attempt passes', async () => {
25982598
receiver.setInfoResponse({ endpoints: ['/evp_proxy/v4'] })
25992599
// Tests from ci-visibility/test/occasionally-failing-test will be considered new
26002600
receiver.setKnownTests({ jest: {} })
@@ -2617,6 +2617,8 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
26172617

26182618
const testSession = events.find(event => event.type === 'test_session_end').content
26192619
assert.strictEqual(testSession.meta[TEST_EARLY_FLAKE_ENABLED], 'true')
2620+
// Session is passed because at least one retry of the new flaky test passes
2621+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
26202622

26212623
const tests = events.filter(event => event.type === 'test').map(event => event.content)
26222624

@@ -2636,14 +2638,16 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
26362638
})
26372639
})
26382640

2641+
let testOutput = ''
26392642
childProcess = exec(
26402643
'node ./node_modules/jest/bin/jest --config config-jest.js',
26412644
{
26422645
cwd,
26432646
env: {
26442647
...getCiVisEvpProxyConfig(receiver.port),
2645-
TESTS_TO_RUN: '**/ci-visibility/test-early-flake-detection/occasionally-failing-test*'
2646-
},
2648+
TESTS_TO_RUN: '**/ci-visibility/test-early-flake-detection/occasionally-failing-test*',
2649+
SHOULD_CHECK_RESULTS: '1'
2650+
}
26472651
}
26482652
)
26492653

@@ -2654,13 +2658,11 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
26542658
testOutput += chunk.toString()
26552659
})
26562660

2657-
childProcess.on('exit', (exitCode) => {
2658-
assert.match(testOutput, /2 failed, 2 passed/)
2659-
assert.strictEqual(exitCode, 0)
2660-
eventsPromise.then(() => {
2661-
done()
2662-
}).catch(done)
2663-
})
2661+
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), eventsPromise])
2662+
2663+
assert.match(testOutput, /2 failed, 2 passed/)
2664+
// Exit code is 0 because at least one retry of the new flaky test passes
2665+
assert.strictEqual(exitCode, 0)
26642666
})
26652667

26662668
// resetting snapshot state logic only works in latest versions
@@ -2694,6 +2696,8 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
26942696

26952697
const testSession = events.find(event => event.type === 'test_session_end').content
26962698
assert.strictEqual(testSession.meta[TEST_EARLY_FLAKE_ENABLED], 'true')
2699+
// Session is passed because at least one retry of each new flaky test passes
2700+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
26972701

26982702
const tests = events.filter(event => event.type === 'test').map(event => event.content)
26992703
// 6 tests, 4 of which are new: 4*(1 test + 3 retries) + 2*(1 test) = 18
@@ -2720,14 +2724,16 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
27202724
env: {
27212725
...getCiVisEvpProxyConfig(receiver.port),
27222726
TESTS_TO_RUN: 'ci-visibility/test-early-flake-detection/jest-snapshot',
2723-
CI: '1' // needs to be run as CI so snapshots are not written
2724-
},
2727+
CI: '1', // needs to be run as CI so snapshots are not written
2728+
SHOULD_CHECK_RESULTS: '1'
2729+
}
27252730
})
27262731

27272732
const [[exitCode]] = await Promise.all([
27282733
once(childProcess, 'exit'),
27292734
eventsPromise
27302735
])
2736+
// Exit code is 0 because at least one retry of each new flaky test passes
27312737
assert.strictEqual(exitCode, 0)
27322738
})
27332739

@@ -2757,6 +2763,8 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
27572763

27582764
const testSession = events.find(event => event.type === 'test_session_end').content
27592765
assert.strictEqual(testSession.meta[TEST_EARLY_FLAKE_ENABLED], 'true')
2766+
// Session is passed because at least one retry of the new flaky test passes
2767+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
27602768

27612769
const tests = events.filter(event => event.type === 'test').map(event => event.content)
27622770
// 1 new test
@@ -2779,14 +2787,16 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
27792787
env: {
27802788
...getCiVisEvpProxyConfig(receiver.port),
27812789
TESTS_TO_RUN: 'ci-visibility/test-early-flake-detection/jest-image-snapshot',
2782-
CI: '1'
2783-
},
2790+
CI: '1',
2791+
SHOULD_CHECK_RESULTS: '1'
2792+
}
27842793
})
27852794

27862795
const [[exitCode]] = await Promise.all([
27872796
once(childProcess, 'exit'),
27882797
eventsPromise
27892798
])
2799+
// Exit code is 0 because at least one retry of the new flaky test passes
27902800
assert.strictEqual(exitCode, 0)
27912801
})
27922802

@@ -4820,8 +4830,11 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
48204830

48214831
if (isQuarantining) {
48224832
assert.strictEqual(testSession.meta[TEST_MANAGEMENT_ENABLED], 'true')
4833+
// test session is passed even though a test fails because the test is quarantined
4834+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
48234835
} else {
48244836
assert.ok(!('TEST_MANAGEMENT_ENABLED' in testSession.meta))
4837+
assert.strictEqual(testSession.meta[TEST_STATUS], 'fail')
48254838
}
48264839

48274840
const resourceNames = tests.map(span => span.resource)
@@ -4855,7 +4868,7 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
48554868
}
48564869
})
48574870

4858-
const runQuarantineTest = (done, isQuarantining, extraEnvVars = {}, isParallel = false) => {
4871+
const runQuarantineTest = async (isQuarantining, extraEnvVars = {}, isParallel = false) => {
48594872
let stdout = ''
48604873
const testAssertionsPromise = getTestAssertions(isQuarantining, isParallel)
48614874

@@ -4868,7 +4881,7 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
48684881
TESTS_TO_RUN: 'test-management/test-quarantine-1',
48694882
SHOULD_CHECK_RESULTS: '1',
48704883
...extraEnvVars
4871-
},
4884+
}
48724885
}
48734886
)
48744887

@@ -4877,44 +4890,40 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
48774890
stdout += chunk.toString()
48784891
})
48794892

4880-
childProcess.on('exit', exitCode => {
4881-
testAssertionsPromise.then(() => {
4882-
// it runs regardless of quarantine status
4883-
assert.match(stdout, /I am running when quarantined/)
4884-
if (isQuarantining) {
4885-
// even though a test fails, the exit code is 0 because the test is quarantined
4886-
assert.strictEqual(exitCode, 0)
4887-
} else {
4888-
assert.strictEqual(exitCode, 1)
4889-
}
4890-
done()
4891-
}).catch(done)
4892-
})
4893+
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), testAssertionsPromise])
4894+
4895+
// it runs regardless of quarantine status
4896+
assert.match(stdout, /I am running when quarantined/)
4897+
if (isQuarantining) {
4898+
// even though a test fails, the exit code is 0 because the test is quarantined
4899+
assert.strictEqual(exitCode, 0)
4900+
} else {
4901+
assert.strictEqual(exitCode, 1)
4902+
}
48934903
}
48944904

4895-
it('can quarantine tests', (done) => {
4905+
it('can quarantine tests', async () => {
48964906
receiver.setSettings({ test_management: { enabled: true } })
48974907

4898-
runQuarantineTest(done, true)
4908+
await runQuarantineTest(true)
48994909
})
49004910

4901-
it('fails if quarantine is not enabled', (done) => {
4911+
it('fails if quarantine is not enabled', async () => {
49024912
receiver.setSettings({ test_management: { enabled: false } })
49034913

4904-
runQuarantineTest(done, false)
4914+
await runQuarantineTest(false)
49054915
})
49064916

4907-
it('does not enable quarantine tests if DD_TEST_MANAGEMENT_ENABLED is set to false', (done) => {
4917+
it('does not enable quarantine tests if DD_TEST_MANAGEMENT_ENABLED is set to false', async () => {
49084918
receiver.setSettings({ test_management: { enabled: true } })
49094919

4910-
runQuarantineTest(done, false, { DD_TEST_MANAGEMENT_ENABLED: '0' })
4920+
await runQuarantineTest(false, { DD_TEST_MANAGEMENT_ENABLED: '0' })
49114921
})
49124922

4913-
it('can quarantine in parallel mode', (done) => {
4923+
it('can quarantine in parallel mode', async () => {
49144924
receiver.setSettings({ test_management: { enabled: true } })
49154925

4916-
runQuarantineTest(
4917-
done,
4926+
await runQuarantineTest(
49184927
true,
49194928
{
49204929
// we need to run more than 1 suite for parallel mode to kick in
@@ -4924,6 +4933,73 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
49244933
true
49254934
)
49264935
})
4936+
4937+
it('fails if a non-quarantined test fails even when a quarantined test also fails', async () => {
4938+
receiver.setSettings({ test_management: { enabled: true } })
4939+
4940+
// Only quarantine one of the failing tests, leaving another failing test non-quarantined
4941+
receiver.setTestManagementTests({
4942+
jest: {
4943+
suites: {
4944+
'ci-visibility/test-management/test-partial-quarantine.js': {
4945+
tests: {
4946+
'partial quarantine tests quarantined failing test': {
4947+
properties: {
4948+
quarantined: true
4949+
}
4950+
}
4951+
// Note: 'partial quarantine tests non-quarantined failing test' is NOT quarantined
4952+
}
4953+
}
4954+
}
4955+
}
4956+
})
4957+
4958+
const testAssertionsPromise = receiver
4959+
.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => {
4960+
const events = payloads.flatMap(({ payload }) => payload.events)
4961+
const tests = events.filter(event => event.type === 'test').map(event => event.content)
4962+
const testSession = events.find(event => event.type === 'test_session_end').content
4963+
4964+
// Session should be marked as failed because a non-quarantined test failed
4965+
assert.strictEqual(testSession.meta[TEST_MANAGEMENT_ENABLED], 'true')
4966+
assert.strictEqual(testSession.meta[TEST_STATUS], 'fail')
4967+
4968+
// Verify the quarantined test has the quarantine tag
4969+
const quarantinedTest = tests.find(
4970+
test => test.meta[TEST_NAME] === 'partial quarantine tests quarantined failing test'
4971+
)
4972+
assert.strictEqual(quarantinedTest.meta[TEST_STATUS], 'fail')
4973+
assert.strictEqual(quarantinedTest.meta[TEST_MANAGEMENT_IS_QUARANTINED], 'true')
4974+
4975+
// Verify the non-quarantined test does NOT have the quarantine tag
4976+
const nonQuarantinedTest = tests.find(
4977+
test => test.meta[TEST_NAME] === 'partial quarantine tests non-quarantined failing test'
4978+
)
4979+
assert.strictEqual(nonQuarantinedTest.meta[TEST_STATUS], 'fail')
4980+
assert.ok(
4981+
!(TEST_MANAGEMENT_IS_QUARANTINED in nonQuarantinedTest.meta),
4982+
'Non-quarantined test should not have quarantine tag'
4983+
)
4984+
})
4985+
4986+
childProcess = exec(
4987+
runTestsCommand,
4988+
{
4989+
cwd,
4990+
env: {
4991+
...getCiVisAgentlessConfig(receiver.port),
4992+
TESTS_TO_RUN: 'test-management/test-partial-quarantine',
4993+
SHOULD_CHECK_RESULTS: '1'
4994+
}
4995+
}
4996+
)
4997+
4998+
const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), testAssertionsPromise])
4999+
5000+
// Exit code should be 1 because a non-quarantined test failed
5001+
assert.strictEqual(exitCode, 1)
5002+
})
49275003
})
49285004

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

0 commit comments

Comments
 (0)