blob: 923e27adc5b43ced15593a84c9956fcbb9403644 [file] [log] [blame]
package memstore
import (
"fmt"
"io/ioutil"
"os"
"runtime"
"testing"
"veyron/services/store/memstore/refs"
"veyron/services/store/memstore/state"
"veyron2/security"
"veyron2/storage"
"veyron2/vom"
)
var (
rootPublicID security.PublicID = security.FakePublicID("root")
)
type Dir struct {
Entries map[string]storage.ID
}
type Data struct {
Comment string
}
func init() {
vom.Register(&Data{})
}
func newData(c string) *Data {
return &Data{Comment: c}
}
func expectEqDir(t *testing.T, file string, line int, d1, d2 *Dir) {
for name, id1 := range d1.Entries {
id2, ok := d2.Entries[name]
if !ok {
t.Errorf("%s(%d): does not exist: %s", file, line, name)
} else if id2 != id1 {
t.Errorf("%s(%d): expected ID %s, got %s", file, line, id1, id2)
}
}
for name, _ := range d2.Entries {
_, ok := d1.Entries[name]
if !ok {
t.Errorf("%s(%d): should not exist: %s", file, line, name)
}
}
}
func expectEqData(t *testing.T, file string, line int, d1, d2 *Data) {
if d1.Comment != d2.Comment {
t.Errorf("%s(%d): expected %q, got %q", d1.Comment, d2.Comment)
}
}
// expectEqValues compares two items. They are equal only if they have the same
// type, and their contents are equal.
func expectEqValues(t *testing.T, file string, line int, v1, v2 interface{}) {
switch x1 := v1.(type) {
case *Dir:
x2, ok := v2.(*Dir)
if !ok {
t.Errorf("%s(%d): not a Dir: %v", file, line, v2)
} else {
expectEqDir(t, file, line, x1, x2)
}
case *Data:
x2, ok := v2.(*Data)
if !ok {
t.Errorf("%s(%d): not a Data: %v", file, line, v2)
} else {
expectEqData(t, file, line, x1, x2)
}
default:
t.Errorf("Unknown type: %T, %v", v1, v1)
}
}
// expectEqImplicitDir compares two directories.
func expectEqImplicitDir(t *testing.T, file string, line int, d1, d2 refs.Dir) {
l1 := refs.FlattenDir(d1)
l2 := refs.FlattenDir(d2)
i1 := 0
i2 := 0
for i1 < len(l1) && i2 < len(l2) {
e1 := l1[i1]
e2 := l2[i2]
if e1.Name == e2.Name {
if e1.ID != e2.ID {
t.Errorf("%s(%d): expected id %s, got %s", file, line, e1.ID, e2.ID)
}
i1++
i2++
} else if e1.Name < e2.Name {
t.Errorf("%s(%d): missing directory %s", file, line, e1.Name)
i1++
} else {
t.Errorf("%s(%d): unexpected directory %s", file, line, e2.Name)
i2++
}
}
for _, e1 := range l1[i1:] {
t.Errorf("%s(%d): missing directory %s", file, line, e1.Name)
}
for _, e2 := range l2[i2:] {
t.Errorf("%s(%d): unexpected directory %s", file, line, e2.Name)
}
}
func readTransaction(r *RLog) (*state.Mutations, error) {
type result struct {
mu *state.Mutations
err error
}
results := make(chan result)
go func() {
defer close(results)
mu, err := r.ReadTransaction()
results <- result{mu: mu, err: err}
}()
res := <-results
return res.mu, res.err
}
// readTransactions reads and applies the transactions. Returns the last transaction read.
func readTransactions(t *testing.T, r *RLog, st *Store, n int) *state.Mutations {
_, file, line, _ := runtime.Caller(1)
var m *state.Mutations
for i := 0; i < n; i++ {
var err error
m, err = readTransaction(r)
if err != nil {
t.Errorf("%s(%d): error in readTransaction(): %s", file, line, err)
return nil
}
if err := st.ApplyMutations(m); err != nil {
t.Errorf("%s(%d): error in ApplyMutations(): %s", file, line, err)
return nil
}
}
return m
}
func TestTransaction(t *testing.T) {
dbName, err := ioutil.TempDir(os.TempDir(), "vstore")
if err != nil {
t.Fatalf("ioutil.TempDir() failed: %v", err)
}
defer os.RemoveAll(dbName)
w, err := createLog(dbName)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
// Create an initial state.
st1, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("newState() failed: %v", err)
}
tr := &Transaction{}
mkdir(t, st1, tr, "/")
mkdir(t, st1, tr, "/a")
mkdir(t, st1, tr, "/a/b")
mkdir(t, st1, tr, "/a/b/c")
commit(t, tr)
// Write the initial state.
defer w.close()
w.writeState(st1)
// Write some Transactions.
var data [5]*Data
var paths [5]string
for i := 0; i != 5; i++ {
name := fmt.Sprintf("data%d", i)
tr := &Transaction{}
data[i] = newData(name)
paths[i] = "/a/b/c/" + name
put(t, st1, tr, paths[i], data[i])
commit(t, tr)
w.appendTransaction(tr.snapshot.Mutations())
}
r, err := OpenLog(dbName, true)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
defer r.Close()
st2, err := r.ReadState(rootPublicID)
if err != nil {
t.Fatalf("Can't read state: %s", err)
}
readTransactions(t, r, st2, 5)
// Remove data3.
{
tr := &Transaction{}
remove(t, st1, tr, "/a/b/c/data3")
commit(t, tr)
w.appendTransaction(tr.snapshot.Mutations())
}
readTransactions(t, r, st2, 1)
{
tr := &Transaction{}
expectExists(t, st1, tr, paths[0])
expectNotExists(t, st1, tr, paths[3])
}
// Remove all entries.
{
tr := &Transaction{}
remove(t, st1, tr, "/a/b/c")
commit(t, tr)
w.appendTransaction(tr.snapshot.Mutations())
}
readTransactions(t, r, st2, 1)
{
tr := &Transaction{}
for i := 0; i != 5; i++ {
expectNotExists(t, st1, tr, paths[i])
}
}
}
func TestDeletions(t *testing.T) {
dbName, err := ioutil.TempDir(os.TempDir(), "vstore")
if err != nil {
t.Fatalf("ioutil.TempDir() failed: %v", err)
}
defer os.RemoveAll(dbName)
// Create an initial state.
st1, err := New(rootPublicID, dbName)
if err != nil {
t.Fatalf("newState() failed: %v", err)
}
tr := &Transaction{}
mkdir(t, st1, tr, "/")
mkdir(t, st1, tr, "/a")
ids := make(map[string]storage.ID)
ids["/a/b"], _ = mkdir(t, st1, tr, "/a/b")
ids["/a/b/c"], _ = mkdir(t, st1, tr, "/a/b/c")
ids["/a/b/d"], _ = mkdir(t, st1, tr, "/a/b/d")
commit(t, tr)
// Reconstruct the state from the log.
r, err := OpenLog(dbName, true)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
defer r.Close()
st2, err := r.ReadState(rootPublicID)
if err != nil {
t.Fatalf("Can't read state: %s", err)
}
readTransactions(t, r, st2, 1)
// Remove b.
{
tr := &Transaction{}
remove(t, st1, tr, "/a/b")
commit(t, tr)
}
readTransactions(t, r, st2, 1)
{
tr := &Transaction{}
expectExists(t, st1, tr, "/a")
expectNotExists(t, st1, tr, "/a/b")
}
// Perform a GC.
if err := st1.GC(); err != nil {
t.Errorf("Unexpected error: %s", err)
}
// The transaction should include deletions.
mu := readTransactions(t, r, st2, 1)
for name, id := range ids {
if _, ok := mu.Deletions[id]; !ok {
t.Errorf("Expected deletion for path %s", name)
}
if len(mu.Deletions) != len(ids) {
t.Errorf("Unexpected deletion: %v", mu.Deletions)
}
}
}