Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
W
wendelin.core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
Kirill Smelkov
wendelin.core
Commits
00b6ac98
Commit
00b6ac98
authored
Jul 11, 2021
by
Kirill Smelkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
.
parent
84986acb
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
1204 additions
and
1195 deletions
+1204
-1195
wcfs/internal/xbtree/δbtail_test.go
wcfs/internal/xbtree/δbtail_test.go
+1204
-1195
No files found.
wcfs/internal/xbtree/δbtail_test.go
View file @
00b6ac98
...
@@ -61,47 +61,6 @@ import (
...
@@ -61,47 +61,6 @@ import (
type
Δstring
=
xbtreetest
.
Δstring
type
Δstring
=
xbtreetest
.
Δstring
// trackSet returns what should be ΔBtail.trackSet coverage for specified tracked key set.
func
trackSet
(
rbs
xbtreetest
.
RBucketSet
,
tracked
setKey
)
blib
.
PPTreeSubSet
{
// nil = don't compute keyCover
// (trackSet is called from inside hot inner loop of rebuild test)
return
_trackSetWithCov
(
rbs
,
tracked
,
nil
)
}
// trackSetWithCov returns what should be ΔBtail.trackSet and its key coverage for specified tracked key set.
func
trackSetWithCov
(
rbs
xbtreetest
.
RBucketSet
,
tracked
setKey
)
(
trackSet
blib
.
PPTreeSubSet
,
keyCover
*
blib
.
RangedKeySet
)
{
keyCover
=
&
blib
.
RangedKeySet
{}
trackSet
=
_trackSetWithCov
(
rbs
,
tracked
,
keyCover
)
return
trackSet
,
keyCover
}
func
_trackSetWithCov
(
rbs
xbtreetest
.
RBucketSet
,
tracked
setKey
,
outKeyCover
*
blib
.
RangedKeySet
)
(
trackSet
blib
.
PPTreeSubSet
)
{
trackSet
=
blib
.
PPTreeSubSet
{}
for
k
:=
range
tracked
{
kb
:=
rbs
.
Get
(
k
)
if
outKeyCover
!=
nil
{
outKeyCover
.
AddRange
(
kb
.
Keycov
)
}
trackSet
.
AddPath
(
kb
.
Path
())
}
return
trackSet
}
// XGetδKV translates {k -> δ<oid>} to {k -> δ(ZBlk(oid).data)} according to t1..t2 db snapshots.
func
XGetδKV
(
t1
,
t2
*
xbtreetest
.
Commit
,
δkvOid
map
[
Key
]
ΔValue
)
map
[
Key
]
Δstring
{
δkv
:=
make
(
map
[
Key
]
Δstring
,
len
(
δkvOid
))
for
k
,
δvOid
:=
range
δkvOid
{
δkv
[
k
]
=
Δstring
{
Old
:
t1
.
XGetBlkData
(
δvOid
.
Old
),
New
:
t2
.
XGetBlkData
(
δvOid
.
New
),
}
}
return
δkv
}
// KAdjMatrix is adjacency matrix that describes how set of tracked keys
// KAdjMatrix is adjacency matrix that describes how set of tracked keys
// changes (always grow) when tree topology is updated from A to B.
// changes (always grow) when tree topology is updated from A to B.
//
//
...
@@ -129,1436 +88,1486 @@ func XGetδKV(t1, t2 *xbtreetest.Commit, δkvOid map[Key]ΔValue) map[Key]Δstri
...
@@ -129,1436 +88,1486 @@ func XGetδKV(t1, t2 *xbtreetest.Commit, δkvOid map[Key]ΔValue) map[Key]Δstri
//
//
// XXX fix definition for "and changed, or coverage changed"
// XXX fix definition for "and changed, or coverage changed"
//
//
// Use:
//
// - KAdj(A,B) to build adjacency matrix for A -> B transition.
// - kadj.Map(keys) to compute kadj·keys.
// - kadj1.Mul(kadj2) to compute kadj1·kadj2.
//
// Note: adjacency matrix is symmetric (KAdj verifies this at runtime):
// Note: adjacency matrix is symmetric (KAdj verifies this at runtime):
//
//
//
kadj(A,B) == ka
dj(B,A)
//
KAdj(A,B) == KA
dj(B,A)
type
KAdjMatrix
map
[
Key
]
setKey
type
KAdjMatrix
map
[
Key
]
setKey
// Map returns kadj·keys .
func
(
kadj
KAdjMatrix
)
Map
(
keys
setKey
)
setKey
{
res
:=
make
(
setKey
,
len
(
keys
))
for
k
:=
range
keys
{
to
,
ok
:=
kadj
[
k
]
if
!
ok
{
panicf
(
"kadj.Map: %d ∉ kadj
\n\n
kadj: %v"
,
k
,
kadj
)
}
res
.
Update
(
to
)
}
return
res
}
// Mul returns kadjA·kadjB .
//
// (kadjA·kadjB).Map(keys) = kadjA.Map(kadjB.Map(keys))
func
(
kadjA
KAdjMatrix
)
Mul
(
kadjB
KAdjMatrix
)
KAdjMatrix
{
// ~ assert kadjA.keys == kadjB.keys
// check only len here; the rest will be asserted by Map
if
len
(
kadjA
)
!=
len
(
kadjB
)
{
panicf
(
"kadj.Mul: different keys:
\n\n
kadjA: %v
\n
kadjB: %v"
,
kadjA
,
kadjB
)
}
kadj
:=
make
(
KAdjMatrix
,
len
(
kadjB
))
// ΔBTestEntry represents one entry in ΔBTail tests.
for
k
,
tob
:=
range
kadjB
{
type
ΔBTestEntry
struct
{
kadj
[
k
]
=
kadjA
.
Map
(
tob
)
tree
string
// next tree topology
}
kadjOK
KAdjMatrix
// adjacency matrix against previous case (optional)
return
kadj
flags
ΔBTestFlags
}
}
// KAdj builds adjacency matrix for t1 -> t2 transition.
type
ΔBTestFlags
int
//
const
ΔBTest_SkipUpdate
ΔBTestFlags
=
1
// skip verifying Update for this test entry
// The set of keys for which kadj matrix is computed can be optionally provided.
const
ΔBTest_SkipRebuild
ΔBTestFlags
=
2
// skip verifying rebuild for this test entry
// This set of keys defaults to allTestKeys(t1,t2).
//
// KAdj itself is verified by testΔBTail on entries with .kadjOK set.
func
KAdj
(
t1
,
t2
*
xbtreetest
.
Commit
,
keysv
...
setKey
)
(
kadj
KAdjMatrix
)
{
// assert KAdj(A,B) == KAdj(B,A)
kadj12
:=
_KAdj
(
t1
,
t2
,
keysv
...
)
kadj21
:=
_KAdj
(
t2
,
t1
,
keysv
...
)
if
!
reflect
.
DeepEqual
(
kadj12
,
kadj21
)
{
panicf
(
"KAdj not symmetric:
\n
t1: %s
\n
t2: %s
\n
kadj12: %v
\n
kadj21: %v"
,
t1
.
Tree
,
t2
.
Tree
,
kadj12
,
kadj21
)
}
return
kadj12
}
const
debugKAdj
=
false
// ΔBTest converts xtest into ΔBTestEntry.
func
debugfKAdj
(
format
string
,
argv
...
interface
{})
{
// xtest can be string|ΔBTestEntry.
if
debugKAdj
{
func
ΔBTest
(
xtest
interface
{})
ΔBTestEntry
{
fmt
.
Printf
(
format
,
argv
...
)
var
test
ΔBTestEntry
switch
xtest
:=
xtest
.
(
type
)
{
case
string
:
test
.
tree
=
xtest
test
.
kadjOK
=
nil
test
.
flags
=
0
case
ΔBTestEntry
:
test
=
xtest
default
:
panicf
(
"BUG: ΔBTest: bad type %T"
,
xtest
)
}
}
return
test
}
}
func
_KAdj
(
t1
,
t2
*
xbtreetest
.
Commit
,
keysv
...
setKey
)
(
kadj
KAdjMatrix
)
{
// TestΔBTail verifies ΔBTail for explicitly provided tree topologies.
var
keys
setKey
func
TestΔBTail
(
t
*
testing
.
T
)
{
switch
len
(
keysv
)
{
// K is shorthand for setKey
case
0
:
K
:=
func
(
keyv
...
Key
)
setKey
{
keys
=
allTestKeys
(
t1
,
t2
)
ks
:=
setKey
{}
case
1
:
for
_
,
k
:=
range
keyv
{
ks
.
Add
(
k
)
}
keys
=
keysv
[
0
]
return
ks
default
:
}
panic
(
"multiple key sets on the call"
)
// oo is shorthand for KeyMax
const
oo
=
KeyMax
// A is shorthand for KAdjMatrix
type
A
=
KAdjMatrix
// Δ is shorthand for ΔBTestEntry
Δ
:=
func
(
tree
string
,
kadjOK
A
)
(
test
ΔBTestEntry
)
{
test
.
tree
=
tree
test
.
kadjOK
=
kadjOK
return
test
}
}
debugfKAdj
(
"
\n\n
_KAdj
\n
"
)
// test known cases going through tree1 -> tree2 -> ...
debugfKAdj
(
"t1: %s
\n
"
,
t1
.
Tree
)
testv
:=
[]
interface
{}
{
debugfKAdj
(
"t2: %s
\n
"
,
t2
.
Tree
)
// start from non-empty tree to verify both ->empty and empty-> transitions
debugfKAdj
(
"keys: %s
\n
"
,
keys
)
"T/B1:a,2:b"
,
defer
func
()
{
debugfKAdj
(
"kadj -> %v
\n
"
,
kadj
)
}()
// kadj = {} k -> adjacent keys.
// empty
// if k is tracked and covered by changed leaf -> changes to adjacents must be in Update(t1->t2).
"T/B:"
,
kadj
=
KAdjMatrix
{}
for
k
:=
range
keys
{
adj1
:=
setKey
{}
adj2
:=
setKey
{}
q1
:=
&
blib
.
RangedKeySet
{};
q1
.
Add
(
k
)
// +1
q2
:=
&
blib
.
RangedKeySet
{};
q2
.
Add
(
k
)
Δ
(
"T/B1:a"
,
done1
:=
&
blib
.
RangedKeySet
{}
A
{
1
:
K
(
1
,
oo
),
done2
:=
&
blib
.
RangedKeySet
{}
oo
:
K
(
1
,
oo
)}),
debugfKAdj
(
"
\n
k%s
\n
"
,
kstr
(
k
))
// +2
for
!
q1
.
Empty
()
||
!
q2
.
Empty
()
{
Δ
(
"T/B1:a,2:b"
,
debugfKAdj
(
"q1: %s
\t
done1: %s
\n
"
,
q1
,
done1
)
A
{
1
:
K
(
1
,
2
,
oo
),
debugfKAdj
(
"q2: %s
\t
done2: %s
\n
"
,
q2
,
done2
)
2
:
K
(
1
,
2
,
oo
),
for
_
,
r1
:=
range
q1
.
AllRanges
()
{
oo
:
K
(
1
,
2
,
oo
)}),
lo1
:=
r1
.
Lo
for
{
b1
:=
t1
.
Xkv
.
Get
(
lo1
)
debugfKAdj
(
" b1: %s
\n
"
,
b1
)
for
k_
:=
range
keys
{
if
b1
.
Keycov
.
Has
(
k_
)
{
adj1
.
Add
(
k_
)
debugfKAdj
(
" adj1 += %s
\t
-> %s
\n
"
,
kstr
(
k_
),
adj1
)
}
}
done1
.
AddRange
(
b1
.
Keycov
)
// q2 |= (b1.keyrange \ done2)
δq2
:=
&
blib
.
RangedKeySet
{}
δq2
.
AddRange
(
b1
.
Keycov
)
δq2
.
DifferenceInplace
(
done2
)
q2
.
UnionInplace
(
δq2
)
debugfKAdj
(
"q2 += %s
\t
-> %s
\n
"
,
δq2
,
q2
)
// continue with next right bucket until r1 coverage is complete
// -1
if
r1
.
Hi_
<=
b1
.
Keycov
.
Hi_
{
Δ
(
"T/B2:b"
,
break
A
{
1
:
K
(
1
,
2
,
oo
),
}
2
:
K
(
1
,
2
,
oo
),
lo1
=
b1
.
Keycov
.
Hi_
+
1
oo
:
K
(
1
,
2
,
oo
)}),
}
}
q1
.
Clear
()
for
_
,
r2
:=
range
q2
.
AllRanges
()
{
// 2: b->c
lo2
:=
r2
.
Lo
Δ
(
"T/B2:c"
,
for
{
A
{
2
:
K
(
2
,
oo
),
b2
:=
t2
.
Xkv
.
Get
(
lo2
)
oo
:
K
(
2
,
oo
)}),
debugfKAdj
(
" b2: %s
\n
"
,
b2
)
for
k_
:=
range
keys
{
if
b2
.
Keycov
.
Has
(
k_
)
{
adj2
.
Add
(
k_
)
debugfKAdj
(
" adj2 += %s
\t
-> %s
\n
"
,
kstr
(
k_
),
adj2
)
}
}
done2
.
AddRange
(
b2
.
Keycov
)
// q1 |= (b2.keyrange \ done1)
δq1
:=
&
blib
.
RangedKeySet
{}
δq1
.
AddRange
(
b2
.
Keycov
)
δq1
.
DifferenceInplace
(
done1
)
q1
.
UnionInplace
(
δq1
)
debugfKAdj
(
"q1 += %s
\t
-> %s
\n
"
,
δq1
,
q1
)
// continue with next right bucket until r2 coverage is complete
// +1 in new bucket (to the left)
if
r2
.
Hi_
<=
b2
.
Keycov
.
Hi_
{
Δ
(
"T2/B1:a-B2:c"
,
break
A
{
1
:
K
(
1
,
2
,
oo
),
}
2
:
K
(
1
,
2
,
oo
),
lo2
=
b2
.
Keycov
.
Hi_
+
1
oo
:
K
(
1
,
2
,
oo
)}),
}
}
q2
.
Clear
()
}
adj
:=
setKey
{};
adj
.
Update
(
adj1
);
adj
.
Update
(
adj2
)
// +3 in new bucket (to the right)
kadj
[
k
]
=
adj
Δ
(
"T2,3/B1:a-B2:c-B3:c"
,
}
A
{
1
:
K
(
1
),
2
:
K
(
2
,
3
,
oo
),
3
:
K
(
2
,
3
,
oo
),
oo
:
K
(
2
,
3
,
oo
)}),
return
kadj
// bucket split; +3 in new bucket
}
"T/B1:a,2:b"
,
Δ
(
"T2/B1:a-B2:b,3:c"
,
A
{
1
:
K
(
1
,
2
,
3
,
oo
),
2
:
K
(
1
,
2
,
3
,
oo
),
3
:
K
(
1
,
2
,
3
,
oo
),
oo
:
K
(
1
,
2
,
3
,
oo
)}),
// bucket split; +3 in new bucket; +4 +5 in another new bucket
// everything becomes tracked because original bucket had [-∞,∞) coverage
"T/B1:a,2:b"
,
Δ
(
"T2,4/B1:a-B2:b,3:c-B4:d,5:e"
,
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
2
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
3
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
4
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
5
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
)}),
// xverifyΔBTail_Update verifies how ΔBTail handles ZODB update for a tree with changes in between t1->t2.
// reflow of keys: even if tracked={1}, changes to all B nodes need to be rescanned:
//
// +B12 forces to look in -B23 which adds -3 into δ, which
// Note: this test verifies only single treediff step of ΔBtail.Update.
// forces to look into +B34 and so on.
// the cycling phase of update, that is responsible to recompute older
"T2,4,6/B1:a-B2:b,3:c-B4:d,5:e-B6:f,7:g"
,
// entries when key coverage grows, is exercised by
Δ
(
"T3,5,7/B1:g,2:f-B3:e,4:d-B5:c,6:b-B7:a"
,
// xverifyΔBTail_rebuild.
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
func
xverifyΔBTail_Update
(
t
*
testing
.
T
,
subj
string
,
db
*
zodb
.
DB
,
treeRoot
zodb
.
Oid
,
t1
,
t2
*
xbtreetest
.
Commit
)
{
2
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
// verify transition at1->at2 for all initial states of tracked {keys} from kv1 + kv2 + ∞
3
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
t
.
Run
(
fmt
.
Sprintf
(
"Update/%s→%s"
,
t1
.
Tree
,
t2
.
Tree
),
func
(
t
*
testing
.
T
)
{
4
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
allKeys
:=
allTestKeys
(
t1
,
t2
)
5
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
allKeyv
:=
allKeys
.
SortedElements
()
6
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
7
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
kadj12
:=
KAdj
(
t1
,
t2
)
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
)}),
// verify at1->at2 for all combination of initial tracked keys.
for
kidx
:=
range
IntSets
(
len
(
allKeyv
))
{
keys
:=
setKey
{}
for
_
,
idx
:=
range
kidx
{
keys
.
Add
(
allKeyv
[
idx
])
}
// this t.Run allocates and keeps too much memory in -verylong
// also it is not so useful as above "Update/t1->t2"
//t.Run(fmt.Sprintf(" track=%s", keys), func(t *testing.T) {
xverifyΔBTail_Update1
(
t
,
subj
,
db
,
treeRoot
,
t1
,
t2
,
keys
,
kadj12
)
//})
}
})
}
// xverifyΔBTail_Update1 verifies how ΔBTail handles ZODB update at1->at2 from initial
// tracked state defined by initialTrackedKeys.
func
xverifyΔBTail_Update1
(
t
*
testing
.
T
,
subj
string
,
db
*
zodb
.
DB
,
treeRoot
zodb
.
Oid
,
t1
,
t2
*
xbtreetest
.
Commit
,
initialTrackedKeys
setKey
,
kadj
KAdjMatrix
)
{
X
:=
exc
.
Raiseif
//t.Logf("\n>>> Track=%s\n", initialTrackedKeys)
δZ
:=
t2
.
ΔZ
// reflow of keys for rebuild: even if tracked1={}, tracked2={1}, changes to
d12
:=
t2
.
Δxkv
// all A/B/C nodes need to be rescanned. Contrary to the above case the reflow
// is not detectable at separate diff(A,B) and diff(B,C) runs.
"T3,5,7/B1:a,2:b-B3:c,4:d-B5:e,6:f-B7:g,8:h"
,
"T/B1:b"
,
"T2,4,6/B1:a-B2:b,3:c-B4:d,5:e-B6:f,7:g"
,
// similar situation where rebuild has to detect reflow in between non-neighbour trees
"T3,6/B1:a,2:b-B3:c,4:d-B6:f,7:g"
,
"T4,7/B1:b-B4:d,5:e-B7:g,8:h"
,
"T2,5,8/B1:a-B2:b,3:c-B5:e,6:f-B8:h,9:i"
,
var
TrackedδZ
setKey
=
nil
// depth=2; bucket split; +3 in new bucket; left T remain
var
kadjTrackedδZ
setKey
=
nil
// _unchanged_ even though B under it is modified.
var
δT
,
δTok
map
[
Key
]
Δstring
=
nil
,
nil
"T/T/B1:a,2:b"
,
δZset
:=
setOid
{}
Δ
(
"T2/T-T/B1:a-B2:b,3:c"
,
for
_
,
oid
:=
range
δZ
.
Changev
{
A
{
1
:
K
(
1
,
2
,
3
,
oo
),
δZset
.
Add
(
oid
)
2
:
K
(
1
,
2
,
3
,
oo
),
}
3
:
K
(
1
,
2
,
3
,
oo
),
oo
:
K
(
1
,
2
,
3
,
oo
)}),
// badf queues error message to be reported on return.
// depth=2; like prev. case, but additional right arm with +4 +5 is added.
var
badv
[]
string
"T/T/B1:a,2:b"
,
badf
:=
func
(
format
string
,
argv
...
interface
{})
{
Δ
(
"T2,4/T-T-T/B1:a-B2:b,3:c-B4:d,5:e"
,
badv
=
append
(
badv
,
fmt
.
Sprintf
(
format
,
argv
...
))
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
}
2
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
defer
func
()
{
3
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
if
badv
!=
nil
||
t
.
Failed
()
{
4
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
emsg
:=
fmt
.
Sprintf
(
"%s ; tracked=%v :
\n\n
"
,
subj
,
initialTrackedKeys
)
5
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
emsg
+=
fmt
.
Sprintf
(
"d12: %v
\n
δTok: %v
\n
δT: %v
\n\n
"
,
d12
,
δTok
,
δT
)
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
)}),
emsg
+=
fmt
.
Sprintf
(
"δZ: %v
\n
"
,
δZset
)
emsg
+=
fmt
.
Sprintf
(
"Tracked^δZ: %v
\n
"
,
TrackedδZ
)
emsg
+=
fmt
.
Sprintf
(
"kadj[Tracked^δZ]: %v
\n
"
,
kadjTrackedδZ
)
emsg
+=
fmt
.
Sprintf
(
"kadj: %v
\n\n
"
,
kadj
)
emsg
+=
strings
.
Join
(
badv
,
"
\n
"
)
emsg
+=
"
\n
"
t
.
Fatal
(
emsg
)
// depth=2; bucket split; +3 in new bucket; t0 and t1 split;
}
// +right arm (T7/B45-B89).
}()
"T/T/B1:a,2:b"
,
Δ
(
"T4/T2-T7/B1:a-B2:b,3:c-B4:d,5:e-B8:h,9:i"
,
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
2
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
3
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
4
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
5
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
8
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
9
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
)}),
// δbtail @at1 with initial tracked set
// 2 reflow to right B neighbour; 8 splits into new B; δ=ø
δbtail
:=
NewΔBtail
(
t1
.
At
,
db
)
"T3/B1:a,2:b-B4:d,8:h"
,
xtrackKeys
(
δbtail
,
t1
,
initialTrackedKeys
)
"T2,5/B1:a-B2:b,4:d-B8:h"
,
// TrackedδZ = Tracked ^ δZ (i.e. a tracked node has changed, or its coverage was changed)
// case where kadj does not grow too much as leafs coverage remains stable
TrackedδZ
=
setKey
{}
"T4,8/B1:a,2:b-B5:d,6:e-B10:g,11:h"
,
for
k
:=
range
initialTrackedKeys
{
Δ
(
"T4,8/B2:b,3:c-B6:e,7:f-B11:h,12:i"
,
leaf1
:=
t1
.
Xkv
.
Get
(
k
)
A
{
1
:
K
(
1
,
2
,
3
),
oid1
:=
leaf1
.
Oid
2
:
K
(
1
,
2
,
3
),
if
oid1
==
zodb
.
InvalidOid
{
// embedded bucket
3
:
K
(
1
,
2
,
3
),
oid1
=
leaf1
.
Parent
.
Oid
5
:
K
(
5
,
6
,
7
),
}
6
:
K
(
5
,
6
,
7
),
leaf2
:=
t2
.
Xkv
.
Get
(
k
)
7
:
K
(
5
,
6
,
7
,),
oid2
:=
leaf2
.
Oid
10
:
K
(
10
,
11
,
12
,
oo
),
if
oid2
==
zodb
.
InvalidOid
{
// embedded bucket
11
:
K
(
10
,
11
,
12
,
oo
),
oid2
=
leaf2
.
Parent
.
Oid
12
:
K
(
10
,
11
,
12
,
oo
),
}
oo
:
K
(
10
,
11
,
12
,
oo
)}),
if
δZset
.
Has
(
oid1
)
||
δZset
.
Has
(
oid2
)
||
(
leaf1
.
Keycov
!=
leaf2
.
Keycov
)
{
TrackedδZ
.
Add
(
k
)
}
}
kadjTrackedδZ
=
setKey
{}
// kadj[Tracked^δZ] (all keys adjacent to tracked^δZ)
for
k
:=
range
TrackedδZ
{
kadjTrackedδZ
.
Update
(
kadj
[
k
])
}
// 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)
xbtreetest
.
DEL
,
// assert TrackedδZ ∈ kadj[TrackedδZ]
// tree rotation
trackNotInKadj
:=
TrackedδZ
.
Difference
(
kadjTrackedδZ
)
"T3/B2:b-B3:c,4:d"
,
if
len
(
trackNotInKadj
)
>
0
{
"T5/T3-T7/B2:a-B3:a,4:a-B6:a-B8:a"
,
badf
(
"BUG: Tracked^δZ ∉ kadj[Tracked^δZ] ; extra=%v"
,
trackNotInKadj
)
return
}
// k ∈ d12
// found by AllStructs ([1] is not changed, but because B1 is
// k ∈ δT <=>
// unlinked and 1 migrates to other bucket, changes in that
// k ∈ U kadj[·]
// other bucket must be included into δT)
// ·∈tracking^δZ
"T1,2/B0:e-B1:d-B2:g,3:a"
,
δTok
=
map
[
Key
]
Δstring
{}
// d12[all keys that should be present in δT]
"T1/B0:d-B1:d,2:d"
,
for
k
,
δv
:=
range
d12
{
// ----//---- with depth=2
if
kadjTrackedδZ
.
Has
(
k
)
{
"T1,2/T-T-T/B0:a-B1:b-B2:c,3:d"
,
δTok
[
k
]
=
δv
"T1/T-T/B0:e-B1:b,2:f"
,
}
}
ø
:=
blib
.
PPTreeSubSet
{}
// XXX depth=3 (to verify recursion and selecting which tree children to follow or not)
// trackSet1 = xkv1[tracked1]
// trackSet2 = xkv2[tracked2] ( = xkv2[kadj[tracked1]]
trackSet1
,
tkeyCov1
:=
trackSetWithCov
(
t1
.
Xkv
,
initialTrackedKeys
)
trackSet2
,
tkeyCov2
:=
trackSetWithCov
(
t2
.
Xkv
,
initialTrackedKeys
.
Union
(
kadjTrackedδZ
))
// verify δbtail.trackSet against @at1
// degenerate topology from ZODB tests
δbtail
.
assertTrack
(
t
,
"1"
,
ø
,
trackSet1
)
// https://github.com/zopefoundation/ZODB/commit/6cd24e99f89b
// https://github.com/zopefoundation/BTrees/blob/4.7.2-1-g078ba60/BTrees/tests/testBTrees.py#L20-L57
"T4/T2-T/T-T-T6,10/B1:a-B3:b-T-T-T/T-B7:c-B11:d/B5:e"
,
"T/B1:e,5:d,7:c,8:b,11:a"
,
// -3 +8
// δB <- δZ
// was leading treegen to generate corrupt trees
//
"T/T1/T-T/B0:g-B1:e,2:d,3:h"
,
// also call _Update1 directly to verify δtkeycov return from treediff
"T1/T-T3/B0:g-T-T/B1:e,2:d-B3:h"
,
// the result of Update and _Update1 should be the same since δbtail is initially empty.
δbtail_
:=
δbtail
.
Clone
()
δB1
,
err
:=
δbtail_
.
_Update1
(
δZ
);
X
(
err
)
// XXX don't compute treediff twice
// assert tkeyCov1 ⊂ tkeyCov2
// was leading to wrongly computed trackSet2 due to top not
dkeycov12
:=
tkeyCov1
.
Difference
(
tkeyCov2
)
// being tracked to tree root.
if
!
dkeycov12
.
Empty
()
{
"T/T1/B0:a-B1:b"
,
t
.
Errorf
(
"BUG: tkeyCov1 ⊄ tkeyCov2:
\n\t
tkeyCov1: %s
\n\t
tkeyCov2: %s
\n\t
tkeyCov1
\\
tkeyCov2: %s"
,
tkeyCov1
,
tkeyCov2
,
dkeycov12
)
"T/T1/T-T/B0:c-B1:d"
,
}
// assert δtkeycov == δ(tkeyCov1, tkeyCov2)
// was leading to wrongly computed trackSet2: leaf bucket not
δtkeycovOK
:=
tkeyCov2
.
Difference
(
tkeyCov1
)
// reparented to root.
δtkeycov
:=
&
blib
.
RangedKeySet
{}
"T/T/B0:a"
,
if
__
,
ok
:=
δB1
.
ByRoot
[
treeRoot
];
ok
{
"T/B0:a"
,
δtkeycov
=
__
.
δtkeycov1
}
if
!
δtkeycov
.
Equal
(
δtkeycovOK
)
{
badf
(
"δtkeycov wrong:
\n
have: %s
\n
want: %s"
,
δtkeycov
,
δtkeycovOK
)
}
δB
,
err
:=
δbtail
.
Update
(
δZ
);
X
(
err
)
// δtkeycov grows due to change in parent tree only
// XXX assert δB.roots == δTKeyCov roots
"T3/B1:a-B8:c"
,
// XXX assert δBtail[root].vδT = δBtail_[root].vδT
"T7/B1:a-B8:c"
,
// ----//----
"T3/B1:a,2:b-B8:c,9:d"
,
"T7/B1:a,2:b-B8:c,9:d"
,
// ----//---- depth=2
"T3/T-T/B1:a,2:b-B8:c,9:d"
,
"T7/T-T/B1:a,2:b-B8:c,9:d"
,
// ----//---- found by AllStructs
"T1,3/B0:d-B1:a-B3:d,4:g"
,
"T1,4/B0:e-B1:a-B4:c"
,
// ----//---- found by AllStructs
"T2,4/T-T-T/T1-T-B4:f/T-T-B3:f/B0:h-B1:f"
,
"T4/T-T/B3:f-T/B4:a"
,
if
δB
.
Rev
!=
δZ
.
Tid
{
badf
(
"δB: rev != δZ.Tid ; rev=%s δZ.Tid=%s"
,
δB
.
Rev
,
δZ
.
Tid
)
return
}
// verify δbtail.trackSet against @at2
// ---- found by AllStructs ----
δbtail
.
assertTrack
(
t
,
"2"
,
trackSet2
,
ø
)
// trackSet2 wrongly computed due to top not being tracked to tree root
"T2/T1-T/B0:g-B1:b-T/B2:b,3:a"
,
"T2/T1-T/T-T-B2:a/B0:c-B1:g"
,
// assert δB.ByRoot == {treeRoot -> ...} if δTok != ø
// unchanged node is reparented
// == ø if δTok == ø
"T1/B0:c-B1:f"
,
rootsOK
:=
setOid
{}
"T1/T-T/B0:c-T/B1:h"
,
if
len
(
δTok
)
>
0
{
rootsOK
.
Add
(
treeRoot
)
}
roots
:=
setOid
{}
for
root
:=
range
δB
.
ΔByRoot
{
roots
.
Add
(
root
)
}
if
!
reflect
.
DeepEqual
(
roots
,
rootsOK
)
{
badf
(
"δB: roots != rootsOK ; roots=%v rootsOK=%v"
,
roots
,
rootsOK
)
}
_
,
inδB
:=
δB
.
ΔByRoot
[
treeRoot
]
if
!
inδB
{
return
}
// SIGSEGV in ApplyΔ
"T1/T-T2/T-B1:c-B2:c/B0:g"
,
"T1/T-T/B0:g-T/B1:e"
,
// δT <- δB
// trackSet corruption: oid is pointed by some .parent but is not present
δToid
:=
δB
.
ΔByRoot
[
treeRoot
]
// {} k -> δoid
"T1/T-T/B0:g-T2/B1:h-B2:g"
,
δT
=
XGetδKV
(
t1
,
t2
,
δToid
)
// {} k -> δ(ZBlk(oid).data)
"T/T1/T-T2/B0:e-B1:f-B2:g"
,
// δT must be subset of d12.
// ApplyΔ -> xunion: node is reachable from multiple parents
// changed keys, that are
// ( because xdifference did not remove common non-leaf node
// - in tracked set -> must be present in δT
// under which there were also other changed, but not initially
// - outside tracked set -> may be present in δT (kadj gives exact answer)
// tracked, node )
"T4/T1-T/T-T2-B4:c/T-T-T/B0:f-B1:h-B2:g,3:b"
,
"T1/T-T/T-T2/T-T-T/B0:f-B1:h-B2:f"
,
// ----//----
"T3/T1-T/T-T2-T/B0:b-T-T-B3:h/B1:e-B2:a"
,
"T1/T-T4/T-T2-T/T-T-T-T/B0:b-B1:e-B2:a,3:c-B4:e"
,
// ----//----
"T/T1,3/T-T2-T4/B0:b-T-T-B3:g-B4:c/B1:b-B2:e"
,
"T1,4/T-T-T/T-T2-B4:f/T-T-T/B0:h-B1:b-B2:h,3:a"
,
// δT is subset of d12
"T2/B1:a-B7:g"
,
for
_
,
k
:=
range
sortedKeys
(
δT
)
{
"T2,8/B1:a-B7:g-B9:i"
,
_
,
ind12
:=
d12
[
k
]
if
!
ind12
{
badf
(
"δT[%v] ∉ d12"
,
k
)
}
}
// k ∈ tracked set -> must be present in δT
"T2/B1:a-B2:b"
,
"T/B1:a,2:b"
,
// k ∉ tracked set -> may be present in δT (kadj gives exact answer)
"T2,3/B1:a-B2:b-B3:c"
,
"T/B1:a,2:b"
,
for
_
,
k
:=
range
sortedKeys
(
d12
)
{
"T2,3/B1:a-B2:c-B3:c"
,
"T/B1:a,2:b"
,
_
,
inδT
:=
δT
[
k
]
_
,
inδTok
:=
δTok
[
k
]
if
inδT
&&
!
inδTok
{
badf
(
"δT[%v] ∉ δTok"
,
k
)
}
if
!
inδT
&&
inδTok
{
"T2/B1:a-B2:c"
,
"T2,3/B1:a-B2:c-B3:c"
,
badf
(
"δT ∌ δTok[%v]"
,
k
)
}
if
inδT
{
"T2/B1:a-B3:c"
,
if
δT
[
k
]
!=
d12
[
k
]
{
Δ
(
"T2/T-T4/B1:b-B3:d-B99:h"
,
badf
(
"δT[%v] ≠ δTok[%v]"
,
k
,
k
)
A
{
1
:
K
(
1
),
3
:
K
(
3
,
99
,
oo
),
99
:
K
(
3
,
99
,
oo
),
oo
:
K
(
3
,
99
,
oo
)}),
}
}
// direct tree_i -> tree_{i+1} -> _{i+2} ... plus
// reverse ... tree_i <- _{i+1} <- _{i+2}
kadjOK
:=
ΔBTest
(
testv
[
len
(
testv
)
-
1
])
.
kadjOK
for
i
:=
len
(
testv
)
-
2
;
i
>=
0
;
i
--
{
test
:=
ΔBTest
(
testv
[
i
])
kadjOK
,
test
.
kadjOK
=
test
.
kadjOK
,
kadjOK
testv
=
append
(
testv
,
test
)
}
}
testq
:=
make
(
chan
ΔBTestEntry
)
go
func
()
{
defer
close
(
testq
)
for
_
,
test
:=
range
testv
{
testq
<-
ΔBTest
(
test
)
}
}
}()
testΔBTail
(
t
,
testq
)
}
}
// assertTrack verifies state of .trackSet and ΔTtail.trackNew.
// TestΔBTailRandom verifies ΔBtail on random tree topologies generated by AllStructs.
// it assumes that only one tree root is being tracked.
func
TestΔBTailRandom
(
t
*
testing
.
T
)
{
// XXX place
X
:=
exc
.
Raiseif
func
(
δBtail
*
ΔBtail
)
assertTrack
(
t
*
testing
.
T
,
subj
string
,
trackSetOK
blib
.
PPTreeSubSet
,
trackNewOK
blib
.
PPTreeSubSet
)
{
t
.
Helper
()
if
!
δBtail
.
trackSet
.
Equal
(
trackSetOK
)
{
t
.
Errorf
(
"%s: trackSet:
\n\t
have: %v
\n\t
want: %v"
,
subj
,
δBtail
.
trackSet
,
trackSetOK
)
}
roots
:=
setOid
{}
// considerations:
for
root
:=
range
δBtail
.
vδTbyRoot
{
// - maxdepth↑ better for testing (more tricky topologies)
roots
.
Add
(
root
)
// - maxsplit↑ not so better for testing (leave s=1, max s=2)
}
// - |kmin - kmax| affects N(variants) significantly
// -> keep key range small (dumb increase does not help testing)
// - N(keys) affects N(variants) significantly
// -> keep Nkeys reasonably small/medium (dumb increase does not help testing)
//
// - spawning python subprocess is very slow (takes 300-500ms for
// imports; https://github.com/pypa/setuptools/issues/510)
// -> we spawn `treegen allstructs` once and use request/response approach.
nrootsOK
:=
1
maxdepth
:=
xbtreetest
.
N
(
2
,
3
,
4
)
if
trackSetOK
.
Empty
()
&&
trackNewOK
.
Empty
()
{
maxsplit
:=
xbtreetest
.
N
(
1
,
2
,
2
)
nrootsOK
=
0
n
:=
xbtreetest
.
N
(
10
,
10
,
100
)
}
nkeys
:=
xbtreetest
.
N
(
3
,
5
,
10
)
if
len
(
roots
)
!=
nrootsOK
{
t
.
Errorf
(
"%s: len(vδTbyRoot) != %d ; roots=%v"
,
subj
,
nrootsOK
,
roots
)
return
}
if
nrootsOK
==
0
{
return
}
root
:=
roots
.
Elements
()[
0
]
// server to generate AllStructs(kv, ...)
sg
,
err
:=
xbtreetest
.
StartAllStructsSrv
();
X
(
err
)
defer
func
()
{
err
:=
sg
.
Close
();
X
(
err
)
}()
δTtail
:=
δBtail
.
vδTbyRoot
[
root
]
// random-number generator
rng
,
seed
:=
xbtreetest
.
NewRand
()
t
.
Logf
(
"# maxdepth=%d maxsplit=%d nkeys=%d n=%d seed=%d"
,
maxdepth
,
maxsplit
,
nkeys
,
n
,
seed
)
trackNewRootsOK
:=
setOid
{}
// generate (kv1, kv2, kv3) randomly
if
!
trackNewOK
.
Empty
()
{
trackNewRootsOK
.
Add
(
root
)
}
if
!
δBtail
.
trackNewRoots
.
Equal
(
trackNewRootsOK
)
{
// keysv1, keysv2 and keysv3 are random shuffle of IntSets
t
.
Errorf
(
"%s: trackNewRoots:
\n\t
have: %v
\n\t
want: %v"
,
subj
,
δBtail
.
trackNewRoots
,
trackNewRootsOK
)
var
keysv1
[][]
int
var
keysv2
[][]
int
var
keysv3
[][]
int
for
keys
:=
range
IntSets
(
nkeys
)
{
keysv1
=
append
(
keysv1
,
keys
)
keysv2
=
append
(
keysv2
,
keys
)
keysv3
=
append
(
keysv3
,
keys
)
}
}
v
:=
keysv1
rng
.
Shuffle
(
len
(
v
),
func
(
i
,
j
int
)
{
v
[
i
],
v
[
j
]
=
v
[
j
],
v
[
i
]
})
v
=
keysv2
rng
.
Shuffle
(
len
(
v
),
func
(
i
,
j
int
)
{
v
[
i
],
v
[
j
]
=
v
[
j
],
v
[
i
]
})
v
=
keysv3
rng
.
Shuffle
(
len
(
v
),
func
(
i
,
j
int
)
{
v
[
i
],
v
[
j
]
=
v
[
j
],
v
[
i
]
})
if
!
δTtail
.
trackNew
.
Equal
(
trackNewOK
)
{
// given random (kv1, kv2, kv3) generate corresponding set of random tree
t
.
Errorf
(
"%s: vδT.trackNew:
\n\t
have: %v
\n\t
want: %v"
,
subj
,
δTtail
.
trackNew
,
trackNewOK
)
// topology sets (T1, T2, T3). Then iterate through T1->T2->T3->T1...
// elements such that all right-directed triplets are visited and only once.
// Test Update and rebuild on the generated tree sequences.
vv
:=
"abcdefghij"
randv
:=
func
()
string
{
i
:=
rng
.
Intn
(
len
(
vv
))
return
vv
[
i
:
i
+
1
]
}
}
}
// xverifyΔBTail_rebuild verifies ΔBtail.rebuild during t0->t1->t2 transition.
//
// t0->t1 exercises from-scratch rebuild,
// t1->t2 further exercises incremental rebuild.
//
// It also exercises rebuild phase of ΔBtail.Update.
func
xverifyΔBTail_rebuild
(
t
*
testing
.
T
,
db
*
zodb
.
DB
,
treeRoot
zodb
.
Oid
,
t0
,
t1
,
t2
*
xbtreetest
.
Commit
)
{
t
.
Run
(
fmt
.
Sprintf
(
"rebuild/%s→%s"
,
t0
.
Tree
,
t1
.
Tree
),
func
(
t
*
testing
.
T
)
{
tAllKeys
:=
allTestKeys
(
t0
,
t1
,
t2
)
tAllKeyv
:=
tAllKeys
.
SortedElements
()
// tid -> "at_i"
// the number of pairs is 3·n^2
xat
:=
map
[
zodb
.
Tid
]
string
{
// the number of triplets is n^3
t0
.
At
:
"at0"
,
//
t1
.
At
:
"at1"
,
// limit n for emitted triplets, so that the amount of work for Update
t2
.
At
:
"at2"
,
// and rebuild tests is approximately of the same order.
nrebuild
:=
int
(
math
.
Ceil
(
math
.
Pow
(
3
*
float64
(
n
*
n
),
1.
/
3
)))
// in non-short mode rebuild tests are exercising more keys variants, plus every test case
// takes more time. Compensate for that as well.
if
!
testing
.
Short
()
{
nrebuild
-=
3
}
}
//fmt.Printf("@%s: %v\n", xat[t0.At], t0.Xkv.Flatten())
testq
:=
make
(
chan
ΔBTestEntry
)
//fmt.Printf("@%s: %v\n", xat[t1.At], t1.Xkv.Flatten())
go
func
()
{
//fmt.Printf("@%s: %v\n", xat[t2.At], t2.Xkv.Flatten())
defer
close
(
testq
)
for
i
:=
range
keysv1
{
kadj10
:=
KAdj
(
t1
,
t0
,
allTestKeys
(
t0
,
t1
,
t2
))
keys1
:=
keysv1
[
i
]
kadj21
:=
KAdj
(
t2
,
t1
,
allTestKeys
(
t0
,
t1
,
t2
))
keys2
:=
keysv2
[
i
]
kadj12
:=
KAdj
(
t1
,
t2
,
allTestKeys
(
t0
,
t1
,
t2
))
keys3
:=
keysv3
[
i
]
// kadj210 = kadj10·kadj21
kadj210
:=
kadj10
.
Mul
(
kadj21
)
ø
:=
blib
.
PPTreeSubSet
{}
kv1
:=
map
[
Key
]
string
{}
kv2
:=
map
[
Key
]
string
{}
kv3
:=
map
[
Key
]
string
{}
for
_
,
k
:=
range
keys1
{
kv1
[
Key
(
k
)]
=
randv
()
}
for
_
,
k
:=
range
keys2
{
kv2
[
Key
(
k
)]
=
randv
()
}
for
_
,
k
:=
range
keys3
{
kv3
[
Key
(
k
)]
=
randv
()
}
// verify t0 -> t1 Track(keys1) Rebuild -> t2 Track(keys2) Rebuild
treev1
,
err1
:=
sg
.
AllStructs
(
kv1
,
maxdepth
,
maxsplit
,
n
,
rng
.
Int63
())
// for all combinations of keys1 and keys2
treev2
,
err2
:=
sg
.
AllStructs
(
kv2
,
maxdepth
,
maxsplit
,
n
,
rng
.
Int63
())
for
k1idx
:=
range
IntSets
(
len
(
tAllKeyv
))
{
treev3
,
err3
:=
sg
.
AllStructs
(
kv3
,
maxdepth
,
maxsplit
,
n
,
rng
.
Int63
())
keys1
:=
setKey
{}
err
:=
xerr
.
Merge
(
err1
,
err2
,
err3
)
for
_
,
idx1
:=
range
k1idx
{
if
err
!=
nil
{
keys1
.
Add
(
tAllKeyv
[
idx1
]
)
t
.
Fatal
(
err
)
}
}
// δkv1_1 = t1.δxkv / kadj10(keys1)
emit
:=
func
(
tree
string
,
flags
ΔBTestFlags
)
{
keys1_0
:=
kadj10
.
Map
(
keys1
)
// skip emitting this entry if both Update and
δkv1_1
:=
map
[
Key
]
Δstring
{}
// Rebuild are requested to be skipped.
for
k
:=
range
keys1_0
{
if
flags
==
(
ΔBTest_SkipUpdate
|
ΔBTest_SkipRebuild
)
{
δv
,
ok
:=
t1
.
Δxkv
[
k
]
return
if
ok
{
δkv1_1
[
k
]
=
δv
}
}
testq
<-
ΔBTestEntry
{
tree
,
nil
,
flags
}
}
}
Tkeys1
:=
trackSet
(
t1
.
Xkv
,
keys1
)
URSkipIf
:=
func
(
ucond
,
rcond
bool
)
ΔBTestFlags
{
Tkeys1_0
:=
trackSet
(
t1
.
Xkv
,
keys1_0
)
var
flags
ΔBTestFlags
if
ucond
{
t
.
Run
(
fmt
.
Sprintf
(
" T%s;R"
,
keys1
),
func
(
t
*
testing
.
T
)
{
flags
|=
ΔBTest_SkipUpdate
δbtail
:=
NewΔBtail
(
t0
.
At
,
db
)
}
if
rcond
{
flags
|=
ΔBTest_SkipRebuild
}
return
flags
}
// assert trackSet=ø, trackNew=ø, vδB=[]
for
j
:=
range
treev1
{
δbtail
.
assertTrack
(
t
,
"@at0"
,
ø
,
ø
)
for
k
:=
range
treev2
{
assertΔTtail
(
t
,
"@at0"
,
δbtail
,
t0
,
treeRoot
,
xat
,
for
l
:=
range
treev3
{
/*vδT=ø*/
)
// limit rebuild to subset of tree topologies,
// because #(triplets) grow as n^3. See nrebuild
// definition above for details.
norebuild
:=
(
j
>=
nrebuild
||
k
>=
nrebuild
||
l
>=
nrebuild
)
xverifyΔBTail_rebuild_U
(
t
,
δbtail
,
treeRoot
,
t0
,
t1
,
xat
,
// C_{l-1} -> Aj (pair first seen on k=0)
/*trackSet=*/
ø
,
emit
(
treev1
[
j
],
URSkipIf
(
k
!=
0
,
norebuild
))
/*vδT=ø*/
)
xverifyΔBTail_rebuild_TR
(
t
,
δbtail
,
t1
,
treeRoot
,
xat
,
// after Track(keys1)
keys1
,
/*trackSet=*/
ø
,
/*trackNew=*/
Tkeys1
,
// after rebuild
// Aj -> Bk (pair first seen on l=0)
/*trackSet=*/
Tkeys1_0
,
emit
(
treev2
[
k
],
URSkipIf
(
l
!=
0
,
norebuild
))
/*vδT=*/
δkv1_1
)
t
.
Run
((
" →"
+
t2
.
Tree
),
func
(
t
*
testing
.
T
)
{
// Bk -> Cl (pair first seen on j=0)
// keys1R2 is full set of keys that should become tracked after
emit
(
treev3
[
l
],
URSkipIf
(
j
!=
0
,
norebuild
))
// Update() (which includes rebuild)
keys1R2
:=
kadj12
.
Map
(
keys1
)
for
{
keys1R2_
:=
kadj210
.
Map
(
keys1R2
)
if
keys1R2
.
Equal
(
keys1R2_
)
{
break
}
keys1R2
=
keys1R2_
}
}
// δkvX_k1R2 = tX.δxkv / keys1R2
δkv1_k1R2
:=
map
[
Key
]
Δstring
{}
δkv2_k1R2
:=
map
[
Key
]
Δstring
{}
for
k
:=
range
keys1R2
{
δv1
,
ok
:=
t1
.
Δxkv
[
k
]
if
ok
{
δkv1_k1R2
[
k
]
=
δv1
}
}
δv2
,
ok
:=
t2
.
Δxkv
[
k
]
if
ok
{
δkv2_k1R2
[
k
]
=
δv2
}
}
}
}
}()
testΔBTail
(
t
,
testq
)
}
// testΔBTail verifies ΔBTail on sequence of tree topologies coming from testq.
func
testΔBTail
(
t_
*
testing
.
T
,
testq
chan
ΔBTestEntry
)
{
t
:=
xbtreetest
.
NewT
(
t_
)
Tkeys1R2
:=
trackSet
(
t2
.
Xkv
,
keys1R2
)
var
t0
*
xbtreetest
.
Commit
for
test
:=
range
testq
{
t1
:=
t
.
Head
()
t2
:=
t
.
CommitTree
(
test
.
tree
)
xverifyΔBTail_rebuild_U
(
t
,
δbtail
,
treeRoot
,
t1
,
t2
,
xat
,
subj
:=
fmt
.
Sprintf
(
"%s -> %s"
,
t1
.
Tree
,
t2
.
Tree
)
/*trackSet=*/
Tkeys1R2
,
//t.Logf("\n\n\n**** %s ****\n\n", subj)
/*vδT=*/
δkv1_k1R2
,
δkv2_k1R2
)
// tRestKeys2 = tAllKeys - keys1
// KAdj
// reduce that to = tAllKeys - keys1R2 in short mode
if
kadjOK
:=
test
.
kadjOK
;
kadjOK
!=
nil
{
// ( if key from keys2 already became tracked after Track(keys1) + Update,
t
.
Run
(
fmt
.
Sprintf
(
"KAdj/%s→%s"
,
t1
.
Tree
,
t2
.
Tree
),
func
(
t
*
testing
.
T
)
{
// adding Track(that-key), is not adding much testing coverage to recompute paths )
kadj
:=
KAdj
(
t1
,
t2
)
var
tRestKeys2
setKey
if
!
reflect
.
DeepEqual
(
kadj
,
kadjOK
)
{
if
testing
.
Short
()
{
t
.
Fatalf
(
"BUG: computed kadj is wrong:
\n
kadjOK: %v
\n
kadj : %v
\n\n
"
,
kadjOK
,
kadj
)
tRestKeys2
=
tAllKeys
.
Difference
(
keys1R2
)
}
}
else
{
})
tRestKeys2
=
tAllKeys
.
Difference
(
keys1
)
}
}
tRestKeyv2
:=
tRestKeys2
.
SortedElements
()
// ΔBTail.Update
for
k2idx
:=
range
IntSets
(
len
(
tRestKeyv2
))
{
if
test
.
flags
&
ΔBTest_SkipUpdate
==
0
{
keys2
:=
setKey
{}
xverifyΔBTail_Update
(
t
.
T
,
subj
,
t
.
DB
,
t
.
Root
(),
t1
,
t2
)
for
_
,
idx2
:=
range
k2idx
{
keys2
.
Add
(
tRestKeyv2
[
idx2
])
}
}
// keys12R2 is full set of keys that should become tracked after
// ΔBTail.rebuild
// Track(keys2) + rebuild
if
t0
!=
nil
&&
(
test
.
flags
&
ΔBTest_SkipRebuild
==
0
)
{
keys12R2
:=
keys1R2
.
Union
(
keys2
)
xverifyΔBTail_rebuild
(
t
.
T
,
t
.
DB
,
t
.
Root
(),
t0
,
t1
,
t2
)
for
{
keys12R2_
:=
kadj210
.
Map
(
keys12R2
)
if
keys12R2
.
Equal
(
keys12R2_
)
{
break
}
}
keys12R2
=
keys12R2_
t0
,
t1
=
t1
,
t2
}
}
}
Tkeys2
:=
trackSet
(
t2
.
Xkv
,
keys2
)
Tkeys12R2
:=
trackSet
(
t2
.
Xkv
,
keys12R2
)
/*
fmt.Printf("\n\n\nKKK\nkeys1=%s keys2=%s\n", keys1, keys2)
fmt.Printf("keys1R2: %s\n", keys1R2)
fmt.Printf("keys12R2: %s\n", keys12R2)
fmt.Printf("t0.Xkv: %v\n", t0.Xkv)
// xverifyΔBTail_Update verifies how ΔBTail handles ZODB update for a tree with changes in between t1->t2.
fmt.Printf("t1.Xkv: %v\n", t1.Xkv)
//
fmt.Printf("t2.Xkv: %v\n", t2.Xkv)
// Note: this test verifies only single treediff step of ΔBtail.Update.
fmt.Printf("kadj21: %v\n", kadj21)
// the cycling phase of update, that is responsible to recompute older
fmt.Printf("kadj12: %v\n", kadj12)
// entries when key coverage grows, is exercised by
fmt.Printf("Tkeys2 -> %s\n", Tkeys2)
// xverifyΔBTail_rebuild.
fmt.Printf("Tkeys1R2 -> %s\n", Tkeys1R2)
func
xverifyΔBTail_Update
(
t
*
testing
.
T
,
subj
string
,
db
*
zodb
.
DB
,
treeRoot
zodb
.
Oid
,
t1
,
t2
*
xbtreetest
.
Commit
)
{
fmt.Printf("Tkeys2 \\ Tkeys1R2 -> %s\n", Tkeys2.Difference(Tkeys1R2))
// verify transition at1->at2 for all initial states of tracked {keys} from kv1 + kv2 + ∞
fmt.Printf("\n\n\n")
t
.
Run
(
fmt
.
Sprintf
(
"Update/%s→%s"
,
t1
.
Tree
,
t2
.
Tree
),
func
(
t
*
testing
.
T
)
{
*/
allKeys
:=
allTestKeys
(
t1
,
t2
)
allKeyv
:=
allKeys
.
SortedElements
()
kadj12
:=
KAdj
(
t1
,
t2
)
// δkvX_k12R2 = tX.δxkv / keys12R2
// verify at1->at2 for all combination of initial tracked keys.
δkv1_k12R2
:=
make
(
map
[
Key
]
Δstring
,
len
(
t1
.
Δxkv
))
for
kidx
:=
range
IntSets
(
len
(
allKeyv
))
{
δkv2_k12R2
:=
make
(
map
[
Key
]
Δstring
,
len
(
t2
.
Δxkv
))
keys
:=
setKey
{}
for
k
:=
range
keys12R2
{
for
_
,
idx
:=
range
kidx
{
δv1
,
ok
:=
t1
.
Δxkv
[
k
]
keys
.
Add
(
allKeyv
[
idx
])
if
ok
{
δkv1_k12R2
[
k
]
=
δv1
}
δv2
,
ok
:=
t2
.
Δxkv
[
k
]
if
ok
{
δkv2_k12R2
[
k
]
=
δv2
}
}
}
// t.Run is expensive at this level of nest
// this t.Run allocates and keeps too much memory in -verylong
//t.Run(" T"+keys2.String()+";R", func(t *testing.T) {
// also it is not so useful as above "Update/t1->t2"
δbtail_
:=
δbtail
.
Clone
()
//t.Run(fmt.Sprintf(" track=%s", keys), func(t *testing.T) {
xverifyΔBTail_rebuild_TR
(
t
,
δbtail_
,
t2
,
treeRoot
,
xat
,
xverifyΔBTail_Update1
(
t
,
subj
,
db
,
treeRoot
,
t1
,
t2
,
keys
,
kadj12
)
// after Track(keys2)
keys2
,
/*trackSet*/
Tkeys1R2
,
/*trackNew*/
Tkeys2
.
Difference
(
// trackNew should not cover ranges that are
// already in trackSet
Tkeys1R2
),
// after rebuild
/* trackSet=*/
Tkeys12R2
,
/*vδT=*/
δkv1_k12R2
,
δkv2_k12R2
)
//})
//})
}
}
})
})
})
}
})
}
}
// xverifyΔBTail_
rebuild_U verifies ΔBtail state after Update(ti->tj).
// xverifyΔBTail_
Update1 verifies how ΔBTail handles ZODB update at1->at2 from initial
func
xverifyΔBTail_rebuild_U
(
t
*
testing
.
T
,
δbtail
*
ΔBtail
,
treeRoot
zodb
.
Oid
,
ti
,
tj
*
xbtreetest
.
Commit
,
xat
map
[
zodb
.
Tid
]
string
,
trackSet
blib
.
PPTreeSubSet
,
vδTok
...
map
[
Key
]
Δstring
)
{
// tracked state defined by initialTrackedKeys.
t
.
Helper
()
func
xverifyΔBTail_Update1
(
t
*
testing
.
T
,
subj
string
,
db
*
zodb
.
DB
,
treeRoot
zodb
.
Oid
,
t1
,
t2
*
xbtreetest
.
Commit
,
initialTrackedKeys
setKey
,
kadj
KAdjMatrix
)
{
X
:=
exc
.
Raiseif
X
:=
exc
.
Raiseif
ø
:=
blib
.
PPTreeSubSet
{}
//t.Logf("\n>>> Track=%s\n", initialTrackedKeys)
subj
:=
fmt
.
Sprintf
(
"after Update(@%s→@%s)"
,
xat
[
ti
.
At
],
xat
[
tj
.
At
])
// Update ati -> atj
δZ
:=
t2
.
ΔZ
δB
,
err
:=
δbtail
.
Update
(
tj
.
ΔZ
);
X
(
err
)
d12
:=
t2
.
Δxkv
δbtail
.
assertTrack
(
t
,
subj
,
trackSet
,
ø
)
assertΔTtail
(
t
,
subj
,
δbtail
,
tj
,
treeRoot
,
xat
,
vδTok
...
)
// assert δB = vδTok[-1]
var
TrackedδZ
setKey
=
nil
var
δT
,
δTok
map
[
Key
]
Δstring
var
kadjTrackedδZ
setKey
=
nil
if
l
:=
len
(
vδTok
);
l
>
0
{
var
δT
,
δTok
map
[
Key
]
Δstring
=
nil
,
nil
δTok
=
vδTok
[
l
-
1
]
δZset
:=
setOid
{}
for
_
,
oid
:=
range
δZ
.
Changev
{
δZset
.
Add
(
oid
)
}
}
if
len
(
δTok
)
==
0
{
δTok
=
nil
// badf queues error message to be reported on return.
var
badv
[]
string
badf
:=
func
(
format
string
,
argv
...
interface
{})
{
badv
=
append
(
badv
,
fmt
.
Sprintf
(
format
,
argv
...
))
}
}
δrootsOK
:=
1
defer
func
()
{
if
δTok
==
nil
{
if
badv
!=
nil
||
t
.
Failed
()
{
δrootsOK
=
0
emsg
:=
fmt
.
Sprintf
(
"%s ; tracked=%v :
\n\n
"
,
subj
,
initialTrackedKeys
)
emsg
+=
fmt
.
Sprintf
(
"d12: %v
\n
δTok: %v
\n
δT: %v
\n\n
"
,
d12
,
δTok
,
δT
)
emsg
+=
fmt
.
Sprintf
(
"δZ: %v
\n
"
,
δZset
)
emsg
+=
fmt
.
Sprintf
(
"Tracked^δZ: %v
\n
"
,
TrackedδZ
)
emsg
+=
fmt
.
Sprintf
(
"kadj[Tracked^δZ]: %v
\n
"
,
kadjTrackedδZ
)
emsg
+=
fmt
.
Sprintf
(
"kadj: %v
\n\n
"
,
kadj
)
emsg
+=
strings
.
Join
(
badv
,
"
\n
"
)
emsg
+=
"
\n
"
t
.
Fatal
(
emsg
)
}
}
}()
δroots
:=
setOid
{}
for
root
:=
range
δbtail
.
vδTbyRoot
{
// δbtail @at1 with initial tracked set
δroots
.
Add
(
root
)
δbtail
:=
NewΔBtail
(
t1
.
At
,
db
)
xtrackKeys
(
δbtail
,
t1
,
initialTrackedKeys
)
// TrackedδZ = Tracked ^ δZ (i.e. a tracked node has changed, or its coverage was changed)
TrackedδZ
=
setKey
{}
for
k
:=
range
initialTrackedKeys
{
leaf1
:=
t1
.
Xkv
.
Get
(
k
)
oid1
:=
leaf1
.
Oid
if
oid1
==
zodb
.
InvalidOid
{
// embedded bucket
oid1
=
leaf1
.
Parent
.
Oid
}
}
δToid
,
ok
:=
δB
.
ΔByRoot
[
treeRoot
]
leaf2
:=
t2
.
Xkv
.
Get
(
k
)
if
ok
{
oid2
:=
leaf2
.
Oid
δT
=
XGetδKV
(
ti
,
tj
,
δToid
)
if
oid2
==
zodb
.
InvalidOid
{
// embedded bucket
oid2
=
leaf2
.
Parent
.
Oid
}
}
if
δB
.
Rev
!=
tj
.
At
{
if
δZset
.
Has
(
oid1
)
||
δZset
.
Has
(
oid2
)
||
(
leaf1
.
Keycov
!=
leaf2
.
Keycov
)
{
t
.
Errorf
(
"%s: δB.Rev: have %s ; want %s"
,
subj
,
δB
.
Rev
,
tj
.
At
)
TrackedδZ
.
Add
(
k
)
}
}
if
len
(
δB
.
ΔByRoot
)
!=
δrootsOK
{
t
.
Errorf
(
"%s: len(δB.ΔByRoot) != %d ; δroots=%v"
,
subj
,
δrootsOK
,
δroots
)
}
}
if
!
δTEqual
(
δT
,
δTok
)
{
t
.
Errorf
(
"%s: δB.ΔBByRoot[%s]:
\n
have: %v
\n
want: %v"
,
subj
,
treeRoot
,
δT
,
δTok
)
kadjTrackedδZ
=
setKey
{}
// kadj[Tracked^δZ] (all keys adjacent to tracked^δZ)
for
k
:=
range
TrackedδZ
{
kadjTrackedδZ
.
Update
(
kadj
[
k
])
}
// assert TrackedδZ ∈ kadj[TrackedδZ]
trackNotInKadj
:=
TrackedδZ
.
Difference
(
kadjTrackedδZ
)
if
len
(
trackNotInKadj
)
>
0
{
badf
(
"BUG: Tracked^δZ ∉ kadj[Tracked^δZ] ; extra=%v"
,
trackNotInKadj
)
return
}
// k ∈ d12
// k ∈ δT <=>
// k ∈ U kadj[·]
// ·∈tracking^δZ
δTok
=
map
[
Key
]
Δstring
{}
// d12[all keys that should be present in δT]
for
k
,
δv
:=
range
d12
{
if
kadjTrackedδZ
.
Has
(
k
)
{
δTok
[
k
]
=
δv
}
}
}
}
// xverifyΔBTail_rebuild_TR verifies ΔBtail state after Track(keys) + rebuild.
func
xverifyΔBTail_rebuild_TR
(
t
*
testing
.
T
,
δbtail
*
ΔBtail
,
tj
*
xbtreetest
.
Commit
,
treeRoot
zodb
.
Oid
,
xat
map
[
zodb
.
Tid
]
string
,
keys
setKey
,
trackSet
blib
.
PPTreeSubSet
,
trackNew
,
trackSetAfterRebuild
blib
.
PPTreeSubSet
,
vδTok
...
map
[
Key
]
Δstring
)
{
t
.
Helper
()
ø
:=
blib
.
PPTreeSubSet
{}
ø
:=
blib
.
PPTreeSubSet
{}
// Track(keys)
// trackSet1 = xkv1[tracked1]
xtrackKeys
(
δbtail
,
tj
,
keys
)
// trackSet2 = xkv2[tracked2] ( = xkv2[kadj[tracked1]]
trackSet1
,
tkeyCov1
:=
trackSetWithCov
(
t1
.
Xkv
,
initialTrackedKeys
)
trackSet2
,
tkeyCov2
:=
trackSetWithCov
(
t2
.
Xkv
,
initialTrackedKeys
.
Union
(
kadjTrackedδZ
))
subj
:=
fmt
.
Sprintf
(
"@%s: after Track%v"
,
xat
[
tj
.
At
],
keys
)
// verify δbtail.trackSet against @at1
δbtail
.
assertTrack
(
t
,
subj
,
trackSet
,
trackNew
)
δbtail
.
assertTrack
(
t
,
"1"
,
ø
,
trackSet1
)
δbtail
.
rebuildAll
()
// δB <- δZ
//
// also call _Update1 directly to verify δtkeycov return from treediff
// the result of Update and _Update1 should be the same since δbtail is initially empty.
δbtail_
:=
δbtail
.
Clone
()
δB1
,
err
:=
δbtail_
.
_Update1
(
δZ
);
X
(
err
)
// XXX don't compute treediff twice
// assert tkeyCov1 ⊂ tkeyCov2
dkeycov12
:=
tkeyCov1
.
Difference
(
tkeyCov2
)
if
!
dkeycov12
.
Empty
()
{
t
.
Errorf
(
"BUG: tkeyCov1 ⊄ tkeyCov2:
\n\t
tkeyCov1: %s
\n\t
tkeyCov2: %s
\n\t
tkeyCov1
\\
tkeyCov2: %s"
,
tkeyCov1
,
tkeyCov2
,
dkeycov12
)
}
// assert δtkeycov == δ(tkeyCov1, tkeyCov2)
δtkeycovOK
:=
tkeyCov2
.
Difference
(
tkeyCov1
)
δtkeycov
:=
&
blib
.
RangedKeySet
{}
if
__
,
ok
:=
δB1
.
ByRoot
[
treeRoot
];
ok
{
δtkeycov
=
__
.
δtkeycov1
}
if
!
δtkeycov
.
Equal
(
δtkeycovOK
)
{
badf
(
"δtkeycov wrong:
\n
have: %s
\n
want: %s"
,
δtkeycov
,
δtkeycovOK
)
}
subj
+=
" + rebuild"
δB
,
err
:=
δbtail
.
Update
(
δZ
);
X
(
err
)
δbtail
.
assertTrack
(
t
,
subj
,
trackSetAfterRebuild
,
ø
)
// XXX assert δB.roots == δTKeyCov roots
// XXX assert δBtail[root].vδT = δBtail_[root].vδT
// XXX verify Get -> XXX assertΔTtail ?
if
δB
.
Rev
!=
δZ
.
Tid
{
badf
(
"δB: rev != δZ.Tid ; rev=%s δZ.Tid=%s"
,
δB
.
Rev
,
δZ
.
Tid
)
return
}
// verify δbtail.vδTbyRoot[treeRoot]
// verify δbtail.trackSet against @at2
assertΔTtail
(
t
,
subj
,
δbtail
,
tj
,
treeRoot
,
xat
,
vδTok
...
)
δbtail
.
assertTrack
(
t
,
"2"
,
trackSet2
,
ø
)
}
// assertΔTtail verifies state of ΔTtail that corresponds to treeRoot in δbtail.
// it also verifies that δbtail.vδBroots matches ΔTtail data.
func
assertΔTtail
(
t
*
testing
.
T
,
subj
string
,
δbtail
*
ΔBtail
,
tj
*
xbtreetest
.
Commit
,
treeRoot
zodb
.
Oid
,
xat
map
[
zodb
.
Tid
]
string
,
vδTok
...
map
[
Key
]
Δstring
)
{
t
.
Helper
()
// XXX +KVAtTail, +lastRevOf
l
:=
len
(
vδTok
)
// assert δB.ByRoot == {treeRoot -> ...} if δTok != ø
var
vatOK
[]
zodb
.
Tid
// == ø if δTok == ø
var
vδTok_
[]
map
[
Key
]
Δstring
rootsOK
:=
setOid
{}
at2t
:=
map
[
zodb
.
Tid
]
*
xbtreetest
.
Commit
{
tj
.
At
:
tj
}
if
len
(
δTok
)
>
0
{
t0
:=
tj
rootsOK
.
Add
(
treeRoot
)
for
i
:=
0
;
i
<
l
;
i
++
{
// empty vδTok entries means they should be absent in vδT
if
δTok
:=
vδTok
[
l
-
i
-
1
];
len
(
δTok
)
!=
0
{
vatOK
=
append
([]
zodb
.
Tid
{
t0
.
At
},
vatOK
...
)
vδTok_
=
append
([]
map
[
Key
]
Δstring
{
δTok
},
vδTok_
...
)
}
}
t0
=
t0
.
Prev
roots
:=
setOid
{}
at2t
[
t0
.
At
]
=
t0
for
root
:=
range
δB
.
ΔByRoot
{
roots
.
Add
(
root
)
}
}
vδTok
=
vδTok_
if
!
reflect
.
DeepEqual
(
roots
,
rootsOK
)
{
δTtail
,
ok
:=
δbtail
.
vδTbyRoot
[
treeRoot
]
badf
(
"δB: roots != rootsOK ; roots=%v rootsOK=%v"
,
roots
,
rootsOK
)
var
vδToid
[]
ΔTree
if
ok
{
vδToid
=
δTtail
.
vδT
}
}
_
,
inδB
:=
δB
.
ΔByRoot
[
treeRoot
]
l
=
len
(
vδToid
)
if
!
inδB
{
var
vat
[]
zodb
.
Tid
return
var
vδT
[]
map
[
Key
]
Δstring
atPrev
:=
t0
.
At
for
_
,
δToid
:=
range
vδToid
{
vat
=
append
(
vat
,
δToid
.
Rev
)
δT
:=
XGetδKV
(
at2t
[
atPrev
],
at2t
[
δToid
.
Rev
],
δToid
.
ΔKV
)
// {} k -> δ(ZBlk(oid).data)
vδT
=
append
(
vδT
,
δT
)
atPrev
=
δToid
.
Rev
}
}
var
vatδB
[]
zodb
.
Tid
// δbtail.vδBroots/treeRoot
for
_
,
δBroots
:=
range
δbtail
.
vδBroots
{
// δT <- δB
if
δBroots
.
ΔRoots
.
Has
(
treeRoot
)
{
δToid
:=
δB
.
ΔByRoot
[
treeRoot
]
// {} k -> δoid
vatδB
=
append
(
vatδB
,
δBroots
.
Rev
)
δT
=
XGetδKV
(
t1
,
t2
,
δToid
)
// {} k -> δ(ZBlk(oid).data)
// δT must be subset of d12.
// changed keys, that are
// - in tracked set -> must be present in δT
// - outside tracked set -> may be present in δT (kadj gives exact answer)
// δT is subset of d12
for
_
,
k
:=
range
sortedKeys
(
δT
)
{
_
,
ind12
:=
d12
[
k
]
if
!
ind12
{
badf
(
"δT[%v] ∉ d12"
,
k
)
}
}
}
}
tok
:=
tidvEqual
(
vat
,
vatOK
)
&&
vδTEqual
(
vδT
,
vδTok
)
// k ∈ tracked set -> must be present in δT
bok
:=
tidvEqual
(
vatδB
,
vatOK
)
// k ∉ tracked set -> may be present in δT (kadj gives exact answer
)
if
!
(
tok
&&
bok
)
{
for
_
,
k
:=
range
sortedKeys
(
d12
)
{
emsg
:=
fmt
.
Sprintf
(
"%s: vδT:
\n
"
,
subj
)
_
,
inδT
:=
δT
[
k
]
have
:=
""
_
,
inδTok
:=
δTok
[
k
]
for
i
:=
0
;
i
<
len
(
vδT
);
i
++
{
if
inδT
&&
!
inδTok
{
have
+=
fmt
.
Sprintf
(
"
\n\t
@%s: %v"
,
xat
[
vat
[
i
]],
vδT
[
i
]
)
badf
(
"δT[%v] ∉ δTok"
,
k
)
}
}
emsg
+=
fmt
.
Sprintf
(
"have: %s
\n
"
,
have
)
if
!
tok
{
if
!
inδT
&&
inδTok
{
want
:=
""
badf
(
"δT ∌ δTok[%v]"
,
k
)
for
i
:=
0
;
i
<
len
(
vδTok
);
i
++
{
want
+=
fmt
.
Sprintf
(
"
\n\t
@%s: %v"
,
xat
[
vatOK
[
i
]],
vδTok
[
i
])
}
emsg
+=
fmt
.
Sprintf
(
"want: %s
\n
"
,
want
)
}
}
if
!
bok
{
if
inδT
{
vδb_root
:=
""
if
δT
[
k
]
!=
d12
[
k
]
{
for
i
:=
0
;
i
<
len
(
vatδB
);
i
++
{
badf
(
"δT[%v] ≠ δTok[%v]"
,
k
,
k
)
vδb_root
+=
fmt
.
Sprintf
(
"
\n\t
@%s"
,
xat
[
vatδB
[
i
]])
}
}
emsg
+=
fmt
.
Sprintf
(
"vδb/root: %s
\n
"
,
vδb_root
)
}
}
t
.
Error
(
emsg
)
}
}
}
}
// xtrackKeys issues δbtail.Track requests for tree[keys].
// xverifyΔBTail_rebuild verifies ΔBtail.rebuild during t0->t1->t2 transition.
// XXX place
//
func
xtrackKeys
(
δbtail
*
ΔBtail
,
t
*
xbtreetest
.
Commit
,
keys
setKey
)
{
// t0->t1 exercises from-scratch rebuild,
X
:=
exc
.
Raiseif
// t1->t2 further exercises incremental rebuild.
head
:=
δbtail
.
Head
()
//
if
head
!=
t
.
At
{
// It also exercises rebuild phase of ΔBtail.Update.
panicf
(
"BUG: δbtail.head: %s ; t.at: %s"
,
head
,
t
.
At
)
func
xverifyΔBTail_rebuild
(
t
*
testing
.
T
,
db
*
zodb
.
DB
,
treeRoot
zodb
.
Oid
,
t0
,
t1
,
t2
*
xbtreetest
.
Commit
)
{
}
t
.
Run
(
fmt
.
Sprintf
(
"rebuild/%s→%s"
,
t0
.
Tree
,
t1
.
Tree
),
func
(
t
*
testing
.
T
)
{
tAllKeys
:=
allTestKeys
(
t0
,
t1
,
t2
)
tAllKeyv
:=
tAllKeys
.
SortedElements
()
for
k
:=
range
keys
{
// tid -> "at_i"
// NOTE: if tree is deleted - the following adds it to tracked
xat
:=
map
[
zodb
.
Tid
]
string
{
// set with every key being a hole. This aligns with the
t0
.
At
:
"at0"
,
// following situation
t1
.
At
:
"at1"
,
//
t2
.
At
:
"at2"
,
// T1 -> ø -> T2
//
// where after T1->ø, even though the tree becomes deleted, its root
// continues to be tracked and all keys migrate to holes in the
// tracking set. By aligning initial state to the same as after
// T1->ø, we test what will happen on ø->T2.
b
:=
t
.
Xkv
.
Get
(
k
)
err
:=
δbtail
.
track
(
k
,
b
.
Path
());
X
(
err
)
}
}
}
// xverifyΔBTail_GetAt verifies δBtail.Get on series of vt ZODB changes.
//fmt.Printf("@%s: %v\n", xat[t0.At], t0.Xkv.Flatten())
// XXX
//fmt.Printf("@%s: %v\n", xat[t1.At], t1.Xkv.Flatten())
// XXX kill
//fmt.Printf("@%s: %v\n", xat[t2.At], t2.Xkv.Flatten())
/*
func ___xverifyΔBTail_GetAt(t *testing.T, db *zodb.DB, treeRoot zodb.Oid, vt ...*xbtreetest.Commit) {
subj := vt[0].Tree
for _, t := range vt[1:] {
subj += "→" + t.Tree
}
t.Run(fmt.Sprintf("Get/%s", subj), func(t *testing.T) {
kadj10
:=
KAdj
(
t1
,
t0
,
allTestKeys
(
t0
,
t1
,
t2
))
// tid -> "at_i"
kadj21
:=
KAdj
(
t2
,
t1
,
allTestKeys
(
t0
,
t1
,
t2
))
xat := map[zodb.Tid]string{}
kadj12
:=
KAdj
(
t1
,
t2
,
allTestKeys
(
t0
,
t1
,
t2
))
for i := range vt {
xat[vt[i].At] = fmt.Sprintf("at%d", i)
fmt.Printf("@%s: %v\n", xat[vt[i].At], vt[i].Xkv.Flatten())
// kadj210 = kadj10·kadj21
}
kadj210
:=
kadj10
.
Mul
(
kadj21
)
tkeys := allTestKeys(vt...)
ø
:=
blib
.
PPTreeSubSet
{}
tkeyv := tkeys.SortedElements()
// verify
t1->t2-> ... ->tn Track(keys) Get(keys, @at)
// verify
t0 -> t1 Track(keys1) Rebuild -> t2 Track(keys2) Rebuild
// for all combinations of
tracked keys and at
// for all combinations of
keys1 and keys2
for k
idx := range IntSets(len(tk
eyv)) {
for
k
1idx
:=
range
IntSets
(
len
(
tAllK
eyv
))
{
keys := setKey{}
keys
1
:=
setKey
{}
for _, idx
:= range k
idx {
for
_
,
idx
1
:=
range
k1
idx
{
keys
.Add(tkeyv[idx
])
keys
1
.
Add
(
tAllKeyv
[
idx1
])
}
}
t.Run(fmt.Sprintf("track=%s", keys), func(t *testing.T) {
// δkv1_1 = t1.δxkv / kadj10(keys1)
xverifyΔBTail_GetAt1(t, db, treeRoot, vt, xat, keys)
keys1_0
:=
kadj10
.
Map
(
keys1
)
})
δkv1_1
:=
map
[
Key
]
Δstring
{}
for
k
:=
range
keys1_0
{
δv
,
ok
:=
t1
.
Δxkv
[
k
]
if
ok
{
δkv1_1
[
k
]
=
δv
}
}
}
})
}
func xverifyΔBTail_GetAt1(t *testing.T, db *zodb.DB, treeRoot zodb.Oid, vt []*xbtreetest.Commit, xat map[zodb.Tid]string, keys setKey) {
Tkeys1
:=
trackSet
(
t1
.
Xkv
,
keys1
)
X := exc.Raiseif
Tkeys1_0
:=
trackSet
(
t1
.
Xkv
,
keys1_0
)
// t1->t2-> ... -> tn
t
.
Run
(
fmt
.
Sprintf
(
" T%s;R"
,
keys1
),
func
(
t
*
testing
.
T
)
{
δbtail := NewΔBtail(vt[0].At, db)
δbtail
:=
NewΔBtail
(
t0
.
At
,
db
)
for i := 1; i < len(vt); i++ {
_, err := δbtail.Update(vt[i].ΔZ); X(err)
// assert trackSet=ø, trackNew=ø, vδB=[]
δbtail
.
assertTrack
(
t
,
"@at0"
,
ø
,
ø
)
assertΔTtail
(
t
,
"@at0"
,
δbtail
,
t0
,
treeRoot
,
xat
,
/*vδT=ø*/
)
xverifyΔBTail_rebuild_U
(
t
,
δbtail
,
treeRoot
,
t0
,
t1
,
xat
,
/*trackSet=*/
ø
,
/*vδT=ø*/
)
xverifyΔBTail_rebuild_TR
(
t
,
δbtail
,
t1
,
treeRoot
,
xat
,
// after Track(keys1)
keys1
,
/*trackSet=*/
ø
,
/*trackNew=*/
Tkeys1
,
// after rebuild
/*trackSet=*/
Tkeys1_0
,
/*vδT=*/
δkv1_1
)
t
.
Run
((
" →"
+
t2
.
Tree
),
func
(
t
*
testing
.
T
)
{
// keys1R2 is full set of keys that should become tracked after
// Update() (which includes rebuild)
keys1R2
:=
kadj12
.
Map
(
keys1
)
for
{
keys1R2_
:=
kadj210
.
Map
(
keys1R2
)
if
keys1R2
.
Equal
(
keys1R2_
)
{
break
}
keys1R2
=
keys1R2_
}
}
// Track(keys)
// δkvX_k1R2 = tX.δxkv / keys1R2
txn, ctx := transaction.New(context.Background())
δkv1_k1R2
:=
map
[
Key
]
Δstring
{}
defer txn.Abort()
δkv2_k1R2
:=
map
[
Key
]
Δstring
{}
zconn, err := db.Open(ctx, &zodb.ConnOptions{At: vt[len(vt)-1].At}); X(err)
for
k
:=
range
keys1R2
{
xtree, err := zconn.Get(ctx, treeRoot); X(err)
δv1
,
ok
:=
t1
.
Δxkv
[
k
]
ztree := xtree.(*Tree)
if
ok
{
δkv1_k1R2
[
k
]
=
δv1
}
δv2
,
ok
:=
t2
.
Δxkv
[
k
]
if
ok
{
δkv2_k1R2
[
k
]
=
δv2
}
}
Tkeys1R2
:=
trackSet
(
t2
.
Xkv
,
keys1R2
)
xverifyΔBTail_rebuild_U
(
t
,
δbtail
,
treeRoot
,
t1
,
t2
,
xat
,
/*trackSet=*/
Tkeys1R2
,
/*vδT=*/
δkv1_k1R2
,
δkv2_k1R2
)
for k := range keys {
// tRestKeys2 = tAllKeys - keys1
_, _, path, err := ZTreeGetBlkData(ctx, ztree, k); X(err)
// reduce that to = tAllKeys - keys1R2 in short mode
err = δbtail.Track(k, path); X(err)
// ( if key from keys2 already became tracked after Track(keys1) + Update,
// adding Track(that-key), is not adding much testing coverage to recompute paths )
var
tRestKeys2
setKey
if
testing
.
Short
()
{
tRestKeys2
=
tAllKeys
.
Difference
(
keys1R2
)
}
else
{
tRestKeys2
=
tAllKeys
.
Difference
(
keys1
)
}
}
// verify GetAt(k, @at) for all keys and @at
tRestKeyv2
:=
tRestKeys2
.
SortedElements
()
for i := 1; i < len(vt); i++
{
for
k2idx
:=
range
IntSets
(
len
(
tRestKeyv2
))
{
at := vt[i].At
keys2
:=
setKey
{}
for _, k := range keys.SortedElements()
{
for
_
,
idx2
:=
range
k2idx
{
vOid, ok, rev, revExact, err := δbtail.GetAt(ctx, ztree, k, at); X(err
)
keys2
.
Add
(
tRestKeyv2
[
idx2
]
)
v := xzgetBlkDataAt(db, vOid, rev)
}
v_, ok_ := vt[i].Xkv.Get(k).kv[k]
// keys12R2 is full set of keys that should become tracked after
rev_, revExact_ := vt[i].At, false
// Track(keys2) + rebuild
for j := i-1; j >= 0; j-- {
keys12R2
:=
keys1R2
.
Union
(
keys2
)
v__ := vt[j].Xkv.Get(k).kv[k]
for
{
if v__ != v_ {
keys12R2_
:=
kadj210
.
Map
(
keys12R2
)
rev_ = vt[j+1].At
if
keys12R2
.
Equal
(
keys12R2_
)
{
revExact_ = true
break
break
}
}
rev_ = vt[j].At
keys12R2
=
keys12R2_
}
}
if v == "" { v = DEL }
Tkeys2
:=
trackSet
(
t2
.
Xkv
,
keys2
)
if v_ == "" { v_ = DEL }
Tkeys12R2
:=
trackSet
(
t2
.
Xkv
,
keys12R2
)
/*
fmt.Printf("\n\n\nKKK\nkeys1=%s keys2=%s\n", keys1, keys2)
fmt.Printf("keys1R2: %s\n", keys1R2)
fmt.Printf("keys12R2: %s\n", keys12R2)
if !(v == v_ && ok == ok_ && rev == rev_ && revExact == revExact_) {
fmt.Printf("t0.Xkv: %v\n", t0.Xkv)
t.Errorf("Get(%d, @%s) ->\nhave: %s, %v, @%s, %v\nwant: %s, %v, @%s, %v",
fmt.Printf("t1.Xkv: %v\n", t1.Xkv)
k, xat[at],
fmt.Printf("t2.Xkv: %v\n", t2.Xkv)
v, ok, xat[rev], revExact,
fmt.Printf("kadj21: %v\n", kadj21)
v_, ok_, xat[rev_], revExact_
)
fmt.Printf("kadj12: %v\n", kadj12
)
}
fmt.Printf("Tkeys2 -> %s\n", Tkeys2)
}
fmt.Printf("Tkeys1R2 -> %s\n", Tkeys1R2)
}
fmt.Printf("Tkeys2 \\ Tkeys1R2 -> %s\n", Tkeys2.Difference(Tkeys1R2))
}
fmt.Printf("\n\n\n")
*/
*/
// ----------------------------------------
// δkvX_k12R2 = tX.δxkv / keys12R2
δkv1_k12R2
:=
make
(
map
[
Key
]
Δstring
,
len
(
t1
.
Δxkv
))
// ΔBTestEntry represents one entry in ΔBTail tests.
δkv2_k12R2
:=
make
(
map
[
Key
]
Δstring
,
len
(
t2
.
Δxkv
))
type
ΔBTestEntry
struct
{
for
k
:=
range
keys12R2
{
tree
string
// next tree topology
δv1
,
ok
:=
t1
.
Δxkv
[
k
]
kadjOK
KAdjMatrix
// adjacency matrix against previous case (optional)
if
ok
{
flags
ΔBTestFlags
δkv1_k12R2
[
k
]
=
δv1
}
}
δv2
,
ok
:=
t2
.
Δxkv
[
k
]
if
ok
{
δkv2_k12R2
[
k
]
=
δv2
}
}
type
ΔBTestFlags
int
// t.Run is expensive at this level of nest
const
ΔBTest_SkipUpdate
ΔBTestFlags
=
1
// skip verifying Update for this test entry
//t.Run(" T"+keys2.String()+";R", func(t *testing.T) {
const
ΔBTest_SkipRebuild
ΔBTestFlags
=
2
// skip verifying rebuild for this test entry
δbtail_
:=
δbtail
.
Clone
()
xverifyΔBTail_rebuild_TR
(
t
,
δbtail_
,
t2
,
treeRoot
,
xat
,
// after Track(keys2)
keys2
,
/*trackSet*/
Tkeys1R2
,
/*trackNew*/
Tkeys2
.
Difference
(
// trackNew should not cover ranges that are
// already in trackSet
Tkeys1R2
),
// ΔBTest converts xtest into ΔBTestEntry.
// after rebuild
// xtest can be string|ΔBTestEntry.
/* trackSet=*/
Tkeys12R2
,
func
ΔBTest
(
xtest
interface
{})
ΔBTestEntry
{
/*vδT=*/
δkv1_k12R2
,
δkv2_k12R2
)
var
test
ΔBTestEntry
//})
switch
xtest
:=
xtest
.
(
type
)
{
case
string
:
test
.
tree
=
xtest
test
.
kadjOK
=
nil
test
.
flags
=
0
case
ΔBTestEntry
:
test
=
xtest
default
:
panicf
(
"BUG: ΔBTest: bad type %T"
,
xtest
)
}
}
return
test
})
})
}
})
}
}
// xverifyΔBTail_rebuild_U verifies ΔBtail state after Update(ti->tj).
func
xverifyΔBTail_rebuild_U
(
t
*
testing
.
T
,
δbtail
*
ΔBtail
,
treeRoot
zodb
.
Oid
,
ti
,
tj
*
xbtreetest
.
Commit
,
xat
map
[
zodb
.
Tid
]
string
,
trackSet
blib
.
PPTreeSubSet
,
vδTok
...
map
[
Key
]
Δstring
)
{
t
.
Helper
()
X
:=
exc
.
Raiseif
ø
:=
blib
.
PPTreeSubSet
{}
// testΔBTail verifies ΔBTail on sequence of tree topologies coming from testq.
subj
:=
fmt
.
Sprintf
(
"after Update(@%s→@%s)"
,
xat
[
ti
.
At
],
xat
[
tj
.
At
])
func
testΔBTail
(
t_
*
testing
.
T
,
testq
chan
ΔBTestEntry
)
{
t
:=
xbtreetest
.
NewT
(
t_
)
var
t0
*
xbtreetest
.
Commit
for
test
:=
range
testq
{
t1
:=
t
.
Head
()
t2
:=
t
.
CommitTree
(
test
.
tree
)
subj
:=
fmt
.
Sprintf
(
"%s -> %s"
,
t1
.
Tree
,
t2
.
Tree
)
// Update ati -> atj
//t.Logf("\n\n\n**** %s ****\n\n", subj)
δB
,
err
:=
δbtail
.
Update
(
tj
.
ΔZ
);
X
(
err
)
δbtail
.
assertTrack
(
t
,
subj
,
trackSet
,
ø
)
assertΔTtail
(
t
,
subj
,
δbtail
,
tj
,
treeRoot
,
xat
,
vδTok
...
)
// KAdj
// assert δB = vδTok[-1]
if
kadjOK
:=
test
.
kadjOK
;
kadjOK
!=
nil
{
var
δT
,
δTok
map
[
Key
]
Δstring
t
.
Run
(
fmt
.
Sprintf
(
"KAdj/%s→%s"
,
t1
.
Tree
,
t2
.
Tree
),
func
(
t
*
testing
.
T
)
{
if
l
:=
len
(
vδTok
);
l
>
0
{
kadj
:=
KAdj
(
t1
,
t2
)
δTok
=
vδTok
[
l
-
1
]
if
!
reflect
.
DeepEqual
(
kadj
,
kadjOK
)
{
t
.
Fatalf
(
"BUG: computed kadj is wrong:
\n
kadjOK: %v
\n
kadj : %v
\n\n
"
,
kadjOK
,
kadj
)
}
}
})
if
len
(
δTok
)
==
0
{
δTok
=
nil
}
}
δrootsOK
:=
1
// ΔBTail.Update
if
δTok
==
nil
{
if
test
.
flags
&
ΔBTest_SkipUpdate
==
0
{
δrootsOK
=
0
xverifyΔBTail_Update
(
t
.
T
,
subj
,
t
.
DB
,
t
.
Root
(),
t1
,
t2
)
}
}
// ΔBTail.rebuild
δroots
:=
setOid
{}
if
t0
!=
nil
&&
(
test
.
flags
&
ΔBTest_SkipRebuild
==
0
)
{
for
root
:=
range
δbtail
.
vδTbyRoot
{
xverifyΔBTail_rebuild
(
t
.
T
,
t
.
DB
,
t
.
Root
(),
t0
,
t1
,
t2
)
δroots
.
Add
(
root
)
}
}
δToid
,
ok
:=
δB
.
ΔByRoot
[
treeRoot
]
t0
,
t1
=
t1
,
t2
if
ok
{
δT
=
XGetδKV
(
ti
,
tj
,
δToid
)
}
}
}
if
δB
.
Rev
!=
tj
.
At
{
t
.
Errorf
(
"%s: δB.Rev: have %s ; want %s"
,
subj
,
δB
.
Rev
,
tj
.
At
)
// TestΔBTail verifies ΔBTail for explicitly provided tree topologies.
func
TestΔBTail
(
t
*
testing
.
T
)
{
// K is shorthand for setKey
K
:=
func
(
keyv
...
Key
)
setKey
{
ks
:=
setKey
{}
for
_
,
k
:=
range
keyv
{
ks
.
Add
(
k
)
}
return
ks
}
}
// oo is shorthand for KeyMax
if
len
(
δB
.
ΔByRoot
)
!=
δrootsOK
{
const
oo
=
KeyMax
t
.
Errorf
(
"%s: len(δB.ΔByRoot) != %d ; δroots=%v"
,
subj
,
δrootsOK
,
δroots
)
// A is shorthand for KAdjMatrix
type
A
=
KAdjMatrix
// Δ is shorthand for ΔBTestEntry
Δ
:=
func
(
tree
string
,
kadjOK
A
)
(
test
ΔBTestEntry
)
{
test
.
tree
=
tree
test
.
kadjOK
=
kadjOK
return
test
}
}
if
!
δTEqual
(
δT
,
δTok
)
{
t
.
Errorf
(
"%s: δB.ΔBByRoot[%s]:
\n
have: %v
\n
want: %v"
,
subj
,
treeRoot
,
δT
,
δTok
)
}
}
// test known cases going through tree1 -> tree2 -> ...
// xverifyΔBTail_rebuild_TR verifies ΔBtail state after Track(keys) + rebuild.
testv
:=
[]
interface
{}
{
func
xverifyΔBTail_rebuild_TR
(
t
*
testing
.
T
,
δbtail
*
ΔBtail
,
tj
*
xbtreetest
.
Commit
,
treeRoot
zodb
.
Oid
,
xat
map
[
zodb
.
Tid
]
string
,
keys
setKey
,
trackSet
blib
.
PPTreeSubSet
,
trackNew
,
trackSetAfterRebuild
blib
.
PPTreeSubSet
,
vδTok
...
map
[
Key
]
Δstring
)
{
// start from non-empty tree to verify both ->empty and empty-> transitions
t
.
Helper
()
"T/B1:a,2:b"
,
ø
:=
blib
.
PPTreeSubSet
{}
// empty
"T/B:"
,
// +1
Δ
(
"T/B1:a"
,
A
{
1
:
K
(
1
,
oo
),
oo
:
K
(
1
,
oo
)}),
// +2
Δ
(
"T/B1:a,2:b"
,
A
{
1
:
K
(
1
,
2
,
oo
),
2
:
K
(
1
,
2
,
oo
),
oo
:
K
(
1
,
2
,
oo
)}),
// -1
// Track(keys)
Δ
(
"T/B2:b"
,
xtrackKeys
(
δbtail
,
tj
,
keys
)
A
{
1
:
K
(
1
,
2
,
oo
),
2
:
K
(
1
,
2
,
oo
),
oo
:
K
(
1
,
2
,
oo
)}),
// 2: b->c
subj
:=
fmt
.
Sprintf
(
"@%s: after Track%v"
,
xat
[
tj
.
At
],
keys
)
Δ
(
"T/B2:c"
,
δbtail
.
assertTrack
(
t
,
subj
,
trackSet
,
trackNew
)
A
{
2
:
K
(
2
,
oo
),
oo
:
K
(
2
,
oo
)}),
// +1 in new bucket (to the left)
δbtail
.
rebuildAll
()
Δ
(
"T2/B1:a-B2:c"
,
A
{
1
:
K
(
1
,
2
,
oo
),
2
:
K
(
1
,
2
,
oo
),
oo
:
K
(
1
,
2
,
oo
)}),
// +3 in new bucket (to the right)
subj
+=
" + rebuild"
Δ
(
"T2,3/B1:a-B2:c-B3:c"
,
δbtail
.
assertTrack
(
t
,
subj
,
trackSetAfterRebuild
,
ø
)
A
{
1
:
K
(
1
),
2
:
K
(
2
,
3
,
oo
),
3
:
K
(
2
,
3
,
oo
),
oo
:
K
(
2
,
3
,
oo
)}),
// bucket split; +3 in new bucket
// XXX verify Get -> XXX assertΔTtail ?
"T/B1:a,2:b"
,
Δ
(
"T2/B1:a-B2:b,3:c"
,
A
{
1
:
K
(
1
,
2
,
3
,
oo
),
2
:
K
(
1
,
2
,
3
,
oo
),
3
:
K
(
1
,
2
,
3
,
oo
),
oo
:
K
(
1
,
2
,
3
,
oo
)}),
// bucket split; +3 in new bucket; +4 +5 in another new bucket
// verify δbtail.vδTbyRoot[treeRoot]
// everything becomes tracked because original bucket had [-∞,∞) coverage
assertΔTtail
(
t
,
subj
,
δbtail
,
tj
,
treeRoot
,
xat
,
vδTok
...
)
"T/B1:a,2:b"
,
}
Δ
(
"T2,4/B1:a-B2:b,3:c-B4:d,5:e"
,
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
2
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
3
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
4
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
5
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
)}),
// reflow of keys: even if tracked={1}, changes to all B nodes need to be rescanned:
// xverifyΔBTail_GetAt verifies δBtail.Get on series of vt ZODB changes.
// +B12 forces to look in -B23 which adds -3 into δ, which
// XXX
// forces to look into +B34 and so on.
// XXX kill
"T2,4,6/B1:a-B2:b,3:c-B4:d,5:e-B6:f,7:g"
,
/*
Δ
(
"T3,5,7/B1:g,2:f-B3:e,4:d-B5:c,6:b-B7:a"
,
func ___xverifyΔBTail_GetAt(t *testing.T, db *zodb.DB, treeRoot zodb.Oid, vt ...*xbtreetest.Commit) {
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
subj := vt[0].Tree
2
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
for _, t := range vt[1:] {
3
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
subj += "→" + t.Tree
4
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
}
5
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
6
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
7
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
),
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
oo
)}),
// reflow of keys for rebuild: even if tracked1={}, tracked2={1}, changes to
t.Run(fmt.Sprintf("Get/%s", subj), func(t *testing.T) {
// all A/B/C nodes need to be rescanned. Contrary to the above case the reflow
// tid -> "at_i"
// is not detectable at separate diff(A,B) and diff(B,C) runs.
xat := map[zodb.Tid]string{}
"T3,5,7/B1:a,2:b-B3:c,4:d-B5:e,6:f-B7:g,8:h"
,
for i := range vt {
"T/B1:b"
,
xat[vt[i].At] = fmt.Sprintf("at%d", i)
"T2,4,6/B1:a-B2:b,3:c-B4:d,5:e-B6:f,7:g"
,
// similar situation where rebuild has to detect reflow in between non-neighbour trees
"T3,6/B1:a,2:b-B3:c,4:d-B6:f,7:g"
,
"T4,7/B1:b-B4:d,5:e-B7:g,8:h"
,
"T2,5,8/B1:a-B2:b,3:c-B5:e,6:f-B8:h,9:i"
,
// depth=2; bucket split; +3 in new bucket; left T remain
fmt.Printf("@%s: %v\n", xat[vt[i].At], vt[i].Xkv.Flatten())
// _unchanged_ even though B under it is modified.
}
"T/T/B1:a,2:b"
,
Δ
(
"T2/T-T/B1:a-B2:b,3:c"
,
A
{
1
:
K
(
1
,
2
,
3
,
oo
),
2
:
K
(
1
,
2
,
3
,
oo
),
3
:
K
(
1
,
2
,
3
,
oo
),
oo
:
K
(
1
,
2
,
3
,
oo
)}),
// depth=2; like prev. case, but additional right arm with +4 +5 is added.
tkeys := allTestKeys(vt...)
"T/T/B1:a,2:b"
,
tkeyv := tkeys.SortedElements()
Δ
(
"T2,4/T-T-T/B1:a-B2:b,3:c-B4:d,5:e"
,
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
2
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
3
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
4
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
5
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
),
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
oo
)}),
// depth=2; bucket split; +3 in new bucket; t0 and t1 split;
// verify t1->t2-> ... ->tn Track(keys) Get(keys, @at)
// +right arm (T7/B45-B89).
// for all combinations of tracked keys and at
"T/T/B1:a,2:b"
,
for kidx := range IntSets(len(tkeyv)) {
Δ
(
"T4/T2-T7/B1:a-B2:b,3:c-B4:d,5:e-B8:h,9:i"
,
keys := setKey{}
A
{
1
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
for _, idx := range kidx {
2
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
keys.Add(tkeyv[idx])
3
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
}
4
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
5
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
8
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
9
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
),
oo
:
K
(
1
,
2
,
3
,
4
,
5
,
8
,
9
,
oo
)}),
t.Run(fmt.Sprintf("track=%s", keys), func(t *testing.T) {
xverifyΔBTail_GetAt1(t, db, treeRoot, vt, xat, keys)
})
}
})
}
// 2 reflow to right B neighbour; 8 splits into new B; δ=ø
func xverifyΔBTail_GetAt1(t *testing.T, db *zodb.DB, treeRoot zodb.Oid, vt []*xbtreetest.Commit, xat map[zodb.Tid]string, keys setKey) {
"T3/B1:a,2:b-B4:d,8:h"
,
X := exc.Raiseif
"T2,5/B1:a-B2:b,4:d-B8:h"
,
// case where kadj does not grow too much as leafs coverage remains stable
// t1->t2-> ... -> tn
"T4,8/B1:a,2:b-B5:d,6:e-B10:g,11:h"
,
δbtail := NewΔBtail(vt[0].At, db)
Δ
(
"T4,8/B2:b,3:c-B6:e,7:f-B11:h,12:i"
,
for i := 1; i < len(vt); i++ {
A
{
1
:
K
(
1
,
2
,
3
),
_, err := δbtail.Update(vt[i].ΔZ); X(err)
2
:
K
(
1
,
2
,
3
),
}
3
:
K
(
1
,
2
,
3
),
5
:
K
(
5
,
6
,
7
),
6
:
K
(
5
,
6
,
7
),
7
:
K
(
5
,
6
,
7
,),
10
:
K
(
10
,
11
,
12
,
oo
),
11
:
K
(
10
,
11
,
12
,
oo
),
12
:
K
(
10
,
11
,
12
,
oo
),
oo
:
K
(
10
,
11
,
12
,
oo
)}),
// Track(keys)
txn, ctx := transaction.New(context.Background())
defer txn.Abort()
zconn, err := db.Open(ctx, &zodb.ConnOptions{At: vt[len(vt)-1].At}); X(err)
xtree, err := zconn.Get(ctx, treeRoot); X(err)
ztree := xtree.(*Tree)
// tree deletion
for k := range keys {
// having ø in the middle of the test cases exercises all:
_, _, path, err := ZTreeGetBlkData(ctx, ztree, k); X(err)
// * `ø -> Tree ...` (tree is created anew),
err = δbtail.Track(k, path); X(err)
// * `... Tree -> ø` (tree is deleted), and
}
// * `Tree -> ø -> Tree` (tree is deleted and then recreated)
xbtreetest
.
DEL
,
// tree rotation
// verify GetAt(k, @at) for all keys and @at
"T3/B2:b-B3:c,4:d"
,
for i := 1; i < len(vt); i++ {
"T5/T3-T7/B2:a-B3:a,4:a-B6:a-B8:a"
,
at := vt[i].At
for _, k := range keys.SortedElements() {
vOid, ok, rev, revExact, err := δbtail.GetAt(ctx, ztree, k, at); X(err)
v := xzgetBlkDataAt(db, vOid, rev)
// found by AllStructs ([1] is not changed, but because B1 is
v_, ok_ := vt[i].Xkv.Get(k).kv[k]
// unlinked and 1 migrates to other bucket, changes in that
rev_, revExact_ := vt[i].At, false
// other bucket must be included into δT)
for j := i-1; j >= 0; j-- {
"T1,2/B0:e-B1:d-B2:g,3:a"
,
v__ := vt[j].Xkv.Get(k).kv[k]
"T1/B0:d-B1:d,2:d"
,
if v__ != v_ {
// ----//---- with depth=2
rev_ = vt[j+1].At
"T1,2/T-T-T/B0:a-B1:b-B2:c,3:d"
,
revExact_ = true
"T1/T-T/B0:e-B1:b,2:f"
,
break
}
rev_ = vt[j].At
}
// XXX depth=3 (to verify recursion and selecting which tree children to follow or not)
if v == "" { v = DEL }
if v_ == "" { v_ = DEL }
if !(v == v_ && ok == ok_ && rev == rev_ && revExact == revExact_) {
t.Errorf("Get(%d, @%s) ->\nhave: %s, %v, @%s, %v\nwant: %s, %v, @%s, %v",
k, xat[at],
v, ok, xat[rev], revExact,
v_, ok_, xat[rev_], revExact_)
}
}
}
}
*/
// degenerate topology from ZODB tests
// https://github.com/zopefoundation/ZODB/commit/6cd24e99f89b
// https://github.com/zopefoundation/BTrees/blob/4.7.2-1-g078ba60/BTrees/tests/testBTrees.py#L20-L57
"T4/T2-T/T-T-T6,10/B1:a-B3:b-T-T-T/T-B7:c-B11:d/B5:e"
,
"T/B1:e,5:d,7:c,8:b,11:a"
,
// -3 +8
// was leading treegen to generate corrupt trees
// ----------------------------------------
"T/T1/T-T/B0:g-B1:e,2:d,3:h"
,
"T1/T-T3/B0:g-T-T/B1:e,2:d-B3:h"
,
// was leading to wrongly computed trackSet2 due to top not
func
TestΔBtailForget
(
t_
*
testing
.
T
)
{
// being tracked to tree root.
t
:=
xbtreetest
.
NewT
(
t_
)
"T/T1/B0:a-B1:b"
,
X
:=
exc
.
Raiseif
"T/T1/T-T/B0:c-B1:d"
,
// was leading to wrongly computed trackSet2: leaf bucket not
t0
:=
t
.
CommitTree
(
"T/B:"
)
// reparented to root.
t1
:=
t
.
CommitTree
(
"T/B1:a"
)
"T/T/B0:a"
,
t2
:=
t
.
CommitTree
(
"T2/B1:a-B2:b"
)
"T/B0:a"
,
t3
:=
t
.
CommitTree
(
"T/B2:b"
)
// δtkeycov grows due to change in parent tree only
δbtail
:=
NewΔBtail
(
t0
.
At
,
t
.
DB
)
"T3/B1:a-B8:c"
,
_
,
err
:=
δbtail
.
Update
(
t1
.
ΔZ
);
X
(
err
)
"T7/B1:a-B8:c"
,
_
,
err
=
δbtail
.
Update
(
t2
.
ΔZ
);
X
(
err
)
// ----//----
"T3/B1:a,2:b-B8:c,9:d"
,
"T7/B1:a,2:b-B8:c,9:d"
,
// ----//---- depth=2
"T3/T-T/B1:a,2:b-B8:c,9:d"
,
"T7/T-T/B1:a,2:b-B8:c,9:d"
,
// ----//---- found by AllStructs
"T1,3/B0:d-B1:a-B3:d,4:g"
,
"T1,4/B0:e-B1:a-B4:c"
,
// ----//---- found by AllStructs
"T2,4/T-T-T/T1-T-B4:f/T-T-B3:f/B0:h-B1:f"
,
"T4/T-T/B3:f-T/B4:a"
,
// start tracking. everything becomes tracked because t1's T/B1:a has [-∞,∞) coverage
// By starting tracking after t2 we verify vδBroots update in both Update and rebuild
_0
:=
setKey
{};
_0
.
Add
(
0
)
xtrackKeys
(
δbtail
,
t2
,
_0
)
// ---- found by AllStructs ----
_
,
err
=
δbtail
.
Update
(
t3
.
ΔZ
);
X
(
err
)
// trackSet2 wrongly computed due to top not being tracked to tree root
xat
:=
map
[
zodb
.
Tid
]
string
{
"T2/T1-T/B0:g-B1:b-T/B2:b,3:a"
,
t0
.
At
:
"at0"
,
"T2/T1-T/T-T-B2:a/B0:c-B1:g"
,
t1
.
At
:
"at1"
,
t2
.
At
:
"at2"
,
t3
.
At
:
"at3"
,
}
assertΔTtail
(
t
.
T
,
"init"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
t1
.
Δxkv
,
t2
.
Δxkv
,
t3
.
Δxkv
)
δbtail
.
ForgetPast
(
t0
.
At
)
assertΔTtail
(
t
.
T
,
"forget ≤ at0"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
t1
.
Δxkv
,
t2
.
Δxkv
,
t3
.
Δxkv
)
δbtail
.
ForgetPast
(
t1
.
At
)
assertΔTtail
(
t
.
T
,
"forget ≤ at1"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
t2
.
Δxkv
,
t3
.
Δxkv
)
δbtail
.
ForgetPast
(
t3
.
At
)
assertΔTtail
(
t
.
T
,
"forget ≤ at3"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
)
}
func
TestΔBtailClone
(
t_
*
testing
.
T
)
{
// ΔBtail.Clone had bug that aliased klon data to orig
t
:=
xbtreetest
.
NewT
(
t_
)
X
:=
exc
.
Raiseif
// unchanged node is reparented
t0
:=
t
.
CommitTree
(
"T2/B1:a-B2:b"
)
"T1/B0:c-B1:f"
,
t1
:=
t
.
CommitTree
(
"T2/B1:c-B2:d"
)
"T1/T-T/B0:c-T/B1:h"
,
δbtail
:=
NewΔBtail
(
t0
.
At
,
t
.
DB
)
_
,
err
:=
δbtail
.
Update
(
t1
.
ΔZ
);
X
(
err
)
_2
:=
setKey
{};
_2
.
Add
(
2
)
xtrackKeys
(
δbtail
,
t1
,
_2
)
err
=
δbtail
.
rebuildAll
();
X
(
err
)
// SIGSEGV in ApplyΔ
xat
:=
map
[
zodb
.
Tid
]
string
{
"T1/T-T2/T-B1:c-B2:c/B0:g"
,
t0
.
At
:
"at0"
,
"T1/T-T/B0:g-T/B1:e"
,
t1
.
At
:
"at1"
,
}
// trackSet corruption: oid is pointed by some .parent but is not present
δkv1_1
:=
map
[
Key
]
Δstring
{
2
:
{
"b"
,
"d"
}}
"T1/T-T/B0:g-T2/B1:h-B2:g"
,
assertΔTtail
(
t
.
T
,
"orig @at1"
,
δbtail
,
t1
,
t
.
Root
(),
xat
,
δkv1_1
)
"T/T1/T-T2/B0:e-B1:f-B2:g"
,
δbklon
:=
δbtail
.
Clone
()
assertΔTtail
(
t
.
T
,
"klon @at1"
,
δbklon
,
t1
,
t
.
Root
(),
xat
,
δkv1_1
)
// ApplyΔ -> xunion: node is reachable from multiple parents
t2
:=
t
.
CommitTree
(
"T/B1:b,2:a"
)
// ( because xdifference did not remove common non-leaf node
_
,
err
=
δbtail
.
Update
(
t2
.
ΔZ
);
X
(
err
)
// under which there were also other changed, but not initially
xat
[
t2
.
At
]
=
"at2"
// tracked, node )
"T4/T1-T/T-T2-B4:c/T-T-T/B0:f-B1:h-B2:g,3:b"
,
"T1/T-T/T-T2/T-T-T/B0:f-B1:h-B2:f"
,
// ----//----
"T3/T1-T/T-T2-T/B0:b-T-T-B3:h/B1:e-B2:a"
,
"T1/T-T4/T-T2-T/T-T-T-T/B0:b-B1:e-B2:a,3:c-B4:e"
,
// ----//----
"T/T1,3/T-T2-T4/B0:b-T-T-B3:g-B4:c/B1:b-B2:e"
,
"T1,4/T-T-T/T-T2-B4:f/T-T-T/B0:h-B1:b-B2:h,3:a"
,
"T2/B1:a-B7:g"
,
δkv1_2
:=
map
[
Key
]
Δstring
{
1
:
{
"a"
,
"c"
},
2
:
{
"b"
,
"d"
}}
"T2,8/B1:a-B7:g-B9:i"
,
δkv2_2
:=
map
[
Key
]
Δstring
{
1
:
{
"c"
,
"b"
},
2
:
{
"d"
,
"a"
}}
assertΔTtail
(
t
.
T
,
"orig @at2"
,
δbtail
,
t2
,
t
.
Root
(),
xat
,
δkv1_2
,
δkv2_2
)
assertΔTtail
(
t
.
T
,
"klon @at1 after orig @at->@at2"
,
δbklon
,
t1
,
t
.
Root
(),
xat
,
δkv1_1
)
}
"T2/B1:a-B2:b"
,
"T/B1:a,2:b"
,
"T2,3/B1:a-B2:b-B3:c"
,
"T/B1:a,2:b"
,
"T2,3/B1:a-B2:c-B3:c"
,
"T/B1:a,2:b"
,
"T2/B1:a-B2:c"
,
"T2,3/B1:a-B2:c-B3:c"
,
// -------- KAdj --------
"T2/B1:a-B3:c"
,
// Map returns kadj·keys.
Δ
(
"T2/T-T4/B1:b-B3:d-B99:h"
,
func
(
kadj
KAdjMatrix
)
Map
(
keys
setKey
)
setKey
{
A
{
1
:
K
(
1
),
res
:=
make
(
setKey
,
len
(
keys
))
3
:
K
(
3
,
99
,
oo
),
for
k
:=
range
keys
{
99
:
K
(
3
,
99
,
oo
),
to
,
ok
:=
kadj
[
k
]
oo
:
K
(
3
,
99
,
oo
)}),
if
!
ok
{
panicf
(
"kadj.Map: %d ∉ kadj
\n\n
kadj: %v"
,
k
,
kadj
)
}
}
// direct tree_i -> tree_{i+1} -> _{i+2} ... plus
res
.
Update
(
to
)
// reverse ... tree_i <- _{i+1} <- _{i+2}
kadjOK
:=
ΔBTest
(
testv
[
len
(
testv
)
-
1
])
.
kadjOK
for
i
:=
len
(
testv
)
-
2
;
i
>=
0
;
i
--
{
test
:=
ΔBTest
(
testv
[
i
])
kadjOK
,
test
.
kadjOK
=
test
.
kadjOK
,
kadjOK
testv
=
append
(
testv
,
test
)
}
}
return
res
}
testq
:=
make
(
chan
ΔBTestEntry
)
// Mul returns kadjA·kadjB.
go
func
()
{
//
defer
close
(
testq
)
// (kadjA·kadjB).Map(keys) = kadjA.Map(kadjB.Map(keys))
for
_
,
test
:=
range
testv
{
func
(
kadjA
KAdjMatrix
)
Mul
(
kadjB
KAdjMatrix
)
KAdjMatrix
{
testq
<-
ΔBTest
(
test
)
// ~ assert kadjA.keys == kadjB.keys
// check only len here; the rest will be asserted by Map
if
len
(
kadjA
)
!=
len
(
kadjB
)
{
panicf
(
"kadj.Mul: different keys:
\n\n
kadjA: %v
\n
kadjB: %v"
,
kadjA
,
kadjB
)
}
}
}()
testΔBTail
(
t
,
testq
)
kadj
:=
make
(
KAdjMatrix
,
len
(
kadjB
))
for
k
,
tob
:=
range
kadjB
{
kadj
[
k
]
=
kadjA
.
Map
(
tob
)
}
return
kadj
}
}
// TestΔBTailRandom verifies ΔBtail on random tree topologies generated by AllStructs.
// KAdj computes adjacency matrix for t1 -> t2 transition.
func
TestΔBTailRandom
(
t
*
testing
.
T
)
{
//
X
:=
exc
.
Raiseif
// The set of keys for which kadj matrix is computed can be optionally provided.
// This set of keys defaults to allTestKeys(t1,t2).
//
// KAdj itself is verified by testΔBTail on entries with .kadjOK set.
func
KAdj
(
t1
,
t2
*
xbtreetest
.
Commit
,
keysv
...
setKey
)
(
kadj
KAdjMatrix
)
{
// assert KAdj(A,B) == KAdj(B,A)
kadj12
:=
_KAdj
(
t1
,
t2
,
keysv
...
)
kadj21
:=
_KAdj
(
t2
,
t1
,
keysv
...
)
if
!
reflect
.
DeepEqual
(
kadj12
,
kadj21
)
{
panicf
(
"KAdj not symmetric:
\n
t1: %s
\n
t2: %s
\n
kadj12: %v
\n
kadj21: %v"
,
t1
.
Tree
,
t2
.
Tree
,
kadj12
,
kadj21
)
}
return
kadj12
}
// considerations:
const
debugKAdj
=
false
// - maxdepth↑ better for testing (more tricky topologies)
func
debugfKAdj
(
format
string
,
argv
...
interface
{})
{
// - maxsplit↑ not so better for testing (leave s=1, max s=2)
if
debugKAdj
{
// - |kmin - kmax| affects N(variants) significantly
fmt
.
Printf
(
format
,
argv
...
)
// -> keep key range small (dumb increase does not help testing)
}
// - N(keys) affects N(variants) significantly
}
// -> keep Nkeys reasonably small/medium (dumb increase does not help testing)
//
// - spawning python subprocess is very slow (takes 300-500ms for
// imports; https://github.com/pypa/setuptools/issues/510)
// -> we spawn `treegen allstructs` once and use request/response approach.
maxdepth
:=
xbtreetest
.
N
(
2
,
3
,
4
)
func
_KAdj
(
t1
,
t2
*
xbtreetest
.
Commit
,
keysv
...
setKey
)
(
kadj
KAdjMatrix
)
{
maxsplit
:=
xbtreetest
.
N
(
1
,
2
,
2
)
var
keys
setKey
n
:=
xbtreetest
.
N
(
10
,
10
,
100
)
switch
len
(
keysv
)
{
nkeys
:=
xbtreetest
.
N
(
3
,
5
,
10
)
case
0
:
keys
=
allTestKeys
(
t1
,
t2
)
case
1
:
keys
=
keysv
[
0
]
default
:
panic
(
"multiple key sets on the call"
)
}
// server to generate AllStructs(kv, ...)
debugfKAdj
(
"
\n\n
_KAdj
\n
"
)
sg
,
err
:=
xbtreetest
.
StartAllStructsSrv
();
X
(
err
)
debugfKAdj
(
"t1: %s
\n
"
,
t1
.
Tree
)
debugfKAdj
(
"t2: %s
\n
"
,
t2
.
Tree
)
debugfKAdj
(
"keys: %s
\n
"
,
keys
)
defer
func
()
{
defer
func
()
{
err
:=
sg
.
Close
();
X
(
err
)
debugfKAdj
(
"kadj -> %v
\n
"
,
kadj
)
}()
}()
// random-number generator
// kadj = {} k -> adjacent keys.
rng
,
seed
:=
xbtreetest
.
NewRand
()
// if k is tracked and covered by changed leaf -> changes to adjacents must be in Update(t1->t2).
t
.
Logf
(
"# maxdepth=%d maxsplit=%d nkeys=%d n=%d seed=%d"
,
maxdepth
,
maxsplit
,
nkeys
,
n
,
seed
)
kadj
=
KAdjMatrix
{}
for
k
:=
range
keys
{
adj1
:=
setKey
{}
adj2
:=
setKey
{}
// generate (kv1, kv2, kv3) randomly
q1
:=
&
blib
.
RangedKeySet
{};
q1
.
Add
(
k
)
q2
:=
&
blib
.
RangedKeySet
{};
q2
.
Add
(
k
)
done1
:=
&
blib
.
RangedKeySet
{}
done2
:=
&
blib
.
RangedKeySet
{}
// keysv1, keysv2 and keysv3 are random shuffle of IntSets
debugfKAdj
(
"
\n
k%s
\n
"
,
kstr
(
k
))
var
keysv1
[][]
int
for
!
q1
.
Empty
()
||
!
q2
.
Empty
()
{
var
keysv2
[][]
int
debugfKAdj
(
"q1: %s
\t
done1: %s
\n
"
,
q1
,
done1
)
var
keysv3
[][]
int
debugfKAdj
(
"q2: %s
\t
done2: %s
\n
"
,
q2
,
done2
)
for
keys
:=
range
IntSets
(
nkeys
)
{
for
_
,
r1
:=
range
q1
.
AllRanges
()
{
keysv1
=
append
(
keysv1
,
keys
)
lo1
:=
r1
.
Lo
keysv2
=
append
(
keysv2
,
keys
)
for
{
keysv3
=
append
(
keysv3
,
keys
)
b1
:=
t1
.
Xkv
.
Get
(
lo1
)
debugfKAdj
(
" b1: %s
\n
"
,
b1
)
for
k_
:=
range
keys
{
if
b1
.
Keycov
.
Has
(
k_
)
{
adj1
.
Add
(
k_
)
debugfKAdj
(
" adj1 += %s
\t
-> %s
\n
"
,
kstr
(
k_
),
adj1
)
}
}
v
:=
keysv1
}
rng
.
Shuffle
(
len
(
v
),
func
(
i
,
j
int
)
{
v
[
i
],
v
[
j
]
=
v
[
j
],
v
[
i
]
})
done1
.
AddRange
(
b1
.
Keycov
)
v
=
keysv2
// q2 |= (b1.keyrange \ done2)
rng
.
Shuffle
(
len
(
v
),
func
(
i
,
j
int
)
{
v
[
i
],
v
[
j
]
=
v
[
j
],
v
[
i
]
})
δq2
:=
&
blib
.
RangedKeySet
{}
v
=
keysv3
δq2
.
AddRange
(
b1
.
Keycov
)
rng
.
Shuffle
(
len
(
v
),
func
(
i
,
j
int
)
{
v
[
i
],
v
[
j
]
=
v
[
j
],
v
[
i
]
})
δq2
.
DifferenceInplace
(
done2
)
q2
.
UnionInplace
(
δq2
)
debugfKAdj
(
"q2 += %s
\t
-> %s
\n
"
,
δq2
,
q2
)
// given random (kv1, kv2, kv3) generate corresponding set of random tree
// continue with next right bucket until r1 coverage is complete
// topology sets (T1, T2, T3). Then iterate through T1->T2->T3->T1...
if
r1
.
Hi_
<=
b1
.
Keycov
.
Hi_
{
// elements such that all right-directed triplets are visited and only once.
break
// Test Update and rebuild on the generated tree sequences.
vv
:=
"abcdefghij"
randv
:=
func
()
string
{
i
:=
rng
.
Intn
(
len
(
vv
))
return
vv
[
i
:
i
+
1
]
}
}
lo1
=
b1
.
Keycov
.
Hi_
+
1
}
}
q1
.
Clear
()
// the number of pairs is 3·n^2
for
_
,
r2
:=
range
q2
.
AllRanges
()
{
// the number of triplets is n^3
lo2
:=
r2
.
Lo
//
for
{
// limit n for emitted triplets, so that the amount of work for Update
b2
:=
t2
.
Xkv
.
Get
(
lo2
)
// and rebuild tests is approximately of the same order.
debugfKAdj
(
" b2: %s
\n
"
,
b2
)
nrebuild
:=
int
(
math
.
Ceil
(
math
.
Pow
(
3
*
float64
(
n
*
n
),
1.
/
3
)))
for
k_
:=
range
keys
{
// in non-short mode rebuild tests are exercising more keys variants, plus every test case
if
b2
.
Keycov
.
Has
(
k_
)
{
// takes more time. Compensate for that as well.
adj2
.
Add
(
k_
)
if
!
testing
.
Short
()
{
debugfKAdj
(
" adj2 += %s
\t
-> %s
\n
"
,
kstr
(
k_
),
adj2
)
nrebuild
-=
3
}
}
done2
.
AddRange
(
b2
.
Keycov
)
// q1 |= (b2.keyrange \ done1)
δq1
:=
&
blib
.
RangedKeySet
{}
δq1
.
AddRange
(
b2
.
Keycov
)
δq1
.
DifferenceInplace
(
done1
)
q1
.
UnionInplace
(
δq1
)
debugfKAdj
(
"q1 += %s
\t
-> %s
\n
"
,
δq1
,
q1
)
// continue with next right bucket until r2 coverage is complete
if
r2
.
Hi_
<=
b2
.
Keycov
.
Hi_
{
break
}
lo2
=
b2
.
Keycov
.
Hi_
+
1
}
}
q2
.
Clear
()
}
adj
:=
setKey
{};
adj
.
Update
(
adj1
);
adj
.
Update
(
adj2
)
kadj
[
k
]
=
adj
}
}
testq
:=
make
(
chan
ΔBTestEntry
)
return
kadj
go
func
()
{
}
defer
close
(
testq
)
for
i
:=
range
keysv1
{
keys1
:=
keysv1
[
i
]
keys2
:=
keysv2
[
i
]
keys3
:=
keysv3
[
i
]
kv1
:=
map
[
Key
]
string
{}
kv2
:=
map
[
Key
]
string
{}
kv3
:=
map
[
Key
]
string
{}
for
_
,
k
:=
range
keys1
{
kv1
[
Key
(
k
)]
=
randv
()
}
for
_
,
k
:=
range
keys2
{
kv2
[
Key
(
k
)]
=
randv
()
}
for
_
,
k
:=
range
keys3
{
kv3
[
Key
(
k
)]
=
randv
()
}
treev1
,
err1
:=
sg
.
AllStructs
(
kv1
,
maxdepth
,
maxsplit
,
n
,
rng
.
Int63
())
// ----------------------------------------
treev2
,
err2
:=
sg
.
AllStructs
(
kv2
,
maxdepth
,
maxsplit
,
n
,
rng
.
Int63
())
treev3
,
err3
:=
sg
.
AllStructs
(
kv3
,
maxdepth
,
maxsplit
,
n
,
rng
.
Int63
())
err
:=
xerr
.
Merge
(
err1
,
err2
,
err3
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
emit
:=
func
(
tree
string
,
flags
ΔBTestFlags
)
{
// assertΔTtail verifies state of ΔTtail that corresponds to treeRoot in δbtail.
// skip emitting this entry if both Update and
// it also verifies that δbtail.vδBroots matches ΔTtail data.
// Rebuild are requested to be skipped.
func
assertΔTtail
(
t
*
testing
.
T
,
subj
string
,
δbtail
*
ΔBtail
,
tj
*
xbtreetest
.
Commit
,
treeRoot
zodb
.
Oid
,
xat
map
[
zodb
.
Tid
]
string
,
vδTok
...
map
[
Key
]
Δstring
)
{
if
flags
==
(
ΔBTest_SkipUpdate
|
ΔBTest_SkipRebuild
)
{
t
.
Helper
()
return
// XXX +KVAtTail, +lastRevOf
}
testq
<-
ΔBTestEntry
{
tree
,
nil
,
flags
}
}
URSkipIf
:=
func
(
ucond
,
rcond
bool
)
ΔBTestFlags
{
l
:=
len
(
vδTok
)
var
flags
ΔBTestFlags
var
vatOK
[]
zodb
.
Tid
if
ucond
{
var
vδTok_
[]
map
[
Key
]
Δstring
flags
|=
ΔBTest_SkipUpdate
at2t
:=
map
[
zodb
.
Tid
]
*
xbtreetest
.
Commit
{
tj
.
At
:
tj
}
t0
:=
tj
for
i
:=
0
;
i
<
l
;
i
++
{
// empty vδTok entries means they should be absent in vδT
if
δTok
:=
vδTok
[
l
-
i
-
1
];
len
(
δTok
)
!=
0
{
vatOK
=
append
([]
zodb
.
Tid
{
t0
.
At
},
vatOK
...
)
vδTok_
=
append
([]
map
[
Key
]
Δstring
{
δTok
},
vδTok_
...
)
}
}
if
rcond
{
t0
=
t0
.
Prev
flags
|=
ΔBTest_SkipRebuild
at2t
[
t0
.
At
]
=
t0
}
}
return
flags
vδTok
=
vδTok_
δTtail
,
ok
:=
δbtail
.
vδTbyRoot
[
treeRoot
]
var
vδToid
[]
ΔTree
if
ok
{
vδToid
=
δTtail
.
vδT
}
}
for
j
:=
range
treev1
{
l
=
len
(
vδToid
)
for
k
:=
range
treev2
{
var
vat
[]
zodb
.
Tid
for
l
:=
range
treev3
{
var
vδT
[]
map
[
Key
]
Δstring
// limit rebuild to subset of tree topologies,
atPrev
:=
t0
.
At
// because #(triplets) grow as n^3. See nrebuild
for
_
,
δToid
:=
range
vδToid
{
// definition above for details.
vat
=
append
(
vat
,
δToid
.
Rev
)
norebuild
:=
(
j
>=
nrebuild
||
δT
:=
XGetδKV
(
at2t
[
atPrev
],
at2t
[
δToid
.
Rev
],
δToid
.
ΔKV
)
// {} k -> δ(ZBlk(oid).data)
k
>=
nrebuild
||
vδT
=
append
(
vδT
,
δT
)
l
>=
nrebuild
)
atPrev
=
δToid
.
Rev
}
// C_{l-1} -> Aj (pair first seen on k=0)
var
vatδB
[]
zodb
.
Tid
// δbtail.vδBroots/treeRoot
emit
(
treev1
[
j
],
URSkipIf
(
k
!=
0
,
norebuild
))
for
_
,
δBroots
:=
range
δbtail
.
vδBroots
{
if
δBroots
.
ΔRoots
.
Has
(
treeRoot
)
{
vatδB
=
append
(
vatδB
,
δBroots
.
Rev
)
}
}
// Aj -> Bk (pair first seen on l=0)
tok
:=
tidvEqual
(
vat
,
vatOK
)
&&
vδTEqual
(
vδT
,
vδTok
)
emit
(
treev2
[
k
],
URSkipIf
(
l
!=
0
,
norebuild
))
bok
:=
tidvEqual
(
vatδB
,
vatOK
)
if
!
(
tok
&&
bok
)
{
emsg
:=
fmt
.
Sprintf
(
"%s: vδT:
\n
"
,
subj
)
have
:=
""
for
i
:=
0
;
i
<
len
(
vδT
);
i
++
{
have
+=
fmt
.
Sprintf
(
"
\n\t
@%s: %v"
,
xat
[
vat
[
i
]],
vδT
[
i
])
}
emsg
+=
fmt
.
Sprintf
(
"have: %s
\n
"
,
have
)
// Bk -> Cl (pair first seen on j=0)
if
!
tok
{
emit
(
treev3
[
l
],
URSkipIf
(
j
!=
0
,
norebuild
))
want
:=
""
for
i
:=
0
;
i
<
len
(
vδTok
);
i
++
{
want
+=
fmt
.
Sprintf
(
"
\n\t
@%s: %v"
,
xat
[
vatOK
[
i
]],
vδTok
[
i
])
}
}
emsg
+=
fmt
.
Sprintf
(
"want: %s
\n
"
,
want
)
}
}
if
!
bok
{
vδb_root
:=
""
for
i
:=
0
;
i
<
len
(
vatδB
);
i
++
{
vδb_root
+=
fmt
.
Sprintf
(
"
\n\t
@%s"
,
xat
[
vatδB
[
i
]])
}
}
emsg
+=
fmt
.
Sprintf
(
"vδb/root: %s
\n
"
,
vδb_root
)
}
}
}()
testΔBTail
(
t
,
testq
)
t
.
Error
(
emsg
)
}
}
}
// xtrackKeys issues δbtail.Track requests for tree[keys].
func
TestΔBtailForget
(
t_
*
testing
.
T
)
{
func
xtrackKeys
(
δbtail
*
ΔBtail
,
t
*
xbtreetest
.
Commit
,
keys
setKey
)
{
t
:=
xbtreetest
.
NewT
(
t_
)
X
:=
exc
.
Raiseif
X
:=
exc
.
Raiseif
head
:=
δbtail
.
Head
()
if
head
!=
t
.
At
{
panicf
(
"BUG: δbtail.head: %s ; t.at: %s"
,
head
,
t
.
At
)
}
t0
:=
t
.
CommitTree
(
"T/B:"
)
for
k
:=
range
keys
{
t1
:=
t
.
CommitTree
(
"T/B1:a"
)
// NOTE: if tree is deleted - the following adds it to tracked
t2
:=
t
.
CommitTree
(
"T2/B1:a-B2:b"
)
// set with every key being a hole. This aligns with the
t3
:=
t
.
CommitTree
(
"T/B2:b"
)
// following situation
//
δbtail
:=
NewΔBtail
(
t0
.
At
,
t
.
DB
)
// T1 -> ø -> T2
_
,
err
:=
δbtail
.
Update
(
t1
.
ΔZ
);
X
(
err
)
//
_
,
err
=
δbtail
.
Update
(
t2
.
ΔZ
);
X
(
err
)
// where after T1->ø, even though the tree becomes deleted, its root
// continues to be tracked and all keys migrate to holes in the
// tracking set. By aligning initial state to the same as after
// T1->ø, we test what will happen on ø->T2.
b
:=
t
.
Xkv
.
Get
(
k
)
err
:=
δbtail
.
track
(
k
,
b
.
Path
());
X
(
err
)
}
}
// start tracking. everything becomes tracked because t1's T/B1:a has [-∞,∞) coverage
// trackSet returns what should be ΔBtail.trackSet coverage for specified tracked key set.
// By starting tracking after t2 we verify vδBroots update in both Update and rebuild
func
trackSet
(
rbs
xbtreetest
.
RBucketSet
,
tracked
setKey
)
blib
.
PPTreeSubSet
{
_0
:=
setKey
{};
_0
.
Add
(
0
)
// nil = don't compute keyCover
xtrackKeys
(
δbtail
,
t2
,
_0
)
// (trackSet is called from inside hot inner loop of rebuild test)
return
_trackSetWithCov
(
rbs
,
tracked
,
nil
)
}
_
,
err
=
δbtail
.
Update
(
t3
.
ΔZ
);
X
(
err
)
// trackSetWithCov returns what should be ΔBtail.trackSet and its key coverage for specified tracked key set.
func
trackSetWithCov
(
rbs
xbtreetest
.
RBucketSet
,
tracked
setKey
)
(
trackSet
blib
.
PPTreeSubSet
,
keyCover
*
blib
.
RangedKeySet
)
{
keyCover
=
&
blib
.
RangedKeySet
{}
trackSet
=
_trackSetWithCov
(
rbs
,
tracked
,
keyCover
)
return
trackSet
,
keyCover
}
xat
:=
map
[
zodb
.
Tid
]
string
{
func
_trackSetWithCov
(
rbs
xbtreetest
.
RBucketSet
,
tracked
setKey
,
outKeyCover
*
blib
.
RangedKeySet
)
(
trackSet
blib
.
PPTreeSubSet
)
{
t0
.
At
:
"at0"
,
trackSet
=
blib
.
PPTreeSubSet
{}
t1
.
At
:
"at1"
,
for
k
:=
range
tracked
{
t2
.
At
:
"at2"
,
kb
:=
rbs
.
Get
(
k
)
t3
.
At
:
"at3"
,
if
outKeyCover
!=
nil
{
outKeyCover
.
AddRange
(
kb
.
Keycov
)
}
}
assertΔTtail
(
t
.
T
,
"init"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
t1
.
Δxkv
,
t2
.
Δxkv
,
t3
.
Δxkv
)
trackSet
.
AddPath
(
kb
.
Path
())
δbtail
.
ForgetPast
(
t0
.
At
)
}
assertΔTtail
(
t
.
T
,
"forget ≤ at0"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
t1
.
Δxkv
,
t2
.
Δxkv
,
t3
.
Δxkv
)
return
trackSet
δbtail
.
ForgetPast
(
t1
.
At
)
assertΔTtail
(
t
.
T
,
"forget ≤ at1"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
t2
.
Δxkv
,
t3
.
Δxkv
)
δbtail
.
ForgetPast
(
t3
.
At
)
assertΔTtail
(
t
.
T
,
"forget ≤ at3"
,
δbtail
,
t3
,
t
.
Root
(),
xat
,
)
}
}
// assertTrack verifies state of .trackSet and ΔTtail.trackNew.
// it assumes that only one tree root is being tracked.
func
(
δBtail
*
ΔBtail
)
assertTrack
(
t
*
testing
.
T
,
subj
string
,
trackSetOK
blib
.
PPTreeSubSet
,
trackNewOK
blib
.
PPTreeSubSet
)
{
t
.
Helper
()
if
!
δBtail
.
trackSet
.
Equal
(
trackSetOK
)
{
t
.
Errorf
(
"%s: trackSet:
\n\t
have: %v
\n\t
want: %v"
,
subj
,
δBtail
.
trackSet
,
trackSetOK
)
}
func
TestΔBtailClone
(
t_
*
testing
.
T
)
{
roots
:=
setOid
{}
// ΔBtail.Clone had bug that aliased klon data to orig
for
root
:=
range
δBtail
.
vδTbyRoot
{
t
:=
xbtreetest
.
NewT
(
t_
)
roots
.
Add
(
root
)
X
:=
exc
.
Raiseif
}
t0
:=
t
.
CommitTree
(
"T2/B1:a-B2:b"
)
nrootsOK
:=
1
t1
:=
t
.
CommitTree
(
"T2/B1:c-B2:d"
)
if
trackSetOK
.
Empty
()
&&
trackNewOK
.
Empty
()
{
δbtail
:=
NewΔBtail
(
t0
.
At
,
t
.
DB
)
nrootsOK
=
0
_
,
err
:=
δbtail
.
Update
(
t1
.
ΔZ
);
X
(
err
)
}
_2
:=
setKey
{};
_2
.
Add
(
2
)
if
len
(
roots
)
!=
nrootsOK
{
xtrackKeys
(
δbtail
,
t1
,
_2
)
t
.
Errorf
(
"%s: len(vδTbyRoot) != %d ; roots=%v"
,
subj
,
nrootsOK
,
roots
)
err
=
δbtail
.
rebuildAll
();
X
(
err
)
return
}
if
nrootsOK
==
0
{
return
}
xat
:=
map
[
zodb
.
Tid
]
string
{
root
:=
roots
.
Elements
()[
0
]
t0
.
At
:
"at0"
,
t1
.
At
:
"at1"
,
δTtail
:=
δBtail
.
vδTbyRoot
[
root
]
trackNewRootsOK
:=
setOid
{}
if
!
trackNewOK
.
Empty
()
{
trackNewRootsOK
.
Add
(
root
)
}
}
δkv1_1
:=
map
[
Key
]
Δstring
{
2
:
{
"b"
,
"d"
}}
if
!
δBtail
.
trackNewRoots
.
Equal
(
trackNewRootsOK
)
{
assertΔTtail
(
t
.
T
,
"orig @at1"
,
δbtail
,
t1
,
t
.
Root
(),
xat
,
δkv1_1
)
t
.
Errorf
(
"%s: trackNewRoots:
\n\t
have: %v
\n\t
want: %v"
,
subj
,
δBtail
.
trackNewRoots
,
trackNewRootsOK
)
δbklon
:=
δbtail
.
Clone
()
}
assertΔTtail
(
t
.
T
,
"klon @at1"
,
δbklon
,
t1
,
t
.
Root
(),
xat
,
δkv1_1
)
t2
:=
t
.
CommitTree
(
"T/B1:b,2:a"
)
if
!
δTtail
.
trackNew
.
Equal
(
trackNewOK
)
{
_
,
err
=
δbtail
.
Update
(
t2
.
ΔZ
);
X
(
err
)
t
.
Errorf
(
"%s: vδT.trackNew:
\n\t
have: %v
\n\t
want: %v"
,
subj
,
δTtail
.
trackNew
,
trackNewOK
)
xat
[
t2
.
At
]
=
"at2"
}
}
δkv1_2
:=
map
[
Key
]
Δstring
{
1
:
{
"a"
,
"c"
},
2
:
{
"b"
,
"d"
}}
δkv2_2
:=
map
[
Key
]
Δstring
{
1
:
{
"c"
,
"b"
},
2
:
{
"d"
,
"a"
}}
assertΔTtail
(
t
.
T
,
"orig @at2"
,
δbtail
,
t2
,
t
.
Root
(),
xat
,
δkv1_2
,
δkv2_2
)
// XGetδKV translates {k -> δ<oid>} to {k -> δ(ZBlk(oid).data)} according to t1..t2 db snapshots.
assertΔTtail
(
t
.
T
,
"klon @at1 after orig @at->@at2"
,
δbklon
,
t1
,
t
.
Root
(),
xat
,
δkv1_1
)
func
XGetδKV
(
t1
,
t2
*
xbtreetest
.
Commit
,
δkvOid
map
[
Key
]
ΔValue
)
map
[
Key
]
Δstring
{
δkv
:=
make
(
map
[
Key
]
Δstring
,
len
(
δkvOid
))
for
k
,
δvOid
:=
range
δkvOid
{
δkv
[
k
]
=
Δstring
{
Old
:
t1
.
XGetBlkData
(
δvOid
.
Old
),
New
:
t2
.
XGetBlkData
(
δvOid
.
New
),
}
}
return
δkv
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment