From 67055ea800ceaaa42091fd4c391acf47bc035e7c Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Thu, 10 Sep 2026 13:44:12 +1000 Subject: [PATCH 1/3] [#3127] Restored the process environment after every installer test. --- .../AbstractHandlerProcessTestCase.php | 6 +- .../AbstractHandlerDiscoveryTestCase.php | 2 + .vortex/installer/tests/Unit/SelfTest.php | 36 ++++++++++ .vortex/installer/tests/Unit/UnitTestCase.php | 65 +++++++++++++++++++ .../installer/tests/Unit/Utils/EnvTest.php | 24 ------- 5 files changed, 104 insertions(+), 29 deletions(-) diff --git a/.vortex/installer/tests/Functional/Prompts/Handlers/AbstractHandlerProcessTestCase.php b/.vortex/installer/tests/Functional/Prompts/Handlers/AbstractHandlerProcessTestCase.php index 059132549..7ba62eac4 100644 --- a/.vortex/installer/tests/Functional/Prompts/Handlers/AbstractHandlerProcessTestCase.php +++ b/.vortex/installer/tests/Functional/Prompts/Handlers/AbstractHandlerProcessTestCase.php @@ -36,11 +36,7 @@ abstract class AbstractHandlerProcessTestCase extends FunctionalTestCase { protected function setUp(): void { parent::setUp(); - static::envUnsetPrefix('VORTEX_'); - static::envUnsetPrefix('DRUPAL_'); - static::envUnsetPrefix('LAGOON_'); - static::envUnset('WEBROOT'); - static::envUnset('TZ'); + static::envUnsetProjectVars(); static::applicationInitFromCommand(InstallCommand::class); diff --git a/.vortex/installer/tests/Unit/Prompts/Handlers/AbstractHandlerDiscoveryTestCase.php b/.vortex/installer/tests/Unit/Prompts/Handlers/AbstractHandlerDiscoveryTestCase.php index 93e0116ab..1f0ae785f 100644 --- a/.vortex/installer/tests/Unit/Prompts/Handlers/AbstractHandlerDiscoveryTestCase.php +++ b/.vortex/installer/tests/Unit/Prompts/Handlers/AbstractHandlerDiscoveryTestCase.php @@ -68,6 +68,8 @@ abstract class AbstractHandlerDiscoveryTestCase extends UnitTestCase { protected function setUp(): void { parent::setUp(); + static::envUnsetProjectVars(); + static::tuiSetUp(); static::$sut = File::mkdir(static::$sut . DIRECTORY_SEPARATOR . 'myproject'); diff --git a/.vortex/installer/tests/Unit/SelfTest.php b/.vortex/installer/tests/Unit/SelfTest.php index b4ba38c22..0c1382a4f 100644 --- a/.vortex/installer/tests/Unit/SelfTest.php +++ b/.vortex/installer/tests/Unit/SelfTest.php @@ -10,6 +10,22 @@ #[CoversClass(UnitTestCase::class)] class SelfTest extends UnitTestCase { + const AMBIENT_VAR = 'VORTEX_TEST_AMBIENT_VAR'; + + const LEAKED_VAR = 'VORTEX_TEST_LEAKED_VAR'; + + public static function setUpBeforeClass(): void { + parent::setUpBeforeClass(); + + putenv(self::AMBIENT_VAR . '=ambient'); + } + + public static function tearDownAfterClass(): void { + putenv(self::AMBIENT_VAR); + + parent::tearDownAfterClass(); + } + public function testEnvCleanup1SetVariables(): void { static::envSet('VORTEX_TEST_VAR_1', 'value1'); static::envSet('VORTEX_TEST_VAR_2', 'value2'); @@ -34,4 +50,24 @@ public function testEnvCleanup2VerifyCleanup(): void { $this->assertFalse(getenv('VORTEX_TEST_VAR_4'), 'VORTEX_TEST_VAR_4 should be cleaned up after previous test'); } + public function testEnvRestore1WriteRawValues(): void { + // envSet() is bypassed here to reproduce what production code does when it + // writes the environment directly. + putenv(self::LEAKED_VAR . '=leaked'); + putenv(self::AMBIENT_VAR . '=changed'); + $_ENV[self::LEAKED_VAR] = 'leaked'; + $_SERVER[self::LEAKED_VAR] = 'leaked'; + + $this->assertSame('leaked', getenv(self::LEAKED_VAR)); + $this->assertSame('changed', getenv(self::AMBIENT_VAR)); + } + + #[Depends('testEnvRestore1WriteRawValues')] + public function testEnvRestore2VerifyRestored(): void { + $this->assertFalse(getenv(self::LEAKED_VAR), 'A variable added by the previous test should not outlive it'); + $this->assertSame('ambient', getenv(self::AMBIENT_VAR), 'A variable changed by the previous test should be back to its original value'); + $this->assertArrayNotHasKey(self::LEAKED_VAR, $_ENV); + $this->assertArrayNotHasKey(self::LEAKED_VAR, $_SERVER); + } + } diff --git a/.vortex/installer/tests/Unit/UnitTestCase.php b/.vortex/installer/tests/Unit/UnitTestCase.php index 0ed2ef6a2..6e8a7111d 100644 --- a/.vortex/installer/tests/Unit/UnitTestCase.php +++ b/.vortex/installer/tests/Unit/UnitTestCase.php @@ -25,10 +25,35 @@ abstract class UnitTestCase extends UpstreamUnitTestCase { use SnapshotTrait; use EnvTrait; + /** + * The process environment before the test ran. + * + * @var array + */ + protected array $processEnvBackup; + + /** + * The $_ENV superglobal before the test ran. + * + * @var array + */ + protected array $globalEnvBackup; + + /** + * The $_SERVER superglobal before the test ran. + * + * @var array + */ + protected array $globalServerBackup; + /** * {@inheritdoc} */ protected function setUp(): void { + $this->processEnvBackup = getenv(); + $this->globalEnvBackup = $_ENV; + $this->globalServerBackup = $_SERVER; + $cwd = getcwd(); if ($cwd === FALSE) { throw new \RuntimeException('Failed to determine current working directory.'); @@ -43,9 +68,49 @@ protected function setUp(): void { */ protected function tearDown(): void { static::envReset(); + $this->envRestore(); parent::tearDown(); } + /** + * Restore the process environment captured before the test ran. + * + * envReset() only reverses names recorded by envSet(). Code under test can + * call putenv() directly, and those values would otherwise be read by every + * later test in the same process. + */ + protected function envRestore(): void { + foreach (array_keys(getenv()) as $name) { + if (!array_key_exists($name, $this->processEnvBackup)) { + putenv($name); + } + } + + foreach ($this->processEnvBackup as $name => $value) { + if (getenv($name) !== $value) { + putenv($name . '=' . $value); + } + } + + $_ENV = $this->globalEnvBackup; + $_SERVER = $this->globalServerBackup; + } + + /** + * Unset the environment variables that a project's .env file defines. + * + * Handlers read the environment before the project's .env file, so a + * variable exported by the shell running the suite would win over the + * fixture. + */ + protected static function envUnsetProjectVars(): void { + static::envUnsetPrefix('VORTEX_'); + static::envUnsetPrefix('DRUPAL_'); + static::envUnsetPrefix('LAGOON_'); + static::envUnset('WEBROOT'); + static::envUnset('TZ'); + } + /** * {@inheritdoc} */ diff --git a/.vortex/installer/tests/Unit/Utils/EnvTest.php b/.vortex/installer/tests/Unit/Utils/EnvTest.php index b101f52b5..3426cfb6e 100644 --- a/.vortex/installer/tests/Unit/Utils/EnvTest.php +++ b/.vortex/installer/tests/Unit/Utils/EnvTest.php @@ -15,30 +15,6 @@ #[RunTestsInSeparateProcesses] class EnvTest extends UnitTestCase { - /** - * @var array - */ - protected $backupServer; - - /** - * @var array - */ - protected $backupEnv; - - protected function setUp(): void { - $this->backupEnv = $GLOBALS['_ENV']; - $this->backupServer = $GLOBALS['_SERVER']; - - parent::setUp(); - } - - protected function tearDown(): void { - $GLOBALS['_ENV'] = $this->backupEnv; - $GLOBALS['_SERVER'] = $this->backupServer; - - parent::tearDown(); - } - #[DataProvider('dataProviderGet')] public function testGet(string $name, string $value, ?string $default, ?string $expected): void { static::envSet($name, $expected); From ba706844f7924f5ff8834cf052877e6e6ee9656a Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Thu, 10 Sep 2026 13:46:27 +1000 Subject: [PATCH 2/3] [#3127] Started the 'envRestore()' docblock description with a capital letter. --- .vortex/installer/tests/Unit/UnitTestCase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vortex/installer/tests/Unit/UnitTestCase.php b/.vortex/installer/tests/Unit/UnitTestCase.php index 6e8a7111d..f4c93550e 100644 --- a/.vortex/installer/tests/Unit/UnitTestCase.php +++ b/.vortex/installer/tests/Unit/UnitTestCase.php @@ -75,9 +75,9 @@ protected function tearDown(): void { /** * Restore the process environment captured before the test ran. * - * envReset() only reverses names recorded by envSet(). Code under test can - * call putenv() directly, and those values would otherwise be read by every - * later test in the same process. + * EnvTrait::envReset() only reverses names recorded by envSet(). Code under + * test can call putenv() directly, and those values would otherwise be read + * by every later test in the same process. */ protected function envRestore(): void { foreach (array_keys(getenv()) as $name) { From 4a9f591771379afc8d77004428494181a5ce2e12 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Thu, 10 Sep 2026 14:16:41 +1000 Subject: [PATCH 3/3] Addressed code review: restored the ambient variable to its original value in 'SelfTest' class teardown. --- .vortex/installer/tests/Unit/SelfTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.vortex/installer/tests/Unit/SelfTest.php b/.vortex/installer/tests/Unit/SelfTest.php index 0c1382a4f..b40a37251 100644 --- a/.vortex/installer/tests/Unit/SelfTest.php +++ b/.vortex/installer/tests/Unit/SelfTest.php @@ -14,14 +14,25 @@ class SelfTest extends UnitTestCase { const LEAKED_VAR = 'VORTEX_TEST_LEAKED_VAR'; + /** + * @var string|false + */ + protected static $ambientOriginal; + public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); + static::$ambientOriginal = getenv(self::AMBIENT_VAR); putenv(self::AMBIENT_VAR . '=ambient'); } public static function tearDownAfterClass(): void { - putenv(self::AMBIENT_VAR); + if (static::$ambientOriginal === FALSE) { + putenv(self::AMBIENT_VAR); + } + else { + putenv(self::AMBIENT_VAR . '=' . static::$ambientOriginal); + } parent::tearDownAfterClass(); }