blob: ba12bf088be1681ee4d53ce9a751333c667130c1 [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
// Tests for the Veyron Sync ILog component.
import (
"fmt"
"math/rand"
"os"
"reflect"
"runtime"
"testing"
)
// TestILogStore tests creating a backing file for ILog.
func TestILogStore(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new ILog file %s, err %v", logfile, err)
}
fsize := getFileSize(logfile)
if fsize < 0 {
//t.Errorf("Log file %s not created", logfile)
}
if err := log.flush(); err != nil {
t.Errorf("Cannot flush ILog file %s, err %v", logfile, err)
}
oldfsize := fsize
fsize = getFileSize(logfile)
if fsize <= oldfsize {
//t.Errorf("Log file %s not flushed", logfile)
}
if err := log.close(); err != nil {
t.Errorf("Cannot close ILog file %s, err %v", logfile, err)
}
oldfsize = getFileSize(logfile)
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot re-open existing log file %s, err %v", logfile, err)
}
fsize = getFileSize(logfile)
if fsize != oldfsize {
t.Errorf("Log file %s size changed across re-open (%d %d)", logfile, fsize, oldfsize)
}
if err := log.flush(); err != nil {
t.Errorf("Cannot flush ILog file %s, err %v", logfile, err)
}
if err := log.close(); err != nil {
t.Errorf("Cannot close ILog file %s, err %v", logfile, err)
}
}
// TestInvalidLog tests log methods on an invalid (closed) log ptr.
func TestInvalidLog(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new ILog file %s, err %v", logfile, err)
}
if err := log.close(); err != nil {
t.Errorf("Cannot close ILog file %s, err %v", logfile, err)
}
validateError := func(err error, funcName string) {
_, file, line, _ := runtime.Caller(1)
if err == nil || err != errInvalidLog {
t.Errorf("%s:%d %s() did not fail on a closed log: %v", file, line, funcName, err)
}
}
err = log.close()
validateError(err, "close")
err = log.flush()
validateError(err, "flush")
srid := ObjId("haha")
err = log.initSyncRoot(srid)
validateError(err, "initSyncRoot")
_, err = log.putLogRec(&LogRec{})
validateError(err, "putLogRec")
var devid DeviceId = "VeyronPhone"
var gnum GenId = 1
var lsn SeqNum
_, err = log.getLogRec(devid, srid, gnum, lsn)
validateError(err, "getLogRec")
if log.hasLogRec(devid, srid, gnum, lsn) {
t.Errorf("hasLogRec() did not fail on a closed log: %v", err)
}
err = log.delLogRec(devid, srid, gnum, lsn)
validateError(err, "delLogRec")
_, err = log.getGenMetadata(devid, srid, gnum)
validateError(err, "getGenMetadata")
err = log.delGenMetadata(devid, srid, gnum)
validateError(err, "delGenMetadata")
err = log.createRemoteGeneration(devid, srid, gnum, &genMetadata{})
validateError(err, "createRemoteGeneration")
_, err = log.createLocalGeneration(srid)
validateError(err, "createLocalGeneration")
err = log.processWatchRecord(ObjId("foobar"), 2, Version(999), &LogValue{}, srid)
validateError(err, "processWatchRecord")
// Harmless NOP.
log.dump()
}
// TestPutGetLogHeader tests setting and getting log header across log open/close/reopen.
func TestPutGetLogHeader(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
// In memory head should be initialized.
if len(log.head.CurSRGens) != 0 || log.head.Curorder != 0 {
t.Errorf("First time log create should reset header")
}
// No head should be there in db.
if err = log.getHead(); err == nil {
t.Errorf("getHead() found non-existent head in log file %s, err %v", logfile, err)
}
if log.hasHead() {
t.Errorf("hasHead() found non-existent head in log file %s", logfile)
}
expVal := map[ObjId]*curLocalGen{ObjId("bla"): &curLocalGen{CurGenNum: 10, CurSeqNum: 100},
ObjId("qwerty"): &curLocalGen{CurGenNum: 40, CurSeqNum: 80}}
log.head = &iLogHeader{
CurSRGens: expVal,
Curorder: 1000,
}
if err := log.putHead(); err != nil {
t.Errorf("Cannot put head %v in log file %s, err %v", log.head, logfile, err)
}
// Reset values.
log.head.CurSRGens = nil
log.head.Curorder = 0
for i := 0; i < 2; i++ {
if err := log.getHead(); err != nil {
t.Fatalf("getHead() can not find head (i=%d) in log file %s, err %v", i, logfile, err)
}
if !log.hasHead() {
t.Errorf("hasHead() can not find head (i=%d) in log file %s", i, logfile)
}
if !reflect.DeepEqual(log.head.CurSRGens, expVal) || log.head.Curorder != 1000 {
t.Errorf("Data mismatch for head (i=%d) in log file %s: %v",
i, logfile, log.head)
}
if i == 0 {
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot re-open log file %s, err %v", logfile, err)
}
}
}
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// TestPersistLogHeader tests that log header is automatically persisted across log open/close/reopen.
func TestPersistLogHeader(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
// In memory head should be initialized.
if len(log.head.CurSRGens) != 0 || log.head.Curorder != 0 {
t.Errorf("First time log create should reset header")
}
expVal := map[ObjId]*curLocalGen{ObjId("blabla"): &curLocalGen{CurGenNum: 10, CurSeqNum: 100},
ObjId("xyz"): &curLocalGen{CurGenNum: 40, CurSeqNum: 80}}
log.head = &iLogHeader{
CurSRGens: expVal,
Curorder: 1000,
}
if err = log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
// In memory head should be initialized from db.
if !reflect.DeepEqual(log.head.CurSRGens, expVal) || log.head.Curorder != 1000 {
t.Errorf("Data mismatch for head in log file %s: %v", logfile, log.head)
}
for sr := range expVal {
expVal[sr] = &curLocalGen{CurGenNum: 1000, CurSeqNum: 10}
break
}
log.head = &iLogHeader{
CurSRGens: expVal,
Curorder: 100,
}
if err := log.flush(); err != nil {
t.Errorf("Cannot flush ILog file %s, err %v", logfile, err)
}
// Reset values.
log.head.CurSRGens = nil
log.head.Curorder = 0
if err := log.getHead(); err != nil {
t.Fatalf("getHead() can not find head in log file %s, err %v", logfile, err)
}
// In memory head should be initialized from db.
if !reflect.DeepEqual(log.head.CurSRGens, expVal) || log.head.Curorder != 100 {
t.Errorf("Data mismatch for head in log file %s: %v", logfile, log.head)
}
if err = log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// TestLogInitDelSyncRoot tests initing and deleting a new SyncRoot.
func TestLogInitDelSyncRoot(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
srid1 := ObjId("haha")
if err := log.initSyncRoot(srid1); err != nil {
t.Fatalf("Cannot create new SyncRoot %s, err %v", srid1.String(), err)
}
srid2 := ObjId("asdf")
if err := log.initSyncRoot(srid2); err != nil {
t.Fatalf("Cannot create new SyncRoot %s, err %v", srid2.String(), err)
}
if err := log.initSyncRoot(srid2); err == nil {
t.Fatalf("Creating existing SyncRoot didn't fail %s, err %v", srid2.String(), err)
}
expGen := &curLocalGen{CurGenNum: 1, CurSeqNum: 0}
expSRGens := map[ObjId]*curLocalGen{
srid1: expGen,
srid2: expGen,
}
if !reflect.DeepEqual(log.head.CurSRGens, expSRGens) {
t.Errorf("Data mismatch for head in log file %s: %v instead of %v",
logfile, log.head.CurSRGens, expSRGens)
}
if err := log.delSyncRoot(srid1); err != nil {
t.Fatalf("Cannot delete SyncRoot %s, err %v", srid1.String(), err)
}
if err := log.delSyncRoot(srid2); err != nil {
t.Fatalf("Cannot delete SyncRoot %s, err %v", srid1.String(), err)
}
if err := log.delSyncRoot(srid1); err == nil {
t.Fatalf("Deleting non-existent SyncRoot didn't fail %s", srid1.String())
}
expSRGens = map[ObjId]*curLocalGen{}
if !reflect.DeepEqual(log.head.CurSRGens, expSRGens) {
t.Errorf("Data mismatch for head in log file %s: %v instead of %v",
logfile, log.head.CurSRGens, expSRGens)
}
if err := log.putHead(); err != nil {
t.Errorf("Cannot put head %v in log file %s, err %v", log.head, logfile, err)
}
// Reset values.
log.head.CurSRGens = nil
log.head.Curorder = 0
if err := log.getHead(); err != nil {
t.Fatalf("getHead() can not find head in log file %s, err %v", logfile, err)
}
if !reflect.DeepEqual(log.head.CurSRGens, expSRGens) {
t.Errorf("Data mismatch for head in log file %s: %v instead of %v",
logfile, log.head.CurSRGens, expSRGens)
}
}
// TestStringAndParseKeys tests conversion to/from string for log record and generation keys.
func TestStringAndParseKeys(t *testing.T) {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
randString := func(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
for i := 0; i < 10; i++ {
devid := DeviceId(randString(rand.Intn(20) + 1))
srid := ObjId(fmt.Sprintf("haha-%d", i))
gnum := GenId(rand.Int63())
lsn := SeqNum(rand.Int63())
devid1, srid1, gnum1, lsn1, err := splitLogRecKey(logRecKey(devid, srid, gnum, lsn))
if err != nil || devid1 != devid || srid1 != srid || gnum1 != gnum || lsn1 != lsn {
t.Fatalf("LogRec conversion failed in iter %d: got %s %v %v %v want %s %v %v %v, err %v",
i, devid1, srid1, gnum1, lsn1, devid, srid, gnum, lsn, err)
}
devid2, srid2, gnum2, err := splitGenerationKey(generationKey(devid, srid, gnum))
if err != nil || devid2 != devid || srid2 != srid || gnum2 != gnum {
t.Fatalf("Gen conversion failed in iter %d: got %s %v %v want %s %v %v, err %v",
i, devid2, srid2, gnum2, devid, srid, gnum, err)
}
}
//badStrs := []string{"abc:3:4", "abc:123:4:5", "abc:1234:-1:10", "ijk:7890:1000:-1", "abc:3:4:5:6", "abc::3:4:5"}
badStrs := []string{"abc:3:4", "abc:1234:-1:10", "ijk:7890:1000:-1", "abc:3:4:5:6", "abc::3:4:5"}
for _, s := range badStrs {
if _, _, _, _, err := splitLogRecKey(s); err == nil {
t.Fatalf("LogRec conversion didn't fail for str %s", s)
}
}
//badStrs = []string{"abc:3", "abc:123:4", "abc:1234:-1", "abc:3:4:5", "abc:4::5"}
badStrs = []string{"abc:3", "abc:1234:-1", "abc:3:4:5", "abc:4::5"}
for _, s := range badStrs {
if _, _, _, err := splitGenerationKey(s); err == nil {
t.Fatalf("Gen conversion didn't fail for str %s", s)
}
}
}
// TestPutGetLogRec tests setting and getting a log record across log open/close/reopen.
func TestPutGetLogRec(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
var devid DeviceId = "VeyronTab"
srid := ObjId("haha")
var gnum GenId = 100
var lsn SeqNum = 1000
rec, err := log.getLogRec(devid, srid, gnum, lsn)
if err == nil || rec != nil {
t.Errorf("GetLogRec() found non-existent object %s:%s:%d:%d in log file %s: %v, err %v",
devid, srid.String(), gnum, lsn, logfile, rec, err)
}
if log.hasLogRec(devid, srid, gnum, lsn) {
t.Errorf("HasLogRec() found non-existent object %s:%s:%d:%d in log file %s",
devid, srid.String(), gnum, lsn, logfile)
}
rec = &LogRec{
DevId: devid,
SyncRootId: srid,
GenNum: gnum,
SeqNum: lsn,
ObjId: ObjId("bla"),
CurVers: 2,
Parents: []Version{0, 1},
Value: LogValue{},
}
if _, err := log.putLogRec(rec); err != nil {
t.Errorf("Cannot put object %s:%s:%d:%d (%v) in log file %s, err %v", devid, srid.String(), gnum, lsn, rec, logfile, err)
}
for i := 0; i < 2; i++ {
curRec, err := log.getLogRec(devid, srid, gnum, lsn)
if err != nil || curRec == nil {
t.Fatalf("GetLogRec() can not find object %s:%s:%d:%d (i=%d) in log file %s, err %v",
devid, srid.String(), gnum, lsn, i, logfile, err)
}
if !log.hasLogRec(devid, srid, gnum, lsn) {
t.Errorf("HasLogRec() can not find object %s:%s:%d:%d (i=%d) in log file %s",
devid, srid.String(), gnum, lsn, i, logfile)
}
if !reflect.DeepEqual(curRec, rec) {
t.Errorf("Data mismatch for object %s:%d:%d (i=%d) in log file %s: %v instead of %v",
devid, gnum, lsn, i, logfile, curRec, rec)
}
if i == 0 {
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot re-open log file %s, err %v", logfile, err)
}
}
}
log.dump()
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// TestDelLogRec tests deleting a log record across log open/close/reopen.
func TestDelLogRec(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
var devid DeviceId = "VeyronTab"
srid := ObjId("haha")
var gnum GenId = 100
var lsn SeqNum = 1000
rec := &LogRec{
DevId: devid,
SyncRootId: srid,
GenNum: gnum,
SeqNum: lsn,
ObjId: ObjId("bla"),
CurVers: 2,
Parents: []Version{0, 1},
Value: LogValue{},
}
if _, err := log.putLogRec(rec); err != nil {
t.Errorf("Cannot put object %s:%s:%d:%d (%v) in log file %s, err %v", devid, srid.String(), gnum, lsn, rec, logfile, err)
}
curRec, err := log.getLogRec(devid, srid, gnum, lsn)
if err != nil || curRec == nil {
t.Fatalf("GetLogRec() can not find object %s:%s:%d:%d in log file %s, err %v",
devid, srid.String(), gnum, lsn, logfile, err)
}
if err := log.delLogRec(devid, srid, gnum, lsn); err != nil {
t.Fatalf("DelLogRec() can not delete object %s:%s:%d:%d in log file %s, err %v",
devid, srid.String(), gnum, lsn, logfile, err)
}
for i := 0; i < 2; i++ {
curRec, err = log.getLogRec(devid, srid, gnum, lsn)
if err == nil || curRec != nil {
t.Fatalf("GetLogRec() finds deleted object %s:%s:%d:%d (i=%d) in log file %s, err %v",
devid, srid.String(), gnum, lsn, i, logfile, err)
}
if log.hasLogRec(devid, srid, gnum, lsn) {
t.Errorf("HasLogRec() finds deleted object %s:%s:%d:%d (i=%d) in log file %s",
devid, srid.String(), gnum, lsn, i, logfile)
}
if i == 0 {
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot re-open log file %s, err %v", logfile, err)
}
}
}
log.dump()
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// TestPutGetGenMetadata tests setting and getting generation metadata across log open/close/reopen.
func TestPutGetGenMetadata(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
var devid DeviceId = "VeyronTab"
srid := ObjId("haha")
var gnum GenId = 100
val, err := log.getGenMetadata(devid, srid, gnum)
if err == nil || val != nil {
t.Errorf("GetGenMetadata() found non-existent object %s:%s:%d in log file %s: %v, err %v",
devid, srid.String(), gnum, logfile, val, err)
}
if log.hasGenMetadata(devid, srid, gnum) {
t.Errorf("hasGenMetadata() found non-existent object %s:%s:%d in log file %s",
devid, srid.String(), gnum, logfile)
}
val = &genMetadata{Pos: 40, Count: 100, MaxSeqNum: 99}
if err := log.putGenMetadata(devid, srid, gnum, val); err != nil {
t.Errorf("Cannot put object %s:%s:%d in log file %s, err %v", devid, srid.String(), gnum, logfile, err)
}
for i := 0; i < 2; i++ {
curVal, err := log.getGenMetadata(devid, srid, gnum)
if err != nil || curVal == nil {
t.Fatalf("GetGenMetadata() can not find object %s:%s:%d (i=%d) in log file %s, err %v",
devid, srid.String(), gnum, i, logfile, err)
}
if !log.hasGenMetadata(devid, srid, gnum) {
t.Errorf("hasGenMetadata() can not find object %s:%s:%d (i=%d) in log file %s",
devid, srid.String(), gnum, i, logfile)
}
if !reflect.DeepEqual(curVal, val) {
t.Errorf("Data mismatch for object %s:%s:%d (i=%d) in log file %s: %v instead of %v",
devid, srid.String(), gnum, i, logfile, curVal, val)
}
if i == 0 {
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot re-open log file %s, err %v", logfile, err)
}
}
}
log.dump()
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// TestDelGenMetadata tests deleting generation metadata across log open/close/reopen.
func TestDelGenMetadata(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
var devid DeviceId = "VeyronTab"
srid := ObjId("haha")
var gnum GenId = 100
val := &genMetadata{Pos: 40, Count: 100, MaxSeqNum: 99}
if err := log.putGenMetadata(devid, srid, gnum, val); err != nil {
t.Errorf("Cannot put object %s:%s:%d in log file %s, err %v", devid, srid.String(), gnum, logfile, err)
}
curVal, err := log.getGenMetadata(devid, srid, gnum)
if err != nil || curVal == nil {
t.Fatalf("GetGenMetadata() can not find object %s:%s:%d in log file %s, err %v",
devid, srid.String(), gnum, logfile, err)
}
if err := log.delGenMetadata(devid, srid, gnum); err != nil {
t.Fatalf("DelGenMetadata() can not delete object %s:%s:%d in log file %s, err %v",
devid, srid.String(), gnum, logfile, err)
}
for i := 0; i < 2; i++ {
curVal, err := log.getGenMetadata(devid, srid, gnum)
if err == nil || curVal != nil {
t.Fatalf("GetGenMetadata() finds deleted object %s:%s:%d (i=%d) in log file %s, err %v",
devid, srid.String(), gnum, i, logfile, err)
}
if log.hasGenMetadata(devid, srid, gnum) {
t.Errorf("hasGenMetadata() finds deleted object %s:%s:%d (i=%d) in log file %s",
devid, srid.String(), gnum, i, logfile)
}
if i == 0 {
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot re-open log file %s, err %v", logfile, err)
}
}
}
log.dump()
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// TestPersistLogState tests that generation metadata and record state
// is persisted across log open/close/reopen.
func TestPersistLogState(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
log, err := openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
var devid DeviceId = "VeyronTab"
srid := ObjId("haha")
// Add several generations.
for i := uint32(0); i < 10; i++ {
val := &genMetadata{Pos: i}
if err := log.putGenMetadata(devid, srid, GenId(i+10), val); err != nil {
t.Errorf("Cannot put object %s:%s:%d in log file %s, err %v", devid, srid.String(),
i, logfile, err)
}
}
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, nil)
if err != nil {
t.Fatalf("Cannot re-open log file %s, err %v", logfile, err)
}
for i := uint32(0); i < 10; i++ {
curVal, err := log.getGenMetadata(devid, srid, GenId(i+10))
if err != nil || curVal == nil {
t.Fatalf("GetGenMetadata() can not find object %s:%s:%d in log file %s, err %v",
devid, srid.String(), i, logfile, err)
}
if curVal.Pos != i {
t.Errorf("Data mismatch for object %s:%s:%d in log file %s: %v",
devid, srid.String(), i, logfile, curVal)
}
// Should safely overwrite the same keys.
curVal.Pos = i + 10
if err := log.putGenMetadata(devid, srid, GenId(i+10), curVal); err != nil {
t.Errorf("Cannot put object %s:%s:%d in log file %s, err %v", devid, srid.String(),
i, logfile, err)
}
}
for i := uint32(0); i < 10; i++ {
curVal, err := log.getGenMetadata(devid, srid, GenId(i+10))
if err != nil || curVal == nil {
t.Fatalf("GetGenMetadata() can not find object %s:%s:%d in log file %s, err %v",
devid, srid.String(), i, logfile, err)
}
if curVal.Pos != (i + 10) {
t.Errorf("Data mismatch for object %s:%s:%d in log file %s: %v, err %v",
devid, srid.String(), i, logfile, curVal, err)
}
}
log.dump()
if err := log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// fillFakeLogRecords fills fake log records for testing purposes.
func (l *iLog) fillFakeLogRecords(srid ObjId) {
const num = 10
var parvers []Version
id := ObjId("haha")
for i := int(0); i < num; i++ {
// Create a local log record.
curvers := Version(i)
rec, err := l.createLocalLogRec(id, curvers, parvers, &LogValue{}, srid)
if err != nil {
return
}
// Insert the new log record into the log.
_, err = l.putLogRec(rec)
if err != nil {
return
}
parvers = []Version{curvers}
}
}
// TestCreateGeneration tests that local log records and local
// generations are created uniquely and remote generations are
// correctly inserted in log order.
func TestCreateGeneration(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
s := &syncd{id: "VeyronTab"}
log, err := openILog(logfile, s)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
// Try using a wrong SyncRoot ID.
bad_srid := ObjId("xyz")
if _, err := log.createLocalLogRec(ObjId("asdf"), NoVersion, nil, &LogValue{}, bad_srid); err == nil {
t.Errorf("createLocalLogRec did not fail when using an invalid SyncRoot ID %v", bad_srid)
}
if _, err := log.createLocalLinkLogRec(ObjId("qwerty"), NoVersion, NoVersion, bad_srid); err == nil {
t.Errorf("createLocalLinkLogRec did not fail when using an invalid SyncRoot ID %v", bad_srid)
}
if _, err := log.createLocalGeneration(bad_srid); err == nil {
t.Errorf("createLocalGeneration did not fail when using an invalid SyncRoot ID %v", bad_srid)
}
srids := []ObjId{ObjId("foo"), ObjId("bar")}
num := []uint64{10, 20}
for pos, sr := range srids {
if err := log.initSyncRoot(sr); err != nil {
t.Fatalf("Cannot create new SyncRoot %s, err %v", sr.String(), err)
}
if g, err := log.createLocalGeneration(sr); err != errNoUpdates {
t.Errorf("Should not find local updates gen %d with error %v", g, err)
}
var parvers []Version
id := ObjId(fmt.Sprintf("haha-%d", pos))
for i := uint64(0); i < num[pos]; i++ {
// Create a local log record.
curvers := Version(i)
rec, err := log.createLocalLogRec(id, curvers, parvers, &LogValue{}, sr)
if err != nil {
t.Fatalf("Cannot create local log rec ObjId: %v Current: %v Parents: %v Error: %v",
id, curvers, parvers, err)
}
temprec := &LogRec{
DevId: log.s.id,
SyncRootId: sr,
GenNum: GenId(1),
SeqNum: SeqNum(i),
ObjId: id,
CurVers: curvers,
Parents: parvers,
Value: LogValue{},
}
// Verify that the log record has the right values.
if !reflect.DeepEqual(rec, temprec) {
t.Errorf("Data mismtach in log record %v instead of %v", rec, temprec)
}
// Insert the new log record into the log.
_, err = log.putLogRec(rec)
if err != nil {
t.Errorf("Cannot put log record:: failed with err %v", err)
}
parvers = []Version{curvers}
}
}
if err = log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
log, err = openILog(logfile, s)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
for pos, sr := range srids {
if log.head.CurSRGens[sr].CurGenNum != 1 || log.head.CurSRGens[sr].CurSeqNum != SeqNum(num[pos]) {
t.Errorf("Data mismatch in log header %v (pos=%d)", log.head, pos)
}
g, err := log.createLocalGeneration(sr)
if g != 1 || err != nil {
t.Errorf("Could not create local generation srid %s gen %d (pos=%d) with error %v",
sr.String(), g, pos, err)
}
curVal, err := log.getGenMetadata(log.s.id, sr, g)
if err != nil || curVal == nil {
t.Fatalf("GetGenMetadata() can not find object %s:%s:%d (pos=%d) in log file %s, err %v",
log.s.id, sr.String(), g, pos, logfile, err)
}
expVal := &genMetadata{Pos: uint32(pos), Count: num[pos], MaxSeqNum: SeqNum(num[pos] - 1)}
if !reflect.DeepEqual(curVal, expVal) {
t.Errorf("Data mismatch for object %s:%s:%d (pos=%d) in log file %s: %v instead of %v",
log.s.id, sr.String(), g, pos, logfile, curVal, expVal)
}
if log.head.CurSRGens[sr].CurGenNum != 2 || log.head.CurSRGens[sr].CurSeqNum != 0 ||
log.head.Curorder != uint32(pos+1) {
t.Errorf("Data mismatch in log header (pos=%d) %v %v",
pos, log.head.CurSRGens[sr], log.head.Curorder)
}
if g, err := log.createLocalGeneration(sr); err != errNoUpdates {
t.Errorf("Should not find local updates sr %s gen %d (pos=%d) with error %v",
sr.String(), g, pos, err)
}
}
// Populate one more generation for only one syncroot.
log.fillFakeLogRecords(srids[0])
if g, err := log.createLocalGeneration(srids[0]); g != 2 || err != nil {
t.Errorf("Could not create local generation sgid %s gen %d with error %v",
srids[0].String(), g, err)
}
if g, err := log.createLocalGeneration(srids[1]); err != errNoUpdates {
t.Errorf("Should not find local updates sr %s gen %d with error %v", srids[1].String(), g, err)
}
// Create a remote generation.
expGen := &genMetadata{Count: 1, MaxSeqNum: 50}
if err = log.createRemoteGeneration("VeyronPhone", srids[0], 1, expGen); err == nil {
t.Errorf("Remote generation create should have failed")
}
expGen.MaxSeqNum = 0
if err = log.createRemoteGeneration("VeyronPhone", srids[0], 1, expGen); err != nil {
t.Errorf("createRemoteGeneration failed with err %v", err)
}
if expGen.Pos != 3 {
t.Errorf("createRemoteGeneration created incorrect log order %d", expGen.Pos)
}
if err = log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
// Reopen the log and ensure that all log records for generations exist.
// Also ensure that generation metadata is accurate.
log, err = openILog(logfile, s)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
gens := []GenId{2, 1}
order := map[ObjId][]uint32{srids[0]: []uint32{0, 2},
srids[1]: []uint32{1}}
for pos, sr := range srids {
for g := GenId(1); g <= gens[pos]; g++ {
// Check all log records.
for i := SeqNum(0); i < SeqNum(num[pos]); i++ {
curRec, err := log.getLogRec(log.s.id, sr, g, i)
if err != nil || curRec == nil {
t.Fatalf("GetLogRec() can not find object %s:%s:%d:%d in log file %s, err %v",
log.s.id, sr.String(), g, i, logfile, err)
}
}
// Check generation metadata.
curVal, err := log.getGenMetadata(log.s.id, sr, g)
if err != nil || curVal == nil {
t.Fatalf("GetGenMetadata() can not find object %s:%s:%d in log file %s, err %v",
log.s.id, sr.String(), g, logfile, err)
}
expVal := &genMetadata{Count: num[pos], MaxSeqNum: SeqNum(num[pos] - 1), Pos: order[sr][g-1]}
if !reflect.DeepEqual(curVal, expVal) {
t.Errorf("Data mismatch for object %s:%s:%d in log file %s: %v instead of %v",
log.s.id, sr.String(), g, logfile, curVal, expVal)
}
}
}
// Check remote generation metadata.
curVal, err := log.getGenMetadata("VeyronPhone", srids[0], 1)
if err != nil || curVal == nil {
t.Fatalf("GetGenMetadata() can not find object in log file %s, err %v",
logfile, err)
}
if !reflect.DeepEqual(curVal, expGen) {
t.Errorf("Data mismatch for object in log file %s: %v instead of %v",
logfile, curVal, expGen)
}
expSRGens := map[ObjId]*curLocalGen{srids[0]: &curLocalGen{3, 0},
srids[1]: &curLocalGen{2, 0}}
if !reflect.DeepEqual(log.head.CurSRGens, expSRGens) || log.head.Curorder != 4 {
t.Errorf("Data mismatch in log header %v %v", log.head.CurSRGens, log.head.Curorder)
}
log.dump()
if err = log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
// TestProcessWatchRecord tests that local updates are correctly handled.
// Commands are in file testdata/<local-init-00.log.sync, local-init-02.log.sync>.
/*
func TestProcessWatchRecord(t *testing.T) {
logfile := getFileName()
defer os.Remove(logfile)
dagfile := getFileName()
defer os.Remove(dagfile)
var err error
s := &syncd{id: "VeyronTab"}
if s.dag, err = openDAG(dagfile); err != nil {
t.Fatalf("Cannot open new dag file %s, err %v", logfile, err)
}
log, err := openILog(logfile, s)
if err != nil {
t.Fatalf("Cannot open new log file %s, err %v", logfile, err)
}
srids := []ObjId{"foo", "bar"}
fnames := []string{"local-init-00.log.sync", "local-init-02.log.sync"}
num := []uint32{3, 5}
// Try using an invalid SyncRoot ID.
if err = log.processWatchRecord(ObjId("haha"), 2, Version(999), &LogValue{}, NoObjId); err == nil {
t.Errorf("processWatchRecord did not fail when using an invalid SyncRoot ID")
}
// Test echo suppression on JoinSyncGroup.
if err := log.processWatchRecord(srids[0], 5, NoVersion, &LogValue{}, srids[0]); err != nil {
t.Fatalf("Echo suppression on JoinSyncGroup failed with err %v", err)
}
for pos, sr := range srids {
if err := log.initSyncRoot(sr); err != nil {
t.Fatalf("Cannot create new SyncRoot %s, err %v", sr.String(), err)
}
if err := logReplayCommands(log, fnames[pos], sr); err != nil {
t.Error(err)
}
}
checkObject := func(obj string, expHead Version) {
_, file, line, _ := runtime.Caller(1)
objid, err := strToObjId(obj)
if err != nil {
t.Errorf("%s:%d Could not create objid %v", file, line, err)
}
// Verify DAG state.
if head, err := log.s.dag.getHead(objid); err != nil || head != expHead {
t.Errorf("%s:%d Invalid object %d head in DAG %v want %v, err %v", file, line,
objid, head, expHead, err)
}
}
for pos, sr := range srids {
if log.head.CurSRGens[sr].CurGenNum != 1 || log.head.CurSRGens[sr].CurSeqNum != SeqNum(num[pos]) ||
log.head.Curorder != 0 {
t.Errorf("Data mismatch in log header %v", log.head)
}
// Check all log records.
for i := SeqNum(0); i < SeqNum(num[pos]); i++ {
curRec, err := log.getLogRec(log.s.id, sr, GenId(1), i)
if err != nil || curRec == nil {
t.Fatalf("GetLogRec() can not find object %s:%s:1:%d in log file %s, err %v",
log.s.id, sr.String(), i, logfile, err)
}
}
if pos == 0 {
checkObject("1234", 3)
} else if pos == 1 {
checkObject("12", 3)
checkObject("45", 20)
}
}
s.dag.flush()
s.dag.close()
log.dump()
if err = log.close(); err != nil {
t.Errorf("Cannot close log file %s, err %v", logfile, err)
}
}
*/