33 KiB
Processes
- Introduction
- Invoking Processes
- Process Options
- Process Output
- Pipelines
- Asynchronous Processes
- Process IDs and Signals
- Asynchronous Process Output
- Asynchronous Process Timeouts
- Concurrent Processes
- Naming Pool Processes
- Pool Process IDs and Signals
- Testing
- Faking Processes
- Faking Specific Processes
- Faking Process Sequences
- Faking Asynchronous Process Lifecycles
- Available Assertions
- Preventing Stray Processes
Introduction
Laravel provides an expressive, minimal API around the Symfony Process component, allowing you to conveniently invoke external processes from your Laravel application. Laravel's process features are focused on the most common use cases and a wonderful developer experience.
Invoking Processes
To invoke a process, you may use the run and start methods offered by the
Process facade. The run method will invoke a process and wait for the
process to finish executing, while the start method is used for asynchronous
process execution. We'll examine both approaches within this documentation.
First, let's examine how to invoke a basic, synchronous process and inspect
its result:
1use Illuminate\Support\Facades\Process;
2
3$result = Process::run('ls -la');
4
5return $result->output();
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
return $result->output();
Of course, the Illuminate\Contracts\Process\ProcessResult instance returned
by the run method offers a variety of helpful methods that may be used to
inspect the process result:
1$result = Process::run('ls -la');
2
3$result->successful();
4$result->failed();
5$result->exitCode();
6$result->output();
7$result->errorOutput();
$result = Process::run('ls -la');
$result->successful();
$result->failed();
$result->exitCode();
$result->output();
$result->errorOutput();
Throwing Exceptions
If you have a process result and would like to throw an instance of
Illuminate\Process\Exceptions\ProcessFailedException if the exit code is
greater than zero (thus indicating failure), you may use the throw and
throwIf methods. If the process did not fail, the ProcessResult instance
will be returned:
1$result = Process::run('ls -la')->throw();
2
3$result = Process::run('ls -la')->throwIf($condition);
$result = Process::run('ls -la')->throw();
$result = Process::run('ls -la')->throwIf($condition);
Process Options
Of course, you may need to customize the behavior of a process before invoking it. Thankfully, Laravel allows you to tweak a variety of process features, such as the working directory, timeout, and environment variables.
Working Directory Path
You may use the path method to specify the working directory of the process.
If this method is not invoked, the process will inherit the working directory
of the currently executing PHP script:
1$result = Process::path(__DIR__)->run('ls -la');
$result = Process::path(__DIR__)->run('ls -la');
Input
You may provide input via the "standard input" of the process using the
input method:
1$result = Process::input('Hello World')->run('cat');
$result = Process::input('Hello World')->run('cat');
Timeouts
By default, processes will throw an instance of
Illuminate\Process\Exceptions\ProcessTimedOutException after executing for
more than 60 seconds. However, you can customize this behavior via the
timeout method:
1$result = Process::timeout(120)->run('bash import.sh');
$result = Process::timeout(120)->run('bash import.sh');
Or, if you would like to disable the process timeout entirely, you may invoke
the forever method:
1$result = Process::forever()->run('bash import.sh');
$result = Process::forever()->run('bash import.sh');
The idleTimeout method may be used to specify the maximum number of seconds
the process may run without returning any output:
1$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
Environment Variables
Environment variables may be provided to the process via the env method. The
invoked process will also inherit all of the environment variables defined by
your system:
1$result = Process::forever()
2 ->env(['IMPORT_PATH' => __DIR__])
3 ->run('bash import.sh');
$result = Process::forever()
->env(['IMPORT_PATH' => __DIR__])
->run('bash import.sh');
If you wish to remove an inherited environment variable from the invoked
process, you may provide that environment variable with a value of false:
1$result = Process::forever()
2 ->env(['LOAD_PATH' => false])
3 ->run('bash import.sh');
$result = Process::forever()
->env(['LOAD_PATH' => false])
->run('bash import.sh');
TTY Mode
The tty method may be used to enable TTY mode for your process. TTY mode
connects the input and output of the process to the input and output of your
program, allowing your process to open an editor like Vim or Nano as a
process:
1Process::forever()->tty()->run('vim');
Process::forever()->tty()->run('vim');
TTY mode is not supported on Windows.
Process Output
As previously discussed, process output may be accessed using the output
(stdout) and errorOutput (stderr) methods on a process result:
1use Illuminate\Support\Facades\Process;
2
3$result = Process::run('ls -la');
4
5echo $result->output();
6echo $result->errorOutput();
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
echo $result->output();
echo $result->errorOutput();
However, output may also be gathered in real-time by passing a closure as the
second argument to the run method. The closure will receive two arguments:
the "type" of output (stdout or stderr) and the output string itself:
1$result = Process::run('ls -la', function (string $type, string $output) {
2 echo $output;
3});
$result = Process::run('ls -la', function (string $type, string $output) {
echo $output;
});
Laravel also offers the seeInOutput and seeInErrorOutput methods, which
provide a convenient way to determine if a given string was contained in the
process' output:
1if (Process::run('ls -la')->seeInOutput('laravel')) {
2 // ...
3}
if (Process::run('ls -la')->seeInOutput('laravel')) {
// ...
}
Disabling Process Output
If your process is writing a significant amount of output that you are not
interested in, you can conserve memory by disabling output retrieval entirely.
To accomplish this, invoke the quietly method while building the process:
1use Illuminate\Support\Facades\Process;
2
3$result = Process::quietly()->run('bash import.sh');
use Illuminate\Support\Facades\Process;
$result = Process::quietly()->run('bash import.sh');
Pipelines
Sometimes you may want to make the output of one process the input of another
process. This is often referred to as "piping" the output of a process into
another. The pipe method provided by the Process facades makes this easy
to accomplish. The pipe method will execute the piped processes
synchronously and return the process result for the last process in the
pipeline:
1use Illuminate\Process\Pipe;
2use Illuminate\Support\Facades\Process;
3
4$result = Process::pipe(function (Pipe $pipe) {
5 $pipe->command('cat example.txt');
6 $pipe->command('grep -i "laravel"');
7});
8
9if ($result->successful()) {
10 // ...
11}
use Illuminate\Process\Pipe;
use Illuminate\Support\Facades\Process;
$result = Process::pipe(function (Pipe $pipe) {
$pipe->command('cat example.txt');
$pipe->command('grep -i "laravel"');
});
if ($result->successful()) {
// ...
}
If you do not need to customize the individual processes that make up the
pipeline, you may simply pass an array of command strings to the pipe
method:
1$result = Process::pipe([
2 'cat example.txt',
3 'grep -i "laravel"',
4]);
$result = Process::pipe([
'cat example.txt',
'grep -i "laravel"',
]);
The process output may be gathered in real-time by passing a closure as the
second argument to the pipe method. The closure will receive two arguments:
the "type" of output (stdout or stderr) and the output string itself:
1$result = Process::pipe(function (Pipe $pipe) {
2 $pipe->command('cat example.txt');
3 $pipe->command('grep -i "laravel"');
4}, function (string $type, string $output) {
5 echo $output;
6});
$result = Process::pipe(function (Pipe $pipe) {
$pipe->command('cat example.txt');
$pipe->command('grep -i "laravel"');
}, function (string $type, string $output) {
echo $output;
});
Laravel also allows you to assign string keys to each process within a
pipeline via the as method. This key will also be passed to the output
closure provided to the pipe method, allowing you to determine which process
the output belongs to:
1$result = Process::pipe(function (Pipe $pipe) {
2 $pipe->as('first')->command('cat example.txt');
3 $pipe->as('second')->command('grep -i "laravel"');
4}, function (string $type, string $output, string $key) {
5 // ...
6});
$result = Process::pipe(function (Pipe $pipe) {
$pipe->as('first')->command('cat example.txt');
$pipe->as('second')->command('grep -i "laravel"');
}, function (string $type, string $output, string $key) {
// ...
});
Asynchronous Processes
While the run method invokes processes synchronously, the start method may
be used to invoke a process asynchronously. This allows your application to
continue performing other tasks while the process runs in the background. Once
the process has been invoked, you may utilize the running method to
determine if the process is still running:
1$process = Process::timeout(120)->start('bash import.sh');
2
3while ($process->running()) {
4 // ...
5}
6
7$result = $process->wait();
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
// ...
}
$result = $process->wait();
As you may have noticed, you may invoke the wait method to wait until the
process is finished executing and retrieve the ProcessResult instance:
1$process = Process::timeout(120)->start('bash import.sh');
2
3// ...
4
5$result = $process->wait();
$process = Process::timeout(120)->start('bash import.sh');
// ...
$result = $process->wait();
Process IDs and Signals
The id method may be used to retrieve the operating system assigned process
ID of the running process:
1$process = Process::start('bash import.sh');
2
3return $process->id();
$process = Process::start('bash import.sh');
return $process->id();
You may use the signal method to send a "signal" to the running process. A
list of predefined signal constants can be found within the PHP
documentation:
1$process->signal(SIGUSR2);
$process->signal(SIGUSR2);
Asynchronous Process Output
While an asynchronous process is running, you may access its entire current
output using the output and errorOutput methods; however, you may utilize
the latestOutput and latestErrorOutput to access the output from the
process that has occurred since the output was last retrieved:
1$process = Process::timeout(120)->start('bash import.sh');
2
3while ($process->running()) {
4 echo $process->latestOutput();
5 echo $process->latestErrorOutput();
6
7 sleep(1);
8}
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
echo $process->latestOutput();
echo $process->latestErrorOutput();
sleep(1);
}
Like the run method, output may also be gathered in real-time from
asynchronous processes by passing a closure as the second argument to the
start method. The closure will receive two arguments: the "type" of output
(stdout or stderr) and the output string itself:
1$process = Process::start('bash import.sh', function (string $type, string $output) {
2 echo $output;
3});
4
5$result = $process->wait();
$process = Process::start('bash import.sh', function (string $type, string $output) {
echo $output;
});
$result = $process->wait();
Instead of waiting until the process has finished, you may use the waitUntil
method to stop waiting based on the output of the process. Laravel will stop
waiting for the process to finish when the closure given to the waitUntil
method returns true:
1$process = Process::start('bash import.sh');
2
3$process->waitUntil(function (string $type, string $output) {
4 return $output === 'Ready...';
5});
$process = Process::start('bash import.sh');
$process->waitUntil(function (string $type, string $output) {
return $output === 'Ready...';
});
Asynchronous Process Timeouts
While an asynchronous process is running, you may verify that the process has
not timed out using the ensureNotTimedOut method. This method will throw a
timeout exception if the process has timed out:
1$process = Process::timeout(120)->start('bash import.sh');
2
3while ($process->running()) {
4 $process->ensureNotTimedOut();
5
6 // ...
7
8 sleep(1);
9}
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
$process->ensureNotTimedOut();
// ...
sleep(1);
}
Concurrent Processes
Laravel also makes it a breeze to manage a pool of concurrent, asynchronous
processes, allowing you to easily execute many tasks simultaneously. To get
started, invoke the pool method, which accepts a closure that receives an
instance of Illuminate\Process\Pool.
Within this closure, you may define the processes that belong to the pool.
Once a process pool is started via the start method, you may access the
collection of running processes via the running
method:
1use Illuminate\Process\Pool;
2use Illuminate\Support\Facades\Process;
3
4$pool = Process::pool(function (Pool $pool) {
5 $pool->path(__DIR__)->command('bash import-1.sh');
6 $pool->path(__DIR__)->command('bash import-2.sh');
7 $pool->path(__DIR__)->command('bash import-3.sh');
8})->start(function (string $type, string $output, int $key) {
9 // ...
10});
11
12while ($pool->running()->isNotEmpty()) {
13 // ...
14}
15
16$results = $pool->wait();
use Illuminate\Process\Pool;
use Illuminate\Support\Facades\Process;
$pool = Process::pool(function (Pool $pool) {
$pool->path(__DIR__)->command('bash import-1.sh');
$pool->path(__DIR__)->command('bash import-2.sh');
$pool->path(__DIR__)->command('bash import-3.sh');
})->start(function (string $type, string $output, int $key) {
// ...
});
while ($pool->running()->isNotEmpty()) {
// ...
}
$results = $pool->wait();
As you can see, you may wait for all of the pool processes to finish executing
and resolve their results via the wait method. The wait method returns an
array accessible object that allows you to access the ProcessResult instance
of each process in the pool by its key:
1$results = $pool->wait();
2
3echo $results[0]->output();
$results = $pool->wait();
echo $results[0]->output();
Or, for convenience, the concurrently method may be used to start an
asynchronous process pool and immediately wait on its results. This can
provide particularly expressive syntax when combined with PHP's array
destructuring capabilities:
1[$first, $second, $third] = Process::concurrently(function (Pool $pool) {
2 $pool->path(__DIR__)->command('ls -la');
3 $pool->path(app_path())->command('ls -la');
4 $pool->path(storage_path())->command('ls -la');
5});
6
7echo $first->output();
[$first, $second, $third] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('ls -la');
$pool->path(app_path())->command('ls -la');
$pool->path(storage_path())->command('ls -la');
});
echo $first->output();
Naming Pool Processes
Accessing process pool results via a numeric key is not very expressive;
therefore, Laravel allows you to assign string keys to each process within a
pool via the as method. This key will also be passed to the closure provided
to the start method, allowing you to determine which process the output
belongs to:
1$pool = Process::pool(function (Pool $pool) {
2 $pool->as('first')->command('bash import-1.sh');
3 $pool->as('second')->command('bash import-2.sh');
4 $pool->as('third')->command('bash import-3.sh');
5})->start(function (string $type, string $output, string $key) {
6 // ...
7});
8
9$results = $pool->wait();
10
11return $results['first']->output();
$pool = Process::pool(function (Pool $pool) {
$pool->as('first')->command('bash import-1.sh');
$pool->as('second')->command('bash import-2.sh');
$pool->as('third')->command('bash import-3.sh');
})->start(function (string $type, string $output, string $key) {
// ...
});
$results = $pool->wait();
return $results['first']->output();
Pool Process IDs and Signals
Since the process pool's running method provides a collection of all invoked
processes within the pool, you may easily access the underlying pool process
IDs:
1$processIds = $pool->running()->each->id();
$processIds = $pool->running()->each->id();
And, for convenience, you may invoke the signal method on a process pool to
send a signal to every process within the pool:
1$pool->signal(SIGUSR2);
$pool->signal(SIGUSR2);
Testing
Many Laravel services provide functionality to help you easily and
expressively write tests, and Laravel's process service is no exception. The
Process facade's fake method allows you to instruct Laravel to return
stubbed / dummy results when processes are invoked.
Faking Processes
To explore Laravel's ability to fake processes, let's imagine a route that invokes a process:
1use Illuminate\Support\Facades\Process;
2use Illuminate\Support\Facades\Route;
3
4Route::get('/import', function () {
5 Process::run('bash import.sh');
6
7 return 'Import complete!';
8});
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;
Route::get('/import', function () {
Process::run('bash import.sh');
return 'Import complete!';
});
When testing this route, we can instruct Laravel to return a fake, successful
process result for every invoked process by calling the fake method on the
Process facade with no arguments. In addition, we can even assert that a
given process was "run":
Pest PHPUnit
1<?php
2
3use Illuminate\Contracts\Process\ProcessResult;
4use Illuminate\Process\PendingProcess;
5use Illuminate\Support\Facades\Process;
6
7test('process is invoked', function () {
8 Process::fake();
9
10 $response = $this->get('/import');
11
12 // Simple process assertion...
13 Process::assertRan('bash import.sh');
14
15 // Or, inspecting the process configuration...
16 Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
17 return $process->command === 'bash import.sh' &&
18 $process->timeout === 60;
19 });
20});
<?php
use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
test('process is invoked', function () {
Process::fake();
$response = $this->get('/import');
// Simple process assertion...
Process::assertRan('bash import.sh');
// Or, inspecting the process configuration...
Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'bash import.sh' &&
$process->timeout === 60;
});
});
1<?php
2
3namespace Tests\Feature;
4
5use Illuminate\Contracts\Process\ProcessResult;
6use Illuminate\Process\PendingProcess;
7use Illuminate\Support\Facades\Process;
8use Tests\TestCase;
9
10class ExampleTest extends TestCase
11{
12 public function test_process_is_invoked(): void
13 {
14 Process::fake();
15
16 $response = $this->get('/import');
17
18 // Simple process assertion...
19 Process::assertRan('bash import.sh');
20
21 // Or, inspecting the process configuration...
22 Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
23 return $process->command === 'bash import.sh' &&
24 $process->timeout === 60;
25 });
26 }
27}
<?php
namespace Tests\Feature;
use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_process_is_invoked(): void
{
Process::fake();
$response = $this->get('/import');
// Simple process assertion...
Process::assertRan('bash import.sh');
// Or, inspecting the process configuration...
Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'bash import.sh' &&
$process->timeout === 60;
});
}
}
As discussed, invoking the fake method on the Process facade will instruct
Laravel to always return a successful process result with no output. However,
you may easily specify the output and exit code for faked processes using the
Process facade's result method:
1Process::fake([
2 '*' => Process::result(
3 output: 'Test output',
4 errorOutput: 'Test error output',
5 exitCode: 1,
6 ),
7]);
Process::fake([
'*' => Process::result(
output: 'Test output',
errorOutput: 'Test error output',
exitCode: 1,
),
]);
Faking Specific Processes
As you may have noticed in a previous example, the Process facade allows you
to specify different fake results per process by passing an array to the
fake method.
The array's keys should represent command patterns that you wish to fake and
their associated results. The * character may be used as a wildcard
character. Any process commands that have not been faked will actually be
invoked. You may use the Process facade's result method to construct stub
/ fake results for these commands:
1Process::fake([
2 'cat *' => Process::result(
3 output: 'Test "cat" output',
4 ),
5 'ls *' => Process::result(
6 output: 'Test "ls" output',
7 ),
8]);
Process::fake([
'cat *' => Process::result(
output: 'Test "cat" output',
),
'ls *' => Process::result(
output: 'Test "ls" output',
),
]);
If you do not need to customize the exit code or error output of a faked process, you may find it more convenient to specify the fake process results as simple strings:
1Process::fake([
2 'cat *' => 'Test "cat" output',
3 'ls *' => 'Test "ls" output',
4]);
Process::fake([
'cat *' => 'Test "cat" output',
'ls *' => 'Test "ls" output',
]);
Faking Process Sequences
If the code you are testing invokes multiple processes with the same command,
you may wish to assign a different fake process result to each process
invocation. You may accomplish this via the Process facade's sequence
method:
1Process::fake([
2 'ls *' => Process::sequence()
3 ->push(Process::result('First invocation'))
4 ->push(Process::result('Second invocation')),
5]);
Process::fake([
'ls *' => Process::sequence()
->push(Process::result('First invocation'))
->push(Process::result('Second invocation')),
]);
Faking Asynchronous Process Lifecycles
Thus far, we have primarily discussed faking processes which are invoked
synchronously using the run method. However, if you are attempting to test
code that interacts with asynchronous processes invoked via start, you may
need a more sophisticated approach to describing your fake processes.
For example, let's imagine the following route which interacts with an asynchronous process:
1use Illuminate\Support\Facades\Log;
2use Illuminate\Support\Facades\Route;
3
4Route::get('/import', function () {
5 $process = Process::start('bash import.sh');
6
7 while ($process->running()) {
8 Log::info($process->latestOutput());
9 Log::info($process->latestErrorOutput());
10 }
11
12 return 'Done';
13});
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
Route::get('/import', function () {
$process = Process::start('bash import.sh');
while ($process->running()) {
Log::info($process->latestOutput());
Log::info($process->latestErrorOutput());
}
return 'Done';
});
To properly fake this process, we need to be able to describe how many times
the running method should return true. In addition, we may want to specify
multiple lines of output that should be returned in sequence. To accomplish
this, we can use the Process facade's describe method:
1Process::fake([
2 'bash import.sh' => Process::describe()
3 ->output('First line of standard output')
4 ->errorOutput('First line of error output')
5 ->output('Second line of standard output')
6 ->exitCode(0)
7 ->iterations(3),
8]);
Process::fake([
'bash import.sh' => Process::describe()
->output('First line of standard output')
->errorOutput('First line of error output')
->output('Second line of standard output')
->exitCode(0)
->iterations(3),
]);
Let's dig into the example above. Using the output and errorOutput
methods, we may specify multiple lines of output that will be returned in
sequence. The exitCode method may be used to specify the final exit code of
the fake process. Finally, the iterations method may be used to specify how
many times the running method should return true.
Available Assertions
As previously discussed, Laravel provides several process assertions for your feature tests. We'll discuss each of these assertions below.
assertRan
Assert that a given process was invoked:
1use Illuminate\Support\Facades\Process;
2
3Process::assertRan('ls -la');
use Illuminate\Support\Facades\Process;
Process::assertRan('ls -la');
The assertRan method also accepts a closure, which will receive an instance
of a process and a process result, allowing you to inspect the process'
configured options. If this closure returns true, the assertion will "pass":
1Process::assertRan(fn ($process, $result) =>
2 $process->command === 'ls -la' &&
3 $process->path === __DIR__ &&
4 $process->timeout === 60
5);
Process::assertRan(fn ($process, $result) =>
$process->command === 'ls -la' &&
$process->path === __DIR__ &&
$process->timeout === 60
);
The $process passed to the assertRan closure is an instance of
Illuminate\Process\PendingProcess, while the $result is an instance of
Illuminate\Contracts\Process\ProcessResult.
assertDidntRun
Assert that a given process was not invoked:
1use Illuminate\Support\Facades\Process;
2
3Process::assertDidntRun('ls -la');
use Illuminate\Support\Facades\Process;
Process::assertDidntRun('ls -la');
Like the assertRan method, the assertDidntRun method also accepts a
closure, which will receive an instance of a process and a process result,
allowing you to inspect the process' configured options. If this closure
returns true, the assertion will "fail":
1Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
2 $process->command === 'ls -la'
3);
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
$process->command === 'ls -la'
);
assertRanTimes
Assert that a given process was invoked a given number of times:
1use Illuminate\Support\Facades\Process;
2
3Process::assertRanTimes('ls -la', times: 3);
use Illuminate\Support\Facades\Process;
Process::assertRanTimes('ls -la', times: 3);
The assertRanTimes method also accepts a closure, which will receive an
instance of PendingProcess and ProcessResult, allowing you to inspect the
process' configured options. If this closure returns true and the process
was invoked the specified number of times, the assertion will "pass":
1Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
2 return $process->command === 'ls -la';
3}, times: 3);
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'ls -la';
}, times: 3);
Preventing Stray Processes
If you would like to ensure that all invoked processes have been faked
throughout your individual test or complete test suite, you can call the
preventStrayProcesses method. After calling this method, any processes that
do not have a corresponding fake result will throw an exception rather than
starting an actual process:
1use Illuminate\Support\Facades\Process;
2
3Process::preventStrayProcesses();
4
5Process::fake([
6 'ls *' => 'Test output...',
7]);
8
9// Fake response is returned...
10Process::run('ls -la');
11
12// An exception is thrown...
13Process::run('bash import.sh');
use Illuminate\Support\Facades\Process;
Process::preventStrayProcesses();
Process::fake([
'ls *' => 'Test output...',
]);
// Fake response is returned...
Process::run('ls -la');
// An exception is thrown...
Process::run('bash import.sh');