Description: Fix bugs associated with packaging meta-data units
 This patch contains the following upstream changes, squashed
 and cherry-picked on top of the 0.22 release. They are:
 - https://bugs.launchpad.net/plainbox/+bug/1477095
 - https://bugs.launchpad.net/plainbox/+bug/1476678
 - https://bugs.launchpad.net/plainbox/+bug/1477131
 The relevant changes are linked to each bug. In short, they fix bugs that
 prevent the use of packaging meta-data units to generate Debian dependencies,
 suggestions and recommendations to work. This is a part of a long-term plan to
 simplify packaging for providers by moving bulk of the burden upstream.
Origin: upstream
Last-Update: 2015-07-22

--- plainbox-0.22.orig/plainbox/impl/unit/packaging.py
+++ plainbox-0.22/plainbox/impl/unit/packaging.py
@@ -114,6 +114,7 @@ not documented.
 import abc
 import errno
 import logging
+import re
 import sys
 
 from plainbox.i18n import gettext as _
@@ -142,13 +143,13 @@ class PackagingMetaDataUnit(Unit):
 
     @property
     def os_id(self):
-        """ Identifier of the operating system.  """
-        return self.get_record_value('os-id')
+        """Identifier of the operating system."""
+        return self.get_record_value(self.Meta.fields.os_id)
 
     @property
     def os_version_id(self):
-        """ Version of the operating system.  """
-        return self.get_record_value('os-version')
+        """Version of the operating system."""
+        return self.get_record_value(self.Meta.fields.os_version_id)
 
     class Meta:
 
@@ -156,7 +157,7 @@ class PackagingMetaDataUnit(Unit):
 
         class fields(SymbolDef):
 
-            """ Symbols for each field of a packaging meta-data unit. """
+            """Symbols for each field of a packaging meta-data unit."""
 
             os_id = 'os-id'
             os_version_id = 'os-version-id'
@@ -174,70 +175,144 @@ class PackagingMetaDataUnit(Unit):
 
 class PackagingDriverError(Exception):
 
-    """ Base for all packaging driver exceptions. """
+    """Base for all packaging driver exceptions."""
 
 
 class NoPackagingDetected(PackagingDriverError):
 
-    """ Exception raised when packaging cannot be found. """
+    """Exception raised when packaging cannot be found."""
 
 
 class NoApplicableBinaryPackages(PackagingDriverError):
 
-    """ Exception raised when no applicable binary packages are found. """
+    """Exception raised when no applicable binary packages are found."""
 
 
 class IPackagingDriver(metaclass=abc.ABCMeta):
 
-    """ Interface for all packaging drivers. """
+    """Interface for all packaging drivers."""
 
     @abc.abstractmethod
     def __init__(self, os_release: 'Dict[str, str]'):
-        pass
+        """
+        Initialize the packaging driver.
+
+        :param os_release:
+            The dictionary that represents the contents of the
+            ``/etc/os-release`` file. Using this file the packaging driver can
+            infer information about the target operating system that the
+            packaging will be built for.
+
+            This assumes that packages are built natively, not through a
+            cross-compiler of some sort where the target distribution is
+            different from the host distribution.
+        """
 
     @abc.abstractmethod
     def inspect_provider(self, provider: 'Provider1') -> None:
-        pass
+        """
+        Inspect a provider looking for packaging meta-data.
+
+        :param provider:
+            A provider object to look at. All of the packaging meta-data units
+            there are inspected, if they are applicable (see
+            :meth:`is_applicable()`. Information from applicable units is
+            collected using the :meth:`collect()` method.
+        """
 
     @abc.abstractmethod
     def is_applicable(self, unit: Unit) -> bool:
-        pass
+        """
+        Check if the given unit is applicable for collecting.
+
+        :param unit:
+            The unit to inspect. This doesn't have to be a packaging meta-data
+            unit. In fact, all units are checked with this method.
+        :returns:
+            True if the unit is applicable for collection.
+
+        Packaging meta-data units that have certain properties are applicable.
+        Refer to the documentation of the module for details.
+        """
 
     @abc.abstractmethod
     def collect(self, unit: Unit) -> None:
-        pass
+        """
+        Collect information from the given applicable unit.
+
+        :param unit:
+            The unit to collect information from. This is usually expressed as
+            additional fields that are specific to the type of native packaging
+            for the system.
+
+        Collected information is recorded and made available for the
+        :meth:`modify_packaging_tree()` method later.
+        """
 
     @abc.abstractmethod
     def inspect_packaging(self) -> None:
-        pass
+        """
+        Inspect the packaging tree for additional information.
+
+        :raises NoPackagingDetected:
+            Exception raised when packaging cannot be found.
+        :raises NoApplicableBinaryPackages:
+            Exception raised when no applicable binary packages are found.
+
+        This method looks at the packaging system located in the current
+        directory. This can be the ``debian/`` directory, a particular
+        ``.spec`` file or anything else. Information obtained from the package
+        is used to infer additional properties that can aid in the packaging
+        process.
+        """
 
     @abc.abstractmethod
     def modify_packaging_tree(self) -> None:
-        pass
+        """
+        Modify the packaging tree with information from the packaging units.
+
+        This method uses all of the available information collected from
+        particular packaging meta-data units and from the native packaging to
+        modify the packaging. Additional dependencies may be injected in
+        appropriate places. Please refer to the documentation specific to your
+        packaging system for details.
+        """
 
 
 def _strategy_id_version(unit, os_release):
     _logger.debug(_("Considering strategy: %s"),
-                  _("os-id == ID and os-version-id == VERSION"))
-    return (unit.os_id == os_release['ID']
-            and unit.os_version_id == os_release['VERSION_ID'])
+                  _("os-id == ID and os-version-id == VERSION_ID"))
+    return (
+        'ID' in os_release
+        and unit.os_id == os_release['ID']
+        and 'VERSION_ID' in os_release
+        and unit.os_version_id == os_release['VERSION_ID']
+    )
 
 
 def _strategy_id(unit, os_release):
     _logger.debug(_("Considering strategy: %s"),
                   _("os-id == ID and os-version-id == undefined"))
-    return unit.os_id == os_release['ID'] and unit.os_version_id is None
+    return (
+        'ID' in os_release
+        and unit.os_id == os_release['ID']
+        and unit.os_version_id is None
+    )
 
 
 def _strategy_id_like(unit, os_release):
     _logger.debug(_("Considering strategy: %s"),
                   _("os-id == ID_LIKE and os-version-id == undefined"))
-    return unit.os_id == os_release['ID_LIKE'] and unit.os_version_id is None
+    return (
+        'ID_LIKE' in os_release
+        and unit.os_id == os_release['ID_LIKE']
+        and unit.os_version_id is None
+    )
 
 
 class PackagingDriverBase(IPackagingDriver):
 
-    """ Base implementation of a packaging driver. """
+    """Base implementation of a packaging driver."""
 
     def __init__(self, os_release: 'Dict[str, str]'):
         self.os_release = os_release
@@ -262,7 +337,12 @@ class PackagingDriverBase(IPackagingDriv
 
 class NullPackagingDriver(PackagingDriverBase):
 
-    """ Null implementation of a packaging driver. """
+    """
+    Null implementation of a packaging driver.
+
+    This driver just does nothing at all. It is used as a fall-back when
+    nothing better is detected.
+    """
 
     def is_applicable(self, unit: Unit) -> bool:
         return False
@@ -316,10 +396,14 @@ class DebianPackagingDriver(PackagingDri
     def collect(self, unit: Unit) -> None:
         def rel_list(field):
             relations = unit.get_record_value(field, '').replace('\n', ' ')
-            return ', '.join([rel.strip() for rel in relations.split(',')])
-        self._depends.append(rel_list('Depends'))
-        self._suggests.append(rel_list('Suggests'))
-        self._recommends.append(rel_list('Recommends'))
+            return (
+                rel.strip()
+                for rel in re.split(', *', relations)
+                if rel.strip()
+            )
+        self._depends.extend(rel_list('Depends'))
+        self._suggests.extend(rel_list('Suggests'))
+        self._recommends.extend(rel_list('Recommends'))
 
     def _write_pkg_substvars(self, pkg):
         fname = 'debian/{}.substvars'.format(pkg)
@@ -357,7 +441,7 @@ class DebianPackagingDriver(PackagingDri
 
 
 def get_packaging_driver() -> IPackagingDriver:
-    """ Get the packaging driver appropriate for the current platform. """
+    """Get the packaging driver appropriate for the current platform."""
     if sys.platform.startswith("linux"):
         os_release = get_os_release()
         if (os_release.get('ID') == 'debian'
--- /dev/null
+++ plainbox-0.22/plainbox/impl/unit/test_packging.py
@@ -0,0 +1,180 @@
+# This file is part of Checkbox.
+#
+# Copyright 2015 Canonical Ltd.
+# Written by:
+#   Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
+#
+# Checkbox is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Checkbox is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Tests for the PackagingMetaDataUnit and friends."""
+
+from unittest import TestCase
+
+from plainbox.impl.unit.packaging import DebianPackagingDriver
+from plainbox.impl.unit.packaging import PackagingMetaDataUnit
+from plainbox.impl.unit.packaging import _strategy_id
+from plainbox.impl.unit.packaging import _strategy_id_like
+from plainbox.impl.unit.packaging import _strategy_id_version
+
+
+class DebianPackagingDriverTests(TestCase):
+
+    """Tests for the DebianPackagingDriver class."""
+
+    DEBIAN_JESSIE = {
+        'PRETTY_NAME': "Debian GNU/Linux 8 (jessie)",
+        'NAME': "Debian GNU/Linux",
+        'VERSION_ID': "8",
+        'VERSION': "8 (jessie)",
+        'ID': 'debian',
+        'HOME_URL': "http://www.debian.org/",
+        'SUPPORT_URL': "http://www.debian.org/support/",
+        'BUG_REPORT_URL': "https://bugs.debian.org/",
+    }
+
+    DEBIAN_SID = {
+        'PRETTY_NAME': "Debian GNU/Linux stretch/sid",
+        'NAME': "Debian GNU/Linux",
+        'ID': 'debian',
+        'HOME_URL': "https://www.debian.org/",
+        'SUPPORT_URL': "https://www.debian.org/support/",
+        'BUG_REPORT_URL': "https://bugs.debian.org/",
+    }
+
+    UBUNTU_VIVID = {
+        'NAME': "Ubuntu",
+        'VERSION': "15.04 (Vivid Vervet)",
+        'ID': 'ubuntu',
+        'ID_LIKE': 'debian',
+        'PRETTY_NAME': "Ubuntu 15.04",
+        'VERSION_ID': "15.04",
+        'HOME_URL': "http://www.ubuntu.com/",
+        'SUPPORT_URL': "http://help.ubuntu.com/",
+        'BUG_REPORT_URL': "http://bugs.launchpad.net/ubuntu/",
+    }
+
+    def test_fix_1476678(self):
+        """Check https://bugs.launchpad.net/plainbox/+bug/1476678."""
+        driver = DebianPackagingDriver({})
+        driver.collect(PackagingMetaDataUnit({
+            'Depends': (
+                'python3-checkbox-support (>= 0.2),\n'
+                'python3 (>= 3.2),\n'),
+            'Recommends': (
+                'dmidecode,\n'
+                'dpkg (>= 1.13),\n'
+                'lsb-release,\n'
+                'wodim')
+        }))
+        self.assertEqual(driver._depends, [
+            'python3-checkbox-support (>= 0.2)',
+            'python3 (>= 3.2)',
+        ])
+        self.assertEqual(driver._recommends, [
+            'dmidecode',
+            'dpkg (>= 1.13)',
+            'lsb-release',
+            'wodim'
+        ])
+        self.assertEqual(driver._suggests, [])
+
+    def test_fix_1477095(self):
+        """Check https://bugs.launchpad.net/plainbox/+bug/1477095."""
+        # This unit is supposed to for Debian (any version) and derivatives.
+        # Note below that id match lets both Debian Jessie and Debian Sid pass
+        # and that id_like match also lets Ubuntu Vivid pass.
+        unit = PackagingMetaDataUnit({
+            'os-id': 'debian',
+        })
+        # Using id and version match
+        self.assertFalse(_strategy_id_version(unit, {}))
+        self.assertFalse(_strategy_id_version(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id_version(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id_version(unit, self.UBUNTU_VIVID))
+        # Using id match
+        self.assertFalse(_strategy_id(unit, {}))
+        self.assertTrue(_strategy_id(unit, self.DEBIAN_SID))
+        self.assertTrue(_strategy_id(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id(unit, self.UBUNTU_VIVID))
+        # Using id like
+        self.assertFalse(_strategy_id_like(unit, {}))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_JESSIE))
+        self.assertTrue(_strategy_id_like(unit, self.UBUNTU_VIVID))
+        # This unit is supposed to for Debian Jessie only.  Note below that
+        # only Debian Jessie is passed and only by id and version match.
+        # Nothing else is allowed.
+        unit = PackagingMetaDataUnit({
+            'os-id': 'debian',
+            'os-version-id': '8'
+        })
+        # Using id and version match
+        self.assertFalse(_strategy_id_version(unit, {}))
+        self.assertFalse(_strategy_id_version(unit, self.DEBIAN_SID))
+        self.assertTrue(_strategy_id_version(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id_version(unit, self.UBUNTU_VIVID))
+        # Using id match
+        self.assertFalse(_strategy_id(unit, {}))
+        self.assertFalse(_strategy_id(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id(unit, self.UBUNTU_VIVID))
+        # Using id like
+        self.assertFalse(_strategy_id_like(unit, {}))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id_like(unit, self.UBUNTU_VIVID))
+        # This unit is supposed to for Ubuntu (any version) and derivatives.
+        # Note that None of the Debian versions pass anymore and the only
+        # version that is allowed here is the one Vivid version we test for.
+        # (If there was an Elementary test here it would have passed as well, I
+        # hope).
+        unit = PackagingMetaDataUnit({
+            'os-id': 'ubuntu',
+        })
+        # Using id and version match
+        self.assertFalse(_strategy_id_version(unit, {}))
+        self.assertFalse(_strategy_id_version(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id_version(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id_version(unit, self.UBUNTU_VIVID))
+        # Using id match
+        self.assertFalse(_strategy_id(unit, {}))
+        self.assertFalse(_strategy_id(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id(unit, self.DEBIAN_JESSIE))
+        self.assertTrue(_strategy_id(unit, self.UBUNTU_VIVID))
+        # Using id like
+        self.assertFalse(_strategy_id_like(unit, {}))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id_like(unit, self.UBUNTU_VIVID))
+        # This unit is supposed to for Ubuntu Vivid only.  Note that it behaves
+        # exactly like the Debian Jessie test above.  Only Ubuntu Vivid is
+        # passed and only by the id and version match.
+        unit = PackagingMetaDataUnit({
+            'os-id': 'ubuntu',
+            'os-version-id': '15.04'
+        })
+        # Using id and version match
+        self.assertFalse(_strategy_id_version(unit, {}))
+        self.assertFalse(_strategy_id_version(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id_version(unit, self.DEBIAN_JESSIE))
+        self.assertTrue(_strategy_id_version(unit, self.UBUNTU_VIVID))
+        # Using id match
+        self.assertFalse(_strategy_id(unit, {}))
+        self.assertFalse(_strategy_id(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id(unit, self.UBUNTU_VIVID))
+        # Using id like
+        self.assertFalse(_strategy_id_like(unit, {}))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_SID))
+        self.assertFalse(_strategy_id_like(unit, self.DEBIAN_JESSIE))
+        self.assertFalse(_strategy_id_like(unit, self.UBUNTU_VIVID))
