diff --git a/setup.py b/setup.py
index 4ec0404..0f4c121 100644
--- a/setup.py
+++ b/setup.py
@@ -27,9 +27,14 @@
 
 """Setup file for pygit2."""
 
-from distutils.core import setup, Extension
-from distutils.core import Distribution
+try:
+    from setuptools import setup, Extension, Command
+    SETUPTOOLS = True
+except ImportError:
+    from distutils.core import setup, Extension, Command
+    SETUPTOOLS = False
 
+import sys
 
 # Replace with your libgit2 configuration.
 include_dirs = ['/usr/local/include']
@@ -37,6 +42,30 @@ library_dirs = ['/usr/local/lib']
 libraries = ['git2', 'z', 'crypto']
 
 
+class TestCommand(Command):
+    """Command for running pygit2 tests."""
+
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        self.run_command('build')
+        import test
+        test.main()
+
+
+kwargs = {}
+if SETUPTOOLS:
+    kwargs = {'test_suite': 'test.test_suite'}
+else:
+    kwargs = {'cmdclass': {'test': TestCommand}}
+
+
 setup(name='pygit2',
       description='Python bindings for libgit2.',
       keywords='git',
@@ -55,4 +84,5 @@ setup(name='pygit2',
                     library_dirs=library_dirs,
                     libraries=libraries),
           ],
+      **kwargs
       )
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..febb37b
--- /dev/null
+++ b/test/__init__.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Google, Inc.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# In addition to the permissions in the GNU General Public License,
+# the authors give you unlimited permission to link the compiled
+# version of this file into combinations with other programs,
+# and to distribute those combinations without any restriction
+# coming from the use of this file.  (The General Public License
+# restrictions do apply in other respects; for example, they cover
+# modification of the file, and distribution when not linked into
+# a combined executable.)
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING.  If not, write to
+# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+"""Pygit2 test definitions.
+
+These tests are run automatically with 'setup.py test', but can also be run
+manually.
+"""
+
+import sys
+import unittest
+
+
+def test_suite():
+    names = ['repository']
+    modules = ['test.test_%s' % n for n in names]
+    return unittest.defaultTestLoader.loadTestsFromNames(modules)
+
+
+def main():
+    unittest.main(module=__name__, defaultTest='test_suite', argv=sys.argv[:1])
+
+
+if __name__ == '__main__':
+    main()
diff --git a/test/data/testrepo.git/HEAD b/test/data/testrepo.git/HEAD
new file mode 100644
index 0000000..cb089cd
--- /dev/null
+++ b/test/data/testrepo.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/test/data/testrepo.git/config b/test/data/testrepo.git/config
new file mode 100644
index 0000000..83142d2
--- /dev/null
+++ b/test/data/testrepo.git/config
@@ -0,0 +1,6 @@
+[core]
+	repositoryformatversion = 0
+	filemode = true
+	bare = true
+[gc]
+	auto = no
diff --git a/test/data/testrepo.git/objects/29/7efb891a47de80be0cfe9c639e4b8c9b450989 b/test/data/testrepo.git/objects/29/7efb891a47de80be0cfe9c639e4b8c9b450989
new file mode 100644
index 0000000..fed0df4
Binary files /dev/null and b/test/data/testrepo.git/objects/29/7efb891a47de80be0cfe9c639e4b8c9b450989 differ
diff --git a/test/data/testrepo.git/objects/5f/e808e8953c12735680c257f56600cb0de44b10 b/test/data/testrepo.git/objects/5f/e808e8953c12735680c257f56600cb0de44b10
new file mode 100644
index 0000000..7640152
--- /dev/null
+++ b/test/data/testrepo.git/objects/5f/e808e8953c12735680c257f56600cb0de44b10
@@ -0,0 +1 @@
+x��[j�0�~{��[N��R���Pdy��%Qt�
4��s�e�0���@���Rsd�."�HC�V��P]�8:4�ɪ�3r%L�$xt���F����b�eC:�
^�S�ڶ�5�<��<�o����q[��aJ}�>x�ؚ�=�T��On����U��Tv�BJ���6���`���P)��m��A��3��X]f
\ No newline at end of file
diff --git a/test/data/testrepo.git/objects/61/4fd9a3094bf618ea938fffc00e7d1a54f89ad0 b/test/data/testrepo.git/objects/61/4fd9a3094bf618ea938fffc00e7d1a54f89ad0
new file mode 100644
index 0000000..c3ba7b0
Binary files /dev/null and b/test/data/testrepo.git/objects/61/4fd9a3094bf618ea938fffc00e7d1a54f89ad0 differ
diff --git a/test/data/testrepo.git/objects/7f/129fd57e31e935c6d60a0c794efe4e6927664b b/test/data/testrepo.git/objects/7f/129fd57e31e935c6d60a0c794efe4e6927664b
new file mode 100644
index 0000000..1abe9e8
Binary files /dev/null and b/test/data/testrepo.git/objects/7f/129fd57e31e935c6d60a0c794efe4e6927664b differ
diff --git a/test/data/testrepo.git/objects/96/7fce8df97cc71722d3c2a5930ef3e6f1d27b12 b/test/data/testrepo.git/objects/96/7fce8df97cc71722d3c2a5930ef3e6f1d27b12
new file mode 100644
index 0000000..2070adf
Binary files /dev/null and b/test/data/testrepo.git/objects/96/7fce8df97cc71722d3c2a5930ef3e6f1d27b12 differ
diff --git a/test/data/testrepo.git/objects/info/packs b/test/data/testrepo.git/objects/info/packs
new file mode 100644
index 0000000..e6cd1f1
--- /dev/null
+++ b/test/data/testrepo.git/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.pack
+
diff --git a/test/data/testrepo.git/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.idx b/test/data/testrepo.git/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.idx
new file mode 100644
index 0000000..e2e1819
Binary files /dev/null and b/test/data/testrepo.git/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.idx differ
diff --git a/test/data/testrepo.git/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.pack b/test/data/testrepo.git/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.pack
new file mode 100644
index 0000000..a9d5307
Binary files /dev/null and b/test/data/testrepo.git/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.pack differ
diff --git a/test/data/testrepo.git/packed-refs b/test/data/testrepo.git/packed-refs
new file mode 100644
index 0000000..506a416
--- /dev/null
+++ b/test/data/testrepo.git/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled 
+c2792cfa289ae6321ecf2cd5806c2194b0fd070c refs/heads/master
diff --git a/test/data/testrepo.git/refs/heads/master b/test/data/testrepo.git/refs/heads/master
new file mode 100644
index 0000000..b7105a1
--- /dev/null
+++ b/test/data/testrepo.git/refs/heads/master
@@ -0,0 +1 @@
+5fe808e8953c12735680c257f56600cb0de44b10
diff --git a/test/test_repository.py b/test/test_repository.py
new file mode 100644
index 0000000..90ed777
--- /dev/null
+++ b/test/test_repository.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Google, Inc.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# In addition to the permissions in the GNU General Public License,
+# the authors give you unlimited permission to link the compiled
+# version of this file into combinations with other programs,
+# and to distribute those combinations without any restriction
+# coming from the use of this file.  (The General Public License
+# restrictions do apply in other respects; for example, they cover
+# modification of the file, and distribution when not linked into
+# a combined executable.)
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING.  If not, write to
+# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+"""Tests for Repository objects."""
+
+__author__ = 'dborowitz@google.com (Dave Borowitz)'
+
+import binascii
+import unittest
+
+import pygit2
+import utils
+
+A_HEX_SHA = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16'
+A_BIN_SHA = binascii.unhexlify(A_HEX_SHA)
+
+
+class RepositoryTest(utils.TestRepoTestCase):
+
+    def test_read(self):
+        self.assertRaises(TypeError, self.repo.read, 123)
+        self.assertRaises(ValueError, self.repo.read, A_BIN_SHA)
+
+        a = self.repo.read(A_HEX_SHA)
+        self.assertEqual((pygit2.GIT_OBJ_BLOB, 'a contents\n'), a)
+
+        a2 = self.repo.read('7f129fd57e31e935c6d60a0c794efe4e6927664b')
+        self.assertEqual((pygit2.GIT_OBJ_BLOB, 'a contents 2\n'), a2)
+
+    def test_contains(self):
+        self.assertRaises(TypeError, lambda: 123 in self.repo)
+        self.assertRaises(ValueError, lambda: A_BIN_SHA in self.repo)
+        self.assertTrue(A_HEX_SHA in self.repo)
+        self.assertFalse('a' * 40 in self.repo)
+
+    def test_lookup_blob(self):
+        self.assertRaises(TypeError, lambda: self.repo[123])
+        self.assertRaises(ValueError, lambda: self.repo[A_BIN_SHA])
+        a = self.repo[A_HEX_SHA]
+        self.assertEqual('a contents\n', a.read_raw())
+        self.assertEqual(A_HEX_SHA, a.sha)
+        self.assertEqual(pygit2.GIT_OBJ_BLOB, a.type)
+
+    def test_lookup_commit(self):
+        commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10'
+        commit = self.repo[commit_sha]
+        self.assertEqual(commit_sha, commit.sha)
+        self.assertEqual(pygit2.GIT_OBJ_COMMIT, commit.type)
+        self.assertEqual(('Second test data commit.\n\n'
+                          'This commit has some additional text.\n'),
+                         commit.message)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/test/utils.py b/test/utils.py
new file mode 100644
index 0000000..8858ddc
--- /dev/null
+++ b/test/utils.py
@@ -0,0 +1,52 @@
+# Copyright 2010 Google, Inc.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2,
+# as published by the Free Software Foundation.
+#
+# In addition to the permissions in the GNU General Public License,
+# the authors give you unlimited permission to link the compiled
+# version of this file into combinations with other programs,
+# and to distribute those combinations without any restriction
+# coming from the use of this file.  (The General Public License
+# restrictions do apply in other respects; for example, they cover
+# modification of the file, and distribution when not linked into
+# a combined executable.)
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING.  If not, write to
+# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+"""Test utilities for libgit2."""
+
+__author__ = 'dborowitz@google.com (Dave Borowitz)'
+
+import os
+import shutil
+import tempfile
+import unittest
+
+import pygit2
+
+
+def open_repo(repo_dir):
+    repo_path = os.path.join(os.path.dirname(__file__), 'data', repo_dir)
+    temp_dir = tempfile.mkdtemp()
+    temp_repo_path = os.path.join(temp_dir, repo_dir)
+    shutil.copytree(repo_path, temp_repo_path)
+    return temp_dir, pygit2.Repository(temp_repo_path)
+
+
+class TestRepoTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self._temp_dir, self.repo = open_repo('testrepo.git')
+
+    def tearDown(self):
+        shutil.rmtree(self._temp_dir)