@@ -3556,6 +3556,106 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
35563556 ] )
35573557 } )
35583558 } )
3559+
3560+ it ( 'does not flip exit code to 0 when a test suite fails to parse' , async ( ) => {
3561+ receiver . setKnownTests ( { jest : { } } )
3562+ receiver . setSettings ( {
3563+ early_flake_detection : {
3564+ enabled : true ,
3565+ slow_test_retries : { '5s' : 3 } ,
3566+ faulty_session_threshold : 100 ,
3567+ } ,
3568+ known_tests_enabled : true ,
3569+ } )
3570+
3571+ // Scenario: (1) test-suite-failed-to-run-parse.js fails to parse,
3572+ // (2) occasionally-failing-test is new, flaky (pass/fail alternates), EFD would ignore its failures.
3573+ const testAssertionsPromise = receiver
3574+ . gatherPayloadsMaxTimeout ( ( { url } ) => url . endsWith ( '/api/v2/citestcycle' ) , ( payloads ) => {
3575+ const events = payloads . flatMap ( ( { payload } ) => payload . events )
3576+ const testSession = events . find ( event => event . type === 'test_session_end' ) ?. content
3577+ assert . strictEqual ( testSession . meta [ TEST_STATUS ] , 'fail' )
3578+ assert . strictEqual ( testSession . meta [ TEST_EARLY_FLAKE_ENABLED ] , 'true' , 'EFD should be running' )
3579+
3580+ // TODO: parsing errors do not report test suite
3581+ const tests = events . filter ( event => event . type === 'test' ) . map ( event => event . content )
3582+ const occasionallyFailingTests = tests . filter ( t => t . resource ?. includes ( 'occasionally-failing-test' ) )
3583+ const numRetries = 3 // slow_test_retries: { '5s': 3 }
3584+ assert . strictEqual ( occasionallyFailingTests . length , 1 + numRetries , '1 original + 3 EFD retries' )
3585+ const efdRetried = occasionallyFailingTests . filter ( t =>
3586+ t . meta ?. [ TEST_IS_RETRY ] === 'true' && t . meta ?. [ TEST_RETRY_REASON ] === TEST_RETRY_REASON_TYPES . efd
3587+ )
3588+ assert . strictEqual ( efdRetried . length , numRetries , 'all but 1 should have EFD retry tag and reason' )
3589+ } )
3590+
3591+ childProcess = exec (
3592+ runTestsCommand ,
3593+ {
3594+ cwd,
3595+ env : {
3596+ ...getCiVisAgentlessConfig ( receiver . port ) ,
3597+ TESTS_TO_RUN : '(test-management/test-suite-failed-to-run-parse|' +
3598+ 'test-early-flake-detection/occasionally-failing-test)' ,
3599+ SHOULD_CHECK_RESULTS : '1' ,
3600+ } ,
3601+ }
3602+ )
3603+
3604+ const [ [ exitCode ] ] = await Promise . all ( [ once ( childProcess , 'exit' ) , testAssertionsPromise ] )
3605+ assert . strictEqual ( exitCode , 1 , 'exit code 1 when test suite fails to parse' )
3606+ } )
3607+
3608+ it ( 'does not flip exit code to 0 when a test suite fails due to module resolution error' , async ( ) => {
3609+ receiver . setKnownTests ( { jest : { } } )
3610+ receiver . setSettings ( {
3611+ early_flake_detection : {
3612+ enabled : true ,
3613+ slow_test_retries : { '5s' : 3 } ,
3614+ faulty_session_threshold : 100 ,
3615+ } ,
3616+ known_tests_enabled : true ,
3617+ } )
3618+
3619+ // Scenario: (1) test-suite-failed-to-run-resolution.js fails to load,
3620+ // (2) occasionally-failing-test is new, flaky, EFD would ignore its failures.
3621+ const testAssertionsPromise = receiver
3622+ . gatherPayloadsMaxTimeout ( ( { url } ) => url . endsWith ( '/api/v2/citestcycle' ) , ( payloads ) => {
3623+ const events = payloads . flatMap ( ( { payload } ) => payload . events )
3624+ const testSession = events . find ( event => event . type === 'test_session_end' ) ?. content
3625+ assert . strictEqual ( testSession . meta [ TEST_STATUS ] , 'fail' )
3626+ assert . strictEqual ( testSession . meta [ TEST_EARLY_FLAKE_ENABLED ] , 'true' , 'EFD should be running' )
3627+
3628+ const suites = events . filter ( event => event . type === 'test_suite_end' ) . map ( event => event . content )
3629+ const failedSuite = suites . find ( s => s . meta ?. [ TEST_SUITE ] ?. includes ( 'test-suite-failed-to-run-resolution' ) )
3630+ assert . ok ( failedSuite , 'failing test suite should be reported' )
3631+ assert . strictEqual ( failedSuite . meta [ TEST_STATUS ] , 'fail' )
3632+
3633+ const tests = events . filter ( event => event . type === 'test' ) . map ( event => event . content )
3634+ const occasionallyFailingTests = tests . filter ( t => t . resource ?. includes ( 'occasionally-failing-test' ) )
3635+ const numRetries = 3 // slow_test_retries: { '5s': 3 }
3636+ assert . strictEqual ( occasionallyFailingTests . length , 1 + numRetries , '1 original + 3 EFD retries' )
3637+ const efdRetried = occasionallyFailingTests . filter ( t =>
3638+ t . meta ?. [ TEST_IS_RETRY ] === 'true' && t . meta ?. [ TEST_RETRY_REASON ] === TEST_RETRY_REASON_TYPES . efd
3639+ )
3640+ assert . strictEqual ( efdRetried . length , numRetries , 'all but 1 should have EFD retry tag and reason' )
3641+ } )
3642+
3643+ childProcess = exec (
3644+ runTestsCommand ,
3645+ {
3646+ cwd,
3647+ env : {
3648+ ...getCiVisAgentlessConfig ( receiver . port ) ,
3649+ TESTS_TO_RUN : '(test-management/test-suite-failed-to-run-resolution|' +
3650+ 'test-early-flake-detection/occasionally-failing-test)' ,
3651+ SHOULD_CHECK_RESULTS : '1' ,
3652+ } ,
3653+ }
3654+ )
3655+
3656+ const [ [ exitCode ] ] = await Promise . all ( [ once ( childProcess , 'exit' ) , testAssertionsPromise ] )
3657+ assert . strictEqual ( exitCode , 1 , 'exit code 1 when suite fails (resolution error, EFD)' )
3658+ } )
35593659 } )
35603660
35613661 context ( 'flaky test retries' , ( ) => {
@@ -5590,6 +5690,114 @@ describe(`jest@${JEST_VERSION} commonJS`, () => {
55905690 // - The always-failing test is quarantined
55915691 assert . strictEqual ( exitCode , 0 )
55925692 } )
5693+
5694+ it ( 'does not flip exit code to 0 when a test suite fails to parse' , async ( ) => {
5695+ receiver . setSettings ( { test_management : { enabled : true } } )
5696+
5697+ // Scenario: (1) test-suite-failed-to-run-parse.js fails to parse so no tests run,
5698+ // (2) test-quarantine-1.js parses and runs, its only failing test is quarantined.
5699+ receiver . setTestManagementTests ( {
5700+ jest : {
5701+ suites : {
5702+ 'ci-visibility/test-management/test-quarantine-1.js' : {
5703+ tests : {
5704+ 'quarantine tests can quarantine a test' : {
5705+ properties : {
5706+ quarantined : true ,
5707+ } ,
5708+ } ,
5709+ } ,
5710+ } ,
5711+ } ,
5712+ } ,
5713+ } )
5714+
5715+ const testAssertionsPromise = receiver
5716+ . gatherPayloadsMaxTimeout ( ( { url } ) => url . endsWith ( '/api/v2/citestcycle' ) , ( payloads ) => {
5717+ const events = payloads . flatMap ( ( { payload } ) => payload . events )
5718+ const testSession = events . find ( event => event . type === 'test_session_end' ) ?. content
5719+ assert . strictEqual ( testSession . meta [ TEST_STATUS ] , 'fail' )
5720+ assert . strictEqual ( testSession . meta [ TEST_MANAGEMENT_ENABLED ] , 'true' , 'test management should be running' )
5721+
5722+ // TODO: parsing errors do not report test suite
5723+ const tests = events . filter ( event => event . type === 'test' ) . map ( event => event . content )
5724+ const quarantine1Tests = tests . filter ( t => t . resource ?. includes ( 'test-quarantine-1' ) )
5725+ const withQuarantineTag = quarantine1Tests . filter ( t => t . meta ?. [ TEST_MANAGEMENT_IS_QUARANTINED ] === 'true' )
5726+ assert . strictEqual ( withQuarantineTag . length , 1 , 'only one test from test-quarantine-1 has quarantine tag' )
5727+ assert . strictEqual ( withQuarantineTag [ 0 ] . meta [ TEST_MANAGEMENT_IS_QUARANTINED ] , 'true' )
5728+ } )
5729+
5730+ childProcess = exec (
5731+ runTestsCommand ,
5732+ {
5733+ cwd,
5734+ env : {
5735+ ...getCiVisAgentlessConfig ( receiver . port ) ,
5736+ TESTS_TO_RUN : 'test-management/(test-suite-failed-to-run-parse|test-quarantine-1)' ,
5737+ SHOULD_CHECK_RESULTS : '1' ,
5738+ } ,
5739+ }
5740+ )
5741+
5742+ const [ [ exitCode ] ] = await Promise . all ( [ once ( childProcess , 'exit' ) , testAssertionsPromise ] )
5743+ assert . strictEqual ( exitCode , 1 , 'exit code should be 1 when a test suite fails to parse' )
5744+ } )
5745+
5746+ it ( 'does not flip exit code to 0 when a test suite fails due to module resolution error' , async ( ) => {
5747+ receiver . setSettings ( { test_management : { enabled : true } } )
5748+
5749+ // Scenario: (1) test-suite-failed-to-run-resolution.js fails to load (invalid require),
5750+ // (2) test-quarantine-1.js parses and runs, its only failing test is quarantined.
5751+ receiver . setTestManagementTests ( {
5752+ jest : {
5753+ suites : {
5754+ 'ci-visibility/test-management/test-quarantine-1.js' : {
5755+ tests : {
5756+ 'quarantine tests can quarantine a test' : {
5757+ properties : {
5758+ quarantined : true ,
5759+ } ,
5760+ } ,
5761+ } ,
5762+ } ,
5763+ } ,
5764+ } ,
5765+ } )
5766+
5767+ const testAssertionsPromise = receiver
5768+ . gatherPayloadsMaxTimeout ( ( { url } ) => url . endsWith ( '/api/v2/citestcycle' ) , ( payloads ) => {
5769+ const events = payloads . flatMap ( ( { payload } ) => payload . events )
5770+ const testSession = events . find ( event => event . type === 'test_session_end' ) ?. content
5771+ assert . strictEqual ( testSession . meta [ TEST_STATUS ] , 'fail' )
5772+ assert . strictEqual ( testSession . meta [ TEST_MANAGEMENT_ENABLED ] , 'true' , 'test management should be running' )
5773+
5774+ const suites = events . filter ( event => event . type === 'test_suite_end' ) . map ( event => event . content )
5775+ const failedSuite = suites . find ( s => s . meta ?. [ TEST_SUITE ] ?. includes ( 'test-suite-failed-to-run-resolution' ) )
5776+ assert . ok ( failedSuite , 'failing test suite should be reported' )
5777+ assert . strictEqual ( failedSuite . meta [ TEST_STATUS ] , 'fail' )
5778+
5779+ const tests = events . filter ( event => event . type === 'test' ) . map ( event => event . content )
5780+ const quarantine1Tests = tests . filter ( t => t . resource ?. includes ( 'test-quarantine-1' ) )
5781+ const withQuarantineTag = quarantine1Tests . filter ( t => t . meta ?. [ TEST_MANAGEMENT_IS_QUARANTINED ] === 'true' )
5782+ assert . strictEqual ( withQuarantineTag . length , 1 , 'only one test from test-quarantine-1 has quarantine tag' )
5783+ assert . strictEqual ( withQuarantineTag [ 0 ] . meta [ TEST_MANAGEMENT_IS_QUARANTINED ] , 'true' )
5784+ } )
5785+
5786+ childProcess = exec (
5787+ runTestsCommand ,
5788+ {
5789+ cwd,
5790+ env : {
5791+ ...getCiVisAgentlessConfig ( receiver . port ) ,
5792+ TESTS_TO_RUN : 'test-management/(test-suite-failed-to-run-resolution|test-quarantine-1)' ,
5793+ SHOULD_CHECK_RESULTS : '1' ,
5794+ } ,
5795+ }
5796+ )
5797+
5798+ const [ [ exitCode ] ] = await Promise . all ( [ once ( childProcess , 'exit' ) , testAssertionsPromise ] )
5799+ assert . strictEqual ( exitCode , 1 , 'exit code 1 when suite fails (resolution error)' )
5800+ } )
55935801 } )
55945802
55955803 it ( 'does not crash if the request to get test management tests fails' , async ( ) => {
0 commit comments