Merge lp:~james-w/linaro-image-tools/tarfile-matchers into lp:linaro-image-tools/11.11

Proposed by James Westby
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: 84
Merged at revision: 55
Proposed branch: lp:~james-w/linaro-image-tools/tarfile-matchers
Merge into: lp:linaro-image-tools/11.11
Prerequisite: lp:~james-w/linaro-image-tools/tarfile-improvements
Diff against target: 423 lines (+398/-0)
4 files modified
hwpack/tarfile_matchers.py (+130/-0)
hwpack/testing.py (+39/-0)
hwpack/tests/__init__.py (+1/-0)
hwpack/tests/test_tarfile_matchers.py (+228/-0)
To merge this branch: bzr merge lp:~james-w/linaro-image-tools/tarfile-matchers
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle (community) Approve
Review via email: mp+34147@code.launchpad.net

Description of the change

Hi,

This adds some matchers that I will use for testing the output of
hwpack-create. It's a lot easier to read and write the tests with this
style, rather than exercising the tarfile API directly.

I don't much like these tests though, suggestions for how to improve them
welcome.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

It looks mostly fine. I agree the tests are a bit horrible.

Could you fold this sort of thing:

+ backing_file = StringIO()
+ with writeable_tarfile(backing_file, default_mtime=12345) as tf:
+ tf.create_file_from_string("foo", "")
+ with standard_tarfile(backing_file) as tf:

into some kind of context manager that would let you write:

with test_tarfile(default_mtime=12345, contents=[('foo', '')]) as tf:

? That would help a bit.

As for the assertions, could you construct a mismatch using keyword arguments and then test that the mismatches match? I don't know if that would be better, but it seems worth a try.

review: Approve
77. By James Westby

Merged tarfile-improvements into tarfile-matchers.

78. By James Westby

Merged tarfile-improvements into tarfile-matchers.

79. By James Westby

Merged tarfile-improvements into tarfile-matchers.

80. By James Westby

Merged tarfile-improvements into tarfile-matchers.

81. By James Westby

Reduce duplication in the tests. Thanks Michael.

82. By James Westby

Add __eq__ and __hash__ on the mismatches.

83. By James Westby

Take advantage of the new __eq__ when checking TarfileWrongValueMismatch.

84. By James Westby

Also test the values set on TarfileMissingPathMismatch.

Revision history for this message
James Westby (james-w) wrote :

On Tue, 31 Aug 2010 02:44:41 -0000, Michael Hudson <email address hidden> wrote:
> Review: Approve
> It looks mostly fine. I agree the tests are a bit horrible.
>
> Could you fold this sort of thing:
>
> + backing_file = StringIO()
> + with writeable_tarfile(backing_file, default_mtime=12345) as tf:
> + tf.create_file_from_string("foo", "")
> + with standard_tarfile(backing_file) as tf:
>
> into some kind of context manager that would let you write:
>
> with test_tarfile(default_mtime=12345, contents=[('foo', '')]) as tf:
>
> ? That would help a bit.

Done, thanks for the suggestion.

> As for the assertions, could you construct a mismatch using keyword
> arguments and then test that the mismatches match? I don't know if
> that would be better, but it seems worth a try.

I took a stab at this, let me know what you think now.

Thanks,

James

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

I think the test_tarfile helper has improved things.

For the second of my suggestions, I actually meant constructing the mismatch in the test and then using assertEquals. But I don't know if that would be any better either (probably not).

I feel bad for causing you to write all those hash and eq tests!

85. By James Westby

Merged tarfile-improvements into tarfile-matchers.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'hwpack/tarfile_matchers.py'
2--- hwpack/tarfile_matchers.py 1970-01-01 00:00:00 +0000
3+++ hwpack/tarfile_matchers.py 2010-09-01 20:48:43 +0000
4@@ -0,0 +1,130 @@
5+from testtools.matchers import Matcher, Mismatch
6+
7+
8+class TarfileMissingPathMismatch(Mismatch):
9+ """A Mismatch indicating that a required path was missing from a tarfile.
10+ """
11+
12+ def __init__(self, tarball, path):
13+ """Create a TarfileMissingPathMismatch Mismatch.
14+
15+ :param tarball: the tarfile that was checked.
16+ :param path: the path that was expected to be present.
17+ """
18+ self.tarball = tarball
19+ self.path = path
20+
21+ def describe(self):
22+ return '"%s" has no path "%s"' % (self.tarball, self.path)
23+
24+ def __eq__(self, other):
25+ return self.tarball == other.tarball and self.path == other.path
26+
27+ def __hash__(self):
28+ return hash((self.tarball, self.path))
29+
30+
31+class TarfileWrongValueMismatch(Mismatch):
32+ """A Mismatch indicating that an entry in a tarfile was not as expected.
33+ """
34+
35+ def __init__(self, attribute, tarball, path, expected, actual):
36+ """Create a TarfileWrongValueMismatch Mismatch.
37+
38+ :param attribute: the attribute that was not as expected.
39+ :type attribute: str
40+ :param tarball: the tarfile that was checked.
41+ :param path: the path that was checked.
42+ :param expected: the expected value of the attribute.
43+ :param actual: the value that was found.
44+ """
45+ self.attribute = attribute
46+ self.tarball = tarball
47+ self.path = path
48+ self.expected = expected
49+ self.actual = actual
50+
51+ def describe(self):
52+ return 'The path "%s" in "%s" has %s %s, expected %s' % (
53+ self.path, self.tarball, self.attribute, self.actual,
54+ self.expected)
55+
56+ def __eq__(self, other):
57+ return (self.attribute == other.attribute
58+ and self.tarball == other.tarball
59+ and self.path == other.path
60+ and self.expected == other.expected
61+ and self.actual == other.actual)
62+
63+ def __hash__(self):
64+ return hash(
65+ (self.attribute, self.tarball, self.path, self.expected,
66+ self.actual))
67+
68+
69+class TarfileHasFile(Matcher):
70+ """Check that a tarfile has an entry with certain values."""
71+
72+ def __init__(self, path, type=None, size=None, mtime=None, mode=None,
73+ linkname=None, uid=None, gid=None, uname=None, gname=None,
74+ content=None):
75+ """Create a TarfileHasFile Matcher.
76+
77+ :param path: the path that must be present.
78+ :type path: str
79+ :param type: the type of TarInfo that must be at `path`, or None
80+ to not check.
81+ :param size: the size that the entry at `path` must have, or None
82+ to not check.
83+ :param mtime: the mtime that the entry at `path` must have, or None
84+ to not check.
85+ :param mode: the mode that the entry at `path` must have, or None
86+ to not check.
87+ :param linkname: the linkname that the entry at `path` must have,
88+ or None to not check.
89+ :param uid: the user id that the entry at `path` must have, or None
90+ to not check.
91+ :param gid: the group id that the entry at `path` must have, or None
92+ to not check.
93+ :param uname: the username that the entry at `path` must have, or
94+ None to not check.
95+ :param gname: the group name that the entry at `path` must have, or
96+ None to not check.
97+ :param content: the content that `path` must have when extracted,
98+ or None to not check.
99+ """
100+ self.path = path
101+ self.type = type
102+ self.size = size
103+ self.mtime = mtime
104+ self.mode = mode
105+ self.linkname = linkname
106+ self.uid = uid
107+ self.gid = gid
108+ self.uname = uname
109+ self.gname = gname
110+ self.content = content
111+
112+ def match(self, tarball):
113+ """Match a tarfile.TarFile against the expected values."""
114+ if self.path not in tarball.getnames():
115+ return TarfileMissingPathMismatch(tarball, self.path)
116+ info = tarball.getmember(self.path)
117+ for attr in (
118+ "type", "size", "mtime", "mode", "linkname", "uid", "gid",
119+ "uname", "gname"):
120+ expected = getattr(self, attr, None)
121+ if expected is not None:
122+ actual = getattr(info, attr)
123+ if expected != actual:
124+ return TarfileWrongValueMismatch(
125+ attr, tarball, self.path, expected, actual)
126+ if self.content is not None:
127+ actual = tarball.extractfile(self.path).read()
128+ if actual != self.content:
129+ return TarfileWrongValueMismatch(
130+ "content", tarball, self.path, self.content, actual)
131+ return None
132+
133+ def __str__(self):
134+ return 'tarfile has file "%s"' % (self.path, )
135
136=== added file 'hwpack/testing.py'
137--- hwpack/testing.py 1970-01-01 00:00:00 +0000
138+++ hwpack/testing.py 2010-09-01 20:48:43 +0000
139@@ -0,0 +1,39 @@
140+from contextlib import contextmanager
141+from StringIO import StringIO
142+import tarfile
143+
144+from hwpack.better_tarfile import writeable_tarfile
145+
146+
147+@contextmanager
148+def test_tarfile(contents=[], **kwargs):
149+ """Create a tarfile with the given contents, then re-open it for reading.
150+
151+ This context manager creates a tarfile with the given contents, then
152+ reopens it for reading and yields it for use in a with block. When
153+ the block ends the tarfile will be closed.
154+
155+ The contents can be specified as a list of tuples of (path, contents),
156+ where if the path ends with '/' it is considered to be a directory and
157+ the contents ignored.
158+
159+ :param contents: the contents to put in the tarball, defaults to the
160+ empty list.
161+ :type contents: a list of tuples of (str, str)
162+ :param kwargs: keyword arguments for the better_tarfile.TarFile
163+ constructor.
164+ """
165+ backing_file = StringIO()
166+ with writeable_tarfile(backing_file, **kwargs) as tf:
167+ for path, content in contents:
168+ if path[-1] == "/":
169+ tf.create_dir(path)
170+ else:
171+ tf.create_file_from_string(path, content)
172+ if contents:
173+ backing_file.seek(0)
174+ tf = tarfile.TarFile.open(mode="r", fileobj=backing_file)
175+ try:
176+ yield tf
177+ finally:
178+ tf.close()
179
180=== modified file 'hwpack/tests/__init__.py'
181--- hwpack/tests/__init__.py 2010-09-01 20:48:43 +0000
182+++ hwpack/tests/__init__.py 2010-09-01 20:48:43 +0000
183@@ -3,6 +3,7 @@
184 def test_suite():
185 module_names = ['hwpack.tests.test_config',
186 'hwpack.tests.test_better_tarfile',
187+ 'hwpack.tests.test_tarfile_matchers',
188 ]
189 loader = unittest.TestLoader()
190 suite = loader.loadTestsFromNames(module_names)
191
192=== added file 'hwpack/tests/test_tarfile_matchers.py'
193--- hwpack/tests/test_tarfile_matchers.py 1970-01-01 00:00:00 +0000
194+++ hwpack/tests/test_tarfile_matchers.py 2010-09-01 20:48:43 +0000
195@@ -0,0 +1,228 @@
196+from StringIO import StringIO
197+import tarfile
198+
199+from testtools import TestCase
200+
201+from hwpack.tarfile_matchers import (
202+ TarfileHasFile,
203+ TarfileMissingPathMismatch,
204+ TarfileWrongValueMismatch,
205+ )
206+from hwpack.testing import test_tarfile
207+
208+
209+class TarfileMissingPathMismatchTests(TestCase):
210+
211+ def test_describe(self):
212+ mismatch = TarfileMissingPathMismatch("foo", "bar")
213+ self.assertEqual('"foo" has no path "bar"', mismatch.describe())
214+
215+ def test_eq(self):
216+ mismatch1 = TarfileMissingPathMismatch("foo", "bar")
217+ mismatch2 = TarfileMissingPathMismatch("foo", "bar")
218+ self.assertEqual(mismatch1, mismatch2)
219+
220+ def test_no_eq_different_tarball(self):
221+ mismatch1 = TarfileMissingPathMismatch("foo", "bar")
222+ mismatch2 = TarfileMissingPathMismatch("baz", "bar")
223+ self.assertNotEqual(mismatch1, mismatch2)
224+
225+ def test_no_eq_different_path(self):
226+ mismatch1 = TarfileMissingPathMismatch("foo", "bar")
227+ mismatch2 = TarfileMissingPathMismatch("foo", "baz")
228+ self.assertNotEqual(mismatch1, mismatch2)
229+
230+ def test_hash_equal(self):
231+ mismatch1 = TarfileMissingPathMismatch("foo", "bar")
232+ mismatch2 = TarfileMissingPathMismatch("foo", "bar")
233+ self.assertEqual(hash(mismatch1), hash(mismatch2))
234+
235+ def test_different_tarball_different_hash(self):
236+ mismatch1 = TarfileMissingPathMismatch("foo", "bar")
237+ mismatch2 = TarfileMissingPathMismatch("baz", "bar")
238+ self.assertNotEqual(hash(mismatch1), hash(mismatch2))
239+
240+ def test_different_path_different_hash(self):
241+ mismatch1 = TarfileMissingPathMismatch("foo", "bar")
242+ mismatch2 = TarfileMissingPathMismatch("foo", "baz")
243+ self.assertNotEqual(hash(mismatch1), hash(mismatch2))
244+
245+
246+class TarfileWrongTypeMismatchTests(TestCase):
247+
248+ def test_describe(self):
249+ mismatch = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
250+ self.assertEqual(
251+ 'The path "bar" in "foo" has type 2, expected 1',
252+ mismatch.describe())
253+
254+ def test_eq(self):
255+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
256+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
257+ self.assertEqual(mismatch1, mismatch2)
258+
259+ def test_not_eq_different_attribute(self):
260+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
261+ mismatch2 = TarfileWrongValueMismatch("size", "foo", "bar", 1, 2)
262+ self.assertNotEqual(mismatch1, mismatch2)
263+
264+ def test_not_eq_different_tarball(self):
265+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
266+ mismatch2 = TarfileWrongValueMismatch("type", "baz", "bar", 1, 2)
267+ self.assertNotEqual(mismatch1, mismatch2)
268+
269+ def test_not_eq_different_path(self):
270+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
271+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "baz", 1, 2)
272+ self.assertNotEqual(mismatch1, mismatch2)
273+
274+ def test_not_eq_different_expected(self):
275+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
276+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "bar", 3, 2)
277+ self.assertNotEqual(mismatch1, mismatch2)
278+
279+ def test_not_eq_different_actual(self):
280+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
281+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 3)
282+ self.assertNotEqual(mismatch1, mismatch2)
283+
284+ def test_hash_equal(self):
285+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
286+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
287+ self.assertEqual(hash(mismatch1), hash(mismatch2))
288+
289+ def test_different_attribute_different_hash(self):
290+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
291+ mismatch2 = TarfileWrongValueMismatch("size", "foo", "bar", 1, 2)
292+ self.assertNotEqual(hash(mismatch1), hash(mismatch2))
293+
294+ def test_different_tarball_different_hash(self):
295+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
296+ mismatch2 = TarfileWrongValueMismatch("type", "baz", "bar", 1, 2)
297+ self.assertNotEqual(hash(mismatch1), hash(mismatch2))
298+
299+ def test_different_path_different_hash(self):
300+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
301+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "baz", 1, 2)
302+ self.assertNotEqual(hash(mismatch1), hash(mismatch2))
303+
304+ def test_different_expected_different_hash(self):
305+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
306+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "bar", 3, 2)
307+ self.assertNotEqual(hash(mismatch1), hash(mismatch2))
308+
309+ def test_different_actual_different_hash(self):
310+ mismatch1 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 2)
311+ mismatch2 = TarfileWrongValueMismatch("type", "foo", "bar", 1, 3)
312+ self.assertNotEqual(hash(mismatch1), hash(mismatch2))
313+
314+
315+class TarfileHasFileTests(TestCase):
316+
317+ def test_str(self):
318+ matcher = TarfileHasFile("foo")
319+ self.assertEqual('tarfile has file "foo"', str(matcher))
320+
321+ def test_matches(self):
322+ backing_file = StringIO()
323+ with test_tarfile(contents=[("foo", "")]) as tf:
324+ matcher = TarfileHasFile("foo")
325+ self.assertIs(None, matcher.match(tf))
326+
327+ def test_mismatches_missing_path(self):
328+ backing_file = StringIO()
329+ with test_tarfile() as tf:
330+ matcher = TarfileHasFile("foo")
331+ mismatch = matcher.match(tf)
332+ self.assertIsInstance(mismatch, TarfileMissingPathMismatch)
333+ self.assertEqual(TarfileMissingPathMismatch(tf, "foo"), mismatch)
334+
335+ def assertValueMismatch(self, mismatch, tarball, path, attribute,
336+ expected, actual):
337+ self.assertIsInstance(mismatch, TarfileWrongValueMismatch)
338+ expected_mismatch = TarfileWrongValueMismatch(
339+ attribute, tarball, path, expected, actual)
340+ self.assertEqual(expected_mismatch, mismatch)
341+
342+ def test_mismatches_wrong_type(self):
343+ backing_file = StringIO()
344+ with test_tarfile(contents=[("foo", "")]) as tf:
345+ matcher = TarfileHasFile("foo", type=tarfile.DIRTYPE)
346+ mismatch = matcher.match(tf)
347+ self.assertValueMismatch(
348+ mismatch, tf, "foo", "type", tarfile.DIRTYPE,
349+ tarfile.REGTYPE)
350+
351+ def test_mismatches_wrong_size(self):
352+ backing_file = StringIO()
353+ with test_tarfile(contents=[("foo", "")]) as tf:
354+ matcher = TarfileHasFile("foo", size=1235)
355+ mismatch = matcher.match(tf)
356+ self.assertValueMismatch(
357+ mismatch, tf, "foo", "size", 1235, 0)
358+
359+ def test_mismatches_wrong_mtime(self):
360+ backing_file = StringIO()
361+ with test_tarfile(contents=[("foo", "")], default_mtime=12345) as tf:
362+ matcher = TarfileHasFile("foo", mtime=54321)
363+ mismatch = matcher.match(tf)
364+ self.assertValueMismatch(
365+ mismatch, tf, "foo", "mtime", 54321, 12345)
366+
367+ def test_mismatches_wrong_mode(self):
368+ backing_file = StringIO()
369+ with test_tarfile(contents=[("foo", "")]) as tf:
370+ matcher = TarfileHasFile("foo", mode=0000)
371+ mismatch = matcher.match(tf)
372+ self.assertValueMismatch(
373+ mismatch, tf, "foo", "mode", 0000, 0644)
374+
375+ def test_mismatches_wrong_linkname(self):
376+ backing_file = StringIO()
377+ with test_tarfile(contents=[("foo", "")]) as tf:
378+ matcher = TarfileHasFile("foo", linkname="somelink")
379+ mismatch = matcher.match(tf)
380+ self.assertValueMismatch(
381+ mismatch, tf, "foo", "linkname", "somelink", "")
382+
383+ def test_mismatches_wrong_uid(self):
384+ backing_file = StringIO()
385+ with test_tarfile(contents=[("foo", "")], default_uid=100) as tf:
386+ matcher = TarfileHasFile("foo", uid=99)
387+ mismatch = matcher.match(tf)
388+ self.assertValueMismatch(
389+ mismatch, tf, "foo", "uid", 99, 100)
390+
391+ def test_mismatches_wrong_gid(self):
392+ backing_file = StringIO()
393+ with test_tarfile(contents=[("foo", "")], default_gid=100) as tf:
394+ matcher = TarfileHasFile("foo", gid=99)
395+ mismatch = matcher.match(tf)
396+ self.assertValueMismatch(
397+ mismatch, tf, "foo", "gid", 99, 100)
398+
399+ def test_mismatches_wrong_uname(self):
400+ backing_file = StringIO()
401+ with test_tarfile(
402+ contents=[("foo", "")], default_uname="someuser") as tf:
403+ matcher = TarfileHasFile("foo", uname="otheruser")
404+ mismatch = matcher.match(tf)
405+ self.assertValueMismatch(
406+ mismatch, tf, "foo", "uname", "otheruser", "someuser")
407+
408+ def test_mismatches_wrong_gname(self):
409+ backing_file = StringIO()
410+ with test_tarfile(
411+ contents=[("foo", "")], default_gname="somegroup") as tf:
412+ matcher = TarfileHasFile("foo", gname="othergroup")
413+ mismatch = matcher.match(tf)
414+ self.assertValueMismatch(
415+ mismatch, tf, "foo", "gname", "othergroup", "somegroup")
416+
417+ def test_mismatches_wrong_content(self):
418+ backing_file = StringIO()
419+ with test_tarfile(contents=[("foo", "somecontent")]) as tf:
420+ matcher = TarfileHasFile("foo", content="othercontent")
421+ mismatch = matcher.match(tf)
422+ self.assertValueMismatch(
423+ mismatch, tf, "foo", "content", "othercontent", "somecontent")

Subscribers

People subscribed via source and target branches