1
0
Fork 0
composer/tests/Composer/Test/Mock/IOMock.php

197 lines
7.0 KiB
PHP

<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Mock;
use Composer\Config;
use Composer\IO\BufferIO;
use Composer\IO\IOInterface;
use Composer\Pcre\PcreException;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Util\Http\Response;
use Composer\Downloader\TransportException;
use Composer\Util\Platform;
use LogicException;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\AssertionFailedError;
use Symfony\Component\Console\Output\OutputInterface;
class IOMock extends BufferIO
{
/**
* @var list<array{text: string, verbosity?: IOInterface::*}|array{ask: string, reply?: string}|array{auth: array{string, string, string|null}}>|null
*/
private $expectations = null;
/**
* @var bool
*/
private $strict = false;
/**
* @var list<array{string, string, string|null}>
*/
private $authLog = [];
/**
* @param IOInterface::* $verbosity
*/
public function __construct(int $verbosity)
{
$sfVerbosity = [
self::QUIET => OutputInterface::VERBOSITY_QUIET,
self::NORMAL => OutputInterface::VERBOSITY_NORMAL,
self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
self::DEBUG => OutputInterface::VERBOSITY_DEBUG,
][$verbosity];
parent::__construct('', $sfVerbosity);
}
/**
* @param list<array{text: string, verbosity?: IOInterface::*, regex?: true}|array{ask: string, reply: string}|array{auth: array{string, string, string|null}}> $expectations
* @param bool $strict set to true if you want to provide *all* expected messages, and not just a subset you are interested in testing
*/
public function expects(array $expectations, bool $strict = false): void
{
$this->expectations = $expectations;
$inputs = [];
foreach ($expectations as $expect) {
if (isset($expect['ask'])) {
if (!array_key_exists('reply', $expect) || !is_string($expect['reply'])) {
throw new \LogicException('A question\'s reply must be a string, use empty string for null replies');
}
$inputs[] = $expect['reply'];
}
}
if (count($inputs) > 0) {
$this->setUserInputs($inputs);
}
$this->strict = $strict;
}
public function assertComplete(): void
{
$output = $this->getOutput();
if (Platform::getEnv('DEBUG_OUTPUT') === '1') {
echo PHP_EOL.'Collected output: '.$output.PHP_EOL;
}
// this was not configured to expect anything, so no need to react here
if (!is_array($this->expectations)) {
return;
}
if (count($this->expectations) > 0) {
$lines = Preg::split("{\r?\n}", $output);
foreach ($this->expectations as $expect) {
if (isset($expect['auth'])) {
while (count($this->authLog) > 0) {
$auth = array_shift($this->authLog);
if ($auth === $expect['auth']) {
continue 2;
}
if ($this->strict) {
throw new AssertionFailedError('IO authentication mismatch. Expected:'.PHP_EOL.json_encode($expect['auth']).PHP_EOL.'Got:'.PHP_EOL.json_encode($auth));
}
}
throw new AssertionFailedError('Expected "'.json_encode($expect['auth']).'" auth to be set but there are no setAuthentication calls left to consume.');
}
if (isset($expect['ask'], $expect['reply'])) {
$pattern = '{^'.preg_quote($expect['ask']).'$}';
} elseif (isset($expect['regex']) && $expect['regex']) {
$pattern = $expect['text'];
} else {
$pattern = '{^'.preg_quote($expect['text']).'$}';
}
while (count($lines) > 0) {
$line = array_shift($lines);
try {
if (Preg::isMatch($pattern, $line)) {
continue 2;
}
} catch (PcreException $e) {
throw new LogicException('Invalid regex pattern in IO expectation "'.$pattern.'": '.$e->getMessage());
}
if ($this->strict) {
throw new AssertionFailedError('IO output mismatch. Expected:'.PHP_EOL.($expect['text'] ?? $expect['ask']).PHP_EOL.'Got:'.PHP_EOL.$line);
}
}
throw new AssertionFailedError('Expected "'.($expect['text'] ?? $expect['ask']).'" to be output still but there is no output left to consume. Complete output:'.PHP_EOL.$output);
}
} elseif ($output !== '' && $this->strict) {
throw new AssertionFailedError('There was strictly no output expected but some output occurred: '.$output);
}
// dummy assertion to ensure the test is not marked as having no assertions
Assert::assertTrue(true); // @phpstan-ignore-line
}
/**
* @inheritDoc
*/
public function ask($question, $default = null)
{
return parent::ask(rtrim($question, "\r\n").PHP_EOL, $default);
}
/**
* @inheritDoc
*/
public function askConfirmation($question, $default = true)
{
return parent::askConfirmation(rtrim($question, "\r\n").PHP_EOL, $default);
}
/**
* @inheritDoc
*/
public function askAndValidate($question, $validator, $attempts = null, $default = null)
{
return parent::askAndValidate(rtrim($question, "\r\n").PHP_EOL, $validator, $attempts, $default);
}
/**
* @inheritDoc
*/
public function askAndHideAnswer($question)
{
// do not hide answer in tests because that blocks on windows with hiddeninput.exe
return parent::ask(rtrim($question, "\r\n").PHP_EOL);
}
/**
* @inheritDoc
*/
public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
{
return parent::select(rtrim($question, "\r\n").PHP_EOL, $choices, $default, $attempts, $errorMessage, $multiselect);
}
public function setAuthentication($repositoryName, $username, $password = null)
{
$this->authentications[$repositoryName] = ['username' => $username, 'password' => $password];
$this->authLog[] = [$repositoryName, $username, $password];
parent::setAuthentication($repositoryName, $username, $password);
}
}