22 checkFilesExist ,
33 cleanupProject ,
44 fileExists ,
5- getStrippedEnvironmentVariables ,
65 isWindows ,
76 newProject ,
87 readJson ,
@@ -15,7 +14,6 @@ import {
1514 updateFile ,
1615 updateJson ,
1716} from '@nx/e2e-utils' ;
18- import { execSync } from 'child_process' ;
1917import { PackageJson } from 'nx/src/utils/package-json' ;
2018import * as path from 'path' ;
2119
@@ -775,301 +773,6 @@ describe('Nx Running Tests', () => {
775773 } ) ;
776774 } ) ;
777775
778- describe ( 'SIGINT process cleanup' , ( ) => {
779- if ( isWindows ( ) ) {
780- it ( 'skipped on windows' , ( ) => { } ) ;
781- } else {
782- it ( 'should kill executor-based (discrete) task processes on SIGINT' , async ( ) => {
783- const lib = uniq ( 'sigint-lib' ) ;
784- runCLI ( `generate @nx/js:lib libs/${ lib } ` ) ;
785-
786- const pidFile = path . join ( tmpProjPath ( ) , `libs/${ lib } /build.pid` ) ;
787- const scriptBody = writePidScript ( 'build.pid' ) ;
788-
789- updateFile ( `libs/${ lib } /slow-build.js` , scriptBody ) ;
790-
791- // nx:run-script executor goes through the discrete/forked process path
792- updateJson ( `libs/${ lib } /project.json` , ( config ) => {
793- config . targets = {
794- build : {
795- executor : 'nx:run-script' ,
796- options : { script : 'build' } ,
797- } ,
798- } ;
799- return config ;
800- } ) ;
801-
802- updateJson ( `libs/${ lib } /package.json` , ( pkg ) => {
803- pkg . scripts = { build : 'node slow-build.js' } ;
804- return pkg ;
805- } ) ;
806-
807- const pids = await runNxAndSigint (
808- [ 'build' , lib , '--skip-nx-cache' ] ,
809- pidFile
810- ) ;
811-
812- for ( const pid of pids ) {
813- expect ( isProcessAlive ( pid ) ) . toBe ( false ) ;
814- }
815- } , 60_000 ) ;
816-
817- it ( 'should kill run-commands task processes on SIGINT' , async ( ) => {
818- const lib = uniq ( 'sigint-rc' ) ;
819- runCLI ( `generate @nx/js:lib libs/${ lib } ` ) ;
820-
821- const pidFile = path . join ( tmpProjPath ( ) , `libs/${ lib } /build.pid` ) ;
822-
823- updateFile ( `libs/${ lib } /slow-build.js` , writePidScript ( 'build.pid' ) ) ;
824-
825- updateJson ( `libs/${ lib } /project.json` , ( config ) => {
826- config . targets = {
827- build : {
828- command : 'node slow-build.js' ,
829- options : { cwd : `libs/${ lib } ` } ,
830- } ,
831- } ;
832- return config ;
833- } ) ;
834-
835- const pids = await runNxAndSigint (
836- [ 'build' , lib , '--skip-nx-cache' ] ,
837- pidFile
838- ) ;
839-
840- for ( const pid of pids ) {
841- expect ( isProcessAlive ( pid ) ) . toBe ( false ) ;
842- }
843- } , 60_000 ) ;
844-
845- it ( 'should kill continuous task processes on SIGINT' , async ( ) => {
846- const lib = uniq ( 'sigint-cont' ) ;
847- runCLI ( `generate @nx/js:lib libs/${ lib } ` ) ;
848-
849- const pidFile = path . join ( tmpProjPath ( ) , `libs/${ lib } /serve.pid` ) ;
850-
851- updateFile ( `libs/${ lib } /serve.js` , writePidScript ( 'serve.pid' ) ) ;
852-
853- updateJson ( `libs/${ lib } /project.json` , ( config ) => {
854- config . targets = {
855- serve : {
856- command : 'node serve.js' ,
857- options : { cwd : `libs/${ lib } ` } ,
858- continuous : true ,
859- } ,
860- } ;
861- return config ;
862- } ) ;
863-
864- const pids = await runNxAndSigint (
865- [ 'serve' , lib , '--skip-nx-cache' ] ,
866- pidFile
867- ) ;
868-
869- for ( const pid of pids ) {
870- expect ( isProcessAlive ( pid ) ) . toBe ( false ) ;
871- }
872- } , 60_000 ) ;
873-
874- it ( 'should kill executor-based (discrete) task processes on SIGINT (TUI mode)' , async ( ) => {
875- const lib = uniq ( 'sigint-tui' ) ;
876- runCLI ( `generate @nx/js:lib libs/${ lib } ` ) ;
877-
878- const pidFile = path . join ( tmpProjPath ( ) , `libs/${ lib } /build.pid` ) ;
879-
880- updateFile ( `libs/${ lib } /slow-build.js` , writePidScript ( 'build.pid' ) ) ;
881-
882- updateJson ( `libs/${ lib } /project.json` , ( config ) => {
883- config . targets = {
884- build : {
885- executor : 'nx:run-script' ,
886- options : { script : 'build' } ,
887- } ,
888- } ;
889- return config ;
890- } ) ;
891-
892- updateJson ( `libs/${ lib } /package.json` , ( pkg ) => {
893- pkg . scripts = { build : 'node slow-build.js' } ;
894- return pkg ;
895- } ) ;
896-
897- const pids = await runNxAndSigint (
898- [ 'build' , lib , '--skip-nx-cache' ] ,
899- pidFile ,
900- { useTui : true }
901- ) ;
902-
903- for ( const pid of pids ) {
904- expect ( isProcessAlive ( pid ) ) . toBe ( false ) ;
905- }
906- } , 60_000 ) ;
907-
908- it ( 'should kill run-commands task processes on SIGINT (TUI mode)' , async ( ) => {
909- const lib = uniq ( 'sigint-tui-rc' ) ;
910- runCLI ( `generate @nx/js:lib libs/${ lib } ` ) ;
911-
912- const pidFile = path . join ( tmpProjPath ( ) , `libs/${ lib } /build.pid` ) ;
913-
914- updateFile ( `libs/${ lib } /slow-build.js` , writePidScript ( 'build.pid' ) ) ;
915-
916- updateJson ( `libs/${ lib } /project.json` , ( config ) => {
917- config . targets = {
918- build : {
919- command : 'node slow-build.js' ,
920- options : { cwd : `libs/${ lib } ` } ,
921- } ,
922- } ;
923- return config ;
924- } ) ;
925-
926- const pids = await runNxAndSigint (
927- [ 'build' , lib , '--skip-nx-cache' ] ,
928- pidFile ,
929- { useTui : true }
930- ) ;
931-
932- for ( const pid of pids ) {
933- expect ( isProcessAlive ( pid ) ) . toBe ( false ) ;
934- }
935- } , 60_000 ) ;
936-
937- it ( 'should kill continuous task processes on SIGINT (TUI mode)' , async ( ) => {
938- const lib = uniq ( 'sigint-tui-cont' ) ;
939- runCLI ( `generate @nx/js:lib libs/${ lib } ` ) ;
940-
941- const pidFile = path . join ( tmpProjPath ( ) , `libs/${ lib } /serve.pid` ) ;
942-
943- updateFile ( `libs/${ lib } /serve.js` , writePidScript ( 'serve.pid' ) ) ;
944-
945- updateJson ( `libs/${ lib } /project.json` , ( config ) => {
946- config . targets = {
947- serve : {
948- command : 'node serve.js' ,
949- options : { cwd : `libs/${ lib } ` } ,
950- continuous : true ,
951- } ,
952- } ;
953- return config ;
954- } ) ;
955-
956- const pids = await runNxAndSigint (
957- [ 'serve' , lib , '--skip-nx-cache' ] ,
958- pidFile ,
959- { useTui : true }
960- ) ;
961-
962- for ( const pid of pids ) {
963- expect ( isProcessAlive ( pid ) ) . toBe ( false ) ;
964- }
965- } , 60_000 ) ;
966- }
967-
968- /** Script that writes its PID to a file and keeps running. */
969- function writePidScript ( filename : string ) : string {
970- return `
971- const fs = require('fs');
972- const path = require('path');
973- fs.writeFileSync(path.join(__dirname, '${ filename } '), String(process.pid));
974- setInterval(() => {}, 1000);
975- ` ;
976- }
977-
978- /**
979- * Runs nx inside a real pseudo-terminal, polls for a PID file,
980- * sends SIGINT (identical to Ctrl+C), and asserts nx exits
981- * promptly with all processes in the tree dead.
982- */
983- async function runNxAndSigint (
984- args : string [ ] ,
985- pidFile : string ,
986- { useTui } : { useTui ?: boolean } = { }
987- ) : Promise < number [ ] > {
988- const { existsSync, readFileSync, unlinkSync } = require ( 'fs' ) ;
989- const { RustPseudoTerminal } = require ( 'nx/src/native' ) ;
990-
991- try {
992- unlinkSync ( pidFile ) ;
993- } catch { }
994-
995- const nxBin = path . join ( tmpProjPath ( ) , 'node_modules' , '.bin' , 'nx' ) ;
996- const command = `${ nxBin } ${ args . join ( ' ' ) } ` ;
997-
998- const pty = new RustPseudoTerminal ( ) ;
999- const childProcess = pty . runCommand ( command , tmpProjPath ( ) , {
1000- ...getStrippedEnvironmentVariables ( ) ,
1001- CI : 'true' ,
1002- FORCE_COLOR : 'false' ,
1003- NX_DAEMON : 'false' ,
1004- NX_TUI : useTui ? 'true' : 'false' ,
1005- ...( useTui ? { NX_TUI_SKIP_CAPABILITY_CHECK : 'true' } : { } ) ,
1006- } ) ;
1007-
1008- const nxPid = childProcess . getPid ( ) ;
1009- console . log ( `[sigint-test] started PID=${ nxPid } , useTui=${ ! ! useTui } ` ) ;
1010-
1011- // Track exit immediately so we don't miss it
1012- let exited = false ;
1013- let exitResolve : ( clean : boolean ) => void ;
1014- const exitPromise = new Promise < boolean > ( ( r ) => ( exitResolve = r ) ) ;
1015- childProcess . onExit ( ( msg ) => {
1016- console . log ( `[sigint-test] onExit: ${ msg } ` ) ;
1017- exited = true ;
1018- exitResolve ( true ) ;
1019- } ) ;
1020-
1021- // Poll for PID file written by the task script
1022- let taskPid : number | undefined ;
1023- const startTime = Date . now ( ) ;
1024- while ( Date . now ( ) - startTime < 30_000 && ! exited ) {
1025- if ( existsSync ( pidFile ) ) {
1026- taskPid = parseInt ( readFileSync ( pidFile , 'utf-8' ) . trim ( ) , 10 ) ;
1027- if ( taskPid && ! isNaN ( taskPid ) ) break ;
1028- }
1029- await new Promise ( ( r ) => setTimeout ( r , 200 ) ) ;
1030- }
1031-
1032- if ( ! taskPid ) {
1033- childProcess . kill ( 'SIGKILL' ) ;
1034- throw new Error (
1035- exited
1036- ? 'nx exited before task script wrote PID file'
1037- : 'Timed out waiting for PID file from task'
1038- ) ;
1039- }
1040-
1041- expect ( isProcessAlive ( taskPid ) ) . toBe ( true ) ;
1042-
1043- // Capture entire process tree before sending SIGINT
1044- const processTree = getProcessTree ( nxPid ) ;
1045-
1046- console . log (
1047- `[sigint-test] taskPid=${ taskPid } alive=${ isProcessAlive ( taskPid ) } , tree=${ JSON . stringify ( processTree ) } `
1048- ) ;
1049-
1050- // Send SIGINT — identical to Ctrl+C in a real terminal
1051- console . log ( `[sigint-test] sending SIGINT` ) ;
1052- childProcess . kill ( 'SIGINT' ) ;
1053-
1054- // Nx should exit promptly. Without the fix, cleanup hangs
1055- // waiting for unkilled discrete tasks.
1056- const exitTimeout = setTimeout ( ( ) => {
1057- console . log ( `[sigint-test] TIMEOUT — sending SIGKILL` ) ;
1058- childProcess . kill ( 'SIGKILL' ) ;
1059- exitResolve ( false ) ;
1060- } , 10_000 ) ;
1061- const exitedCleanly = await exitPromise ;
1062- clearTimeout ( exitTimeout ) ;
1063-
1064- console . log ( `[sigint-test] exitedCleanly=${ exitedCleanly } ` ) ;
1065- expect ( exitedCleanly ) . toBe ( true ) ;
1066-
1067- await new Promise ( ( r ) => setTimeout ( r , 2000 ) ) ;
1068-
1069- return processTree ;
1070- }
1071- } ) ;
1072-
1073776 describe ( 'exec' , ( ) => {
1074777 let pkg : string ;
1075778 let pkg2 : string ;
@@ -1242,46 +945,3 @@ describe('Nx Running Tests', () => {
1242945 } ) ;
1243946 } ) ;
1244947} ) ;
1245-
1246- function isProcessAlive ( pid : number ) : boolean {
1247- try {
1248- process . kill ( pid , 0 ) ;
1249- return true ;
1250- } catch {
1251- return false ;
1252- }
1253- }
1254-
1255- /**
1256- * Get all descendant PIDs of a process (recursive).
1257- * Uses `pgrep -P` on macOS/Linux.
1258- */
1259- function getProcessTree ( rootPid : number ) : number [ ] {
1260- const pids : number [ ] = [ rootPid ] ;
1261- try {
1262- const children = execSync ( `pgrep -P ${ rootPid } ` , { encoding : 'utf-8' } )
1263- . trim ( )
1264- . split ( '\n' )
1265- . filter ( Boolean )
1266- . map ( Number ) ;
1267- for ( const child of children ) {
1268- pids . push ( ...getProcessTree ( child ) ) ;
1269- }
1270- } catch {
1271- // No children or process already exited
1272- }
1273- return pids ;
1274- }
1275-
1276- /**
1277- * Kill an entire process tree by walking descendants.
1278- * Used as a fallback when cleanup times out.
1279- */
1280- function killProcessTree ( pid : number ) {
1281- const pids = getProcessTree ( pid ) ;
1282- for ( const p of pids ) {
1283- try {
1284- process . kill ( p , 'SIGKILL' ) ;
1285- } catch { }
1286- }
1287- }
0 commit comments