@@ -505,15 +505,7 @@ ${ENDGROUP}`)
505505 const testSignal = testController . signal
506506 let hadFailures = false
507507
508- async function yourTurn ( ) {
509- await sema . acquire ( )
510- if ( testSignal . aborted ) {
511- sema . release ( )
512- throw testSignal . reason
513- }
514- }
515-
516- const runTest = ( /** @type {TestFile } */ test , isFinalRun , isRetry ) =>
508+ const runTestOnce = ( /** @type {TestFile } */ test , isFinalRun , isRetry ) =>
517509 new Promise ( ( resolve , reject ) => {
518510 const start = new Date ( ) . getTime ( )
519511 let outputChunks = [ ]
@@ -691,6 +683,100 @@ ${ENDGROUP}`)
691683 } )
692684 } )
693685
686+ const runTest = async ( /** @type {TestFile } */ test ) => {
687+ let passed = false
688+
689+ const shouldSkipRetries = skipRetryTestManifest . find ( ( t ) =>
690+ t . includes ( test . file )
691+ )
692+ const numRetries = shouldSkipRetries ? 0 : originalRetries
693+ if ( shouldSkipRetries ) {
694+ console . log (
695+ `Skipping retry for ${ test . file } due to skipRetryTestManifest`
696+ )
697+ }
698+
699+ for ( let i = 0 ; i < numRetries + 1 ; i ++ ) {
700+ try {
701+ console . log ( `Starting ${ test . file } retry ${ i } /${ numRetries } ` )
702+ const time = await runTestOnce (
703+ test ,
704+ shouldSkipRetries || i === numRetries ,
705+ shouldSkipRetries || i > 0
706+ )
707+ timings . push ( {
708+ file : test . file ,
709+ time,
710+ } )
711+ passed = true
712+ console . log (
713+ `${ test . file } finished on retry ${ i } /${ numRetries } in ${ time / 1000 } s`
714+ )
715+ break
716+ } catch ( err ) {
717+ if ( i < numRetries ) {
718+ try {
719+ let testDir = path . dirname ( path . join ( __dirname , test . file ) )
720+
721+ // if test is nested in a test folder traverse up a dir to ensure
722+ // we clean up relevant test files
723+ if ( testDir . endsWith ( '/test' ) || testDir . endsWith ( '\\test' ) ) {
724+ testDir = path . join ( testDir , '..' )
725+ }
726+ console . log ( 'Cleaning test files at' , testDir )
727+ await exec ( `git clean -fdx "${ testDir } "` )
728+ await exec ( `git checkout "${ testDir } "` )
729+ } catch ( err ) { }
730+ } else {
731+ console . error ( `${ test . file } failed due to ${ err } ` )
732+ }
733+ }
734+ }
735+
736+ if ( ! passed ) {
737+ hadFailures = true
738+ const error = new Error (
739+ `Test ${ test . file } failed to pass within ${ numRetries } retries`
740+ )
741+
742+ if ( ! shouldContinueTestsOnError ) {
743+ testController . abort ( error )
744+ } else {
745+ console . error ( error )
746+ console . log (
747+ `CONTINUE_ON_ERROR enabled, continuing tests after ${ test . file } failed`
748+ )
749+ }
750+ }
751+
752+ // Emit test output if test failed or if we're continuing tests on error
753+ // This is parsed by the commenter webhook to notify about failing tests
754+ if ( ( ! passed || shouldContinueTestsOnError ) && isTestJob ) {
755+ try {
756+ const testsOutput = await fsp . readFile (
757+ `${ test . file } ${ RESULTS_EXT } ` ,
758+ 'utf8'
759+ )
760+ const obj = JSON . parse ( testsOutput )
761+ obj . processEnv = {
762+ NEXT_TEST_MODE : process . env . NEXT_TEST_MODE ,
763+ HEADLESS : process . env . HEADLESS ,
764+ }
765+ await outputSema . acquire ( )
766+ if ( GROUP ) console . log ( `${ GROUP } Result as JSON for tooling` )
767+ console . log (
768+ `--test output start--` ,
769+ JSON . stringify ( obj ) ,
770+ `--test output end--`
771+ )
772+ if ( ENDGROUP ) console . log ( ENDGROUP )
773+ outputSema . release ( )
774+ } catch ( err ) {
775+ console . log ( `Failed to load test output` , err )
776+ }
777+ }
778+ }
779+
694780 const directorySemas = new Map ( )
695781
696782 const originalRetries = numRetries
@@ -704,112 +790,26 @@ ${ENDGROUP}`)
704790 if ( / ^ t e s t [ / \\ ] i n t e g r a t i o n / . test ( test . file ) && dirSema === undefined ) {
705791 directorySemas . set ( dirName , ( dirSema = new Sema ( 1 ) ) )
706792 }
793+ // TODO: Use explicit resource managment instead of this acquire/release pattern
794+ // once CI runs with Node.js 24+.
707795 if ( dirSema ) await dirSema . acquire ( )
708-
709- try {
710- await yourTurn ( )
711- } catch ( cause ) {
712- const error = new Error ( `Skipped due to abort.` , { cause } )
796+ await sema . acquire ( )
797+ if ( testSignal . aborted ) {
798+ const error = new Error ( `Skipped due to abort.` , {
799+ cause : testSignal . reason ,
800+ } )
713801 error . name = test . file
802+ if ( dirSema ) dirSema . release ( )
803+ sema . release ( )
714804 throw error
715805 }
716806
717- let passed = false
718-
719- const shouldSkipRetries = skipRetryTestManifest . find ( ( t ) =>
720- t . includes ( test . file )
721- )
722- const numRetries = shouldSkipRetries ? 0 : originalRetries
723- if ( shouldSkipRetries ) {
724- console . log (
725- `Skipping retry for ${ test . file } due to skipRetryTestManifest`
726- )
727- }
728-
729- for ( let i = 0 ; i < numRetries + 1 ; i ++ ) {
730- try {
731- console . log ( `Starting ${ test . file } retry ${ i } /${ numRetries } ` )
732- const time = await runTest (
733- test ,
734- shouldSkipRetries || i === numRetries ,
735- shouldSkipRetries || i > 0
736- )
737- timings . push ( {
738- file : test . file ,
739- time,
740- } )
741- passed = true
742- console . log (
743- `${ test . file } finished on retry ${ i } /${ numRetries } in ${
744- time / 1000
745- } s`
746- )
747- break
748- } catch ( err ) {
749- if ( i < numRetries ) {
750- try {
751- let testDir = path . dirname ( path . join ( __dirname , test . file ) )
752-
753- // if test is nested in a test folder traverse up a dir to ensure
754- // we clean up relevant test files
755- if ( testDir . endsWith ( '/test' ) || testDir . endsWith ( '\\test' ) ) {
756- testDir = path . join ( testDir , '..' )
757- }
758- console . log ( 'Cleaning test files at' , testDir )
759- await exec ( `git clean -fdx "${ testDir } "` )
760- await exec ( `git checkout "${ testDir } "` )
761- } catch ( err ) { }
762- } else {
763- console . error ( `${ test . file } failed due to ${ err } ` )
764- }
765- }
766- }
767-
768- if ( ! passed ) {
769- hadFailures = true
770- const error = new Error (
771- `Test ${ test . file } failed to pass within ${ numRetries } retries`
772- )
773-
774- if ( ! shouldContinueTestsOnError ) {
775- testController . abort ( error )
776- } else {
777- console . error ( error )
778- console . log (
779- `CONTINUE_ON_ERROR enabled, continuing tests after ${ test . file } failed`
780- )
781- }
782- }
783-
784- // Emit test output if test failed or if we're continuing tests on error
785- // This is parsed by the commenter webhook to notify about failing tests
786- if ( ( ! passed || shouldContinueTestsOnError ) && isTestJob ) {
787- try {
788- const testsOutput = await fsp . readFile (
789- `${ test . file } ${ RESULTS_EXT } ` ,
790- 'utf8'
791- )
792- const obj = JSON . parse ( testsOutput )
793- obj . processEnv = {
794- NEXT_TEST_MODE : process . env . NEXT_TEST_MODE ,
795- HEADLESS : process . env . HEADLESS ,
796- }
797- await outputSema . acquire ( )
798- if ( GROUP ) console . log ( `${ GROUP } Result as JSON for tooling` )
799- console . log (
800- `--test output start--` ,
801- JSON . stringify ( obj ) ,
802- `--test output end--`
803- )
804- if ( ENDGROUP ) console . log ( ENDGROUP )
805- outputSema . release ( )
806- } catch ( err ) {
807- console . log ( `Failed to load test output` , err )
808- }
807+ try {
808+ await runTest ( test )
809+ } finally {
810+ sema . release ( )
811+ if ( dirSema ) dirSema . release ( )
809812 }
810-
811- sema . release ( )
812- if ( dirSema ) dirSema . release ( )
813813 } )
814814 )
815815
0 commit comments