浏览代码

Test case refactoring

Jonas Borgström 12 年之前
父节点
当前提交
9421b64895

+ 6 - 0
darc/cache.py

@@ -27,6 +27,9 @@ class Cache(object):
             self.sync()
             self.commit()
 
+    def __del__(self):
+        self.close()
+
     def create(self):
         """Create a new empty repository at `path`
         """
@@ -59,6 +62,9 @@ class Cache(object):
         self.chunks = ChunkIndex(os.path.join(self.path, 'chunks').encode('utf-8'))
         self.files = None
 
+    def close(self):
+        self.lock_fd.close()
+
     def _read_files(self):
         self.files = {}
         self._newest_mtime = 0

+ 1 - 47
darc/crypto.py

@@ -1,8 +1,6 @@
-from binascii import hexlify
-from ctypes import cdll, c_char_p, c_int, c_uint, c_void_p, byref, POINTER, create_string_buffer
+from ctypes import cdll, c_char_p, c_int, c_uint, c_void_p, POINTER, create_string_buffer
 from ctypes.util import find_library
 import struct
-import unittest
 
 libcrypto = cdll.LoadLibrary(find_library('crypto'))
 libcrypto.PKCS5_PBKDF2_HMAC.argtypes = (c_char_p, c_int, c_char_p, c_int, c_int, c_void_p, c_int, c_char_p)
@@ -55,47 +53,3 @@ class AES:
         libcrypto.AES_ctr128_encrypt(data, out, len(data), self.key, self.iv, self.buf, self.num)
         return out.raw
     decrypt = encrypt
-
-
-class CryptoTestCase(unittest.TestCase):
-
-    def test_bytes_to_int(self):
-        self.assertEqual(bytes_to_int(b'\0\0\0\1'), 1)
-
-    def test_bytes_to_long(self):
-        self.assertEqual(bytes_to_long(b'\0\0\0\0\0\0\0\1'), 1)
-        self.assertEqual(long_to_bytes(1), b'\0\0\0\0\0\0\0\1')
-
-    def test_pbkdf2_sha256(self):
-        self.assertEqual(hexlify(pbkdf2_sha256(b'password', b'salt', 1, 32)),
-                         b'120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b')
-        self.assertEqual(hexlify(pbkdf2_sha256(b'password', b'salt', 2, 32)),
-                         b'ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43')
-        self.assertEqual(hexlify(pbkdf2_sha256(b'password', b'salt', 4096, 32)),
-                         b'c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a')
-
-    def test_get_random_bytes(self):
-        bytes = get_random_bytes(10)
-        bytes2 = get_random_bytes(10)
-        self.assertEqual(len(bytes), 10)
-        self.assertEqual(len(bytes2), 10)
-        self.assertNotEqual(bytes, bytes2)
-
-    def test_aes(self):
-        key = b'X' * 32
-        data = b'foo' * 10
-        aes = AES(key)
-        self.assertEqual(bytes_to_long(aes.iv.raw, 8), 0)
-        cdata = aes.encrypt(data)
-        self.assertEqual(hexlify(cdata), b'c6efb702de12498f34a2c2bbc8149e759996d08bf6dc5c610aefc0c3a466')
-        self.assertEqual(bytes_to_long(aes.iv.raw, 8), 2)
-        self.assertNotEqual(data, aes.decrypt(cdata))
-        aes.reset(iv=b'\0' * 16)
-        self.assertEqual(data, aes.decrypt(cdata))
-
-
-def suite():
-    return unittest.TestLoader().loadTestsFromTestCase(CryptoTestCase)
-
-if __name__ == '__main__':
-    unittest.main()

+ 0 - 30
darc/helpers.py

@@ -114,21 +114,6 @@ def exclude_path(path, patterns):
 
 class IncludePattern(object):
     """--include PATTERN
-
-    >>> py = IncludePattern('*.py')
-    >>> foo = IncludePattern('/foo')
-    >>> py.match('/foo/foo.py')
-    True
-    >>> py.match('/bar/foo.java')
-    False
-    >>> foo.match('/foo/foo.py')
-    True
-    >>> foo.match('/bar/foo.java')
-    False
-    >>> foo.match('/foobar/foo.py')
-    False
-    >>> foo.match('/foo')
-    True
     """
     def __init__(self, pattern):
         self.pattern = self.dirpattern = pattern
@@ -172,12 +157,6 @@ def format_time(t):
 
 def format_timedelta(td):
     """Format timedelta in a human friendly format
-
-    >>> from datetime import datetime
-    >>> t0 = datetime(2001, 1, 1, 10, 20, 3, 0)
-    >>> t1 = datetime(2001, 1, 1, 12, 20, 4, 100000)
-    >>> format_timedelta(t1 - t0)
-    '2 hours 1.10 seconds'
     """
     # Since td.total_seconds() requires python 2.7
     ts = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / float(10 ** 6)
@@ -268,15 +247,6 @@ def group2gid(group):
 
 class Location(object):
     """Object representing a repository / archive location
-
-    >>> Location('ssh://user@host:1234/some/path::archive')
-    Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')
-    >>> Location('file:///some/path::archive')
-    Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
-    >>> Location('user@host:/some/path::archive')
-    Location(proto='ssh', user='user', host='host', port=22, path='/some/path', archive='archive')
-    >>> Location('/some/path::archive')
-    Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
     """
     proto = user = host = port = path = archive = None
     ssh_re = re.compile(r'(?P<proto>ssh)://(?:(?P<user>[^@]+)@)?'

+ 3 - 103
darc/key.py

@@ -1,18 +1,14 @@
-from binascii import hexlify, unhexlify, a2b_base64, b2a_base64
+from binascii import hexlify, a2b_base64, b2a_base64
 from getpass import getpass
 import os
 import msgpack
-import re
-import shutil
-import tempfile
 import textwrap
-import unittest
 import hmac
 from hashlib import sha256
 import zlib
 
 from .crypto import pbkdf2_sha256, get_random_bytes, AES, bytes_to_long, long_to_bytes, bytes_to_int
-from .helpers import IntegrityError, get_keys_dir, Location
+from .helpers import IntegrityError, get_keys_dir
 
 PREFIX = b'\0' * 8
 
@@ -20,6 +16,7 @@ KEYFILE = b'\0'
 PASSPHRASE = b'\1'
 PLAINTEXT = b'\2'
 
+
 class HMAC(hmac.HMAC):
 
     def update(self, msg):
@@ -300,100 +297,3 @@ class KeyfileKey(AESKeyBase):
         print('Key file "%s" created.' % key.path)
         print('Keep this file safe. Your data will be inaccessible without it.')
         return key
-
-
-class KeyTestCase(unittest.TestCase):
-
-    class MockArgs(object):
-        repository = Location(tempfile.mkstemp()[1])
-
-    keyfile2_key_file = """
-        DARC KEY 0000000000000000000000000000000000000000000000000000000000000000
-        hqppdGVyYXRpb25zzgABhqCkaGFzaNoAIMyonNI+7Cjv0qHi0AOBM6bLGxACJhfgzVD2oq
-        bIS9SFqWFsZ29yaXRobaZzaGEyNTakc2FsdNoAINNK5qqJc1JWSUjACwFEWGTdM7Nd0a5l
-        1uBGPEb+9XM9p3ZlcnNpb24BpGRhdGHaANAYDT5yfPpU099oBJwMomsxouKyx/OG4QIXK2
-        hQCG2L2L/9PUu4WIuKvGrsXoP7syemujNfcZws5jLp2UPva4PkQhQsrF1RYDEMLh2eF9Ol
-        rwtkThq1tnh7KjWMG9Ijt7/aoQtq0zDYP/xaFF8XXSJxiyP5zjH5+spB6RL0oQHvbsliSh
-        /cXJq7jrqmrJ1phd6dg4SHAM/i+hubadZoS6m25OQzYAW09wZD/phG8OVa698Z5ed3HTaT
-        SmrtgJL3EoOKgUI9d6BLE4dJdBqntifo""".strip()
-
-    keyfile2_cdata = unhexlify(re.sub('\W', '', """
-        0055f161493fcfc16276e8c31493c4641e1eb19a79d0326fad0291e5a9c98e5933
-        00000000000003e8d21eaf9b86c297a8cd56432e1915bb
-        """))
-    keyfile2_id = unhexlify('c3fbf14bc001ebcc3cd86e696c13482ed071740927cd7cbe1b01b4bfcee49314')
-
-    def setUp(self):
-        self.tmppath = tempfile.mkdtemp()
-        os.environ['DARC_KEYS_DIR'] = self.tmppath
-
-    def tearDown(self):
-        shutil.rmtree(self.tmppath)
-
-    class MockRepository(object):
-        class _Location(object):
-            orig = '/some/place'
-
-        _location = _Location()
-        id = bytes(32)
-
-    def setUp(self):
-        self.tmpdir = tempfile.mkdtemp()
-        os.environ['DARC_KEYS_DIR'] = self.tmpdir
-
-    def tearDown(self):
-        shutil.rmtree(self.tmpdir)
-
-    def test_plaintext(self):
-        key = PlaintextKey.create(None, None)
-        data = b'foo'
-        self.assertEqual(hexlify(key.id_hash(data)), b'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae')
-        self.assertEqual(data, key.decrypt(key.id_hash(data), key.encrypt(data)))
-
-    def test_keyfile(self):
-        os.environ['DARC_PASSPHRASE'] = 'test'
-        key = KeyfileKey.create(self.MockRepository(), self.MockArgs())
-        self.assertEqual(bytes_to_long(key.enc_cipher.iv, 8), 0)
-        manifest = key.encrypt(b'')
-        iv = key.extract_iv(manifest)
-        key2 = KeyfileKey.detect(self.MockRepository(), manifest)
-        self.assertEqual(bytes_to_long(key2.enc_cipher.iv, 8), iv + 1000)
-        # Key data sanity check
-        self.assertEqual(len(set([key2.id_key, key2.enc_key, key2.enc_hmac_key])), 3)
-        self.assertEqual(key2.chunk_seed == 0, False)
-        data = b'foo'
-        self.assertEqual(data, key2.decrypt(key.id_hash(data), key.encrypt(data)))
-
-    def test_keyfile2(self):
-        with open(os.path.join(os.environ['DARC_KEYS_DIR'], 'keyfile'), 'w') as fd:
-            fd.write(self.keyfile2_key_file)
-        os.environ['DARC_PASSPHRASE'] = 'passphrase'
-        key = KeyfileKey.detect(self.MockRepository(), self.keyfile2_cdata)
-        self.assertEqual(key.decrypt(self.keyfile2_id, self.keyfile2_cdata), b'payload')
-
-    def test_passphrase(self):
-        os.environ['DARC_PASSPHRASE'] = 'test'
-        key = PassphraseKey.create(self.MockRepository(), None)
-        self.assertEqual(bytes_to_long(key.enc_cipher.iv, 8), 0)
-        self.assertEqual(hexlify(key.id_key), b'793b0717f9d8fb01c751a487e9b827897ceea62409870600013fbc6b4d8d7ca6')
-        self.assertEqual(hexlify(key.enc_hmac_key), b'b885a05d329a086627412a6142aaeb9f6c54ab7950f996dd65587251f6bc0901')
-        self.assertEqual(hexlify(key.enc_key), b'2ff3654c6daf7381dbbe718d2b20b4f1ea1e34caa6cc65f6bb3ac376b93fed2a')
-        self.assertEqual(key.chunk_seed, -775740477)
-        manifest = key.encrypt(b'')
-        iv = key.extract_iv(manifest)
-        key2 = PassphraseKey.detect(self.MockRepository(), manifest)
-        self.assertEqual(bytes_to_long(key2.enc_cipher.iv, 8), iv + 1000)
-        self.assertEqual(key.id_key, key2.id_key)
-        self.assertEqual(key.enc_hmac_key, key2.enc_hmac_key)
-        self.assertEqual(key.enc_key, key2.enc_key)
-        self.assertEqual(key.chunk_seed, key2.chunk_seed)
-        data = b'foo'
-        self.assertEqual(hexlify(key.id_hash(data)), b'818217cf07d37efad3860766dcdf1d21e401650fed2d76ed1d797d3aae925990')
-        self.assertEqual(data, key2.decrypt(key2.id_hash(data), key.encrypt(data)))
-
-
-def suite():
-    return unittest.TestLoader().loadTestsFromTestCase(KeyTestCase)
-
-if __name__ == '__main__':
-    unittest.main()

+ 0 - 48
darc/lrucache.py

@@ -1,5 +1,4 @@
 from heapq import heappush, heapify, heapreplace, heappop
-import unittest
 
 
 class LRUCache(dict):
@@ -44,50 +43,3 @@ class LRUCache(dict):
     def _not_implemented(self, *args, **kw):
         raise NotImplementedError
     popitem = setdefault = update = _not_implemented
-
-
-class LRUCacheTestCase(unittest.TestCase):
-
-    def test(self):
-        c = LRUCache(2)
-        self.assertEqual(len(c), 0)
-        for i, x in enumerate('abc'):
-            c[x] = i
-        self.assertEqual(len(c), 2)
-        self.assertEqual(set(c), set(['b', 'c']))
-        self.assertEqual(set(c.items()), set([('b', 1), ('c', 2)]))
-        self.assertEqual(False, 'a' in c)
-        self.assertEqual(True, 'b' in c)
-        self.assertRaises(KeyError, lambda: c['a'])
-        self.assertEqual(c['b'], 1)
-        self.assertEqual(c['c'], 2)
-        c['d'] = 3
-        self.assertEqual(len(c), 2)
-        self.assertEqual(c['c'], 2)
-        self.assertEqual(c['d'], 3)
-        c['c'] = 22
-        c['e'] = 4
-        self.assertEqual(len(c), 2)
-        self.assertRaises(KeyError, lambda: c['d'])
-        self.assertEqual(c['c'], 22)
-        self.assertEqual(c['e'], 4)
-        del c['c']
-        self.assertEqual(len(c), 1)
-        self.assertRaises(KeyError, lambda: c['c'])
-        self.assertEqual(c['e'], 4)
-
-    def test_pop(self):
-        c = LRUCache(2)
-        c[1] = 1
-        c[2] = 2
-        c.pop(1)
-        c[3] = 3
-
-
-def suite():
-    return unittest.TestLoader().loadTestsFromTestCase(LRUCacheTestCase)
-
-
-if __name__ == '__main__':
-    unittest.main()
-

+ 1 - 17
darc/remote.py

@@ -5,9 +5,8 @@ import select
 from subprocess import Popen, PIPE
 import sys
 import getpass
-import unittest
 
-from .repository import Repository, RepositoryTestCase
+from .repository import Repository
 from .lrucache import LRUCache
 
 BUFSIZE = 10 * 1024 * 1024
@@ -262,18 +261,3 @@ class RemoteRepository(object):
             self.p.stdout.close()
             self.p.wait()
             self.p = None
-
-
-class RemoteRepositoryTestCase(RepositoryTestCase):
-
-    def open(self, create=False):
-        from .helpers import Location
-        return RemoteRepository(Location('localhost:' + os.path.join(self.tmppath, 'repository')), create=create)
-
-
-def suite():
-    return unittest.TestLoader().loadTestsFromTestCase(RemoteRepositoryTestCase)
-
-if __name__ == '__main__':
-    unittest.main()
-

+ 8 - 90
darc/repository.py

@@ -41,10 +41,14 @@ class Repository(object):
 
     def __init__(self, path, create=False):
         self.io = None
+        self.lock_fd = None
         if create:
             self.create(path)
         self.open(path)
 
+    def __del__(self):
+        self.close()
+
     def create(self, path):
         """Create a new empty repository at `path`
         """
@@ -81,8 +85,10 @@ class Repository(object):
         self.rollback()
 
     def close(self):
-        self.rollback()
-        self.lock_fd.close()
+        if self.lock_fd:
+            self.rollback()
+            self.lock_fd.close()
+            self.lock_fd = None
 
     def commit(self, rollback=True):
         """Commit transaction
@@ -413,91 +419,3 @@ class LoggedIO(object):
             os.fsync(self._write_fd)
             self._write_fd.close()
             self._write_fd = None
-
-
-class RepositoryTestCase(unittest.TestCase):
-
-    def open(self, create=False):
-        return Repository(os.path.join(self.tmppath, 'repository'), create=create)
-
-    def setUp(self):
-        self.tmppath = tempfile.mkdtemp()
-        self.repository = self.open(create=True)
-
-    def tearDown(self):
-        self.repository.close()
-        shutil.rmtree(self.tmppath)
-
-    def test1(self):
-        for x in range(100):
-            self.repository.put(('%-32d' % x).encode('ascii'), b'SOMEDATA')
-        key50 = ('%-32d' % 50).encode('ascii')
-        self.assertEqual(self.repository.get(key50), b'SOMEDATA')
-        self.repository.delete(key50)
-        self.assertRaises(Repository.DoesNotExist, lambda: self.repository.get(key50))
-        self.repository.commit()
-        self.repository.close()
-        repository2 = self.open()
-        self.assertRaises(Repository.DoesNotExist, lambda: repository2.get(key50))
-        for x in range(100):
-            if x == 50:
-                continue
-            self.assertEqual(repository2.get(('%-32d' % x).encode('ascii')), b'SOMEDATA')
-        repository2.close()
-
-    def test2(self):
-        """Test multiple sequential transactions
-        """
-        self.repository.put(b'00000000000000000000000000000000', b'foo')
-        self.repository.put(b'00000000000000000000000000000001', b'foo')
-        self.repository.commit()
-        self.repository.delete(b'00000000000000000000000000000000')
-        self.repository.put(b'00000000000000000000000000000001', b'bar')
-        self.repository.commit()
-        self.assertEqual(self.repository.get(b'00000000000000000000000000000001'), b'bar')
-
-    def test_consistency(self):
-        """Test cache consistency
-        """
-        self.repository.put(b'00000000000000000000000000000000', b'foo')
-        self.assertEqual(self.repository.get(b'00000000000000000000000000000000'), b'foo')
-        self.repository.put(b'00000000000000000000000000000000', b'foo2')
-        self.assertEqual(self.repository.get(b'00000000000000000000000000000000'), b'foo2')
-        self.repository.put(b'00000000000000000000000000000000', b'bar')
-        self.assertEqual(self.repository.get(b'00000000000000000000000000000000'), b'bar')
-        self.repository.delete(b'00000000000000000000000000000000')
-        self.assertRaises(Repository.DoesNotExist, lambda: self.repository.get(b'00000000000000000000000000000000'))
-
-    def test_consistency2(self):
-        """Test cache consistency2
-        """
-        self.repository.put(b'00000000000000000000000000000000', b'foo')
-        self.assertEqual(self.repository.get(b'00000000000000000000000000000000'), b'foo')
-        self.repository.commit()
-        self.repository.put(b'00000000000000000000000000000000', b'foo2')
-        self.assertEqual(self.repository.get(b'00000000000000000000000000000000'), b'foo2')
-        self.repository.rollback()
-        self.assertEqual(self.repository.get(b'00000000000000000000000000000000'), b'foo')
-
-    def test_single_kind_transactions(self):
-        # put
-        self.repository.put(b'00000000000000000000000000000000', b'foo')
-        self.repository.commit()
-        self.repository.close()
-        # replace
-        self.repository = self.open()
-        self.repository.put(b'00000000000000000000000000000000', b'bar')
-        self.repository.commit()
-        self.repository.close()
-        # delete
-        self.repository = self.open()
-        self.repository.delete(b'00000000000000000000000000000000')
-        self.repository.commit()
-
-
-
-def suite():
-    return unittest.TestLoader().loadTestsFromTestCase(RepositoryTestCase)
-
-if __name__ == '__main__':
-    unittest.main()

+ 35 - 0
darc/testsuite/__init__.py

@@ -0,0 +1,35 @@
+import unittest
+
+
+class DarcTestCase(unittest.TestCase):
+    """
+    """
+    assert_equal = unittest.TestCase.assertEqual
+    assert_not_equal = unittest.TestCase.assertNotEqual
+    assert_raises = unittest.TestCase.assertRaises
+
+
+def get_tests(suite):
+    """Generates a sequence of tests from a test suite
+    """
+    for item in suite:
+        try:
+            # TODO: This could be "yield from..." with Python 3.3+ 
+            for i in get_tests(item):
+                yield i
+        except TypeError:
+            yield item
+
+
+class TestLoader(unittest.TestLoader):
+    """A customzied test loader that properly detects and filters our test cases
+    """
+    def loadTestsFromName(self, pattern, module=None):
+        suite = self.discover('darc.testsuite', '*.py')
+        tests = unittest.TestSuite()
+        for test in get_tests(suite):
+            if pattern.lower() in test.id().lower():
+                tests.addTest(test)
+        return tests
+
+

+ 18 - 65
darc/test.py → darc/testsuite/archiver.py

@@ -1,26 +1,23 @@
-import doctest
 import filecmp
 import os
-from io import BytesIO, StringIO
+from io import StringIO
 import stat
 import sys
 import shutil
 import tempfile
-import unittest
 import xattr
 
-from . import helpers, lrucache, crypto
-from .chunker import chunkify, buzhash, buzhash_update
-from .archiver import Archiver
-from .key import suite as KeySuite
-from .repository import Repository, suite as RepositorySuite
-from .remote import Repository, suite as RemoteRepositorySuite
+from darc.archiver import Archiver
+from darc.repository import Repository
+from darc.testsuite import DarcTestCase
 
 has_mtime_ns = sys.version >= '3.3'
 utime_supports_fd = os.utime in getattr(os, 'supports_fd', {})
 
+src_dir = os.path.join(os.getcwd(), os.path.dirname(__file__), '..', '..')
 
-class Test(unittest.TestCase):
+
+class ArchiverTestCase(DarcTestCase):
 
     prefix = ''
 
@@ -55,13 +52,12 @@ class Test(unittest.TestCase):
             sys.stdout, sys.stderr = stdout, stderr
             if ret != exit_code:
                 print(output.getvalue())
-            self.assertEqual(exit_code, ret)
+            self.assert_equal(exit_code, ret)
             return output.getvalue()
         finally:
             sys.stdout, sys.stderr = stdout, stderr
 
     def create_src_archive(self, name):
-        src_dir = os.path.join(os.getcwd(), os.path.dirname(__file__), '..')
         self.darc('init', self.repository_location)
         self.darc('create', self.repository_location + '::' + name, src_dir)
 
@@ -80,9 +76,9 @@ class Test(unittest.TestCase):
 
     def diff_dirs(self, dir1, dir2):
         diff = filecmp.dircmp(dir1, dir2)
-        self.assertEqual(diff.left_only, [])
-        self.assertEqual(diff.right_only, [])
-        self.assertEqual(diff.diff_files, [])
+        self.assert_equal(diff.left_only, [])
+        self.assert_equal(diff.right_only, [])
+        self.assert_equal(diff.diff_files, [])
         for filename in diff.common:
             path1 = os.path.join(dir1, filename)
             path2 = os.path.join(dir2, filename)
@@ -99,7 +95,7 @@ class Test(unittest.TestCase):
                 d2[-1] = round(d2[-1], 4)
             d1.append(self.get_xattrs(path1))
             d2.append(self.get_xattrs(path2))
-            self.assertEqual(d1, d2)
+            self.assert_equal(d1, d2)
 
     def test_basic_functionality(self):
         # File
@@ -127,8 +123,8 @@ class Test(unittest.TestCase):
         self.darc('create', self.repository_location + '::test', 'input')
         self.darc('create', self.repository_location + '::test.2', 'input')
         self.darc('extract', self.repository_location + '::test', 'output')
-        self.assertEqual(len(self.darc('list', self.repository_location).splitlines()), 2)
-        self.assertEqual(len(self.darc('list', self.repository_location + '::test').splitlines()), 9)
+        self.assert_equal(len(self.darc('list', self.repository_location).splitlines()), 2)
+        self.assert_equal(len(self.darc('list', self.repository_location + '::test').splitlines()), 9)
         self.diff_dirs('input', 'output/input')
         info_output = self.darc('info', self.repository_location + '::test')
         shutil.rmtree(self.cache_path)
@@ -145,9 +141,9 @@ class Test(unittest.TestCase):
         self.create_regual_file('file4', size=1024 * 80)
         self.darc('create', '--exclude=input/file4', self.repository_location + '::test', 'input')
         self.darc('extract', '--include=file1', self.repository_location + '::test', 'output')
-        self.assertEqual(sorted(os.listdir('output/input')), ['file1'])
+        self.assert_equal(sorted(os.listdir('output/input')), ['file1'])
         self.darc('extract', '--exclude=file2', self.repository_location + '::test', 'output')
-        self.assertEqual(sorted(os.listdir('output/input')), ['file1', 'file3'])
+        self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file3'])
 
     def test_overwrite(self):
         self.create_regual_file('file1', size=1024 * 80)
@@ -179,7 +175,7 @@ class Test(unittest.TestCase):
         self.darc('delete', self.repository_location + '::test.2')
         # Make sure all data except the manifest has been deleted
         repository = Repository(self.repository_path)
-        self.assertEqual(repository._len(), 1)
+        self.assert_equal(repository._len(), 1)
 
     def test_corrupted_repository(self):
         self.create_src_archive('test')
@@ -192,7 +188,6 @@ class Test(unittest.TestCase):
         self.darc('verify', self.repository_location + '::test', exit_code=1)
 
     def test_prune_repository(self):
-        src_dir = os.path.join(os.getcwd(), os.path.dirname(__file__))
         self.darc('init', self.repository_location)
         self.darc('create', self.repository_location + '::test1', src_dir)
         self.darc('create', self.repository_location + '::test2', src_dir)
@@ -202,47 +197,5 @@ class Test(unittest.TestCase):
         assert 'test2' in output
 
 
-class ChunkTest(unittest.TestCase):
-
-    def test_chunkify(self):
-        data = b'0' * 1024 * 1024 * 15 + b'Y'
-        parts = [bytes(c) for c in chunkify(BytesIO(data), 2, 0x3, 2, 0)]
-        self.assertEqual(len(parts), 2)
-        self.assertEqual(b''.join(parts), data)
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b''), 2, 0x3, 2, 0)], [])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 0)], [b'fooba', b'rboobaz', b'fooba', b'rboobaz', b'fooba', b'rboobaz'])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 1)], [b'fo', b'obarb', b'oob', b'azf', b'oobarb', b'oob', b'azf', b'oobarb', b'oobaz'])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 2)], [b'foob', b'ar', b'boobazfoob', b'ar', b'boobazfoob', b'ar', b'boobaz'])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 0)], [b'foobarboobaz' * 3])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 1)], [b'foobar', b'boo', b'bazfo', b'obar', b'boo', b'bazfo', b'obar', b'boobaz'])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 2)], [b'foo', b'barboobaz', b'foo', b'barboobaz', b'foo', b'barboobaz'])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 0)], [b'foobarboobaz' * 3])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 1)], [b'foobar', b'boobazfo', b'obar', b'boobazfo', b'obar', b'boobaz'])
-        self.assertEqual([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 2)], [b'foob', b'arboobaz', b'foob', b'arboobaz', b'foob', b'arboobaz'])
-
-    def test_buzhash(self):
-        self.assertEqual(buzhash(b'abcdefghijklmnop', 0), 3795437769)
-        self.assertEqual(buzhash(b'abcdefghijklmnop', 1), 3795400502)
-        self.assertEqual(buzhash(b'abcdefghijklmnop', 1), buzhash_update(buzhash(b'Xabcdefghijklmno', 1), ord('X'), ord('p'), 16, 1))
-
-
-class RemoteTest(Test):
+class RemoteArchiverTestCase(ArchiverTestCase):
     prefix = 'localhost:'
-
-
-def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(ChunkTest))
-    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(Test))
-    if not '--no-remote' in sys.argv:
-        suite.addTest(unittest.TestLoader().loadTestsFromTestCase(RemoteTest))
-        suite.addTest(RemoteRepositorySuite())
-    suite.addTest(KeySuite())
-    suite.addTest(RepositorySuite())
-    suite.addTest(doctest.DocTestSuite(helpers))
-    suite.addTest(lrucache.suite())
-    suite.addTest(crypto.suite())
-    return suite
-
-if __name__ == '__main__':
-    unittest.TextTestRunner(verbosity=2).run(suite())

+ 27 - 0
darc/testsuite/chunker.py

@@ -0,0 +1,27 @@
+from darc.chunker import chunkify, buzhash, buzhash_update
+from darc.testsuite import DarcTestCase
+from io import BytesIO
+
+
+class ChunkerTestCase(DarcTestCase):
+
+    def test_chunkify(self):
+        data = b'0' * 1024 * 1024 * 15 + b'Y'
+        parts = [bytes(c) for c in chunkify(BytesIO(data), 2, 0x3, 2, 0)]
+        self.assert_equal(len(parts), 2)
+        self.assert_equal(b''.join(parts), data)
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b''), 2, 0x3, 2, 0)], [])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 0)], [b'fooba', b'rboobaz', b'fooba', b'rboobaz', b'fooba', b'rboobaz'])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 1)], [b'fo', b'obarb', b'oob', b'azf', b'oobarb', b'oob', b'azf', b'oobarb', b'oobaz'])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 2, 0x3, 2, 2)], [b'foob', b'ar', b'boobazfoob', b'ar', b'boobazfoob', b'ar', b'boobaz'])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 0)], [b'foobarboobaz' * 3])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 1)], [b'foobar', b'boo', b'bazfo', b'obar', b'boo', b'bazfo', b'obar', b'boobaz'])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 3, 2)], [b'foo', b'barboobaz', b'foo', b'barboobaz', b'foo', b'barboobaz'])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 0)], [b'foobarboobaz' * 3])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 1)], [b'foobar', b'boobazfo', b'obar', b'boobazfo', b'obar', b'boobaz'])
+        self.assert_equal([bytes(c) for c in chunkify(BytesIO(b'foobarboobaz' * 3), 3, 0x3, 4, 2)], [b'foob', b'arboobaz', b'foob', b'arboobaz', b'foob', b'arboobaz'])
+
+    def test_buzhash(self):
+        self.assert_equal(buzhash(b'abcdefghijklmnop', 0), 3795437769)
+        self.assert_equal(buzhash(b'abcdefghijklmnop', 1), 3795400502)
+        self.assert_equal(buzhash(b'abcdefghijklmnop', 1), buzhash_update(buzhash(b'Xabcdefghijklmno', 1), ord('X'), ord('p'), 16, 1))

+ 40 - 0
darc/testsuite/crypto.py

@@ -0,0 +1,40 @@
+from binascii import hexlify
+from darc.testsuite import DarcTestCase
+from darc.crypto import AES, bytes_to_long, bytes_to_int, long_to_bytes, pbkdf2_sha256, get_random_bytes
+
+
+class CryptoTestCase(DarcTestCase):
+
+    def test_bytes_to_int(self):
+        self.assert_equal(bytes_to_int(b'\0\0\0\1'), 1)
+
+    def test_bytes_to_long(self):
+        self.assert_equal(bytes_to_long(b'\0\0\0\0\0\0\0\1'), 1)
+        self.assert_equal(long_to_bytes(1), b'\0\0\0\0\0\0\0\1')
+
+    def test_pbkdf2_sha256(self):
+        self.assert_equal(hexlify(pbkdf2_sha256(b'password', b'salt', 1, 32)),
+                         b'120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b')
+        self.assert_equal(hexlify(pbkdf2_sha256(b'password', b'salt', 2, 32)),
+                         b'ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43')
+        self.assert_equal(hexlify(pbkdf2_sha256(b'password', b'salt', 4096, 32)),
+                         b'c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a')
+
+    def test_get_random_bytes(self):
+        bytes = get_random_bytes(10)
+        bytes2 = get_random_bytes(10)
+        self.assert_equal(len(bytes), 10)
+        self.assert_equal(len(bytes2), 10)
+        self.assert_not_equal(bytes, bytes2)
+
+    def test_aes(self):
+        key = b'X' * 32
+        data = b'foo' * 10
+        aes = AES(key)
+        self.assert_equal(bytes_to_long(aes.iv.raw, 8), 0)
+        cdata = aes.encrypt(data)
+        self.assert_equal(hexlify(cdata), b'c6efb702de12498f34a2c2bbc8149e759996d08bf6dc5c610aefc0c3a466')
+        self.assert_equal(bytes_to_long(aes.iv.raw, 8), 2)
+        self.assert_not_equal(data, aes.decrypt(cdata))
+        aes.reset(iv=b'\0' * 16)
+        self.assert_equal(data, aes.decrypt(cdata))

+ 48 - 0
darc/testsuite/helpers.py

@@ -0,0 +1,48 @@
+from datetime import datetime
+from darc.helpers import Location, format_timedelta, IncludePattern
+from darc.testsuite import DarcTestCase
+
+
+class LocationTestCase(DarcTestCase):
+
+    def test(self):
+        self.assert_equal(
+            repr(Location('ssh://user@host:1234/some/path::archive')),
+            "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')"
+        )
+        self.assert_equal(
+            repr(Location('file:///some/path::archive')),
+            "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')"
+        )
+        self.assert_equal(
+            repr(Location('user@host:/some/path::archive')),
+            "Location(proto='ssh', user='user', host='host', port=22, path='/some/path', archive='archive')"
+        )
+        self.assert_equal(
+            repr(Location('/some/path::archive')),
+            "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')"
+        )
+
+
+class FormatTimedeltaTestCase(DarcTestCase):
+
+    def test(self):
+        t0 = datetime(2001, 1, 1, 10, 20, 3, 0)
+        t1 = datetime(2001, 1, 1, 12, 20, 4, 100000)
+        self.assert_equal(
+            format_timedelta(t1 - t0),
+            '2 hours 1.10 seconds'
+        )
+
+
+class PatternTestCase(DarcTestCase):
+
+    def test(self):
+        py = IncludePattern('*.py')
+        foo = IncludePattern('/foo')
+        self.assert_equal(py.match('/foo/foo.py'), True)
+        self.assert_equal(py.match('/bar/foo.java'), False)
+        self.assert_equal(foo.match('/foo/foo.py'), True)
+        self.assert_equal(foo.match('/bar/foo.java'), False)
+        self.assert_equal(foo.match('/foobar/foo.py'), False)
+        self.assert_equal(foo.match('/foo'), True)

+ 99 - 0
darc/testsuite/key.py

@@ -0,0 +1,99 @@
+import os
+import re
+import shutil
+import tempfile
+from binascii import hexlify, unhexlify
+from darc.crypto import bytes_to_long
+from darc.testsuite import DarcTestCase
+from darc.key import PlaintextKey, PassphraseKey, KeyfileKey
+from darc.helpers import Location
+
+
+class KeyTestCase(DarcTestCase):
+
+    class MockArgs(object):
+        repository = Location(tempfile.mkstemp()[1])
+
+    keyfile2_key_file = """
+        DARC KEY 0000000000000000000000000000000000000000000000000000000000000000
+        hqppdGVyYXRpb25zzgABhqCkaGFzaNoAIMyonNI+7Cjv0qHi0AOBM6bLGxACJhfgzVD2oq
+        bIS9SFqWFsZ29yaXRobaZzaGEyNTakc2FsdNoAINNK5qqJc1JWSUjACwFEWGTdM7Nd0a5l
+        1uBGPEb+9XM9p3ZlcnNpb24BpGRhdGHaANAYDT5yfPpU099oBJwMomsxouKyx/OG4QIXK2
+        hQCG2L2L/9PUu4WIuKvGrsXoP7syemujNfcZws5jLp2UPva4PkQhQsrF1RYDEMLh2eF9Ol
+        rwtkThq1tnh7KjWMG9Ijt7/aoQtq0zDYP/xaFF8XXSJxiyP5zjH5+spB6RL0oQHvbsliSh
+        /cXJq7jrqmrJ1phd6dg4SHAM/i+hubadZoS6m25OQzYAW09wZD/phG8OVa698Z5ed3HTaT
+        SmrtgJL3EoOKgUI9d6BLE4dJdBqntifo""".strip()
+
+    keyfile2_cdata = unhexlify(re.sub('\W', '', """
+        0055f161493fcfc16276e8c31493c4641e1eb19a79d0326fad0291e5a9c98e5933
+        00000000000003e8d21eaf9b86c297a8cd56432e1915bb
+        """))
+    keyfile2_id = unhexlify('c3fbf14bc001ebcc3cd86e696c13482ed071740927cd7cbe1b01b4bfcee49314')
+
+    def setUp(self):
+        self.tmppath = tempfile.mkdtemp()
+        os.environ['DARC_KEYS_DIR'] = self.tmppath
+
+    def tearDown(self):
+        shutil.rmtree(self.tmppath)
+
+    class MockRepository(object):
+        class _Location(object):
+            orig = '/some/place'
+
+        _location = _Location()
+        id = bytes(32)
+
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp()
+        os.environ['DARC_KEYS_DIR'] = self.tmpdir
+
+    def tearDown(self):
+        shutil.rmtree(self.tmpdir)
+
+    def test_plaintext(self):
+        key = PlaintextKey.create(None, None)
+        data = b'foo'
+        self.assert_equal(hexlify(key.id_hash(data)), b'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae')
+        self.assert_equal(data, key.decrypt(key.id_hash(data), key.encrypt(data)))
+
+    def test_keyfile(self):
+        os.environ['DARC_PASSPHRASE'] = 'test'
+        key = KeyfileKey.create(self.MockRepository(), self.MockArgs())
+        self.assert_equal(bytes_to_long(key.enc_cipher.iv, 8), 0)
+        manifest = key.encrypt(b'')
+        iv = key.extract_iv(manifest)
+        key2 = KeyfileKey.detect(self.MockRepository(), manifest)
+        self.assert_equal(bytes_to_long(key2.enc_cipher.iv, 8), iv + 1000)
+        # Key data sanity check
+        self.assert_equal(len(set([key2.id_key, key2.enc_key, key2.enc_hmac_key])), 3)
+        self.assert_equal(key2.chunk_seed == 0, False)
+        data = b'foo'
+        self.assert_equal(data, key2.decrypt(key.id_hash(data), key.encrypt(data)))
+
+    def test_keyfile2(self):
+        with open(os.path.join(os.environ['DARC_KEYS_DIR'], 'keyfile'), 'w') as fd:
+            fd.write(self.keyfile2_key_file)
+        os.environ['DARC_PASSPHRASE'] = 'passphrase'
+        key = KeyfileKey.detect(self.MockRepository(), self.keyfile2_cdata)
+        self.assert_equal(key.decrypt(self.keyfile2_id, self.keyfile2_cdata), b'payload')
+
+    def test_passphrase(self):
+        os.environ['DARC_PASSPHRASE'] = 'test'
+        key = PassphraseKey.create(self.MockRepository(), None)
+        self.assert_equal(bytes_to_long(key.enc_cipher.iv, 8), 0)
+        self.assert_equal(hexlify(key.id_key), b'793b0717f9d8fb01c751a487e9b827897ceea62409870600013fbc6b4d8d7ca6')
+        self.assert_equal(hexlify(key.enc_hmac_key), b'b885a05d329a086627412a6142aaeb9f6c54ab7950f996dd65587251f6bc0901')
+        self.assert_equal(hexlify(key.enc_key), b'2ff3654c6daf7381dbbe718d2b20b4f1ea1e34caa6cc65f6bb3ac376b93fed2a')
+        self.assert_equal(key.chunk_seed, -775740477)
+        manifest = key.encrypt(b'')
+        iv = key.extract_iv(manifest)
+        key2 = PassphraseKey.detect(self.MockRepository(), manifest)
+        self.assert_equal(bytes_to_long(key2.enc_cipher.iv, 8), iv + 1000)
+        self.assert_equal(key.id_key, key2.id_key)
+        self.assert_equal(key.enc_hmac_key, key2.enc_hmac_key)
+        self.assert_equal(key.enc_key, key2.enc_key)
+        self.assert_equal(key.chunk_seed, key2.chunk_seed)
+        data = b'foo'
+        self.assert_equal(hexlify(key.id_hash(data)), b'818217cf07d37efad3860766dcdf1d21e401650fed2d76ed1d797d3aae925990')
+        self.assert_equal(data, key2.decrypt(key2.id_hash(data), key.encrypt(data)))

+ 40 - 0
darc/testsuite/lrucache.py

@@ -0,0 +1,40 @@
+from darc.lrucache import LRUCache
+from darc.testsuite import DarcTestCase
+
+
+class LRUCacheTestCase(DarcTestCase):
+
+    def test(self):
+        c = LRUCache(2)
+        self.assert_equal(len(c), 0)
+        for i, x in enumerate('abc'):
+            c[x] = i
+        self.assert_equal(len(c), 2)
+        self.assert_equal(set(c), set(['b', 'c']))
+        self.assert_equal(set(c.items()), set([('b', 1), ('c', 2)]))
+        self.assert_equal(False, 'a' in c)
+        self.assert_equal(True, 'b' in c)
+        self.assert_raises(KeyError, lambda: c['a'])
+        self.assert_equal(c['b'], 1)
+        self.assert_equal(c['c'], 2)
+        c['d'] = 3
+        self.assert_equal(len(c), 2)
+        self.assert_equal(c['c'], 2)
+        self.assert_equal(c['d'], 3)
+        c['c'] = 22
+        c['e'] = 4
+        self.assert_equal(len(c), 2)
+        self.assert_raises(KeyError, lambda: c['d'])
+        self.assert_equal(c['c'], 22)
+        self.assert_equal(c['e'], 4)
+        del c['c']
+        self.assert_equal(len(c), 1)
+        self.assert_raises(KeyError, lambda: c['c'])
+        self.assert_equal(c['e'], 4)
+
+    def test_pop(self):
+        c = LRUCache(2)
+        c[1] = 1
+        c[2] = 2
+        c.pop(1)
+        c[3] = 3

+ 93 - 0
darc/testsuite/repository.py

@@ -0,0 +1,93 @@
+import os
+import shutil
+import tempfile
+from darc.helpers import Location
+from darc.remote import RemoteRepository
+from darc.repository import Repository
+from darc.testsuite import DarcTestCase
+
+
+class RepositoryTestCase(DarcTestCase):
+
+    def open(self, create=False):
+        return Repository(os.path.join(self.tmppath, 'repository'), create=create)
+
+    def setUp(self):
+        self.tmppath = tempfile.mkdtemp()
+        self.repository = self.open(create=True)
+
+    def tearDown(self):
+        self.repository.close()
+        shutil.rmtree(self.tmppath)
+
+    def test1(self):
+        for x in range(100):
+            self.repository.put(('%-32d' % x).encode('ascii'), b'SOMEDATA')
+        key50 = ('%-32d' % 50).encode('ascii')
+        self.assert_equal(self.repository.get(key50), b'SOMEDATA')
+        self.repository.delete(key50)
+        self.assert_raises(Repository.DoesNotExist, lambda: self.repository.get(key50))
+        self.repository.commit()
+        self.repository.close()
+        repository2 = self.open()
+        self.assert_raises(Repository.DoesNotExist, lambda: repository2.get(key50))
+        for x in range(100):
+            if x == 50:
+                continue
+            self.assert_equal(repository2.get(('%-32d' % x).encode('ascii')), b'SOMEDATA')
+        repository2.close()
+
+    def test2(self):
+        """Test multiple sequential transactions
+        """
+        self.repository.put(b'00000000000000000000000000000000', b'foo')
+        self.repository.put(b'00000000000000000000000000000001', b'foo')
+        self.repository.commit()
+        self.repository.delete(b'00000000000000000000000000000000')
+        self.repository.put(b'00000000000000000000000000000001', b'bar')
+        self.repository.commit()
+        self.assert_equal(self.repository.get(b'00000000000000000000000000000001'), b'bar')
+
+    def test_consistency(self):
+        """Test cache consistency
+        """
+        self.repository.put(b'00000000000000000000000000000000', b'foo')
+        self.assert_equal(self.repository.get(b'00000000000000000000000000000000'), b'foo')
+        self.repository.put(b'00000000000000000000000000000000', b'foo2')
+        self.assert_equal(self.repository.get(b'00000000000000000000000000000000'), b'foo2')
+        self.repository.put(b'00000000000000000000000000000000', b'bar')
+        self.assert_equal(self.repository.get(b'00000000000000000000000000000000'), b'bar')
+        self.repository.delete(b'00000000000000000000000000000000')
+        self.assert_raises(Repository.DoesNotExist, lambda: self.repository.get(b'00000000000000000000000000000000'))
+
+    def test_consistency2(self):
+        """Test cache consistency2
+        """
+        self.repository.put(b'00000000000000000000000000000000', b'foo')
+        self.assert_equal(self.repository.get(b'00000000000000000000000000000000'), b'foo')
+        self.repository.commit()
+        self.repository.put(b'00000000000000000000000000000000', b'foo2')
+        self.assert_equal(self.repository.get(b'00000000000000000000000000000000'), b'foo2')
+        self.repository.rollback()
+        self.assert_equal(self.repository.get(b'00000000000000000000000000000000'), b'foo')
+
+    def test_single_kind_transactions(self):
+        # put
+        self.repository.put(b'00000000000000000000000000000000', b'foo')
+        self.repository.commit()
+        self.repository.close()
+        # replace
+        self.repository = self.open()
+        self.repository.put(b'00000000000000000000000000000000', b'bar')
+        self.repository.commit()
+        self.repository.close()
+        # delete
+        self.repository = self.open()
+        self.repository.delete(b'00000000000000000000000000000000')
+        self.repository.commit()
+
+
+class RemoteRepositoryTestCase(RepositoryTestCase):
+
+    def open(self, create=False):
+        return RemoteRepository(Location('localhost:' + os.path.join(self.tmppath, 'repository')), create=create)

+ 10 - 0
darc/testsuite/run.py

@@ -0,0 +1,10 @@
+import unittest
+from darc.testsuite import TestLoader
+
+
+def main():
+    unittest.main(testLoader=TestLoader(), defaultTest='')
+
+
+if __name__ == '__main__':
+    main()

+ 1 - 1
setup.py

@@ -62,7 +62,7 @@ setup(
         'Topic :: Security :: Cryptography',
         'Topic :: System :: Archiving :: Backup',
     ],
-    packages=['darc'],
+    packages=['darc', 'darc.testsuite'],
     scripts=['scripts/darc'],
     cmdclass={'build_ext': build_ext, 'sdist': Sdist},
     ext_modules=[

+ 1 - 1
tox.ini

@@ -2,4 +2,4 @@
 envlist = py32, py33
 
 [testenv]
-commands = fakeroot {envpython} -m darc.test
+commands = fakeroot {envpython} -m darc.testsuite.run