← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~james-w/launchpad/move-soyuz-test-publisher into lp:launchpad/devel

 

James Westby has proposed merging lp:~james-w/launchpad/move-soyuz-test-publisher into lp:launchpad/devel with lp:~james-w/launchpad/more-matchers as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)


Hi,

This branch moves SoyuzTestPublisher and the associated
TestNativePublishingBase to lp.soyuz.testing.publisher,
and also changes the API somewhat.

The main thrust of this change is to get tests away from
sampledata, but there are far to many using these classes
to do it in one go. Therefore I created a new class that
can be transitioned to gradually.

In addition to this I changed the API somewhat to be what
I consider to be cleaner. This way mainly a taste thing,
but some of the changes will also stop tests being too
tightly bound to the implementation of these classes, giving
us more freedom to change things in future. Some of the changes
were a little more than that though, such as removing lots
of commits from the tests using TestNativePublishingBase.
If there are lots of tests that need that many commits then
we can add it back as an opt-in thing, we should punish
every test with it.

In addition I also documented the new interface with docstrings,
and a module docstring to help with porting.

This branch clocks in a bit heavy, but I hope you will forgive
me as quite a lot of that is added docstrings, and there is
quite a lot of moved code.

Thanks,

James

-- 
https://code.launchpad.net/~james-w/launchpad/move-soyuz-test-publisher/+merge/32157
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~james-w/launchpad/move-soyuz-test-publisher into lp:launchpad/devel.
=== modified file 'lib/lp/archivepublisher/config.py'
--- lib/lp/archivepublisher/config.py	2010-07-07 06:28:03 +0000
+++ lib/lp/archivepublisher/config.py	2010-08-09 22:38:53 +0000
@@ -7,6 +7,7 @@
 # distribution and distroseries tables
 
 import os
+import shutil
 from StringIO import StringIO
 from ConfigParser import ConfigParser
 
@@ -192,3 +193,21 @@
                 continue
             if not os.path.exists(directory):
                 os.makedirs(directory, 0755)
+
+    def removeArchiveDirs(self):
+        required_directories = [
+            self.distroroot,
+            self.poolroot,
+            self.distsroot,
+            self.archiveroot,
+            self.cacheroot,
+            self.overrideroot,
+            self.miscroot,
+            self.temproot,
+            ]
+
+        for directory in required_directories:
+            if directory is None:
+                continue
+            if os.path.exists(directory):
+                shutil.rmtree(directory)

=== modified file 'lib/lp/archivepublisher/tests/test_config.py'
--- lib/lp/archivepublisher/tests/test_config.py	2010-07-18 00:24:06 +0000
+++ lib/lp/archivepublisher/tests/test_config.py	2010-08-09 22:38:53 +0000
@@ -5,52 +5,79 @@
 
 __metaclass__ = type
 
-import unittest
-
-from zope.component import getUtility
+import os
 
 from canonical.config import config
-from canonical.launchpad.interfaces import IDistributionSet
 from canonical.testing import LaunchpadZopelessLayer
-
-
-class TestConfig(unittest.TestCase):
+from lp.archivepublisher.config import Config
+from lp.soyuz.testing.publisher import (
+    makeDistributionLucilleconfig, makeDistroSeriesLucilleconfig)
+from lp.testing import TestCaseWithFactory
+
+
+class TestConfig(TestCaseWithFactory):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
+        super(TestConfig, self).setUp()
         self.layer.switchDbUser(config.archivepublisher.dbuser)
-        self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
+        self.distribution = self.factory.makeDistribution()
+        makeDistributionLucilleconfig(self.distribution)
+        series1 = self.factory.makeDistroSeries(
+            distribution=self.distribution)
+        makeDistroSeriesLucilleconfig(series1)
+        series2 = self.factory.makeDistroSeries(
+            distribution=self.distribution)
+        makeDistroSeriesLucilleconfig(series2)
+        for series in (series1, series2):
+            for i in range(2):
+                self.factory.makeDistroArchSeries(distroseries=series)
+
+    def getConfig(self):
+        return Config(self.distribution)
 
     def testInstantiate(self):
         """Config should instantiate"""
-        from lp.archivepublisher.config import Config
-        d = Config(self.ubuntutest)
+        d = self.getConfig()
 
     def testDistroName(self):
         """Config should be able to return the distroName"""
-        from lp.archivepublisher.config import Config
-        d = Config(self.ubuntutest)
-        self.assertEqual(d.distroName, "ubuntutest")
+        d = self.getConfig()
+        self.assertEqual(self.distribution.name, d.distroName)
 
     def testDistroSeriesNames(self):
         """Config should return two distroseries names"""
-        from lp.archivepublisher.config import Config
-        d = Config(self.ubuntutest)
+        d = self.getConfig()
         dsns = d.distroSeriesNames()
-        self.assertEquals(len(dsns), 2)
-        self.assertEquals(dsns[0], "breezy-autotest")
-        self.assertEquals(dsns[1], "hoary-test")
+        self.assertEqual(
+            sorted([ds.name for ds in self.distribution.series]),
+            sorted(dsns))
 
     def testArchTagsForSeries(self):
         """Config should have the arch tags for the drs"""
-        from lp.archivepublisher.config import Config
-        d = Config(self.ubuntutest)
-        archs = d.archTagsForSeries("hoary-test")
+        d = self.getConfig()
+        archs = d.archTagsForSeries(self.distribution.series[0].name)
         self.assertEquals(len(archs), 2)
 
     def testDistroConfig(self):
         """Config should have parsed a distro config"""
-        from lp.archivepublisher.config import Config
-        d = Config(self.ubuntutest)
+        d = self.getConfig()
         # NOTE: Add checks here when you add stuff in util.py
         self.assertEquals(d.stayofexecution, 5)
+
+    def test_removeArchiveDirs(self):
+        d = self.getConfig()
+        d.setupArchiveDirs()
+        d.removeArchiveDirs()
+        for directory in [
+            d.distroroot,
+            d.poolroot,
+            d.distsroot,
+            d.archiveroot,
+            d.cacheroot,
+            d.overrideroot,
+            d.miscroot,
+            d.temproot,
+            ]:
+            self.assertFalse(
+                os.path.exists(directory), "%s was not deleted" % directory)

=== modified file 'lib/lp/registry/browser/tests/test_person_view.py'
--- lib/lp/registry/browser/tests/test_person_view.py	2010-08-02 02:13:52 +0000
+++ lib/lp/registry/browser/tests/test_person_view.py	2010-08-09 22:38:53 +0000
@@ -7,6 +7,7 @@
 
 import transaction
 from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
 
 from canonical.config import config
 from canonical.launchpad.ftests import ANONYMOUS, login
@@ -528,7 +529,8 @@
             pub_source=src_pub)
         self.build = binaries[0].binarypackagerelease.build
         self.build.status = BuildStatus.FAILEDTOBUILD
-        self.build.archive = publisher.distroseries.main_archive
+        removeSecurityProxy(
+            self.build).archive = publisher.distroseries.main_archive
         login(ANONYMOUS)
 
     def test_related_software_with_failed_build(self):

=== added file 'lib/lp/soyuz/testing/__init__.py'
=== renamed file 'lib/lp/soyuz/testing/__init__.py' => 'lib/lp/soyuz/testing/publisher.py'
--- lib/lp/soyuz/testing/__init__.py	2010-08-09 22:38:48 +0000
+++ lib/lp/soyuz/testing/publisher.py	2010-08-09 22:38:53 +0000
@@ -0,0 +1,1059 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Testing helpers for publishing.
+
+Porting from TestNativePublishingBase to PublishingTestCase:
+
+  * The publisher methods are no longer available on `self`. Use the
+    instance variable `publisher` now.
+  * `self.pool_dir` and `self.temp_dir` are now not available by
+    default, either use `self.config.poolroot` and `self.config.temproot`,
+    or create a subclass with a setUp that sets them if you access them
+    a lot.
+  * The archive directories on disk will be removed at `tearDown` time.
+  * `checkPublication`, `checkPublications`, `checkPastDate` and
+    `checkSuperseded` are now not available by default, replaced by
+    matchers.
+    - `self.checkPublication(pub, status)` is now
+      `self.assertThat(pub, PublishedStateIs(status))`
+    - `self.checkPublications(pubs, status)` is now
+      `self.assertThat(pubs, PublishedStateIs(status))`
+    - `self.checkPastDate(date, lag=lag)` is now
+      `self.assertThat(date, DateIsInPast(lag=lag))`
+    - `self.checkSuperseded(pubs, supersededby=supersededby)` is now
+      `self.assertThat(pubs, IsSupersededBy(supersededby))`
+    Where `PublishedStateIs` and `IsSupersededBy` can be imported from
+    `lp.soyuz.testing.matchers`, and `DateIsInPast` can be imported
+    from `lp.testing.matchers`.
+  * As `self.getPubSource` and `self.getPubBinaries` are no more they
+    no longer commit after they are called.
+  * See below for changed usage of `SoyuzTestPublisher` as well.
+
+Porting from `lp.soyuz.tests.test_publishing.SoyuzTestPublisher` to
+`lp.soyuz.testing.publisher.SoyuzTestPublisher`:
+
+  * `prepareBreezyAutotest` is removed, in favour of using the
+    factory. Replace calls by `prepare`, or `prepareForBinaryPublications`
+    depending on whether you plan to publish binaries.
+  * The `ubuntutest` attribute is replaced by `distribution` and now
+    could be any distribution.
+  * The `breezy_autotest` attribute has been replaced by `distroseries`,
+    and now could be any distroseries of `distribution`.
+  * The `person` attribute has been removed, and uploads are now
+    attributed to arbitrary people.
+  * The `breezy_autotest_i386` and `breezy_autotest_hppa` attributes
+    have been removed.
+  * The `SourcePackageName` for the default package name isn't created
+    until it is needed.
+  * Some methods may have different defaults for some less important
+    parameters. These should be specified explicitly if needed.
+  * The following methods have been replaced,
+    - `getPubSource` is replaced by `publishSource`
+    - `getPubBinaries` is replaced by `publishBinaries`
+    - `addPackageUpload` is replaced by `makePackageUpload`
+    - `addMockFile` is replaced by `addFile`
+"""
+
+__metaclass__ = type
+
+__all__ = [
+    'makeDistributionLucilleconfig',
+    'makeDistroSeriesLucilleconfig',
+    'PublishingTestCase',
+    'SoyuzTestPublisher',
+]
+
+import datetime
+import operator
+import os
+
+import pytz
+from zope.security.proxy import removeSecurityProxy
+
+from canonical.config import config
+from canonical.database.constants import UTC_NOW
+from canonical.launchpad.scripts import QuietFakeLogger
+from canonical.testing import LaunchpadZopelessLayer
+from lp.archivepublisher.config import Config
+from lp.archivepublisher.diskpool import DiskPool
+from lp.buildmaster.interfaces.buildbase import BuildStatus
+from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
+from lp.soyuz.interfaces.publishing import (
+    PackagePublishingPriority, PackagePublishingStatus)
+from lp.testing import TestCaseWithFactory
+from lp.testing.factory import LaunchpadObjectFactory
+
+
+class SoyuzTestPublisher:
+    """Helper class able to publish source and binary packages.
+
+    Once you have a test publisher you can use it to publish packages,
+    either source only, or source and binary, with all required
+    links between them. This can ease complicated test setup where lots
+    of coherent objects are required. Where your needs are more simple,
+    you may be better served by using the LaunchpadObjectFactory directly,
+    to avoid coupling your tests with aspects that aren't important to it.
+
+    To create a SoyuzTestPublisher, simply instantiate the class. If you
+    already have an instance of the LaunchpadObjectFactory you should
+    pass this to the constructor, but if you don't you can omit it and
+    one will be created for you.
+
+        publisher = SoyuzTestPublisher(factory=factory)
+
+    The test publisher has a distribution and a distroseries within
+    that distribution which are the default targets of the packages
+    that it publishes. It is required to set these up by calling
+
+        publisher.prepare()
+
+    or if you are going to publish binary packages
+
+        publisher.prepareForBinaryPublications()
+
+    At any time you can change the default distroseries, for instance
+    if you wish to publish packages to two distroseries during the course
+    of your tests. You can do this by calling
+
+        publisher.setDefaultDistroSeries(distroseries=distroseries)
+
+    or alternatively omit the distroseries parameter to have a new
+    distroseries created in the default distribution.
+
+    You are then ready to publish packages. For this use the publish
+    methods. To publish a source package call
+
+        source_publishing = publisher.publishSource()
+
+    which will publish a new source package in to the default distroseries,
+    and return the publishing record to you.
+
+    To publish binary packages call
+
+        binary_publishings = publisher.publishBinaries()
+
+    which will create you a source package, and then a binary package for
+    each architecture in the default distroseries, with the associated
+    build records, and return you a list of the binary publishing records
+    that it created.
+
+    Each method has a number of paramters that can be used to control
+    various aspects of the packages that are published, such as the name,
+    version, pocket, status, etc. You should specify all of the parameters
+    that are germane to your test, to decouple your tests from the defaults
+    of the test publisher.
+
+    There are additional methods that may be of use in certain situations,
+    as they allow you to create subsets of the objects created by the
+    above methods, or supplemental objects that are required by some tests.
+    """
+
+    def __init__(self, factory=None):
+        """Create a `SoyuzTestPublisher`
+
+        :param factory: the `LaunchpadObjectFactory` to use for creating
+            objects, or None to create one.
+        """
+        self.factory = factory
+        if self.factory is None:
+            self.factory = LaunchpadObjectFactory()
+        self.default_package_name = 'foo'
+
+    def prepare(self):
+        """Setup the instance with a standard skeleton.
+
+        See `setUpForBinaryPublications` if you wish to publish binaries.
+        """
+        self.distribution = self.factory.makeDistribution()
+        self.setDefaultDistroSeries()
+
+    def prepareForBinaryPublications(self):
+        """Setup this instance for publishing binary packages."""
+        self.prepare()
+        distroarchseries1 = self.factory.makeDistroArchSeries(
+            distroseries=self.distroseries, supports_virtualized=True)
+        distroarchseries2 = self.factory.makeDistroArchSeries(
+            distroseries=self.distroseries, supports_virtualized=True)
+        removeSecurityProxy(self.distroseries).nominatedarchindep = (
+            distroarchseries1)
+        self.addFakeChroots(distroseries=self.distroseries)
+
+    def setDefaultDistroSeries(self, distroseries=None):
+        """Set the default series for publishing packages.
+
+        If you don't specify a distroseries as a target in other method
+        calls then the distroseries set as default by the last call
+        to this method will be used.
+
+        :param distroseries: The `IDistroSeries` to use as default. If
+            it's None, one will be created.
+        :return: The default `IDistroSeries`.
+        """
+        if distroseries is None:
+            distroseries = self.factory.makeDistroRelease(
+                distribution=self.distribution)
+        self.distroseries = distroseries
+        return self.distroseries
+
+    def addFakeChroots(self, distroseries=None):
+        """Add fake chroots for all the architectures in distroseries.
+
+        This creates files on the librarian for each `IDistroArchSeries`
+        in the distroseries. The chroots aren't suitable for actually
+        building packages, but are sufficient for other methods to
+        believe it would be worth trying.
+
+        :param distroseries: the `IDistroSeries` for which each architecture
+            will get a chroot, this can be None to use the default
+            distroseries.
+        """
+        if distroseries is None:
+            distroseries = self.distroseries
+        fake_chroot = self.addFile('fake_chroot.tar.gz')
+        for arch in distroseries.architectures:
+            arch.addOrUpdateChroot(fake_chroot)
+
+    def addFile(self, filename, filecontent=None, restricted=False,
+                expires=None):
+        """Add a file to the Librarian.
+
+        :param filename: the filename to add.
+        :param filecontent: the content of the file, or None to generate
+            some content.
+        :param restricted: whether the file should be on the restricted
+            librarian.
+        :param exipres: a `datetime.datetime` at which the file should
+            expire, or None to not have the file expire.
+        :return: an `ILibraryFileAlias` corresponding to the file uploaded.
+        """
+        return self.factory.makeLibraryFileAlias(
+            filename=filename, content=filecontent, restricted=restricted,
+            content_type='application/text', expires=expires)
+
+    # backwards compatibility for the old interface.
+    addMockFile = addFile
+
+    def makePackageUpload(self, archive=None, distroseries=None,
+                         pocket=None, changes_file_name=None,
+                         changes_file_content=None, signing_key=None,
+                         status=None):
+        """Make a `PackageUpload`.
+
+        :param archive: the archive to upload to, or None to use
+            the default archive.
+        :param distroseries: the distroseries to upload to, or
+            None to use the default.
+        :param pocket: the `PackagePublishingPocket` to upload to,
+            or None for the default pocket, RELEASE.
+        :param changes_file_name: the filename of the changes file
+            to create, or None for an arbitrary name.
+        :param changes_file_content: the content of the changes
+            file, or None to generate some.
+        :param signing_key: the key with which to sign the upload,
+            or None to have an unsigned upload.
+        :param status: the `PackageUploadStatus` of the upload, or
+            None for an arbitrary status.
+        :return: the created `PackageUpload`.
+        """
+        if distroseries is None:
+            distroseries = self.distroseries
+        if archive is None:
+            archive = distroseries.distribution.main_archive
+        if pocket is None:
+            pocket = PackagePublishingPocket.RELEASE
+        package_upload = self.factory.makePackageUpload(
+            archive=archive, distroseries=distroseries, pocket=pocket,
+            changes_filename=changes_file_name,
+            changes_file_content=changes_file_content,
+            signing_key=signing_key, status=status)
+        return package_upload
+
+    def makeSourcePackageRelease(self, distroseries=None, sourcename=None,
+                                 archive=None, version=None, urgency=None,
+                                 component_name=None, section_name=None,
+                                 maintainer=None, creator=None,
+                                 builddepends=None, builddependsindep=None,
+                                 build_conflicts=None,
+                                 build_conflicts_indep=None,
+                                 architecturehintlist=None,
+                                 dsc_signing_key=None,
+                                 dsc_maintainer_rfc822=None,
+                                 dsc_standards_version=None,
+                                 dsc_format=None, dsc_binaries=None,
+                                 date_uploaded=None, changelog_entry=None):
+        """Make a `SourcePackageRelease`.
+
+        :param distroseries: the distroseries that the package should
+            be targered to, or None to use the default.
+        :param archive: the archive that should be targeted, or
+            None to use the default.
+        :param sourcename: the (string) source package name to create
+            a package for, or None to use the default.
+        :param version: the (string) version of the package to create,
+            or None to use an arbitrary version.
+        :param component_name: the name of the component the package
+            should be targeted at, or None to use an arbitrary component.
+        :param section_name: the name of the section the package
+            should be targeted to, or None to use an arbitrary section.
+        :param urgency: the `SourcePackageUrgency` that should be used for
+            the created `SourcePackageRelease`, or None to use an arbitrary
+            urgency.
+        :param builddepends: the build depends that should be recorded
+            for the source package.
+        :type builddepends: str or None
+        :param builddependsindep: the Build-Depends-Indep that should
+            be recorded for the source package.
+        :type builddependsindep: str or None
+        :param buildconflicts: the build conflicts that should be recorded
+            for the source package.
+        :type buildconflicts: str or None
+        :param buildconflictsindep: the Build-Conflicts-Indep that should
+            be recorded for the source package.
+        :type buildconflictsindep: str or None
+        :param architecturehintlist: the list of architectures the
+            source should state it should be built for, either 'all',
+            'any', a list of architectures, or None for an arbitrary
+            hint list.
+        :type architecturehintlist: str or None
+        :param dsc_standards_version: the standards version that should
+            be recorded as having come from the uploaded .dsc, or None
+            for an arbitrary standards version.
+        :type dsc_standards_version: str or None
+        :param dsc_format: the dsc formath that sould be recorded
+            as having come from the uploaded .dsc, or None for an
+            arbitrary format.
+        :type dsc_standards_version: str or None
+        :param dsc_binaries: the list of binary packages that
+            should be recorded has having come from the uploaded
+            .dsc, or None for an arbitrary list.
+        :type dsc_binaries: str or None
+        :param dsc_maintainer_rfc822: the string containing the
+            name and email address of the person that changed the
+            package, in rfc822 format, or None for an arbitrary
+            person's details.
+        :param maintainer: the `IPerson` that should be recorded as
+            the maintainer of the package, or None for an arbitrary
+            person.
+        :param creator: the `IPerson` that should be recorded as
+            the uploader of the package, or None for an arbitrary
+            person.
+        :param date_uploaded: the datetime that the package should
+            appear to have been uploaded at, or None for an arbitrary
+            date.
+        :param changelog_entry: the changelog entry of the upload, or
+            None for no entry.
+        :return: the created `SourcePackageRelease`.
+        """
+        if distroseries is None:
+            distroseries = self.distroseries
+        if archive is None:
+            archive = distroseries.distribution.main_archive
+        if sourcename is None:
+            sourcename = self.default_package_name
+        spn = self.factory.getOrMakeSourcePackageName(name=sourcename)
+        component = self.factory.makeComponent(name=component_name)
+        if dsc_binaries is None:
+            dsc_binaries = '%s-bin' % sourcename
+        if architecturehintlist is None:
+            architecturehintlist = 'all'
+        if dsc_standards_version is None:
+            dsc_standards_version = '3.6.2'
+        if dsc_format is None:
+            dsc_format = '1.0'
+        if date_uploaded is None:
+            date_uploaded = UTC_NOW
+        return self.factory.makeSourcePackageRelease(
+            distroseries=distroseries,
+            sourcepackagename=spn,
+            maintainer=maintainer,
+            creator=creator,
+            component=component,
+            section_name=section_name,
+            urgency=urgency,
+            version=version,
+            builddepends=builddepends,
+            builddependsindep=builddependsindep,
+            build_conflicts=build_conflicts,
+            build_conflicts_indep=build_conflicts_indep,
+            architecturehintlist=architecturehintlist,
+            dscsigningkey=dsc_signing_key,
+            dsc_maintainer_rfc822=dsc_maintainer_rfc822,
+            dsc_standards_version=dsc_standards_version,
+            dsc_format=dsc_format,
+            dsc_binaries=dsc_binaries,
+            archive=archive,
+            date_uploaded=date_uploaded,
+            changelog_entry=changelog_entry,
+            )
+
+    def publishSource(self, sourcename=None, version=None,
+                      component_name=None, section_name=None,
+                      filename=None, filecontent=None,
+                      changes_file_content=None, status=None, pocket=None,
+                      urgency=None, scheduleddeletiondate=None,
+                      dateremoved=None, distroseries=None, archive=None,
+                      builddepends=None, builddependsindep=None,
+                      build_conflicts=None, build_conflicts_indep=None,
+                      architecturehintlist=None, dsc_standards_version=None,
+                      dsc_format=None, dsc_binaries=None,
+                      dsc_maintainer_rfc822=None, maintainer=None,
+                      creator=None, date_uploaded=None,
+                      files_expire=None, changelog_entry=None):
+        """Publish a source package.
+
+        Publishes a source package and returns the resulting
+        `SourcePackagePublishingHistory`.
+
+        :param sourcename: the (string) source package name to publish
+            a package for, or None to use the default.
+        :param version: the (string) version of the package to publish,
+            or None to use an arbitrary version.
+        :param component_name: the name of the component to publish to,
+            or None to use an arbitrary component.
+        :param section_name: the name of the section to publish to,
+            or None to use an arbitrary section.
+        :param filename: the filename of the file making up the
+            `SourcePackageRelease`, or None to use a name based
+            on sourcename and version.
+        :param filecontent: the content of the file making up the
+            `SourcePackageRelease`, or None to use arbitrary content.
+        :param changes_file_content: the content of the changes
+            file for the upload, or None to use arbitrary content.
+        :param status: the `PackagePublishingStatus` that should be used
+            for the created publishing record, or None for the default
+            status, PUBLISHED.
+        :param pocket: the `PackagePublishingPocket` that should be
+            used for the created publishing record and the associated
+            `PackageUpload`, or None for teh default pocket, RELEASE.
+        :param urgency: the `SourcePackageUrgency` that should be used for
+            the created `SourcePackageRelease`, or None to use an arbitrary
+            urgency.
+        :param scheduleddeletiondate: the scheduled deletion date that
+            should be set on the created publishing record, or None
+            to have no scheduled deletion date.
+        :type scheduleddeletiondate: `datetime.datetime`
+        :param dateremoved: the date that should be recorded as being
+            the date the package was removed on the created publishing
+            record, or None if it hasn't been removed.
+        :type dateremoved: `datetime.datetime`
+        :param distroseries: the distroseries that should be
+            published to, or None to use the default.
+        :param archive: the archive that should be published to, or
+            None to use the default.
+        :param builddepends: the build depends that should be recorded
+            for the source package.
+        :type builddepends: str or None
+        :param builddependsindep: the Build-Depends-Indep that should
+            be recorded for the source package.
+        :type builddependsindep: str or None
+        :param buildconflicts: the build conflicts that should be recorded
+            for the source package.
+        :type buildconflicts: str or None
+        :param buildconflictsindep: the Build-Conflicts-Indep that should
+            be recorded for the source package.
+        :type buildconflictsindep: str or None
+        :param architecturehintlist: the list of architectures the
+            source should state it should be built for, either 'all',
+            'any', a list of architectures, or None for an arbitrary
+            hint list.
+        :type architecturehintlist: str or None
+        :param dsc_standards_version: the standards version that should
+            be recorded as having come from the uploaded .dsc, or None
+            for an arbitrary standards version.
+        :type dsc_standards_version: str or None
+        :param dsc_format: the dsc formath that sould be recorded
+            as having come from the uploaded .dsc, or None for an
+            arbitrary format.
+        :type dsc_standards_version: str or None
+        :param dsc_binaries: the list of binary packages that
+            should be recorded has having come from the uploaded
+            .dsc, or None for an arbitrary list.
+        :type dsc_binaries: str or None
+        :param dsc_maintainer_rfc822: the string containing the
+            name and email address of the person that changed the
+            package, in rfc822 format, or None for an arbitrary
+            person's details.
+        :param maintainer: the `IPerson` that should be recorded as
+            the maintainer of the package, or None for an arbitrary
+            person.
+        :param creator: the `IPerson` that should be recorded as
+            the uploader of the package, or None for an arbitrary
+            person.
+        :param date_uploaded: the datetime that the package should
+            appear to have been uploaded at, or None for an arbitrary
+            date.
+        :param files_expire: the date at which the librarian files
+            for the package should expire, or None for no expiry.
+        :param changelog_entry: the changelog entry of the upload, or
+            None for no entry.
+        :return: the created `SourcePackagePublishingHistory`.
+        """
+        spr = self.makeSourcePackageRelease(
+            distroseries=distroseries,
+            sourcename=sourcename,
+            archive=archive,
+            version=version,
+            urgency=urgency,
+            component_name=component_name,
+            section_name=section_name,
+            maintainer=maintainer,
+            creator=creator,
+            builddepends=builddepends,
+            builddependsindep=builddependsindep,
+            build_conflicts=build_conflicts,
+            build_conflicts_indep=build_conflicts_indep,
+            architecturehintlist=architecturehintlist,
+            dsc_maintainer_rfc822=dsc_maintainer_rfc822,
+            dsc_standards_version=dsc_standards_version,
+            dsc_format=dsc_format,
+            dsc_binaries=dsc_binaries,
+            date_uploaded=date_uploaded,
+            changelog_entry=changelog_entry,
+            )
+        changes_file_name = "%s_%s_source.changes" % (
+            spr.sourcepackagename.name, spr.version)
+        package_upload = self.makePackageUpload(
+            archive=spr.upload_archive,
+            distroseries=spr.upload_distroseries,
+            pocket=pocket,
+            changes_file_name=changes_file_name,
+            changes_file_content=changes_file_content,
+            )
+        removeSecurityProxy(package_upload).addSource(spr)
+
+        if filename is None:
+            filename = "%s_%s.dsc" % (
+                spr.sourcepackagename.name, spr.version)
+        alias = self.addFile(
+            filename, filecontent=filecontent,
+            restricted=spr.upload_archive.private, expires=files_expire)
+        spr.addFile(alias)
+
+        if status is None:
+            status = PackagePublishingStatus.PUBLISHED
+        if date_uploaded is None:
+            date_uploaded = UTC_NOW
+        spph = self.factory.makeSourcePackagePublishingHistory(
+            distroseries=spr.upload_distroseries,
+            archive=spr.upload_archive,
+            sourcepackagerelease=spr,
+            status=status,
+            date_uploaded=date_uploaded,
+            dateremoved=dateremoved,
+            scheduleddeletiondate=scheduleddeletiondate,
+            pocket=package_upload.pocket,
+            )
+        return spph
+
+    def publishBinaries(self, binaryname=None, summary=None,
+                        description=None, shlibdep=None, depends=None,
+                        recommends=None, suggests=None, conflicts=None,
+                        replaces=None, provides=None, pre_depends=None,
+                        enhances=None, breaks=None, filecontent=None,
+                        changes_file_content=None, status=None,
+                        pocket=None, format=None, scheduleddeletiondate=None,
+                        dateremoved=None, distroseries=None, archive=None,
+                        pub_source=None, version=None,
+                        architecturespecific=False, builder=None,
+                        component_name=None, section_name=None,
+                        priority=None, installed_size=None,
+                        files_expire=None):
+        """Publish some binary packages.
+
+        Publishes a number of binary packages and returns a list of the
+        resulting `BinaryPackagePublishingHistory` objects.
+
+        :param binaryname: the binary package name to use, or None
+            for a name derived from the default package name.
+        :type binaryname: str or None
+        :param summary: the summary (first line) of the package description.
+        :type summary: str or None.
+        :param description: the continuation lines of the package
+            description.
+        :type description: str or None
+        :param shlibdep: the shared library dependencies the binary packages
+            should have.
+        :type shlibdep: str or None
+        :param depends: the string containing the package dependencies the
+            binary packages should have.
+        :type depends: str or None
+        :param recommends: the string containing the package recommendations
+            the binary packages should have.
+        :type recommends: str or None
+        :param suggests: the string containing the package suggestions
+            the binary packages should have.
+        :type suggests: str or None
+        :param conflicts: the string containing the package conflicts
+            the binary packages should have.
+        :type conflicts: str or None
+        :param replaces: the string containing the package replaces
+            relationships the binary packages should have.
+        :type replaces: str or None
+        :param provides: the string containing the package provides
+            relationships the binary packages should have.
+        :type provides: str or None
+        :param pre_depends: the string containing the package
+            pre-dependencies the binary packages should have.
+        :type pre_depends: str or None
+        :param enhances: the string containing the package enhances
+            relationships the binary packages should have.
+        :type enhances: str or None
+        :param breaks: the string containing the package breaks
+            relationships the binary packages should have.
+        :type breaks: str or None
+        :param filecontent: the content that the files associated with
+            the binary packages should have.
+        :type filecontent: str or None
+        :param changes_file_content: the content that the uploaded changes
+            file associated with the binary packages should have.
+        :type changes_file_content: str or None
+        :param status: the `PackagePublishingStatus` that the binary
+            packages should have, or None for the default,
+            `PackagePublishingStatus.PUBLISHED`.
+        :type status: `PackagePublishingStatus` or None
+        :param pocket: the `PackagePublishingPocket` that the binary
+            packages should be published to, or None for the default,
+            `PackagePublishingPocket.RELEASE`.
+        :type pocket: `PackagePublishingPocket` or None
+        :param format: the `BinaryPackageFormat` that the binary packages
+            should be, or None for the default, `BinaryPackageFormat.DEB`.
+        :type format: `BinaryPackageFormat` or None
+        :param scheduleddeletiondate: the date that the packages should
+            be scheduled for deletion, or None if it should not
+            be.
+        :type scheduleddeletiondate: `datetime.datetime` or None
+        :param dateremoved: the date that the packages should
+            be recorded as having been deleted, or None for packages
+            that haven't been deleted.
+        :type dateremoved: `datetime.datetime` or None
+        :param distroseries: the distroseries that the packages should
+            be published to, or None for the default distroseries.
+        :type distroseries: `IDistroSeries` or None
+        :param archive: the archive that the packages should be published
+            to, or None for the default archive.
+        :type archive: `IArchive` or None
+        :param pub_source: the `SourcePackagePublishingHistory` that
+             the builds and binaries should be associated with, or
+             None to create a new one.
+        :type pub_source: `SourcePackagePublishingHistory` or None
+        :param version: the version that the binary packages should
+             have, or None for an arbitrary version.
+        :type version: str or None
+        :param architecturespecific: whether the binary packages
+            should be architecturespecific, default is False.
+        :type architecturespecific: bool
+        :param builder: the builder that the builds should state they
+            were built by, or None.
+        :type builder: `IBuilder` or None
+        :param component_name: the name of the component that the packages
+            should be published to, or None for an arbitrary component.
+        :type component_name: str or None
+        :param section_name: the name of the section that the packages
+            should be published to, or None for an arbitrary section.
+        :type section_name: str or None
+        :param priority: the `PackagePublishingPriority` that the packages
+            should be published with, or None for an arbitrary priority.
+        :type priority: `PackagePublishingPriority` or None
+        :param installed_size: the size that the packages should report
+            that they take up when installed, or None for an arbitrary
+            size.
+        :type installed_size: int or None
+        :param files_expire: when the files associated with the packages
+            should expire, or None to not expire.
+        :type files_expire: `datetime.datetime` or None
+        :return: the binary publishing records that were created.
+        :rtype: an iterable of `BinaryPackagePublishingHistory`.
+        """
+        if binaryname is None:
+            binaryname = "%s-bin" % self.default_package_name
+        if distroseries is None:
+            distroseries = self.distroseries
+
+        if distroseries.nominatedarchindep is None:
+            raise AssertionError(
+                "distroseries not set up for binary publishing. If you "
+                "are using the default you should use "
+                "setUpForBinaryPublications() rather than setUp().")
+
+        if archive is None:
+            archive = distroseries.main_archive
+
+        if pub_source is None:
+            sourcename = "%s" % binaryname.split('-')[0]
+            if architecturespecific:
+                architecturehintlist = 'any'
+            else:
+                architecturehintlist = 'all'
+
+            pub_source = self.publishSource(
+                sourcename=sourcename, status=status, pocket=pocket,
+                archive=archive, distroseries=distroseries,
+                version=version, architecturehintlist=architecturehintlist,
+                component_name=component_name, files_expire=files_expire)
+        else:
+            archive = pub_source.archive
+
+        builds = pub_source.createMissingBuilds()
+        published_binaries = []
+        for build in builds:
+            build.builder = builder
+            binarypackagerelease = self.uploadBinaryForBuild(
+                build, binaryname, filecontent=filecontent, summary=summary,
+                description=description, shlibdep=shlibdep,
+                depends=depends, recommends=recommends, suggests=suggests,
+                conflicts=conflicts, replaces=replaces,
+                provides=provides, pre_depends=pre_depends,
+                enhances=enhances, breaks=breaks, format=format,
+                installed_size=installed_size, component_name=component_name,
+                files_expire=files_expire)
+            pub_binaries = self.publishBinaryInArchive(
+                binarypackagerelease, archive, status=status, pocket=pocket,
+                scheduleddeletiondate=scheduleddeletiondate,
+                dateremoved=dateremoved, section_name=section_name,
+                priority=priority)
+            published_binaries.extend(pub_binaries)
+            package_upload = self.makePackageUpload(
+                archive=archive, distroseries=distroseries, pocket=pocket,
+                changes_file_content=changes_file_content,
+                changes_file_name='%s_%s_%s.changes' %
+                    (binaryname, binarypackagerelease.version,
+                     build.arch_tag))
+            package_upload.addBuild(build)
+
+        return sorted(
+            published_binaries, key=operator.attrgetter('id'), reverse=True)
+
+    def uploadBinaryForBuild(self, build, binaryname, filecontent=None,
+                             summary=None, description=None, shlibdep=None,
+                             depends=None, recommends=None, suggests=None,
+                             conflicts=None, replaces=None, provides=None,
+                             pre_depends=None, enhances=None, breaks=None,
+                             format=None, installed_size=None,
+                             component_name=None, version=None,
+                             files_expire=None):
+        """Create a `BinaryPackageRelease` for a `PackageBuild`.
+
+        Given a build this method will create a `BinaryPackageRelease`
+        for a package that results from the build. It will also
+        add a file that represents the contents of that binary package,
+        and will adjust the build to look like it has completed.
+
+        :param build: the build that the binary should be associated with.
+        :type build: `PackageBuild`
+        :param binaryname: the name of the binary package that should
+            be created.
+        :type binaryname: str
+        :param filecontent: the content that the file associated with
+            the binary package should have.
+        :type filecontent: str or None
+        :param summary: the summary (first line) of the package description.
+        :type summary: str or None.
+        :param description: the continuation lines of the package
+            description.
+        :type description: str or None
+        :param shlibdep: the shared library dependencies the binary package
+            should have.
+        :type shlibdep: str or None
+        :param depends: the string containing the package dependencies the
+            binary package should have.
+        :type depends: str or None
+        :param recommends: the string containing the package recommendations
+            the binary package should have.
+        :type recommends: str or None
+        :param suggests: the string containing the package suggestions
+            the binary package should have.
+        :type suggests: str or None
+        :param conflicts: the string containing the package conflicts
+            the binary package should have.
+        :type conflicts: str or None
+        :param replaces: the string containing the package replaces
+            relationships the binary package should have.
+        :type replaces: str or None
+        :param provides: the string containing the package provides
+            relationships the binary package should have.
+        :type provides: str or None
+        :param pre_depends: the string containing the package
+            pre-dependencies the binary package should have.
+        :type pre_depends: str or None
+        :param enhances: the string containing the package enhances
+            relationships the binary package should have.
+        :type enhances: str or None
+        :param breaks: the string containing the package breaks
+            relationships the binary package should have.
+        :type breaks: str or None
+        :param format: the `BinaryPackageFormat` that the binary package
+            should be, or None for the default, `BinaryPackageFormat.DEB`.
+        :type format: `BinaryPackageFormat` or None
+        :param installed_size: the size that the package should report
+            that it takes up when installed, or None for an arbitrary
+            size.
+        :type installed_size: int or None
+        :param component_name: the name of the component that the package
+            should be targeted at, or None for an arbitrary component.
+        :type component_name: str or None
+        :param files_expire: when the file associated with the package
+            should expire, or None to not expire.
+        :param version: the version that the binary package should
+             have, or None for an arbitrary version.
+        :type version: str or None
+        :type files_expire: `datetime.datetime` or None
+        :return: the created binary package
+        :rtype: `BinaryPackageRelease`
+        """
+        sourcepackagerelease = build.source_package_release
+        distroarchseries = build.distro_arch_series
+        architecturespecific = (
+            not sourcepackagerelease.architecturehintlist == 'all')
+        component = self.factory.makeComponent(name=component_name)
+        binarypackagename = self.factory.getOrMakeBinaryPackageName(
+            name=binaryname)
+        if format is None:
+            format = BinaryPackageFormat.DEB
+
+        binarypackagerelease = self.factory.makeBinaryPackageRelease(
+            build=build,
+            binarypackagename=binarypackagename,
+            summary=summary,
+            description=description,
+            shlibdeps=shlibdep,
+            depends=depends,
+            recommends=recommends,
+            suggests=suggests,
+            conflicts=conflicts,
+            replaces=replaces,
+            provides=provides,
+            pre_depends=pre_depends,
+            enhances=enhances,
+            breaks=breaks,
+            essential=False,
+            architecturespecific=architecturespecific,
+            binpackageformat=format,
+            priority=PackagePublishingPriority.STANDARD,
+            installed_size=installed_size,
+            component=component,
+            version=version,
+            )
+
+        # Create the corresponding binary file.
+        if architecturespecific:
+            filearchtag = distroarchseries.architecturetag
+        else:
+            filearchtag = 'all'
+        filename = '%s_%s_%s.%s' % (binaryname, sourcepackagerelease.version,
+                                    filearchtag, format.name.lower())
+        alias = self.addFile(
+            filename, filecontent=filecontent,
+            restricted=build.archive.private, expires=files_expire)
+        binarypackagerelease.addFile(alias)
+
+        # Adjust the build record in way it looks complete.
+        naked_build = removeSecurityProxy(build)
+        naked_build.status = BuildStatus.FULLYBUILT
+        naked_build.date_finished = datetime.datetime(
+            2008, 1, 1, 0, 5, 0, tzinfo=pytz.UTC)
+        naked_build.date_started = (
+            build.date_finished - datetime.timedelta(minutes=5))
+        buildlog_filename = 'buildlog_%s-%s-%s.%s_%s_%s.txt.gz' % (
+            build.distribution.name,
+            build.distro_series.name,
+            build.distro_arch_series.architecturetag,
+            build.source_package_release.name,
+            build.source_package_release.version,
+            build.status.name)
+        naked_build.log = self.addFile(
+            buildlog_filename, filecontent='Built!',
+            restricted=build.archive.private)
+
+        return binarypackagerelease
+
+    def publishBinaryInArchive(self, binarypackagerelease, archive,
+                               status=None, pocket=None,
+                               scheduleddeletiondate=None,
+                               dateremoved=None, section_name=None,
+                               priority=None):
+        """Publish a `BinaryPackageRelease` in to an archive.
+
+        Returns the created `BinaryPackagePublishingHistory`.
+
+        :param binarypackagerelease: the binary package to publish.
+        :type binarypackagerelease: `BinaryPackageRelease`
+        :param archive: the archive to publish to.
+        :type archive: `IArchive`
+        :param status: the status that the created publishing record
+            should have, or None for the default of
+            `PackagePublishingStatus.PENDING`.
+        :type status: `PackagePublishingStatus` or None
+        :param pocket: the pocket to publish to, or None for the
+            default pocket of `PackagePublishingPocket.RELEASE`.
+        :type pocket: `PackagePublishingPocket` or None
+        :param scheduleddeletiondate: the date at which the record should
+            be scheduled for deletion, or None to not schedule for
+            deletion.
+        :type scheduleddeletiondate: `datetime.datetime` or None
+        :param dateremoved: the date that the publishing should
+            be recorded as having been deleted, or None to not set it.
+        :type dateremoved: `datetime.datetime` or None
+        :param section_name: the name of the section that should be
+            published to, or None for an arbitrary section.
+        :type section_name: str or None
+        :param priority: the `PackagePublishingPriority` that the created
+            publishing record should have, or None for an arbitrary
+            priority.
+        :type priority: `PackagePublishingPriority` or None
+        :return: the created publishing record.
+        :rtype: `BinaryPackagePublishingHistory`
+        """
+        distroarchseries = binarypackagerelease.build.distro_arch_series
+
+        # Publish the binary.
+        if binarypackagerelease.architecturespecific:
+            archs = [distroarchseries]
+        else:
+            archs = distroarchseries.distroseries.architectures
+
+        if status is None:
+            status = PackagePublishingStatus.PENDING
+        if pocket is None:
+            pocket = PackagePublishingPocket.RELEASE
+
+        pub_binaries = []
+        for arch in archs:
+            pub = self.factory.makeBinaryPackagePublishingHistory(
+                distroarchseries=arch,
+                binarypackagerelease=binarypackagerelease,
+                status=status,
+                scheduleddeletiondate=scheduleddeletiondate,
+                dateremoved=dateremoved,
+                pocket=pocket,
+                archive=archive,
+                section_name=section_name,
+                priority=priority,
+                )
+            pub_binaries.append(pub)
+
+        return pub_binaries
+
+    def _findChangesFile(self, top, name_fragment):
+        """File with given name fragment in directory tree starting at top."""
+        for root, dirs, files in os.walk(top, topdown=False):
+            for name in files:
+                if (name.endswith('.changes') and
+                    name.find(name_fragment) > -1):
+                    return os.path.join(root, name)
+        return None
+
+    def createSource(self, sourcename, version, archive=None,
+                     distroseries=None, new_version=None,
+                     component_name=None):
+        """Create source with meaningful '.changes' file.
+
+        :param sourcename: the source package name that the changes
+            file should reference.
+        :type sourcename: str
+        :param version: the version of the changes file that should
+            be used.
+        :type version: str
+        :param archive: the archive that should be uploaded to, or None
+            for the default archive.
+        :type archive: `IArchive` or None
+        :param distroseries: the distroseries to publish to, or None
+            for the default.
+        :type distroseries: `IDistroSeries` or None
+        :param new_version: the version of the package to publish,
+            or None to use the found version.
+        :type new_version: str or None
+        :param component_name: the name of the component to upload
+            and publish to, or None for an arbitrary component.
+        :type component_name: str
+        :return: the created `SourcePackagePublishingHistory`, or
+            None if the changes file wasn't found.
+        :rtype: `SourcePackagePublishingHistory` or None
+        """
+        top = 'lib/lp/archiveuploader/tests/data/suite'
+        name_fragment = '%s_%s' % (sourcename, version)
+        changesfile_path = self._findChangesFile(top, name_fragment)
+
+        source = None
+
+        if changesfile_path is not None:
+            if new_version is None:
+                new_version = version
+            changesfile_content = ''
+            handle = open(changesfile_path, 'r')
+            try:
+                changesfile_content = handle.read()
+            finally:
+                handle.close()
+
+            source = self.publishSource(
+                sourcename=sourcename, archive=archive, version=new_version,
+                changes_file_content=changesfile_content,
+                distroseries=distroseries, component_name=component_name)
+
+        return source
+
+
+def makeDistributionLucilleconfig(distribution):
+    """Create a lucille config for the distribution."""
+    removeSecurityProxy(distribution).lucilleconfig = """[publishing]
+pendingremovalduration=5
+root=/var/tmp/archive
+archiveroot=/var/tmp/archive/%(name)s
+poolroot=/var/tmp/archive/%(name)s/pool
+distsroot=/var/tmp/archive/%(name)s/dists
+overrideroot=/var/tmp/archive/%(name)s-overrides
+cacheroot=/var/tmp/archive/%(name)s-cache
+miscroot=/var/tmp/archive/%(name)s-misc
+""" % dict(name=distribution.name)
+
+
+def makeDistroSeriesLucilleconfig(distroseries):
+    """Create a lucille config for the distroseries."""
+    removeSecurityProxy(distroseries).lucilleconfig = """[publishing]
+components = %(components)s
+""" % dict(components=[c.name for c in distroseries.components])
+
+
+class PublishingTestCase(TestCaseWithFactory):
+    """A test case that has access to a `SoyuzTestPublisher`.
+
+    In addition:
+
+        * the default distribution and distroseries of
+          the publisher will have lucille configurations, so that they
+          can be used with the publisher.
+        * the publisher configuration will have been asked to create
+          the directories necessary for publishing the default
+          distribution.
+
+    :ivar publisher: a `SoyuzTestPublisher` instance, prepared for
+        binary uploads.
+    :type publisher: `SoyuzTestPublisher`
+    :ivar config: the publisher configuration for the default
+        distribution of `publisher`.
+    :type config: `lp.archivepublisher.config.Config`
+    :ivar logger: a logger that the publisher will use.
+    :type logger: `canonical.launchpad.scripts.QuietFakeLogger`
+    :ivar disk_pool: a `DiskPool` for the default distribution
+        of `publisher`.
+    :type disk_pool: `lp.archivepublisher.diskpool.DiskPool`
+    """
+
+    layer = LaunchpadZopelessLayer
+    dbuser = config.archivepublisher.dbuser
+
+    def setUp(self):
+        super(PublishingTestCase, self).setUp()
+        self.layer.switchDbUser(self.dbuser)
+        self.publisher = SoyuzTestPublisher(factory=self.factory)
+        self.publisher.prepareForBinaryPublications()
+        makeDistributionLucilleconfig(self.publisher.distribution)
+        makeDistroSeriesLucilleconfig(self.publisher.distroseries)
+        self.config = Config(self.publisher.distribution)
+        self.addCleanup(self.config.removeArchiveDirs)
+        self.config.setupArchiveDirs()
+        self.logger = QuietFakeLogger()
+        self.disk_pool = DiskPool(
+            self.config.poolroot, self.config.temproot, self.logger)

=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py	2010-08-09 22:38:48 +0000
+++ lib/lp/soyuz/tests/test_publishing.py	2010-08-09 22:38:53 +0000
@@ -22,7 +22,6 @@
 from lp.app.errors import NotFoundError
 from lp.archivepublisher.config import Config
 from lp.archivepublisher.diskpool import DiskPool
-from lp.buildmaster.interfaces.buildbase import BuildStatus
 from lp.registry.interfaces.distribution import IDistributionSet
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -37,20 +36,50 @@
     IPublishingSet, PackagePublishingPriority, PackagePublishingStatus)
 from lp.soyuz.interfaces.queue import PackageUploadStatus
 from lp.soyuz.testing.matchers import IsSupersededBy, PublishedStateIs
+from lp.soyuz.testing.publisher import (
+    SoyuzTestPublisher as _SoyuzTestPublisher)
 from canonical.launchpad.scripts import FakeLogger
 from lp.testing import TestCaseWithFactory
 from lp.testing.matchers import DateIsInPast
-from lp.testing.factory import LaunchpadObjectFactory
 from lp.testing.sampledata import UBUNTU_DEVELOPER_ADMIN_NAME
 from lp.testing.fakemethod import FakeMethod
 
 
-class SoyuzTestPublisher:
-    """Helper class able to publish coherent source and binaries in Soyuz."""
-
-    def __init__(self):
-        self.factory = LaunchpadObjectFactory()
-        self.default_package_name = 'foo'
+class SoyuzTestPublisher(_SoyuzTestPublisher):
+
+    def regetBreezyAutotest(self):
+        self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
+        self.breezy_autotest = self.ubuntutest['breezy-autotest']
+        self.person = getUtility(IPersonSet).getByName('name16')
+        self.breezy_autotest_i386 = self.breezy_autotest['i386']
+        self.breezy_autotest_hppa = self.breezy_autotest['hppa']
+
+    def prepareBreezyAutotest(self):
+        """Prepare ubuntutest/breezy-autotest for publications.
+
+        It's also called during the normal test-case setUp.
+        """
+        self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
+        self.breezy_autotest = self.ubuntutest['breezy-autotest']
+        self.setUpDefaultDistroSeries(self.breezy_autotest)
+        # Only create the DistroArchSeries needed if they do not exist yet.
+        # This makes it easier to experiment at the python command line
+        # (using "make harness").
+        try:
+            self.breezy_autotest_i386 = self.breezy_autotest['i386']
+        except NotFoundError:
+            self.breezy_autotest_i386 = self.breezy_autotest.newArch(
+                'i386', ProcessorFamily.get(1), False, self.person,
+                supports_virtualized=True)
+        try:
+            self.breezy_autotest_hppa = self.breezy_autotest['hppa']
+        except NotFoundError:
+            self.breezy_autotest_hppa = self.breezy_autotest.newArch(
+                'hppa', ProcessorFamily.get(4), False, self.person)
+        self.breezy_autotest.nominatedarchindep = self.breezy_autotest_i386
+        fake_chroot = self.addMockFile('fake_chroot.tar.gz')
+        self.breezy_autotest_i386.addOrUpdateChroot(fake_chroot)
+        self.breezy_autotest_hppa.addOrUpdateChroot(fake_chroot)
 
     def setUpDefaultDistroSeries(self, distroseries=None):
         """Set up a distroseries that will be used by default.
@@ -67,8 +96,8 @@
         :return: The `IDistroSeries` that got set as default.
         """
         if distroseries is None:
-            distroseries = self.factory.makeDistroRelease()
-        self.distroseries = distroseries
+            distroseries = self.factory.makeDistroSeries()
+        distroseries = self.setDefaultDistroSeries(distroseries=distroseries)
         # Set up a person that has a GPG key.
         self.person = getUtility(IPersonSet).getByName(
             UBUNTU_DEVELOPER_ADMIN_NAME)
@@ -78,58 +107,6 @@
         name_set.getOrCreateByName(self.default_package_name)
         return self.distroseries
 
-    def prepareBreezyAutotest(self):
-        """Prepare ubuntutest/breezy-autotest for publications.
-
-        It's also called during the normal test-case setUp.
-        """
-        self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
-        self.breezy_autotest = self.ubuntutest['breezy-autotest']
-        self.setUpDefaultDistroSeries(self.breezy_autotest)
-        # Only create the DistroArchSeries needed if they do not exist yet.
-        # This makes it easier to experiment at the python command line
-        # (using "make harness").
-        try:
-            self.breezy_autotest_i386 = self.breezy_autotest['i386']
-        except NotFoundError:
-            self.breezy_autotest_i386 = self.breezy_autotest.newArch(
-                'i386', ProcessorFamily.get(1), False, self.person,
-                supports_virtualized=True)
-        try:
-            self.breezy_autotest_hppa = self.breezy_autotest['hppa']
-        except NotFoundError:
-            self.breezy_autotest_hppa = self.breezy_autotest.newArch(
-                'hppa', ProcessorFamily.get(4), False, self.person)
-        self.breezy_autotest.nominatedarchindep = self.breezy_autotest_i386
-        fake_chroot = self.addMockFile('fake_chroot.tar.gz')
-        self.breezy_autotest_i386.addOrUpdateChroot(fake_chroot)
-        self.breezy_autotest_hppa.addOrUpdateChroot(fake_chroot)
-
-    def addFakeChroots(self, distroseries=None):
-        """Add fake chroots for all the architectures in distroseries."""
-        if distroseries is None:
-            distroseries = self.distroseries
-        fake_chroot = self.addMockFile('fake_chroot.tar.gz')
-        for arch in distroseries.architectures:
-            arch.addOrUpdateChroot(fake_chroot)
-
-    def regetBreezyAutotest(self):
-        self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
-        self.breezy_autotest = self.ubuntutest['breezy-autotest']
-        self.person = getUtility(IPersonSet).getByName('name16')
-        self.breezy_autotest_i386 = self.breezy_autotest['i386']
-        self.breezy_autotest_hppa = self.breezy_autotest['hppa']
-
-    def addMockFile(self, filename, filecontent='nothing', restricted=False,
-                    expires=None):
-        """Add a mock file in Librarian.
-
-        Returns a ILibraryFileAlias corresponding to the file uploaded.
-        """
-        return self.factory.makeLibraryFileAlias(
-            filename=filename, content=filecontent, restricted=restricted,
-            content_type='application/text', expires=expires)
-
     def addPackageUpload(self, archive, distroseries,
                          pocket=PackagePublishingPocket.RELEASE,
                          changes_file_name="foo_666_source.changes",
@@ -137,12 +114,11 @@
                          upload_status=PackageUploadStatus.DONE):
         person = self.factory.makePerson()
         signing_key = self.factory.makeGPGKey(person)
-        package_upload = self.factory.makePackageUpload(
-            archive=archive, distroseries=distroseries, pocket=pocket,
-            changes_filename=changes_file_name,
+        return self.makePackageUpload(
+            archive=archive, distroseries=distroseries,
+            pocket=pocket, changes_file_name=changes_file_name,
             changes_file_content=changes_file_content,
             signing_key=signing_key, status=upload_status)
-        return package_upload
 
     def getPubSource(self, sourcename=None, version='666', component='main',
                      filename=None, section='base',
@@ -161,23 +137,16 @@
                      maintainer=None, creator=None, date_uploaded=UTC_NOW,
                      spr_only=False, files_expire=None,
                      changelog_entry=None):
-        """Return a mock source publishing record.
-
-        if spr_only is specified, the source is not published and the
-        sourcepackagerelease object is returned instead.
-        """
         if sourcename is None:
             sourcename = self.default_package_name
         spn = self.factory.getOrMakeSourcePackageName(name=sourcename)
         component = self.factory.makeComponent(name=component)
-
-        if distroseries is None:
-            distroseries = self.distroseries
-        if archive is None:
-            archive = distroseries.main_archive
         if creator is None:
             creator = self.factory.makePerson()
-            self.factory.makeGPGKey(creator)
+        if not creator.gpg_keys:
+            dscsigningkey = self.factory.makeGPGKey(creator)
+        else:
+            dscsigningkey = creator.gpg_keys[0]
 
         changes_file_name = "%s_%s_source.changes" % (sourcename, version)
         if spr_only:
@@ -191,7 +160,7 @@
             upload_status=upload_status)
 
         spr = self.factory.makeSourcePackageRelease(
-            distroseries=distroseries,
+            distroseries=package_upload.distroseries,
             sourcepackagename=spn,
             maintainer=maintainer,
             creator=creator,
@@ -204,12 +173,12 @@
             build_conflicts=build_conflicts,
             build_conflicts_indep=build_conflicts_indep,
             architecturehintlist=architecturehintlist,
-            dscsigningkey=creator.gpg_keys[0],
+            dscsigningkey=dscsigningkey,
             dsc_maintainer_rfc822=dsc_maintainer_rfc822,
             dsc_standards_version=dsc_standards_version,
             dsc_format=dsc_format,
             dsc_binaries=dsc_binaries,
-            archive=archive,
+            archive=package_upload.archive,
             date_uploaded=date_uploaded,
             changelog_entry=changelog_entry,
             )
@@ -217,8 +186,8 @@
 
         if filename is None:
             filename = "%s_%s.dsc" % (sourcename, version)
-        alias = self.addMockFile(
-            filename, filecontent, restricted=archive.private,
+        alias = self.addFile(
+            filename, filecontent, restricted=package_upload.archive.private,
             expires=files_expire)
         spr.addFile(alias)
 
@@ -226,7 +195,7 @@
             return spr
 
         spph = self.factory.makeSourcePackagePublishingHistory(
-            distroseries=distroseries,
+            distroseries=package_upload.distroseries,
             sourcepackagerelease=spr,
             component=spr.component,
             section_name=spr.section.name,
@@ -234,8 +203,8 @@
             date_uploaded=date_uploaded,
             dateremoved=dateremoved,
             scheduleddeletiondate=scheduleddeletiondate,
-            pocket=pocket,
-            archive=archive,
+            pocket=package_upload.pocket,
+            archive=package_upload.archive,
             )
         return spph
 
@@ -307,147 +276,41 @@
         return sorted(
             published_binaries, key=operator.attrgetter('id'), reverse=True)
 
-    def uploadBinaryForBuild(
-        self, build, binaryname, filecontent="anything",
-        summary="summary", description="description", shlibdep=None,
-        depends=None, recommends=None, suggests=None, conflicts=None,
-        replaces=None, provides=None, pre_depends=None, enhances=None,
-        breaks=None, format=BinaryPackageFormat.DEB, installed_size=None,
-        component=None, version=None, files_expire=None):
+    def uploadBinaryForBuild(self, build, binaryname, filecontent="anything",
+                             summary="summary", description="description",
+                             shlibdep=None, depends=None, recommends=None,
+                             suggests=None, conflicts=None, replaces=None,
+                             provides=None, pre_depends=None, enhances=None,
+                             breaks=None, format=BinaryPackageFormat.DEB,
+                             installed_size=None, component=None,
+                             version=None, files_expire=None):
         """Return the corresponding `BinaryPackageRelease`."""
-        sourcepackagerelease = build.source_package_release
-        distroarchseries = build.distro_arch_series
-        architecturespecific = (
-            not sourcepackagerelease.architecturehintlist == 'all')
-        if component is not None:
-            component = self.factory.makeComponent(name=component)
-
-        binarypackagename = self.factory.getOrMakeBinaryPackageName(
-            name=binaryname)
-
-        binarypackagerelease = self.factory.makeBinaryPackageRelease(
-            build=build,
-            binarypackagename=binarypackagename,
-            summary=summary,
-            description=description,
-            shlibdeps=shlibdep,
-            depends=depends,
-            recommends=recommends,
-            suggests=suggests,
-            conflicts=conflicts,
-            replaces=replaces,
-            provides=provides,
-            pre_depends=pre_depends,
-            enhances=enhances,
-            breaks=breaks,
-            essential=False,
-            architecturespecific=architecturespecific,
-            binpackageformat=format,
-            priority=PackagePublishingPriority.STANDARD,
-            installed_size=installed_size,
-            component=component,
-            version=version,
-            )
-
-        # Create the corresponding binary file.
-        if architecturespecific:
-            filearchtag = distroarchseries.architecturetag
-        else:
-            filearchtag = 'all'
-        filename = '%s_%s_%s.%s' % (binaryname, sourcepackagerelease.version,
-                                    filearchtag, format.name.lower())
-        alias = self.addMockFile(
-            filename, filecontent=filecontent,
-            restricted=build.archive.private, expires=files_expire)
-        binarypackagerelease.addFile(alias)
-
-        # Adjust the build record in way it looks complete.
-        naked_build = removeSecurityProxy(build)
-        naked_build.status = BuildStatus.FULLYBUILT
-        naked_build.date_finished = datetime.datetime(
-            2008, 1, 1, 0, 5, 0, tzinfo=pytz.UTC)
-        naked_build.date_started = (
-            build.date_finished - datetime.timedelta(minutes=5))
-        buildlog_filename = 'buildlog_%s-%s-%s.%s_%s_%s.txt.gz' % (
-            build.distribution.name,
-            build.distro_series.name,
-            build.distro_arch_series.architecturetag,
-            build.source_package_release.name,
-            build.source_package_release.version,
-            build.status.name)
-        naked_build.log = self.addMockFile(
-            buildlog_filename, filecontent='Built!',
-            restricted=build.archive.private)
-
-        return binarypackagerelease
-
-    def publishBinaryInArchive(
-        self, binarypackagerelease, archive,
-        status=PackagePublishingStatus.PENDING,
-        pocket=PackagePublishingPocket.RELEASE,
-        scheduleddeletiondate=None, dateremoved=None, section=None,
-        priority=None):
+        return super(SoyuzTestPublisher, self).uploadBinaryForBuild(
+            build, binaryname, filecontent=filecontent, summary=summary,
+            description=description, shlibdep=shlibdep, depends=depends,
+            recommends=recommends, suggests=suggests, conflicts=conflicts,
+            replaces=replaces, provides=provides, pre_depends=pre_depends,
+            enhances=enhances, breaks=breaks, format=format,
+            installed_size=installed_size, component_name=component,
+            version=version, files_expire=files_expire)
+
+    def publishBinaryInArchive(self, binarypackagerelease, archive,
+                               status=PackagePublishingStatus.PENDING,
+                               pocket=PackagePublishingPocket.RELEASE,
+                               scheduleddeletiondate=None, dateremoved=None,
+                               section=None, priority=None):
         """Return the corresponding BinaryPackagePublishingHistory."""
-        distroarchseries = binarypackagerelease.build.distro_arch_series
-
-        # Publish the binary.
-        if binarypackagerelease.architecturespecific:
-            archs = [distroarchseries]
-        else:
-            archs = distroarchseries.distroseries.architectures
-
-        pub_binaries = []
-        for arch in archs:
-            pub = self.factory.makeBinaryPackagePublishingHistory(
-                distroarchseries=arch,
-                binarypackagerelease=binarypackagerelease,
-                status=status,
-                scheduleddeletiondate=scheduleddeletiondate,
-                dateremoved=dateremoved,
-                pocket=pocket,
-                archive=archive,
-                section_name=section,
-                priority=priority,
-                )
-            pub_binaries.append(pub)
-
-        return pub_binaries
-
-    def _findChangesFile(self, top, name_fragment):
-        """File with given name fragment in directory tree starting at top."""
-        for root, dirs, files in os.walk(top, topdown=False):
-            for name in files:
-                if (name.endswith('.changes') and
-                    name.find(name_fragment) > -1):
-                    return os.path.join(root, name)
-        return None
-
-    def createSource(
-        self, archive, sourcename, version, distroseries=None,
-        new_version=None, component='main'):
+        return super(SoyuzTestPublisher, self).publishBinaryInArchive(
+            binarypackagerelease, archive, status=status, pocket=pocket,
+            scheduleddeletiondate=scheduleddeletiondate,
+            dateremoved=dateremoved, section_name=section, priority=priority)
+
+    def createSource(self, archive, sourcename, version, distroseries=None,
+                     new_version=None, component='main'):
         """Create source with meaningful '.changes' file."""
-        top = 'lib/lp/archiveuploader/tests/data/suite'
-        name_fragment = '%s_%s' % (sourcename, version)
-        changesfile_path = self._findChangesFile(top, name_fragment)
-
-        source = None
-
-        if changesfile_path is not None:
-            if new_version is None:
-                new_version = version
-            changesfile_content = ''
-            handle = open(changesfile_path, 'r')
-            try:
-                changesfile_content = handle.read()
-            finally:
-                handle.close()
-
-            source = self.getPubSource(
-                sourcename=sourcename, archive=archive, version=new_version,
-                changes_file_content=changesfile_content,
-                distroseries=distroseries, component=component)
-
-        return source
+        return super(SoyuzTestPublisher, self).createSource(
+            sourcename, version, archive=archive, distroseries=distroseries,
+            new_version=new_version, component_name=component)
 
 
 class TestNativePublishingBase(TestCaseWithFactory, SoyuzTestPublisher):


Follow ups