Exemple #1
0
def do_incremental_backup(options, reposz, repofiles):
    options.full = False
    dest = os.path.join(options.repository, gen_filename(options))
    if os.path.exists(dest):
        raise WouldOverwriteFiles('Cannot overwrite existing file: %s' % dest)
    # Find the file position of the last completed transaction.
    fs = FileStorage(options.file, read_only=True)
    # Note that the FileStorage ctor calls read_index() which scans the file
    # and returns "the position just after the last valid transaction record".
    # getSize() then returns this position, which is exactly what we want,
    # because we only want to copy stuff from the beginning of the file to the
    # last valid transaction record.
    pos = fs.getSize()
    log('writing index')
    index_file = os.path.join(options.repository,
                              gen_filename(options, '.index'))
    fs._index.save(pos, index_file)
    fs.close()
    log('writing incremental: %s bytes to %s',  pos-reposz, dest)
    sum = copyfile(options, dest, reposz, pos - reposz)
    # The first file in repofiles points to the last full backup.  Use this to
    # get the .dat file and append the information for this incrementatl to
    # that file.
    fullfile = repofiles[0]
    datfile = os.path.splitext(fullfile)[0] + '.dat'
    # This .dat file better exist.  Let the exception percolate if not.
    fp = open(datfile, 'a')
    print >> fp, dest, reposz, pos, sum
    fp.flush()
    os.fsync(fp.fileno())
    fp.close()
Exemple #2
0
def do_full_backup(options):
    options.full = True
    dest = os.path.join(options.repository, gen_filename(options))
    if os.path.exists(dest):
        raise WouldOverwriteFiles('Cannot overwrite existing file: %s' % dest)
    # Find the file position of the last completed transaction.
    fs = FileStorage(options.file, read_only=True)
    # Note that the FileStorage ctor calls read_index() which scans the file
    # and returns "the position just after the last valid transaction record".
    # getSize() then returns this position, which is exactly what we want,
    # because we only want to copy stuff from the beginning of the file to the
    # last valid transaction record.
    pos = fs.getSize()
    # Save the storage index into the repository
    index_file = os.path.join(options.repository,
                              gen_filename(options, '.index'))
    log('writing index')
    fs._index.save(pos, index_file)
    fs.close()
    log('writing full backup: %s bytes to %s', pos, dest)
    sum = copyfile(options, dest, 0, pos)
    # Write the data file for this full backup
    datfile = os.path.splitext(dest)[0] + '.dat'
    fp = open(datfile, 'w')
    print >> fp, dest, 0, pos, sum
    fp.flush()
    os.fsync(fp.fileno())
    fp.close()
    if options.killold:
        delete_old_backups(options)
Exemple #3
0
def do_incremental_backup(options, reposz, repofiles):
    options.full = False
    dest = os.path.join(options.repository, gen_filename(options))
    if os.path.exists(dest):
        raise WouldOverwriteFiles('Cannot overwrite existing file: %s' % dest)
    # Find the file position of the last completed transaction.
    fs = FileStorage(options.file, read_only=True)
    # Note that the FileStorage ctor calls read_index() which scans the file
    # and returns "the position just after the last valid transaction record".
    # getSize() then returns this position, which is exactly what we want,
    # because we only want to copy stuff from the beginning of the file to the
    # last valid transaction record.
    pos = fs.getSize()
    log('writing index')
    index_file = os.path.join(options.repository,
                              gen_filename(options, '.index'))
    fs._index.save(pos, index_file)
    fs.close()
    log('writing incremental: %s bytes to %s',  pos-reposz, dest)
    sum = copyfile(options, dest, reposz, pos - reposz)
    # The first file in repofiles points to the last full backup.  Use this to
    # get the .dat file and append the information for this incrementatl to
    # that file.
    fullfile = repofiles[0]
    datfile = os.path.splitext(fullfile)[0] + '.dat'
    # This .dat file better exist.  Let the exception percolate if not.
    fp = open(datfile, 'a')
    print >> fp, dest, reposz, pos, sum
    fp.flush()
    os.fsync(fp.fileno())
    fp.close()
Exemple #4
0
def do_full_backup(options):
    options.full = True
    dest = os.path.join(options.repository, gen_filename(options))
    if os.path.exists(dest):
        raise WouldOverwriteFiles('Cannot overwrite existing file: %s' % dest)
    # Find the file position of the last completed transaction.
    fs = FileStorage(options.file, read_only=True)
    # Note that the FileStorage ctor calls read_index() which scans the file
    # and returns "the position just after the last valid transaction record".
    # getSize() then returns this position, which is exactly what we want,
    # because we only want to copy stuff from the beginning of the file to the
    # last valid transaction record.
    pos = fs.getSize()
    # Save the storage index into the repository
    index_file = os.path.join(options.repository,
                              gen_filename(options, '.index'))
    log('writing index')
    fs._index.save(pos, index_file)
    fs.close()
    log('writing full backup: %s bytes to %s', pos, dest)
    sum = copyfile(options, dest, 0, pos)
    # Write the data file for this full backup
    datfile = os.path.splitext(dest)[0] + '.dat'
    fp = open(datfile, 'w')
    print >> fp, dest, 0, pos, sum
    fp.flush()
    os.fsync(fp.fileno())
    fp.close()
    if options.killold:
        delete_old_backups(options)
Exemple #5
0
class TestNodeExtZODB(NodeTestCase):
    def setUp(self):
        super(TestNodeExtZODB, self).setUp()
        self.tempdir = tempfile.mkdtemp()

    def tearDown(self):
        super(TestNodeExtZODB, self).tearDown()
        shutil.rmtree(self.tempdir)

    def open(self):
        self.storage = FileStorage(os.path.join(self.tempdir, 'Data.fs'))
        self.db = DB(self.storage)
        self.connection = self.db.open()
        return self.connection.root()

    def close(self):
        transaction.commit()
        self.connection.close()
        self.db.close()

    def test_OOBTree_usage(self):
        # Test OOBTree persistence
        root = self.open()
        bt = OOBTree()
        root['btree'] = bt
        cld_bt = OOBTree()
        bt['key'] = PersistentList([1, cld_bt, 3])
        self.checkOutput(
            """\
        [1, <BTrees.OOBTree.OOBTree object at ...>, 3]
        """, str(bt['key']))

        # Commit and reopen database
        self.close()
        root = self.open()

        # Check whether we get back object as it was stored
        self.checkOutput(
            """\
        [1, <BTrees.OOBTree.OOBTree object at ...>, 3]
        """, str(root['btree']['key']))
        # Delete OOBTree
        del root['btree']
        self.close()

    def test_OOBTodict(self):
        # Test bases
        od = OOBTodict()
        self.assertEqual(od.__class__.__bases__, (_odict, OOBTree))

        # Test dict impl
        dict_ = od._dict_impl()
        self.assertTrue(dict_ is OOBTree)

        # Test list factory
        list_ = od._list_factory()
        self.assertTrue(list_ is PersistentList)

        # Test list tail and head
        od = OOBTodict()
        self.assertEqual(od.lt, _nil)
        self.assertEqual(od.lh, _nil)
        self.assertEqual(dict_.__getitem__(od, '____lt'), _nil)
        self.assertEqual(dict_.__getitem__(od, '____lh'), _nil)
        self.assertEqual(sorted(dict_.keys(od)), ['____lh', '____lt'])

        # Add OOBTodict to root
        root = self.open()
        od = root['oobtodict'] = OOBTodict()

        # Add some children
        foo = od['foo'] = OOBTodict()
        bar = od['bar'] = OOBTodict()
        baz = od['baz'] = OOBTodict()
        self.checkOutput(
            """\
        OOBTodict([('foo', OOBTodict()),
        ('bar', OOBTodict()),
        ('baz', OOBTodict())])
        """, repr(od))

        # Internal data representation
        self.assertEqual(sorted(dict_.keys(od)),
                         ['____lh', '____lt', 'bar', 'baz', 'foo'])
        self.assertEqual(dict_.__getitem__(od, '____lt'), 'baz')
        self.assertEqual(dict_.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(od, 'foo'), [_nil, foo, 'bar'])
        self.assertEqual(dict_.__getitem__(od, 'bar'), ['foo', bar, 'baz'])
        self.assertEqual(dict_.__getitem__(od, 'baz'), ['bar', baz, _nil])
        self.assertIsInstance(dict_.__getitem__(od, 'baz'), PersistentList)

        # List tail and list head
        self.assertEqual(od.lt, 'baz')
        self.assertEqual(od.lh, 'foo')

        # Check keys
        self.assertEqual(od.keys(), ['foo', 'bar', 'baz'])

        # Check iterkeys
        self.assertEqual(list(od.iterkeys()), ['foo', 'bar', 'baz'])

        # Check values
        self.assertEqual(od.values(), [foo, bar, baz])

        # Check itervalues
        self.assertEqual(list(od.itervalues()), [foo, bar, baz])

        # Check items
        self.assertEqual(od.items(), [('foo', foo), ('bar', bar),
                                      ('baz', baz)])

        # Check iteritems
        self.assertEqual(list(od.iteritems()), [('foo', foo), ('bar', bar),
                                                ('baz', baz)])

        # Check __iter__
        self.assertEqual(list(od.__iter__()), ['foo', 'bar', 'baz'])

        # Check __getitem__
        self.assertEqual(od['foo'], foo)

        # Check __delitem__
        del od['baz']
        self.assertTrue('foo' in od)
        self.assertTrue('bar' in od)
        self.assertFalse('baz' in od)

        # Check __len__
        self.assertEqual(len(od), 2)

        # Check get
        self.assertEqual(od.get('foo'), foo)
        self.assertEqual(od.get('baz'), None)

        # Check copy
        od2 = od.copy()
        self.checkOutput(
            """\
        OOBTodict([('foo', OOBTodict()), ('bar', OOBTodict())])
        """, repr(od2))

        # Copied object not original one
        self.assertFalse(od is od2)
        self.assertEqual(od2.keys(), ['foo', 'bar'])

        # Check sort
        od2.sort(key=lambda x: x[0])
        self.checkOutput(
            """\
        OOBTodict([('bar', OOBTodict()), ('foo', OOBTodict())])
        """, repr(od2))
        self.assertEqual(od2.keys(), ['bar', 'foo'])

        # Check update
        bam = OOBTodict()
        od2.update([('bam', bam)])
        self.assertEqual(od2.keys(), ['bar', 'foo', 'bam'])

        # Check popitem
        self.assertEqual(od2.popitem(), ('bam', bam))
        self.assertEqual(od2.keys(), ['bar', 'foo'])

        # Reopen database connection and check structure
        self.close()
        root = self.open()
        self.assertEqual(list(root.keys()), ['oobtodict'])
        od = root['oobtodict']
        self.assertEqual(sorted(dict_.keys(od)),
                         ['____lh', '____lt', 'bar', 'foo'])
        self.assertEqual(dict_.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(od, '____lt'), 'bar')
        self.assertEqual(dict_.__getitem__(od, 'foo'),
                         [_nil, od['foo'], 'bar'])
        self.assertEqual(dict_.__getitem__(od, 'bar'),
                         ['foo', od['bar'], _nil])
        self.assertEqual(od.lt, 'bar')
        self.assertEqual(od.lh, 'foo')
        self.assertIsInstance(dict_.__getitem__(od, 'bar'), PersistentList)

        # Add attributes and reopen database connection and check structure
        od['baz'] = OOBTodict()
        od['bam'] = OOBTodict()
        self.close()
        root = self.open()
        od = root['oobtodict']
        self.assertEqual(sorted(dict_.keys(od)),
                         ['____lh', '____lt', 'bam', 'bar', 'baz', 'foo'])
        self.assertEqual(dict_.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(od, '____lt'), 'bam')
        self.assertEqual(dict_.__getitem__(od, 'bam'),
                         ['baz', od['bam'], _nil])
        self.assertEqual(dict_.__getitem__(od, 'bar'),
                         ['foo', od['bar'], 'baz'])
        self.assertEqual(dict_.__getitem__(od, 'baz'),
                         ['bar', od['baz'], 'bam'])
        self.assertEqual(dict_.__getitem__(od, 'foo'),
                         [_nil, od['foo'], 'bar'])
        self.assertEqual(od.keys(), ['foo', 'bar', 'baz', 'bam'])
        self.assertEqual(od.lt, 'bam')
        self.assertEqual(od.lh, 'foo')

        # Add and delete attributes and reopen database connection and check
        # structure
        del od['bar']
        od['cow'] = OOBTodict()
        od['chick'] = OOBTodict()
        self.close()
        root = self.open()
        od = root['oobtodict']
        self.assertEqual(od.keys(), ['foo', 'baz', 'bam', 'cow', 'chick'])
        self.assertEqual(
            sorted(dict_.keys(od)),
            ['____lh', '____lt', 'bam', 'baz', 'chick', 'cow', 'foo'])
        self.assertEqual(dict_.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(od, '____lt'), 'chick')
        self.assertEqual(dict_.__getitem__(od, 'bam'),
                         ['baz', od['bam'], 'cow'])
        self.assertEqual(dict_.__getitem__(od, 'baz'),
                         ['foo', od['baz'], 'bam'])
        self.assertEqual(dict_.__getitem__(od, 'chick'),
                         ['cow', od['chick'], _nil])
        self.assertEqual(dict_.__getitem__(od, 'cow'),
                         ['bam', od['cow'], 'chick'])
        self.assertEqual(dict_.__getitem__(od, 'foo'),
                         [_nil, od['foo'], 'baz'])
        self.assertEqual(od.lh, 'foo')
        self.assertEqual(od.lt, 'chick')

        # Delete from database
        del root['oobtodict']
        self.close()

    def test_ZODBNode(self):
        # Based on PersistentDict as storage
        zodbnode = ZODBNode(name='zodbnode')

        # Interface check
        self.assertTrue(IZODBNode.providedBy(zodbnode))

        # Storage check
        self.assertTrue(isinstance(zodbnode.storage, Podict))
        self.assertTrue(isinstance(zodbnode._storage, Podict))

        # Structure check
        root = self.open()
        root[zodbnode.__name__] = zodbnode
        zodbnode['child'] = ZODBNode(name='child')
        self.checkOutput(
            """\
        {'zodbnode': <ZODBNode object 'zodbnode' at ...>}
        """, repr(root))
        self.assertEqual(zodbnode.keys(), ['child'])
        self.assertEqual(zodbnode.values(), [zodbnode['child']])
        self.assertEqual(zodbnode.treerepr(),
                         ('<class \'node.ext.zodb.ZODBNode\'>: zodbnode\n'
                          '  <class \'node.ext.zodb.ZODBNode\'>: child\n'))
        self.assertEqual(list(root.keys()), ['zodbnode'])

        # Reopen database connection and check again
        self.close()
        root = self.open()
        self.assertEqual(list(root.keys()), ['zodbnode'])
        zodbnode = root['zodbnode']
        self.assertEqual(zodbnode.treerepr(),
                         ('<class \'node.ext.zodb.ZODBNode\'>: zodbnode\n'
                          '  <class \'node.ext.zodb.ZODBNode\'>: child\n'))

        # Delete child node
        del zodbnode['child']
        self.assertEqual(zodbnode.treerepr(),
                         ('<class \'node.ext.zodb.ZODBNode\'>: zodbnode\n'))

        # Check node attributes
        self.assertTrue(isinstance(zodbnode.attrs, ZODBNodeAttributes))
        self.assertEqual(zodbnode.attrs.name, '_attrs')
        zodbnode.attrs['foo'] = 1
        bar = zodbnode.attrs['bar'] = ZODBNode()
        self.assertEqual(zodbnode.attrs.values(), [1, bar])

        # Fill root with some ZODBNodes and check memory usage
        transaction.commit()
        old_size = self.storage.getSize()
        root['largezodb'] = ZODBNode(name='largezodb')
        for i in range(1000):
            root['largezodb'][str(i)] = ZODBNode()
        self.assertEqual(len(root['largezodb']), 1000)
        transaction.commit()
        new_size = self.storage.getSize()
        self.assertTrue((new_size - old_size) / 1000 <= 899)
        self.close()

    def test_OOBTNode(self):
        # Based on OOBTree as storage
        oobtnode = OOBTNode(name='oobtnode')

        # Interface check
        self.assertTrue(IZODBNode.providedBy(oobtnode))

        # Storage check
        self.assertTrue(isinstance(oobtnode.storage, OOBTodict))
        self.assertTrue(isinstance(oobtnode._storage, OOBTodict))

        # Structure check
        root = self.open()
        root[oobtnode.__name__] = oobtnode
        oobtnode['child'] = OOBTNode(name='child')
        self.assertEqual(sorted(root.keys()), ['oobtnode'])
        self.assertEqual(oobtnode.keys(), ['child'])
        self.assertEqual(oobtnode.values(), [oobtnode['child']])
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: child\n'))
        self.checkOutput(
            """\
        OOBTodict([('child', <OOBTNode object 'child' at ...>)])
        """, repr(oobtnode.storage))

        # Reopen database connection and check again
        self.close()
        root = self.open()
        self.assertEqual(sorted(root.keys()), ['oobtnode'])
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.keys(), ['child'])
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: child\n'))
        self.assertTrue(oobtnode['child'].__parent__ is oobtnode)

        # Delete child node
        del oobtnode['child']
        transaction.commit()
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'))

        # Check node attributes
        self.assertTrue(isinstance(oobtnode.attrs, OOBTNodeAttributes))
        self.assertEqual(oobtnode.attrs.name, '_attrs')
        oobtnode.attrs['foo'] = 1
        bar = oobtnode.attrs['bar'] = OOBTNode()
        self.assertEqual(oobtnode.attrs.values(), [1, bar])

        # Check attribute access for node attributes
        oobtnode.attribute_access_for_attrs = True
        self.assertEqual(oobtnode.attrs.foo, 1)

        # Check whether flag has been persisted
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.attrs.foo, 1)
        self.assertEqual(oobtnode.attrs.bar, oobtnode.attrs['bar'])
        oobtnode.attrs.foo = 2
        self.assertEqual(oobtnode.attrs.foo, 2)
        oobtnode.attribute_access_for_attrs = False

        # Check attrs storage
        self.checkOutput(
            """\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs.storage))
        self.checkOutput(
            """\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs._storage))
        self.assertTrue(oobtnode.attrs.storage is oobtnode.attrs._storage)
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        oobtnode.attribute_access_for_attrs = False
        self.checkOutput(
            """\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs.storage))

        # Check internal datastructure of attrs
        storage = oobtnode.attrs.storage
        dict_ = storage._dict_impl()
        self.assertTrue(dict_ is OOBTree)
        self.assertEqual(sorted(dict_.keys(storage)),
                         ['____lh', '____lt', 'bar', 'foo'])

        # values ``foo`` and ``bar`` are list tail and list head values
        self.assertEqual(dict_.__getitem__(storage, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(storage, '____lt'), 'bar')
        attrs = oobtnode.attrs
        self.assertEqual(dict_.__getitem__(storage, 'bar'),
                         ['foo', attrs['bar'], _nil])
        self.assertEqual(dict_.__getitem__(storage, 'foo'), [_nil, 2, 'bar'])
        self.assertEqual(storage.lt, 'bar')
        self.assertEqual(storage.lh, 'foo')

        # Add attribute, reopen database connection and check again
        oobtnode.attrs['baz'] = 'some added value'
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        storage = oobtnode.attrs.storage
        dict_ = storage._dict_impl()
        self.assertEqual(dict_.__getitem__(storage, '____lh'), 'foo')
        self.assertEqual(dict_.__getitem__(storage, '____lt'), 'baz')
        attrs = oobtnode.attrs
        self.assertEqual(dict_.__getitem__(storage, 'bar'),
                         ['foo', attrs['bar'], 'baz'])
        self.assertEqual(dict_.__getitem__(storage, 'baz'),
                         ['bar', 'some added value', _nil])
        self.assertEqual(dict_.__getitem__(storage, 'foo'), [_nil, 2, 'bar'])
        self.assertEqual(storage.lt, 'baz')
        self.assertEqual(storage.lh, 'foo')

        # Test copy and detach
        oobtnode['c1'] = OOBTNode()
        oobtnode['c2'] = OOBTNode()
        oobtnode['c3'] = OOBTNode()
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Detach c1
        c1 = oobtnode.detach('c1')
        self.assertTrue(isinstance(c1, OOBTNode))
        self.assertEqual(c1.name, 'c1')
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Add c1 as child to c2
        oobtnode['c2'][c1.name] = c1
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Reopen database connection and check again
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'))

        # Copy c1
        c1_copy = oobtnode['c2']['c1'].copy()
        self.assertFalse(c1_copy is oobtnode['c2']['c1'])
        oobtnode['c1'] = c1_copy
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'))
        oobtnode['c4'] = oobtnode['c2'].copy()
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'))
        self.assertFalse(oobtnode['c2']['c1'] is oobtnode['c4']['c1'])
        self.assertFalse(
            oobtnode['c2']['c1'].attrs is oobtnode['c4']['c1'].attrs)
        transaction.commit()

        # Swap nodes
        oobtnode.swap(oobtnode['c1'], oobtnode['c3'])
        oobtnode.swap(oobtnode['c1'], oobtnode['c2'])
        self.assertEqual(oobtnode.treerepr(),
                         ('<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
                          '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
                          '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'))
        st = oobtnode.storage
        self.assertIsInstance(dict_.__getitem__(st, 'c1'), PersistentList)
        self.assertIsInstance(dict_.__getitem__(st, 'c2'), PersistentList)
        self.assertIsInstance(dict_.__getitem__(st, 'c3'), PersistentList)

        # Calling nodes does nothing, persisting is left to transaction
        # mechanism
        oobtnode()

        # Fill root with some OOBTNodes and check memory usage
        old_size = self.storage.getSize()
        root['large'] = OOBTNode()
        for i in range(1000):
            root['large'][str(i)] = OOBTNode()
        self.assertEqual(len(root['large']), 1000)
        transaction.commit()
        new_size = self.storage.getSize()
        self.assertTrue((new_size - old_size) / 1000 <= 914)
        self.close()

    def test_utils(self):
        # Test ``volatile_property``
        class PropTest(object):
            @volatile_property
            def foo(self):
                return 'foo'

        inst = PropTest()
        self.assertTrue('foo' in dir(inst))
        self.assertFalse('_v_foo' in dir(inst))
        self.assertEqual(inst.foo, 'foo')
        self.assertTrue('_v_foo' in dir(inst))
        self.assertEqual(inst._v_foo, 'foo')
        self.assertTrue(inst._v_foo is inst.foo)

        # Check odict consistency
        od = OOBTodict()
        od['foo'] = 'foo'
        od['bar'] = 'bar'
        od['baz'] = 'baz'

        # Ignore key callback for OOBTree odicts needs to ignore keys starting
        # with four underscores since these entries define the object
        # attributes
        def ignore_key(key):
            return key.startswith('____')

        check_odict_consistency(od, ignore_key=ignore_key)

        # Check if ``_nil`` marker set irregulary
        dict_impl = od._dict_impl()
        dict_impl.__setitem__(od, 'bam', ['foo', 'bam', _nil])
        self.assertEqual(od.keys(), ['foo', 'bar', 'baz'])
        self.assertEqual(sorted(dict_impl.keys(od)),
                         ['____lh', '____lt', 'bam', 'bar', 'baz', 'foo'])
        err = self.expectError(UnexpextedEndOfList,
                               check_odict_consistency,
                               od,
                               ignore_key=ignore_key)
        expected = ('Unexpected ``_nil`` pointer found in double linked '
                    'list. Resulting key count does not match:  4 != 3')
        self.assertEqual(str(err), expected)

        # Manually sanitize odict
        dict_impl.__delitem__(od, 'bam')
        check_odict_consistency(od, ignore_key=ignore_key)

        # Check whether double linked list contains inexistent key
        dict_impl.__setitem__(od, 'foo', [_nil, 'foo', 'inexistent'])
        err = self.expectError(ListReferenceInconsistency,
                               check_odict_consistency,
                               od,
                               ignore_key=ignore_key)
        expected = (
            'Double linked list contains a reference to a non existing dict '
            'entry: \'inexistent\' not in [\'bar\', \'baz\', \'foo\']')
        self.assertEqual(str(err), expected)

        # Manually sanitize odict
        dict_impl.__setitem__(od, 'foo', [_nil, 'foo', 'bar'])
        check_odict_consistency(od, ignore_key=ignore_key)

        # Check broken list head
        od.lh = 'inexistent'
        err = self.expectError(ListHeadInconsistency,
                               check_odict_consistency,
                               od,
                               ignore_key=ignore_key)
        expected = (
            'List head contains a reference to a non existing dict entry: '
            '\'inexistent\' not in [\'bar\', \'baz\', \'foo\']')
        self.assertEqual(str(err), expected)

        # Manually sanitize odict
        od.lh = 'foo'
        check_odict_consistency(od, ignore_key=ignore_key)

        # Check broken list tail
        od.lt = 'inexistent'
        err = self.expectError(ListTailInconsistency,
                               check_odict_consistency,
                               od,
                               ignore_key=ignore_key)
        expected = (
            'List tail contains a reference to a non existing dict entry: '
            '\'inexistent\' not in [\'bar\', \'baz\', \'foo\']')
        self.assertEqual(str(err), expected)

        # Manually sanitize odict
        od.lt = 'baz'
        check_odict_consistency(od, ignore_key=ignore_key)

        # Reset odict
        od.lh = 'inexistent'
        od.lt = 'baz'
        dict_impl.__setitem__(od, 'foo', ['123', 'foo', 'bar'])
        dict_impl.__setitem__(od, '123', [_nil, 'foo', _nil])
        reset_odict(od, ignore_key=ignore_key)
        self.assertEqual(od.lh, '123')
        self.assertEqual(od.lt, 'foo')
        self.checkOutput(
            """\
        OOBTodict([('123', 'foo'),
        ('bar', 'bar'),
        ('baz', 'baz'),
        ('foo', 'foo')])
        """, repr(od))
        check_odict_consistency(od, ignore_key=ignore_key)
Exemple #6
0
class TestNodeExtZODB(NodeTestCase):

    def setUp(self):
        super(TestNodeExtZODB, self).setUp()
        self.tempdir = tempfile.mkdtemp()

    def tearDown(self):
        super(TestNodeExtZODB, self).tearDown()
        shutil.rmtree(self.tempdir)

    def open(self):
        self.storage = FileStorage(os.path.join(self.tempdir, 'Data.fs'))
        self.db = DB(self.storage)
        self.connection = self.db.open()
        return self.connection.root()

    def close(self):
        transaction.commit()
        self.connection.close()
        self.db.close()

    def test_OOBTree_usage(self):
        # Test OOBTree persistence
        root = self.open()
        bt = OOBTree()
        root['btree'] = bt
        cld_bt = OOBTree()
        bt['key'] = [1, cld_bt, 3]
        self.check_output("""\
        [1, <BTrees.OOBTree.OOBTree object at ...>, 3]
        """, str(bt['key']))
        # Commit and reopen database
        self.close()
        root = self.open()
        # Check whether we get back object as it was stored
        self.check_output("""\
        [1, <BTrees.OOBTree.OOBTree object at ...>, 3]
        """, str(root['btree']['key']))
        # Delete OOBTree
        del root['btree']
        self.close()

    def test_OOBTodict(self):
        # Test bases
        od = OOBTodict()
        self.assertEqual(od.__class__.__bases__, (_odict, OOBTree))
        # Test dict impl
        cls = od._dict_impl()
        self.assertTrue(cls is OOBTree)
        # Test list tail and head
        od = OOBTodict()
        self.assertEqual(od.lt, _nil)
        self.assertEqual(od.lh, _nil)
        self.assertEqual(cls.__getitem__(od, '____lt'), _nil)
        self.assertEqual(cls.__getitem__(od, '____lh'), _nil)
        self.assertEqual(sorted(cls.keys(od)), ['____lh', '____lt'])
        # Add OOBTodict to root
        root = self.open()
        od = root['oobtodict'] = OOBTodict()
        # Add some children
        foo = od['foo'] = OOBTodict()
        bar = od['bar'] = OOBTodict()
        baz = od['baz'] = OOBTodict()
        self.check_output("""\
        OOBTodict([('foo', OOBTodict()),
        ('bar', OOBTodict()),
        ('baz', OOBTodict())])
        """, repr(od))
        # Internal data representation
        self.assertEqual(
            sorted(cls.keys(od)),
            ['____lh', '____lt', 'bar', 'baz', 'foo']
        )
        self.assertEqual(cls.__getitem__(od, '____lt'), 'baz')
        self.assertEqual(cls.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(cls.__getitem__(od, 'foo'), [_nil, foo, 'bar'])
        self.assertEqual(cls.__getitem__(od, 'bar'), ['foo', bar, 'baz'])
        self.assertEqual(cls.__getitem__(od, 'baz'), ['bar', baz, _nil])
        # List tail and list head
        self.assertEqual(od.lt, 'baz')
        self.assertEqual(od.lh, 'foo')
        # Check keys
        self.assertEqual(od.keys(), ['foo', 'bar', 'baz'])
        # Check iterkeys
        self.assertEqual(list(od.iterkeys()), ['foo', 'bar', 'baz'])
        # Check values
        self.assertEqual(od.values(), [foo, bar, baz])
        # Check itervalues
        self.assertEqual(list(od.itervalues()), [foo, bar, baz])
        # Check items
        self.assertEqual(
            od.items(),
            [('foo', foo), ('bar', bar), ('baz', baz)]
        )
        # Check iteritems
        self.assertEqual(
            list(od.iteritems()),
            [('foo', foo), ('bar', bar), ('baz', baz)]
        )
        # Check __iter__
        self.assertEqual(list(od.__iter__()), ['foo', 'bar', 'baz'])
        # Check __getitem__
        self.assertEqual(od['foo'], foo)
        # Check __delitem__
        del od['baz']
        self.assertTrue('foo' in od)
        self.assertTrue('bar' in od)
        self.assertFalse('baz' in od)
        # Check __len__
        self.assertEqual(len(od), 2)
        # Check get
        self.assertEqual(od.get('foo'), foo)
        self.assertEqual(od.get('baz'), None)
        # Check copy
        od2 = od.copy()
        self.check_output("""\
        OOBTodict([('foo', OOBTodict()), ('bar', OOBTodict())])
        """, repr(od2))
        # Copied object not original one
        self.assertFalse(od is od2)
        self.assertEqual(od2.keys(), ['foo', 'bar'])
        # Check sort
        od2.sort(key=lambda x: x[0])
        self.check_output("""\
        OOBTodict([('bar', OOBTodict()), ('foo', OOBTodict())])
        """, repr(od2))
        self.assertEqual(od2.keys(), ['bar', 'foo'])
        # Check update
        bam = OOBTodict()
        od2.update([('bam', bam)])
        self.assertEqual(od2.keys(), ['bar', 'foo', 'bam'])
        # Check popitem
        self.assertEqual(od2.popitem(), ('bam', bam))
        self.assertEqual(od2.keys(), ['bar', 'foo'])
        # Reopen database connection and check structure
        self.close()
        root = self.open()
        self.assertEqual(list(root.keys()), ['oobtodict'])
        od = root['oobtodict']
        self.assertEqual(
            sorted(cls.keys(od)),
            ['____lh', '____lt', 'bar', 'foo']
        )
        self.assertEqual(cls.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(cls.__getitem__(od, '____lt'), 'bar')
        self.assertEqual(cls.__getitem__(od, 'foo'), [_nil, od['foo'], 'bar'])
        self.assertEqual(cls.__getitem__(od, 'bar'), ['foo', od['bar'], _nil])
        self.assertEqual(od.lt, 'bar')
        self.assertEqual(od.lh, 'foo')
        # Add attributes and reopen database connection and check structure
        od['baz'] = OOBTodict()
        od['bam'] = OOBTodict()
        self.close()
        root = self.open()
        od = root['oobtodict']
        self.assertEqual(
            sorted(cls.keys(od)),
            ['____lh', '____lt', 'bam', 'bar', 'baz', 'foo']
        )
        self.assertEqual(cls.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(cls.__getitem__(od, '____lt'), 'bam')
        self.assertEqual(cls.__getitem__(od, 'bam'), ['baz', od['bam'], _nil])
        self.assertEqual(cls.__getitem__(od, 'bar'), ['foo', od['bar'], 'baz'])
        self.assertEqual(cls.__getitem__(od, 'baz'), ['bar', od['baz'], 'bam'])
        self.assertEqual(cls.__getitem__(od, 'foo'), [_nil, od['foo'], 'bar'])
        self.assertEqual(od.keys(), ['foo', 'bar', 'baz', 'bam'])
        self.assertEqual(od.lt, 'bam')
        self.assertEqual(od.lh, 'foo')
        # Add and delete attributes and reopen database connection and check
        # structure
        del od['bar']
        od['cow'] = OOBTodict()
        od['chick'] = OOBTodict()
        self.close()
        root = self.open()
        od = root['oobtodict']
        self.assertEqual(od.keys(), ['foo', 'baz', 'bam', 'cow', 'chick'])
        self.assertEqual(
            sorted(cls.keys(od)),
            ['____lh', '____lt', 'bam', 'baz', 'chick', 'cow', 'foo']
        )
        self.assertEqual(cls.__getitem__(od, '____lh'), 'foo')
        self.assertEqual(cls.__getitem__(od, '____lt'), 'chick')
        self.assertEqual(cls.__getitem__(od, 'bam'), ['baz', od['bam'], 'cow'])
        self.assertEqual(cls.__getitem__(od, 'baz'), ['foo', od['baz'], 'bam'])
        self.assertEqual(
            cls.__getitem__(od, 'chick'),
            ['cow', od['chick'], _nil]
        )
        self.assertEqual(
            cls.__getitem__(od, 'cow'),
            ['bam', od['cow'], 'chick']
        )
        self.assertEqual(cls.__getitem__(od, 'foo'), [_nil, od['foo'], 'baz'])
        self.assertEqual(od.lh, 'foo')
        self.assertEqual(od.lt, 'chick')
        # Delete from database
        del root['oobtodict']
        self.close()

    def test_ZODBNode(self):
        # Based on PersistentDict as storage
        zodbnode = ZODBNode('zodbnode')
        # Interface check
        self.assertTrue(IZODBNode.providedBy(zodbnode))
        # Storage check
        self.assertTrue(isinstance(zodbnode.storage, Podict))
        self.assertTrue(isinstance(zodbnode._storage, Podict))
        # Structure check
        root = self.open()
        root[zodbnode.__name__] = zodbnode
        zodbnode['child'] = ZODBNode('child')
        self.check_output("""\
        {'zodbnode': <ZODBNode object 'zodbnode' at ...>}
        """, repr(root))
        self.assertEqual(zodbnode.keys(), ['child'])
        self.assertEqual(zodbnode.values(), [zodbnode['child']])
        self.assertEqual(zodbnode.treerepr(), (
            '<class \'node.ext.zodb.ZODBNode\'>: zodbnode\n'
            '  <class \'node.ext.zodb.ZODBNode\'>: child\n'
        ))
        self.assertEqual(list(root.keys()), ['zodbnode'])
        # Reopen database connection and check again
        self.close()
        root = self.open()
        self.assertEqual(list(root.keys()), ['zodbnode'])
        zodbnode = root['zodbnode']
        self.assertEqual(zodbnode.treerepr(), (
            '<class \'node.ext.zodb.ZODBNode\'>: zodbnode\n'
            '  <class \'node.ext.zodb.ZODBNode\'>: child\n'
        ))
        # Delete child node
        del zodbnode['child']
        self.assertEqual(zodbnode.treerepr(), (
            '<class \'node.ext.zodb.ZODBNode\'>: zodbnode\n'
        ))
        # Check node attributes
        self.assertTrue(isinstance(zodbnode.attrs, ZODBNodeAttributes))
        self.assertEqual(zodbnode.attrs.name, '_attrs')
        zodbnode.attrs['foo'] = 1
        bar = zodbnode.attrs['bar'] = ZODBNode()
        self.assertEqual(zodbnode.attrs.values(), [1, bar])
        # Fill root with some ZODBNodes and check memory usage
        transaction.commit()
        old_size = self.storage.getSize()
        root['largezodb'] = ZODBNode('largezodb')
        for i in range(1000):
            root['largezodb'][str(i)] = ZODBNode()
        self.assertEqual(len(root['largezodb']), 1000)
        transaction.commit()
        new_size = self.storage.getSize()
        # ZODB 3 and ZODB 5 return different sizes so check whether lower or
        # equal higher value
        self.assertTrue((new_size - old_size) / 1000 <= 160)
        self.close()

    def test_OOBTNode(self):
        # Based on OOBTree as storage
        oobtnode = OOBTNode('oobtnode')
        # Interface check
        self.assertTrue(IZODBNode.providedBy(oobtnode))
        # Storage check
        self.assertTrue(isinstance(oobtnode.storage, OOBTodict))
        self.assertTrue(isinstance(oobtnode._storage, OOBTodict))
        # Structure check
        root = self.open()
        root[oobtnode.__name__] = oobtnode
        oobtnode['child'] = OOBTNode('child')
        self.assertEqual(sorted(root.keys()), ['oobtnode'])
        self.assertEqual(oobtnode.keys(), ['child'])
        self.assertEqual(oobtnode.values(), [oobtnode['child']])
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: child\n'
        ))
        self.check_output("""\
        OOBTodict([('child', <OOBTNode object 'child' at ...>)])
        """, repr(oobtnode.storage))
        # Reopen database connection and check again
        self.close()
        root = self.open()
        self.assertEqual(sorted(root.keys()), ['oobtnode'])
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.keys(), ['child'])
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: child\n'
        ))
        self.assertTrue(oobtnode['child'].__parent__ is oobtnode)
        # Delete child node
        del oobtnode['child']
        transaction.commit()
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
        ))
        # Check node attributes
        self.assertTrue(isinstance(oobtnode.attrs, OOBTNodeAttributes))
        self.assertEqual(oobtnode.attrs.name, '_attrs')
        oobtnode.attrs['foo'] = 1
        bar = oobtnode.attrs['bar'] = OOBTNode()
        self.assertEqual(oobtnode.attrs.values(), [1, bar])
        # Check attribute access for node attributes
        oobtnode.attribute_access_for_attrs = True
        self.assertEqual(oobtnode.attrs.foo, 1)
        # Check whether flag has been persisted
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.attrs.foo, 1)
        self.assertEqual(oobtnode.attrs.bar, oobtnode.attrs['bar'])
        oobtnode.attrs.foo = 2
        self.assertEqual(oobtnode.attrs.foo, 2)
        oobtnode.attribute_access_for_attrs = False
        # Check attrs storage
        self.check_output("""\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs.storage))
        self.check_output("""\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs._storage))
        self.assertTrue(oobtnode.attrs.storage is oobtnode.attrs._storage)
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        oobtnode.attribute_access_for_attrs = False
        self.check_output("""\
        OOBTodict([('foo', 2), ('bar', <OOBTNode object 'bar' at ...>)])
        """, repr(oobtnode.attrs.storage))
        # Check internal datastructure of attrs
        storage = oobtnode.attrs.storage
        cls = storage._dict_impl()
        self.assertTrue(cls is OOBTree)
        self.assertEqual(
            sorted(cls.keys(storage)),
            ['____lh', '____lt', 'bar', 'foo']
        )
        # values ``foo`` and ``bar`` are list tail and list head values
        self.assertEqual(cls.__getitem__(storage, '____lh'), 'foo')
        self.assertEqual(cls.__getitem__(storage, '____lt'), 'bar')
        attrs = oobtnode.attrs
        self.assertEqual(
            cls.__getitem__(storage, 'bar'),
            ['foo', attrs['bar'], _nil]
        )
        self.assertEqual(
            cls.__getitem__(storage, 'foo'),
            [_nil, 2, 'bar']
        )
        self.assertEqual(storage.lt, 'bar')
        self.assertEqual(storage.lh, 'foo')
        # Add attribute, reopen database connection and check again
        oobtnode.attrs['baz'] = 'some added value'
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        storage = oobtnode.attrs.storage
        cls = storage._dict_impl()
        self.assertEqual(cls.__getitem__(storage, '____lh'), 'foo')
        self.assertEqual(cls.__getitem__(storage, '____lt'), 'baz')
        attrs = oobtnode.attrs
        self.assertEqual(
            cls.__getitem__(storage, 'bar'),
            ['foo', attrs['bar'], 'baz']
        )
        self.assertEqual(
            cls.__getitem__(storage, 'baz'),
            ['bar', 'some added value', _nil]
        )
        self.assertEqual(
            cls.__getitem__(storage, 'foo'),
            [_nil, 2, 'bar']
        )
        self.assertEqual(storage.lt, 'baz')
        self.assertEqual(storage.lh, 'foo')
        # Test copy and detach
        oobtnode['c1'] = OOBTNode()
        oobtnode['c2'] = OOBTNode()
        oobtnode['c3'] = OOBTNode()
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
        ))
        # Detach c1
        c1 = oobtnode.detach('c1')
        self.assertTrue(isinstance(c1, OOBTNode))
        self.assertEqual(c1.name, 'c1')
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
        ))
        # Add c1 as child to c2
        oobtnode['c2'][c1.name] = c1
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
            '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
        ))
        # Reopen database connection and check again
        self.close()
        root = self.open()
        oobtnode = root['oobtnode']
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
            '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
        ))
        # Copy c1
        c1_copy = oobtnode['c2']['c1'].copy()
        self.assertFalse(c1_copy is oobtnode['c2']['c1'])
        oobtnode['c1'] = c1_copy
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
            '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
        ))
        oobtnode['c4'] = oobtnode['c2'].copy()
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
            '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
            '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
        ))
        self.assertFalse(oobtnode['c2']['c1'] is oobtnode['c4']['c1'])
        self.assertFalse(
            oobtnode['c2']['c1'].attrs is oobtnode['c4']['c1'].attrs
        )
        transaction.commit()
        # Swap nodes
        oobtnode.swap(oobtnode['c1'], oobtnode['c3'])
        oobtnode.swap(oobtnode['c1'], oobtnode['c2'])
        self.assertEqual(oobtnode.treerepr(), (
            '<class \'node.ext.zodb.OOBTNode\'>: oobtnode\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c2\n'
            '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c3\n'
            '  <class \'node.ext.zodb.OOBTNode\'>: c4\n'
            '    <class \'node.ext.zodb.OOBTNode\'>: c1\n'
        ))
        # Calling nodes does nothing, persisting is left to transaction
        # mechanism
        oobtnode()
        # Fill root with some OOBTNodes and check memory usage
        old_size = self.storage.getSize()
        root['large'] = OOBTNode()
        for i in range(1000):
            root['large'][str(i)] = OOBTNode()
        self.assertEqual(len(root['large']), 1000)
        transaction.commit()
        new_size = self.storage.getSize()
        # ZODB 3 and ZODB 5 return different sizes so check whether lower or
        # equal higher value
        self.assertTrue((new_size - old_size) / 1000 <= 160)
        self.close()

    def test_utils(self):
        # Test ``volatile_property``
        class PropTest(object):
            @volatile_property
            def foo(self):
                return 'foo'
        inst = PropTest()
        self.assertTrue('foo' in dir(inst))
        self.assertFalse('_v_foo' in dir(inst))
        self.assertEqual(inst.foo, 'foo')
        self.assertTrue('_v_foo' in dir(inst))
        self.assertEqual(inst._v_foo, 'foo')
        self.assertTrue(inst._v_foo is inst.foo)
        # Check odict consistency
        od = OOBTodict()
        od['foo'] = 'foo'
        od['bar'] = 'bar'
        od['baz'] = 'baz'
        # Ignore key callback for OOBTree odicts needs to ignore keys starting
        # with four underscores since these entries define the object
        # attributes

        def ignore_key(key):
            return key.startswith('____')

        check_odict_consistency(od, ignore_key=ignore_key)
        # Check if ``_nil`` marker set irregulary
        dict_impl = od._dict_impl()
        dict_impl.__setitem__(od, 'bam', ['foo', 'bam', _nil])
        self.assertEqual(od.keys(), ['foo', 'bar', 'baz'])
        self.assertEqual(
            sorted(dict_impl.keys(od)),
            ['____lh', '____lt', 'bam', 'bar', 'baz', 'foo']
        )
        err = self.expect_error(
            UnexpextedEndOfList,
            check_odict_consistency,
            od,
            ignore_key=ignore_key
        )
        expected = (
            'Unexpected ``_nil`` pointer found in double linked '
            'list. Resulting key count does not match:  4 != 3'
        )
        self.assertEqual(str(err), expected)
        # Manually sanitize odict
        dict_impl.__delitem__(od, 'bam')
        check_odict_consistency(od, ignore_key=ignore_key)
        # Check whether double linked list contains inexistent key
        dict_impl.__setitem__(od, 'foo', [_nil, 'foo', 'inexistent'])
        err = self.expect_error(
            ListReferenceInconsistency,
            check_odict_consistency,
            od,
            ignore_key=ignore_key
        )
        expected = (
            'Double linked list contains a reference to a non existing dict '
            'entry: \'inexistent\' not in [\'bar\', \'baz\', \'foo\']'
        )
        self.assertEqual(str(err), expected)
        # Manually sanitize odict
        dict_impl.__setitem__(od, 'foo', [_nil, 'foo', 'bar'])
        check_odict_consistency(od, ignore_key=ignore_key)
        # Check broken list head
        od.lh = 'inexistent'
        err = self.expect_error(
            ListHeadInconsistency,
            check_odict_consistency,
            od,
            ignore_key=ignore_key
        )
        expected = (
            'List head contains a reference to a non existing dict entry: '
            '\'inexistent\' not in [\'bar\', \'baz\', \'foo\']'
        )
        self.assertEqual(str(err), expected)
        # Manually sanitize odict
        od.lh = 'foo'
        check_odict_consistency(od, ignore_key=ignore_key)
        # Check broken list tail
        od.lt = 'inexistent'
        err = self.expect_error(
            ListTailInconsistency,
            check_odict_consistency,
            od,
            ignore_key=ignore_key
        )
        expected = (
            'List tail contains a reference to a non existing dict entry: '
            '\'inexistent\' not in [\'bar\', \'baz\', \'foo\']'
        )
        self.assertEqual(str(err), expected)
        # Manually sanitize odict
        od.lt = 'baz'
        check_odict_consistency(od, ignore_key=ignore_key)
        # Reset odict
        od.lh = 'inexistent'
        od.lt = 'baz'
        dict_impl.__setitem__(od, 'foo', ['123', 'foo', 'bar'])
        dict_impl.__setitem__(od, '123', [_nil, 'foo', _nil])
        reset_odict(od, ignore_key=ignore_key)
        self.assertEqual(od.lh, '123')
        self.assertEqual(od.lt, 'foo')
        self.check_output("""\
        OOBTodict([('123', 'foo'),
        ('bar', 'bar'),
        ('baz', 'baz'),
        ('foo', 'foo')])
        """, repr(od))
        check_odict_consistency(od, ignore_key=ignore_key)