← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/archive-dependencies-unittest into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/archive-dependencies-unittest into lp:launchpad.

Commit message:
Convert archive-dependencies.txt to unit tests.  Make InvalidExternalDependencies have a slightly more sensible message in the process.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/archive-dependencies-unittest/+merge/323129

I needed to do this to make it less painful to change get_sources_list_for_building to talk to the keyserver, which I need in order to fix bug 1626739.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/archive-dependencies-unittest into lp:launchpad.
=== added file 'lib/lp/soyuz/adapters/tests/test_archivedependencies.py'
--- lib/lp/soyuz/adapters/tests/test_archivedependencies.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/adapters/tests/test_archivedependencies.py	2017-04-25 11:44:32 +0000
@@ -0,0 +1,517 @@
+# Copyright 2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test archive dependencies."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+
+from testtools.matchers import StartsWith
+import transaction
+from zope.component import getUtility
+
+from lp.registry.interfaces.distribution import IDistributionSet
+from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.services.log.logger import BufferLogger
+from lp.soyuz.adapters.archivedependencies import (
+    default_component_dependency_name,
+    default_pocket_dependency,
+    get_components_for_context,
+    get_primary_current_component,
+    get_sources_list_for_building,
+    pocket_dependencies,
+    )
+from lp.soyuz.enums import PackagePublishingStatus
+from lp.soyuz.interfaces.archive import IArchive
+from lp.soyuz.interfaces.component import IComponentSet
+from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import (
+    LaunchpadZopelessLayer,
+    ZopelessDatabaseLayer,
+    )
+
+
+class TestOgreModel(TestCaseWithFactory):
+    """Test ogre-model component handling.
+
+    The Ubuntu "ogre model" (cf. Shrek) ensures that build-dependencies are
+    consistent with the component in which the source is published.
+    """
+
+    layer = ZopelessDatabaseLayer
+
+    def setUpComponents(self, distroseries, component_names):
+        for component_name in component_names:
+            component = getUtility(IComponentSet)[component_name]
+            self.factory.makeComponentSelection(distroseries, component)
+
+    def assertComponentMap(self, expected, distroseries, pocket):
+        for component_name, expected_components in expected.items():
+            component = getUtility(IComponentSet)[component_name]
+            self.assertEqual(
+                expected_components,
+                get_components_for_context(component, distroseries, pocket))
+
+    def test_strict_supported_component_dependencies(self):
+        # In strict-supported-component-dependencies mode, a source
+        # published in main is only allowed to build-depend on binaries also
+        # published in main, while a source published in universe is allowed
+        # to build-depend on main and universe.
+        distroseries = self.factory.makeDistroSeries()
+        expected = {
+            "main": ["main"],
+            "restricted": ["main", "restricted"],
+            "universe": ["main", "universe"],
+            "multiverse": ["main", "restricted", "universe", "multiverse"],
+            "partner": ["partner"],
+            }
+        self.setUpComponents(distroseries, expected.keys())
+        self.assertComponentMap(
+            expected, distroseries, PackagePublishingPocket.RELEASE)
+
+    def test_lax_supported_component_dependencies(self):
+        # In lax-supported-component-dependencies mode, source packages in
+        # "supported" components (main and restricted) may additionally
+        # build-depend on binary packages in "unsupported" components
+        # (universe and multiverse).
+        distroseries = self.factory.makeDistroSeries()
+        distroseries.strict_supported_component_dependencies = False
+        expected = {
+            "main": ["main", "universe"],
+            "restricted": ["main", "restricted", "universe", "multiverse"],
+            "universe": ["main", "universe"],
+            "multiverse": ["main", "restricted", "universe", "multiverse"],
+            "partner": ["partner"],
+            }
+        self.setUpComponents(distroseries, expected.keys())
+        self.assertComponentMap(
+            expected, distroseries, PackagePublishingPocket.RELEASE)
+
+    def test_backports(self):
+        # Source packages in the BACKPORTS pocket are allowed to
+        # build-depend on binary packages in any component.  This avoids
+        # having to make potentially-invasive changes to accommodate
+        # backporting to stable series.
+        distroseries = self.factory.makeDistroSeries()
+        expected = {
+            "main": ["main", "restricted", "universe", "multiverse"],
+            "restricted": ["main", "restricted", "universe", "multiverse"],
+            "universe": ["main", "restricted", "universe", "multiverse"],
+            "multiverse": ["main", "restricted", "universe", "multiverse"],
+            "partner": ["main", "restricted", "universe", "multiverse"],
+            }
+        self.setUpComponents(distroseries, expected.keys())
+        self.assertComponentMap(
+            expected, distroseries, PackagePublishingPocket.BACKPORTS)
+
+
+class TestSourcesList(TestCaseWithFactory):
+    """Test sources.list contents for building, and related mechanisms."""
+
+    layer = LaunchpadZopelessLayer
+
+    ubuntu_components = [
+        "main", "restricted", "universe", "multiverse", "partner"]
+
+    def setUp(self):
+        super(TestSourcesList, self).setUp()
+        self.publisher = SoyuzTestPublisher()
+        self.ubuntu = getUtility(IDistributionSet).getByName("ubuntu")
+        self.hoary = self.ubuntu.getSeries("hoary")
+        self.publisher.addFakeChroots(self.hoary)
+        self.publisher.setUpDefaultDistroSeries(self.hoary)
+        for component_name in self.ubuntu_components:
+            component = getUtility(IComponentSet)[component_name]
+            if component not in self.hoary.components:
+                self.factory.makeComponentSelection(self.hoary, component)
+
+    def test_defaults(self):
+        # Non-primary archives by default use the Release, Security and
+        # Updates pockets from the primary archive, and all its available
+        # components.
+        self.assertEqual(
+            PackagePublishingPocket.UPDATES, default_pocket_dependency)
+        self.assertEqual("multiverse", default_component_dependency_name)
+        self.assertEqual(
+            (PackagePublishingPocket.RELEASE,
+             PackagePublishingPocket.SECURITY,
+             PackagePublishingPocket.UPDATES),
+            pocket_dependencies[default_pocket_dependency])
+
+    def makeArchive(self, publish_binary=False, **kwargs):
+        archive = self.factory.makeArchive(distribution=self.ubuntu, **kwargs)
+        if publish_binary:
+            self.publisher.getPubBinaries(
+                archive=archive, status=PackagePublishingStatus.PUBLISHED)
+        return archive
+
+    def makeBuild(self, **kwargs):
+        pub_source = self.publisher.getPubSource(**kwargs)
+        [build] = pub_source.createMissingBuilds()
+        return build
+
+    def assertPrimaryCurrentComponent(self, expected, build):
+        self.assertEqual(
+            expected,
+            get_primary_current_component(
+                build.archive, build.distro_series,
+                build.source_package_release.name).name)
+
+    def assertSourcesList(self, expected, build, **kwargs):
+        expected_lines = []
+        for archive_or_prefix, suffixes in expected:
+            if IArchive.providedBy(archive_or_prefix):
+                prefix = "deb %s " % archive_or_prefix.archive_url
+            else:
+                prefix = archive_or_prefix + " "
+            expected_lines.extend([prefix + suffix for suffix in suffixes])
+        sources_list = get_sources_list_for_building(
+            build, build.distro_arch_series, build.source_package_release.name,
+            **kwargs)
+        self.assertEqual(expected_lines, sources_list)
+
+    def test_ppa_with_no_binaries(self):
+        # If there are no published binaries in a PPA, only its primary
+        # archive dependencies need to be considered.
+        ppa = self.makeArchive()
+        build = self.makeBuild(archive=ppa)
+        self.assertEqual(
+            0, ppa.getAllPublishedBinaries(
+                distroarchseries=build.distro_arch_series,
+                status=PackagePublishingStatus.PUBLISHED).count())
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_ppa_with_binaries(self):
+        # If there are binaries published in a PPA, then the PPA is
+        # considered as well as its primary dependencies.
+        ppa = self.makeArchive(publish_binary=True)
+        build = self.makeBuild(archive=ppa)
+        self.assertSourcesList(
+            [(ppa, ["hoary main"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_dependent_ppa_with_no_binaries(self):
+        # A depended-upon PPA is not considered if it has no published
+        # binaries.
+        lower_ppa = self.makeArchive()
+        upper_ppa = self.makeArchive(publish_binary=True)
+        upper_ppa.addArchiveDependency(
+            lower_ppa, PackagePublishingPocket.RELEASE,
+            getUtility(IComponentSet)["main"])
+        build = self.makeBuild(archive=upper_ppa)
+        self.assertSourcesList(
+            [(upper_ppa, ["hoary main"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_dependent_ppa_with_binaries(self):
+        # A depended-upon PPA is considered if it has published binaries.
+        lower_ppa = self.makeArchive(publish_binary=True)
+        upper_ppa = self.makeArchive(publish_binary=True)
+        upper_ppa.addArchiveDependency(
+            lower_ppa, PackagePublishingPocket.RELEASE,
+            getUtility(IComponentSet)["main"])
+        build = self.makeBuild(archive=upper_ppa)
+        self.assertSourcesList(
+            [(upper_ppa, ["hoary main"]),
+             (lower_ppa, ["hoary main"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_lax_supported_component_dependencies(self):
+        # Dependencies for series with
+        # strict_supported_component_dependencies=False are reasonable.
+        # PPAs only have the "main" component.
+        lower_ppa = self.makeArchive(publish_binary=True)
+        upper_ppa = self.makeArchive(publish_binary=True)
+        upper_ppa.addArchiveDependency(
+            lower_ppa, PackagePublishingPocket.RELEASE,
+            getUtility(IComponentSet)["main"])
+        upper_ppa.addArchiveDependency(
+            self.ubuntu.main_archive, PackagePublishingPocket.UPDATES,
+            getUtility(IComponentSet)["restricted"])
+        build = self.makeBuild(archive=upper_ppa)
+        self.assertSourcesList(
+            [(upper_ppa, ["hoary main"]),
+             (lower_ppa, ["hoary main"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted",
+                 "hoary-security main restricted",
+                 "hoary-updates main restricted",
+                 ]),
+             ], build)
+        self.hoary.strict_supported_component_dependencies = False
+        transaction.commit()
+        self.assertSourcesList(
+            [(upper_ppa, ["hoary main"]),
+             (lower_ppa, ["hoary main"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_no_op_primary_archive_dependency(self):
+        # Overriding the default primary archive dependencies with exactly
+        # the same values has no effect.
+        ppa = self.makeArchive()
+        ppa.addArchiveDependency(
+            self.ubuntu.main_archive, PackagePublishingPocket.UPDATES,
+            getUtility(IComponentSet)["multiverse"])
+        build = self.makeBuild(archive=ppa)
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_primary_archive_dependency_security(self):
+        # The primary archive dependency can be modified to behave as an
+        # embargoed archive that builds security updates.  This is done by
+        # setting the SECURITY pocket dependencies (RELEASE and SECURITY)
+        # and following the component dependencies of the component where
+        # the source was last published in the primary archive.
+        ppa = self.makeArchive()
+        ppa.addArchiveDependency(
+            self.ubuntu.main_archive, PackagePublishingPocket.SECURITY)
+        build = self.makeBuild(archive=ppa)
+        self.assertPrimaryCurrentComponent("universe", build)
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, [
+                 "hoary main universe",
+                 "hoary-security main universe",
+                 ]),
+             ], build)
+        self.publisher.getPubSource(
+            sourcename="with-ancestry", version="1.0",
+            archive=self.ubuntu.main_archive)
+        [build_with_ancestry] = self.publisher.getPubSource(
+            sourcename="with-ancestry", version="1.1",
+            archive=ppa).createMissingBuilds()
+        self.assertPrimaryCurrentComponent("main", build_with_ancestry)
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, [
+                 "hoary main",
+                 "hoary-security main",
+                 ]),
+             ], build_with_ancestry)
+
+    def test_primary_archive_dependency_release(self):
+        # The primary archive dependency can be modified to behave as a
+        # pristine build environment based only on what was included in the
+        # original release of the corresponding series.
+        ppa = self.makeArchive()
+        ppa.addArchiveDependency(
+            self.ubuntu.main_archive, PackagePublishingPocket.RELEASE,
+            getUtility(IComponentSet)["restricted"])
+        build = self.makeBuild(archive=ppa)
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, ["hoary main restricted"])], build)
+
+    def test_primary_archive_dependency_proposed(self):
+        # The primary archive dependency can be modified to extend the build
+        # environment for PROPOSED.
+        ppa = self.makeArchive()
+        ppa.addArchiveDependency(
+            self.ubuntu.main_archive, PackagePublishingPocket.PROPOSED,
+            getUtility(IComponentSet)["multiverse"])
+        build = self.makeBuild(archive=ppa)
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 "hoary-proposed main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_primary_archive_dependency_backports(self):
+        # The primary archive dependency can be modified to extend the build
+        # environment for PROPOSED.
+        ppa = self.makeArchive()
+        ppa.addArchiveDependency(
+            self.ubuntu.main_archive, PackagePublishingPocket.BACKPORTS,
+            getUtility(IComponentSet)["multiverse"])
+        build = self.makeBuild(archive=ppa)
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 "hoary-backports main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_partner(self):
+        # Similarly to what happens with PPA builds, partner builds may
+        # depend on any component in the primary archive.  This behaviour
+        # allows scenarios where partner packages may use other
+        # restricted/non-free applications from multiverse, and also other
+        # partner applications.
+        primary, partner = self.ubuntu.all_distro_archives
+        self.publisher.getPubBinaries(
+            archive=partner, component="partner",
+            status=PackagePublishingStatus.PUBLISHED)
+        build = self.makeBuild(archive=partner, component="partner")
+        self.assertSourcesList(
+            [(partner, ["hoary partner"]),
+             (primary, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_partner_proposed(self):
+        # The partner archive's PROPOSED pocket builds against itself, but
+        # still uses the default UPDATES dependency for the primary archive
+        # unless overridden by ArchiveDependency.
+        primary, partner = self.ubuntu.all_distro_archives
+        self.publisher.getPubBinaries(
+            archive=partner, component="partner",
+            status=PackagePublishingStatus.PUBLISHED)
+        self.publisher.getPubBinaries(
+            archive=partner, component="partner",
+            status=PackagePublishingStatus.PUBLISHED,
+            pocket=PackagePublishingPocket.PROPOSED)
+        build = self.makeBuild(
+            archive=partner, component="partner",
+            pocket=PackagePublishingPocket.PROPOSED)
+        self.assertSourcesList(
+            [(partner, [
+                 "hoary partner",
+                 "hoary-proposed partner",
+                 ]),
+             (primary, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_archive_external_dependencies(self):
+        # An archive can be manually given additional external dependencies.
+        # If present, "%(series)s" is replaced with the series name for the
+        # build being dispatched.
+        ppa = self.makeArchive(publish_binary=True)
+        ppa.external_dependencies = (
+            "deb http://user:pass@repository zoing everything\n"
+            "deb http://user:pass@repository %(series)s public private\n"
+            "deb http://user:pass@repository %(series)s-extra public")
+        build = self.makeBuild(archive=ppa)
+        self.assertSourcesList(
+            [(ppa, ["hoary main"]),
+             ("deb http://user:pass@repository";, [
+                 "zoing everything",
+                 "hoary public private",
+                 "hoary-extra public",
+                 ]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_build_external_dependencies(self):
+        # A single build can be manually given additional external
+        # dependencies.
+        ppa = self.makeArchive(publish_binary=True)
+        build = self.makeBuild(archive=ppa)
+        build.api_external_dependencies = (
+            "deb http://user:pass@repository foo bar")
+        self.assertSourcesList(
+            [(ppa, ["hoary main"]),
+             ("deb http://user:pass@repository";, ["foo bar"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build)
+
+    def test_build_tools(self):
+        # We can force an extra build tools line to be added to
+        # sources.list, which is useful for specialised build types.
+        ppa = self.makeArchive(publish_binary=True)
+        build = self.makeBuild(archive=ppa)
+        self.assertSourcesList(
+            [(ppa, ["hoary main"]),
+             ("deb http://example.org";, ["hoary main"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ], build, tools_source="deb http://example.org %(series)s main")
+
+    def test_build_tools_bad_formatting(self):
+        # If tools_source is badly formatted, we log the error but don't
+        # blow up.  (Note the missing "s" at the end of "%(series)".)
+        ppa = self.makeArchive(publish_binary=True)
+        build = self.makeBuild(archive=ppa)
+        logger = BufferLogger()
+        self.assertSourcesList(
+            [(ppa, ["hoary main"]),
+             (self.ubuntu.main_archive, [
+                 "hoary main restricted universe multiverse",
+                 "hoary-security main restricted universe multiverse",
+                 "hoary-updates main restricted universe multiverse",
+                 ]),
+             ],
+            build, tools_source="deb http://example.org %(series) main",
+            logger=logger)
+        self.assertThat(logger.getLogBuffer(), StartsWith(
+            "ERROR Exception processing build tools sources.list entry:\n"))
+
+    def test_overlay(self):
+        # An overlay distroseries is a derived distribution which works like
+        # a PPA.  This means that the parent's details gets added to the
+        # sources.list passed to the builders.
+        depdistro = self.factory.makeDistribution(
+            "depdistro", publish_base_url="http://archive.launchpad.dev/";)
+        depseries = self.factory.makeDistroSeries(
+            distribution=depdistro, name="depseries")
+        self.factory.makeDistroArchSeries(
+            distroseries=depseries, architecturetag="i386")
+        self.publisher.addFakeChroots(depseries)
+        for component_name in self.ubuntu_components:
+            component = getUtility(IComponentSet)[component_name]
+            self.factory.makeComponentSelection(depseries, component)
+        self.factory.makeDistroSeriesParent(
+            derived_series=self.hoary, parent_series=depseries,
+            initialized=True, is_overlay=True,
+            pocket=PackagePublishingPocket.SECURITY,
+            component=getUtility(IComponentSet)["universe"])
+        build = self.makeBuild()
+        self.assertSourcesList(
+            [(self.ubuntu.main_archive, ["hoary main"]),
+             (depdistro.main_archive, [
+                 "depseries main universe",
+                 "depseries-security main universe",
+                 ]),
+             ], build)

=== removed file 'lib/lp/soyuz/doc/archive-dependencies.txt'
--- lib/lp/soyuz/doc/archive-dependencies.txt	2016-04-07 00:04:42 +0000
+++ lib/lp/soyuz/doc/archive-dependencies.txt	1970-01-01 00:00:00 +0000
@@ -1,637 +0,0 @@
-= Archive dependencies =
-
-`ArchiveDependencies` class models archive dependencies mechanics and
-is used to provided the contents of 'sources_list' file used to build
-sources in the given IBuildQueue context.
-
-
-== Testing scenario setup ==
-
-We use `SoyuzTestPublisher` to generate a source publications and
-build candidates for ubuntu/hoary.
-
-    >>> from lp.registry.interfaces.distribution import IDistributionSet
-    >>> from lp.soyuz.interfaces.component import IComponentSet
-    >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
-    >>> from lp.testing import login
-
-    >>> login('foo.bar@xxxxxxxxxxxxx')
-
-    >>> test_publisher = SoyuzTestPublisher()
-
-    >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
-    >>> hoary = ubuntu.getSeries('hoary')
-
-    >>> test_publisher.addFakeChroots(hoary)
-    >>> _ = test_publisher.setUpDefaultDistroSeries(hoary)
-
-    >>> ubuntu_components = [
-    ...     'main', 'restricted', 'universe', 'multiverse', 'partner']
-    >>> for component_name in ubuntu_components:
-    ...     component = getUtility(IComponentSet)[component_name]
-    ...     if component not in hoary.components:
-    ...         _ = factory.makeComponentSelection(hoary, component)
-
-
-== Static dependency maps ==
-
-`pocket_dependencies` contains a static map of the default ubuntu
-pocket dependencies.
-
-    >>> from lp.soyuz.adapters.archivedependencies import pocket_dependencies
-
-    >>> def show_pocket_deps():
-    ...     print "Pocket    |   Dependencies"
-    ...     print "----------+---------------"
-    ...     for (key, value) in sorted(pocket_dependencies.items()):
-    ...         print "%7s |" % (key.name,),
-    ...         for pocket in value:
-    ...             print pocket.name,
-    ...         print
-
-    >>> show_pocket_deps()
-    Pocket    |   Dependencies
-    ----------+---------------
-      RELEASE | RELEASE
-     SECURITY | RELEASE SECURITY
-      UPDATES | RELEASE SECURITY UPDATES
-     PROPOSED | RELEASE SECURITY UPDATES PROPOSED
-    BACKPORTS | RELEASE SECURITY UPDATES BACKPORTS
-
-
-== 'Ogre' components ==
-
-The ubuntu 'ogre-model' ensures that build dependencies are
-consistently spread according to the source target component, i.e. a
-source published in 'main' component is only allowed to depend on
-binaries also published in 'main', on the other hand a source
-published in 'universe' is allowed to depend on binaries published in
-'main' and 'universe' components.
-
-A proper name for this "model" would be 'cross-component-dependency'.
-
-    >>> from lp.services.database.sqlbase import flush_database_caches
-
-    >>> from zope.security.proxy import removeSecurityProxy
-    >>> from lp.registry.interfaces.pocket import (
-    ...      PackagePublishingPocket)
-    >>> from lp.soyuz.adapters.archivedependencies import (
-    ...     get_components_for_context)
-    >>> from lp.soyuz.enums import ArchivePurpose
-
-    >>> def testOgreComponents(build):
-    ...     print " Component | Ogre-Model"
-    ...     print "-----------+---------------"
-    ...     for component in ubuntu_components:
-    ...         component = getUtility(IComponentSet)[component]
-    ...         npub = removeSecurityProxy(build.current_source_publication)
-    ...         npub.component = component
-    ...         flush_database_caches()
-    ...         components_term = " ".join(get_components_for_context(
-    ...             build.current_component, build.distro_series,
-    ...             build.pocket))
-    ...         print '%10s | %s' % (build.current_component.name,
-    ...                              components_term)
-
-    >>> archive = factory.makeArchive(
-    ...     distribution=ubuntu, purpose=ArchivePurpose.PRIMARY)
-    >>> ogre_build = factory.makeBinaryPackageBuild(
-    ...     distroarchseries=hoary.architectures[0], archive=archive,
-    ...     pocket=PackagePublishingPocket.RELEASE)
-    >>> testOgreComponents(ogre_build)
-     Component | Ogre-Model
-    -----------+---------------
-          main | main
-    restricted | main restricted
-      universe | main universe
-    multiverse | main restricted universe multiverse
-       partner | partner
-
-Series with strict_supported_component_dependencies=False additionally allow
-source packages in 'supported' components (main and restricted) to
-build-depend on binary packages in 'unsupported' components (universe and
-multiverse).
-
-    >>> from lp.buildmaster.interfaces.processor import IProcessorSet
-
-    >>> lax_distroseries = factory.makeDistroSeries(
-    ...     distribution=archive.distribution, name='lax')
-    >>> lax_distroseries.strict_supported_component_dependencies = False
-    >>> lax_distroarchseries = factory.makeDistroArchSeries(
-    ...     distroseries=lax_distroseries, architecturetag='i386',
-    ...     processor=getUtility(IProcessorSet).getByName('386'))
-    >>> test_publisher.addFakeChroots(lax_distroseries)
-    >>> for component_name in ubuntu_components:
-    ...     component = getUtility(IComponentSet)[component_name]
-    ...     _ = factory.makeComponentSelection(lax_distroseries, component)
-    >>> lax_ogre_build = factory.makeBinaryPackageBuild(
-    ...     distroarchseries=lax_distroarchseries, archive=archive,
-    ...     pocket=PackagePublishingPocket.RELEASE)
-    >>> testOgreComponents(lax_ogre_build)
-     Component | Ogre-Model
-    -----------+---------------
-          main | main universe
-    restricted | main restricted universe multiverse
-      universe | main universe
-    multiverse | main restricted universe multiverse
-       partner | partner
-
-As fixed for bug #198936, builds for the BACKPORTS pocket are allowed
-to use any component available, independently of the component they
-are currently published. This special-case is important because it
-avoids changes to accommodate the backported source in the already
-released series.
-
-Ultimately, it means that a build targeted to the BACKPORTS pocket
-will behave as if it were published in the multiverse component,
-despite the component it is actually published in.
-
-    >>> back_build = factory.makeBinaryPackageBuild(
-    ...     distroarchseries=hoary.architectures[0],
-    ...     archive=archive, pocket=PackagePublishingPocket.BACKPORTS)
-    >>> testOgreComponents(back_build)
-     Component | Ogre-Model
-    -----------+---------------
-          main | main restricted universe multiverse
-    restricted | main restricted universe multiverse
-      universe | main restricted universe multiverse
-    multiverse | main restricted universe multiverse
-       partner | main restricted universe multiverse
-
-
-== Sources.list contents for building ==
-
-We will use Celso's PPA for testing these mechanisms.
-
-    >>> from lp.registry.interfaces.person import IPersonSet
-    >>> cprov = getUtility(IPersonSet).getByName('cprov')
-    >>> print cprov.archive.displayname
-    PPA for Celso Providelo
-
-Non-primary archives by default use primary Release, Security and Updates
-pockets and all its available components.
-
-    >>> from lp.soyuz.adapters.archivedependencies import (
-    ...     default_component_dependency_name,  default_pocket_dependency)
-
-    >>> print default_pocket_dependency
-    Updates
-
-    >>> print default_component_dependency_name
-    multiverse
-
-The default values get applied to their corresponding dependency maps
-and then will expand to distinct values that will be used to produce
-the building 'sources_list' contents.
-
-    >>> for pocket in pocket_dependencies[default_pocket_dependency]:
-    ...     print pocket
-    Release
-    Security
-    Updates
-
-    >>> for component_name in get_components_for_context(
-    ...         getUtility(IComponentSet)[default_component_dependency_name],
-    ...         hoary, pocket):
-    ...     print component_name
-    main
-    restricted
-    universe
-    multiverse
-
-We will create a testing source publication and probe its build
-environment.
-
-    >>> pub_source = test_publisher.getPubSource(
-    ...     version='1.1', archive=cprov.archive)
-    >>> [a_build] = pub_source.createMissingBuilds()
-
-Now we can verify if get_sources_list_for_building() method returns the
-expected content for building the just-created source.
-
-    >>> from lp.soyuz.adapters.archivedependencies import (
-    ...     get_sources_list_for_building)
-
-    >>> def print_building_sources_list(candidate):
-    ...     sources_list = get_sources_list_for_building(
-    ...     candidate, candidate.distro_arch_series,
-    ...     candidate.source_package_release.name)
-    ...     for line in sources_list:
-    ...         print line
-
-Note that only the default ubuntu dependencies for a public PPA will be
-considered when building the source candidate. That's because there is
-no binary published in Celso's PPA hoary/i386, so there is
-no need to request the builder to load its archive indexes.
-
-    >>> from lp.soyuz.enums import PackagePublishingStatus
-
-    >>> cprov.archive.getAllPublishedBinaries(
-    ...      distroarchseries=a_build.distro_arch_series,
-    ...      status=PackagePublishingStatus.PUBLISHED).count()
-    0
-
-    >>> print_building_sources_list(a_build)
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-Once we publish a test binary in Celso's PPA hoary/i386,
-this archive becomes relevant for building, and thus listed in the
-returned 'sources_list' content.
-
-    >>> pub_binaries = test_publisher.getPubBinaries(
-    ...     binaryname='dep-bin', archive=cprov.archive,
-    ...     status=PackagePublishingStatus.PUBLISHED)
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-Similarly, unpopulated PPA dependencies are *not* listed in the building
-'sources_list'.
-
-    >>> mark = getUtility(IPersonSet).getByName('mark')
-    >>> archive_dependency = cprov.archive.addArchiveDependency(
-    ...     mark.archive, PackagePublishingPocket.RELEASE,
-    ...     getUtility(IComponentSet)['main'])
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-But *populated* PPA dependencies *are* listed in the building 'sources_list'.
-
-    >>> pub_binaries = test_publisher.getPubBinaries(
-    ...     binaryname='dep-bin', archive=mark.archive,
-    ...     status=PackagePublishingStatus.PUBLISHED)
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ppa.launchpad.dev/mark/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-Dependencies for series with strict_supported_component_dependencies=False
-are reasonable too.  PPAs only have the 'main' component.
-
-    >>> lax_pub_source = test_publisher.getPubSource(
-    ...     version='1.2', distroseries=lax_distroseries,
-    ...     archive=cprov.archive)
-    >>> [lax_build] = lax_pub_source.createMissingBuilds()
-    >>> _ = test_publisher.getPubBinaries(
-    ...     binaryname='dep-bin', distroseries=lax_distroseries,
-    ...     archive=cprov.archive, status=PackagePublishingStatus.PUBLISHED)
-    >>> _ = test_publisher.getPubBinaries(
-    ...     binaryname='dep-bin', distroseries=lax_distroseries,
-    ...     archive=mark.archive, status=PackagePublishingStatus.PUBLISHED)
-    >>> print_building_sources_list(lax_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu lax main
-    deb http://ppa.launchpad.dev/mark/ppa/ubuntu lax main
-    deb http://archive.launchpad.dev/ubuntu lax
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu lax-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu lax-updates
-        main restricted universe multiverse
-
-    >>> cprov.archive.removeArchiveDependency(mark.archive)
-
-What when we supply invalid dependencies, an exception is raised.
-
-    >>> cprov.archive.external_dependencies = (
-    ...     "Malformed format string here --> %(series)")
-    Traceback (most recent call last):
-    InvalidExternalDependencies: (InvalidExternalDependencies(...),
-    "Invalid external dependencies:\nMalformed format string here
-    --> %(series): Must start with 'deb'\nMalformed format string here
-    --> %(series): Invalid URL\n")
-
-
-== Overriding default primary archive dependencies ==
-
-Despite being private or public, default primary archive dependencies
-can be overridden by simply creating a `ArchiveDependency`record
-targeted to the primary archive.
-
-The 'pocket' and 'component' dependency attributes can be adjusted to
-produce the desired build behaviour.
-
-By default, public PPAs depend on all of the pocket dependencies of
-UPDATES, and all of the primary archive's 'multiverse' component
-dependencies.
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-The default build behaviour will remain unchanged when we override the
-default primary archive dependencies with exactly the same values.
-
-    >>> default_dependency = cprov.archive.addArchiveDependency(
-    ...     ubuntu.main_archive, PackagePublishingPocket.UPDATES,
-    ...     getUtility(IComponentSet)['multiverse'])
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-    >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
-
-The dependency can be modified to behave as an embargoed archive that
-builds security updates. This is done by setting the SECURITY pocket
-dependencies (RELEASE and SECURITY) and following the component
-dependencies of the component where the source was last published in
-the primary archive.
-
-    >>> security_dependency = cprov.archive.addArchiveDependency(
-    ...     ubuntu.main_archive, PackagePublishingPocket.SECURITY)
-
-    >>> from lp.soyuz.adapters.archivedependencies import (
-    ...     get_primary_current_component)
-
-    >>> print get_primary_current_component(a_build.archive,
-    ...     a_build.distro_series, a_build.source_package_release.name).name
-    universe
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main universe
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main universe
-
-    >>> _ = test_publisher.getPubSource(
-    ...     sourcename='with-ancestry', version='1.0',
-    ...     archive=ubuntu.main_archive)
-    >>> [build_with_ancestry] = test_publisher.getPubSource(
-    ...     sourcename='with-ancestry', version='1.1',
-    ...     archive=cprov.archive).createMissingBuilds()
-    >>> print get_primary_current_component(
-    ...     build_with_ancestry.archive, build_with_ancestry.distro_series,
-    ...     build_with_ancestry.source_package_release.name).name
-    main
-    >>> print_building_sources_list(build_with_ancestry)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary-security main
-
-    >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
-
-It's also possible to modify the PPA to act as a super-free and
-pristine build environment based only on what was included in the
-original ubuntu release.
-
-    >>> release_dependency = cprov.archive.addArchiveDependency(
-    ...     ubuntu.main_archive, PackagePublishingPocket.RELEASE,
-    ...     getUtility(IComponentSet)['restricted'])
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary main restricted
-
-    >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
-
-The PPA can also be configured to extend the ubuntu PROPOSED build
-environment.
-
-    >>> proposed_dependency = cprov.archive.addArchiveDependency(
-    ...     ubuntu.main_archive, PackagePublishingPocket.PROPOSED,
-    ...     getUtility(IComponentSet)['multiverse'])
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-proposed
-        main restricted universe multiverse
-
-    >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
-
-Similarly an extension of the BACKPORTS environment can be set.
-
-    >>> backports_dependency = cprov.archive.addArchiveDependency(
-    ...     ubuntu.main_archive, PackagePublishingPocket.BACKPORTS,
-    ...     getUtility(IComponentSet)['multiverse'])
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-backports
-        main restricted universe multiverse
-
-    >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
-
-
-== Partner archive builds ==
-
-Similarly to what happens with PPA builds, PARTNER builds may depend
-on any Ubuntu component in the PRIMARY archive. This behaviour allows
-scenarios where partner packages may use other restricted/non-free
-applications from 'multiverse', for instance 'sun-java', and also
-other partner applications by default.
-
-    # Populate the ubuntutest PARTNER archive with one built and one
-    # pending build source.
-    >>> primary, partner = ubuntu.all_distro_archives
-    >>> unused_source = test_publisher.getPubSource(
-    ...     archive=partner, component='partner')
-    >>> unused = test_publisher.getPubBinaries(
-    ...     pub_source=unused_source,
-    ...     status=PackagePublishingStatus.PUBLISHED)
-    >>> pub_source = test_publisher.getPubSource(
-    ...     version='1.2', archive=partner, component='partner')
-    >>> [partner_build] = pub_source.createMissingBuilds()
-
-    >>> print_building_sources_list(partner_build)
-    deb http://archive.launchpad.dev/ubuntu-partner hoary partner
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-PARTNER's PROPOSED pocket builds against itself, but still uses the
-default UPDATES dependency for PRIMARY unless overridden by
-ArchiveDependency.
-
-    >>> proposed_source = test_publisher.getPubSource(
-    ...     version='1.2', archive=partner, component='partner',
-    ...     pocket=PackagePublishingPocket.PROPOSED)
-    >>> unused = test_publisher.getPubBinaries(
-    ...     archive=partner, distroseries=proposed_source.distroseries,
-    ...     pocket=PackagePublishingPocket.PROPOSED,
-    ...     status=PackagePublishingStatus.PUBLISHED)
-    >>> [partner_proposed_build] = proposed_source.createMissingBuilds()
-
-    >>> print_building_sources_list(partner_proposed_build)
-    deb http://archive.launchpad.dev/ubuntu-partner hoary partner
-    deb http://archive.launchpad.dev/ubuntu-partner hoary-proposed partner
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-== External build dependencies ==
-
-Via an administrator change, any PPA hosted in launchpad can be
-assigned to one or more 'external' build dependencies additionally to
-the internal ones.
-
-There is a column on IArchive called 'external_dependencies' which can be set
-for any hosted PPA.  It is a string listing the comma-separated external
-dependencies in the debian sources_list format.
-
- deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]
-
-The '%(series)s' part is optional and will be replaced on-the-fly with
-the series name for the build record being dispatched.
-
-We will create some dependencies for Celso's PPA.
-
-    >>> cprov.archive.external_dependencies = (
-    ... "deb http://user:pass@repository zoing everything\n"
-    ... "deb http://user:pass@repository %(series)s public private\n"
-    ... "deb http://user:pass@repository %(series)s-extra public")
-
-Now builds in Celso's PPA will use the external dependencies.
-
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://user:pass@repository zoing everything
-    deb http://user:pass@repository hoary public private
-    deb http://user:pass@repository hoary-extra public
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-    >>> cprov.archive.external_dependencies = None
-
-We can also set external dependencies for a single build.
-
-    >>> a_build.api_external_dependencies = (
-    ...     u"deb http://user:pass@repository foo bar")
-    >>> print_building_sources_list(a_build)
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://user:pass@repository foo bar
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-    >>> a_build.api_external_dependencies = None
-
-
-== Build tools sources.list entries ==
-
-We can force an extra build tools line to be added to the sources.list,
-which is useful for specialised build types.
-
-    >>> for line in get_sources_list_for_building(
-    ...         a_build, a_build.distro_arch_series,
-    ...         a_build.source_package_release.name,
-    ...         tools_source="deb http://example.org %(series)s main"):
-    ...     print line
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://example.org hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-
-If tools_source is badly formatted, we log the error but don't blow up.
-(Note the missing "s" at the end of "%(series)".)
-
-    >>> from lp.services.log.logger import BufferLogger
-    >>> logger = BufferLogger()
-    >>> for line in get_sources_list_for_building(
-    ...         a_build, a_build.distro_arch_series,
-    ...         a_build.source_package_release.name,
-    ...         tools_source="deb http://example.org %(series) main",
-    ...         logger=logger):
-    ...     print line
-    deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://archive.launchpad.dev/ubuntu hoary
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://archive.launchpad.dev/ubuntu hoary-updates
-        main restricted universe multiverse
-    >>> print logger.getLogBuffer()
-    ERROR Exception processing build tools sources.list entry:
-    ...
-
-
-== Overlays ==
-
-An overlay distroseries is a derived distribution which works like a PPA.
-This means that the parent's details gets added to the sources.list passed to
-the builders.
-
-    >>> depdistro = factory.makeDistribution('depdistro',
-    ...     publish_base_url=u'http://archive.launchpad.dev/')
-    >>> depseries = factory.makeDistroSeries(
-    ...     name='depseries', distribution=depdistro)
-    >>> deparchseries = factory.makeDistroArchSeries(
-    ...     distroseries = depseries, architecturetag = 'i386')
-    >>> test_publisher.addFakeChroots(depseries)
-    >>> for component_name in ubuntu_components:
-    ...     component = getUtility(IComponentSet)[component_name]
-    ...     _ = factory.makeComponentSelection(depseries, component)
-    >>> universe_component = getUtility(IComponentSet)['universe']
-    >>> dsp = factory.makeDistroSeriesParent(
-    ...     derived_series=hoary, parent_series=depseries,
-    ...     initialized=True, is_overlay=True,
-    ...     pocket=PackagePublishingPocket.SECURITY,
-    ...     component=universe_component)
-    >>> pub_source = test_publisher.getPubSource(
-    ...     version='1.1', archive=hoary.main_archive)
-    >>> [hoary_build] = pub_source.createMissingBuilds()
-    >>> print_building_sources_list(hoary_build)
-    deb http://archive.launchpad.dev/ubuntu hoary main
-    deb http://archive.launchpad.dev/depdistro depseries main universe
-    deb http://archive.launchpad.dev/depdistro depseries-security
-        main universe

=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py	2016-11-14 19:55:07 +0000
+++ lib/lp/soyuz/interfaces/archive.py	2017-04-25 11:44:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Archive interfaces."""
@@ -306,7 +306,7 @@
 
     def __init__(self, errors):
         error_msg = 'Invalid external dependencies:\n%s\n' % '\n'.join(errors)
-        super(Exception, self).__init__(self, error_msg)
+        super(Exception, self).__init__(error_msg)
         self.errors = errors
 
 

=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py	2016-11-07 16:42:23 +0000
+++ lib/lp/soyuz/tests/test_archive.py	2017-04-25 11:44:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test Archive features."""
@@ -78,6 +78,7 @@
     DuplicateTokenName,
     IArchiveSet,
     InsufficientUploadRights,
+    InvalidExternalDependencies,
     InvalidPocketForPartnerArchive,
     InvalidPocketForPPA,
     NAMED_AUTH_TOKEN_FEATURE_FLAG,
@@ -1767,6 +1768,18 @@
                 "person-name-.*/dependency/ubuntu distroseries-.* main")
             self.assertThat(sources_list[0], matches)
 
+    def test_invalid_external_dependencies(self):
+        """Trying to set invalid external dependencies raises an exception."""
+        ppa = self.factory.makeArchive()
+        self.assertRaisesWithContent(
+            InvalidExternalDependencies,
+            "Invalid external dependencies:\n"
+            "Malformed format string here --> %(series): "
+            "Must start with 'deb'\n"
+            "Malformed format string here --> %(series): Invalid URL\n",
+            setattr, ppa, "external_dependencies",
+            "Malformed format string here --> %(series)")
+
 
 class TestFindDepCandidates(TestCaseWithFactory):
     """Tests for Archive.findDepCandidates."""

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2016-12-02 12:04:11 +0000
+++ lib/lp/testing/factory.py	2017-04-25 11:44:32 +0000
@@ -2,7 +2,7 @@
 # NOTE: The first line above must stay first; do not move the copyright
 # notice to the top.  See http://www.python.org/dev/peps/pep-0263/.
 #
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Testing infrastructure for the Launchpad application.
@@ -268,7 +268,10 @@
     )
 from lp.services.oauth.interfaces import IOAuthConsumerSet
 from lp.services.openid.model.openididentifier import OpenIdIdentifier
-from lp.services.propertycache import clear_property_cache
+from lp.services.propertycache import (
+    clear_property_cache,
+    get_property_cache,
+    )
 from lp.services.temporaryblobstorage.interfaces import (
     ITemporaryStorageManager,
     )
@@ -2832,8 +2835,10 @@
         if not IComponent.providedBy(component):
             component = self.makeComponent(component)
 
-        return ComponentSelection(
+        selection = ComponentSelection(
             distroseries=distroseries, component=component)
+        del get_property_cache(distroseries).components
+        return selection
 
     def makeArchive(self, distribution=None, owner=None, name=None,
                     purpose=None, enabled=True, private=False,


Follow ups