aboutsummaryrefslogtreecommitdiff
path: root/hwpack
diff options
context:
space:
mode:
authorGuilherme Salgado <salgado@canonical.com>2010-12-08 17:51:42 -0200
committerGuilherme Salgado <salgado@canonical.com>2010-12-08 17:51:42 -0200
commit79235ec6c2336337e057c582b43ee240bd668773 (patch)
treeca0fbea9013abd891394967a31fb3a4b48c811fc /hwpack
parent353921eb1fea138c7bab7936312f8ebe82cd57e5 (diff)
parent13267f9eb87ea3ce9f266063a778389cc64d6442 (diff)
merge trunk
Diffstat (limited to 'hwpack')
-rw-r--r--hwpack/packages.py63
-rw-r--r--hwpack/tarfile_matchers.py29
-rw-r--r--hwpack/testing.py212
-rw-r--r--hwpack/tests/__init__.py18
-rw-r--r--hwpack/tests/test_builder.py34
-rw-r--r--hwpack/tests/test_tarfile_matchers.py6
-rw-r--r--hwpack/tests/test_testing.py232
7 files changed, 538 insertions, 56 deletions
diff --git a/hwpack/packages.py b/hwpack/packages.py
index f3a6da6..485d8f9 100644
--- a/hwpack/packages.py
+++ b/hwpack/packages.py
@@ -332,45 +332,40 @@ class FetchedPackage(object):
pkg.content = open(deb_file_path)
return pkg
- def __eq__(self, other):
- # Note that we don't compare the contents here -- we assume that
- # comparing the md5 checksum is enough (more philosophically,
- # FetchedPackages are equal if they represent the same underlying
- # package, even if they represent it in slightly different ways)
- return (self.name == other.name
- and self.version == other.version
- and self.filename == other.filename
- and self.size == other.size
- and self.md5 == other.md5
- and self.architecture == other.architecture
- and self.depends == other.depends
- and self.pre_depends == other.pre_depends
- and self.conflicts == other.conflicts
- and self.recommends == other.recommends
- and self.provides == other.provides
- and self.replaces == other.replaces
- and self.breaks == other.breaks
- )
+ # A list of attributes that are compared to determine equality. Note that
+ # we don't include the contents here -- we assume that comparing the md5
+ # checksum is enough (more philosophically, FetchedPackages are equal if
+ # they represent the same underlying package, even if they represent it in
+ # slightly different ways)
+ _equality_attributes = (
+ 'name',
+ 'version',
+ 'filename',
+ 'size',
+ 'md5',
+ 'architecture',
+ 'depends',
+ 'pre_depends',
+ 'conflicts',
+ 'recommends',
+ 'provides',
+ 'replaces',
+ 'breaks')
+
+ @property
+ def _equality_data(self):
+ return tuple(
+ getattr(self, attr) for attr in self._equality_attributes)
- def __hash__(self):
- return hash((
- self.name,
- self.version,
- self.filename,
- self.size,
- self.md5,
- self.architecture,
- self.depends,
- self.pre_depends,
- self.conflicts,
- self.recommends,
- self.provides,
- self.replaces,
- self.breaks))
+ def __eq__(self, other):
+ return self._equality_data == other._equality_data
def __ne__(self, other):
return not self.__eq__(other)
+ def __hash__(self):
+ return hash(self._equality_data)
+
def __repr__(self):
has_content = self.content and "yes" or "no"
return (
diff --git a/hwpack/tarfile_matchers.py b/hwpack/tarfile_matchers.py
index 20bfeaf..919efd5 100644
--- a/hwpack/tarfile_matchers.py
+++ b/hwpack/tarfile_matchers.py
@@ -1,4 +1,4 @@
-from testtools.matchers import Matcher, Mismatch
+from testtools.matchers import Annotate, Equals, Matcher, Mismatch
class TarfileMissingPathMismatch(Mismatch):
@@ -65,7 +65,8 @@ class TarfileHasFile(Matcher):
def __init__(self, path, type=None, size=None, mtime=None,
mtime_skew=None, mode=None, linkname=None, uid=None,
- gid=None, uname=None, gname=None, content=None):
+ gid=None, uname=None, gname=None, content=None,
+ content_matcher=None):
"""Create a TarfileHasFile Matcher.
:param path: the path that must be present.
@@ -92,6 +93,9 @@ class TarfileHasFile(Matcher):
None to not check.
:param content: the content that `path` must have when extracted,
or None to not check.
+ :param content_matcher: a matcher to match the content that `path` has
+ when extracted, or None to not check. You can't specify both
+ content_matcher and content.
"""
self.path = path
self.type = type
@@ -104,7 +108,18 @@ class TarfileHasFile(Matcher):
self.gid = gid
self.uname = uname
self.gname = gname
- self.content = content
+ if content is not None:
+ if content_matcher is not None:
+ raise ValueError(
+ "doesn't make sense to specify content and "
+ "content_matcher")
+ content_matcher = Equals(content)
+ if content_matcher is not None:
+ self.content_matcher = Annotate(
+ 'The content of path "%s" did not match' % path,
+ content_matcher)
+ else:
+ self.content_matcher = None
def match(self, tarball):
"""Match a tarfile.TarFile against the expected values."""
@@ -125,11 +140,11 @@ class TarfileHasFile(Matcher):
if abs(self.mtime - info.mtime) > mtime_skew:
return TarfileWrongValueMismatch(
"mtime", tarball, self.path, self.mtime, info.mtime)
- if self.content is not None:
+ if self.content_matcher is not None:
actual = tarball.extractfile(self.path).read()
- if actual != self.content:
- return TarfileWrongValueMismatch(
- "content", tarball, self.path, self.content, actual)
+ content_mismatch = self.content_matcher.match(actual)
+ if content_mismatch:
+ return content_mismatch
return None
def __str__(self):
diff --git a/hwpack/testing.py b/hwpack/testing.py
index 67398fe..eeb8bb7 100644
--- a/hwpack/testing.py
+++ b/hwpack/testing.py
@@ -7,8 +7,10 @@ from StringIO import StringIO
import tarfile
import time
+from debian.deb822 import Packages
+
from testtools import TestCase
-from testtools.matchers import Matcher, Mismatch
+from testtools.matchers import Annotate, Equals, Matcher, Mismatch
from hwpack.better_tarfile import writeable_tarfile
from hwpack.tarfile_matchers import TarfileHasFile
@@ -345,10 +347,16 @@ class IsHardwarePack(Matcher):
matchers.append(HardwarePackHasFile("FORMAT", content="1.0\n"))
matchers.append(HardwarePackHasFile(
"metadata", content=str(self.metadata)))
- manifest = ""
+ manifest_lines = []
for package in self.packages:
- manifest += "%s=%s\n" % (package.name, package.version)
- matchers.append(HardwarePackHasFile("manifest", content=manifest))
+ manifest_lines.append(
+ "%s=%s" % (package.name, package.version))
+ matchers.append(
+ HardwarePackHasFile(
+ "manifest",
+ content_matcher=AfterPreproccessing(
+ str.splitlines,
+ MatchesSetwise(*map(Equals, manifest_lines)))))
matchers.append(HardwarePackHasFile("pkgs", type=tarfile.DIRTYPE))
packages_with_content = [p for p in self.packages
if p not in self.packages_without_content]
@@ -358,7 +366,8 @@ class IsHardwarePack(Matcher):
content=package.content.read()))
matchers.append(HardwarePackHasFile(
"pkgs/Packages",
- content=get_packages_file(packages_with_content)))
+ content_matcher=MatchesAsPackagesFile(
+ *[MatchesPackage(p) for p in packages_with_content])))
matchers.append(HardwarePackHasFile(
"sources.list.d", type=tarfile.DIRTYPE))
for source_id, sources_entry in self.sources.items():
@@ -373,3 +382,196 @@ class IsHardwarePack(Matcher):
def __str__(self):
return "Is a valid hardware pack."
+
+
+class EachOf(object):
+ """Matches if each matcher matches the corresponding value.
+
+ More easily explained by example than in words:
+
+ >>> EachOf([Equals(1)]).match([1])
+ >>> EachOf([Equals(1), Equals(2)]).match([1, 2])
+ >>> EachOf([Equals(1), Equals(2)]).match([2, 1]) #doctest: +ELLIPSIS
+ <...MismatchesAll...>
+ """
+
+ def __init__(self, matchers):
+ self.matchers = matchers
+
+ def match(self, values):
+ mismatches = []
+ length_mismatch = Annotate(
+ "Length mismatch", Equals(len(self.matchers))).match(len(values))
+ if length_mismatch:
+ mismatches.append(length_mismatch)
+ for matcher, value in zip(self.matchers, values):
+ mismatch = matcher.match(value)
+ if mismatch:
+ mismatches.append(mismatch)
+ if mismatches:
+ return MismatchesAll(mismatches)
+
+
+class MatchesStructure(object):
+ """Matcher that matches an object structurally.
+
+ 'Structurally' here means that attributes of the object being matched are
+ compared against given matchers.
+
+ `fromExample` allows the creation of a matcher from a prototype object and
+ then modified versions can be created with `update`.
+ """
+
+ def __init__(self, **kwargs):
+ self.kws = kwargs
+
+ @classmethod
+ def fromExample(cls, example, *attributes):
+ kwargs = {}
+ for attr in attributes:
+ kwargs[attr] = Equals(getattr(example, attr))
+ return cls(**kwargs)
+
+ def update(self, **kws):
+ new_kws = self.kws.copy()
+ for attr, matcher in kws.iteritems():
+ if matcher is None:
+ new_kws.pop(attr, None)
+ else:
+ new_kws[attr] = matcher
+ return type(self)(**new_kws)
+
+ def match(self, value):
+ matchers = []
+ values = []
+ for attr, matcher in self.kws.iteritems():
+ matchers.append(Annotate(attr, matcher))
+ values.append(getattr(value, attr))
+ return EachOf(matchers).match(values)
+
+
+def MatchesPackage(example):
+ """Create a `MatchesStructure` object from a `FetchedPackage`."""
+ return MatchesStructure.fromExample(
+ example, *example._equality_attributes)
+
+
+class MatchesSetwise(object):
+ """Matches if all the matchers match elements of the value being matched.
+
+ The difference compared to `EachOf` is that the order of the matchings
+ does not matter.
+ """
+
+ def __init__(self, *matchers):
+ self.matchers = matchers
+
+ def match(self, observed):
+ remaining_matchers = set(self.matchers)
+ not_matched = []
+ for value in observed:
+ for matcher in remaining_matchers:
+ if matcher.match(value) is None:
+ remaining_matchers.remove(matcher)
+ break
+ else:
+ not_matched.append(value)
+ if not_matched or remaining_matchers:
+ remaining_matchers = list(remaining_matchers)
+ # There are various cases that all should be reported somewhat
+ # differently.
+
+ # There are two trivial cases:
+ # 1) There are just some matchers left over.
+ # 2) There are just some values left over.
+
+ # Then there are three more interesting cases:
+ # 3) There are the same number of matchers and values left over.
+ # 4) There are more matchers left over than values.
+ # 5) There are more values left over than matchers.
+
+ if len(not_matched) == 0:
+ if len(remaining_matchers) > 1:
+ msg = "There were %s matchers left over: " % (
+ len(remaining_matchers),)
+ else:
+ msg = "There was 1 matcher left over: "
+ msg += ', '.join(map(str, remaining_matchers))
+ return Mismatch(msg)
+ elif len(remaining_matchers) == 0:
+ if len(not_matched) > 1:
+ return Mismatch(
+ "There were %s values left over: %s" % (
+ len(not_matched), not_matched))
+ else:
+ return Mismatch(
+ "There was 1 value left over: %s" % (
+ not_matched, ))
+ else:
+ common_length = min(len(remaining_matchers), len(not_matched))
+ if common_length == 0:
+ raise AssertionError("common_length can't be 0 here")
+ if common_length > 1:
+ msg = "There were %s mismatches" % (common_length,)
+ else:
+ msg = "There was 1 mismatch"
+ if len(remaining_matchers) > len(not_matched):
+ extra_matchers = remaining_matchers[common_length:]
+ msg += " and %s extra matcher" % (len(extra_matchers), )
+ if len(extra_matchers) > 1:
+ msg += "s"
+ msg += ': ' + ', '.join(map(str, extra_matchers))
+ elif len(not_matched) > len(remaining_matchers):
+ extra_values = not_matched[common_length:]
+ msg += " and %s extra value" % (len(extra_values), )
+ if len(extra_values) > 1:
+ msg += "s"
+ msg += ': ' + str(extra_values)
+ return Annotate(
+ msg, EachOf(remaining_matchers[:common_length])
+ ).match(not_matched[:common_length])
+
+
+def parse_packages_file_content(file_content):
+ packages = []
+ for para in Packages.iter_paragraphs(StringIO(file_content)):
+ args = {}
+ for key, value in para.iteritems():
+ key = key.lower()
+ if key == 'md5sum':
+ key = 'md5'
+ elif key == 'package':
+ key = 'name'
+ elif key == 'size':
+ value = int(value)
+ if key in FetchedPackage._equality_attributes:
+ args[key] = value
+ packages.append(FetchedPackage(**args))
+ return packages
+
+
+class AfterPreproccessing(object):
+ """Matches if the value matches after passing through a function."""
+
+ def __init__(self, preprocessor, matcher):
+ self.preprocessor = preprocessor
+ self.matcher = matcher
+
+ def __str__(self):
+ return "AfterPreproccessing(%s, %s)" % (
+ self.preprocessor, self.matcher)
+
+ def match(self, value):
+ value = self.preprocessor(value)
+ return self.matcher.match(value)
+
+
+def MatchesAsPackagesFile(*package_matchers):
+ """Matches the contents of a Packages file against the given matchers.
+
+ The contents of the Packages file is turned into a list of FetchedPackages
+ using `parse_packages_file_content` above.
+ """
+
+ return AfterPreproccessing(
+ parse_packages_file_content, MatchesSetwise(*package_matchers))
diff --git a/hwpack/tests/__init__.py b/hwpack/tests/__init__.py
index 973d9f1..11c982f 100644
--- a/hwpack/tests/__init__.py
+++ b/hwpack/tests/__init__.py
@@ -1,14 +1,16 @@
import unittest
def test_suite():
- module_names = ['hwpack.tests.test_config',
- 'hwpack.tests.test_better_tarfile',
- 'hwpack.tests.test_builder',
- 'hwpack.tests.test_hardwarepack',
- 'hwpack.tests.test_packages',
- 'hwpack.tests.test_script',
- 'hwpack.tests.test_tarfile_matchers',
- ]
+ module_names = [
+ 'hwpack.tests.test_better_tarfile',
+ 'hwpack.tests.test_builder',
+ 'hwpack.tests.test_config',
+ 'hwpack.tests.test_hardwarepack',
+ 'hwpack.tests.test_packages',
+ 'hwpack.tests.test_script',
+ 'hwpack.tests.test_tarfile_matchers',
+ 'hwpack.tests.test_testing',
+ ]
loader = unittest.TestLoader()
suite = loader.loadTestsFromNames(module_names)
return suite
diff --git a/hwpack/tests/test_builder.py b/hwpack/tests/test_builder.py
index 460c9d4..ecb75b4 100644
--- a/hwpack/tests/test_builder.py
+++ b/hwpack/tests/test_builder.py
@@ -76,6 +76,40 @@ class HardwarePackBuilderTests(TestCaseWithFixtures):
metadata, [available_package],
{source_id: source.sources_entry}))
+ def test_builds_correct_contents_multiple_packages(self):
+ hwpack_name = "ahwpack"
+ hwpack_version = "1.0"
+ architecture = "armel"
+ package_name1 = "foo"
+ package_name2 = "goo"
+ source_id = "ubuntu"
+ available_package1 = DummyFetchedPackage(
+ package_name1, "1.1", architecture=architecture)
+ available_package2 = DummyFetchedPackage(
+ package_name2, "1.2", architecture=architecture)
+ source = self.useFixture(
+ AptSourceFixture([available_package1, available_package2]))
+ config = self.useFixture(ConfigFileFixture(
+ '[hwpack]\nname=%s\npackages=%s %s\narchitectures=%s\n'
+ '\n[%s]\nsources-entry=%s\n'
+ % (hwpack_name, package_name1, package_name2, architecture,
+ source_id, source.sources_entry)))
+ builder = HardwarePackBuilder(config.filename, hwpack_version)
+ builder.build()
+ metadata = Metadata(hwpack_name, hwpack_version, architecture)
+ hwpack_filename = "hwpack_%s_%s_%s.tar.gz" % (
+ hwpack_name, hwpack_version, architecture)
+ self.assertThat(
+ hwpack_filename,
+ IsHardwarePack(
+ metadata, [available_package1, available_package2],
+ {source_id: source.sources_entry}))
+ self.assertThat(
+ hwpack_filename,
+ IsHardwarePack(
+ metadata, [available_package2, available_package1],
+ {source_id: source.sources_entry}))
+
def test_obeys_include_debs(self):
hwpack_name = "ahwpack"
hwpack_version = "1.0"
diff --git a/hwpack/tests/test_tarfile_matchers.py b/hwpack/tests/test_tarfile_matchers.py
index 7a85692..aa4f3bc 100644
--- a/hwpack/tests/test_tarfile_matchers.py
+++ b/hwpack/tests/test_tarfile_matchers.py
@@ -168,8 +168,10 @@ class TarfileHasFileTests(TestCase):
with test_tarfile(contents=[("foo", "somecontent")]) as tf:
matcher = TarfileHasFile("foo", content="othercontent")
mismatch = matcher.match(tf)
- self.assertValueMismatch(
- mismatch, tf, "foo", "content", "othercontent", "somecontent")
+ self.assertEquals(
+ "'othercontent' != 'somecontent': The content of "
+ "path \"foo\" did not match",
+ mismatch.describe())
def test_matches_mtime_with_skew(self):
with test_tarfile(contents=[("foo", "")], default_mtime=12345) as tf:
diff --git a/hwpack/tests/test_testing.py b/hwpack/tests/test_testing.py
new file mode 100644
index 0000000..e5f1a15
--- /dev/null
+++ b/hwpack/tests/test_testing.py
@@ -0,0 +1,232 @@
+import doctest
+import re
+from StringIO import StringIO
+import sys
+
+from testtools import TestCase
+from testtools.matchers import (
+ Annotate,
+ Equals,
+ Mismatch,
+ NotEquals,
+ )
+from hwpack.testing import (
+ DummyFetchedPackage,
+ EachOf,
+ MatchesAsPackagesFile,
+ MatchesPackage,
+ MatchesStructure,
+ MatchesSetwise,
+ parse_packages_file_content,
+ )
+from hwpack.packages import (
+ get_packages_file,
+ )
+
+
+def run_doctest(obj, name):
+ p = doctest.DocTestParser()
+ t = p.get_doctest(
+ obj.__doc__, sys.modules[obj.__module__].__dict__, name, '', 0)
+ r = doctest.DocTestRunner()
+ output = StringIO()
+ r.run(t, out=output.write)
+ return r.failures, output.getvalue()
+
+
+class TestEachOf(TestCase):
+
+ def test_docstring(self):
+ failure_count, output = run_doctest(EachOf, "EachOf")
+ if failure_count:
+ self.fail("Doctest failed with %s" % output)
+
+
+class TestMatchesStructure(TestCase):
+
+ class SimpleClass:
+ def __init__(self, x):
+ self.x = x
+
+ def test_matches(self):
+ self.assertThat(
+ self.SimpleClass(1), MatchesStructure(x=Equals(1)))
+
+ def test_mismatch(self):
+ self.assertRaises(
+ AssertionError, self.assertThat, self.SimpleClass(1),
+ MatchesStructure(x=NotEquals(1)))
+
+ def test_fromExample(self):
+ self.assertThat(
+ self.SimpleClass(1),
+ MatchesStructure.fromExample(self.SimpleClass(1), 'x'))
+
+ def test_update(self):
+ self.assertThat(
+ self.SimpleClass(1),
+ MatchesStructure(x=NotEquals(1)).update(x=Equals(1)))
+
+ def test_update_none(self):
+ self.assertThat(
+ self.SimpleClass(1),
+ MatchesStructure(x=Equals(1), y=NotEquals(42)).update(
+ y=None))
+
+
+class TestMatchesPackage(TestCase):
+
+ def test_simple(self):
+ observed = DummyFetchedPackage("foo", "1.1", architecture="armel")
+ expected = DummyFetchedPackage("foo", "1.1", architecture="armel")
+ self.assertThat(
+ observed, MatchesPackage(expected))
+
+ def test_mismatch(self):
+ observed = DummyFetchedPackage("foo", "1.1", depends="bar")
+ expected = DummyFetchedPackage("foo", "1.1", depends="baz")
+ self.assertRaises(AssertionError, self.assertThat, observed,
+ MatchesPackage(expected))
+
+ def test_skip_one_attribute(self):
+ observed = DummyFetchedPackage("foo", "1.1", depends="bar")
+ expected = DummyFetchedPackage("foo", "1.1", depends="baz")
+ self.assertThat(
+ observed,
+ MatchesPackage(expected).update(depends=None))
+
+
+class MatchesRegex(object):
+
+ def __init__(self, pattern, flags=0):
+ self.pattern = pattern
+ self.flags = flags
+ def match(self, value):
+ if not re.match(self.pattern, value, self.flags):
+ return Mismatch("%r did not match %r" % (self.pattern, value))
+
+
+class TestMatchesSetwise(TestCase):
+
+ def assertMismatchWithDescriptionMatching(self, value, matcher,
+ description_matcher):
+ mismatch = matcher.match(value)
+ if mismatch is None:
+ self.fail("%s matched %s" % (matcher, value))
+ actual_description = mismatch.describe()
+ self.assertThat(
+ actual_description,
+ Annotate(
+ "%s matching %s" % (matcher, value),
+ description_matcher))
+
+ def test_matches(self):
+ self.assertIs(
+ None, MatchesSetwise(Equals(1), Equals(2)).match([2, 1]))
+
+ def test_mismatches(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex('.*There was 1 mismatch$', re.S))
+
+ def test_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3], MatchesSetwise(Equals(1), Equals(2), Equals(3)),
+ Equals('There was 1 matcher left over: Equals(1)'))
+
+ def test_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [1, 2, 3], MatchesSetwise(Equals(1), Equals(2)),
+ Equals('There was 1 value left over: [3]'))
+
+ def test_two_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [3], MatchesSetwise(Equals(1), Equals(2), Equals(3)),
+ MatchesRegex(
+ 'There were 2 matchers left over: Equals\([12]\), '
+ 'Equals\([12]\)'))
+
+ def test_two_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [1, 2, 3, 4], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex(
+ 'There were 2 values left over: \[[34], [34]\]'))
+
+ def test_mismatch_and_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3], MatchesSetwise(Equals(0), Equals(1), Equals(2)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 1 extra matcher: Equals\([01]\)',
+ re.S))
+
+ def test_mismatch_and_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3, 4], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 1 extra value: \[[34]\]',
+ re.S))
+
+ def test_mismatch_and_two_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [3, 4], MatchesSetwise(
+ Equals(0), Equals(1), Equals(2), Equals(3)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 2 extra matchers: '
+ 'Equals\([012]\), Equals\([012]\)', re.S))
+
+ def test_mismatch_and_two_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3, 4, 5], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 2 extra values: \[[145], [145]\]',
+ re.S))
+
+
+class TestParsePackagesFileContent(TestCase):
+
+ def test_one(self):
+ observed = DummyFetchedPackage("foo", "1.1")
+ packages_content = get_packages_file([observed])
+ parsed = parse_packages_file_content(packages_content)
+ self.assertThat(len(parsed), Equals(1))
+ self.assertThat(parsed[0], MatchesPackage(observed))
+
+ def test_several(self):
+ observed1 = DummyFetchedPackage("foo", "1.1")
+ observed2 = DummyFetchedPackage("bar", "1.2")
+ observed3 = DummyFetchedPackage("baz", "1.5")
+ packages_content = get_packages_file(
+ [observed1, observed2, observed3])
+ parsed = parse_packages_file_content(packages_content)
+ self.assertThat(parsed, MatchesSetwise(
+ MatchesPackage(observed3),
+ MatchesPackage(observed2),
+ MatchesPackage(observed1)))
+
+
+class TestMatchesAsPackagesFile(TestCase):
+
+ def test_one(self):
+ observed = DummyFetchedPackage("foo", "1.1")
+ packages_content = get_packages_file([observed])
+ self.assertThat(
+ packages_content,
+ MatchesAsPackagesFile(
+ MatchesPackage(observed)))
+
+ def test_ignore_one_md5(self):
+ # This is what I actually care about: being able to specify that a
+ # packages file matches a set of packages, ignoring just a few
+ # details on just one package.
+ observed1 = DummyFetchedPackage("foo", "1.1")
+ observed2 = DummyFetchedPackage("bar", "1.2")
+ observed3 = DummyFetchedPackage("baz", "1.5")
+ packages_content = get_packages_file(
+ [observed1, observed2, observed3])
+ oldmd5 = observed3.md5
+ observed3._content = ''.join(reversed(observed3._content_str()))
+ self.assertNotEqual(oldmd5, observed3.md5)
+ self.assertThat(packages_content, MatchesAsPackagesFile(
+ MatchesPackage(observed1),
+ MatchesPackage(observed2),
+ MatchesPackage(observed3).update(md5=None)))