1
0
Fork 0

Merge remote-tracking branch 'naderman/solver-refactor'

pull/722/merge
Jordi Boggiano 2012-05-22 11:00:27 +02:00
commit 4ea9b33a6c
18 changed files with 1123 additions and 1237 deletions

View File

@ -0,0 +1,83 @@
<?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 DebugSolver extends Solver
{
protected function printDecisionMap()
{
echo "\nDecisionMap: \n";
foreach ($this->decisionMap as $packageId => $level) {
if ($packageId === 0) {
continue;
}
if ($level > 0) {
echo ' +' . $this->pool->packageById($packageId)."\n";
} elseif ($level < 0) {
echo ' -' . $this->pool->packageById($packageId)."\n";
} else {
echo ' ?' . $this->pool->packageById($packageId)."\n";
}
}
echo "\n";
}
protected function printDecisionQueue()
{
echo "DecisionQueue: \n";
foreach ($this->decisionQueue as $i => $literal) {
echo ' ' . $this->pool->literalToString($literal) . ' ' . $this->decisionQueueWhy[$i]." level ".$this->decisionMap[abs($literal)]."\n";
}
echo "\n";
}
protected 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

@ -30,7 +30,7 @@ class DefaultPolicy implements PolicyInterface
return $constraint->matchSpecific($version); return $constraint->matchSpecific($version);
} }
public function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package) public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package)
{ {
$packages = array(); $packages = array();
@ -43,12 +43,6 @@ class DefaultPolicy implements PolicyInterface
return $packages; return $packages;
} }
public function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package)
{
// todo: package blacklist?
return true;
}
public function getPriority(Pool $pool, PackageInterface $package) public function getPriority(Pool $pool, PackageInterface $package)
{ {
return $pool->getPriority($package->getRepository()); return $pool->getPriority($package->getRepository());
@ -56,44 +50,44 @@ class DefaultPolicy implements PolicyInterface
public function selectPreferedPackages(Pool $pool, array $installedMap, array $literals) public function selectPreferedPackages(Pool $pool, array $installedMap, array $literals)
{ {
$packages = $this->groupLiteralsByNamePreferInstalled($installedMap, $literals); $packages = $this->groupLiteralsByNamePreferInstalled($pool,$installedMap, $literals);
foreach ($packages as &$literals) { foreach ($packages as &$literals) {
$policy = $this; $policy = $this;
usort($literals, function ($a, $b) use ($policy, $pool, $installedMap) { usort($literals, function ($a, $b) use ($policy, $pool, $installedMap) {
return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $a->getPackage(), $b->getPackage(), true); return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), true);
}); });
} }
foreach ($packages as &$literals) { foreach ($packages as &$literals) {
$literals = $this->pruneToBestVersion($literals); $literals = $this->pruneToBestVersion($pool, $literals);
$literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals); $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
$literals = $this->pruneRemoteAliases($literals); $literals = $this->pruneRemoteAliases($pool, $literals);
} }
$selected = call_user_func_array('array_merge', $packages); $selected = call_user_func_array('array_merge', $packages);
// now sort the result across all packages to respect replaces across packages // now sort the result across all packages to respect replaces across packages
usort($selected, function ($a, $b) use ($policy, $pool, $installedMap) { usort($selected, function ($a, $b) use ($policy, $pool, $installedMap) {
return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $a->getPackage(), $b->getPackage()); return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b));
}); });
return $selected; return $selected;
} }
protected function groupLiteralsByNamePreferInstalled(array $installedMap, $literals) protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals)
{ {
$packages = array(); $packages = array();
foreach ($literals as $literal) { foreach ($literals as $literal) {
$packageName = $literal->getPackage()->getName(); $packageName = $pool->literalToPackage($literal)->getName();
if (!isset($packages[$packageName])) { if (!isset($packages[$packageName])) {
$packages[$packageName] = array(); $packages[$packageName] = array();
} }
if (isset($installedMap[$literal->getPackageId()])) { if (isset($installedMap[abs($literal)])) {
array_unshift($packages[$packageName], $literal); array_unshift($packages[$packageName], $literal);
} else { } else {
$packages[$packageName][] = $literal; $packages[$packageName][] = $literal;
@ -171,19 +165,21 @@ class DefaultPolicy implements PolicyInterface
return false; return false;
} }
protected function pruneToBestVersion($literals) protected function pruneToBestVersion(Pool $pool, $literals)
{ {
$bestLiterals = array($literals[0]); $bestLiterals = array($literals[0]);
$bestPackage = $literals[0]->getPackage(); $bestPackage = $pool->literalToPackage($literals[0]);
foreach ($literals as $i => $literal) { foreach ($literals as $i => $literal) {
if (0 === $i) { if (0 === $i) {
continue; continue;
} }
if ($this->versionCompare($literal->getPackage(), $bestPackage, '>')) { $package = $pool->literalToPackage($literal);
$bestPackage = $literal->getPackage();
if ($this->versionCompare($package, $bestPackage, '>')) {
$bestPackage = $package;
$bestLiterals = array($literal); $bestLiterals = array($literal);
} else if ($this->versionCompare($literal->getPackage(), $bestPackage, '==')) { } else if ($this->versionCompare($package, $bestPackage, '==')) {
$bestLiterals[] = $literal; $bestLiterals[] = $literal;
} }
} }
@ -221,7 +217,7 @@ class DefaultPolicy implements PolicyInterface
$priority = null; $priority = null;
foreach ($literals as $literal) { foreach ($literals as $literal) {
$package = $literal->getPackage(); $package = $pool->literalToPackage($literal);
if (isset($installedMap[$package->getId()])) { if (isset($installedMap[$package->getId()])) {
$selected[] = $literal; $selected[] = $literal;
@ -247,12 +243,12 @@ class DefaultPolicy implements PolicyInterface
* *
* If no package is a local alias, nothing happens * If no package is a local alias, nothing happens
*/ */
protected function pruneRemoteAliases(array $literals) protected function pruneRemoteAliases(Pool $pool, array $literals)
{ {
$hasLocalAlias = false; $hasLocalAlias = false;
foreach ($literals as $literal) { foreach ($literals as $literal) {
$package = $literal->getPackage(); $package = $pool->literalToPackage($literal);
if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
$hasLocalAlias = true; $hasLocalAlias = true;
@ -266,7 +262,7 @@ class DefaultPolicy implements PolicyInterface
$selected = array(); $selected = array();
foreach ($literals as $literal) { foreach ($literals as $literal) {
$package = $literal->getPackage(); $package = $pool->literalToPackage($literal);
if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
$selected[] = $literal; $selected[] = $literal;

View File

@ -1,67 +0,0 @@
<?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;
use Composer\Package\PackageInterface;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
class Literal
{
protected $package;
protected $wanted;
protected $id;
public function __construct(PackageInterface $package, $wanted)
{
$this->package = $package;
$this->wanted = $wanted;
$this->id = ($this->wanted ? '' : '-') . $this->package->getId();
}
public function isWanted()
{
return $this->wanted;
}
public function getPackage()
{
return $this->package;
}
public function getPackageId()
{
return $this->package->getId();
}
public function getId()
{
return $this->id;
}
public function __toString()
{
return ($this->wanted ? '+' : '-') . $this->getPackage();
}
public function inverted()
{
return new Literal($this->getPackage(), !$this->isWanted());
}
public function equals(Literal $b)
{
return $this->id === $b->id;
}
}

View File

@ -21,7 +21,6 @@ use Composer\Package\PackageInterface;
interface PolicyInterface interface PolicyInterface
{ {
function versionCompare(PackageInterface $a, PackageInterface $b, $operator); function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package); function findUpdatePackages(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

@ -151,4 +151,15 @@ class Pool
return $result; return $result;
} }
public function literalToPackage($literal)
{
$packageId = abs($literal);
return $this->packageById($packageId);
}
public function literalToString($literal)
{
return ($literal > 0 ? '+' : '-') . $this->literalToPackage($literal);
}
} }

View File

@ -25,20 +25,6 @@ class Problem
*/ */
protected $reasons; protected $reasons;
/**
* Add a job as a reason
*
* @param array $job A job descriptor which is a reason for this problem
* @param Rule $rule An optional rule associated with the job
*/
public function addJobRule($job, Rule $rule = null)
{
$this->addReason(serialize($job), array(
'rule' => $rule,
'job' => $job,
));
}
/** /**
* Add a rule as a reason * Add a rule as a reason
* *
@ -48,7 +34,7 @@ class Problem
{ {
$this->addReason($rule->getId(), array( $this->addReason($rule->getId(), array(
'rule' => $rule, 'rule' => $rule,
'job' => null, 'job' => $rule->getJob(),
)); ));
} }

View File

@ -20,8 +20,6 @@ class Rule
const RULE_INTERNAL_ALLOW_UPDATE = 1; const RULE_INTERNAL_ALLOW_UPDATE = 1;
const RULE_JOB_INSTALL = 2; const RULE_JOB_INSTALL = 2;
const RULE_JOB_REMOVE = 3; const RULE_JOB_REMOVE = 3;
const RULE_JOB_LOCK = 4;
const RULE_NOT_INSTALLABLE = 5;
const RULE_PACKAGE_CONFLICT = 6; const RULE_PACKAGE_CONFLICT = 6;
const RULE_PACKAGE_REQUIRES = 7; const RULE_PACKAGE_REQUIRES = 7;
const RULE_PACKAGE_OBSOLETES = 8; const RULE_PACKAGE_OBSOLETES = 8;
@ -31,40 +29,35 @@ class Rule
const RULE_LEARNED = 12; const RULE_LEARNED = 12;
const RULE_PACKAGE_ALIAS = 13; const RULE_PACKAGE_ALIAS = 13;
protected $pool;
protected $disabled; protected $disabled;
protected $literals; protected $literals;
protected $type; protected $type;
protected $id; protected $id;
protected $weak;
public $watch1; protected $job;
public $watch2;
public $next1; protected $ruleHash;
public $next2;
public $ruleHash; public function __construct(Pool $pool, array $literals, $reason, $reasonData, $job = null)
public function __construct(array $literals, $reason, $reasonData)
{ {
$this->pool = $pool;
// sort all packages ascending by id // sort all packages ascending by id
usort($literals, array($this, 'compareLiteralsById')); sort($literals);
$this->literals = $literals; $this->literals = $literals;
$this->reason = $reason; $this->reason = $reason;
$this->reasonData = $reasonData; $this->reasonData = $reasonData;
$this->disabled = false; $this->disabled = false;
$this->weak = false;
$this->watch1 = (count($this->literals) > 0) ? $literals[0]->getId() : 0; $this->job = $job;
$this->watch2 = (count($this->literals) > 1) ? $literals[1]->getId() : 0;
$this->type = -1; $this->type = -1;
$this->ruleHash = substr(md5(implode(',', array_map(function ($l) { $this->ruleHash = substr(md5(implode(',', $this->literals)), 0, 5);
return $l->getId();
}, $this->literals))), 0, 5);
} }
public function getHash() public function getHash()
@ -82,6 +75,11 @@ class Rule
return $this->id; return $this->id;
} }
public function getJob()
{
return $this->job;
}
/** /**
* Checks if this rule is equal to another one * Checks if this rule is equal to another one
* *
@ -101,7 +99,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] !== $rule->literals[$i]) {
return false; return false;
} }
} }
@ -139,16 +137,6 @@ class Rule
return !$this->disabled; return !$this->disabled;
} }
public function isWeak()
{
return $this->weak;
}
public function setWeak($weak)
{
$this->weak = $weak;
}
public function getLiterals() public function getLiterals()
{ {
return $this->literals; return $this->literals;
@ -159,24 +147,6 @@ class Rule
return 1 === count($this->literals); return 1 === count($this->literals);
} }
public function getNext(Literal $literal)
{
if ($this->watch1 == $literal->getId()) {
return $this->next1;
} else {
return $this->next2;
}
}
public function getOtherWatch(Literal $literal)
{
if ($this->watch1 == $literal->getId()) {
return $this->watch2;
} else {
return $this->watch1;
}
}
public function toHumanReadableString() public function toHumanReadableString()
{ {
$ruleText = ''; $ruleText = '';
@ -184,7 +154,7 @@ class Rule
if ($i != 0) { if ($i != 0) {
$ruleText .= '|'; $ruleText .= '|';
} }
$ruleText .= $literal; $ruleText .= $this->pool->literalToString($literal);
} }
switch ($this->reason) { switch ($this->reason) {
@ -197,25 +167,19 @@ class Rule
case self::RULE_JOB_REMOVE: case self::RULE_JOB_REMOVE:
return "Remove command rule ($ruleText)"; return "Remove command rule ($ruleText)";
case self::RULE_JOB_LOCK:
return "Lock command rule ($ruleText)";
case self::RULE_NOT_INSTALLABLE:
return $ruleText;
case self::RULE_PACKAGE_CONFLICT: case self::RULE_PACKAGE_CONFLICT:
$package1 = $this->literals[0]->getPackage(); $package1 = $this->pool->literalToPackage($this->literals[0]);
$package2 = $this->literals[1]->getPackage(); $package2 = $this->pool->literalToPackage($this->literals[1]);
return 'Package "'.$package1.'" conflicts with "'.$package2.'"'; return 'Package "'.$package1.'" conflicts with "'.$package2.'"';
case self::RULE_PACKAGE_REQUIRES: case self::RULE_PACKAGE_REQUIRES:
$literals = $this->literals; $literals = $this->literals;
$sourceLiteral = array_shift($literals); $sourceLiteral = array_shift($literals);
$sourcePackage = $sourceLiteral->getPackage(); $sourcePackage = $this->pool->literalToPackage($sourceLiteral);
$requires = array(); $requires = array();
foreach ($literals as $literal) { foreach ($literals as $literal) {
$requires[] = $literal->getPackage(); $requires[] = $this->pool->literalToPackage($literal);
} }
$text = 'Package "'.$sourcePackage.'" contains the rule '.$this->reasonData.'. '; $text = 'Package "'.$sourcePackage.'" contains the rule '.$this->reasonData.'. ';
@ -254,26 +218,11 @@ class Rule
if ($i != 0) { if ($i != 0) {
$result .= '|'; $result .= '|';
} }
$result .= $literal; $result .= $this->pool->literalToString($literal);
} }
$result .= ')'; $result .= ')';
return $result; return $result;
} }
/**
* Comparison function for sorting literals by their id
*
* @param Literal $a
* @param Literal $b
* @return int 0 if the literals are equal, 1 if b is larger than a, -1 else
*/
private function compareLiteralsById(Literal $a, Literal $b)
{
if ($a->getId() === $b->getId()) {
return 0;
}
return $a->getId() < $b->getId() ? -1 : 1;
}
} }

View File

@ -0,0 +1,289 @@
<?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;
use Composer\Repository\RepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\DependencyResolver\Operation;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
class RuleSetGenerator
{
protected $policy;
protected $pool;
protected $rules;
protected $jobs;
protected $installedMap;
public function __construct(PolicyInterface $policy, Pool $pool)
{
$this->policy = $policy;
$this->pool = $pool;
}
/**
* Creates a new rule for the requirements of a package
*
* This rule is of the form (-A|B|C), where B and C are the providers of
* one requirement of the package A.
*
* @param PackageInterface $package The package with a requirement
* @param array $providers The providers of the requirement
* @param int $reason A RULE_* constant describing the
* reason for generating this rule
* @param mixed $reasonData Any data, e.g. the requirement name,
* that goes with the reason
* @return Rule The generated rule or null if tautological
*/
protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
{
$literals = array(-$package->getId());
foreach ($providers as $provider) {
// self fulfilling rule?
if ($provider === $package) {
return null;
}
$literals[] = $provider->getId();
}
return new Rule($this->pool, $literals, $reason, $reasonData);
}
/**
* Creates a rule to install at least one of a set of packages
*
* The rule is (A|B|C) with A, B and C different packages. If the given
* set of packages is empty an impossible rule is generated.
*
* @param array $packages The set of packages to choose from
* @param int $reason A RULE_* constant describing the reason for
* generating this rule
* @param array $job The job this rule was created from
* @return Rule The generated rule
*/
protected function createInstallOneOfRule(array $packages, $reason, $job)
{
$literals = array();
foreach ($packages as $package) {
$literals[] = $package->getId();
}
return new Rule($this->pool, $literals, $reason, $job['packageName'], $job);
}
/**
* Creates a rule to remove a package
*
* The rule for a package A is (-A).
*
* @param PackageInterface $package The package to be removed
* @param int $reason A RULE_* constant describing the
* reason for generating this rule
* @param array $job The job this rule was created from
* @return Rule The generated rule
*/
protected function createRemoveRule(PackageInterface $package, $reason, $job)
{
return new Rule($this->pool, array(-$package->getId()), $reason, $job['packageName'], $job);
}
/**
* Creates a rule for two conflicting packages
*
* The rule for conflicting packages A and B is (-A|-B). A is called the issuer
* and B the provider.
*
* @param PackageInterface $issuer The package declaring the conflict
* @param Package $provider The package causing the conflict
* @param int $reason A RULE_* constant describing the
* reason for generating this rule
* @param mixed $reasonData Any data, e.g. the package name, that
* goes with the reason
* @return Rule The generated rule
*/
protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
{
// ignore self conflict
if ($issuer === $provider) {
return null;
}
return new Rule($this->pool, array(-$issuer->getId(), -$provider->getId()), $reason, $reasonData);
}
/**
* Adds a rule unless it duplicates an existing one of any type
*
* To be able to directly pass in the result of one of the rule creation
* methods.
*
* @param int $type A TYPE_* constant defining the rule type
* @param Rule $newRule The rule about to be added
*/
private function addRule($type, Rule $newRule = null) {
if ($this->rules->containsEqual($newRule)) {
return;
}
$this->rules->add($newRule, $type);
}
protected function addRulesForPackage(PackageInterface $package)
{
$workQueue = new \SplQueue;
$workQueue->enqueue($package);
while (!$workQueue->isEmpty()) {
$package = $workQueue->dequeue();
if (isset($this->addedMap[$package->getId()])) {
continue;
}
$this->addedMap[$package->getId()] = true;
foreach ($package->getRequires() as $link) {
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, (string) $link));
foreach ($possibleRequires as $require) {
$workQueue->enqueue($require);
}
}
foreach ($package->getConflicts() as $link) {
$possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($possibleConflicts as $conflict) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, (string) $link));
}
}
// check obsoletes and implicit obsoletes of a package
$isInstalled = (isset($this->installedMap[$package->getId()]));
foreach ($package->getReplaces() as $link) {
$obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($obsoleteProviders as $provider) {
if ($provider === $package) {
continue;
}
if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
}
}
}
// check implicit obsoletes
// for installed packages we only need to check installed/installed problems,
// as the others are picked up when looking at the uninstalled package.
if (!$isInstalled) {
$obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
foreach ($obsoleteProviders as $provider) {
if ($provider === $package) {
continue;
}
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, (string) $package));
} elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
}
}
}
}
}
protected function obsoleteImpossibleForAlias($package, $provider)
{
$packageIsAlias = $package instanceof AliasPackage;
$providerIsAlias = $provider instanceof AliasPackage;
$impossible = (
($packageIsAlias && $package->getAliasOf() === $provider) ||
($providerIsAlias && $provider->getAliasOf() === $package) ||
($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf())
);
return $impossible;
}
/**
* Adds all rules for all update packages of a given package
*
* @param PackageInterface $package Rules for this package's updates are to
* be added
* @param bool $allowAll Whether downgrades are allowed
*/
private function addRulesForUpdatePackages(PackageInterface $package)
{
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
foreach ($updates as $update) {
$this->addRulesForPackage($update);
}
}
protected function addRulesForJobs()
{
foreach ($this->jobs as $job) {
switch ($job['cmd']) {
case 'install':
if ($job['packages']) {
foreach ($job['packages'] as $package) {
if (!isset($this->installedMap[$package->getId()])) {
$this->addRulesForPackage($package);
}
}
$rule = $this->createInstallOneOfRule($job['packages'], Rule::RULE_JOB_INSTALL, $job);
$this->addRule(RuleSet::TYPE_JOB, $rule);
}
break;
case 'remove':
// remove all packages with this name including uninstalled
// ones to make sure none of them are picked as replacements
foreach ($job['packages'] as $package) {
$rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job);
$this->addRule(RuleSet::TYPE_JOB, $rule);
}
break;
}
}
}
public function getRulesFor($jobs, $installedMap)
{
$this->jobs = $jobs;
$this->rules = new RuleSet;
$this->installedMap = $installedMap;
foreach ($this->installedMap as $package) {
$this->addRulesForPackage($package);
$this->addRulesForUpdatePackages($package);
}
$this->addRulesForJobs();
return $this->rules;
}
}

View File

@ -0,0 +1,52 @@
<?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;
/**
* An extension of SplDoublyLinkedList with seek and removal of current element
*
* SplDoublyLinkedList only allows deleting a particular offset and has no
* method to set the internal iterator to a particular offset.
*
* @author Nils Adermann <naderman@naderman.de>
*/
class RuleWatchChain extends \SplDoublyLinkedList
{
protected $offset = 0;
/**
* Moves the internal iterator to the specified offset
*
* @param int $offset The offset to seek to.
*/
public function seek($offset)
{
$this->rewind();
for ($i = 0; $i < $offset; $i++, $this->next());
}
/**
* Removes the current element from the list
*
* As SplDoublyLinkedList only allows deleting a particular offset and
* incorrectly sets the internal iterator if you delete the current value
* this method sets the internal iterator back to the following element
* using the seek method.
*/
public function remove()
{
$offset = $this->key();
$this->offsetUnset($offset);
$this->seek($offset);
}
}

View File

@ -0,0 +1,149 @@
<?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;
/**
* The RuleWatchGraph efficiently propagates decisions to other rules
*
* All rules generated for solving a SAT problem should be inserted into the
* graph. When a decision on a literal is made, the graph can be used to
* propagate the decision to all other rules involving the rule, leading to
* other trivial decisions resulting from unit clauses.
*
* @author Nils Adermann <naderman@naderman.de>
*/
class RuleWatchGraph
{
protected $watchChains = array();
/**
* Inserts a rule node into the appropriate chains within the graph
*
* The node is prepended to the watch chains for each of the two literals it
* watches.
*
* Assertions are skipped because they only depend on a single package and
* have no alternative literal that could be true, so there is no need to
* watch chnages in any literals.
*
* @param RuleWatchNode $node The rule node to be inserted into the graph
*/
public function insert(RuleWatchNode $node)
{
if ($node->getRule()->isAssertion()) {
return;
}
foreach (array($node->watch1, $node->watch2) as $literal) {
if (!isset($this->watchChains[$literal])) {
$this->watchChains[$literal] = new RuleWatchChain;
}
$this->watchChains[$literal]->unshift($node);
}
}
/**
* Propagates a decision on a literal to all rules watching the literal
*
* If a decision, e.g. +A has been made, then all rules containing -A, e.g.
* (-A|+B|+C) now need to satisfy at least one of the other literals, so
* that the rule as a whole becomes true, since with +A applied the rule
* is now (false|+B|+C) so essentialy (+B|+C).
*
* This means that all rules watching the literal -A need to be updated to
* watch 2 other literals which can still be satisfied instead. So literals
* that conflict with previously made decisions are not an option.
*
* Alternatively it can occur that a unit clause results: e.g. if in the
* above example the rule was (-A|+B), then A turning true means that
* B must now be decided true as well.
*
* @param int $decidedLiteral The literal which was decided (A in our example)
* @param int $level The level at which the decision took place and at which
* all resulting decisions should be made.
* @param Callable $decisionsSatisfyCallback A callback which checks if a
* literal has already been positively decided and the rule is thus
* already true and can be skipped.
* @param Callable $conflictCallback A callback which checks if a literal
* would conflict with previously made decisions on the same package
* @param Callable $decideCallback A callback which is responsible for
* registering decided literals resulting from unit clauses
* @return Rule|null If a conflict is found the conflicting rule is returned
*/
public function propagateLiteral($decidedLiteral, $level, $decisionsSatisfyCallback, $conflictCallback, $decideCallback)
{
// we invert the decided literal here, example:
// A was decided => (-A|B) now requires B to be true, so we look for
// rules which are fulfilled by -A, rather than A.
$literal = -$decidedLiteral;
if (!isset($this->watchChains[$literal])) {
return null;
}
$chain = $this->watchChains[$literal];
$chain->rewind();
while ($chain->valid()) {
$node = $chain->current();
$otherWatch = $node->getOtherWatch($literal);
if (!$node->getRule()->isDisabled() && !call_user_func($decisionsSatisfyCallback, $otherWatch)) {
$ruleLiterals = $node->getRule()->getLiterals();
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $conflictCallback) {
return $literal !== $ruleLiteral &&
$otherWatch !== $ruleLiteral &&
!call_user_func($conflictCallback, $ruleLiteral);
});
if ($alternativeLiterals) {
reset($alternativeLiterals);
$this->moveWatch($literal, current($alternativeLiterals), $node);
continue;
}
if (call_user_func($conflictCallback, $otherWatch)) {
return $node->getRule();
}
call_user_func($decideCallback, $otherWatch, $level, $node->getRule());
}
$chain->next();
}
return null;
}
/**
* Moves a rule node from one watch chain to another
*
* The rule node's watched literals are updated accordingly.
*
* @param $fromLiteral A literal the node used to watch
* @param $toLiteral A literal the node should watch now
* @param $node The rule node to be moved
*/
protected function moveWatch($fromLiteral, $toLiteral, $node)
{
if (!isset($this->watchChains[$toLiteral])) {
$this->watchChains[$toLiteral] = new RuleWatchChain;
}
$node->moveWatch($fromLiteral, $toLiteral);
$this->watchChains[$fromLiteral]->remove();
$this->watchChains[$toLiteral]->unshift($node);
}
}

View File

@ -0,0 +1,112 @@
<?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;
/**
* Wrapper around a Rule which keeps track of the two literals it watches
*
* Used by RuleWatchGraph to store rules in two RuleWatchChains.
*
* @author Nils Adermann <naderman@naderman.de>
*/
class RuleWatchNode
{
public $watch1;
public $watch2;
protected $rule;
/**
* Creates a new node watching the first and second literals of the rule.
*
* @param Rule $rule The rule to wrap
*/
public function __construct($rule)
{
$this->rule = $rule;
$literals = $rule->getLiterals();
$this->watch1 = count($literals) > 0 ? $literals[0] : 0;
$this->watch2 = count($literals) > 1 ? $literals[1] : 0;
}
/**
* Places the second watch on the rule's literal, decided at the highest level
*
* Useful for learned rules where the literal for the highest rule is most
* likely to quickly lead to further decisions.
*
* @param SplFixedArray $decisionMap A package to decision lookup table
*/
public function watch2OnHighest($decisionMap)
{
$literals = $this->rule->getLiterals();
// if there are only 2 elements, both are being watched anyway
if ($literals < 3) {
return;
}
$watchLevel = 0;
foreach ($literals as $literal) {
$level = abs($decisionMap[abs($literal)]);
if ($level > $watchLevel) {
$this->rule->watch2 = $literal;
$watchLevel = $level;
}
}
}
/**
* Returns the rule this node wraps
*
* @return Rule
*/
public function getRule()
{
return $this->rule;
}
/**
* Given one watched literal, this method returns the other watched literal
*
* @param int The watched literal that should not be returned
* @return int A literal
*/
public function getOtherWatch($literal)
{
if ($this->watch1 == $literal) {
return $this->watch2;
} else {
return $this->watch1;
}
}
/**
* Moves a watch from one literal to another
*
* @param int $from The previously watched literal
* @param int $to The literal to be watched now
*/
public function moveWatch($from, $to)
{
if ($this->watch1 == $from) {
$this->watch1 = $to;
} else {
$this->watch2 = $to;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
<?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;
use Composer\Package\AliasPackage;
use Composer\DependencyResolver\Operation;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
class Transaction
{
protected $policy;
protected $pool;
protected $installedMap;
protected $decisionMap;
protected $decisionQueue;
protected $decisionQueueWhy;
public function __construct($policy, $pool, $installedMap, $decisionMap, array $decisionQueue, $decisionQueueWhy)
{
$this->policy = $policy;
$this->pool = $pool;
$this->installedMap = $installedMap;
$this->decisionMap = $decisionMap;
$this->decisionQueue = $decisionQueue;
$this->decisionQueueWhy = $decisionQueueWhy;
}
public function getOperations()
{
$transaction = array();
$installMeansUpdateMap = array();
foreach ($this->decisionQueue as $i => $literal) {
$package = $this->pool->literalToPackage($literal);
// !wanted & installed
if ($literal <= 0 && isset($this->installedMap[$package->getId()])) {
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
$literals = array($package->getId());
foreach ($updates as $update) {
$literals[] = $update->getId();
}
foreach ($literals as $updateLiteral) {
if ($updateLiteral !== $literal) {
$installMeansUpdateMap[abs($updateLiteral)] = $package;
}
}
}
}
foreach ($this->decisionQueue as $i => $literal) {
$package = $this->pool->literalToPackage($literal);
// wanted & installed || !wanted & !installed
if (($literal > 0) == (isset($this->installedMap[$package->getId()]))) {
continue;
}
if ($literal > 0) {
if ($package instanceof AliasPackage) {
$transaction[] = new Operation\MarkAliasInstalledOperation(
$package, $this->decisionQueueWhy[$i]
);
continue;
}
if (isset($installMeansUpdateMap[abs($literal)])) {
$source = $installMeansUpdateMap[abs($literal)];
$transaction[] = new Operation\UpdateOperation(
$source, $package, $this->decisionQueueWhy[$i]
);
// avoid updates to one package from multiple origins
unset($installMeansUpdateMap[abs($literal)]);
$ignoreRemove[$source->getId()] = true;
} else {
$transaction[] = new Operation\InstallOperation(
$package, $this->decisionQueueWhy[$i]
);
}
} else if (!isset($ignoreRemove[$package->getId()])) {
if ($package instanceof AliasPackage) {
$transaction[] = new Operation\MarkAliasInstalledOperation(
$package, $this->decisionQueueWhy[$i]
);
} else {
$transaction[] = new Operation\UninstallOperation(
$package, $this->decisionQueueWhy[$i]
);
}
}
}
$allDecidedMap = $this->decisionMap;
foreach ($this->decisionMap as $packageId => $decision) {
if ($decision != 0) {
$package = $this->pool->packageById($packageId);
if ($package instanceof AliasPackage) {
$allDecidedMap[$package->getAliasOf()->getId()] = $decision;
}
}
}
foreach ($allDecidedMap as $packageId => $decision) {
if ($packageId === 0) {
continue;
}
if (0 == $decision && isset($this->installedMap[$packageId])) {
$package = $this->pool->packageById($packageId);
if ($package instanceof AliasPackage) {
$transaction[] = new Operation\MarkAliasInstalledOperation(
$package, null
);
} else {
$transaction[] = new Operation\UninstallOperation(
$package, null
);
}
$this->decisionMap[$packageId] = -1;
}
}
foreach ($allDecidedMap as $packageId => $decision) {
if ($packageId === 0) {
continue;
}
if (0 == $decision && isset($this->installedMap[$packageId])) {
$package = $this->pool->packageById($packageId);
if ($package instanceof AliasPackage) {
$transaction[] = new Operation\MarkAliasInstalledOperation(
$package, null
);
} else {
$transaction[] = new Operation\UninstallOperation(
$package, null
);
}
$this->decisionMap[$packageId] = -1;
}
}
return array_reverse($transaction);
}
}

View File

@ -16,7 +16,6 @@ use Composer\Repository\ArrayRepository;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Literal;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\VersionConstraint;
@ -44,8 +43,8 @@ class DefaultPolicyTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->pool->addRepository($this->repo); $this->pool->addRepository($this->repo);
$literals = array(new Literal($packageA, true)); $literals = array($packageA->getId());
$expected = array(new Literal($packageA, true)); $expected = array($packageA->getId());
$selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
@ -58,8 +57,8 @@ class DefaultPolicyTest extends TestCase
$this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
$this->pool->addRepository($this->repo); $this->pool->addRepository($this->repo);
$literals = array(new Literal($packageA1, true), new Literal($packageA2, true)); $literals = array($packageA1->getId(), $packageA2->getId());
$expected = array(new Literal($packageA2, true)); $expected = array($packageA2->getId());
$selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
@ -73,8 +72,8 @@ class DefaultPolicyTest extends TestCase
$this->pool->addRepository($this->repoInstalled); $this->pool->addRepository($this->repoInstalled);
$this->pool->addRepository($this->repo); $this->pool->addRepository($this->repo);
$literals = array(new Literal($packageA, true), new Literal($packageAInstalled, true)); $literals = array($packageA->getId(), $packageAInstalled->getId());
$expected = array(new Literal($packageA, true)); $expected = array($packageA->getId());
$selected = $this->policy->selectPreferedPackages($this->pool, $this->mapFromRepo($this->repoInstalled), $literals); $selected = $this->policy->selectPreferedPackages($this->pool, $this->mapFromRepo($this->repoInstalled), $literals);
@ -92,8 +91,8 @@ class DefaultPolicyTest extends TestCase
$this->pool->addRepository($this->repoImportant); $this->pool->addRepository($this->repoImportant);
$this->pool->addRepository($this->repo); $this->pool->addRepository($this->repo);
$literals = array(new Literal($packageA, true), new Literal($packageAImportant, true)); $literals = array($packageA->getId(), $packageAImportant->getId());
$expected = array(new Literal($packageAImportant, true)); $expected = array($packageAImportant->getId());
$selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
@ -119,10 +118,10 @@ class DefaultPolicyTest extends TestCase
$packages = $this->pool->whatProvides('a', new VersionConstraint('=', '2.1.9999999.9999999-dev')); $packages = $this->pool->whatProvides('a', new VersionConstraint('=', '2.1.9999999.9999999-dev'));
$literals = array(); $literals = array();
foreach ($packages as $package) { foreach ($packages as $package) {
$literals[] = new Literal($package, true); $literals[] = $package->getId();
} }
$expected = array(new Literal($packageAAliasImportant, true)); $expected = array($packageAAliasImportant->getId());
$selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
@ -139,7 +138,7 @@ class DefaultPolicyTest extends TestCase
$this->pool->addRepository($this->repo); $this->pool->addRepository($this->repo);
$literals = array(new Literal($packageA, true), new Literal($packageB, true)); $literals = array($packageA->getId(), $packageB->getId());
$expected = $literals; $expected = $literals;
$selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
@ -157,8 +156,8 @@ class DefaultPolicyTest extends TestCase
$this->pool->addRepository($this->repo); $this->pool->addRepository($this->repo);
$literals = array(new Literal($packageA, true), new Literal($packageB, true)); $literals = array($packageA->getId(), $packageB->getId());
$expected = array(new Literal($packageA, true), new Literal($packageB, true)); $expected = $literals;
$selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);

View File

@ -1,63 +0,0 @@
<?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\Literal;
use Composer\Test\TestCase;
class LiteralTest extends TestCase
{
protected $package;
public function setUp()
{
$this->package = $this->getPackage('foo', '1');
$this->package->setId(12);
}
public function testLiteralWanted()
{
$literal = new Literal($this->package, true);
$this->assertEquals(12, $literal->getId());
$this->assertEquals('+'.(string) $this->package, (string) $literal);
}
public function testLiteralUnwanted()
{
$literal = new Literal($this->package, false);
$this->assertEquals(-12, $literal->getId());
$this->assertEquals('-'.(string) $this->package, (string) $literal);
}
public function testLiteralInverted()
{
$literal = new Literal($this->package, false);
$inverted = $literal->inverted();
$this->assertInstanceOf('\Composer\DependencyResolver\Literal', $inverted);
$this->assertTrue($inverted->isWanted());
$this->assertSame($this->package, $inverted->getPackage());
$this->assertFalse($literal->equals($inverted));
$doubleInverted = $inverted->inverted();
$this->assertInstanceOf('\Composer\DependencyResolver\Literal', $doubleInverted);
$this->assertFalse($doubleInverted->isWanted());
$this->assertSame($this->package, $doubleInverted->getPackage());
$this->assertTrue($literal->equals($doubleInverted));
}
}

View File

@ -15,6 +15,7 @@ namespace Composer\Test\DependencyResolver;
use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\Rule;
use Composer\DependencyResolver\RuleSet; use Composer\DependencyResolver\RuleSet;
use Composer\DependencyResolver\RuleSetIterator; use Composer\DependencyResolver\RuleSetIterator;
use Composer\DependencyResolver\Pool;
class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
{ {
@ -22,13 +23,15 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
protected function setUp() protected function setUp()
{ {
$this->pool = new Pool;
$this->rules = array( $this->rules = array(
RuleSet::TYPE_JOB => array( RuleSet::TYPE_JOB => array(
new Rule(array(), 'job1', null), new Rule($this->pool, array(), 'job1', null),
new Rule(array(), 'job2', null), new Rule($this->pool, array(), 'job2', null),
), ),
RuleSet::TYPE_LEARNED => array( RuleSet::TYPE_LEARNED => array(
new Rule(array(), 'update1', null), new Rule($this->pool, array(), 'update1', null),
), ),
RuleSet::TYPE_PACKAGE => array(), RuleSet::TYPE_PACKAGE => array(),
); );

View File

@ -14,21 +14,29 @@ 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\DependencyResolver\Pool;
use Composer\Repository\ArrayRepository;
use Composer\Test\TestCase; use Composer\Test\TestCase;
class RuleSetTest extends TestCase class RuleSetTest extends TestCase
{ {
protected $pool;
public function setUp()
{
$this->pool = new Pool;
}
public function testAdd() public function testAdd()
{ {
$rules = array( $rules = array(
RuleSet::TYPE_PACKAGE => array(), RuleSet::TYPE_PACKAGE => array(),
RuleSet::TYPE_JOB => array( RuleSet::TYPE_JOB => array(
new Rule(array(), 'job1', null), new Rule($this->pool, array(), 'job1', null),
new Rule(array(), 'job2', null), new Rule($this->pool, array(), 'job2', null),
), ),
RuleSet::TYPE_LEARNED => array( RuleSet::TYPE_LEARNED => array(
new Rule(array(), 'update1', null), new Rule($this->pool, array(), 'update1', null),
), ),
); );
@ -48,15 +56,15 @@ class RuleSetTest extends TestCase
{ {
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$ruleSet->add(new Rule(array(), 'job1', null), 7); $ruleSet->add(new Rule($this->pool, array(), 'job1', null), 7);
} }
public function testCount() public function testCount()
{ {
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$ruleSet->add(new Rule(array(), 'job1', null), RuleSet::TYPE_JOB); $ruleSet->add(new Rule($this->pool, array(), 'job1', null), RuleSet::TYPE_JOB);
$ruleSet->add(new Rule(array(), 'job2', null), RuleSet::TYPE_JOB); $ruleSet->add(new Rule($this->pool, array(), 'job2', null), RuleSet::TYPE_JOB);
$this->assertEquals(2, $ruleSet->count()); $this->assertEquals(2, $ruleSet->count());
} }
@ -65,7 +73,7 @@ class RuleSetTest extends TestCase
{ {
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$rule = new Rule(array(), 'job1', null); $rule = new Rule($this->pool, array(), 'job1', null);
$ruleSet->add($rule, RuleSet::TYPE_JOB); $ruleSet->add($rule, RuleSet::TYPE_JOB);
$this->assertSame($rule, $ruleSet->ruleById(0)); $this->assertSame($rule, $ruleSet->ruleById(0));
@ -75,8 +83,8 @@ class RuleSetTest extends TestCase
{ {
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$rule1 = new Rule(array(), 'job1', null); $rule1 = new Rule($this->pool, array(), 'job1', null);
$rule2 = new Rule(array(), 'job1', null); $rule2 = new Rule($this->pool, array(), 'job1', null);
$ruleSet->add($rule1, RuleSet::TYPE_JOB); $ruleSet->add($rule1, RuleSet::TYPE_JOB);
$ruleSet->add($rule2, RuleSet::TYPE_LEARNED); $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
@ -90,8 +98,8 @@ class RuleSetTest extends TestCase
public function testGetIteratorFor() public function testGetIteratorFor()
{ {
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$rule1 = new Rule(array(), 'job1', null); $rule1 = new Rule($this->pool, array(), 'job1', null);
$rule2 = new Rule(array(), 'job1', null); $rule2 = new Rule($this->pool, array(), 'job1', null);
$ruleSet->add($rule1, RuleSet::TYPE_JOB); $ruleSet->add($rule1, RuleSet::TYPE_JOB);
$ruleSet->add($rule2, RuleSet::TYPE_LEARNED); $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
@ -104,8 +112,8 @@ class RuleSetTest extends TestCase
public function testGetIteratorWithout() public function testGetIteratorWithout()
{ {
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$rule1 = new Rule(array(), 'job1', null); $rule1 = new Rule($this->pool, array(), 'job1', null);
$rule2 = new Rule(array(), 'job1', null); $rule2 = new Rule($this->pool, array(), 'job1', null);
$ruleSet->add($rule1, RuleSet::TYPE_JOB); $ruleSet->add($rule1, RuleSet::TYPE_JOB);
$ruleSet->add($rule2, RuleSet::TYPE_LEARNED); $ruleSet->add($rule2, RuleSet::TYPE_LEARNED);
@ -149,9 +157,13 @@ class RuleSetTest extends TestCase
public function testToString() public function testToString()
{ {
$repo = new ArrayRepository;
$repo->addPackage($p = $this->getPackage('foo', '2.1'));
$this->pool->addRepository($repo);
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$literal = new Literal($this->getPackage('foo', '2.1'), true); $literal = $p->getId();
$rule = new Rule(array($literal), 'job1', null); $rule = new Rule($this->pool, array($literal), 'job1', null);
$ruleSet->add($rule, RuleSet::TYPE_JOB); $ruleSet->add($rule, RuleSet::TYPE_JOB);

View File

@ -13,22 +13,29 @@
namespace Composer\Test\DependencyResolver; namespace Composer\Test\DependencyResolver;
use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\Rule;
use Composer\DependencyResolver\Literal; use Composer\DependencyResolver\Pool;
use Composer\Repository\ArrayRepository;
use Composer\Test\TestCase; use Composer\Test\TestCase;
class RuleTest extends TestCase class RuleTest extends TestCase
{ {
protected $pool;
public function setUp()
{
$this->pool = new Pool;
}
public function testGetHash() public function testGetHash()
{ {
$rule = new Rule(array(), 'job1', null); $rule = new Rule($this->pool, array(123), 'job1', null);
$rule->ruleHash = '123';
$this->assertEquals('123', $rule->getHash()); $this->assertEquals(substr(md5('123'), 0, 5), $rule->getHash());
} }
public function testSetAndGetId() public function testSetAndGetId()
{ {
$rule = new Rule(array(), 'job1', null); $rule = new Rule($this->pool, array(), 'job1', null);
$rule->setId(666); $rule->setId(666);
$this->assertEquals(666, $rule->getId()); $this->assertEquals(666, $rule->getId());
@ -36,73 +43,31 @@ class RuleTest extends TestCase
public function testEqualsForRulesWithDifferentHashes() public function testEqualsForRulesWithDifferentHashes()
{ {
$rule = new Rule(array(), 'job1', null); $rule = new Rule($this->pool, array(1, 2), 'job1', null);
$rule->ruleHash = '123'; $rule2 = new Rule($this->pool, array(1, 3), 'job1', null);
$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)); $this->assertFalse($rule->equals($rule2));
} }
public function testEqualsForRulesWithDifferLiteralsQuantity() public function testEqualsForRulesWithDifferLiteralsQuantity()
{ {
$literal = $this->getLiteralMock(); $rule = new Rule($this->pool, array(1, 12), 'job1', null);
$literal->expects($this->any()) $rule2 = new Rule($this->pool, array(1), 'job1', null);
->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)); $this->assertFalse($rule->equals($rule2));
} }
public function testEqualsForRulesWithThisSameLiterals() public function testEqualsForRulesWithSameLiterals()
{ {
$literal = $this->getLiteralMock(); $rule = new Rule($this->pool, array(1, 12), 'job1', null);
$literal->expects($this->any()) $rule2 = new Rule($this->pool, array(1, 12), 'job1', null);
->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)); $this->assertTrue($rule->equals($rule2));
} }
public function testSetAndGetType() public function testSetAndGetType()
{ {
$rule = new Rule(array(), 'job1', null); $rule = new Rule($this->pool, array(), 'job1', null);
$rule->setType('someType'); $rule->setType('someType');
$this->assertEquals('someType', $rule->getType()); $this->assertEquals('someType', $rule->getType());
@ -110,7 +75,7 @@ class RuleTest extends TestCase
public function testEnable() public function testEnable()
{ {
$rule = new Rule(array(), 'job1', null); $rule = new Rule($this->pool, array(), 'job1', null);
$rule->disable(); $rule->disable();
$rule->enable(); $rule->enable();
@ -120,7 +85,7 @@ class RuleTest extends TestCase
public function testDisable() public function testDisable()
{ {
$rule = new Rule(array(), 'job1', null); $rule = new Rule($this->pool, array(), 'job1', null);
$rule->enable(); $rule->enable();
$rule->disable(); $rule->disable();
@ -128,24 +93,10 @@ class RuleTest extends TestCase
$this->assertFalse($rule->isEnabled()); $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() public function testIsAssertions()
{ {
$literal = $this->getLiteralMock(); $rule = new Rule($this->pool, array(1, 12), 'job1', null);
$literal2 = $this->getLiteralMock(); $rule2 = new Rule($this->pool, array(1), 'job1', null);
$rule = new Rule(array($literal, $literal2), 'job1', null);
$rule2 = new Rule(array($literal), 'job1', null);
$this->assertFalse($rule->isAssertion()); $this->assertFalse($rule->isAssertion());
$this->assertTrue($rule2->isAssertion()); $this->assertTrue($rule2->isAssertion());
@ -153,18 +104,13 @@ class RuleTest extends TestCase
public function testToString() public function testToString()
{ {
$literal = new Literal($this->getPackage('foo', '2.1'), true); $repo = new ArrayRepository;
$literal2 = new Literal($this->getPackage('baz', '1.1'), false); $repo->addPackage($p1 = $this->getPackage('foo', '2.1'));
$repo->addPackage($p2 = $this->getPackage('baz', '1.1'));
$this->pool->addRepository($repo);
$rule = new Rule(array($literal, $literal2), 'job1', null); $rule = new Rule($this->pool, array($p1->getId(), -$p2->getId()), 'job1', null);
$this->assertEquals('(-baz-1.1.0.0|+foo-2.1.0.0)', $rule->__toString()); $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();
}
} }