Rename test files to test_*.py
authorJoel Rosdahl <joel@rosdahl.net>
Sat, 26 May 2007 15:54:05 +0000 (17:54 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sat, 26 May 2007 15:54:05 +0000 (17:54 +0200)
src/test/alltests.py [deleted file]
src/test/dagtests.py [deleted file]
src/test/searchtests.py [deleted file]
src/test/shelftests.py [deleted file]
src/test/test_all.py [new file with mode: 0755]
src/test/test_dag.py [new file with mode: 0755]
src/test/test_iodict.py [changed mode: 0644->0755]
src/test/test_searching.py [new file with mode: 0755]
src/test/test_shelf.py [new file with mode: 0755]

diff --git a/src/test/alltests.py b/src/test/alltests.py
deleted file mode 100755 (executable)
index 40d55b1..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import os
-import sys
-import unittest
-
-tests = ["dagtests", "searchtests", "shelftests", "test_iodict"]
-
-cwd = os.getcwd()
-libdir = unicode(os.path.realpath(
-    os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
-os.chdir(libdir)
-sys.path.insert(0, libdir)
-
-def suite():
-    alltests = unittest.TestSuite()
-    for module in [__import__(x) for x in tests]:
-        alltests.addTest(unittest.findTestCases(module))
-    return alltests
-
-if __name__ == "__main__":
-    unittest.main(defaultTest="suite")
diff --git a/src/test/dagtests.py b/src/test/dagtests.py
deleted file mode 100755 (executable)
index e92730e..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-#! /usr/bin/env python
-
-import os
-import sys
-import unittest
-
-if __name__ == "__main__":
-    cwd = os.getcwd()
-    libdir = unicode(os.path.realpath(
-        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
-    os.chdir(libdir)
-    sys.path.insert(0, libdir)
-from kofoto.dag import *
-
-PICDIR = unicode(os.path.realpath(
-    os.path.join("..", "reference_pictures", "working")))
-
-class TestDAG(unittest.TestCase):
-    def setUp(self):
-        self.dag = DAG()
-        for x in [1, 2, 3, 4, 5, 6]:
-            self.dag.add(x)
-        for x, y in [(1, 3), (1, 4), (2, 3), (3, 5), (4, 5), (5, 6)]:
-            self.dag.connect(x, y)
-
-    def tearDown(self):
-        del self.dag
-
-    def test_iter(self):
-        assert sorted(list(self.dag)) == [1, 2, 3, 4, 5, 6]
-
-    def test_contains(self):
-        assert 3 in self.dag
-
-    def test_negative_contains(self):
-        assert not 4711 in self.dag
-
-    def test_redundant_add(self):
-        self.dag.add(1)
-        assert sorted(list(self.dag)) == [1, 2, 3, 4, 5, 6]
-
-    def test_redundant_connect(self):
-        assert self.dag.reachable(1, 3)
-        self.dag.connect(1, 3)
-        assert self.dag.reachable(1, 3)
-
-    def test_connect_loop(self):
-        try:
-            self.dag.connect(6, 1)
-        except LoopError:
-            pass
-        else:
-            assert False
-
-    def test_connected(self):
-        assert self.dag.connected(1, 3)
-        assert self.dag.connected(1, 4)
-        assert not self.dag.connected(1, 2)
-        assert not self.dag.connected(1, 5)
-
-    def test_disconnect(self):
-        assert self.dag.reachable(1, 3)
-        self.dag.disconnect(1, 3)
-        assert not self.dag.reachable(1, 3)
-
-    def test_idempotent_disconnect(self):
-        self.dag.disconnect(3, 1)
-        assert self.dag.reachable(1, 3)
-        assert not self.dag.reachable(3, 1)
-
-    def test_getAncestors(self):
-        for x, y in [(1, [1]),
-                     (2, [2]),
-                     (3, [1, 2, 3]),
-                     (4, [1, 4]),
-                     (5, [1, 2, 3, 4, 5]),
-                     (6, [1, 2, 3, 4, 5, 6])]:
-            assert sorted(list(self.dag.getAncestors(x))) == sorted(y)
-
-    def test_getChildren(self):
-        for x, y in [(1, [3, 4]),
-                     (2, [3]),
-                     (3, [5]),
-                     (4, [5]),
-                     (5, [6]),
-                     (6, [])]:
-            assert sorted(list(self.dag.getChildren(x))) == sorted(y)
-
-    def test_getDescendants(self):
-        for x, y in [(1, [1, 3, 4, 5, 6]),
-                     (2, [2, 3, 5, 6]),
-                     (3, [3, 5, 6]),
-                     (4, [4, 5, 6]),
-                     (5, [5, 6]),
-                     (6, [6])]:
-            assert sorted(list(self.dag.getDescendants(x))) == sorted(y)
-
-    def test_getParents(self):
-        for x, y in [(1, []),
-                     (2, []),
-                     (3, [1, 2]),
-                     (4, [1]),
-                     (5, [3, 4]),
-                     (6, [5])]:
-            assert sorted(list(self.dag.getParents(x))) == sorted(y)
-
-    def test_getRoots(self):
-        assert sorted(list(self.dag.getRoots())) == [1, 2]
-
-    def test_reachable(self):
-        assert self.dag.reachable(1, 3)
-        assert self.dag.reachable(1, 6)
-        assert not self.dag.reachable(1, 2)
-        assert not self.dag.reachable(1, 4711)
-
-    def test_remove(self):
-        assert self.dag.reachable(1, 6)
-        self.dag.remove(5)
-        assert not self.dag.reachable(1, 6)
-
-    def test_negative_remove(self):
-        try:
-            self.dag.remove(4711)
-        except:
-            pass
-        else:
-            assert False
-
-######################################################################
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/src/test/searchtests.py b/src/test/searchtests.py
deleted file mode 100755 (executable)
index 6c0f29a..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-#! /usr/bin/env python
-
-import os
-import sys
-import unittest
-
-if __name__ == "__main__":
-    cwd = os.getcwd()
-    libdir = unicode(os.path.realpath(
-        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
-    os.chdir(libdir)
-    sys.path.insert(0, libdir)
-from kofoto.shelf import *
-from kofoto.search import *
-
-PICDIR = unicode(os.path.realpath(
-    os.path.join("..", "reference_pictures", "working")))
-
-######################################################################
-
-db = "shelf.tmp"
-codeset = "latin1"
-
-def removeTmpDb():
-    for x in [db, db + "-journal"]:
-        if os.path.exists(x):
-            os.unlink(x)
-
-from shelftests import TestShelfFixture
-
-class TestSearch(TestShelfFixture):
-    def setUp(self):
-        TestShelfFixture.setUp(self)
-        self.images = list(self.shelf.getAllImages())
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_b = self.shelf.getCategoryByTag(u"b")
-        cat_c = self.shelf.getCategoryByTag(u"c")
-        cat_d = self.shelf.getCategoryByTag(u"d")
-        self.images[0].addCategory(cat_a)
-        self.images[0].addCategory(cat_b)
-        self.images[1].addCategory(cat_c)
-        self.images[0].setAttribute(u"foo", u"abc")
-        self.images[0].setAttribute(u"bar", u"17")
-        self.images[1].setAttribute(u"foo", u"xyz")
-        self.images[2].setAttribute(u"fie", u"fum")
-
-    def tearDown(self):
-        TestShelfFixture.tearDown(self)
-
-    def test_search(self):
-        tests = [
-            (u"a", [self.images[0], self.images[1]]),
-            (u"b", [self.images[0]]),
-            (u"c", [self.images[1]]),
-            (u"d", []),
-            (u"(((b)))", [self.images[0]]),
-            (u'@foo >= "b\'ar"', [self.images[1]]),
-            (u"a and b", [self.images[0]]),
-            (u'a and @foo = "abc"', [self.images[0]]),
-            (u'a and @foo = xyz', [self.images[1]]),
-            (u'@foo = "abc" and @bar = 17', [self.images[0]]),
-            (u'a and b and @bar = "17" and @foo = abc', [self.images[0]]),
-            (u"not a and c", []),
-            (u"not exactly a and c", [self.images[1]]),
-            (u"not (a and b) and a", [self.images[1]]),
-            (u"not a and not b and @fie=fum", [self.images[2]]),
-            (u"a and b or c", [self.images[0], self.images[1]]),
-            (u"b or c and d", [self.images[0]]),
-            (u"a or b or c or d", [self.images[0], self.images[1]]),
-            (ur' ((a and not b) or @gazonk != "hej \"ju\"") and c ', [self.images[1]]),
-            (u"/alpha and a", [self.images[0], self.images[1]]),
-            (u"/epsilon", []),
-            (u"/zeta", [self.images[0], self.images[1]]),
-            ]
-        parser = Parser(self.shelf)
-        for expression, expectedResult in tests:
-            parseTree = parser.parse(expression)
-            result = sorted(
-                self.shelf.search(parseTree), key=lambda x: x.getId())
-            assert result == expectedResult, (expression, expectedResult, result)
-
-    def test_parseErrors(self):
-        tests = [
-            (u"+", BadTokenError),
-            (u":a and +", BadTokenError),
-            (u'"', UnterminatedStringError),
-            (ur'"\"\\\"', UnterminatedStringError),
-            ]
-        parser = Parser(self.shelf)
-        for expression, expectedException in tests:
-            try:
-                parser.parse(expression)
-            except expectedException:
-                pass
-            except Exception, e:
-                assert False, (expression, expectedException, e)
-
-######################################################################
-
-if __name__ == "__main__":
-    removeTmpDb()
-    unittest.main()
diff --git a/src/test/shelftests.py b/src/test/shelftests.py
deleted file mode 100755 (executable)
index 4c77fa5..0000000
+++ /dev/null
@@ -1,1026 +0,0 @@
-#! /usr/bin/env python
-
-import gc
-import os
-import shutil
-import sys
-import threading
-import unittest
-
-if __name__ == "__main__":
-    cwd = os.getcwd()
-    libdir = unicode(os.path.realpath(
-        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
-    os.chdir(libdir)
-    sys.path.insert(0, libdir)
-from kofoto.shelf import \
-    Shelf, \
-    computeImageHash, \
-    makeValidTag, \
-    verifyValidAlbumTag, \
-    verifyValidCategoryTag
-from kofoto.albumtype import AlbumType
-from kofoto.imageversiontype import ImageVersionType
-from kofoto.shelfexceptions import \
-    AlbumDoesNotExistError, \
-    AlbumExistsError, \
-    BadAlbumTagError, \
-    BadCategoryTagError, \
-    CategoriesAlreadyConnectedError, \
-    CategoryDoesNotExistError, \
-    CategoryExistsError, \
-    CategoryLoopError, \
-    CategoryPresentError, \
-    FailedWritingError, \
-    ImageDoesNotExistError, \
-    ImageVersionDoesNotExistError, \
-    ImageVersionExistsError, \
-    ShelfLockedError, \
-    ShelfNotFoundError, \
-    UndeletableAlbumError, \
-    UndeletableAlbumError, \
-    UnsettableChildrenError, \
-    UnsupportedShelfError
-
-PICDIR = unicode(os.path.realpath(
-    os.path.join("..", "reference_pictures", "working")))
-
-######################################################################
-
-db = "shelf.tmp"
-
-def removeTmpDb():
-    for x in [db, db + "-journal"]:
-        if os.path.exists(x):
-            os.unlink(x)
-
-class LockerThread(threading.Thread):
-    def __init__(self, mContinue, ltContinue):
-        threading.Thread.__init__(self)
-        self.mContinue = mContinue
-        self.ltContinue = ltContinue
-
-    def run(self):
-        s = Shelf(db)
-        s.create()
-        s.begin()
-        self.mContinue.set()
-        self.ltContinue.wait()
-        s.rollback()
-
-######################################################################
-
-class TestPublicShelfFunctions(unittest.TestCase):
-    def test_computeImageHash(self):
-        s = computeImageHash(os.path.join(PICDIR, "arlaharen.png"))
-        assert s == "39a1266d2689f53d48b09a5e0ca0af1f"
-        try:
-            computeImageHash("nonexisting")
-        except IOError:
-            pass
-        else:
-            assert False, s
-
-    def test_verifyValidAlbumTag(self):
-        # Valid tags.
-        for x in ("foo", "1foo", "and", "exactly", "not", "or"):
-            try:
-                verifyValidAlbumTag(x)
-            except:
-                assert False, x
-        # Invalid tags.
-        for x in (None, 1, 1L, "1" "foo " "@foo"):
-            try:
-                verifyValidAlbumTag(x)
-            except BadAlbumTagError:
-                pass
-            else:
-                assert False, x
-
-    def test_verifyValidCategoryTag(self):
-        # Valid tags.
-        for x in ("foo", "1foo"):
-            try:
-                verifyValidCategoryTag(x)
-            except:
-                assert False, x
-        # Invalid tags.
-        for x in (None, 1, 1L, "1" "foo " "@foo",
-                  "and", "exactly", "not", "or"):
-            try:
-                verifyValidCategoryTag(x)
-            except BadCategoryTagError:
-                pass
-            else:
-                assert False, x
-
-    def test_makeValidTag(self):
-        for tag, validTag in [("", "_"),
-                              ("@", "_"),
-                              (" ", "_"),
-                              ("1", "1_"),
-                              ("@foo_", "foo_"),
-                              ("fo@o __", "fo@o__")]:
-            assert makeValidTag(tag) == validTag, (tag, validTag)
-
-class TestNegativeShelfOpens(unittest.TestCase):
-    def tearDown(self):
-        removeTmpDb()
-
-    def test_NonexistingShelf(self):
-        try:
-            s = Shelf(db)
-            s.begin()
-        except ShelfNotFoundError:
-            pass
-        else:
-            assert False
-        assert not os.path.exists(db)
-
-    def test_BadShelf(self):
-        file(db, "w") # Create empty file.
-        try:
-            s = Shelf(db)
-            s.begin()
-        except UnsupportedShelfError:
-            pass
-        else:
-            assert False
-
-    def test_LockedShelf(self):
-        mContinue = threading.Event()
-        ltContinue = threading.Event()
-        lt = LockerThread(mContinue, ltContinue)
-        lt.start()
-        mContinue.wait()
-        try:
-            try:
-                s = Shelf(db)
-                s.begin()
-            except ShelfLockedError:
-                pass
-            else:
-                assert False
-        finally:
-            ltContinue.set()
-            lt.join()
-
-class TestShelfCreation(unittest.TestCase):
-    def tearDown(self):
-        removeTmpDb()
-
-    def test_CreateShelf1(self):
-        s = Shelf(db)
-        s.create()
-        assert os.path.exists(db)
-
-    def test_CreateShelf2(self):
-        file(db, "w") # Create empty file.
-        s = Shelf(db)
-        try:
-            s.create()
-        except FailedWritingError:
-            pass
-        else:
-            assert False
-
-class TestShelfMemoryLeakage(unittest.TestCase):
-    def tearDown(self):
-        removeTmpDb()
-
-    def test_MemoryLeak1(self):
-        s = Shelf(db)
-        s.create()
-        assert gc.collect() == 0
-
-    def test_MemoryLeak2(self):
-        s = Shelf(db)
-        s.create()
-        s.begin()
-        s.getObject(0)
-        s.rollback()
-        assert gc.collect() == 0
-
-    def test_MemoryLeak3(self):
-        s = Shelf(db)
-        s.create()
-        s.begin()
-        s.getObject(0)
-        s.commit()
-        assert gc.collect() == 0
-
-class TestShelfTransactions(unittest.TestCase):
-    def tearDown(self):
-        removeTmpDb()
-
-    def test_commit(self):
-        s = Shelf(db)
-        s.create()
-        s.begin()
-        s.createAlbum(u"foo")
-        assert s.getAlbumByTag(u"foo")
-        s.commit()
-        s = Shelf(db)
-        s.begin()
-        assert s.getAlbumByTag(u"foo")
-        s.rollback()
-
-    def test_rollback(self):
-        s = Shelf(db)
-        s.create()
-        s.begin()
-        s.createAlbum(u"foo")
-        s.rollback()
-        s.begin()
-        try:
-            s.getAlbumByTag(u"foo")
-        except AlbumDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_isModified(self):
-        s = Shelf(db)
-        s.create()
-        s.begin()
-        assert not s.isModified()
-        s.createAlbum(u"foo")
-        assert s.isModified()
-        s.rollback()
-        s.begin()
-        assert not s.isModified()
-        s.rollback()
-
-    def test_registerModificationCallback(self):
-        res = [False]
-        def f(x):
-            res[0] = True
-        s = Shelf(db)
-        s.create()
-        s.begin()
-        s.registerModificationCallback(f)
-        assert not res[0]
-        s.createAlbum(u"foo")
-        assert res[0]
-        s.rollback()
-        res[0] = False
-        s.begin()
-        assert not res[0]
-        s.unregisterModificationCallback(f)
-        s.createAlbum(u"foo")
-        assert not res[0]
-        s.rollback()
-
-class TestShelfFixture(unittest.TestCase):
-    def setUp(self):
-        self.shelf = Shelf(db)
-        self.shelf.create()
-        self.shelf.begin()
-        root = self.shelf.getRootAlbum()
-        alpha = self.shelf.createAlbum(u"alpha")
-        beta = self.shelf.createAlbum(u"beta")
-        children = [beta]
-        for x in os.listdir(PICDIR):
-            loc = os.path.join(PICDIR, x)
-            if not os.path.isfile(loc):
-                continue
-            image = self.shelf.createImage()
-            imageversion = self.shelf.createImageVersion(
-                image, loc, ImageVersionType.Original)
-            children.append(image)
-        del children[-1] # The last image becomes orphaned.
-        alpha.setChildren(children)
-        beta.setChildren(list(beta.getChildren()) + [children[-1]])
-        root.setChildren(list(root.getChildren()) + [alpha, beta])
-        self.shelf.createAlbum(u"epsilon", AlbumType.Plain) # Orphaned album.
-        zeta = self.shelf.createAlbum(u"zeta", AlbumType.Search)
-        zeta.setAttribute(u"query", u"a")
-        root.setChildren(list(root.getChildren()) + [zeta])
-
-        cat_a = self.shelf.createCategory(u"a", u"A")
-        cat_b = self.shelf.createCategory(u"b", u"B")
-        cat_c = self.shelf.createCategory(u"c", u"C")
-        cat_d = self.shelf.createCategory(u"d", u"D")
-        cat_a.connectChild(cat_b)
-        cat_a.connectChild(cat_c)
-        cat_b.connectChild(cat_d)
-        cat_c.connectChild(cat_d)
-
-        self.shelf.flushObjectCache()
-        self.shelf.flushCategoryCache()
-
-    def tearDown(self):
-        self.shelf.rollback()
-        removeTmpDb()
-
-class TestShelfMethods(TestShelfFixture):
-    def test_flushCaches(self):
-        self.shelf.flushCategoryCache()
-        self.shelf.flushObjectCache()
-
-    def test_getStatistics(self):
-        s = self.shelf.getStatistics()
-        assert s["nalbums"] == 6
-        assert s["nimages"] == 11
-        assert s["nimageversions"] == 11
-
-    def test_createdObjects(self):
-        root = self.shelf.getRootAlbum()
-        children = list(root.getChildren())
-        assert len(children) == 4
-        orphans, alpha, beta, zeta = children
-        assert self.shelf.getAlbum(alpha.getId()) == alpha
-        assert self.shelf.getAlbumByTag(u"beta") == beta
-        assert len(list(alpha.getChildren())) == 11
-        assert len(list(beta.getChildren())) == 1
-
-    def test_createdAttributes(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
-        image = imageversion.getImage()
-        assert image.getAttribute(u"captured") == "2002-02-02 22:20:51"
-        assert image.getAttribute(u"cameramake") == "Canon"
-        assert image.getAttribute(u"cameramodel") == "Canon DIGITAL IXUS"
-
-    def test_negativeAlbumCreation(self):
-        try:
-            self.shelf.createAlbum(u"beta")
-        except AlbumExistsError:
-            pass
-        else:
-            assert False
-
-    def test_getAlbum(self):
-        album = self.shelf.getAlbum(0)
-        assert album == self.shelf.getRootAlbum()
-
-    def test_negativeGetAlbum(self):
-        try:
-            self.shelf.getAlbum(12345678)
-        except AlbumDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getAlbumByTag(self):
-        album = self.shelf.getAlbumByTag(u"root")
-        assert album == self.shelf.getRootAlbum()
-
-    def test_negativeGetAlbumByTag(self):
-        try:
-            self.shelf.getAlbum(u"12345678")
-        except AlbumDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getRootAlbum(self):
-        root = self.shelf.getRootAlbum()
-        assert root.getId() == 0
-        assert root == self.shelf.getAlbumByTag(u"root")
-
-    def test_getAllAlbums(self):
-        albums = list(self.shelf.getAllAlbums())
-        assert len(albums) == 6
-
-    def test_getAllImageVersions(self):
-        imageversions = list(self.shelf.getAllImageVersions())
-        assert len(imageversions) == 11
-
-    def test_getImageVersionsInDirectory(self):
-        self.shelf.flushImageVersionCache()
-
-        imageversions = list(self.shelf.getImageVersionsInDirectory(u"."))
-        assert len(imageversions) == 0
-
-        # Image versions not in cache.
-        imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
-        assert len(imageversions) == 11
-
-        # Image versions in cache.
-        imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
-        assert len(imageversions) == 11
-
-    def test_deleteAlbum(self):
-        album = self.shelf.getAlbumByTag(u"beta")
-        self.shelf.deleteAlbum(album.getId())
-
-    def test_negativeRootAlbumDeletion(self):
-        root = self.shelf.getRootAlbum()
-        try:
-            self.shelf.deleteAlbum(root.getId())
-        except UndeletableAlbumError:
-            pass
-        else:
-            assert False
-
-    def test_negativeAlbumDeletion(self):
-        try:
-            self.shelf.deleteAlbum(12345678)
-        except AlbumDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_negativeImageCreation(self):
-        try:
-            image = self.shelf.createImage()
-            self.shelf.createImageVersion(
-                image,
-                os.path.join(PICDIR, "arlaharen.png"),
-                ImageVersionType.Original)
-        except ImageVersionExistsError:
-            pass
-        else:
-            assert False
-
-    def test_getImage(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        image = imageversion.getImage()
-        assert self.shelf.getImage(image.getId()) == image
-
-    def test_negativeGetImage(self):
-        try:
-            self.shelf.getImage(12345678)
-        except ImageDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getImageVersion(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        assert self.shelf.getImageVersion(imageversion.getId()) == imageversion
-
-    def test_negativeGetImageVersion(self):
-        try:
-            self.shelf.getImageVersion(12345678)
-        except ImageVersionDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getImageVersionByHash(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        assert self.shelf.getImageVersionByHash(
-            imageversion.getHash()) == imageversion
-
-    def test_negativeGetImageVersionByHash(self):
-        try:
-            self.shelf.getImageVersion(u"badhash")
-        except ImageVersionDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getImageVersionByLocation(self):
-        imageversion1 = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, u"arlaharen.png"))
-        currentDir = os.getcwd()
-        try:
-            os.chdir(PICDIR)
-            imageversion2 = self.shelf.getImageVersionByLocation(
-                u"arlaharen.png")
-        finally:
-            os.chdir(currentDir)
-        assert imageversion1 == imageversion2
-
-    def test_negativeGetImageVersionByHash(self):
-        try:
-            self.shelf.getImageVersionByLocation(u"/bad/location")
-        except ImageVersionDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_deleteImage(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        imageid = imageversion.getImage().getId()
-        self.shelf.deleteImage(imageid)
-        try:
-            self.shelf.getImageVersionByLocation(
-                os.path.join(PICDIR, "arlaharen.png"))
-        except ImageVersionDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_negativeImageDeletion(self):
-        try:
-            self.shelf.deleteImage(12345678)
-        except ImageDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_deleteImageVersion(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        self.shelf.deleteImageVersion(imageversion.getId())
-
-    def test_negativeImageVersionDeletion(self):
-        try:
-            self.shelf.deleteImageVersion(12345678)
-        except ImageVersionDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getObject(self):
-        rootalbum = self.shelf.getRootAlbum()
-        album = self.shelf.getObject(rootalbum.getId())
-        assert album == rootalbum
-
-    def test_deleteObject(self):
-        albumid = self.shelf.getAlbumByTag(u"beta").getId()
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        imageid = imageversion.getImage().getId()
-        self.shelf.deleteObject(albumid)
-        self.shelf.deleteObject(imageid)
-        try:
-            self.shelf.getAlbum(albumid)
-        except AlbumDoesNotExistError:
-            pass
-        else:
-            assert False
-        try:
-            self.shelf.getImage(imageid)
-        except ImageDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getAllAttributeNames(self):
-        attrnames = sorted(self.shelf.getAllAttributeNames())
-        assert attrnames == [
-            "cameramake", "cameramodel", "captured", "description",
-            "digitalzoom", "exposurebias", "exposureprogram", "exposuretime",
-            "flash", "fnumber", "focallength", "iso", "orientation", "query",
-            "title"
-            ], attrnames
-
-    def test_getCategory(self):
-        category = self.shelf.getCategory(1)
-        assert category.getId() == 1
-
-    def test_getCategoryByTag(self):
-        category = self.shelf.getCategoryByTag(u"a")
-        assert category.getTag() == u"a"
-
-    def test_negativeCreateCategory(self):
-        try:
-            self.shelf.createCategory(u"a", u"Foo")
-        except CategoryExistsError:
-            pass
-        else:
-            assert False
-
-    def test_deleteCategory(self):
-        category = self.shelf.getCategoryByTag(u"a")
-        self.shelf.deleteCategory(category.getId())
-
-    def test_negativeDeleteCategory(self):
-        try:
-            self.shelf.deleteCategory(12345678)
-        except CategoryDoesNotExistError:
-            pass
-        else:
-            assert False
-
-    def test_getRootCategories(self):
-        categories = sorted(
-            self.shelf.getRootCategories(), key=lambda x: x.getTag())
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_events = self.shelf.getCategoryByTag(u"events")
-        cat_locations = self.shelf.getCategoryByTag(u"locations")
-        cat_people = self.shelf.getCategoryByTag(u"people")
-        assert categories == [cat_a, cat_events, cat_locations, cat_people], \
-               categories
-
-class TestCategory(TestShelfFixture):
-    def test_categoryMethods(self):
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_b = self.shelf.getCategoryByTag(u"b")
-        cat_c = self.shelf.getCategoryByTag(u"c")
-        cat_d = self.shelf.getCategoryByTag(u"d")
-
-        assert self.shelf.getCategory(cat_a.getId()) == cat_a
-        cat_a.setTag(u"foo")
-        assert self.shelf.getCategoryByTag(u"foo") == cat_a
-
-        assert cat_a.getDescription() == "A"
-        cat_a.setDescription(u"foo")
-        assert cat_a.getDescription() == "foo"
-
-        a_children = sorted(cat_a.getChildren(), key=lambda x: x.getId())
-        assert a_children == [cat_b, cat_c]
-        b_children = list(cat_b.getChildren())
-        assert b_children == [cat_d]
-        d_children = list(cat_d.getChildren())
-        assert d_children == []
-
-        a_parents = list(cat_a.getParents())
-        assert a_parents == []
-        b_parents = list(cat_b.getParents())
-        assert b_parents == [cat_a]
-        d_parents = sorted(cat_d.getParents(), key=lambda x: x.getTag())
-        assert d_parents == [cat_b, cat_c]
-
-        assert not cat_a.isChildOf(cat_a)
-        assert cat_b.isChildOf(cat_a)
-        assert cat_c.isChildOf(cat_a)
-        assert not cat_d.isChildOf(cat_a)
-        assert cat_a.isChildOf(cat_a, recursive=True)
-        assert cat_b.isChildOf(cat_a, recursive=True)
-        assert cat_c.isChildOf(cat_a, recursive=True)
-        assert cat_d.isChildOf(cat_a, recursive=True)
-
-        assert not cat_d.isParentOf(cat_d)
-        assert cat_b.isParentOf(cat_d)
-        assert cat_c.isParentOf(cat_d)
-        assert not cat_a.isParentOf(cat_d)
-        assert cat_d.isParentOf(cat_d, recursive=True)
-        assert cat_b.isParentOf(cat_d, recursive=True)
-        assert cat_c.isParentOf(cat_d, recursive=True)
-        assert cat_a.isParentOf(cat_d, recursive=True)
-
-    def test_negativeCategoryConnectChild(self):
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_b = self.shelf.getCategoryByTag(u"b")
-        try:
-            cat_a.connectChild(cat_b)
-        except CategoriesAlreadyConnectedError:
-            pass
-        else:
-            assert False
-        try:
-            cat_b.connectChild(cat_a)
-        except CategoryLoopError:
-            pass
-        else:
-            assert False
-
-    def test_categoryDisconnectChild(self):
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_b = self.shelf.getCategoryByTag(u"b")
-        cat_a.disconnectChild(cat_b)
-        assert not cat_a.isParentOf(cat_b)
-
-    def test_negativeCategoryDisconnectChild(self):
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_d = self.shelf.getCategoryByTag(u"d")
-        cat_a.disconnectChild(cat_d) # No exception.
-
-class TestObject(TestShelfFixture):
-    def test_getParents(self):
-        root = self.shelf.getRootAlbum()
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        beta = self.shelf.getAlbumByTag(u"beta")
-        parents = sorted(beta.getParents(), key=lambda x: x.getTag())
-        assert parents == [alpha, root]
-
-    def test_getAttribute(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        assert orphans.getAttribute(u"title")
-        assert orphans.getAttribute(u"description")
-        orphans.getAttributeMap() # Just populate the cache.
-        assert not orphans.getAttribute(u"nonexisting")
-        assert u"nonexisting" not in orphans.getAttributeMap()
-
-    def test_getAttributeMap(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        map = orphans.getAttributeMap()
-        assert "description" in map
-        assert "title" in map
-
-    def test_getAttributeNames(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        names = sorted(orphans.getAttributeNames())
-        assert names == ["description", "title"]
-
-    def test_setAttribute(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        orphans.setAttribute(u"foo", u"fie") # New.
-        assert orphans.getAttribute(u"foo") == u"fie"
-        assert u"foo" in orphans.getAttributeMap()
-        assert u"foo" in orphans.getAttributeNames()
-        assert orphans.getAttribute(u"title")
-        orphans.setAttribute(u"title", u"gazonk") # Existing
-        assert orphans.getAttribute(u"title") == u"gazonk"
-        assert u"foo" in orphans.getAttributeMap()
-        assert u"foo" in orphans.getAttributeNames()
-
-    def test_deleteAttribute(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        orphans.deleteAttribute(u"nonexisting") # No exception.
-        assert orphans.getAttribute(u"title")
-        orphans.deleteAttribute(u"title")
-        orphans.getAttributeMap() # Just populate the cache.
-        assert not orphans.getAttribute(u"title")
-        assert u"title" not in orphans.getAttributeMap()
-
-    def test_addCategory(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        assert list(orphans.getCategories()) == []
-        orphans.addCategory(cat_a)
-        assert list(orphans.getCategories()) == [cat_a]
-        try:
-            orphans.addCategory(cat_a)
-        except CategoryPresentError:
-            pass
-        else:
-            assert False
-        assert list(orphans.getCategories()) == [cat_a]
-
-    def test_removeCategory(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        assert list(orphans.getCategories()) == []
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        orphans.addCategory(cat_a)
-        assert list(orphans.getCategories()) == [cat_a]
-        orphans.removeCategory(cat_a)
-        assert list(orphans.getCategories()) == []
-
-    def test_deleteCategoryInvalidatesCategoryCache(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        assert list(orphans.getCategories()) == []
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        orphans.addCategory(cat_a)
-        assert list(orphans.getCategories()) == [cat_a]
-        self.shelf.deleteCategory(cat_a.getId())
-        assert list(orphans.getCategories()) == []
-
-class TestAlbum(TestShelfFixture):
-    def test_getType(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        assert alpha.getType() == AlbumType.Plain
-
-    def test_isMutable(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        assert alpha.isMutable()
-
-    def test_getTag(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        assert alpha.getTag() == u"alpha"
-
-    def test_setTag(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        alpha.setTag(u"alfa")
-        assert alpha.getTag() == u"alfa"
-
-    def test_getAlbumParents(self):
-        root = self.shelf.getRootAlbum()
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        parents = sorted(alpha.getAlbumParents(), key=lambda x: x.getTag())
-        assert parents == [root]
-
-    def test_isAlbum(self):
-        assert self.shelf.getRootAlbum().isAlbum()
-
-class TestPlainAlbum(TestShelfFixture):
-    def test_getType(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        assert alpha.getType() == AlbumType.Plain
-
-    def test_isMutable(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        assert alpha.isMutable()
-
-    def test_getChildren(self):
-        epsilon = self.shelf.getAlbumByTag(u"epsilon")
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        beta = self.shelf.getAlbumByTag(u"beta")
-        assert list(epsilon.getChildren()) == []
-        alphaChildren = list(alpha.getChildren())
-        assert list(beta.getChildren()) == [alphaChildren[-1]]
-
-    def test_getAlbumChildren(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        beta = self.shelf.getAlbumByTag(u"beta")
-        epsilon = self.shelf.getAlbumByTag(u"epsilon")
-        alphaAlbumChildren = list(alpha.getAlbumChildren())
-        assert alphaAlbumChildren == [beta]
-        assert list(epsilon.getAlbumChildren()) == []
-
-    def test_setChildren(self):
-        root = self.shelf.getRootAlbum()
-        beta = self.shelf.getAlbumByTag(u"beta")
-        assert list(beta.getChildren()) != []
-        beta.setChildren([beta, root])
-        assert list(beta.getChildren()) == [beta, root]
-        beta.setChildren([]) # Break the cycle.
-        assert list(beta.getChildren()) == []
-
-class TestImage(TestShelfFixture):
-    def test_isAlbum(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        assert not imageversion.getImage().isAlbum()
-
-    def test_getImageVersions(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        image = imageversion.getImage()
-        assert list(image.getImageVersions()) == [imageversion]
-        imageversion2 = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
-        imageversion2.setImage(image)
-        imageversions = set(image.getImageVersions())
-        assert set(image.getImageVersions()) == imageversions
-        self.shelf.deleteImageVersion(imageversion.getId())
-        self.shelf.deleteImageVersion(imageversion2.getId())
-        assert list(image.getImageVersions()) == []
-
-    def test_getPrimaryVersion(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        image = imageversion.getImage()
-        assert image.getPrimaryVersion() == imageversion
-        imageversion2 = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
-        imageversion2.setImage(image)
-        assert image.getPrimaryVersion() == imageversion
-        imageversion2.makePrimary()
-        assert image.getPrimaryVersion() == imageversion2
-
-        newImage = self.shelf.createImage()
-        lastImageVersion = list(image.getImageVersions())[-1]
-        lastImageVersion.setImage(newImage)
-        assert image.getPrimaryVersion() != lastImageVersion
-        assert newImage.getPrimaryVersion() == lastImageVersion
-        lastImageVersion.setImage(image)
-
-        self.shelf.deleteImageVersion(imageversion2.getId())
-        assert image.getPrimaryVersion() == imageversion
-        self.shelf.deleteImageVersion(imageversion.getId())
-        assert image.getPrimaryVersion() == None
-
-class TestImageVersion(TestShelfFixture):
-    def test_getType(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        assert imageversion.getType() == ImageVersionType.Original
-        imageversion.setType(ImageVersionType.Important)
-        assert imageversion.getType() == ImageVersionType.Important
-        imageversion.setType(ImageVersionType.Other)
-        assert imageversion.getType() == ImageVersionType.Other
-        imageversion.setType(ImageVersionType.Original)
-        assert imageversion.getType() == ImageVersionType.Original
-
-    # ImageVersion.makePrimary tested in TestImage.test_getPrimaryVersion.
-    # ImageVersion.setImage tested in TestImage.test_getPrimaryVersion.
-    # ImageVersion.setType tested in TestImageVersion.test_getType.
-    # ImageVersion.setComment tested in TestImageVersion.test_getComment.
-
-    def test_getComment(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        assert imageversion.getComment() == ""
-        imageversion.setComment(u"a comment")
-        assert imageversion.getComment() == u"a comment"
-
-    def test_getHash(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        assert imageversion.getHash() == "39a1266d2689f53d48b09a5e0ca0af1f"
-
-    def test_getLocation(self):
-        location = os.path.join(PICDIR, "arlaharen.png")
-        imageversion = self.shelf.getImageVersionByLocation(location)
-        assert imageversion.getLocation() == os.path.realpath(location)
-
-    def test_getModificationTime(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        t = imageversion.getModificationTime()
-        assert isinstance(t, int)
-        assert t > 0
-
-    def test_getSize(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        assert imageversion.getSize() == (304, 540)
-
-    def test_contentChanged(self):
-        path = os.path.join(PICDIR, "arlaharen.png")
-        imageversion = self.shelf.getImageVersionByLocation(path)
-        self.shelf.deleteImageVersion(imageversion.getId())
-        newpath = u"tmp.png"
-        try:
-            shutil.copy2(path, newpath)
-            newimage = self.shelf.createImage()
-            newimageversion = self.shelf.createImageVersion(
-                newimage, newpath, ImageVersionType.Original)
-            oldmtime = imageversion.getModificationTime()
-            f = open(newpath, "a")
-            f.write("foo")
-            f.close()
-            newimageversion.contentChanged()
-            assert newimageversion.getHash() == "b27312d9739c0edfd115f824be244b75"
-            assert newimageversion.getModificationTime() > oldmtime
-        finally:
-            try:
-                os.unlink(newpath)
-            except OSError:
-                pass
-
-    def test_locationChanged(self):
-        location = os.path.join(PICDIR, "arlaharen.png")
-        imageversion = self.shelf.getImageVersionByLocation(location)
-        imageversion.locationChanged(u"/foo/../bar")
-        assert imageversion.getLocation() == "/bar"
-
-    def test_importExifTags(self):
-        imageversion = self.shelf.getImageVersionByLocation(
-            os.path.join(PICDIR, "arlaharen.png"))
-        imageversion.importExifTags(True) # TODO: Test more.
-        imageversion.importExifTags(False) # TODO: Test more.
-
-class TestOrphansAlbum(TestShelfFixture):
-    def test_getType(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        assert orphans.getType() == AlbumType.Orphans
-
-    def test_isMutable(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        assert not orphans.isMutable()
-
-    def test_getChildren(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        assert len(list(orphans.getChildren())) == 2
-
-    def test_getAlbumChildren(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        epsilon = self.shelf.getAlbumByTag(u"epsilon")
-        assert list(orphans.getAlbumChildren()) == [epsilon]
-
-    def test_setChildren(self):
-        orphans = self.shelf.getAlbumByTag(u"orphans")
-        try:
-            orphans.setChildren([])
-        except UnsettableChildrenError:
-            pass
-        else:
-            assert False
-
-    def test_isAlbum(self):
-        assert self.shelf.getAlbumByTag(u"orphans").isAlbum()
-
-class TestSearchAlbum(TestShelfFixture):
-    def test_getType(self):
-        zeta = self.shelf.getAlbumByTag(u"zeta")
-        assert zeta.getType() == AlbumType.Search
-
-    def test_isMutable(self):
-        zeta = self.shelf.getAlbumByTag(u"zeta")
-        assert not zeta.isMutable()
-
-    def test_getChildren(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        image1, image2 = list(alpha.getChildren())[0:2]
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_b = self.shelf.getCategoryByTag(u"b")
-        image1.addCategory(cat_a)
-        image2.addCategory(cat_b)
-        zeta = self.shelf.getAlbumByTag(u"zeta")
-        assert zeta
-        zeta.setAttribute(u"query", u"b")
-        children = zeta.getChildren()
-        assert list(children) == [image2]
-
-    def test_getAlbumChildren(self):
-        alpha = self.shelf.getAlbumByTag(u"alpha")
-        image1, image2 = list(alpha.getChildren())[0:2]
-        cat_a = self.shelf.getCategoryByTag(u"a")
-        cat_b = self.shelf.getCategoryByTag(u"b")
-        image1.addCategory(cat_a)
-        image2.addCategory(cat_b)
-        zeta = self.shelf.getAlbumByTag(u"zeta")
-        assert zeta
-        zeta.setAttribute(u"query", u"b")
-        children = zeta.getAlbumChildren()
-        l = list(children)
-        assert list(children) == []
-
-    def test_setChildren(self):
-        zeta = self.shelf.getAlbumByTag(u"zeta")
-        try:
-            zeta.setChildren([])
-        except UnsettableChildrenError:
-            pass
-        else:
-            assert False
-
-    def test_isAlbum(self):
-        assert self.shelf.getAlbumByTag(u"zeta").isAlbum()
-
-######################################################################
-
-removeTmpDb()
-if __name__ == "__main__":
-    unittest.main()
diff --git a/src/test/test_all.py b/src/test/test_all.py
new file mode 100755 (executable)
index 0000000..d10ed1d
--- /dev/null
@@ -0,0 +1,22 @@
+#! /usr/bin/env python
+
+import os
+import sys
+import unittest
+
+tests = ["dag", "iodict", "searching", "shelf"]
+
+cwd = os.getcwd()
+libdir = unicode(os.path.realpath(
+    os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
+os.chdir(libdir)
+sys.path.insert(0, libdir)
+
+def suite():
+    alltests = unittest.TestSuite()
+    for module in [__import__("test_%s" % x) for x in tests]:
+        alltests.addTest(unittest.findTestCases(module))
+    return alltests
+
+if __name__ == "__main__":
+    unittest.main(defaultTest="suite")
diff --git a/src/test/test_dag.py b/src/test/test_dag.py
new file mode 100755 (executable)
index 0000000..e92730e
--- /dev/null
@@ -0,0 +1,132 @@
+#! /usr/bin/env python
+
+import os
+import sys
+import unittest
+
+if __name__ == "__main__":
+    cwd = os.getcwd()
+    libdir = unicode(os.path.realpath(
+        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
+    os.chdir(libdir)
+    sys.path.insert(0, libdir)
+from kofoto.dag import *
+
+PICDIR = unicode(os.path.realpath(
+    os.path.join("..", "reference_pictures", "working")))
+
+class TestDAG(unittest.TestCase):
+    def setUp(self):
+        self.dag = DAG()
+        for x in [1, 2, 3, 4, 5, 6]:
+            self.dag.add(x)
+        for x, y in [(1, 3), (1, 4), (2, 3), (3, 5), (4, 5), (5, 6)]:
+            self.dag.connect(x, y)
+
+    def tearDown(self):
+        del self.dag
+
+    def test_iter(self):
+        assert sorted(list(self.dag)) == [1, 2, 3, 4, 5, 6]
+
+    def test_contains(self):
+        assert 3 in self.dag
+
+    def test_negative_contains(self):
+        assert not 4711 in self.dag
+
+    def test_redundant_add(self):
+        self.dag.add(1)
+        assert sorted(list(self.dag)) == [1, 2, 3, 4, 5, 6]
+
+    def test_redundant_connect(self):
+        assert self.dag.reachable(1, 3)
+        self.dag.connect(1, 3)
+        assert self.dag.reachable(1, 3)
+
+    def test_connect_loop(self):
+        try:
+            self.dag.connect(6, 1)
+        except LoopError:
+            pass
+        else:
+            assert False
+
+    def test_connected(self):
+        assert self.dag.connected(1, 3)
+        assert self.dag.connected(1, 4)
+        assert not self.dag.connected(1, 2)
+        assert not self.dag.connected(1, 5)
+
+    def test_disconnect(self):
+        assert self.dag.reachable(1, 3)
+        self.dag.disconnect(1, 3)
+        assert not self.dag.reachable(1, 3)
+
+    def test_idempotent_disconnect(self):
+        self.dag.disconnect(3, 1)
+        assert self.dag.reachable(1, 3)
+        assert not self.dag.reachable(3, 1)
+
+    def test_getAncestors(self):
+        for x, y in [(1, [1]),
+                     (2, [2]),
+                     (3, [1, 2, 3]),
+                     (4, [1, 4]),
+                     (5, [1, 2, 3, 4, 5]),
+                     (6, [1, 2, 3, 4, 5, 6])]:
+            assert sorted(list(self.dag.getAncestors(x))) == sorted(y)
+
+    def test_getChildren(self):
+        for x, y in [(1, [3, 4]),
+                     (2, [3]),
+                     (3, [5]),
+                     (4, [5]),
+                     (5, [6]),
+                     (6, [])]:
+            assert sorted(list(self.dag.getChildren(x))) == sorted(y)
+
+    def test_getDescendants(self):
+        for x, y in [(1, [1, 3, 4, 5, 6]),
+                     (2, [2, 3, 5, 6]),
+                     (3, [3, 5, 6]),
+                     (4, [4, 5, 6]),
+                     (5, [5, 6]),
+                     (6, [6])]:
+            assert sorted(list(self.dag.getDescendants(x))) == sorted(y)
+
+    def test_getParents(self):
+        for x, y in [(1, []),
+                     (2, []),
+                     (3, [1, 2]),
+                     (4, [1]),
+                     (5, [3, 4]),
+                     (6, [5])]:
+            assert sorted(list(self.dag.getParents(x))) == sorted(y)
+
+    def test_getRoots(self):
+        assert sorted(list(self.dag.getRoots())) == [1, 2]
+
+    def test_reachable(self):
+        assert self.dag.reachable(1, 3)
+        assert self.dag.reachable(1, 6)
+        assert not self.dag.reachable(1, 2)
+        assert not self.dag.reachable(1, 4711)
+
+    def test_remove(self):
+        assert self.dag.reachable(1, 6)
+        self.dag.remove(5)
+        assert not self.dag.reachable(1, 6)
+
+    def test_negative_remove(self):
+        try:
+            self.dag.remove(4711)
+        except:
+            pass
+        else:
+            assert False
+
+######################################################################
+
+if __name__ == "__main__":
+    unittest.main()
old mode 100644 (file)
new mode 100755 (executable)
index 07a361e..76a5710
@@ -1,3 +1,5 @@
+#! /usr/bin/env python
+
 import os
 import sys
 import unittest
@@ -5,7 +7,7 @@ import unittest
 if __name__ == "__main__":
     cwd = os.getcwd()
     libdir = unicode(os.path.realpath(
-        os.path.join(os.path.dirname(sys.argv[0]), "..")))
+        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
     os.chdir(libdir)
     sys.path.insert(0, libdir)
 
diff --git a/src/test/test_searching.py b/src/test/test_searching.py
new file mode 100755 (executable)
index 0000000..0b3adff
--- /dev/null
@@ -0,0 +1,102 @@
+#! /usr/bin/env python
+
+import os
+import sys
+import unittest
+
+if __name__ == "__main__":
+    cwd = os.getcwd()
+    libdir = unicode(os.path.realpath(
+        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
+    os.chdir(libdir)
+    sys.path.insert(0, libdir)
+from kofoto.shelf import *
+from kofoto.search import *
+
+PICDIR = unicode(os.path.realpath(
+    os.path.join("..", "reference_pictures", "working")))
+
+######################################################################
+
+db = "shelf.tmp"
+codeset = "latin1"
+
+def removeTmpDb():
+    for x in [db, db + "-journal"]:
+        if os.path.exists(x):
+            os.unlink(x)
+
+from test_shelf import TestShelfFixture
+
+class TestSearch(TestShelfFixture):
+    def setUp(self):
+        TestShelfFixture.setUp(self)
+        self.images = list(self.shelf.getAllImages())
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        cat_c = self.shelf.getCategoryByTag(u"c")
+        cat_d = self.shelf.getCategoryByTag(u"d")
+        self.images[0].addCategory(cat_a)
+        self.images[0].addCategory(cat_b)
+        self.images[1].addCategory(cat_c)
+        self.images[0].setAttribute(u"foo", u"abc")
+        self.images[0].setAttribute(u"bar", u"17")
+        self.images[1].setAttribute(u"foo", u"xyz")
+        self.images[2].setAttribute(u"fie", u"fum")
+
+    def tearDown(self):
+        TestShelfFixture.tearDown(self)
+
+    def test_search(self):
+        tests = [
+            (u"a", [self.images[0], self.images[1]]),
+            (u"b", [self.images[0]]),
+            (u"c", [self.images[1]]),
+            (u"d", []),
+            (u"(((b)))", [self.images[0]]),
+            (u'@foo >= "b\'ar"', [self.images[1]]),
+            (u"a and b", [self.images[0]]),
+            (u'a and @foo = "abc"', [self.images[0]]),
+            (u'a and @foo = xyz', [self.images[1]]),
+            (u'@foo = "abc" and @bar = 17', [self.images[0]]),
+            (u'a and b and @bar = "17" and @foo = abc', [self.images[0]]),
+            (u"not a and c", []),
+            (u"not exactly a and c", [self.images[1]]),
+            (u"not (a and b) and a", [self.images[1]]),
+            (u"not a and not b and @fie=fum", [self.images[2]]),
+            (u"a and b or c", [self.images[0], self.images[1]]),
+            (u"b or c and d", [self.images[0]]),
+            (u"a or b or c or d", [self.images[0], self.images[1]]),
+            (ur' ((a and not b) or @gazonk != "hej \"ju\"") and c ', [self.images[1]]),
+            (u"/alpha and a", [self.images[0], self.images[1]]),
+            (u"/epsilon", []),
+            (u"/zeta", [self.images[0], self.images[1]]),
+            ]
+        parser = Parser(self.shelf)
+        for expression, expectedResult in tests:
+            parseTree = parser.parse(expression)
+            result = sorted(
+                self.shelf.search(parseTree), key=lambda x: x.getId())
+            assert result == expectedResult, (expression, expectedResult, result)
+
+    def test_parseErrors(self):
+        tests = [
+            (u"+", BadTokenError),
+            (u":a and +", BadTokenError),
+            (u'"', UnterminatedStringError),
+            (ur'"\"\\\"', UnterminatedStringError),
+            ]
+        parser = Parser(self.shelf)
+        for expression, expectedException in tests:
+            try:
+                parser.parse(expression)
+            except expectedException:
+                pass
+            except Exception, e:
+                assert False, (expression, expectedException, e)
+
+######################################################################
+
+if __name__ == "__main__":
+    removeTmpDb()
+    unittest.main()
diff --git a/src/test/test_shelf.py b/src/test/test_shelf.py
new file mode 100755 (executable)
index 0000000..aad3e16
--- /dev/null
@@ -0,0 +1,1030 @@
+#! /usr/bin/env python
+
+import gc
+import os
+import shutil
+import sys
+import threading
+import unittest
+
+if __name__ == "__main__":
+    cwd = os.getcwd()
+    libdir = unicode(os.path.realpath(
+        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
+    os.chdir(libdir)
+    sys.path.insert(0, libdir)
+from kofoto.shelf import \
+    Shelf, \
+    computeImageHash, \
+    makeValidTag, \
+    verifyValidAlbumTag, \
+    verifyValidCategoryTag
+from kofoto.albumtype import AlbumType
+from kofoto.imageversiontype import ImageVersionType
+from kofoto.shelfexceptions import \
+    AlbumDoesNotExistError, \
+    AlbumExistsError, \
+    BadAlbumTagError, \
+    BadCategoryTagError, \
+    CategoriesAlreadyConnectedError, \
+    CategoryDoesNotExistError, \
+    CategoryExistsError, \
+    CategoryLoopError, \
+    CategoryPresentError, \
+    FailedWritingError, \
+    ImageDoesNotExistError, \
+    ImageVersionDoesNotExistError, \
+    ImageVersionExistsError, \
+    ShelfLockedError, \
+    ShelfNotFoundError, \
+    UndeletableAlbumError, \
+    UndeletableAlbumError, \
+    UnsettableChildrenError, \
+    UnsupportedShelfError
+
+PICDIR = unicode(os.path.realpath(
+    os.path.join("..", "reference_pictures", "working")))
+
+######################################################################
+
+db = "shelf.tmp"
+
+def removeTmpDb():
+    for x in [db, db + "-journal"]:
+        if os.path.exists(x):
+            os.unlink(x)
+
+class LockerThread(threading.Thread):
+    def __init__(self, mContinue, ltContinue):
+        threading.Thread.__init__(self)
+        self.mContinue = mContinue
+        self.ltContinue = ltContinue
+
+    def run(self):
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        self.mContinue.set()
+        self.ltContinue.wait()
+        s.rollback()
+
+######################################################################
+
+class TestPublicShelfFunctions(unittest.TestCase):
+    def test_computeImageHash(self):
+        s = computeImageHash(os.path.join(PICDIR, "arlaharen.png"))
+        assert s == "39a1266d2689f53d48b09a5e0ca0af1f"
+        try:
+            computeImageHash("nonexisting")
+        except IOError:
+            pass
+        else:
+            assert False, s
+
+    def test_verifyValidAlbumTag(self):
+        # Valid tags.
+        for x in ("foo", "1foo", "and", "exactly", "not", "or"):
+            try:
+                verifyValidAlbumTag(x)
+            except:
+                assert False, x
+        # Invalid tags.
+        for x in (None, 1, 1L, "1" "foo " "@foo"):
+            try:
+                verifyValidAlbumTag(x)
+            except BadAlbumTagError:
+                pass
+            else:
+                assert False, x
+
+    def test_verifyValidCategoryTag(self):
+        # Valid tags.
+        for x in ("foo", "1foo"):
+            try:
+                verifyValidCategoryTag(x)
+            except:
+                assert False, x
+        # Invalid tags.
+        for x in (None, 1, 1L, "1" "foo " "@foo",
+                  "and", "exactly", "not", "or"):
+            try:
+                verifyValidCategoryTag(x)
+            except BadCategoryTagError:
+                pass
+            else:
+                assert False, x
+
+    def test_makeValidTag(self):
+        for tag, validTag in [("", "_"),
+                              ("@", "_"),
+                              (" ", "_"),
+                              ("1", "1_"),
+                              ("@foo_", "foo_"),
+                              ("fo@o __", "fo@o__")]:
+            assert makeValidTag(tag) == validTag, (tag, validTag)
+
+class TestNegativeShelfOpens(unittest.TestCase):
+    def tearDown(self):
+        removeTmpDb()
+
+    def test_NonexistingShelf(self):
+        try:
+            s = Shelf(db)
+            s.begin()
+        except ShelfNotFoundError:
+            pass
+        else:
+            assert False
+        assert not os.path.exists(db)
+
+    def test_BadShelf(self):
+        file(db, "w") # Create empty file.
+        try:
+            s = Shelf(db)
+            s.begin()
+        except UnsupportedShelfError:
+            pass
+        else:
+            assert False
+
+    def test_LockedShelf(self):
+        mContinue = threading.Event()
+        ltContinue = threading.Event()
+        lt = LockerThread(mContinue, ltContinue)
+        lt.start()
+        mContinue.wait()
+        try:
+            try:
+                s = Shelf(db)
+                s.begin()
+            except ShelfLockedError:
+                pass
+            else:
+                assert False
+        finally:
+            ltContinue.set()
+            lt.join()
+
+class TestShelfCreation(unittest.TestCase):
+    def tearDown(self):
+        removeTmpDb()
+
+    def test_CreateShelf1(self):
+        s = Shelf(db)
+        s.create()
+        assert os.path.exists(db)
+
+    def test_CreateShelf2(self):
+        file(db, "w") # Create empty file.
+        s = Shelf(db)
+        try:
+            s.create()
+        except FailedWritingError:
+            pass
+        else:
+            assert False
+
+class TestShelfMemoryLeakage(unittest.TestCase):
+    def setUp(self):
+        # Ignore any previous leaks.
+        gc.collect()
+
+    def tearDown(self):
+        removeTmpDb()
+
+    def test_MemoryLeak1(self):
+        s = Shelf(db)
+        s.create()
+        assert gc.collect() == 0
+
+    def test_MemoryLeak2(self):
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        s.getObject(0)
+        s.rollback()
+        assert gc.collect() == 0
+
+    def test_MemoryLeak3(self):
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        s.getObject(0)
+        s.commit()
+        assert gc.collect() == 0
+
+class TestShelfTransactions(unittest.TestCase):
+    def tearDown(self):
+        removeTmpDb()
+
+    def test_commit(self):
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        s.createAlbum(u"foo")
+        assert s.getAlbumByTag(u"foo")
+        s.commit()
+        s = Shelf(db)
+        s.begin()
+        assert s.getAlbumByTag(u"foo")
+        s.rollback()
+
+    def test_rollback(self):
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        s.createAlbum(u"foo")
+        s.rollback()
+        s.begin()
+        try:
+            s.getAlbumByTag(u"foo")
+        except AlbumDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_isModified(self):
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        assert not s.isModified()
+        s.createAlbum(u"foo")
+        assert s.isModified()
+        s.rollback()
+        s.begin()
+        assert not s.isModified()
+        s.rollback()
+
+    def test_registerModificationCallback(self):
+        res = [False]
+        def f(x):
+            res[0] = True
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        s.registerModificationCallback(f)
+        assert not res[0]
+        s.createAlbum(u"foo")
+        assert res[0]
+        s.rollback()
+        res[0] = False
+        s.begin()
+        assert not res[0]
+        s.unregisterModificationCallback(f)
+        s.createAlbum(u"foo")
+        assert not res[0]
+        s.rollback()
+
+class TestShelfFixture(unittest.TestCase):
+    def setUp(self):
+        self.shelf = Shelf(db)
+        self.shelf.create()
+        self.shelf.begin()
+        root = self.shelf.getRootAlbum()
+        alpha = self.shelf.createAlbum(u"alpha")
+        beta = self.shelf.createAlbum(u"beta")
+        children = [beta]
+        for x in os.listdir(PICDIR):
+            loc = os.path.join(PICDIR, x)
+            if not os.path.isfile(loc):
+                continue
+            image = self.shelf.createImage()
+            imageversion = self.shelf.createImageVersion(
+                image, loc, ImageVersionType.Original)
+            children.append(image)
+        del children[-1] # The last image becomes orphaned.
+        alpha.setChildren(children)
+        beta.setChildren(list(beta.getChildren()) + [children[-1]])
+        root.setChildren(list(root.getChildren()) + [alpha, beta])
+        self.shelf.createAlbum(u"epsilon", AlbumType.Plain) # Orphaned album.
+        zeta = self.shelf.createAlbum(u"zeta", AlbumType.Search)
+        zeta.setAttribute(u"query", u"a")
+        root.setChildren(list(root.getChildren()) + [zeta])
+
+        cat_a = self.shelf.createCategory(u"a", u"A")
+        cat_b = self.shelf.createCategory(u"b", u"B")
+        cat_c = self.shelf.createCategory(u"c", u"C")
+        cat_d = self.shelf.createCategory(u"d", u"D")
+        cat_a.connectChild(cat_b)
+        cat_a.connectChild(cat_c)
+        cat_b.connectChild(cat_d)
+        cat_c.connectChild(cat_d)
+
+        self.shelf.flushObjectCache()
+        self.shelf.flushCategoryCache()
+
+    def tearDown(self):
+        self.shelf.rollback()
+        removeTmpDb()
+
+class TestShelfMethods(TestShelfFixture):
+    def test_flushCaches(self):
+        self.shelf.flushCategoryCache()
+        self.shelf.flushObjectCache()
+
+    def test_getStatistics(self):
+        s = self.shelf.getStatistics()
+        assert s["nalbums"] == 6
+        assert s["nimages"] == 11
+        assert s["nimageversions"] == 11
+
+    def test_createdObjects(self):
+        root = self.shelf.getRootAlbum()
+        children = list(root.getChildren())
+        assert len(children) == 4
+        orphans, alpha, beta, zeta = children
+        assert self.shelf.getAlbum(alpha.getId()) == alpha
+        assert self.shelf.getAlbumByTag(u"beta") == beta
+        assert len(list(alpha.getChildren())) == 11
+        assert len(list(beta.getChildren())) == 1
+
+    def test_createdAttributes(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
+        image = imageversion.getImage()
+        assert image.getAttribute(u"captured") == "2002-02-02 22:20:51"
+        assert image.getAttribute(u"cameramake") == "Canon"
+        assert image.getAttribute(u"cameramodel") == "Canon DIGITAL IXUS"
+
+    def test_negativeAlbumCreation(self):
+        try:
+            self.shelf.createAlbum(u"beta")
+        except AlbumExistsError:
+            pass
+        else:
+            assert False
+
+    def test_getAlbum(self):
+        album = self.shelf.getAlbum(0)
+        assert album == self.shelf.getRootAlbum()
+
+    def test_negativeGetAlbum(self):
+        try:
+            self.shelf.getAlbum(12345678)
+        except AlbumDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getAlbumByTag(self):
+        album = self.shelf.getAlbumByTag(u"root")
+        assert album == self.shelf.getRootAlbum()
+
+    def test_negativeGetAlbumByTag(self):
+        try:
+            self.shelf.getAlbum(u"12345678")
+        except AlbumDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getRootAlbum(self):
+        root = self.shelf.getRootAlbum()
+        assert root.getId() == 0
+        assert root == self.shelf.getAlbumByTag(u"root")
+
+    def test_getAllAlbums(self):
+        albums = list(self.shelf.getAllAlbums())
+        assert len(albums) == 6
+
+    def test_getAllImageVersions(self):
+        imageversions = list(self.shelf.getAllImageVersions())
+        assert len(imageversions) == 11
+
+    def test_getImageVersionsInDirectory(self):
+        self.shelf.flushImageVersionCache()
+
+        imageversions = list(self.shelf.getImageVersionsInDirectory(u"."))
+        assert len(imageversions) == 0
+
+        # Image versions not in cache.
+        imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
+        assert len(imageversions) == 11
+
+        # Image versions in cache.
+        imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
+        assert len(imageversions) == 11
+
+    def test_deleteAlbum(self):
+        album = self.shelf.getAlbumByTag(u"beta")
+        self.shelf.deleteAlbum(album.getId())
+
+    def test_negativeRootAlbumDeletion(self):
+        root = self.shelf.getRootAlbum()
+        try:
+            self.shelf.deleteAlbum(root.getId())
+        except UndeletableAlbumError:
+            pass
+        else:
+            assert False
+
+    def test_negativeAlbumDeletion(self):
+        try:
+            self.shelf.deleteAlbum(12345678)
+        except AlbumDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_negativeImageCreation(self):
+        try:
+            image = self.shelf.createImage()
+            self.shelf.createImageVersion(
+                image,
+                os.path.join(PICDIR, "arlaharen.png"),
+                ImageVersionType.Original)
+        except ImageVersionExistsError:
+            pass
+        else:
+            assert False
+
+    def test_getImage(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        image = imageversion.getImage()
+        assert self.shelf.getImage(image.getId()) == image
+
+    def test_negativeGetImage(self):
+        try:
+            self.shelf.getImage(12345678)
+        except ImageDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getImageVersion(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert self.shelf.getImageVersion(imageversion.getId()) == imageversion
+
+    def test_negativeGetImageVersion(self):
+        try:
+            self.shelf.getImageVersion(12345678)
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getImageVersionByHash(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert self.shelf.getImageVersionByHash(
+            imageversion.getHash()) == imageversion
+
+    def test_negativeGetImageVersionByHash(self):
+        try:
+            self.shelf.getImageVersion(u"badhash")
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getImageVersionByLocation(self):
+        imageversion1 = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, u"arlaharen.png"))
+        currentDir = os.getcwd()
+        try:
+            os.chdir(PICDIR)
+            imageversion2 = self.shelf.getImageVersionByLocation(
+                u"arlaharen.png")
+        finally:
+            os.chdir(currentDir)
+        assert imageversion1 == imageversion2
+
+    def test_negativeGetImageVersionByHash(self):
+        try:
+            self.shelf.getImageVersionByLocation(u"/bad/location")
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_deleteImage(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        imageid = imageversion.getImage().getId()
+        self.shelf.deleteImage(imageid)
+        try:
+            self.shelf.getImageVersionByLocation(
+                os.path.join(PICDIR, "arlaharen.png"))
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_negativeImageDeletion(self):
+        try:
+            self.shelf.deleteImage(12345678)
+        except ImageDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_deleteImageVersion(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        self.shelf.deleteImageVersion(imageversion.getId())
+
+    def test_negativeImageVersionDeletion(self):
+        try:
+            self.shelf.deleteImageVersion(12345678)
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getObject(self):
+        rootalbum = self.shelf.getRootAlbum()
+        album = self.shelf.getObject(rootalbum.getId())
+        assert album == rootalbum
+
+    def test_deleteObject(self):
+        albumid = self.shelf.getAlbumByTag(u"beta").getId()
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        imageid = imageversion.getImage().getId()
+        self.shelf.deleteObject(albumid)
+        self.shelf.deleteObject(imageid)
+        try:
+            self.shelf.getAlbum(albumid)
+        except AlbumDoesNotExistError:
+            pass
+        else:
+            assert False
+        try:
+            self.shelf.getImage(imageid)
+        except ImageDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getAllAttributeNames(self):
+        attrnames = sorted(self.shelf.getAllAttributeNames())
+        assert attrnames == [
+            "cameramake", "cameramodel", "captured", "description",
+            "digitalzoom", "exposurebias", "exposureprogram", "exposuretime",
+            "flash", "fnumber", "focallength", "iso", "orientation", "query",
+            "title"
+            ], attrnames
+
+    def test_getCategory(self):
+        category = self.shelf.getCategory(1)
+        assert category.getId() == 1
+
+    def test_getCategoryByTag(self):
+        category = self.shelf.getCategoryByTag(u"a")
+        assert category.getTag() == u"a"
+
+    def test_negativeCreateCategory(self):
+        try:
+            self.shelf.createCategory(u"a", u"Foo")
+        except CategoryExistsError:
+            pass
+        else:
+            assert False
+
+    def test_deleteCategory(self):
+        category = self.shelf.getCategoryByTag(u"a")
+        self.shelf.deleteCategory(category.getId())
+
+    def test_negativeDeleteCategory(self):
+        try:
+            self.shelf.deleteCategory(12345678)
+        except CategoryDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getRootCategories(self):
+        categories = sorted(
+            self.shelf.getRootCategories(), key=lambda x: x.getTag())
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_events = self.shelf.getCategoryByTag(u"events")
+        cat_locations = self.shelf.getCategoryByTag(u"locations")
+        cat_people = self.shelf.getCategoryByTag(u"people")
+        assert categories == [cat_a, cat_events, cat_locations, cat_people], \
+               categories
+
+class TestCategory(TestShelfFixture):
+    def test_categoryMethods(self):
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        cat_c = self.shelf.getCategoryByTag(u"c")
+        cat_d = self.shelf.getCategoryByTag(u"d")
+
+        assert self.shelf.getCategory(cat_a.getId()) == cat_a
+        cat_a.setTag(u"foo")
+        assert self.shelf.getCategoryByTag(u"foo") == cat_a
+
+        assert cat_a.getDescription() == "A"
+        cat_a.setDescription(u"foo")
+        assert cat_a.getDescription() == "foo"
+
+        a_children = sorted(cat_a.getChildren(), key=lambda x: x.getId())
+        assert a_children == [cat_b, cat_c]
+        b_children = list(cat_b.getChildren())
+        assert b_children == [cat_d]
+        d_children = list(cat_d.getChildren())
+        assert d_children == []
+
+        a_parents = list(cat_a.getParents())
+        assert a_parents == []
+        b_parents = list(cat_b.getParents())
+        assert b_parents == [cat_a]
+        d_parents = sorted(cat_d.getParents(), key=lambda x: x.getTag())
+        assert d_parents == [cat_b, cat_c]
+
+        assert not cat_a.isChildOf(cat_a)
+        assert cat_b.isChildOf(cat_a)
+        assert cat_c.isChildOf(cat_a)
+        assert not cat_d.isChildOf(cat_a)
+        assert cat_a.isChildOf(cat_a, recursive=True)
+        assert cat_b.isChildOf(cat_a, recursive=True)
+        assert cat_c.isChildOf(cat_a, recursive=True)
+        assert cat_d.isChildOf(cat_a, recursive=True)
+
+        assert not cat_d.isParentOf(cat_d)
+        assert cat_b.isParentOf(cat_d)
+        assert cat_c.isParentOf(cat_d)
+        assert not cat_a.isParentOf(cat_d)
+        assert cat_d.isParentOf(cat_d, recursive=True)
+        assert cat_b.isParentOf(cat_d, recursive=True)
+        assert cat_c.isParentOf(cat_d, recursive=True)
+        assert cat_a.isParentOf(cat_d, recursive=True)
+
+    def test_negativeCategoryConnectChild(self):
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        try:
+            cat_a.connectChild(cat_b)
+        except CategoriesAlreadyConnectedError:
+            pass
+        else:
+            assert False
+        try:
+            cat_b.connectChild(cat_a)
+        except CategoryLoopError:
+            pass
+        else:
+            assert False
+
+    def test_categoryDisconnectChild(self):
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        cat_a.disconnectChild(cat_b)
+        assert not cat_a.isParentOf(cat_b)
+
+    def test_negativeCategoryDisconnectChild(self):
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_d = self.shelf.getCategoryByTag(u"d")
+        cat_a.disconnectChild(cat_d) # No exception.
+
+class TestObject(TestShelfFixture):
+    def test_getParents(self):
+        root = self.shelf.getRootAlbum()
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        beta = self.shelf.getAlbumByTag(u"beta")
+        parents = sorted(beta.getParents(), key=lambda x: x.getTag())
+        assert parents == [alpha, root]
+
+    def test_getAttribute(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert orphans.getAttribute(u"title")
+        assert orphans.getAttribute(u"description")
+        orphans.getAttributeMap() # Just populate the cache.
+        assert not orphans.getAttribute(u"nonexisting")
+        assert u"nonexisting" not in orphans.getAttributeMap()
+
+    def test_getAttributeMap(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        map = orphans.getAttributeMap()
+        assert "description" in map
+        assert "title" in map
+
+    def test_getAttributeNames(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        names = sorted(orphans.getAttributeNames())
+        assert names == ["description", "title"]
+
+    def test_setAttribute(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        orphans.setAttribute(u"foo", u"fie") # New.
+        assert orphans.getAttribute(u"foo") == u"fie"
+        assert u"foo" in orphans.getAttributeMap()
+        assert u"foo" in orphans.getAttributeNames()
+        assert orphans.getAttribute(u"title")
+        orphans.setAttribute(u"title", u"gazonk") # Existing
+        assert orphans.getAttribute(u"title") == u"gazonk"
+        assert u"foo" in orphans.getAttributeMap()
+        assert u"foo" in orphans.getAttributeNames()
+
+    def test_deleteAttribute(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        orphans.deleteAttribute(u"nonexisting") # No exception.
+        assert orphans.getAttribute(u"title")
+        orphans.deleteAttribute(u"title")
+        orphans.getAttributeMap() # Just populate the cache.
+        assert not orphans.getAttribute(u"title")
+        assert u"title" not in orphans.getAttributeMap()
+
+    def test_addCategory(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        assert list(orphans.getCategories()) == []
+        orphans.addCategory(cat_a)
+        assert list(orphans.getCategories()) == [cat_a]
+        try:
+            orphans.addCategory(cat_a)
+        except CategoryPresentError:
+            pass
+        else:
+            assert False
+        assert list(orphans.getCategories()) == [cat_a]
+
+    def test_removeCategory(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert list(orphans.getCategories()) == []
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        orphans.addCategory(cat_a)
+        assert list(orphans.getCategories()) == [cat_a]
+        orphans.removeCategory(cat_a)
+        assert list(orphans.getCategories()) == []
+
+    def test_deleteCategoryInvalidatesCategoryCache(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert list(orphans.getCategories()) == []
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        orphans.addCategory(cat_a)
+        assert list(orphans.getCategories()) == [cat_a]
+        self.shelf.deleteCategory(cat_a.getId())
+        assert list(orphans.getCategories()) == []
+
+class TestAlbum(TestShelfFixture):
+    def test_getType(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.getType() == AlbumType.Plain
+
+    def test_isMutable(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.isMutable()
+
+    def test_getTag(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.getTag() == u"alpha"
+
+    def test_setTag(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        alpha.setTag(u"alfa")
+        assert alpha.getTag() == u"alfa"
+
+    def test_getAlbumParents(self):
+        root = self.shelf.getRootAlbum()
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        parents = sorted(alpha.getAlbumParents(), key=lambda x: x.getTag())
+        assert parents == [root]
+
+    def test_isAlbum(self):
+        assert self.shelf.getRootAlbum().isAlbum()
+
+class TestPlainAlbum(TestShelfFixture):
+    def test_getType(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.getType() == AlbumType.Plain
+
+    def test_isMutable(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.isMutable()
+
+    def test_getChildren(self):
+        epsilon = self.shelf.getAlbumByTag(u"epsilon")
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        beta = self.shelf.getAlbumByTag(u"beta")
+        assert list(epsilon.getChildren()) == []
+        alphaChildren = list(alpha.getChildren())
+        assert list(beta.getChildren()) == [alphaChildren[-1]]
+
+    def test_getAlbumChildren(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        beta = self.shelf.getAlbumByTag(u"beta")
+        epsilon = self.shelf.getAlbumByTag(u"epsilon")
+        alphaAlbumChildren = list(alpha.getAlbumChildren())
+        assert alphaAlbumChildren == [beta]
+        assert list(epsilon.getAlbumChildren()) == []
+
+    def test_setChildren(self):
+        root = self.shelf.getRootAlbum()
+        beta = self.shelf.getAlbumByTag(u"beta")
+        assert list(beta.getChildren()) != []
+        beta.setChildren([beta, root])
+        assert list(beta.getChildren()) == [beta, root]
+        beta.setChildren([]) # Break the cycle.
+        assert list(beta.getChildren()) == []
+
+class TestImage(TestShelfFixture):
+    def test_isAlbum(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert not imageversion.getImage().isAlbum()
+
+    def test_getImageVersions(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        image = imageversion.getImage()
+        assert list(image.getImageVersions()) == [imageversion]
+        imageversion2 = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
+        imageversion2.setImage(image)
+        imageversions = set(image.getImageVersions())
+        assert set(image.getImageVersions()) == imageversions
+        self.shelf.deleteImageVersion(imageversion.getId())
+        self.shelf.deleteImageVersion(imageversion2.getId())
+        assert list(image.getImageVersions()) == []
+
+    def test_getPrimaryVersion(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        image = imageversion.getImage()
+        assert image.getPrimaryVersion() == imageversion
+        imageversion2 = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
+        imageversion2.setImage(image)
+        assert image.getPrimaryVersion() == imageversion
+        imageversion2.makePrimary()
+        assert image.getPrimaryVersion() == imageversion2
+
+        newImage = self.shelf.createImage()
+        lastImageVersion = list(image.getImageVersions())[-1]
+        lastImageVersion.setImage(newImage)
+        assert image.getPrimaryVersion() != lastImageVersion
+        assert newImage.getPrimaryVersion() == lastImageVersion
+        lastImageVersion.setImage(image)
+
+        self.shelf.deleteImageVersion(imageversion2.getId())
+        assert image.getPrimaryVersion() == imageversion
+        self.shelf.deleteImageVersion(imageversion.getId())
+        assert image.getPrimaryVersion() == None
+
+class TestImageVersion(TestShelfFixture):
+    def test_getType(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getType() == ImageVersionType.Original
+        imageversion.setType(ImageVersionType.Important)
+        assert imageversion.getType() == ImageVersionType.Important
+        imageversion.setType(ImageVersionType.Other)
+        assert imageversion.getType() == ImageVersionType.Other
+        imageversion.setType(ImageVersionType.Original)
+        assert imageversion.getType() == ImageVersionType.Original
+
+    # ImageVersion.makePrimary tested in TestImage.test_getPrimaryVersion.
+    # ImageVersion.setImage tested in TestImage.test_getPrimaryVersion.
+    # ImageVersion.setType tested in TestImageVersion.test_getType.
+    # ImageVersion.setComment tested in TestImageVersion.test_getComment.
+
+    def test_getComment(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getComment() == ""
+        imageversion.setComment(u"a comment")
+        assert imageversion.getComment() == u"a comment"
+
+    def test_getHash(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getHash() == "39a1266d2689f53d48b09a5e0ca0af1f"
+
+    def test_getLocation(self):
+        location = os.path.join(PICDIR, "arlaharen.png")
+        imageversion = self.shelf.getImageVersionByLocation(location)
+        assert imageversion.getLocation() == os.path.realpath(location)
+
+    def test_getModificationTime(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        t = imageversion.getModificationTime()
+        assert isinstance(t, int)
+        assert t > 0
+
+    def test_getSize(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getSize() == (304, 540)
+
+    def test_contentChanged(self):
+        path = os.path.join(PICDIR, "arlaharen.png")
+        imageversion = self.shelf.getImageVersionByLocation(path)
+        self.shelf.deleteImageVersion(imageversion.getId())
+        newpath = u"tmp.png"
+        try:
+            shutil.copy2(path, newpath)
+            newimage = self.shelf.createImage()
+            newimageversion = self.shelf.createImageVersion(
+                newimage, newpath, ImageVersionType.Original)
+            oldmtime = imageversion.getModificationTime()
+            f = open(newpath, "a")
+            f.write("foo")
+            f.close()
+            newimageversion.contentChanged()
+            assert newimageversion.getHash() == "b27312d9739c0edfd115f824be244b75"
+            assert newimageversion.getModificationTime() > oldmtime
+        finally:
+            try:
+                os.unlink(newpath)
+            except OSError:
+                pass
+
+    def test_locationChanged(self):
+        location = os.path.join(PICDIR, "arlaharen.png")
+        imageversion = self.shelf.getImageVersionByLocation(location)
+        imageversion.locationChanged(u"/foo/../bar")
+        assert imageversion.getLocation() == "/bar"
+
+    def test_importExifTags(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        imageversion.importExifTags(True) # TODO: Test more.
+        imageversion.importExifTags(False) # TODO: Test more.
+
+class TestOrphansAlbum(TestShelfFixture):
+    def test_getType(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert orphans.getType() == AlbumType.Orphans
+
+    def test_isMutable(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert not orphans.isMutable()
+
+    def test_getChildren(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert len(list(orphans.getChildren())) == 2
+
+    def test_getAlbumChildren(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        epsilon = self.shelf.getAlbumByTag(u"epsilon")
+        assert list(orphans.getAlbumChildren()) == [epsilon]
+
+    def test_setChildren(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        try:
+            orphans.setChildren([])
+        except UnsettableChildrenError:
+            pass
+        else:
+            assert False
+
+    def test_isAlbum(self):
+        assert self.shelf.getAlbumByTag(u"orphans").isAlbum()
+
+class TestSearchAlbum(TestShelfFixture):
+    def test_getType(self):
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert zeta.getType() == AlbumType.Search
+
+    def test_isMutable(self):
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert not zeta.isMutable()
+
+    def test_getChildren(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        image1, image2 = list(alpha.getChildren())[0:2]
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        image1.addCategory(cat_a)
+        image2.addCategory(cat_b)
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert zeta
+        zeta.setAttribute(u"query", u"b")
+        children = zeta.getChildren()
+        assert list(children) == [image2]
+
+    def test_getAlbumChildren(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        image1, image2 = list(alpha.getChildren())[0:2]
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        image1.addCategory(cat_a)
+        image2.addCategory(cat_b)
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert zeta
+        zeta.setAttribute(u"query", u"b")
+        children = zeta.getAlbumChildren()
+        l = list(children)
+        assert list(children) == []
+
+    def test_setChildren(self):
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        try:
+            zeta.setChildren([])
+        except UnsettableChildrenError:
+            pass
+        else:
+            assert False
+
+    def test_isAlbum(self):
+        assert self.shelf.getAlbumByTag(u"zeta").isAlbum()
+
+######################################################################
+
+removeTmpDb()
+if __name__ == "__main__":
+    unittest.main()