| package vsync |
| |
| // Tests for the Veyron Sync K/V DB component. |
| |
| import ( |
| "fmt" |
| "os" |
| "reflect" |
| "testing" |
| "time" |
| ) |
| |
| // A user structure stores info in the "users" table. |
| type user struct { |
| Username string |
| Drinks []string |
| } |
| |
| // A drink structure stores info in the "drinks" table. |
| type drink struct { |
| Name string |
| Alcohol bool |
| } |
| |
| var ( |
| users = []user{ |
| {Username: "lancelot", Drinks: []string{"beer", "coffee"}}, |
| {Username: "arthur", Drinks: []string{"coke", "beer", "coffee"}}, |
| {Username: "robin", Drinks: []string{"pepsi"}}, |
| {Username: "galahad"}, |
| } |
| drinks = []drink{ |
| {Name: "coke", Alcohol: false}, |
| {Name: "pepsi", Alcohol: false}, |
| {Name: "beer", Alcohol: true}, |
| {Name: "coffee", Alcohol: false}, |
| } |
| ) |
| |
| // createTestDB creates a K/V DB with 2 tables. |
| func createTestDB(t *testing.T) (fname string, db *kvdb, usersTbl, drinksTbl *kvtable) { |
| fname = fmt.Sprintf("%s/sync_kvdb_test_%d_%d", os.TempDir(), os.Getpid(), time.Now().UnixNano()) |
| db, tbls, err := kvdbOpen(fname, []string{"users", "drinks"}) |
| if err != nil { |
| os.Remove(fname) |
| t.Fatalf("cannot create new K/V DB file %s: %v", fname, err) |
| } |
| |
| if _, err = os.Stat(fname); err != nil { |
| os.Remove(fname) |
| t.Fatalf("newly created K/V DB file %s not found: %v", fname, err) |
| } |
| |
| usersTbl, drinksTbl = tbls[0], tbls[1] |
| return |
| } |
| |
| // initTestTables initializes the K/V tables used by the tests. |
| func initTestTables(t *testing.T, usersTbl, drinksTbl *kvtable, useCreate bool) { |
| userPut, drinkPut, funcName := usersTbl.set, drinksTbl.set, "set()" |
| if useCreate { |
| userPut, drinkPut, funcName = usersTbl.create, drinksTbl.create, "create()" |
| } |
| |
| for _, uu := range users { |
| if err := userPut(uu.Username, &uu); err != nil { |
| t.Fatalf("%s failed for user %s", funcName, uu.Username) |
| } |
| } |
| |
| for _, dd := range drinks { |
| if err := drinkPut(dd.Name, &dd); err != nil { |
| t.Fatalf("%s failed for drink %s", funcName, dd.Name) |
| } |
| } |
| |
| return |
| } |
| |
| // checkTestTables verifies the contents of the K/V tables. |
| func checkTestTables(t *testing.T, usersTbl, drinksTbl *kvtable) { |
| for _, uu := range users { |
| var u2 user |
| if err := usersTbl.get(uu.Username, &u2); err != nil { |
| t.Fatalf("get() failed for user %s", uu.Username) |
| } |
| if !reflect.DeepEqual(u2, uu) { |
| t.Fatalf("got wrong data for user %s: %#v instead of %#v", uu.Username, u2, uu) |
| } |
| if !usersTbl.hasKey(uu.Username) { |
| t.Fatalf("hasKey() did not find user %s", uu.Username) |
| } |
| } |
| for _, dd := range drinks { |
| var d2 drink |
| if err := drinksTbl.get(dd.Name, &d2); err != nil { |
| t.Fatalf("get() failed for drink %s", dd.Name) |
| } |
| if !reflect.DeepEqual(d2, dd) { |
| t.Fatalf("got wrong data for drink %s: %#v instead of %#v", dd.Name, d2, dd) |
| } |
| if !drinksTbl.hasKey(dd.Name) { |
| t.Fatalf("hasKey() did not find drink %s", dd.Name) |
| } |
| } |
| } |
| |
| func TestKVDBSet(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| |
| db.flush() |
| } |
| |
| func TestKVDBCreate(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, true) |
| |
| db.flush() |
| } |
| |
| func TestKVDBBadGet(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| // The DB is empty, all gets must fail. |
| for _, uu := range users { |
| var u2 user |
| if err := usersTbl.get(uu.Username, &u2); err == nil { |
| t.Fatalf("get() found non-existent user %s in file %s: %v", uu.Username, kvdbfile, u2) |
| } |
| } |
| for _, dd := range drinks { |
| var d2 drink |
| if err := drinksTbl.get(dd.Name, &d2); err == nil { |
| t.Fatalf("get() found non-existent drink %s in file %s: %v", dd.Name, kvdbfile, d2) |
| } |
| } |
| } |
| |
| func TestKVDBBadUpdate(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| // The DB is empty, all updates must fail. |
| for _, uu := range users { |
| u2 := user{Username: uu.Username} |
| if err := usersTbl.update(uu.Username, &u2); err == nil { |
| t.Fatalf("update() worked for a non-existent user %s in file %s", uu.Username, kvdbfile) |
| } |
| } |
| for _, dd := range drinks { |
| d2 := drink{Name: dd.Name} |
| if err := drinksTbl.update(dd.Name, &d2); err == nil { |
| t.Fatalf("update() worked for a non-existent drink %s in file %s", dd.Name, kvdbfile) |
| } |
| } |
| } |
| |
| func TestKVDBBadHasKey(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| // The DB is empty, all key-checks must fail. |
| for _, uu := range users { |
| if usersTbl.hasKey(uu.Username) { |
| t.Fatalf("hasKey() found non-existent user %s in file %s", uu.Username, kvdbfile) |
| } |
| } |
| for _, dd := range drinks { |
| if drinksTbl.hasKey(dd.Name) { |
| t.Fatalf("hasKey() found non-existent drink %s in file %s", dd.Name, kvdbfile) |
| } |
| } |
| } |
| |
| func TestKVDBGet(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| checkTestTables(t, usersTbl, drinksTbl) |
| |
| db.flush() |
| checkTestTables(t, usersTbl, drinksTbl) |
| } |
| |
| func TestKVDBBadCreate(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| |
| // Must not be able to re-create the same entries. |
| for _, uu := range users { |
| u2 := user{Username: uu.Username} |
| if err := usersTbl.create(uu.Username, &u2); err == nil { |
| t.Fatalf("create() worked for an existing user %s in file %s", uu.Username, kvdbfile) |
| } |
| } |
| for _, dd := range drinks { |
| d2 := drink{Name: dd.Name} |
| if err := drinksTbl.create(dd.Name, &d2); err == nil { |
| t.Fatalf("create() worked for an existing drink %s in file %s", dd.Name, kvdbfile) |
| } |
| } |
| |
| db.flush() |
| } |
| |
| func TestKVDBReopen(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| |
| initTestTables(t, usersTbl, drinksTbl, true) |
| |
| // Close the re-open the file. |
| db.flush() |
| db.close() |
| |
| db, tbls, err := kvdbOpen(kvdbfile, []string{"users", "drinks"}) |
| if err != nil { |
| t.Fatalf("Cannot re-open existing K/V DB file %s", kvdbfile) |
| } |
| defer db.close() |
| |
| usersTbl, drinksTbl = tbls[0], tbls[1] |
| checkTestTables(t, usersTbl, drinksTbl) |
| } |
| |
| func TestKVDBKeyIter(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| |
| // Get the list of all entry keys in each table. |
| keylist := "" |
| err := usersTbl.keyIter(func(key string) { |
| keylist += key + "," |
| }) |
| if err != nil || keylist != "arthur,galahad,lancelot,robin," { |
| t.Fatalf("keyIter() failed in file %s: err %v, user names: %v", kvdbfile, err, keylist) |
| } |
| keylist = "" |
| err = drinksTbl.keyIter(func(key string) { |
| keylist += key + "," |
| }) |
| if err != nil || keylist != "beer,coffee,coke,pepsi," { |
| t.Fatalf("keyIter() failed in file %s: err %v, drink names: %v", kvdbfile, err, keylist) |
| } |
| |
| db.flush() |
| } |
| |
| func TestKVDBUpdate(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| db.flush() |
| db.close() |
| |
| db, tbls, err := kvdbOpen(kvdbfile, []string{"users", "drinks"}) |
| if err != nil { |
| t.Fatalf("Cannot re-open existing K/V DB file %s", kvdbfile) |
| } |
| defer db.close() |
| |
| usersTbl, drinksTbl = tbls[0], tbls[1] |
| |
| for _, uu := range users { |
| key := uu.Username |
| u2 := uu |
| u2.Username += "-New" |
| |
| if err = usersTbl.update(key, &u2); err != nil { |
| t.Fatalf("update() failed for user %s in file %s", key, kvdbfile) |
| } |
| |
| var u3 user |
| if err = usersTbl.get(key, &u3); err != nil { |
| t.Fatalf("get() failed for user %s in file %s", key, kvdbfile) |
| } |
| if !reflect.DeepEqual(u3, u2) { |
| t.Fatalf("got wrong new data for user %s in file %s: %#v instead of %#v", key, kvdbfile, u3, u2) |
| } |
| } |
| |
| for _, dd := range drinks { |
| key := dd.Name |
| d2 := dd |
| d2.Alcohol = !d2.Alcohol |
| |
| if err = drinksTbl.update(key, &d2); err != nil { |
| t.Fatalf("update() failed for drink %s in file %s", key, kvdbfile) |
| } |
| |
| var d3 drink |
| if err = drinksTbl.get(key, &d3); err != nil { |
| t.Fatalf("get() failed for drink %s in file %s", key, kvdbfile) |
| } |
| if !reflect.DeepEqual(d3, d2) { |
| t.Fatalf("got wrong new data for drink %s in file %s: %#v instead of %#v", key, kvdbfile, d3, d2) |
| } |
| } |
| |
| db.flush() |
| } |
| |
| func TestKVDBSetAgain(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| |
| for _, uu := range users { |
| key := uu.Username |
| u2 := uu |
| u2.Username += "-New" |
| |
| if err := usersTbl.set(key, &u2); err != nil { |
| t.Fatalf("set() again failed for user %s in file %s", key, kvdbfile) |
| } |
| |
| var u3 user |
| if err := usersTbl.get(key, &u3); err != nil { |
| t.Fatalf("get() failed for user %s in file %s", key, kvdbfile) |
| } |
| if !reflect.DeepEqual(u3, u2) { |
| t.Fatalf("got wrong new data for user %s in file %s: %#v instead of %#v", key, kvdbfile, u3, u2) |
| } |
| } |
| |
| for _, dd := range drinks { |
| key := dd.Name |
| d2 := dd |
| d2.Alcohol = !d2.Alcohol |
| |
| if err := drinksTbl.update(key, &d2); err != nil { |
| t.Fatalf("set() again failed for drink %s in file %s", key, kvdbfile) |
| } |
| |
| var d3 drink |
| if err := drinksTbl.get(key, &d3); err != nil { |
| t.Fatalf("get() failed for drink %s in file %s", key, kvdbfile) |
| } |
| if !reflect.DeepEqual(d3, d2) { |
| t.Fatalf("got wrong new data for drink %s in file %s: %#v instead of %#v", key, kvdbfile, d3, d2) |
| } |
| } |
| |
| db.flush() |
| } |
| |
| func TestKVDBCompact(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| |
| db.flush() |
| |
| // Make some NOP changes and flush the DB after each change to grow the file. |
| for _, uu := range users { |
| key := uu.Username |
| if err := usersTbl.set(key, &uu); err != nil { |
| t.Fatalf("set() NOP failed for user %s in file %s", key, kvdbfile) |
| } |
| db.flush() |
| } |
| |
| for _, dd := range drinks { |
| key := dd.Name |
| if err := drinksTbl.update(key, &dd); err != nil { |
| t.Fatalf("set() NOP failed for drink %s in file %s", key, kvdbfile) |
| } |
| db.flush() |
| } |
| |
| // Compact the DB file and verify that it contains the same data and has a smaller size. |
| oldSize := fileSize(kvdbfile) |
| |
| db, tbls, err := db.compact(kvdbfile, []string{"users", "drinks"}) |
| if err != nil { |
| t.Fatalf("compact() failed to create a smaller version of K/V DB file %s: %v", kvdbfile, err) |
| } |
| defer db.close() |
| |
| usersTbl, drinksTbl = tbls[0], tbls[1] |
| |
| newSize := fileSize(kvdbfile) |
| if newSize >= oldSize { |
| t.Errorf("compact() failed to shrink file %s: old size %d, new size %d", kvdbfile, oldSize, newSize) |
| } |
| |
| checkTestTables(t, usersTbl, drinksTbl) |
| |
| // Verify that compact() rejects a filename without a basename. |
| for _, badfile := range []string{"/", ".", ".."} { |
| _, _, err := db.compact(badfile, []string{"users", "drinks"}) |
| if err == nil { |
| t.Errorf("compact() did not fail when given the invalid filename: %s", badfile) |
| } |
| } |
| } |
| |
| func TestKVDBDelete(t *testing.T) { |
| kvdbfile, db, usersTbl, drinksTbl := createTestDB(t) |
| defer os.Remove(kvdbfile) |
| defer db.close() |
| |
| initTestTables(t, usersTbl, drinksTbl, false) |
| |
| db.flush() |
| |
| // Delete entries and verify that they no longer exist. |
| |
| for _, uu := range users { |
| key := uu.Username |
| if err := usersTbl.del(key); err != nil { |
| t.Errorf("del() failed for user %s in file %s", key, kvdbfile) |
| } |
| if usersTbl.hasKey(key) { |
| t.Errorf("hasKey() still finds deleted user %s in file %s", key, kvdbfile) |
| } |
| } |
| |
| for _, dd := range drinks { |
| key := dd.Name |
| if err := drinksTbl.del(key); err != nil { |
| t.Errorf("del() failed for drink %s in file %s", key, kvdbfile) |
| } |
| if drinksTbl.hasKey(key) { |
| t.Errorf("hasKey() still finds deleted drink %s in file %s", key, kvdbfile) |
| } |
| } |
| |
| db.flush() |
| } |