diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon
index 3c445f08e..3597f9fee 100644
--- a/phpstan/baseline.neon
+++ b/phpstan/baseline.neon
@@ -5301,6 +5301,16 @@ parameters:
count: 1
path: ../tests/Composer/Test/Json/JsonManipulatorTest.php
+ -
+ message: "#^Offset 'ask' might not exist on array\\{ask\\: string, reply\\?\\: string\\}\\|array\\{auth\\: array\\{string, string, string\\|null\\}\\}\\|array\\{text\\: string, verbosity\\?\\: 1\\|2\\|4\\|8\\|16\\}\\.$#"
+ count: 2
+ path: ../tests/Composer/Test/Mock/IOMock.php
+
+ -
+ message: "#^Offset 'text' might not exist on array\\{ask\\: string, reply\\?\\: string\\}\\|array\\{auth\\: array\\{string, string, string\\|null\\}\\}\\|array\\{text\\: string, verbosity\\?\\: 1\\|2\\|4\\|8\\|16\\}\\.$#"
+ count: 1
+ path: ../tests/Composer/Test/Mock/IOMock.php
+
-
message: "#^Composer\\\\Test\\\\Mock\\\\InstallationManagerMock\\:\\:__construct\\(\\) does not call parent constructor from Composer\\\\Installer\\\\InstallationManager\\.$#"
count: 1
diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
index cb98b38ca..fda3b06ed 100644
--- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
+++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
@@ -1328,10 +1328,16 @@ EOF;
$this->eventDispatcher
->expects($this->exactly(2))
->method('dispatchScript')
- ->withConsecutive(
- [ScriptEvents::PRE_AUTOLOAD_DUMP, false],
- [ScriptEvents::POST_AUTOLOAD_DUMP, false]
- );
+ ->willReturnCallback(function ($type, $dev) {
+ static $series = [
+ [ScriptEvents::PRE_AUTOLOAD_DUMP, false],
+ [ScriptEvents::POST_AUTOLOAD_DUMP, false]
+ ];
+
+ $this->assertSame(array_shift($series), [$type, $dev]);
+
+ return 0;
+ });
$package = new RootPackage('root/a', '1.0', '1.0');
$package->setAutoload(['psr-0' => ['Prefix' => 'foo/bar/non/existing/']]);
diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php
index 2a2690765..428c4f265 100644
--- a/tests/Composer/Test/ConfigTest.php
+++ b/tests/Composer/Test/ConfigTest.php
@@ -307,12 +307,9 @@ class ConfigTest extends TestCase
public function testProhibitedUrlsWarningVerifyPeer(): void
{
- $io = $this->getMockBuilder(IOInterface::class)->disableOriginalConstructor()->getMock();
+ $io = $this->getIOMock();
- $io
- ->expects($this->once())
- ->method('writeError')
- ->with($this->equalTo('Warning: Accessing example.org with verify_peer and verify_peer_name disabled.'));
+ $io->expects([['text' => 'Warning: Accessing example.org with verify_peer and verify_peer_name disabled.']], true);
$config = new Config(false);
$config->prohibitUrlByConfig('https://example.org', $io, [
diff --git a/tests/Composer/Test/Downloader/DownloadManagerTest.php b/tests/Composer/Test/Downloader/DownloadManagerTest.php
index 9717c3a7a..05d169cad 100644
--- a/tests/Composer/Test/Downloader/DownloadManagerTest.php
+++ b/tests/Composer/Test/Downloader/DownloadManagerTest.php
@@ -258,10 +258,14 @@ class DownloadManagerTest extends TestCase
$package
->expects($this->exactly(2))
->method('setInstallationSource')
- ->withConsecutive(
- ['dist'],
- ['source']
- );
+ ->willReturnCallback(function ($type) {
+ static $series = [
+ 'dist',
+ 'source',
+ ];
+
+ $this->assertSame(array_shift($series), $type);
+ });
$downloaderFail = $this->createDownloaderMock();
$downloaderFail
diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php
index 5a7cb667f..0978392f0 100644
--- a/tests/Composer/Test/Downloader/FileDownloaderTest.php
+++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php
@@ -375,13 +375,11 @@ class FileDownloaderTest extends TestCase
$newPackage = self::getPackage('dummy/pkg', '1.0.0');
$newPackage->setDistUrl($distUrl = 'http://example.com/script.js');
- $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
- $ioMock->expects($this->atLeast(2))
- ->method('writeError')
- ->withConsecutive(
- [$this->stringContains('Downloading')],
- [$this->stringContains('Downgrading')]
- );
+ $ioMock = $this->getIOMock();
+ $ioMock->expects([
+ ['text' => '{Downloading .*}', 'regex' => true],
+ ['text' => '{Downgrading .*}', 'regex' => true],
+ ]);
$path = self::getUniqueTmpDirectory();
$config = $this->getConfig(['vendor-dir' => $path.'/vendor']);
diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php
index 66a9c504d..5411767e5 100644
--- a/tests/Composer/Test/Downloader/GitDownloaderTest.php
+++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php
@@ -547,12 +547,10 @@ composer https://github.com/old/url (push)
$process = $this->getProcessExecutorMock();
- $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
- $ioMock->expects($this->atLeastOnce())
- ->method('writeError')
- ->withConsecutive(
- [$this->stringContains('Downgrading')]
- );
+ $ioMock = $this->getIOMock();
+ $ioMock->expects([
+ ['text' => '{Downgrading .*}', 'regex' => true],
+ ]);
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock($ioMock, null, $process);
@@ -591,12 +589,10 @@ composer https://github.com/old/url (push)
$process = $this->getProcessExecutorMock();
- $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
- $ioMock->expects($this->atLeastOnce())
- ->method('writeError')
- ->withConsecutive(
- [$this->stringContains('Upgrading')]
- );
+ $ioMock = $this->getIOMock();
+ $ioMock->expects([
+ ['text' => '{Upgrading .*}', 'regex' => true],
+ ]);
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock($ioMock, null, $process);
diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
index 7f4c6b73a..f0a7ecc5f 100644
--- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
+++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
@@ -14,6 +14,7 @@ namespace Composer\Test\EventDispatcher;
use Composer\EventDispatcher\Event;
use Composer\EventDispatcher\EventDispatcher;
+use Composer\EventDispatcher\ScriptExecutionException;
use Composer\Installer\InstallerEvents;
use Composer\Config;
use Composer\Composer;
@@ -32,21 +33,15 @@ class EventDispatcherTest extends TestCase
{
self::expectException('RuntimeException');
- $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
+ $io = $this->getIOMock(IOInterface::NORMAL);
$dispatcher = $this->getDispatcherStubForListenersTest([
'Composer\Test\EventDispatcher\EventDispatcherTest::call',
], $io);
- $io->expects($this->once())
- ->method('isVerbose')
- ->willReturn(0);
-
- $io->expects($this->atLeast(2))
- ->method('writeError')
- ->withConsecutive(
- ['> Composer\Test\EventDispatcher\EventDispatcherTest::call'],
- ['Script Composer\Test\EventDispatcher\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception']
- );
+ $io->expects([
+ ['text' => '> Composer\Test\EventDispatcher\EventDispatcherTest::call'],
+ ['text' => 'Script Composer\Test\EventDispatcher\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception'],
+ ], true);
$dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
}
@@ -528,7 +523,7 @@ class EventDispatcherTest extends TestCase
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs([
$this->createComposerInstance(),
- $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
+ $io = $this->getIOMock(IOInterface::NORMAL),
new ProcessExecutor,
])
->onlyMethods(['getListeners'])
@@ -540,22 +535,13 @@ class EventDispatcherTest extends TestCase
->method('getListeners')
->will($this->returnValue($listener));
- $io->expects($this->once())
- ->method('isVerbose')
- ->willReturn(0);
+ $io->expects([
+ ['text' => '> exit 1'],
+ ['text' => 'Script '.$code.' handling the post-install-cmd event returned with error code 1'],
+ ], true);
- $io->expects($this->atLeast(2))
- ->method('writeError')
- ->withConsecutive(
- ['> exit 1'],
- ['Script '.$code.' handling the post-install-cmd event returned with error code 1']
- );
-
- $io->expects($this->once())
- ->method('isInteractive')
- ->willReturn(1);
-
- self::expectException('RuntimeException');
+ self::expectException(ScriptExecutionException::class);
+ self::expectExceptionMessage('Error Output: ');
$dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
}
diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php
index 04483e751..2d152eb61 100644
--- a/tests/Composer/Test/IO/ConsoleIOTest.php
+++ b/tests/Composer/Test/IO/ConsoleIOTest.php
@@ -111,15 +111,25 @@ class ConsoleIOTest extends TestCase
->willReturn(OutputInterface::VERBOSITY_NORMAL);
$outputMock->expects($this->atLeast(7))
->method('write')
- ->withConsecutive(
- [$this->equalTo('something (strlen = 23)')],
- [$this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false)],
- [$this->equalTo('shorter (12)'), $this->equalTo(false)],
- [$this->equalTo(str_repeat(' ', 11)), $this->equalTo(false)],
- [$this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false)],
- [$this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false)],
- [$this->equalTo('something longer than initial (34)')]
- );
+ ->willReturnCallback(function (...$args) {
+ static $series = null;
+
+ if ($series === null) {
+ $series = [
+ ['something (strlen = 23)', true],
+ [str_repeat("\x08", 23), false],
+ ['shorter (12)', false],
+ [str_repeat(' ', 11), false],
+ [str_repeat("\x08", 11), false],
+ [str_repeat("\x08", 12), false],
+ ['something longer than initial (34)', false],
+ ];
+ }
+
+ if (count($series) > 0) {
+ $this->assertSame(array_shift($series), [$args[0], $args[1]]);
+ }
+ });
$helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock();
diff --git a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php
index 64a961f0d..a9969c4f4 100644
--- a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php
+++ b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php
@@ -15,6 +15,7 @@ namespace Composer\Test\Installer;
use Composer\InstalledVersions;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\Semver\VersionParser;
+use Composer\Test\Mock\IOMock;
use Composer\Test\TestCase;
/**
@@ -23,7 +24,7 @@ use Composer\Test\TestCase;
class SuggestedPackagesReporterTest extends TestCase
{
/**
- * @var \PHPUnit\Framework\MockObject\MockObject
+ * @var IOMock
*/
private $io;
@@ -34,7 +35,7 @@ class SuggestedPackagesReporterTest extends TestCase
protected function setUp(): void
{
- $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
+ $this->io = $this->getIOMock();
$this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
}
@@ -44,8 +45,7 @@ class SuggestedPackagesReporterTest extends TestCase
*/
public function testConstructor(): void
{
- $this->io->expects($this->once())
- ->method('write');
+ $this->io->expects([['text' => 'b']], true);
$this->suggestedPackagesReporter->addPackage('a', 'b', 'c');
$this->suggestedPackagesReporter->output(SuggestedPackagesReporter::MODE_LIST);
@@ -143,13 +143,11 @@ class SuggestedPackagesReporterTest extends TestCase
{
$this->suggestedPackagesReporter->addPackage('a', 'b', 'c');
- $this->io->expects($this->exactly(3))
- ->method('write')
- ->withConsecutive(
- ['a suggests:'],
- [' - b: c'],
- ['']
- );
+ $this->io->expects([
+ ['text' => 'a suggests:'],
+ ['text' => ' - b: c'],
+ ['text' => ''],
+ ], true);
$this->suggestedPackagesReporter->output(SuggestedPackagesReporter::MODE_BY_PACKAGE);
}
@@ -161,13 +159,11 @@ class SuggestedPackagesReporterTest extends TestCase
{
$this->suggestedPackagesReporter->addPackage('a', 'b', '');
- $this->io->expects($this->exactly(3))
- ->method('write')
- ->withConsecutive(
- ['a suggests:'],
- [' - b'],
- ['']
- );
+ $this->io->expects([
+ ['text' => 'a suggests:'],
+ ['text' => ' - b'],
+ ['text' => ''],
+ ], true);
$this->suggestedPackagesReporter->output(SuggestedPackagesReporter::MODE_BY_PACKAGE);
}
@@ -180,18 +176,12 @@ class SuggestedPackagesReporterTest extends TestCase
$this->suggestedPackagesReporter->addPackage('source', 'target1', "\x1b[1;37;42m Like us\r\non Facebook \x1b[0m");
$this->suggestedPackagesReporter->addPackage('source', 'target2', "Like us on Facebook>");
- $expectedWrite = InstalledVersions::satisfies(new VersionParser(), 'symfony/console', '^4.4.37 || ~5.3.14 || ^5.4.3 || ^6.0.3')
- ? ' - target2: \\Like us on Facebook\\\\>'
- : ' - target2: \\Like us on Facebook\\>';
-
- $this->io->expects($this->exactly(4))
- ->method('write')
- ->withConsecutive(
- ['source suggests:'],
- [' - target1: [1;37;42m Like us on Facebook [0m'],
- [$expectedWrite],
- ['']
- );
+ $this->io->expects([
+ ['text' => 'source suggests:'],
+ ['text' => ' - target1: [1;37;42m Like us on Facebook [0m'],
+ ['text' => ' - target2: Like us on Facebook>'],
+ ['text' => ''],
+ ], true);
$this->suggestedPackagesReporter->output(SuggestedPackagesReporter::MODE_BY_PACKAGE);
}
@@ -204,16 +194,14 @@ class SuggestedPackagesReporterTest extends TestCase
$this->suggestedPackagesReporter->addPackage('a', 'b', 'c');
$this->suggestedPackagesReporter->addPackage('source package', 'target', 'because reasons');
- $this->io->expects($this->exactly(6))
- ->method('write')
- ->withConsecutive(
- ['a suggests:'],
- [' - b: c'],
- [''],
- ['source package suggests:'],
- [' - target: because reasons'],
- ['']
- );
+ $this->io->expects([
+ ['text' => 'a suggests:'],
+ ['text' => ' - b: c'],
+ ['text' => ''],
+ ['text' => 'source package suggests:'],
+ ['text' => ' - target: because reasons'],
+ ['text' => ''],
+ ], true);
$this->suggestedPackagesReporter->output(SuggestedPackagesReporter::MODE_BY_PACKAGE);
}
@@ -245,13 +233,11 @@ class SuggestedPackagesReporterTest extends TestCase
$this->suggestedPackagesReporter->addPackage('a', 'b', 'c');
$this->suggestedPackagesReporter->addPackage('source package', 'target', 'because reasons');
- $this->io->expects($this->exactly(3))
- ->method('write')
- ->withConsecutive(
- ['source package suggests:'],
- [' - target: because reasons'],
- ['']
- );
+ $this->io->expects([
+ ['text' => 'source package suggests:'],
+ ['text' => ' - target: because reasons'],
+ ['text' => ''],
+ ], true);
$this->suggestedPackagesReporter->output(SuggestedPackagesReporter::MODE_BY_PACKAGE, $repository);
}
diff --git a/tests/Composer/Test/Mock/IOMock.php b/tests/Composer/Test/Mock/IOMock.php
new file mode 100644
index 000000000..f3e9f79e7
--- /dev/null
+++ b/tests/Composer/Test/Mock/IOMock.php
@@ -0,0 +1,196 @@
+
+ * Jordi Boggiano
+ *
+ * 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|null
+ */
+ private $expectations = null;
+ /**
+ * @var bool
+ */
+ private $strict = false;
+ /**
+ * @var list
+ */
+ 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 $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'], $expect['reply'])) {
+ if (!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);
+ }
+}
diff --git a/tests/Composer/Test/Repository/Vcs/GitDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php
index 5dd57dead..b54e29ad1 100644
--- a/tests/Composer/Test/Repository/Vcs/GitDriverTest.php
+++ b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php
@@ -55,7 +55,7 @@ class GitDriverTest extends TestCase
public function testGetRootIdentifierFromRemoteLocalRepository(): void
{
$process = $this->getProcessExecutorMock();
- $io = $this->getMockBuilder(IOInterface::class)->getMock();
+ $io = $this->getIOMock();
$driver = new GitDriver(['url' => $this->home], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);
@@ -82,11 +82,9 @@ GIT;
public function testGetRootIdentifierFromRemote(): void
{
$process = $this->getProcessExecutorMock();
- $io = $this->getMockBuilder(IOInterface::class)->getMock();
+ $io = $this->getIOMock();
- $io
- ->expects($this->never())
- ->method('writeError');
+ $io->expects([], true);
$driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);
@@ -119,7 +117,7 @@ GIT;
Platform::putEnv('COMPOSER_DISABLE_NETWORK', '1');
$process = $this->getProcessExecutorMock();
- $io = $this->getMockBuilder(IOInterface::class)->getMock();
+ $io = $this->getIOMock();
$driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);
diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php
index 6dcc04510..6a2d7ce86 100644
--- a/tests/Composer/Test/TestCase.php
+++ b/tests/Composer/Test/TestCase.php
@@ -24,6 +24,7 @@ use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Test\Mock\FactoryMock;
use Composer\Test\Mock\HttpDownloaderMock;
+use Composer\Test\Mock\IOMock;
use Composer\Test\Mock\ProcessExecutorMock;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
@@ -59,6 +60,10 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
* @var list
*/
private $processExecutorMocks = [];
+ /**
+ * @var list
+ */
+ private $ioMocks = [];
/**
* @var list
*/
@@ -75,6 +80,9 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
foreach ($this->processExecutorMocks as $mock) {
$mock->assertComplete();
}
+ foreach ($this->ioMocks as $mock) {
+ $mock->assertComplete();
+ }
if (null !== $this->prevCwd) {
chdir($this->prevCwd);
@@ -181,7 +189,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
{
$factory = new FactoryMock();
- $locker = new Locker($this->getMockBuilder(IOInterface::class)->getMock(), new JsonFile('./composer.lock'), $factory->createInstallationManager(), (string) file_get_contents('./composer.json'));
+ $locker = new Locker($this->getIOMock(), new JsonFile('./composer.lock'), $factory->createInstallationManager(), (string) file_get_contents('./composer.json'));
$locker->setLockData($packages, $devPackages, [], [], [], 'dev', [], false, false, []);
}
@@ -356,6 +364,16 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
return $mock;
}
+ /**
+ * @param IOInterface::* $verbosity
+ */
+ protected function getIOMock(int $verbosity = IOInterface::DEBUG): IOMock
+ {
+ $this->ioMocks[] = $mock = new IOMock($verbosity);
+
+ return $mock;
+ }
+
protected function createTempFile(?string $dir = null): string
{
$dir = $dir ?? sys_get_temp_dir();
diff --git a/tests/Composer/Test/Util/BitbucketTest.php b/tests/Composer/Test/Util/BitbucketTest.php
index bd380b87b..dfaac651d 100644
--- a/tests/Composer/Test/Util/BitbucketTest.php
+++ b/tests/Composer/Test/Util/BitbucketTest.php
@@ -12,6 +12,7 @@
namespace Composer\Test\Util;
+use Composer\Test\Mock\IOMock;
use Composer\Util\Bitbucket;
use Composer\Util\Http\Response;
use Composer\Test\TestCase;
@@ -36,7 +37,7 @@ class BitbucketTest extends TestCase
/** @var string */
private $token = 'bitbuckettoken';
- /** @var \Composer\IO\ConsoleIO&\PHPUnit\Framework\MockObject\MockObject */
+ /** @var IOMock */
private $io;
/** @var \Composer\Util\HttpDownloader&\PHPUnit\Framework\MockObject\MockObject */
private $httpDownloader;
@@ -49,11 +50,7 @@ class BitbucketTest extends TestCase
protected function setUp(): void
{
- $this->io = $this
- ->getMockBuilder('Composer\IO\ConsoleIO')
- ->disableOriginalConstructor()
- ->getMock()
- ;
+ $this->io = $this->getIOMock();
$this->httpDownloader = $this
->getMockBuilder('Composer\Util\HttpDownloader')
@@ -70,9 +67,9 @@ class BitbucketTest extends TestCase
public function testRequestAccessTokenWithValidOAuthConsumer(): void
{
- $this->io->expects($this->once())
- ->method('setAuthentication')
- ->with($this->origin, $this->consumer_key, $this->consumer_secret);
+ $this->io->expects([
+ ['auth' => [$this->origin, $this->consumer_key, $this->consumer_secret]],
+ ]);
$this->httpDownloader->expects($this->once())
->method('get')
@@ -151,9 +148,9 @@ class BitbucketTest extends TestCase
]
);
- $this->io->expects($this->once())
- ->method('setAuthentication')
- ->with($this->origin, $this->consumer_key, $this->consumer_secret);
+ $this->io->expects([
+ ['auth' => [$this->origin, $this->consumer_key, $this->consumer_secret]],
+ ]);
$this->httpDownloader->expects($this->once())
->method('get')
@@ -189,19 +186,14 @@ class BitbucketTest extends TestCase
public function testRequestAccessTokenWithUsernameAndPassword(): void
{
- $this->io->expects($this->once())
- ->method('setAuthentication')
- ->with($this->origin, $this->username, $this->password);
-
- $this->io->expects($this->any())
- ->method('writeError')
- ->withConsecutive(
- ['Invalid OAuth consumer provided.'],
- ['This can have three reasons:'],
- ['1. You are authenticating with a bitbucket username/password combination'],
- ['2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url'],
- ['3. You are using an OAuth consumer, but didn\'t configure it as private consumer']
- );
+ $this->io->expects([
+ ['auth' => [$this->origin, $this->username, $this->password]],
+ ['text' => 'Invalid OAuth consumer provided.'],
+ ['text' => 'This can have three reasons:'],
+ ['text' => '1. You are authenticating with a bitbucket username/password combination'],
+ ['text' => '2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url'],
+ ['text' => '3. You are using an OAuth consumer, but didn\'t configure it as private consumer'],
+ ], true);
$this->httpDownloader->expects($this->once())
->method('get')
@@ -240,16 +232,11 @@ class BitbucketTest extends TestCase
->with('bitbucket-oauth')
->willReturn(null);
- $this->io->expects($this->once())
- ->method('setAuthentication')
- ->with($this->origin, $this->username, $this->password);
-
- $this->io->expects($this->any())
- ->method('writeError')
- ->withConsecutive(
- ['Invalid OAuth consumer provided.'],
- ['You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "']
- );
+ $this->io->expects([
+ ['auth' => [$this->origin, $this->username, $this->password]],
+ ['text' => 'Invalid OAuth consumer provided.'],
+ ['text' => 'You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'],
+ ], true);
$this->httpDownloader->expects($this->once())
->method('get')
@@ -276,9 +263,9 @@ class BitbucketTest extends TestCase
->with('bitbucket-oauth')
->willReturn(null);
- $this->io->expects($this->once())
- ->method('setAuthentication')
- ->with($this->origin, $this->username, $this->password);
+ $this->io->expects([
+ ['auth' => [$this->origin, $this->username, $this->password]],
+ ]);
$exception = new \Composer\Downloader\TransportException('HTTP/1.1 404 NOT FOUND', 404);
$this->httpDownloader->expects($this->once())
@@ -300,19 +287,11 @@ class BitbucketTest extends TestCase
public function testUsernamePasswordAuthenticationFlow(): void
{
- $this->io
- ->expects($this->atLeastOnce())
- ->method('writeError')
- ->withConsecutive([$this->message])
- ;
-
- $this->io->expects($this->exactly(2))
- ->method('askAndHideAnswer')
- ->withConsecutive(
- ['Consumer Key (hidden): '],
- ['Consumer Secret (hidden): ']
- )
- ->willReturnOnConsecutiveCalls($this->consumer_key, $this->consumer_secret);
+ $this->io->expects([
+ ['text' => $this->message],
+ ['ask' => 'Consumer Key (hidden): ', 'reply' => $this->consumer_key],
+ ['ask' => 'Consumer Secret (hidden): ', 'reply' => $this->consumer_secret],
+ ]);
$this->httpDownloader
->expects($this->once())
@@ -346,10 +325,9 @@ class BitbucketTest extends TestCase
->method('getAuthConfigSource')
->willReturn($authConfigSourceMock);
- $this->io->expects($this->once())
- ->method('askAndHideAnswer')
- ->with('Consumer Key (hidden): ')
- ->willReturnOnConsecutiveCalls(null);
+ $this->io->expects([
+ ['ask' => 'Consumer Key (hidden): ', 'reply' => ''],
+ ]);
$this->assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message));
}
@@ -361,13 +339,11 @@ class BitbucketTest extends TestCase
->method('getAuthConfigSource')
->willReturn($authConfigSourceMock);
- $this->io->expects($this->exactly(2))
- ->method('askAndHideAnswer')
- ->withConsecutive(
- ['Consumer Key (hidden): '],
- ['Consumer Secret (hidden): ']
- )
- ->willReturnOnConsecutiveCalls($this->consumer_key, null);
+ $this->io->expects([
+ ['text' => $this->message],
+ ['ask' => 'Consumer Key (hidden): ', 'reply' => $this->consumer_key],
+ ['ask' => 'Consumer Secret (hidden): ', 'reply' => ''],
+ ]);
$this->assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message));
}
@@ -379,13 +355,11 @@ class BitbucketTest extends TestCase
->method('getAuthConfigSource')
->willReturn($authConfigSourceMock);
- $this->io->expects($this->exactly(2))
- ->method('askAndHideAnswer')
- ->withConsecutive(
- ['Consumer Key (hidden): '],
- ['Consumer Secret (hidden): ']
- )
- ->willReturnOnConsecutiveCalls($this->consumer_key, $this->consumer_secret);
+ $this->io->expects([
+ ['text' => $this->message],
+ ['ask' => 'Consumer Key (hidden): ', 'reply' => $this->consumer_key],
+ ['ask' => 'Consumer Secret (hidden): ', 'reply' => $this->consumer_secret],
+ ]);
$this->httpDownloader
->expects($this->once())
diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php
index 72b2e1c2c..d9e0e5412 100644
--- a/tests/Composer/Test/Util/GitHubTest.php
+++ b/tests/Composer/Test/Util/GitHubTest.php
@@ -30,17 +30,10 @@ class GitHubTest extends TestCase
public function testUsernamePasswordAuthenticationFlow(): void
{
$io = $this->getIOMock();
- $io
- ->expects($this->atLeastOnce())
- ->method('writeError')
- ->withConsecutive([$this->message])
- ;
- $io
- ->expects($this->once())
- ->method('askAndHideAnswer')
- ->with('Token (hidden): ')
- ->willReturn($this->password)
- ;
+ $io->expects([
+ ['text' => $this->message],
+ ['ask' => 'Token (hidden): ', 'reply' => $this->password],
+ ]);
$httpDownloader = $this->getHttpDownloaderMock();
$httpDownloader->expects(
@@ -68,12 +61,9 @@ class GitHubTest extends TestCase
public function testUsernamePasswordFailure(): void
{
$io = $this->getIOMock();
- $io
- ->expects($this->exactly(1))
- ->method('askAndHideAnswer')
- ->with('Token (hidden): ')
- ->willReturn($this->password)
- ;
+ $io->expects([
+ ['ask' => 'Token (hidden): ', 'reply' => $this->password],
+ ]);
$httpDownloader = $this->getHttpDownloaderMock();
$httpDownloader->expects(
@@ -93,20 +83,6 @@ class GitHubTest extends TestCase
$this->assertFalse($github->authorizeOAuthInteractively($this->origin));
}
- /**
- * @return \PHPUnit\Framework\MockObject\MockObject&\Composer\IO\ConsoleIO
- */
- private function getIOMock()
- {
- $io = $this
- ->getMockBuilder('Composer\IO\ConsoleIO')
- ->disableOriginalConstructor()
- ->getMock()
- ;
-
- return $io;
- }
-
/**
* @return \PHPUnit\Framework\MockObject\MockObject&\Composer\Config
*/
diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php
index 1ce746176..5c8cc9443 100644
--- a/tests/Composer/Test/Util/GitLabTest.php
+++ b/tests/Composer/Test/Util/GitLabTest.php
@@ -36,23 +36,11 @@ class GitLabTest extends TestCase
public function testUsernamePasswordAuthenticationFlow(): void
{
$io = $this->getIOMock();
- $io
- ->expects($this->atLeastOnce())
- ->method('writeError')
- ->withConsecutive([$this->message])
- ;
- $io
- ->expects($this->once())
- ->method('ask')
- ->with('Username: ')
- ->willReturn($this->username)
- ;
- $io
- ->expects($this->once())
- ->method('askAndHideAnswer')
- ->with('Password: ')
- ->willReturn($this->password)
- ;
+ $io->expects([
+ ['text' => $this->message],
+ ['ask' => 'Username: ', 'reply' => $this->username],
+ ['ask' => 'Password: ', 'reply' => $this->password],
+ ]);
$httpDownloader = $this->getHttpDownloaderMock();
$httpDownloader->expects(
@@ -77,18 +65,18 @@ class GitLabTest extends TestCase
self::expectException('RuntimeException');
self::expectExceptionMessage('Invalid GitLab credentials 5 times in a row, aborting.');
$io = $this->getIOMock();
- $io
- ->expects($this->exactly(5))
- ->method('ask')
- ->with('Username: ')
- ->willReturn($this->username)
- ;
- $io
- ->expects($this->exactly(5))
- ->method('askAndHideAnswer')
- ->with('Password: ')
- ->willReturn($this->password)
- ;
+ $io->expects([
+ ['ask' => 'Username: ', 'reply' => $this->username],
+ ['ask' => 'Password: ', 'reply' => $this->password],
+ ['ask' => 'Username: ', 'reply' => $this->username],
+ ['ask' => 'Password: ', 'reply' => $this->password],
+ ['ask' => 'Username: ', 'reply' => $this->username],
+ ['ask' => 'Password: ', 'reply' => $this->password],
+ ['ask' => 'Username: ', 'reply' => $this->username],
+ ['ask' => 'Password: ', 'reply' => $this->password],
+ ['ask' => 'Username: ', 'reply' => $this->username],
+ ['ask' => 'Password: ', 'reply' => $this->password],
+ ]);
$httpDownloader = $this->getHttpDownloaderMock();
$httpDownloader->expects(
@@ -114,20 +102,6 @@ class GitLabTest extends TestCase
$gitLab->authorizeOAuthInteractively('https', $this->origin);
}
- /**
- * @return \PHPUnit\Framework\MockObject\MockObject&\Composer\IO\ConsoleIO
- */
- private function getIOMock()
- {
- $io = $this
- ->getMockBuilder('Composer\IO\ConsoleIO')
- ->disableOriginalConstructor()
- ->getMock()
- ;
-
- return $io;
- }
-
/**
* @return \PHPUnit\Framework\MockObject\MockObject&\Composer\Config
*/