> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 패키지의 외부 프로세스 실행과 Process::fake

> Laravel 13의 Process facade를 사용해 외부 명령의 인수, 타임아웃, 실패 예외, 테스트에서의 fake와 배열 명령의 대조를 설명합니다.

패키지가 `ffmpeg`나 `git` 같은 외부 명령을 호출하면, 이용하는 애플리케이션의 환경 차이나 실패가 그대로 패키지의 동작이 됩니다. `shell_exec()`나 `exec()`를 직접 호출하지 말고 **Process facade**로 실행하며, 테스트에서는 `Process::fake()`로 대체합니다.

> [!NOTE]
>
> 이 페이지는 Laravel 13을 대상으로 Framework `v13.42.0`의 구현을 확인하고 있습니다. [프로세스](https://kawax.biz/ko/processes.md)와 [패키지 테스트](https://kawax.biz/ko/advanced/package-testing.md)를 전제로 합니다. 여기서 다루는 배열 명령의 대조는 Process 내부에서 사용되는 Symfony Process의 인수 변환을 전제로 합니다.

## 직접 실행을 피하는 이유

직접 함수를 호출하면 테스트에서 대체할 수 없고, 타임아웃이나 종료 코드의 처리도 호출하는 쪽마다 제각각이 됩니다. Process facade를 거치면 실행 기록, fake, 타임아웃, 실패 시 예외를 통일할 수 있습니다.

```mermaid theme={null}
flowchart LR
    A["패키지의<br>래퍼 클래스"] --> B["Process facade<br>PendingProcess"]
    B --> C{"fake가<br>등록되었나?"}
    C -->|예| D["FakeProcessResult<br>외부 명령은 실행하지 않음"]
    C -->|아니요| E["외부 명령을 실행"]
    E --> F["ProcessResult"]
    D --> F
    F --> G["throw()로 실패를 예외로 변환"]
```

## 배열로 명령을 전달하기

문자열 명령은 `sh`의 해석을 거치므로, 사용자 입력을 포함하면 따옴표 문제나 주입의 여지가 생깁니다. 패키지 내부에서는 배열로 인수를 전달합니다.

```php theme={null}
<?php

namespace Vendor\Package\Support;

use Illuminate\Support\Facades\Process;

final class VideoConverter
{
    public function toMp4(string $source, string $target): void
    {
        Process::timeout(120)
            ->run(['ffmpeg', '-y', '-i', $source, $target])
            ->throw();
    }
}
```

`PendingProcess`의 기본 타임아웃은 `60`초, 유휴 타임아웃은 `null`입니다. 오래 걸리는 변환에서는 `timeout()`으로 명시하고, 출력이 멈추는 종류의 처리에서는 `idleTimeout()`을 함께 사용합니다.

## 실패 시 예외와 메시지

`ProcessResult::throw()`는 종료 코드가 성공이 아닌 경우 `ProcessFailedException`을 던집니다. 메시지에는 실행한 명령, 종료 코드, 표준 출력과 표준 에러 출력이 포함됩니다.

> [!WARNING]
>
> 명령의 인수에 비밀 정보를 넣으면 `ProcessFailedException`의 메시지에 포함되어 로그나 화면에 나올 수 있습니다. 토큰 등은 인수로 전달하지 말고 `env()`로 전달하거나 명령의 설정 파일에 쓰는 방식을 선택합니다.

타임아웃은 `ProcessTimedOutException`, 유휴 타임아웃은 `ProcessIdleTimedOutException`으로 표현됩니다. 패키지 이용자에게는 어떤 예외를 던지는지, 잡았을 때 무엇을 기대할 수 있는지를 명시합니다.

## 테스트에서 fake하기

테스트에서는 `Process::fake()`로 외부 명령을 대체합니다. `Process::result()`는 표준 출력, 표준 에러, 종료 코드를 지정할 수 있습니다.

```php theme={null}
<?php

namespace Vendor\Package\Tests\Feature;

use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\Exceptions\ProcessFailedException;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
use Vendor\Package\Support\VideoConverter;
use Vendor\Package\Tests\TestCase;

class VideoConverterTest extends TestCase
{
    public function test_it_runs_ffmpeg_with_the_expected_arguments(): void
    {
        Process::fake([
            '*' => Process::result(output: 'converted'),
        ]);

        (new VideoConverter)->toMp4('/tmp/in.mov', '/tmp/out.mp4');

        Process::assertRan(
            fn (PendingProcess $process, ProcessResult $result) => $process->command === ['ffmpeg', '-y', '-i', '/tmp/in.mov', '/tmp/out.mp4']
        );
    }

    public function test_it_fails_when_ffmpeg_exits_with_an_error(): void
    {
        Process::fake([
            '*' => Process::result(errorOutput: 'Invalid data', exitCode: 1),
        ]);

        $this->expectException(ProcessFailedException::class);

        (new VideoConverter)->toMp4('/tmp/in.mov', '/tmp/out.mp4');
    }
}
```

`assertRan()`에 클로저를 전달하면 `PendingProcess`의 `command`, `timeout`, `environment` 등을 확인할 수 있습니다. 배열 명령은 `$process->command`와 완전 일치로 비교되므로, 인수의 순서나 값의 변경은 테스트에서 검출됩니다.

## 배열 명령의 fake 키

Process 내부에서는 `fake()`의 키를 `getCommandline()`의 결과와 `Str::is()`로 대조합니다. 배열 명령은 셸용으로 따옴표가 붙은 문자열로 변환되므로, `'ffmpeg*'` 같은 문자열 패턴은 일치하지 않습니다. 배열 명령의 테스트에서는 다음 중 하나를 사용합니다.

| 방법 | 사용처 | 주의점 |
| - | - | - |
| `'*'` 키 | 명령에 관계없이 같은 결과를 반환 | 다른 명령이 호출되어도 알아차리기 어려움 |
| `Process::assertRan()` | 호출 후에 인수를 검증 | fake의 응답은 별도로 준비 |
| 클로저 fake | 인수에 따라 결과를 변경 | 처리가 복잡해지므로 테스트의 목적을 좁힘 |

## 예기치 않은 외부 명령 방지하기

테스트에서 실행되어서는 안 되는 외부 명령을 검출하려면 `preventStrayProcesses()`를 사용합니다. fake와 일치하지 않는 명령은 `RuntimeException`이 되며, 실제로는 실행되지 않습니다.

```php theme={null}
protected function setUp(): void
{
    parent::setUp();

    Process::preventStrayProcesses();
}
```

## 주입된 Factory를 사용하는 경우

패키지의 생성자에서 `Illuminate\Process\Factory`를 받는 설계에서는 `Process::fake()`의 상태가 주입 대상까지 도달하는지 확인합니다. Facade는 해석된 인스턴스를 캐시하고, `swap()`으로 컨테이너의 인스턴스를 대체합니다. 한편 `Factory`가 컨테이너에서 공유되는지는 프레임워크의 명시적인 등록에 의존할 수 없으므로, 테스트에서 실제로 확인합니다.

주입 대상까지 도달하지 않는 경우에는 테스트에서 Factory를 명시적으로 대체하거나, `Process` facade를 경유하는 설계로 합니다. 어느 경우든 `assertRan()`으로 호출이 기록된 것을 확인하면 대체 누락을 검출할 수 있습니다.

## 장기 유지보수를 위해 추가할 검증

| 검증 | 목적 |
| - | - |
| 배열 인수의 순서를 고정한 테스트 | `-y`나 입출력의 위치가 바뀌었을 때 알아차림 |
| 실패 시 예외와 메시지 확인 | 이용자의 에러 처리가 바뀌지 않도록 유지 |
| `timeout()`과 `idleTimeout()`의 기본값 확인 | 기본값 변경을 이용자의 동작 변화로 취급 |
| `preventStrayProcesses()` 활성화 | 예상치 못한 외부 명령 실행을 테스트에서 중지 |
| 비밀 정보를 인수에 넣지 않았는지 확인 | 예외 메시지나 로그로의 유출 방지 |

## 참조한 1차 정보

* [Laravel 공식 문서: 프로세스](https://github.com/laravel/docs/blob/13.x/processes.md)
* [Laravel 공식 문서: Process::fake와 assertRan](https://github.com/laravel/docs/blob/13.x/processes.md#faking-processes)
* [Process Factory: fake와 assertRan](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Factory.php)
* [PendingProcess: 기본 타임아웃과 fake의 대조](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/PendingProcess.php)
* [ProcessResult: throw()의 동작](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/ProcessResult.php)
* [ProcessFailedException: 메시지의 구성](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Exceptions/ProcessFailedException.php)
* [Facade: 캐시와 swap()](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Support/Facades/Facade.php)


## Related topics

- [고급 주제](https://kawax.biz/ko/advanced/index.md)
- [패키지의 정기 태스크 등록과 유지보수](https://kawax.biz/ko/advanced/package-scheduling.md)
- [프로세스](https://kawax.biz/ko/processes.md)
- [패키지의 동적 관계 등록](https://kawax.biz/ko/advanced/package-dynamic-relations.md)
- [패키지의 Blade 확장과 컴파일 캐시](https://kawax.biz/ko/advanced/package-blade-extensions.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.