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..b40a37251 100644 --- a/.vortex/installer/tests/Unit/SelfTest.php +++ b/.vortex/installer/tests/Unit/SelfTest.php @@ -10,6 +10,33 @@ #[CoversClass(UnitTestCase::class)] class SelfTest extends UnitTestCase { + const AMBIENT_VAR = 'VORTEX_TEST_AMBIENT_VAR'; + + 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 { + if (static::$ambientOriginal === FALSE) { + putenv(self::AMBIENT_VAR); + } + else { + putenv(self::AMBIENT_VAR . '=' . static::$ambientOriginal); + } + + parent::tearDownAfterClass(); + } + public function testEnvCleanup1SetVariables(): void { static::envSet('VORTEX_TEST_VAR_1', 'value1'); static::envSet('VORTEX_TEST_VAR_2', 'value2'); @@ -34,4 +61,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..f4c93550e 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. + * + * 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) { + 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);