diff --git a/niceman/distributions/debian.py b/niceman/distributions/debian.py index 007bee803..a88739430 100644 --- a/niceman/distributions/debian.py +++ b/niceman/distributions/debian.py @@ -13,6 +13,8 @@ import attr from collections import defaultdict +from six.moves import map + import pytz from niceman import utils @@ -89,8 +91,24 @@ class DEBPackage(Package): versions = attr.ib(default=None) # Hash ver_str -> [Array of source names] install_date = attr.ib(default=None) files = attr.ib(default=attr.Factory(list)) -_register_with_representer(DEBPackage) + def satisfies(self, other): + """return True if this package (self) satisfies the requirements of + the passed package (other)""" + if not isinstance(other, Package): + raise TypeError('satisfies() requires a package argument') + if not isinstance(other, DEBPackage): + return False + if self.name != other.name: + return False + if other.version is not None and self.version != other.version: + return False + if other.architecture is not None \ + and self.architecture != other.architecture: + return False + return True + +_register_with_representer(DEBPackage) @attr.s class DebianDistribution(Distribution): @@ -153,6 +171,31 @@ def normalize(self): # would make us require supporting flexible typing -- string or a list pass + def satisfies_package(self, package): + """return True if this distribution (self) satisfies the requirements + of the passed package""" + if not isinstance(package, Package): + raise TypeError('satisfies_package() requires a package argument') + if not isinstance(package, DEBPackage): + return False + return any([ p.satisfies(package) for p in self.packages ]) + + def satisfies(self, other): + """return True if this distribution (self) satisfies the requirements + of the other distribution (other)""" + if not isinstance(other, Distribution): + raise TypeError('satisfies() requires a distribution argument') + if not isinstance(other, DebianDistribution): + return False + return all(map(self.satisfies_package, other.packages)) + + def __sub__(self, other): + # the semantics of distribution subtraction are, for d1 - d2: + # what is specified in d1 that is not specified in d2 + # or how does d2 fall short of d1 + # or what is in d1 that isn't satisfied by d2 + return [ p for p in self.packages if not other.satisfies_package(p) ] + # to grow: # def __iadd__(self, another_instance or DEBPackage, or APTSource) # def __add__(self, another_instance or DEBPackage, or APTSource) diff --git a/niceman/distributions/tests/test_debian.py b/niceman/distributions/tests/test_debian.py index a4228db2c..a2c6a4bd6 100644 --- a/niceman/distributions/tests/test_debian.py +++ b/niceman/distributions/tests/test_debian.py @@ -12,6 +12,10 @@ from pprint import pprint from niceman.distributions.debian import DebTracer +from niceman.distributions.debian import DEBPackage +from niceman.distributions.debian import DebianDistribution + +import pytest import mock @@ -132,4 +136,67 @@ def _run_dpkg_query(subfiles): '/usr/bin/fail2ban-server': {'name': u'fail2ban'}, '/usr/lib/afni/bin/afni': {'name': u'afni'}, '/bin/sh': {'name': u'dash'} - } \ No newline at end of file + } + +@pytest.fixture +def setup_packages(): + """set up the package comparison tests""" + a = DEBPackage(name='p1') + b = DEBPackage(name='p1', version='1.0') + c = DEBPackage(name='p1', version='1.1') + d = DEBPackage(name='p1', architecture='i386') + e = DEBPackage(name='p1', architecture='alpha') + f = DEBPackage(name='p1', version='1.1', architecture='i386') + g = DEBPackage(name='p2') + return (a, b, c, d, e, f, g) + +def test_package_satisfies(setup_packages): + (p1, p1v10, p1v11, p1ai, p1aa, p1v11ai, p2) = setup_packages + assert p1.satisfies(p1) + assert p1v10.satisfies(p1v10) + assert not p1.satisfies(p1v10) + assert p1v10.satisfies(p1) + assert not p1v10.satisfies(p1v11) + assert not p1.satisfies(p2) + assert not p1v10.satisfies(p2) + assert not p2.satisfies(p1v10) + assert not p1v10.satisfies(p1aa) + assert p1aa.satisfies(p1) + assert not p1aa.satisfies(p1v10) + assert not p1aa.satisfies(p1ai) + assert not p1v11.satisfies(p1v11ai) + assert p1v11ai.satisfies(p1v11) + +@pytest.fixture +def setup_distributions(): + (p1, p1v10, p1v11, p1ai, p1aa, p1v11ai, p2) = setup_packages() + d1 = DebianDistribution(name='debian 1') + d1.packages = [p1] + d2 = DebianDistribution(name='debian 2') + d2.packages = [p1v11] + return (d1, d2) + +def test_distribution_satisfies_package(setup_distributions, setup_packages): + (d1, d2) = setup_distributions + (p1, p1v10, p1v11, p1ai, p1aa, p1v11ai, p2) = setup_packages + assert d1.satisfies_package(p1) + assert not d1.satisfies_package(p1v10) + assert d2.satisfies_package(p1) + assert not d2.satisfies_package(p1v10) + assert d2.satisfies_package(p1v11) + +def test_distribution_statisfies(setup_distributions): + (d1, d2) = setup_distributions + assert not d1.satisfies(d2) + assert d2.satisfies(d1) + +def test_distribution_sub(): + (p1, p1v10, p1v11, p1ai, p1aa, p1v11ai, p2) = setup_packages() + d1 = DebianDistribution(name='debian 1') + d1.packages = [p1, p2] + d2 = DebianDistribution(name='debian 2') + d2.packages = [p1v11, p2] + assert d1-d2 == [] + result = d2-d1 + assert len(result) == 1 + assert result[0] == p1v11