[
  {
    "path": ".gitattributes",
    "content": "/Tests export-ignore\n/phpunit.xml.dist export-ignore\n/.git* export-ignore\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "Please do not submit any Pull Requests here. They will be closed.\n---\n\nPlease submit your PR here instead:\nhttps://github.com/symfony/symfony\n\nThis repository is what we call a \"subtree split\": a read-only subset of that main repository.\nWe're looking forward to your PR there!\n"
  },
  {
    "path": ".github/workflows/close-pull-request.yml",
    "content": "name: Close Pull Request\n\non:\n  pull_request_target:\n    types: [opened]\n\njobs:\n  run:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: superbrothers/close-pull-request@v3\n      with:\n        comment: |\n          Thanks for your Pull Request! We love contributions.\n\n          However, you should instead open your PR on the main repository:\n          https://github.com/symfony/symfony\n\n          This repository is what we call a \"subtree split\": a read-only subset of that main repository.\n          We're looking forward to your PR there!\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "CHANGELOG\n=========\n\n\n7.3\n---\n\n * Add `RunProcessMessage::fromShellCommandline()` to instantiate a Process via the fromShellCommandline method\n\n7.1\n---\n\n * Add `Process::setIgnoredSignals()` to disable signal propagation to the child process\n\n6.4\n---\n\n * Add `PhpSubprocess` to handle PHP subprocesses that take over the\n   configuration from their parent\n * Add `RunProcessMessage` and `RunProcessMessageHandler`\n\n5.2.0\n-----\n\n * added `Process::setOptions()` to set `Process` specific options\n * added option `create_new_console` to allow a subprocess to continue\n   to run after the main script exited, both on Linux and on Windows\n\n5.1.0\n-----\n\n * added `Process::getStartTime()` to retrieve the start time of the process as float\n\n5.0.0\n-----\n\n * removed `Process::inheritEnvironmentVariables()`\n * removed `PhpProcess::setPhpBinary()`\n * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell\n * removed `Process::setCommandLine()`\n\n4.4.0\n-----\n\n * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited.\n * added `Process::getLastOutputTime()` method\n\n4.2.0\n-----\n\n * added the `Process::fromShellCommandline()` to run commands in a shell wrapper\n * deprecated passing a command as string when creating a `Process` instance\n * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods\n * added the `Process::waitUntil()` method to wait for the process only for a\n   specific output, then continue the normal execution of your application\n\n4.1.0\n-----\n\n * added the `Process::isTtySupported()` method that allows to check for TTY support\n * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary\n * added the `ProcessSignaledException` class to properly catch signaled process errors\n\n4.0.0\n-----\n\n * environment variables will always be inherited\n * added a second `array $env = []` argument to the `start()`, `run()`,\n   `mustRun()`, and `restart()` methods of the `Process` class\n * added a second `array $env = []` argument to the `start()` method of the\n   `PhpProcess` class\n * the `ProcessUtils::escapeArgument()` method has been removed\n * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()`\n   methods of the `Process` class have been removed\n * support for passing `proc_open()` options has been removed\n * removed the `ProcessBuilder` class, use the `Process` class instead\n * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class\n * passing a not existing working directory to the constructor of the `Symfony\\Component\\Process\\Process` class is not\n   supported anymore\n\n3.4.0\n-----\n\n * deprecated the ProcessBuilder class\n * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor)\n\n3.3.0\n-----\n\n * added command line arrays in the `Process` class\n * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods\n * deprecated the `ProcessUtils::escapeArgument()` method\n * deprecated not inheriting environment variables\n * deprecated configuring `proc_open()` options\n * deprecated configuring enhanced Windows compatibility\n * deprecated configuring enhanced sigchild compatibility\n\n2.5.0\n-----\n\n * added support for PTY mode\n * added the convenience method \"mustRun\"\n * deprecation: Process::setStdin() is deprecated in favor of Process::setInput()\n * deprecation: Process::getStdin() is deprecated in favor of Process::getInput()\n * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types\n\n2.4.0\n-----\n\n * added the ability to define an idle timeout\n\n2.3.0\n-----\n\n * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows\n * added Process::signal()\n * added Process::getPid()\n * added support for a TTY mode\n\n2.2.0\n-----\n\n * added ProcessBuilder::setArguments() to reset the arguments on a builder\n * added a way to retrieve the standard and error output incrementally\n * added Process:restart()\n\n2.1.0\n-----\n\n * added support for non-blocking processes (start(), wait(), isRunning(), stop())\n * enhanced Windows compatibility\n * added Process::getExitCodeText() that returns a string representation for\n   the exit code returned by the process\n * added ProcessBuilder\n"
  },
  {
    "path": "Exception/ExceptionInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\n/**\n * Marker Interface for the Process Component.\n *\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\ninterface ExceptionInterface extends \\Throwable\n{\n}\n"
  },
  {
    "path": "Exception/InvalidArgumentException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\n/**\n * InvalidArgumentException for the Process Component.\n *\n * @author Romain Neutron <imprec@gmail.com>\n */\nclass InvalidArgumentException extends \\InvalidArgumentException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "Exception/LogicException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\n/**\n * LogicException for the Process Component.\n *\n * @author Romain Neutron <imprec@gmail.com>\n */\nclass LogicException extends \\LogicException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "Exception/ProcessFailedException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * Exception for failed processes.\n *\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\nclass ProcessFailedException extends RuntimeException\n{\n    public function __construct(\n        private Process $process,\n    ) {\n        if ($process->isSuccessful()) {\n            throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');\n        }\n\n        $error = \\sprintf('The command \"%s\" failed.'.\"\\n\\nExit Code: %s(%s)\\n\\nWorking directory: %s\",\n            $process->getCommandLine(),\n            $process->getExitCode(),\n            $process->getExitCodeText(),\n            $process->getWorkingDirectory()\n        );\n\n        if (!$process->isOutputDisabled()) {\n            $error .= \\sprintf(\"\\n\\nOutput:\\n================\\n%s\\n\\nError Output:\\n================\\n%s\",\n                $process->getOutput(),\n                $process->getErrorOutput()\n            );\n        }\n\n        parent::__construct($error);\n\n        $this->process = $process;\n    }\n\n    public function getProcess(): Process\n    {\n        return $this->process;\n    }\n}\n"
  },
  {
    "path": "Exception/ProcessSignaledException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * Exception that is thrown when a process has been signaled.\n *\n * @author Sullivan Senechal <soullivaneuh@gmail.com>\n */\nfinal class ProcessSignaledException extends RuntimeException\n{\n    public function __construct(\n        private Process $process,\n    ) {\n        parent::__construct(\\sprintf('The process has been signaled with signal \"%s\".', $process->getTermSignal()));\n    }\n\n    public function getProcess(): Process\n    {\n        return $this->process;\n    }\n\n    public function getSignal(): int\n    {\n        return $this->getProcess()->getTermSignal();\n    }\n}\n"
  },
  {
    "path": "Exception/ProcessStartFailedException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * Exception for processes failed during startup.\n */\nclass ProcessStartFailedException extends ProcessFailedException\n{\n    public function __construct(\n        private Process $process,\n        ?string $message,\n    ) {\n        if ($process->isStarted()) {\n            throw new InvalidArgumentException('Expected a process that failed during startup, but the given process was started successfully.');\n        }\n\n        $error = \\sprintf('The command \"%s\" failed.'.\"\\n\\nWorking directory: %s\\n\\nError: %s\",\n            $process->getCommandLine(),\n            $process->getWorkingDirectory(),\n            $message ?? 'unknown'\n        );\n\n        // Skip parent constructor\n        RuntimeException::__construct($error);\n    }\n\n    public function getProcess(): Process\n    {\n        return $this->process;\n    }\n}\n"
  },
  {
    "path": "Exception/ProcessTimedOutException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * Exception that is thrown when a process times out.\n *\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\nclass ProcessTimedOutException extends RuntimeException\n{\n    public const TYPE_GENERAL = 1;\n    public const TYPE_IDLE = 2;\n\n    public function __construct(\n        private Process $process,\n        private int $timeoutType,\n    ) {\n        parent::__construct(\\sprintf(\n            'The process \"%s\" exceeded the timeout of %s seconds.',\n            $process->getCommandLine(),\n            $this->getExceededTimeout()\n        ));\n    }\n\n    public function getProcess(): Process\n    {\n        return $this->process;\n    }\n\n    public function isGeneralTimeout(): bool\n    {\n        return self::TYPE_GENERAL === $this->timeoutType;\n    }\n\n    public function isIdleTimeout(): bool\n    {\n        return self::TYPE_IDLE === $this->timeoutType;\n    }\n\n    public function getExceededTimeout(): ?float\n    {\n        return match ($this->timeoutType) {\n            self::TYPE_GENERAL => $this->process->getTimeout(),\n            self::TYPE_IDLE => $this->process->getIdleTimeout(),\n            default => throw new \\LogicException(\\sprintf('Unknown timeout type \"%d\".', $this->timeoutType)),\n        };\n    }\n}\n"
  },
  {
    "path": "Exception/RunProcessFailedException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\nuse Symfony\\Component\\Process\\Messenger\\RunProcessContext;\n\n/**\n * @author Kevin Bond <kevinbond@gmail.com>\n */\nfinal class RunProcessFailedException extends RuntimeException\n{\n    public function __construct(ProcessFailedException $exception, public readonly RunProcessContext $context)\n    {\n        parent::__construct($exception->getMessage(), $exception->getCode());\n    }\n}\n"
  },
  {
    "path": "Exception/RuntimeException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Exception;\n\n/**\n * RuntimeException for the Process Component.\n *\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\nclass RuntimeException extends \\RuntimeException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "ExecutableFinder.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process;\n\n/**\n * Generic executable finder.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\nclass ExecutableFinder\n{\n    private const CMD_BUILTINS = [\n        'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date',\n        'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto',\n        'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause',\n        'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set',\n        'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol',\n    ];\n\n    private array $suffixes = [];\n\n    /**\n     * Replaces default suffixes of executable.\n     */\n    public function setSuffixes(array $suffixes): void\n    {\n        $this->suffixes = $suffixes;\n    }\n\n    /**\n     * Adds new possible suffix to check for executable, including the dot (.).\n     *\n     *     $finder = new ExecutableFinder();\n     *     $finder->addSuffix('.foo');\n     */\n    public function addSuffix(string $suffix): void\n    {\n        $this->suffixes[] = $suffix;\n    }\n\n    /**\n     * Finds an executable by name.\n     *\n     * @param string      $name      The executable name (without the extension)\n     * @param string|null $default   The default to return if no executable is found\n     * @param array       $extraDirs Additional dirs to check into\n     */\n    public function find(string $name, ?string $default = null, array $extraDirs = []): ?string\n    {\n        // windows built-in commands that are present in cmd.exe should not be resolved using PATH as they do not exist as exes\n        if ('\\\\' === \\DIRECTORY_SEPARATOR && \\in_array(strtolower($name), self::CMD_BUILTINS, true)) {\n            return $name;\n        }\n\n        $dirs = array_merge(\n            explode(\\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path') ?: ''),\n            $extraDirs\n        );\n\n        $suffixes = $this->suffixes;\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $pathExt = getenv('PATHEXT') ?: '';\n            $suffixes = array_merge($suffixes, $pathExt ? explode(\\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']);\n        }\n        $suffixes = '' !== pathinfo($name, \\PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']);\n        foreach ($suffixes as $suffix) {\n            foreach ($dirs as $dir) {\n                if ('' === $dir) {\n                    $dir = '.';\n                }\n                if (@is_file($file = $dir.\\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\\\' === \\DIRECTORY_SEPARATOR || @is_executable($file))) {\n                    return $file;\n                }\n\n                if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) {\n                    return $dir;\n                }\n            }\n        }\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR || !\\function_exists('exec') || \\strlen($name) !== strcspn($name, '/'.\\DIRECTORY_SEPARATOR)) {\n            return $default;\n        }\n\n        $execResult = exec('command -v -- '.escapeshellarg($name));\n\n        if (($executablePath = substr($execResult, 0, strpos($execResult, \\PHP_EOL) ?: null)) && @is_executable($executablePath)) {\n            return $executablePath;\n        }\n\n        return $default;\n    }\n}\n"
  },
  {
    "path": "InputStream.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process;\n\nuse Symfony\\Component\\Process\\Exception\\RuntimeException;\n\n/**\n * Provides a way to continuously write to the input of a Process until the InputStream is closed.\n *\n * @author Nicolas Grekas <p@tchwork.com>\n *\n * @implements \\IteratorAggregate<int, string>\n */\nclass InputStream implements \\IteratorAggregate\n{\n    private ?\\Closure $onEmpty = null;\n    private array $input = [];\n    private bool $open = true;\n\n    /**\n     * Sets a callback that is called when the write buffer becomes empty.\n     */\n    public function onEmpty(?callable $onEmpty = null): void\n    {\n        $this->onEmpty = null !== $onEmpty ? $onEmpty(...) : null;\n    }\n\n    /**\n     * Appends an input to the write buffer.\n     *\n     * @param resource|string|int|float|bool|\\Traversable|null $input The input to append as scalar,\n     *                                                                stream resource or \\Traversable\n     */\n    public function write(mixed $input): void\n    {\n        if (null === $input) {\n            return;\n        }\n        if ($this->isClosed()) {\n            throw new RuntimeException(\\sprintf('\"%s\" is closed.', static::class));\n        }\n        $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);\n    }\n\n    /**\n     * Closes the write buffer.\n     */\n    public function close(): void\n    {\n        $this->open = false;\n    }\n\n    /**\n     * Tells whether the write buffer is closed or not.\n     */\n    public function isClosed(): bool\n    {\n        return !$this->open;\n    }\n\n    public function getIterator(): \\Traversable\n    {\n        $this->open = true;\n\n        while ($this->open || $this->input) {\n            if (!$this->input) {\n                yield '';\n                continue;\n            }\n            $current = array_shift($this->input);\n\n            if ($current instanceof \\Iterator) {\n                yield from $current;\n            } else {\n                yield $current;\n            }\n            if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {\n                $this->write($onEmpty($this));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2004-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Messenger/RunProcessContext.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Messenger;\n\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * @author Kevin Bond <kevinbond@gmail.com>\n */\nfinal class RunProcessContext\n{\n    public readonly ?int $exitCode;\n    public readonly ?string $output;\n    public readonly ?string $errorOutput;\n\n    public function __construct(\n        public readonly RunProcessMessage $message,\n        Process $process,\n    ) {\n        $this->exitCode = $process->getExitCode();\n        $this->output = !$process->isStarted() || $process->isOutputDisabled() ? null : $process->getOutput();\n        $this->errorOutput = !$process->isStarted() || $process->isOutputDisabled() ? null : $process->getErrorOutput();\n    }\n}\n"
  },
  {
    "path": "Messenger/RunProcessMessage.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Messenger;\n\n/**\n * @author Kevin Bond <kevinbond@gmail.com>\n */\nclass RunProcessMessage implements \\Stringable\n{\n    public ?string $commandLine = null;\n\n    public function __construct(\n        public readonly array $command,\n        public readonly ?string $cwd = null,\n        public readonly ?array $env = null,\n        public readonly mixed $input = null,\n        public readonly ?float $timeout = 60.0,\n    ) {\n    }\n\n    public function __toString(): string\n    {\n        return $this->commandLine ?? implode(' ', $this->command);\n    }\n\n    /**\n     * Create a process message instance that will instantiate a Process using the fromShellCommandline method.\n     *\n     * @see Process::fromShellCommandline\n     */\n    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): self\n    {\n        $message = new self([], $cwd, $env, $input, $timeout);\n        $message->commandLine = $command;\n\n        return $message;\n    }\n}\n"
  },
  {
    "path": "Messenger/RunProcessMessageHandler.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Messenger;\n\nuse Symfony\\Component\\Process\\Exception\\ProcessFailedException;\nuse Symfony\\Component\\Process\\Exception\\RunProcessFailedException;\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * @author Kevin Bond <kevinbond@gmail.com>\n */\nfinal class RunProcessMessageHandler\n{\n    public function __invoke(RunProcessMessage $message): RunProcessContext\n    {\n        $process = match ($message->commandLine) {\n            null => new Process($message->command, $message->cwd, $message->env, $message->input, $message->timeout),\n            default => Process::fromShellCommandline($message->commandLine, $message->cwd, $message->env, $message->input, $message->timeout),\n        };\n\n        try {\n            return new RunProcessContext($message, $process->mustRun());\n        } catch (ProcessFailedException $e) {\n            throw new RunProcessFailedException($e, new RunProcessContext($message, $e->getProcess()));\n        }\n    }\n}\n"
  },
  {
    "path": "PhpExecutableFinder.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process;\n\n/**\n * An executable finder specifically designed for the PHP executable.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\nclass PhpExecutableFinder\n{\n    private ExecutableFinder $executableFinder;\n\n    public function __construct()\n    {\n        $this->executableFinder = new ExecutableFinder();\n    }\n\n    /**\n     * Finds The PHP executable.\n     */\n    public function find(bool $includeArgs = true): string|false\n    {\n        if ($php = getenv('PHP_BINARY')) {\n            if (!is_executable($php) && !$php = $this->executableFinder->find($php)) {\n                return false;\n            }\n\n            if (@is_dir($php)) {\n                return false;\n            }\n\n            return $php;\n        }\n\n        $args = $this->findArguments();\n        $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';\n\n        // PHP_BINARY return the current sapi executable\n        if (\\PHP_BINARY && \\in_array(\\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {\n            return \\PHP_BINARY.$args;\n        }\n\n        if ($php = getenv('PHP_PATH')) {\n            if (!@is_executable($php) || @is_dir($php)) {\n                return false;\n            }\n\n            return $php;\n        }\n\n        if ($php = getenv('PHP_PEAR_PHP_BIN')) {\n            if (@is_executable($php) && !@is_dir($php)) {\n                return $php;\n            }\n        }\n\n        if (@is_executable($php = \\PHP_BINDIR.('\\\\' === \\DIRECTORY_SEPARATOR ? '\\\\php.exe' : '/php')) && !@is_dir($php)) {\n            return $php;\n        }\n\n        $dirs = [\\PHP_BINDIR];\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $dirs[] = 'C:\\xampp\\php\\\\';\n        }\n\n        if ($herdPath = getenv('HERD_HOME')) {\n            $dirs[] = $herdPath.\\DIRECTORY_SEPARATOR.'bin';\n        }\n\n        return $this->executableFinder->find('php', false, $dirs);\n    }\n\n    /**\n     * Finds the PHP executable arguments.\n     *\n     * @return list<non-empty-string>\n     */\n    public function findArguments(): array\n    {\n        $arguments = [];\n        if ('phpdbg' === \\PHP_SAPI) {\n            $arguments[] = '-qrr';\n        }\n\n        return $arguments;\n    }\n}\n"
  },
  {
    "path": "PhpProcess.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process;\n\nuse Symfony\\Component\\Process\\Exception\\LogicException;\nuse Symfony\\Component\\Process\\Exception\\RuntimeException;\n\n/**\n * PhpProcess runs a PHP script in an independent process.\n *\n *     $p = new PhpProcess('<?php echo \"foo\"; ?>');\n *     $p->run();\n *     print $p->getOutput().\"\\n\";\n *\n * @author Fabien Potencier <fabien@symfony.com>\n *\n * @psalm-import-type EnvArray from Process\n */\nclass PhpProcess extends Process\n{\n    /**\n     * @param string        $script  The PHP script to run (as a string)\n     * @param string|null   $cwd     The working directory or null to use the working dir of the current PHP process\n     * @param EnvArray|null $env     The environment variables or null to use the same environment as the current PHP process\n     * @param int           $timeout The timeout in seconds\n     * @param array|null    $php     Path to the PHP binary to use with any additional arguments\n     */\n    public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)\n    {\n        if (null === $php) {\n            $executableFinder = new PhpExecutableFinder();\n            $php = $executableFinder->find(false);\n            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());\n        }\n        if ('phpdbg' === \\PHP_SAPI) {\n            $file = tempnam(sys_get_temp_dir(), 'dbg');\n            file_put_contents($file, $script);\n            register_shutdown_function('unlink', $file);\n            $php[] = $file;\n            $script = null;\n        }\n\n        parent::__construct($php, $cwd, $env, $script, $timeout);\n    }\n\n    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static\n    {\n        throw new LogicException(\\sprintf('The \"%s()\" method cannot be called when using \"%s\".', __METHOD__, self::class));\n    }\n\n    /**\n     * @param (callable('out'|'err', string):void)|null $callback\n     * @param EnvArray                                  $env\n     */\n    public function start(?callable $callback = null, array $env = []): void\n    {\n        if (null === $this->getCommandLine()) {\n            throw new RuntimeException('Unable to find the PHP executable.');\n        }\n\n        parent::start($callback, $env);\n    }\n}\n"
  },
  {
    "path": "PhpSubprocess.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process;\n\nuse Symfony\\Component\\Process\\Exception\\LogicException;\nuse Symfony\\Component\\Process\\Exception\\RuntimeException;\n\n/**\n * PhpSubprocess runs a PHP command as a subprocess while keeping the original php.ini settings.\n *\n * For this, it generates a temporary php.ini file taking over all the current settings and disables\n * loading additional .ini files. Basically, your command gets prefixed using \"php -n -c /tmp/temp.ini\".\n *\n * Given your php.ini contains \"memory_limit=-1\" and you have a \"MemoryTest.php\" with the following content:\n *\n *     <?php var_dump(ini_get('memory_limit'));\n *\n * These are the differences between the regular Process and PhpSubprocess classes:\n *\n *     $p = new Process(['php', '-d', 'memory_limit=256M', 'MemoryTest.php']);\n *     $p->run();\n *     print $p->getOutput().\"\\n\";\n *\n * This will output \"string(2) \"-1\", because the process is started with the default php.ini settings.\n *\n *     $p = new PhpSubprocess(['MemoryTest.php'], null, null, 60, ['php', '-d', 'memory_limit=256M']);\n *     $p->run();\n *     print $p->getOutput().\"\\n\";\n *\n * This will output \"string(4) \"256M\"\", because the process is started with the temporarily created php.ini settings.\n *\n * @author Yanick Witschi <yanick.witschi@terminal42.ch>\n * @author Partially copied and heavily inspired from composer/xdebug-handler by John Stevenson <john-stevenson@blueyonder.co.uk>\n *\n * @psalm-import-type EnvArray from Process\n */\nclass PhpSubprocess extends Process\n{\n    /**\n     * @param array         $command The command to run and its arguments listed as separate entries. They will automatically\n     *                               get prefixed with the PHP binary\n     * @param string|null   $cwd     The working directory or null to use the working dir of the current PHP process\n     * @param EnvArray|null $env     The environment variables or null to use the same environment as the current PHP process\n     * @param int           $timeout The timeout in seconds\n     * @param array|null    $php     Path to the PHP binary to use with any additional arguments\n     */\n    public function __construct(array $command, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)\n    {\n        if (null === $php) {\n            $executableFinder = new PhpExecutableFinder();\n            $php = $executableFinder->find(false);\n            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());\n        }\n\n        if (null === $php) {\n            throw new RuntimeException('Unable to find PHP binary.');\n        }\n\n        $tmpIni = $this->writeTmpIni($this->getAllIniFiles(), sys_get_temp_dir());\n\n        $php = array_merge($php, ['-n', '-c', $tmpIni]);\n        register_shutdown_function('unlink', $tmpIni);\n\n        $command = array_merge($php, $command);\n\n        parent::__construct($command, $cwd, $env, null, $timeout);\n    }\n\n    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static\n    {\n        throw new LogicException(\\sprintf('The \"%s()\" method cannot be called when using \"%s\".', __METHOD__, self::class));\n    }\n\n    /**\n     * @param (callable('out'|'err', string):void)|null $callback\n     */\n    public function start(?callable $callback = null, array $env = []): void\n    {\n        if (null === $this->getCommandLine()) {\n            throw new RuntimeException('Unable to find the PHP executable.');\n        }\n\n        parent::start($callback, $env);\n    }\n\n    private function writeTmpIni(array $iniFiles, string $tmpDir): string\n    {\n        if (false === $tmpfile = @tempnam($tmpDir, '')) {\n            throw new RuntimeException('Unable to create temporary ini file.');\n        }\n\n        // $iniFiles has at least one item and it may be empty\n        if ('' === $iniFiles[0]) {\n            array_shift($iniFiles);\n        }\n\n        $content = '';\n\n        foreach ($iniFiles as $file) {\n            // Check for inaccessible ini files\n            if (($data = @file_get_contents($file)) === false) {\n                throw new RuntimeException('Unable to read ini: '.$file);\n            }\n            // Check and remove directives after HOST and PATH sections\n            if (preg_match('/^\\s*\\[(?:PATH|HOST)\\s*=/mi', $data, $matches, \\PREG_OFFSET_CAPTURE)) {\n                $data = substr($data, 0, $matches[0][1]);\n            }\n\n            $content .= $data.\"\\n\";\n        }\n\n        // Merge loaded settings into our ini content, if it is valid\n        $config = parse_ini_string($content);\n        $loaded = ini_get_all(null, false);\n\n        if (false === $config || false === $loaded) {\n            throw new RuntimeException('Unable to parse ini data.');\n        }\n\n        $content .= $this->mergeLoadedConfig($loaded, $config);\n\n        // Work-around for https://bugs.php.net/bug.php?id=75932\n        $content .= \"opcache.enable_cli=0\\n\";\n\n        if (false === @file_put_contents($tmpfile, $content)) {\n            throw new RuntimeException('Unable to write temporary ini file.');\n        }\n\n        return $tmpfile;\n    }\n\n    private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string\n    {\n        $content = '';\n\n        foreach ($loadedConfig as $name => $value) {\n            if (!\\is_string($value)) {\n                continue;\n            }\n\n            if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {\n                // Double-quote escape each value\n                $content .= $name.'=\"'.addcslashes($value, '\\\\\"').\"\\\"\\n\";\n            }\n        }\n\n        return $content;\n    }\n\n    private function getAllIniFiles(): array\n    {\n        $paths = [(string) php_ini_loaded_file()];\n\n        if (false !== $scanned = php_ini_scanned_files()) {\n            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));\n        }\n\n        return $paths;\n    }\n}\n"
  },
  {
    "path": "Pipes/AbstractPipes.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Pipes;\n\nuse Symfony\\Component\\Process\\Exception\\InvalidArgumentException;\n\n/**\n * @author Romain Neutron <imprec@gmail.com>\n *\n * @internal\n */\nabstract class AbstractPipes implements PipesInterface\n{\n    public array $pipes = [];\n\n    private string $inputBuffer = '';\n    /** @var resource|string|\\Iterator */\n    private $input;\n    private bool $blocked = true;\n    private ?string $lastError = null;\n\n    /**\n     * @param resource|string|\\Iterator $input\n     */\n    public function __construct($input)\n    {\n        if (\\is_resource($input) || $input instanceof \\Iterator) {\n            $this->input = $input;\n        } else {\n            $this->inputBuffer = (string) $input;\n        }\n    }\n\n    public function close(): void\n    {\n        foreach ($this->pipes as $pipe) {\n            if (\\is_resource($pipe)) {\n                fclose($pipe);\n            }\n        }\n        $this->pipes = [];\n    }\n\n    /**\n     * Returns true if a system call has been interrupted.\n     *\n     * stream_select() returns false when the `select` system call is interrupted by an incoming signal.\n     */\n    protected function hasSystemCallBeenInterrupted(): bool\n    {\n        $lastError = $this->lastError;\n        $this->lastError = null;\n\n        if (null === $lastError) {\n            return false;\n        }\n\n        if (false !== stripos($lastError, 'interrupted system call')) {\n            return true;\n        }\n\n        // on applications with a different locale than english, the message above is not found because\n        // it's translated. So we also check for the SOCKET_EINTR constant which is defined under\n        // Windows and UNIX-like platforms (if available on the platform).\n        return \\defined('SOCKET_EINTR') && str_starts_with($lastError, 'stream_select(): Unable to select ['.\\SOCKET_EINTR.']');\n    }\n\n    /**\n     * Unblocks streams.\n     */\n    protected function unblock(): void\n    {\n        if (!$this->blocked) {\n            return;\n        }\n\n        foreach ($this->pipes as $pipe) {\n            stream_set_blocking($pipe, false);\n        }\n        if (\\is_resource($this->input)) {\n            stream_set_blocking($this->input, false);\n        }\n\n        $this->blocked = false;\n    }\n\n    /**\n     * Writes input to stdin.\n     *\n     * @throws InvalidArgumentException When an input iterator yields a non supported value\n     */\n    protected function write(): ?array\n    {\n        if (!isset($this->pipes[0])) {\n            return null;\n        }\n        $input = $this->input;\n\n        if ($input instanceof \\Iterator) {\n            if (!$input->valid()) {\n                $input = null;\n            } elseif (\\is_resource($input = $input->current())) {\n                stream_set_blocking($input, false);\n            } elseif (!isset($this->inputBuffer[0])) {\n                if (!\\is_string($input)) {\n                    if (!\\is_scalar($input)) {\n                        throw new InvalidArgumentException(\\sprintf('\"%s\" yielded a value of type \"%s\", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));\n                    }\n                    $input = (string) $input;\n                }\n                $this->inputBuffer = $input;\n                $this->input->next();\n                $input = null;\n            } else {\n                $input = null;\n            }\n        }\n\n        $r = $e = [];\n        $w = [$this->pipes[0]];\n\n        // let's have a look if something changed in streams\n        if (false === @stream_select($r, $w, $e, 0, 0)) {\n            return null;\n        }\n\n        foreach ($w as $stdin) {\n            if (isset($this->inputBuffer[0])) {\n                if (false === $written = @fwrite($stdin, $this->inputBuffer)) {\n                    return $this->closeBrokenInputPipe();\n                }\n                $this->inputBuffer = substr($this->inputBuffer, $written);\n                if (isset($this->inputBuffer[0]) && isset($this->pipes[0])) {\n                    return [$this->pipes[0]];\n                }\n            }\n\n            if ($input) {\n                while (true) {\n                    $data = fread($input, self::CHUNK_SIZE);\n                    if (!isset($data[0])) {\n                        break;\n                    }\n                    if (false === $written = @fwrite($stdin, $data)) {\n                        return $this->closeBrokenInputPipe();\n                    }\n                    $data = substr($data, $written);\n                    if (isset($data[0])) {\n                        $this->inputBuffer = $data;\n\n                        return isset($this->pipes[0]) ? [$this->pipes[0]] : null;\n                    }\n                }\n                if (feof($input)) {\n                    if ($this->input instanceof \\Iterator) {\n                        $this->input->next();\n                    } else {\n                        $this->input = null;\n                    }\n                }\n            }\n        }\n\n        // no input to read on resource, buffer is empty\n        if (!isset($this->inputBuffer[0]) && !($this->input instanceof \\Iterator ? $this->input->valid() : $this->input)) {\n            $this->input = null;\n            fclose($this->pipes[0]);\n            unset($this->pipes[0]);\n        } elseif (!$w) {\n            return [$this->pipes[0]];\n        }\n\n        return null;\n    }\n\n    private function closeBrokenInputPipe(): void\n    {\n        $this->lastError = error_get_last()['message'] ?? null;\n        if (\\is_resource($this->pipes[0] ?? null)) {\n            fclose($this->pipes[0]);\n        }\n        unset($this->pipes[0]);\n\n        $this->input = null;\n        $this->inputBuffer = '';\n    }\n\n    /**\n     * @internal\n     */\n    public function handleError(int $type, string $msg): void\n    {\n        $this->lastError = $msg;\n    }\n}\n"
  },
  {
    "path": "Pipes/PipesInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Pipes;\n\n/**\n * PipesInterface manages descriptors and pipes for the use of proc_open.\n *\n * @author Romain Neutron <imprec@gmail.com>\n *\n * @internal\n */\ninterface PipesInterface\n{\n    public const CHUNK_SIZE = 16384;\n\n    /**\n     * Returns an array of descriptors for the use of proc_open.\n     */\n    public function getDescriptors(): array;\n\n    /**\n     * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.\n     *\n     * @return string[]\n     */\n    public function getFiles(): array;\n\n    /**\n     * Reads data in file handles and pipes.\n     *\n     * @param bool $blocking Whether to use blocking calls or not\n     * @param bool $close    Whether to close pipes if they've reached EOF\n     *\n     * @return string[] An array of read data indexed by their fd\n     */\n    public function readAndWrite(bool $blocking, bool $close = false): array;\n\n    /**\n     * Returns if the current state has open file handles or pipes.\n     */\n    public function areOpen(): bool;\n\n    /**\n     * Returns if pipes are able to read output.\n     */\n    public function haveReadSupport(): bool;\n\n    /**\n     * Closes file handles and pipes.\n     */\n    public function close(): void;\n}\n"
  },
  {
    "path": "Pipes/UnixPipes.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Pipes;\n\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * UnixPipes implementation uses unix pipes as handles.\n *\n * @author Romain Neutron <imprec@gmail.com>\n *\n * @internal\n */\nclass UnixPipes extends AbstractPipes\n{\n    public function __construct(\n        private ?bool $ttyMode,\n        private bool $ptyMode,\n        mixed $input,\n        private bool $haveReadSupport,\n    ) {\n        parent::__construct($input);\n    }\n\n    public function __serialize(): array\n    {\n        throw new \\BadMethodCallException('Cannot serialize '.__CLASS__);\n    }\n\n    public function __unserialize(array $data): void\n    {\n        throw new \\BadMethodCallException('Cannot unserialize '.__CLASS__);\n    }\n\n    public function __destruct()\n    {\n        $this->close();\n    }\n\n    public function getDescriptors(): array\n    {\n        if (!$this->haveReadSupport) {\n            $nullstream = fopen('/dev/null', 'c');\n\n            return [\n                ['pipe', 'r'],\n                $nullstream,\n                $nullstream,\n            ];\n        }\n\n        if ($this->ttyMode) {\n            return [\n                ['file', '/dev/tty', 'r'],\n                ['file', '/dev/tty', 'w'],\n                ['file', '/dev/tty', 'w'],\n            ];\n        }\n\n        if ($this->ptyMode && Process::isPtySupported()) {\n            return [\n                ['pty'],\n                ['pty'],\n                ['pipe', 'w'], // stderr needs to be in a pipe to correctly split error and output, since PHP will use the same stream for both\n            ];\n        }\n\n        return [\n            ['pipe', 'r'],\n            ['pipe', 'w'], // stdout\n            ['pipe', 'w'], // stderr\n        ];\n    }\n\n    public function getFiles(): array\n    {\n        return [];\n    }\n\n    public function readAndWrite(bool $blocking, bool $close = false): array\n    {\n        $this->unblock();\n        $w = $this->write();\n\n        $read = $e = [];\n        $r = $this->pipes;\n        unset($r[0]);\n\n        // let's have a look if something changed in streams\n        set_error_handler($this->handleError(...));\n        if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {\n            restore_error_handler();\n            // if a system call has been interrupted, forget about it, let's try again\n            // otherwise, an error occurred, let's reset pipes\n            if (!$this->hasSystemCallBeenInterrupted()) {\n                $this->pipes = [];\n            }\n\n            return $read;\n        }\n        restore_error_handler();\n\n        foreach ($r as $pipe) {\n            // prior PHP 5.4 the array passed to stream_select is modified and\n            // lose key association, we have to find back the key\n            $read[$type = array_search($pipe, $this->pipes, true)] = '';\n\n            do {\n                $data = @fread($pipe, self::CHUNK_SIZE);\n                $read[$type] .= $data;\n            } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));\n\n            if (!isset($read[$type][0])) {\n                unset($read[$type]);\n            }\n\n            if ($close && feof($pipe)) {\n                fclose($pipe);\n                unset($this->pipes[$type]);\n            }\n        }\n\n        return $read;\n    }\n\n    public function haveReadSupport(): bool\n    {\n        return $this->haveReadSupport;\n    }\n\n    public function areOpen(): bool\n    {\n        return (bool) $this->pipes;\n    }\n}\n"
  },
  {
    "path": "Pipes/WindowsPipes.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Pipes;\n\nuse Symfony\\Component\\Process\\Exception\\RuntimeException;\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * WindowsPipes implementation uses temporary files as handles.\n *\n * @see https://bugs.php.net/51800\n * @see https://bugs.php.net/65650\n *\n * @author Romain Neutron <imprec@gmail.com>\n *\n * @internal\n */\nclass WindowsPipes extends AbstractPipes\n{\n    private array $files = [];\n    private array $fileHandles = [];\n    private array $lockHandles = [];\n    private array $readBytes = [\n        Process::STDOUT => 0,\n        Process::STDERR => 0,\n    ];\n\n    public function __construct(\n        mixed $input,\n        private bool $haveReadSupport,\n    ) {\n        if ($this->haveReadSupport) {\n            // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.\n            // Workaround for this problem is to use temporary files instead of pipes on Windows platform.\n            //\n            // @see https://bugs.php.net/51800\n            $pipes = [\n                Process::STDOUT => Process::OUT,\n                Process::STDERR => Process::ERR,\n            ];\n            $tmpDir = sys_get_temp_dir();\n            $lastError = 'unknown reason';\n            set_error_handler(static function ($type, $msg) use (&$lastError) { $lastError = $msg; });\n            for ($i = 0;; ++$i) {\n                foreach ($pipes as $pipe => $name) {\n                    $file = \\sprintf('%s\\\\sf_proc_%02X.%s', $tmpDir, $i, $name);\n\n                    if (!$h = fopen($file.'.lock', 'w')) {\n                        if (file_exists($file.'.lock')) {\n                            continue 2;\n                        }\n                        restore_error_handler();\n                        throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);\n                    }\n                    if (!flock($h, \\LOCK_EX | \\LOCK_NB)) {\n                        continue 2;\n                    }\n                    if (isset($this->lockHandles[$pipe])) {\n                        flock($this->lockHandles[$pipe], \\LOCK_UN);\n                        fclose($this->lockHandles[$pipe]);\n                    }\n                    $this->lockHandles[$pipe] = $h;\n\n                    if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {\n                        flock($this->lockHandles[$pipe], \\LOCK_UN);\n                        fclose($this->lockHandles[$pipe]);\n                        unset($this->lockHandles[$pipe]);\n                        continue 2;\n                    }\n                    $this->fileHandles[$pipe] = $h;\n                    $this->files[$pipe] = $file;\n                }\n                break;\n            }\n            restore_error_handler();\n        }\n\n        parent::__construct($input);\n    }\n\n    public function __serialize(): array\n    {\n        throw new \\BadMethodCallException('Cannot serialize '.__CLASS__);\n    }\n\n    public function __unserialize(array $data): void\n    {\n        throw new \\BadMethodCallException('Cannot unserialize '.__CLASS__);\n    }\n\n    public function __destruct()\n    {\n        $this->close();\n    }\n\n    public function getDescriptors(): array\n    {\n        if (!$this->haveReadSupport) {\n            $nullstream = fopen('NUL', 'c');\n\n            return [\n                ['pipe', 'r'],\n                $nullstream,\n                $nullstream,\n            ];\n        }\n\n        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)\n        // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650\n        // So we redirect output within the commandline and pass the nul device to the process\n        return [\n            ['pipe', 'r'],\n            ['file', 'NUL', 'w'],\n            ['file', 'NUL', 'w'],\n        ];\n    }\n\n    public function getFiles(): array\n    {\n        return $this->files;\n    }\n\n    public function readAndWrite(bool $blocking, bool $close = false): array\n    {\n        $this->unblock();\n        $w = $this->write();\n        $read = $r = $e = [];\n\n        if ($blocking) {\n            if ($w) {\n                @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);\n            } elseif ($this->fileHandles) {\n                usleep((int) (Process::TIMEOUT_PRECISION * 1E6));\n            }\n        }\n        foreach ($this->fileHandles as $type => $fileHandle) {\n            $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);\n\n            if (isset($data[0])) {\n                $this->readBytes[$type] += \\strlen($data);\n                $read[$type] = $data;\n            }\n            if ($close) {\n                ftruncate($fileHandle, 0);\n                fclose($fileHandle);\n                flock($this->lockHandles[$type], \\LOCK_UN);\n                fclose($this->lockHandles[$type]);\n                unset($this->fileHandles[$type], $this->lockHandles[$type]);\n            }\n        }\n\n        return $read;\n    }\n\n    public function haveReadSupport(): bool\n    {\n        return $this->haveReadSupport;\n    }\n\n    public function areOpen(): bool\n    {\n        return $this->pipes && $this->fileHandles;\n    }\n\n    public function close(): void\n    {\n        parent::close();\n        foreach ($this->fileHandles as $type => $handle) {\n            ftruncate($handle, 0);\n            fclose($handle);\n            flock($this->lockHandles[$type], \\LOCK_UN);\n            fclose($this->lockHandles[$type]);\n        }\n        $this->fileHandles = $this->lockHandles = [];\n    }\n}\n"
  },
  {
    "path": "Process.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process;\n\nuse Symfony\\Component\\Process\\Exception\\InvalidArgumentException;\nuse Symfony\\Component\\Process\\Exception\\LogicException;\nuse Symfony\\Component\\Process\\Exception\\ProcessFailedException;\nuse Symfony\\Component\\Process\\Exception\\ProcessSignaledException;\nuse Symfony\\Component\\Process\\Exception\\ProcessStartFailedException;\nuse Symfony\\Component\\Process\\Exception\\ProcessTimedOutException;\nuse Symfony\\Component\\Process\\Exception\\RuntimeException;\nuse Symfony\\Component\\Process\\Pipes\\UnixPipes;\nuse Symfony\\Component\\Process\\Pipes\\WindowsPipes;\n\n/**\n * Process is a thin wrapper around proc_* functions to easily\n * start independent PHP processes.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n * @author Romain Neutron <imprec@gmail.com>\n *\n * @implements \\IteratorAggregate<string, string>\n *\n * @psalm-type EnvArray = array<string, string|\\Stringable|false>\n */\nclass Process implements \\IteratorAggregate\n{\n    public const ERR = 'err';\n    public const OUT = 'out';\n\n    public const STATUS_READY = 'ready';\n    public const STATUS_STARTED = 'started';\n    public const STATUS_TERMINATED = 'terminated';\n\n    public const STDIN = 0;\n    public const STDOUT = 1;\n    public const STDERR = 2;\n\n    // Timeout Precision in seconds.\n    public const TIMEOUT_PRECISION = 0.2;\n\n    public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking\n    public const ITER_KEEP_OUTPUT = 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory\n    public const ITER_SKIP_OUT = 4;     // Use this flag to skip STDOUT while iterating\n    public const ITER_SKIP_ERR = 8;     // Use this flag to skip STDERR while iterating\n\n    /**\n     * Maximum number of UTF-16 code units allowed in the Windows environment block.\n     *\n     * The Win32 CreateProcess API encodes env vars as KEY=VALUE\\0 in UTF-16LE,\n     * terminated by an extra \\0. Exceeding this limit causes proc_open() to hang\n     * silently rather than returning false.\n     *\n     * @see https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa\n     */\n    private const WINDOWS_ENV_BLOCK_MAX_LENGTH = 32767;\n\n    /** @var \\Closure('out'|'err', string):bool|null */\n    private ?\\Closure $callback = null;\n    /** @var string[]|string */\n    private array|string $commandline;\n    private ?string $cwd;\n    /** @var EnvArray */\n    private array $env = [];\n    /** @var resource|string|\\Iterator|null */\n    private $input;\n    private ?float $starttime = null;\n    private ?float $lastOutputTime = null;\n    private ?float $timeout = null;\n    private ?float $idleTimeout = null;\n    private ?int $exitcode = null;\n    private array $fallbackStatus = [];\n    private array $processInformation;\n    private bool $outputDisabled = false;\n    /** @var resource */\n    private $stdout;\n    /** @var resource */\n    private $stderr;\n    /** @var resource|null */\n    private $process;\n    private string $status = self::STATUS_READY;\n    private int $incrementalOutputOffset = 0;\n    private int $incrementalErrorOutputOffset = 0;\n    private bool $tty = false;\n    private bool $pty;\n    private array $options = ['suppress_errors' => true, 'bypass_shell' => true];\n    private array $ignoredSignals = [];\n\n    private WindowsPipes|UnixPipes $processPipes;\n\n    private ?int $latestSignal = null;\n\n    private static ?bool $sigchild = null;\n    private static array $executables = [];\n\n    /**\n     * Exit codes translation table.\n     *\n     * User-defined errors must use exit codes in the 64-113 range.\n     */\n    public static array $exitCodes = [\n        0 => 'OK',\n        1 => 'General error',\n        2 => 'Misuse of shell builtins',\n\n        126 => 'Invoked command cannot execute',\n        127 => 'Command not found',\n        128 => 'Invalid exit argument',\n\n        // signals\n        129 => 'Hangup',\n        130 => 'Interrupt',\n        131 => 'Quit and dump core',\n        132 => 'Illegal instruction',\n        133 => 'Trace/breakpoint trap',\n        134 => 'Process aborted',\n        135 => 'Bus error: \"access to undefined portion of memory object\"',\n        136 => 'Floating point exception: \"erroneous arithmetic operation\"',\n        137 => 'Kill (terminate immediately)',\n        138 => 'User-defined 1',\n        139 => 'Segmentation violation',\n        140 => 'User-defined 2',\n        141 => 'Write to pipe with no one reading',\n        142 => 'Signal raised by alarm',\n        143 => 'Termination (request to terminate)',\n        // 144 - not defined\n        145 => 'Child process terminated, stopped (or continued*)',\n        146 => 'Continue if stopped',\n        147 => 'Stop executing temporarily',\n        148 => 'Terminal stop signal',\n        149 => 'Background process attempting to read from tty (\"in\")',\n        150 => 'Background process attempting to write to tty (\"out\")',\n        151 => 'Urgent data available on socket',\n        152 => 'CPU time limit exceeded',\n        153 => 'File size limit exceeded',\n        154 => 'Signal raised by timer counting virtual time: \"virtual timer expired\"',\n        155 => 'Profiling timer expired',\n        // 156 - not defined\n        157 => 'Pollable event',\n        // 158 - not defined\n        159 => 'Bad syscall',\n    ];\n\n    /**\n     * @param string[]       $command The command to run and its arguments listed as separate entries\n     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process\n     * @param EnvArray|null  $env     The environment variables or null to use the same environment as the current PHP process\n     * @param mixed          $input   The input as stream resource, scalar or \\Traversable, or null for no input\n     * @param int|float|null $timeout The timeout in seconds or null to disable\n     *\n     * @throws LogicException When proc_open is not installed\n     */\n    public function __construct(array $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60)\n    {\n        if (!\\function_exists('proc_open')) {\n            throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');\n        }\n\n        $this->commandline = $command;\n        $this->cwd = $cwd;\n\n        // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started\n        // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected\n        // @see : https://bugs.php.net/51800\n        // @see : https://bugs.php.net/50524\n        if (null === $this->cwd && (\\defined('ZEND_THREAD_SAFE') || '\\\\' === \\DIRECTORY_SEPARATOR)) {\n            $this->cwd = getcwd();\n        }\n        if (null !== $env) {\n            $this->setEnv($env);\n        }\n\n        $this->setInput($input);\n        $this->setTimeout($timeout);\n        $this->pty = false;\n    }\n\n    /**\n     * Creates a Process instance as a command-line to be run in a shell wrapper.\n     *\n     * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)\n     * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the\n     * shell wrapper and not to your commands.\n     *\n     * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.\n     * This will save escaping values, which is not portable nor secure anyway:\n     *\n     *   $process = Process::fromShellCommandline('my_command \"${:MY_VAR}\"');\n     *   $process->run(null, ['MY_VAR' => $theValue]);\n     *\n     * @param string         $command The command line to pass to the shell of the OS\n     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process\n     * @param EnvArray|null  $env     The environment variables or null to use the same environment as the current PHP process\n     * @param mixed          $input   The input as stream resource, scalar or \\Traversable, or null for no input\n     * @param int|float|null $timeout The timeout in seconds or null to disable\n     *\n     * @throws LogicException When proc_open is not installed\n     */\n    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static\n    {\n        $process = new static([], $cwd, $env, $input, $timeout);\n        $process->commandline = $command;\n\n        return $process;\n    }\n\n    public function __serialize(): array\n    {\n        throw new \\BadMethodCallException('Cannot serialize '.__CLASS__);\n    }\n\n    public function __unserialize(array $data): void\n    {\n        throw new \\BadMethodCallException('Cannot unserialize '.__CLASS__);\n    }\n\n    public function __destruct()\n    {\n        if ($this->options['create_new_console'] ?? false) {\n            $this->processPipes->close();\n        } else {\n            $this->stop(0);\n        }\n    }\n\n    public function __clone()\n    {\n        $this->resetProcessData();\n    }\n\n    /**\n     * Runs the process.\n     *\n     * The callback receives the type of output (out or err) and\n     * some bytes from the output in real-time. It allows to have feedback\n     * from the independent process during execution.\n     *\n     * The STDOUT and STDERR are also available after the process is finished\n     * via the getOutput() and getErrorOutput() methods.\n     *\n     * @param (callable('out'|'err', string):void)|null $callback A PHP callback to run whenever there is some\n     *                                                            output available on STDOUT or STDERR\n     * @param EnvArray                                  $env\n     *\n     * @return int The exit status code\n     *\n     * @throws ProcessStartFailedException When process can't be launched\n     * @throws RuntimeException            When process is already running\n     * @throws ProcessTimedOutException    When process timed out\n     * @throws ProcessSignaledException    When process stopped after receiving signal\n     * @throws LogicException              In case a callback is provided and output has been disabled\n     *\n     * @final\n     */\n    public function run(?callable $callback = null, array $env = []): int\n    {\n        $this->start($callback, $env);\n\n        return $this->wait();\n    }\n\n    /**\n     * Runs the process.\n     *\n     * This is identical to run() except that an exception is thrown if the process\n     * exits with a non-zero exit code.\n     *\n     * @param (callable('out'|'err', string):void)|null $callback A PHP callback to run whenever there is some\n     *                                                            output available on STDOUT or STDERR\n     * @param EnvArray                                  $env\n     *\n     * @return $this\n     *\n     * @throws ProcessFailedException   When process didn't terminate successfully\n     * @throws RuntimeException         When process can't be launched\n     * @throws RuntimeException         When process is already running\n     * @throws ProcessTimedOutException When process timed out\n     * @throws ProcessSignaledException When process stopped after receiving signal\n     * @throws LogicException           In case a callback is provided and output has been disabled\n     *\n     * @final\n     */\n    public function mustRun(?callable $callback = null, array $env = []): static\n    {\n        if (0 !== $this->run($callback, $env)) {\n            throw new ProcessFailedException($this);\n        }\n\n        return $this;\n    }\n\n    /**\n     * Starts the process and returns after writing the input to STDIN.\n     *\n     * This method blocks until all STDIN data is sent to the process then it\n     * returns while the process runs in the background.\n     *\n     * The termination of the process can be awaited with wait().\n     *\n     * The callback receives the type of output (out or err) and some bytes from\n     * the output in real-time while writing the standard input to the process.\n     * It allows to have feedback from the independent process during execution.\n     *\n     * @param (callable('out'|'err', string):void)|null $callback A PHP callback to run whenever there is some\n     *                                                            output available on STDOUT or STDERR\n     * @param EnvArray                                  $env\n     *\n     * @throws ProcessStartFailedException When process can't be launched\n     * @throws RuntimeException            When process is already running\n     * @throws LogicException              In case a callback is provided and output has been disabled\n     */\n    public function start(?callable $callback = null, array $env = []): void\n    {\n        if ($this->isRunning()) {\n            throw new RuntimeException('Process is already running.');\n        }\n\n        $this->resetProcessData();\n        $this->starttime = $this->lastOutputTime = microtime(true);\n        $this->callback = $this->buildCallback($callback);\n        $descriptors = $this->getDescriptors(null !== $callback);\n\n        if ($this->env) {\n            $env += '\\\\' === \\DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env;\n        }\n\n        $env += '\\\\' === \\DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();\n\n        if (\\is_array($commandline = $this->commandline)) {\n            $commandline = array_values(array_map(strval(...), $commandline));\n        } else {\n            $commandline = $this->replacePlaceholders($commandline, $env);\n        }\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $commandline = $this->prepareWindowsCommandLine($commandline, $env);\n        } elseif ($this->isSigchildEnabled()) {\n            // last exit code is output on the fourth pipe and caught to work around --enable-sigchild\n            $descriptors[3] = ['pipe', 'w'];\n\n            if (\\is_array($commandline)) {\n                // exec is mandatory to deal with sending a signal to the process\n                $commandline = 'exec '.$this->buildShellCommandline($commandline);\n            }\n\n            // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input\n            $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';\n            $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code';\n        }\n\n        $envPairs = [];\n        foreach ($env as $k => $v) {\n            if (false !== $v && !\\in_array($k = (string) $k, ['', 'argc', 'argv', 'ARGC', 'ARGV'], true) && !str_contains($k, '=') && !str_contains($k, \"\\0\")) {\n                $envPairs[] = $k.'='.$v;\n            }\n        }\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->validateWindowsEnvBlockSize($envPairs);\n        }\n\n        if (!is_dir($this->cwd)) {\n            throw new RuntimeException(\\sprintf('The provided cwd \"%s\" does not exist.', $this->cwd));\n        }\n\n        $lastError = null;\n        set_error_handler(static function ($type, $msg) use (&$lastError) {\n            $lastError = $msg;\n\n            return true;\n        });\n\n        $oldMask = [];\n\n        if ($this->ignoredSignals && \\function_exists('pcntl_sigprocmask')) {\n            // we block signals we want to ignore, as proc_open will use fork / posix_spawn which will copy the signal mask this allow to block\n            // signals in the child process\n            pcntl_sigprocmask(\\SIG_BLOCK, $this->ignoredSignals, $oldMask);\n        }\n\n        try {\n            $process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);\n\n            // Ensure array vs string commands behave the same\n            if (!$process && \\is_array($commandline)) {\n                $process = @proc_open('exec '.$this->buildShellCommandline($commandline), $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);\n            }\n        } finally {\n            if ($this->ignoredSignals && \\function_exists('pcntl_sigprocmask')) {\n                // we restore the signal mask here to avoid any side effects\n                pcntl_sigprocmask(\\SIG_SETMASK, $oldMask);\n            }\n\n            restore_error_handler();\n        }\n\n        if (!$process) {\n            throw new ProcessStartFailedException($this, $lastError);\n        }\n        $this->process = $process;\n        $this->status = self::STATUS_STARTED;\n\n        if (isset($descriptors[3])) {\n            $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);\n        }\n\n        if ($this->tty) {\n            return;\n        }\n\n        $this->updateStatus(false);\n        $this->checkTimeout();\n    }\n\n    /**\n     * Restarts the process.\n     *\n     * Be warned that the process is cloned before being started.\n     *\n     * @param (callable('out'|'err', string):void)|null $callback A PHP callback to run whenever there is some\n     *                                                            output available on STDOUT or STDERR\n     * @param EnvArray                                  $env\n     *\n     * @throws ProcessStartFailedException When process can't be launched\n     * @throws RuntimeException            When process is already running\n     *\n     * @see start()\n     *\n     * @final\n     */\n    public function restart(?callable $callback = null, array $env = []): static\n    {\n        if ($this->isRunning()) {\n            throw new RuntimeException('Process is already running.');\n        }\n\n        $process = clone $this;\n        $process->start($callback, $env);\n\n        return $process;\n    }\n\n    /**\n     * Waits for the process to terminate.\n     *\n     * The callback receives the type of output (out or err) and some bytes\n     * from the output in real-time while writing the standard input to the process.\n     * It allows to have feedback from the independent process during execution.\n     *\n     * @param (callable('out'|'err', string):void)|null $callback A PHP callback to run whenever there is some\n     *                                                            output available on STDOUT or STDERR\n     *\n     * @return int The exitcode of the process\n     *\n     * @throws ProcessTimedOutException When process timed out\n     * @throws ProcessSignaledException When process stopped after receiving signal\n     * @throws LogicException           When process is not yet started\n     */\n    public function wait(?callable $callback = null): int\n    {\n        $this->requireProcessIsStarted(__FUNCTION__);\n\n        $this->updateStatus(false);\n\n        if (null !== $callback) {\n            if (!$this->processPipes->haveReadSupport()) {\n                $this->stop(0);\n                throw new LogicException('Pass the callback to the \"Process::start\" method or call enableOutput to use a callback with \"Process::wait\".');\n            }\n            $this->callback = $this->buildCallback($callback);\n        }\n\n        do {\n            $this->checkTimeout();\n            $running = $this->isRunning() && ('\\\\' === \\DIRECTORY_SEPARATOR || $this->processPipes->areOpen());\n            $this->readPipes($running, '\\\\' !== \\DIRECTORY_SEPARATOR || !$running);\n        } while ($running);\n\n        while ($this->isRunning()) {\n            $this->checkTimeout();\n            usleep(1000);\n        }\n\n        if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {\n            throw new ProcessSignaledException($this);\n        }\n\n        return $this->exitcode;\n    }\n\n    /**\n     * Waits until the callback returns true.\n     *\n     * The callback receives the type of output (out or err) and some bytes\n     * from the output in real-time while writing the standard input to the process.\n     * It allows to have feedback from the independent process during execution.\n     *\n     * @param (callable('out'|'err', string):bool)|null $callback A PHP callback to run whenever there is some\n     *                                                            output available on STDOUT or STDERR\n     *\n     * @throws RuntimeException         When process timed out\n     * @throws LogicException           When process is not yet started\n     * @throws ProcessTimedOutException In case the timeout was reached\n     */\n    public function waitUntil(callable $callback): bool\n    {\n        $this->requireProcessIsStarted(__FUNCTION__);\n        $this->updateStatus(false);\n\n        if (!$this->processPipes->haveReadSupport()) {\n            $this->stop(0);\n            throw new LogicException('Pass the callback to the \"Process::start\" method or call enableOutput to use a callback with \"Process::waitUntil\".');\n        }\n        $callback = $this->buildCallback($callback);\n\n        $ready = false;\n        while (true) {\n            $this->checkTimeout();\n            $running = '\\\\' === \\DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();\n            $output = $this->processPipes->readAndWrite($running, '\\\\' !== \\DIRECTORY_SEPARATOR || !$running);\n\n            foreach ($output as $type => $data) {\n                if (3 !== $type) {\n                    $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;\n                } elseif (!isset($this->fallbackStatus['signaled'])) {\n                    $this->fallbackStatus['exitcode'] = (int) $data;\n                }\n            }\n            if ($ready) {\n                return true;\n            }\n            if (!$running) {\n                return false;\n            }\n\n            usleep(1000);\n        }\n    }\n\n    /**\n     * Returns the Pid (process identifier), if applicable.\n     *\n     * @return int|null The process id if running, null otherwise\n     */\n    public function getPid(): ?int\n    {\n        return $this->isRunning() ? $this->processInformation['pid'] : null;\n    }\n\n    /**\n     * Sends a POSIX signal to the process.\n     *\n     * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)\n     *\n     * @return $this\n     *\n     * @throws LogicException   In case the process is not running\n     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed\n     * @throws RuntimeException In case of failure\n     */\n    public function signal(int $signal): static\n    {\n        $this->doSignal($signal, true);\n\n        return $this;\n    }\n\n    /**\n     * Disables fetching output and error output from the underlying process.\n     *\n     * @return $this\n     *\n     * @throws RuntimeException In case the process is already running\n     * @throws LogicException   if an idle timeout is set\n     */\n    public function disableOutput(): static\n    {\n        if ($this->isRunning()) {\n            throw new RuntimeException('Disabling output while the process is running is not possible.');\n        }\n        if (null !== $this->idleTimeout) {\n            throw new LogicException('Output cannot be disabled while an idle timeout is set.');\n        }\n\n        $this->outputDisabled = true;\n\n        return $this;\n    }\n\n    /**\n     * Enables fetching output and error output from the underlying process.\n     *\n     * @return $this\n     *\n     * @throws RuntimeException In case the process is already running\n     */\n    public function enableOutput(): static\n    {\n        if ($this->isRunning()) {\n            throw new RuntimeException('Enabling output while the process is running is not possible.');\n        }\n\n        $this->outputDisabled = false;\n\n        return $this;\n    }\n\n    /**\n     * Returns true in case the output is disabled, false otherwise.\n     */\n    public function isOutputDisabled(): bool\n    {\n        return $this->outputDisabled;\n    }\n\n    /**\n     * Returns the current output of the process (STDOUT).\n     *\n     * @throws LogicException in case the output has been disabled\n     * @throws LogicException In case the process is not started\n     */\n    public function getOutput(): string\n    {\n        $this->readPipesForOutput(__FUNCTION__);\n\n        if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {\n            return '';\n        }\n\n        return $ret;\n    }\n\n    /**\n     * Returns the output incrementally.\n     *\n     * In comparison with the getOutput method which always return the whole\n     * output, this one returns the new output since the last call.\n     *\n     * @throws LogicException in case the output has been disabled\n     * @throws LogicException In case the process is not started\n     */\n    public function getIncrementalOutput(): string\n    {\n        $this->readPipesForOutput(__FUNCTION__);\n\n        $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);\n        $this->incrementalOutputOffset = ftell($this->stdout);\n\n        if (false === $latest) {\n            return '';\n        }\n\n        return $latest;\n    }\n\n    /**\n     * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).\n     *\n     * @param int $flags A bit field of Process::ITER_* flags\n     *\n     * @return \\Generator<string, string>\n     *\n     * @throws LogicException in case the output has been disabled\n     * @throws LogicException In case the process is not started\n     */\n    public function getIterator(int $flags = 0): \\Generator\n    {\n        $this->readPipesForOutput(__FUNCTION__, false);\n\n        $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);\n        $blocking = !(self::ITER_NON_BLOCKING & $flags);\n        $yieldOut = !(self::ITER_SKIP_OUT & $flags);\n        $yieldErr = !(self::ITER_SKIP_ERR & $flags);\n\n        while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {\n            if ($yieldOut) {\n                $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);\n\n                if (isset($out[0])) {\n                    if ($clearOutput) {\n                        $this->clearOutput();\n                    } else {\n                        $this->incrementalOutputOffset = ftell($this->stdout);\n                    }\n\n                    yield self::OUT => $out;\n                }\n            }\n\n            if ($yieldErr) {\n                $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);\n\n                if (isset($err[0])) {\n                    if ($clearOutput) {\n                        $this->clearErrorOutput();\n                    } else {\n                        $this->incrementalErrorOutputOffset = ftell($this->stderr);\n                    }\n\n                    yield self::ERR => $err;\n                }\n            }\n\n            if (!$blocking && !isset($out[0]) && !isset($err[0])) {\n                yield self::OUT => '';\n            }\n\n            $this->checkTimeout();\n            $this->readPipesForOutput(__FUNCTION__, $blocking);\n        }\n    }\n\n    /**\n     * Clears the process output.\n     *\n     * @return $this\n     */\n    public function clearOutput(): static\n    {\n        ftruncate($this->stdout, 0);\n        fseek($this->stdout, 0);\n        $this->incrementalOutputOffset = 0;\n\n        return $this;\n    }\n\n    /**\n     * Returns the current error output of the process (STDERR).\n     *\n     * @throws LogicException in case the output has been disabled\n     * @throws LogicException In case the process is not started\n     */\n    public function getErrorOutput(): string\n    {\n        $this->readPipesForOutput(__FUNCTION__);\n\n        if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {\n            return '';\n        }\n\n        return $ret;\n    }\n\n    /**\n     * Returns the errorOutput incrementally.\n     *\n     * In comparison with the getErrorOutput method which always return the\n     * whole error output, this one returns the new error output since the last\n     * call.\n     *\n     * @throws LogicException in case the output has been disabled\n     * @throws LogicException In case the process is not started\n     */\n    public function getIncrementalErrorOutput(): string\n    {\n        $this->readPipesForOutput(__FUNCTION__);\n\n        $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);\n        $this->incrementalErrorOutputOffset = ftell($this->stderr);\n\n        if (false === $latest) {\n            return '';\n        }\n\n        return $latest;\n    }\n\n    /**\n     * Clears the process output.\n     *\n     * @return $this\n     */\n    public function clearErrorOutput(): static\n    {\n        ftruncate($this->stderr, 0);\n        fseek($this->stderr, 0);\n        $this->incrementalErrorOutputOffset = 0;\n\n        return $this;\n    }\n\n    /**\n     * Returns the exit code returned by the process.\n     *\n     * @return int|null The exit status code, null if the Process is not terminated\n     */\n    public function getExitCode(): ?int\n    {\n        $this->updateStatus(false);\n\n        return $this->exitcode;\n    }\n\n    /**\n     * Returns a string representation for the exit code returned by the process.\n     *\n     * This method relies on the Unix exit code status standardization\n     * and might not be relevant for other operating systems.\n     *\n     * @return string|null A string representation for the exit status code, null if the Process is not terminated\n     *\n     * @see http://tldp.org/LDP/abs/html/exitcodes.html\n     * @see http://en.wikipedia.org/wiki/Unix_signal\n     */\n    public function getExitCodeText(): ?string\n    {\n        if (null === $exitcode = $this->getExitCode()) {\n            return null;\n        }\n\n        return self::$exitCodes[$exitcode] ?? 'Unknown error';\n    }\n\n    /**\n     * Checks if the process ended successfully.\n     */\n    public function isSuccessful(): bool\n    {\n        return 0 === $this->getExitCode();\n    }\n\n    /**\n     * Returns true if the child process has been terminated by an uncaught signal.\n     *\n     * It always returns false on Windows.\n     *\n     * @throws LogicException In case the process is not terminated\n     */\n    public function hasBeenSignaled(): bool\n    {\n        $this->requireProcessIsTerminated(__FUNCTION__);\n\n        return $this->processInformation['signaled'];\n    }\n\n    /**\n     * Returns the number of the signal that caused the child process to terminate its execution.\n     *\n     * It is only meaningful if hasBeenSignaled() returns true.\n     *\n     * @throws RuntimeException In case --enable-sigchild is activated\n     * @throws LogicException   In case the process is not terminated\n     */\n    public function getTermSignal(): int\n    {\n        $this->requireProcessIsTerminated(__FUNCTION__);\n\n        if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {\n            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.');\n        }\n\n        return $this->processInformation['termsig'];\n    }\n\n    /**\n     * Returns true if the child process has been stopped by a signal.\n     *\n     * It always returns false on Windows.\n     *\n     * @throws LogicException In case the process is not terminated\n     */\n    public function hasBeenStopped(): bool\n    {\n        $this->requireProcessIsTerminated(__FUNCTION__);\n\n        return $this->processInformation['stopped'];\n    }\n\n    /**\n     * Returns the number of the signal that caused the child process to stop its execution.\n     *\n     * It is only meaningful if hasBeenStopped() returns true.\n     *\n     * @throws LogicException In case the process is not terminated\n     */\n    public function getStopSignal(): int\n    {\n        $this->requireProcessIsTerminated(__FUNCTION__);\n\n        return $this->processInformation['stopsig'];\n    }\n\n    /**\n     * Checks if the process is currently running.\n     */\n    public function isRunning(): bool\n    {\n        if (self::STATUS_STARTED !== $this->status) {\n            return false;\n        }\n\n        $this->updateStatus(false);\n\n        return $this->processInformation['running'];\n    }\n\n    /**\n     * Checks if the process has been started with no regard to the current state.\n     */\n    public function isStarted(): bool\n    {\n        return self::STATUS_READY != $this->status;\n    }\n\n    /**\n     * Checks if the process is terminated.\n     */\n    public function isTerminated(): bool\n    {\n        $this->updateStatus(false);\n\n        return self::STATUS_TERMINATED == $this->status;\n    }\n\n    /**\n     * Gets the process status.\n     *\n     * The status is one of: ready, started, terminated.\n     */\n    public function getStatus(): string\n    {\n        $this->updateStatus(false);\n\n        return $this->status;\n    }\n\n    /**\n     * Stops the process.\n     *\n     * @param int|float $timeout The timeout in seconds\n     * @param int|null  $signal  A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)\n     *\n     * @return int|null The exit-code of the process or null if it's not running\n     */\n    public function stop(float $timeout = 10, ?int $signal = null): ?int\n    {\n        $timeoutMicro = microtime(true) + $timeout;\n        if ($this->isRunning()) {\n            // given SIGTERM may not be defined and that \"proc_terminate\" uses the constant value and not the constant itself, we use the same here\n            $this->doSignal(15, false);\n            do {\n                usleep(1000);\n            } while ($this->isRunning() && microtime(true) < $timeoutMicro);\n\n            if ($this->isRunning()) {\n                // Avoid exception here: process is supposed to be running, but it might have stopped just\n                // after this line. In any case, let's silently discard the error, we cannot do anything.\n                $this->doSignal($signal ?: 9, false);\n            }\n        }\n\n        if ($this->isRunning()) {\n            if (isset($this->fallbackStatus['pid'])) {\n                unset($this->fallbackStatus['pid']);\n\n                return $this->stop(0, $signal);\n            }\n            $this->close();\n        }\n\n        return $this->exitcode;\n    }\n\n    /**\n     * Adds a line to the STDOUT stream.\n     *\n     * @internal\n     */\n    public function addOutput(string $line): void\n    {\n        $this->lastOutputTime = microtime(true);\n\n        fseek($this->stdout, 0, \\SEEK_END);\n        fwrite($this->stdout, $line);\n        fseek($this->stdout, $this->incrementalOutputOffset);\n    }\n\n    /**\n     * Adds a line to the STDERR stream.\n     *\n     * @internal\n     */\n    public function addErrorOutput(string $line): void\n    {\n        $this->lastOutputTime = microtime(true);\n\n        fseek($this->stderr, 0, \\SEEK_END);\n        fwrite($this->stderr, $line);\n        fseek($this->stderr, $this->incrementalErrorOutputOffset);\n    }\n\n    /**\n     * Gets the last output time in seconds.\n     */\n    public function getLastOutputTime(): ?float\n    {\n        return $this->lastOutputTime;\n    }\n\n    /**\n     * Gets the command line to be executed.\n     */\n    public function getCommandLine(): string\n    {\n        return $this->buildShellCommandline($this->commandline);\n    }\n\n    /**\n     * Gets the process timeout in seconds (max. runtime).\n     */\n    public function getTimeout(): ?float\n    {\n        return $this->timeout;\n    }\n\n    /**\n     * Gets the process idle timeout in seconds (max. time since last output).\n     */\n    public function getIdleTimeout(): ?float\n    {\n        return $this->idleTimeout;\n    }\n\n    /**\n     * Sets the process timeout (max. runtime) in seconds.\n     *\n     * To disable the timeout, set this value to null.\n     *\n     * @return $this\n     *\n     * @throws InvalidArgumentException if the timeout is negative\n     */\n    public function setTimeout(?float $timeout): static\n    {\n        $this->timeout = $this->validateTimeout($timeout);\n\n        return $this;\n    }\n\n    /**\n     * Sets the process idle timeout (max. time since last output) in seconds.\n     *\n     * To disable the timeout, set this value to null.\n     *\n     * @return $this\n     *\n     * @throws LogicException           if the output is disabled\n     * @throws InvalidArgumentException if the timeout is negative\n     */\n    public function setIdleTimeout(?float $timeout): static\n    {\n        if (null !== $timeout && $this->outputDisabled) {\n            throw new LogicException('Idle timeout cannot be set while the output is disabled.');\n        }\n\n        $this->idleTimeout = $this->validateTimeout($timeout);\n\n        return $this;\n    }\n\n    /**\n     * Enables or disables the TTY mode.\n     *\n     * @return $this\n     *\n     * @throws RuntimeException In case the TTY mode is not supported\n     */\n    public function setTty(bool $tty): static\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR && $tty) {\n            throw new RuntimeException('TTY mode is not supported on Windows platform.');\n        }\n\n        if ($tty && !self::isTtySupported()) {\n            throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');\n        }\n\n        $this->tty = $tty;\n\n        return $this;\n    }\n\n    /**\n     * Checks if the TTY mode is enabled.\n     */\n    public function isTty(): bool\n    {\n        return $this->tty;\n    }\n\n    /**\n     * Sets PTY mode.\n     *\n     * @return $this\n     */\n    public function setPty(bool $bool): static\n    {\n        $this->pty = $bool;\n\n        return $this;\n    }\n\n    /**\n     * Returns PTY state.\n     */\n    public function isPty(): bool\n    {\n        return $this->pty;\n    }\n\n    /**\n     * Gets the working directory.\n     */\n    public function getWorkingDirectory(): ?string\n    {\n        if (null === $this->cwd) {\n            // getcwd() will return false if any one of the parent directories does not have\n            // the readable or search mode set, even if the current directory does\n            return getcwd() ?: null;\n        }\n\n        return $this->cwd;\n    }\n\n    /**\n     * Sets the current working directory.\n     *\n     * @return $this\n     */\n    public function setWorkingDirectory(string $cwd): static\n    {\n        $this->cwd = $cwd;\n\n        return $this;\n    }\n\n    /**\n     * Gets the environment variables.\n     *\n     * @psalm-return EnvArray\n     */\n    public function getEnv(): array\n    {\n        return $this->env;\n    }\n\n    /**\n     * Sets the environment variables.\n     *\n     * @param EnvArray $env The new environment variables\n     *\n     * @return $this\n     */\n    public function setEnv(array $env): static\n    {\n        $this->env = $env;\n\n        return $this;\n    }\n\n    /**\n     * Gets the Process input.\n     *\n     * @return resource|string|\\Iterator|null\n     */\n    public function getInput()\n    {\n        return $this->input;\n    }\n\n    /**\n     * Sets the input.\n     *\n     * This content will be passed to the underlying process standard input.\n     *\n     * @param string|resource|\\Traversable|self|null $input The content\n     *\n     * @return $this\n     *\n     * @throws LogicException In case the process is running\n     */\n    public function setInput(mixed $input): static\n    {\n        if ($this->isRunning()) {\n            throw new LogicException('Input cannot be set while the process is running.');\n        }\n\n        $this->input = ProcessUtils::validateInput(__METHOD__, $input);\n\n        return $this;\n    }\n\n    /**\n     * Performs a check between the timeout definition and the time the process started.\n     *\n     * In case you run a background process (with the start method), you should\n     * trigger this method regularly to ensure the process timeout\n     *\n     * @throws ProcessTimedOutException In case the timeout was reached\n     */\n    public function checkTimeout(): void\n    {\n        if (self::STATUS_STARTED !== $this->status) {\n            return;\n        }\n\n        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {\n            $this->stop(0);\n\n            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);\n        }\n\n        if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {\n            $this->stop(0);\n\n            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);\n        }\n    }\n\n    /**\n     * @throws LogicException in case process is not started\n     */\n    public function getStartTime(): float\n    {\n        if (!$this->isStarted()) {\n            throw new LogicException('Start time is only available after process start.');\n        }\n\n        return $this->starttime;\n    }\n\n    /**\n     * Defines options to pass to the underlying proc_open().\n     *\n     * @see https://php.net/proc_open for the options supported by PHP.\n     *\n     * Enabling the \"create_new_console\" option allows a subprocess to continue\n     * to run after the main process exited, on both Windows and *nix\n     */\n    public function setOptions(array $options): void\n    {\n        if ($this->isRunning()) {\n            throw new RuntimeException('Setting options while the process is running is not possible.');\n        }\n\n        $defaultOptions = $this->options;\n        $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];\n\n        foreach ($options as $key => $value) {\n            if (!\\in_array($key, $existingOptions)) {\n                $this->options = $defaultOptions;\n                throw new LogicException(\\sprintf('Invalid option \"%s\" passed to \"%s()\". Supported options are \"%s\".', $key, __METHOD__, implode('\", \"', $existingOptions)));\n            }\n            $this->options[$key] = $value;\n        }\n    }\n\n    /**\n     * Defines a list of posix signals that will not be propagated to the process.\n     *\n     * @param list<\\SIG*> $signals\n     */\n    public function setIgnoredSignals(array $signals): void\n    {\n        if ($this->isRunning()) {\n            throw new RuntimeException('Setting ignored signals while the process is running is not possible.');\n        }\n\n        $this->ignoredSignals = $signals;\n    }\n\n    /**\n     * Returns whether TTY is supported on the current operating system.\n     */\n    public static function isTtySupported(): bool\n    {\n        static $isTtySupported;\n\n        return $isTtySupported ??= ('/' === \\DIRECTORY_SEPARATOR && stream_isatty(\\STDOUT) && @is_writable('/dev/tty'));\n    }\n\n    /**\n     * Returns whether PTY is supported on the current operating system.\n     */\n    public static function isPtySupported(): bool\n    {\n        static $result;\n\n        if (null !== $result) {\n            return $result;\n        }\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            return $result = false;\n        }\n\n        return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);\n    }\n\n    /**\n     * Creates the descriptors needed by the proc_open.\n     */\n    private function getDescriptors(bool $hasCallback): array\n    {\n        if ($this->input instanceof \\Iterator) {\n            $this->input->rewind();\n        }\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $hasCallback);\n        } else {\n            $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $hasCallback);\n        }\n\n        return $this->processPipes->getDescriptors();\n    }\n\n    /**\n     * Builds up the callback used by wait().\n     *\n     * The callbacks adds all occurred output to the specific buffer and calls\n     * the user callback (if present) with the received output.\n     *\n     * @param (callable('out'|'err', string):void)|null $callback\n     *\n     * @return \\Closure('out'|'err', string):bool\n     */\n    protected function buildCallback(?callable $callback = null): \\Closure\n    {\n        if ($this->outputDisabled) {\n            return static fn ($type, $data): bool => null !== $callback && $callback($type, $data);\n        }\n\n        return function ($type, $data) use ($callback): bool {\n            match ($type) {\n                self::OUT => $this->addOutput($data),\n                self::ERR => $this->addErrorOutput($data),\n            };\n\n            return null !== $callback && $callback($type, $data);\n        };\n    }\n\n    /**\n     * Updates the status of the process, reads pipes.\n     *\n     * @param bool $blocking Whether to use a blocking read call\n     */\n    protected function updateStatus(bool $blocking): void\n    {\n        if (self::STATUS_STARTED !== $this->status) {\n            return;\n        }\n\n        if ($this->processInformation['running'] ?? true) {\n            $this->processInformation = proc_get_status($this->process);\n        }\n        $running = $this->processInformation['running'];\n\n        $this->readPipes($running && $blocking, '\\\\' !== \\DIRECTORY_SEPARATOR || !$running);\n\n        if ($this->fallbackStatus && $this->isSigchildEnabled()) {\n            $this->processInformation = $this->fallbackStatus + $this->processInformation;\n        }\n\n        if (!$running) {\n            $this->close();\n        }\n    }\n\n    /**\n     * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.\n     */\n    protected function isSigchildEnabled(): bool\n    {\n        if (null !== self::$sigchild) {\n            return self::$sigchild;\n        }\n\n        if (!\\function_exists('phpinfo')) {\n            return self::$sigchild = false;\n        }\n\n        ob_start();\n        phpinfo(\\INFO_GENERAL);\n\n        return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');\n    }\n\n    /**\n     * Reads pipes for the freshest output.\n     *\n     * @param string $caller   The name of the method that needs fresh outputs\n     * @param bool   $blocking Whether to use blocking calls or not\n     *\n     * @throws LogicException in case output has been disabled or process is not started\n     */\n    private function readPipesForOutput(string $caller, bool $blocking = false): void\n    {\n        if ($this->outputDisabled) {\n            throw new LogicException('Output has been disabled.');\n        }\n\n        $this->requireProcessIsStarted($caller);\n\n        $this->updateStatus($blocking);\n    }\n\n    /**\n     * Validates and returns the filtered timeout.\n     *\n     * @throws InvalidArgumentException if the given timeout is a negative number\n     */\n    private function validateTimeout(?float $timeout): ?float\n    {\n        $timeout = (float) $timeout;\n\n        if (0.0 === $timeout) {\n            $timeout = null;\n        } elseif ($timeout < 0) {\n            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');\n        }\n\n        return $timeout;\n    }\n\n    /**\n     * Reads pipes, executes callback.\n     *\n     * @param bool $blocking Whether to use blocking calls or not\n     * @param bool $close    Whether to close file handles or not\n     */\n    private function readPipes(bool $blocking, bool $close): void\n    {\n        $result = $this->processPipes->readAndWrite($blocking, $close);\n\n        $callback = $this->callback;\n        foreach ($result as $type => $data) {\n            if (3 !== $type) {\n                $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);\n            } elseif (!isset($this->fallbackStatus['signaled'])) {\n                $this->fallbackStatus['exitcode'] = (int) $data;\n            }\n        }\n    }\n\n    /**\n     * Closes process resource, closes file handles, sets the exitcode.\n     *\n     * @return int The exitcode\n     */\n    private function close(): int\n    {\n        $this->processPipes->close();\n        if ($this->process) {\n            proc_close($this->process);\n            $this->process = null;\n        }\n        $this->exitcode = $this->processInformation['exitcode'];\n        $this->status = self::STATUS_TERMINATED;\n\n        if (-1 === $this->exitcode) {\n            if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {\n                // if process has been signaled, no exitcode but a valid termsig, apply Unix convention\n                $this->exitcode = 128 + $this->processInformation['termsig'];\n            } elseif ($this->isSigchildEnabled()) {\n                $this->processInformation['signaled'] = true;\n                $this->processInformation['termsig'] = -1;\n            }\n        }\n\n        // Free memory from self-reference callback created by buildCallback\n        // Doing so in other contexts like __destruct or by garbage collector is ineffective\n        // Now pipes are closed, so the callback is no longer necessary\n        $this->callback = null;\n\n        return $this->exitcode;\n    }\n\n    /**\n     * Resets data related to the latest run of the process.\n     */\n    private function resetProcessData(): void\n    {\n        $this->starttime = null;\n        $this->callback = null;\n        $this->exitcode = null;\n        $this->fallbackStatus = [];\n        $this->processInformation = [];\n        $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');\n        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');\n        $this->process = null;\n        $this->latestSignal = null;\n        $this->status = self::STATUS_READY;\n        $this->incrementalOutputOffset = 0;\n        $this->incrementalErrorOutputOffset = 0;\n    }\n\n    /**\n     * Sends a POSIX signal to the process.\n     *\n     * @param int  $signal         A valid POSIX signal (see https://php.net/pcntl.constants)\n     * @param bool $throwException Whether to throw exception in case signal failed\n     *\n     * @throws LogicException   In case the process is not running\n     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed\n     * @throws RuntimeException In case of failure\n     */\n    private function doSignal(int $signal, bool $throwException): bool\n    {\n        // Signal seems to be send when sigchild is enable, this allow blocking the signal correctly in this case\n        if ($this->isSigchildEnabled() && \\in_array($signal, $this->ignoredSignals)) {\n            return false;\n        }\n\n        if (null === $pid = $this->getPid()) {\n            if ($throwException) {\n                throw new LogicException('Cannot send signal on a non running process.');\n            }\n\n            return false;\n        }\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            exec(\\sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);\n            if ($exitCode && $this->isRunning()) {\n                if ($throwException) {\n                    throw new RuntimeException(\\sprintf('Unable to kill the process (%s).', implode(' ', $output)));\n                }\n\n                return false;\n            }\n        } else {\n            if (!$this->isSigchildEnabled()) {\n                $ok = @proc_terminate($this->process, $signal);\n            } elseif (\\function_exists('posix_kill')) {\n                $ok = @posix_kill($pid, $signal);\n            } elseif ($ok = proc_open(\\sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {\n                $ok = false === fgets($pipes[2]);\n            }\n            if (!$ok) {\n                if ($throwException) {\n                    throw new RuntimeException(\\sprintf('Error while sending signal \"%s\".', $signal));\n                }\n\n                return false;\n            }\n        }\n\n        $this->latestSignal = $signal;\n        $this->fallbackStatus['signaled'] = true;\n        $this->fallbackStatus['exitcode'] = -1;\n        $this->fallbackStatus['termsig'] = $this->latestSignal;\n\n        return true;\n    }\n\n    /**\n     * @param string|list<string> $commandline\n     */\n    private function buildShellCommandline(string|array $commandline): string\n    {\n        if (\\is_string($commandline)) {\n            return $commandline;\n        }\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR && isset($commandline[0][0]) && \\strlen($commandline[0]) === strcspn($commandline[0], ':/\\\\')) {\n            // On Windows, we don't rely on the OS to find the executable if possible to avoid lookups\n            // in the current directory which could be untrusted. Instead we use the ExecutableFinder.\n            $commandline[0] = (self::$executables[$commandline[0]] ??= (new ExecutableFinder())->find($commandline[0])) ?? $commandline[0];\n        }\n\n        return implode(' ', array_map($this->escapeArgument(...), $commandline));\n    }\n\n    /**\n     * @param string|list<string> $cmd\n     * @param EnvArray            $env\n     *\n     * @param-out EnvArray $env\n     */\n    private function prepareWindowsCommandLine(string|array $cmd, array &$env): string\n    {\n        $cmd = $this->buildShellCommandline($cmd);\n        $uid = bin2hex(random_bytes(4));\n        $cmd = preg_replace_callback(\n            '/\"(?:(\n                [^\"%!^]*+\n                (?:\n                    (?: !LF! | \"(?:\\^[%!^])?+\" )\n                    [^\"%!^]*+\n                )++\n            ) | [^\"]*+ )\"/x',\n            static function ($m) use (&$env, $uid) {\n                static $varCount = 0;\n                static $varCache = [];\n                if (!isset($m[1])) {\n                    return $m[0];\n                }\n                if (isset($varCache[$m[0]])) {\n                    return $varCache[$m[0]];\n                }\n                if (str_contains($value = $m[1], \"\\0\")) {\n                    $value = str_replace(\"\\0\", '?', $value);\n                }\n                if (false === strpbrk($value, \"\\\"%!\\n\")) {\n                    return '\"'.$value.'\"';\n                }\n\n                $value = str_replace(['!LF!', '\"^!\"', '\"^%\"', '\"^^\"', '\"\"'], [\"\\n\", '!', '%', '^', '\"'], $value);\n                $value = '\"'.preg_replace('/(\\\\\\\\*)\"/', '$1$1\\\\\"', $value).'\"';\n                $var = $uid.++$varCount;\n\n                $env[$var] = $value;\n\n                return $varCache[$m[0]] = '!'.$var.'!';\n            },\n            $cmd\n        );\n\n        static $comSpec;\n\n        if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) {\n            // Escape according to CommandLineToArgvW rules\n            $comSpec = '\"'.preg_replace('{(\\\\\\\\*+)\"}', '$1$1\\\"', $comSpec).'\"';\n        }\n\n        $cmd = ($comSpec ?? 'cmd').' /V:ON /E:ON /D /C ('.str_replace(\"\\n\", ' ', $cmd).')';\n        foreach ($this->processPipes->getFiles() as $offset => $filename) {\n            $cmd .= ' '.$offset.'>\"'.$filename.'\"';\n        }\n\n        return $cmd;\n    }\n\n    /**\n     * Ensures the process is running or terminated, throws a LogicException if the process has a not started.\n     *\n     * @throws LogicException if the process has not run\n     */\n    private function requireProcessIsStarted(string $functionName): void\n    {\n        if (!$this->isStarted()) {\n            throw new LogicException(\\sprintf('Process must be started before calling \"%s()\".', $functionName));\n        }\n    }\n\n    /**\n     * Ensures the process is terminated, throws a LogicException if the process has a status different than \"terminated\".\n     *\n     * @throws LogicException if the process is not yet terminated\n     */\n    private function requireProcessIsTerminated(string $functionName): void\n    {\n        if (!$this->isTerminated()) {\n            throw new LogicException(\\sprintf('Process must be terminated before calling \"%s()\".', $functionName));\n        }\n    }\n\n    /**\n     * Escapes a string to be used as a shell argument.\n     */\n    private function escapeArgument(?string $argument): string\n    {\n        if ('' === $argument || null === $argument) {\n            return '\"\"';\n        }\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            return \"'\".str_replace(\"'\", \"'\\\\''\", $argument).\"'\";\n        }\n        if (str_contains($argument, \"\\0\")) {\n            $argument = str_replace(\"\\0\", '?', $argument);\n        }\n        if (!preg_match('/[()%!^\"<>&|\\s[\\]=;*?\\'$]/', $argument)) {\n            return $argument;\n        }\n        $argument = preg_replace('/(\\\\\\\\+)$/', '$1$1', $argument);\n\n        return '\"'.str_replace(['\"', '^', '%', '!', \"\\n\"], ['\"\"', '\"^^\"', '\"^%\"', '\"^!\"', '!LF!'], $argument).'\"';\n    }\n\n    /**\n     * @param EnvArray $env\n     */\n    private function replacePlaceholders(string $commandline, array $env): string\n    {\n        return preg_replace_callback('/\"\\$\\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\\}\"/', function ($matches) use ($commandline, $env) {\n            if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {\n                throw new InvalidArgumentException(\\sprintf('Command line is missing a value for parameter \"%s\": ', $matches[1]).$commandline);\n            }\n\n            return $this->escapeArgument($env[$matches[1]]);\n        }, $commandline);\n    }\n\n    /**\n     * @return EnvArray\n     */\n    private function getDefaultEnv(): array\n    {\n        $env = getenv();\n        $env = ('\\\\' === \\DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env;\n\n        return $_ENV + ('\\\\' === \\DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env);\n    }\n\n    private function validateWindowsEnvBlockSize(array $envPairs): void\n    {\n        $block = implode(\"\\0\", $envPairs).\"\\0\";\n        @preg_replace('/./u', '', $block, -1, $blockLength)\n            ?? preg_replace('/./', '', $block, -1, $blockLength);\n        $blockLength += 1 + preg_match_all('/[\\xF0-\\xF4][\\x80-\\xBF]{3}/', $block);\n\n        if ($blockLength > self::WINDOWS_ENV_BLOCK_MAX_LENGTH) {\n            throw new InvalidArgumentException(\\sprintf('The environment block size (%d) exceeds the Windows limit of %d UTF-16 code units.', $blockLength, self::WINDOWS_ENV_BLOCK_MAX_LENGTH));\n        }\n    }\n}\n"
  },
  {
    "path": "ProcessUtils.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process;\n\nuse Symfony\\Component\\Process\\Exception\\InvalidArgumentException;\n\n/**\n * ProcessUtils is a bunch of utility methods.\n *\n * This class contains static methods only and is not meant to be instantiated.\n *\n * @author Martin Hasoň <martin.hason@gmail.com>\n */\nclass ProcessUtils\n{\n    /**\n     * This class should not be instantiated.\n     */\n    private function __construct()\n    {\n    }\n\n    /**\n     * Validates and normalizes a Process input.\n     *\n     * @param string $caller The name of method call that validates the input\n     * @param mixed  $input  The input to validate\n     *\n     * @throws InvalidArgumentException In case the input is not valid\n     */\n    public static function validateInput(string $caller, mixed $input): mixed\n    {\n        if (null !== $input) {\n            if (\\is_resource($input)) {\n                return $input;\n            }\n            if (\\is_scalar($input)) {\n                return (string) $input;\n            }\n            if ($input instanceof Process) {\n                return $input->getIterator($input::ITER_SKIP_ERR);\n            }\n            if ($input instanceof \\Iterator) {\n                return $input;\n            }\n            if ($input instanceof \\Traversable) {\n                return new \\IteratorIterator($input);\n            }\n\n            throw new InvalidArgumentException(\\sprintf('\"%s\" only accepts strings, Traversable objects or stream resources.', $caller));\n        }\n\n        return $input;\n    }\n}\n"
  },
  {
    "path": "README.md",
    "content": "Process Component\n=================\n\nThe Process component executes commands in sub-processes.\n\nResources\n---------\n\n * [Documentation](https://symfony.com/doc/current/components/process.html)\n * [Contributing](https://symfony.com/doc/current/contributing/index.html)\n * [Report issues](https://github.com/symfony/symfony/issues) and\n   [send Pull Requests](https://github.com/symfony/symfony/pulls)\n   in the [main Symfony repository](https://github.com/symfony/symfony)\n"
  },
  {
    "path": "Tests/CreateNewConsoleTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * @author Andrei Olteanu <andrei@flashsoft.eu>\n */\nclass CreateNewConsoleTest extends TestCase\n{\n    public function testOptionCreateNewConsole()\n    {\n        $this->expectNotToPerformAssertions();\n        try {\n            $process = new Process(['php', __DIR__.'/ThreeSecondProcess.php']);\n            $process->setOptions(['create_new_console' => true]);\n            $process->disableOutput();\n            $process->start();\n        } catch (\\Exception $e) {\n            $this->fail($e);\n        }\n    }\n\n    public function testItReturnsFastAfterStart()\n    {\n        // The started process must run in background after the main has finished but that can't be tested with PHPUnit\n        $startTime = microtime(true);\n        $process = new Process(['php', __DIR__.'/ThreeSecondProcess.php']);\n        $process->setOptions(['create_new_console' => true]);\n        $process->disableOutput();\n        $process->start();\n        $this->assertLessThan(3000, $startTime - microtime(true));\n    }\n}\n"
  },
  {
    "path": "Tests/ErrorProcessInitiator.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse Symfony\\Component\\Process\\Exception\\ProcessTimedOutException;\nuse Symfony\\Component\\Process\\Process;\n\nrequire is_file(\\dirname(__DIR__).'/vendor/autoload.php') ? \\dirname(__DIR__).'/vendor/autoload.php' : \\dirname(__DIR__, 5).'/vendor/autoload.php';\n\n['e' => $php] = getopt('e:') + ['e' => 'php'];\n\ntry {\n    $process = new Process([$php, '-r', \"echo 'ready'; trigger_error('error', E_USER_ERROR);\"]);\n    $process->start();\n    $process->setTimeout(0.5);\n    while (!str_contains($process->getOutput(), 'ready')) {\n        usleep(1000);\n    }\n    $process->isRunning() && $process->signal(\\SIGSTOP);\n    $process->wait();\n\n    return $process->getExitCode();\n} catch (ProcessTimedOutException $t) {\n    echo $t->getMessage().\\PHP_EOL;\n\n    return 1;\n}\n"
  },
  {
    "path": "Tests/ExecutableFinderTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\Attributes\\RunInSeparateProcess;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\ExecutableFinder;\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * @author Chris Smith <chris@cs278.org>\n */\nclass ExecutableFinderTest extends TestCase\n{\n    protected function tearDown(): void\n    {\n        putenv('PATH='.($_SERVER['PATH'] ?? $_SERVER['Path']));\n    }\n\n    public function testFind()\n    {\n        if (\\ini_get('open_basedir')) {\n            $this->markTestSkipped('Cannot test when open_basedir is set');\n        }\n\n        putenv('PATH='.\\dirname(\\PHP_BINARY));\n\n        $finder = new ExecutableFinder();\n        $result = $finder->find($this->getPhpBinaryName());\n\n        $this->assertSamePath(\\PHP_BINARY, $result);\n    }\n\n    public function testFindWithDefault()\n    {\n        if (\\ini_get('open_basedir')) {\n            $this->markTestSkipped('Cannot test when open_basedir is set');\n        }\n\n        $expected = 'defaultValue';\n\n        putenv('PATH=');\n\n        $finder = new ExecutableFinder();\n        $result = $finder->find('foo', $expected);\n\n        $this->assertEquals($expected, $result);\n    }\n\n    public function testFindWithNullAsDefault()\n    {\n        if (\\ini_get('open_basedir')) {\n            $this->markTestSkipped('Cannot test when open_basedir is set');\n        }\n\n        putenv('PATH=');\n\n        $finder = new ExecutableFinder();\n\n        $result = $finder->find('foo');\n\n        $this->assertNull($result);\n    }\n\n    public function testFindWithExtraDirs()\n    {\n        if (\\ini_get('open_basedir')) {\n            $this->markTestSkipped('Cannot test when open_basedir is set');\n        }\n\n        putenv('PATH=');\n\n        $extraDirs = [\\dirname(\\PHP_BINARY)];\n\n        $finder = new ExecutableFinder();\n        $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);\n\n        $this->assertSamePath(\\PHP_BINARY, $result);\n    }\n\n    public function testFindWithoutSuffix()\n    {\n        $fixturesDir = __DIR__.\\DIRECTORY_SEPARATOR.'Fixtures';\n        $name = 'executable_without_suffix';\n\n        $finder = new ExecutableFinder();\n        $result = $finder->find($name, null, [$fixturesDir]);\n\n        $this->assertSamePath($fixturesDir.\\DIRECTORY_SEPARATOR.$name, $result);\n    }\n\n    public function testFindWithAddedSuffixes()\n    {\n        $fixturesDir = __DIR__.\\DIRECTORY_SEPARATOR.'Fixtures';\n        $name = 'executable_with_added_suffix';\n        $suffix = '.foo';\n\n        $finder = new ExecutableFinder();\n        $finder->addSuffix($suffix);\n\n        $result = $finder->find($name, null, [$fixturesDir]);\n\n        $this->assertSamePath($fixturesDir.\\DIRECTORY_SEPARATOR.$name.$suffix, $result);\n    }\n\n    #[RunInSeparateProcess]\n    public function testFindWithOpenBaseDir()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Cannot run test on windows');\n        }\n\n        if (\\ini_get('open_basedir')) {\n            $this->markTestSkipped('Cannot test when open_basedir is set');\n        }\n\n        $process = new Process([\\PHP_BINARY, '-d', 'open_basedir='.\\dirname(\\PHP_BINARY).\\PATH_SEPARATOR.'/', __DIR__.'/Fixtures/open_basedir.php']);\n        $process->run();\n        $result = $process->getOutput();\n\n        $this->assertSamePath(\\PHP_BINARY, $result);\n    }\n\n    #[RunInSeparateProcess]\n    public function testFindBatchExecutableOnWindows()\n    {\n        if (\\ini_get('open_basedir')) {\n            $this->markTestSkipped('Cannot test when open_basedir is set');\n        }\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Can be only tested on windows');\n        }\n\n        $tempDir = realpath(sys_get_temp_dir());\n        $target = str_replace('.tmp', '_tmp', tempnam($tempDir, 'example-windows-executable'));\n\n        try {\n            touch($target);\n            touch($target.'.BAT');\n\n            $this->assertFalse(is_executable($target));\n\n            putenv('PATH='.$tempDir);\n\n            $finder = new ExecutableFinder();\n            $result = $finder->find(basename($target), false);\n        } finally {\n            unlink($target);\n            unlink($target.'.BAT');\n        }\n\n        $this->assertSamePath($target.'.BAT', $result);\n    }\n\n    #[RunInSeparateProcess]\n    public function testEmptyDirInPath()\n    {\n        putenv(\\sprintf('PATH=%s%s', \\dirname(\\PHP_BINARY), \\PATH_SEPARATOR));\n\n        try {\n            touch('executable');\n            chmod('executable', 0o700);\n\n            $finder = new ExecutableFinder();\n            $result = $finder->find('executable');\n\n            $this->assertSame(\\sprintf('.%sexecutable', \\DIRECTORY_SEPARATOR), $result);\n        } finally {\n            unlink('executable');\n        }\n    }\n\n    public function testFindBuiltInCommandOnWindows()\n    {\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Can be only tested on windows');\n        }\n\n        $finder = new ExecutableFinder();\n        $this->assertSame('rmdir', strtolower($finder->find('RMDIR')));\n        $this->assertSame('cd', strtolower($finder->find('cd')));\n        $this->assertSame('move', strtolower($finder->find('MoVe')));\n    }\n\n    private function assertSamePath($expected, $tested)\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->assertEquals(strtolower($expected), strtolower($tested));\n        } else {\n            $this->assertEquals($expected, $tested);\n        }\n    }\n\n    private function getPhpBinaryName()\n    {\n        return basename(\\PHP_BINARY, '\\\\' === \\DIRECTORY_SEPARATOR ? '.exe' : '');\n    }\n}\n"
  },
  {
    "path": "Tests/Fixtures/executable_with_added_suffix.foo",
    "content": "See \\Symfony\\Component\\Process\\Tests\\ExecutableFinderTest::testFindWithAddedSuffixes()\n"
  },
  {
    "path": "Tests/Fixtures/executable_without_suffix",
    "content": "See \\Symfony\\Component\\Process\\Tests\\ExecutableFinderTest::testFindWithoutSuffix()\n"
  },
  {
    "path": "Tests/Fixtures/memory.php",
    "content": "<?php\n\necho ini_get('memory_limit');\n"
  },
  {
    "path": "Tests/Fixtures/open_basedir.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nrequire_once __DIR__.'/../../ExecutableFinder.php';\n\nuse Symfony\\Component\\Process\\ExecutableFinder;\n\nputenv('PATH='.dirname(PHP_BINARY));\n\nfunction getPhpBinaryName(): string\n{\n    return basename(PHP_BINARY, '\\\\' === DIRECTORY_SEPARATOR ? '.exe' : '');\n}\n\necho (new ExecutableFinder())->find(getPhpBinaryName());\n"
  },
  {
    "path": "Tests/KillableProcessWithOutput.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n$outputs = [\n    'First iteration output',\n    'Second iteration output',\n    'One more iteration output',\n    'This took more time',\n];\n\n$iterationTime = 10000;\n\nforeach ($outputs as $output) {\n    usleep($iterationTime);\n    $iterationTime *= 10;\n    echo $output.\"\\n\";\n}\n"
  },
  {
    "path": "Tests/Messenger/RunProcessMessageHandlerTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests\\Messenger;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\Exception\\RunProcessFailedException;\nuse Symfony\\Component\\Process\\Messenger\\RunProcessMessage;\nuse Symfony\\Component\\Process\\Messenger\\RunProcessMessageHandler;\n\nclass RunProcessMessageHandlerTest extends TestCase\n{\n    public function testRunSuccessfulProcess()\n    {\n        $context = (new RunProcessMessageHandler())(new RunProcessMessage(['ls'], cwd: __DIR__));\n\n        $this->assertSame(['ls'], $context->message->command);\n        $this->assertSame(0, $context->exitCode);\n        $this->assertStringContainsString(basename(__FILE__), $context->output);\n    }\n\n    public function testRunFailedProcess()\n    {\n        try {\n            (new RunProcessMessageHandler())(new RunProcessMessage(['invalid']));\n        } catch (RunProcessFailedException $e) {\n            $this->assertSame(['invalid'], $e->context->message->command);\n            $this->assertContains(\n                $e->context->exitCode,\n                [null, '\\\\' === \\DIRECTORY_SEPARATOR ? 1 : 127],\n                'Exit code should be 1 on Windows, 127 on other systems, or null',\n            );\n\n            return;\n        }\n\n        $this->fail('Exception not thrown');\n    }\n\n    public function testRunSuccessfulProcessFromShellCommandline()\n    {\n        $context = (new RunProcessMessageHandler())(RunProcessMessage::fromShellCommandline('ls | grep Test', cwd: __DIR__));\n\n        $this->assertSame('ls | grep Test', $context->message->commandLine);\n        $this->assertSame(0, $context->exitCode);\n        $this->assertStringContainsString(basename(__FILE__), $context->output);\n    }\n\n    public function testRunFailedProcessFromShellCommandline()\n    {\n        try {\n            (new RunProcessMessageHandler())(RunProcessMessage::fromShellCommandline('invalid'));\n            $this->fail('Exception not thrown');\n        } catch (RunProcessFailedException $e) {\n            $this->assertSame('invalid', $e->context->message->commandLine);\n            $this->assertContains(\n                $e->context->exitCode,\n                [null, '\\\\' === \\DIRECTORY_SEPARATOR ? 1 : 127],\n                'Exit code should be 1 on Windows, 127 on other systems, or null',\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/NonStopableProcess.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n/**\n * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.\n *\n * @args duration Run this script with a custom duration\n *\n * @example `php NonStopableProcess.php 42` will run the script for 42 seconds\n */\nfunction handleSignal($signal)\n{\n    $name = match ($signal) {\n        \\SIGTERM => 'SIGTERM',\n        \\SIGINT => 'SIGINT',\n        default => $signal.' (unknown)',\n    };\n\n    echo \"signal $name\\n\";\n}\n\npcntl_signal(\\SIGTERM, 'handleSignal');\npcntl_signal(\\SIGINT, 'handleSignal');\n\necho 'received ';\n\n$duration = isset($argv[1]) ? (int) $argv[1] : 3;\n$start = microtime(true);\n\nwhile ($duration > (microtime(true) - $start)) {\n    usleep(10000);\n    pcntl_signal_dispatch();\n}\n"
  },
  {
    "path": "Tests/OutputMemoryLimitProcess.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse Symfony\\Component\\Process\\PhpSubprocess;\nuse Symfony\\Component\\Process\\Process;\n\nrequire is_file(\\dirname(__DIR__).'/vendor/autoload.php') ? \\dirname(__DIR__).'/vendor/autoload.php' : \\dirname(__DIR__, 5).'/vendor/autoload.php';\n\n['e' => $php, 'p' => $process] = getopt('e:p:') + ['e' => 'php', 'p' => 'Process'];\n\nif ('Process' === $process) {\n    $p = new Process([$php, __DIR__.'/Fixtures/memory.php']);\n} else {\n    $p = new PhpSubprocess([__DIR__.'/Fixtures/memory.php'], null, null, 60, [$php]);\n}\n\n$p->mustRun();\necho $p->getOutput();\n"
  },
  {
    "path": "Tests/PhpExecutableFinderTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\PhpExecutableFinder;\n\n/**\n * @author Robert Schönthal <seroscho@googlemail.com>\n */\nclass PhpExecutableFinderTest extends TestCase\n{\n    /**\n     * tests find() with the constant PHP_BINARY.\n     */\n    public function testFind()\n    {\n        $f = new PhpExecutableFinder();\n\n        $current = \\PHP_BINARY;\n        $args = 'phpdbg' === \\PHP_SAPI ? ' -qrr' : '';\n\n        $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');\n        $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');\n    }\n\n    /**\n     * tests find() with the env var PHP_PATH.\n     */\n    public function testFindArguments()\n    {\n        $f = new PhpExecutableFinder();\n\n        if ('phpdbg' === \\PHP_SAPI) {\n            $this->assertEquals(['-qrr'], $f->findArguments(), '::findArguments() returns phpdbg arguments');\n        } else {\n            $this->assertEquals([], $f->findArguments(), '::findArguments() returns no arguments');\n        }\n    }\n\n    public function testNotExitsBinaryFile()\n    {\n        $f = new PhpExecutableFinder();\n\n        $originalPhpBinary = getenv('PHP_BINARY');\n\n        try {\n            putenv('PHP_BINARY=/usr/local/php/bin/php-invalid');\n\n            $this->assertFalse($f->find(), '::find() returns false because of not exist file');\n            $this->assertFalse($f->find(false), '::find(false) returns false because of not exist file');\n        } finally {\n            putenv('PHP_BINARY='.$originalPhpBinary);\n        }\n    }\n\n    public function testFindWithExecutableDirectory()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Directories are not executable on Windows');\n        }\n\n        $originalPhpBinary = getenv('PHP_BINARY');\n\n        try {\n            $executableDirectoryPath = sys_get_temp_dir().'/PhpExecutableFinderTest_testFindWithExecutableDirectory';\n            @mkdir($executableDirectoryPath);\n            $this->assertTrue(is_executable($executableDirectoryPath));\n            putenv('PHP_BINARY='.$executableDirectoryPath);\n\n            $this->assertFalse((new PhpExecutableFinder())->find());\n        } finally {\n            putenv('PHP_BINARY='.$originalPhpBinary);\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/PhpProcessTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\Exception\\LogicException;\nuse Symfony\\Component\\Process\\PhpExecutableFinder;\nuse Symfony\\Component\\Process\\PhpProcess;\n\nclass PhpProcessTest extends TestCase\n{\n    public function testNonBlockingWorks()\n    {\n        $expected = 'hello world!';\n        $process = new PhpProcess(<<<PHP\n            <?php echo '$expected';\n            PHP\n        );\n        $process->start();\n        $process->wait();\n        $this->assertEquals($expected, $process->getOutput());\n    }\n\n    public function testCommandLine()\n    {\n        $process = new PhpProcess(<<<'PHP'\n            <?php echo phpversion().PHP_SAPI;\n            PHP\n        );\n\n        $commandLine = $process->getCommandLine();\n\n        $process->start();\n        $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');\n\n        $process->wait();\n        $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');\n\n        $this->assertSame(\\PHP_VERSION.\\PHP_SAPI, $process->getOutput());\n    }\n\n    public function testPassingPhpExplicitly()\n    {\n        $finder = new PhpExecutableFinder();\n        $php = array_merge([$finder->find(false)], $finder->findArguments());\n\n        $expected = 'hello world!';\n        $script = <<<PHP\n            <?php echo '$expected';\n            PHP;\n        $process = new PhpProcess($script, null, null, 60, $php);\n        $process->run();\n        $this->assertEquals($expected, $process->getOutput());\n    }\n\n    public function testProcessCannotBeCreatedUsingFromShellCommandLine()\n    {\n        static::expectException(LogicException::class);\n        static::expectExceptionMessage('The \"Symfony\\Component\\Process\\PhpProcess::fromShellCommandline()\" method cannot be called when using \"Symfony\\Component\\Process\\PhpProcess\".');\n        PhpProcess::fromShellCommandline(<<<PHP\n            <?php echo 'Hello World!';\n            PHP\n        );\n    }\n}\n"
  },
  {
    "path": "Tests/PhpSubprocessTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\PhpExecutableFinder;\nuse Symfony\\Component\\Process\\Process;\n\nclass PhpSubprocessTest extends TestCase\n{\n    private static $phpBin;\n\n    public static function setUpBeforeClass(): void\n    {\n        $phpBin = new PhpExecutableFinder();\n        self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \\PHP_SAPI ? 'php' : $phpBin->find());\n    }\n\n    #[DataProvider('subprocessProvider')]\n    public function testSubprocess(string $processClass, string $memoryLimit, string $expectedMemoryLimit)\n    {\n        $process = new Process([self::$phpBin,\n            '-d',\n            'memory_limit='.$memoryLimit,\n            __DIR__.'/OutputMemoryLimitProcess.php',\n            '-e', self::$phpBin,\n            '-p', $processClass,\n        ]);\n\n        $process->mustRun();\n        $this->assertEquals($expectedMemoryLimit, trim($process->getOutput()));\n    }\n\n    public static function subprocessProvider(): \\Generator\n    {\n        yield 'Process does ignore dynamic memory_limit' => [\n            'Process',\n            self::getRandomMemoryLimit(),\n            self::getDefaultMemoryLimit(),\n        ];\n\n        yield 'PhpSubprocess does not ignore dynamic memory_limit' => [\n            'PhpSubprocess',\n            self::getRandomMemoryLimit(),\n            self::getRandomMemoryLimit(),\n        ];\n    }\n\n    private static function getDefaultMemoryLimit(): string\n    {\n        return trim(ini_get_all()['memory_limit']['global_value']);\n    }\n\n    private static function getRandomMemoryLimit(): string\n    {\n        $memoryLimit = 123; // Take something that's really unlikely to be configured on a user system.\n\n        while (($formatted = $memoryLimit.'M') === self::getDefaultMemoryLimit()) {\n            ++$memoryLimit;\n        }\n\n        return $formatted;\n    }\n}\n"
  },
  {
    "path": "Tests/PipeStdinInStdoutStdErrStreamSelect.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\ndefine('ERR_SELECT_FAILED', 1);\ndefine('ERR_TIMEOUT', 2);\ndefine('ERR_READ_FAILED', 3);\ndefine('ERR_WRITE_FAILED', 4);\n\n$read = [\\STDIN];\n$write = [\\STDOUT, \\STDERR];\n\nstream_set_blocking(\\STDIN, false);\nstream_set_blocking(\\STDOUT, false);\nstream_set_blocking(\\STDERR, false);\n\n$out = $err = '';\nwhile ($read || $write) {\n    $r = $read;\n    $w = $write;\n    $e = null;\n    $n = stream_select($r, $w, $e, 5);\n\n    if (false === $n) {\n        exit(ERR_SELECT_FAILED);\n    } elseif ($n < 1) {\n        exit(ERR_TIMEOUT);\n    }\n\n    if (in_array(\\STDOUT, $w) && '' !== $out) {\n        $written = fwrite(\\STDOUT, (string) $out, 32768);\n        if (false === $written) {\n            exit(ERR_WRITE_FAILED);\n        }\n        $out = (string) substr($out, $written);\n    }\n    if (null === $read && '' === $out) {\n        $write = array_diff($write, [\\STDOUT]);\n    }\n\n    if (in_array(\\STDERR, $w) && '' !== $err) {\n        $written = fwrite(\\STDERR, (string) $err, 32768);\n        if (false === $written) {\n            exit(ERR_WRITE_FAILED);\n        }\n        $err = (string) substr($err, $written);\n    }\n    if (null === $read && '' === $err) {\n        $write = array_diff($write, [\\STDERR]);\n    }\n\n    if ($r) {\n        $str = fread(\\STDIN, 32768);\n        if (false !== $str) {\n            $out .= $str;\n            $err .= $str;\n        }\n        if (false === $str || feof(\\STDIN)) {\n            $read = null;\n            if (!feof(\\STDIN)) {\n                exit(ERR_READ_FAILED);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ProcessFailedExceptionTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\Exception\\ProcessFailedException;\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * @author Sebastian Marek <proofek@gmail.com>\n */\nclass ProcessFailedExceptionTest extends TestCase\n{\n    /**\n     * tests ProcessFailedException throws exception if the process was successful.\n     */\n    public function testProcessFailedExceptionThrowsException()\n    {\n        $process = $this->getMockBuilder(Process::class)->onlyMethods(['isSuccessful'])->setConstructorArgs([['php']])->getMock();\n        $process->expects($this->once())\n            ->method('isSuccessful')\n            ->willReturn(true);\n\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->expectExceptionMessage('Expected a failed process, but the given process was successful.');\n\n        new ProcessFailedException($process);\n    }\n\n    /**\n     * tests ProcessFailedException uses information from process output\n     * to generate exception message.\n     */\n    public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()\n    {\n        $cmd = 'php';\n        $exitCode = 1;\n        $exitText = 'General error';\n        $output = 'Command output';\n        $errorOutput = 'FATAL: Unexpected error';\n        $workingDirectory = getcwd();\n\n        $process = $this->getMockBuilder(Process::class)->onlyMethods(['isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();\n        $process->expects($this->once())\n            ->method('isSuccessful')\n            ->willReturn(false);\n\n        $process->expects($this->once())\n            ->method('getOutput')\n            ->willReturn($output);\n\n        $process->expects($this->once())\n            ->method('getErrorOutput')\n            ->willReturn($errorOutput);\n\n        $process->expects($this->once())\n            ->method('getExitCode')\n            ->willReturn($exitCode);\n\n        $process->expects($this->once())\n            ->method('getExitCodeText')\n            ->willReturn($exitText);\n\n        $process->expects($this->once())\n            ->method('isOutputDisabled')\n            ->willReturn(false);\n\n        $process->expects($this->once())\n            ->method('getWorkingDirectory')\n            ->willReturn($workingDirectory);\n\n        $exception = new ProcessFailedException($process);\n\n        $this->assertStringMatchesFormat(\n            \"The command \\\"%s\\\" failed.\\n\\nExit Code: $exitCode($exitText)\\n\\nWorking directory: {$workingDirectory}\\n\\nOutput:\\n================\\n{$output}\\n\\nError Output:\\n================\\n{$errorOutput}\",\n            str_replace(\"'php'\", 'php', $exception->getMessage())\n        );\n    }\n\n    /**\n     * Tests that ProcessFailedException does not extract information from\n     * process output if it was previously disabled.\n     */\n    public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()\n    {\n        $cmd = 'php';\n        $exitCode = 1;\n        $exitText = 'General error';\n        $workingDirectory = getcwd();\n\n        $process = $this->getMockBuilder(Process::class)->onlyMethods(['isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();\n        $process->expects($this->once())\n            ->method('isSuccessful')\n            ->willReturn(false);\n\n        $process->expects($this->never())\n            ->method('getOutput');\n\n        $process->expects($this->never())\n            ->method('getErrorOutput');\n\n        $process->expects($this->once())\n            ->method('getExitCode')\n            ->willReturn($exitCode);\n\n        $process->expects($this->once())\n            ->method('getExitCodeText')\n            ->willReturn($exitText);\n\n        $process->expects($this->once())\n            ->method('isOutputDisabled')\n            ->willReturn(true);\n\n        $process->expects($this->once())\n            ->method('getWorkingDirectory')\n            ->willReturn($workingDirectory);\n\n        $exception = new ProcessFailedException($process);\n\n        $this->assertStringMatchesFormat(\n            \"The command \\\"%s\\\" failed.\\n\\nExit Code: $exitCode($exitText)\\n\\nWorking directory: {$workingDirectory}\",\n            $exception->getMessage()\n        );\n    }\n}\n"
  },
  {
    "path": "Tests/ProcessTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\Group;\nuse PHPUnit\\Framework\\Attributes\\RequiresPhpExtension;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\Exception\\InvalidArgumentException;\nuse Symfony\\Component\\Process\\Exception\\LogicException;\nuse Symfony\\Component\\Process\\Exception\\ProcessFailedException;\nuse Symfony\\Component\\Process\\Exception\\ProcessSignaledException;\nuse Symfony\\Component\\Process\\Exception\\ProcessTimedOutException;\nuse Symfony\\Component\\Process\\Exception\\RuntimeException;\nuse Symfony\\Component\\Process\\InputStream;\nuse Symfony\\Component\\Process\\PhpExecutableFinder;\nuse Symfony\\Component\\Process\\Pipes\\PipesInterface;\nuse Symfony\\Component\\Process\\Process;\n\n/**\n * @author Robert Schönthal <seroscho@googlemail.com>\n */\nclass ProcessTest extends TestCase\n{\n    private static string $phpBin;\n    private static ?Process $process = null;\n    private static bool $sigchild;\n\n    public static function setUpBeforeClass(): void\n    {\n        $phpBin = new PhpExecutableFinder();\n        self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \\PHP_SAPI ? 'php' : $phpBin->find());\n\n        ob_start();\n        phpinfo(\\INFO_GENERAL);\n        self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');\n    }\n\n    protected function tearDown(): void\n    {\n        if (self::$process) {\n            self::$process->stop(0);\n            self::$process = null;\n        }\n    }\n\n    public function testInvalidCwd()\n    {\n        $this->expectException(RuntimeException::class);\n        $this->expectExceptionMessageMatches('/The provided cwd \".*\" does not exist\\./');\n        try {\n            // Check that it works fine if the CWD exists\n            $cmd = new Process(['echo', 'test'], __DIR__);\n            $cmd->run();\n        } catch (\\Exception $e) {\n            $this->fail($e);\n        }\n\n        $cmd = new Process(['echo', 'test'], __DIR__.'/notfound/');\n        $cmd->run();\n    }\n\n    #[DataProvider('invalidProcessProvider')]\n    public function testInvalidCommand(Process $process)\n    {\n        // An invalid command should not fail during start\n        $this->assertSame('\\\\' === \\DIRECTORY_SEPARATOR ? 1 : 127, $process->run());\n    }\n\n    public static function invalidProcessProvider(): array\n    {\n        return [\n            [new Process(['invalid'])],\n            [Process::fromShellCommandline('invalid')],\n        ];\n    }\n\n    #[Group('transient-on-windows')]\n    public function testThatProcessDoesNotThrowWarningDuringRun()\n    {\n        @trigger_error('Test Error', \\E_USER_NOTICE);\n        $process = $this->getProcessForCode('sleep(3)');\n        $process->run();\n        $actualError = error_get_last();\n        $this->assertEquals('Test Error', $actualError['message']);\n        $this->assertEquals(\\E_USER_NOTICE, $actualError['type']);\n    }\n\n    public function testNegativeTimeoutFromConstructor()\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->getProcess('', null, null, null, -1);\n    }\n\n    public function testNegativeTimeoutFromSetter()\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $p = $this->getProcess('');\n        $p->setTimeout(-1);\n    }\n\n    public function testFloatAndNullTimeout()\n    {\n        $p = $this->getProcess('');\n\n        $p->setTimeout(10);\n        $this->assertSame(10.0, $p->getTimeout());\n\n        $p->setTimeout(null);\n        $this->assertNull($p->getTimeout());\n\n        $p->setTimeout(0.0);\n        $this->assertNull($p->getTimeout());\n    }\n\n    #[RequiresPhpExtension('pcntl')]\n    public function testStopWithTimeoutIsActuallyWorking()\n    {\n        $p = $this->getProcess([self::$phpBin, __DIR__.'/NonStopableProcess.php', 30]);\n        $p->start();\n\n        while ($p->isRunning() && !str_contains($p->getOutput(), 'received')) {\n            usleep(1000);\n        }\n\n        if (!$p->isRunning()) {\n            throw new \\LogicException('Process is not running: '.$p->getErrorOutput());\n        }\n\n        $start = microtime(true);\n        $p->stop(0.1);\n\n        $p->wait();\n\n        $this->assertLessThan(15, microtime(true) - $start);\n    }\n\n    #[Group('transient-on-windows')]\n    public function testWaitUntilSpecificOutput()\n    {\n        $p = $this->getProcess([self::$phpBin, __DIR__.'/KillableProcessWithOutput.php']);\n        $p->start();\n\n        $start = microtime(true);\n\n        $completeOutput = '';\n        $result = $p->waitUntil(static function ($type, $output) use (&$completeOutput) {\n            return str_contains($completeOutput .= $output, 'One more');\n        });\n        $this->assertTrue($result);\n        $this->assertLessThan(20, microtime(true) - $start);\n        $this->assertStringStartsWith(\"First iteration output\\nSecond iteration output\\nOne more\", $completeOutput);\n        $p->stop();\n    }\n\n    public function testWaitUntilCanReturnFalse()\n    {\n        $p = $this->getProcess('echo foo');\n        $p->start();\n        $this->assertFalse($p->waitUntil(static fn () => false));\n    }\n\n    public function testAllOutputIsActuallyReadOnTermination()\n    {\n        // this code will result in a maximum of 2 reads of 8192 bytes by calling\n        // start() and isRunning().  by the time getOutput() is called the process\n        // has terminated so the internal pipes array is already empty. normally\n        // the call to start() will not read any data as the process will not have\n        // generated output, but this is non-deterministic so we must count it as\n        // a possibility.  therefore we need 2 * PipesInterface::CHUNK_SIZE plus\n        // another byte which will never be read.\n        $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;\n\n        $code = \\sprintf('echo str_repeat(\\'*\\', %d);', $expectedOutputSize);\n        $p = $this->getProcessForCode($code);\n\n        $p->start();\n\n        // Don't call Process::run nor Process::wait to avoid any read of pipes\n        $h = new \\ReflectionProperty($p, 'process');\n        $h = $h->getValue($p);\n        $s = @proc_get_status($h);\n\n        while (!empty($s['running'])) {\n            usleep(1000);\n            $s = proc_get_status($h);\n        }\n\n        $o = $p->getOutput();\n\n        $this->assertEquals($expectedOutputSize, \\strlen($o));\n    }\n\n    public function testCallbacksAreExecutedWithStart()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->start(static function ($type, $buffer) use (&$data) {\n            $data .= $buffer;\n        });\n\n        $process->wait();\n\n        $this->assertSame('foo'.\\PHP_EOL, $data);\n    }\n\n    public function testReadSupportIsDisabledWithoutCallback()\n    {\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage('Pass the callback to the \"Process::start\" method or call enableOutput to use a callback with \"Process::wait\".');\n\n        $process = $this->getProcess('echo foo');\n        // disabling output + not passing a callback to start() => read support disabled\n        $process->disableOutput();\n        $process->start();\n        $process->wait(static function ($type, $buffer) use (&$data) {\n            $data .= $buffer;\n        });\n    }\n\n    /**\n     * tests results from sub processes.\n     */\n    #[DataProvider('responsesCodeProvider')]\n    public function testProcessResponses($expected, $getter, $code)\n    {\n        $p = $this->getProcessForCode($code);\n        $p->run();\n\n        $this->assertSame($expected, $p->$getter());\n    }\n\n    /**\n     * tests results from sub processes.\n     */\n    #[DataProvider('pipesCodeProvider')]\n    public function testProcessPipes($code, $size)\n    {\n        $expected = str_repeat(str_repeat('*', 1024), $size).'!';\n        $expectedLength = (1024 * $size) + 1;\n\n        $p = $this->getProcessForCode($code);\n        $p->setInput($expected);\n        $p->run();\n\n        $this->assertEquals($expectedLength, \\strlen($p->getOutput()));\n        $this->assertEquals($expectedLength, \\strlen($p->getErrorOutput()));\n    }\n\n    #[DataProvider('pipesCodeProvider')]\n    public function testSetStreamAsInput($code, $size)\n    {\n        $expected = str_repeat(str_repeat('*', 1024), $size).'!';\n        $expectedLength = (1024 * $size) + 1;\n\n        $stream = fopen('php://temporary', 'w+');\n        fwrite($stream, $expected);\n        rewind($stream);\n\n        $p = $this->getProcessForCode($code);\n        $p->setInput($stream);\n        $p->run();\n\n        fclose($stream);\n\n        $this->assertEquals($expectedLength, \\strlen($p->getOutput()));\n        $this->assertEquals($expectedLength, \\strlen($p->getErrorOutput()));\n    }\n\n    public function testLiveStreamAsInput()\n    {\n        $stream = fopen('php://memory', 'r+');\n        fwrite($stream, 'hello');\n        rewind($stream);\n\n        $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');\n        $p->setInput($stream);\n        $p->start(static function ($type, $data) use ($stream) {\n            if ('hello' === $data) {\n                fclose($stream);\n            }\n        });\n        $p->wait();\n\n        $this->assertSame('hello', $p->getOutput());\n    }\n\n    public function testSetInputWhileRunningThrowsAnException()\n    {\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage('Input cannot be set while the process is running.');\n        $process = $this->getProcessForCode('sleep(30);');\n        $process->start();\n        try {\n            $process->setInput('foobar');\n            $process->stop();\n            $this->fail('A LogicException should have been raised.');\n        } catch (LogicException $e) {\n        }\n        $process->stop();\n\n        throw $e;\n    }\n\n    #[DataProvider('provideInvalidInputValues')]\n    public function testInvalidInput(array|object $value)\n    {\n        $process = $this->getProcess('foo');\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('\"Symfony\\Component\\Process\\Process::setInput\" only accepts strings, Traversable objects or stream resources.');\n\n        $process->setInput($value);\n    }\n\n    public static function provideInvalidInputValues()\n    {\n        return [\n            [[]],\n            [new NonStringifiable()],\n        ];\n    }\n\n    #[DataProvider('provideInputValues')]\n    public function testValidInput(?string $expected, float|string|null $value)\n    {\n        $process = $this->getProcess('foo');\n        $process->setInput($value);\n        $this->assertSame($expected, $process->getInput());\n    }\n\n    public static function provideInputValues()\n    {\n        return [\n            [null, null],\n            ['24.5', 24.5],\n            ['input data', 'input data'],\n        ];\n    }\n\n    public static function chainedCommandsOutputProvider()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            return [\n                [\"2 \\r\\n2\\r\\n\", '&&', '2'],\n            ];\n        }\n\n        return [\n            [\"1\\n1\\n\", ';', '1'],\n            [\"2\\n2\\n\", '&&', '2'],\n        ];\n    }\n\n    #[DataProvider('chainedCommandsOutputProvider')]\n    public function testChainedCommandsOutput($expected, $operator, $input)\n    {\n        $process = $this->getProcess(\\sprintf('echo %s %s echo %s', $input, $operator, $input));\n        $process->run();\n        $this->assertEquals($expected, $process->getOutput());\n    }\n\n    public function testCallbackIsExecutedForOutput()\n    {\n        $p = $this->getProcessForCode('echo \\'foo\\';');\n\n        $called = false;\n        $p->run(static function ($type, $buffer) use (&$called) {\n            $called = 'foo' === $buffer;\n        });\n\n        $this->assertTrue($called, 'The callback should be executed with the output');\n    }\n\n    public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()\n    {\n        $p = $this->getProcessForCode('echo \\'foo\\';');\n        $p->disableOutput();\n\n        $called = false;\n        $p->run(static function ($type, $buffer) use (&$called) {\n            $called = 'foo' === $buffer;\n        });\n\n        $this->assertTrue($called, 'The callback should be executed with the output');\n    }\n\n    public function testGetErrorOutput()\n    {\n        $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\\'php://stderr\\', \\'ERROR\\'); $n++; }');\n\n        $p->run();\n        $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));\n    }\n\n    public function testFlushErrorOutput()\n    {\n        $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\\'php://stderr\\', \\'ERROR\\'); $n++; }');\n\n        $p->run();\n        $p->clearErrorOutput();\n        $this->assertSame('', $p->getErrorOutput());\n    }\n\n    #[DataProvider('provideIncrementalOutput')]\n    public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)\n    {\n        $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);\n\n        $p = $this->getProcessForCode('file_put_contents($s = \\''.$uri.'\\', \\'foo\\'); flock(fopen('.var_export($lock, true).', \\'r\\'), LOCK_EX); file_put_contents($s, \\'bar\\');');\n\n        $h = fopen($lock, 'w');\n        flock($h, \\LOCK_EX);\n\n        $p->start();\n\n        foreach (['foo', 'bar'] as $s) {\n            while (!str_contains($p->$getOutput(), $s)) {\n                usleep(1000);\n            }\n\n            $this->assertSame($s, $p->$getIncrementalOutput());\n            $this->assertSame('', $p->$getIncrementalOutput());\n\n            flock($h, \\LOCK_UN);\n        }\n\n        fclose($h);\n    }\n\n    public static function provideIncrementalOutput()\n    {\n        return [\n            ['getOutput', 'getIncrementalOutput', 'php://stdout'],\n            ['getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'],\n        ];\n    }\n\n    public function testGetOutput()\n    {\n        $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \\' foo \\'; $n++; }');\n\n        $p->run();\n        $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));\n    }\n\n    public function testFlushOutput()\n    {\n        $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \\' foo \\';$n++;}');\n\n        $p->run();\n        $p->clearOutput();\n        $this->assertSame('', $p->getOutput());\n    }\n\n    public function testZeroAsOutput()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line\n            $p = $this->getProcess('echo | set /p dummyName=0');\n        } else {\n            $p = $this->getProcess('printf 0');\n        }\n\n        $p->run();\n        $this->assertSame('0', $p->getOutput());\n    }\n\n    public function testExitCodeCommandFailed()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows does not support POSIX exit code');\n        }\n\n        // such command run in bash return an exitcode 127\n        $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');\n        $process->run();\n\n        $this->assertGreaterThan(0, $process->getExitCode());\n    }\n\n    public function testTTYCommand()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows does not have /dev/tty support');\n        }\n\n        if (!Process::isTtySupported()) {\n            $this->markTestSkipped('There is no TTY support');\n        }\n\n        $process = $this->getProcess('echo \"foo\" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());\n        $process->setTty(true);\n        $process->start();\n        $this->assertTrue($process->isRunning());\n        $process->wait();\n\n        $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());\n    }\n\n    public function testTTYCommandExitCode()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows does have /dev/tty support');\n        }\n\n        if (!Process::isTtySupported()) {\n            $this->markTestSkipped('There is no TTY support');\n        }\n\n        $process = $this->getProcess('echo \"foo\" >> /dev/null');\n        $process->setTty(true);\n        $process->run();\n\n        $this->assertTrue($process->isSuccessful());\n    }\n\n    public function testTTYInWindowsEnvironment()\n    {\n        $this->expectException(RuntimeException::class);\n        $this->expectExceptionMessage('TTY mode is not supported on Windows platform.');\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('This test is for Windows platform only');\n        }\n\n        $process = $this->getProcess('echo \"foo\" >> /dev/null');\n        $process->setTty(false);\n        $process->setTty(true);\n    }\n\n    public function testExitCodeTextIsNullWhenExitCodeIsNull()\n    {\n        $process = $this->getProcess('');\n        $this->assertNull($process->getExitCodeText());\n    }\n\n    public function testStderrNotMixedWithStdout()\n    {\n        if (!Process::isPtySupported()) {\n            $this->markTestSkipped('PTY is not supported on this operating system.');\n        }\n\n        $process = $this->getProcess('echo \"foo\" && echo \"bar\" >&2');\n        $process->setPty(true);\n        $process->run();\n\n        $this->assertSame(\"foo\\r\\n\", $process->getOutput());\n        $this->assertSame(\"bar\\n\", $process->getErrorOutput());\n    }\n\n    public function testPTYCommand()\n    {\n        if (!Process::isPtySupported()) {\n            $this->markTestSkipped('PTY is not supported on this operating system.');\n        }\n\n        $process = $this->getProcess('echo \"foo\"');\n        $process->setPty(true);\n        $process->run();\n\n        $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());\n        $this->assertEquals(\"foo\\r\\n\", $process->getOutput());\n    }\n\n    public function testMustRun()\n    {\n        $process = $this->getProcess('echo foo');\n\n        $this->assertSame($process, $process->mustRun());\n        $this->assertEquals('foo'.\\PHP_EOL, $process->getOutput());\n    }\n\n    public function testSuccessfulMustRunHasCorrectExitCode()\n    {\n        $process = $this->getProcess('echo foo')->mustRun();\n        $this->assertEquals(0, $process->getExitCode());\n    }\n\n    public function testMustRunThrowsException()\n    {\n        $process = $this->getProcess('exit 1');\n\n        $this->expectException(ProcessFailedException::class);\n\n        $process->mustRun();\n    }\n\n    public function testExitCodeText()\n    {\n        $process = $this->getProcess('');\n        $r = new \\ReflectionObject($process);\n        $p = $r->getProperty('exitcode');\n\n        $p->setValue($process, 2);\n        $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());\n    }\n\n    public function testStartIsNonBlocking()\n    {\n        $process = $this->getProcessForCode('usleep(500000);');\n        $start = microtime(true);\n        $process->start();\n        $end = microtime(true);\n        $this->assertLessThan(0.4, $end - $start);\n        $process->stop();\n    }\n\n    public function testUpdateStatus()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->run();\n        $this->assertGreaterThan(0, \\strlen($process->getOutput()));\n    }\n\n    public function testGetExitCodeIsNullOnStart()\n    {\n        $process = $this->getProcessForCode('usleep(100000);');\n        $this->assertNull($process->getExitCode());\n        $process->start();\n        $this->assertNull($process->getExitCode());\n        $process->wait();\n        $this->assertEquals(0, $process->getExitCode());\n    }\n\n    public function testGetExitCodeIsNullOnWhenStartingAgain()\n    {\n        $process = $this->getProcessForCode('usleep(100000);');\n        $process->run();\n        $this->assertEquals(0, $process->getExitCode());\n        $process->start();\n        $this->assertNull($process->getExitCode());\n        $process->wait();\n        $this->assertEquals(0, $process->getExitCode());\n    }\n\n    public function testGetExitCode()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->run();\n        $this->assertSame(0, $process->getExitCode());\n    }\n\n    public function testStatus()\n    {\n        $process = $this->getProcessForCode('usleep(100000);');\n        $this->assertFalse($process->isRunning());\n        $this->assertFalse($process->isStarted());\n        $this->assertFalse($process->isTerminated());\n        $this->assertSame(Process::STATUS_READY, $process->getStatus());\n        $process->start();\n        $this->assertTrue($process->isRunning());\n        $this->assertTrue($process->isStarted());\n        $this->assertFalse($process->isTerminated());\n        $this->assertSame(Process::STATUS_STARTED, $process->getStatus());\n        $process->wait();\n        $this->assertFalse($process->isRunning());\n        $this->assertTrue($process->isStarted());\n        $this->assertTrue($process->isTerminated());\n        $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());\n    }\n\n    public function testStop()\n    {\n        $process = $this->getProcessForCode('sleep(31);');\n        $process->start();\n        $this->assertTrue($process->isRunning());\n        $process->stop();\n        $this->assertFalse($process->isRunning());\n    }\n\n    public function testStopDoesNotThrowAfterBrokenPipe()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Broken pipe notices are specific to Unix-like platforms.');\n        }\n\n        $process = $this->getProcess([self::$phpBin, '-r', 'exit(0);'], null, null, str_repeat('*', PipesInterface::CHUNK_SIZE * 32));\n\n        $process->run();\n        $this->assertSame(0, $process->getExitCode());\n\n        $process->stop(0);\n\n        // __destruct() should not trigger a broken pipe notice\n        self::$process = $process = null;\n        gc_collect_cycles();\n    }\n\n    public function testIsSuccessful()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->run();\n        $this->assertTrue($process->isSuccessful());\n    }\n\n    public function testIsSuccessfulOnlyAfterTerminated()\n    {\n        $process = $this->getProcessForCode('usleep(100000);');\n        $process->start();\n\n        $this->assertFalse($process->isSuccessful());\n\n        $process->wait();\n\n        $this->assertTrue($process->isSuccessful());\n    }\n\n    public function testIsNotSuccessful()\n    {\n        $process = $this->getProcessForCode('throw new \\Exception(\\'BOUM\\');');\n        $process->run();\n        $this->assertFalse($process->isSuccessful());\n    }\n\n    public function testProcessIsNotSignaled()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows does not support POSIX signals');\n        }\n\n        $process = $this->getProcess('echo foo');\n        $process->run();\n        $this->assertFalse($process->hasBeenSignaled());\n    }\n\n    public function testProcessWithoutTermSignal()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows does not support POSIX signals');\n        }\n\n        $process = $this->getProcess('echo foo');\n        $process->run();\n        $this->assertEquals(0, $process->getTermSignal());\n    }\n\n    public function testProcessIsSignaledIfStopped()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows does not support POSIX signals');\n        }\n\n        $process = $this->getProcessForCode('sleep(32);');\n        $process->start();\n        $process->stop();\n        $this->assertTrue($process->hasBeenSignaled());\n        $this->assertEquals(15, $process->getTermSignal()); // SIGTERM\n    }\n\n    public function testProcessThrowsExceptionWhenExternallySignaled()\n    {\n        $this->expectException(ProcessSignaledException::class);\n        $this->expectExceptionMessage('The process has been signaled with signal \"9\".');\n        if (!\\function_exists('posix_kill')) {\n            $this->markTestSkipped('Function posix_kill is required.');\n        }\n\n        if (self::$sigchild) {\n            $this->markTestSkipped('PHP is compiled with --enable-sigchild.');\n        }\n\n        $process = $this->getProcessForCode('sleep(32.1);');\n        $process->start();\n        posix_kill($process->getPid(), 9); // SIGKILL\n\n        $process->wait();\n    }\n\n    public function testRestart()\n    {\n        $process1 = $this->getProcessForCode('echo getmypid();');\n        $process1->run();\n        $process2 = $process1->restart();\n\n        $process2->wait(); // wait for output\n\n        // Ensure that both processed finished and the output is numeric\n        $this->assertFalse($process1->isRunning());\n        $this->assertFalse($process2->isRunning());\n        $this->assertIsNumeric($process1->getOutput());\n        $this->assertIsNumeric($process2->getOutput());\n\n        // Ensure that restart returned a new process by check that the output is different\n        $this->assertNotEquals($process1->getOutput(), $process2->getOutput());\n    }\n\n    public function testRunProcessWithTimeout()\n    {\n        $this->expectException(ProcessTimedOutException::class);\n        $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');\n        $process = $this->getProcessForCode('sleep(30);');\n        $process->setTimeout(0.1);\n        $start = microtime(true);\n        try {\n            $process->run();\n            $this->fail('A RuntimeException should have been raised');\n        } catch (RuntimeException $e) {\n        }\n\n        $this->assertLessThan(15, microtime(true) - $start);\n\n        throw $e;\n    }\n\n    public function testIterateOverProcessWithTimeout()\n    {\n        $this->expectException(ProcessTimedOutException::class);\n        $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');\n        $process = $this->getProcessForCode('sleep(30);');\n        $process->setTimeout(0.1);\n        $start = microtime(true);\n        try {\n            $process->start();\n            foreach ($process as $buffer) {\n            }\n            $this->fail('A RuntimeException should have been raised');\n        } catch (RuntimeException $e) {\n        }\n\n        $this->assertLessThan(15, microtime(true) - $start);\n\n        throw $e;\n    }\n\n    public function testCheckTimeoutOnNonStartedProcess()\n    {\n        $process = $this->getProcess('echo foo');\n        $this->assertNull($process->checkTimeout());\n    }\n\n    public function testCheckTimeoutOnTerminatedProcess()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->run();\n        $this->assertNull($process->checkTimeout());\n    }\n\n    public function testCheckTimeoutOnStartedProcess()\n    {\n        $this->expectException(ProcessTimedOutException::class);\n        $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');\n        $process = $this->getProcessForCode('sleep(33);');\n        $process->setTimeout(0.1);\n\n        $process->start();\n        $start = microtime(true);\n\n        try {\n            while ($process->isRunning()) {\n                $process->checkTimeout();\n                usleep(100000);\n            }\n            $this->fail('A ProcessTimedOutException should have been raised');\n        } catch (ProcessTimedOutException $e) {\n        }\n\n        $this->assertLessThan(15, microtime(true) - $start);\n\n        throw $e;\n    }\n\n    public function testIdleTimeout()\n    {\n        $process = $this->getProcessForCode('sleep(34);');\n        $process->setTimeout(60);\n        $process->setIdleTimeout(0.1);\n\n        try {\n            $process->run();\n\n            $this->fail('A timeout exception was expected.');\n        } catch (ProcessTimedOutException $e) {\n            $this->assertTrue($e->isIdleTimeout());\n            $this->assertFalse($e->isGeneralTimeout());\n            $this->assertEquals(0.1, $e->getExceededTimeout());\n        }\n    }\n\n    public function testIdleTimeoutNotExceededWhenOutputIsSent()\n    {\n        $process = $this->getProcessForCode('while (true) {echo \\'foo \\'; usleep(1000);}');\n        $process->setTimeout(1);\n        $process->start();\n\n        while (!str_contains($process->getOutput(), 'foo')) {\n            usleep(1000);\n        }\n\n        $process->setIdleTimeout(0.5);\n\n        try {\n            $process->wait();\n            $this->fail('A timeout exception was expected.');\n        } catch (ProcessTimedOutException $e) {\n            $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');\n            $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');\n            $this->assertEquals(1, $e->getExceededTimeout());\n        }\n    }\n\n    public function testStartAfterATimeout()\n    {\n        $this->expectException(ProcessTimedOutException::class);\n        $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');\n        $process = $this->getProcessForCode('sleep(35);');\n        $process->setTimeout(0.1);\n\n        try {\n            $process->run();\n            $this->fail('A ProcessTimedOutException should have been raised.');\n        } catch (ProcessTimedOutException $e) {\n        }\n        $this->assertFalse($process->isRunning());\n        $process->start();\n        $this->assertTrue($process->isRunning());\n        $process->stop(0);\n\n        throw $e;\n    }\n\n    public function testGetPid()\n    {\n        $process = $this->getProcessForCode('sleep(36);');\n        $process->start();\n        $this->assertGreaterThan(0, $process->getPid());\n        $process->stop(0);\n    }\n\n    public function testGetPidIsNullBeforeStart()\n    {\n        $process = $this->getProcess('foo');\n        $this->assertNull($process->getPid());\n    }\n\n    public function testGetPidIsNullAfterRun()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->run();\n        $this->assertNull($process->getPid());\n    }\n\n    #[RequiresPhpExtension('pcntl')]\n    public function testSignal()\n    {\n        $process = $this->getProcess([self::$phpBin, __DIR__.'/SignalListener.php']);\n        $process->start();\n\n        while (!str_contains($process->getOutput(), 'Caught')) {\n            usleep(1000);\n        }\n        $process->signal(\\SIGUSR1);\n        $process->wait();\n\n        $this->assertEquals('Caught SIGUSR1', $process->getOutput());\n    }\n\n    #[RequiresPhpExtension('pcntl')]\n    public function testExitCodeIsAvailableAfterSignal()\n    {\n        $process = $this->getProcess('sleep 4');\n        $process->start();\n        $process->signal(\\SIGKILL);\n\n        while ($process->isRunning()) {\n            usleep(10000);\n        }\n\n        $this->assertFalse($process->isRunning());\n        $this->assertTrue($process->hasBeenSignaled());\n        $this->assertFalse($process->isSuccessful());\n        $this->assertEquals(137, $process->getExitCode());\n    }\n\n    public function testSignalProcessNotRunning()\n    {\n        $process = $this->getProcess('foo');\n\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage('Cannot send signal on a non running process.');\n\n        $process->signal(1); // SIGHUP\n    }\n\n    #[DataProvider('provideMethodsThatNeedARunningProcess')]\n    public function testMethodsThatNeedARunningProcess($method)\n    {\n        $process = $this->getProcess('foo');\n\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage(\\sprintf('Process must be started before calling \"%s()\".', $method));\n\n        $process->{$method}();\n    }\n\n    public static function provideMethodsThatNeedARunningProcess()\n    {\n        return [\n            ['getOutput'],\n            ['getIncrementalOutput'],\n            ['getErrorOutput'],\n            ['getIncrementalErrorOutput'],\n            ['wait'],\n        ];\n    }\n\n    #[DataProvider('provideMethodsThatNeedATerminatedProcess')]\n    public function testMethodsThatNeedATerminatedProcess($method)\n    {\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage('Process must be terminated before calling');\n        $process = $this->getProcessForCode('sleep(37);');\n        $process->start();\n        try {\n            $process->{$method}();\n            $process->stop(0);\n            $this->fail('A LogicException must have been thrown');\n        } catch (\\Exception $e) {\n        }\n        $process->stop(0);\n\n        throw $e;\n    }\n\n    public static function provideMethodsThatNeedATerminatedProcess()\n    {\n        return [\n            ['hasBeenSignaled'],\n            ['getTermSignal'],\n            ['hasBeenStopped'],\n            ['getStopSignal'],\n        ];\n    }\n\n    public function testWrongSignal()\n    {\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('POSIX signals do not work on Windows');\n        }\n\n        $this->expectException(RuntimeException::class);\n\n        $process = $this->getProcessForCode('sleep(38);');\n        $process->start();\n        try {\n            $process->signal(-4);\n            $this->fail('A RuntimeException must have been thrown');\n        } finally {\n            $process->stop(0);\n        }\n    }\n\n    public function testDisableOutputDisablesTheOutput()\n    {\n        $p = $this->getProcess('foo');\n        $this->assertFalse($p->isOutputDisabled());\n        $p->disableOutput();\n        $this->assertTrue($p->isOutputDisabled());\n        $p->enableOutput();\n        $this->assertFalse($p->isOutputDisabled());\n    }\n\n    public function testDisableOutputWhileRunningThrowsException()\n    {\n        $p = $this->getProcessForCode('sleep(39);');\n        $p->start();\n\n        $this->expectException(RuntimeException::class);\n        $this->expectExceptionMessage('Disabling output while the process is running is not possible.');\n\n        $p->disableOutput();\n    }\n\n    public function testEnableOutputWhileRunningThrowsException()\n    {\n        $p = $this->getProcessForCode('sleep(40);');\n        $p->disableOutput();\n        $p->start();\n\n        $this->expectException(RuntimeException::class);\n        $this->expectExceptionMessage('Enabling output while the process is running is not possible.');\n\n        $p->enableOutput();\n    }\n\n    public function testEnableOrDisableOutputAfterRunDoesNotThrowException()\n    {\n        $p = $this->getProcess('echo foo');\n        $p->disableOutput();\n        $p->run();\n        $p->enableOutput();\n        $p->disableOutput();\n        $this->assertTrue($p->isOutputDisabled());\n    }\n\n    public function testDisableOutputWhileIdleTimeoutIsSet()\n    {\n        $process = $this->getProcess('foo');\n        $process->setIdleTimeout(1);\n\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage('Output cannot be disabled while an idle timeout is set.');\n\n        $process->disableOutput();\n    }\n\n    public function testSetIdleTimeoutWhileOutputIsDisabled()\n    {\n        $process = $this->getProcess('foo');\n        $process->disableOutput();\n\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage('timeout cannot be set while the output is disabled.');\n\n        $process->setIdleTimeout(1);\n    }\n\n    public function testSetNullIdleTimeoutWhileOutputIsDisabled()\n    {\n        $process = $this->getProcess('foo');\n        $process->disableOutput();\n        $this->assertSame($process, $process->setIdleTimeout(null));\n    }\n\n    #[DataProvider('provideOutputFetchingMethods')]\n    public function testGetOutputWhileDisabled($fetchMethod)\n    {\n        $p = $this->getProcessForCode('sleep(41);');\n        $p->disableOutput();\n        $p->start();\n\n        $this->expectException(LogicException::class);\n        $this->expectExceptionMessage('Output has been disabled.');\n\n        $p->{$fetchMethod}();\n    }\n\n    public static function provideOutputFetchingMethods()\n    {\n        return [\n            ['getOutput'],\n            ['getIncrementalOutput'],\n            ['getErrorOutput'],\n            ['getIncrementalErrorOutput'],\n        ];\n    }\n\n    public function testStopTerminatesProcessCleanly()\n    {\n        $process = $this->getProcessForCode('echo 123; sleep(42);');\n        $process->run(static function () use ($process) {\n            $process->stop();\n        });\n        $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');\n    }\n\n    public function testKillSignalTerminatesProcessCleanly()\n    {\n        $process = $this->getProcessForCode('echo 123; sleep(43);');\n        $process->run(static function () use ($process) {\n            $process->signal(9); // SIGKILL\n        });\n        $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');\n    }\n\n    public function testTermSignalTerminatesProcessCleanly()\n    {\n        $process = $this->getProcessForCode('echo 123; sleep(44);');\n        $process->run(static function () use ($process) {\n            $process->signal(15); // SIGTERM\n        });\n        $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');\n    }\n\n    public static function responsesCodeProvider()\n    {\n        return [\n            // expected output / getter / code to execute\n            // [1,'getExitCode','exit(1);'],\n            // [true,'isSuccessful','exit();'],\n            ['output', 'getOutput', 'echo \\'output\\';'],\n        ];\n    }\n\n    public static function pipesCodeProvider()\n    {\n        $variations = [\n            'fwrite(STDOUT, $in = file_get_contents(\\'php://stdin\\')); fwrite(STDERR, $in);',\n            'include \\''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\\';',\n        ];\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            // Avoid XL buffers on Windows because of https://bugs.php.net/65650\n            $sizes = [1, 2, 4, 8];\n        } else {\n            $sizes = [1, 16, 64, 1024, 4096];\n        }\n\n        $codes = [];\n        foreach ($sizes as $size) {\n            foreach ($variations as $code) {\n                $codes[] = [$code, $size];\n            }\n        }\n\n        return $codes;\n    }\n\n    #[DataProvider('provideVariousIncrementals')]\n    public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)\n    {\n        $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\\''.$stream.'\\', $n, 1); $n++; usleep(1000); }', null, null, null, null);\n        $process->start();\n        $result = '';\n        $limit = microtime(true) + 3;\n        $expected = '012';\n\n        while ($result !== $expected && microtime(true) < $limit) {\n            $result .= $process->$method();\n        }\n\n        $this->assertSame($expected, $result);\n        $process->stop();\n    }\n\n    public static function provideVariousIncrementals()\n    {\n        return [\n            ['php://stdout', 'getIncrementalOutput'],\n            ['php://stderr', 'getIncrementalErrorOutput'],\n        ];\n    }\n\n    public function testIteratorInput()\n    {\n        $input = static function () {\n            yield 'ping';\n            yield 'pong';\n        };\n\n        $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());\n        $process->run();\n        $this->assertSame('pingpong', $process->getOutput());\n    }\n\n    public function testSimpleInputStream()\n    {\n        $input = new InputStream();\n\n        $process = $this->getProcessForCode('echo \\'ping\\'; echo fread(STDIN, 4); echo fread(STDIN, 4);');\n        $process->setInput($input);\n\n        $process->start(static function ($type, $data) use ($input) {\n            if ('ping' === $data) {\n                $input->write('pang');\n            } elseif (!$input->isClosed()) {\n                $input->write('pong');\n                $input->close();\n            }\n        });\n\n        $process->wait();\n        $this->assertSame('pingpangpong', $process->getOutput());\n    }\n\n    public function testInputStreamWithCallable()\n    {\n        $i = 0;\n        $stream = fopen('php://memory', 'w+');\n        $stream = static function () use ($stream, &$i) {\n            if ($i < 3) {\n                rewind($stream);\n                fwrite($stream, ++$i);\n                rewind($stream);\n\n                return $stream;\n            }\n\n            return null;\n        };\n\n        $input = new InputStream();\n        $input->onEmpty($stream);\n        $input->write($stream());\n\n        $process = $this->getProcessForCode('echo fread(STDIN, 3);');\n        $process->setInput($input);\n        $process->start(static function ($type, $data) use ($input) {\n            $input->close();\n        });\n\n        $process->wait();\n        $this->assertSame('123', $process->getOutput());\n    }\n\n    public function testInputStreamWithGenerator()\n    {\n        $input = new InputStream();\n        $input->onEmpty(static function ($input) {\n            yield 'pong';\n            $input->close();\n        });\n\n        $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');\n        $process->setInput($input);\n        $process->start();\n        $input->write('ping');\n        $process->wait();\n        $this->assertSame('pingpong', $process->getOutput());\n    }\n\n    public function testInputStreamOnEmpty()\n    {\n        $i = 0;\n        $input = new InputStream();\n        $input->onEmpty(static function () use (&$i) { ++$i; });\n\n        $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');\n        $process->setInput($input);\n        $process->start(static function ($type, $data) use ($input) {\n            if ('123' === $data) {\n                $input->close();\n            }\n        });\n        $process->wait();\n\n        $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');\n        $this->assertSame('123456', $process->getOutput());\n    }\n\n    public function testIteratorOutput()\n    {\n        $input = new InputStream();\n\n        $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');\n        $process->setInput($input);\n        $process->start();\n        $output = [];\n\n        foreach ($process as $type => $data) {\n            $output[] = [$type, $data];\n            break;\n        }\n        $expectedOutput = [\n            [$process::OUT, '123'],\n        ];\n        $this->assertSame($expectedOutput, $output);\n\n        $input->write(345);\n\n        foreach ($process as $type => $data) {\n            $output[] = [$type, $data];\n        }\n\n        $this->assertSame('', $process->getOutput());\n        $this->assertFalse($process->isRunning());\n\n        $expectedOutput = [\n            [$process::OUT, '123'],\n            [$process::ERR, '234'],\n            [$process::OUT, '345'],\n            [$process::ERR, '456'],\n        ];\n        $this->assertSame($expectedOutput, $output);\n    }\n\n    public function testNonBlockingNorClearingIteratorOutput()\n    {\n        $input = new InputStream();\n\n        $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');\n        $process->setInput($input);\n        $process->start();\n        $output = [];\n\n        foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {\n            $output[] = [$type, $data];\n            break;\n        }\n        $expectedOutput = [\n            [$process::OUT, ''],\n        ];\n        $this->assertSame($expectedOutput, $output);\n\n        $input->write(123);\n\n        foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {\n            if ('' !== $data) {\n                $output[] = [$type, $data];\n            }\n        }\n\n        $this->assertSame('123', $process->getOutput());\n        $this->assertFalse($process->isRunning());\n\n        $expectedOutput = [\n            [$process::OUT, ''],\n            [$process::OUT, '123'],\n        ];\n        $this->assertSame($expectedOutput, $output);\n    }\n\n    public function testChainedProcesses()\n    {\n        $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');\n        $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');\n        $p2->setInput($p1);\n\n        $p1->start();\n        $p2->run();\n\n        $this->assertSame('123', $p1->getErrorOutput());\n        $this->assertSame('', $p1->getOutput());\n        $this->assertSame('', $p2->getErrorOutput());\n        $this->assertSame('456', $p2->getOutput());\n    }\n\n    public function testSetBadEnv()\n    {\n        $process = $this->getProcess('echo hello');\n        $process->setEnv(['bad%%' => '123']);\n\n        $process->run();\n\n        $this->assertSame('hello'.\\PHP_EOL, $process->getOutput());\n        $this->assertSame('', $process->getErrorOutput());\n    }\n\n    public function testEnvBackupDoesNotDeleteExistingVars()\n    {\n        putenv('existing_var=foo');\n        $_ENV['existing_var'] = 'foo';\n        $process = $this->getProcess('php -r \"echo getenv(\\'new_test_var\\');\"');\n        $process->setEnv(['existing_var' => 'bar', 'new_test_var' => 'foo']);\n\n        $process->run();\n\n        $this->assertSame('foo', $process->getOutput());\n        $this->assertSame('foo', getenv('existing_var'));\n        $this->assertFalse(getenv('new_test_var'));\n\n        putenv('existing_var');\n        unset($_ENV['existing_var']);\n    }\n\n    public function testEnvIsInherited()\n    {\n        $process = $this->getProcessForCode('echo serialize($_SERVER);', null, ['BAR' => 'BAZ', 'EMPTY' => '']);\n\n        putenv('FOO=BAR');\n        $_ENV['FOO'] = 'BAR';\n\n        $process->run();\n\n        $expected = ['BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR'];\n        $env = array_intersect_key(unserialize($process->getOutput()), $expected);\n\n        $this->assertEquals($expected, $env);\n\n        putenv('FOO');\n        unset($_ENV['FOO']);\n    }\n\n    public function testGetCommandLine()\n    {\n        $p = new Process(['/usr/bin/php']);\n\n        $expected = '\\\\' === \\DIRECTORY_SEPARATOR ? '/usr/bin/php' : \"'/usr/bin/php'\";\n        $this->assertSame($expected, $p->getCommandLine());\n\n        $p = new Process(['cd', '/d']);\n\n        $expected = '\\\\' === \\DIRECTORY_SEPARATOR ? 'cd /d' : \"'cd' '/d'\";\n        $this->assertSame($expected, $p->getCommandLine());\n    }\n\n    #[DataProvider('provideEscapeArgument')]\n    public function testEscapeArgument($arg)\n    {\n        $p = new Process([self::$phpBin, '-r', 'echo $argv[1];', $arg]);\n        $p->run();\n\n        $this->assertSame((string) $arg, $p->getOutput());\n    }\n\n    public function testRawCommandLine()\n    {\n        $p = Process::fromShellCommandline(\\sprintf('\"%s\" -r %s \"a\" \"\" \"b\"', self::$phpBin, escapeshellarg('print_r($argv);')));\n        $p->run();\n\n        $expected = \"Array\\n(\\n    [0] => -\\n    [1] => a\\n    [2] => \\n    [3] => b\\n)\\n\";\n        $this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));\n    }\n\n    public static function provideEscapeArgument()\n    {\n        yield ['a\"b%c%'];\n        yield ['a\"b^c^'];\n        yield [\"a\\nb'c\"];\n        yield ['a^b c!'];\n        yield [\"a!b\\tc\"];\n        yield ['a\\\\\\\\\"\\\\\"'];\n        yield ['éÉèÈàÀöä'];\n        yield [null];\n        yield [1];\n        yield [1.1];\n    }\n\n    public function testMsysEscapingOnWindows()\n    {\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('This test is for Windows platform only');\n        }\n\n        file_put_contents('=foo.txt', 'This is a test file.');\n\n        try {\n            $p = $this->getProcess(['type', substr_replace(getcwd(), '=foo.txt', 2)]);\n            $p->mustRun();\n\n            $this->assertSame('This is a test file.', $p->getOutput());\n        } finally {\n            unlink('=foo.txt');\n        }\n\n        $this->assertSame(\\sprintf('type \"%s=foo.txt\"', substr(getcwd(), 0, 2)), $p->getCommandLine());\n    }\n\n    public function testPreparedCommand()\n    {\n        $p = Process::fromShellCommandline('echo \"${:abc}\"DEF');\n        $p->run(null, ['abc' => 'ABC']);\n\n        $this->assertSame('ABCDEF', rtrim($p->getOutput()));\n    }\n\n    public function testPreparedCommandMulti()\n    {\n        $p = Process::fromShellCommandline('echo \"${:abc}\"\"${:def}\"');\n        $p->run(null, ['abc' => 'ABC', 'def' => 'DEF']);\n\n        $this->assertSame('ABCDEF', rtrim($p->getOutput()));\n    }\n\n    public function testPreparedCommandWithQuoteInIt()\n    {\n        $p = Process::fromShellCommandline('php -r \"${:code}\" \"${:def}\"');\n        $p->run(null, ['code' => 'echo $argv[1];', 'def' => '\"DEF\"']);\n\n        $this->assertSame('\"DEF\"', rtrim($p->getOutput()));\n    }\n\n    public function testPreparedCommandWithMissingValue()\n    {\n        $p = Process::fromShellCommandline('echo \"${:abc}\"');\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('Command line is missing a value for parameter \"abc\": echo \"${:abc}\"');\n\n        $p->run(null, ['bcd' => 'BCD']);\n    }\n\n    public function testPreparedCommandWithNoValues()\n    {\n        $p = Process::fromShellCommandline('echo \"${:abc}\"');\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('Command line is missing a value for parameter \"abc\": echo \"${:abc}\"');\n\n        $p->run(null, []);\n    }\n\n    public function testEnvArgument()\n    {\n        $cmd = '\\\\' === \\DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';\n        $p = Process::fromShellCommandline($cmd);\n        $this->assertSame([], $p->getEnv());\n\n        $env = ['FOO' => 'Foo', 'BAR' => 'Bar'];\n        $p = Process::fromShellCommandline($cmd, null, $env);\n        $p->run(null, ['BAR' => 'baR', 'BAZ' => 'baZ']);\n\n        $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));\n        $this->assertSame($env, $p->getEnv());\n    }\n\n    public function testEnvVarNamesCastToString()\n    {\n        $process = $this->getProcess('echo hello');\n        $process->setEnv([123 => 'value']);\n\n        $process->run();\n\n        $this->assertSame('hello'.\\PHP_EOL, $process->getOutput());\n    }\n\n    public function testEnvVarNamesWithEqualsSigns()\n    {\n        $process = $this->getProcess('echo hello');\n        $process->setEnv(['VAR=NAME' => 'value']);\n\n        $process->run();\n\n        $this->assertSame('hello'.\\PHP_EOL, $process->getOutput());\n    }\n\n    public function testEnvVarNamesWithNullBytes()\n    {\n        $process = $this->getProcess('echo hello');\n        $process->setEnv([\"VAR\\0NAME\" => 'value']);\n\n        $process->run();\n\n        $this->assertSame('hello'.\\PHP_EOL, $process->getOutput());\n    }\n\n    public function testEnvVarNamesEmpty()\n    {\n        $process = $this->getProcess('echo hello');\n        $process->setEnv(['' => 'value']);\n\n        $process->run();\n\n        $this->assertSame('hello'.\\PHP_EOL, $process->getOutput());\n    }\n\n    public function testWaitStoppedDeadProcess()\n    {\n        $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/ErrorProcessInitiator.php -e '.self::$phpBin);\n        $process->start();\n        $process->setTimeout(2);\n        $process->wait();\n        $this->assertFalse($process->isRunning());\n\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR && !\\Closure::bind(fn () => $this->isSigchildEnabled(), $process, $process)()) {\n            $this->assertSame(0, $process->getExitCode());\n        }\n    }\n\n    public function testEnvCaseInsensitiveOnWindows()\n    {\n        $p = $this->getProcessForCode('print_r([$_SERVER[\\'PATH\\'] ?? 1, $_SERVER[\\'Path\\'] ?? 2]);', null, ['PATH' => 'bar/baz']);\n        $p->run(null, ['Path' => 'foo/bar']);\n\n        if ('\\\\' === \\DIRECTORY_SEPARATOR) {\n            $this->assertSame('Array ( [0] => 1 [1] => foo/bar )', preg_replace('/\\s++/', ' ', trim($p->getOutput())));\n        } else {\n            $this->assertSame('Array ( [0] => bar/baz [1] => foo/bar )', preg_replace('/\\s++/', ' ', trim($p->getOutput())));\n        }\n    }\n\n    public function testMultipleCallsToProcGetStatus()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->start(static fn () => $process->isRunning());\n        while ($process->isRunning()) {\n            usleep(1000);\n        }\n        $this->assertSame(0, $process->getExitCode());\n    }\n\n    public function testFailingProcessWithMultipleCallsToProcGetStatus()\n    {\n        $process = $this->getProcess('exit 123');\n        $process->start(static fn () => $process->isRunning());\n        while ($process->isRunning()) {\n            usleep(1000);\n        }\n        $this->assertSame(123, $process->getExitCode());\n    }\n\n    #[Group('slow')]\n    public function testLongRunningProcessWithMultipleCallsToProcGetStatus()\n    {\n        $process = $this->getProcess('sleep 1 && echo \"done\" && php -r \"exit(0);\"');\n        $process->start(static fn () => $process->isRunning());\n        while ($process->isRunning()) {\n            usleep(1000);\n        }\n        $this->assertSame(0, $process->getExitCode());\n    }\n\n    #[Group('slow')]\n    public function testLongRunningProcessWithMultipleCallsToProcGetStatusError()\n    {\n        $process = $this->getProcess('sleep 1 && echo \"failure\" && php -r \"exit(123);\"');\n        $process->start(static fn () => $process->isRunning());\n        while ($process->isRunning()) {\n            usleep(1000);\n        }\n        $this->assertSame(123, $process->getExitCode());\n    }\n\n    #[Group('transient-on-windows')]\n    public function testNotTerminableInputPipe()\n    {\n        $process = $this->getProcess('echo foo');\n        $process->setInput(\\STDIN);\n        $process->start();\n        $process->setTimeout(2);\n        $process->wait();\n        $this->assertFalse($process->isRunning());\n    }\n\n    public function testIgnoringSignal()\n    {\n        if (!\\function_exists('pcntl_signal')) {\n            $this->markTestSkipped('pnctl extension is required.');\n        }\n\n        $process = $this->getProcess(['sleep', '10']);\n        $process->setIgnoredSignals([\\SIGTERM]);\n\n        $process->start();\n        $process->stop(timeout: 0.2);\n\n        $this->assertNotSame(\\SIGTERM, $process->getTermSignal());\n    }\n\n    // This test ensure that the previous test is reliable, in case of the sleep command ignoring the SIGTERM signal\n    public function testNotIgnoringSignal()\n    {\n        if (!\\function_exists('pcntl_signal')) {\n            $this->markTestSkipped('pnctl extension is required.');\n        }\n\n        $process = $this->getProcess(['sleep', '10']);\n\n        $process->start();\n        $process->stop(timeout: 0.2);\n\n        $this->assertSame(\\SIGTERM, $process->getTermSignal());\n    }\n\n    public function testPathResolutionOnWindows()\n    {\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('This test is for Windows platform only');\n        }\n\n        $process = $this->getProcess(['where']);\n\n        $this->assertSame('C:\\\\Windows\\\\system32\\\\where.EXE', $process->getCommandLine());\n    }\n\n    private function getProcess(string|array $commandline, ?string $cwd = null, ?array $env = null, mixed $input = null, ?int $timeout = 60): Process\n    {\n        if (\\is_string($commandline)) {\n            $process = Process::fromShellCommandline($commandline, $cwd, $env, $input, $timeout);\n        } else {\n            $process = new Process($commandline, $cwd, $env, $input, $timeout);\n        }\n\n        self::$process?->stop(0);\n\n        return self::$process = $process;\n    }\n\n    private function getProcessForCode(string $code, ?string $cwd = null, ?array $env = null, $input = null, ?int $timeout = 60): Process\n    {\n        return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout);\n    }\n}\n\nclass NonStringifiable\n{\n}\n"
  },
  {
    "path": "Tests/ProcessWindowsEnvBlockTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\Process\\Tests;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\Process\\Exception\\InvalidArgumentException;\nuse Symfony\\Component\\Process\\PhpExecutableFinder;\nuse Symfony\\Component\\Process\\Process;\n\nclass ProcessWindowsEnvBlockTest extends TestCase\n{\n    private static string $phpBin;\n\n    public static function setUpBeforeClass(): void\n    {\n        $phpBin = new PhpExecutableFinder();\n        self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \\PHP_SAPI ? 'php' : $phpBin->find());\n    }\n\n    public function testStartThrowsWhenSingleEnvValueExceedsWindowsLimit()\n    {\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows-only.');\n        }\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('The environment block size (');\n\n        // \"KEY=\" (4) + 32767 + \"\\0\" (1) + block terminator (1) = 32773 > 32767\n        $this->getProcess([self::$phpBin, '--version'], null, ['KEY' => str_repeat('x', 32767)])->start();\n    }\n\n    public function testStartThrowsWhenMultipleEntriesCollectivelyExceedWindowsLimit()\n    {\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows-only.');\n        }\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('The environment block size (');\n\n        $env = [];\n        for ($i = 0; $i < 10; ++$i) {\n            $env['KEY_'.$i] = str_repeat('v', 3277);\n        }\n\n        $this->getProcess([self::$phpBin, '--version'], null, $env)->start();\n    }\n\n    public function testStartDoesNotThrowForSmallEnv()\n    {\n        $p = $this->getProcess([self::$phpBin, '--version'], null, ['SMALL' => 'value']);\n        $p->start();\n        $p->stop(0);\n\n        $this->assertTrue(true, 'start() must not throw for a small env block.');\n    }\n\n    public function testStartDoesNotThrowForEmptyEnv()\n    {\n        $p = $this->getProcess([self::$phpBin, '--version'], null, []);\n        $p->start();\n        $p->stop(0);\n\n        $this->assertTrue(true, 'start() must not throw for an empty env block.');\n    }\n\n    public function testStartDoesNotThrowForNullEnv()\n    {\n        $p = new Process([self::$phpBin, '--version']);\n        $p->start();\n        $p->stop(0);\n\n        $this->assertTrue(true, 'start() must not throw when env is null.');\n    }\n\n    public function testStartDoesNotThrowWhenEnvBlockIsExactlyAtWindowsLimit()\n    {\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows-only.');\n        }\n\n        $method = new \\ReflectionMethod(Process::class, 'validateWindowsEnvBlockSize');\n\n        // \"KEY=\" (4) + 32761 + \"\\0\" (1) + terminator (1) = 32767 exactly\n        $method->invoke(new Process([self::$phpBin, '--version']), ['KEY='.str_repeat('x', 32761)]);\n\n        $this->assertTrue(true, 'start() must not throw when the env block is exactly at the limit.');\n    }\n\n    public function testStartDoesNotThrowWhenFalseEnvValuesExceedLimit()\n    {\n        if ('\\\\' !== \\DIRECTORY_SEPARATOR) {\n            $this->markTestSkipped('Windows-only.');\n        }\n\n        // false values are stripped before the size check\n        $p = $this->getProcess([self::$phpBin, '--version'], null, ['IGNORED' => false, 'SMALL' => 'value']);\n        $p->start();\n        $p->stop(0);\n\n        $this->assertTrue(true, 'false env values must not count toward the block size.');\n    }\n\n    public function testWindowsEnvBlockValidationThrowsViaReflection()\n    {\n        $method = new \\ReflectionMethod(Process::class, 'validateWindowsEnvBlockSize');\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('The environment block size (');\n\n        $method->invoke(new Process([self::$phpBin, '--version']), ['KEY='.str_repeat('x', 32767)]);\n    }\n\n    public function testWindowsEnvBlockValidationPassesAtExactLimitViaReflection()\n    {\n        $method = new \\ReflectionMethod(Process::class, 'validateWindowsEnvBlockSize');\n\n        // \"KEY=\" (4) + 32761 + \"\\0\" (1) + terminator (1) = 32767 exactly\n        $method->invoke(new Process([self::$phpBin, '--version']), ['KEY='.str_repeat('x', 32761)]);\n\n        $this->assertTrue(true, 'No exception must be thrown when the block is exactly at the limit.');\n    }\n\n    public function testWindowsEnvBlockValidationCountsMultibyteInCodeUnitsViaReflection()\n    {\n        $method = new \\ReflectionMethod(Process::class, 'validateWindowsEnvBlockSize');\n\n        // \"é\" = 2 UTF-8 bytes but 1 UTF-16 code unit\n        // \"KEY=\" (4) + 32761 code units + \"\\0\" (1) + terminator (1) = 32767 exactly → must not throw\n        $method->invoke(new Process([self::$phpBin, '--version']), ['KEY='.str_repeat('é', 32761)]);\n\n        $this->assertTrue(true, 'Multibyte chars must be counted in UTF-16 code units, not bytes.');\n    }\n\n    public function testWindowsEnvBlockValidationCountsSupplementaryCharsAsTwoCodeUnitsViaReflection()\n    {\n        $method = new \\ReflectionMethod(Process::class, 'validateWindowsEnvBlockSize');\n\n        // U+1F389 \"🎉\" = 4 UTF-8 bytes, 1 codepoint, 2 UTF-16 code units (surrogate pair)\n        // \"KK=\" (3) + 16381 × 2 code units (32762) + \"\\0\" (1) + terminator (1) = 32767 exactly → must not throw\n        $method->invoke(new Process([self::$phpBin, '--version']), ['KK='.str_repeat('🎉', 16381)]);\n\n        $this->assertTrue(true, 'Supplementary chars must be counted as 2 UTF-16 code units.');\n    }\n\n    public function testWindowsEnvBlockValidationThrowsWhenSupplementaryCharsPushOverLimitViaReflection()\n    {\n        $method = new \\ReflectionMethod(Process::class, 'validateWindowsEnvBlockSize');\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('The environment block size (');\n\n        // \"KK=\" (3) + 16382 × 2 code units (32764) + \"\\0\" (1) + terminator (1) = 32769 > 32767\n        $method->invoke(new Process([self::$phpBin, '--version']), ['KK='.str_repeat('🎉', 16382)]);\n    }\n\n    private function getProcess(array $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?int $timeout = 60): Process\n    {\n        return new Process($command, $cwd, $env, $input, $timeout);\n    }\n}\n"
  },
  {
    "path": "Tests/SignalListener.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\npcntl_signal(\\SIGUSR1, static function () {\n    echo 'SIGUSR1';\n    exit;\n});\n\necho 'Caught ';\n\n$n = 0;\n\nwhile ($n++ < 400) {\n    usleep(10000);\n    pcntl_signal_dispatch();\n}\n"
  },
  {
    "path": "Tests/ThreeSecondProcess.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\necho 'Worker started';\nsleep(3);\necho 'Worker done';\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"symfony/process\",\n    \"type\": \"library\",\n    \"description\": \"Executes commands in sub-processes\",\n    \"keywords\": [],\n    \"homepage\": \"https://symfony.com\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\"\n        },\n        {\n            \"name\": \"Symfony Community\",\n            \"homepage\": \"https://symfony.com/contributors\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.4\"\n    },\n    \"autoload\": {\n        \"psr-4\": { \"Symfony\\\\Component\\\\Process\\\\\": \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    },\n    \"minimum-stability\": \"dev\"\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/11.3/phpunit.xsd\"\n         backupGlobals=\"false\"\n         colors=\"true\"\n         bootstrap=\"vendor/autoload.php\"\n         failOnDeprecation=\"true\"\n         failOnRisky=\"true\"\n         failOnWarning=\"true\"\n>\n    <php>\n        <ini name=\"error_reporting\" value=\"-1\" />\n    </php>\n\n    <testsuites>\n        <testsuite name=\"Symfony Process Component Test Suite\">\n            <directory>./Tests/</directory>\n        </testsuite>\n    </testsuites>\n\n    <source ignoreSuppressionOfDeprecations=\"true\">\n        <include>\n            <directory>./</directory>\n        </include>\n        <exclude>\n            <directory>./Tests</directory>\n            <directory>./vendor</directory>\n        </exclude>\n    </source>\n\n    <extensions>\n        <bootstrap class=\"Symfony\\Bridge\\PhpUnit\\SymfonyExtension\" />\n    </extensions>\n</phpunit>\n"
  }
]