Browse Source

Merge remote-tracking branch 'github-seldaek/stability'

* github-seldaek/stability:
  Add stability docs
  Add test for Pool handling and refactor a couple things
  Add support for stabilities in lock file
  Infer stability flags for requirements that have an explicit version required of a lower stability
  Add InstalledArrayRepository to make sure the root package is not purged by the Pool because of a lower stability
  Basic handling of stability flags
  Add list of stabilities to base package
  Add minimum-stability flag on root package to filter packages by stability
  Add CompositeRepo::getRepositories
  Add package stability
Nils Adermann 13 years ago
parent
commit
7c7cac61b6

+ 70 - 17
doc/04-schema.md

@@ -200,22 +200,10 @@ An example:
 
 Optional, but highly recommended.
 
-### Package links <span>(require, require-dev, conflict, replace, provide)</span>
-
-Each of these takes an object which maps package names to version constraints.
-
-* **require:** Packages required by this package.
-* **require-dev:** Packages required for developing this package, or running
-  tests, etc. They are installed if install or update is ran with `--dev`.
-* **conflict:** Mark this version of this package as conflicting with other
-  packages.
-* **replace:** Packages that can be replaced by this package. This is useful
-  for large repositories with subtree splits. It allows the main package to
-  replace all of it's child packages.
-* **provide:** List of other packages that are provided by this package. This
-  is mostly useful for common interfaces. A package could depend on some virtual
-  `logger` package, any library that provides this logger, would simply list it
-  in `provide`.
+### Package links
+
+All of the following take an object which maps package names to
+[version constraints](01-basic-usage.md#package-versions).
 
 Example:
 
@@ -225,7 +213,59 @@ Example:
         }
     }
 
-Optional.
+All links are optional fields.
+
+`require` and `require-dev` additionally support stability flags (root-only).
+These allow you to further restrict or expand the stability of a package beyond
+the scope of the [minimum-stability](#minimum-stability) setting. You can apply
+them to a constraint, or just apply them to an empty constraint if you want to
+allow unstable packages of a dependency's dependency for example.
+
+Example:
+
+    {
+        "require": {
+            "monolog/monolog": "1.0.*@beta"
+            "acme/foo": "@dev"
+        }
+    }
+
+#### require
+
+Lists packages required by this package. The package will not be installed
+unless those requirements can be met.
+
+#### require-dev
+
+Lists packages required for developing this package, or running
+tests, etc. They are installed if install or update is ran with `--dev`.
+
+#### conflict
+
+Lists packages that conflict with this version of this package. They
+will not be allowed to be installed together with your package.
+
+#### replace
+
+Lists packages that are replaced by this package.
+
+This is useful for packages that contain sub-packages, for example the main
+symfony/symfony package contains all the Symfony Components which are also
+available as individual packages. If you require the main package it will
+automatically fulfill any requirement of one of the individual components,
+since it replaces them.
+
+Caution is advised when using replace however, for the sub-package example
+above you should typically only replace using `self.version` as a version
+constraint, to make sure the main package only replaces the sub-packages of
+that exact version, and not any other version, which would be incorrect.
+
+#### provide
+
+List of other packages that are provided by this package. This is mostly
+useful for common interfaces. A package could depend on some virtual
+`logger` package, any library that implements this logger interface would
+simply list it in `provide`.
 
 ### suggest
 
@@ -340,6 +380,19 @@ To do that, `autoload` and `target-dir` are defined as follows:
 
 Optional.
 
+### minimum-stability <span>(root-only)</span>
+
+This defines the default behavior for filtering packages by stability. This
+defaults to `dev` but in the future will be switched to `stable`. As such if
+you rely on a default of `dev` you should specify it in your file to avoid
+surprises.
+
+All versions of each package is checked for stability, and those that are less
+stable than the `minimum-stability` setting will be ignored when resolving
+your project dependencies. Specific changes to the stability requirements of
+a given package can be done in `require` or `require-dev` (see
+[package links](#package-links)).
+
 ### repositories <span>(root-only)</span>
 
 Custom package repositories to use.

+ 4 - 0
res/composer-schema.json

@@ -143,6 +143,10 @@
             "description": "A set of additional repositories where packages can be found.",
             "additionalProperties": true
         },
+        "minimum-stability": {
+            "type": ["string"],
+            "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable."
+        },
         "bin": {
             "type": ["array"],
             "description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).",

+ 51 - 7
src/Composer/DependencyResolver/Pool.php

@@ -12,19 +12,39 @@
 
 namespace Composer\DependencyResolver;
 
+use Composer\Package\BasePackage;
 use Composer\Package\LinkConstraint\LinkConstraintInterface;
 use Composer\Repository\RepositoryInterface;
+use Composer\Repository\CompositeRepository;
+use Composer\Repository\InstalledRepositoryInterface;
+use Composer\Repository\PlatformRepository;
 
 /**
  * A package pool contains repositories that provide packages.
  *
  * @author Nils Adermann <naderman@naderman.de>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
  */
 class Pool
 {
     protected $repositories = array();
     protected $packages = array();
     protected $packageByName = array();
+    protected $acceptableStabilities;
+    protected $stabilityFlags;
+
+    // TODO BC change to stable end of june?
+    public function __construct($minimumStability = 'dev', array $stabilityFlags = array())
+    {
+        $stabilities = BasePackage::$stabilities;
+        $this->acceptableStabilities = array();
+        foreach (BasePackage::$stabilities as $stability => $value) {
+            if ($value <= BasePackage::$stabilities[$minimumStability]) {
+                $this->acceptableStabilities[$stability] = $value;
+            }
+        }
+        $this->stabilityFlags = $stabilityFlags;
+    }
 
     /**
      * Adds a repository and its packages to this package pool
@@ -33,14 +53,38 @@ class Pool
      */
     public function addRepository(RepositoryInterface $repo)
     {
-        $this->repositories[] = $repo;
-
-        foreach ($repo->getPackages() as $package) {
-            $package->setId(count($this->packages) + 1);
-            $this->packages[] = $package;
+        if ($repo instanceof CompositeRepository) {
+            $repos = $repo->getRepositories();
+        } else {
+            $repos = array($repo);
+        }
 
-            foreach ($package->getNames() as $name) {
-                $this->packageByName[$name][] = $package;
+        $id = count($this->packages) + 1;
+        foreach ($repos as $repo) {
+            $this->repositories[] = $repo;
+
+            $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
+            foreach ($repo->getPackages() as $package) {
+                $name = $package->getName();
+                $stability = $package->getStability();
+                if (
+                    // always allow exempt repos
+                    $exempt
+                    // allow if package matches the global stability requirement and has no exception
+                    || (!isset($this->stabilityFlags[$name])
+                        && isset($this->acceptableStabilities[$stability]))
+                    // allow if package matches the package-specific stability flag
+                    || (isset($this->stabilityFlags[$name])
+                        && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$name]
+                    )
+                ) {
+                    $package->setId($id++);
+                    $this->packages[] = $package;
+
+                    foreach ($package->getNames() as $name) {
+                        $this->packageByName[$name][] = $package;
+                    }
+                }
             }
         }
     }

+ 11 - 3
src/Composer/Installer.php

@@ -29,6 +29,7 @@ use Composer\Package\Locker;
 use Composer\Package\PackageInterface;
 use Composer\Repository\ArrayRepository;
 use Composer\Repository\CompositeRepository;
+use Composer\Repository\InstalledArrayRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\RepositoryManager;
@@ -139,7 +140,7 @@ class Installer
         $repos = array_merge(
             $this->repositoryManager->getLocalRepositories(),
             array(
-                new ArrayRepository(array($this->package)),
+                new InstalledArrayRepository(array($this->package)),
                 new PlatformRepository(),
             )
         );
@@ -179,7 +180,9 @@ class Installer
                 $updatedLock = $this->locker->setLockData(
                     $this->repositoryManager->getLocalRepository()->getPackages(),
                     $this->devMode ? $this->repositoryManager->getLocalDevRepository()->getPackages() : null,
-                    $aliases
+                    $aliases,
+                    $this->package->getMinimumStability(),
+                    $this->package->getStabilityFlags()
                 );
                 if ($updatedLock) {
                     $this->io->write('<info>Writing lock file</info>');
@@ -201,13 +204,18 @@ class Installer
 
     protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = false)
     {
+        $minimumStability = $this->package->getMinimumStability();
+        $stabilityFlags = $this->package->getStabilityFlags();
+
         // initialize locker to create aliased packages
         if (!$this->update && $this->locker->isLocked($devMode)) {
             $lockedPackages = $this->locker->getLockedPackages($devMode);
+            $minimumStability = $this->locker->getMinimumStability();
+            $stabilityFlags = $this->locker->getStabilityFlags();
         }
 
         // creating repository pool
-        $pool = new Pool;
+        $pool = new Pool($minimumStability, $stabilityFlags);
         $pool->addRepository($installedRepo);
         foreach ($this->repositoryManager->getRepositories() as $repository) {
             $pool->addRepository($repository);

+ 11 - 1
src/Composer/Package/AliasPackage.php

@@ -28,6 +28,7 @@ class AliasPackage extends BasePackage
     protected $dev;
     protected $aliasOf;
     protected $rootPackageAlias = false;
+    protected $stability;
 
     protected $requires;
     protected $conflicts;
@@ -50,7 +51,8 @@ class AliasPackage extends BasePackage
         $this->version = $version;
         $this->prettyVersion = $prettyVersion;
         $this->aliasOf = $aliasOf;
-        $this->dev = VersionParser::isDev($version);
+        $this->stability = VersionParser::parseStability($version);
+        $this->dev = $this->stability === 'dev';
 
         // replace self.version dependencies
         foreach (array('requires', 'devRequires') as $type) {
@@ -91,6 +93,14 @@ class AliasPackage extends BasePackage
         return $this->version;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function getStability()
+    {
+        return $this->stability;
+    }
+
     /**
      * {@inheritDoc}
      */

+ 14 - 0
src/Composer/Package/BasePackage.php

@@ -32,6 +32,20 @@ abstract class BasePackage implements PackageInterface
         'require-dev' => array('description' => 'requires (for development)', 'method' => 'devRequires'),
     );
 
+    const STABILITY_STABLE  = 0;
+    const STABILITY_RC      = 5;
+    const STABILITY_BETA    = 10;
+    const STABILITY_ALPHA   = 15;
+    const STABILITY_DEV     = 20;
+
+    public static $stabilities = array(
+        'stable' => self::STABILITY_STABLE,
+        'RC'     => self::STABILITY_RC,
+        'beta'   => self::STABILITY_BETA,
+        'alpha'  => self::STABILITY_ALPHA,
+        'dev'    => self::STABILITY_DEV,
+    );
+
     protected $name;
     protected $prettyName;
 

+ 62 - 12
src/Composer/Package/Loader/RootPackageLoader.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Package\Loader;
 
+use Composer\Package\BasePackage;
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\RepositoryManager;
 use Composer\Util\ProcessExecutor;
@@ -60,20 +61,22 @@ class RootPackageLoader extends ArrayLoader
 
         $package = parent::load($config);
 
+        $aliases = array();
+        $stabilityFlags = array();
         if (isset($config['require'])) {
-            $aliases = array();
-            foreach ($config['require'] as $reqName => $reqVersion) {
-                if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $reqVersion, $match)) {
-                    $aliases[] = array(
-                        'package' => strtolower($reqName),
-                        'version' => $this->versionParser->normalize($match[1]),
-                        'alias' => $match[2],
-                        'alias_normalized' => $this->versionParser->normalize($match[2]),
-                    );
-                }
-            }
+            $aliases = $this->extractAliases($config['require'], $aliases);
+            $stabilityFlags = $this->extractStabilityFlags($config['require'], $stabilityFlags);
+        }
+        if (isset($config['require-dev'])) {
+            $aliases = $this->extractAliases($config['require-dev'], $aliases);
+            $stabilityFlags = $this->extractStabilityFlags($config['require-dev'], $stabilityFlags);
+        }
 
-            $package->setAliases($aliases);
+        $package->setAliases($aliases);
+        $package->setStabilityFlags($stabilityFlags);
+
+        if (isset($config['minimum-stability'])) {
+            $package->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability']));
         }
 
         if (isset($config['repositories'])) {
@@ -95,4 +98,51 @@ class RootPackageLoader extends ArrayLoader
 
         return $package;
     }
+
+    private function extractAliases(array $requires, array $aliases)
+    {
+        foreach ($requires as $reqName => $reqVersion) {
+            if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $reqVersion, $match)) {
+                $aliases[] = array(
+                    'package' => strtolower($reqName),
+                    'version' => $this->versionParser->normalize($match[1]),
+                    'alias' => $match[2],
+                    'alias_normalized' => $this->versionParser->normalize($match[2]),
+                );
+            }
+        }
+
+        return $aliases;
+    }
+
+    private function extractStabilityFlags(array $requires, array $stabilityFlags)
+    {
+        $stabilities = BasePackage::$stabilities;
+        foreach ($requires as $reqName => $reqVersion) {
+            // parse explicit stability flags
+            if (preg_match('{^[^,\s]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) {
+                $name = strtolower($reqName);
+                $stability = $stabilities[VersionParser::normalizeStability($match[1])];
+
+                if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
+                    continue;
+                }
+                $stabilityFlags[$name] = $stability;
+
+                continue;
+            }
+
+            // infer flags for requirements that have an explicit -dev or -beta version specified for example
+            if (preg_match('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) {
+                $name = strtolower($reqName);
+                $stability = $stabilities[$stabilityName];
+                if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
+                    continue;
+                }
+                $stabilityFlags[$name] = $stability;
+            }
+        }
+
+        return $stabilityFlags;
+    }
 }

+ 27 - 9
src/Composer/Package/Locker.php

@@ -82,10 +82,10 @@ class Locker
      */
     public function getLockedPackages($dev = false)
     {
-        $lockList = $this->getLockData();
+        $lockData = $this->getLockData();
         $packages = array();
 
-        $lockedPackages = $dev ? $lockList['packages-dev'] : $lockList['packages'];
+        $lockedPackages = $dev ? $lockData['packages-dev'] : $lockData['packages'];
         $repo = $dev ? $this->repositoryManager->getLocalDevRepository() : $this->repositoryManager->getLocalRepository();
 
         foreach ($lockedPackages as $info) {
@@ -128,22 +128,38 @@ class Locker
         return $packages;
     }
 
+    public function getMinimumStability()
+    {
+        $lockData = $this->getLockData();
+
+        // TODO BC change dev to stable end of june?
+        return isset($lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'dev';
+    }
+
+    public function getStabilityFlags()
+    {
+        $lockData = $this->getLockData();
+
+        return isset($lockData['stability-flags']) ? $lockData['stability-flags'] : array();
+    }
+
     public function getAliases()
     {
-        $lockList = $this->getLockData();
-        return isset($lockList['aliases']) ? $lockList['aliases'] : array();
+        $lockData = $this->getLockData();
+
+        return isset($lockData['aliases']) ? $lockData['aliases'] : array();
     }
 
     public function getLockData()
     {
-        if (!$this->lockFile->exists()) {
-            throw new \LogicException('No lockfile found. Unable to read locked packages');
-        }
-
         if (null !== $this->lockDataCache) {
             return $this->lockDataCache;
         }
 
+        if (!$this->lockFile->exists()) {
+            throw new \LogicException('No lockfile found. Unable to read locked packages');
+        }
+
         return $this->lockDataCache = $this->lockFile->read();
     }
 
@@ -156,13 +172,15 @@ class Locker
      *
      * @return Boolean
      */
-    public function setLockData(array $packages, $devPackages, array $aliases)
+    public function setLockData(array $packages, $devPackages, array $aliases, $minimumStability, array $stabilityFlags)
     {
         $lock = array(
             'hash' => $this->hash,
             'packages' => null,
             'packages-dev' => null,
             'aliases' => $aliases,
+            'minimum-stability' => $minimumStability,
+            'stability-flags' => $stabilityFlags,
         );
 
         $lock['packages'] = $this->lockPackages($packages);

+ 50 - 1
src/Composer/Package/MemoryPackage.php

@@ -48,6 +48,10 @@ class MemoryPackage extends BasePackage
     protected $prettyAlias;
     protected $dev;
 
+    // TODO BC change dev to stable end of june?
+    protected $minimumStability = 'dev';
+    protected $stabilityFlags = array();
+
     protected $requires = array();
     protected $conflicts = array();
     protected $provides = array();
@@ -71,7 +75,8 @@ class MemoryPackage extends BasePackage
         $this->version = $version;
         $this->prettyVersion = $prettyVersion;
 
-        $this->dev = VersionParser::isDev($version);
+        $this->stability = VersionParser::parseStability($version);
+        $this->dev = $this->stability === 'dev';
     }
 
     /**
@@ -98,6 +103,14 @@ class MemoryPackage extends BasePackage
         return $this->type ?: 'library';
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function getStability()
+    {
+        return $this->stability;
+    }
+
     /**
      * @param string $targetDir
      */
@@ -588,6 +601,42 @@ class MemoryPackage extends BasePackage
         return $this->homepage;
     }
 
+    /**
+     * Set the minimumStability
+     *
+     * @param string $minimumStability
+     */
+    public function setMinimumStability($minimumStability)
+    {
+        $this->minimumStability = $minimumStability;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMinimumStability()
+    {
+        return $this->minimumStability;
+    }
+
+    /**
+     * Set the stabilityFlags
+     *
+     * @param array $stabilityFlags
+     */
+    public function setStabilityFlags(array $stabilityFlags)
+    {
+        $this->stabilityFlags = $stabilityFlags;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getStabilityFlags()
+    {
+        return $this->stabilityFlags;
+    }
+
     /**
      * Set the autoload mapping
      *

+ 7 - 0
src/Composer/Package/PackageInterface.php

@@ -180,6 +180,13 @@ interface PackageInterface
      */
     function getPrettyVersion();
 
+    /**
+     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
+     *
+     * @return string
+     */
+    function getStability();
+
     /**
      * Returns the package license, e.g. MIT, BSD, GPL
      *

+ 32 - 7
src/Composer/Package/Version/VersionParser.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Package\Version;
 
+use Composer\Package\BasePackage;
 use Composer\Package\LinkConstraint\MultiConstraint;
 use Composer\Package\LinkConstraint\VersionConstraint;
 
@@ -22,17 +23,37 @@ use Composer\Package\LinkConstraint\VersionConstraint;
  */
 class VersionParser
 {
-    private $modifierRegex = '[.-]?(?:(beta|RC|alpha|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
+    private static $modifierRegex = '[.-]?(?:(beta|RC|alpha|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
 
     /**
-     * Checks if a version is dev or not
+     * Returns the stability of a version
      *
      * @param string $version
-     * @return Boolean
+     * @return string
      */
-    static public function isDev($version)
+    static public function parseStability($version)
     {
-        return 'dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4);
+        if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) {
+            return 'dev';
+        }
+
+        preg_match('{'.self::$modifierRegex.'$}', $version, $match);
+        if (!empty($match[3])) {
+            return 'dev';
+        }
+
+        if (!empty($match[1]) && ($match[1] === 'beta' || $match[1] === 'alpha' || $match[1] === 'RC')) {
+            return $match[1];
+        }
+
+        return 'stable';
+    }
+
+    static public function normalizeStability($stability)
+    {
+        $stability = strtolower($stability);
+
+        return $stability === 'rc' ? 'RC' : $stability;
     }
 
     /**
@@ -60,13 +81,13 @@ class VersionParser
         }
 
         // match classical versioning
-        if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?'.$this->modifierRegex.'$}i', $version, $matches)) {
+        if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?'.self::$modifierRegex.'$}i', $version, $matches)) {
             $version = $matches[1]
                 .(!empty($matches[2]) ? $matches[2] : '.0')
                 .(!empty($matches[3]) ? $matches[3] : '.0')
                 .(!empty($matches[4]) ? $matches[4] : '.0');
             $index = 5;
-        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)'.$this->modifierRegex.'$}i', $version, $matches)) { // match date-based versioning
+        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)'.self::$modifierRegex.'$}i', $version, $matches)) { // match date-based versioning
             $version = preg_replace('{\D}', '-', $matches[1]);
             $index = 2;
         }
@@ -130,6 +151,10 @@ class VersionParser
      */
     public function parseConstraints($constraints)
     {
+        if (preg_match('{^([^,\s]*?)@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $constraints, $match)) {
+            $constraints = empty($match[1]) ? '*' : $match[1];
+        }
+
         $constraints = preg_split('{\s*,\s*}', trim($constraints));
 
         if (count($constraints) > 1) {

+ 10 - 0
src/Composer/Repository/CompositeRepository.php

@@ -36,6 +36,16 @@ class CompositeRepository implements RepositoryInterface
         $this->repositories = $repositories;
     }
 
+    /**
+     * Returns all the wrapped repositories
+     *
+     * @return array
+     */
+    public function getRepositories()
+    {
+        return $this->repositories;
+    }
+
     /**
      * {@inheritdoc}
      */

+ 42 - 0
src/Composer/Repository/InstalledArrayRepository.php

@@ -0,0 +1,42 @@
+<?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\Repository;
+
+use Composer\Json\JsonFile;
+use Composer\Package\PackageInterface;
+use Composer\Package\Loader\ArrayLoader;
+use Composer\Package\Dumper\ArrayDumper;
+
+/**
+ * Installed array repository.
+ *
+ * This is used for serving the RootPackage inside an in-memory InstalledRepository
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class InstalledArrayRepository extends ArrayRepository implements InstalledRepositoryInterface
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function write()
+    {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function reload()
+    {
+    }
+}

+ 17 - 0
tests/Composer/Test/DependencyResolver/PoolTest.php

@@ -14,6 +14,7 @@ namespace Composer\Test\DependencyResolver;
 
 use Composer\DependencyResolver\Pool;
 use Composer\Repository\ArrayRepository;
+use Composer\Package\BasePackage;
 use Composer\Test\TestCase;
 
 class PoolTest extends TestCase
@@ -31,6 +32,22 @@ class PoolTest extends TestCase
         $this->assertEquals(array($package), $pool->whatProvides('foo'));
     }
 
+    public function testPoolIgnoresIrrelevantPackages()
+    {
+        $pool = new Pool('stable', array('bar' => BasePackage::STABILITY_BETA));
+        $repo = new ArrayRepository;
+        $repo->addPackage($package = $this->getPackage('bar', '1'));
+        $repo->addPackage($betaPackage = $this->getPackage('bar', '1-beta'));
+        $repo->addPackage($alphaPackage = $this->getPackage('bar', '1-alpha'));
+        $repo->addPackage($package2 = $this->getPackage('foo', '1'));
+        $repo->addPackage($rcPackage2 = $this->getPackage('foo', '1rc'));
+
+        $pool->addRepository($repo);
+
+        $this->assertEquals(array($package, $betaPackage), $pool->whatProvides('bar'));
+        $this->assertEquals(array($package2), $pool->whatProvides('foo'));
+    }
+
     /**
      * @expectedException \RuntimeException
      */

+ 4 - 2
tests/Composer/Test/Package/LockerTest.php

@@ -157,9 +157,11 @@ class LockerTest extends \PHPUnit_Framework_TestCase
                 ),
                 'packages-dev' => array(),
                 'aliases' => array(),
+                'minimum-stability' => 'dev',
+                'stability-flags' => array(),
             ));
 
-        $locker->setLockData(array($package1, $package2), array(), array());
+        $locker->setLockData(array($package1, $package2), array(), array(), 'dev', array());
     }
 
     public function testLockBadPackages()
@@ -177,7 +179,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
 
         $this->setExpectedException('LogicException');
 
-        $locker->setLockData(array($package1), array(), array());
+        $locker->setLockData(array($package1), array(), array(), 'dev', array());
     }
 
     public function testIsFresh()

+ 19 - 9
tests/Composer/Test/Package/Version/VersionParserTest.php

@@ -106,6 +106,12 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
         );
     }
 
+    public function testParseConstraintsIgnoresStabilityFlag()
+    {
+        $parser = new VersionParser;
+        $this->assertSame((string) new VersionConstraint('=', '1.0.0.0'), (string) $parser->parseConstraints('1.0@dev'));
+    }
+
     /**
      * @dataProvider simpleConstraints
      */
@@ -195,21 +201,25 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * @dataProvider dataIsDev
+     * @dataProvider stabilityProvider
      */
-    public function testIsDev($expected, $version)
+    public function testParseStability($expected, $version)
     {
-        $this->assertSame($expected, VersionParser::isDev($version));
+        $this->assertSame($expected, VersionParser::parseStability($version));
     }
 
-    public function dataIsDev()
+    public function stabilityProvider()
     {
         return array(
-            array(false, '1.0'),
-            array(false, 'v2.0.*'),
-            array(false, '3.0dev'),
-            array(true, 'dev-master'),
-            array(true, '3.1.2-dev'),
+            array('stable', '1.0'),
+            array('dev',    'v2.0.x-dev'),
+            array('RC',     '3.0-RC2'),
+            array('dev',    'dev-master'),
+            array('dev',    '3.1.2-dev'),
+            array('stable', '3.1.2-pl2'),
+            array('stable', '3.1.2-patch'),
+            array('alpha',  '3.1.2-alpha5'),
+            array('beta',   '3.1.2-beta'),
         );
     }
 }