Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better rendering of run errors #571

Merged
merged 1 commit into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Not released yet

### Features

* Better rendering of run errors

### Internal

* Rework the releasing
Expand Down
4 changes: 2 additions & 2 deletions examples/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ function testFile(): int
}

#[AsTask(description: 'Run a command that will fail')]
function exception(): void
function exception(int $repeat = 1): void
{
if (!output()->isVerbose()) {
output()->writeln('Re-run with -v, -vv, -vvv for different output.');
}

run('echo foo; echo bar>&2; exit 1', context: context()->withPty(false)->withQuiet());
run('echo $foo; echo ' . str_repeat('bar', $repeat) . '>&2; exit 1', context: context()->withPty(false)->withQuiet()->withEnvironment(['foo' => 'bar']));
}

#[AsTask(description: 'Run a sub-process and display information about it, with ProcessHelper')]
Expand Down
17 changes: 17 additions & 0 deletions src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
use Castor\Container;
use Castor\Exception\ProblemException;
use Castor\Kernel;
use Castor\Runner\ProcessRunner;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Process\Exception\ProcessFailedException;

/** @internal */
class Application extends SymfonyApplication
Expand All @@ -28,6 +31,8 @@ public function __construct(
private readonly Kernel $kernel,
#[Autowire(lazy: true)]
private readonly SymfonyStyle $io,
#[Autowire(lazy: true)]
private readonly ProcessRunner $processRunner,
) {
parent::__construct(static::NAME, static::VERSION);
}
Expand Down Expand Up @@ -68,6 +73,18 @@ public function renderThrowable(\Throwable $e, OutputInterface $output): void

return;
}

if ($e instanceof ProcessFailedException) {
$process = $e->getProcess();
$runnable = $this->processRunner->buildRunnableCommand($process);

$this->io->writeln(sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))));
$this->io->error('The following process did not finish successfully (exit code ' . $process->getExitCode() . '):');
$this->io->writeln("<fg=yellow>{$runnable}</>");
$this->io->newLine();

return;
}
}

parent::renderThrowable($e, $output);
Expand Down
23 changes: 10 additions & 13 deletions src/Monolog/Processor/ProcessProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

namespace Castor\Monolog\Processor;

use Castor\Runner\ProcessRunner;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Process\Process;

/** @internal */
class ProcessProcessor implements ProcessorInterface
final class ProcessProcessor implements ProcessorInterface
{
public function __construct(
#[Autowire(lazy: true)]
private readonly ProcessRunner $processRunner,
) {
}

public function __invoke(LogRecord $record): LogRecord
{
foreach ($record->context as $key => $value) {
Expand All @@ -30,21 +38,10 @@ public function __invoke(LogRecord $record): LogRecord
*/
private function formatProcess(Process $process): array
{
$runnable = $process->getCommandLine();

foreach ($process->getEnv() as $key => $value) {
if ('argv' === $key || 'argc' === $key) {
continue;
}
$runnable = \sprintf('%s=%s %s ', $key, escapeshellarg($value), $runnable);
}

$runnable = rtrim($runnable, ' ');

return [
'cwd' => $process->getWorkingDirectory(),
'env' => $process->getEnv(),
'runnable' => $runnable,
'runnable' => $this->processRunner->buildRunnableCommand($process),
];
}
}
23 changes: 17 additions & 6 deletions src/Runner/ProcessRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\Process\Process;

use function Castor\context;
use function Symfony\Component\String\u;

/** @internal */
class ProcessRunner
Expand Down Expand Up @@ -156,7 +157,7 @@ public function run(

$this->eventDispatcher->dispatch(new Event\ProcessCreatedEvent($process));

$this->logger->notice(\sprintf('Running command: "%s".', $process->getCommandLine()), [
$this->logger->notice(\sprintf('Running command: "%s".', u($process->getCommandLine())->truncate(40, '...')), [
'process' => $process,
]);

Expand Down Expand Up @@ -213,11 +214,7 @@ public function run(
}

if (!$context->allowFailure) {
if ($context->verbosityLevel->isVerbose()) {
throw new ProcessFailedException($process);
}

throw new \RuntimeException("The command \"{$process->getCommandLine()}\" failed.");
throw new ProcessFailedException($process);
}

return $process;
Expand Down Expand Up @@ -291,4 +288,18 @@ public function exitCode(

return $process->getExitCode() ?? 0;
}

public function buildRunnableCommand(Process $process): string
{
$runnable = $process->getCommandLine();

foreach ($process->getEnv() as $key => $value) {
if ('argv' === $key || 'argc' === $key) {
continue;
}
$runnable = \sprintf('%s=%s %s ', $key, escapeshellarg($value), $runnable);
}

return rtrim($runnable, ' ');
}
}
8 changes: 0 additions & 8 deletions tests/Generated/FailureFailureTest.php.err.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1 @@
bash: line 1: i_do_not_exist: command not found

In failure.php line XXXX:

The command "bash -c i_do_not_exist" failed.


failure:failure

6 changes: 6 additions & 0 deletions tests/Generated/FailureFailureTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
In failure.php line XXXX:

[ERROR] The following process did not finish successfully (exit code 127):

bash -c i_do_not_exist

2 changes: 1 addition & 1 deletion tests/Generated/FailureVerboseArgumentsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public function test(): void
}

$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
$this->assertSame('', $process->getErrorOutput());
}
}
7 changes: 0 additions & 7 deletions tests/Generated/FailureVerboseArgumentsTest.php.err.txt

This file was deleted.

7 changes: 6 additions & 1 deletion tests/Generated/FailureVerboseArgumentsTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
bash: line 1: i_do_not_exist: command not found

Do you want to retry the command with verbose arguments? (yes/no) [no]:
>
> In failure.php line XXXX:

[ERROR] The following process did not finish successfully (exit code 127):

bash -c i_do_not_exist

2 changes: 1 addition & 1 deletion tests/Generated/FailureVerboseArgumentsTrueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public function test(): void
}

$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
$this->assertSame('', $process->getErrorOutput());
}
}
19 changes: 0 additions & 19 deletions tests/Generated/FailureVerboseArgumentsTrueTest.php.err.txt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ bash: line 1: i_do_not_exist: command not found
Do you want to retry the command with verbose arguments? (yes/no) [no]:
>
-x: line 1: i_do_not_exist: command not found
In failure.php line XXXX:

[ERROR] The following process did not finish successfully (exit code 127):

bash -c i_do_not_exist -x -e

8 changes: 0 additions & 8 deletions tests/Generated/ParallelExceptionTest.php.err.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ In parallel.php line XXXX:
parallel:exception


In parallel.php line XXXX:

The command "exit 1" failed.


parallel:exception


In parallel.php line XXXX:

One or more exceptions were thrown in parallel.
Expand Down
6 changes: 6 additions & 0 deletions tests/Generated/ParallelExceptionTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
In parallel.php line XXXX:

[ERROR] The following process did not finish successfully (exit code 1):

exit 1

I am executed
4 changes: 2 additions & 2 deletions tests/Generated/RunExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ class RunExceptionTest extends TaskTestCase
// run:exception
public function test(): void
{
$process = $this->runTask(['run:exception']);
$process = $this->runTask(['run:exception', '--repeat', 1]);

if (1 !== $process->getExitCode()) {
throw new ProcessFailedException($process);
}

$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
$this->assertSame('', $process->getErrorOutput());
}
}
7 changes: 0 additions & 7 deletions tests/Generated/RunExceptionTest.php.err.txt

This file was deleted.

6 changes: 6 additions & 0 deletions tests/Generated/RunExceptionTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
Re-run with -v, -vv, -vvv for different output.
In run.php line XXXX:

[ERROR] The following process did not finish successfully (exit code 1):

foo='bar' echo $foo; echo bar>&2; exit 1

6 changes: 3 additions & 3 deletions tests/Generated/RunExceptionVerboseTest.php.err.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
In ProcessRunner.php line XXXX:

[Symfony\Component\Process\Exception\ProcessFailedException]
The command "echo foo; echo bar>&2; exit 1" failed.
The command "echo $foo; echo bar>&2; exit 1" failed.

Exit Code: 1(General error)

Working directory: ...

Output:
================
foo
bar


Error Output:
Expand All @@ -30,5 +30,5 @@ Exception trace:
Castor\Console\Application->doRun() at .../vendor/symfony/console/Application.php:XXXX
Symfony\Component\Console\Application->run() at .../bin/castor:XXXX

run:exception
run:exception [--repeat [REPEAT]]

2 changes: 1 addition & 1 deletion tests/Generated/RunExceptionVerboseTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
hh:mm:ss NOTICE [castor] Running command: "echo foo; echo bar>&2; exit 1". ["process" => ["cwd" => "...","env" => [],"runnable" => "echo foo; echo bar>&2; exit 1"]]
hh:mm:ss NOTICE [castor] Running command: "echo $foo; echo bar>&2; exit 1". ["process" => ["cwd" => "...","env" => ["foo" => "bar"],"runnable" => "foo='bar' echo $foo; echo bar>&2; exit 1"]]
hh:mm:ss NOTICE [castor] Command finished with an error (exit code=1).
8 changes: 0 additions & 8 deletions tests/Generated/SshDownloadTest.php.err.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1 @@
ssh: Could not resolve hostname server-1.example.com: Name or service not known

In ssh.php line XXXX:

The command "scp -r [email protected]:/tmp/test.html /var/www/index.html" failed.


ssh:download

6 changes: 6 additions & 0 deletions tests/Generated/SshDownloadTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
In ssh.php line XXXX:

[ERROR] The following process did not finish successfully (exit code 1):

scp -r [email protected]:/tmp/test.html /var/www/index.html

10 changes: 0 additions & 10 deletions tests/Generated/SshLsTest.php.err.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1 @@
ssh: Could not resolve hostname server-1.example.com: Name or service not known

In ssh.php line XXXX:

The command "ssh -p 2222 [email protected] 'bash -se' << \EOF-SPATIE-SSH
cd /var/www && ls -alh
EOF-SPATIE-SSH" failed.


ssh:ls

8 changes: 8 additions & 0 deletions tests/Generated/SshLsTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
In ssh.php line XXXX:

[ERROR] The following process did not finish successfully (exit code 255):

ssh -p 2222 [email protected] 'bash -se' << \EOF-SPATIE-SSH
cd /var/www && ls -alh
EOF-SPATIE-SSH

2 changes: 1 addition & 1 deletion tests/Generated/SshRealTimeOutputTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public function test(): void
}

$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
$this->assertSame('', $process->getErrorOutput());
}
}
9 changes: 0 additions & 9 deletions tests/Generated/SshRealTimeOutputTest.php.err.txt

This file was deleted.

Loading