diff --git a/Resources/composer-schema.json b/Resources/composer-schema.json index 70b8bff13..e4b9c4162 100644 --- a/Resources/composer-schema.json +++ b/Resources/composer-schema.json @@ -101,6 +101,20 @@ "description": "This is a hash of package name (keys) and version constraints (values) that this package suggests work well with it (typically this will only be suggested to the user).", "additionalProperties": true }, + "config": { + "type": ["object"], + "description": "Composer options.", + "properties": { + "vendor-dir": { + "type": "string", + "description": "The location where all packages are installed, defaults to \"vendor\"." + }, + "bin-dir": { + "type": "string", + "description": "The location where all binaries are linked, defaults to \"vendor/bin\"." + } + } + }, "extra": { "type": ["object", "array"], "description": "Arbitrary extra data that can be used by custom installers, for example, package of type composer-installer must have a 'class' key defining the installer class name.", @@ -128,6 +142,52 @@ "items": { "type": "string" } + }, + "scripts": { + "type": ["object"], + "description": "Scripts listeners that will be executed before/after some events.", + "properties": { + "pre-install-cmd": { + "type": ["array", "string"], + "description": "Occurs before the install command is executed, contains one or more Class::method callables.", + }, + "post-install-cmd": { + "type": ["array", "string"], + "description": "Occurs after the install command is executed, contains one or more Class::method callables.", + }, + "pre-update-cmd": { + "type": ["array", "string"], + "description": "Occurs before the update command is executed, contains one or more Class::method callables.", + }, + "post-update-cmd": { + "type": ["array", "string"], + "description": "Occurs after the update command is executed, contains one or more Class::method callables.", + }, + "pre-package-install": { + "type": ["array", "string"], + "description": "Occurs before a package is installed, contains one or more Class::method callables.", + }, + "post-package-install": { + "type": ["array", "string"], + "description": "Occurs after a package is installed, contains one or more Class::method callables.", + }, + "pre-package-update": { + "type": ["array", "string"], + "description": "Occurs before a package is updated, contains one or more Class::method callables.", + }, + "post-package-update": { + "type": ["array", "string"], + "description": "Occurs after a package is updated, contains one or more Class::method callables.", + }, + "pre-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables.", + }, + "post-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables.", + } + } } } } diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index f695487ff..82ff5e02d 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -25,6 +25,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ class DependsCommand extends Command { + protected $linkTypes = array('require', 'recommend', 'suggest'); + protected function configure() { $this @@ -32,7 +34,7 @@ class DependsCommand extends Command ->setDescription('Shows which packages depend on the given package') ->setDefinition(array( new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'), - new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', array('requires', 'recommends', 'suggests')) + new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', $this->linkTypes) )) ->setHelp(<<getRepositoryManager()->getRepositories(); $types = $input->getOption('link-type'); + foreach ($repos as $repository) { foreach ($repository->getPackages() as $package) { foreach ($types as $type) { - foreach ($package->{'get'.$type}() as $link) { + $type = rtrim($type, 's'); + if (!in_array($type, $this->linkTypes)) { + throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', $this->linkTypes)); + } + foreach ($package->{'get'.$type.'s'}() as $link) { if ($link->getTarget() === $needle) { if ($verbose) { $references[] = array($type, $package, $link); diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index d656ad5d1..d6f60028b 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -119,14 +119,9 @@ EOT $installedPackages = $installedRepo->getPackages(); $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests); - foreach ($links as $link) { - foreach ($installedPackages as $package) { - if ($package->getName() === $link->getTarget()) { - $request->update($package->getName(), new VersionConstraint('=', $package->getVersion())); - break; - } - } + $request->updateAll(); + foreach ($links as $link) { $request->install($link->getTarget(), $link->getConstraint()); } } elseif ($composer->getLocker()->isLocked()) { diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 41918b260..63699d883 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -21,16 +21,6 @@ use Composer\Package\LinkConstraint\VersionConstraint; */ class DefaultPolicy implements PolicyInterface { - public function allowUninstall() - { - return true; - } - - public function allowDowngrade() - { - return true; - } - public function versionCompare(PackageInterface $a, PackageInterface $b, $operator) { $constraint = new VersionConstraint($operator, $b->getVersion()); @@ -39,16 +29,11 @@ class DefaultPolicy implements PolicyInterface return $constraint->matchSpecific($version); } - public function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package, $allowAll = false) + public function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package) { $packages = array(); foreach ($pool->whatProvides($package->getName()) as $candidate) { - // skip old packages unless downgrades are an option - if (!$allowAll && !$this->allowDowngrade() && $this->versionCompare($package, $candidate, '>')) { - continue; - } - if ($candidate !== $package) { $packages[] = $candidate; } diff --git a/src/Composer/DependencyResolver/PolicyInterface.php b/src/Composer/DependencyResolver/PolicyInterface.php index 0271fdab2..7c26ab63f 100644 --- a/src/Composer/DependencyResolver/PolicyInterface.php +++ b/src/Composer/DependencyResolver/PolicyInterface.php @@ -20,10 +20,8 @@ use Composer\Package\PackageInterface; */ interface PolicyInterface { - function allowUninstall(); - function allowDowngrade(); function versionCompare(PackageInterface $a, PackageInterface $b, $operator); - function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package, $allowAll); + function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package); function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package); function selectPreferedPackages(Pool $pool, array $installedMap, array $literals); } diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php index 201caa1d3..3d1b28448 100644 --- a/src/Composer/DependencyResolver/Request.php +++ b/src/Composer/DependencyResolver/Request.php @@ -55,6 +55,11 @@ class Request ); } + public function updateAll() + { + $this->jobs[] = array('cmd' => 'update-all', 'packages' => array()); + } + public function getJobs() { return $this->jobs; diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index d08c4a5ed..8af24094e 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -29,6 +29,8 @@ class Rule public $next1; public $next2; + public $ruleHash; + public function __construct(array $literals, $reason, $reasonData) { // sort all packages ascending by id @@ -85,7 +87,7 @@ class Rule } for ($i = 0, $n = count($this->literals); $i < $n; $i++) { - if (!$this->literals[$i]->getId() === $rule->literals[$i]->getId()) { + if ($this->literals[$i]->getId() !== $rule->literals[$i]->getId()) { return false; } } diff --git a/src/Composer/DependencyResolver/RuleSet.php b/src/Composer/DependencyResolver/RuleSet.php index 6d79a9678..1f444c788 100644 --- a/src/Composer/DependencyResolver/RuleSet.php +++ b/src/Composer/DependencyResolver/RuleSet.php @@ -20,7 +20,6 @@ class RuleSet implements \IteratorAggregate, \Countable // highest priority => lowest number const TYPE_PACKAGE = 0; const TYPE_JOB = 1; - const TYPE_UPDATE = 2; const TYPE_FEATURE = 3; const TYPE_CHOICE = 4; const TYPE_LEARNED = 5; @@ -29,7 +28,6 @@ class RuleSet implements \IteratorAggregate, \Countable -1 => 'UNKNOWN', self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_FEATURE => 'FEATURE', - self::TYPE_UPDATE => 'UPDATE', self::TYPE_JOB => 'JOB', self::TYPE_CHOICE => 'CHOICE', self::TYPE_LEARNED => 'LEARNED', diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index b865aa82e..e1d6575cd 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -52,7 +52,6 @@ class Solver protected $decisionMap; protected $installedMap; - protected $packageToUpdateRule = array(); protected $packageToFeatureRule = array(); public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed) @@ -77,7 +76,7 @@ class Solver * that goes with the reason * @return Rule The generated rule or null if tautological */ - public function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) + protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) { $literals = array(new Literal($package, false)); @@ -128,7 +127,7 @@ class Solver * goes with the reason * @return Rule The generated rule */ - public function createInstallRule(PackageInterface $package, $reason, $reasonData = null) + protected function createInstallRule(PackageInterface $package, $reason, $reasonData = null) { return new Rule(new Literal($package, true)); } @@ -146,7 +145,7 @@ class Solver * the reason * @return Rule The generated rule */ - public function createInstallOneOfRule(array $packages, $reason, $reasonData = null) + protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null) { if (empty($packages)) { return $this->createImpossibleRule($reason, $reasonData); @@ -172,7 +171,7 @@ class Solver * goes with the reason * @return Rule The generated rule */ - public function createRemoveRule(PackageInterface $package, $reason, $reasonData = null) + protected function createRemoveRule(PackageInterface $package, $reason, $reasonData = null) { return new Rule(array(new Literal($package, false)), $reason, $reasonData); } @@ -191,7 +190,7 @@ class Solver * goes with the reason * @return Rule The generated rule */ - public function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) + protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) { // ignore self conflict if ($issuer === $provider) { @@ -212,7 +211,7 @@ class Solver * the reason * @return Rule An empty rule */ - public function createImpossibleRule($reason, $reasonData = null) + protected function createImpossibleRule($reason, $reasonData = null) { return new Rule(array(), $reason, $reasonData); } @@ -237,7 +236,7 @@ class Solver } } - public function addRulesForPackage(PackageInterface $package) + protected function addRulesForPackage(PackageInterface $package) { $workQueue = new \SplQueue; $workQueue->enqueue($package); @@ -305,7 +304,7 @@ class Solver // if ignoreinstalledsobsoletes is not set, we're also checking // obsoletes of installed packages (like newer rpm versions) // - /** @TODO: if ($this->noInstalledObsoletes) */ + /** TODO if ($this->noInstalledObsoletes) */ if (true) { $noObsoletes = isset($this->noObsoletes[$package->getId()]); $isInstalled = (isset($this->installedMap[$package->getId()])); @@ -375,9 +374,9 @@ class Solver * be added * @param bool $allowAll Whether downgrades are allowed */ - private function addRulesForUpdatePackages(PackageInterface $package, $allowAll) + private function addRulesForUpdatePackages(PackageInterface $package) { - $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package, $allowAll); + $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $this->addRulesForPackage($package); @@ -508,7 +507,7 @@ class Solver // push all of our rules (can only be feature or job rules) // asserting this literal on the problem stack - foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_UPDATE, RuleSet::TYPE_FEATURE)) as $assertRule) { + foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_FEATURE)) as $assertRule) { if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) { continue; } @@ -571,7 +570,7 @@ class Solver } } - public function addChoiceRules() + protected function addChoiceRules() { // void @@ -882,11 +881,6 @@ class Solver protected function disableUpdateRule($package) { - // find update & feature rule and disable - if (isset($this->packageToUpdateRule[$package->getId()])) { - $this->packageToUpdateRule[$package->getId()]->disable(); - } - if (isset($this->packageToFeatureRule[$package->getId()])) { $this->packageToFeatureRule[$package->getId()]->disable(); } @@ -944,20 +938,6 @@ class Solver } foreach ($this->jobs as $job) { - switch ($job['cmd']) { - case 'update-all': - foreach ($installedPackages as $package) { - $this->updateMap[$package->getId()] = true; - } - break; - - case 'fix-all': - foreach ($installedPackages as $package) { - $this->fixMap[$package->getId()] = true; - } - break; - } - foreach ($job['packages'] as $package) { switch ($job['cmd']) { case 'fix': @@ -972,6 +952,14 @@ class Solver break; } } + + switch ($job['cmd']) { + case 'update-all': + foreach ($installedPackages as $package) { + $this->updateMap[$package->getId()] = true; + } + break; + } } foreach ($installedPackages as $package) { @@ -979,11 +967,17 @@ class Solver } foreach ($installedPackages as $package) { - $this->addRulesForUpdatePackages($package, true); + $this->addRulesForUpdatePackages($package); } foreach ($this->jobs as $job) { + if (empty($job['packages']) && $job['cmd'] == 'install') { + $this->addRule( + RuleSet::TYPE_JOB, + $this->createImpossibleRule(static::RULE_JOB_INSTALL, $job) + ); + } foreach ($job['packages'] as $package) { switch ($job['cmd']) { case 'install': @@ -997,33 +991,12 @@ class Solver // solver_addrpmrulesforweak(solv, &addedmap); foreach ($installedPackages as $package) { - // create a feature rule which allows downgrades - $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package, true); - $featureRule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); - - // create an update rule which does not allow downgrades - $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package, false); + $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); - if ($rule->equals($featureRule)) { - if ($this->policy->allowUninstall()) { - $featureRule->setWeak(true); - $this->addRule(RuleSet::TYPE_FEATURE, $featureRule); - $this->packageToFeatureRule[$package->getId()] = $rule; - } else { - $this->addRule(RuleSet::TYPE_UPDATE, $rule); - $this->packageToUpdateRule[$package->getId()] = $rule; - } - } else if ($this->policy->allowUninstall()) { - $featureRule->setWeak(true); - $rule->setWeak(true); - - $this->addRule(RuleSet::TYPE_FEATURE, $featureRule); - $this->addRule(RuleSet::TYPE_UPDATE, $rule); - - $this->packageToFeatureRule[$package->getId()] = $rule; - $this->packageToUpdateRule[$package->getId()] = $rule; - } + $rule->setWeak(true); + $this->addRule(RuleSet::TYPE_FEATURE, $rule); + $this->packageToFeatureRule[$package->getId()] = $rule; } foreach ($this->jobs as $job) { @@ -1076,6 +1049,10 @@ class Solver //findrecommendedsuggested(solv); //solver_prepare_solutions(solv); + if ($this->problems) { + throw new SolverProblemsException($this->problems, $this->learnedPool); + } + return $this->createTransaction(); } @@ -1091,9 +1068,6 @@ class Solver if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) { $literals = array(); - if (isset($this->packageToUpdateRule[$package->getId()])) { - $literals = array_merge($literals, $this->packageToUpdateRule[$package->getId()]->getLiterals()); - } if (isset($this->packageToFeatureRule[$package->getId()])) { $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals()); } @@ -1157,6 +1131,8 @@ class Solver protected function addDecision(Literal $l, $level) { + assert($this->decisionMap[$l->getPackageId()] == 0); + if ($l->isWanted()) { $this->decisionMap[$l->getPackageId()] = $level; } else { @@ -1167,6 +1143,9 @@ class Solver protected function addDecisionId($literalId, $level) { $packageId = abs($literalId); + + assert($this->decisionMap[$packageId] == 0); + if ($literalId > 0) { $this->decisionMap[$packageId] = $level; } else { @@ -1209,8 +1188,8 @@ class Solver { $packageId = abs($literalId); return ( - $this->decisionMap[$packageId] > 0 && !($literalId < 0) || - $this->decisionMap[$packageId] < 0 && $literalId > 0 + ($this->decisionMap[$packageId] > 0 && $literalId < 0) || + ($this->decisionMap[$packageId] < 0 && $literalId > 0) ); } @@ -1257,7 +1236,8 @@ class Solver continue; } - for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) { + $prevRule = null; + for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) { $nextRule = $rule->getNext($literal); if ($rule->isDisabled()) { @@ -1277,16 +1257,27 @@ class Solver if ($otherWatch !== $ruleLiteral->getId() && !$this->decisionsConflict($ruleLiteral)) { - if ($literal->getId() === $rule->watch1) { $rule->watch1 = $ruleLiteral->getId(); - $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ; + $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null; } else { $rule->watch2 = $ruleLiteral->getId(); - $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ; + $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null; + } + + if ($prevRule) { + if ($prevRule->next1 == $rule) { + $prevRule->next1 = $nextRule; + } else { + $prevRule->next2 = $nextRule; + } + } else { + $this->watches[$literal->getId()] = $nextRule; } $this->watches[$ruleLiteral->getId()] = $rule; + + $rule = $prevRule; continue 2; } } @@ -1517,7 +1508,7 @@ class Solver } $why = count($this->learnedPool) - 1; - + assert($learnedLiterals[0] !== null); $newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why); return array($ruleLevel, $newRule, $why); @@ -1843,11 +1834,7 @@ class Solver $rule = null; - if (isset($this->packageToUpdateRule[$literal->getPackageId()])) { - $rule = $this->packageToUpdateRule[$literal->getPackageId()]; - } - - if ((!$rule || $rule->isDisabled()) && isset($this->packageToFeatureRule[$literal->getPackageId()])) { + if (isset($this->packageToFeatureRule[$literal->getPackageId()])) { $rule = $this->packageToFeatureRule[$literal->getPackageId()]; } @@ -2057,8 +2044,10 @@ class Solver } if ($level > 0) { echo ' +' . $this->pool->packageById($packageId)."\n"; - } else { + } elseif ($level < 0) { echo ' -' . $this->pool->packageById($packageId)."\n"; + } else { + echo ' ?' . $this->pool->packageById($packageId)."\n"; } } echo "\n"; @@ -2072,4 +2061,41 @@ class Solver } echo "\n"; } + + private function printWatches() + { + echo "\nWatches:\n"; + foreach ($this->watches as $literalId => $watch) { + echo ' '.$this->literalFromId($literalId)."\n"; + $queue = array(array(' ', $watch)); + + while (!empty($queue)) { + list($indent, $watch) = array_pop($queue); + + echo $indent.$watch; + + if ($watch) { + echo ' [id='.$watch->getId().',watch1='.$this->literalFromId($watch->watch1).',watch2='.$this->literalFromId($watch->watch2)."]"; + } + + echo "\n"; + + if ($watch && ($watch->next1 == $watch || $watch->next2 == $watch)) { + if ($watch->next1 == $watch) { + echo $indent." 1 *RECURSION*"; + } + if ($watch->next2 == $watch) { + echo $indent." 2 *RECURSION*"; + } + } elseif ($watch && ($watch->next1 || $watch->next2)) { + $indent = str_replace(array('1', '2'), ' ', $indent); + + array_push($queue, array($indent.' 2 ', $watch->next2)); + array_push($queue, array($indent.' 1 ', $watch->next1)); + } + } + + echo "\n"; + } + } } diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php new file mode 100644 index 000000000..cbc4fd571 --- /dev/null +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + */ +class SolverProblemsException extends \RuntimeException +{ + protected $problems; + + public function __construct(array $problems, array $learnedPool) + { + $message = ''; + foreach ($problems as $i => $problem) { + $message .= '['; + foreach ($problem as $why) { + + if (is_int($why) && isset($learnedPool[$why])) { + $rules = $learnedPool[$why]; + } else { + $rules = $why; + } + + if (isset($rules['packages'])) { + $message .= $this->jobToText($rules); + } else { + $message .= '('; + foreach ($rules as $rule) { + if ($rule instanceof Rule) { + if ($rule->getType() == RuleSet::TYPE_LEARNED) { + $message .= 'learned: '; + } + $message .= $rule . ', '; + } else { + $message .= 'String(' . $rule . '), '; + } + } + $message .= ')'; + } + $message .= ', '; + } + $message .= "]\n"; + } + + parent::__construct($message); + } + + public function jobToText($job) + { + //$output = serialize($job); + $output = 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.implode(', ', $job['packages']).'])'; + return $output; + } +} diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 4bcc79d0c..5a30dacc5 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -24,11 +24,13 @@ abstract class VcsDownloader implements DownloaderInterface { protected $io; protected $process; + protected $filesystem; - public function __construct(IOInterface $io, ProcessExecutor $process = null) + public function __construct(IOInterface $io, ProcessExecutor $process = null, Filesystem $fs = null) { $this->io = $io; $this->process = $process ?: new ProcessExecutor; + $this->filesystem = $fs ?: new Filesystem; } /** @@ -74,8 +76,7 @@ abstract class VcsDownloader implements DownloaderInterface public function remove(PackageInterface $package, $path) { $this->enforceCleanDirectory($path); - $fs = new Filesystem(); - $fs->removeDirectory($path); + $this->filesystem->removeDirectory($path); } /** @@ -101,4 +102,4 @@ abstract class VcsDownloader implements DownloaderInterface * @throws \RuntimeException if the directory is not clean */ abstract protected function enforceCleanDirectory($path); -} \ No newline at end of file +} diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 1f94f4071..a3f0b2b74 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -78,11 +78,9 @@ class LibraryInstaller implements InstallerInterface */ public function install(PackageInterface $package) { + $this->initializeDirs(); $downloadPath = $this->getInstallPath($package); - $this->filesystem->ensureDirectoryExists($this->vendorDir); - $this->filesystem->ensureDirectoryExists($this->binDir); - // remove the binaries if it appears the package files are missing if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) { $this->removeBinaries($package); @@ -104,11 +102,9 @@ class LibraryInstaller implements InstallerInterface throw new \InvalidArgumentException('Package is not installed: '.$initial); } + $this->initializeDirs(); $downloadPath = $this->getInstallPath($initial); - $this->filesystem->ensureDirectoryExists($this->vendorDir); - $this->filesystem->ensureDirectoryExists($this->binDir); - $this->removeBinaries($initial); $this->downloadManager->update($initial, $target, $downloadPath); $this->installBinaries($target); @@ -191,6 +187,14 @@ class LibraryInstaller implements InstallerInterface } } + protected function initializeDirs() + { + $this->filesystem->ensureDirectoryExists($this->vendorDir); + $this->filesystem->ensureDirectoryExists($this->binDir); + $this->vendorDir = realpath($this->vendorDir); + $this->binDir = realpath($this->binDir); + } + private function generateWindowsProxyCode($bin, $link) { $binPath = $this->filesystem->findShortestPath($link, $bin); diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 286cf762a..e99a0e5f8 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -15,6 +15,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface protected $branches; protected $rootIdentifier; protected $infoCache = array(); + protected $isLocal = false; public function __construct($url, IOInterface $io, ProcessExecutor $process = null) { @@ -30,10 +31,15 @@ class GitDriver extends VcsDriver implements VcsDriverInterface { $url = escapeshellarg($this->url); $tmpDir = escapeshellarg($this->tmpDir); - if (is_dir($this->tmpDir)) { - $this->process->execute(sprintf('cd %s && git fetch origin', $tmpDir), $output); + + if (static::isLocalUrl($url)) { + $this->isLocal = true; } else { - $this->process->execute(sprintf('git clone %s %s', $url, $tmpDir), $output); + if (is_dir($this->tmpDir)) { + $this->process->execute(sprintf('cd %s && git fetch origin', $tmpDir), $output); + } else { + $this->process->execute(sprintf('git clone %s %s', $url, $tmpDir), $output); + } } $this->getTags(); @@ -47,11 +53,27 @@ class GitDriver extends VcsDriver implements VcsDriverInterface { if (null === $this->rootIdentifier) { $this->rootIdentifier = 'master'; - $this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->tmpDir)), $output); - foreach ($this->process->splitLines($output) as $branch) { - if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) { - $this->rootIdentifier = $match[1]; - break; + + if ($this->isLocal) { + // select currently checked out branch if master is not available + $this->process->execute(sprintf('cd %s && git branch --no-color', escapeshellarg($this->tmpDir)), $output); + $branches = $this->process->splitLines($output); + if (!in_array('* master', $branches)) { + foreach ($branches as $branch) { + if ($branch && preg_match('{^\* +(\S+)}', $branch, $match)) { + $this->rootIdentifier = $match[1]; + break; + } + } + } + } else { + // try to find a non-master remote HEAD branch + $this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->tmpDir)), $output); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) { + $this->rootIdentifier = $match[1]; + break; + } } } } @@ -101,7 +123,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface if (!isset($composer['time'])) { $this->process->execute(sprintf('cd %s && git log -1 --format=%%at %s', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $output); - $date = new \DateTime('@'.$output[0]); + $date = new \DateTime('@'.trim($output)); $composer['time'] = $date->format('Y-m-d H:i:s'); } $this->infoCache[$identifier] = $composer; @@ -132,7 +154,11 @@ class GitDriver extends VcsDriver implements VcsDriverInterface if (null === $this->branches) { $branches = array(); - $this->process->execute(sprintf('cd %s && git branch --no-color -rv', escapeshellarg($this->tmpDir)), $output); + $this->process->execute(sprintf( + 'cd %s && git branch --no-color --no-abbrev -v %s', + escapeshellarg($this->tmpDir), + $this->isLocal ? '' : '-r' + ), $output); foreach ($this->process->splitLines($output) as $branch) { if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { preg_match('{^ *[^/]+/(\S+) *([a-f0-9]+) .*$}', $branch, $match); @@ -169,6 +195,15 @@ class GitDriver extends VcsDriver implements VcsDriverInterface return true; } + // local filesystem + if (static::isLocalUrl($url)) { + $process = new ProcessExecutor(); + // check whether there is a git repo in that path + if ($process->execute(sprintf('cd %s && git show', escapeshellarg($url)), $output) === 0) { + return true; + } + } + if (!$deep) { return false; } diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index f65f683c5..1e73104f9 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -107,7 +107,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface if (!isset($composer['time'])) { $this->process->execute(sprintf('cd %s && hg log --template "{date|rfc822date}" -r %s', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $output); - $date = new \DateTime($output[0]); + $date = new \DateTime(trim($output)); $composer['time'] = $date->format('Y-m-d H:i:s'); } $this->infoCache[$identifier] = $composer; diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 7008f41be..75b631a42 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -68,4 +68,9 @@ abstract class VcsDriver $rfs = new RemoteFilesystem($this->io); return $rfs->getContents($this->url, $url, false); } + + protected static function isLocalUrl($url) + { + return (Boolean) preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url); + } } diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 1d6e82ab3..a2506d2df 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -19,10 +19,6 @@ class VcsRepository extends ArrayRepository public function __construct(array $config, IOInterface $io, array $drivers = null) { - if (!filter_var($config['url'], FILTER_VALIDATE_URL)) { - throw new \UnexpectedValueException('Invalid url given for VCS repository: '.$config['url']); - } - $this->drivers = $drivers ?: array( 'Composer\Repository\Vcs\GitHubDriver', 'Composer\Repository\Vcs\GitBitbucketDriver', diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index c4dcb0bcc..643be0428 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -30,4 +30,65 @@ class PoolTest extends TestCase $this->assertEquals(array($package), $pool->whatProvides('foo')); $this->assertEquals(array($package), $pool->whatProvides('foo')); } + + /** + * @expectedException \RuntimeException + */ + public function testGetPriorityForNotRegisteredRepository() + { + $pool = new Pool; + $repository = new ArrayRepository; + + $pool->getPriority($repository); + } + + public function testGetPriorityWhenRepositoryIsRegistered() + { + $pool = new Pool; + $firstRepository = new ArrayRepository; + $pool->addRepository($firstRepository); + $secondRepository = new ArrayRepository; + $pool->addRepository($secondRepository); + + $firstPriority = $pool->getPriority($firstRepository); + $secondPriority = $pool->getPriority($secondRepository); + + $this->assertEquals(0, $firstPriority); + $this->assertEquals(1, $secondPriority); + } + + public function testPackageById() + { + $pool = new Pool; + $repository = new ArrayRepository; + $package = $this->getPackage('foo', '1'); + + $repository->addPackage($package); + $pool->addRepository($repository); + + $this->assertSame($package, $pool->packageById(1)); + } + + public function testWhatProvidesWhenPackageCannotBeFound() + { + $pool = new Pool; + + $this->assertEquals(array(), $pool->whatProvides('foo')); + } + + public function testGetMaxId() + { + $pool = new Pool; + $repository = new ArrayRepository; + $firstPackage = $this->getPackage('foo', '1'); + $secondPackage = $this->getPackage('foo1', '1'); + + $this->assertEquals(0, $pool->getMaxId()); + + $repository->addPackage($firstPackage); + $repository->addPackage($secondPackage); + $pool->addRepository($repository); + + $this->assertEquals(2, $pool->getMaxId()); + } } diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php index da186edb2..e5010e0e4 100644 --- a/tests/Composer/Test/DependencyResolver/RequestTest.php +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -46,4 +46,16 @@ class RequestTest extends TestCase ), $request->getJobs()); } + + public function testUpdateAll() + { + $pool = new Pool; + $request = new Request($pool); + + $request->updateAll(); + + $this->assertEquals( + array(array('cmd' => 'update-all', 'packages' => array())), + $request->getJobs()); + } } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php index 2daa64eb6..28db18131 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php @@ -27,7 +27,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase new Rule(array(), 'job1', null), new Rule(array(), 'job2', null), ), - RuleSet::TYPE_UPDATE => array( + RuleSet::TYPE_FEATURE => array( new Rule(array(), 'update1', null), ), RuleSet::TYPE_PACKAGE => array(), @@ -39,15 +39,32 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase $ruleSetIterator = new RuleSetIterator($this->rules); $result = array(); - foreach ($ruleSetIterator as $rule) - { + foreach ($ruleSetIterator as $rule) { $result[] = $rule; } $expected = array( $this->rules[RuleSet::TYPE_JOB][0], $this->rules[RuleSet::TYPE_JOB][1], - $this->rules[RuleSet::TYPE_UPDATE][0], + $this->rules[RuleSet::TYPE_FEATURE][0], + ); + + $this->assertEquals($expected, $result); + } + + public function testKeys() + { + $ruleSetIterator = new RuleSetIterator($this->rules); + + $result = array(); + foreach ($ruleSetIterator as $key => $rule) { + $result[] = $key; + } + + $expected = array( + RuleSet::TYPE_JOB, + RuleSet::TYPE_JOB, + RuleSet::TYPE_FEATURE, ); $this->assertEquals($expected, $result); diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index fa42d4522..54ad88a58 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -14,8 +14,10 @@ namespace Composer\Test\DependencyResolver; use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\RuleSet; +use Composer\DependencyResolver\Literal; +use Composer\Test\TestCase; -class RuleSetTest extends \PHPUnit_Framework_TestCase +class RuleSetTest extends TestCase { public function testAdd() { @@ -25,10 +27,9 @@ class RuleSetTest extends \PHPUnit_Framework_TestCase new Rule(array(), 'job1', null), new Rule(array(), 'job2', null), ), - RuleSet::TYPE_UPDATE => array( + RuleSet::TYPE_FEATURE => array( new Rule(array(), 'update1', null), ), - RuleSet::TYPE_FEATURE => array(), RuleSet::TYPE_LEARNED => array(), RuleSet::TYPE_CHOICE => array(), ); @@ -36,9 +37,133 @@ class RuleSetTest extends \PHPUnit_Framework_TestCase $ruleSet = new RuleSet; $ruleSet->add($rules[RuleSet::TYPE_JOB][0], RuleSet::TYPE_JOB); - $ruleSet->add($rules[RuleSet::TYPE_UPDATE][0], RuleSet::TYPE_UPDATE); + $ruleSet->add($rules[RuleSet::TYPE_FEATURE][0], RuleSet::TYPE_FEATURE); $ruleSet->add($rules[RuleSet::TYPE_JOB][1], RuleSet::TYPE_JOB); $this->assertEquals($rules, $ruleSet->getRules()); } + + /** + * @expectedException \OutOfBoundsException + */ + public function testAddWhenTypeIsNotRecognized() + { + $ruleSet = new RuleSet; + + $ruleSet->add(new Rule(array(), 'job1', null), 7); + } + + public function testCount() + { + $ruleSet = new RuleSet; + + $ruleSet->add(new Rule(array(), 'job1', null), RuleSet::TYPE_JOB); + $ruleSet->add(new Rule(array(), 'job2', null), RuleSet::TYPE_JOB); + + $this->assertEquals(2, $ruleSet->count()); + } + + public function testRuleById() + { + $ruleSet = new RuleSet; + + $rule = new Rule(array(), 'job1', null); + $ruleSet->add($rule, RuleSet::TYPE_JOB); + + $this->assertSame($rule, $ruleSet->ruleById(0)); + } + + public function testGetIterator() + { + $ruleSet = new RuleSet; + + $rule1 = new Rule(array(), 'job1', null); + $rule2 = new Rule(array(), 'job1', null); + $ruleSet->add($rule1, RuleSet::TYPE_JOB); + $ruleSet->add($rule2, RuleSet::TYPE_FEATURE); + + $iterator = $ruleSet->getIterator(); + + $this->assertSame($rule1, $iterator->current()); + $iterator->next(); + $this->assertSame($rule2, $iterator->current()); + } + + public function testGetIteratorFor() + { + $ruleSet = new RuleSet; + $rule1 = new Rule(array(), 'job1', null); + $rule2 = new Rule(array(), 'job1', null); + + $ruleSet->add($rule1, RuleSet::TYPE_JOB); + $ruleSet->add($rule2, RuleSet::TYPE_FEATURE); + + $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_FEATURE); + + $this->assertSame($rule2, $iterator->current()); + } + + public function testGetIteratorWithout() + { + $ruleSet = new RuleSet; + $rule1 = new Rule(array(), 'job1', null); + $rule2 = new Rule(array(), 'job1', null); + + $ruleSet->add($rule1, RuleSet::TYPE_JOB); + $ruleSet->add($rule2, RuleSet::TYPE_FEATURE); + + $iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_JOB); + + $this->assertSame($rule2, $iterator->current()); + } + + public function testContainsEqual() + { + $ruleSet = new RuleSet; + + $rule = $this->getRuleMock(); + $rule->expects($this->any()) + ->method('getHash') + ->will($this->returnValue('rule_1_hash')); + $rule->expects($this->any()) + ->method('equals') + ->will($this->returnValue(true)); + + $rule2 = $this->getRuleMock(); + $rule2->expects($this->any()) + ->method('getHash') + ->will($this->returnValue('rule_2_hash')); + + $rule3 = $this->getRuleMock(); + $rule3->expects($this->any()) + ->method('getHash') + ->will($this->returnValue('rule_1_hash')); + $rule3->expects($this->any()) + ->method('equal') + ->will($this->returnValue(false)); + + $ruleSet->add($rule, RuleSet::TYPE_FEATURE); + + $this->assertTrue($ruleSet->containsEqual($rule)); + $this->assertFalse($ruleSet->containsEqual($rule2)); + $this->assertFalse($ruleSet->containsEqual($rule3)); + } + + public function testToString() + { + $ruleSet = new RuleSet; + $literal = new Literal($this->getPackage('foo', '2.1'), true); + $rule = new Rule(array($literal), 'job1', null); + + $ruleSet->add($rule, RuleSet::TYPE_FEATURE); + + $this->assertContains('FEATURE : (+foo-2.1.0.0)', $ruleSet->__toString()); + } + + private function getRuleMock() + { + return $this->getMockBuilder('Composer\DependencyResolver\Rule') + ->disableOriginalConstructor() + ->getMock(); + } } diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php new file mode 100644 index 000000000..f76f46eb1 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -0,0 +1,170 @@ + + * Jordi Boggiano + * + * 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\Rule; +use Composer\DependencyResolver\Literal; +use Composer\Test\TestCase; + +class RuleTest extends TestCase +{ + public function testGetHash() + { + $rule = new Rule(array(), 'job1', null); + $rule->ruleHash = '123'; + + $this->assertEquals('123', $rule->getHash()); + } + + public function testSetAndGetId() + { + $rule = new Rule(array(), 'job1', null); + $rule->setId(666); + + $this->assertEquals(666, $rule->getId()); + } + + public function testEqualsForRulesWithDifferentHashes() + { + $rule = new Rule(array(), 'job1', null); + $rule->ruleHash = '123'; + + $rule2 = new Rule(array(), 'job1', null); + $rule2->ruleHash = '321'; + + $this->assertFalse($rule->equals($rule2)); + } + + public function testEqualsForRulesWithDifferentLiterals() + { + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(1)); + $rule = new Rule(array($literal), 'job1', null); + $rule->ruleHash = '123'; + + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(12)); + $rule2 = new Rule(array($literal), 'job1', null); + $rule2->ruleHash = '123'; + + $this->assertFalse($rule->equals($rule2)); + } + + public function testEqualsForRulesWithDifferLiteralsQuantity() + { + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(1)); + $literal2 = $this->getLiteralMock(); + $literal2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(12)); + + $rule = new Rule(array($literal, $literal2), 'job1', null); + $rule->ruleHash = '123'; + $rule2 = new Rule(array($literal), 'job1', null); + $rule2->ruleHash = '123'; + + $this->assertFalse($rule->equals($rule2)); + } + + public function testEqualsForRulesWithThisSameLiterals() + { + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(1)); + $literal2 = $this->getLiteralMock(); + $literal2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(12)); + + $rule = new Rule(array($literal, $literal2), 'job1', null); + $rule2 = new Rule(array($literal, $literal2), 'job1', null); + + $this->assertTrue($rule->equals($rule2)); + } + + public function testSetAndGetType() + { + $rule = new Rule(array(), 'job1', null); + $rule->setType('someType'); + + $this->assertEquals('someType', $rule->getType()); + } + + public function testEnable() + { + $rule = new Rule(array(), 'job1', null); + $rule->disable(); + $rule->enable(); + + $this->assertTrue($rule->isEnabled()); + $this->assertFalse($rule->isDisabled()); + } + + public function testDisable() + { + $rule = new Rule(array(), 'job1', null); + $rule->enable(); + $rule->disable(); + + $this->assertTrue($rule->isDisabled()); + $this->assertFalse($rule->isEnabled()); + } + + public function testSetWeak() + { + $rule = new Rule(array(), 'job1', null); + $rule->setWeak(true); + + $rule2 = new Rule(array(), 'job1', null); + $rule2->setWeak(false); + + $this->assertTrue($rule->isWeak()); + $this->assertFalse($rule2->isWeak()); + } + + public function testIsAssertions() + { + $literal = $this->getLiteralMock(); + $literal2 = $this->getLiteralMock(); + $rule = new Rule(array($literal, $literal2), 'job1', null); + $rule2 = new Rule(array($literal), 'job1', null); + + $this->assertFalse($rule->isAssertion()); + $this->assertTrue($rule2->isAssertion()); + } + + public function testToString() + { + $literal = new Literal($this->getPackage('foo', '2.1'), true); + $literal2 = new Literal($this->getPackage('baz', '1.1'), false); + + $rule = new Rule(array($literal, $literal2), 'job1', null); + + $this->assertEquals('(-baz-1.1.0.0|+foo-2.1.0.0)', $rule->__toString()); + } + + private function getLiteralMock() + { + return $this->getMockBuilder('Composer\DependencyResolver\Literal') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 7496834e6..28ab91851 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -19,6 +19,7 @@ use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Solver; +use Composer\DependencyResolver\SolverProblemsException; use Composer\Package\Link; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Test\TestCase; @@ -54,13 +55,30 @@ class SolverTest extends TestCase )); } + public function testInstallNonExistingPackageFails() + { + $this->markTestIncomplete('Reporting this failure is not implemented/working yet'); + + $this->repo->addPackage($this->getPackage('A', '1.0')); + $this->reposComplete(); + + $this->request->install('B'); + + try { + $transaction = $this->solver->solve($this->request); + $this->fail('Unsolvable conflict did not resolve in exception.'); + } catch (SolverProblemsException $e) { + // TODO assert problem properties + } + } + public function testSolverInstallWithDeps() { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); $this->reposComplete(); @@ -122,12 +140,12 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->reposComplete(); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0.0.0'), 'requires'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0.0.0'), 'requires'))); - $this->request->install('A', new VersionConstraint('=', '1.0.0.0')); - $this->request->install('B', new VersionConstraint('=', '1.1.0.0')); - $this->request->update('A', new VersionConstraint('=', '1.0.0.0')); - $this->request->update('B', new VersionConstraint('=', '1.0.0.0')); + $this->request->install('A', $this->getVersionConstraint('=', '1.0.0.0')); + $this->request->install('B', $this->getVersionConstraint('=', '1.1.0.0')); + $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0')); + $this->request->update('B', $this->getVersionConstraint('=', '1.0.0.0')); $this->checkSolverResult(array( array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB), @@ -147,6 +165,26 @@ class SolverTest extends TestCase )); } + public function testSolverUpdateAll() + { + $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.1')); + $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); + + $packageA->setRequires(array(new Link('A', 'B', null, 'requires'))); + + $this->reposComplete(); + + $this->request->install('A'); + $this->request->updateAll(); + + $this->checkSolverResult(array( + array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB), + array('job' => 'update', 'from' => $packageA, 'to' => $newPackageA), + )); + } + public function testSolverUpdateCurrent() { $this->repoInstalled->addPackage($this->getPackage('A', '1.0')); @@ -158,6 +196,22 @@ class SolverTest extends TestCase $this->checkSolverResult(array()); } + public function testSolverUpdateOnlyUpdatesSelectedPackage() + { + $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($packageAnewer = $this->getPackage('A', '1.1')); + $this->repo->addPackage($packageBnewer = $this->getPackage('B', '1.1')); + + $this->reposComplete(); + + $this->request->update('A'); + + $this->checkSolverResult(array( + array('job' => 'update', 'from' => $packageA, 'to' => $packageAnewer), + )); + } + public function testSolverUpdateConstrained() { $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); @@ -165,7 +219,7 @@ class SolverTest extends TestCase $this->repo->addPackage($this->getPackage('A', '2.0')); $this->reposComplete(); - $this->request->install('A', new VersionConstraint('<', '2.0.0.0')); + $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0')); $this->request->update('A'); $this->checkSolverResult(array(array( @@ -182,8 +236,26 @@ class SolverTest extends TestCase $this->repo->addPackage($this->getPackage('A', '2.0')); $this->reposComplete(); - $this->request->install('A', new VersionConstraint('<', '2.0.0.0')); - $this->request->update('A', new VersionConstraint('=', '1.0.0.0')); + $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0')); + $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0')); + + $this->checkSolverResult(array(array( + 'job' => 'update', + 'from' => $packageA, + 'to' => $newPackageA, + ))); + } + + public function testSolverUpdateFullyConstrainedPrunesInstalledPackages() + { + $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repoInstalled->addPackage($this->getPackage('B', '1.0')); + $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.2')); + $this->repo->addPackage($this->getPackage('A', '2.0')); + $this->reposComplete(); + + $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0')); + $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0')); $this->checkSolverResult(array(array( 'job' => 'update', @@ -202,7 +274,7 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.1')); $this->repo->addPackage($this->getPackage('D', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); $this->reposComplete(); @@ -224,8 +296,8 @@ class SolverTest extends TestCase $this->repo->addPackage($middlePackageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($oldPackageB = $this->getPackage('B', '0.9')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires'))); - $packageA->setConflicts(array(new Link('A', 'B', new VersionConstraint('<', '1.0'), 'conflicts'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires'))); + $packageA->setConflicts(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.0'), 'conflicts'))); $this->reposComplete(); @@ -271,8 +343,8 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '0.8')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setProvides(array(new Link('Q', 'B', new VersionConstraint('=', '1.0'), 'provides'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setProvides(array(new Link('Q', 'B', $this->getVersionConstraint('=', '1.0'), 'provides'))); $this->reposComplete(); @@ -289,8 +361,8 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); $this->reposComplete(); @@ -306,8 +378,8 @@ class SolverTest extends TestCase { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); $this->reposComplete(); @@ -324,8 +396,8 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); - $packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'))); $this->reposComplete(); @@ -342,24 +414,24 @@ class SolverTest extends TestCase { $this->repo->addPackage($packageX = $this->getPackage('X', '1.0')); $packageX->setRequires(array( - new Link('X', 'A', new VersionConstraint('>=', '2.0.0.0'), 'requires'), - new Link('X', 'B', new VersionConstraint('>=', '2.0.0.0'), 'requires'))); + new Link('X', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'), + new Link('X', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'))); $this->repo->addPackage($packageA = $this->getPackage('A', '2.0.0')); $this->repo->addPackage($newPackageA = $this->getPackage('A', '2.1.0')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '2.1.0')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '2.0.0.0'), 'requires'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'))); // new package A depends on version of package B that does not exist // => new package A is not installable - $newPackageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '2.2.0.0'), 'requires'))); + $newPackageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.2.0.0'), 'requires'))); // add a package S replacing both A and B, so that S and B or S and A cannot be simultaneously installed // but an alternative option for A and B both exists // this creates a more difficult so solve conflict $this->repo->addPackage($packageS = $this->getPackage('S', '2.0.0')); - $packageS->setReplaces(array(new Link('S', 'A', new VersionConstraint('>=', '2.0.0.0'), 'replaces'), new Link('S', 'B', new VersionConstraint('>=', '2.0.0.0'), 'replaces'))); + $packageS->setReplaces(array(new Link('S', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'), new Link('S', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'))); $this->reposComplete(); @@ -377,8 +449,8 @@ class SolverTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageB1 = $this->getPackage('B', '0.9')); $this->repo->addPackage($packageB2 = $this->getPackage('B', '1.1')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); - $packageB2->setRequires(array(new Link('B', 'A', new VersionConstraint('>=', '1.0'), 'requires'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires'))); $this->reposComplete(); @@ -398,10 +470,10 @@ class SolverTest extends TestCase $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.0')); $this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); - $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); - $packageB->setRequires(array(new Link('B', 'Virtual', new VersionConstraint('>=', '1.0'), 'requires'))); - $packageC->setRequires(array(new Link('C', 'Virtual', new VersionConstraint('==', '1.0'), 'provides'))); - $packageD->setRequires(array(new Link('D', 'Virtual', new VersionConstraint('==', '1.0'), 'provides'))); + $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageB->setRequires(array(new Link('B', 'Virtual', $this->getVersionConstraint('>=', '1.0'), 'requires'))); + $packageC->setRequires(array(new Link('C', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides'))); + $packageD->setRequires(array(new Link('D', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides'))); $this->reposComplete(); @@ -414,6 +486,119 @@ class SolverTest extends TestCase )); } + /** + * If a replacer D replaces B and C with C not otherwise available, + * D must be installed instead of the original B. + */ + public function testUseReplacerIfNecessary() + { + $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); + $this->repo->addPackage($packageD2 = $this->getPackage('D', '1.1')); + + $packageA->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'), + new Link('A', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), + )); + + $packageD->setReplaces(array( + new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + )); + + $packageD2->setReplaces(array( + new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'), + )); + + $this->reposComplete(); + + $this->request->install('A'); + + $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageD2), + array('job' => 'install', 'package' => $packageA), + )); + } + + public function testIssue265() + { + $this->repo->addPackage($packageA1 = $this->getPackage('A', '2.0.999999-dev')); + $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.1-dev')); + $this->repo->addPackage($packageA3 = $this->getPackage('A', '2.2-dev')); + $this->repo->addPackage($packageB1 = $this->getPackage('B', '2.0.10')); + $this->repo->addPackage($packageB2 = $this->getPackage('B', '2.0.9')); + $this->repo->addPackage($packageC = $this->getPackage('C', '2.0-dev')); + $this->repo->addPackage($packageD = $this->getPackage('D', '2.0.9')); + + $packageC->setRequires(array( + new Link('C', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'), + new Link('C', 'D', $this->getVersionConstraint('>=', '2.0'), 'requires'), + )); + + $packageD->setRequires(array( + new Link('D', 'A', $this->getVersionConstraint('>=', '2.1'), 'requires'), + new Link('D', 'B', $this->getVersionConstraint('>=', '2.0-dev'), 'requires'), + )); + + $packageB1->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires'))); + $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires'))); + + $packageB2->setReplaces(array(new Link('B', 'D', $this->getVersionConstraint('==', '2.0.9.0'), 'replaces'))); + + $this->reposComplete(); + + $this->request->install('C', $this->getVersionConstraint('==', '2.0.0.0-dev')); + + $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException'); + + $this->solver->solve($this->request); + } + + public function testConflictResultEmpty() + { + $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));; + + $packageA->setConflicts(array( + new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'conflicts'), + )); + + $this->reposComplete(); + + $this->request->install('A'); + $this->request->install('B'); + + try { + $transaction = $this->solver->solve($this->request); + $this->fail('Unsolvable conflict did not resolve in exception.'); + } catch (SolverProblemsException $e) { + // TODO assert problem properties + } + } + + public function testUnsatisfiableRequires() + { + $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); + + $packageA->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('>=', '2.0'), 'requires'), + )); + + $this->reposComplete(); + + $this->request->install('A'); + + try { + $transaction = $this->solver->solve($this->request); + $this->fail('Unsolvable conflict did not resolve in exception.'); + } catch (SolverProblemsException $e) { + // TODO assert problem properties + } + } + protected function reposComplete() { $this->pool->addRepository($this->repoInstalled); @@ -443,5 +628,4 @@ class SolverTest extends TestCase $this->assertEquals($expected, $result); } - } diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php new file mode 100644 index 000000000..ff1c3ac07 --- /dev/null +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -0,0 +1,124 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Downloader; + +use Composer\Downloader\GitDownloader; + +class GitDownloaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testDownloadForPackageWithoutSourceReference() + { + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader->download($packageMock, '/path'); + } + + public function testDownload() + { + $expectedGitCommand = $this->getCmd('git clone \'https://github.com/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->once()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->once()) + ->method('execute') + ->with($this->equalTo($expectedGitCommand)); + + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader->download($packageMock, 'composerPath'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUpdateforPackageWithoutSourceReference() + { + $initialPackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); + } + + public function testUpdate() + { + $expectedGitUpdateCommand = $this->getCmd('cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain'); + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->any()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->at(0)) + ->method('execute') + ->with($this->equalTo($expectedGitResetCommand)); + $processExecutor->expects($this->at(1)) + ->method('execute') + ->with($this->equalTo($expectedGitUpdateCommand)); + + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader->update($packageMock, $packageMock, 'composerPath'); + } + + public function testRemove() + { + $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain'); + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->any()) + ->method('execute') + ->with($this->equalTo($expectedGitResetCommand)); + $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem->expects($this->any()) + ->method('removeDirectory') + ->with($this->equalTo('composerPath')); + + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor, $filesystem); + $downloader->remove($packageMock, 'composerPath'); + } + + public function testGetInstallationSource() + { + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface')); + + $this->assertEquals('source', $downloader->getInstallationSource()); + } + + private function getCmd($cmd) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return strtr($cmd, "'", '"'); + } + + return $cmd; + } +} diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php new file mode 100644 index 000000000..a7c246394 --- /dev/null +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -0,0 +1,124 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Downloader; + +use Composer\Downloader\HgDownloader; + +class HgDownloaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testDownloadForPackageWithoutSourceReference() + { + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader->download($packageMock, '/path'); + } + + public function testDownload() + { + $expectedGitCommand = $this->getCmd('hg clone \'https://mercurial.dev/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && hg up \'ref\''); + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->once()) + ->method('getSourceUrl') + ->will($this->returnValue('https://mercurial.dev/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->once()) + ->method('execute') + ->with($this->equalTo($expectedGitCommand)); + + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader->download($packageMock, 'composerPath'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUpdateforPackageWithoutSourceReference() + { + $initialPackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); + } + + public function testUpdate() + { + $expectedUpdateCommand = $this->getCmd('cd \'composerPath\' && hg pull && hg up \'ref\''); + $expectedResetCommand = $this->getCmd('cd \'composerPath\' && hg st'); + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->any()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->at(0)) + ->method('execute') + ->with($this->equalTo($expectedResetCommand)); + $processExecutor->expects($this->at(1)) + ->method('execute') + ->with($this->equalTo($expectedUpdateCommand)); + + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader->update($packageMock, $packageMock, 'composerPath'); + } + + public function testRemove() + { + $expectedResetCommand = $this->getCmd('cd \'composerPath\' && hg st'); + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->any()) + ->method('execute') + ->with($this->equalTo($expectedResetCommand)); + $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem->expects($this->any()) + ->method('removeDirectory') + ->with($this->equalTo('composerPath')); + + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor, $filesystem); + $downloader->remove($packageMock, 'composerPath'); + } + + public function testGetInstallationSource() + { + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface')); + + $this->assertEquals('source', $downloader->getInstallationSource()); + } + + private function getCmd($cmd) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return strtr($cmd, "'", '"'); + } + + return $cmd; + } +} diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php new file mode 100644 index 000000000..78450eb53 --- /dev/null +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -0,0 +1,219 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\IO; + +use Composer\IO\ConsoleIO; +use Composer\Test\TestCase; + +class ConsoleIOTest extends TestCase +{ + public function testIsInteractive() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $inputMock->expects($this->at(0)) + ->method('isInteractive') + ->will($this->returnValue(true)); + $inputMock->expects($this->at(1)) + ->method('isInteractive') + ->will($this->returnValue(false)); + + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + + $this->assertTrue($consoleIO->isInteractive()); + $this->assertFalse($consoleIO->isInteractive()); + } + + public function testWrite() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $outputMock->expects($this->once()) + ->method('write') + ->with($this->equalTo('some information about something'), $this->equalTo(false)); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->write('some information about something', false); + } + + public function testOverwrite() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $outputMock->expects($this->at(0)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(19)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(20)) + ->method('write') + ->with($this->equalTo('some information'), $this->equalTo(false)); + $outputMock->expects($this->at(21)) + ->method('write') + ->with($this->equalTo(' '), $this->equalTo(false)); + $outputMock->expects($this->at(24)) + ->method('write') + ->with($this->equalTo(' '), $this->equalTo(false)); + $outputMock->expects($this->at(25)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(28)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(29)) + ->method('write') + ->with($this->equalTo('')); + + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->overwrite('some information', true, 20); + } + + public function testAsk() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $dialogMock = $this->getMock('Symfony\Component\Console\Helper\DialogHelper'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $dialogMock->expects($this->once()) + ->method('ask') + ->with($this->isInstanceOf('Symfony\Component\Console\Output\OutputInterface'), + $this->equalTo('Why?'), + $this->equalTo('default')); + $helperMock->expects($this->once()) + ->method('get') + ->with($this->equalTo('dialog')) + ->will($this->returnValue($dialogMock)); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->ask('Why?', 'default'); + } + + public function testAskConfirmation() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $dialogMock = $this->getMock('Symfony\Component\Console\Helper\DialogHelper'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $dialogMock->expects($this->once()) + ->method('askConfirmation') + ->with($this->isInstanceOf('Symfony\Component\Console\Output\OutputInterface'), + $this->equalTo('Why?'), + $this->equalTo('default')); + $helperMock->expects($this->once()) + ->method('get') + ->with($this->equalTo('dialog')) + ->will($this->returnValue($dialogMock)); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->askConfirmation('Why?', 'default'); + } + + public function testAskAndValidate() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $dialogMock = $this->getMock('Symfony\Component\Console\Helper\DialogHelper'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $dialogMock->expects($this->once()) + ->method('askAndValidate') + ->with($this->isInstanceOf('Symfony\Component\Console\Output\OutputInterface'), + $this->equalTo('Why?'), + $this->equalTo('validator'), + $this->equalTo(10), + $this->equalTo('default')); + $helperMock->expects($this->once()) + ->method('get') + ->with($this->equalTo('dialog')) + ->will($this->returnValue($dialogMock)); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->askAndValidate('Why?', 'validator', 10, 'default'); + } + + public function testSetAndGetAuthorization() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + + $this->assertEquals( + array('username' => 'l3l0', 'password' => 'passwd'), + $consoleIO->getAuthorization('repoName') + ); + } + + public function testGetAuthorizationWhenDidNotSet() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + + $this->assertEquals( + array('username' => null, 'password' => null), + $consoleIO->getAuthorization('repoName') + ); + } + + public function testHasAuthorization() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + + $this->assertTrue($consoleIO->hasAuthorization('repoName')); + $this->assertFalse($consoleIO->hasAuthorization('repoName2')); + } + + public function testGetLastUsername() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + $consoleIO->setAuthorization('repoName2', 'l3l02', 'passwd2'); + + $this->assertEquals('l3l02', $consoleIO->getLastUsername()); + } + + public function testGetLastPassword() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + $consoleIO->setAuthorization('repoName2', 'l3l02', 'passwd2'); + + $this->assertEquals('passwd2', $consoleIO->getLastPassword()); + } +} diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 00711bb11..fc205eceb 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -133,6 +133,9 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase public function testUnicode() { + if (!function_exists('mb_convert_encoding')) { + $this->markTestSkipped('Test requires the mbstring extension'); + } $data = array("Žluťoučký \" kůň" => "úpěl ďábelské ódy za €"); $json = '{ "Žluťoučký \" kůň": "úpěl ďábelské ódy za €" @@ -143,6 +146,9 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase public function testEscapedSlashes() { + if (!function_exists('mb_convert_encoding')) { + $this->markTestSkipped('Test requires the mbstring extension'); + } $data = "\\/fooƌ"; $this->assertJsonFormat('"\\\\\\/fooƌ"', $data, JSON_UNESCAPED_UNICODE); diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index 17255ab2e..1e3ae257e 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -1,34 +1,43 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test; - -use Composer\Package\Version\VersionParser; -use Composer\Package\MemoryPackage; - -abstract class TestCase extends \PHPUnit_Framework_TestCase -{ - private static $versionParser; - - public static function setUpBeforeClass() - { - if (!self::$versionParser) { - self::$versionParser = new VersionParser(); - } - } - - protected function getPackage($name, $version) - { - $normVersion = self::$versionParser->normalize($version); - return new MemoryPackage($name, $normVersion, $version); - } -} \ No newline at end of file + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use Composer\Package\Version\VersionParser; +use Composer\Package\MemoryPackage; +use Composer\Package\LinkConstraint\VersionConstraint; + +abstract class TestCase extends \PHPUnit_Framework_TestCase +{ + private static $versionParser; + + public static function setUpBeforeClass() + { + if (!self::$versionParser) { + self::$versionParser = new VersionParser(); + } + } + + protected function getVersionConstraint($operator, $version) + { + return new VersionConstraint( + $operator, + self::$versionParser->normalize($version) + ); + } + + protected function getPackage($name, $version) + { + $normVersion = self::$versionParser->normalize($version); + return new MemoryPackage($name, $normVersion, $version); + } +}