logger = Mockery::mock(Logger::class); $this->logger->shouldReceive('debug')->andReturn(null); $this->process = new SystemProcess($this->logger); }); afterEach(function () { Mockery::close(); }); describe('SystemProcess - Basic Execution', function () { it('can execute simple command successfully', function () { $result = $this->process->run( Command::fromArray(['echo', 'hello']) ); expect($result->isSuccess())->toBeTrue() ->and($result->exitCode)->toBe(ExitCode::SUCCESS) ->and($result->getOutput())->toContain('hello') ->and($result->hasOutput())->toBeTrue(); }); it('captures stdout correctly', function () { $result = $this->process->run( Command::fromArray(['echo', 'test output']) ); expect($result->stdout)->toContain('test output') ->and($result->getOutput())->toContain('test output'); }); it('captures stderr correctly', function () { $result = $this->process->run( Command::fromString('>&2 echo "error message"') ); expect($result->stderr)->toContain('error message') ->and($result->getErrorOutput())->toContain('error message') ->and($result->hasErrors())->toBeTrue(); }); it('handles failed commands', function () { $result = $this->process->run( Command::fromString('exit 1') ); expect($result->isFailed())->toBeTrue() ->and($result->exitCode)->toBe(ExitCode::GENERAL_ERROR); }); it('tracks execution time', function () { $result = $this->process->run( Command::fromArray(['sleep', '0.1']) ); expect($result->runtime->toMilliseconds())->toBeGreaterThan(50); }); }); describe('SystemProcess - Working Directory', function () { it('can execute command in specific directory', function () { $tempDir = sys_get_temp_dir(); $workingDir = FilePath::create($tempDir); $result = $this->process->run( command: Command::fromString('pwd'), workingDirectory: $workingDir ); expect($result->isSuccess())->toBeTrue() ->and($result->stdout)->toContain($tempDir); }); it('throws exception for invalid working directory', function () { $invalidDir = FilePath::create('/nonexistent/directory/path'); $this->process->run( command: Command::fromArray(['echo', 'test']), workingDirectory: $invalidDir ); })->throws(ProcessException::class); }); describe('SystemProcess - Environment Variables', function () { it('can pass environment variables', function () { $env = EnvironmentVariables::fromArray([ 'TEST_VAR' => 'test_value', ]); $result = $this->process->run( command: Command::fromString('echo $TEST_VAR'), env: $env ); expect($result->stdout)->toContain('test_value'); }); it('merges with existing environment', function () { $env = EnvironmentVariables::fromArray([ 'CUSTOM_VAR' => 'custom_value', ]); $result = $this->process->run( command: Command::fromString('echo $PATH'), env: $env ); // PATH should still be available expect($result->stdout)->not->toBeEmpty(); }); }); describe('SystemProcess - Timeout', function () { it('respects timeout for long-running commands', function () { $timeout = Duration::fromSeconds(1); $result = $this->process->run( command: Command::fromArray(['sleep', '0.5']), timeout: $timeout ); expect($result->isSuccess())->toBeTrue() ->and($result->runtime->toSeconds())->toBeLessThan(1.0); }); it('handles commands that complete within timeout', function () { $timeout = Duration::fromSeconds(5); $result = $this->process->run( command: Command::fromArray(['echo', 'fast']), timeout: $timeout ); expect($result->isSuccess())->toBeTrue(); }); }); describe('SystemProcess - Command Existence', function () { it('detects existing commands', function () { $exists = $this->process->commandExists('echo'); expect($exists)->toBeTrue(); }); it('detects non-existing commands', function () { $exists = $this->process->commandExists('nonexistent_command_xyz'); expect($exists)->toBeFalse(); }); }); describe('SystemProcess - Async Execution', function () { it('can start process asynchronously', function () { $running = $this->process->start( Command::fromArray(['sleep', '0.1']) ); expect($running->isRunning())->toBeTrue() ->and($running->getPid())->toBeGreaterThan(0); $result = $running->wait(); expect($result->isSuccess())->toBeTrue() ->and($running->isRunning())->toBeFalse(); }); it('can terminate running process', function () { $running = $this->process->start( Command::fromArray(['sleep', '10']) ); expect($running->isRunning())->toBeTrue(); $running->terminate(); // Give it a moment to terminate usleep(100000); expect($running->isRunning())->toBeFalse(); }); it('can kill running process', function () { $running = $this->process->start( Command::fromArray(['sleep', '10']) ); expect($running->isRunning())->toBeTrue(); $running->kill(); // Give it a moment to be killed usleep(100000); expect($running->isRunning())->toBeFalse(); }); }); describe('SystemProcess - ProcessResult', function () { it('provides combined output', function () { $result = $this->process->run( Command::fromString('echo "stdout" && >&2 echo "stderr"') ); $combined = $result->getCombinedOutput(); expect($combined)->toContain('stdout') ->and($combined)->toContain('stderr'); }); it('converts result to array', function () { $result = $this->process->run( Command::fromArray(['echo', 'test']) ); $array = $result->toArray(); expect($array)->toHaveKeys([ 'command', 'stdout', 'stderr', 'exit_code', 'exit_description', 'runtime_ms', 'success', ]) ->and($array['success'])->toBeTrue() ->and($array['exit_code'])->toBe(0); }); });