← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/bug-598345-restrict-dep-contexts into lp:launchpad/devel

 

William Grant has proposed merging lp:~wgrant/launchpad/bug-598345-restrict-dep-contexts into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #52698 Auto-Dep retry algorithm doesn't check component
  https://bugs.launchpad.net/bugs/52698
  #598345 findDepCandidateByName does not restrict to available pockets
  https://bugs.launchpad.net/bugs/598345
  #606789 findDepCandidateByName blindly returns the first result
  https://bugs.launchpad.net/bugs/606789


This branches fixes bug #52698 and bug #598345. At present some depwait builds are retried prematurely, as the candidate query in Archive.findDepCandidateByName constrains just the archive and distroarchseries context -- not the pocket or component, which are also included in the sources.list entries and restrict the available packages. A notable case is that of Hardy PPA packages dependent on debhelper 7: while a sufficient version is present in hardy-backports, most PPAs are not configured to use -backports. This results in such builds being retried every time the script runs, using vast amounts of buildd time to no effect.

A related (and also fixed here) issue is bug #606789: findDepCandidateByName() returned just the latest publication (by BPPH.id), and BinaryPackageBuild._isDependencySatisfied() checked the version constraint against that. But this ignores the fact that apt will look at *all* available versions across all archives and pockets, so _isDependencySatisfied needs to check that at least one of *any* of the available versions satisfies the version constraint.

I've fixed the first problem by refactoring the archive dependency expansion logic in lp.soyuz.adapters.archivedependencies, creating a new expand_dependencies() which returns a list of archives with the available pockets and components. The existing get_sources_list_for_building() wraps that, converting each dependency into a sources.list line, and adding any custom external dependencies as before. findDepCandidateByName() then uses expand_dependencies() to create the context restriction clause (archive, pocket and component). This is the same logic used to generate sources.list itself -- so it results in a completely accurate query.

For the second issue, findDepCandidateByName() has been replaced with findDepCandidates(), which returns a sequence of all matching binaries, latest first. _isDependencySatisfied() has been tweaked to look through all of them, returning True when a satisfying candidate is identified.

I've also removed some manual ogre-model logic from _isDependencySatisfied(), since that's now handled by findDepCandidates().

The findDepCandidateByName() doctests made me sad, so they've been replaced with expanded unit tests in test_archive. test_binarypackagebuild has two new tests: one checking that versioned dependencies work (a hole in the existing suite), and another verifying that more than just the latest binary is considered (new behaviour).
-- 
https://code.launchpad.net/~wgrant/launchpad/bug-598345-restrict-dep-contexts/+merge/30203
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/bug-598345-restrict-dep-contexts into lp:launchpad/devel.
=== modified file 'lib/lp/soyuz/adapters/archivedependencies.py'
--- lib/lp/soyuz/adapters/archivedependencies.py	2010-05-10 19:49:02 +0000
+++ lib/lp/soyuz/adapters/archivedependencies.py	2010-07-18 05:05:59 +0000
@@ -12,8 +12,9 @@
 
 Auxiliary functions exposed for testing purposes:
 
- * get_components_for_building: return the corresponding component
-       dependencies for a build, this result is known as 'ogre_components';
+ * get_components_for_context: return the corresponding component
+       dependencies for a component and pocket, this result is known as
+       'ogre_components';
  * get_primary_current_component: return the component name where the
        building source is published in the primary archive.
 
@@ -30,7 +31,8 @@
     'component_dependencies',
     'default_component_dependency_name',
     'default_pocket_dependency',
-    'get_components_for_building',
+    'expand_dependencies',
+    'get_components_for_context',
     'get_primary_current_component',
     'get_sources_list_for_building',
     'pocket_dependencies',
@@ -86,19 +88,20 @@
 default_component_dependency_name = 'multiverse'
 
 
-def get_components_for_building(build):
+def get_components_for_context(component, pocket):
     """Return the components allowed to be used in the build context.
 
-    :param build: a context `IBuild`.
+    :param component: the context `IComponent`.
+    :param pocket: the context `IPocket`.
     :return: a list of component names.
     """
     # BACKPORTS should be able to fetch build dependencies from any
     # component in order to cope with component changes occurring
-    # accross distroseries. See bug #198936 for further information.
-    if build.pocket == PackagePublishingPocket.BACKPORTS:
+    # across distroseries. See bug #198936 for further information.
+    if pocket == PackagePublishingPocket.BACKPORTS:
         return component_dependencies['multiverse']
 
-    return component_dependencies[build.current_component.name]
+    return component_dependencies[component.name]
 
 
 def get_primary_current_component(archive, distroseries, sourcepackagename):
@@ -119,30 +122,73 @@
     return 'universe'
 
 
+def expand_dependencies(archive, distro_series, pocket, component,
+                        source_package_name):
+    """Return the set of dependency archives, pockets and components.
+
+    :param archive: the context `IArchive`.
+    :param distro_series: the context `IDistroSeries`.
+    :param pocket: the context `PackagePublishingPocket`.
+    :param component: the context `IComponent`.
+    :param source_package_name: A source package name (as text)
+    :return: a list of (archive, pocket, [component]), representing the
+        dependencies defined by the given build context.
+    """
+    deps = []
+
+    # Add implicit self-dependency for non-primary contexts.
+    if archive.purpose in ALLOW_RELEASE_BUILDS:
+        deps.append((
+            archive, PackagePublishingPocket.RELEASE,
+            get_components_for_context(component, pocket)))
+
+    primary_component = get_primary_current_component(
+        archive, distro_series, source_package_name)
+    # Consider user-selected archive dependencies.
+    for archive_dependency in archive.dependencies:
+        # When the dependency component is undefined, we should use
+        # the component where the source is published in the primary
+        # archive.
+        if archive_dependency.component is None:
+            components = component_dependencies[primary_component]
+        else:
+            components = component_dependencies[
+                archive_dependency.component.name]
+        # Follow pocket dependencies.
+        for pocket in pocket_dependencies[archive_dependency.pocket]:
+            deps.append(
+                (archive_dependency.dependency, pocket, components))
+
+    # Consider primary archive dependency override. Add the default
+    # primary archive dependencies if it's not present.
+    if archive.getArchiveDependency(
+        archive.distribution.main_archive) is None:
+        primary_dependencies = _get_default_primary_dependencies(
+            archive, component, pocket)
+        deps.extend(primary_dependencies)
+
+    return deps
+
+
 def get_sources_list_for_building(build, distroarchseries, sourcepackagename):
     """Return the sources_list entries required to build the given item.
 
     The entries are returned in the order that is most useful;
      1. the context archive itself
-     2. external dependencies
-     3. user-selected archive dependencies
-     4. the default primary archive
+     2. user-selected archive dependencies
+     3. the default primary archive
+     4. external dependencies
 
-    :param build: a context `IBuild`.
-    :param distroarchseries: A `IDistroArchSeries`
-    :param sourcepackagename: A source package name (as text)
-    :return: a deb sources_list entries (lines).
+    :param build: the context `IBuild`.
+    :param distroarchseries: the context `IDistroArchSeries`.
+    :param sourcepackagename: the source package name (as text).
+    :return: a list of sources.list entries.
     """
-    deps = []
-    sources_list_lines = []
-
-    # Add implicit self-dependency for non-primary contexts.
-    if build.archive.purpose in ALLOW_RELEASE_BUILDS:
-        self_dep = [(
-            build.archive, PackagePublishingPocket.RELEASE,
-            get_components_for_building(build))]
-        sources_list_lines = _get_sources_list_for_dependencies(
-            self_dep, distroarchseries)
+    deps = expand_dependencies(
+        build.archive, distroarchseries.distroseries, build.pocket,
+        build.current_component, sourcepackagename)
+    sources_list_lines = \
+        _get_sources_list_for_dependencies(deps, distroarchseries)
 
     # Append external sources_list lines for this archive if it's
     # specified in the configuration.
@@ -166,35 +212,9 @@
         if build.archive.enabled == True:
             build.archive.disable()
 
-    # Consider user-selected archive dependencies.
-    primary_component = get_primary_current_component(
-        build.archive, build.distro_series, sourcepackagename)
-    for archive_dependency in build.archive.dependencies:
-        # When the dependency component is undefined, we should use
-        # the component where the source is published in the primary
-        # archive.
-        if archive_dependency.component is None:
-            components = component_dependencies[primary_component]
-        else:
-            components = component_dependencies[
-                archive_dependency.component.name]
-        # Follow pocket dependencies.
-        for pocket in pocket_dependencies[archive_dependency.pocket]:
-            deps.append(
-                (archive_dependency.dependency, pocket, components)
-                )
-
-    # Consider primary archive dependency override. Add the default
-    # primary archive dependencies if it's not present.
-    if build.archive.getArchiveDependency(
-        build.archive.distribution.main_archive) is None:
-        primary_dependencies = _get_default_primary_dependencies(build)
-        deps.extend(primary_dependencies)
-
-    sources_list_lines.extend(
-        _get_sources_list_for_dependencies(deps, distroarchseries))
     return sources_list_lines
 
+
 def _has_published_binaries(archive, distroarchseries, pocket):
     """Whether or not the archive dependency has published binaries."""
     # The primary archive dependencies are always relevant.
@@ -252,27 +272,29 @@
     return sources_list_lines
 
 
-def _get_default_primary_dependencies(build):
-    """Return the default primary dependencies for a given build.
+def _get_default_primary_dependencies(archive, component, pocket):
+    """Return the default primary dependencies for a given context.
 
-    :param build: the `IBuild` context;
+    :param archive: the context `IArchive`.
+    :param component: the context `IComponent`.
+    :param pocket: the context `PackagePublishingPocket`.
 
     :return: a list containing the default dependencies to primary
         archive.
     """
-    if build.archive.purpose in ALLOW_RELEASE_BUILDS:
+    if archive.purpose in ALLOW_RELEASE_BUILDS:
         primary_pockets = pocket_dependencies[
             default_pocket_dependency]
         primary_components = component_dependencies[
             default_component_dependency_name]
     else:
-        primary_pockets = pocket_dependencies[build.pocket]
-        primary_components = get_components_for_building(build)
+        primary_pockets = pocket_dependencies[pocket]
+        primary_components = get_components_for_context(component, pocket)
 
     primary_dependencies = []
     for pocket in primary_pockets:
         primary_dependencies.append(
-            (build.distro_series.distribution.main_archive, pocket,
+            (archive.distribution.main_archive, pocket,
              primary_components))
 
     return primary_dependencies

=== modified file 'lib/lp/soyuz/doc/archive-dependencies.txt'
--- lib/lp/soyuz/doc/archive-dependencies.txt	2010-05-10 19:49:02 +0000
+++ lib/lp/soyuz/doc/archive-dependencies.txt	2010-07-18 05:05:59 +0000
@@ -97,7 +97,7 @@
     ...     'main', 'restricted', 'universe', 'multiverse', 'partner']
 
     >>> from lp.soyuz.adapters.archivedependencies import (
-    ...     get_components_for_building)
+    ...     get_components_for_context)
 
     >>> ogre_pub = test_publisher.getPubSource(sourcename='ogre')
     >>> [ogre_build] = ogre_pub.createMissingBuilds()
@@ -111,7 +111,8 @@
     ...         syncUpdate(ogre_pub)
     ...         flush_database_caches()
     ...         components_term = " ".join(
-    ...             get_components_for_building(ogre_build))
+    ...             get_components_for_context(
+    ...                 ogre_build.current_component, ogre_build.pocket))
     ...         print '%10s | %s' % (ogre_build.current_component.name,
     ...                              components_term)
 
@@ -557,13 +558,12 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
+    deb http://ftpmaster.internal/ubuntu hoary
+        main restricted universe multiverse
+    deb http://ftpmaster.internal/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://ftpmaster.internal/ubuntu hoary-updates
+        main restricted universe multiverse
     deb http://user:pass@repository zoing everything
     deb http://user:pass@repository hoary public private
     deb http://user:pass@repository hoary-extra public
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
-        main restricted universe multiverse
-

=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt	2010-07-02 21:25:36 +0000
+++ lib/lp/soyuz/doc/archive.txt	2010-07-18 05:05:59 +0000
@@ -1285,72 +1285,6 @@
     ...
     AssertionError: This dependency does not exist.
 
-== Find binary package dependency candidates ==
-
-Archive allows a lookup on a single binary package dependency
-candidate by its name, via the `PublishedPackage` table:
-
-    >>> warty_i386 = warty['i386']
-
-    >>> candidate = ubuntu.main_archive.findDepCandidateByName(
-    ...     warty_i386, "pmount")
-    >>> print candidate.binarypackagerelease.binarypackagename.name
-    pmount
-
-    >>> candidate = cprov.archive.findDepCandidateByName(
-    ...     warty_i386, "pmount")
-    >>> print candidate.binarypackagerelease.binarypackagename.name
-    pmount
-
-Since 'python2.4' isn't available in our sampledata (not even
-published), None is returned:
-
-    >>> print ubuntu.main_archive.findDepCandidateByName(
-    ...     warty_i386, "python2.4")
-    None
-
-    >>> print cprov.archive.findDepCandidateByName(
-    ...     warty_i386, "python2.4")
-    None
-
-This method is aware of the archive dependency tree. So, even when a
-package is not published on the context PPA but is available somewhere
-in the archive dependency domain it will be found.
-
-We also add another archive dependency here to exercise findDepCandidateByName
-a little more.
-
-    >>> joe = factory.makePerson(email='joe@xxxxxxxxxxx')
-    >>> second_ppa = factory.makeArchive(name="secondppa", owner=joe)
-    >>> second_archive_dep = cprov.archive.addArchiveDependency(
-    ...     second_ppa, release_pocket, main_component)
-
-'at' binary package is not present in Celso's PPA.
-
-    >>> cprov_archive.getAllPublishedBinaries(name='at').count()
-    0
-
-But it is available in PRIMARY ubuntu archive.
-
-    >>> primary_candidate = ubuntu.main_archive.findDepCandidateByName(
-    ...     warty_i386, "at")
-    >>> primary_candidate is not None
-    True
-
-Then a lookup on Celso's PPA will find it.
-
-    >>> ppa_candidate = cprov.archive.findDepCandidateByName(
-    ...     warty_i386, "at")
-    >>> ppa_candidate is not None
-    True
-
-    >>> primary_candidate == ppa_candidate
-    True
-
-And clean-up after ourselves:
-
-    >>> ignore = cprov.archive.removeArchiveDependency(second_ppa)
-
 == Creating a package copy request from an IArchive ==
 
 The IArchive interface includes a convenience method for creating a

=== modified file 'lib/lp/soyuz/doc/binarypackagebuild.txt'
--- lib/lp/soyuz/doc/binarypackagebuild.txt	2010-06-10 22:12:08 +0000
+++ lib/lp/soyuz/doc/binarypackagebuild.txt	2010-07-18 05:05:59 +0000
@@ -987,8 +987,9 @@
     main
 
     >>> from lp.soyuz.adapters.archivedependencies import (
-    ...     get_components_for_building)
-    >>> print get_components_for_building(depwait_build)
+    ...     get_components_for_context)
+    >>> print get_components_for_context(
+    ...     depwait_build.current_component, depwait_build.pocket)
     ['main']
 
 Thus the 'pmount' dependency remains unsatisfied.
@@ -1036,7 +1037,8 @@
     >>> flush_database_caches()
     >>> login(ANONYMOUS)
 
-    >>> print get_components_for_building(depwait_build)
+    >>> print get_components_for_context(
+    ...     depwait_build.current_component, depwait_build.pocket)
     ['main']
 
     >>> print pmount_pub.component.name

=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py	2010-07-12 08:45:32 +0000
+++ lib/lp/soyuz/interfaces/archive.py	2010-07-18 05:05:59 +0000
@@ -443,11 +443,24 @@
         Person table indexes while searching.
         """
 
-    def findDepCandidateByName(distroarchseries, name):
-        """Return the last published binarypackage by given name.
-
-        Return the `BinaryPackagePublishingHistory` record by distroarchseries
-        and name, or None if not found.
+    def findDepCandidates(distro_arch_series, pocket, component,
+                          source_package_name, dep_name):
+        """Return matching binaries in this archive and its dependencies.
+
+        Return all published `IBinaryPackagePublishingHistory` records with
+        the given name, in this archive and dependencies as specified by the
+        given build context, using the usual archive dependency rules.
+
+        We can't just use the first, since there may be other versions
+        published in other dependency archives.
+
+        :param distro_arch_series: the context `IDistroArchSeries`.
+        :param pocket: the context `PackagePublishingPocket`.
+        :param component: the context `IComponent`.
+        :param source_package_name: the context source package name (as text).
+        :param dep_name: the name of the binary package to look up.
+        :return: a sequence of matching `IBinaryPackagePublishingHistory`
+            records.
         """
 
     def removeArchiveDependency(dependency):

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2010-07-12 08:45:32 +0000
+++ lib/lp/soyuz/model/archive.py	2010-07-18 05:05:59 +0000
@@ -37,6 +37,7 @@
 from lp.buildmaster.model.buildfarmjob import BuildFarmJob
 from lp.buildmaster.model.packagebuild import PackageBuild
 from lp.services.job.interfaces.job import JobStatus
+from lp.soyuz.adapters.archivedependencies import expand_dependencies
 from lp.soyuz.adapters.packagelocation import PackageLocation
 from canonical.launchpad.components.tokens import (
     create_unique_token_for_table)
@@ -47,6 +48,7 @@
 from lp.soyuz.model.binarypackagerelease import (
     BinaryPackageReleaseDownloadCount)
 from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
+from lp.soyuz.model.component import Component
 from lp.soyuz.model.distributionsourcepackagecache import (
     DistributionSourcePackageCache)
 from lp.soyuz.model.distroseriespackagecache import DistroSeriesPackageCache
@@ -807,30 +809,32 @@
         self.sources_cached = sources_cached.count()
         self.binaries_cached = binaries_cached.count()
 
-    def findDepCandidateByName(self, distroarchseries, name):
+    def findDepCandidates(self, distro_arch_series, pocket, component,
+                          source_package_name, dep_name):
         """See `IArchive`."""
-        archives = []
-        if self.is_ppa:
-            archives.append(self.distribution.main_archive.id)
-        archives.append(self.id)
-        archives.extend(
-            IResultSet(self.dependencies).values(
-            ArchiveDependency.dependencyID))
+        deps = expand_dependencies(
+            self, distro_arch_series.distroseries, pocket, component,
+            source_package_name)
+        archive_clause = Or([And(
+            BinaryPackagePublishingHistory.archiveID == archive.id,
+            BinaryPackagePublishingHistory.pocket == pocket,
+            Component.name.is_in(components))
+            for (archive, pocket, components) in deps])
 
         store = ISlaveStore(BinaryPackagePublishingHistory)
-        candidate = store.find(
+        return store.find(
             BinaryPackagePublishingHistory,
-            BinaryPackageName.name == name,
+            BinaryPackageName.name == dep_name,
             BinaryPackageRelease.binarypackagename == BinaryPackageName.id,
             BinaryPackagePublishingHistory.binarypackagerelease ==
                 BinaryPackageRelease.id,
             BinaryPackagePublishingHistory.distroarchseries ==
-                distroarchseries,
-            In(BinaryPackagePublishingHistory.archiveID, archives),
+                distro_arch_series,
             BinaryPackagePublishingHistory.status ==
-                PackagePublishingStatus.PUBLISHED
-            ).order_by(Desc(BinaryPackagePublishingHistory.id))
-        return candidate.first()
+                PackagePublishingStatus.PUBLISHED,
+            BinaryPackagePublishingHistory.componentID == Component.id,
+            archive_clause).order_by(
+                Desc(BinaryPackagePublishingHistory.id))
 
     def getArchiveDependency(self, dependency):
         """See `IArchive`."""

=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
--- lib/lp/soyuz/model/binarypackagebuild.py	2010-06-15 11:11:27 +0000
+++ lib/lp/soyuz/model/binarypackagebuild.py	2010-07-18 05:05:59 +0000
@@ -50,7 +50,6 @@
 from lp.buildmaster.model.packagebuild import (
     PackageBuild, PackageBuildDerived)
 from lp.services.job.model.job import Job
-from lp.soyuz.adapters.archivedependencies import get_components_for_building
 from lp.soyuz.interfaces.archive import ArchivePurpose
 from lp.soyuz.interfaces.binarypackagebuild import (
     BuildSetStatus, CannotBeRescored, IBinaryPackageBuild,
@@ -387,33 +386,25 @@
     def _isDependencySatisfied(self, token):
         """Check if the given dependency token is satisfied.
 
-        Check if the dependency exists, if its version constraint is
-        satisfied and if it is reachable in the build context.
+        Check if the dependency exists and that its version constraint is
+        satisfied.
         """
         name, version, relation = self._parseDependencyToken(token)
 
-        dep_candidate = self.archive.findDepCandidateByName(
-            self.distro_arch_series, name)
-
-        if not dep_candidate:
-            return False
-
-        if not self._checkDependencyVersion(
-            dep_candidate.binarypackagerelease.version, version, relation):
-            return False
-
-        # Only PRIMARY archive build dependencies should be restricted
-        # to the ogre_components. Both PARTNER and PPA can reach
-        # dependencies from all components in the PRIMARY archive.
-        # Moreover, PARTNER and PPA component domain is single, i.e,
-        # PARTNER only contains packages in 'partner' component and PPAs
-        # only contains packages in 'main' component.
-        ogre_components = get_components_for_building(self)
-        if (self.archive.purpose == ArchivePurpose.PRIMARY and
-            dep_candidate.component.name not in ogre_components):
-            return False
-
-        return True
+        # There may be several published versions in the available
+        # archives and pockets. If any one of them satisifies our
+        # constraints, the dependency is satisfied.
+        dep_candidates = self.archive.findDepCandidates(
+            self.distro_arch_series, self.pocket, self.current_component,
+            self.source_package_release.sourcepackagename.name, name)
+
+        for dep_candidate in dep_candidates:
+            if self._checkDependencyVersion(
+                dep_candidate.binarypackagerelease.version, version,
+                relation):
+                return True
+
+        return False
 
     def _toAptFormat(self, token):
         """Rebuild dependencies line in apt format."""

=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py	2010-07-12 13:32:53 +0000
+++ lib/lp/soyuz/tests/test_archive.py	2010-07-18 05:05:59 +0000
@@ -4,9 +4,10 @@
 """Test Archive features."""
 
 from datetime import date, datetime, timedelta
+import unittest
+
 import pytz
-import unittest
-
+import transaction
 from zope.component import getUtility
 from zope.security.interfaces import Unauthorized
 from zope.security.proxy import removeSecurityProxy
@@ -1099,7 +1100,141 @@
         login("commercial-member@xxxxxxxxxxxxx")
         self.setCommercial(self.archive, True)
         self.assertTrue(self.archive.commercial)
-        
+
+
+class TestFindDepCandidates(TestCaseWithFactory):
+    """Tests for Archive.findDepCandidates."""
+
+    layer = LaunchpadZopelessLayer
+
+    def setUp(self):
+        super(TestFindDepCandidates, self).setUp()
+        self.archive = self.factory.makeArchive()
+        self.publisher = SoyuzTestPublisher()
+        login('admin@xxxxxxxxxxxxx')
+        self.publisher.prepareBreezyAutotest()
+
+    def assertDep(self, arch_tag, name, expected, archive=None,
+                  pocket=PackagePublishingPocket.RELEASE, component=None,
+                  source_package_name='something-new'):
+        """Helper to check that findDepCandidates works.
+
+        Searches for the given dependency name in the given architecture and
+        archive, and compares it to the given expected value.
+        The archive defaults to self.archive.
+
+        Also commits, since findDepCandidates uses the slave store.
+        """
+        transaction.commit()
+
+        if component is None:
+            component = getUtility(IComponentSet)['main']
+        if archive is None:
+            archive = self.archive
+
+        self.assertEquals(
+            list(
+                archive.findDepCandidates(
+                    self.publisher.distroseries[arch_tag], pocket, component,
+                    source_package_name, name)),
+            expected)
+
+    def test_finds_candidate_in_same_archive(self):
+        # A published candidate in the same archive should be found.
+        bins = self.publisher.getPubBinaries(
+            binaryname='foo', archive=self.archive,
+            status=PackagePublishingStatus.PUBLISHED)
+        self.assertDep('i386', 'foo', [bins[0]])
+        self.assertDep('hppa', 'foo', [bins[1]])
+
+    def test_does_not_find_pending_publication(self):
+        # A pending candidate in the same archive should not be found.
+        bins = self.publisher.getPubBinaries(
+            binaryname='foo', archive=self.archive)
+        self.assertDep('i386', 'foo', [])
+
+    def test_ppa_searches_primary_archive(self):
+        # PPA searches implicitly look in the primary archive too.
+        self.assertEquals(self.archive.purpose, ArchivePurpose.PPA)
+        self.assertDep('i386', 'foo', [])
+
+        bins = self.publisher.getPubBinaries(
+            binaryname='foo', archive=self.archive.distribution.main_archive,
+            status=PackagePublishingStatus.PUBLISHED)
+
+        self.assertDep('i386', 'foo', [bins[0]])
+
+    def test_searches_dependencies(self):
+        # Candidates from archives on which the target explicitly depends
+        # should be found.
+        bins = self.publisher.getPubBinaries(
+            binaryname='foo', archive=self.archive,
+            status=PackagePublishingStatus.PUBLISHED)
+        other_archive = self.factory.makeArchive()
+        self.assertDep('i386', 'foo', [], archive=other_archive)
+
+        other_archive.addArchiveDependency(
+            self.archive, PackagePublishingPocket.RELEASE)
+        self.assertDep('i386', 'foo', [bins[0]], archive=other_archive)
+
+    def test_obeys_dependency_pockets(self):
+        # Only packages published in a pocket matching the dependency should
+        # be found.
+        release_bins = self.publisher.getPubBinaries(
+            binaryname='foo-release', archive=self.archive,
+            status=PackagePublishingStatus.PUBLISHED)
+        updates_bins = self.publisher.getPubBinaries(
+            binaryname='foo-updates', archive=self.archive,
+            status=PackagePublishingStatus.PUBLISHED,
+            pocket=PackagePublishingPocket.UPDATES)
+        proposed_bins = self.publisher.getPubBinaries(
+            binaryname='foo-proposed', archive=self.archive,
+            status=PackagePublishingStatus.PUBLISHED,
+            pocket=PackagePublishingPocket.PROPOSED)
+
+        # Temporarily turn our test PPA into a copy archive, so we can
+        # add non-RELEASE dependencies on it.
+        removeSecurityProxy(self.archive).purpose = ArchivePurpose.COPY
+
+        other_archive = self.factory.makeArchive()
+        other_archive.addArchiveDependency(
+            self.archive, PackagePublishingPocket.UPDATES)
+        self.assertDep(
+            'i386', 'foo-release', [release_bins[0]], archive=other_archive)
+        self.assertDep(
+            'i386', 'foo-updates', [updates_bins[0]], archive=other_archive)
+        self.assertDep('i386', 'foo-proposed', [], archive=other_archive)
+
+        other_archive.removeArchiveDependency(self.archive)
+        other_archive.addArchiveDependency(
+            self.archive, PackagePublishingPocket.PROPOSED)
+        self.assertDep(
+            'i386', 'foo-proposed', [proposed_bins[0]], archive=other_archive)
+
+    def test_obeys_dependency_components(self):
+        # Only packages published in a component matching the dependency
+        # should be found.
+        primary = self.archive.distribution.main_archive
+        main_bins = self.publisher.getPubBinaries(
+            binaryname='foo-main', archive=primary, component='main',
+            status=PackagePublishingStatus.PUBLISHED)
+        universe_bins = self.publisher.getPubBinaries(
+            binaryname='foo-universe', archive=primary,
+            component='universe',
+            status=PackagePublishingStatus.PUBLISHED)
+
+        self.archive.addArchiveDependency(
+            primary, PackagePublishingPocket.RELEASE,
+            component=getUtility(IComponentSet)['main'])
+        self.assertDep('i386', 'foo-main', [main_bins[0]])
+        self.assertDep('i386', 'foo-universe', [])
+
+        self.archive.removeArchiveDependency(primary)
+        self.archive.addArchiveDependency(
+            primary, PackagePublishingPocket.RELEASE,
+            component=getUtility(IComponentSet)['universe'])
+        self.assertDep('i386', 'foo-main', [main_bins[0]])
+        self.assertDep('i386', 'foo-universe', [universe_bins[0]])
 
 
 def test_suite():

=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py	2010-06-21 07:26:51 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py	2010-07-18 05:05:59 +0000
@@ -164,13 +164,13 @@
         Return an `IBinaryPackageBuild` in MANUALDEWAIT state and depending on a
         binary that exists and is reachable.
         """
-        test_publisher = SoyuzTestPublisher()
-        test_publisher.prepareBreezyAutotest()
+        self.publisher = SoyuzTestPublisher()
+        self.publisher.prepareBreezyAutotest()
 
-        depwait_source = test_publisher.getPubSource(
+        depwait_source = self.publisher.getPubSource(
             sourcename='depwait-source')
 
-        test_publisher.getPubBinaries(
+        self.publisher.getPubBinaries(
             binaryname='dep-bin',
             status=PackagePublishingStatus.PUBLISHED)
 
@@ -273,6 +273,41 @@
         depwait_build.updateDependencies()
         self.assertEquals(depwait_build.dependencies, '')
 
+    def testVersionedDependencies(self):
+        # `IBinaryPackageBuild.updateDependencies` supports versioned
+        # dependencies. A build will not be retried unless the candidate
+        # complies with the version restriction.
+        # In this case, dep-bin 666 is available. >> 666 isn't
+        # satisified, but >= 666 is.
+        depwait_build = self._setupSimpleDepwaitContext()
+        self.layer.txn.commit()
+
+        depwait_build.dependencies = u'dep-bin (>> 666)'
+        depwait_build.updateDependencies()
+        self.assertEquals(depwait_build.dependencies, u'dep-bin (>> 666)')
+        depwait_build.dependencies = u'dep-bin (>= 666)'
+        depwait_build.updateDependencies()
+        self.assertEquals(depwait_build.dependencies, u'')
+
+    def testVersionedDependencyOnOldPublication(self):
+        # `IBinaryPackageBuild.updateDependencies` doesn't just consider
+        # the latest publication. There may be older publications which
+        # satisfy the version constraints (in other archives or pockets).
+        # In this case, dep-bin 666 and 999 are available, so both = 666
+        # and = 999 are satisfied.
+        depwait_build = self._setupSimpleDepwaitContext()
+        self.publisher.getPubBinaries(
+            binaryname='dep-bin', version='999',
+            status=PackagePublishingStatus.PUBLISHED)
+        self.layer.txn.commit()
+
+        depwait_build.dependencies = u'dep-bin (= 666)'
+        depwait_build.updateDependencies()
+        self.assertEquals(depwait_build.dependencies, u'')
+        depwait_build.dependencies = u'dep-bin (= 999)'
+        depwait_build.updateDependencies()
+        self.assertEquals(depwait_build.dependencies, u'')
+
 
 class BaseTestCaseWithThreeBuilds(TestCaseWithFactory):
 

=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py	2010-07-15 14:19:40 +0000
+++ lib/lp/soyuz/tests/test_publishing.py	2010-07-18 05:05:59 +0000
@@ -267,7 +267,8 @@
                        pub_source=None,
                        version='666',
                        architecturespecific=False,
-                       builder=None):
+                       builder=None,
+                       component='main'):
         """Return a list of binary publishing records."""
         if distroseries is None:
             distroseries = self.distroseries
@@ -285,7 +286,8 @@
             pub_source = self.getPubSource(
                 sourcename=sourcename, status=status, pocket=pocket,
                 archive=archive, distroseries=distroseries,
-                version=version, architecturehintlist=architecturehintlist)
+                version=version, architecturehintlist=architecturehintlist,
+                component=component)
         else:
             archive = pub_source.archive