Browse Source

Merge branch 'python3'.

This improves python3 support and makes the testsuite pass by basically
disabling most tests on Python3.

This is still a work in progress. DULWICH DOES NOT YET SUPPORT PYTHON3.
Jelmer Vernooij 10 years ago
parent
commit
058b3e9298

+ 2 - 1
.travis.yml

@@ -1,10 +1,11 @@
 language: python
-# Workaround to make 2.7 use system site packages, and 2.6 not use system
+# Workaround to make 2.7 use system site packages, and 2.6 and 3.4 not use system
 # site packages.
 # https://github.com/travis-ci/travis-ci/issues/2219#issuecomment-41804942
 python:
 - "2.6"
 - "2.7_with_system_site_packages"
+- "3.4"
 script: PYTHONHASHSEED=random python setup.py test
 install:
   - sudo apt-get update

+ 7 - 0
README.md

@@ -25,3 +25,10 @@ Help
 
 There is a #dulwich IRC channel on Freenode, and a dulwich mailing list at
 https://launchpad.net/~dulwich-users.
+
+Python3
+-------
+
+The process of porting to Python3 is ongoing. Please not that although the
+test suite pass in python3, this is due to the tests of features that are not
+yet ported being skipped, and *not* an indication that the port is complete.

+ 2 - 1
dulwich/_compat.py

@@ -1,9 +1,10 @@
 # _compat.py -- For dealing with python2.6 oddness
+# Copyright (C) 2012-2014 Jelmer Vernooij and others.
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 # as published by the Free Software Foundation; version 2
-# of the License or (at your option) a later version.
+# of the License or (at your option) a later version of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of

+ 7 - 2
dulwich/client.py

@@ -44,8 +44,13 @@ import select
 import socket
 import subprocess
 import sys
-import urllib2
-import urlparse
+
+try:
+    import urllib2
+    import urlparse
+except ImportError:
+    import urllib.request as urllib2
+    import urllib.parse as urlparse
 
 from dulwich.errors import (
     GitProtocolError,

+ 10 - 10
dulwich/contrib/swift.py

@@ -32,7 +32,7 @@ import tempfile
 import posixpath
 
 from urlparse import urlparse
-from cStringIO import StringIO
+from io import BytesIO
 from ConfigParser import ConfigParser
 from geventhttpclient import HTTPClient
 
@@ -453,7 +453,7 @@ class SwiftConnector(object):
 
         if range:
             return content
-        return StringIO(content)
+        return BytesIO(content)
 
     def del_object(self, name):
         """Delete an object
@@ -718,7 +718,7 @@ class SwiftObjectStore(PackBasedObjectStore):
         :return: Fileobject to write to and a commit function to
             call when the pack is finished.
         """
-        f = StringIO()
+        f = BytesIO()
 
         def commit():
             f.seek(0)
@@ -729,7 +729,7 @@ class SwiftObjectStore(PackBasedObjectStore):
                                           "pack-%s" %
                                           iter_sha1(entry[0] for
                                                     entry in entries))
-                index = StringIO()
+                index = BytesIO()
                 write_pack_index_v2(index, entries, pack.get_stored_checksum())
                 self.scon.put_object(basename + ".pack", f)
                 f.close()
@@ -808,7 +808,7 @@ class SwiftObjectStore(PackBasedObjectStore):
 
         # Write the index.
         filename = pack_base_name + '.idx'
-        index_file = StringIO()
+        index_file = BytesIO()
         write_pack_index_v2(index_file, entries, pack_sha)
         self.scon.put_object(filename, index_file)
 
@@ -820,7 +820,7 @@ class SwiftObjectStore(PackBasedObjectStore):
         serialized_pack_info = pack_info_create(pack_data, pack_index)
         f.close()
         index_file.close()
-        pack_info_file = StringIO(serialized_pack_info)
+        pack_info_file = BytesIO(serialized_pack_info)
         filename = pack_base_name + '.info'
         self.scon.put_object(filename, pack_info_file)
         pack_info_file.close()
@@ -842,7 +842,7 @@ class SwiftInfoRefsContainer(InfoRefsContainer):
         self.store = store
         f = self.scon.get_object(self.filename)
         if not f:
-            f = StringIO('')
+            f = BytesIO('')
         super(SwiftInfoRefsContainer, self).__init__(f)
 
     def _load_check_ref(self, name, old_ref):
@@ -857,7 +857,7 @@ class SwiftInfoRefsContainer(InfoRefsContainer):
         return refs
 
     def _write_refs(self, refs):
-        f = StringIO()
+        f = BytesIO()
         f.writelines(write_info_refs(refs, self.store))
         self.scon.put_object(self.filename, f)
 
@@ -928,7 +928,7 @@ class SwiftRepo(BaseRepo):
         :param filename: the path to the object to put on Swift
         :param contents: the content as bytestring
         """
-        f = StringIO()
+        f = BytesIO()
         f.write(contents)
         self.scon.put_object(filename, f)
         f.close()
@@ -944,7 +944,7 @@ class SwiftRepo(BaseRepo):
         scon.create_root()
         for obj in [posixpath.join(OBJECTDIR, PACKDIR),
                     posixpath.join(INFODIR, 'refs')]:
-            scon.put_object(obj, StringIO(''))
+            scon.put_object(obj, BytesIO(''))
         ret = cls(scon.root, conf)
         ret._init_files(True)
         return ret

+ 16 - 16
dulwich/contrib/test_swift.py

@@ -24,7 +24,7 @@
 import posixpath
 
 from time import time
-from cStringIO import StringIO
+from io import BytesIO
 try:
     from unittest import skipIf
 except ImportError:
@@ -224,7 +224,7 @@ class FakeSwiftConnector(object):
         name = posixpath.join(self.root, name)
         if not range:
             try:
-                return StringIO(self.store[name])
+                return BytesIO(self.store[name])
             except KeyError:
                 return None
         else:
@@ -260,14 +260,14 @@ class TestSwiftObjectStore(TestCase):
 
     def setUp(self):
         super(TestSwiftObjectStore, self).setUp()
-        self.conf = swift.load_conf(file=StringIO(config_file %
+        self.conf = swift.load_conf(file=BytesIO(config_file %
                                                   def_config_file))
         self.fsc = FakeSwiftConnector('fakerepo', conf=self.conf)
 
     def _put_pack(self, sos, commit_amount=1, marker='Default'):
         odata = create_commits(length=commit_amount, marker=marker)
         data = [(d.type_num, d.as_raw_string()) for d in odata]
-        f = StringIO()
+        f = BytesIO()
         build_pack(f, data, store=sos)
         sos.add_thin_pack(f.read, None)
         return odata
@@ -368,7 +368,7 @@ class TestSwiftObjectStore(TestCase):
                 (tree.type_num, tree.as_raw_string()),
                 (cmt.type_num, cmt.as_raw_string()),
                 (tag.type_num, tag.as_raw_string())]
-        f = StringIO()
+        f = BytesIO()
         build_pack(f, data, store=sos)
         sos.add_thin_pack(f.read, None)
         self.assertEqual(len(self.fsc.store), 6)
@@ -379,7 +379,7 @@ class TestSwiftRepo(TestCase):
 
     def setUp(self):
         super(TestSwiftRepo, self).setUp()
-        self.conf = swift.load_conf(file=StringIO(config_file %
+        self.conf = swift.load_conf(file=BytesIO(config_file %
                                                   def_config_file))
 
     def test_init(self):
@@ -428,15 +428,15 @@ class TestSwiftRepo(TestCase):
 @skipIf(missing_libs, skipmsg)
 class TestPackInfoLoadDump(TestCase):
     def setUp(self):
-        conf = swift.load_conf(file=StringIO(config_file %
+        conf = swift.load_conf(file=BytesIO(config_file %
                                              def_config_file))
         sos = swift.SwiftObjectStore(
             FakeSwiftConnector('fakerepo', conf=conf))
         commit_amount = 10
         self.commits = create_commits(length=commit_amount, marker="m")
         data = [(d.type_num, d.as_raw_string()) for d in self.commits]
-        f = StringIO()
-        fi = StringIO()
+        f = BytesIO()
+        fi = BytesIO()
         expected = build_pack(f, data, store=sos)
         entries = [(sha, ofs, checksum) for
                    ofs, _, _, sha, checksum in expected]
@@ -455,14 +455,14 @@ class TestPackInfoLoadDump(TestCase):
 #            dump_time.append(time() - start)
 #        for i in xrange(0, 100):
 #            start = time()
-#            pack_infos = swift.load_pack_info('', file=StringIO(dumps))
+#            pack_infos = swift.load_pack_info('', file=BytesIO(dumps))
 #            load_time.append(time() - start)
 #        print sum(dump_time) / float(len(dump_time))
 #        print sum(load_time) / float(len(load_time))
 
     def test_pack_info(self):
         dumps = swift.pack_info_create(self.pack_data, self.pack_index)
-        pack_infos = swift.load_pack_info('', file=StringIO(dumps))
+        pack_infos = swift.load_pack_info('', file=BytesIO(dumps))
         for obj in self.commits:
             self.assertIn(obj.id, pack_infos)
 
@@ -476,7 +476,7 @@ class TestSwiftInfoRefsContainer(TestCase):
             "22effb216e3a82f97da599b8885a6cadb488b4c5\trefs/heads/master\n" + \
             "cca703b0e1399008b53a1a236d6b4584737649e4\trefs/heads/dev"
         self.store = {'fakerepo/info/refs': content}
-        self.conf = swift.load_conf(file=StringIO(config_file %
+        self.conf = swift.load_conf(file=BytesIO(config_file %
                                                   def_config_file))
         self.fsc = FakeSwiftConnector('fakerepo', conf=self.conf)
         self.object_store = {}
@@ -510,7 +510,7 @@ class TestSwiftConnector(TestCase):
 
     def setUp(self):
         super(TestSwiftConnector, self).setUp()
-        self.conf = swift.load_conf(file=StringIO(config_file %
+        self.conf = swift.load_conf(file=BytesIO(config_file %
                                                   def_config_file))
         with patch('geventhttpclient.HTTPClient.request',
                    fake_auth_request_v1):
@@ -594,7 +594,7 @@ class TestSwiftConnector(TestCase):
     def test_put_object(self):
         with patch('geventhttpclient.HTTPClient.request',
                    lambda *args, **kwargs: Response()):
-            self.assertEqual(self.conn.put_object('a', StringIO('content')),
+            self.assertEqual(self.conn.put_object('a', BytesIO('content')),
                              None)
 
     def test_put_object_fails(self):
@@ -602,7 +602,7 @@ class TestSwiftConnector(TestCase):
                    lambda *args, **kwargs: Response(status=400)):
             self.assertRaises(swift.SwiftException,
                               lambda: self.conn.put_object(
-                                  'a', StringIO('content')))
+                                  'a', BytesIO('content')))
 
     def test_get_object(self):
         with patch('geventhttpclient.HTTPClient.request',
@@ -638,7 +638,7 @@ class SwiftObjectStoreTests(ObjectStoreTests, TestCase):
 
     def setUp(self):
         TestCase.setUp(self)
-        conf = swift.load_conf(file=StringIO(config_file %
+        conf = swift.load_conf(file=BytesIO(config_file %
                                def_config_file))
         fsc = FakeSwiftConnector('fakerepo', conf=conf)
         self.store = swift.SwiftObjectStore(fsc)

+ 6 - 1
dulwich/diff_tree.py

@@ -24,9 +24,14 @@ from collections import (
     )
 
 from io import BytesIO
-from itertools import chain, izip
+from itertools import chain
 import stat
 
+try:
+    from itertools import izip
+except ImportError:
+    # Python3
+    izip = zip
 from dulwich.objects import (
     S_ISGITLINK,
     TreeEntry,

+ 13 - 6
dulwich/pack.py

@@ -33,13 +33,19 @@ a pointer in to the corresponding packfile.
 from collections import defaultdict
 
 import binascii
-from io import BytesIO
+from io import BytesIO, UnsupportedOperation
 from collections import (
     deque,
     )
 import difflib
 
-from itertools import chain, imap, izip
+from itertools import chain
+try:
+    from itertools import imap, izip
+except ImportError:
+    # Python3
+    imap = map
+    izip = zip
 
 try:
     import mmap
@@ -55,7 +61,6 @@ from os import (
     )
 import struct
 from struct import unpack_from
-import warnings
 import zlib
 
 from dulwich.errors import (
@@ -263,10 +268,12 @@ def load_pack_index(path):
 
 
 def _load_file_contents(f, size=None):
-    fileno = getattr(f, 'fileno', None)
-    # Attempt to use mmap if possible
-    if fileno is not None:
+    try:
         fd = f.fileno()
+    except (UnsupportedOperation, AttributeError):
+        fd = None
+    # Attempt to use mmap if possible
+    if fd is not None:
         if size is None:
             size = os.fstat(fd).st_size
         if has_mmap:

+ 5 - 1
dulwich/server.py

@@ -42,10 +42,14 @@ Currently supported capabilities:
 import collections
 import os
 import socket
-import SocketServer
 import sys
 import zlib
 
+try:
+    import SocketServer
+except ImportError:
+    import socketserver as SocketServer
+
 from dulwich.errors import (
     ApplyDeltaError,
     ChecksumMismatch,

+ 2 - 1
dulwich/tests/__init__.py

@@ -179,7 +179,8 @@ def compat_test_suite():
 def test_suite():
     result = unittest.TestSuite()
     result.addTests(self_test_suite())
-    result.addTests(tutorial_test_suite())
+    if sys.version_info[0] == 2:
+        result.addTests(tutorial_test_suite())
     from dulwich.tests.compat import test_suite as compat_test_suite
     result.addTests(compat_test_suite())
     from dulwich.contrib import test_suite as contrib_test_suite

+ 2 - 0
dulwich/tests/compat/server_utils.py

@@ -32,6 +32,7 @@ from dulwich.server import (
     )
 from dulwich.tests.utils import (
     tear_down_repo,
+    skipIfPY3,
     )
 from dulwich.tests.compat.utils import (
     import_repo,
@@ -63,6 +64,7 @@ def _get_shallow(repo):
     return shallows
 
 
+@skipIfPY3
 class ServerTests(object):
     """Base tests for testing servers.
 

+ 12 - 3
dulwich/tests/compat/test_client.py

@@ -20,8 +20,6 @@
 """Compatibilty tests between the Dulwich client and the cgit server."""
 
 from io import BytesIO
-import BaseHTTPServer
-import SimpleHTTPServer
 import copy
 import os
 import select
@@ -34,6 +32,14 @@ import tempfile
 import threading
 import urllib
 
+try:
+    import BaseHTTPServer
+    import SimpleHTTPServer
+except ImportError:
+    import http.server
+    BaseHTTPServer = http.server
+    SimpleHTTPServer = http.server
+
 if sys.platform == 'win32':
     import ctypes
 
@@ -50,7 +56,9 @@ from dulwich.tests import (
     get_safe_env,
     SkipTest,
     )
-
+from dulwich.tests.utils import (
+    skipIfPY3,
+    )
 from dulwich.tests.compat.utils import (
     CompatTestCase,
     check_for_daemon,
@@ -60,6 +68,7 @@ from dulwich.tests.compat.utils import (
     )
 
 
+@skipIfPY3
 class DulwichClientTestBase(object):
     """Tests for client/server compatibility."""
 

+ 2 - 0
dulwich/tests/compat/test_repository.py

@@ -32,6 +32,7 @@ from dulwich.repo import (
     )
 from dulwich.tests.utils import (
     tear_down_repo,
+    skipIfPY3,
     )
 
 from dulwich.tests.compat.utils import (
@@ -41,6 +42,7 @@ from dulwich.tests.compat.utils import (
     )
 
 
+@skipIfPY3
 class ObjectStoreTestCase(CompatTestCase):
     """Tests for git repository compatibility."""
 

+ 6 - 6
dulwich/tests/compat/test_utils.py

@@ -43,19 +43,19 @@ class GitVersionTests(TestCase):
         utils.run_git = self._orig_run_git
 
     def test_git_version_none(self):
-        self._version_str = 'not a git version'
+        self._version_str = b'not a git version'
         self.assertEqual(None, utils.git_version())
 
     def test_git_version_3(self):
-        self._version_str = 'git version 1.6.6'
+        self._version_str = b'git version 1.6.6'
         self.assertEqual((1, 6, 6, 0), utils.git_version())
 
     def test_git_version_4(self):
-        self._version_str = 'git version 1.7.0.2'
+        self._version_str = b'git version 1.7.0.2'
         self.assertEqual((1, 7, 0, 2), utils.git_version())
 
     def test_git_version_extra(self):
-        self._version_str = 'git version 1.7.0.3.295.gd8fa2'
+        self._version_str = b'git version 1.7.0.3.295.gd8fa2'
         self.assertEqual((1, 7, 0, 3), utils.git_version())
 
     def assertRequireSucceeds(self, required_version):
@@ -70,7 +70,7 @@ class GitVersionTests(TestCase):
 
     def test_require_git_version(self):
         try:
-            self._version_str = 'git version 1.6.6'
+            self._version_str = b'git version 1.6.6'
             self.assertRequireSucceeds((1, 6, 6))
             self.assertRequireSucceeds((1, 6, 6, 0))
             self.assertRequireSucceeds((1, 6, 5))
@@ -80,7 +80,7 @@ class GitVersionTests(TestCase):
             self.assertRaises(ValueError, utils.require_git_version,
                               (1, 6, 6, 0, 0))
 
-            self._version_str = 'git version 1.7.0.2'
+            self._version_str = b'git version 1.7.0.2'
             self.assertRequireSucceeds((1, 6, 6))
             self.assertRequireSucceeds((1, 6, 6, 0))
             self.assertRequireSucceeds((1, 7, 0))

+ 2 - 2
dulwich/tests/compat/utils.py

@@ -53,11 +53,11 @@ def git_version(git_path=_DEFAULT_GIT):
         output = run_git_or_fail(['--version'], git_path=git_path)
     except OSError:
         return None
-    version_prefix = 'git version '
+    version_prefix = b'git version '
     if not output.startswith(version_prefix):
         return None
 
-    parts = output[len(version_prefix):].split('.')
+    parts = output[len(version_prefix):].split(b'.')
     nums = []
     for part in parts:
         try:

+ 5 - 0
dulwich/tests/test_blackbox.py

@@ -26,8 +26,12 @@ from dulwich.repo import (
 from dulwich.tests import (
     BlackboxTestCase,
     )
+from dulwich.tests.utils import (
+    skipIfPY3,
+    )
 
 
+@skipIfPY3
 class GitReceivePackTests(BlackboxTestCase):
     """Blackbox tests for dul-receive-pack."""
 
@@ -51,6 +55,7 @@ class GitReceivePackTests(BlackboxTestCase):
         self.assertEqual(1, process.returncode)
 
 
+@skipIfPY3
 class GitUploadPackTests(BlackboxTestCase):
     """Blackbox tests for dul-upload-pack."""
 

+ 8 - 0
dulwich/tests/test_client.py

@@ -56,6 +56,7 @@ from dulwich.objects import (
 from dulwich.repo import MemoryRepo
 from dulwich.tests.utils import (
     open_repo,
+    skipIfPY3,
     )
 
 
@@ -72,6 +73,7 @@ class DummyClient(TraditionalGitClient):
 
 
 # TODO(durin42): add unit-level tests of GitClient
+@skipIfPY3
 class GitClientTests(TestCase):
 
     def setUp(self):
@@ -288,6 +290,7 @@ class GitClientTests(TestCase):
         self.assertEqual(self.rout.getvalue(), '0000')
 
 
+@skipIfPY3
 class TestGetTransportAndPath(TestCase):
 
     def test_tcp(self):
@@ -408,6 +411,7 @@ class TestGetTransportAndPath(TestCase):
         self.assertEqual('/jelmer/dulwich', path)
 
 
+@skipIfPY3
 class TestGetTransportAndPathFromUrl(TestCase):
 
     def test_tcp(self):
@@ -486,6 +490,7 @@ class TestGetTransportAndPathFromUrl(TestCase):
         self.assertEqual('/home/jelmer/foo', path)
 
 
+@skipIfPY3
 class TestSSHVendor(object):
 
     def __init__(self):
@@ -508,6 +513,7 @@ class TestSSHVendor(object):
         return Subprocess()
 
 
+@skipIfPY3
 class SSHGitClientTests(TestCase):
 
     def setUp(self):
@@ -550,6 +556,7 @@ class SSHGitClientTests(TestCase):
                           server.command)
 
 
+@skipIfPY3
 class ReportStatusParserTests(TestCase):
 
     def test_invalid_pack(self):
@@ -574,6 +581,7 @@ class ReportStatusParserTests(TestCase):
         parser.check()
 
 
+@skipIfPY3
 class LocalGitClientTests(TestCase):
 
     def test_fetch_into_empty(self):

+ 10 - 0
dulwich/tests/test_config.py

@@ -31,8 +31,10 @@ from dulwich.config import (
     _unescape_value,
     )
 from dulwich.tests import TestCase
+from dulwich.tests.utils import skipIfPY3
 
 
+@skipIfPY3
 class ConfigFileTests(TestCase):
 
     def from_file(self, text):
@@ -151,6 +153,7 @@ class ConfigFileTests(TestCase):
         self.assertEqual("bar", cf.get(("branch", "foo"), "foo"))
 
 
+@skipIfPY3
 class ConfigDictTests(TestCase):
 
     def test_get_set(self):
@@ -206,12 +209,14 @@ class ConfigDictTests(TestCase):
 
 
 
+@skipIfPY3
 class StackedConfigTests(TestCase):
 
     def test_default_backends(self):
         StackedConfig.default_backends()
 
 
+@skipIfPY3
 class UnescapeTests(TestCase):
 
     def test_nothing(self):
@@ -227,6 +232,7 @@ class UnescapeTests(TestCase):
         self.assertEqual("\"foo\"", _unescape_value("\\\"foo\\\""))
 
 
+@skipIfPY3
 class EscapeValueTests(TestCase):
 
     def test_nothing(self):
@@ -239,6 +245,7 @@ class EscapeValueTests(TestCase):
         self.assertEqual("foo\\n", _escape_value("foo\n"))
 
 
+@skipIfPY3
 class FormatStringTests(TestCase):
 
     def test_quoted(self):
@@ -250,6 +257,7 @@ class FormatStringTests(TestCase):
         self.assertEqual('foo bar', _format_string("foo bar"))
 
 
+@skipIfPY3
 class ParseStringTests(TestCase):
 
     def test_quoted(self):
@@ -261,6 +269,7 @@ class ParseStringTests(TestCase):
         self.assertEqual('foo bar', _parse_string("foo bar"))
 
 
+@skipIfPY3
 class CheckVariableNameTests(TestCase):
 
     def test_invalid(self):
@@ -274,6 +283,7 @@ class CheckVariableNameTests(TestCase):
         self.assertTrue(_check_variable_name("foo-bar"))
 
 
+@skipIfPY3
 class CheckSectionNameTests(TestCase):
 
     def test_invalid(self):

+ 4 - 0
dulwich/tests/test_diff_tree.py

@@ -57,9 +57,11 @@ from dulwich.tests.utils import (
     make_object,
     functest_builder,
     ext_functest_builder,
+    skipIfPY3,
     )
 
 
+@skipIfPY3
 class DiffTestCase(TestCase):
 
     def setUp(self):
@@ -84,6 +86,7 @@ class DiffTestCase(TestCase):
         return self.store[commit_tree(self.store, commit_blobs)]
 
 
+@skipIfPY3
 class TreeChangesTest(DiffTestCase):
 
     def setUp(self):
@@ -465,6 +468,7 @@ class TreeChangesTest(DiffTestCase):
           [parent1, parent2], merge, rename_detector=self.detector)
 
 
+@skipIfPY3
 class RenameDetectionTest(DiffTestCase):
 
     def _do_test_count_blocks(self, count_blocks):

+ 5 - 0
dulwich/tests/test_file.py

@@ -28,8 +28,12 @@ from dulwich.tests import (
     SkipTest,
     TestCase,
     )
+from dulwich.tests.utils import (
+    skipIfPY3
+    )
 
 
+@skipIfPY3
 class FancyRenameTests(TestCase):
 
     def setUp(self):
@@ -87,6 +91,7 @@ class FancyRenameTests(TestCase):
         new_f.close()
 
 
+@skipIfPY3
 class GitFileTests(TestCase):
 
     def setUp(self):

+ 6 - 0
dulwich/tests/test_grafts.py

@@ -23,6 +23,7 @@ import shutil
 
 from dulwich.errors import ObjectFormatException
 from dulwich.tests import TestCase
+from dulwich.tests.utils import skipIfPY3
 from dulwich.objects import (
     Tree,
     )
@@ -38,6 +39,7 @@ def makesha(digit):
     return (str(digit) * 40)[:40]
 
 
+@skipIfPY3
 class GraftParserTests(TestCase):
 
     def assertParse(self, expected, graftpoints):
@@ -63,6 +65,7 @@ class GraftParserTests(TestCase):
              ' '.join([makesha(3), makesha(4), makesha(5)])])
 
 
+@skipIfPY3
 class GraftSerializerTests(TestCase):
 
     def assertSerialize(self, expected, graftpoints):
@@ -91,6 +94,7 @@ class GraftSerializerTests(TestCase):
              makesha(3): [makesha(4), makesha(5)]})
 
 
+@skipIfPY3
 class GraftsInRepositoryBase(object):
 
     def tearDown(self):
@@ -135,6 +139,7 @@ class GraftsInRepositoryBase(object):
             {self._shas[-1]: ['1']})
 
 
+@skipIfPY3
 class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
 
     def setUp(self):
@@ -178,6 +183,7 @@ class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
         self.assertEqual({self._shas[-1]: [self._shas[0]]}, r._graftpoints)
 
 
+@skipIfPY3
 class GraftsInMemoryRepoTests(GraftsInRepositoryBase, TestCase):
 
     def setUp(self):

+ 2 - 0
dulwich/tests/test_hooks.py

@@ -31,8 +31,10 @@ from dulwich.hooks import (
 )
 
 from dulwich.tests import TestCase
+from dulwich.tests.utils import skipIfPY3
 
 
+@skipIfPY3
 class ShellHookTests(TestCase):
 
     def setUp(self):

+ 11 - 0
dulwich/tests/test_index.py

@@ -48,8 +48,10 @@ from dulwich.objects import (
     )
 from dulwich.repo import Repo
 from dulwich.tests import TestCase
+from dulwich.tests.utils import skipIfPY3
 
 
+@skipIfPY3
 class IndexTestCase(TestCase):
 
     datadir = os.path.join(os.path.dirname(__file__), 'data/indexes')
@@ -58,6 +60,7 @@ class IndexTestCase(TestCase):
         return Index(os.path.join(self.datadir, name))
 
 
+@skipIfPY3
 class SimpleIndexTestCase(IndexTestCase):
 
     def test_len(self):
@@ -86,6 +89,7 @@ class SimpleIndexTestCase(IndexTestCase):
         self.assertEqual('e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', newsha)
 
 
+@skipIfPY3
 class SimpleIndexWriterTestCase(IndexTestCase):
 
     def setUp(self):
@@ -108,6 +112,7 @@ class SimpleIndexWriterTestCase(IndexTestCase):
             self.assertEqual(entries, list(read_index(x)))
 
 
+@skipIfPY3
 class ReadIndexDictTests(IndexTestCase):
 
     def setUp(self):
@@ -130,6 +135,7 @@ class ReadIndexDictTests(IndexTestCase):
             self.assertEqual(entries, read_index_dict(x))
 
 
+@skipIfPY3
 class CommitTreeTests(TestCase):
 
     def setUp(self):
@@ -161,6 +167,7 @@ class CommitTreeTests(TestCase):
                           set(self.store._data.keys()))
 
 
+@skipIfPY3
 class CleanupModeTests(TestCase):
 
     def test_file(self):
@@ -179,6 +186,7 @@ class CleanupModeTests(TestCase):
         self.assertEqual(0o160000, cleanup_mode(0o160744))
 
 
+@skipIfPY3
 class WriteCacheTimeTests(TestCase):
 
     def test_write_string(self):
@@ -201,6 +209,7 @@ class WriteCacheTimeTests(TestCase):
         self.assertEqual(struct.pack(">LL", 434343, 21), f.getvalue())
 
 
+@skipIfPY3
 class IndexEntryFromStatTests(TestCase):
 
     def test_simple(self):
@@ -239,6 +248,7 @@ class IndexEntryFromStatTests(TestCase):
             0))
 
 
+@skipIfPY3
 class BuildIndexTests(TestCase):
 
     def assertReasonableIndexEntry(self, index_entry, mode, filesize, sha):
@@ -336,6 +346,7 @@ class BuildIndexTests(TestCase):
             sorted(os.listdir(os.path.join(repo.path, 'c'))))
 
 
+@skipIfPY3
 class GetUnstagedChangesTests(TestCase):
 
     def test_get_unstaged_changes(self):

+ 5 - 0
dulwich/tests/test_lru_cache.py

@@ -22,8 +22,12 @@ from dulwich import (
 from dulwich.tests import (
     TestCase,
     )
+from dulwich.tests.utils import (
+    skipIfPY3,
+    )
 
 
+@skipIfPY3
 class TestLRUCache(TestCase):
     """Test that LRU cache properly keeps track of entries."""
 
@@ -287,6 +291,7 @@ class TestLRUCache(TestCase):
         self.assertEqual([6, 7, 8, 9, 10, 11], sorted(cache.keys()))
 
 
+@skipIfPY3
 class TestLRUSizeCache(TestCase):
 
     def test_basic_init(self):

+ 4 - 0
dulwich/tests/test_missing_obj_finder.py

@@ -26,9 +26,11 @@ from dulwich.tests import TestCase
 from dulwich.tests.utils import (
     make_object,
     build_commit_graph,
+    skipIfPY3,
     )
 
 
+@skipIfPY3
 class MissingObjectFinderTest(TestCase):
 
     def setUp(self):
@@ -49,6 +51,7 @@ class MissingObjectFinderTest(TestCase):
             "some objects are not reported as missing: %s" % (expected, ))
 
 
+@skipIfPY3
 class MOFLinearRepoTest(MissingObjectFinderTest):
 
     def setUp(self):
@@ -108,6 +111,7 @@ class MOFLinearRepoTest(MissingObjectFinderTest):
         self.assertMissingMatch([self.cmt(3).id], [self.cmt(3).id], [])
 
 
+@skipIfPY3
 class MOFMergeForkRepoTest(MissingObjectFinderTest):
     # 1 --- 2 --- 4 --- 6 --- 7
     #          \        /

+ 6 - 0
dulwich/tests/test_object_store.py

@@ -54,6 +54,7 @@ from dulwich.tests import (
 from dulwich.tests.utils import (
     make_object,
     build_pack,
+    skipIfPY3,
     )
 
 
@@ -194,6 +195,7 @@ class ObjectStoreTests(object):
         self.store.close()
 
 
+@skipIfPY3
 class MemoryObjectStoreTests(ObjectStoreTests, TestCase):
 
     def setUp(self):
@@ -236,6 +238,7 @@ class MemoryObjectStoreTests(ObjectStoreTests, TestCase):
         o.add_thin_pack(f.read, None)
 
 
+@skipIfPY3
 class PackBasedObjectStoreTests(ObjectStoreTests):
 
     def tearDown(self):
@@ -256,6 +259,7 @@ class PackBasedObjectStoreTests(ObjectStoreTests):
         self.assertEqual(0, self.store.pack_loose_objects())
 
 
+@skipIfPY3
 class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
     def setUp(self):
@@ -350,6 +354,7 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
         o.add_thin_pack(f.read, None)
 
 
+@skipIfPY3
 class TreeLookupPathTests(TestCase):
 
     def setUp(self):
@@ -393,6 +398,7 @@ class TreeLookupPathTests(TestCase):
 
 # TODO: MissingObjectFinderTests
 
+@skipIfPY3
 class ObjectStoreGraphWalkerTests(TestCase):
 
     def get_walker(self, heads, parent_map):

+ 13 - 0
dulwich/tests/test_objects.py

@@ -62,6 +62,7 @@ from dulwich.tests.utils import (
     make_object,
     functest_builder,
     ext_functest_builder,
+    skipIfPY3,
     )
 
 a_sha = '6f670c0fb53f9463760b7295fbb814e965fb20c8'
@@ -71,6 +72,7 @@ tree_sha = '70c190eb48fa8bbb50ddc692a17b44cb781af7f6'
 tag_sha = '71033db03a03c6a36721efcf1968dd8f8e0cf023'
 
 
+@skipIfPY3
 class TestHexToSha(TestCase):
 
     def test_simple(self):
@@ -80,6 +82,7 @@ class TestHexToSha(TestCase):
         self.assertEqual("abcd" * 10, sha_to_hex("\xab\xcd" * 10))
 
 
+@skipIfPY3
 class BlobReadTests(TestCase):
     """Test decompression of blobs"""
 
@@ -217,6 +220,7 @@ class BlobReadTests(TestCase):
         self.assertNotEqual(sha, c._make_sha())
 
 
+@skipIfPY3
 class ShaFileCheckTests(TestCase):
 
     def assertCheckFails(self, cls, data):
@@ -246,6 +250,7 @@ small_buffer_zlib_object = (
  )
 
 
+@skipIfPY3
 class ShaFileTests(TestCase):
 
     def test_deflated_smaller_window_buffer(self):
@@ -257,6 +262,7 @@ class ShaFileTests(TestCase):
         self.assertEqual(sf.tagger, " <@localhost>")
 
 
+@skipIfPY3
 class CommitSerializationTests(TestCase):
 
     def make_commit(self, **kwargs):
@@ -473,6 +479,7 @@ Merge ../b
 
 default_committer = 'James Westby <jw+debian@jameswestby.net> 1174773719 +0000'
 
+@skipIfPY3
 class CommitParseTests(ShaFileCheckTests):
 
     def make_commit_lines(self,
@@ -636,6 +643,7 @@ _SORTED_TREE_ITEMS = [
   ]
 
 
+@skipIfPY3
 class TreeTests(ShaFileCheckTests):
 
     def test_add(self):
@@ -782,6 +790,7 @@ class TreeTests(ShaFileCheckTests):
         self.assertEqual(set(["foo"]), set(t))
 
 
+@skipIfPY3
 class TagSerializeTests(TestCase):
 
     def test_serialize_simple(self):
@@ -814,6 +823,7 @@ OK2XeQOiEeXtT76rV4t2WR4=
 """
 
 
+@skipIfPY3
 class TagParseTests(ShaFileCheckTests):
 
     def make_tag_lines(self,
@@ -895,6 +905,7 @@ class TagParseTests(ShaFileCheckTests):
                 self.assertCheckFails(Tag, text)
 
 
+@skipIfPY3
 class CheckTests(TestCase):
 
     def test_check_hexsha(self):
@@ -926,6 +937,7 @@ class CheckTests(TestCase):
                           "trailing characters")
 
 
+@skipIfPY3
 class TimezoneTests(TestCase):
 
     def test_parse_timezone_utc(self):
@@ -971,6 +983,7 @@ class TimezoneTests(TestCase):
             (int(((7 * 60)) * 60), True), parse_timezone("--700"))
 
 
+@skipIfPY3
 class ShaFileCopyTests(TestCase):
 
     def assert_copy(self, orig):

+ 3 - 0
dulwich/tests/test_objectspec.py

@@ -35,9 +35,11 @@ from dulwich.tests import (
     )
 from dulwich.tests.utils import (
     build_commit_graph,
+    skipIfPY3,
     )
 
 
+@skipIfPY3
 class ParseObjectTests(TestCase):
     """Test parse_object."""
 
@@ -52,6 +54,7 @@ class ParseObjectTests(TestCase):
         self.assertEqual(b, parse_object(r, b.id))
 
 
+@skipIfPY3
 class ParseCommitRangeTests(TestCase):
     """Test parse_commit_range."""
 

+ 18 - 0
dulwich/tests/test_pack.py

@@ -73,6 +73,7 @@ from dulwich.tests import (
 from dulwich.tests.utils import (
     make_object,
     build_pack,
+    skipIfPY3,
     )
 
 pack1_sha = 'bc63ddad95e7321ee734ea11a7a62d314e0d7481'
@@ -82,6 +83,7 @@ tree_sha = 'b2a2766a2879c209ab1176e7e778b81ae422eeaa'
 commit_sha = 'f18faa16531ac570a3fdc8c7ca16682548dafd12'
 
 
+@skipIfPY3
 class PackTests(TestCase):
     """Base class for testing packs"""
 
@@ -111,6 +113,7 @@ class PackTests(TestCase):
             self.fail(e)
 
 
+@skipIfPY3
 class PackIndexTests(PackTests):
     """Class that tests the index of packfiles"""
 
@@ -151,6 +154,7 @@ class PackIndexTests(PackTests):
         self.assertEqual(set([tree_sha, commit_sha, a_sha]), set(p))
 
 
+@skipIfPY3
 class TestPackDeltas(TestCase):
 
     test_string1 = 'The answer was flailing in the wind'
@@ -188,6 +192,7 @@ class TestPackDeltas(TestCase):
                              self.test_string_huge + self.test_string2)
 
 
+@skipIfPY3
 class TestPackData(PackTests):
     """Tests getting the data from the packfile."""
 
@@ -273,6 +278,7 @@ class TestPackData(PackTests):
             end_ofs=-12)
 
 
+@skipIfPY3
 class TestPack(PackTests):
 
     def test_len(self):
@@ -420,6 +426,7 @@ class TestPack(PackTests):
             self.assertTrue(isinstance(objs[commit_sha], Commit))
 
 
+@skipIfPY3
 class TestThinPack(PackTests):
 
     def setUp(self):
@@ -476,6 +483,7 @@ class TestThinPack(PackTests):
                 sorted(o.id for o in p.iterobjects()))
 
 
+@skipIfPY3
 class WritePackTests(TestCase):
 
     def test_write_pack_header(self):
@@ -596,6 +604,7 @@ class BaseTestFilePackIndexWriting(BaseTestPackIndexWriting):
             self._write_fn(f, entries, pack_checksum)
 
 
+@skipIfPY3
 class TestMemoryIndexWriting(TestCase, BaseTestPackIndexWriting):
 
     def setUp(self):
@@ -610,6 +619,7 @@ class TestMemoryIndexWriting(TestCase, BaseTestPackIndexWriting):
         TestCase.tearDown(self)
 
 
+@skipIfPY3
 class TestPackIndexWritingv1(TestCase, BaseTestFilePackIndexWriting):
 
     def setUp(self):
@@ -625,6 +635,7 @@ class TestPackIndexWritingv1(TestCase, BaseTestFilePackIndexWriting):
         BaseTestFilePackIndexWriting.tearDown(self)
 
 
+@skipIfPY3
 class TestPackIndexWritingv2(TestCase, BaseTestFilePackIndexWriting):
 
     def setUp(self):
@@ -640,6 +651,7 @@ class TestPackIndexWritingv2(TestCase, BaseTestFilePackIndexWriting):
         BaseTestFilePackIndexWriting.tearDown(self)
 
 
+@skipIfPY3
 class ReadZlibTests(TestCase):
 
     decomp = (
@@ -721,6 +733,7 @@ class ReadZlibTests(TestCase):
         self.assertEqual(self.comp, ''.join(self.unpacked.comp_chunks))
 
 
+@skipIfPY3
 class DeltifyTests(TestCase):
 
     def test_empty(self):
@@ -743,6 +756,7 @@ class DeltifyTests(TestCase):
             list(deltify_pack_objects([(b1, ""), (b2, "")])))
 
 
+@skipIfPY3
 class TestPackStreamReader(TestCase):
 
     def test_read_objects_emtpy(self):
@@ -793,6 +807,7 @@ class TestPackStreamReader(TestCase):
         self.assertEqual([], list(reader.read_objects()))
 
 
+@skipIfPY3
 class TestPackIterator(DeltaChainIterator):
 
     _compute_crc32 = True
@@ -814,6 +829,7 @@ class TestPackIterator(DeltaChainIterator):
           offset, pack_type_num, base_chunks)
 
 
+@skipIfPY3
 class DeltaChainIteratorTests(TestCase):
 
     def setUp(self):
@@ -1025,6 +1041,7 @@ class DeltaChainIteratorTests(TestCase):
             self.assertEqual((sorted([b2.id, b3.id]),), (sorted(e.args[0]),))
 
 
+@skipIfPY3
 class DeltaEncodeSizeTests(TestCase):
 
     def test_basic(self):
@@ -1035,6 +1052,7 @@ class DeltaEncodeSizeTests(TestCase):
         self.assertEquals('\xa0\x8d\x06', _delta_encode_size(100000))
 
 
+@skipIfPY3
 class EncodeCopyOperationTests(TestCase):
 
     def test_basic(self):

+ 6 - 0
dulwich/tests/test_patch.py

@@ -40,8 +40,12 @@ from dulwich.tests import (
     SkipTest,
     TestCase,
     )
+from dulwich.tests.utils import (
+    skipIfPY3,
+    )
 
 
+@skipIfPY3
 class WriteCommitPatchTests(TestCase):
 
     def test_simple(self):
@@ -72,6 +76,7 @@ class WriteCommitPatchTests(TestCase):
             self.assertEqual(lines[8], " 0 files changed\n")
 
 
+@skipIfPY3
 class ReadGitAmPatch(TestCase):
 
     def test_extract(self):
@@ -201,6 +206,7 @@ More help   : https://help.launchpad.net/ListHelp
         self.assertEqual(None, version)
 
 
+@skipIfPY3
 class DiffTests(TestCase):
     """Tests for write_blob_diff and write_tree_diff."""
 

+ 27 - 0
dulwich/tests/test_porcelain.py

@@ -39,6 +39,7 @@ from dulwich.tests.compat.utils import require_git_version
 from dulwich.tests.utils import (
     build_commit_graph,
     make_object,
+    skipIfPY3,
     )
 
 
@@ -51,6 +52,7 @@ class PorcelainTestCase(TestCase):
         self.repo = Repo.init(repo_dir)
 
 
+@skipIfPY3
 class ArchiveTests(PorcelainTestCase):
     """Tests for the archive command."""
 
@@ -69,6 +71,7 @@ class ArchiveTests(PorcelainTestCase):
         self.assertEqual([], tf.getnames())
 
 
+@skipIfPY3
 class UpdateServerInfoTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -80,6 +83,7 @@ class UpdateServerInfoTests(PorcelainTestCase):
             'info', 'refs')))
 
 
+@skipIfPY3
 class CommitTests(PorcelainTestCase):
 
     def test_custom_author(self):
@@ -92,6 +96,7 @@ class CommitTests(PorcelainTestCase):
         self.assertEqual(len(sha), 40)
 
 
+@skipIfPY3
 class CloneTests(PorcelainTestCase):
 
     def test_simple_local(self):
@@ -168,6 +173,7 @@ class CloneTests(PorcelainTestCase):
             target_path, checkout=True, bare=True, outstream=outstream)
 
 
+@skipIfPY3
 class InitTests(TestCase):
 
     def test_non_bare(self):
@@ -181,6 +187,7 @@ class InitTests(TestCase):
         porcelain.init(repo_dir, bare=True)
 
 
+@skipIfPY3
 class AddTests(PorcelainTestCase):
 
     def test_add_default_paths(self):
@@ -210,6 +217,7 @@ class AddTests(PorcelainTestCase):
         porcelain.add(self.repo.path, paths=["foo"])
 
 
+@skipIfPY3
 class RemoveTests(PorcelainTestCase):
 
     def test_remove_file(self):
@@ -219,6 +227,7 @@ class RemoveTests(PorcelainTestCase):
         porcelain.rm(self.repo.path, paths=["foo"])
 
 
+@skipIfPY3
 class LogTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -238,6 +247,7 @@ class LogTests(PorcelainTestCase):
         self.assertEqual(1, outstream.getvalue().count("-" * 50))
 
 
+@skipIfPY3
 class ShowTests(PorcelainTestCase):
 
     def test_nolist(self):
@@ -264,6 +274,7 @@ class ShowTests(PorcelainTestCase):
         self.assertEqual(outstream.getvalue(), "The Foo\n")
 
 
+@skipIfPY3
 class SymbolicRefTests(PorcelainTestCase):
 
     def test_set_wrong_symbolic_ref(self):
@@ -306,6 +317,7 @@ class SymbolicRefTests(PorcelainTestCase):
         self.assertEqual(new_ref, b'ref: refs/heads/develop\n')
 
 
+@skipIfPY3
 class DiffTreeTests(PorcelainTestCase):
 
     def test_empty(self):
@@ -317,6 +329,7 @@ class DiffTreeTests(PorcelainTestCase):
         self.assertEqual(outstream.getvalue(), "")
 
 
+@skipIfPY3
 class CommitTreeTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -336,6 +349,7 @@ class CommitTreeTests(PorcelainTestCase):
         self.assertEqual(len(sha), 40)
 
 
+@skipIfPY3
 class RevListTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -349,6 +363,7 @@ class RevListTests(PorcelainTestCase):
             outstream.getvalue())
 
 
+@skipIfPY3
 class TagCreateTests(PorcelainTestCase):
 
     def test_annotated(self):
@@ -379,6 +394,7 @@ class TagCreateTests(PorcelainTestCase):
         self.assertEqual(tags.values(), [self.repo.head()])
 
 
+@skipIfPY3
 class TagListTests(PorcelainTestCase):
 
     def test_empty(self):
@@ -393,6 +409,7 @@ class TagListTests(PorcelainTestCase):
         self.assertEqual(["bar/bla", "foo"], tags)
 
 
+@skipIfPY3
 class TagDeleteTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -404,6 +421,7 @@ class TagDeleteTests(PorcelainTestCase):
         self.assertFalse("foo" in porcelain.tag_list(self.repo))
 
 
+@skipIfPY3
 class ResetTests(PorcelainTestCase):
 
     def test_hard_head(self):
@@ -427,6 +445,7 @@ class ResetTests(PorcelainTestCase):
         self.assertEqual([], changes)
 
 
+@skipIfPY3
 class PushTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -471,6 +490,7 @@ class PushTests(PorcelainTestCase):
         self.assertEqual(os.path.basename(fullpath), change.new.path)
 
 
+@skipIfPY3
 class PullTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -504,6 +524,7 @@ class PullTests(PorcelainTestCase):
         self.assertEqual(r['HEAD'].id, self.repo['HEAD'].id)
 
 
+@skipIfPY3
 class StatusTests(PorcelainTestCase):
 
     def test_status(self):
@@ -601,6 +622,7 @@ class StatusTests(PorcelainTestCase):
 # TODO(jelmer): Add test for dulwich.porcelain.daemon
 
 
+@skipIfPY3
 class UploadPackTests(PorcelainTestCase):
     """Tests for upload_pack."""
 
@@ -612,6 +634,7 @@ class UploadPackTests(PorcelainTestCase):
         self.assertEqual(0, exitcode)
 
 
+@skipIfPY3
 class ReceivePackTests(PorcelainTestCase):
     """Tests for receive_pack."""
 
@@ -634,6 +657,7 @@ class ReceivePackTests(PorcelainTestCase):
         self.assertEqual(0, exitcode)
 
 
+@skipIfPY3
 class BranchListTests(PorcelainTestCase):
 
     def test_standard(self):
@@ -648,6 +672,7 @@ class BranchListTests(PorcelainTestCase):
             set(porcelain.branch_list(self.repo)))
 
 
+@skipIfPY3
 class BranchCreateTests(PorcelainTestCase):
 
     def test_branch_exists(self):
@@ -666,6 +691,7 @@ class BranchCreateTests(PorcelainTestCase):
             set(porcelain.branch_list(self.repo)))
 
 
+@skipIfPY3
 class BranchDeleteTests(PorcelainTestCase):
 
     def test_simple(self):
@@ -677,6 +703,7 @@ class BranchDeleteTests(PorcelainTestCase):
         self.assertFalse("foo" in porcelain.branch_list(self.repo))
 
 
+@skipIfPY3
 class FetchTests(PorcelainTestCase):
 
     def test_simple(self):

+ 6 - 0
dulwich/tests/test_protocol.py

@@ -37,6 +37,7 @@ from dulwich.protocol import (
     BufferedPktLineWriter,
     )
 from dulwich.tests import TestCase
+from dulwich.tests.utils import skipIfPY3
 
 
 class BaseProtocolTests(object):
@@ -106,6 +107,7 @@ class BaseProtocolTests(object):
         self.assertRaises(AssertionError, self.proto.read_cmd)
 
 
+@skipIfPY3
 class ProtocolTests(BaseProtocolTests, TestCase):
 
     def setUp(self):
@@ -133,6 +135,7 @@ class ReceivableBytesIO(BytesIO):
         return self.read(size - 1)
 
 
+@skipIfPY3
 class ReceivableProtocolTests(BaseProtocolTests, TestCase):
 
     def setUp(self):
@@ -204,6 +207,7 @@ class ReceivableProtocolTests(BaseProtocolTests, TestCase):
         self.assertEqual(all_data, data)
 
 
+@skipIfPY3
 class CapabilitiesTestCase(TestCase):
 
     def test_plain(self):
@@ -233,6 +237,7 @@ class CapabilitiesTestCase(TestCase):
                                     'multi_ack_detailed']))
 
 
+@skipIfPY3
 class BufferedPktLineWriterTests(TestCase):
 
     def setUp(self):
@@ -288,6 +293,7 @@ class BufferedPktLineWriterTests(TestCase):
         self.assertOutputEquals('0005z')
 
 
+@skipIfPY3
 class PktLineParserTests(TestCase):
 
     def test_none(self):

+ 5 - 1
dulwich/tests/test_refs.py

@@ -45,6 +45,7 @@ from dulwich.tests import (
 from dulwich.tests.utils import (
     open_repo,
     tear_down_repo,
+    skipIfPY3,
     )
 
 
@@ -79,6 +80,7 @@ TWOS = "2" * 40
 THREES = "3" * 40
 FOURS = "4" * 40
 
+@skipIfPY3
 class PackedRefsFileTests(TestCase):
 
     def test_split_ref_line_errors(self):
@@ -238,7 +240,7 @@ class RefsContainerTests(object):
 
 
 
-
+@skipIfPY3
 class DictRefsContainerTests(RefsContainerTests, TestCase):
 
     def setUp(self):
@@ -254,6 +256,7 @@ class DictRefsContainerTests(RefsContainerTests, TestCase):
         self.assertEqual(expected_refs, self._refs.as_dict())
 
 
+@skipIfPY3
 class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
     def setUp(self):
@@ -425,6 +428,7 @@ _TEST_REFS_SERIALIZED = (
 '3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8\trefs/tags/refs-0.2\n')
 
 
+@skipIfPY3
 class InfoRefsContainerTests(TestCase):
 
     def test_invalid_refname(self):

+ 4 - 0
dulwich/tests/test_repository.py

@@ -42,11 +42,13 @@ from dulwich.tests.utils import (
     open_repo,
     tear_down_repo,
     setup_warning_catcher,
+    skipIfPY3,
     )
 
 missing_sha = 'b91fa4d900e17e99b433218e988c4eb4a3e9a097'
 
 
+@skipIfPY3
 class CreateRepositoryTests(TestCase):
 
     def assertFileContentsEqual(self, expected, repo, path):
@@ -85,6 +87,7 @@ class CreateRepositoryTests(TestCase):
         self._check_repo_contents(repo, True)
 
 
+@skipIfPY3
 class RepositoryTests(TestCase):
 
     def setUp(self):
@@ -494,6 +497,7 @@ exit 1
         self.assertEqual([commit_sha], r[commit_sha2].parents)
 
 
+@skipIfPY3
 class BuildRepoTests(TestCase):
     """Tests that build on-disk repos from scratch.
 

+ 15 - 0
dulwich/tests/test_server.py

@@ -59,6 +59,7 @@ from dulwich.tests import TestCase
 from dulwich.tests.utils import (
     make_commit,
     make_object,
+    skipIfPY3,
     )
 from dulwich.protocol import (
     ZERO_SHA,
@@ -159,6 +160,7 @@ class HandlerTestCase(TestCase):
         self.assertFalse(self._handler.has_capability('capxxx'))
 
 
+@skipIfPY3
 class UploadPackHandlerTestCase(TestCase):
 
     def setUp(self):
@@ -212,6 +214,7 @@ class UploadPackHandlerTestCase(TestCase):
         self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
 
 
+@skipIfPY3
 class FindShallowTests(TestCase):
 
     def setUp(self):
@@ -292,6 +295,7 @@ class TestUploadPackHandler(UploadPackHandler):
     def required_capabilities(self):
         return ()
 
+@skipIfPY3
 class ReceivePackHandlerTestCase(TestCase):
 
     def setUp(self):
@@ -314,6 +318,7 @@ class ReceivePackHandlerTestCase(TestCase):
         self.assertEqual(status[1][1], 'ok')
 
 
+@skipIfPY3
 class ProtocolGraphWalkerEmptyTestCase(TestCase):
     def setUp(self):
         super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
@@ -335,6 +340,7 @@ class ProtocolGraphWalkerEmptyTestCase(TestCase):
 
 
 
+@skipIfPY3
 class ProtocolGraphWalkerTestCase(TestCase):
 
     def setUp(self):
@@ -515,6 +521,7 @@ class ProtocolGraphWalkerTestCase(TestCase):
           ])
 
 
+@skipIfPY3
 class TestProtocolGraphWalker(object):
 
     def __init__(self):
@@ -545,6 +552,7 @@ class TestProtocolGraphWalker(object):
         return self.acks.pop(0)
 
 
+@skipIfPY3
 class AckGraphWalkerImplTestCase(TestCase):
     """Base setup and asserts for AckGraphWalker tests."""
 
@@ -577,6 +585,7 @@ class AckGraphWalkerImplTestCase(TestCase):
         self.assertEqual(sha, next(self._impl))
 
 
+@skipIfPY3
 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
     impl_cls = SingleAckGraphWalkerImpl
@@ -645,6 +654,7 @@ class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNak()
 
 
+@skipIfPY3
 class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
     impl_cls = MultiAckGraphWalkerImpl
@@ -719,6 +729,7 @@ class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNak()
 
 
+@skipIfPY3
 class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
 
     impl_cls = MultiAckDetailedGraphWalkerImpl
@@ -832,6 +843,7 @@ class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
         self.assertNak()
 
 
+@skipIfPY3
 class FileSystemBackendTests(TestCase):
     """Tests for FileSystemBackend."""
 
@@ -860,6 +872,7 @@ class FileSystemBackendTests(TestCase):
                           lambda: backend.open_repository('/ups'))
 
 
+@skipIfPY3
 class DictBackendTests(TestCase):
     """Tests for DictBackend."""
 
@@ -877,6 +890,7 @@ class DictBackendTests(TestCase):
                           lambda: backend.open_repository('/ups'))
 
 
+@skipIfPY3
 class ServeCommandTests(TestCase):
     """Tests for serve_command."""
 
@@ -902,6 +916,7 @@ class ServeCommandTests(TestCase):
         self.assertEqual(0, exitcode)
 
 
+@skipIfPY3
 class UpdateServerInfoTests(TestCase):
     """Tests for update_server_info."""
 

+ 2 - 0
dulwich/tests/test_walk.py

@@ -49,6 +49,7 @@ from dulwich.tests.utils import (
     F,
     make_object,
     build_commit_graph,
+    skipIfPY3,
     )
 
 
@@ -70,6 +71,7 @@ class TestWalkEntry(object):
         return self.changes == other.changes()
 
 
+@skipIfPY3
 class WalkerTest(TestCase):
 
     def setUp(self):

+ 5 - 0
dulwich/tests/test_web.py

@@ -61,6 +61,7 @@ from dulwich.web import (
 
 from dulwich.tests.utils import (
     make_object,
+    skipIfPY3,
     )
 
 
@@ -115,6 +116,7 @@ def _test_backend(objects, refs=None, named_files=None):
     return DictBackend({'/': repo})
 
 
+@skipIfPY3
 class DumbHandlersTestCase(WebTestCase):
 
     def test_send_file_not_found(self):
@@ -284,6 +286,7 @@ class DumbHandlersTestCase(WebTestCase):
         self.assertFalse(self._req.cached)
 
 
+@skipIfPY3
 class SmartHandlersTestCase(WebTestCase):
 
     class _TestUploadPackHandler(object):
@@ -361,6 +364,7 @@ class SmartHandlersTestCase(WebTestCase):
         self.assertFalse(self._req.cached)
 
 
+@skipIfPY3
 class LengthLimitedFileTestCase(TestCase):
     def test_no_cutoff(self):
         f = _LengthLimitedFile(BytesIO('foobar'), 1024)
@@ -419,6 +423,7 @@ class HTTPGitRequestTestCase(WebTestCase):
         self.assertEqual(402, self._status)
 
 
+@skipIfPY3
 class HTTPGitApplicationTestCase(TestCase):
 
     def setUp(self):

+ 8 - 1
dulwich/tests/utils.py

@@ -23,10 +23,15 @@
 import datetime
 import os
 import shutil
+import sys
 import tempfile
 import time
 import types
 
+from unittest import (
+    SkipTest,
+    skipIf,
+    )
 import warnings
 
 from dulwich.index import (
@@ -98,7 +103,7 @@ def make_object(cls, **attrs):
         pass
 
     obj = TestObject()
-    for name, value in attrs.iteritems():
+    for name, value in attrs.items():
         if name == 'id':
             # id property is read-only, so we overwrite sha instead.
             sha = FixedSha(value)
@@ -328,3 +333,5 @@ def setup_warning_catcher():
         warnings.showwarning = original_showwarning
 
     return caught_warnings, restore_showwarning
+
+skipIfPY3 = skipIf(sys.version_info[0] == 3, "Feature not yet ported to python3.")

+ 6 - 1
dulwich/web.py

@@ -27,7 +27,6 @@ import os
 import re
 import sys
 import time
-from urlparse import parse_qs
 from wsgiref.simple_server import (
     WSGIRequestHandler,
     ServerHandler,
@@ -35,6 +34,12 @@ from wsgiref.simple_server import (
     make_server,
     )
 
+try:
+    from urlparse import parse_qs
+except ImportError:
+    from urllib.parse import parse_qs
+
+
 from dulwich import log_utils
 from dulwich.protocol import (
     ReceivableProtocol,

+ 6 - 4
setup.py

@@ -17,7 +17,6 @@ import sys
 if sys.platform == 'win32':
     include_dirs.append('dulwich')
 
-
 class DulwichDistribution(Distribution):
 
     def is_pure(self):
@@ -47,7 +46,12 @@ if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
         if l.startswith('Xcode') and int(l.split()[1].split('.')[0]) >= 4:
             os.environ['ARCHFLAGS'] = ''
 
-tests_require = ['fastimport', 'mock', 'gevent', 'geventhttpclient']
+if sys.version_info[0] == 2:
+    tests_require = ['fastimport', 'mock', 'gevent', 'geventhttpclient']
+else:
+    # fastimport, gevent, geventhttpclient are not available for PY3
+    # mock only used for test_swift, which requires gevent/geventhttpclient
+    tests_require = []
 if sys.version_info < (2, 7):
     tests_require.append('unittest2')
 
@@ -91,6 +95,4 @@ setup(name='dulwich',
       tests_require=tests_require,
       distclass=DulwichDistribution,
       include_package_data=True,
-      use_2to3=True,
-      convert_2to3_doctests=['../docs/*', '../docs/tutorial/*', ],
       )