1 # test_objects.py -- tests for objects.py
2 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
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
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.
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,
21 """Tests for git base objects."""
23 # TODO: Round-trip parse-serialize-parse and serialize-parse-serialize tests.
31 from dulwich.errors import (
33 ObjectFormatException,
35 from dulwich.objects import (
50 from dulwich.tests import (
58 a_sha = '6f670c0fb53f9463760b7295fbb814e965fb20c8'
59 b_sha = '2969be3e8ee1c0222396a5611407e4769f14e54b'
60 c_sha = '954a536f7819d40e6f637f849ee187dd10066349'
61 tree_sha = '70c190eb48fa8bbb50ddc692a17b44cb781af7f6'
62 tag_sha = '71033db03a03c6a36721efcf1968dd8f8e0cf023'
66 from itertools import permutations
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)
82 cycles = range(n, n-r, -1)
83 yield tuple(pool[i] for i in indices[:r])
85 for i in reversed(range(r)):
88 indices[i:] = indices[i+1:] + indices[i:i+1]
92 indices[i], indices[-j] = indices[-j], indices[i]
93 yield tuple(pool[i] for i in indices[:r])
99 class TestHexToSha(unittest.TestCase):
101 def test_simple(self):
102 self.assertEquals("\xab\xcd" * 10, hex_to_sha("abcd" * 10))
104 def test_reverse(self):
105 self.assertEquals("abcd" * 10, sha_to_hex("\xab\xcd" * 10))
108 class BlobReadTests(unittest.TestCase):
109 """Test decompression of blobs"""
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))
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)
119 def get_tree(self, sha):
120 return self.get_sha_file(Tree, 'trees', sha)
122 def get_tag(self, sha):
123 return self.get_sha_file(Tag, 'tags', sha)
125 def commit(self, sha):
126 return self.get_sha_file(Commit, 'commits', sha)
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)
134 b = self.get_blob(a_sha)
135 self.assertEqual(hash(b.id), hash(b))
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)
144 def test_create_blob_from_string(self):
146 b = Blob.from_string(string)
147 self.assertEqual(b.data, string)
148 self.assertEqual(b.sha().hexdigest(), b_sha)
150 def test_chunks(self):
152 b = Blob.from_string(string)
153 self.assertEqual([string], b.chunked)
155 def test_set_chunks(self):
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())
162 def test_parse_legacy_blob(self):
164 b = self.get_blob(c_sha)
165 self.assertEqual(b.data, string)
166 self.assertEqual(b.sha().hexdigest(), c_sha)
169 blob1 = self.get_blob(a_sha)
170 blob2 = self.get_blob(a_sha)
171 self.assertEqual(blob1, blob2)
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))
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')
186 def test_read_commit_from_file(self):
187 sha = '60dacdc733de308bb77bb76ce0fb0f9b44c9769e'
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')
201 def test_read_commit_no_parents(self):
202 sha = '0d89f20333fbb1d2f3a94da77f4981373d8f4310'
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')
215 def test_read_commit_two_parents(self):
216 sha = '5dac377bdded4c9aeb8dff595f0faeebcc8498cc'
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')
231 class ShaFileCheckTests(unittest.TestCase):
233 def assertCheckFails(self, cls, data):
236 obj.set_raw_string(data)
238 self.assertRaises(ObjectFormatException, do_check)
240 def assertCheckSucceeds(self, cls, data):
242 obj.set_raw_string(data)
243 self.assertEqual(None, obj.check())
246 class CommitSerializationTests(unittest.TestCase):
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'}
260 return make_commit(**attrs)
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())
266 def test_short_timestamp(self):
267 c = self.make_commit(commit_time=30)
269 c1.set_raw_string(c.as_raw_string())
270 self.assertEquals(30, c1.commit_time)
272 def test_raw_length(self):
273 c = self.make_commit()
274 self.assertEquals(len(c.as_raw_string()), c.raw_length())
276 def test_simple(self):
277 c = self.make_commit()
278 self.assertEquals(c.id, '5dac377bdded4c9aeb8dff595f0faeebcc8498cc')
280 'tree d80c186a03f423a81b39df39dc87fd269736ca86\n'
281 'parent ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd\n'
282 'parent 4cffe90e0a41ad3f5190079d7c8f036bde29cbe6\n'
283 'author James Westby <jw+debian@jameswestby.net> '
285 'committer James Westby <jw+debian@jameswestby.net> '
288 'Merge ../b\n', c.as_raw_string())
290 def test_timezone(self):
291 c = self.make_commit(commit_timezone=(5 * 60))
292 self.assertTrue(" +0005\n" in c.as_raw_string())
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())
299 default_committer = 'James Westby <jw+debian@jameswestby.net> 1174773719 +0000'
301 class CommitParseTests(ShaFileCheckTests):
303 def make_commit_lines(self,
304 tree='d80c186a03f423a81b39df39dc87fd269736ca86',
305 parents=['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
306 '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'],
307 author=default_committer,
308 committer=default_committer,
310 message='Merge ../b\n',
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))
327 if message is not None:
328 lines.append(message)
331 def make_commit_text(self, **kwargs):
332 return '\n'.join(self.make_commit_lines(**kwargs))
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>',
340 self.assertEquals('d80c186a03f423a81b39df39dc87fd269736ca86', c.tree)
341 self.assertEquals(['ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
342 '4cffe90e0a41ad3f5190079d7c8f036bde29cbe6'],
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)
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)
358 def test_encoding(self):
359 c = Commit.from_string(self.make_commit_text(encoding='UTF-8'))
360 self.assertEquals('UTF-8', c.encoding)
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'))
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))
381 def test_check_duplicates(self):
382 # duplicate each of the header fields
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)
391 self.assertCheckFails(Commit, text)
393 def test_check_order(self):
394 lines = self.make_commit_lines(parents=[a_sha], encoding='UTF-8')
397 # of all possible permutations, ensure only the original succeeds
398 for perm in permutations(headers):
400 text = '\n'.join(perm + rest)
402 self.assertCheckSucceeds(Commit, text)
404 self.assertCheckFails(Commit, text)
407 class TreeTests(ShaFileCheckTests):
409 def test_simple(self):
410 myhexsha = "d80c186a03f423a81b39df39dc87fd269736ca86"
412 x["myname"] = (0100755, myhexsha)
413 self.assertEquals('100755 myname\0' + hex_to_sha(myhexsha),
416 def test_tree_update_id(self):
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)
423 def test_tree_dir_sort(self):
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()])
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())))
436 def test_parse_tree(self):
437 self._do_test_parse_tree(_parse_tree_py)
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)
444 def test_check(self):
446 sha = hex_to_sha(a_sha)
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)
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)
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))
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))
477 t["foo"] = (0100644, a_sha)
478 self.assertEquals(set(["foo"]), set(t))
481 class TagSerializeTests(unittest.TestCase):
483 def test_serialize_simple(self):
485 tagger='Jelmer Vernooij <jelmer@samba.org>',
488 object=(Blob, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
491 self.assertEquals(('object d80c186a03f423a81b39df39dc87fd269736ca86\n'
494 'tagger Jelmer Vernooij <jelmer@samba.org> '
497 'Tag 0.1'), x.as_raw_string())
500 default_tagger = ('Linus Torvalds <torvalds@woody.linux-foundation.org> '
502 default_message = """Linux 2.6.22-rc7
503 -----BEGIN PGP SIGNATURE-----
504 Version: GnuPG v1.4.7 (GNU/Linux)
506 iD8DBQBGiAaAF3YsRnbiHLsRAitMAKCiLboJkQECM/jpYsY3WPfvUgLXkACgg3ql
507 OK2XeQOiEeXtT76rV4t2WR4=
509 -----END PGP SIGNATURE-----
513 class TagParseTests(ShaFileCheckTests):
514 def make_tag_lines(self,
515 object_sha="a38d6181ff27824c79fc7df825164a212eff6a3f",
516 object_type_name="commit",
518 tagger=default_tagger,
519 message=default_message):
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)
526 lines.append("tag %s" % name)
527 if tagger is not None:
528 lines.append("tagger %s" % tagger)
530 if message is not None:
531 lines.append(message)
534 def make_tag_text(self, **kwargs):
535 return "\n".join(self.make_tag_lines(**kwargs))
537 def test_parse(self):
539 x.set_raw_string(self.make_tag_text())
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",
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)
551 def test_parse_no_tagger(self):
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)
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"))
572 def test_check_duplicates(self):
573 # duplicate each of the header fields
575 lines = self.make_tag_lines()
576 lines.insert(i, lines[i])
577 self.assertCheckFails(Tag, '\n'.join(lines))
579 def test_check_order(self):
580 lines = self.make_tag_lines()
583 # of all possible permutations, ensure only the original succeeds
584 for perm in permutations(headers):
586 text = '\n'.join(perm + rest)
588 self.assertCheckSucceeds(Tag, text)
590 self.assertCheckFails(Tag, text)
593 class CheckTests(unittest.TestCase):
595 def test_check_hexsha(self):
596 check_hexsha(a_sha, "failed to check good sha")
597 self.assertRaises(ObjectFormatException, check_hexsha, '1' * 39,
599 self.assertRaises(ObjectFormatException, check_hexsha, '1' * 41,
601 self.assertRaises(ObjectFormatException, check_hexsha, 'x' * 40,
602 'invalid characters')
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")
624 class TimezoneTests(unittest.TestCase):
626 def test_parse_timezone_utc(self):
627 self.assertEquals((0, False), parse_timezone("+0000"))
629 def test_parse_timezone_utc_negative(self):
630 self.assertEquals((0, True), parse_timezone("-0000"))
632 def test_generate_timezone_utc(self):
633 self.assertEquals("+0000", format_timezone(0))
635 def test_generate_timezone_utc_negative(self):
636 self.assertEquals("-0000", format_timezone(0, True))
638 def test_parse_timezone_cet(self):
639 self.assertEquals((60 * 60, False), parse_timezone("+0100"))
641 def test_format_timezone_cet(self):
642 self.assertEquals("+0100", format_timezone(60 * 60))
644 def test_format_timezone_pdt(self):
645 self.assertEquals("-0400", format_timezone(-4 * 60 * 60))
647 def test_parse_timezone_pdt(self):
648 self.assertEquals((-4 * 60 * 60, False), parse_timezone("-0400"))
650 def test_format_timezone_pdt_half(self):
651 self.assertEquals("-0440",
652 format_timezone(int(((-4 * 60) - 40) * 60)))
654 def test_parse_timezone_pdt_half(self):
655 self.assertEquals((((-4 * 60) - 40) * 60, False),
656 parse_timezone("-0440"))