Skip to content
45 changes: 44 additions & 1 deletion niceman/distributions/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import attr
from collections import defaultdict

from six.moves import map

import pytz

from niceman import utils
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
69 changes: 68 additions & 1 deletion niceman/distributions/tests/test_debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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'}
}
}

@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