Skip to content

Commit aa88ab3

Browse files
committed
Apply dogfood project environment variables
1 parent c5109e2 commit aa88ab3

13 files changed

Lines changed: 98 additions & 22 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Apply configured environment variables to dogfood processes
56
- Navigate and complete form fields mapped to data class properties
67
- Complete and navigate PHP constants and enums in Twig
78

tests/Tool/Dogfood/ComposerSetupTest.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ public function testInstallsThePinnedDependencySet(): void
3535
self::assertCount(1, $processes->calls);
3636
self::assertSame(['composer', 'install', '--no-interaction', '--no-progress'], $processes->calls[0]['command']);
3737
self::assertSame($this->directory, $processes->calls[0]['directory']);
38+
self::assertSame([], $processes->calls[0]['environment']);
39+
}
40+
41+
public function testPassesConfiguredEnvironmentVariablesToComposer(): void
42+
{
43+
file_put_contents(Path::join($this->directory, 'composer.lock'), '{}');
44+
$processes = new FakeProcessRunner(static fn (): ProcessResult => new ProcessResult(0, '', '', false));
45+
46+
(new ComposerSetup($processes))->setUp($this->configuration(environmentVariables: ['DATABASE_URL' => 'mysql://root@127.0.0.1:9/app']), $this->directory);
47+
48+
self::assertSame(['DATABASE_URL' => 'mysql://root@127.0.0.1:9/app'], $processes->calls[0]['environment']);
3849
}
3950

4051
public function testSkipsScriptsWhenDisabled(): void
@@ -109,10 +120,11 @@ public function testReportsInstallationFailures(): void
109120
}
110121

111122
/**
112-
* @param list<string> $allowPlugins
123+
* @param list<string> $allowPlugins
124+
* @param array<string, string> $environmentVariables
113125
*/
114-
private function configuration(?string $lockFile = null, array $allowPlugins = []): ProjectConfiguration
126+
private function configuration(?string $lockFile = null, array $allowPlugins = [], array $environmentVariables = []): ProjectConfiguration
115127
{
116-
return new ProjectConfiguration('acme', 'https://github.com/acme/app.git', str_repeat('a', 40), null, 'dev', 'composer', false, 120, lockFile: $lockFile, allowPlugins: $allowPlugins);
128+
return new ProjectConfiguration('acme', 'https://github.com/acme/app.git', str_repeat('a', 40), null, 'dev', 'composer', false, 120, lockFile: $lockFile, allowPlugins: $allowPlugins, environmentVariables: $environmentVariables);
117129
}
118130
}

tests/Tool/Dogfood/ConfigurationLoaderTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public function testLoadsMinimalConfigurationWithDefaults(): void
4545
self::assertSame(self::REVISION, $configuration->revision);
4646
self::assertNull($configuration->directory);
4747
self::assertSame('dev', $configuration->environment);
48+
self::assertSame([], $configuration->environmentVariables);
4849
self::assertSame('composer', $configuration->setup);
4950
self::assertTrue($configuration->ci);
5051
self::assertSame(120, $configuration->indexTimeout);
@@ -61,6 +62,10 @@ public function testLoadsFullConfiguration(): void
6162
'revision' => self::REVISION,
6263
'directory' => 'twig.symfony.com',
6364
'environment' => 'test',
65+
'environmentVariables' => [
66+
'DATABASE_URL' => 'mysql://root@127.0.0.1:9/app',
67+
'APP_SECRET' => 'dogfood-not-a-secret',
68+
],
6469
'setup' => 'composer',
6570
'ci' => false,
6671
'indexTimeout' => 300,
@@ -75,6 +80,10 @@ public function testLoadsFullConfiguration(): void
7580
self::assertSame('twig.symfony.com', $configuration->name);
7681
self::assertSame('twig.symfony.com', $configuration->directory);
7782
self::assertSame('test', $configuration->environment);
83+
self::assertSame([
84+
'APP_SECRET' => 'dogfood-not-a-secret',
85+
'DATABASE_URL' => 'mysql://root@127.0.0.1:9/app',
86+
], $configuration->environmentVariables);
7887
self::assertFalse($configuration->ci);
7988
self::assertSame(300, $configuration->indexTimeout);
8089
self::assertSame(20, $configuration->requestTimeout);
@@ -176,6 +185,10 @@ public static function invalidConfigurationProvider(): iterable
176185
yield 'absolute directory' => [['directory' => '/srv'], 'relative path inside the repository'];
177186
yield 'parent directory' => [['directory' => '../other'], 'relative path inside the repository'];
178187
yield 'invalid environment' => [['environment' => 'dev; rm'], 'simple environment name'];
188+
yield 'environment variable list' => [['environmentVariables' => ['DATABASE_URL']], 'map of environment variable names to string values'];
189+
yield 'invalid environment variable name' => [['environmentVariables' => ['database-url' => 'value']], 'map of environment variable names to string values'];
190+
yield 'non-string environment variable' => [['environmentVariables' => ['DATABASE_URL' => 42]], 'map of environment variable names to string values'];
191+
yield 'null byte environment variable' => [['environmentVariables' => ['DATABASE_URL' => "value\0suffix"]], 'map of environment variable names to string values'];
179192
yield 'unknown setup' => [['setup' => 'shell'], 'The "setup" in'];
180193
yield 'missing ci' => [['ci' => null], 'boolean "ci"'];
181194
yield 'string ci' => [['ci' => 'yes'], 'boolean "ci"'];

tests/Tool/Dogfood/FakeProcessRunner.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77

88
final class FakeProcessRunner implements ProcessRunnerInterface
99
{
10-
/** @var list<array{command: list<string>, directory: ?string, timeout: float}> */
10+
/** @var list<array{command: list<string>, directory: ?string, timeout: float, environment: array<string, string>}> */
1111
public array $calls = [];
1212

1313
/**
14-
* @param \Closure(list<string>, ?string): ProcessResult $handler
14+
* @param \Closure(list<string>, ?string, array<string, string>): ProcessResult $handler
1515
*/
1616
public function __construct(
1717
private \Closure $handler,
1818
) {
1919
}
2020

21-
public function run(array $command, ?string $directory = null, float $timeout = 600.0): ProcessResult
21+
public function run(array $command, ?string $directory = null, float $timeout = 600.0, array $environment = []): ProcessResult
2222
{
23-
$this->calls[] = ['command' => $command, 'directory' => $directory, 'timeout' => $timeout];
23+
$this->calls[] = ['command' => $command, 'directory' => $directory, 'timeout' => $timeout, 'environment' => $environment];
2424

25-
return ($this->handler)($command, $directory);
25+
return ($this->handler)($command, $directory, $environment);
2626
}
2727
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Symfony\Lsp\Tests\Tool\Dogfood;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Lsp\Tools\Dogfood\NativeProcessRunner;
7+
8+
final class NativeProcessRunnerTest extends TestCase
9+
{
10+
public function testOverridesTheInheritedEnvironment(): void
11+
{
12+
$result = (new NativeProcessRunner())->run(
13+
[\PHP_BINARY, '-r', 'fwrite(STDOUT, (string) getenv("SYMFONY_LSP_DOGFOOD_TEST"));'],
14+
environment: ['SYMFONY_LSP_DOGFOOD_TEST' => 'configured'],
15+
);
16+
17+
self::assertTrue($result->successful());
18+
self::assertSame('configured', $result->standardOutput);
19+
}
20+
}

tests/Tool/Dogfood/ServerHarnessTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,13 @@ public function testDerivesTheProcessBudgetFromDiscoveredProbes(array $files, fl
4646
20,
4747
requestTimeout: 3,
4848
probeRoots: ['src', 'templates'],
49+
environmentVariables: ['DATABASE_URL' => 'mysql://root@127.0.0.1:9/app'],
4950
);
5051

5152
(new ServerHarness($processes, '/tools/dogfood-server', '/bin/symfony-lsp'))->run($configuration, $this->directory);
5253

5354
self::assertSame($expectedTimeout, $processes->calls[0]['timeout']);
55+
self::assertSame(['DATABASE_URL' => 'mysql://root@127.0.0.1:9/app'], $processes->calls[0]['environment']);
5456
}
5557

5658
/** @return iterable<string, array{array<string, string>, float}> */

tools/dogfood/ComposerSetup.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function setUp(ProjectConfiguration $configuration, string $applicationRo
2323
}
2424
$manifest = [] === $configuration->allowPlugins ? false : @file_get_contents($applicationRoot.'/composer.json');
2525
foreach ($configuration->allowPlugins as $plugin) {
26-
$allow = $this->processes->run(['composer', 'config', '--no-plugins', '--no-interaction', 'allow-plugins.'.$plugin, 'true'], $applicationRoot, $this->timeout);
26+
$allow = $this->processes->run(['composer', 'config', '--no-plugins', '--no-interaction', 'allow-plugins.'.$plugin, 'true'], $applicationRoot, $this->timeout, $configuration->environmentVariables);
2727
if (!$allow->successful()) {
2828
throw new SetupException(\sprintf('Unable to allow the Composer plugin "%s".', $plugin));
2929
}
@@ -35,7 +35,7 @@ public function setUp(ProjectConfiguration $configuration, string $applicationRo
3535
if (!$this->scripts) {
3636
$command[] = '--no-scripts';
3737
}
38-
$result = $this->processes->run($command, $applicationRoot, $this->timeout);
38+
$result = $this->processes->run($command, $applicationRoot, $this->timeout, $configuration->environmentVariables);
3939
if (false !== $manifest) {
4040
file_put_contents($applicationRoot.'/composer.json', $manifest);
4141
}

tools/dogfood/ConfigurationLoader.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
final class ConfigurationLoader
66
{
77
private const VERSION = 1;
8-
private const KEYS = ['version', 'repository', 'revision', 'directory', 'environment', 'setup', 'ci', 'indexTimeout', 'requestTimeout', 'probeRoots', 'probesPerCategory', 'allowPlugins', 'ignorePlatformRequirements', 'setupChanges'];
8+
private const KEYS = ['version', 'repository', 'revision', 'directory', 'environment', 'environmentVariables', 'setup', 'ci', 'indexTimeout', 'requestTimeout', 'probeRoots', 'probesPerCategory', 'allowPlugins', 'ignorePlatformRequirements', 'setupChanges'];
99
private const DEFAULT_INDEX_TIMEOUT = 120;
1010
private const MAX_INDEX_TIMEOUT = 900;
1111
private const DEFAULT_REQUEST_TIMEOUT = 10;
@@ -85,6 +85,7 @@ private function loadFile(string $file, array $setupIds): ProjectConfiguration
8585
$this->allowPlugins($data, $file),
8686
$this->ignorePlatformRequirements($data, $file),
8787
$this->setupChanges($data, $file),
88+
$this->environmentVariables($data, $file),
8889
);
8990
}
9091

@@ -149,6 +150,27 @@ private function environment(array $data, string $file): string
149150
return $environment;
150151
}
151152

153+
/**
154+
* @param array<array-key, mixed> $data
155+
*
156+
* @return array<string, string>
157+
*/
158+
private function environmentVariables(array $data, string $file): array
159+
{
160+
$variables = $data['environmentVariables'] ?? [];
161+
if (!\is_array($variables) || ([] !== $variables && array_is_list($variables))) {
162+
throw new ConfigurationException(\sprintf('The "environmentVariables" in "%s" must be a map of environment variable names to string values.', $file));
163+
}
164+
foreach ($variables as $name => $value) {
165+
if (!\is_string($name) || 1 !== preg_match('/^[A-Z_][A-Z0-9_]*$/', $name) || !\is_string($value) || str_contains($value, "\0")) {
166+
throw new ConfigurationException(\sprintf('The "environmentVariables" in "%s" must be a map of environment variable names to string values.', $file));
167+
}
168+
}
169+
ksort($variables);
170+
171+
return $variables;
172+
}
173+
152174
/**
153175
* @param array<array-key, mixed> $data
154176
* @param list<string> $setupIds

tools/dogfood/NativeProcessRunner.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111

1212
final class NativeProcessRunner implements ProcessRunnerInterface
1313
{
14-
public function run(array $command, ?string $directory = null, float $timeout = 600.0): ProcessResult
14+
public function run(array $command, ?string $directory = null, float $timeout = 600.0, array $environment = []): ProcessResult
1515
{
16-
$environment = [];
16+
$inheritedEnvironment = [];
1717
foreach (getenv() as $key => $value) {
18-
$environment[(string) $key] = $value;
18+
$inheritedEnvironment[(string) $key] = $value;
1919
}
20-
$process = Process::start($command, $directory, $environment);
20+
$process = Process::start($command, $directory, array_replace($inheritedEnvironment, $environment));
2121
$process->getStdin()->close();
2222
/** @var \Amp\Future<string> $stdout */
2323
$stdout = async(static fn (): string => buffer($process->getStdout()));

tools/dogfood/ProcessRunnerInterface.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
interface ProcessRunnerInterface
66
{
77
/**
8-
* @param list<string> $command
8+
* @param list<string> $command
9+
* @param array<string, string> $environment
910
*/
10-
public function run(array $command, ?string $directory = null, float $timeout = 600.0): ProcessResult;
11+
public function run(array $command, ?string $directory = null, float $timeout = 600.0, array $environment = []): ProcessResult;
1112
}

0 commit comments

Comments
 (0)