Пример #1
0
    def test_dictionary(self):
        s1 = DictDatastore()
        s2 = DictDatastore()
        s3 = DictDatastore()
        stores = [s1, s2, s3]

        self.subtest_simple(stores)
Пример #2
0
    def test_simple(self):
        from datastore.core.basic import NamespaceDatastore

        s1 = NamespaceDatastore(Key('a'), DictDatastore())
        s2 = NamespaceDatastore(Key('b'), DictDatastore())
        s3 = NamespaceDatastore(Key('c'), DictDatastore())
        stores = [s1, s2, s3]

        self.subtest_simple(stores)
Пример #3
0
    def test_simple(self):
        from datastore.core.basic import LowercaseKeyDatastore

        s1 = LowercaseKeyDatastore(DictDatastore())
        s2 = LowercaseKeyDatastore(DictDatastore())
        s3 = LowercaseKeyDatastore(DictDatastore())
        stores = [s1, s2, s3]

        self.subtest_simple(stores)
Пример #4
0
    def test_simple(self):
        from datastore.core.basic import KeyTransformDatastore

        s1 = KeyTransformDatastore(DictDatastore())
        s2 = KeyTransformDatastore(DictDatastore())
        s3 = KeyTransformDatastore(DictDatastore())
        stores = [s1, s2, s3]

        self.subtest_simple(stores)
Пример #5
0
    def test_simple(self):
        from datastore.core.basic import SymlinkDatastore

        s1 = SymlinkDatastore(DictDatastore())
        s2 = SymlinkDatastore(DictDatastore())
        s3 = SymlinkDatastore(DictDatastore())
        s4 = SymlinkDatastore(DictDatastore())
        stores = [s1, s2, s3, s4]

        self.subtest_simple(stores)
Пример #6
0
    def test_simple(self):
        from datastore.core.basic import NestedPathDatastore

        s1 = NestedPathDatastore(DictDatastore())
        s2 = NestedPathDatastore(DictDatastore(), depth=2)
        s3 = NestedPathDatastore(DictDatastore(), length=2)
        s4 = NestedPathDatastore(DictDatastore(), length=1, depth=2)
        stores = [s1, s2, s3, s4]

        self.subtest_simple(stores)
Пример #7
0
    def test_namespace(self):
        from datastore.core.basic import NamespaceDatastore

        k1 = Key('/c/d')
        k2 = Key('/a/b')
        k3 = Key('/a/b/c/d')

        ds = DictDatastore()
        nd = NamespaceDatastore(k2, ds)

        ds.put(k1, 'cd')
        ds.put(k3, 'abcd')

        self.assertEqual(ds.get(k1), 'cd')
        self.assertFalse(ds.contains(k2))
        self.assertEqual(ds.get(k3), 'abcd')

        self.assertEqual(nd.get(k1), 'abcd')
        self.assertFalse(nd.contains(k2))
        self.assertFalse(nd.contains(k3))

        def test(key, val):
            nd.put(key, val)
            self.assertEqual(nd.get(key), val)
            self.assertFalse(ds.contains(key))
            self.assertFalse(nd.contains(k2.child(key)))
            self.assertEqual(ds.get(k2.child(key)), val)

        for i in range(0, 10):
            test(Key(str(i)), 'val%d' % i)
Пример #8
0
    def test_directory_simple(self):
        from datastore.core.basic import DirectoryDatastore

        ds = DirectoryDatastore(DictDatastore())

        # initialize directory at /foo
        dir_key = Key('/foo')
        ds.directory(dir_key)

        # adding directory entries
        bar_key = Key('/foo/bar')
        baz_key = Key('/foo/baz')
        ds.directoryAdd(dir_key, bar_key)
        ds.directoryAdd(dir_key, baz_key)
        keys = list(ds.directoryRead(dir_key))
        self.assertEqual(keys, [bar_key, baz_key])

        # removing directory entries
        ds.directoryRemove(dir_key, bar_key)
        keys = list(ds.directoryRead(dir_key))
        self.assertEqual(keys, [baz_key])

        ds.directoryRemove(dir_key, baz_key)
        keys = list(ds.directoryRead(dir_key))
        self.assertEqual(keys, [])

        # generator
        with self.assertRaises(StopIteration):
            gen = ds.directoryRead(dir_key)
            gen.next()
Пример #9
0
    def test_simple(self):
        from datastore.core.basic import CacheShimDatastore
        from datastore.core.basic import NullDatastore

        class NullMinusQueryDatastore(NullDatastore):
            def query(self, query):
                raise NotImplementedError

        # make sure the cache is used
        s1 = CacheShimDatastore(NullMinusQueryDatastore(),
                                cache=DictDatastore())

        # make sure the cache is not relief upon
        s2 = CacheShimDatastore(DictDatastore(), cache=NullDatastore())

        # make sure the cache works in tandem
        s3 = CacheShimDatastore(DictDatastore(), cache=DictDatastore())

        self.subtest_simple([s1, s2, s3])
Пример #10
0
    def test_symlink_recursive(self):
        from datastore.core.basic import SymlinkDatastore

        dds = DictDatastore()
        sds1 = SymlinkDatastore(dds)
        sds2 = SymlinkDatastore(sds1)

        a = Key('/A')
        b = Key('/B')

        sds2.put(a, 1)
        self.assertEqual(sds2.get(a), 1)
        self.assertEqual(sds2.get(b), None)
        self.assertNotEqual(sds2.get(b), sds2.get(a))

        sds2.link(a, b)
        self.assertEqual(sds2.get(a), 1)
        self.assertEqual(sds2.get(b), 1)
        self.assertEqual(sds2.get(a), sds2.get(b))
        self.assertEqual(sds1.get(a), sds1.get(b))

        sds2.link(a, b)
        self.assertEqual(sds2.get(a), 1)
        self.assertEqual(sds2.get(b), 1)
        self.assertEqual(sds2.get(a), sds2.get(b))
        self.assertEqual(sds1.get(a), sds1.get(b))

        sds2.link(a, b)
        self.assertEqual(sds2.get(a), 1)
        self.assertEqual(sds2.get(b), 1)
        self.assertEqual(sds2.get(a), sds2.get(b))
        self.assertEqual(sds1.get(a), sds1.get(b))

        sds2.put(b, 2)
        self.assertEqual(sds2.get(a), 2)
        self.assertEqual(sds2.get(b), 2)
        self.assertEqual(sds2.get(a), sds2.get(b))
        self.assertEqual(sds1.get(a), sds1.get(b))

        sds2.delete(a)
        self.assertEqual(sds2.get(a), None)
        self.assertEqual(sds2.get(b), None)
        self.assertEqual(sds2.get(b), sds2.get(a))

        sds2.put(a, 3)
        self.assertEqual(sds2.get(a), 3)
        self.assertEqual(sds2.get(b), 3)
        self.assertEqual(sds2.get(b), sds2.get(a))

        sds2.delete(b)
        self.assertEqual(sds2.get(a), 3)
        self.assertEqual(sds2.get(b), None)
        self.assertNotEqual(sds2.get(b), sds2.get(a))
Пример #11
0
    def test_lowercase(self):
        from datastore.core.basic import LowercaseKeyDatastore

        ds = DictDatastore()
        lds = LowercaseKeyDatastore(ds)

        k1 = Key('hello')
        k2 = Key('HELLO')
        k3 = Key('HeLlo')

        ds.put(k1, 'world')
        ds.put(k2, 'WORLD')

        self.assertEqual(ds.get(k1), 'world')
        self.assertEqual(ds.get(k2), 'WORLD')
        self.assertFalse(ds.contains(k3))

        self.assertEqual(lds.get(k1), 'world')
        self.assertEqual(lds.get(k2), 'world')
        self.assertEqual(lds.get(k3), 'world')

        def test(key, val):
            lds.put(key, val)
            self.assertEqual(lds.get(k1), val)
            self.assertEqual(lds.get(k2), val)
            self.assertEqual(lds.get(k3), val)

        test(k1, 'a')
        test(k2, 'b')
        test(k3, 'c')
Пример #12
0
    def test_lowercase_transform(self):
        from datastore.core.basic import KeyTransformDatastore

        def transform(key):
            return Key(str(key).lower())

        ds = DictDatastore()
        lds = KeyTransformDatastore(ds, keytransform=transform)

        k1 = Key('hello')
        k2 = Key('HELLO')
        k3 = Key('HeLlo')

        ds.put(k1, 'world')
        ds.put(k2, 'WORLD')

        self.assertEqual(ds.get(k1), 'world')
        self.assertEqual(ds.get(k2), 'WORLD')
        self.assertFalse(ds.contains(k3))

        self.assertEqual(lds.get(k1), 'world')
        self.assertEqual(lds.get(k2), 'world')
        self.assertEqual(lds.get(k3), 'world')

        def test(key, val):
            lds.put(key, val)
            self.assertEqual(lds.get(k1), val)
            self.assertEqual(lds.get(k2), val)
            self.assertEqual(lds.get(k3), val)

        test(k1, 'a')
        test(k2, 'b')
        test(k3, 'c')
Пример #13
0
    def test_simple(self):
        from datastore.core.basic import LoggingDatastore

        class NullLogger(logging.getLoggerClass()):
            def debug(self, *args, **kwargs):
                pass

            def info(self, *args, **kwargs):
                pass

            def warning(self, *args, **kwargs):
                pass

            def error(self, *args, **kwargs):
                pass

            def critical(self, *args, **kwargs):
                pass

        s1 = LoggingDatastore(DictDatastore(), logger=NullLogger('null'))
        s2 = LoggingDatastore(DictDatastore())
        self.subtest_simple([s1, s2])
    def subtest_serializer_shim(self, serializer, numelems=100):

        child = DictDatastore()
        shim = SerializerShimDatastore(child, serializer=serializer)

        values_raw = [{'value': i} for i in range(0, numelems)]

        values_serial = [serializer.dumps(v) for v in values_raw]
        values_deserial = [serializer.loads(v) for v in values_serial]
        self.assertEqual(values_deserial, values_raw)

        for value in values_raw:
            key = Key(value['value'])
            value_serialized = serializer.dumps(value)

            # should not be there yet
            self.assertFalse(shim.contains(key))
            self.assertEqual(shim.get(key), None)

            # put (should be there)
            shim.put(key, value)
            self.assertTrue(shim.contains(key))
            self.assertEqual(shim.get(key), value)

            # make sure underlying DictDatastore is storing the serialized value.
            self.assertEqual(shim.child_datastore.get(key), value_serialized)

            # delete (should not be there)
            shim.delete(key)
            self.assertFalse(shim.contains(key))
            self.assertEqual(shim.get(key), None)

            # make sure manipulating underlying DictDatastore works equally well.
            shim.child_datastore.put(key, value_serialized)
            self.assertTrue(shim.contains(key))
            self.assertEqual(shim.get(key), value)

            shim.child_datastore.delete(key)
            self.assertFalse(shim.contains(key))
            self.assertEqual(shim.get(key), None)

        if serializer is not bson:  # bson can't handle non mapping types
            self.subtest_simple([shim], numelems)
Пример #15
0
    def test_directory_init(self):
        from datastore.core.basic import DirectoryDatastore

        ds = DirectoryDatastore(DictDatastore())

        # initialize directory at /foo
        dir_key = Key('/foo')
        ds.directory(dir_key)
        self.assertEqual(ds.get(dir_key), [])

        # can add to dir
        bar_key = Key('/foo/bar')
        ds.directoryAdd(dir_key, bar_key)
        self.assertEqual(ds.get(dir_key), [str(bar_key)])

        # re-init does not wipe out directory at /foo
        dir_key = Key('/foo')
        ds.directory(dir_key)
        self.assertEqual(ds.get(dir_key), [str(bar_key)])
Пример #16
0
    def test_directory_double_add(self):
        from datastore.core.basic import DirectoryDatastore

        ds = DirectoryDatastore(DictDatastore())

        # initialize directory at /foo
        dir_key = Key('/foo')
        ds.directory(dir_key)

        # adding directory entries
        bar_key = Key('/foo/bar')
        baz_key = Key('/foo/baz')
        ds.directoryAdd(dir_key, bar_key)
        ds.directoryAdd(dir_key, baz_key)
        ds.directoryAdd(dir_key, bar_key)
        ds.directoryAdd(dir_key, baz_key)
        ds.directoryAdd(dir_key, baz_key)
        ds.directoryAdd(dir_key, bar_key)

        keys = list(ds.directoryRead(dir_key))
        self.assertEqual(keys, [bar_key, baz_key])
Пример #17
0
    def test_sharded(self, numelems=1000):
        from datastore.core.basic import ShardedDatastore

        s1 = DictDatastore()
        s2 = DictDatastore()
        s3 = DictDatastore()
        s4 = DictDatastore()
        s5 = DictDatastore()
        stores = [s1, s2, s3, s4, s5]
        hash = lambda key: int(key.name) * len(stores) / numelems
        sharded = ShardedDatastore(stores, shardingfn=hash)
        sumlens = lambda stores: sum(map(lambda s: len(s), stores))

        def checkFor(key, value, sharded, shard=None):
            correct_shard = sharded._stores[hash(key) % len(sharded._stores)]

            for s in sharded._stores:
                if shard and s == shard:
                    self.assertTrue(s.contains(key))
                    self.assertEqual(s.get(key), value)
                else:
                    self.assertFalse(s.contains(key))

            if correct_shard == shard:
                self.assertTrue(sharded.contains(key))
                self.assertEqual(sharded.get(key), value)
            else:
                self.assertFalse(sharded.contains(key))

        self.assertEqual(sumlens(stores), 0)
        # test all correct.
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            shard = stores[hash(key) % len(stores)]
            checkFor(key, value, sharded)
            shard.put(key, value)
            checkFor(key, value, sharded, shard)
        self.assertEqual(sumlens(stores), numelems)

        # ensure its in the same spots.
        for i in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            shard = stores[hash(key) % len(stores)]
            checkFor(key, value, sharded, shard)
            shard.put(key, value)
            checkFor(key, value, sharded, shard)
        self.assertEqual(sumlens(stores), numelems)

        # ensure its in the same spots.
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            shard = stores[hash(key) % len(stores)]
            checkFor(key, value, sharded, shard)
            sharded.put(key, value)
            checkFor(key, value, sharded, shard)
        self.assertEqual(sumlens(stores), numelems)

        # ensure its in the same spots.
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            shard = stores[hash(key) % len(stores)]
            checkFor(key, value, sharded, shard)
            if value % 2 == 0:
                shard.delete(key)
            else:
                sharded.delete(key)
            checkFor(key, value, sharded)
        self.assertEqual(sumlens(stores), 0)

        # try out adding it to the wrong shards.
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            incorrect_shard = stores[(hash(key) + 1) % len(stores)]
            checkFor(key, value, sharded)
            incorrect_shard.put(key, value)
            checkFor(key, value, sharded, incorrect_shard)
        self.assertEqual(sumlens(stores), numelems)

        # ensure its in the same spots.
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            incorrect_shard = stores[(hash(key) + 1) % len(stores)]
            checkFor(key, value, sharded, incorrect_shard)
            incorrect_shard.put(key, value)
            checkFor(key, value, sharded, incorrect_shard)
        self.assertEqual(sumlens(stores), numelems)

        # this wont do anything
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            incorrect_shard = stores[(hash(key) + 1) % len(stores)]
            checkFor(key, value, sharded, incorrect_shard)
            sharded.delete(key)
            checkFor(key, value, sharded, incorrect_shard)
        self.assertEqual(sumlens(stores), numelems)

        # this will place it correctly.
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            incorrect_shard = stores[(hash(key) + 1) % len(stores)]
            correct_shard = stores[(hash(key)) % len(stores)]
            checkFor(key, value, sharded, incorrect_shard)
            sharded.put(key, value)
            incorrect_shard.delete(key)
            checkFor(key, value, sharded, correct_shard)
        self.assertEqual(sumlens(stores), numelems)

        # this will place it correctly.
        for value in range(0, numelems):
            key = Key('/fdasfdfdsafdsafdsa/%d' % value)
            correct_shard = stores[(hash(key)) % len(stores)]
            checkFor(key, value, sharded, correct_shard)
            sharded.delete(key)
            checkFor(key, value, sharded)
        self.assertEqual(sumlens(stores), 0)

        self.subtest_simple([sharded])
Пример #18
0
    def test_tiered(self):
        from datastore.core.basic import TieredDatastore

        s1 = DictDatastore()
        s2 = DictDatastore()
        s3 = DictDatastore()
        ts = TieredDatastore([s1, s2, s3])

        k1 = Key('1')
        k2 = Key('2')
        k3 = Key('3')

        s1.put(k1, '1')
        s2.put(k2, '2')
        s3.put(k3, '3')

        self.assertTrue(s1.contains(k1))
        self.assertFalse(s2.contains(k1))
        self.assertFalse(s3.contains(k1))
        self.assertTrue(ts.contains(k1))

        self.assertEqual(ts.get(k1), '1')
        self.assertEqual(s1.get(k1), '1')
        self.assertFalse(s2.contains(k1))
        self.assertFalse(s3.contains(k1))

        self.assertFalse(s1.contains(k2))
        self.assertTrue(s2.contains(k2))
        self.assertFalse(s3.contains(k2))
        self.assertTrue(ts.contains(k2))

        self.assertEqual(s2.get(k2), '2')
        self.assertFalse(s1.contains(k2))
        self.assertFalse(s3.contains(k2))

        self.assertEqual(ts.get(k2), '2')
        self.assertEqual(s1.get(k2), '2')
        self.assertEqual(s2.get(k2), '2')
        self.assertFalse(s3.contains(k2))

        self.assertFalse(s1.contains(k3))
        self.assertFalse(s2.contains(k3))
        self.assertTrue(s3.contains(k3))
        self.assertTrue(ts.contains(k3))

        self.assertEqual(s3.get(k3), '3')
        self.assertFalse(s1.contains(k3))
        self.assertFalse(s2.contains(k3))

        self.assertEqual(ts.get(k3), '3')
        self.assertEqual(s1.get(k3), '3')
        self.assertEqual(s2.get(k3), '3')
        self.assertEqual(s3.get(k3), '3')

        ts.delete(k1)
        ts.delete(k2)
        ts.delete(k3)

        self.assertFalse(ts.contains(k1))
        self.assertFalse(ts.contains(k2))
        self.assertFalse(ts.contains(k3))

        self.subtest_simple([ts])
Пример #19
0
    def test_simple(self):
        from datastore.core.basic import DirectoryTreeDatastore

        s1 = DirectoryTreeDatastore(DictDatastore())
        s2 = DirectoryTreeDatastore(DictDatastore())
        self.subtest_simple([s1, s2])
Пример #20
0
    def test_reverse_transform(self):
        from datastore.core.basic import KeyTransformDatastore

        def transform(key):
            return key.reverse

        ds = DictDatastore()
        kt = KeyTransformDatastore(ds, keytransform=transform)

        k1 = Key('/a/b/c')
        k2 = Key('/c/b/a')
        self.assertFalse(ds.contains(k1))
        self.assertFalse(ds.contains(k2))
        self.assertFalse(kt.contains(k1))
        self.assertFalse(kt.contains(k2))

        ds.put(k1, 'abc')
        self.assertEqual(ds.get(k1), 'abc')
        self.assertFalse(ds.contains(k2))
        self.assertFalse(kt.contains(k1))
        self.assertEqual(kt.get(k2), 'abc')

        kt.put(k1, 'abc')
        self.assertEqual(ds.get(k1), 'abc')
        self.assertEqual(ds.get(k2), 'abc')
        self.assertEqual(kt.get(k1), 'abc')
        self.assertEqual(kt.get(k2), 'abc')

        ds.delete(k1)
        self.assertFalse(ds.contains(k1))
        self.assertEqual(ds.get(k2), 'abc')
        self.assertEqual(kt.get(k1), 'abc')
        self.assertFalse(kt.contains(k2))

        kt.delete(k1)
        self.assertFalse(ds.contains(k1))
        self.assertFalse(ds.contains(k2))
        self.assertFalse(kt.contains(k1))
        self.assertFalse(kt.contains(k2))
Пример #21
0
    def subtest_nested_path_ds(self, **kwargs):
        from datastore.core.basic import NestedPathDatastore

        k1 = kwargs.pop('k1')
        k2 = kwargs.pop('k2')
        k3 = kwargs.pop('k3')
        k4 = kwargs.pop('k4')

        ds = DictDatastore()
        np = NestedPathDatastore(ds, **kwargs)

        self.assertFalse(ds.contains(k1))
        self.assertFalse(ds.contains(k2))
        self.assertFalse(ds.contains(k3))
        self.assertFalse(ds.contains(k4))

        self.assertFalse(np.contains(k1))
        self.assertFalse(np.contains(k2))
        self.assertFalse(np.contains(k3))
        self.assertFalse(np.contains(k4))

        np.put(k1, k1)
        np.put(k2, k2)

        self.assertFalse(ds.contains(k1))
        self.assertFalse(ds.contains(k2))
        self.assertTrue(ds.contains(k3))
        self.assertTrue(ds.contains(k4))

        self.assertTrue(np.contains(k1))
        self.assertTrue(np.contains(k2))
        self.assertFalse(np.contains(k3))
        self.assertFalse(np.contains(k4))

        self.assertEqual(np.get(k1), k1)
        self.assertEqual(np.get(k2), k2)
        self.assertEqual(ds.get(k3), k1)
        self.assertEqual(ds.get(k4), k2)

        np.delete(k1)
        np.delete(k2)

        self.assertFalse(ds.contains(k1))
        self.assertFalse(ds.contains(k2))
        self.assertFalse(ds.contains(k3))
        self.assertFalse(ds.contains(k4))

        self.assertFalse(np.contains(k1))
        self.assertFalse(np.contains(k2))
        self.assertFalse(np.contains(k3))
        self.assertFalse(np.contains(k4))

        ds.put(k3, k1)
        ds.put(k4, k2)

        self.assertFalse(ds.contains(k1))
        self.assertFalse(ds.contains(k2))
        self.assertTrue(ds.contains(k3))
        self.assertTrue(ds.contains(k4))

        self.assertTrue(np.contains(k1))
        self.assertTrue(np.contains(k2))
        self.assertFalse(np.contains(k3))
        self.assertFalse(np.contains(k4))

        self.assertEqual(np.get(k1), k1)
        self.assertEqual(np.get(k2), k2)
        self.assertEqual(ds.get(k3), k1)
        self.assertEqual(ds.get(k4), k2)

        ds.delete(k3)
        ds.delete(k4)

        self.assertFalse(ds.contains(k1))
        self.assertFalse(ds.contains(k2))
        self.assertFalse(ds.contains(k3))
        self.assertFalse(ds.contains(k4))

        self.assertFalse(np.contains(k1))
        self.assertFalse(np.contains(k2))
        self.assertFalse(np.contains(k3))
        self.assertFalse(np.contains(k4))
Пример #22
0
    def test_symlink_internals(self):
        from datastore.core.basic import SymlinkDatastore

        dds = DictDatastore()
        sds = SymlinkDatastore(dds)

        a = Key('/A')
        b = Key('/B')
        c = Key('/C')
        d = Key('/D')

        lva = sds._link_value_for_key(a)
        lvb = sds._link_value_for_key(b)
        lvc = sds._link_value_for_key(c)
        lvd = sds._link_value_for_key(d)

        # helper to check queries
        sds_query = lambda: list(sds.query(Query(Key('/'))))
        dds_query = lambda: list(dds.query(Query(Key('/'))))

        # ensure _link_value_for_key and _link_for_value work
        self.assertEqual(lva, str(a.child(sds.sentinel)))
        self.assertEqual(a, sds._link_for_value(lva))

        # adding a value should work like usual
        sds.put(a, 1)
        self.assertEqual(sds.get(a), 1)
        self.assertEqual(sds.get(b), None)
        self.assertNotEqual(sds.get(b), sds.get(a))

        self.assertEqual(dds.get(a), 1)
        self.assertEqual(dds.get(b), None)

        self.assertEqual(sds_query(), [1])
        self.assertEqual(dds_query(), [1])

        # _follow_link(sds._link_value_for_key(a)) should == get(a)
        self.assertEqual(sds._follow_link(lva), 1)
        self.assertEqual(list(sds._follow_link_gen([lva])), [1])

        # linking keys should work
        sds.link(a, b)
        self.assertEqual(sds.get(a), 1)
        self.assertEqual(sds.get(b), 1)
        self.assertEqual(sds.get(a), sds.get(b))

        self.assertEqual(dds.get(a), 1)
        self.assertEqual(dds.get(b), lva)

        self.assertEqual(sds_query(), [1, 1])
        self.assertEqual(dds_query(), [lva, 1])

        # changing link should affect source
        sds.put(b, 2)
        self.assertEqual(sds.get(a), 2)
        self.assertEqual(sds.get(b), 2)
        self.assertEqual(sds.get(a), sds.get(b))

        self.assertEqual(dds.get(a), 2)
        self.assertEqual(dds.get(b), lva)

        self.assertEqual(sds_query(), [2, 2])
        self.assertEqual(dds_query(), [lva, 2])

        # deleting source should affect link
        sds.delete(a)
        self.assertEqual(sds.get(a), None)
        self.assertEqual(sds.get(b), None)
        self.assertEqual(sds.get(b), sds.get(a))

        self.assertEqual(dds.get(a), None)
        self.assertEqual(dds.get(b), lva)

        self.assertEqual(sds_query(), [None])
        self.assertEqual(dds_query(), [lva])

        # putting back source should yield working link
        sds.put(a, 3)
        self.assertEqual(sds.get(a), 3)
        self.assertEqual(sds.get(b), 3)
        self.assertEqual(sds.get(b), sds.get(a))

        self.assertEqual(dds.get(a), 3)
        self.assertEqual(dds.get(b), lva)

        self.assertEqual(sds_query(), [3, 3])
        self.assertEqual(dds_query(), [lva, 3])

        # deleting link should not affect source
        sds.delete(b)
        self.assertEqual(sds.get(a), 3)
        self.assertEqual(sds.get(b), None)
        self.assertNotEqual(sds.get(b), sds.get(a))

        self.assertEqual(dds.get(a), 3)
        self.assertEqual(dds.get(b), None)

        self.assertEqual(sds_query(), [3])
        self.assertEqual(dds_query(), [3])

        # linking should bring back to normal
        sds.link(a, b)
        self.assertEqual(sds.get(a), 3)
        self.assertEqual(sds.get(b), 3)
        self.assertEqual(sds.get(b), sds.get(a))

        self.assertEqual(dds.get(a), 3)
        self.assertEqual(dds.get(b), lva)

        self.assertEqual(sds_query(), [3, 3])
        self.assertEqual(dds_query(), [lva, 3])

        # Adding another link should not affect things.
        sds.link(a, c)
        self.assertEqual(sds.get(a), 3)
        self.assertEqual(sds.get(b), 3)
        self.assertEqual(sds.get(c), 3)
        self.assertEqual(sds.get(a), sds.get(b))
        self.assertEqual(sds.get(a), sds.get(c))

        self.assertEqual(dds.get(a), 3)
        self.assertEqual(dds.get(b), lva)
        self.assertEqual(dds.get(c), lva)

        self.assertEqual(sds_query(), [3, 3, 3])
        self.assertEqual(dds_query(), [lva, lva, 3])

        # linking should be transitive
        sds.link(b, c)
        sds.link(c, d)
        self.assertEqual(sds.get(a), 3)
        self.assertEqual(sds.get(b), 3)
        self.assertEqual(sds.get(c), 3)
        self.assertEqual(sds.get(d), 3)
        self.assertEqual(sds.get(a), sds.get(b))
        self.assertEqual(sds.get(a), sds.get(c))
        self.assertEqual(sds.get(a), sds.get(d))

        self.assertEqual(dds.get(a), 3)
        self.assertEqual(dds.get(b), lva)
        self.assertEqual(dds.get(c), lvb)
        self.assertEqual(dds.get(d), lvc)

        self.assertEqual(sds_query(), [3, 3, 3, 3])
        self.assertEqual(set(dds_query()), set([3, lva, lvb, lvc]))

        self.assertRaises(AssertionError, sds.link, d, a)