@@ -82,20 +82,27 @@ public sealed class CliInstallationTests
8282 const shim = process.platform === 'win32' ? '@echo off\r\n' : `#!/bin/sh\nexec ${shellQuote(process.execPath)} ${shellQuote(path.join(packageRoot, 'PACKAGE_ENTRY'))} "$@"\n`;
8383 fs.writeFileSync(shimPath, shim);
8484 if (process.platform !== 'win32') fs.chmodSync(shimPath, 0o755);
85+ function writeProcessId(filePath) {
86+ const temporaryPath = filePath + '.tmp';
87+ fs.writeFileSync(temporaryPath, String(process.pid));
88+ fs.renameSync(temporaryPath, filePath);
89+ }
8590 if (fs.existsSync(path.join(__dirname, 'hang'))) {
86- fs.writeFileSync (path.join(__dirname, 'child.pid'), String(process.pid ));
91+ writeProcessId (path.join(__dirname, 'child.pid'));
8792 setInterval(() => {}, 1000);
8893 }
8994 if (fs.existsSync(path.join(__dirname, 'pipe-holder'))) {
90- const { spawnSync } = require('node:child_process');
91- const shellCommand = `setsid /bin/sh -c 'echo $$ > ${shellQuote(path.join(__dirname, 'pipe-holder.pid'))}; exec /bin/sleep 60' holder &`;
92- spawnSync('/bin/sh', ['-c', shellCommand], { stdio: 'inherit' });
95+ const { spawn } = require('node:child_process');
96+ const childScript = 'printf "%s" "$$" > "$1.tmp" && mv "$1.tmp" "$1" && exec /bin/sleep 60';
97+ const holder = spawn('setsid', ['/bin/sh', '-c', childScript, 'holder',
98+ path.join(__dirname, 'pipe-holder.pid')], { detached: true, stdio: 'inherit' });
99+ holder.unref();
93100 }
94101 if (fs.existsSync(path.join(__dirname, 'overflow'))) {
95102 process.stdout.write('x'.repeat(8192));
96103 setInterval(() => {}, 1000);
97104 }
98- fs.writeFileSync (path.join(__dirname, 'child.pid'), String(process.pid ));
105+ writeProcessId (path.join(__dirname, 'child.pid'));
99106 console.log('installation output');
100107 console.error('installation diagnostics');
101108 """ ;
@@ -176,9 +183,7 @@ public async Task InstallOrUpdate_ReportsFinalOutputWhenConsumerResumesAfterProc
176183 await Assert . That ( updates . Current . Stage ) . IsEqualTo ( CliInstallationStage . PackageManagerStarted ) ;
177184 var childPidPath = Path . Combine ( fixture . NpmRoot , BinDirectoryName , ChildPidFileName ) ;
178185 using var markerTimeout = new CancellationTokenSource ( TimeSpan . FromSeconds ( 3 ) ) ;
179- await WaitForFileAsync ( childPidPath , markerTimeout . Token ) ;
180- var childPid = int . Parse ( await File . ReadAllTextAsync ( childPidPath ) ,
181- System . Globalization . CultureInfo . InvariantCulture ) ;
186+ var childPid = await WaitForProcessIdAsync ( childPidPath , markerTimeout . Token ) ;
182187 await WaitForProcessExitAsync ( childPid , markerTimeout . Token ) ;
183188
184189 var sawFinalOutput = false ;
@@ -219,9 +224,7 @@ public async Task InstallOrUpdate_EnforcesTimeoutWhileConsumerIsPausedAfterStart
219224 await Assert . That ( updates . Current . Stage ) . IsEqualTo ( CliInstallationStage . PackageManagerStarted ) ;
220225
221226 using var deadline = new CancellationTokenSource ( TimeSpan . FromSeconds ( 5 ) ) ;
222- await WaitForFileAsync ( childPidPath , deadline . Token ) ;
223- var childPid = int . Parse ( await File . ReadAllTextAsync ( childPidPath ) ,
224- System . Globalization . CultureInfo . InvariantCulture ) ;
227+ var childPid = await WaitForProcessIdAsync ( childPidPath , deadline . Token ) ;
225228 await WaitForProcessExitAsync ( childPid , deadline . Token ) ;
226229 await Assert . That ( IsProcessRunning ( childPid ) ) . IsFalse ( ) ;
227230
@@ -266,12 +269,8 @@ await File.WriteAllTextAsync(Path.Combine(fixture.NpmRoot, BinDirectoryName, Pip
266269 fixture . LocalApplicationDataRoot , CancellationToken . None ) . GetAsyncEnumerator ( ) ;
267270 await Assert . That ( await updates . MoveNextAsync ( ) ) . IsTrue ( ) ;
268271 using var markerTimeout = new CancellationTokenSource ( TimeSpan . FromSeconds ( 3 ) ) ;
269- await WaitForFileAsync ( rootPidFile , markerTimeout . Token ) ;
270- await WaitForFileAsync ( holderPidFile , markerTimeout . Token ) ;
271- var rootPid = int . Parse ( await File . ReadAllTextAsync ( rootPidFile ) ,
272- System . Globalization . CultureInfo . InvariantCulture ) ;
273- holderPid = int . Parse ( await File . ReadAllTextAsync ( holderPidFile ) ,
274- System . Globalization . CultureInfo . InvariantCulture ) ;
272+ var rootPid = await WaitForProcessIdAsync ( rootPidFile , markerTimeout . Token ) ;
273+ holderPid = await WaitForProcessIdAsync ( holderPidFile , markerTimeout . Token ) ;
275274 await WaitForProcessExitAsync ( rootPid , markerTimeout . Token ) ;
276275 await Assert . That ( IsProcessRunning ( rootPid ) ) . IsFalse ( ) ;
277276 await Assert . That ( IsProcessRunning ( holderPid ) ) . IsTrue ( ) ;
@@ -350,7 +349,7 @@ public async Task InstallOrUpdate_CancellationStopsAndJoinsPackageManager()
350349 await Task . Delay ( TimeSpan . FromMilliseconds ( 10 ) , markerTimeout . Token ) ;
351350 }
352351
353- var childPid = int . Parse ( await File . ReadAllTextAsync ( childPidPath ) , System . Globalization . CultureInfo . InvariantCulture ) ;
352+ var childPid = await WaitForProcessIdAsync ( childPidPath , markerTimeout . Token ) ;
354353 var competingOptions = fixture . Options with { InstallationLockTimeout = TimeSpan . FromMilliseconds ( 100 ) } ;
355354 var lockException = await Assert . That ( async ( ) =>
356355 await RunInstallationAsync ( fixture , competingOptions ) ) . ThrowsException ( ) ;
@@ -403,12 +402,8 @@ public async Task CancellationWithDetachedPipeHolderSurfacesUnconfirmedCleanup()
403402 fixture . LocalApplicationDataRoot , cancellation . Token ) . GetAsyncEnumerator ( ) ;
404403 await Assert . That ( await updates . MoveNextAsync ( ) ) . IsTrue ( ) ;
405404 using var markerTimeout = new CancellationTokenSource ( TimeSpan . FromSeconds ( 3 ) ) ;
406- await WaitForFileAsync ( packageManagerPidFile , markerTimeout . Token ) ;
407- await WaitForFileAsync ( holderPidFile , markerTimeout . Token ) ;
408- var packageManagerPid = int . Parse ( await File . ReadAllTextAsync ( packageManagerPidFile ) ,
409- System . Globalization . CultureInfo . InvariantCulture ) ;
410- holderProcessId = int . Parse ( await File . ReadAllTextAsync ( holderPidFile ) ,
411- System . Globalization . CultureInfo . InvariantCulture ) ;
405+ var packageManagerPid = await WaitForProcessIdAsync ( packageManagerPidFile , markerTimeout . Token ) ;
406+ holderProcessId = await WaitForProcessIdAsync ( holderPidFile , markerTimeout . Token ) ;
412407 await Assert . That ( IsProcessRunning ( packageManagerPid ) ) . IsTrue ( ) ;
413408 await Assert . That ( IsProcessRunning ( holderProcessId ) ) . IsTrue ( ) ;
414409
@@ -510,10 +505,20 @@ private static string FindExecutablePath(string executableName)
510505 return Path . GetFullPath ( executable ?? throw new InvalidOperationException ( SetsidRequiredMessage ) ) ;
511506 }
512507
513- private static async Task WaitForFileAsync ( string path , CancellationToken cancellationToken )
508+ private static async Task < int > WaitForProcessIdAsync ( string path , CancellationToken cancellationToken )
514509 {
515- while ( ! File . Exists ( path ) )
510+ while ( true )
516511 {
512+ if ( File . Exists ( path ) )
513+ {
514+ var contents = await File . ReadAllTextAsync ( path , cancellationToken ) ;
515+ if ( int . TryParse ( contents , System . Globalization . CultureInfo . InvariantCulture , out var processId ) &&
516+ processId > 0 )
517+ {
518+ return processId ;
519+ }
520+ }
521+
517522 await Task . Delay ( TimeSpan . FromMilliseconds ( 10 ) , cancellationToken ) ;
518523 }
519524 }
0 commit comments