2020-01-19 18:46:16 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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\DependencyResolver;
|
|
|
|
|
2020-10-01 14:42:02 +00:00
|
|
|
use Composer\DependencyResolver\DefaultPolicy;
|
|
|
|
use Composer\DependencyResolver\Pool;
|
|
|
|
use Composer\DependencyResolver\PoolOptimizer;
|
2021-10-27 19:28:09 +00:00
|
|
|
use Composer\Config;
|
2020-01-19 18:46:16 +00:00
|
|
|
use Composer\IO\NullIO;
|
|
|
|
use Composer\Repository\ArrayRepository;
|
2020-08-28 11:07:11 +00:00
|
|
|
use Composer\Repository\FilterRepository;
|
2020-01-19 18:46:16 +00:00
|
|
|
use Composer\Repository\LockArrayRepository;
|
|
|
|
use Composer\DependencyResolver\Request;
|
|
|
|
use Composer\Package\BasePackage;
|
|
|
|
use Composer\Package\AliasPackage;
|
|
|
|
use Composer\Json\JsonFile;
|
|
|
|
use Composer\Package\Loader\ArrayLoader;
|
|
|
|
use Composer\Package\Version\VersionParser;
|
2021-10-27 19:28:09 +00:00
|
|
|
use Composer\Repository\RepositoryFactory;
|
2020-01-19 18:46:16 +00:00
|
|
|
use Composer\Repository\RepositorySet;
|
|
|
|
use Composer\Test\TestCase;
|
|
|
|
|
|
|
|
class PoolBuilderTest extends TestCase
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @dataProvider getIntegrationTests
|
2021-11-02 13:32:09 +00:00
|
|
|
* @param string $file
|
|
|
|
* @param string $message
|
2020-10-01 14:42:02 +00:00
|
|
|
* @param string[] $expect
|
|
|
|
* @param string[] $expectOptimized
|
2021-11-02 13:32:09 +00:00
|
|
|
* @param mixed[] $root
|
|
|
|
* @param mixed[] $requestData
|
|
|
|
* @param mixed[] $packageRepos
|
|
|
|
* @param mixed[] $fixed
|
2020-01-19 18:46:16 +00:00
|
|
|
*/
|
2020-10-01 14:42:02 +00:00
|
|
|
public function testPoolBuilder($file, $message, $expect, $expectOptimized, $root, $requestData, $packageRepos, $fixed)
|
2020-01-19 18:46:16 +00:00
|
|
|
{
|
|
|
|
$rootAliases = !empty($root['aliases']) ? $root['aliases'] : array();
|
|
|
|
$minimumStability = !empty($root['minimum-stability']) ? $root['minimum-stability'] : 'stable';
|
|
|
|
$stabilityFlags = !empty($root['stability-flags']) ? $root['stability-flags'] : array();
|
2020-06-06 15:16:54 +00:00
|
|
|
$rootReferences = !empty($root['references']) ? $root['references'] : array();
|
2020-01-19 18:46:16 +00:00
|
|
|
$stabilityFlags = array_map(function ($stability) {
|
|
|
|
return BasePackage::$stabilities[$stability];
|
|
|
|
}, $stabilityFlags);
|
|
|
|
|
|
|
|
$parser = new VersionParser();
|
2020-04-08 10:20:18 +00:00
|
|
|
foreach ($rootAliases as $index => $alias) {
|
|
|
|
$rootAliases[$index]['version'] = $parser->normalize($alias['version']);
|
|
|
|
$rootAliases[$index]['alias_normalized'] = $parser->normalize($alias['alias']);
|
2020-01-19 18:46:16 +00:00
|
|
|
}
|
|
|
|
|
2021-10-27 19:28:09 +00:00
|
|
|
$loader = new ArrayLoader(null, true);
|
2020-01-19 18:46:16 +00:00
|
|
|
$packageIds = array();
|
|
|
|
$loadPackage = function ($data) use ($loader, &$packageIds) {
|
2020-10-01 14:42:02 +00:00
|
|
|
/** @var ?int $id */
|
|
|
|
$id = null;
|
2020-01-19 18:46:16 +00:00
|
|
|
if (!empty($data['id'])) {
|
|
|
|
$id = $data['id'];
|
|
|
|
unset($data['id']);
|
|
|
|
}
|
|
|
|
|
|
|
|
$pkg = $loader->load($data);
|
|
|
|
|
|
|
|
if (!empty($id)) {
|
|
|
|
if (!empty($packageIds[$id])) {
|
|
|
|
throw new \LogicException('Duplicate package id '.$id.' defined');
|
|
|
|
}
|
|
|
|
$packageIds[$id] = $pkg;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $pkg;
|
|
|
|
};
|
|
|
|
|
2021-10-27 19:28:09 +00:00
|
|
|
$oldCwd = getcwd();
|
|
|
|
chdir(__DIR__.'/Fixtures/poolbuilder/');
|
|
|
|
|
2020-06-06 15:16:54 +00:00
|
|
|
$repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $rootReferences);
|
2020-08-28 10:15:19 +00:00
|
|
|
foreach ($packageRepos as $packages) {
|
2021-10-27 19:28:09 +00:00
|
|
|
if (isset($packages['type'])) {
|
|
|
|
$repo = RepositoryFactory::createRepo(new NullIO, new Config(false), $packages);
|
|
|
|
$repositorySet->addRepository($repo);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-28 11:07:11 +00:00
|
|
|
$repo = new ArrayRepository();
|
|
|
|
if (isset($packages['canonical']) || isset($packages['only']) || isset($packages['exclude'])) {
|
|
|
|
$options = $packages;
|
|
|
|
$packages = $options['packages'];
|
|
|
|
unset($options['packages']);
|
|
|
|
$repositorySet->addRepository(new FilterRepository($repo, $options));
|
|
|
|
} else {
|
|
|
|
$repositorySet->addRepository($repo);
|
|
|
|
}
|
|
|
|
foreach ($packages as $package) {
|
2020-08-28 10:15:19 +00:00
|
|
|
$repo->addPackage($loadPackage($package));
|
|
|
|
}
|
2020-01-19 18:46:16 +00:00
|
|
|
}
|
2020-08-28 10:15:19 +00:00
|
|
|
$repositorySet->addRepository($lockedRepo = new LockArrayRepository());
|
2020-01-19 18:46:16 +00:00
|
|
|
|
2020-04-29 22:04:55 +00:00
|
|
|
if (isset($requestData['locked'])) {
|
|
|
|
foreach ($requestData['locked'] as $package) {
|
|
|
|
$lockedRepo->addPackage($loadPackage($package));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$request = new Request($lockedRepo);
|
|
|
|
foreach ($requestData['require'] as $package => $constraint) {
|
2020-01-19 22:28:00 +00:00
|
|
|
$request->requireName($package, $parser->parseConstraints($constraint));
|
2020-01-19 18:46:16 +00:00
|
|
|
}
|
2020-04-29 22:04:55 +00:00
|
|
|
if (isset($requestData['allowList'])) {
|
|
|
|
$transitiveDeps = Request::UPDATE_ONLY_LISTED;
|
|
|
|
if (isset($requestData['allowTransitiveDepsNoRootRequire']) && $requestData['allowTransitiveDepsNoRootRequire']) {
|
|
|
|
$transitiveDeps = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
|
|
|
|
}
|
|
|
|
if (isset($requestData['allowTransitiveDeps']) && $requestData['allowTransitiveDeps']) {
|
|
|
|
$transitiveDeps = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
|
|
|
|
}
|
|
|
|
$request->setUpdateAllowList(array_flip($requestData['allowList']), $transitiveDeps);
|
|
|
|
}
|
2020-01-19 18:46:16 +00:00
|
|
|
|
|
|
|
foreach ($fixed as $fixedPackage) {
|
|
|
|
$request->fixPackage($loadPackage($fixedPackage));
|
|
|
|
}
|
|
|
|
|
2020-04-01 13:27:51 +00:00
|
|
|
$pool = $repositorySet->createPool($request, new NullIO());
|
2020-10-01 14:42:02 +00:00
|
|
|
|
|
|
|
$result = $this->getPackageResultSet($pool, $packageIds);
|
|
|
|
|
|
|
|
$this->assertSame($expect, $result, 'Unoptimized pool does not match expected package set');
|
|
|
|
|
|
|
|
$optimizer = new PoolOptimizer(new DefaultPolicy());
|
|
|
|
$result = $this->getPackageResultSet($optimizer->optimize($request, $pool), $packageIds);
|
|
|
|
$this->assertSame($expectOptimized, $result, 'Optimized pool does not match expected package set');
|
2021-11-12 22:25:06 +00:00
|
|
|
|
|
|
|
chdir($oldCwd);
|
2020-10-01 14:42:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array<int, BasePackage> $packageIds
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
private function getPackageResultSet(Pool $pool, $packageIds)
|
|
|
|
{
|
2020-04-07 12:40:51 +00:00
|
|
|
$result = array();
|
2020-01-19 18:46:16 +00:00
|
|
|
for ($i = 1, $count = count($pool); $i <= $count; $i++) {
|
|
|
|
$result[] = $pool->packageById($i);
|
|
|
|
}
|
|
|
|
|
2020-10-01 14:42:02 +00:00
|
|
|
return array_map(function (BasePackage $package) use ($packageIds) {
|
2020-01-19 18:46:16 +00:00
|
|
|
if ($id = array_search($package, $packageIds, true)) {
|
|
|
|
return $id;
|
|
|
|
}
|
|
|
|
|
2020-06-06 13:19:55 +00:00
|
|
|
$suffix = '';
|
2020-06-06 15:16:54 +00:00
|
|
|
if ($package->getSourceReference()) {
|
|
|
|
$suffix = '#'.$package->getSourceReference();
|
|
|
|
}
|
2020-06-06 13:19:55 +00:00
|
|
|
if ($package->getRepository() instanceof LockArrayRepository) {
|
2020-06-06 15:16:54 +00:00
|
|
|
$suffix .= ' (locked)';
|
2020-06-06 13:19:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 15:16:54 +00:00
|
|
|
if ($package instanceof AliasPackage) {
|
|
|
|
if ($id = array_search($package->getAliasOf(), $packageIds, true)) {
|
|
|
|
return (string) $package->getName().'-'.$package->getVersion() . $suffix . ' (alias of '.$id . ')';
|
|
|
|
}
|
|
|
|
|
|
|
|
return (string) $package->getName().'-'.$package->getVersion() . $suffix . ' (alias of '.$package->getAliasOf()->getVersion().')';
|
2020-01-19 18:46:16 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 15:16:54 +00:00
|
|
|
return (string) $package->getName().'-'.$package->getVersion() . $suffix;
|
2020-01-19 18:46:16 +00:00
|
|
|
}, $result);
|
|
|
|
}
|
|
|
|
|
2021-10-30 08:30:36 +00:00
|
|
|
/**
|
|
|
|
* @return array<string, array<string>>
|
|
|
|
*/
|
2020-01-19 18:46:16 +00:00
|
|
|
public function getIntegrationTests()
|
|
|
|
{
|
|
|
|
$fixturesDir = realpath(__DIR__.'/Fixtures/poolbuilder/');
|
|
|
|
$tests = array();
|
|
|
|
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
|
|
|
if (!preg_match('/\.test$/', $file)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$testData = $this->readTestFile($file, $fixturesDir);
|
|
|
|
|
|
|
|
$message = $testData['TEST'];
|
|
|
|
|
|
|
|
$request = JsonFile::parseJson($testData['REQUEST']);
|
|
|
|
$root = !empty($testData['ROOT']) ? JsonFile::parseJson($testData['ROOT']) : array();
|
|
|
|
|
2020-08-28 10:15:19 +00:00
|
|
|
$packageRepos = JsonFile::parseJson($testData['PACKAGE-REPOS']);
|
2020-01-19 18:46:16 +00:00
|
|
|
$fixed = array();
|
|
|
|
if (!empty($testData['FIXED'])) {
|
|
|
|
$fixed = JsonFile::parseJson($testData['FIXED']);
|
|
|
|
}
|
|
|
|
$expect = JsonFile::parseJson($testData['EXPECT']);
|
2020-10-01 14:42:02 +00:00
|
|
|
$expectOptimized = !empty($testData['EXPECT-OPTIMIZED']) ? JsonFile::parseJson($testData['EXPECT-OPTIMIZED']) : $expect;
|
2020-01-19 18:46:16 +00:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
|
|
|
}
|
|
|
|
|
2020-10-01 14:42:02 +00:00
|
|
|
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $expect, $expectOptimized, $root, $request, $packageRepos, $fixed);
|
2020-01-19 18:46:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $tests;
|
|
|
|
}
|
|
|
|
|
2021-10-30 08:30:36 +00:00
|
|
|
/**
|
|
|
|
* @param \SplFileInfo $file
|
|
|
|
* @param string $fixturesDir
|
|
|
|
* @return array<string, string>
|
|
|
|
*/
|
2020-01-19 18:46:16 +00:00
|
|
|
protected function readTestFile(\SplFileInfo $file, $fixturesDir)
|
|
|
|
{
|
PHP 8.1: fix deprecation warnings about incorrect default values (#10036)
* PHP 8.1/Tests: fix some deprecation warnings
The default value for the `preg_split()` `$limit` parameter is `-1`, not `null`.
Fixes numerous `preg_split(): Passing null to parameter #3 ($limit) of type int is deprecated` notices when running the test suite.
Ref: https://www.php.net/manual/en/function.preg-split.php
* PHP 8.1/NoProxyPattern: fix deprecation warning
The default value for the `preg_split()` `$limit` parameter is `-1`, not `null`.
Fixes some `preg_split(): Passing null to parameter #3 ($limit) of type int is deprecated` notices when running the test suite.
```
Deprecation triggered by Composer\Test\Util\Http\ProxyManagerTest::testGetProxyForRequest:
preg_split(): Passing null to parameter #3 ($limit) of type int is deprecated
Stack trace:
0 [internal function]: Symfony\Bridge\PhpUnit\DeprecationErrorHandler->handleError(8192, '...', '...', 42)
1 src/Composer/Util/NoProxyPattern.php(42): preg_split('...', '...', NULL, 1)
2 src/Composer/Util/Http/ProxyManager.php(148): Composer\Util\NoProxyPattern->__construct('...')
3 src/Composer/Util/Http/ProxyManager.php(50): Composer\Util\Http\ProxyManager->initProxyData()
4 src/Composer/Util/Http/ProxyManager.php(59): Composer\Util\Http\ProxyManager->__construct()
5 tests/Composer/Test/Util/Http/ProxyManagerTest.php(75): Composer\Util\Http\ProxyManager::getInstance()
...
```
Ref: https://www.php.net/manual/en/function.preg-split.php
* PHP 8.1: fix deprecation warnings / http_build_query()
This fixes all relevant calls to the PHP native `http_build_query()` function.
The second parameter of which is the _optional_ `$numeric_prefix` parameter which expects a `string`.
A parameter being optional, however, does not automatically make it nullable.
As of PHP 8.1, passing `null` to a non-nullable PHP native function will generate a deprecation notice.
In this case, these function calls yielded a `http_build_query(): Passing null to parameter #2 ($numeric_prefix) of type string is deprecated` notice.
Changing the `null` to an empty string fixes this without BC-break.
Fixes a few deprecation warnings found when running the tests.
Refs:
* https://www.php.net/manual/en/function.http-build-query.php
* https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg
* PHP 8.1: fix deprecation notices / PharData::__construct()
This fixes all relevant calls to the PHP native `PharData::__construct()` method.
The second parameter of this method is the _optional_ `$flags` parameter which expects an `int` of flags to be passed to the `Phar` parent class `RecursiveDirectoryIterator`.
Fixed by passing the default value for the `$flags` parameter as per the `RecursiveDirectoryIterator::__construct()` method.
The third parameter of the method is the _optional_ `$alias` parameter which expects an `string`.
Fixed by passing an empty string.
Fixes various notices along the lines of:
```
Deprecation triggered by Composer\Test\Package\Archiver\ArchiveManagerTest::testArchiveTar:
PharData::__construct(): Passing null to parameter #2 ($flags) of type int is deprecated
Stack trace:
0 [internal function]: Symfony\Bridge\PhpUnit\DeprecationErrorHandler->handleError(8192, '...', '...', 55)
1 src/Composer/Package/Archiver/PharArchiver.php(55): PharData->__construct('...', NULL, NULL, 2)
2 src/Composer/Package/Archiver/ArchiveManager.php(193): Composer\Package\Archiver\PharArchiver->archive('...', '...', '...', Array, false)
3 tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php(65): Composer\Package\Archiver\ArchiveManager->archive(Object(Composer\Package\CompletePackage), '...', '...')
...
```
Refs:
* https://www.php.net/manual/en/phardata.construct.php
* https://www.php.net/manual/en/recursivedirectoryiterator.construct.php
Co-authored-by: jrfnl <jrfnl@users.noreply.github.com>
2021-08-11 11:05:45 +00:00
|
|
|
$tokens = preg_split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), -1, PREG_SPLIT_DELIM_CAPTURE);
|
2020-01-19 18:46:16 +00:00
|
|
|
|
|
|
|
$sectionInfo = array(
|
|
|
|
'TEST' => true,
|
|
|
|
'ROOT' => false,
|
|
|
|
'REQUEST' => true,
|
|
|
|
'FIXED' => false,
|
2020-08-28 10:15:19 +00:00
|
|
|
'PACKAGE-REPOS' => true,
|
2020-01-19 18:46:16 +00:00
|
|
|
'EXPECT' => true,
|
2020-10-01 14:42:02 +00:00
|
|
|
'EXPECT-OPTIMIZED' => false,
|
2020-01-19 18:46:16 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
$section = null;
|
2020-04-07 12:40:51 +00:00
|
|
|
$data = array();
|
2020-01-19 18:46:16 +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;
|
|
|
|
}
|
|
|
|
}
|