← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/launchpad/bug-820452 into lp:launchpad

 

Raphaël Victor Badin has proposed merging lp:~rvb/launchpad/bug-820452 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #820452 in Launchpad itself: "Initializing a distroseries is too eager to check in-progress builds in the parent"
  https://bugs.launchpad.net/launchpad/+bug/820452

For more details, see:
https://code.launchpad.net/~rvb/launchpad/bug-820452/+merge/71392

This branch improves the checks performed before we initialize a new distroseries. The checks are twofold: we make sure the parents' queue is empty and we also assert that there are no pending builds for the given parent series.

This branch:
- extends the checks to include the 3 pockets RELEASE, SECURITY and UPDATES.
- restricts the checks to only consider the packages that will be copied if the initialization is restricted to a selection of the parents's packagesets.
- changes the check performed on builds to only consider builds in the selected architectures (if no architectures a selected, this mean we will copy all the architectures from the parents).
- fixes a few lints.
- adds proper tests to make sure that the queue check only considers the items of the right status.

= Tests =

./bin/test -vvc test_initialize_distroseries test_failure_with_pending_builds_specific_arches
./bin/test -vvc test_initialize_distroseries test_check_success_with_pending_builds_in_other_arches
./bin/test -vvc test_initialize_distroseries test_failure_if_build_present_in_selected_packagesets
./bin/test -vvc test_initialize_distroseries test_check_success_if_build_present_in_non_selected_packagesets
./bin/test -vvc test_initialize_distroseries test_check_success_with_binary_queue_items_pockets
./bin/test -vvc test_initialize_distroseries test_failure_with_binary_queue_items_pockets
./bin/test -vvc test_initialize_distroseries test_failure_with_binary_queue_items_status
./bin/test -vvc test_initialize_distroseries test_check_success_with_binary_queue_items_status
./bin/test -vvc test_initialize_distroseries test_check_success_with_source_queue_items
./bin/test -vvc test_initialize_distroseries test_check_success_with_binary_queue_items_outside_packagesets
./bin/test -vvc test_initialize_distroseries test_failure_with_binary_queue_items_in_packagesets

= Q/A =

- Create a parent with 2 architectures 'a' and 'b'. Create pending builds for architecture 'a'. Initializing a series from this parent should only be possible when selecting only the architecture 'b'.
- Create builds for a package from the packageset 'p1' of the parent series. Initializing a series from this parent should be only possible when selecting only other packagesets than 'p1' for the initialization.
- Create an unapproved queue item for a package from the packageset 'p1' of the parent series. Initializing a series from this parent should be only possible when selecting only other packagesets than 'p1' for the initialization.
-- 
https://code.launchpad.net/~rvb/launchpad/bug-820452/+merge/71392
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/launchpad/bug-820452 into lp:launchpad.
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg	2011-08-10 15:57:35 +0000
+++ database/schema/security.cfg	2011-08-12 16:32:27 +0000
@@ -998,6 +998,7 @@
 public.packagesetsources                        = SELECT, INSERT
 public.packageupload                            = SELECT
 public.packageuploadsource                      = SELECT
+public.packageuploadbuild                       = SELECT
 public.packaging                                = SELECT, INSERT
 public.person                                   = SELECT
 public.processor                                = SELECT

=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py	2011-08-05 03:58:16 +0000
+++ lib/lp/registry/interfaces/distroseries.py	2011-08-12 16:32:27 +0000
@@ -541,7 +541,8 @@
         :param created_since_date: If specified, only returns items uploaded
             since the timestamp supplied.
         :param archive: Filter results for this `IArchive`.
-        :param pocket: Filter results by this `PackagePublishingPocket`.
+        :param pocket: Filter results by this `PackagePublishingPocket` or a
+            list of `PackagePublishingPocket`.
         :param custom_type: Filter results by this
             `PackageUploadCustomFormat`.
         :param name: Filter results by this file name or package name.

=== modified file 'lib/lp/soyuz/interfaces/queue.py'
--- lib/lp/soyuz/interfaces/queue.py	2011-06-23 12:42:46 +0000
+++ lib/lp/soyuz/interfaces/queue.py	2011-08-12 16:32:27 +0000
@@ -619,6 +619,7 @@
                name=None, version=None, exact_match=False):
         """Get package upload records for a series with optional filtering.
 
+        :param distroseries: the `IDistroSeries` to consider.
         :param status: Filter results by this `PackageUploadStatus`, or list
             of statuses.
         :param created_since_date: If specified, only returns items uploaded
@@ -646,6 +647,20 @@
         :return: a matching `IPackageUpload` object.
         """
 
+    def getBuildsForSources(self, distroseries, status=None, pockets=None,
+                            names=None):
+        """Return binary package upload records for a series with option
+        filtering.
+
+        :param distroseries: the `IDistroSeries` to consider.
+        :param status: Filter results by this list of `PackageUploadStatus`s.
+        :param pockets: Filter results by this list of
+            `PackagePublishingPocket`s.
+        :param names: Filter results by this list of package names.
+
+        :return: A result set containing `IPackageUpload`s.
+        """
+
     def getBuildByBuildIDs(build_ids):
         """Return `PackageUploadBuilds`s for the supplied build IDs."""
 

=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
--- lib/lp/soyuz/model/binarypackagebuild.py	2011-05-27 21:12:25 +0000
+++ lib/lp/soyuz/model/binarypackagebuild.py	2011-08-12 16:32:27 +0000
@@ -570,7 +570,7 @@
                 # Analysis of previous build data shows that a build rate
                 # of 6 KB/second is realistic. Furthermore we have to add
                 # another minute for generic build overhead.
-                estimate = int(package_size/6.0/60 + 1)
+                estimate = int(package_size / 6.0 / 60 + 1)
             else:
                 # No historic build times and no package size available,
                 # assume a build time of 5 minutes.
@@ -862,10 +862,11 @@
         :param tables: container to which to add joined tables.
         :param status: optional build state for which to add a query clause if
             present.
-        :param name: optional source package release name for which to add a
+        :param name: optional source package release name (or list of source
+            package release names) for which to add a query clause if
+            present.
+        :param pocket: optional pocket (or list of pockets) for which to add a
             query clause if present.
-        :param pocket: optional pocket for which to add a query clause if
-            present.
         :param arch_tag: optional architecture tag for which to add a
             query clause if present.
         """
@@ -883,7 +884,10 @@
 
         # Add query clause that filters on pocket if the latter is provided.
         if pocket:
-            queries.append('PackageBuild.pocket=%s' % sqlvalues(pocket))
+            if not isinstance(pocket, list):
+                pocket = (pocket,)
+
+            queries.append('PackageBuild.pocket IN %s' % sqlvalues(pocket))
 
         # Add query clause that filters on architecture tag if provided.
         if arch_tag is not None:
@@ -896,12 +900,22 @@
         # Add query clause that filters on source package release name if the
         # latter is provided.
         if name is not None:
-            queries.append('''
-                BinaryPackageBuild.source_package_release =
-                    SourcePackageRelease.id AND
-                SourcePackageRelease.sourcepackagename = SourcePackageName.id
-                AND SourcepackageName.name LIKE '%%' || %s || '%%'
-            ''' % quote_like(name))
+            if not isinstance(name, list):
+                queries.append('''
+                    BinaryPackageBuild.source_package_release =
+                        SourcePackageRelease.id AND
+                    SourcePackageRelease.sourcepackagename =
+                        SourcePackageName.id
+                    AND SourcepackageName.name LIKE '%%' || %s || '%%'
+                ''' % quote_like(name))
+            else:
+                queries.append('''
+                    BinaryPackageBuild.source_package_release =
+                        SourcePackageRelease.id AND
+                    SourcePackageRelease.sourcepackagename =
+                        SourcePackageName.id
+                    AND SourcepackageName.name IN %s
+                ''' % sqlvalues(name))
             tables.extend(['SourcePackageRelease', 'SourcePackageName'])
 
     def getBuildsForBuilder(self, builder_id, status=None, name=None,

=== modified file 'lib/lp/soyuz/model/distroarchseries.py'
--- lib/lp/soyuz/model/distroarchseries.py	2011-01-24 21:16:30 +0000
+++ lib/lp/soyuz/model/distroarchseries.py	2011-08-12 16:32:27 +0000
@@ -53,9 +53,7 @@
     IDistroArchSeriesSet,
     IPocketChroot,
     )
-from lp.soyuz.interfaces.publishing import (
-    ICanPublishPackages,
-    )
+from lp.soyuz.interfaces.publishing import ICanPublishPackages
 from lp.soyuz.model.binarypackagename import BinaryPackageName
 from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
 from lp.soyuz.model.distroarchseriesbinarypackage import (
@@ -203,7 +201,7 @@
             find_spec = (
                 BinaryPackageRelease,
                 BinaryPackageName,
-                BinaryPackageName, # dummy value
+                BinaryPackageName,  # dummy value
                 )
         archives = self.distroseries.distribution.getArchiveIDList()
 
@@ -308,7 +306,7 @@
 
         published = BinaryPackagePublishingHistory.select(
             " AND ".join(queries),
-            clauseTables = ['BinaryPackageRelease'],
+            clauseTables=['BinaryPackageRelease'],
             orderBy=['-id'])
 
         return shortlist(published)
@@ -383,8 +381,8 @@
         used simply to keep trusted code DRY.
 
         :param architectures: an iterable of architectures to process.
-        :param arch_tag: an optional architecture tag with which to filter
-            the results.
+        :param arch_tag: an optional architecture tag or a tag list with
+            which to filter the results.
         :return: a list of the ids of the architectures matching arch_tag.
         """
         # If arch_tag was not provided, just return the ids without
@@ -392,8 +390,10 @@
         if arch_tag is None:
             return [arch.id for arch in architectures]
         else:
+            if not isinstance(arch_tag, list):
+                arch_tag = (arch_tag, )
             return [arch.id for arch in architectures
-                        if arch_tag == arch.architecturetag]
+                        if arch.architecturetag in arch_tag]
 
 
 class PocketChroot(SQLBase):
@@ -409,4 +409,3 @@
                      notNull=True)
 
     chroot = ForeignKey(dbName='chroot', foreignKey='LibraryFileAlias')
-

=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py	2011-07-25 13:43:24 +0000
+++ lib/lp/soyuz/model/queue.py	2011-08-12 16:32:27 +0000
@@ -1442,6 +1442,38 @@
 
         return conflicts.one()
 
+    def getBuildsForSources(self, distroseries, status=None, pockets=None,
+                            names=None):
+        """See `IPackageUploadSet`."""
+        # Avoiding circular imports.
+        from lp.registry.model.distroseries import DistroSeries
+        from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
+        from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
+
+        archives = distroseries.distribution.getArchiveIDList()
+        clauses = [
+            DistroSeries.id == PackageUpload.distroseriesID,
+            PackageUpload.archiveID.is_in(archives),
+            PackageUploadBuild.packageuploadID == PackageUpload.id,
+            ]
+
+        if status is not None:
+            clauses.append(PackageUpload.status.is_in(status))
+        if pockets is not None:
+            clauses.append(PackageUpload.pocket.is_in(pockets))
+        if names is not None:
+            clauses.extend([
+                BinaryPackageBuild.id == PackageUploadBuild.buildID,
+                BinaryPackageBuild.source_package_release ==
+                    SourcePackageRelease.id,
+                SourcePackageRelease.sourcepackagename ==
+                    SourcePackageName.id,
+                SourcePackageName.name.is_in(names),
+                ])
+
+        store = IMasterStore(PackageUpload)
+        return store.find(PackageUpload, *clauses)
+
     def count(self, status=None, distroseries=None, pocket=None):
         """See `IPackageUploadSet`."""
         clauses = []

=== modified file 'lib/lp/soyuz/scripts/initialize_distroseries.py'
--- lib/lp/soyuz/scripts/initialize_distroseries.py	2011-08-05 03:58:16 +0000
+++ lib/lp/soyuz/scripts/initialize_distroseries.py	2011-08-12 16:32:27 +0000
@@ -41,6 +41,7 @@
     IPackagesetSet,
     NoSuchPackageSet,
     )
+from lp.soyuz.interfaces.queue import IPackageUploadSet
 from lp.soyuz.model.packageset import Packageset
 from lp.soyuz.scripts.packagecopier import do_copy
 
@@ -48,6 +49,13 @@
 class InitializationError(Exception):
     """Raised when there is an exception during the initialization process."""
 
+# Pockets to consider when initializing the derived series from its parent(s).
+INIT_POCKETS = [
+    PackagePublishingPocket.RELEASE,
+    PackagePublishingPocket.SECURITY,
+    PackagePublishingPocket.UPDATES,
+    ]
+
 
 class InitializeDistroSeries:
     """Copy in all of the parents distroseries's configuration. This
@@ -128,6 +136,7 @@
             if self.parent_ids == []:
                 self.parents = (
                     self.distroseries.previous_series.getParentSeries())
+        self._create_source_names_by_parent()
 
     def check(self):
         if self.distroseries.isDerivedSeries():
@@ -148,6 +157,7 @@
                 self._checkBuilds(parent)
             self._checkQueue(parent)
         self._checkSeries()
+        return True
 
     def _checkParents(self):
         """If self.first_derivation, the parents list cannot be empty."""
@@ -164,13 +174,21 @@
     def _checkBuilds(self, parent):
         """Assert there are no pending builds for the given parent series.
 
-        Only cares about the RELEASE pocket, which is the only one inherited
-        via initializeFromParent method.
+        Only cares about the RELEASE, SECURITY and UPDATES pockets, which are
+        the only ones inherited via initializeFromParent method.
+        Restrict the check to the select architectures (if applicable).
+        Restrict the check to the selected packages if a limited set of
+        packagesets is used by the initialization.
         """
-        # only the RELEASE pocket is inherited, so we only check
-        # pending build records for it.
+        spns = self.source_names_by_parent.get(parent, None)
+        if spns is not None and len(spns) == 0:
+            # If no sources are selected in this parent, skip the check.
+            return
+
+        arch_tags = self.arches if self.arches is not () else None
         pending_builds = parent.getBuildRecords(
-            BuildStatus.NEEDSBUILD, pocket=PackagePublishingPocket.RELEASE)
+            BuildStatus.NEEDSBUILD, pocket=INIT_POCKETS,
+            arch_tag=arch_tags, name=spns)
 
         if pending_builds.any():
             raise InitializationError("Parent series has pending builds.")
@@ -178,18 +196,23 @@
     def _checkQueue(self, parent):
         """Assert upload queue is empty on the given parent series.
 
-        Only cares about the RELEASE pocket, which is the only one inherited
-        via initializeFromParent method.
-        """
-        # only the RELEASE pocket is inherited, so we only check
-        # queue items for it.
+        Only cares about the RELEASE, SECURITY and UPDATES pockets, which are
+        the only ones inherited via initializeFromParent method.
+        Restrict the check to the selected packages if a limited set of
+        packagesets is used by the initialization.
+         """
         statuses = [
             PackageUploadStatus.NEW,
             PackageUploadStatus.ACCEPTED,
             PackageUploadStatus.UNAPPROVED,
             ]
-        items = parent.getPackageUploads(
-            status=statuses, pocket=PackagePublishingPocket.RELEASE)
+        spns = self.source_names_by_parent.get(parent, None)
+        if spns is not None and len(spns) == 0:
+            # If no sources are selected in this parent, skip the check.
+            return
+
+        items = getUtility(IPackageUploadSet).getBuildsForSources(
+            parent, statuses, INIT_POCKETS, spns)
         if not items.is_empty():
             raise InitializationError(
                 "Parent series queues are not empty.")
@@ -373,6 +396,24 @@
                         distroseries=self.distroseries).is_empty()))
         return case_1a or case_1b
 
+    def _create_source_names_by_parent(self):
+        """If only a subset of the packagesets was selected to be copied,
+        create a dict with the list of source names to be copied for each
+        parent.
+        """
+        source_names_by_parent = {}
+        if self.packagesets:
+            for parent in self.derivation_parents:
+                spns = []
+                # The overhead from looking up each packageset is
+                # mitigated by this usually running from a job.
+                for pkgsetid in self.packagesets:
+                    pkgset = self._store.get(Packageset, int(pkgsetid))
+                    if pkgset.distroseries == parent:
+                        spns += list(pkgset.getSourcesIncluded())
+                source_names_by_parent[parent] = spns
+        self.source_names_by_parent = source_names_by_parent
+
     def _copy_publishing_records(self, distroarchseries_lists):
         """Copy the publishing records from the parent arch series
         to the given arch series in ourselves.
@@ -385,21 +426,13 @@
         archive_set = getUtility(IArchiveSet)
 
         for parent in self.derivation_parents:
-            spns = []
-            # The overhead from looking up each packageset is mitigated by
-            # this usually running from a job.
-            if self.packagesets:
-                for pkgsetid in self.packagesets:
-                    pkgset = self._store.get(Packageset, int(pkgsetid))
-                    if pkgset.distroseries == parent:
-                        spns += list(pkgset.getSourcesIncluded())
-
+            spns = self.source_names_by_parent.get(parent, None)
+            if spns is not None and len(spns) == 0:
                 # Some packagesets where selected but not a single
                 # source from this parent: we skip the copy since
                 # calling copy with spns=[] would copy all the packagesets
                 # from this parent.
-                if len(spns) == 0:
-                    continue
+                continue
 
             distroarchseries_list = distroarchseries_lists[parent]
             for archive in parent.distribution.all_distro_archives:
@@ -432,12 +465,8 @@
                     # There is only one available pocket in an unreleased
                     # series.
                     target_pocket = PackagePublishingPocket.RELEASE
-                    pockets_to_copy = (
-                        PackagePublishingPocket.RELEASE,
-                        PackagePublishingPocket.UPDATES,
-                        PackagePublishingPocket.SECURITY)
                     sources = archive.getPublishedSources(
-                        distroseries=parent, pocket=pockets_to_copy,
+                        distroseries=parent, pocket=INIT_POCKETS,
                         name=spns)
                     # XXX: rvb 2011-06-23 bug=801112: do_copy is atomic (all
                     # or none of the sources will be copied). This might

=== modified file 'lib/lp/soyuz/scripts/tests/test_initialize_distroseries.py'
--- lib/lp/soyuz/scripts/tests/test_initialize_distroseries.py	2011-08-09 08:28:56 +0000
+++ lib/lp/soyuz/scripts/tests/test_initialize_distroseries.py	2011-08-12 16:32:27 +0000
@@ -26,6 +26,7 @@
 from lp.services.features.testing import FeatureFixture
 from lp.soyuz.enums import (
     ArchivePurpose,
+    PackageUploadStatus,
     SourcePackageFormat,
     )
 from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
@@ -58,24 +59,34 @@
     # - setup/populate parents with packages;
     # - initialize a child from parents.
 
-    def setupParent(self, packages=None, format_selection=None,
-                    distribution=None,
-                    pocket=PackagePublishingPocket.RELEASE,
-                    ):
-        parent = self.factory.makeDistroSeries(distribution)
-        pf = getUtility(IProcessorFamilySet).getByName('x86')
+    def setupDas(self, parent, proc, arch_tag):
+        pf = getUtility(IProcessorFamilySet).getByName(proc)
         parent_das = self.factory.makeDistroArchSeries(
             distroseries=parent, processorfamily=pf,
-            architecturetag='i386')
+            architecturetag=arch_tag)
         lf = self.factory.makeLibraryFileAlias()
         transaction.commit()
         parent_das.addOrUpdateChroot(lf)
         parent_das.supports_virtualized = True
+        return parent_das
+
+    def setupParent(self, parent=None, packages=None, format_selection=None,
+                    distribution=None,
+                    pocket=PackagePublishingPocket.RELEASE,
+                    proc='x86', arch_tag='i386'
+                    ):
+        if parent is None:
+            parent = self.factory.makeDistroSeries(distribution)
+        parent_das = self.setupDas(parent, proc, arch_tag)
         parent.nominatedarchindep = parent_das
+        # Set a proper source package format if required.
         if format_selection is None:
             format_selection = SourcePackageFormat.FORMAT_1_0
-        getUtility(ISourcePackageFormatSelectionSet).add(
+        spfss_utility = getUtility(ISourcePackageFormatSelectionSet)
+        existing_format_selection = spfss_utility.getBySeriesAndFormat(
             parent, format_selection)
+        if existing_format_selection is None:
+            spfss_utility.add(parent, format_selection)
         parent.backports_not_automatic = True
         self._populate_parent(parent, parent_das, packages, pocket)
         return parent, parent_das
@@ -163,17 +174,126 @@
         # If the parent series has pending builds, and the child is a series
         # of the same distribution (which means they share an archive), we
         # can't initialize.
-        self.parent, self.parent_das = self.setupParent()
-        source = self.factory.makeSourcePackagePublishingHistory(
-            distroseries=self.parent,
-            pocket=PackagePublishingPocket.RELEASE)
-        source.createMissingBuilds()
-        child = self.factory.makeDistroSeries(
-            distribution=self.parent.parent, previous_series=self.parent)
-        ids = InitializeDistroSeries(child, [self.parent.id])
-        self.assertRaisesWithContent(
-            InitializationError, "Parent series has pending builds.",
-            ids.check)
+        pockets = [
+            PackagePublishingPocket.RELEASE,
+            PackagePublishingPocket.SECURITY,
+            PackagePublishingPocket.UPDATES,
+            ]
+        for pocket in pockets:
+            self.parent, self.parent_das = self.setupParent()
+            source = self.factory.makeSourcePackagePublishingHistory(
+                distroseries=self.parent,
+                pocket=pocket)
+            source.createMissingBuilds()
+            child = self.factory.makeDistroSeries(
+                distribution=self.parent.parent, previous_series=self.parent)
+            ids = InitializeDistroSeries(child, [self.parent.id])
+            self.assertRaisesWithContent(
+                InitializationError, "Parent series has pending builds.",
+                ids.check)
+
+    def create2archParentAndSource(self, packages):
+        # Helper to create a parent series with 2 distroarchseries and
+        # a source.
+        parent, parent_das = self.setupParent(packages=packages)
+        unused, parent_das2 = self.setupParent(
+            parent=parent, proc='amd64', arch_tag='amd64',
+            packages=packages)
+        source = self.factory.makeSourcePackagePublishingHistory(
+            distroseries=parent,
+            pocket=PackagePublishingPocket.RELEASE)
+        return parent, parent_das, parent_das2, source
+
+    def test_failure_with_pending_builds_specific_arches(self):
+        # We only check for pending builds of the same architectures we're
+        # copying over from the parents. If a build is present in the
+        # architecture we're initializing with, IDS raises an error.
+        res = self.create2archParentAndSource(packages={'p1': '1.1'})
+        parent, parent_das, parent_das2, source = res
+        # Create builds for the architecture of parent_das2.
+        source.createMissingBuilds(architectures_available=[parent_das2])
+        # Initialize only with parent_das2's architecture.
+        child = self.factory.makeDistroSeries(
+            distribution=parent.distribution, previous_series=parent)
+        ids = InitializeDistroSeries(
+            child, arches=[parent_das2.architecturetag])
+
+        self.assertRaisesWithContent(
+            InitializationError, "Parent series has pending builds.",
+            ids.check)
+
+    def test_check_success_with_pending_builds_in_other_arches(self):
+        # We only check for pending builds of the same architectures we're
+        # copying over from the parents. If *no* build is present in the
+        # architecture we're initializing with, IDS will succeed.
+        res = self.create2archParentAndSource(packages={'p1': '1.1'})
+        parent, parent_das, parent_das2, source = res
+        # Create builds for the architecture of parent_das2.
+        source.createMissingBuilds(architectures_available=[parent_das2])
+        # Initialize only with parent_das's architecture.
+        child = self.factory.makeDistroSeries(
+            distribution=parent.distribution, previous_series=parent)
+        ids = InitializeDistroSeries(
+            child, arches=[parent_das.architecturetag])
+
+        # No error is raised because we're initializing only the architecture
+        # which has no pending builds in it.
+        self.assertTrue(ids.check())
+
+    def createPackageInPackageset(self, distroseries, package_name,
+                                  packageset_name, create_build=False):
+        # Helper method to create a package in a packageset in the given
+        # distroseries, optionaly creating the missing build for this source
+        # package.
+        spn = self.factory.getOrMakeSourcePackageName(package_name)
+        sourcepackagerelease = self.factory.makeSourcePackageRelease(
+            sourcepackagename=spn)
+        source = self.factory.makeSourcePackagePublishingHistory(
+            sourcepackagerelease=sourcepackagerelease,
+            distroseries=distroseries,
+            sourcepackagename=spn,
+            pocket=PackagePublishingPocket.RELEASE)
+        packageset = getUtility(IPackagesetSet).new(
+            packageset_name, packageset_name, distroseries.owner,
+            distroseries=distroseries)
+        packageset.addSources(package_name)
+        if create_build:
+            source.createMissingBuilds()
+        return source, packageset, sourcepackagerelease
+
+    def test_failure_if_build_present_in_selected_packagesets(self):
+        # Pending builds in a parent for source packages included in the
+        # packagesets selected for the copy will make the queue check fail.
+        parent, parent_das = self.setupParent()
+        p1, packageset1, unsed = self.createPackageInPackageset(
+            parent, u'p1', u'packageset1', True)
+        p2, packageset2, unsed = self.createPackageInPackageset(
+            parent, u'p2', u'packageset2', False)
+
+        child = self.factory.makeDistroSeries(
+            distribution=parent.distribution, previous_series=parent)
+        ids = InitializeDistroSeries(
+            child, packagesets=(str(packageset1.id),))
+
+        self.assertRaisesWithContent(
+            InitializationError, "Parent series has pending builds.",
+            ids.check)
+
+    def test_check_success_if_build_present_in_non_selected_packagesets(self):
+        # Pending builds in a parent for source packages not included in the
+        # packagesets selected for the copy won't make the queue check fail.
+        parent, parent_das = self.setupParent()
+        p1, packageset1, unused = self.createPackageInPackageset(
+            parent, u'p1', u'packageset1', True)
+        p2, packageset2, unused = self.createPackageInPackageset(
+            parent, u'p2', u'packageset2', False)
+
+        child = self.factory.makeDistroSeries(
+            distribution=parent.distribution, previous_series=parent)
+        ids = InitializeDistroSeries(
+            child, packagesets=(str(packageset2.id),))
+
+        self.assertTrue(ids.check())
 
     def test_success_with_updates_packages(self):
         # Initialization copies all the package from the UPDATES pocket.
@@ -203,15 +323,149 @@
         self.assertDistroSeriesInitializedCorrectly(
             child, self.parent, self.parent_das)
 
-    def test_failure_with_queue_items(self):
-        # If the parent series has items in its queues, such as NEW and
-        # UNAPPROVED, we can't initialize.
-        self.parent, self.parent_das = self.setupParent()
-        self.parent.createQueueEntry(
-            PackagePublishingPocket.RELEASE, self.parent.main_archive,
-            'foo.changes', 'bar')
-        child = self.factory.makeDistroSeries()
-        ids = InitializeDistroSeries(child, [self.parent.id])
+    def test_check_success_with_binary_queue_items_pockets(self):
+        # If the parent series has binary items in pockets PROPOSED or
+        # BACKPORTS, in its queues, we still can initialize because these
+        # pockets are not considered by the initialization process.
+        pockets = [
+            PackagePublishingPocket.PROPOSED,
+            PackagePublishingPocket.BACKPORTS,
+            ]
+        for pocket in pockets:
+            parent, parent_das = self.setupParent()
+            upload = parent.createQueueEntry(
+                pocket, parent.main_archive, 'foo.changes', 'bar')
+            # Create a binary package upload for this upload.
+            upload.addBuild(self.factory.makeBinaryPackageBuild())
+            child = self.factory.makeDistroSeries()
+            ids = InitializeDistroSeries(child, [parent.id])
+
+            self.assertTrue(ids.check())
+
+    def test_failure_with_binary_queue_items_pockets(self):
+        # If the parent series has binary items in pockets RELEASE,
+        # SECURITY or UPDATES in its queues, we can't initialize.
+        pockets = [
+            PackagePublishingPocket.RELEASE,
+            PackagePublishingPocket.SECURITY,
+            PackagePublishingPocket.UPDATES,
+            ]
+        for pocket in pockets:
+            parent, parent_das = self.setupParent()
+            upload = parent.createQueueEntry(
+                pocket, parent.main_archive, 'foo.changes', 'bar')
+            # Create a binary package upload for this upload.
+            upload.addBuild(self.factory.makeBinaryPackageBuild())
+            child = self.factory.makeDistroSeries()
+            ids = InitializeDistroSeries(child, [parent.id])
+
+            self.assertRaisesWithContent(
+                InitializationError, "Parent series queues are not empty.",
+                ids.check)
+
+    def test_failure_with_binary_queue_items_status(self):
+        # If the parent series has binary items with status NEW,
+        # ACCEPTED or UNAPPROVED we can't initialize.
+        statuses = [
+            PackageUploadStatus.NEW,
+            PackageUploadStatus.ACCEPTED,
+            PackageUploadStatus.UNAPPROVED,
+            ]
+        for status in statuses:
+            parent, parent_das = self.setupParent()
+            upload = self.factory.makePackageUpload(
+                distroseries=parent, status=status,
+                archive=parent.main_archive,
+                pocket=PackagePublishingPocket.RELEASE)
+            # Create a binary package upload for this upload.
+            upload.addBuild(self.factory.makeBinaryPackageBuild())
+            child = self.factory.makeDistroSeries()
+            ids = InitializeDistroSeries(child, [parent.id])
+
+            self.assertRaisesWithContent(
+                InitializationError, "Parent series queues are not empty.",
+                ids.check)
+
+    def test_check_success_with_binary_queue_items_status(self):
+        # If the parent series has binary items with status DONE or
+        # REJECTED we still can initialize.
+        statuses = [
+            PackageUploadStatus.DONE,
+            PackageUploadStatus.REJECTED,
+            ]
+        for status in statuses:
+            parent, parent_das = self.setupParent()
+            upload = self.factory.makePackageUpload(
+                distroseries=parent, status=status,
+                archive=parent.main_archive,
+                pocket=PackagePublishingPocket.RELEASE)
+            # Create a binary package upload for this upload.
+            upload.addBuild(self.factory.makeBinaryPackageBuild())
+            child = self.factory.makeDistroSeries()
+            ids = InitializeDistroSeries(child, [parent.id])
+
+            self.assertTrue(ids.check())
+
+    def test_check_success_with_source_queue_items(self):
+        # If the parent series has *source* items in its queues, we
+        # still can initialize.
+        parent, parent_das = self.setupParent()
+        upload = parent.createQueueEntry(
+            PackagePublishingPocket.RELEASE,
+            parent.main_archive, 'foo.changes', 'bar')
+        # Create a source package upload for this upload.
+        upload.addSource(self.factory.makeSourcePackageRelease())
+        child = self.factory.makeDistroSeries()
+        ids = InitializeDistroSeries(child, [parent.id])
+
+        self.assertTrue(ids.check())
+
+    def test_check_success_with_binary_queue_items_outside_packagesets(self):
+        # If the parent series has binary items in its queues not in the
+        # packagesets selected for the initialization, we still can
+        # initialize.
+        parent, parent_das = self.setupParent()
+        p1, packageset1, spr1 = self.createPackageInPackageset(
+            parent, u'p1', u'packageset1', False)
+        p2, packageset2, spr2 = self.createPackageInPackageset(
+            parent, u'p2', u'packageset2', False)
+
+        # Create a binary package upload for the package 'p2' inside
+        # packageset 'packageset2'.
+        upload = parent.createQueueEntry(
+            PackagePublishingPocket.RELEASE,
+            parent.main_archive, 'foo.changes', 'bar')
+        upload.addBuild(self.factory.makeBinaryPackageBuild(
+            distroarchseries=parent_das,
+            source_package_release=spr2))
+        child = self.factory.makeDistroSeries()
+        # Initialize with packageset1 only.
+        ids = InitializeDistroSeries(child, [parent.id],
+            child, packagesets=(str(packageset1.id),))
+
+        self.assertTrue(ids.check())
+
+    def test_failure_with_binary_queue_items_in_packagesets(self):
+        # If the parent series has binary items in its queues in the
+        # packagesets selected for the initialization, we can't
+        # initialize.
+        parent, parent_das = self.setupParent()
+        p1, packageset1, spr1 = self.createPackageInPackageset(
+            parent, u'p1', u'packageset1', False)
+
+        # Create a binary package upload for the package 'p2' inside
+        # packageset 'packageset2'.
+        upload = parent.createQueueEntry(
+            PackagePublishingPocket.RELEASE,
+            parent.main_archive, 'foo.changes', 'bar')
+        upload.addBuild(self.factory.makeBinaryPackageBuild(
+            distroarchseries=parent_das,
+            source_package_release=spr1))
+        child = self.factory.makeDistroSeries()
+        # Initialize with packageset1 only.
+        ids = InitializeDistroSeries(child, [parent.id],
+            child, packagesets=(str(packageset1.id),))
+
         self.assertRaisesWithContent(
             InitializationError, "Parent series queues are not empty.",
             ids.check)

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2011-08-10 07:02:59 +0000
+++ lib/lp/testing/factory.py	2011-08-12 16:32:27 +0000
@@ -3415,13 +3415,18 @@
             pocket, archive, changes_filename, changes_file_content,
             signing_key=signing_key, package_copy_job=package_copy_job)
         if status is not None:
-            naked_package_upload = removeSecurityProxy(package_upload)
-            status_changers = {
-                PackageUploadStatus.DONE: naked_package_upload.setDone,
-                PackageUploadStatus.ACCEPTED:
-                    naked_package_upload.setAccepted,
-                }
-            status_changers[status]()
+            if status is not PackageUploadStatus.NEW:
+                naked_package_upload = removeSecurityProxy(package_upload)
+                status_changers = {
+                    PackageUploadStatus.UNAPPROVED:
+                        naked_package_upload.setUnapproved,
+                    PackageUploadStatus.REJECTED:
+                        naked_package_upload.setRejected,
+                    PackageUploadStatus.DONE: naked_package_upload.setDone,
+                    PackageUploadStatus.ACCEPTED:
+                        naked_package_upload.setAccepted,
+                    }
+                status_changers[status]()
         return package_upload
 
     def makeSourcePackageUpload(self, distroseries=None,