Merge lp:~james-w/linaro-image-tools/tarfile-matchers into lp:linaro-image-tools/11.11
- tarfile-matchers
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle (community) | Approve | ||
Review via email: mp+34147@code.launchpad.net |
Commit message
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
- 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 TarfileWrongVal
ueMismatch. - 84. By James Westby
-
Also test the values set on TarfileMissingP
athMismatch.
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_
> + tf.create_
> + with standard_
>
> into some kind of context manager that would let you write:
>
> with test_tarfile(
>
> ? 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
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
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") |
It looks mostly fine. I agree the tests are a bit horrible.
Could you fold this sort of thing:
+ backing_file = StringIO() tarfile( backing_ file, default_ mtime=12345) as tf: file_from_ string( "foo", "") tarfile( backing_ file) as tf:
+ with writeable_
+ tf.create_
+ with standard_
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.