1
0
Fork 0
composer/tests/Composer/Test/Command/UpdateCommandTest.php

393 lines
14 KiB
PHP

<?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;
use Composer\Package\Link;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Test\TestCase;
use InvalidArgumentException;
class UpdateCommandTest extends TestCase
{
/**
* @dataProvider provideUpdates
* @param array<mixed> $composerJson
* @param array<mixed> $command
*/
public function testUpdate(array $composerJson, array $command, string $expected, bool $createLock = false): void
{
$this->initTempComposer($composerJson);
if ($createLock) {
$this->createComposerLock();
}
$appTester = $this->getApplicationTester();
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true], $command));
self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true)));
}
public static function provideUpdates(): \Generator
{
$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
];
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
];
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).
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
OUTPUT
];
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
];
}
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]);
$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)));
}
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]);
}
public function testInteractiveModeThrowsIfNoPackageEntered(): void
{
$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.');
$appTester = $this->getApplicationTester();
$appTester->setInputs(['']);
$appTester->run(['command' => 'update', '--interactive' => true]);
}
/**
* @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
];
}
}