1
0
Fork 0

Merge remote-tracking branch 'upstream/master' into docs

* upstream/master: (42 commits)
  Correct the parent path in the watch tree, after moving a rule out of the path
  Add config in composer json schema
  Add scripts to json schema
  Use updateAll request method in upddate mode in install command
  Readd update-all jobs and make them available through the request
  Fix local git repo handling in GitDriver
  Add getVersionConstraint test case to avoid version normalization issues
  Fix line endings of Composer TestCase
  Correct placing of braces
  Clean up the incomplete marker like suggested in https://github.com/composer/composer/pull/324#r465391
  When changing watched literals of a rule, update the parent's next pointer
  Fix TODO tags to confirm with the projects standard
  Display undecided literals as undecided with a ?, when printing the decision map
  Add a debug print method for the entire watch tree to the solver
  Support both require and requires as depends link-type arg
  Create an impossible rule when trying to install something that doesn't exist.
  Add todos to explain why try/catch is inside the test
  Add SolverProblemsException and test basic solver failures
  Deciding to install a package and wanting to install it, is not a conflict
  Restore realpath behavior
  ...

Conflicts:
	Resources/composer-schema.json
pull/319/head
Igor Wiedler 2012-02-19 21:09:06 +01:00
commit ee73b332e5
27 changed files with 1437 additions and 204 deletions

View File

@ -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).", "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 "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": { "extra": {
"type": ["object", "array"], "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.", "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": { "items": {
"type": "string" "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.",
}
}
} }
} }
} }

View File

@ -25,6 +25,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class DependsCommand extends Command class DependsCommand extends Command
{ {
protected $linkTypes = array('require', 'recommend', 'suggest');
protected function configure() protected function configure()
{ {
$this $this
@ -32,7 +34,7 @@ class DependsCommand extends Command
->setDescription('Shows which packages depend on the given package') ->setDescription('Shows which packages depend on the given package')
->setDefinition(array( ->setDefinition(array(
new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'), 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(<<<EOT ->setHelp(<<<EOT
Displays detailed information about where a package is referenced. Displays detailed information about where a package is referenced.
@ -74,10 +76,15 @@ EOT
$repos = $composer->getRepositoryManager()->getRepositories(); $repos = $composer->getRepositoryManager()->getRepositories();
$types = $input->getOption('link-type'); $types = $input->getOption('link-type');
foreach ($repos as $repository) { foreach ($repos as $repository) {
foreach ($repository->getPackages() as $package) { foreach ($repository->getPackages() as $package) {
foreach ($types as $type) { 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 ($link->getTarget() === $needle) {
if ($verbose) { if ($verbose) {
$references[] = array($type, $package, $link); $references[] = array($type, $package, $link);

View File

@ -119,14 +119,9 @@ EOT
$installedPackages = $installedRepo->getPackages(); $installedPackages = $installedRepo->getPackages();
$links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests); $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests);
foreach ($links as $link) { $request->updateAll();
foreach ($installedPackages as $package) {
if ($package->getName() === $link->getTarget()) {
$request->update($package->getName(), new VersionConstraint('=', $package->getVersion()));
break;
}
}
foreach ($links as $link) {
$request->install($link->getTarget(), $link->getConstraint()); $request->install($link->getTarget(), $link->getConstraint());
} }
} elseif ($composer->getLocker()->isLocked()) { } elseif ($composer->getLocker()->isLocked()) {

View File

@ -21,16 +21,6 @@ use Composer\Package\LinkConstraint\VersionConstraint;
*/ */
class DefaultPolicy implements PolicyInterface class DefaultPolicy implements PolicyInterface
{ {
public function allowUninstall()
{
return true;
}
public function allowDowngrade()
{
return true;
}
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator) public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
{ {
$constraint = new VersionConstraint($operator, $b->getVersion()); $constraint = new VersionConstraint($operator, $b->getVersion());
@ -39,16 +29,11 @@ class DefaultPolicy implements PolicyInterface
return $constraint->matchSpecific($version); 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(); $packages = array();
foreach ($pool->whatProvides($package->getName()) as $candidate) { 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) { if ($candidate !== $package) {
$packages[] = $candidate; $packages[] = $candidate;
} }

View File

@ -20,10 +20,8 @@ use Composer\Package\PackageInterface;
*/ */
interface PolicyInterface interface PolicyInterface
{ {
function allowUninstall();
function allowDowngrade();
function versionCompare(PackageInterface $a, PackageInterface $b, $operator); 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 installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package);
function selectPreferedPackages(Pool $pool, array $installedMap, array $literals); function selectPreferedPackages(Pool $pool, array $installedMap, array $literals);
} }

View File

@ -55,6 +55,11 @@ class Request
); );
} }
public function updateAll()
{
$this->jobs[] = array('cmd' => 'update-all', 'packages' => array());
}
public function getJobs() public function getJobs()
{ {
return $this->jobs; return $this->jobs;

View File

@ -29,6 +29,8 @@ class Rule
public $next1; public $next1;
public $next2; public $next2;
public $ruleHash;
public function __construct(array $literals, $reason, $reasonData) public function __construct(array $literals, $reason, $reasonData)
{ {
// sort all packages ascending by id // sort all packages ascending by id
@ -85,7 +87,7 @@ class Rule
} }
for ($i = 0, $n = count($this->literals); $i < $n; $i++) { 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; return false;
} }
} }

View File

@ -20,7 +20,6 @@ class RuleSet implements \IteratorAggregate, \Countable
// highest priority => lowest number // highest priority => lowest number
const TYPE_PACKAGE = 0; const TYPE_PACKAGE = 0;
const TYPE_JOB = 1; const TYPE_JOB = 1;
const TYPE_UPDATE = 2;
const TYPE_FEATURE = 3; const TYPE_FEATURE = 3;
const TYPE_CHOICE = 4; const TYPE_CHOICE = 4;
const TYPE_LEARNED = 5; const TYPE_LEARNED = 5;
@ -29,7 +28,6 @@ class RuleSet implements \IteratorAggregate, \Countable
-1 => 'UNKNOWN', -1 => 'UNKNOWN',
self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_PACKAGE => 'PACKAGE',
self::TYPE_FEATURE => 'FEATURE', self::TYPE_FEATURE => 'FEATURE',
self::TYPE_UPDATE => 'UPDATE',
self::TYPE_JOB => 'JOB', self::TYPE_JOB => 'JOB',
self::TYPE_CHOICE => 'CHOICE', self::TYPE_CHOICE => 'CHOICE',
self::TYPE_LEARNED => 'LEARNED', self::TYPE_LEARNED => 'LEARNED',

View File

@ -52,7 +52,6 @@ class Solver
protected $decisionMap; protected $decisionMap;
protected $installedMap; protected $installedMap;
protected $packageToUpdateRule = array();
protected $packageToFeatureRule = array(); protected $packageToFeatureRule = array();
public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed) public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
@ -77,7 +76,7 @@ class Solver
* that goes with the reason * that goes with the reason
* @return Rule The generated rule or null if tautological * @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)); $literals = array(new Literal($package, false));
@ -128,7 +127,7 @@ class Solver
* goes with the reason * goes with the reason
* @return Rule The generated rule * @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)); return new Rule(new Literal($package, true));
} }
@ -146,7 +145,7 @@ class Solver
* the reason * the reason
* @return Rule The generated rule * @return Rule The generated rule
*/ */
public function createInstallOneOfRule(array $packages, $reason, $reasonData = null) protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
{ {
if (empty($packages)) { if (empty($packages)) {
return $this->createImpossibleRule($reason, $reasonData); return $this->createImpossibleRule($reason, $reasonData);
@ -172,7 +171,7 @@ class Solver
* goes with the reason * goes with the reason
* @return Rule The generated rule * @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); return new Rule(array(new Literal($package, false)), $reason, $reasonData);
} }
@ -191,7 +190,7 @@ class Solver
* goes with the reason * goes with the reason
* @return Rule The generated rule * @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 // ignore self conflict
if ($issuer === $provider) { if ($issuer === $provider) {
@ -212,7 +211,7 @@ class Solver
* the reason * the reason
* @return Rule An empty rule * @return Rule An empty rule
*/ */
public function createImpossibleRule($reason, $reasonData = null) protected function createImpossibleRule($reason, $reasonData = null)
{ {
return new Rule(array(), $reason, $reasonData); 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 = new \SplQueue;
$workQueue->enqueue($package); $workQueue->enqueue($package);
@ -305,7 +304,7 @@ class Solver
// if ignoreinstalledsobsoletes is not set, we're also checking // if ignoreinstalledsobsoletes is not set, we're also checking
// obsoletes of installed packages (like newer rpm versions) // obsoletes of installed packages (like newer rpm versions)
// //
/** @TODO: if ($this->noInstalledObsoletes) */ /** TODO if ($this->noInstalledObsoletes) */
if (true) { if (true) {
$noObsoletes = isset($this->noObsoletes[$package->getId()]); $noObsoletes = isset($this->noObsoletes[$package->getId()]);
$isInstalled = (isset($this->installedMap[$package->getId()])); $isInstalled = (isset($this->installedMap[$package->getId()]));
@ -375,9 +374,9 @@ class Solver
* be added * be added
* @param bool $allowAll Whether downgrades are allowed * @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); $this->addRulesForPackage($package);
@ -508,7 +507,7 @@ class Solver
// push all of our rules (can only be feature or job rules) // push all of our rules (can only be feature or job rules)
// asserting this literal on the problem stack // 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()) { if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) {
continue; continue;
} }
@ -571,7 +570,7 @@ class Solver
} }
} }
public function addChoiceRules() protected function addChoiceRules()
{ {
// void // void
@ -882,11 +881,6 @@ class Solver
protected function disableUpdateRule($package) 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()])) { if (isset($this->packageToFeatureRule[$package->getId()])) {
$this->packageToFeatureRule[$package->getId()]->disable(); $this->packageToFeatureRule[$package->getId()]->disable();
} }
@ -944,20 +938,6 @@ class Solver
} }
foreach ($this->jobs as $job) { 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) { foreach ($job['packages'] as $package) {
switch ($job['cmd']) { switch ($job['cmd']) {
case 'fix': case 'fix':
@ -972,6 +952,14 @@ class Solver
break; break;
} }
} }
switch ($job['cmd']) {
case 'update-all':
foreach ($installedPackages as $package) {
$this->updateMap[$package->getId()] = true;
}
break;
}
} }
foreach ($installedPackages as $package) { foreach ($installedPackages as $package) {
@ -979,11 +967,17 @@ class Solver
} }
foreach ($installedPackages as $package) { foreach ($installedPackages as $package) {
$this->addRulesForUpdatePackages($package, true); $this->addRulesForUpdatePackages($package);
} }
foreach ($this->jobs as $job) { 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) { foreach ($job['packages'] as $package) {
switch ($job['cmd']) { switch ($job['cmd']) {
case 'install': case 'install':
@ -997,33 +991,12 @@ class Solver
// solver_addrpmrulesforweak(solv, &addedmap); // solver_addrpmrulesforweak(solv, &addedmap);
foreach ($installedPackages as $package) { foreach ($installedPackages as $package) {
// create a feature rule which allows downgrades $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
$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);
$rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
if ($rule->equals($featureRule)) { $rule->setWeak(true);
if ($this->policy->allowUninstall()) { $this->addRule(RuleSet::TYPE_FEATURE, $rule);
$featureRule->setWeak(true); $this->packageToFeatureRule[$package->getId()] = $rule;
$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;
}
} }
foreach ($this->jobs as $job) { foreach ($this->jobs as $job) {
@ -1076,6 +1049,10 @@ class Solver
//findrecommendedsuggested(solv); //findrecommendedsuggested(solv);
//solver_prepare_solutions(solv); //solver_prepare_solutions(solv);
if ($this->problems) {
throw new SolverProblemsException($this->problems, $this->learnedPool);
}
return $this->createTransaction(); return $this->createTransaction();
} }
@ -1091,9 +1068,6 @@ class Solver
if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) { if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) {
$literals = array(); $literals = array();
if (isset($this->packageToUpdateRule[$package->getId()])) {
$literals = array_merge($literals, $this->packageToUpdateRule[$package->getId()]->getLiterals());
}
if (isset($this->packageToFeatureRule[$package->getId()])) { if (isset($this->packageToFeatureRule[$package->getId()])) {
$literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals()); $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals());
} }
@ -1157,6 +1131,8 @@ class Solver
protected function addDecision(Literal $l, $level) protected function addDecision(Literal $l, $level)
{ {
assert($this->decisionMap[$l->getPackageId()] == 0);
if ($l->isWanted()) { if ($l->isWanted()) {
$this->decisionMap[$l->getPackageId()] = $level; $this->decisionMap[$l->getPackageId()] = $level;
} else { } else {
@ -1167,6 +1143,9 @@ class Solver
protected function addDecisionId($literalId, $level) protected function addDecisionId($literalId, $level)
{ {
$packageId = abs($literalId); $packageId = abs($literalId);
assert($this->decisionMap[$packageId] == 0);
if ($literalId > 0) { if ($literalId > 0) {
$this->decisionMap[$packageId] = $level; $this->decisionMap[$packageId] = $level;
} else { } else {
@ -1209,8 +1188,8 @@ class Solver
{ {
$packageId = abs($literalId); $packageId = abs($literalId);
return ( 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; 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); $nextRule = $rule->getNext($literal);
if ($rule->isDisabled()) { if ($rule->isDisabled()) {
@ -1277,16 +1257,27 @@ class Solver
if ($otherWatch !== $ruleLiteral->getId() && if ($otherWatch !== $ruleLiteral->getId() &&
!$this->decisionsConflict($ruleLiteral)) { !$this->decisionsConflict($ruleLiteral)) {
if ($literal->getId() === $rule->watch1) { if ($literal->getId() === $rule->watch1) {
$rule->watch1 = $ruleLiteral->getId(); $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 { } else {
$rule->watch2 = $ruleLiteral->getId(); $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; $this->watches[$ruleLiteral->getId()] = $rule;
$rule = $prevRule;
continue 2; continue 2;
} }
} }
@ -1517,7 +1508,7 @@ class Solver
} }
$why = count($this->learnedPool) - 1; $why = count($this->learnedPool) - 1;
assert($learnedLiterals[0] !== null);
$newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why); $newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why);
return array($ruleLevel, $newRule, $why); return array($ruleLevel, $newRule, $why);
@ -1843,11 +1834,7 @@ class Solver
$rule = null; $rule = null;
if (isset($this->packageToUpdateRule[$literal->getPackageId()])) { if (isset($this->packageToFeatureRule[$literal->getPackageId()])) {
$rule = $this->packageToUpdateRule[$literal->getPackageId()];
}
if ((!$rule || $rule->isDisabled()) && isset($this->packageToFeatureRule[$literal->getPackageId()])) {
$rule = $this->packageToFeatureRule[$literal->getPackageId()]; $rule = $this->packageToFeatureRule[$literal->getPackageId()];
} }
@ -2057,8 +2044,10 @@ class Solver
} }
if ($level > 0) { if ($level > 0) {
echo ' +' . $this->pool->packageById($packageId)."\n"; echo ' +' . $this->pool->packageById($packageId)."\n";
} else { } elseif ($level < 0) {
echo ' -' . $this->pool->packageById($packageId)."\n"; echo ' -' . $this->pool->packageById($packageId)."\n";
} else {
echo ' ?' . $this->pool->packageById($packageId)."\n";
} }
} }
echo "\n"; echo "\n";
@ -2072,4 +2061,41 @@ class Solver
} }
echo "\n"; 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";
}
}
} }

View File

@ -0,0 +1,65 @@
<?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\DependencyResolver;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
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;
}
}

View File

@ -24,11 +24,13 @@ abstract class VcsDownloader implements DownloaderInterface
{ {
protected $io; protected $io;
protected $process; 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->io = $io;
$this->process = $process ?: new ProcessExecutor; $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) public function remove(PackageInterface $package, $path)
{ {
$this->enforceCleanDirectory($path); $this->enforceCleanDirectory($path);
$fs = new Filesystem(); $this->filesystem->removeDirectory($path);
$fs->removeDirectory($path);
} }
/** /**
@ -101,4 +102,4 @@ abstract class VcsDownloader implements DownloaderInterface
* @throws \RuntimeException if the directory is not clean * @throws \RuntimeException if the directory is not clean
*/ */
abstract protected function enforceCleanDirectory($path); abstract protected function enforceCleanDirectory($path);
} }

View File

@ -78,11 +78,9 @@ class LibraryInstaller implements InstallerInterface
*/ */
public function install(PackageInterface $package) public function install(PackageInterface $package)
{ {
$this->initializeDirs();
$downloadPath = $this->getInstallPath($package); $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 // remove the binaries if it appears the package files are missing
if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) { if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) {
$this->removeBinaries($package); $this->removeBinaries($package);
@ -104,11 +102,9 @@ class LibraryInstaller implements InstallerInterface
throw new \InvalidArgumentException('Package is not installed: '.$initial); throw new \InvalidArgumentException('Package is not installed: '.$initial);
} }
$this->initializeDirs();
$downloadPath = $this->getInstallPath($initial); $downloadPath = $this->getInstallPath($initial);
$this->filesystem->ensureDirectoryExists($this->vendorDir);
$this->filesystem->ensureDirectoryExists($this->binDir);
$this->removeBinaries($initial); $this->removeBinaries($initial);
$this->downloadManager->update($initial, $target, $downloadPath); $this->downloadManager->update($initial, $target, $downloadPath);
$this->installBinaries($target); $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) private function generateWindowsProxyCode($bin, $link)
{ {
$binPath = $this->filesystem->findShortestPath($link, $bin); $binPath = $this->filesystem->findShortestPath($link, $bin);

View File

@ -15,6 +15,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
protected $branches; protected $branches;
protected $rootIdentifier; protected $rootIdentifier;
protected $infoCache = array(); protected $infoCache = array();
protected $isLocal = false;
public function __construct($url, IOInterface $io, ProcessExecutor $process = null) public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
{ {
@ -30,10 +31,15 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
{ {
$url = escapeshellarg($this->url); $url = escapeshellarg($this->url);
$tmpDir = escapeshellarg($this->tmpDir); $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 { } 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(); $this->getTags();
@ -47,11 +53,27 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
{ {
if (null === $this->rootIdentifier) { if (null === $this->rootIdentifier) {
$this->rootIdentifier = 'master'; $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 ($this->isLocal) {
if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) { // select currently checked out branch if master is not available
$this->rootIdentifier = $match[1]; $this->process->execute(sprintf('cd %s && git branch --no-color', escapeshellarg($this->tmpDir)), $output);
break; $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'])) { if (!isset($composer['time'])) {
$this->process->execute(sprintf('cd %s && git log -1 --format=%%at %s', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $output); $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'); $composer['time'] = $date->format('Y-m-d H:i:s');
} }
$this->infoCache[$identifier] = $composer; $this->infoCache[$identifier] = $composer;
@ -132,7 +154,11 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
if (null === $this->branches) { if (null === $this->branches) {
$branches = array(); $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) { foreach ($this->process->splitLines($output) as $branch) {
if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
preg_match('{^ *[^/]+/(\S+) *([a-f0-9]+) .*$}', $branch, $match); preg_match('{^ *[^/]+/(\S+) *([a-f0-9]+) .*$}', $branch, $match);
@ -169,6 +195,15 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
return true; 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) { if (!$deep) {
return false; return false;
} }

View File

@ -107,7 +107,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface
if (!isset($composer['time'])) { if (!isset($composer['time'])) {
$this->process->execute(sprintf('cd %s && hg log --template "{date|rfc822date}" -r %s', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $output); $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'); $composer['time'] = $date->format('Y-m-d H:i:s');
} }
$this->infoCache[$identifier] = $composer; $this->infoCache[$identifier] = $composer;

View File

@ -68,4 +68,9 @@ abstract class VcsDriver
$rfs = new RemoteFilesystem($this->io); $rfs = new RemoteFilesystem($this->io);
return $rfs->getContents($this->url, $url, false); return $rfs->getContents($this->url, $url, false);
} }
protected static function isLocalUrl($url)
{
return (Boolean) preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url);
}
} }

View File

@ -19,10 +19,6 @@ class VcsRepository extends ArrayRepository
public function __construct(array $config, IOInterface $io, array $drivers = null) 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( $this->drivers = $drivers ?: array(
'Composer\Repository\Vcs\GitHubDriver', 'Composer\Repository\Vcs\GitHubDriver',
'Composer\Repository\Vcs\GitBitbucketDriver', 'Composer\Repository\Vcs\GitBitbucketDriver',

View File

@ -30,4 +30,65 @@ class PoolTest extends TestCase
$this->assertEquals(array($package), $pool->whatProvides('foo')); $this->assertEquals(array($package), $pool->whatProvides('foo'));
$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());
}
} }

View File

@ -46,4 +46,16 @@ class RequestTest extends TestCase
), ),
$request->getJobs()); $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());
}
} }

View File

@ -27,7 +27,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
new Rule(array(), 'job1', null), new Rule(array(), 'job1', null),
new Rule(array(), 'job2', null), new Rule(array(), 'job2', null),
), ),
RuleSet::TYPE_UPDATE => array( RuleSet::TYPE_FEATURE => array(
new Rule(array(), 'update1', null), new Rule(array(), 'update1', null),
), ),
RuleSet::TYPE_PACKAGE => array(), RuleSet::TYPE_PACKAGE => array(),
@ -39,15 +39,32 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
$ruleSetIterator = new RuleSetIterator($this->rules); $ruleSetIterator = new RuleSetIterator($this->rules);
$result = array(); $result = array();
foreach ($ruleSetIterator as $rule) foreach ($ruleSetIterator as $rule) {
{
$result[] = $rule; $result[] = $rule;
} }
$expected = array( $expected = array(
$this->rules[RuleSet::TYPE_JOB][0], $this->rules[RuleSet::TYPE_JOB][0],
$this->rules[RuleSet::TYPE_JOB][1], $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); $this->assertEquals($expected, $result);

View File

@ -14,8 +14,10 @@ namespace Composer\Test\DependencyResolver;
use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\Rule;
use Composer\DependencyResolver\RuleSet; 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() public function testAdd()
{ {
@ -25,10 +27,9 @@ class RuleSetTest extends \PHPUnit_Framework_TestCase
new Rule(array(), 'job1', null), new Rule(array(), 'job1', null),
new Rule(array(), 'job2', null), new Rule(array(), 'job2', null),
), ),
RuleSet::TYPE_UPDATE => array( RuleSet::TYPE_FEATURE => array(
new Rule(array(), 'update1', null), new Rule(array(), 'update1', null),
), ),
RuleSet::TYPE_FEATURE => array(),
RuleSet::TYPE_LEARNED => array(), RuleSet::TYPE_LEARNED => array(),
RuleSet::TYPE_CHOICE => array(), RuleSet::TYPE_CHOICE => array(),
); );
@ -36,9 +37,133 @@ class RuleSetTest extends \PHPUnit_Framework_TestCase
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$ruleSet->add($rules[RuleSet::TYPE_JOB][0], RuleSet::TYPE_JOB); $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); $ruleSet->add($rules[RuleSet::TYPE_JOB][1], RuleSet::TYPE_JOB);
$this->assertEquals($rules, $ruleSet->getRules()); $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();
}
} }

View File

@ -0,0 +1,170 @@
<?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\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();
}
}

View File

@ -19,6 +19,7 @@ use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Test\TestCase; 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() public function testSolverInstallWithDeps()
{ {
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $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(); $this->reposComplete();
@ -122,12 +140,12 @@ class SolverTest extends TestCase
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$this->reposComplete(); $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('A', $this->getVersionConstraint('=', '1.0.0.0'));
$this->request->install('B', new VersionConstraint('=', '1.1.0.0')); $this->request->install('B', $this->getVersionConstraint('=', '1.1.0.0'));
$this->request->update('A', new VersionConstraint('=', '1.0.0.0')); $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
$this->request->update('B', new VersionConstraint('=', '1.0.0.0')); $this->request->update('B', $this->getVersionConstraint('=', '1.0.0.0'));
$this->checkSolverResult(array( $this->checkSolverResult(array(
array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB), 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() public function testSolverUpdateCurrent()
{ {
$this->repoInstalled->addPackage($this->getPackage('A', '1.0')); $this->repoInstalled->addPackage($this->getPackage('A', '1.0'));
@ -158,6 +196,22 @@ class SolverTest extends TestCase
$this->checkSolverResult(array()); $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() public function testSolverUpdateConstrained()
{ {
$this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); $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->repo->addPackage($this->getPackage('A', '2.0'));
$this->reposComplete(); $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->request->update('A');
$this->checkSolverResult(array(array( $this->checkSolverResult(array(array(
@ -182,8 +236,26 @@ class SolverTest extends TestCase
$this->repo->addPackage($this->getPackage('A', '2.0')); $this->repo->addPackage($this->getPackage('A', '2.0'));
$this->reposComplete(); $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', new VersionConstraint('=', '1.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( $this->checkSolverResult(array(array(
'job' => 'update', 'job' => 'update',
@ -202,7 +274,7 @@ class SolverTest extends TestCase
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$this->repo->addPackage($packageC = $this->getPackage('C', '1.1')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.1'));
$this->repo->addPackage($this->getPackage('D', '1.0')); $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(); $this->reposComplete();
@ -224,8 +296,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($middlePackageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($middlePackageB = $this->getPackage('B', '1.0'));
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$this->repo->addPackage($oldPackageB = $this->getPackage('B', '0.9')); $this->repo->addPackage($oldPackageB = $this->getPackage('B', '0.9'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires')));
$packageA->setConflicts(array(new Link('A', 'B', new VersionConstraint('<', '1.0'), 'conflicts'))); $packageA->setConflicts(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.0'), 'conflicts')));
$this->reposComplete(); $this->reposComplete();
@ -271,8 +343,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '0.8')); $this->repo->addPackage($packageB = $this->getPackage('B', '0.8'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setProvides(array(new Link('Q', 'B', new VersionConstraint('=', '1.0'), 'provides'))); $packageQ->setProvides(array(new Link('Q', 'B', $this->getVersionConstraint('=', '1.0'), 'provides')));
$this->reposComplete(); $this->reposComplete();
@ -289,8 +361,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
$this->reposComplete(); $this->reposComplete();
@ -306,8 +378,8 @@ class SolverTest extends TestCase
{ {
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
$this->reposComplete(); $this->reposComplete();
@ -324,8 +396,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
$this->reposComplete(); $this->reposComplete();
@ -342,24 +414,24 @@ class SolverTest extends TestCase
{ {
$this->repo->addPackage($packageX = $this->getPackage('X', '1.0')); $this->repo->addPackage($packageX = $this->getPackage('X', '1.0'));
$packageX->setRequires(array( $packageX->setRequires(array(
new Link('X', 'A', new VersionConstraint('>=', '2.0.0.0'), 'requires'), new Link('X', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'),
new Link('X', 'B', new VersionConstraint('>=', '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($packageA = $this->getPackage('A', '2.0.0'));
$this->repo->addPackage($newPackageA = $this->getPackage('A', '2.1.0')); $this->repo->addPackage($newPackageA = $this->getPackage('A', '2.1.0'));
$this->repo->addPackage($newPackageB = $this->getPackage('B', '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 depends on version of package B that does not exist
// => new package A is not installable // => 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 // 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 // but an alternative option for A and B both exists
// this creates a more difficult so solve conflict // this creates a more difficult so solve conflict
$this->repo->addPackage($packageS = $this->getPackage('S', '2.0.0')); $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(); $this->reposComplete();
@ -377,8 +449,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageB1 = $this->getPackage('B', '0.9')); $this->repo->addPackage($packageB1 = $this->getPackage('B', '0.9'));
$this->repo->addPackage($packageB2 = $this->getPackage('B', '1.1')); $this->repo->addPackage($packageB2 = $this->getPackage('B', '1.1'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageB2->setRequires(array(new Link('B', 'A', new VersionConstraint('>=', '1.0'), 'requires'))); $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$this->reposComplete(); $this->reposComplete();
@ -398,10 +470,10 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$this->repo->addPackage($packageC = $this->getPackage('C', '1.0')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.0'));
$this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); $this->repo->addPackage($packageD = $this->getPackage('D', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageB->setRequires(array(new Link('B', 'Virtual', new VersionConstraint('>=', '1.0'), 'requires'))); $packageB->setRequires(array(new Link('B', 'Virtual', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageC->setRequires(array(new Link('C', 'Virtual', new VersionConstraint('==', '1.0'), 'provides'))); $packageC->setRequires(array(new Link('C', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides')));
$packageD->setRequires(array(new Link('D', 'Virtual', new VersionConstraint('==', '1.0'), 'provides'))); $packageD->setRequires(array(new Link('D', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides')));
$this->reposComplete(); $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() protected function reposComplete()
{ {
$this->pool->addRepository($this->repoInstalled); $this->pool->addRepository($this->repoInstalled);
@ -443,5 +628,4 @@ class SolverTest extends TestCase
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
} }
} }

View File

@ -0,0 +1,124 @@
<?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\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;
}
}

View File

@ -0,0 +1,124 @@
<?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\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;
}
}

View File

@ -0,0 +1,219 @@
<?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\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());
}
}

View File

@ -133,6 +133,9 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
public function testUnicode() 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 €"); $data = array("Žluťoučký \" kůň" => "úpěl ďábelské ódy za €");
$json = '{ $json = '{
"Žluťoučký \" kůň": "úpěl ďábelské ódy za €" "Žluťoučký \" kůň": "úpěl ďábelské ódy za €"
@ -143,6 +146,9 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
public function testEscapedSlashes() public function testEscapedSlashes()
{ {
if (!function_exists('mb_convert_encoding')) {
$this->markTestSkipped('Test requires the mbstring extension');
}
$data = "\\/fooƌ"; $data = "\\/fooƌ";
$this->assertJsonFormat('"\\\\\\/fooƌ"', $data, JSON_UNESCAPED_UNICODE); $this->assertJsonFormat('"\\\\\\/fooƌ"', $data, JSON_UNESCAPED_UNICODE);

View File

@ -1,34 +1,43 @@
<?php <?php
/* /*
* This file is part of Composer. * This file is part of Composer.
* *
* (c) Nils Adermann <naderman@naderman.de> * (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be> * Jordi Boggiano <j.boggiano@seld.be>
* *
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Composer\Test; namespace Composer\Test;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Package\MemoryPackage; use Composer\Package\MemoryPackage;
use Composer\Package\LinkConstraint\VersionConstraint;
abstract class TestCase extends \PHPUnit_Framework_TestCase
{ abstract class TestCase extends \PHPUnit_Framework_TestCase
private static $versionParser; {
private static $versionParser;
public static function setUpBeforeClass()
{ public static function setUpBeforeClass()
if (!self::$versionParser) { {
self::$versionParser = new VersionParser(); if (!self::$versionParser) {
} self::$versionParser = new VersionParser();
} }
}
protected function getPackage($name, $version)
{ protected function getVersionConstraint($operator, $version)
$normVersion = self::$versionParser->normalize($version); {
return new MemoryPackage($name, $normVersion, $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);
}
}