1
0
Fork 0

Merge pull request #736 from Seldaek/require-update

Require command & update <package>
pull/739/head
Nils Adermann 2012-05-27 17:11:48 -07:00
commit 242323cba4
20 changed files with 629 additions and 34 deletions

View File

@ -1,6 +1,8 @@
* 1.0.0-alpha4
* Schema: Added references for dev versions, requiring `dev-master#abcdef` for example will force the abcdef commit
* Added `require` command to add a package to your requirements and install it
* Added a whitelist to `update`. Calling `composer update foo/bar foo/baz` allows you to update only those packages
* Added caching of GitHub metadata (faster startup time with custom GitHub VCS repos)
* Added support for file:// URLs to GitDriver
* Added --dev flag to `create-project` command

View File

@ -53,12 +53,36 @@ In order to get the latest versions of the dependencies and to update the
This will resolve all dependencies of the project and write the exact versions
into `composer.lock`.
If you just want to update a few packages and not all, you can list them as such:
$ php composer.phar update vendor/package vendor/package2
### Options
* **--prefer-source:** Install packages from `source` when available.
* **--dry-run:** Simulate the command without actually doing anything.
* **--dev:** Install packages listed in `require-dev`.
## require
The `require` command adds new packages to the `composer.json` file from
the current directory.
$ php composer.phar require
After adding/changing the requirements, the modified requirements will be
installed or updated.
If you do not want to choose requirements interactively, you can just pass them
to the command.
$ php composer.phar require vendor/package:2.* vendor/package2:dev-master
### Options
* **--prefer-source:** Install packages from `source` when available.
* **--dev:** Add packages to `require-dev`.
## search
The search command allows you to search through the current project's package

View File

@ -60,7 +60,8 @@ class InitCommand extends Command
new InputOption('author', null, InputOption::VALUE_NONE, 'Author name of package'),
// new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'),
new InputOption('homepage', null, InputOption::VALUE_NONE, 'Homepage of package'),
new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'An array required packages'),
new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
))
->setHelp(<<<EOT
The <info>init</info> command creates a basic composer.json file
@ -216,10 +217,15 @@ EOT
));
$requirements = array();
if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dependencies interactively', 'yes', '?'), true)) {
$requirements = $this->determineRequirements($input, $output);
if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dependencies (require) interactively', 'yes', '?'), true)) {
$requirements = $this->determineRequirements($input, $output, $input->getOption('require'));
}
$input->setOption('require', $requirements);
$devRequirements = array();
if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dev dependencies (require-dev) interactively', 'yes', '?'), true)) {
$devRequirements = $this->determineRequirements($input, $output, $input->getOption('require-dev'));
}
$input->setOption('require-dev', $devRequirements);
}
protected function findPackages($name)
@ -246,12 +252,27 @@ EOT
return $packages;
}
protected function determineRequirements(InputInterface $input, OutputInterface $output)
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array())
{
$dialog = $this->getHelperSet()->get('dialog');
$prompt = $dialog->getQuestion('Search for a package', false, ':');
$requires = $input->getOption('require') ?: array();
if ($requires) {
foreach ($requires as $key => $requirement) {
$requires[$key] = preg_replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', $requirement);
if (false === strpos($requires[$key], ' ') && $input->isInteractive()) {
$question = $dialog->getQuestion('Please provide a version constraint for the '.$requirement.' requirement');
if ($constraint = $dialog->ask($output, $question)) {
$requires[$key] .= ' ' . $constraint;
}
}
if (false === strpos($requires[$key], ' ')) {
throw new \InvalidArgumentException('The requirement '.$requirement.' must contain a version constraint');
}
}
return $requires;
}
while (null !== $package = $dialog->ask($output, $prompt)) {
$matches = $this->findPackages($package);
@ -287,7 +308,7 @@ EOT
return sprintf('%s %s', $package->getName(), $package->getPrettyVersion());
};
$package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or a <package> <version> couple if it is not listed', false, ':'), $validator, 3);
$package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or a "[package] [version]" couple if it is not listed', false, ':'), $validator, 3);
if (false !== $package) {
$requires[] = $package;

View File

@ -0,0 +1,123 @@
<?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\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Factory;
use Composer\Installer;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Json\JsonValidationException;
use Composer\Util\RemoteFilesystem;
/**
* @author Jérémy Romey <jeremy@free-agent.fr>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class RequireCommand extends InitCommand
{
protected function configure()
{
$this
->setName('require')
->setDescription('Adds required packages to your composer.json and installs them')
->setDefinition(array(
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
))
->setHelp(<<<EOT
The require command adds required packages to your composer.json and installs them
EOT
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$factory = new Factory;
$file = $factory->getComposerFile();
if (!file_exists($file)) {
$output->writeln('<error>'.$file.' not found.</error>');
return 1;
}
if (!is_readable($file)) {
$output->writeln('<error>'.$file.' is not readable.</error>');
return 1;
}
$dialog = $this->getHelperSet()->get('dialog');
$json = new JsonFile($file);
$composer = $json->read();
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
$baseRequirements = array_key_exists($requireKey, $composer) ? $composer[$requireKey] : array();
$requirements = $this->formatRequirements($requirements);
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey)) {
foreach ($requirements as $package => $version) {
$baseRequirements[$package] = $version;
}
$composer[$requireKey] = $baseRequirements;
$json->write($composer);
}
$output->writeln('<info>'.$file.' has been updated</info>');
// Update packages
$composer = $this->getComposer();
$io = $this->getIO();
$install = Installer::create($io, $composer);
$install
->setVerbose($input->getOption('verbose'))
->setPreferSource($input->getOption('prefer-source'))
->setDevMode($input->getOption('dev'))
->setUpdate(true)
->setUpdateWhitelist($requirements);
;
return $install->run() ? 0 : 1;
}
private function updateFileCleanly($json, array $base, array $new, $requireKey)
{
$contents = file_get_contents($json->getPath());
$manipulator = new JsonManipulator($contents);
foreach ($new as $package => $constraint) {
if (!$manipulator->addLink($requireKey, $package, $constraint)) {
return false;
}
}
file_put_contents($json->getPath(), $manipulator->getContents());
return true;
}
protected function interact(InputInterface $input, OutputInterface $output)
{
return;
}
}

View File

@ -15,6 +15,7 @@ namespace Composer\Command;
use Composer\Installer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
/**
@ -28,6 +29,7 @@ class UpdateCommand extends Command
->setName('update')
->setDescription('Updates your dependencies to the latest version, and updates the composer.lock file.')
->setDefinition(array(
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'),
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of dev-require packages.'),
@ -58,6 +60,7 @@ EOT
->setDevMode($input->getOption('dev'))
->setRunScripts(!$input->getOption('no-scripts'))
->setUpdate(true)
->setUpdateWhitelist($input->getArgument('packages'))
;
return $install->run() ? 0 : 1;

View File

@ -64,7 +64,6 @@ class Application extends BaseApplication
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
$this->registerCommands();
$this->io = new ConsoleIO($input, $output, $this->getHelperSet());
if (version_compare(PHP_VERSION, '5.3.2', '<')) {
@ -106,21 +105,25 @@ class Application extends BaseApplication
/**
* Initializes all the composer commands
*/
protected function registerCommands()
protected function getDefaultCommands()
{
$this->add(new Command\AboutCommand());
$this->add(new Command\DependsCommand());
$this->add(new Command\InitCommand());
$this->add(new Command\InstallCommand());
$this->add(new Command\CreateProjectCommand());
$this->add(new Command\UpdateCommand());
$this->add(new Command\SearchCommand());
$this->add(new Command\ValidateCommand());
$this->add(new Command\ShowCommand());
$commands = parent::getDefaultCommands();
$commands[] = new Command\AboutCommand();
$commands[] = new Command\DependsCommand();
$commands[] = new Command\InitCommand();
$commands[] = new Command\InstallCommand();
$commands[] = new Command\CreateProjectCommand();
$commands[] = new Command\UpdateCommand();
$commands[] = new Command\SearchCommand();
$commands[] = new Command\ValidateCommand();
$commands[] = new Command\ShowCommand();
$commands[] = new Command\RequireCommand();
if ('phar:' === substr(__FILE__, 0, 5)) {
$this->add(new Command\SelfUpdateCommand());
$commands[] = new Command\SelfUpdateCommand();
}
return $commands;
}
/**

View File

@ -62,6 +62,11 @@ class Factory
return $config;
}
public function getComposerFile()
{
return getenv('COMPOSER') ?: 'composer.json';
}
/**
* Creates a Composer instance
*
@ -73,7 +78,7 @@ class Factory
{
// load Composer configuration
if (null === $localConfig) {
$localConfig = getenv('COMPOSER') ?: 'composer.json';
$localConfig = $this->getComposerFile();
}
if (is_string($localConfig)) {

View File

@ -89,6 +89,7 @@ class Installer
protected $verbose = false;
protected $update = false;
protected $runScripts = true;
protected $updateWhitelist = null;
/**
* @var array
@ -219,6 +220,8 @@ class Installer
$stabilityFlags = $this->locker->getStabilityFlags();
}
$this->whitelistUpdateDependencies($localRepo, $devMode);
// creating repository pool
$pool = new Pool($minimumStability, $stabilityFlags);
$pool->addRepository($installedRepo);
@ -275,8 +278,11 @@ class Installer
// fix the version of all installed packages (+ platform) that are not
// in the current local repo to prevent rogue updates (e.g. non-dev
// updating when in dev)
//
// if the updateWhitelist is enabled, packages not in it are also fixed
// to their currently installed version
foreach ($installedRepo->getPackages() as $package) {
if ($package->getRepository() === $localRepo) {
if ($package->getRepository() === $localRepo && (!$this->updateWhitelist || $this->isUpdateable($package))) {
continue;
}
@ -331,6 +337,11 @@ class Installer
} else {
// force update to latest on update
if ($this->update) {
// skip package if the whitelist is enabled and it is not in it
if ($this->updateWhitelist && !$this->isUpdateable($package)) {
continue;
}
$newPackage = $this->repositoryManager->findPackage($package->getName(), $package->getVersion());
if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
$operations[] = new UpdateOperation($package, $newPackage);
@ -441,6 +452,64 @@ class Installer
return $aliases;
}
private function isUpdateable(PackageInterface $package)
{
if (!$this->updateWhitelist) {
throw new \LogicException('isUpdateable should only be called when a whitelist is present');
}
return isset($this->updateWhitelist[$package->getName()]);
}
/**
* Adds all dependencies of the update whitelist to the whitelist, too.
*
* @param RepositoryInterface $localRepo
* @param boolean $devMode
*/
private function whitelistUpdateDependencies($localRepo, $devMode)
{
if (!$this->updateWhitelist) {
return;
}
$pool = new Pool;
$pool->addRepository($localRepo);
$seen = array();
foreach ($this->updateWhitelist as $packageName => $void) {
$packageQueue = new \SplQueue;
foreach ($pool->whatProvides($packageName) as $depPackage) {
$packageQueue->enqueue($depPackage);
}
while (!$packageQueue->isEmpty()) {
$package = $packageQueue->dequeue();
if (isset($seen[$package->getId()])) {
continue;
}
$seen[$package->getId()] = true;
$this->updateWhitelist[$package->getName()] = true;
$requires = $package->getRequires();
if ($devMode) {
$requires = array_merge($requires, $package->getDevRequires());
}
foreach ($requires as $require) {
$requirePackages = $pool->whatProvides($require->getTarget());
foreach ($requirePackages as $requirePackage) {
$packageQueue->enqueue($requirePackage);
}
}
}
}
}
/**
* Create Installer
*
@ -551,4 +620,18 @@ class Installer
return $this;
}
/**
* restrict the update operation to a few packages, all other packages
* that are already installed will be kept at their current version
*
* @param array $packages
* @return Installer
*/
public function setUpdateWhitelist(array $packages)
{
$this->updateWhitelist = array_flip(array_map('strtolower', $packages));
return $this;
}
}

View File

@ -0,0 +1,121 @@
<?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\Json;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class JsonManipulator
{
private $contents;
private $newline;
private $indent;
public function __construct($contents)
{
$contents = trim($contents);
if (!preg_match('#^\{(.*)\}$#s', $contents)) {
throw new \InvalidArgumentException('The json file must be an object ({})');
}
$this->newline = false !== strpos("\r\n", $contents) ? "\r\n": "\n";
$this->contents = $contents;
$this->detectIndenting();
}
public function getContents()
{
return $this->contents . $this->newline;
}
public function addLink($type, $package, $constraint)
{
// no link of that type yet
if (!preg_match('#"'.$type.'":\s*\{#', $this->contents)) {
$this->addMainKey($type, $this->format(array($package => $constraint)));
return true;
}
$linksRegex = '#("'.$type.'":\s*\{)([^}]+)(\})#s';
if (!preg_match($linksRegex, $this->contents, $match)) {
return false;
}
$links = $match[2];
$packageRegex = str_replace('/', '\\\\?/', preg_quote($package));
// link exists already
if (preg_match('{"'.$packageRegex.'"\s*:}i', $links)) {
$links = preg_replace('{"'.$packageRegex.'"(\s*:\s*)"[^"]+"}i', JsonFile::encode($package).'$1"'.$constraint.'"', $links);
} elseif (preg_match('#[^\s](\s*)$#', $links, $match)) {
// link missing but non empty links
$links = preg_replace(
'#'.$match[1].'$#',
',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1],
$links
);
} else {
// links empty
$links = $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $links;
}
$this->contents = preg_replace($linksRegex, '$1'.$links.'$3', $this->contents);
return true;
}
public function addMainKey($key, $content)
{
if (preg_match('#[^{\s](\s*)\}$#', $this->contents, $match)) {
$this->contents = preg_replace(
'#'.$match[1].'\}$#',
',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}',
$this->contents
);
} else {
$this->contents = preg_replace(
'#\}$#',
$this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}',
$this->contents
);
}
}
protected function format($data)
{
if (is_array($data)) {
reset($data);
if (is_numeric(key($data))) {
return '['.implode(', ', $data).']';
}
$out = '{' . $this->newline;
foreach ($data as $key => $val) {
$elems[] = $this->indent . $this->indent . JsonFile::encode($key). ': '.$this->format($val);
}
return $out . implode(','.$this->newline, $elems) . $this->newline . $this->indent . '}';
}
return JsonFile::encode($data);
}
protected function detectIndenting()
{
if (preg_match('{^(\s+)"}', $this->contents, $match)) {
$this->indent = $match[1];
} else {
$this->indent = ' ';
}
}
}

View File

@ -10,5 +10,7 @@
<installed.json file definition>
--INSTALLED:DEV--
<installed_dev.json file definition>
--EXPECT-- or --EXPECT:UPDATE-- or --EXPECT:DEV-- or --EXPECT:UPDATE:DEV--
--RUN--
install
--EXPECT--
<output (stringified operations)>

View File

@ -42,6 +42,8 @@ Aliases take precedence over default package even if default is selected
"a/req": "dev-feature-foo as dev-master"
}
}
--RUN--
install
--EXPECT--
Marking a/req (dev-master feat.f) as installed, alias of a/req (dev-feature-foo feat.f)
Installing a/req (dev-feature-foo feat.f)

View File

@ -44,6 +44,8 @@ Aliases take precedence over default package
"a/c": "dev-feature-foo as dev-master"
}
}
--RUN--
install
--EXPECT--
Installing a/b (dev-master forked)
Marking a/b (1.0.x-dev forked) as installed, alias of a/b (dev-master forked)

View File

@ -18,6 +18,8 @@ Installs a package in dev env
"a/b": "1.0.0"
}
}
--EXPECT:DEV--
--RUN--
install --dev
--EXPECT--
Installing a/a (1.0.0)
Installing a/b (1.0.0)

View File

@ -17,5 +17,7 @@ Installs a dev package forcing it's reference
"a/a": "dev-master#def000"
}
}
--RUN--
install
--EXPECT--
Installing a/a (dev-master def000)

View File

@ -14,5 +14,7 @@ Installs a simple package with exact match requirement
"a/a": "1.0.0"
}
}
--RUN--
install
--EXPECT--
Installing a/a (1.0.0)

View File

@ -36,6 +36,8 @@ Updates updateable packages
[
{ "name": "a/b", "version": "1.0.0" }
]
--EXPECT:UPDATE:DEV--
--RUN--
update --dev
--EXPECT--
Updating a/a (1.0.0) to a/a (1.0.1)
Updating a/b (1.0.0) to a/b (2.0.0)

View File

@ -24,5 +24,7 @@ Updates a dev package forcing it's reference
"source": { "reference": "abc123", "url": "", "type": "git" }
}
]
--RUN--
install
--EXPECT--
Updating a/a (dev-master abc123) to a/a (dev-master def000)

View File

@ -0,0 +1,40 @@
--TEST--
Update with a package whitelist only updates those packages and their dependencies if they are not present in composer.json
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "fixed", "version": "1.1.0" },
{ "name": "fixed", "version": "1.0.0" },
{ "name": "whitelisted", "version": "1.1.0", "require": { "dependency": "1.1.0" } },
{ "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
{ "name": "dependency", "version": "1.1.0" },
{ "name": "dependency", "version": "1.0.0" },
{ "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } },
{ "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
{ "name": "unrelated-dependency", "version": "1.1.0" },
{ "name": "unrelated-dependency", "version": "1.0.0" }
]
}
],
"require": {
"fixed": "1.*",
"whitelisted": "1.*",
"unrelated": "1.*"
}
}
--INSTALLED--
[
{ "name": "fixed", "version": "1.0.0" },
{ "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
{ "name": "dependency", "version": "1.0.0" },
{ "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
{ "name": "unrelated-dependency", "version": "1.0.0" }
]
--RUN--
update whitelisted
--EXPECT--
Updating dependency (1.0.0) to dependency (1.1.0)
Updating whitelisted (1.0.0) to whitelisted (1.1.0)

View File

@ -12,6 +12,7 @@
namespace Composer\Test;
use Composer\Installer;
use Composer\Console\Application;
use Composer\Config;
use Composer\Json\JsonFile;
use Composer\Repository\ArrayRepository;
@ -24,6 +25,8 @@ use Composer\Test\Mock\FactoryMock;
use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
use Composer\Test\Mock\InstallationManagerMock;
use Composer\Test\Mock\WritableRepositoryMock;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\StreamOutput;
class InstallerTest extends TestCase
{
@ -121,7 +124,7 @@ class InstallerTest extends TestCase
/**
* @dataProvider getIntegrationTests
*/
public function testIntegration($file, $message, $condition, $composer, $lock, $installed, $installedDev, $update, $dev, $expect)
public function testIntegration($file, $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expect)
{
if ($condition) {
eval('$res = '.$condition.';');
@ -177,14 +180,31 @@ class InstallerTest extends TestCase
$autoloadGenerator
);
$installer->setDevMode($dev)->setUpdate($update);
$application = new Application;
$application->get('install')->setCode(function ($input, $output) use ($installer) {
$installer->setDevMode($input->getOption('dev'));
$result = $installer->run();
$this->assertTrue($result, $output);
return $installer->run();
});
$expectedInstalled = isset($options['install']) ? $options['install'] : array();
$expectedUpdated = isset($options['update']) ? $options['update'] : array();
$expectedUninstalled = isset($options['uninstall']) ? $options['uninstall'] : array();
$application->get('update')->setCode(function ($input, $output) use ($installer) {
$installer
->setDevMode($input->getOption('dev'))
->setUpdate(true)
->setUpdateWhitelist($input->getArgument('packages'));
return $installer->run();
});
if (!preg_match('{^(install|update)\b}', $run)) {
throw new \UnexpectedValueException('The run command only supports install and update');
}
$application->setAutoExit(false);
$appOutput = fopen('php://memory', 'w+');
$result = $application->run(new StringInput($run), new StreamOutput($appOutput));
fseek($appOutput, 0);
$this->assertEquals(0, $result, $output . stream_get_contents($appOutput));
$installationManager = $composer->getInstallationManager();
$this->assertSame($expect, implode("\n", $installationManager->getTrace()));
@ -210,7 +230,8 @@ class InstallerTest extends TestCase
(?:--LOCK--\s*(?P<lock>'.$content.'))?\s*
(?:--INSTALLED--\s*(?P<installed>'.$content.'))?\s*
(?:--INSTALLED:DEV--\s*(?P<installedDev>'.$content.'))?\s*
--EXPECT(?P<update>:UPDATE)?(?P<dev>:DEV)?--\s*(?P<expect>.*?)\s*
--RUN--\s*(?P<run>.*?)\s*
--EXPECT--\s*(?P<expect>.*?)\s*
$}xs';
$installed = array();
@ -231,8 +252,7 @@ class InstallerTest extends TestCase
if (!empty($match['installedDev'])) {
$installedDev = JsonFile::parseJson($match['installedDev']);
}
$update = !empty($match['update']);
$dev = !empty($match['dev']);
$run = $match['run'];
$expect = $match['expect'];
} catch (\Exception $e) {
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
@ -241,7 +261,7 @@ class InstallerTest extends TestCase
die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file)));
}
$tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $installedDev, $update, $dev, $expect);
$tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expect);
}
return $tests;

View File

@ -0,0 +1,134 @@
<?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\Json;
use Composer\Json\JsonManipulator;
class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider linkProvider
*/
public function testAddLink($json, $type, $package, $constraint, $expected)
{
$manipulator = new JsonManipulator($json);
$this->assertTrue($manipulator->addLink($type, $package, $constraint));
$this->assertEquals($expected, $manipulator->getContents());
}
public function linkProvider()
{
return array(
array(
'{
}',
'require',
'vendor/baz',
'qux',
'{
"require": {
"vendor/baz": "qux"
}
}
'
),
array(
'{
"foo": "bar"
}',
'require',
'vendor/baz',
'qux',
'{
"foo": "bar",
"require": {
"vendor/baz": "qux"
}
}
'
),
array(
'{
"require": {
}
}',
'require',
'vendor/baz',
'qux',
'{
"require": {
"vendor/baz": "qux"
}
}
'
),
array(
'{
"require": {
"foo": "bar"
}
}',
'require',
'vendor/baz',
'qux',
'{
"require": {
"foo": "bar",
"vendor/baz": "qux"
}
}
'
),
array(
'{
"require":
{
"foo": "bar",
"vendor/baz": "baz"
}
}',
'require',
'vendor/baz',
'qux',
'{
"require":
{
"foo": "bar",
"vendor/baz": "qux"
}
}
'
),
array(
'{
"require":
{
"foo": "bar",
"vendor\/baz": "baz"
}
}',
'require',
'vendor/baz',
'qux',
'{
"require":
{
"foo": "bar",
"vendor/baz": "qux"
}
}
'
),
);
}
}