Skip to content

Commit

Permalink
Merge pull request #571 from jolicode/process-exception
Browse files Browse the repository at this point in the history
Better rendering of run errors
  • Loading branch information
pyrech authored Nov 15, 2024
2 parents 2d5a474 + 0e01e47 commit 8017946
Show file tree
Hide file tree
Showing 32 changed files with 133 additions and 129 deletions.
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

0 comments on commit 8017946

Please sign in to comment.