blob: 37431dca11ae0d2574f1d8d1ac07bc963f21ca40 [file] [log] [blame]
package memstore
import (
"runtime"
"testing"
"veyron/services/store/service"
"veyron2/query"
)
func newValue() interface{} {
return &Dir{}
}
func TestPutGetRemoveRoot(t *testing.T) {
st, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("New() failed: %v", err)
}
o := st.Bind("/")
testGetPutRemove(t, st, o)
}
func TestPutGetRemoveChild(t *testing.T) {
st, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("New() failed: %v", err)
}
tr := NewTransaction()
mkdir(t, st, tr, "/")
commit(t, tr)
o := st.Bind("/Entries/a")
testGetPutRemove(t, st, o)
}
func testGetPutRemove(t *testing.T, st *Store, o service.Object) {
value := newValue()
{
// Check that the root object does not exist.
tr := &Transaction{}
if ok, err := o.Exists(rootPublicID, tr); ok && err == nil {
t.Errorf("Should not exist")
}
if v, err := o.Get(rootPublicID, tr); v != nil && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
}
{
// Add the root object.
tr1 := &Transaction{}
s, err := o.Put(rootPublicID, tr1, value)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
if ok, err := o.Exists(rootPublicID, tr1); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
e, err := o.Get(rootPublicID, tr1)
if err != nil {
t.Errorf("Object should exist: %s", err)
}
if e.Stat.ID != s.ID {
t.Errorf("Expected %s, got %s", s.ID, e.Stat.ID)
}
// Transactions are isolated.
tr2 := &Transaction{}
if ok, err := o.Exists(rootPublicID, tr2); ok && err != nil {
t.Errorf("Should not exist")
}
if v, err := o.Get(rootPublicID, tr2); v != nil && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
// Apply tr1.
if err := tr1.Commit(); err != nil {
t.Errorf("Unexpected error")
}
if ok, err := o.Exists(rootPublicID, tr1); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
if _, err := o.Get(rootPublicID, tr1); err != nil {
t.Errorf("Object should exist: %s", err)
}
// tr2 is still isolated.
if ok, err := o.Exists(rootPublicID, tr2); ok && err == nil {
t.Errorf("Should not exist")
}
if v, err := o.Get(rootPublicID, tr2); v != nil && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
// tr3 observes the commit.
tr3 := &Transaction{}
if ok, err := o.Exists(rootPublicID, tr3); !ok || err != nil {
t.Errorf("Should exist")
}
if _, err := o.Get(rootPublicID, tr3); err != nil {
t.Errorf("Object should exist: %s", err)
}
}
{
// Remove the root object.
tr1 := &Transaction{}
if err := o.Remove(rootPublicID, tr1); err != nil {
t.Errorf("Unexpected error: %s", err)
}
if ok, err := o.Exists(rootPublicID, tr1); ok && err != nil {
t.Errorf("Should not exist")
}
if v, err := o.Get(rootPublicID, tr1); v != nil || err == nil {
t.Errorf("Object should exist: %v", v)
}
// The removal is isolated.
tr2 := &Transaction{}
if ok, err := o.Exists(rootPublicID, tr2); !ok || err != nil {
t.Errorf("Should exist")
}
if _, err := o.Get(rootPublicID, tr2); err != nil {
t.Errorf("Object should exist: %s", err)
}
// Apply tr1.
if err := tr1.Commit(); err != nil {
t.Errorf("Unexpected error: %s", err)
}
if ok, err := o.Exists(rootPublicID, tr1); ok && err == nil {
t.Errorf("Should not exist")
}
if v, err := o.Get(rootPublicID, tr1); v != nil || err == nil {
t.Errorf("Object should exist: %v", v)
}
// The removal is isolated.
if ok, err := o.Exists(rootPublicID, tr2); !ok || err != nil {
t.Errorf("Should exist: %s", err)
}
if _, err := o.Get(rootPublicID, tr2); err != nil {
t.Errorf("Object should exist: %s", err)
}
}
{
// Check that the root object does not exist.
tr1 := &Transaction{}
if ok, err := o.Exists(rootPublicID, tr1); ok && err == nil {
t.Errorf("Should not exist")
}
if v, err := o.Get(rootPublicID, tr1); v != nil && err == nil {
t.Errorf("Should not exist: %v, %s", v, err)
}
}
}
func TestConcurrentOK(t *testing.T) {
st, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("New() failed: %v", err)
}
{
tr := NewTransaction()
mkdir(t, st, tr, "/")
mkdir(t, st, tr, "/Entries/a")
mkdir(t, st, tr, "/Entries/b")
commit(t, tr)
}
o1 := st.Bind("/Entries/a/Entries/c")
tr1 := &Transaction{}
v1 := newValue()
s1, err := o1.Put(rootPublicID, tr1, v1)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
o2 := st.Bind("/Entries/b/Entries/d")
tr2 := &Transaction{}
v2 := newValue()
s2, err := o2.Put(rootPublicID, tr2, v2)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
if err := tr1.Commit(); err != nil {
t.Errorf("Unexpected error: %s", err)
}
if err := tr2.Commit(); err != nil {
t.Errorf("Unexpected error: %s", err)
}
tr3 := &Transaction{}
if x, err := o1.Get(rootPublicID, tr3); err != nil || x.Stat.ID != s1.ID {
t.Errorf("Value should exist: %v, %s", x, err)
}
if x, err := o2.Get(rootPublicID, tr3); err != nil || x.Stat.ID != s2.ID {
t.Errorf("Value should exist: %v, %s", x, err)
}
}
func TestQuery(t *testing.T) {
st, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("New() failed: %v", err)
}
{
tr := NewTransaction()
mkdir(t, st, tr, "/")
mkdir(t, st, tr, "/Entries/a")
mkdir(t, st, tr, "/Entries/b")
commit(t, tr)
}
o1 := st.Bind("/Entries")
tr1 := &Transaction{}
stream, err := o1.Query(rootPublicID, tr1, query.Query{"*"})
results := map[string]bool{}
expected := []string{"", "a", "b"}
for stream.Next() {
results[stream.Get().Name] = true
}
if nresults, nexpected := len(results), len(expected); nresults != nexpected {
t.Errorf("Unexpected number of query results. Want %d, got %d. %v",
nexpected, nresults, results)
}
for _, expect := range expected {
if !results[expect] {
t.Errorf("Missing query result %s in %v.", expect, results)
}
}
}
func TestConcurrentConflict(t *testing.T) {
st, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("New() failed: %v", err)
}
{
tr := NewTransaction()
mkdir(t, st, tr, "/")
mkdir(t, st, tr, "/Entries/a")
commit(t, tr)
}
o := st.Bind("/Entries/a/Entries/c")
tr1 := &Transaction{}
v1 := newValue()
s1, err := o.Put(rootPublicID, tr1, v1)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
tr2 := &Transaction{}
v2 := newValue()
if _, err = o.Put(rootPublicID, tr2, v2); err != nil {
t.Errorf("Unexpected error: %s", err)
}
if err := tr1.Commit(); err != nil {
t.Errorf("Unexpected error: %s", err)
}
if err := tr2.Commit(); err == nil || err.Error() != "precondition failed" {
t.Errorf("Expected precondition failed, got %q", err)
}
tr3 := &Transaction{}
if x, err := o.Get(rootPublicID, tr3); err != nil || x.Stat.ID != s1.ID {
t.Errorf("Value should exist: %v, %s", x, err)
}
}
type Foo struct{}
func newFoo() *Foo {
return &Foo{}
}
func getFoo(t *testing.T, st *Store, tr *Transaction, path string) *Foo {
_, file, line, _ := runtime.Caller(1)
v := get(t, st, tr, path)
res, ok := v.(*Foo)
if !ok {
t.Fatalf("%s(%d): %s: not a Foo: %v", file, line, path, v)
}
return res
}
func TestSimpleMove(t *testing.T) {
st, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("New() failed: %v", err)
}
// Create / and /x.
{
tr := &Transaction{}
put(t, st, tr, "/", newFoo())
put(t, st, tr, "/x", newFoo())
commit(t, tr)
}
// Move /x to /y.
{
tr := &Transaction{}
x := getFoo(t, st, tr, "/x")
remove(t, st, tr, "/x")
put(t, st, tr, "/y", x)
commit(t, tr)
}
}
// Test a path conflict where some directory along the path to a value has been
// mutated concurrently.
func TestPathConflict(t *testing.T) {
st, err := New(rootPublicID, "")
if err != nil {
t.Fatalf("New() failed: %v", err)
}
{
tr := &Transaction{}
put(t, st, tr, "/", newFoo())
put(t, st, tr, "/a", newFoo())
put(t, st, tr, "/a/b", newFoo())
put(t, st, tr, "/a/b/c", newFoo())
commit(t, tr)
}
// Add a new value.
tr1 := &Transaction{}
put(t, st, tr1, "/a/b/c/d", newFoo())
// Change a directory along the path.
tr2 := &Transaction{}
put(t, st, tr2, "/a/b", newFoo())
commit(t, tr2)
// First Transaction should abort.
if err := tr1.Commit(); err == nil {
t.Errorf("Expected transaction to abort")
}
}