blob: ac956358308f4a7d39c321d940e7a6038fb9610b [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vsync
import (
"testing"
wire "v.io/v23/services/syncbase"
"v.io/x/ref/services/syncbase/store"
"v.io/x/ref/services/syncbase/store/watchable"
)
// Test setup:
//
// iSt.updObjects contains oids: x, y, b, c, a, p, q
// There are 5 batches:
// xzb : local
// xy : remote
// bc : remote
// pq(L) : local
// pq(R) : remote
//
// a is not a part of any batch.
// z does not have any remote changes (hence not a part of iSt.updObjects)
//
// Result:
// The following closures will be created:
// group1: {x, y, z, b, c}
// group2: {a}
// group3: {p, q}
var (
updObjects map[string]*objConflictState
zVer = string(watchable.NewVersion())
batchxzbId = rand64()
batchxzb = createBatch(true /*local*/, x, z, b)
batchxyId = rand64()
batchxy = createBatch(false /*remote*/, x, y)
batchbcId = rand64()
batchbc = createBatch(false /*remote*/, b, c)
batchpqLocalId = rand64()
batchpqLocal = createBatch(true /*local*/, p, q)
batchpqRemoteId = rand64()
batchpqRemote = createBatch(false /*remote*/, p, q)
)
func createUpdObjectsMap() map[string]*objConflictState {
return map[string]*objConflictState{
//Group1
x: createObjConflictState(true /*isConflict*/, true /*hasLocal*/, true /*hasRemote*/, true /*hasAncestor*/),
y: createObjConflictState(false /*isConflict*/, true /*hasLocal*/, true /*hasRemote*/, false /*hasAncestor*/),
// z has local change only and is not present in updObjects
b: createObjConflictState(true /*isConflict*/, true /*hasLocal*/, true /*hasRemote*/, true /*hasAncestor*/),
c: createObjConflictState(false /*isConflict*/, true /*hasLocal*/, true /*hasRemote*/, false /*hasAncestor*/),
// Group2
a: createObjConflictState(false /*isConflict*/, true /*hasLocal*/, true /*hasRemote*/, false /*hasAncestor*/),
// Group3
p: createObjConflictState(true /*isConflict*/, true /*hasLocal*/, true /*hasRemote*/, true /*hasAncestor*/),
q: createObjConflictState(true /*isConflict*/, true /*hasLocal*/, true /*hasRemote*/, true /*hasAncestor*/),
}
}
func createAndSaveNodeAndBatchData(iSt *initiationState) {
createAndSaveNode(iSt.tx, batchxzbId, x, updObjects[x].oldHead)
createAndSaveNode(iSt.tx, batchxzbId, z, zVer)
//watchable.PutVersion(nil, iSt.tx, []byte(z), []byte(zVer))
setHead(nil, iSt.tx, z, zVer)
createAndSaveNode(iSt.tx, batchxzbId, b, updObjects[b].oldHead)
setBatch(nil, iSt.tx, batchxzbId, batchxzb)
createAndSaveNode(iSt.tx, batchxyId, x, updObjects[x].newHead)
createAndSaveNode(iSt.tx, batchxyId, y, updObjects[y].newHead)
setBatch(nil, iSt.tx, batchxyId, batchxy)
createAndSaveNode(iSt.tx, batchbcId, b, updObjects[b].newHead)
createAndSaveNode(iSt.tx, batchbcId, c, updObjects[c].newHead)
setBatch(nil, iSt.tx, batchbcId, batchbc)
createAndSaveNode(iSt.tx, NoBatchId, a, updObjects[a].newHead)
createAndSaveNode(iSt.tx, batchpqLocalId, p, updObjects[p].oldHead)
createAndSaveNode(iSt.tx, batchpqLocalId, q, updObjects[q].oldHead)
setBatch(nil, iSt.tx, batchpqLocalId, batchpqLocal)
createAndSaveNode(iSt.tx, batchpqRemoteId, p, updObjects[p].newHead)
createAndSaveNode(iSt.tx, batchpqRemoteId, q, updObjects[q].newHead)
setBatch(nil, iSt.tx, batchpqRemoteId, batchpqRemote)
}
func TestGroupFor(t *testing.T) {
service := createService(t)
defer destroyService(t, service)
updObjects = createUpdObjectsMap()
iSt := &initiationState{updObjects: updObjects, tx: createDatabase(t, service).St().NewWatchableTransaction()}
createAndSaveNodeAndBatchData(iSt)
// Group1 is a closure of batches xzb, xy, bc containing oids x, y, z, b, c.
// Following tests run groupFor() method on each of the oids and expects
// the same group (Group1) to be returned regardless of which oid the
// closure was innitiated with.
group := initCrGroup()
iSt.groupFor(nil, group, x)
verifyGroup1(t, group)
group = initCrGroup()
iSt.groupFor(nil, group, y)
verifyGroup1(t, group)
group = initCrGroup()
iSt.groupFor(nil, group, b)
verifyGroup1(t, group)
group = initCrGroup()
iSt.groupFor(nil, group, c)
verifyGroup1(t, group)
// Group2 is a closure of oid "a" only. Since a is not part of any batches,
// Group2 does not have any batches in it.
group = initCrGroup()
iSt.groupFor(nil, group, a)
verifyGroup2(t, group)
// Group3 is a closure of batches pqLocal and pqRemote containing oids p, q.
// Following tests run groupFor() method on each of the oids and expects
// the same group (Group3) to be returned regardless of which oid the
// closure was innitiated with.
group = initCrGroup()
iSt.groupFor(nil, group, p)
verifyGroup3(t, group)
group = initCrGroup()
iSt.groupFor(nil, group, q)
verifyGroup3(t, group)
}
func TestGroupConflicts(t *testing.T) {
service := createService(t)
defer destroyService(t, service)
updObjects = createUpdObjectsMap()
iSt := &initiationState{updObjects: updObjects, tx: createDatabase(t, service).St().NewWatchableTransaction()}
createAndSaveNodeAndBatchData(iSt)
// Assuming that all objects in updObjects are to be resolved by app.
crGroups := iSt.groupConflicts(nil, updObjects)
for _, group := range crGroups.groups {
batchCount := len(group.batchSource)
if batchCount == 3 {
verifyGroup1(t, group)
} else if batchCount == 0 {
verifyGroup2(t, group)
} else if batchCount == 2 {
verifyGroup3(t, group)
} else {
t.Errorf("Encountered unexpected group: %v", group)
}
}
if len(crGroups.oids) != 8 {
t.Errorf("Size of groupedCrData.oids expected: %d actual: %d", 8, len(crGroups.oids))
}
verifyOidList(t, crGroups, x, y, z, a, b, c, p, q)
}
func verifyOidList(t *testing.T, crGroups *groupedCrData, oids ...string) {
for _, oid := range oids {
if !crGroups.oids[oid] {
t.Errorf("Oid %s missing in groupedCrData.oids map", oid)
}
}
}
func verifyGroup1(t *testing.T, group *crGroup) {
verifyBatchSource(t, group, batchxzbId, "xzb", wire.BatchSourceLocal)
verifyBatchSource(t, group, batchxyId, "xy", wire.BatchSourceRemote)
verifyBatchSource(t, group, batchbcId, "bc", wire.BatchSourceRemote)
verifyBatchesByOid(t, group, x, batchxzbId, batchxyId)
verifyBatchesByOid(t, group, z, batchxzbId)
verifyBatchesByOid(t, group, b, batchxzbId, batchbcId)
verifyBatchesByOid(t, group, y, batchxyId)
verifyBatchesByOid(t, group, c, batchbcId)
objSt := updObjects[z]
if (objSt == nil) || (objSt.oldHead != zVer) || objSt.isConflict || !objSt.isAddedByCr {
t.Errorf("Unexpected value of objConflictState for z: %#v", objSt)
}
}
func verifyGroup2(t *testing.T, group *crGroup) {
if len(group.batchSource) != 0 {
t.Errorf("Non empty batchSource map found for Group2: %v", group.batchSource)
}
_, present := group.batchesByOid[a]
if len(group.batchesByOid) != 1 || !present {
t.Errorf("Unexpected batchesByOid map found for Group2: %v", group.batchesByOid)
}
}
func verifyGroup3(t *testing.T, group *crGroup) {
verifyBatchSource(t, group, batchpqLocalId, "pqLocal", wire.BatchSourceLocal)
verifyBatchSource(t, group, batchpqRemoteId, "pqRemote", wire.BatchSourceRemote)
verifyBatchesByOid(t, group, p, batchpqLocalId, batchpqRemoteId)
verifyBatchesByOid(t, group, q, batchpqLocalId, batchpqRemoteId)
}
func verifyBatchSource(t *testing.T, group *crGroup, batchId uint64, batchName string, source wire.BatchSource) {
if v, ok := group.batchSource[batchId]; !ok || (v != source) {
t.Errorf("Missing or incorrect source for batchId: %s", batchName)
}
}
func verifyBatchesByOid(t *testing.T, group *crGroup, oid string, batchIds ...uint64) {
batchesInOid := group.batchesByOid[oid]
batchMap := map[uint64]bool{}
for _, batchId := range batchesInOid {
batchMap[batchId] = true
}
for _, batchId := range batchIds {
if !batchMap[batchId] {
t.Errorf("Batch %v expected to be associated with oid %v but missing.", batchId, oid)
}
}
}
func createBatch(isLocal bool, oids ...string) *BatchInfo {
batch := &BatchInfo{
Count: uint64(len(oids)),
Objects: map[string]string{},
}
for _, oid := range oids {
if _, present := batch.Objects[oid]; !present {
batch.Objects[oid] = zVer
continue
}
if isLocal {
batch.Objects[oid] = updObjects[oid].oldHead
} else {
batch.Objects[oid] = updObjects[oid].newHead
}
}
return batch
}
func createAndSaveNode(tx store.Transaction, batchId uint64, oid, version string) {
node := &DagNode{
BatchId: batchId,
}
setNode(nil, tx, oid, version, node)
}