Skip to content

Commit 3acfefd

Browse files
committed
[console] Align structured output detection
1 parent 08d87f3 commit 3acfefd

5 files changed

Lines changed: 88 additions & 36 deletions

File tree

bin/dev-tools.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
use FastForward\DevTools\Console\DevTools;
2323

24-
$autoloadCandidates = [\dirname(__DIR__) . '/vendor/autoload.php', \dirname(__DIR__, 4) . '/vendor/autoload.php'];
24+
$autoloadCandidates = [\dirname(__DIR__, 4) . '/vendor/autoload.php', \dirname(__DIR__) . '/vendor/autoload.php'];
2525

2626
foreach ($autoloadCandidates as $autoloadCandidate) {
2727
if (is_file($autoloadCandidate)) {

src/Console/Command/TestsCommand.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,15 @@ private function forceAgentReporter(Process $process): void
320320
{
321321
$env = $process->getEnv();
322322

323-
if (\array_key_exists(self::AGENT_ENVIRONMENT_VARIABLE, $env)) {
323+
if (\array_key_exists(self::AGENT_ENVIRONMENT_VARIABLE, $env) || false !== getenv(
324+
self::AGENT_ENVIRONMENT_VARIABLE
325+
)) {
324326
return;
325327
}
326328

329+
// Intentionally reuse the reporter's existing agent detection path for
330+
// structured DevTools output instead of maintaining a separate
331+
// integration that would need to mirror the plugin behavior.
327332
$env[self::AGENT_ENVIRONMENT_VARIABLE] = self::AGENT_ENVIRONMENT_VALUE;
328333
$process->setEnv($env);
329334
}

src/Console/Logger/OutputFormatLogger.php

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

2222
use Stringable;
2323
use DateTimeInterface;
24-
use Ergebnis\AgentDetector\Detector;
24+
use FastForward\DevTools\Environment\RuntimeEnvironmentInterface;
2525
use FastForward\DevTools\Console\Logger\Processor\ContextProcessorInterface;
2626
use FastForward\DevTools\Console\Output\GithubActionOutput;
2727
use Psr\Clock\ClockInterface;
@@ -58,15 +58,15 @@
5858
* @param ArgvInput $input the CLI input instance used to inspect runtime options
5959
* @param ConsoleOutputInterface $output the console output instance used for writing log messages
6060
* @param ClockInterface $clock provides timestamps for rendered log entries
61-
* @param Detector $agentDetector detects agent-oriented execution environments
61+
* @param RuntimeEnvironmentInterface $runtimeEnvironment resolves runtime-specific output behavior
6262
* @param ContextProcessorInterface $contextProcessor expands command input and output metadata
6363
* @param GithubActionOutput $githubActionOutput emits GitHub Actions annotations when supported
6464
*/
6565
public function __construct(
6666
private ArgvInput $input,
6767
private ConsoleOutputInterface $output,
6868
private ClockInterface $clock,
69-
private Detector $agentDetector,
69+
private RuntimeEnvironmentInterface $runtimeEnvironment,
7070
private ContextProcessorInterface $contextProcessor,
7171
private GithubActionOutput $githubActionOutput,
7272
) {}
@@ -179,7 +179,7 @@ private function isJsonOutput(): bool
179179
return true;
180180
}
181181

182-
return $this->agentDetector->isAgentPresent($_SERVER);
182+
return $this->runtimeEnvironment->isAgentPresent() && ! $this->runtimeEnvironment->isComposerTestRun();
183183
}
184184

185185
/**

tests/Console/Command/TestsCommandTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,15 @@ final class TestsCommandTest extends TestCase
8989

9090
private TestsCommand $command;
9191

92+
private string|false $agentEnvironment;
93+
9294
/**
9395
* @return void
9496
*/
9597
protected function setUp(): void
9698
{
9799
ContainerFactory::reset();
100+
$this->agentEnvironment = getenv('AI_AGENT');
98101
$this->coverageSummaryLoader = $this->prophesize(CoverageSummaryLoaderInterface::class);
99102
$this->composerJson = $this->prophesize(ComposerJsonInterface::class);
100103
$this->filesystem = $this->prophesize(FilesystemInterface::class);
@@ -168,6 +171,14 @@ protected function setUp(): void
168171
protected function tearDown(): void
169172
{
170173
ContainerFactory::reset();
174+
175+
if (false === $this->agentEnvironment) {
176+
putenv('AI_AGENT');
177+
178+
return;
179+
}
180+
181+
putenv('AI_AGENT=' . $this->agentEnvironment);
171182
}
172183

173184
/**
@@ -322,6 +333,37 @@ public function executeWillCaptureStructuredPhpUnitSummaryWhenAgentEnvironmentIs
322333
self::assertSame(TestsCommand::SUCCESS, $this->invokeExecute());
323334
}
324335

336+
/**
337+
* @return void
338+
*/
339+
#[Test]
340+
public function executeWillPreserveAnInheritedAgentEnvironmentWhenForcingStructuredPhpUnitOutput(): void
341+
{
342+
putenv('AI_AGENT=existing-agent');
343+
344+
$this->input->getOption('json')
345+
->willReturn(true);
346+
$this->input->getOption('pretty-json')
347+
->willReturn(false);
348+
349+
$this->processQueue->add(
350+
Argument::that(static fn(Process $process): bool => ! \array_key_exists('AI_AGENT', $process->getEnv())),
351+
false,
352+
false,
353+
'Running PHPUnit Tests'
354+
)->shouldBeCalled();
355+
$this->processQueue->run(Argument::type(OutputInterface::class))
356+
->willReturn(TestsCommand::SUCCESS)->shouldBeCalled();
357+
$this->logger->info(Argument::cetera())->shouldNotBeCalled();
358+
$this->logger->log(
359+
'info',
360+
'PHPUnit tests completed successfully.',
361+
Argument::type('array'),
362+
)->shouldBeCalled();
363+
364+
self::assertSame(TestsCommand::SUCCESS, $this->invokeExecute());
365+
}
366+
325367
/**
326368
* @return void
327369
*/

tests/Console/Logger/OutputFormatLoggerTest.php

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
use stdClass;
2323
use DateTimeImmutable;
24-
use Ergebnis\AgentDetector\Detector;
2524
use FastForward\DevTools\Console\Logger\OutputFormatLogger;
2625
use FastForward\DevTools\Console\Logger\Processor\CommandInputProcessor;
2726
use FastForward\DevTools\Console\Logger\Processor\CommandOutputProcessor;
@@ -43,7 +42,6 @@
4342
use Symfony\Component\Console\Output\OutputInterface;
4443

4544
use function Safe\json_decode;
46-
use function Safe\putenv;
4745

4846
#[CoversClass(OutputFormatLogger::class)]
4947
#[UsesClass(CommandInputProcessor::class)]
@@ -65,29 +63,21 @@ final class OutputFormatLoggerTest extends TestCase
6563
*/
6664
private ObjectProphecy $environment;
6765

68-
/**
69-
* @var array<string, mixed>
70-
*/
71-
private array $server;
72-
73-
private string|false $composerTestsAreRunningEnv;
74-
7566
/**
7667
* @return void
7768
*/
7869
protected function setUp(): void
7970
{
80-
$this->server = $_SERVER;
81-
$_SERVER = [];
82-
$this->composerTestsAreRunningEnv = getenv('COMPOSER_TESTS_ARE_RUNNING');
83-
putenv('COMPOSER_TESTS_ARE_RUNNING=1');
84-
8571
$this->output = $this->prophesize(ConsoleOutputInterface::class);
8672
$this->errorOutput = $this->prophesize(OutputInterface::class);
8773
$this->clock = $this->prophesize(ClockInterface::class);
8874
$this->environment = $this->prophesize(RuntimeEnvironmentInterface::class);
8975
$this->environment->isGithubActions()
9076
->willReturn(false);
77+
$this->environment->isAgentPresent()
78+
->willReturn(false);
79+
$this->environment->isComposerTestRun()
80+
->willReturn(false);
9181

9282
$this->output->getErrorOutput()
9383
->willReturn($this->errorOutput->reveal());
@@ -105,7 +95,7 @@ public function logWillWriteInterpolatedInfoMessagesToStandardOutput(): void
10595
new ArgvInput(['dev-tools']),
10696
$this->output->reveal(),
10797
$this->clock->reveal(),
108-
new Detector(),
98+
$this->environment->reveal(),
10999
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
110100
$this->createGithubActionOutput(),
111101
);
@@ -141,7 +131,7 @@ public function logWillWriteErrorMessagesToErrorOutput(): void
141131
new ArgvInput(['dev-tools']),
142132
$this->output->reveal(),
143133
$this->clock->reveal(),
144-
new Detector(),
134+
$this->environment->reveal(),
145135
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
146136
$this->createGithubActionOutput(),
147137
);
@@ -170,7 +160,7 @@ public function logWillWriteStructuredJsonWhenJsonOutputIsRequested(): void
170160
new ArgvInput(['dev-tools', '--json']),
171161
$this->output->reveal(),
172162
$this->clock->reveal(),
173-
new Detector(),
163+
$this->environment->reveal(),
174164
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
175165
$this->createGithubActionOutput(),
176166
);
@@ -197,7 +187,7 @@ public function logWillEmitParseableJsonWhenJsonOutputIsRequested(): void
197187
new ArgvInput(['dev-tools', '--json']),
198188
$this->output->reveal(),
199189
$this->clock->reveal(),
200-
new Detector(),
190+
$this->environment->reveal(),
201191
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
202192
$this->createGithubActionOutput(),
203193
);
@@ -223,7 +213,7 @@ public function logWillWritePrettyPrintedJsonWhenPrettyJsonOutputIsRequested():
223213
new ArgvInput(['dev-tools', '--pretty-json']),
224214
$this->output->reveal(),
225215
$this->clock->reveal(),
226-
new Detector(),
216+
$this->environment->reveal(),
227217
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
228218
$this->createGithubActionOutput(),
229219
);
@@ -250,7 +240,7 @@ public function logWillEmitParseableJsonWhenPrettyJsonOutputIsRequested(): void
250240
new ArgvInput(['dev-tools', '--pretty-json']),
251241
$this->output->reveal(),
252242
$this->clock->reveal(),
253-
new Detector(),
243+
$this->environment->reveal(),
254244
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
255245
$this->createGithubActionOutput(),
256246
);
@@ -276,7 +266,7 @@ public function logWillEmbedDecodedStructuredCommandOutputInsteadOfEscapedJsonSt
276266
new ArgvInput(['dev-tools', '--pretty-json']),
277267
$this->output->reveal(),
278268
$this->clock->reveal(),
279-
new Detector(),
269+
$this->environment->reveal(),
280270
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
281271
$this->createGithubActionOutput(),
282272
);
@@ -314,13 +304,16 @@ public function logWillEmbedDecodedStructuredCommandOutputInsteadOfEscapedJsonSt
314304
#[Test]
315305
public function logWillWriteStructuredJsonWhenAgentEnvironmentIsDetected(): void
316306
{
317-
$_SERVER['CODEX_THREAD_ID'] = 'thread-123';
307+
$this->environment->isAgentPresent()
308+
->willReturn(true);
309+
$this->environment->isComposerTestRun()
310+
->willReturn(false);
318311

319312
$logger = new OutputFormatLogger(
320313
new ArgvInput(['dev-tools']),
321314
$this->output->reveal(),
322315
$this->clock->reveal(),
323-
new Detector(),
316+
$this->environment->reveal(),
324317
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
325318
$this->createGithubActionOutput(),
326319
);
@@ -339,17 +332,29 @@ public function logWillWriteStructuredJsonWhenAgentEnvironmentIsDetected(): void
339332
/**
340333
* @return void
341334
*/
342-
protected function tearDown(): void
335+
#[Test]
336+
public function logWillKeepPlainTextOutputWhenAgentEnvironmentIsDetectedDuringComposerTests(): void
343337
{
344-
$_SERVER = $this->server;
338+
$this->environment->isAgentPresent()
339+
->willReturn(true);
340+
$this->environment->isComposerTestRun()
341+
->willReturn(true);
345342

346-
if (false === $this->composerTestsAreRunningEnv) {
347-
putenv('COMPOSER_TESTS_ARE_RUNNING');
343+
$logger = new OutputFormatLogger(
344+
new ArgvInput(['dev-tools']),
345+
$this->output->reveal(),
346+
$this->clock->reveal(),
347+
$this->environment->reveal(),
348+
new CompositeContextProcessor([new CommandInputProcessor(), new CommandOutputProcessor()]),
349+
$this->createGithubActionOutput(),
350+
);
348351

349-
return;
350-
}
352+
$this->output->writeln('<info>2026-04-21T16:00:00+00:00 [INFO] Agent ready</info>')
353+
->shouldBeCalledOnce();
354+
$this->errorOutput->writeln(Argument::type('string'))
355+
->shouldNotBeCalled();
351356

352-
putenv('COMPOSER_TESTS_ARE_RUNNING=' . $this->composerTestsAreRunningEnv);
357+
$logger->info('Agent ready');
353358
}
354359

355360
/**

0 commit comments

Comments
 (0)