mirror of
https://github.com/composer/composer
synced 2025-05-11 01:22:54 +00:00
Implemented PoolOptimizer
This commit is contained in:
parent
7eca450d9b
commit
34183f49f9
30 changed files with 1492 additions and 18 deletions
|
@ -105,3 +105,13 @@ Check that replacers from additional repositories are loaded when doing a partia
|
|||
"shared/dep-1.0.0.0",
|
||||
"shared/dep-1.2.0.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"indirect/replacer-1.2.0.0",
|
||||
"indirect/replacer-1.0.0.0",
|
||||
"replacer/package-1.2.0.0",
|
||||
"replacer/package-1.0.0.0",
|
||||
"base/package-1.0.0.0",
|
||||
"shared/dep-1.2.0.0"
|
||||
]
|
||||
|
|
|
@ -96,3 +96,13 @@ Check that replacers from additional repositories are loaded
|
|||
"replacer/package-1.0.0.0",
|
||||
"shared/dep-1.0.0.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"base/package-1.0.0.0",
|
||||
"indirect/replacer-1.2.0.0",
|
||||
"indirect/replacer-1.0.0.0",
|
||||
"shared/dep-1.2.0.0",
|
||||
"replacer/package-1.2.0.0",
|
||||
"replacer/package-1.0.0.0"
|
||||
]
|
||||
|
|
|
@ -48,3 +48,13 @@ locked packages still need to be taking into account for loading all necessary v
|
|||
"dep/pkg1-1.0.1.0",
|
||||
"dep/pkg1-2.0.0.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"root/req1-1.0.0.0 (locked)",
|
||||
"root/req2-1.0.0.0 (locked)",
|
||||
"dep/pkg2-1.0.0.0",
|
||||
"dep/pkg2-1.2.0.0",
|
||||
"dep/pkg1-1.0.1.0",
|
||||
"dep/pkg1-2.0.0.0"
|
||||
]
|
||||
|
|
|
@ -50,3 +50,12 @@ Fixed packages and replacers get unfixed correctly (refs https://github.com/comp
|
|||
"replaced/pkg-1.2.3.0",
|
||||
"replaced/pkg-1.2.4.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"root/req3-1.0.0.0 (locked)",
|
||||
"dep/dep-2.3.5.0 (locked)",
|
||||
"root/req1-1.1.0.0",
|
||||
"replacer/pkg-1.1.0.0",
|
||||
"replaced/pkg-1.2.4.0"
|
||||
]
|
||||
|
|
|
@ -46,3 +46,10 @@ Stability flags apply
|
|||
6,
|
||||
"default/pkg-1.2.0.0 (alias of 6)"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
1,
|
||||
6,
|
||||
"default/pkg-1.2.0.0 (alias of 6)"
|
||||
]
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
--TEST--
|
||||
Test aliased and aliasees remain untouched if either is required, but are still optimized away otherwise.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0",
|
||||
"package/required-aliasof-and-alias": "dev-main-both",
|
||||
"package/required-aliasof": "dev-main-direct",
|
||||
"package/required-alias": "1.*"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/required-aliasof-and-alias": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof-and-alias",
|
||||
"version": "dev-main-both",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-both": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof",
|
||||
"version": "dev-main-direct",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-direct": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-alias",
|
||||
"version": "dev-main-alias",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-alias": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/not-referenced",
|
||||
"version": "dev-lonesome-pkg",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-lonesome-pkg": "1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof-and-alias",
|
||||
"version": "dev-main-both",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-both": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof",
|
||||
"version": "dev-main-direct",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-direct": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-alias",
|
||||
"version": "dev-main-alias",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-alias": "1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,46 @@
|
|||
--TEST--
|
||||
Test filters irrelevant package "package/b" in version 1.0.0
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
--TEST--
|
||||
Test filters irrelevant package "package/b" in version 1.0.1 because prefer-lowest
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
},
|
||||
"preferLowest": true
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
--TEST--
|
||||
We have to make sure, conflicts are considered in the grouping so we do not remove packages
|
||||
from the pool which might end up being part of the solution.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"nesty/nest": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0 || 1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.1 || 1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.2.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0 || 1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,103 @@
|
|||
--TEST--
|
||||
We have to make sure, conflicts are considered in the grouping so we do not remove packages
|
||||
from the pool which might end up being part of the solution.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"nesty/nest": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.2.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0 || 1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,99 @@
|
|||
--TEST--
|
||||
We are not allowed to group packages only by their dependency definition. It's also relevant what other
|
||||
packages require (package/b@1.0.1 must not be dropped although it has the very same definition as 2.0.0 and both are
|
||||
allowed by the request). However, package/b@1.0.0 can be removed.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0 || ^2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "2.0.0",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/d": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/d",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": ">=1.0 <1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0 || ^2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "2.0.0",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/d": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/d",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": ">=1.0 <1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
--TEST--
|
||||
Test locked and fixed packages remain untouched.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
},
|
||||
"locked": [
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
],
|
||||
"fixed": [
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,59 @@
|
|||
--TEST--
|
||||
Test replaced packages are correctly removed.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0",
|
||||
"replace": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"replace": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"replace": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -12,6 +12,9 @@
|
|||
|
||||
namespace Composer\Test\DependencyResolver;
|
||||
|
||||
use Composer\DependencyResolver\DefaultPolicy;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\PoolOptimizer;
|
||||
use Composer\IO\NullIO;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
use Composer\Repository\FilterRepository;
|
||||
|
@ -31,13 +34,14 @@ class PoolBuilderTest extends TestCase
|
|||
* @dataProvider getIntegrationTests
|
||||
* @param string $file
|
||||
* @param string $message
|
||||
* @param mixed[] $expect
|
||||
* @param string[] $expect
|
||||
* @param string[] $expectOptimized
|
||||
* @param mixed[] $root
|
||||
* @param mixed[] $requestData
|
||||
* @param mixed[] $packageRepos
|
||||
* @param mixed[] $fixed
|
||||
*/
|
||||
public function testPoolBuilder($file, $message, $expect, $root, $requestData, $packageRepos, $fixed)
|
||||
public function testPoolBuilder($file, $message, $expect, $expectOptimized, $root, $requestData, $packageRepos, $fixed)
|
||||
{
|
||||
$rootAliases = !empty($root['aliases']) ? $root['aliases'] : array();
|
||||
$minimumStability = !empty($root['minimum-stability']) ? $root['minimum-stability'] : 'stable';
|
||||
|
@ -56,6 +60,8 @@ class PoolBuilderTest extends TestCase
|
|||
$loader = new ArrayLoader();
|
||||
$packageIds = array();
|
||||
$loadPackage = function ($data) use ($loader, &$packageIds) {
|
||||
/** @var ?int $id */
|
||||
$id = null;
|
||||
if (!empty($data['id'])) {
|
||||
$id = $data['id'];
|
||||
unset($data['id']);
|
||||
|
@ -115,12 +121,28 @@ class PoolBuilderTest extends TestCase
|
|||
}
|
||||
|
||||
$pool = $repositorySet->createPool($request, new NullIO());
|
||||
|
||||
$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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, BasePackage> $packageIds
|
||||
* @return string[]
|
||||
*/
|
||||
private function getPackageResultSet(Pool $pool, $packageIds)
|
||||
{
|
||||
$result = array();
|
||||
for ($i = 1, $count = count($pool); $i <= $count; $i++) {
|
||||
$result[] = $pool->packageById($i);
|
||||
}
|
||||
|
||||
$result = array_map(function ($package) use ($packageIds) {
|
||||
return array_map(function (BasePackage $package) use ($packageIds) {
|
||||
if ($id = array_search($package, $packageIds, true)) {
|
||||
return $id;
|
||||
}
|
||||
|
@ -143,8 +165,6 @@ class PoolBuilderTest extends TestCase
|
|||
|
||||
return (string) $package->getName().'-'.$package->getVersion() . $suffix;
|
||||
}, $result);
|
||||
|
||||
$this->assertSame($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,11 +193,12 @@ class PoolBuilderTest extends TestCase
|
|||
$fixed = JsonFile::parseJson($testData['FIXED']);
|
||||
}
|
||||
$expect = JsonFile::parseJson($testData['EXPECT']);
|
||||
$expectOptimized = !empty($testData['EXPECT-OPTIMIZED']) ? JsonFile::parseJson($testData['EXPECT-OPTIMIZED']) : $expect;
|
||||
} catch (\Exception $e) {
|
||||
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
||||
}
|
||||
|
||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $expect, $root, $request, $packageRepos, $fixed);
|
||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $expect, $expectOptimized, $root, $request, $packageRepos, $fixed);
|
||||
}
|
||||
|
||||
return $tests;
|
||||
|
@ -199,6 +220,7 @@ class PoolBuilderTest extends TestCase
|
|||
'FIXED' => false,
|
||||
'PACKAGE-REPOS' => true,
|
||||
'EXPECT' => true,
|
||||
'EXPECT-OPTIMIZED' => false,
|
||||
);
|
||||
|
||||
$section = null;
|
||||
|
|
197
tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php
Normal file
197
tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php
Normal file
|
@ -0,0 +1,197 @@
|
|||
<?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;
|
||||
|
||||
use Composer\DependencyResolver\DefaultPolicy;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\PoolOptimizer;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\Loader\ArrayLoader;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Repository\LockArrayRepository;
|
||||
use Composer\Test\TestCase;
|
||||
|
||||
class PoolOptimizerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideIntegrationTests
|
||||
* @param mixed[] $requestData
|
||||
* @param BasePackage[] $packagesBefore
|
||||
* @param BasePackage[] $expectedPackages
|
||||
* @param string $message
|
||||
*/
|
||||
public function testPoolOptimizer(array $requestData, array $packagesBefore, array $expectedPackages, $message)
|
||||
{
|
||||
$lockedRepo = new LockArrayRepository();
|
||||
|
||||
$request = new Request($lockedRepo);
|
||||
$parser = new VersionParser();
|
||||
|
||||
if (isset($requestData['locked'])) {
|
||||
foreach ($requestData['locked'] as $package) {
|
||||
$request->lockPackage($this->loadPackage($package));
|
||||
}
|
||||
}
|
||||
if (isset($requestData['fixed'])) {
|
||||
foreach ($requestData['fixed'] as $package) {
|
||||
$request->fixPackage($this->loadPackage($package));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requestData['require'] as $package => $constraint) {
|
||||
$request->requireName($package, $parser->parseConstraints($constraint));
|
||||
}
|
||||
|
||||
$preferStable = isset($requestData['preferStable']) ? $requestData['preferStable'] : false;
|
||||
$preferLowest = isset($requestData['preferLowest']) ? $requestData['preferLowest'] : false;
|
||||
|
||||
$pool = new Pool($packagesBefore);
|
||||
$poolOptimizer = new PoolOptimizer(new DefaultPolicy($preferStable, $preferLowest));
|
||||
|
||||
$pool = $poolOptimizer->optimize($request, $pool);
|
||||
|
||||
$this->assertSame(
|
||||
$this->reducePackagesInfoForComparison($expectedPackages),
|
||||
$this->reducePackagesInfoForComparison($pool->getPackages()),
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
public function provideIntegrationTests()
|
||||
{
|
||||
$fixturesDir = realpath(__DIR__.'/Fixtures/pooloptimizer/');
|
||||
$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'];
|
||||
$requestData = JsonFile::parseJson($testData['REQUEST']);
|
||||
$packagesBefore = $this->loadPackages(JsonFile::parseJson($testData['POOL-BEFORE']));
|
||||
$expectedPackages = $this->loadPackages(JsonFile::parseJson($testData['POOL-AFTER']));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
||||
}
|
||||
|
||||
$tests[basename($file)] = array($requestData, $packagesBefore, $expectedPackages, $message);
|
||||
}
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fixturesDir
|
||||
* @return mixed[]
|
||||
*/
|
||||
protected function readTestFile(\SplFileInfo $file, $fixturesDir)
|
||||
{
|
||||
$tokens = preg_split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
/** @var array<string, bool> $sectionInfo */
|
||||
$sectionInfo = array(
|
||||
'TEST' => true,
|
||||
'REQUEST' => true,
|
||||
'POOL-BEFORE' => true,
|
||||
'POOL-AFTER' => true,
|
||||
);
|
||||
|
||||
$section = null;
|
||||
$data = array();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BasePackage[] $packages
|
||||
* @return string[]
|
||||
*/
|
||||
private function reducePackagesInfoForComparison(array $packages)
|
||||
{
|
||||
$packagesInfo = array();
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$packagesInfo[] = $package->getName() . '@' . $package->getVersion() . ($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getVersion().')' : '');
|
||||
}
|
||||
|
||||
sort($packagesInfo);
|
||||
|
||||
return $packagesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[][] $packagesData
|
||||
* @return BasePackage[]
|
||||
*/
|
||||
private function loadPackages(array $packagesData)
|
||||
{
|
||||
$packages = array();
|
||||
|
||||
foreach ($packagesData as $packageData) {
|
||||
$packages[] = $package = $this->loadPackage($packageData);
|
||||
if ($package instanceof AliasPackage) {
|
||||
$packages[] = $package->getAliasOf();
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $packageData
|
||||
* @return BasePackage
|
||||
*/
|
||||
private function loadPackage(array $packageData)
|
||||
{
|
||||
$loader = new ArrayLoader();
|
||||
return $loader->load($packageData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
--TEST--
|
||||
|
||||
Test that a package which has a conflict does not get installed and has to be downgraded
|
||||
|
||||
--COMPOSER--
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "nesty/nest", "version": "1.0.0", "require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
} },
|
||||
{ "name": "conflicter/pkg", "version": "1.0.1", "conflict": { "victim/pkg": "1.1.0"} },
|
||||
{ "name": "victim/pkg", "version": "1.0.0" },
|
||||
{ "name": "victim/pkg", "version": "1.0.1" },
|
||||
{ "name": "victim/pkg", "version": "1.0.2" },
|
||||
{ "name": "victim/pkg", "version": "1.1.0" },
|
||||
{ "name": "victim/pkg", "version": "1.2.0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"nesty/nest": "*"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--RUN--
|
||||
update
|
||||
|
||||
--EXPECT-EXIT-CODE--
|
||||
0
|
||||
|
||||
--EXPECT--
|
||||
Installing victim/pkg (1.0.2)
|
||||
Installing conflicter/pkg (1.0.1)
|
||||
Installing nesty/nest (1.0.0)
|
|
@ -0,0 +1,35 @@
|
|||
--TEST--
|
||||
|
||||
Test that a package which has a conflict does not get installed and has to be downgraded
|
||||
|
||||
--COMPOSER--
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "conflicter/pkg", "version": "1.0.1", "conflict": { "victim/pkg": "1.1.0"} },
|
||||
{ "name": "victim/pkg", "version": "1.0.0" },
|
||||
{ "name": "victim/pkg", "version": "1.0.1" },
|
||||
{ "name": "victim/pkg", "version": "1.0.2" },
|
||||
{ "name": "victim/pkg", "version": "1.1.0" },
|
||||
{ "name": "victim/pkg", "version": "1.2.0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--RUN--
|
||||
update
|
||||
|
||||
--EXPECT-EXIT-CODE--
|
||||
0
|
||||
|
||||
--EXPECT--
|
||||
Installing conflicter/pkg (1.0.1)
|
||||
Installing victim/pkg (1.0.2)
|
|
@ -44,5 +44,14 @@ Your requirements could not be resolved to an installable set of packages.
|
|||
- package/a[2.0.0, ..., 2.6.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint.
|
||||
- Root composer.json requires package/a * -> satisfiable by package/a[2.0.0, ..., 2.6.0].
|
||||
|
||||
--EXPECT-OUTPUT-OPTIMIZED--
|
||||
Loading composer repositories with package information
|
||||
Updating dependencies
|
||||
Your requirements could not be resolved to an installable set of packages.
|
||||
|
||||
Problem 1
|
||||
- Root composer.json requires package/a * -> satisfiable by package/a[2.6.0].
|
||||
- package/a 2.6.0 requires missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint.
|
||||
|
||||
--EXPECT--
|
||||
|
||||
|
|
|
@ -128,6 +128,21 @@ Your requirements could not be resolved to an installable set of packages.
|
|||
- You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.7, v2.8.8, v3.1.9, ..., v3.4.29].
|
||||
- illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.9, v3.1.10].
|
||||
- Conclusion: don't install symfony/console v3.1.10 (conflict analysis result)
|
||||
|
||||
--EXPECT-OUTPUT-OPTIMIZED--
|
||||
Loading composer repositories with package information
|
||||
Updating dependencies
|
||||
Your requirements could not be resolved to an installable set of packages.
|
||||
|
||||
Problem 1
|
||||
- Root composer.json requires illuminate/queue * -> satisfiable by illuminate/queue[v5.2.0].
|
||||
- illuminate/queue v5.2.0 requires illuminate/console 5.2.* -> satisfiable by illuminate/console[v5.2.25, v5.2.26].
|
||||
- illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.10].
|
||||
- illuminate/console v5.2.26 requires symfony/console 2.8.* -> satisfiable by symfony/console[v2.8.8].
|
||||
- You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.8, v3.1.10, v3.4.29].
|
||||
- friendsofphp/php-cs-fixer v2.10.5 requires symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.4.29].
|
||||
- Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.5].
|
||||
|
||||
--EXPECT--
|
||||
|
||||
--EXPECT-EXIT-CODE--
|
||||
|
|
|
@ -44,5 +44,15 @@ Your requirements could not be resolved to an installable set of packages.
|
|||
- Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3].
|
||||
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
|
||||
|
||||
--EXPECT-OUTPUT-OPTIMIZED--
|
||||
Loading composer repositories with package information
|
||||
Updating dependencies
|
||||
Your requirements could not be resolved to an installable set of packages.
|
||||
|
||||
Problem 1
|
||||
- Only one of these can be installed: regular/pkg[1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it.
|
||||
- Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.3].
|
||||
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
|
||||
|
||||
--EXPECT--
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ use Composer\Package\Locker;
|
|||
use Composer\Test\Mock\FactoryMock;
|
||||
use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
|
||||
use Composer\Test\Mock\InstallationManagerMock;
|
||||
use Composer\Util\Platform;
|
||||
use Symfony\Component\Console\Input\StringInput;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
@ -56,6 +57,8 @@ class InstallerTest extends TestCase
|
|||
|
||||
public function tearDown()
|
||||
{
|
||||
Platform::clearEnv('COMPOSER_POOL_OPTIMIZER');
|
||||
|
||||
chdir($this->prevCwd);
|
||||
if (isset($this->tempComposerHome) && is_dir($this->tempComposerHome)) {
|
||||
$fs = new Filesystem;
|
||||
|
@ -228,12 +231,15 @@ class InstallerTest extends TestCase
|
|||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param ?string $expectOutputOptimized
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
*/
|
||||
public function testSlowIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult)
|
||||
public function testSlowIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult)
|
||||
{
|
||||
return $this->testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '0');
|
||||
|
||||
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -248,10 +254,56 @@ class InstallerTest extends TestCase
|
|||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param ?string $expectOutputOptimized
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
*/
|
||||
public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult)
|
||||
public function testIntegrationWithPoolOptimizer($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult)
|
||||
{
|
||||
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '1');
|
||||
|
||||
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutputOptimized ?: $expectOutput, $expect, $expectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideIntegrationTests
|
||||
* @param string $file
|
||||
* @param string $message
|
||||
* @param ?string $condition
|
||||
* @param Config $composerConfig
|
||||
* @param ?mixed[] $lock
|
||||
* @param ?mixed[] $installed
|
||||
* @param string $run
|
||||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param ?string $expectOutputOptimized
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
*/
|
||||
public function testIntegrationWithRawPool($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult)
|
||||
{
|
||||
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '0');
|
||||
|
||||
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string $message
|
||||
* @param ?string $condition
|
||||
* @param Config $composerConfig
|
||||
* @param ?mixed[] $lock
|
||||
* @param ?mixed[] $installed
|
||||
* @param string $run
|
||||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
* @return void
|
||||
*/
|
||||
private function doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult)
|
||||
{
|
||||
if ($condition) {
|
||||
eval('$res = '.$condition.';');
|
||||
|
@ -518,6 +570,7 @@ class InstallerTest extends TestCase
|
|||
$expectInstalled = JsonFile::parseJson($testData['EXPECT-INSTALLED']);
|
||||
}
|
||||
$expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
|
||||
$expectOutputOptimized = isset($testData['EXPECT-OUTPUT-OPTIMIZED']) ? $testData['EXPECT-OUTPUT-OPTIMIZED'] : null;
|
||||
$expect = $testData['EXPECT'];
|
||||
if (!empty($testData['EXPECT-EXCEPTION'])) {
|
||||
$expectResult = $testData['EXPECT-EXCEPTION'];
|
||||
|
@ -533,7 +586,7 @@ class InstallerTest extends TestCase
|
|||
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
||||
}
|
||||
|
||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult);
|
||||
}
|
||||
|
||||
return $tests;
|
||||
|
@ -557,6 +610,7 @@ class InstallerTest extends TestCase
|
|||
'EXPECT-LOCK' => false,
|
||||
'EXPECT-INSTALLED' => false,
|
||||
'EXPECT-OUTPUT' => false,
|
||||
'EXPECT-OUTPUT-OPTIMIZED' => false,
|
||||
'EXPECT-EXIT-CODE' => false,
|
||||
'EXPECT-EXCEPTION' => false,
|
||||
'EXPECT' => true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue