1
0
Fork 0
composer/tests/Composer/Test/InstallerTest.php

637 lines
26 KiB
PHP
Raw Normal View History

2022-02-23 15:58:18 +00:00
<?php declare(strict_types=1);
2013-05-27 08:41:50 +00:00
/*
* 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;
use Composer\DependencyResolver\Request;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Installer;
use Composer\Pcre\Preg;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Composer\IO\BufferIO;
use Composer\Json\JsonFile;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Util\Filesystem;
use Composer\Repository\ArrayRepository;
use Composer\Repository\RepositoryManager;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledArrayRepository;
use Composer\Package\RootPackageInterface;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Link;
use Composer\Package\Locker;
use Composer\Test\Mock\FactoryMock;
use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
use Composer\Test\Mock\InstallationManagerMock;
2020-10-01 14:42:02 +00:00
use Composer\Util\Platform;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;
class InstallerTest extends TestCase
{
/** @var string */
private $prevCwd;
/** @var ?string */
protected $tempComposerHome;
2013-03-03 16:18:50 +00:00
2021-12-08 16:03:05 +00:00
public function setUp(): void
2013-03-03 16:18:50 +00:00
{
2022-02-22 15:47:09 +00:00
$this->prevCwd = Platform::getCwd();
2013-03-03 16:18:50 +00:00
chdir(__DIR__);
}
protected function tearDown(): void
2013-03-03 16:18:50 +00:00
{
parent::tearDown();
2020-10-01 14:42:02 +00:00
Platform::clearEnv('COMPOSER_POOL_OPTIMIZER');
Platform::clearEnv('COMPOSER_FUND');
2020-10-01 14:42:02 +00:00
2013-03-03 16:18:50 +00:00
chdir($this->prevCwd);
if (isset($this->tempComposerHome) && is_dir($this->tempComposerHome)) {
$fs = new Filesystem;
$fs->removeDirectory($this->tempComposerHome);
}
2013-03-03 16:18:50 +00:00
}
/**
* @dataProvider provideInstaller
* @param RootPackageInterface&BasePackage $rootPackage
* @param RepositoryInterface[] $repositories
* @param mixed[] $options
*/
2022-02-22 15:47:09 +00:00
public function testInstaller(RootPackageInterface $rootPackage, array $repositories, array $options): void
{
$io = new BufferIO('', OutputInterface::VERBOSITY_NORMAL, new OutputFormatter(false));
$downloadManager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
2022-08-17 12:20:07 +00:00
->setConstructorArgs([$io])
->getMock();
$config = $this->getMockBuilder('Composer\Config')->getMock();
$config->expects($this->any())
->method('get')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function ($key) {
switch ($key) {
case 'vendor-dir':
return 'foo';
2021-10-27 14:18:24 +00:00
case 'lock':
case 'notify-on-install':
return true;
2021-10-27 14:18:24 +00:00
case 'platform':
2022-08-17 12:20:07 +00:00
return [];
}
throw new \UnexpectedValueException('Unknown key '.$key);
}));
$eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
$httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock();
$repositoryManager = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher);
$repositoryManager->setLocalRepository(new InstalledArrayRepository());
foreach ($repositories as $repository) {
$repositoryManager->addRepository($repository);
}
$installationManager = new InstallationManagerMock();
2013-01-06 19:34:52 +00:00
// emulate a writable lock file
2021-06-03 09:29:00 +00:00
/** @var ?string $lockData */
$lockData = null;
$lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
$lockJsonMock->expects($this->any())
->method('read')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function () use (&$lockData) {
return json_decode($lockData, true);
}));
$lockJsonMock->expects($this->any())
->method('exists')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function () use (&$lockData): bool {
return $lockData !== null;
}));
$lockJsonMock->expects($this->any())
->method('write')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function ($value, $options = 0) use (&$lockData): void {
$lockData = json_encode($value, JSON_PRETTY_PRINT);
}));
$tempLockData = null;
$locker = new Locker($io, $lockJsonMock, $installationManager, '{}');
2013-01-06 19:34:52 +00:00
$autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')->disableOriginalConstructor()->getMock();
$installer = new Installer($io, $config, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator);
$installer->setAudit(false);
$result = $installer->run();
$output = str_replace("\r", '', $io->getOutput());
$this->assertEquals(0, $result, $output);
2022-08-17 12:20:07 +00:00
$expectedInstalled = $options['install'] ?? [];
$expectedUpdated = $options['update'] ?? [];
$expectedUninstalled = $options['uninstall'] ?? [];
$installed = $installationManager->getInstalledPackages();
$this->assertEquals($this->makePackagesComparable($expectedInstalled), $this->makePackagesComparable($installed));
$updated = $installationManager->getUpdatedPackages();
$this->assertSame($expectedUpdated, $updated);
$uninstalled = $installationManager->getUninstalledPackages();
$this->assertSame($expectedUninstalled, $uninstalled);
}
/**
* @param PackageInterface[] $packages
* @return mixed[]
*/
2022-02-22 15:47:09 +00:00
protected function makePackagesComparable(array $packages): array
{
$dumper = new ArrayDumper();
2022-08-17 12:20:07 +00:00
$comparable = [];
foreach ($packages as $package) {
$comparable[] = $dumper->dump($package);
}
2020-11-22 13:48:56 +00:00
return $comparable;
}
public static function provideInstaller(): array
{
2022-08-17 12:20:07 +00:00
$cases = [];
// when A requires B and B requires A, and A is a non-published root package
// the install of B should succeed
$a = self::getPackage('A', '1.0.0', 'Composer\Package\RootPackage');
2022-08-17 12:20:07 +00:00
$a->setRequires([
'b' => new Link('A', 'B', $v = self::getVersionConstraint('=', '1.0.0'), Link::TYPE_REQUIRE, $v->getPrettyString()),
2022-08-17 12:20:07 +00:00
]);
$b = self::getPackage('B', '1.0.0');
2022-08-17 12:20:07 +00:00
$b->setRequires([
'a' => new Link('B', 'A', $v = self::getVersionConstraint('=', '1.0.0'), Link::TYPE_REQUIRE, $v->getPrettyString()),
2022-08-17 12:20:07 +00:00
]);
2022-08-17 12:20:07 +00:00
$cases[] = [
$a,
2022-08-17 12:20:07 +00:00
[new ArrayRepository([$b])],
[
'install' => [$b],
],
];
// #480: when A requires B and B requires A, and A is a published root package
// only B should be installed, as A is the root
$a = self::getPackage('A', '1.0.0', 'Composer\Package\RootPackage');
2022-08-17 12:20:07 +00:00
$a->setRequires([
'b' => new Link('A', 'B', $v = self::getVersionConstraint('=', '1.0.0'), Link::TYPE_REQUIRE, $v->getPrettyString()),
2022-08-17 12:20:07 +00:00
]);
$b = self::getPackage('B', '1.0.0');
2022-08-17 12:20:07 +00:00
$b->setRequires([
'a' => new Link('B', 'A', $v = self::getVersionConstraint('=', '1.0.0'), Link::TYPE_REQUIRE, $v->getPrettyString()),
2022-08-17 12:20:07 +00:00
]);
2022-08-17 12:20:07 +00:00
$cases[] = [
$a,
2022-08-17 12:20:07 +00:00
[new ArrayRepository([$a, $b])],
[
'install' => [$b],
],
];
// TODO why are there not more cases with uninstall/update?
return $cases;
}
/**
* @group slow
* @dataProvider provideSlowIntegrationTests
2022-02-22 15:47:09 +00:00
* @param mixed[] $composerConfig
* @param ?array<mixed> $lock
* @param ?array<mixed> $installed
* @param mixed[]|false $expectLock
2022-02-22 15:47:09 +00:00
* @param ?array<mixed> $expectInstalled
* @param int|class-string<\Throwable> $expectResult
*/
2022-02-22 15:47:09 +00:00
public function testSlowIntegration(string $file, string $message, ?string $condition, array $composerConfig, ?array $lock, ?array $installed, string $run, $expectLock, ?array $expectInstalled, ?string $expectOutput, ?string $expectOutputOptimized, string $expect, $expectResult): void
{
2020-10-01 14:42:02 +00:00
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '0');
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
}
/**
* @dataProvider provideIntegrationTests
2022-02-22 15:47:09 +00:00
* @param mixed[] $composerConfig
* @param ?array<mixed> $lock
* @param ?array<mixed> $installed
2020-10-01 14:42:02 +00:00
* @param mixed[]|false $expectLock
2022-02-22 15:47:09 +00:00
* @param ?array<mixed> $expectInstalled
* @param int|class-string<\Throwable> $expectResult
2020-10-01 14:42:02 +00:00
*/
2022-02-22 15:47:09 +00:00
public function testIntegrationWithPoolOptimizer(string $file, string $message, ?string $condition, array $composerConfig, ?array $lock, ?array $installed, string $run, $expectLock, ?array $expectInstalled, ?string $expectOutput, ?string $expectOutputOptimized, string $expect, $expectResult): void
2020-10-01 14:42:02 +00:00
{
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '1');
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutputOptimized ?: $expectOutput, $expect, $expectResult);
}
/**
* @dataProvider provideIntegrationTests
2022-02-22 15:47:09 +00:00
* @param mixed[] $composerConfig
* @param ?array<mixed> $lock
* @param ?array<mixed> $installed
2020-10-01 14:42:02 +00:00
* @param mixed[]|false $expectLock
2022-02-22 15:47:09 +00:00
* @param ?array<mixed> $expectInstalled
* @param int|class-string<\Throwable> $expectResult
2020-10-01 14:42:02 +00:00
*/
2022-02-22 15:47:09 +00:00
public function testIntegrationWithRawPool(string $file, string $message, ?string $condition, array $composerConfig, ?array $lock, ?array $installed, string $run, $expectLock, ?array $expectInstalled, ?string $expectOutput, ?string $expectOutputOptimized, string $expect, $expectResult): void
2020-10-01 14:42:02 +00:00
{
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '0');
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
}
/**
2022-02-22 15:47:09 +00:00
* @param mixed[] $composerConfig
* @param ?array<mixed> $lock
* @param ?array<mixed> $installed
* @param mixed[]|false $expectLock
2022-02-22 15:47:09 +00:00
* @param ?array<mixed> $expectInstalled
2021-12-09 19:55:26 +00:00
* @param int|class-string<\Throwable> $expectResult
*/
2022-02-22 15:47:09 +00:00
private function doTestIntegration(string $file, string $message, ?string $condition, array $composerConfig, ?array $lock, ?array $installed, string $run, $expectLock, ?array $expectInstalled, ?string $expectOutput, string $expect, $expectResult): void
{
if ($condition) {
eval('$res = '.$condition.';');
if (!$res) { // @phpstan-ignore variable.undefined
$this->markTestSkipped($condition);
}
}
$io = new BufferIO('', OutputInterface::VERBOSITY_NORMAL, new OutputFormatter(false));
// Prepare for exceptions
if (!is_int($expectResult)) {
$normalizedOutput = rtrim(str_replace("\n", PHP_EOL, $expect));
2021-12-09 19:55:26 +00:00
self::expectException($expectResult);
self::expectExceptionMessage($normalizedOutput);
}
// Create Composer mock object according to configuration
$composer = FactoryMock::create($io, $composerConfig);
$this->tempComposerHome = $composer->getConfig()->get('home');
$jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
$jsonMock->expects($this->any())
->method('read')
->will($this->returnValue($installed));
$jsonMock->expects($this->any())
->method('exists')
->will($this->returnValue(true));
$repositoryManager = $composer->getRepositoryManager();
$repositoryManager->setLocalRepository(new InstalledFilesystemRepositoryMock($jsonMock));
// emulate a writable lock file
$lockData = $lock ? json_encode($lock, JSON_PRETTY_PRINT) : null;
$lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
$lockJsonMock->expects($this->any())
->method('read')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function () use (&$lockData) {
return json_decode($lockData, true);
}));
$lockJsonMock->expects($this->any())
->method('exists')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function () use (&$lockData): bool {
return $lockData !== null;
}));
$lockJsonMock->expects($this->any())
->method('write')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function ($value, $options = 0) use (&$lockData): void {
$lockData = json_encode($value, JSON_PRETTY_PRINT);
}));
if ($expectLock) {
2022-08-17 12:20:07 +00:00
$actualLock = [];
$lockJsonMock->expects($this->atLeastOnce())
->method('write')
2022-08-17 12:20:07 +00:00
->will($this->returnCallback(static function ($hash, $options) use (&$actualLock): void {
// need to do assertion outside of mock for nice phpunit output
// so store value temporarily in reference for later assertion
$actualLock = $hash;
}));
} elseif ($expectLock === false) {
$lockJsonMock->expects($this->never())
->method('write');
}
$contents = json_encode($composerConfig);
2017-12-27 12:20:12 +00:00
$locker = new Locker($io, $lockJsonMock, $composer->getInstallationManager(), $contents);
$composer->setLocker($locker);
$eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
$autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')
2022-08-17 12:20:07 +00:00
->setConstructorArgs([$eventDispatcher])
->getMock();
2013-02-24 17:21:16 +00:00
$composer->setAutoloadGenerator($autoloadGenerator);
$composer->setEventDispatcher($eventDispatcher);
$installer = Installer::create($io, $composer);
$application = new Application;
$install = new Command('install');
$install->addOption('ignore-platform-reqs', null, InputOption::VALUE_NONE);
$install->addOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY);
$install->addOption('no-dev', null, InputOption::VALUE_NONE);
$install->addOption('dry-run', null, InputOption::VALUE_NONE);
2022-08-17 12:20:07 +00:00
$install->setCode(static function ($input, $output) use ($installer): int {
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$installer
2014-04-07 09:10:57 +00:00
->setDevMode(!$input->getOption('no-dev'))
->setDryRun($input->getOption('dry-run'))
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
->setAudit(false);
2013-11-22 15:17:02 +00:00
return $installer->run();
});
$application->add($install);
$update = new Command('update');
$update->addOption('ignore-platform-reqs', null, InputOption::VALUE_NONE);
$update->addOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY);
$update->addOption('no-dev', null, InputOption::VALUE_NONE);
$update->addOption('no-install', null, InputOption::VALUE_NONE);
$update->addOption('dry-run', null, InputOption::VALUE_NONE);
$update->addOption('lock', null, InputOption::VALUE_NONE);
$update->addOption('with-all-dependencies', null, InputOption::VALUE_NONE);
$update->addOption('with-dependencies', null, InputOption::VALUE_NONE);
$update->addOption('minimal-changes', null, InputOption::VALUE_NONE);
$update->addOption('prefer-stable', null, InputOption::VALUE_NONE);
$update->addOption('prefer-lowest', null, InputOption::VALUE_NONE);
$update->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL);
2022-08-17 12:20:07 +00:00
$update->setCode(static function ($input, $output) use ($installer): int {
$packages = $input->getArgument('packages');
2022-08-17 12:20:07 +00:00
$filteredPackages = array_filter($packages, static function ($package): bool {
return !in_array($package, ['lock', 'nothing', 'mirrors'], true);
});
$updateMirrors = $input->getOption('lock') || count($filteredPackages) !== count($packages);
$packages = $filteredPackages;
$updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
if ($input->getOption('with-all-dependencies')) {
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
} elseif ($input->getOption('with-dependencies')) {
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
}
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$installer
2014-04-07 09:10:57 +00:00
->setDevMode(!$input->getOption('no-dev'))
->setUpdate(true)
2020-04-24 07:07:32 +00:00
->setInstall(!$input->getOption('no-install'))
->setDryRun($input->getOption('dry-run'))
->setUpdateMirrors($updateMirrors)
->setUpdateAllowList($packages)
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest'))
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
->setAudit(false)
->setMinimalUpdate($input->getOption('minimal-changes'));
2013-11-22 15:17:02 +00:00
return $installer->run();
});
$application->add($update);
if (!Preg::isMatch('{^(install|update)\b}', $run)) {
throw new \UnexpectedValueException('The run command only supports install and update');
}
$application->setAutoExit(false);
$appOutput = fopen('php://memory', 'w+');
if (false === $appOutput) {
self::fail('Failed to open memory stream');
}
$input = new StringInput($run.' -vvv');
2017-12-18 16:18:59 +00:00
$input->setInteractive(false);
$result = $application->run($input, new StreamOutput($appOutput));
fseek($appOutput, 0);
// Shouldn't check output and results if an exception was expected by this point
if (!is_int($expectResult)) {
return;
}
$output = str_replace("\r", '', $io->getOutput());
$this->assertEquals($expectResult, $result, $output . stream_get_contents($appOutput));
2020-04-07 12:13:50 +00:00
if ($expectLock && isset($actualLock)) {
unset($actualLock['hash'], $actualLock['content-hash'], $actualLock['_readme'], $actualLock['plugin-api-version']);
$this->assertEquals($expectLock, $actualLock);
}
2020-04-20 19:44:15 +00:00
if ($expectInstalled !== null) {
2022-08-17 12:20:07 +00:00
$actualInstalled = [];
2020-04-20 19:44:15 +00:00
$dumper = new ArrayDumper();
foreach ($repositoryManager->getLocalRepository()->getCanonicalPackages() as $package) {
$package = $dumper->dump($package);
unset($package['version_normalized']);
$actualInstalled[] = $package;
}
2022-08-17 12:20:07 +00:00
usort($actualInstalled, static function ($a, $b): int {
2020-04-20 19:44:15 +00:00
return strcmp($a['name'], $b['name']);
});
$this->assertSame($expectInstalled, $actualInstalled);
}
/** @var InstallationManagerMock $installationManager */
$installationManager = $composer->getInstallationManager();
$this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace()));
if ($expectOutput) {
$output = Preg::replace('{^ - .*?\.ini$}m', '__inilist__', $output);
$output = Preg::replace('{(__inilist__\r?\n)+}', "__inilist__\n", $output);
$this->assertStringMatchesFormat(rtrim($expectOutput), rtrim($output));
}
}
public static function provideSlowIntegrationTests(): array
{
return self::loadIntegrationTests('installer-slow/');
}
public static function provideIntegrationTests(): array
{
return self::loadIntegrationTests('installer/');
}
/**
* @return mixed[]
*/
public static function loadIntegrationTests(string $path): array
{
$fixturesDir = (string) realpath(__DIR__.'/Fixtures/'.$path);
2022-08-17 12:20:07 +00:00
$tests = [];
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
2022-02-23 15:57:47 +00:00
$file = (string) $file;
if (!Preg::isMatch('/\.test$/', $file)) {
continue;
}
try {
$testData = self::readTestFile($file, $fixturesDir);
2022-08-17 12:20:07 +00:00
$installed = [];
$installedDev = [];
$lock = [];
$expectLock = [];
2020-04-20 19:44:15 +00:00
$expectInstalled = null;
$expectResult = 0;
$message = $testData['TEST'];
$condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
$composer = JsonFile::parseJson($testData['COMPOSER']);
2014-11-20 15:41:29 +00:00
if (isset($composer['repositories'])) {
foreach ($composer['repositories'] as &$repo) {
if ($repo['type'] !== 'composer') {
continue;
2014-11-20 15:41:29 +00:00
}
// Change paths like file://foobar to file:///path/to/fixtures
if (Preg::isMatch('{^file://[^/]}', $repo['url'])) {
$repo['url'] = 'file://' . strtr($fixturesDir, '\\', '/') . '/' . substr($repo['url'], 7);
}
unset($repo);
}
}
if (!empty($testData['LOCK'])) {
$lock = JsonFile::parseJson($testData['LOCK']);
if (!isset($lock['hash'])) {
$lock['hash'] = md5(JsonFile::encode($composer, 0));
}
}
if (!empty($testData['INSTALLED'])) {
$installed = JsonFile::parseJson($testData['INSTALLED']);
}
$run = $testData['RUN'];
if (!empty($testData['EXPECT-LOCK'])) {
if ($testData['EXPECT-LOCK'] === 'false') {
$expectLock = false;
} else {
$expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
}
}
2020-04-20 19:44:15 +00:00
if (!empty($testData['EXPECT-INSTALLED'])) {
$expectInstalled = JsonFile::parseJson($testData['EXPECT-INSTALLED']);
}
2022-02-18 08:09:58 +00:00
$expectOutput = $testData['EXPECT-OUTPUT'] ?? null;
$expectOutputOptimized = $testData['EXPECT-OUTPUT-OPTIMIZED'] ?? null;
$expect = $testData['EXPECT'];
if (!empty($testData['EXPECT-EXCEPTION'])) {
$expectResult = $testData['EXPECT-EXCEPTION'];
if (!empty($testData['EXPECT-EXIT-CODE'])) {
throw new \LogicException('EXPECT-EXCEPTION and EXPECT-EXIT-CODE are mutually exclusive');
}
} elseif (!empty($testData['EXPECT-EXIT-CODE'])) {
$expectResult = (int) $testData['EXPECT-EXIT-CODE'];
} else {
$expectResult = 0;
}
} catch (\Exception $e) {
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
}
2022-08-17 12:20:07 +00:00
$tests[basename($file)] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult];
}
return $tests;
}
/**
* @return mixed[]
*/
protected static function readTestFile(string $file, string $fixturesDir): array
{
2022-02-23 15:57:47 +00:00
$tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE);
2022-08-17 12:20:07 +00:00
$sectionInfo = [
'TEST' => true,
'CONDITION' => false,
'COMPOSER' => true,
'LOCK' => false,
2017-03-08 14:07:29 +00:00
'INSTALLED' => false,
'RUN' => true,
'EXPECT-LOCK' => false,
2020-04-20 19:44:15 +00:00
'EXPECT-INSTALLED' => false,
'EXPECT-OUTPUT' => false,
2020-10-01 14:42:02 +00:00
'EXPECT-OUTPUT-OPTIMIZED' => false,
'EXPECT-EXIT-CODE' => false,
'EXPECT-EXCEPTION' => false,
'EXPECT' => true,
2022-08-17 12:20:07 +00:00
];
$section = null;
2022-08-17 12:20:07 +00:00
$data = [];
2015-02-24 14:22:54 +00:00
foreach ($tokens as $i => $token) {
if (null === $section && empty($token)) {
continue; // skip leading blank
}
if (null === $section) {
if (!isset($sectionInfo[$token])) {
throw new \RuntimeException(sprintf(
'The test file "%s" must not contain a section named "%s".',
str_replace($fixturesDir.'/', '', $file),
$token
));
}
$section = $token;
continue;
}
$sectionData = $token;
$data[$section] = $sectionData;
$section = $sectionData = null;
}
foreach ($sectionInfo as $section => $required) {
if ($required && !isset($data[$section])) {
throw new \RuntimeException(sprintf(
'The test file "%s" must have a section named "%s".',
str_replace($fixturesDir.'/', '', $file),
$section
));
}
}
return $data;
}
}