diff --git a/CHANGELOG.md b/CHANGELOG.md
index bda5e92f..3511e2a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Not released yet
+### Features
+
+* Better rendering of run errors
+
### Internal
* Rework the releasing
diff --git a/examples/run.php b/examples/run.php
index 837360b0..6933c155 100644
--- a/examples/run.php
+++ b/examples/run.php
@@ -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')]
diff --git a/src/Console/Application.php b/src/Console/Application.php
index 4e81e444..802c2f47 100644
--- a/src/Console/Application.php
+++ b/src/Console/Application.php
@@ -5,8 +5,10 @@
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;
@@ -14,6 +16,7 @@
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
@@ -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);
}
@@ -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('%s', 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("{$runnable}>");
+ $this->io->newLine();
+
+ return;
+ }
}
parent::renderThrowable($e, $output);
diff --git a/src/Monolog/Processor/ProcessProcessor.php b/src/Monolog/Processor/ProcessProcessor.php
index deebf280..166a77a3 100644
--- a/src/Monolog/Processor/ProcessProcessor.php
+++ b/src/Monolog/Processor/ProcessProcessor.php
@@ -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) {
@@ -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),
];
}
}
diff --git a/src/Runner/ProcessRunner.php b/src/Runner/ProcessRunner.php
index 6f3e051c..62bb8780 100644
--- a/src/Runner/ProcessRunner.php
+++ b/src/Runner/ProcessRunner.php
@@ -18,6 +18,7 @@
use Symfony\Component\Process\Process;
use function Castor\context;
+use function Symfony\Component\String\u;
/** @internal */
class ProcessRunner
@@ -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,
]);
@@ -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;
@@ -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, ' ');
+ }
}
diff --git a/tests/Generated/FailureFailureTest.php.err.txt b/tests/Generated/FailureFailureTest.php.err.txt
index d3a74dc3..2d3470f5 100644
--- a/tests/Generated/FailureFailureTest.php.err.txt
+++ b/tests/Generated/FailureFailureTest.php.err.txt
@@ -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
-
diff --git a/tests/Generated/FailureFailureTest.php.output.txt b/tests/Generated/FailureFailureTest.php.output.txt
index e69de29b..dd0c37d0 100644
--- a/tests/Generated/FailureFailureTest.php.output.txt
+++ b/tests/Generated/FailureFailureTest.php.output.txt
@@ -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
+
diff --git a/tests/Generated/FailureVerboseArgumentsTest.php b/tests/Generated/FailureVerboseArgumentsTest.php
index 4bff60c6..60a0de73 100644
--- a/tests/Generated/FailureVerboseArgumentsTest.php
+++ b/tests/Generated/FailureVerboseArgumentsTest.php
@@ -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());
}
}
diff --git a/tests/Generated/FailureVerboseArgumentsTest.php.err.txt b/tests/Generated/FailureVerboseArgumentsTest.php.err.txt
deleted file mode 100644
index de44c60a..00000000
--- a/tests/Generated/FailureVerboseArgumentsTest.php.err.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-In failure.php line XXXX:
-
- The command "bash -c i_do_not_exist" failed.
-
-
-failure:verbose-arguments
-
diff --git a/tests/Generated/FailureVerboseArgumentsTest.php.output.txt b/tests/Generated/FailureVerboseArgumentsTest.php.output.txt
index 523f7eb9..f7b5e400 100644
--- a/tests/Generated/FailureVerboseArgumentsTest.php.output.txt
+++ b/tests/Generated/FailureVerboseArgumentsTest.php.output.txt
@@ -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]:
- >
\ No newline at end of file
+ > In failure.php line XXXX:
+
+ [ERROR] The following process did not finish successfully (exit code 127):
+
+bash -c i_do_not_exist
+
diff --git a/tests/Generated/FailureVerboseArgumentsTrueTest.php b/tests/Generated/FailureVerboseArgumentsTrueTest.php
index f3bf82b2..51227450 100644
--- a/tests/Generated/FailureVerboseArgumentsTrueTest.php
+++ b/tests/Generated/FailureVerboseArgumentsTrueTest.php
@@ -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());
}
}
diff --git a/tests/Generated/FailureVerboseArgumentsTrueTest.php.err.txt b/tests/Generated/FailureVerboseArgumentsTrueTest.php.err.txt
deleted file mode 100644
index 3c518c7d..00000000
--- a/tests/Generated/FailureVerboseArgumentsTrueTest.php.err.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-In failure.php line XXXX:
-
- The command "bash -c i_do_not_exist -x -e" failed.
-
- Exit Code: 127(Command not found)
-
- Working directory: ...
-
- Output:
- ================
- -x: line 1: i_do_not_exist: command not found
-
-
- Error Output:
- ================
-
-
-failure:verbose-arguments
-
diff --git a/tests/Generated/FailureVerboseArgumentsTrueTest.php.output.txt b/tests/Generated/FailureVerboseArgumentsTrueTest.php.output.txt
index 58dd0869..53dc7f28 100644
--- a/tests/Generated/FailureVerboseArgumentsTrueTest.php.output.txt
+++ b/tests/Generated/FailureVerboseArgumentsTrueTest.php.output.txt
@@ -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
+
diff --git a/tests/Generated/ParallelExceptionTest.php.err.txt b/tests/Generated/ParallelExceptionTest.php.err.txt
index e2624bff..ab68055d 100644
--- a/tests/Generated/ParallelExceptionTest.php.err.txt
+++ b/tests/Generated/ParallelExceptionTest.php.err.txt
@@ -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.
diff --git a/tests/Generated/ParallelExceptionTest.php.output.txt b/tests/Generated/ParallelExceptionTest.php.output.txt
index 20684626..b1f08f16 100644
--- a/tests/Generated/ParallelExceptionTest.php.output.txt
+++ b/tests/Generated/ParallelExceptionTest.php.output.txt
@@ -1 +1,7 @@
+In parallel.php line XXXX:
+
+ [ERROR] The following process did not finish successfully (exit code 1):
+
+exit 1
+
I am executed
diff --git a/tests/Generated/RunExceptionTest.php b/tests/Generated/RunExceptionTest.php
index a1ed80a7..32593e65 100644
--- a/tests/Generated/RunExceptionTest.php
+++ b/tests/Generated/RunExceptionTest.php
@@ -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());
}
}
diff --git a/tests/Generated/RunExceptionTest.php.err.txt b/tests/Generated/RunExceptionTest.php.err.txt
deleted file mode 100644
index 540dd19d..00000000
--- a/tests/Generated/RunExceptionTest.php.err.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-In run.php line XXXX:
-
- The command "echo foo; echo bar>&2; exit 1" failed.
-
-
-run:exception
-
diff --git a/tests/Generated/RunExceptionTest.php.output.txt b/tests/Generated/RunExceptionTest.php.output.txt
index 82f83b6c..83f71b52 100644
--- a/tests/Generated/RunExceptionTest.php.output.txt
+++ b/tests/Generated/RunExceptionTest.php.output.txt
@@ -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
+
diff --git a/tests/Generated/RunExceptionVerboseTest.php.err.txt b/tests/Generated/RunExceptionVerboseTest.php.err.txt
index 4c213f3b..5ea73efb 100644
--- a/tests/Generated/RunExceptionVerboseTest.php.err.txt
+++ b/tests/Generated/RunExceptionVerboseTest.php.err.txt
@@ -1,7 +1,7 @@
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)
@@ -9,7 +9,7 @@ In ProcessRunner.php line XXXX:
Output:
================
- foo
+ bar
Error Output:
@@ -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]]
diff --git a/tests/Generated/RunExceptionVerboseTest.php.output.txt b/tests/Generated/RunExceptionVerboseTest.php.output.txt
index ed97c711..74ec49bf 100644
--- a/tests/Generated/RunExceptionVerboseTest.php.output.txt
+++ b/tests/Generated/RunExceptionVerboseTest.php.output.txt
@@ -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).
diff --git a/tests/Generated/SshDownloadTest.php.err.txt b/tests/Generated/SshDownloadTest.php.err.txt
index f0168303..ef21c34f 100644
--- a/tests/Generated/SshDownloadTest.php.err.txt
+++ b/tests/Generated/SshDownloadTest.php.err.txt
@@ -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 debian@server-1.example.com:/tmp/test.html /var/www/index.html" failed.
-
-
-ssh:download
-
diff --git a/tests/Generated/SshDownloadTest.php.output.txt b/tests/Generated/SshDownloadTest.php.output.txt
index e69de29b..2a51d9bc 100644
--- a/tests/Generated/SshDownloadTest.php.output.txt
+++ b/tests/Generated/SshDownloadTest.php.output.txt
@@ -0,0 +1,6 @@
+In ssh.php line XXXX:
+
+ [ERROR] The following process did not finish successfully (exit code 1):
+
+scp -r debian@server-1.example.com:/tmp/test.html /var/www/index.html
+
diff --git a/tests/Generated/SshLsTest.php.err.txt b/tests/Generated/SshLsTest.php.err.txt
index 8bffa478..ef21c34f 100644
--- a/tests/Generated/SshLsTest.php.err.txt
+++ b/tests/Generated/SshLsTest.php.err.txt
@@ -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 debian@server-1.example.com 'bash -se' << \EOF-SPATIE-SSH
- cd /var/www && ls -alh
- EOF-SPATIE-SSH" failed.
-
-
-ssh:ls
-
diff --git a/tests/Generated/SshLsTest.php.output.txt b/tests/Generated/SshLsTest.php.output.txt
index e69de29b..b25faa0c 100644
--- a/tests/Generated/SshLsTest.php.output.txt
+++ b/tests/Generated/SshLsTest.php.output.txt
@@ -0,0 +1,8 @@
+In ssh.php line XXXX:
+
+ [ERROR] The following process did not finish successfully (exit code 255):
+
+ssh -p 2222 debian@server-1.example.com 'bash -se' << \EOF-SPATIE-SSH
+cd /var/www && ls -alh
+EOF-SPATIE-SSH
+
diff --git a/tests/Generated/SshRealTimeOutputTest.php b/tests/Generated/SshRealTimeOutputTest.php
index 939e8bd0..f33e2f0c 100644
--- a/tests/Generated/SshRealTimeOutputTest.php
+++ b/tests/Generated/SshRealTimeOutputTest.php
@@ -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());
}
}
diff --git a/tests/Generated/SshRealTimeOutputTest.php.err.txt b/tests/Generated/SshRealTimeOutputTest.php.err.txt
deleted file mode 100644
index da52b3fd..00000000
--- a/tests/Generated/SshRealTimeOutputTest.php.err.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-In ssh.php line XXXX:
-
- The command "ssh debian@server-1.example.com 'bash -se' << \EOF-SPATIE-SSH
- ls -alh
- EOF-SPATIE-SSH" failed.
-
-
-ssh:real-time-output
-
diff --git a/tests/Generated/SshRealTimeOutputTest.php.output.txt b/tests/Generated/SshRealTimeOutputTest.php.output.txt
index 624cbdcc..96f23af8 100644
--- a/tests/Generated/SshRealTimeOutputTest.php.output.txt
+++ b/tests/Generated/SshRealTimeOutputTest.php.output.txt
@@ -1,2 +1,10 @@
REAL TIME OUTPUT> ssh: Could not resolve hostname server-1.example.com: Name or service not known
+In ssh.php line XXXX:
+
+ [ERROR] The following process did not finish successfully (exit code 255):
+
+ssh debian@server-1.example.com 'bash -se' << \EOF-SPATIE-SSH
+ls -alh
+EOF-SPATIE-SSH
+
diff --git a/tests/Generated/SshUploadTest.php.err.txt b/tests/Generated/SshUploadTest.php.err.txt
index 145f4ad6..ef21c34f 100644
--- a/tests/Generated/SshUploadTest.php.err.txt
+++ b/tests/Generated/SshUploadTest.php.err.txt
@@ -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 .../examples/ssh.php debian@server-1.example.com:/var/www/index.html" failed.
-
-
-ssh:upload
-
diff --git a/tests/Generated/SshUploadTest.php.output.txt b/tests/Generated/SshUploadTest.php.output.txt
index e69de29b..07773aaf 100644
--- a/tests/Generated/SshUploadTest.php.output.txt
+++ b/tests/Generated/SshUploadTest.php.output.txt
@@ -0,0 +1,6 @@
+In ssh.php line XXXX:
+
+ [ERROR] The following process did not finish successfully (exit code 1):
+
+scp -r .../examples/ssh.php debian@server-1.example.com:/var/www/index.html
+
diff --git a/tests/Generated/SshWhoamiTest.php.err.txt b/tests/Generated/SshWhoamiTest.php.err.txt
index 4c200ea0..ef21c34f 100644
--- a/tests/Generated/SshWhoamiTest.php.err.txt
+++ b/tests/Generated/SshWhoamiTest.php.err.txt
@@ -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 server-1.example.com 'bash -se' << \EOF-SPATIE-SSH
- cd /var/www && whoami
- EOF-SPATIE-SSH" failed.
-
-
-ssh:whoami
-
diff --git a/tests/Generated/SshWhoamiTest.php.output.txt b/tests/Generated/SshWhoamiTest.php.output.txt
index e69de29b..d878ff4f 100644
--- a/tests/Generated/SshWhoamiTest.php.output.txt
+++ b/tests/Generated/SshWhoamiTest.php.output.txt
@@ -0,0 +1,8 @@
+In ssh.php line XXXX:
+
+ [ERROR] The following process did not finish successfully (exit code 255):
+
+ssh -p 2222 server-1.example.com 'bash -se' << \EOF-SPATIE-SSH
+cd /var/www && whoami
+EOF-SPATIE-SSH
+
diff --git a/tests/Monolog/Processor/ProcessProcessorTest.php b/tests/Monolog/Processor/ProcessProcessorTest.php
index 58f662e6..26bce904 100644
--- a/tests/Monolog/Processor/ProcessProcessorTest.php
+++ b/tests/Monolog/Processor/ProcessProcessorTest.php
@@ -3,6 +3,7 @@
namespace Castor\Tests\Monolog\Processor;
use Castor\Monolog\Processor\ProcessProcessor;
+use Castor\Runner\ProcessRunner;
use Monolog\Level;
use Monolog\LogRecord;
use PHPUnit\Framework\TestCase;
@@ -24,7 +25,12 @@ public function test(): void
message: 'new process',
context: ['process' => $process],
);
- $processor = new ProcessProcessor($process);
+ $mock = $this->getMockBuilder(ProcessRunner::class)
+ ->onlyMethods(['buildRunnableCommand'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $processor = new ProcessProcessor($mock);
$this->assertEquals(
[
@@ -34,9 +40,7 @@ public function test(): void
'argc' => 3,
'argv' => ['/home/foo/.local/bin//castor', 'builder', '-vvv'],
],
- 'runnable' => <<<'TXT'
- foo='b'\''"`\ar' 'ls' '-alh'
- TXT,
+ 'runnable' => '',
],
$processor($log)->context['process'],
);