Commit c32055fc authored by Kirill Smelkov's avatar Kirill Smelkov

X wcfs/xbtree: ΔBtail tests += ø -> Tree; Tree -> ø

We need to test this because the following case happens often in
practice: the tree did not exist when ΔBtail was started, later the tree
is created and ΔBtail is recomputed. Since we are testing tree transitions
that involves delete, test it uniformly for all cases.

Currently Update test, if Fatal("TODO") is removed fails with:

    --- FAIL: TestΔBTail/Update/T2,5/B1:a-B2:b,4:d-B8:h→ø (0.00s)
panic: ΔBtail update 03e036b7f59f86cc -> 03e036b7f5a28899: treediff 03e036b7f59f86cc..03e036b7f5a28899 000000000000000c: diffT 03e036b7f59f86cc:000000000000000c 03e036b7f5a28899:000000000000000c: BTrees.LOBTree.LOBTree(000000000000000c): activate: file:///tmp/δBTail851157667/1.fs: load 03e036b7f5a28899:000000000000000c: 000000000000000c: object was deleted @03e036b7f5a28899 [recovered]
parent b5571461
...@@ -119,7 +119,7 @@ import sys ...@@ -119,7 +119,7 @@ import sys
from golang import func, defer, panic from golang import func, defer, panic
from golang import time from golang import time
from ZODB import DB from ZODB import DB
from ZODB.Connection import Connection from ZODB.Connection import Connection, TransactionMetaData
from ZODB.MappingStorage import MappingStorage from ZODB.MappingStorage import MappingStorage
import transaction import transaction
import itertools import itertools
...@@ -202,6 +202,14 @@ def TreesSrv(zstor, r): ...@@ -202,6 +202,14 @@ def TreesSrv(zstor, r):
treetxtPrev = zctx.ztreetxt(ztree) treetxtPrev = zctx.ztreetxt(ztree)
for treetxt in xreadlines(r): for treetxt in xreadlines(r):
subj = "treegen/tree: %s" % treetxt
# ø commands to delete the tree
if treetxt == "ø":
head = commitDelete(ztree, subj)
xprint("%s" % ashex(head))
continue
# mark tree as changed if the same topology is requested twice. # mark tree as changed if the same topology is requested twice.
# this ensures we can actually make a non-empty commit # this ensures we can actually make a non-empty commit
if treetxt == treetxtPrev: if treetxt == treetxtPrev:
...@@ -230,7 +238,7 @@ def TreesSrv(zstor, r): ...@@ -230,7 +238,7 @@ def TreesSrv(zstor, r):
xbtree.Restructure(ztree, tree) xbtree.Restructure(ztree, tree)
# commit tree to storage # commit tree to storage
head = commit("treegen/tree: %s" % treetxt) head = commit(subj)
# verify what was persisted to storage is indeed what we wanted to persist # verify what was persisted to storage is indeed what we wanted to persist
zctx.zconn.cacheMinimize() zctx.zconn.cacheMinimize()
...@@ -444,6 +452,32 @@ def commit(description, skipIfEmpty=False): # -> tid | None ...@@ -444,6 +452,32 @@ def commit(description, skipIfEmpty=False): # -> tid | None
txn.commit() txn.commit()
return obj._p_serial return obj._p_serial
# commitDelete commits deletion of obj with description.
def commitDelete(obj, description): # -> tid
txn = transaction.get()
zstor = obj._p_jar._db.storage
# deleteObject works only at IStorage level, and at that low level
# zstor requires ZODB.IStorageTransactionMetaData not txn (ITransaction)
txn_stormeta = TransactionMetaData(txn.user, description, txn.extension)
zstor.tpc_begin(txn_stormeta)
zstor.deleteObject(obj._p_oid, obj._p_serial, txn_stormeta)
zstor.tpc_vote(txn_stormeta)
_ = []
zstor.tpc_finish(txn_stormeta, lambda tid: _.append(tid))
assert len(_) == 1, _
tid = _[0]
# object in the database is now current as of committed tid
# adjust obj's serial so that the next change to it (possibly recreating
# the object), does not fail with ConflictError.
obj._p_serial = tid
# reset transaction to a new one
transaction.begin()
return tid
# ztreetxt returns text representation of a ZODB tree. # ztreetxt returns text representation of a ZODB tree.
@func(ZCtx) @func(ZCtx)
......
...@@ -608,6 +608,14 @@ func xverifyΔBTail_Update(t *testing.T, subj string, db *zodb.DB, treeRoot zodb ...@@ -608,6 +608,14 @@ func xverifyΔBTail_Update(t *testing.T, subj string, db *zodb.DB, treeRoot zodb
// verify transition at1->at2 for all initial states of tracked {keys} from kv1 + kv2 + ∞ // verify transition at1->at2 for all initial states of tracked {keys} from kv1 + kv2 + ∞
t.Run(fmt.Sprintf("Update/%s→%s", t1.tree, t2.tree), func(t *testing.T) { t.Run(fmt.Sprintf("Update/%s→%s", t1.tree, t2.tree), func(t *testing.T) {
if t1.tree == DEL {
// Track(k) -> Update ; requires that tree exists at Track time
t.Skip("Track(tree1) needs tree1 to exist")
}
if t2.tree == DEL {
t.Fatal("TODO")
}
allKeys := allTestKeys(t1, t2) allKeys := allTestKeys(t1, t2)
allKeyv := allKeys.SortedElements() allKeyv := allKeys.SortedElements()
...@@ -820,6 +828,10 @@ func (δbtail *ΔBtail) assertTrack(t *testing.T, subj string, trackSetOK, track ...@@ -820,6 +828,10 @@ func (δbtail *ΔBtail) assertTrack(t *testing.T, subj string, trackSetOK, track
// t0->t1 exercises from-scratch rebuild, // t0->t1 exercises from-scratch rebuild,
// t1->t2 further exercises incremental rebuild. // t1->t2 further exercises incremental rebuild.
func xverifyΔBTail_rebuild(t *testing.T, db *zodb.DB, treeRoot zodb.Oid, t0, t1, t2 *tTreeCommit) { func xverifyΔBTail_rebuild(t *testing.T, db *zodb.DB, treeRoot zodb.Oid, t0, t1, t2 *tTreeCommit) {
// XXX handle DEL
// XXX Update -> Track -> rebuild ...
// XXX can start with non-existing tree
// t1 := t2.prev // t1 := t2.prev
// t0 := t1.prev // t0 := t1.prev
t.Run(fmt.Sprintf("rebuild/%s→%s", t0.tree, t1.tree), func(t *testing.T) { t.Run(fmt.Sprintf("rebuild/%s→%s", t0.tree, t1.tree), func(t *testing.T) {
...@@ -1220,7 +1232,25 @@ func testΔBTail(t *testing.T, testq chan ΔBTestEntry) { ...@@ -1220,7 +1232,25 @@ func testΔBTail(t *testing.T, testq chan ΔBTestEntry) {
exc.Raisef("treegen -> %s ; watchq -> %s", tid, δZ) exc.Raisef("treegen -> %s ; watchq -> %s", tid, δZ)
} }
xkv := XGetTree(db, δZ.Tid, tg.treeRoot) // load tree structure from the db
// if the tree does not exist yet - report its structure as empty
var xkv RBucketSet
if tree != DEL {
xkv = XGetTree(db, δZ.Tid, tg.treeRoot)
} else {
xkv = RBucketSet{
&RBucket{
oid: zodb.InvalidOid,
parent: &RTree{
oid: zodb.InvalidOid,
parent: nil,
},
lo: KeyMin,
hi_: KeyMax,
kv: map[Key]string{},
},
}
}
return &tTreeCommit{ return &tTreeCommit{
tree: tree, tree: tree,
...@@ -1232,7 +1262,7 @@ func testΔBTail(t *testing.T, testq chan ΔBTestEntry) { ...@@ -1232,7 +1262,7 @@ func testΔBTail(t *testing.T, testq chan ΔBTestEntry) {
var t0 *tTreeCommit var t0 *tTreeCommit
t1 := &tTreeCommit{ t1 := &tTreeCommit{
tree: "ø", // initial XXX ok? tree: "T/B:", // treegen.py creates the tree as initially empty
prev: nil, // XXX ok? prev: nil, // XXX ok?
at: tg.head, at: tg.head,
xkv: XGetTree(db, tg.head, tg.treeRoot), xkv: XGetTree(db, tg.head, tg.treeRoot),
...@@ -1410,6 +1440,13 @@ func TestΔBTail(t *testing.T) { ...@@ -1410,6 +1440,13 @@ func TestΔBTail(t *testing.T) {
"T3/B1:a,2:b-B4:d,8:h", "T3/B1:a,2:b-B4:d,8:h",
"T2,5/B1:a-B2:b,4:d-B8:h", // XXX add A "T2,5/B1:a-B2:b,4:d-B8:h", // XXX add A
// tree deletion
// having ø in the middle of the test cases exercises all:
// * `ø -> Tree ...` (tree is created anew),
// * `... Tree -> ø` (tree is deleted), and
// * `Tree -> ø -> Tree` (tree is deleted and then recreated)
DEL,
// found by AllStructs ([1] is not changed, but because B1 is // found by AllStructs ([1] is not changed, but because B1 is
// unlinked and 1 migrates to other bucket, changes in that // unlinked and 1 migrates to other bucket, changes in that
// other bucket must be included into δT) // other bucket must be included into δT)
...@@ -1671,7 +1708,7 @@ func TestIntSets(t *testing.T) { ...@@ -1671,7 +1708,7 @@ func TestIntSets(t *testing.T) {
// kvdiff returns difference in between kv1 and kv2. // kvdiff returns difference in between kv1 and kv2.
var DEL = "ø" // DEL means key deletion var DEL = "ø" // DEL means deletion
type Δstring struct { type Δstring struct {
Old string Old string
New string New string
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment