2022-05-27 12:51:46 +00:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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\Command;
|
|
|
|
|
2024-08-21 16:14:40 +00:00
|
|
|
use Composer\Package\Link;
|
|
|
|
use Composer\Semver\Constraint\MatchAllConstraint;
|
2022-05-27 12:51:46 +00:00
|
|
|
use Composer\Test\TestCase;
|
2023-12-08 17:26:05 +00:00
|
|
|
use InvalidArgumentException;
|
2022-05-27 12:51:46 +00:00
|
|
|
|
|
|
|
class UpdateCommandTest extends TestCase
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @dataProvider provideUpdates
|
|
|
|
* @param array<mixed> $composerJson
|
|
|
|
* @param array<mixed> $command
|
|
|
|
*/
|
2024-09-18 13:34:25 +00:00
|
|
|
public function testUpdate(array $composerJson, array $command, string $expected, bool $createLock = false): void
|
2022-05-27 12:51:46 +00:00
|
|
|
{
|
|
|
|
$this->initTempComposer($composerJson);
|
|
|
|
|
2024-09-18 13:34:25 +00:00
|
|
|
if ($createLock) {
|
|
|
|
$this->createComposerLock();
|
|
|
|
}
|
|
|
|
|
2022-05-27 12:51:46 +00:00
|
|
|
$appTester = $this->getApplicationTester();
|
2022-06-22 13:14:00 +00:00
|
|
|
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true], $command));
|
2022-05-27 12:51:46 +00:00
|
|
|
|
2024-05-29 21:12:06 +00:00
|
|
|
self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true)));
|
2022-05-27 12:51:46 +00:00
|
|
|
}
|
|
|
|
|
2022-11-24 13:39:08 +00:00
|
|
|
public static function provideUpdates(): \Generator
|
2022-05-27 12:51:46 +00:00
|
|
|
{
|
|
|
|
$rootDepAndTransitiveDep = [
|
|
|
|
'repositories' => [
|
|
|
|
'packages' => [
|
|
|
|
'type' => 'package',
|
|
|
|
'package' => [
|
|
|
|
['name' => 'root/req', 'version' => '1.0.0', 'require' => ['dep/pkg' => '^1']],
|
|
|
|
['name' => 'dep/pkg', 'version' => '1.0.0'],
|
|
|
|
['name' => 'dep/pkg', 'version' => '1.0.1'],
|
|
|
|
['name' => 'dep/pkg', 'version' => '1.0.2'],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'require' => [
|
|
|
|
'root/req' => '1.*',
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
yield 'simple update' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
[],
|
|
|
|
<<<OUTPUT
|
|
|
|
Loading composer repositories with package information
|
|
|
|
Updating dependencies
|
|
|
|
Lock file operations: 2 installs, 0 updates, 0 removals
|
|
|
|
- Locking dep/pkg (1.0.2)
|
|
|
|
- Locking root/req (1.0.0)
|
|
|
|
Installing dependencies from lock file (including require-dev)
|
|
|
|
Package operations: 2 installs, 0 updates, 0 removals
|
|
|
|
- Installing dep/pkg (1.0.2)
|
|
|
|
- Installing root/req (1.0.0)
|
|
|
|
OUTPUT
|
|
|
|
];
|
|
|
|
|
2023-12-19 16:17:48 +00:00
|
|
|
yield 'simple update with very verbose output' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
['-vv' => true],
|
|
|
|
<<<OUTPUT
|
|
|
|
Loading composer repositories with package information
|
|
|
|
Pool optimizer completed in %f seconds
|
|
|
|
Found %d package versions referenced in your dependency graph. %d (%d%%) were optimized away.
|
|
|
|
Updating dependencies
|
|
|
|
Dependency resolution completed in %f seconds
|
|
|
|
Analyzed %d packages to resolve dependencies
|
|
|
|
Analyzed %d rules to resolve dependencies
|
|
|
|
Lock file operations: 2 installs, 0 updates, 0 removals
|
|
|
|
Installs: dep/pkg:1.0.2, root/req:1.0.0
|
|
|
|
- Locking dep/pkg (1.0.2) from package repo (defining 4 packages)
|
|
|
|
- Locking root/req (1.0.0) from package repo (defining 4 packages)
|
|
|
|
Installing dependencies from lock file (including require-dev)
|
|
|
|
Package operations: 2 installs, 0 updates, 0 removals
|
|
|
|
Installs: dep/pkg:1.0.2, root/req:1.0.0
|
|
|
|
- Installing dep/pkg (1.0.2)
|
|
|
|
- Installing root/req (1.0.0)
|
|
|
|
OUTPUT
|
|
|
|
];
|
|
|
|
|
2022-05-27 12:51:46 +00:00
|
|
|
yield 'update with temporary constraint + --no-install' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
['--with' => ['dep/pkg:1.0.0'], '--no-install' => true],
|
|
|
|
<<<OUTPUT
|
|
|
|
Loading composer repositories with package information
|
|
|
|
Updating dependencies
|
|
|
|
Lock file operations: 2 installs, 0 updates, 0 removals
|
|
|
|
- Locking dep/pkg (1.0.0)
|
|
|
|
- Locking root/req (1.0.0)
|
|
|
|
OUTPUT
|
|
|
|
];
|
|
|
|
|
|
|
|
yield 'update with temporary constraint failing resolution' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
['--with' => ['dep/pkg:^2']],
|
|
|
|
<<<OUTPUT
|
|
|
|
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 root/req 1.* -> satisfiable by root/req[1.0.0].
|
|
|
|
- root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).
|
2023-10-26 09:38:02 +00:00
|
|
|
OUTPUT
|
|
|
|
];
|
|
|
|
|
|
|
|
yield 'update with temporary constraint failing resolution on root package' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
['--with' => ['root/req:^2']],
|
|
|
|
<<<OUTPUT
|
|
|
|
The temporary constraint "^2" for "root/req" must be a subset of the constraint in your composer.json (1.*)
|
|
|
|
Run `composer require root/req` or `composer require root/req:^2` instead to replace the constraint
|
2022-05-27 12:51:46 +00:00
|
|
|
OUTPUT
|
|
|
|
];
|
2024-09-18 13:34:25 +00:00
|
|
|
|
|
|
|
yield 'update & bump' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
['--bump-after-update' => true],
|
|
|
|
<<<OUTPUT
|
|
|
|
Loading composer repositories with package information
|
|
|
|
Updating dependencies
|
|
|
|
Lock file operations: 2 installs, 0 updates, 0 removals
|
|
|
|
- Locking dep/pkg (1.0.2)
|
|
|
|
- Locking root/req (1.0.0)
|
|
|
|
Installing dependencies from lock file (including require-dev)
|
|
|
|
Package operations: 2 installs, 0 updates, 0 removals
|
|
|
|
- Installing dep/pkg (1.0.2)
|
|
|
|
- Installing root/req (1.0.0)
|
|
|
|
Bumping dependencies
|
|
|
|
<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>
|
|
|
|
<warning>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</warning>
|
|
|
|
<warning>Alternatively you can use --dev-only to only bump dependencies within "require-dev".</warning>
|
|
|
|
No requirements to update in ./composer.json.
|
|
|
|
OUTPUT
|
|
|
|
, true
|
|
|
|
];
|
|
|
|
|
|
|
|
yield 'update & bump dev only' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
['--bump-after-update' => 'dev'],
|
|
|
|
<<<OUTPUT
|
|
|
|
Loading composer repositories with package information
|
|
|
|
Updating dependencies
|
|
|
|
Lock file operations: 2 installs, 0 updates, 0 removals
|
|
|
|
- Locking dep/pkg (1.0.2)
|
|
|
|
- Locking root/req (1.0.0)
|
|
|
|
Installing dependencies from lock file (including require-dev)
|
|
|
|
Package operations: 2 installs, 0 updates, 0 removals
|
|
|
|
- Installing dep/pkg (1.0.2)
|
|
|
|
- Installing root/req (1.0.0)
|
|
|
|
Bumping dependencies
|
|
|
|
No requirements to update in ./composer.json.
|
|
|
|
OUTPUT
|
|
|
|
, true
|
|
|
|
];
|
|
|
|
|
|
|
|
yield 'update & dump with failing update' => [
|
|
|
|
$rootDepAndTransitiveDep,
|
|
|
|
['--with' => ['dep/pkg:^2'], '--bump-after-update' => true],
|
|
|
|
<<<OUTPUT
|
|
|
|
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 root/req 1.* -> satisfiable by root/req[1.0.0].
|
|
|
|
- root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).
|
|
|
|
OUTPUT
|
|
|
|
];
|
2024-09-21 11:54:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testUpdateWithPatchOnly(): void
|
|
|
|
{
|
|
|
|
$this->initTempComposer([
|
|
|
|
'repositories' => [
|
|
|
|
'packages' => [
|
|
|
|
'type' => 'package',
|
|
|
|
'package' => [
|
|
|
|
['name' => 'root/req', 'version' => '1.0.0'],
|
|
|
|
['name' => 'root/req', 'version' => '1.0.1'],
|
|
|
|
['name' => 'root/req', 'version' => '1.1.0'],
|
|
|
|
['name' => 'root/req2', 'version' => '1.0.0'],
|
|
|
|
['name' => 'root/req2', 'version' => '1.0.1'],
|
|
|
|
['name' => 'root/req2', 'version' => '1.1.0'],
|
|
|
|
['name' => 'root/req3', 'version' => '1.0.0'],
|
|
|
|
['name' => 'root/req3', 'version' => '1.0.1'],
|
|
|
|
['name' => 'root/req3', 'version' => '1.1.0'],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'require' => [
|
|
|
|
'root/req' => '1.*',
|
|
|
|
'root/req2' => '1.*',
|
|
|
|
'root/req3' => '1.*',
|
|
|
|
],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$package = self::getPackage('root/req', '1.0.0');
|
|
|
|
$package2 = self::getPackage('root/req2', '1.0.0');
|
|
|
|
$package3 = self::getPackage('root/req3', '1.0.0');
|
|
|
|
$this->createComposerLock([$package, $package2, $package3]);
|
2024-09-18 13:34:25 +00:00
|
|
|
|
2024-09-21 11:54:03 +00:00
|
|
|
$appTester = $this->getApplicationTester();
|
|
|
|
// root/req fails because of incompatible --with requirement
|
|
|
|
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true, '--no-install' => true, '--patch-only' => true, '--with' => ['root/req:^1.1']]));
|
|
|
|
|
|
|
|
$expected = <<<OUTPUT
|
|
|
|
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 root/req 1.*, found root/req[1.0.0, 1.0.1, 1.1.0] but it conflicts with your temporary update constraint (root/req:[[>= 1.1.0.0-dev < 2.0.0.0-dev] [>= 1.0.0.0-dev < 1.1.0.0-dev]]).
|
|
|
|
OUTPUT;
|
|
|
|
|
|
|
|
self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true)));
|
|
|
|
|
|
|
|
$appTester = $this->getApplicationTester();
|
|
|
|
// root/req upgrades to 1.0.1 as that is compatible with the --with requirement now
|
|
|
|
// root/req2 upgrades to 1.0.1 only due to --patch-only
|
|
|
|
// root/req3 does not update as it is not in the allowlist
|
|
|
|
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true, '--no-install' => true, '--patch-only' => true, '--with' => ['root/req:^1.0.1'], 'packages' => ['root/req', 'root/req2']]));
|
|
|
|
|
|
|
|
$expected = <<<OUTPUT
|
|
|
|
Loading composer repositories with package information
|
|
|
|
Updating dependencies
|
|
|
|
Lock file operations: 0 installs, 2 updates, 0 removals
|
|
|
|
- Upgrading root/req (1.0.0 => 1.0.1)
|
|
|
|
- Upgrading root/req2 (1.0.0 => 1.0.1)
|
|
|
|
OUTPUT;
|
|
|
|
|
|
|
|
self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true)));
|
2022-05-27 12:51:46 +00:00
|
|
|
}
|
2023-12-08 17:26:05 +00:00
|
|
|
|
2024-09-18 08:43:42 +00:00
|
|
|
public function testInteractiveModeThrowsIfNoPackageToUpdate(): void
|
|
|
|
{
|
|
|
|
$this->initTempComposer([
|
|
|
|
'repositories' => [
|
|
|
|
'packages' => [
|
|
|
|
'type' => 'package',
|
|
|
|
'package' => [
|
|
|
|
['name' => 'root/req', 'version' => '1.0.0'],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'require' => [
|
|
|
|
'root/req' => '1.*',
|
|
|
|
],
|
|
|
|
]);
|
|
|
|
$this->createComposerLock([self::getPackage('root/req', '1.0.0')]);
|
|
|
|
self::expectExceptionMessage('Could not find any package with new versions available');
|
|
|
|
|
|
|
|
$appTester = $this->getApplicationTester();
|
|
|
|
$appTester->setInputs(['']);
|
|
|
|
$appTester->run(['command' => 'update', '--interactive' => true]);
|
|
|
|
}
|
|
|
|
|
2023-12-08 17:26:05 +00:00
|
|
|
public function testInteractiveModeThrowsIfNoPackageEntered(): void
|
|
|
|
{
|
2024-09-18 08:43:42 +00:00
|
|
|
$this->initTempComposer([
|
|
|
|
'repositories' => [
|
|
|
|
'packages' => [
|
|
|
|
'type' => 'package',
|
|
|
|
'package' => [
|
|
|
|
['name' => 'root/req', 'version' => '1.0.0'],
|
|
|
|
['name' => 'root/req', 'version' => '1.0.1'],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'require' => [
|
|
|
|
'root/req' => '1.*',
|
|
|
|
],
|
|
|
|
]);
|
|
|
|
$this->createComposerLock([self::getPackage('root/req', '1.0.0')]);
|
|
|
|
self::expectExceptionMessage('No package named "" is installed.');
|
2023-12-08 17:26:05 +00:00
|
|
|
|
|
|
|
$appTester = $this->getApplicationTester();
|
|
|
|
$appTester->setInputs(['']);
|
|
|
|
$appTester->run(['command' => 'update', '--interactive' => true]);
|
|
|
|
}
|
2024-08-21 16:14:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider provideInteractiveUpdates
|
|
|
|
* @param array<mixed> $packageNames
|
|
|
|
*/
|
|
|
|
public function testInteractiveTmp(array $packageNames, string $expected): void
|
|
|
|
{
|
|
|
|
$this->initTempComposer([
|
|
|
|
'repositories' => [
|
|
|
|
'packages' => [
|
|
|
|
'type' => 'package',
|
|
|
|
'package' => [
|
|
|
|
['name' => 'root/req', 'version' => '1.0.0', 'require' => ['dep/pkg' => '^1']],
|
|
|
|
['name' => 'dep/pkg', 'version' => '1.0.0'],
|
|
|
|
['name' => 'dep/pkg', 'version' => '1.0.1'],
|
|
|
|
['name' => 'dep/pkg', 'version' => '1.0.2'],
|
|
|
|
['name' => 'another-dep/pkg', 'version' => '1.0.2'],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'require' => [
|
|
|
|
'root/req' => '1.*',
|
|
|
|
],
|
|
|
|
]);
|
|
|
|
|
|
|
|
$rootPackage = self::getPackage('root/req');
|
|
|
|
$packages = [$rootPackage];
|
|
|
|
|
|
|
|
foreach ($packageNames as $pkg => $ver) {
|
|
|
|
$currentPkg = self::getPackage($pkg, $ver);
|
|
|
|
array_push($packages, $currentPkg);
|
|
|
|
}
|
|
|
|
|
|
|
|
$rootPackage->setRequires([
|
|
|
|
'dep/pkg' => new Link(
|
|
|
|
'root/req',
|
|
|
|
'dep/pkg',
|
|
|
|
new MatchAllConstraint(),
|
|
|
|
Link::TYPE_REQUIRE,
|
|
|
|
'^1'
|
|
|
|
),
|
|
|
|
'another-dep/pkg' => new Link(
|
|
|
|
'root/req',
|
|
|
|
'another-dep/pkg',
|
|
|
|
new MatchAllConstraint(),
|
|
|
|
Link::TYPE_REQUIRE,
|
|
|
|
'^1'
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->createComposerLock($packages);
|
|
|
|
$this->createInstalledJson($packages);
|
|
|
|
|
|
|
|
$appTester = $this->getApplicationTester();
|
|
|
|
$appTester->setInputs(array_merge(array_keys($packageNames), ['', 'yes']));
|
|
|
|
$appTester->run([
|
|
|
|
'command' => 'update', '--interactive' => true,
|
|
|
|
'--no-audit' => true,
|
|
|
|
'--dry-run' => true,
|
|
|
|
]);
|
|
|
|
|
|
|
|
self::assertStringEndsWith(
|
|
|
|
trim($expected),
|
|
|
|
trim($appTester->getDisplay(true))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function provideInteractiveUpdates(): \Generator
|
|
|
|
{
|
|
|
|
yield [
|
|
|
|
['dep/pkg' => '1.0.1'],
|
|
|
|
<<<OUTPUT
|
|
|
|
Lock file operations: 1 install, 1 update, 0 removals
|
|
|
|
- Locking another-dep/pkg (1.0.2)
|
|
|
|
- Upgrading dep/pkg (1.0.1 => 1.0.2)
|
|
|
|
Installing dependencies from lock file (including require-dev)
|
|
|
|
Package operations: 1 install, 1 update, 0 removals
|
|
|
|
- Upgrading dep/pkg (1.0.1 => 1.0.2)
|
|
|
|
- Installing another-dep/pkg (1.0.2)
|
|
|
|
OUTPUT
|
|
|
|
];
|
|
|
|
|
|
|
|
yield [
|
|
|
|
['dep/pkg' => '1.0.1', 'another-dep/pkg' => '1.0.2'],
|
|
|
|
<<<OUTPUT
|
|
|
|
Lock file operations: 0 installs, 1 update, 0 removals
|
|
|
|
- Upgrading dep/pkg (1.0.1 => 1.0.2)
|
|
|
|
Installing dependencies from lock file (including require-dev)
|
|
|
|
Package operations: 0 installs, 1 update, 0 removals
|
|
|
|
- Upgrading dep/pkg (1.0.1 => 1.0.2)
|
|
|
|
OUTPUT
|
|
|
|
];
|
|
|
|
}
|
2022-05-27 12:51:46 +00:00
|
|
|
}
|