@@ -6,6 +6,7 @@ import { AlreadyExistsBehavior, FileSystem, PackageJsonLookup } from '@rushstack
66import type { RushCommandLineParser as RushCommandLineParserType } from '../RushCommandLineParser' ;
77import { FlagFile } from '../../api/FlagFile' ;
88import { RushConstants } from '../../logic/RushConstants' ;
9+ import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration' ;
910
1011export type SpawnMockArgs = Parameters < typeof import ( 'node:child_process' ) . spawn > ;
1112export type SpawnMock = jest . Mock < ReturnType < typeof import ( 'node:child_process' ) . spawn > , SpawnMockArgs > ;
@@ -37,6 +38,23 @@ export interface IChildProcessModuleMock {
3738 spawn : jest . Mock ;
3839}
3940
41+ const DEFAULT_RUSH_ENV_VARS_TO_CLEAR : ReadonlyArray < string > = [
42+ 'RUSH_BUILD_CACHE_OVERRIDE_JSON' ,
43+ 'RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH' ,
44+ 'RUSH_BUILD_CACHE_CREDENTIAL' ,
45+ 'RUSH_BUILD_CACHE_ENABLED' ,
46+ 'RUSH_BUILD_CACHE_WRITE_ALLOWED'
47+ ] ;
48+
49+ export interface IWithEnvironmentConfigIsolationOptions {
50+ envVarNamesToClear ?: ReadonlyArray < string > ;
51+ silenceStderrWrite ?: boolean ;
52+ }
53+
54+ export interface IEnvironmentConfigIsolation {
55+ restore ( ) : void ;
56+ }
57+
4058/**
4159 * Configure the `child_process` `spawn` mock for these tests. This relies on the mock implementation
4260 * in `mock_child_process`.
@@ -102,3 +120,81 @@ export async function getCommandLineParserInstanceAsync(
102120 repoPath
103121 } ;
104122}
123+
124+ /**
125+ * Clears Rush-related environment variables and resets EnvironmentConfiguration for deterministic tests.
126+ *
127+ * Notes:
128+ * - EnvironmentConfiguration caches some values, so we also stub the build-cache override getters.
129+ * - Rush treats any stderr output during `rush test` as a warning, which fails the command; some
130+ * tests intentionally simulate failures and may need stderr silenced.
131+ */
132+ export function isolateEnvironmentConfigurationForTests (
133+ options : IWithEnvironmentConfigIsolationOptions = { }
134+ ) : IEnvironmentConfigIsolation {
135+ const envVarNamesToClear : ReadonlyArray < string > =
136+ options . envVarNamesToClear ?? DEFAULT_RUSH_ENV_VARS_TO_CLEAR ;
137+
138+ const savedProcessEnv : Record < string , string | undefined > = { } ;
139+ for ( const envVarName of envVarNamesToClear ) {
140+ savedProcessEnv [ envVarName ] = process . env [ envVarName ] ;
141+ delete process . env [ envVarName ] ;
142+ }
143+
144+ EnvironmentConfiguration . reset ( ) ;
145+
146+ const restoreFns : Array < ( ) => void > = [ ] ;
147+
148+ restoreFns . push ( ( ) => {
149+ for ( const envVarName of envVarNamesToClear ) {
150+ const oldValue : string | undefined = savedProcessEnv [ envVarName ] ;
151+ if ( oldValue === undefined ) {
152+ delete process . env [ envVarName ] ;
153+ } else {
154+ process . env [ envVarName ] = oldValue ;
155+ }
156+ }
157+ } ) ;
158+
159+ if ( options . silenceStderrWrite ) {
160+ type StderrWrite = typeof process . stderr . write ;
161+ const silentWrite : unknown = (
162+ chunk : string | Uint8Array ,
163+ encoding ?: BufferEncoding | ( ( err ?: Error | null ) => void ) ,
164+ cb ?: ( err ?: Error | null ) => void
165+ ) : boolean => {
166+ if ( typeof encoding === 'function' ) {
167+ encoding ( null ) ;
168+ } else {
169+ cb ?.( null ) ;
170+ }
171+ return true ;
172+ } ;
173+
174+ const writeSpy : jest . SpyInstance < ReturnType < StderrWrite > , Parameters < StderrWrite > > = jest
175+ . spyOn ( process . stderr , 'write' )
176+ . mockImplementation ( silentWrite as StderrWrite ) ;
177+
178+ restoreFns . push ( ( ) => writeSpy . mockRestore ( ) ) ;
179+ }
180+
181+ // EnvironmentConfiguration.reset() does not clear cached values for these fields.
182+ const overrideJsonFilePathSpy : jest . SpyInstance < string | undefined , [ ] > = jest
183+ . spyOn ( EnvironmentConfiguration , 'buildCacheOverrideJsonFilePath' , 'get' )
184+ . mockReturnValue ( undefined ) ;
185+ const overrideJsonSpy : jest . SpyInstance < string | undefined , [ ] > = jest
186+ . spyOn ( EnvironmentConfiguration , 'buildCacheOverrideJson' , 'get' )
187+ . mockReturnValue ( undefined ) ;
188+
189+ restoreFns . push ( ( ) => overrideJsonFilePathSpy . mockRestore ( ) ) ;
190+ restoreFns . push ( ( ) => overrideJsonSpy . mockRestore ( ) ) ;
191+ restoreFns . push ( ( ) => EnvironmentConfiguration . reset ( ) ) ;
192+
193+ return {
194+ restore : ( ) => {
195+ for ( let i : number = restoreFns . length - 1 ; i >= 0 ; i -- ) {
196+ restoreFns [ i ] ( ) ;
197+ }
198+ }
199+ } ;
200+ }
0 commit comments