From 010bad5428b40ca69b0d8db025eacc48f6391609 Mon Sep 17 00:00:00 2001 From: Jason Woods Date: Wed, 29 Dec 2021 12:21:36 +0000 Subject: [PATCH] fix: If a replacer is updated to a version that no longer replaces, the replaced package is not loaded --- .../DependencyResolver/PoolBuilder.php | 8 +++ ...-replaced-package-if-replacer-dropped.test | 50 +++++++++++++++++++ .../DependencyResolver/PoolBuilderTest.php | 4 ++ 3 files changed, 62 insertions(+) create mode 100644 tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/load-replaced-package-if-replacer-dropped.test diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 9de4bed71..8f5f5cd15 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -639,6 +639,8 @@ class PoolBuilder // make sure that any requirements for this package by other locked or fixed packages are now // also loaded, as they were previously ignored because the locked (now unlocked) package already // satisfied their requirements + // and if this package is replacing another that is required by a locked or fixed package, ensure + // that we load that replaced package in case an update to this package removes the replacement foreach ($request->getFixedOrLockedPackages() as $fixedOrLockedPackage) { if ($fixedOrLockedPackage === $lockedPackage) { continue; @@ -649,6 +651,12 @@ class PoolBuilder if (isset($requires[$lockedPackage->getName()])) { $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint()); } + foreach ($lockedPackage->getReplaces() as $replace) { + if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) { + $this->unlockPackage($request, $repositories, $replace->getTarget()); + $this->markPackageNameForLoading($request, $replace->getTarget(), $requires[$replace->getTarget()]->getConstraint()); + } + } } } } diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/load-replaced-package-if-replacer-dropped.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/load-replaced-package-if-replacer-dropped.test new file mode 100644 index 000000000..6c148c1d8 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/load-replaced-package-if-replacer-dropped.test @@ -0,0 +1,50 @@ +--TEST-- +Ensure that a package gets loaded which was previously skipped due to replacement + +--REQUEST-- +{ + "require": { + "root/dep": "*", + "root/no-update": "*" + }, + "locked": [ + {"name": "root/dep", "version": "1.1.0", "require": {"replacer/pkg": "1.*"}}, + {"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}}, + {"name": "root/no-update", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}} + ], + "allowList": [ + "root/dep" + ], + "allowTransitiveDepsNoRootRequire": true +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/dep", "version": "1.2.0", "require": {"replacer/pkg": ">=1.1.0"}}, + {"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}}, + {"name": "replacer/pkg", "version": "1.1.0"}, + {"name": "replaced/pkg", "version": "1.0.0"}, + {"name": "root/no-update", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}} + ] +] + +--EXPECT-- +[ + "root/no-update-1.0.0.0 (locked)", + "root/dep-1.2.0.0", + "replaced/pkg-1.0.0.0", + "replacer/pkg-1.1.0.0" +] + +--EXPECT-OPTIMIZED-- +[ + "root/no-update-1.0.0.0 (locked)", + "root/dep-1.2.0.0", + "replaced/pkg-1.0.0.0", + "replacer/pkg-1.1.0.0" +] diff --git a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php index db7b0390e..433008ddc 100644 --- a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php @@ -137,10 +137,14 @@ class PoolBuilderTest extends TestCase $result = $this->getPackageResultSet($pool, $packageIds); + sort($expect); + sort($result); $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); + sort($expectOptimized); + sort($result); $this->assertSame($expectOptimized, $result, 'Optimized pool does not match expected package set'); chdir($oldCwd);