JFIFxxC      C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace PHPUnit\Util\PHP; use const PHP_BINARY; use const PHP_SAPI; use function array_keys; use function array_merge; use function assert; use function fclose; use function file_put_contents; use function fwrite; use function ini_get_all; use function is_array; use function is_resource; use function proc_close; use function proc_open; use function stream_get_contents; use function sys_get_temp_dir; use function tempnam; use function trim; use function unlink; use PHPUnit\Runner\CodeCoverage; use SebastianBergmann\Environment\Runtime; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final readonly class DefaultJobRunner implements JobRunner { /** * @throws PhpProcessException */ public function run(Job $job): Result { $temporaryFile = null; if ($job->hasInput()) { $temporaryFile = tempnam(sys_get_temp_dir(), 'phpunit_'); if ($temporaryFile === false || file_put_contents($temporaryFile, $job->code()) === false) { // @codeCoverageIgnoreStart throw new PhpProcessException( 'Unable to write temporary file', ); // @codeCoverageIgnoreEnd } $job = new Job( $job->input(), $job->phpSettings(), $job->environmentVariables(), $job->arguments(), null, $job->redirectErrors(), ); } assert($temporaryFile !== ''); return $this->runProcess($job, $temporaryFile); } /** * @param ?non-empty-string $temporaryFile * * @throws PhpProcessException */ private function runProcess(Job $job, ?string $temporaryFile): Result { $environmentVariables = null; if ($job->hasEnvironmentVariables()) { /** @phpstan-ignore nullCoalesce.variable */ $environmentVariables = $_SERVER ?? []; unset($environmentVariables['argv'], $environmentVariables['argc']); $environmentVariables = array_merge($environmentVariables, $job->environmentVariables()); foreach ($environmentVariables as $key => $value) { if (is_array($value)) { unset($environmentVariables[$key]); } } unset($key, $value); } $pipeSpec = [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; if ($job->redirectErrors()) { $pipeSpec[2] = ['redirect', 1]; } $process = proc_open( $this->buildCommand($job, $temporaryFile), $pipeSpec, $pipes, null, $environmentVariables, ); if (!is_resource($process)) { // @codeCoverageIgnoreStart throw new PhpProcessException( 'Unable to spawn worker process', ); // @codeCoverageIgnoreEnd } fwrite($pipes[0], $job->code()); fclose($pipes[0]); $stdout = ''; $stderr = ''; if (isset($pipes[1])) { $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); } if (isset($pipes[2])) { $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); } proc_close($process); if ($temporaryFile !== null) { unlink($temporaryFile); } assert($stdout !== false); assert($stderr !== false); return new Result($stdout, $stderr); } /** * @return non-empty-list */ private function buildCommand(Job $job, ?string $file): array { $runtime = new Runtime; $command = [PHP_BINARY]; $phpSettings = $job->phpSettings(); if ($runtime->hasPCOV()) { $pcovSettings = ini_get_all('pcov'); assert($pcovSettings !== false); $phpSettings = array_merge( $phpSettings, $runtime->getCurrentSettings( array_keys($pcovSettings), ), ); } elseif ($runtime->hasXdebug()) { $xdebugSettings = ini_get_all('xdebug'); assert($xdebugSettings !== false); $phpSettings = array_merge( $phpSettings, $runtime->getCurrentSettings( array_keys($xdebugSettings), ), ); // disable xdebug if not required to reduce xdebug performance overhead in subprocesses if ( !CodeCoverage::instance()->isActive() && xdebug_is_debugger_active() === false ) { $phpSettings['xdebug.mode'] = 'xdebug.mode=off'; } } $command = array_merge($command, $this->settingsToParameters($phpSettings)); if (PHP_SAPI === 'phpdbg') { $command[] = '-qrr'; if ($file === null) { $command[] = 's='; } } if ($file !== null) { $command[] = '-f'; $command[] = $file; } if ($job->hasArguments()) { if ($file === null) { $command[] = '--'; } foreach ($job->arguments() as $argument) { $command[] = trim($argument); } } return $command; } /** * @param list $settings * * @return list */ private function settingsToParameters(array $settings): array { $buffer = []; foreach ($settings as $setting) { $buffer[] = '-d'; $buffer[] = $setting; } return $buffer; } }