testStorageDBTests.py 20.4 KB
Newer Older
1
#
Julien Muchembled's avatar
Julien Muchembled committed
2
# Copyright (C) 2009-2017  Nexedi SA
3 4 5 6 7 8 9 10 11 12 13 14
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
from binascii import a2b_hex
18
from contextlib import contextmanager
19
import unittest
20
from neo.lib.util import add64, p64, u64
21
from neo.lib.protocol import CellStates, ZERO_HASH, ZERO_OID, ZERO_TID, MAX_TID
22
from .. import NeoUnitTestBase
23

24

25 26
class StorageDBTests(NeoUnitTestBase):

27 28
    _last_ttid = ZERO_TID

29 30
    def setUp(self):
        NeoUnitTestBase.setUp(self)
31 32 33 34 35 36 37 38

    @property
    def db(self):
        try:
            return self._db
        except AttributeError:
            self.setNumPartitions(1)
            return self._db
39

40
    def _tearDown(self, success):
41
        try:
42
            self.__dict__.pop('_db', None).close()
43 44
        except AttributeError:
            pass
45
        NeoUnitTestBase._tearDown(self, success)
46

47
    def getDB(self, reset=0):
48 49
        raise NotImplementedError

50 51 52 53 54 55 56
    def setNumPartitions(self, num_partitions, reset=0):
        try:
            db = self._db
        except AttributeError:
            self._db = db = self.getDB(reset)
        else:
            if reset:
57
                db.setup(reset)
58 59 60 61 62 63 64 65
            else:
                try:
                    n = db.getNumPartitions()
                except KeyError:
                    n = 0
                if num_partitions == n:
                    return
                if num_partitions < n:
66
                    db.dropPartitions(n)
67 68
        db.setNumPartitions(num_partitions)
        self.assertEqual(num_partitions, db.getNumPartitions())
69
        uuid = self.getStorageUUID()
70 71
        db.setUUID(uuid)
        self.assertEqual(uuid, db.getUUID())
72 73 74
        db.changePartitionTable(1,
            [(i, uuid, CellStates.UP_TO_DATE) for i in xrange(num_partitions)],
            reset=True)
75 76 77

    def checkConfigEntry(self, get_call, set_call, value):
        # generic test for all configuration entries accessors
78
        self.assertEqual(get_call(), None)
79
        set_call(value)
80
        self.assertEqual(get_call(), value)
81
        set_call(value * 2)
82
        self.assertEqual(get_call(), value * 2)
83

84 85 86 87 88 89 90 91
    @contextmanager
    def commitTransaction(self, tid, objs, txn, commit=True):
        ttid = txn[-1]
        self.db.storeTransaction(ttid, objs, txn)
        self.db.lockTransaction(tid, ttid)
        yield
        if commit:
            self.db.unlockTransaction(tid, ttid)
92
            self.db.commit()
93 94 95
        elif commit is not None:
            self.db.abortTransaction(ttid)

96
    def test_UUID(self):
97
        db = self.getDB()
98
        self.checkConfigEntry(db.getUUID, db.setUUID, 123)
99 100

    def test_Name(self):
101 102
        db = self.getDB()
        self.checkConfigEntry(db.getName, db.setName, 'TEST_NAME')
103 104

    def test_15_PTID(self):
105
        db = self.getDB()
106
        self.checkConfigEntry(db.getPTID, db.setPTID, 1)
107 108

    def test_getPartitionTable(self):
109
        db = self.getDB()
110
        uuid1, uuid2 = self.getStorageUUID(), self.getStorageUUID()
111 112
        cell1 = (0, uuid1, CellStates.OUT_OF_DATE)
        cell2 = (1, uuid1, CellStates.UP_TO_DATE)
113
        db.changePartitionTable(1, [cell1, cell2], 1)
114
        result = db.getPartitionTable()
115
        self.assertEqual(set(result), {cell1, cell2})
116 117

    def getOIDs(self, count):
118
        return map(p64, xrange(count))
119 120 121 122 123 124 125 126

    def getTIDs(self, count):
        tid_list = [self.getNextTID()]
        while len(tid_list) != count:
            tid_list.append(self.getNextTID(tid_list[-1]))
        return tid_list

    def getTransaction(self, oid_list):
127 128
        self._last_ttid = ttid = add64(self._last_ttid, 1)
        transaction = oid_list, 'user', 'desc', 'ext', False, ttid
129
        H = "0" * 20
130
        object_list = [(oid, self.db.holdData(H, '', 1), None)
131
                       for oid in oid_list]
132 133 134 135 136
        return (transaction, object_list)

    def checkSet(self, list1, list2):
        self.assertEqual(set(list1), set(list2))

137
    def test_getUnfinishedTIDDict(self):
138 139 140 141
        tid1, tid2, tid3, tid4 = self.getTIDs(4)
        oid1, oid2 = self.getOIDs(2)
        txn, objs = self.getTransaction([oid1, oid2])
        # one unfinished txn
142 143 144 145 146 147 148 149 150 151 152 153 154
        with self.commitTransaction(tid2, objs, txn):
            expected = {txn[-1]: tid2}
            self.assertEqual(self.db.getUnfinishedTIDDict(), expected)
            # no changes
            self.db.storeTransaction(tid3, objs, None, False)
            self.assertEqual(self.db.getUnfinishedTIDDict(), expected)
            # a second txn known by objs only
            expected[tid4] = None
            self.db.storeTransaction(tid4, objs, None)
            self.assertEqual(self.db.getUnfinishedTIDDict(), expected)
            self.db.abortTransaction(tid4)
        # nothing pending
        self.assertEqual(self.db.getUnfinishedTIDDict(), {})
155 156 157 158 159

    def test_getObject(self):
        oid1, = self.getOIDs(1)
        tid1, tid2 = self.getTIDs(2)
        FOUND_BUT_NOT_VISIBLE = False
160 161 162
        OBJECT_T1_NO_NEXT = (tid1, None, 1, "0"*20, '', None)
        OBJECT_T1_NEXT = (tid1, tid2, 1, "0"*20, '', None)
        OBJECT_T2 = (tid2, None, 1, "0"*20, '', None)
163 164 165 166 167 168
        txn1, objs1 = self.getTransaction([oid1])
        txn2, objs2 = self.getTransaction([oid1])
        # non-present
        self.assertEqual(self.db.getObject(oid1), None)
        self.assertEqual(self.db.getObject(oid1, tid1), None)
        self.assertEqual(self.db.getObject(oid1, before_tid=tid1), None)
Julien Muchembled's avatar
Julien Muchembled committed
169
        # one non-committed version
170 171 172 173
        with self.commitTransaction(tid1, objs1, txn1):
            self.assertEqual(self.db.getObject(oid1), None)
            self.assertEqual(self.db.getObject(oid1, tid1), None)
            self.assertEqual(self.db.getObject(oid1, before_tid=tid1), None)
Julien Muchembled's avatar
Julien Muchembled committed
174
        # one committed version
175 176 177 178
        self.assertEqual(self.db.getObject(oid1), OBJECT_T1_NO_NEXT)
        self.assertEqual(self.db.getObject(oid1, tid1), OBJECT_T1_NO_NEXT)
        self.assertEqual(self.db.getObject(oid1, before_tid=tid1),
            FOUND_BUT_NOT_VISIBLE)
Julien Muchembled's avatar
Julien Muchembled committed
179
        # two version available, one non-committed
180 181 182 183 184 185 186 187 188
        with self.commitTransaction(tid2, objs2, txn2):
            self.assertEqual(self.db.getObject(oid1), OBJECT_T1_NO_NEXT)
            self.assertEqual(self.db.getObject(oid1, tid1), OBJECT_T1_NO_NEXT)
            self.assertEqual(self.db.getObject(oid1, before_tid=tid1),
                FOUND_BUT_NOT_VISIBLE)
            self.assertEqual(self.db.getObject(oid1, tid2),
                FOUND_BUT_NOT_VISIBLE)
            self.assertEqual(self.db.getObject(oid1, before_tid=tid2),
                OBJECT_T1_NO_NEXT)
Julien Muchembled's avatar
Julien Muchembled committed
189
        # two committed versions
190
        self.assertEqual(self.db.getObject(oid1), OBJECT_T2)
191
        self.assertEqual(self.db.getObject(oid1, tid1), OBJECT_T1_NEXT)
192 193 194 195 196 197 198
        self.assertEqual(self.db.getObject(oid1, before_tid=tid1),
            FOUND_BUT_NOT_VISIBLE)
        self.assertEqual(self.db.getObject(oid1, tid2), OBJECT_T2)
        self.assertEqual(self.db.getObject(oid1, before_tid=tid2),
            OBJECT_T1_NEXT)

    def test_setPartitionTable(self):
199
        db = self.getDB()
200
        ptid = 1
201 202 203 204
        uuid = self.getStorageUUID()
        cell1 = 0, uuid, CellStates.OUT_OF_DATE
        cell2 = 1, uuid, CellStates.UP_TO_DATE
        cell3 = 1, uuid, CellStates.DISCARDED
205
        # no partition table
206
        self.assertEqual(list(db.getPartitionTable()), [])
207
        # set one
208
        db.changePartitionTable(ptid, [cell1], 1)
209
        result = db.getPartitionTable()
210
        self.assertEqual(list(result), [cell1])
211
        # then another
212
        db.changePartitionTable(ptid, [cell2], 1)
213
        result = db.getPartitionTable()
214
        self.assertEqual(list(result), [cell2])
215
        # drop discarded cells
216
        db.changePartitionTable(ptid, [cell2, cell3], 1)
217
        result = db.getPartitionTable()
218
        self.assertEqual(list(result), [])
219 220

    def test_changePartitionTable(self):
221
        db = self.getDB()
222
        ptid = 1
223 224 225 226
        uuid = self.getStorageUUID()
        cell1 = 0, uuid, CellStates.OUT_OF_DATE
        cell2 = 1, uuid, CellStates.UP_TO_DATE
        cell3 = 1, uuid, CellStates.DISCARDED
227
        # no partition table
228
        self.assertEqual(list(db.getPartitionTable()), [])
229
        # set one
230 231
        db.changePartitionTable(ptid, [cell1])
        result = db.getPartitionTable()
232
        self.assertEqual(list(result), [cell1])
233
        # add more entries
234 235
        db.changePartitionTable(ptid, [cell2])
        result = db.getPartitionTable()
236
        self.assertEqual(set(result), {cell1, cell2})
237
        # drop discarded cells
238 239
        db.changePartitionTable(ptid, [cell2, cell3])
        result = db.getPartitionTable()
240
        self.assertEqual(list(result), [cell1])
241

242
    def test_commitTransaction(self):
243 244 245 246 247
        oid1, oid2 = self.getOIDs(2)
        tid1, tid2 = self.getTIDs(2)
        txn1, objs1 = self.getTransaction([oid1])
        txn2, objs2 = self.getTransaction([oid2])
        # nothing in database
248
        self.assertEqual(self.db.getLastIDs(), (None, {}, {}, None))
249
        self.assertEqual(self.db.getUnfinishedTIDDict(), {})
250 251 252 253 254 255
        self.assertEqual(self.db.getObject(oid1), None)
        self.assertEqual(self.db.getObject(oid2), None)
        self.assertEqual(self.db.getTransaction(tid1, True), None)
        self.assertEqual(self.db.getTransaction(tid2, True), None)
        self.assertEqual(self.db.getTransaction(tid1, False), None)
        self.assertEqual(self.db.getTransaction(tid2, False), None)
256 257 258 259 260 261 262 263
        with self.commitTransaction(tid1, objs1, txn1), \
             self.commitTransaction(tid2, objs2, txn2):
            self.assertEqual(self.db.getTransaction(tid1, True),
                             ([oid1], 'user', 'desc', 'ext', False, p64(1)))
            self.assertEqual(self.db.getTransaction(tid2, True),
                             ([oid2], 'user', 'desc', 'ext', False, p64(2)))
            self.assertEqual(self.db.getTransaction(tid1, False), None)
            self.assertEqual(self.db.getTransaction(tid2, False), None)
264
        result = self.db.getTransaction(tid1, True)
265
        self.assertEqual(result, ([oid1], 'user', 'desc', 'ext', False, p64(1)))
266
        result = self.db.getTransaction(tid2, True)
267
        self.assertEqual(result, ([oid2], 'user', 'desc', 'ext', False, p64(2)))
268
        result = self.db.getTransaction(tid1, False)
269
        self.assertEqual(result, ([oid1], 'user', 'desc', 'ext', False, p64(1)))
270
        result = self.db.getTransaction(tid2, False)
271
        self.assertEqual(result, ([oid2], 'user', 'desc', 'ext', False, p64(2)))
272 273

    def test_deleteTransaction(self):
274 275 276 277 278 279
        txn, objs = self.getTransaction([])
        tid = txn[-1]
        self.db.storeTransaction(tid, objs, txn, False)
        self.assertEqual(self.db.getTransaction(tid), txn)
        self.db.deleteTransaction(tid)
        self.assertEqual(self.db.getTransaction(tid), None)
280 281 282 283 284 285

    def test_deleteObject(self):
        oid1, oid2 = self.getOIDs(2)
        tid1, tid2 = self.getTIDs(2)
        txn1, objs1 = self.getTransaction([oid1, oid2])
        txn2, objs2 = self.getTransaction([oid1, oid2])
286 287 288 289 290 291
        tid1 = txn1[-1]
        tid2 = txn2[-1]
        self.db.storeTransaction(tid1, objs1, txn1, False)
        self.db.storeTransaction(tid2, objs2, txn2, False)
        self.assertEqual(self.db.getObject(oid1, tid=tid1),
            (tid1, tid2, 1, "0" * 20, '', None))
292
        self.db.deleteObject(oid1)
293 294
        self.assertIs(self.db.getObject(oid1, tid=tid1), None)
        self.assertIs(self.db.getObject(oid1, tid=tid2), None)
295
        self.db.deleteObject(oid2, serial=tid1)
296
        self.assertIs(self.db.getObject(oid2, tid=tid1), False)
297 298
        self.assertEqual(self.db.getObject(oid2, tid=tid2),
            (tid2, None, 1, "0" * 20, '', None))
299

300 301 302
    def test_deleteRange(self):
        np = 4
        self.setNumPartitions(np)
303
        t1, t2, t3 = map(p64, (1, 2, 3))
304 305 306
        oid_list = self.getOIDs(np * 2)
        for tid in t1, t2, t3:
            txn, objs = self.getTransaction(oid_list)
307
            self.db.storeTransaction(tid, objs, txn, False)
308 309 310
        def check(offset, tid_list, *tids):
            self.assertEqual(self.db.getReplicationTIDList(ZERO_TID,
                MAX_TID, len(tid_list) + 1, offset), tid_list)
311
            expected = [(t, oid_list[offset+i]) for t in tids for i in (0, np)]
312 313 314 315 316 317 318 319 320
            self.assertEqual(self.db.getReplicationObjectList(ZERO_TID,
                MAX_TID, len(expected) + 1, offset, ZERO_OID), expected)
        self.db._deleteRange(0, MAX_TID)
        self.db._deleteRange(0, max_tid=ZERO_TID)
        check(0, [], t1, t2, t3)
        self.db._deleteRange(0);             check(0, [])
        self.db._deleteRange(1, t2);         check(1, [t1], t1, t2)
        self.db._deleteRange(2, max_tid=t2); check(2, [], t3)
        self.db._deleteRange(3, t1, t2);     check(3, [t3], t1, t3)
321

322 323 324 325 326 327
    def test_getTransaction(self):
        oid1, oid2 = self.getOIDs(2)
        tid1, tid2 = self.getTIDs(2)
        txn1, objs1 = self.getTransaction([oid1])
        txn2, objs2 = self.getTransaction([oid2])
        # get from temporary table or not
328 329 330
        with self.commitTransaction(tid1, objs1, txn1), \
             self.commitTransaction(tid2, objs2, txn2, None):
            pass
331
        result = self.db.getTransaction(tid1, True)
332
        self.assertEqual(result, ([oid1], 'user', 'desc', 'ext', False, p64(1)))
333
        result = self.db.getTransaction(tid2, True)
334
        self.assertEqual(result, ([oid2], 'user', 'desc', 'ext', False, p64(2)))
335 336
        # get from non-temporary only
        result = self.db.getTransaction(tid1, False)
337
        self.assertEqual(result, ([oid1], 'user', 'desc', 'ext', False, p64(1)))
338 339 340
        self.assertEqual(self.db.getTransaction(tid2, False), None)

    def test_getObjectHistory(self):
341
        oid = p64(1)
342 343 344 345 346
        tid1, tid2, tid3 = self.getTIDs(3)
        txn1, objs1 = self.getTransaction([oid])
        txn2, objs2 = self.getTransaction([oid])
        txn3, objs3 = self.getTransaction([oid])
        # one revision
347
        self.db.storeTransaction(tid1, objs1, txn1, False)
348 349 350 351 352
        result = self.db.getObjectHistory(oid, 0, 3)
        self.assertEqual(result, [(tid1, 0)])
        result = self.db.getObjectHistory(oid, 1, 1)
        self.assertEqual(result, None)
        # two revisions
353
        self.db.storeTransaction(tid2, objs2, txn2, False)
354 355 356 357 358 359 360 361 362 363
        result = self.db.getObjectHistory(oid, 0, 3)
        self.assertEqual(result, [(tid2, 0), (tid1, 0)])
        result = self.db.getObjectHistory(oid, 1, 3)
        self.assertEqual(result, [(tid1, 0)])
        result = self.db.getObjectHistory(oid, 2, 3)
        self.assertEqual(result, None)

    def _storeTransactions(self, count):
        # use OID generator to know result of tid % N
        tid_list = self.getOIDs(count)
364
        oid = p64(1)
365 366
        for tid in tid_list:
            txn, objs = self.getTransaction([oid])
367
            self.db.storeTransaction(tid, objs, txn, False)
368 369 370
        return tid_list

    def test_getTIDList(self):
371
        self.setNumPartitions(2, True)
372 373 374
        tid1, tid2, tid3, tid4 = self._storeTransactions(4)
        # get tids
        # - all partitions
375
        result = self.db.getTIDList(0, 4, [0, 1])
376 377
        self.checkSet(result, [tid1, tid2, tid3, tid4])
        # - one partition
378
        result = self.db.getTIDList(0, 4, [0])
379
        self.checkSet(result, [tid1, tid3])
380
        result = self.db.getTIDList(0, 4, [1])
381 382
        self.checkSet(result, [tid2, tid4])
        # get a subset of tids
383
        result = self.db.getTIDList(0, 1, [0])
384
        self.checkSet(result, [tid3]) # desc order
385
        result = self.db.getTIDList(1, 1, [1])
386
        self.checkSet(result, [tid2])
387
        result = self.db.getTIDList(2, 2, [0])
388 389 390
        self.checkSet(result, [])

    def test_getReplicationTIDList(self):
391
        self.setNumPartitions(2, True)
392 393
        tid1, tid2, tid3, tid4 = self._storeTransactions(4)
        # - one partition
394
        result = self.db.getReplicationTIDList(ZERO_TID, MAX_TID, 10, 0)
395 396
        self.checkSet(result, [tid1, tid3])
        # - another partition
397
        result = self.db.getReplicationTIDList(ZERO_TID, MAX_TID, 10, 1)
398 399
        self.checkSet(result, [tid2, tid4])
        # - min_tid is inclusive
400
        result = self.db.getReplicationTIDList(tid3, MAX_TID, 10, 0)
401 402
        self.checkSet(result, [tid3])
        # - max tid is inclusive
403
        result = self.db.getReplicationTIDList(ZERO_TID, tid2, 10, 0)
404 405
        self.checkSet(result, [tid1])
        # - limit
406
        result = self.db.getReplicationTIDList(ZERO_TID, MAX_TID, 1, 0)
407 408
        self.checkSet(result, [tid1])

409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
    def test_checkRange(self):
        def check(trans, obj, *args):
            self.assertEqual(trans, self.db.checkTIDRange(*args))
            self.assertEqual(obj, self.db.checkSerialRange(*(args+(ZERO_OID,))))
        self.setNumPartitions(2, True)
        tid1, tid2, tid3, tid4 = self._storeTransactions(4)
        z = 0, ZERO_HASH, ZERO_TID, ZERO_HASH, ZERO_OID
        # - one partition
        check((2, a2b_hex('84320eb8dbbe583f67055c15155ab6794f11654d'), tid3),
            z,
            0, 10, ZERO_TID, MAX_TID)
        # - another partition
        check((2, a2b_hex('1f02f98cf775a9e0ce9252ff5972dce728c4ddb0'), tid4),
            (4, a2b_hex('e5b47bddeae2096220298df686737d939a27d736'), tid4,
                a2b_hex('1e9093698424b5370e19acd2d5fc20dcd56a32cd'), p64(1)),
            1, 10, ZERO_TID, MAX_TID)
        self.assertEqual(
            (3, a2b_hex('b85e2d4914e22b5ad3b82b312b3dc405dc17dcb8'), tid4,
                a2b_hex('1b6d73ecdc064595fe915a5c26da06b195caccaa'), p64(1)),
            self.db.checkSerialRange(1, 10, ZERO_TID, MAX_TID, p64(2)))
        # - min_tid is inclusive
        check((1, a2b_hex('da4b9237bacccdf19c0760cab7aec4a8359010b0'), tid3),
            z,
            0, 10, tid3, MAX_TID)
        # - max tid is inclusive
        x = 1, a2b_hex('b6589fc6ab0dc82cf12099d1c2d40ab994e8410c'), tid1
        check(x, z, 0, 10, ZERO_TID, tid2)
        # - limit
        y = 1, a2b_hex('356a192b7913b04c54574d18c28d46e6395428ab'), tid2
        check(y, x + y[1:], 1, 1, ZERO_TID, MAX_TID)

440
    def test_findUndoTID(self):
441
        self.setNumPartitions(4, True)
442 443 444 445 446
        db = self.db
        tid1 = self.getNextTID()
        tid2 = self.getNextTID()
        tid3 = self.getNextTID()
        tid4 = self.getNextTID()
Vincent Pelletier's avatar
Vincent Pelletier committed
447
        tid5 = self.getNextTID()
448
        oid1 = p64(1)
449 450 451
        foo = db.holdData("3" * 20, 'foo', 0)
        bar = db.holdData("4" * 20, 'bar', 0)
        db.releaseData((foo, bar))
452 453
        db.storeTransaction(
            tid1, (
454
                (oid1, foo, None),
455 456 457 458 459 460
            ), None, temporary=False)

        # Undoing oid1 tid1, OK: tid1 is latest
        # Result: current tid is tid1, data_tid is None (undoing object
        # creation)
        self.assertEqual(
Vincent Pelletier's avatar
Vincent Pelletier committed
461
            db.findUndoTID(oid1, tid5, tid4, tid1, None),
462 463 464 465 466
            (tid1, None, True))

        # Store a new transaction
        db.storeTransaction(
            tid2, (
467
                (oid1, bar, None),
468 469 470 471 472
            ), None, temporary=False)

        # Undoing oid1 tid2, OK: tid2 is latest
        # Result: current tid is tid2, data_tid is tid1
        self.assertEqual(
Vincent Pelletier's avatar
Vincent Pelletier committed
473
            db.findUndoTID(oid1, tid5, tid4, tid2, None),
474 475 476 477 478
            (tid2, tid1, True))

        # Undoing oid1 tid1, Error: tid2 is latest
        # Result: current tid is tid2, data_tid is -1
        self.assertEqual(
Vincent Pelletier's avatar
Vincent Pelletier committed
479
            db.findUndoTID(oid1, tid5, tid4, tid1, None),
480 481 482 483
            (tid2, None, False))

        # Undoing oid1 tid1 with tid2 being undone in same transaction,
        # OK: tid1 is latest
484
        # Result: current tid is tid1, data_tid is None (undoing object
485 486 487 488
        # creation)
        # Explanation of transaction_object: oid1, no data but a data serial
        # to tid1
        self.assertEqual(
Vincent Pelletier's avatar
Vincent Pelletier committed
489
            db.findUndoTID(oid1, tid5, tid4, tid1,
490
                (u64(oid1), None, tid1)),
491
            (tid1, None, True))
492 493 494 495

        # Store a new transaction
        db.storeTransaction(
            tid3, (
496
                (oid1, None, tid1),
497 498 499 500 501 502
            ), None, temporary=False)

        # Undoing oid1 tid1, OK: tid3 is latest with tid1 data
        # Result: current tid is tid2, data_tid is None (undoing object
        # creation)
        self.assertEqual(
Vincent Pelletier's avatar
Vincent Pelletier committed
503
            db.findUndoTID(oid1, tid5, tid4, tid1, None),
504 505 506 507
            (tid3, None, True))

if __name__ == "__main__":
    unittest.main()