Make sure ids get updated when the object changes.
[jelmer/dulwich-libgit2.git] / dulwich / tests / test_objects.py
1 # test_objects.py -- tests for objects.py
2 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
3
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # of the License or (at your option) any later version of 
8 # the License.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA  02110-1301, USA.
19
20
21 """Tests for git base objects."""
22
23 # TODO: Round-trip parse-serialize-parse and serialize-parse-serialize tests.
24
25
26 import datetime
27 import os
28 import stat
29 import unittest
30
31 from dulwich.errors import (
32     ChecksumMismatch,
33     ObjectFormatException,
34     )
35 from dulwich.objects import (
36     Blob,
37     Tree,
38     Commit,
39     Tag,
40     format_timezone,
41     hex_to_sha,
42     sha_to_hex,
43     hex_to_filename,
44     check_hexsha,
45     check_identity,
46     parse_timezone,
47     parse_tree,
48     _parse_tree_py,
49     )
50 from dulwich.tests import (
51     TestSkipped,
52     )
53 from utils import (
54     make_commit,
55     make_object,
56     )
57
58 a_sha = '6f670c0fb53f9463760b7295fbb814e965fb20c8'
59 b_sha = '2969be3e8ee1c0222396a5611407e4769f14e54b'
60 c_sha = '954a536f7819d40e6f637f849ee187dd10066349'
61 tree_sha = '70c190eb48fa8bbb50ddc692a17b44cb781af7f6'
62 tag_sha = '71033db03a03c6a36721efcf1968dd8f8e0cf023'
63
64
65 try:
66     from itertools import permutations
67 except ImportError:
68     # Implementation of permutations from Python 2.6 documentation:
69     # http://docs.python.org/2.6/library/itertools.html#itertools.permutations
70     # Copyright (c) 2001-2010 Python Software Foundation; All Rights Reserved
71     # Modified syntax slightly to run under Python 2.4.
72     def permutations(iterable, r=None):
73         # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
74         # permutations(range(3)) --> 012 021 102 120 201 210
75         pool = tuple(iterable)
76         n = len(pool)
77         if r is None:
78             r = n
79         if r > n:
80             return
81         indices = range(n)
82         cycles = range(n, n-r, -1)
83         yield tuple(pool[i] for i in indices[:r])
84         while n:
85             for i in reversed(range(r)):
86                 cycles[i] -= 1
87                 if cycles[i] == 0:
88                     indices[i:] = indices[i+1:] + indices[i:i+1]
89                     cycles[i] = n - i
90                 else:
91                     j = cycles[i]
92                     indices[i], indices[-j] = indices[-j], indices[i]
93                     yield tuple(pool[i] for i in indices[:r])
94                     break
95             else:
96                 return
97
98
99 class TestHexToSha(unittest.TestCase):
100
101     def test_simple(self):
102         self.assertEquals("\xab\xcd" * 10, hex_to_sha("abcd" * 10))
103
104     def test_reverse(self):
105         self.assertEquals("abcd" * 10, sha_to_hex("\xab\xcd" * 10))
106
107
108 class BlobReadTests(unittest.TestCase):
109     """Test decompression of blobs"""
110
111     def get_sha_file(self, cls, base, sha):
112         dir = os.path.join(os.path.dirname(__file__), 'data', base)
113         return cls.from_path(hex_to_filename(dir, sha))
114
115     def get_blob(self, sha):
116         """Return the blob named sha from the test data dir"""
117         return self.get_sha_file(Blob, 'blobs', sha)
118   
119     def get_tree(self, sha):
120         return self.get_sha_file(Tree, 'trees', sha)
121   
122     def get_tag(self, sha):
123         return self.get_sha_file(Tag, 'tags', sha)
124   
125     def commit(self, sha):
126         return self.get_sha_file(Commit, 'commits', sha)
127   
128     def test_decompress_simple_blob(self):
129         b = self.get_blob(a_sha)
130         self.assertEqual(b.data, 'test 1\n')
131         self.assertEqual(b.sha().hexdigest(), a_sha)
132   
133     def test_hash(self):
134         b = self.get_blob(a_sha)
135         self.assertEqual(hash(b.id), hash(b))
136
137     def test_parse_empty_blob_object(self):
138         sha = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
139         b = self.get_blob(sha)
140         self.assertEqual(b.data, '')
141         self.assertEqual(b.id, sha)
142         self.assertEqual(b.sha().hexdigest(), sha)
143   
144     def test_create_blob_from_string(self):
145         string = 'test 2\n'
146         b = Blob.from_string(string)
147         self.assertEqual(b.data, string)
148         self.assertEqual(b.sha().hexdigest(), b_sha)
149
150     def test_chunks(self):
151         string = 'test 5\n'
152         b = Blob.from_string(string)
153         self.assertEqual([string], b.chunked)
154
155     def test_set_chunks(self):
156         b = Blob()
157         b.chunked = ['te', 'st', ' 5\n']
158         self.assertEqual('test 5\n', b.data)
159         b.chunked = ['te', 'st', ' 6\n']
160         self.assertEqual('test 6\n', b.as_raw_string())
161   
162     def test_parse_legacy_blob(self):
163         string = 'test 3\n'
164         b = self.get_blob(c_sha)
165         self.assertEqual(b.data, string)
166         self.assertEqual(b.sha().hexdigest(), c_sha)
167   
168     def test_eq(self):
169         blob1 = self.get_blob(a_sha)
170         blob2 = self.get_blob(a_sha)
171         self.assertEqual(blob1, blob2)
172   
173     def test_read_tree_from_file(self):
174         t = self.get_tree(tree_sha)
175         self.assertEqual(t.entries()[0], (33188, 'a', a_sha))
176         self.assertEqual(t.entries()[1], (33188, 'b', b_sha))
177   
178     def test_read_tag_from_file(self):
179         t = self.get_tag(tag_sha)
180         self.assertEqual(t.object, (Commit, '51b668fd5bf7061b7d6fa525f88803e6cfadaa51'))
181         self.assertEqual(t.name,'signed')
182         self.assertEqual(t.tagger,'Ali Sabil <ali.sabil@gmail.com>')
183         self.assertEqual(t.tag_time, 1231203091)
184         self.assertEqual(t.message, 'This is a signed tag\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v1.4.9 (GNU/Linux)\n\niEYEABECAAYFAkliqx8ACgkQqSMmLy9u/kcx5ACfakZ9NnPl02tOyYP6pkBoEkU1\n5EcAn0UFgokaSvS371Ym/4W9iJj6vh3h\n=ql7y\n-----END PGP SIGNATURE-----\n')
185   
186     def test_read_commit_from_file(self):
187         sha = '60dacdc733de308bb77bb76ce0fb0f9b44c9769e'
188         c = self.commit(sha)
189         self.assertEqual(c.tree, tree_sha)
190         self.assertEqual(c.parents,
191             ['0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
192         self.assertEqual(c.author,
193             'James Westby <jw+debian@jameswestby.net>')
194         self.assertEqual(c.committer,
195             'James Westby <jw+debian@jameswestby.net>')
196         self.assertEqual(c.commit_time, 1174759230)
197         self.assertEqual(c.commit_timezone, 0)
198         self.assertEqual(c.author_timezone, 0)
199         self.assertEqual(c.message, 'Test commit\n')
200   
201     def test_read_commit_no_parents(self):
202         sha = '0d89f20333fbb1d2f3a94da77f4981373d8f4310'
203         c = self.commit(sha)
204         self.assertEqual(c.tree, '90182552c4a85a45ec2a835cadc3451bebdfe870')
205         self.assertEqual(c.parents, [])
206         self.assertEqual(c.author,
207             'James Westby <jw+debian@jameswestby.net>')
208         self.assertEqual(c.committer,
209             'James Westby <jw+debian@jameswestby.net>')
210         self.assertEqual(c.commit_time, 1174758034)
211         self.assertEqual(c.commit_timezone, 0)
212         self.assertEqual(c.author_timezone, 0)
213         self.assertEqual(c.message, 'Test commit\n')
214   
215     def test_read_commit_two_parents(self):
216         sha = '5dac377bdded4c9aeb8dff595f0faeebcc8498cc'
217         c = self.commit(sha)
218         self.assertEqual(c.tree, 'd80c186a03f423a81b39df39dc87fd269736ca86')
219         self.assertEqual(c.parents, ['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
220                                        '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'])
221         self.assertEqual(c.author,
222             'James Westby <jw+debian@jameswestby.net>')
223         self.assertEqual(c.committer,
224             'James Westby <jw+debian@jameswestby.net>')
225         self.assertEqual(c.commit_time, 1174773719)
226         self.assertEqual(c.commit_timezone, 0)
227         self.assertEqual(c.author_timezone, 0)
228         self.assertEqual(c.message, 'Merge ../b\n')
229
230
231 class ShaFileCheckTests(unittest.TestCase):
232
233     def assertCheckFails(self, cls, data):
234         obj = cls()
235         def do_check():
236             obj.set_raw_string(data)
237             obj.check()
238         self.assertRaises(ObjectFormatException, do_check)
239
240     def assertCheckSucceeds(self, cls, data):
241         obj = cls()
242         obj.set_raw_string(data)
243         self.assertEqual(None, obj.check())
244
245
246 class CommitSerializationTests(unittest.TestCase):
247
248     def make_commit(self, **kwargs):
249         attrs = {'tree': 'd80c186a03f423a81b39df39dc87fd269736ca86',
250                  'parents': ['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
251                              '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'],
252                  'author': 'James Westby <jw+debian@jameswestby.net>',
253                  'committer': 'James Westby <jw+debian@jameswestby.net>',
254                  'commit_time': 1174773719,
255                  'author_time': 1174773719,
256                  'commit_timezone': 0,
257                  'author_timezone': 0,
258                  'message':  'Merge ../b\n'}
259         attrs.update(kwargs)
260         return make_commit(**attrs)
261
262     def test_encoding(self):
263         c = self.make_commit(encoding='iso8859-1')
264         self.assertTrue('encoding iso8859-1\n' in c.as_raw_string())
265
266     def test_short_timestamp(self):
267         c = self.make_commit(commit_time=30)
268         c1 = Commit()
269         c1.set_raw_string(c.as_raw_string())
270         self.assertEquals(30, c1.commit_time)
271
272     def test_raw_length(self):
273         c = self.make_commit()
274         self.assertEquals(len(c.as_raw_string()), c.raw_length())
275
276     def test_simple(self):
277         c = self.make_commit()
278         self.assertEquals(c.id, '5dac377bdded4c9aeb8dff595f0faeebcc8498cc')
279         self.assertEquals(
280                 'tree d80c186a03f423a81b39df39dc87fd269736ca86\n'
281                 'parent ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd\n'
282                 'parent 4cffe90e0a41ad3f5190079d7c8f036bde29cbe6\n'
283                 'author James Westby <jw+debian@jameswestby.net> '
284                 '1174773719 +0000\n'
285                 'committer James Westby <jw+debian@jameswestby.net> '
286                 '1174773719 +0000\n'
287                 '\n'
288                 'Merge ../b\n', c.as_raw_string())
289
290     def test_timezone(self):
291         c = self.make_commit(commit_timezone=(5 * 60))
292         self.assertTrue(" +0005\n" in c.as_raw_string())
293
294     def test_neg_timezone(self):
295         c = self.make_commit(commit_timezone=(-1 * 3600))
296         self.assertTrue(" -0100\n" in c.as_raw_string())
297
298
299 default_committer = 'James Westby <jw+debian@jameswestby.net> 1174773719 +0000'
300
301 class CommitParseTests(ShaFileCheckTests):
302
303     def make_commit_lines(self,
304                           tree='d80c186a03f423a81b39df39dc87fd269736ca86',
305                           parents=['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
306                                    '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'],
307                           author=default_committer,
308                           committer=default_committer,
309                           encoding=None,
310                           message='Merge ../b\n',
311                           extra=None):
312         lines = []
313         if tree is not None:
314             lines.append('tree %s' % tree)
315         if parents is not None:
316             lines.extend('parent %s' % p for p in parents)
317         if author is not None:
318             lines.append('author %s' % author)
319         if committer is not None:
320             lines.append('committer %s' % committer)
321         if encoding is not None:
322             lines.append('encoding %s' % encoding)
323         if extra is not None:
324             for name, value in sorted(extra.iteritems()):
325                 lines.append('%s %s' % (name, value))
326         lines.append('')
327         if message is not None:
328             lines.append(message)
329         return lines
330
331     def make_commit_text(self, **kwargs):
332         return '\n'.join(self.make_commit_lines(**kwargs))
333
334     def test_simple(self):
335         c = Commit.from_string(self.make_commit_text())
336         self.assertEquals('Merge ../b\n', c.message)
337         self.assertEquals('James Westby <jw+debian@jameswestby.net>', c.author)
338         self.assertEquals('James Westby <jw+debian@jameswestby.net>',
339                           c.committer)
340         self.assertEquals('d80c186a03f423a81b39df39dc87fd269736ca86', c.tree)
341         self.assertEquals(['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
342                            '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'],
343                           c.parents)
344         expected_time = datetime.datetime(2007, 3, 24, 22, 1, 59)
345         self.assertEquals(expected_time,
346                           datetime.datetime.utcfromtimestamp(c.commit_time))
347         self.assertEquals(0, c.commit_timezone)
348         self.assertEquals(expected_time,
349                           datetime.datetime.utcfromtimestamp(c.author_time))
350         self.assertEquals(0, c.author_timezone)
351         self.assertEquals(None, c.encoding)
352
353     def test_custom(self):
354         c = Commit.from_string(self.make_commit_text(
355           extra={'extra-field': 'data'}))
356         self.assertEquals([('extra-field', 'data')], c.extra)
357
358     def test_encoding(self):
359         c = Commit.from_string(self.make_commit_text(encoding='UTF-8'))
360         self.assertEquals('UTF-8', c.encoding)
361
362     def test_check(self):
363         self.assertCheckSucceeds(Commit, self.make_commit_text())
364         self.assertCheckSucceeds(Commit, self.make_commit_text(parents=None))
365         self.assertCheckSucceeds(Commit,
366                                  self.make_commit_text(encoding='UTF-8'))
367
368         self.assertCheckFails(Commit, self.make_commit_text(tree='xxx'))
369         self.assertCheckFails(Commit, self.make_commit_text(
370           parents=[a_sha, 'xxx']))
371         bad_committer = "some guy without an email address 1174773719 +0000"
372         self.assertCheckFails(Commit,
373                               self.make_commit_text(committer=bad_committer))
374         self.assertCheckFails(Commit,
375                               self.make_commit_text(author=bad_committer))
376         self.assertCheckFails(Commit, self.make_commit_text(author=None))
377         self.assertCheckFails(Commit, self.make_commit_text(committer=None))
378         self.assertCheckFails(Commit, self.make_commit_text(
379           author=None, committer=None))
380
381     def test_check_duplicates(self):
382         # duplicate each of the header fields
383         for i in xrange(5):
384             lines = self.make_commit_lines(parents=[a_sha], encoding='UTF-8')
385             lines.insert(i, lines[i])
386             text = '\n'.join(lines)
387             if lines[i].startswith('parent'):
388                 # duplicate parents are ok for now
389                 self.assertCheckSucceeds(Commit, text)
390             else:
391                 self.assertCheckFails(Commit, text)
392
393     def test_check_order(self):
394         lines = self.make_commit_lines(parents=[a_sha], encoding='UTF-8')
395         headers = lines[:5]
396         rest = lines[5:]
397         # of all possible permutations, ensure only the original succeeds
398         for perm in permutations(headers):
399             perm = list(perm)
400             text = '\n'.join(perm + rest)
401             if perm == headers:
402                 self.assertCheckSucceeds(Commit, text)
403             else:
404                 self.assertCheckFails(Commit, text)
405
406
407 class TreeTests(ShaFileCheckTests):
408
409     def test_simple(self):
410         myhexsha = "d80c186a03f423a81b39df39dc87fd269736ca86"
411         x = Tree()
412         x["myname"] = (0100755, myhexsha)
413         self.assertEquals('100755 myname\0' + hex_to_sha(myhexsha),
414                 x.as_raw_string())
415
416     def test_tree_update_id(self):
417         x = Tree()
418         x["a.c"] = (0100755, "d80c186a03f423a81b39df39dc87fd269736ca86")
419         self.assertEquals("0c5c6bc2c081accfbc250331b19e43b904ab9cdd", x.id)
420         x["a.b"] = (stat.S_IFDIR, "d80c186a03f423a81b39df39dc87fd269736ca86")
421         self.assertEquals("07bfcb5f3ada15bbebdfa3bbb8fd858a363925c8", x.id)
422
423     def test_tree_dir_sort(self):
424         x = Tree()
425         x["a.c"] = (0100755, "d80c186a03f423a81b39df39dc87fd269736ca86")
426         x["a"] = (stat.S_IFDIR, "d80c186a03f423a81b39df39dc87fd269736ca86")
427         x["a/c"] = (stat.S_IFDIR, "d80c186a03f423a81b39df39dc87fd269736ca86")
428         self.assertEquals(["a.c", "a", "a/c"], [p[0] for p in x.iteritems()])
429
430     def _do_test_parse_tree(self, parse_tree):
431         dir = os.path.join(os.path.dirname(__file__), 'data', 'trees')
432         o = Tree.from_path(hex_to_filename(dir, tree_sha))
433         self.assertEquals([('a', 0100644, a_sha), ('b', 0100644, b_sha)],
434                           list(parse_tree(o.as_raw_string())))
435
436     def test_parse_tree(self):
437         self._do_test_parse_tree(_parse_tree_py)
438
439     def test_parse_tree_extension(self):
440         if parse_tree is _parse_tree_py:
441             raise TestSkipped('parse_tree extension not found')
442         self._do_test_parse_tree(parse_tree)
443
444     def test_check(self):
445         t = Tree
446         sha = hex_to_sha(a_sha)
447
448         # filenames
449         self.assertCheckSucceeds(t, '100644 .a\0%s' % sha)
450         self.assertCheckFails(t, '100644 \0%s' % sha)
451         self.assertCheckFails(t, '100644 .\0%s' % sha)
452         self.assertCheckFails(t, '100644 a/a\0%s' % sha)
453         self.assertCheckFails(t, '100644 ..\0%s' % sha)
454
455         # modes
456         self.assertCheckSucceeds(t, '100644 a\0%s' % sha)
457         self.assertCheckSucceeds(t, '100755 a\0%s' % sha)
458         self.assertCheckSucceeds(t, '160000 a\0%s' % sha)
459         # TODO more whitelisted modes
460         self.assertCheckFails(t, '123456 a\0%s' % sha)
461         self.assertCheckFails(t, '123abc a\0%s' % sha)
462
463         # shas
464         self.assertCheckFails(t, '100644 a\0%s' % ('x' * 5))
465         self.assertCheckFails(t, '100644 a\0%s' % ('x' * 18 + '\0'))
466         self.assertCheckFails(t, '100644 a\0%s\n100644 b\0%s' % ('x' * 21, sha))
467
468         # ordering
469         sha2 = hex_to_sha(b_sha)
470         self.assertCheckSucceeds(t, '100644 a\0%s\n100644 b\0%s' % (sha, sha))
471         self.assertCheckSucceeds(t, '100644 a\0%s\n100644 b\0%s' % (sha, sha2))
472         self.assertCheckFails(t, '100644 a\0%s\n100755 a\0%s' % (sha, sha2))
473         self.assertCheckFails(t, '100644 b\0%s\n100644 a\0%s' % (sha2, sha))
474
475     def test_iter(self):
476         t = Tree()
477         t["foo"] = (0100644, a_sha)
478         self.assertEquals(set(["foo"]), set(t))
479
480
481 class TagSerializeTests(unittest.TestCase):
482
483     def test_serialize_simple(self):
484         x = make_object(Tag,
485                         tagger='Jelmer Vernooij <jelmer@samba.org>',
486                         name='0.1',
487                         message='Tag 0.1',
488                         object=(Blob, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
489                         tag_time=423423423,
490                         tag_timezone=0)
491         self.assertEquals(('object d80c186a03f423a81b39df39dc87fd269736ca86\n'
492                            'type blob\n'
493                            'tag 0.1\n'
494                            'tagger Jelmer Vernooij <jelmer@samba.org> '
495                            '423423423 +0000\n'
496                            '\n'
497                            'Tag 0.1'), x.as_raw_string())
498
499
500 default_tagger = ('Linus Torvalds <torvalds@woody.linux-foundation.org> '
501                   '1183319674 -0700')
502 default_message = """Linux 2.6.22-rc7
503 -----BEGIN PGP SIGNATURE-----
504 Version: GnuPG v1.4.7 (GNU/Linux)
505
506 iD8DBQBGiAaAF3YsRnbiHLsRAitMAKCiLboJkQECM/jpYsY3WPfvUgLXkACgg3ql
507 OK2XeQOiEeXtT76rV4t2WR4=
508 =ivrA
509 -----END PGP SIGNATURE-----
510 """
511
512
513 class TagParseTests(ShaFileCheckTests):
514     def make_tag_lines(self,
515                        object_sha="a38d6181ff27824c79fc7df825164a212eff6a3f",
516                        object_type_name="commit",
517                        name="v2.6.22-rc7",
518                        tagger=default_tagger,
519                        message=default_message):
520         lines = []
521         if object_sha is not None:
522             lines.append("object %s" % object_sha)
523         if object_type_name is not None:
524             lines.append("type %s" % object_type_name)
525         if name is not None:
526             lines.append("tag %s" % name)
527         if tagger is not None:
528             lines.append("tagger %s" % tagger)
529         lines.append("")
530         if message is not None:
531             lines.append(message)
532         return lines
533
534     def make_tag_text(self, **kwargs):
535         return "\n".join(self.make_tag_lines(**kwargs))
536
537     def test_parse(self):
538         x = Tag()
539         x.set_raw_string(self.make_tag_text())
540         self.assertEquals(
541             "Linus Torvalds <torvalds@woody.linux-foundation.org>", x.tagger)
542         self.assertEquals("v2.6.22-rc7", x.name)
543         object_type, object_sha = x.object
544         self.assertEquals("a38d6181ff27824c79fc7df825164a212eff6a3f",
545                           object_sha)
546         self.assertEquals(Commit, object_type)
547         self.assertEquals(datetime.datetime.utcfromtimestamp(x.tag_time),
548                           datetime.datetime(2007, 7, 1, 19, 54, 34))
549         self.assertEquals(-25200, x.tag_timezone)
550
551     def test_parse_no_tagger(self):
552         x = Tag()
553         x.set_raw_string(self.make_tag_text(tagger=None))
554         self.assertEquals(None, x.tagger)
555         self.assertEquals("v2.6.22-rc7", x.name)
556
557     def test_check(self):
558         self.assertCheckSucceeds(Tag, self.make_tag_text())
559         self.assertCheckFails(Tag, self.make_tag_text(object_sha=None))
560         self.assertCheckFails(Tag, self.make_tag_text(object_type_name=None))
561         self.assertCheckFails(Tag, self.make_tag_text(name=None))
562         self.assertCheckFails(Tag, self.make_tag_text(name=''))
563         self.assertCheckFails(Tag, self.make_tag_text(
564           object_type_name="foobar"))
565         self.assertCheckFails(Tag, self.make_tag_text(
566           tagger="some guy without an email address 1183319674 -0700"))
567         self.assertCheckFails(Tag, self.make_tag_text(
568           tagger=("Linus Torvalds <torvalds@woody.linux-foundation.org> "
569                   "Sun 7 Jul 2007 12:54:34 +0700")))
570         self.assertCheckFails(Tag, self.make_tag_text(object_sha="xxx"))
571
572     def test_check_duplicates(self):
573         # duplicate each of the header fields
574         for i in xrange(4):
575             lines = self.make_tag_lines()
576             lines.insert(i, lines[i])
577             self.assertCheckFails(Tag, '\n'.join(lines))
578
579     def test_check_order(self):
580         lines = self.make_tag_lines()
581         headers = lines[:4]
582         rest = lines[4:]
583         # of all possible permutations, ensure only the original succeeds
584         for perm in permutations(headers):
585             perm = list(perm)
586             text = '\n'.join(perm + rest)
587             if perm == headers:
588                 self.assertCheckSucceeds(Tag, text)
589             else:
590                 self.assertCheckFails(Tag, text)
591
592
593 class CheckTests(unittest.TestCase):
594
595     def test_check_hexsha(self):
596         check_hexsha(a_sha, "failed to check good sha")
597         self.assertRaises(ObjectFormatException, check_hexsha, '1' * 39,
598                           'sha too short')
599         self.assertRaises(ObjectFormatException, check_hexsha, '1' * 41,
600                           'sha too long')
601         self.assertRaises(ObjectFormatException, check_hexsha, 'x' * 40,
602                           'invalid characters')
603
604     def test_check_identity(self):
605         check_identity("Dave Borowitz <dborowitz@google.com>",
606                        "failed to check good identity")
607         check_identity("<dborowitz@google.com>",
608                        "failed to check good identity")
609         self.assertRaises(ObjectFormatException, check_identity,
610                           "Dave Borowitz", "no email")
611         self.assertRaises(ObjectFormatException, check_identity,
612                           "Dave Borowitz <dborowitz", "incomplete email")
613         self.assertRaises(ObjectFormatException, check_identity,
614                           "dborowitz@google.com>", "incomplete email")
615         self.assertRaises(ObjectFormatException, check_identity,
616                           "Dave Borowitz <<dborowitz@google.com>", "typo")
617         self.assertRaises(ObjectFormatException, check_identity,
618                           "Dave Borowitz <dborowitz@google.com>>", "typo")
619         self.assertRaises(ObjectFormatException, check_identity,
620                           "Dave Borowitz <dborowitz@google.com>xxx",
621                           "trailing characters")
622
623
624 class TimezoneTests(unittest.TestCase):
625
626     def test_parse_timezone_utc(self):
627         self.assertEquals((0, False), parse_timezone("+0000"))
628
629     def test_parse_timezone_utc_negative(self):
630         self.assertEquals((0, True), parse_timezone("-0000"))
631
632     def test_generate_timezone_utc(self):
633         self.assertEquals("+0000", format_timezone(0))
634
635     def test_generate_timezone_utc_negative(self):
636         self.assertEquals("-0000", format_timezone(0, True))
637
638     def test_parse_timezone_cet(self):
639         self.assertEquals((60 * 60, False), parse_timezone("+0100"))
640
641     def test_format_timezone_cet(self):
642         self.assertEquals("+0100", format_timezone(60 * 60))
643
644     def test_format_timezone_pdt(self):
645         self.assertEquals("-0400", format_timezone(-4 * 60 * 60))
646
647     def test_parse_timezone_pdt(self):
648         self.assertEquals((-4 * 60 * 60, False), parse_timezone("-0400"))
649
650     def test_format_timezone_pdt_half(self):
651         self.assertEquals("-0440",
652             format_timezone(int(((-4 * 60) - 40) * 60)))
653
654     def test_parse_timezone_pdt_half(self):
655         self.assertEquals((((-4 * 60) - 40) * 60, False),
656             parse_timezone("-0440"))