Merge "ref/runtime/rpc: Implement the real constructor functinos for xserver."
diff --git a/services/syncbase/server/nosql/database.go b/services/syncbase/server/nosql/database.go
index 172eca5..1755076 100644
--- a/services/syncbase/server/nosql/database.go
+++ b/services/syncbase/server/nosql/database.go
@@ -17,8 +17,8 @@
"v.io/v23/rpc"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase/nosql"
- "v.io/v23/syncbase/nosql/query_db"
- "v.io/v23/syncbase/nosql/query_exec"
+ "v.io/v23/syncbase/nosql/query"
+ "v.io/v23/syncbase/nosql/query/exec"
"v.io/v23/vdl"
"v.io/v23/verror"
"v.io/v23/vom"
@@ -254,7 +254,7 @@
if err := d.checkSchemaVersion(ctx, schemaVersion); err != nil {
return err
}
- impl := func(headers []string, rs ResultStream, err error) error {
+ impl := func(headers []string, rs query.ResultStream, err error) error {
if err != nil {
return err
}
@@ -282,9 +282,8 @@
sntx = d.st.NewSnapshot()
defer sntx.Abort()
}
- // queryDb implements query_db.Database
- // which is needed by the query package's
- // Exec function.
+ // queryDb implements the query.Database interface, which is needed by the
+ // exec.Exec function.
db := &queryDb{
ctx: ctx,
call: call,
@@ -292,7 +291,7 @@
sntx: sntx,
}
- return impl(query_exec.Exec(db, q))
+ return impl(exec.Exec(db, q))
}
func (d *databaseReq) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
@@ -336,36 +335,6 @@
}
////////////////////////////////////////
-// ResultStream interface
-
-// ResultStream is an interface for iterating through results (a.k.a, rows) returned from a
-// query. Each resulting rows are arrays of vdl objects.
-type ResultStream interface {
- // Advance stages an element so the client can retrieve it with Result.
- // Advance returns true iff there is a result to retrieve. The client must
- // call Advance before calling Result. The client must call Cancel if it
- // does not iterate through all elements (i.e. until Advance returns false).
- // Advance may block if an element is not immediately available.
- Advance() bool
-
- // Result returns the row (i.e., array of vdl Values) that was staged by Advance.
- // Result may panic if Advance returned false or was not called at all.
- // Result does not block.
- Result() []*vdl.Value
-
- // Err returns a non-nil error iff the stream encountered any errors. Err does
- // not block.
- Err() error
-
- // Cancel notifies the ResultStream provider that it can stop producing results.
- // The client must call Cancel if it does not iterate through all results
- // (i.e. until Advance returns false). Cancel is idempotent and can be called
- // concurrently with a goroutine that is iterating via Advance/Result.
- // Cancel causes Advance to subsequently return false. Cancel does not block.
- Cancel()
-}
-
-////////////////////////////////////////
// interfaces.Database methods
func (d *database) St() store.Store {
@@ -408,9 +377,9 @@
}
////////////////////////////////////////
-// query_db implementation
+// query interface implementations
-// Implement query_db's Database, Table and KeyValueStream interfaces.
+// queryDb implements query.Database.
type queryDb struct {
ctx *context.T
call wire.DatabaseExecServerCall
@@ -422,7 +391,7 @@
return db.ctx
}
-func (db *queryDb) GetTable(name string) (query_db.Table, error) {
+func (db *queryDb) GetTable(name string) (query.Table, error) {
tDb := &tableDb{
qdb: db,
req: &tableReq{
@@ -437,16 +406,18 @@
return tDb, nil
}
+// tableDb implements query.Table.
type tableDb struct {
qdb *queryDb
req *tableReq
}
-func (t *tableDb) Scan(keyRanges query_db.KeyRanges) (query_db.KeyValueStream, error) {
+func (t *tableDb) Scan(keyRanges query.KeyRanges) (query.KeyValueStream, error) {
streams := []store.Stream{}
for _, keyRange := range keyRanges {
- // TODO(jkline): For now, acquire all of the streams at once to minimize the race condition.
- // Need a way to Scan multiple ranges at the same state of uncommitted changes.
+ // TODO(jkline): For now, acquire all of the streams at once to minimize the
+ // race condition. Need a way to Scan multiple ranges at the same state of
+ // uncommitted changes.
streams = append(streams, t.qdb.sntx.Scan(util.ScanRangeArgs(util.JoinKeyParts(util.RowPrefix, t.req.name), keyRange.Start, keyRange.Limit)))
}
return &kvs{
@@ -458,6 +429,7 @@
}, nil
}
+// kvs implements query.KeyValueStream.
type kvs struct {
t *tableDb
curr int
diff --git a/services/syncbase/server/server_test.go b/services/syncbase/server/server_test.go
index 66c1f41..7aa6560 100644
--- a/services/syncbase/server/server_test.go
+++ b/services/syncbase/server/server_test.go
@@ -11,8 +11,8 @@
import (
"testing"
- tu "v.io/v23/syncbase/testutil"
_ "v.io/x/ref/runtime/factories/generic"
+ tu "v.io/x/ref/services/syncbase/testutil"
)
////////////////////////////////////////
diff --git a/services/syncbase/testutil/doc.go b/services/syncbase/testutil/doc.go
new file mode 100644
index 0000000..fe7013d
--- /dev/null
+++ b/services/syncbase/testutil/doc.go
@@ -0,0 +1,6 @@
+// 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 testutil defines helpers for Syncbase tests.
+package testutil
diff --git a/services/syncbase/testutil/layer.go b/services/syncbase/testutil/layer.go
new file mode 100644
index 0000000..55e49f1
--- /dev/null
+++ b/services/syncbase/testutil/layer.go
@@ -0,0 +1,447 @@
+// 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 testutil
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "v.io/v23/context"
+ "v.io/v23/security"
+ "v.io/v23/security/access"
+ "v.io/v23/syncbase"
+ "v.io/v23/syncbase/nosql"
+ "v.io/v23/syncbase/util"
+ "v.io/v23/verror"
+ "v.io/x/lib/vlog"
+ "v.io/x/ref/test/testutil"
+)
+
+// TestCreate tests that object creation works as expected.
+func TestCreate(t *testing.T, ctx *context.T, i interface{}) {
+ parent := makeLayer(i)
+ self := parent.Child("self")
+ child := self.Child("child")
+
+ // child.Create should fail since self does not exist.
+ if err := child.Create(ctx, nil); verror.ErrorID(err) != verror.ErrNoExist.ID {
+ t.Fatalf("child.Create() should have failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", false)
+ // TODO(ivanpi): Exists on child when parent does not exist currently fails
+ // with an error instead of returning false.
+ //assertExists(t, ctx, child, "child", false)
+
+ // Create self.
+ if err := self.Create(ctx, nil); err != nil {
+ t.Fatalf("self.Create() failed: %v", err)
+ }
+ if gotPerms, wantPerms := getPermsOrDie(t, ctx, self), DefaultPerms("root/client"); !reflect.DeepEqual(gotPerms, wantPerms) {
+ t.Errorf("Perms do not match: got %v, want %v", gotPerms, wantPerms)
+ }
+
+ assertExists(t, ctx, self, "self", true)
+ assertExists(t, ctx, child, "child", false)
+
+ // child.Create should now succeed.
+ if err := child.Create(ctx, nil); err != nil {
+ t.Fatalf("child.Create() failed: %v", err)
+ }
+
+ assertExists(t, ctx, child, "child", true)
+
+ // self.Create should fail since self already exists.
+ if err := self.Create(ctx, nil); verror.ErrorID(err) != verror.ErrExist.ID {
+ t.Fatalf("self.Create() should have failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", true)
+
+ // Test create with non-default perms.
+ self2 := parent.Child("self2")
+ perms := access.Permissions{}
+ perms.Add(security.BlessingPattern("root/client"), string(access.Admin))
+ if err := self2.Create(ctx, perms); err != nil {
+ t.Fatalf("self2.Create() failed: %v", err)
+ }
+ if gotPerms, wantPerms := getPermsOrDie(t, ctx, self2), perms; !reflect.DeepEqual(gotPerms, wantPerms) {
+ t.Errorf("Perms do not match: got %v, want %v", gotPerms, wantPerms)
+ }
+
+ // Even though self2 exists, Exists returns false because Read access is needed.
+ assertExists(t, ctx, self2, "self2", false)
+
+ // Test that create fails if the parent perms disallow access.
+ perms = DefaultPerms("root/client")
+ perms.Blacklist("root/client", string(access.Write))
+ if err := parent.SetPermissions(ctx, perms, ""); err != nil {
+ t.Fatalf("parent.SetPermissions() failed: %v", err)
+ }
+ self3 := parent.Child("self3")
+ if err := self3.Create(ctx, nil); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+ t.Fatalf("self3.Create() should have failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", true)
+ assertExists(t, ctx, self3, "self3", false)
+}
+
+// TestDelete tests that object deletion works as expected.
+func TestDelete(t *testing.T, ctx *context.T, i interface{}) {
+ parent := makeLayer(i)
+ self := parent.Child("self")
+ child := self.Child("child")
+
+ // Create self.
+ if err := self.Create(ctx, nil); err != nil {
+ t.Fatalf("self.Create() failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", true)
+
+ // self.Create should fail, since self already exists.
+ if err := self.Create(ctx, nil); verror.ErrorID(err) != verror.ErrExist.ID {
+ t.Fatalf("self.Create() should have failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", true)
+
+ // By default, self perms are copied from parent, so self.Delete should
+ // succeed.
+ if err := self.Delete(ctx); err != nil {
+ t.Fatalf("self.Delete() failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", false)
+
+ // child.Create should fail, since self does not exist.
+ if err := child.Create(ctx, nil); verror.ErrorID(err) != verror.ErrNoExist.ID {
+ t.Fatalf("child.Create() should have failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", false)
+ // TODO(ivanpi): Exists on child when parent does not exist currently fails
+ // with an error instead of returning false.
+ //assertExists(t, ctx, child, "child", false)
+
+ // self.Create should succeed, since self was deleted.
+ if err := self.Create(ctx, nil); err != nil {
+ t.Fatalf("self.Create() failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", true)
+ assertExists(t, ctx, child, "child", false)
+
+ // Test that delete fails if the perms disallow access.
+ self2 := parent.Child("self2")
+ if err := self2.Create(ctx, nil); err != nil {
+ t.Fatalf("self2.Create() failed: %v", err)
+ }
+ perms := DefaultPerms("root/client")
+ perms.Blacklist("root/client", string(access.Write))
+ if err := self2.SetPermissions(ctx, perms, ""); err != nil {
+ t.Fatalf("self2.SetPermissions() failed: %v", err)
+ }
+ if err := self2.Delete(ctx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+ t.Fatalf("self2.Delete() should have failed: %v", err)
+ }
+
+ assertExists(t, ctx, self2, "self2", true)
+
+ // Test that delete succeeds even if the parent perms disallow access.
+ perms = DefaultPerms("root/client")
+ perms.Blacklist("root/client", string(access.Write))
+ if err := parent.SetPermissions(ctx, perms, ""); err != nil {
+ t.Fatalf("parent.SetPermissions() failed: %v", err)
+ }
+ if err := self.Delete(ctx); err != nil {
+ t.Fatalf("self.Delete() failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", false)
+
+ // Test that delete is idempotent.
+ if err := self.Delete(ctx); err != nil {
+ t.Fatalf("self.Delete() failed: %v", err)
+ }
+
+ assertExists(t, ctx, self, "self", false)
+}
+
+func TestListChildren(t *testing.T, ctx *context.T, i interface{}) {
+ self := makeLayer(i)
+
+ var got, want []string
+ var err error
+
+ got, err = self.ListChildren(ctx)
+ want = []string{}
+ if err != nil {
+ t.Fatalf("self.ListChildren() failed: %v", err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("Lists do not match: got %v, want %v", got, want)
+ }
+
+ if err := self.Child("y").Create(ctx, nil); err != nil {
+ t.Fatalf("y.Create() failed: %v", err)
+ }
+ got, err = self.ListChildren(ctx)
+ want = []string{"y"}
+ if err != nil {
+ t.Fatalf("self.ListChildren() failed: %v", err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("Lists do not match: got %v, want %v", got, want)
+ }
+
+ if err := self.Child("x").Create(ctx, nil); err != nil {
+ t.Fatalf("x.Create() failed: %v", err)
+ }
+ got, err = self.ListChildren(ctx)
+ want = []string{"x", "y"}
+ if err != nil {
+ t.Fatalf("self.ListChildren() failed: %v", err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("Lists do not match: got %v, want %v", got, want)
+ }
+}
+
+// TestPerms tests that {Set,Get}Permissions work as expected.
+// TODO(sadovsky): All Vanadium {Set,Get}Permissions tests ought to share this
+// test implementation. :)
+// Mirrors v.io/groups/x/ref/services/groups/internal/server/server_test.go.
+func TestPerms(t *testing.T, ctx *context.T, ac util.AccessController) {
+ myperms := access.Permissions{}
+ myperms.Add(security.BlessingPattern("root/client"), string(access.Admin))
+ // Demonstrate that myperms differs from the current perms.
+ if reflect.DeepEqual(myperms, getPermsOrDie(t, ctx, ac)) {
+ t.Fatalf("Permissions should not match: %v", myperms)
+ }
+
+ var permsBefore, permsAfter access.Permissions
+ var versionBefore, versionAfter string
+
+ getPermsAndVersionOrDie := func() (access.Permissions, string) {
+ perms, version, err := ac.GetPermissions(ctx)
+ if err != nil {
+ // Use Fatalf rather than t.Fatalf so we get a stack trace.
+ Fatalf(t, "GetPermissions failed: %v", err)
+ }
+ return perms, version
+ }
+
+ // SetPermissions with bad version should fail.
+ permsBefore, versionBefore = getPermsAndVersionOrDie()
+ if err := ac.SetPermissions(ctx, myperms, "20"); verror.ErrorID(err) != verror.ErrBadVersion.ID {
+ t.Fatal("SetPermissions should have failed with version error")
+ }
+ // Since SetPermissions failed, perms and version should not have changed.
+ permsAfter, versionAfter = getPermsAndVersionOrDie()
+ if !reflect.DeepEqual(permsAfter, permsBefore) {
+ t.Errorf("Perms do not match: got %v, want %v", permsAfter, permsBefore)
+ }
+ if versionAfter != versionBefore {
+ t.Errorf("Versions do not match: got %v, want %v", versionAfter, versionBefore)
+ }
+
+ // SetPermissions with correct version should succeed.
+ permsBefore, versionBefore = permsAfter, versionAfter
+ if err := ac.SetPermissions(ctx, myperms, versionBefore); err != nil {
+ t.Fatalf("SetPermissions failed: %v", err)
+ }
+ // Check that perms and version actually changed.
+ permsAfter, versionAfter = getPermsAndVersionOrDie()
+ if !reflect.DeepEqual(permsAfter, myperms) {
+ t.Errorf("Perms do not match: got %v, want %v", permsAfter, myperms)
+ }
+ if versionBefore == versionAfter {
+ t.Errorf("Versions should not match: %v", versionBefore)
+ }
+
+ // SetPermissions with empty version should succeed.
+ permsBefore, versionBefore = permsAfter, versionAfter
+ myperms.Add(security.BlessingPattern("root/client"), string(access.Read))
+ if err := ac.SetPermissions(ctx, myperms, ""); err != nil {
+ t.Fatalf("SetPermissions failed: %v", err)
+ }
+ // Check that perms and version actually changed.
+ permsAfter, versionAfter = getPermsAndVersionOrDie()
+ if !reflect.DeepEqual(permsAfter, myperms) {
+ t.Errorf("Perms do not match: got %v, want %v", permsAfter, myperms)
+ }
+ if versionBefore == versionAfter {
+ t.Errorf("Versions should not match: %v", versionBefore)
+ }
+
+ // SetPermissions with unchanged perms should succeed, and version should
+ // still change.
+ permsBefore, versionBefore = permsAfter, versionAfter
+ if err := ac.SetPermissions(ctx, myperms, ""); err != nil {
+ t.Fatalf("SetPermissions failed: %v", err)
+ }
+ // Check that perms did not change and version did change.
+ permsAfter, versionAfter = getPermsAndVersionOrDie()
+ if !reflect.DeepEqual(permsAfter, permsBefore) {
+ t.Errorf("Perms do not match: got %v, want %v", permsAfter, permsBefore)
+ }
+ if versionBefore == versionAfter {
+ t.Errorf("Versions should not match: %v", versionBefore)
+ }
+
+ // Take away our access. SetPermissions and GetPermissions should fail.
+ if err := ac.SetPermissions(ctx, access.Permissions{}, ""); err != nil {
+ t.Fatalf("SetPermissions failed: %v", err)
+ }
+ if _, _, err := ac.GetPermissions(ctx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+ t.Fatal("GetPermissions should have failed with access error")
+ }
+ if err := ac.SetPermissions(ctx, myperms, ""); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+ t.Fatal("SetPermissions should have failed with access error")
+ }
+}
+
+////////////////////////////////////////
+// Internal helpers
+
+const notAvailable = "not available"
+
+type layer interface {
+ util.AccessController
+ Create(ctx *context.T, perms access.Permissions) error
+ // TODO(aghassemi): Rename to Destroy and drop Destroy impls below once
+ // Table.Delete is renamed to Destroy.
+ Delete(ctx *context.T) error
+ Exists(ctx *context.T) (bool, error)
+ ListChildren(ctx *context.T) ([]string, error)
+ Child(childName string) layer
+}
+
+type service struct {
+ syncbase.Service
+}
+
+func (s *service) Create(ctx *context.T, perms access.Permissions) error {
+ panic(notAvailable)
+}
+func (s *service) Delete(ctx *context.T) error {
+ panic(notAvailable)
+}
+func (s *service) Exists(ctx *context.T) (bool, error) {
+ panic(notAvailable)
+}
+func (s *service) ListChildren(ctx *context.T) ([]string, error) {
+ return s.ListApps(ctx)
+}
+func (s *service) Child(childName string) layer {
+ return makeLayer(s.App(childName))
+}
+
+type app struct {
+ syncbase.App
+}
+
+func (a *app) ListChildren(ctx *context.T) ([]string, error) {
+ return a.ListDatabases(ctx)
+}
+func (a *app) Child(childName string) layer {
+ return makeLayer(a.NoSQLDatabase(childName, nil))
+}
+func (a *app) Delete(ctx *context.T) error {
+ return a.Destroy(ctx)
+}
+
+type database struct {
+ nosql.Database
+}
+
+func (d *database) ListChildren(ctx *context.T) ([]string, error) {
+ return d.ListTables(ctx)
+}
+func (d *database) Child(childName string) layer {
+ return &table{Table: d.Table(childName), d: d}
+}
+func (d *database) Delete(ctx *context.T) error {
+ return d.Destroy(ctx)
+}
+
+type table struct {
+ nosql.Table
+ d nosql.Database
+}
+
+func (t *table) Create(ctx *context.T, perms access.Permissions) error {
+ return t.d.CreateTable(ctx, t.Name(), perms)
+}
+func (t *table) Delete(ctx *context.T) error {
+ return t.d.DeleteTable(ctx, t.Name())
+}
+func (t *table) SetPermissions(ctx *context.T, perms access.Permissions, version string) error {
+ return t.Table.SetPermissions(ctx, nosql.Prefix(""), perms)
+}
+func (t *table) GetPermissions(ctx *context.T) (perms access.Permissions, version string, err error) {
+ permsList, err := t.Table.GetPermissions(ctx, "")
+ if len(permsList) != 1 || permsList[0].Prefix.Prefix() != "" {
+ panic(fmt.Sprintf("unexpected perms list: %v", permsList))
+ }
+ return permsList[0].Perms, "", nil
+}
+func (t *table) ListChildren(ctx *context.T) ([]string, error) {
+ panic(notAvailable)
+}
+func (t *table) Child(childName string) layer {
+ return &row{t.Row(childName)}
+}
+
+type row struct {
+ nosql.Row
+}
+
+func (r *row) Create(ctx *context.T, perms access.Permissions) error {
+ if perms != nil {
+ panic(fmt.Sprintf("bad perms: %v", perms))
+ }
+ return r.Put(ctx, true)
+}
+func (r *row) Delete(ctx *context.T) error {
+ return r.Delete(ctx)
+}
+func (r *row) SetPermissions(ctx *context.T, perms access.Permissions, version string) error {
+ panic(notAvailable)
+}
+func (r *row) GetPermissions(ctx *context.T) (perms access.Permissions, version string, err error) {
+ panic(notAvailable)
+}
+func (r *row) ListChildren(ctx *context.T) ([]string, error) {
+ panic(notAvailable)
+}
+func (r *row) Child(childName string) layer {
+ panic(notAvailable)
+}
+
+func makeLayer(i interface{}) layer {
+ switch t := i.(type) {
+ case syncbase.Service:
+ return &service{t}
+ case syncbase.App:
+ return &app{t}
+ case nosql.Database:
+ return &database{t}
+ default:
+ vlog.Fatalf("unexpected type: %T", t)
+ }
+ return nil
+}
+
+func assertExists(t *testing.T, ctx *context.T, l layer, name string, want bool) {
+ if got, err := l.Exists(ctx); err != nil {
+ t.Fatal(testutil.FormatLogLine(2, "%s.Exists() failed: %v", name, err))
+ } else if got != want {
+ t.Error(testutil.FormatLogLine(2, "%s.Exists() got %v, want %v", name, got, want))
+ }
+}
diff --git a/services/syncbase/testutil/util.go b/services/syncbase/testutil/util.go
new file mode 100644
index 0000000..658794e
--- /dev/null
+++ b/services/syncbase/testutil/util.go
@@ -0,0 +1,291 @@
+// 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 testutil
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "reflect"
+ "runtime/debug"
+ "testing"
+
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/security"
+ "v.io/v23/security/access"
+ wire "v.io/v23/services/syncbase/nosql"
+ "v.io/v23/syncbase"
+ "v.io/v23/syncbase/nosql"
+ "v.io/v23/syncbase/util"
+ "v.io/v23/vdl"
+ "v.io/v23/verror"
+ "v.io/x/lib/vlog"
+ "v.io/x/ref/lib/flags"
+ "v.io/x/ref/lib/xrpc"
+ "v.io/x/ref/services/syncbase/server"
+ tsecurity "v.io/x/ref/test/testutil"
+)
+
+func Fatal(t *testing.T, args ...interface{}) {
+ debug.PrintStack()
+ t.Fatal(args...)
+}
+
+func Fatalf(t *testing.T, format string, args ...interface{}) {
+ debug.PrintStack()
+ t.Fatalf(format, args...)
+}
+
+func CreateApp(t *testing.T, ctx *context.T, s syncbase.Service, name string) syncbase.App {
+ a := s.App(name)
+ if err := a.Create(ctx, nil); err != nil {
+ Fatalf(t, "a.Create() failed: %v", err)
+ }
+ return a
+}
+
+func CreateNoSQLDatabase(t *testing.T, ctx *context.T, a syncbase.App, name string) nosql.Database {
+ d := a.NoSQLDatabase(name, nil)
+ if err := d.Create(ctx, nil); err != nil {
+ Fatalf(t, "d.Create() failed: %v", err)
+ }
+ return d
+}
+
+func CreateTable(t *testing.T, ctx *context.T, d nosql.Database, name string) nosql.Table {
+ if err := d.CreateTable(ctx, name, nil); err != nil {
+ Fatalf(t, "d.CreateTable() failed: %v", err)
+ }
+ return d.Table(name)
+}
+
+// TODO(sadovsky): Drop the 'perms' argument. The only client that passes
+// non-nil, syncgroup_test.go, should use SetupOrDieCustom instead.
+func SetupOrDie(perms access.Permissions) (clientCtx *context.T, serverName string, cleanup func()) {
+ _, clientCtx, serverName, _, cleanup = SetupOrDieCustom("client", "server", perms)
+ return
+}
+
+func SetupOrDieCustom(clientSuffix, serverSuffix string, perms access.Permissions) (ctx, clientCtx *context.T, serverName string, rootp security.Principal, cleanup func()) {
+ // TODO(mattr): Instead of SetDefaultHostPort the arguably more correct thing
+ // would be to call v.io/x/ref/test.Init() from the test packages that import
+ // the profile. Note you should only call that from the package that imports
+ // the profile, not from libraries like this. Also, it would be better if
+ // v23.Init was test.V23Init().
+ flags.SetDefaultHostPort("127.0.0.1:0")
+ ctx, shutdown := v23.Init()
+
+ rootp = tsecurity.NewPrincipal("root")
+ clientCtx, serverCtx := NewCtx(ctx, rootp, clientSuffix), NewCtx(ctx, rootp, serverSuffix)
+
+ if perms == nil {
+ perms = DefaultPerms(fmt.Sprintf("%s/%s", "root", clientSuffix))
+ }
+ serverName, stopServer := newServer(serverCtx, perms)
+ cleanup = func() {
+ stopServer()
+ shutdown()
+ }
+ return
+}
+
+func DefaultPerms(patterns ...string) access.Permissions {
+ perms := access.Permissions{}
+ for _, tag := range access.AllTypicalTags() {
+ for _, pattern := range patterns {
+ perms.Add(security.BlessingPattern(pattern), string(tag))
+ }
+ }
+ return perms
+}
+
+func ScanMatches(ctx *context.T, tb nosql.Table, r nosql.RowRange, wantKeys []string, wantValues []interface{}) error {
+ if len(wantKeys) != len(wantValues) {
+ return fmt.Errorf("bad input args")
+ }
+ it := tb.Scan(ctx, r)
+ gotKeys := []string{}
+ for it.Advance() {
+ gotKey := it.Key()
+ gotKeys = append(gotKeys, gotKey)
+ i := len(gotKeys) - 1
+ if i >= len(wantKeys) {
+ continue
+ }
+ // Check key.
+ wantKey := wantKeys[i]
+ if gotKey != wantKey {
+ return fmt.Errorf("Keys do not match: got %q, want %q", gotKey, wantKey)
+ }
+ // Check value.
+ wantValue := wantValues[i]
+ gotValue := reflect.Zero(reflect.TypeOf(wantValue)).Interface()
+ if err := it.Value(&gotValue); err != nil {
+ return fmt.Errorf("it.Value() failed: %v", err)
+ }
+ if !reflect.DeepEqual(gotValue, wantValue) {
+ return fmt.Errorf("Values do not match: got %v, want %v", gotValue, wantValue)
+ }
+ }
+ if err := it.Err(); err != nil {
+ return fmt.Errorf("tb.Scan() failed: %v", err)
+ }
+ if len(gotKeys) != len(wantKeys) {
+ return fmt.Errorf("Unmatched keys: got %v, want %v", gotKeys, wantKeys)
+ }
+ return nil
+}
+
+func CheckScan(t *testing.T, ctx *context.T, tb nosql.Table, r nosql.RowRange, wantKeys []string, wantValues []interface{}) {
+ if err := ScanMatches(ctx, tb, r, wantKeys, wantValues); err != nil {
+ Fatalf(t, err.Error())
+ }
+}
+
+func CheckExec(t *testing.T, ctx *context.T, db nosql.DatabaseHandle, q string, wantHeaders []string, wantResults [][]*vdl.Value) {
+ gotHeaders, it, err := db.Exec(ctx, q)
+ if err != nil {
+ t.Errorf("query %q: got %v, want nil", q, err)
+ }
+ if !reflect.DeepEqual(gotHeaders, wantHeaders) {
+ t.Errorf("query %q: got %v, want %v", q, gotHeaders, wantHeaders)
+ }
+ gotResults := [][]*vdl.Value{}
+ for it.Advance() {
+ gotResult := it.Result()
+ gotResults = append(gotResults, gotResult)
+ }
+ if it.Err() != nil {
+ t.Errorf("query %q: got %v, want nil", q, it.Err())
+ }
+ if !reflect.DeepEqual(gotResults, wantResults) {
+ t.Errorf("query %q: got %v, want %v", q, gotResults, wantResults)
+ }
+}
+
+func CheckExecError(t *testing.T, ctx *context.T, db nosql.DatabaseHandle, q string, wantErrorID verror.ID) {
+ _, rs, err := db.Exec(ctx, q)
+ if err == nil {
+ if rs.Advance() {
+ t.Errorf("query %q: got true, want false", q)
+ }
+ err = rs.Err()
+ }
+ if verror.ErrorID(err) != wantErrorID {
+ t.Errorf("%q", verror.DebugString(err))
+ t.Errorf("query %q: got %v, want: %v", q, verror.ErrorID(err), wantErrorID)
+ }
+}
+
+// CheckWatch checks that the sequence of elements from the watch stream starts
+// with the given slice of watch changes.
+func CheckWatch(t *testing.T, wstream nosql.WatchStream, changes []nosql.WatchChange) {
+ for _, want := range changes {
+ if !wstream.Advance() {
+ Fatalf(t, "wstream.Advance() reached the end: %v", wstream.Err())
+ }
+ if got := wstream.Change(); !reflect.DeepEqual(got, want) {
+ Fatalf(t, "unexpected watch change: got %v, want %v", got, want)
+ }
+ }
+}
+
+type MockSchemaUpgrader struct {
+ CallCount int
+}
+
+func (msu *MockSchemaUpgrader) Run(db nosql.Database, oldVersion, newVersion int32) error {
+ msu.CallCount++
+ return nil
+}
+
+var _ nosql.SchemaUpgrader = (*MockSchemaUpgrader)(nil)
+
+func DefaultSchema(version int32) *nosql.Schema {
+ return &nosql.Schema{
+ Metadata: wire.SchemaMetadata{
+ Version: version,
+ },
+ Upgrader: nosql.SchemaUpgrader(&MockSchemaUpgrader{}),
+ }
+}
+
+////////////////////////////////////////
+// Internal helpers
+
+func getPermsOrDie(t *testing.T, ctx *context.T, ac util.AccessController) access.Permissions {
+ perms, _, err := ac.GetPermissions(ctx)
+ if err != nil {
+ Fatalf(t, "GetPermissions failed: %v", err)
+ }
+ return perms
+}
+
+func newServer(serverCtx *context.T, perms access.Permissions) (string, func()) {
+ if perms == nil {
+ vlog.Fatal("perms must be specified")
+ }
+ rootDir, err := ioutil.TempDir("", "syncbase")
+ if err != nil {
+ vlog.Fatal("ioutil.TempDir() failed: ", err)
+ }
+ service, err := server.NewService(serverCtx, nil, server.ServiceOptions{
+ Perms: perms,
+ RootDir: rootDir,
+ Engine: "leveldb",
+ })
+ if err != nil {
+ vlog.Fatal("server.NewService() failed: ", err)
+ }
+ s, err := xrpc.NewDispatchingServer(serverCtx, "", server.NewDispatcher(service))
+ if err != nil {
+ vlog.Fatal("xrpc.NewDispatchingServer() failed: ", err)
+ }
+ name := s.Status().Endpoints[0].Name()
+ return name, func() {
+ s.Stop()
+ os.RemoveAll(rootDir)
+ }
+}
+
+// Creates a new context object with blessing "root/<suffix>", configured to
+// present this blessing when acting as a server as well as when acting as a
+// client and talking to a server that presents a blessing rooted at "root".
+func NewCtx(ctx *context.T, rootp security.Principal, suffix string) *context.T {
+ // Principal for the new context.
+ p := tsecurity.NewPrincipal(suffix)
+
+ // Bless the new principal as "root/<suffix>".
+ blessings, err := rootp.Bless(p.PublicKey(), rootp.BlessingStore().Default(), suffix, security.UnconstrainedUse())
+ if err != nil {
+ vlog.Fatal("rootp.Bless() failed: ", err)
+ }
+
+ // Make it so users of the new context present their "root/<suffix>" blessing
+ // when talking to servers with blessings rooted at "root".
+ if _, err := p.BlessingStore().Set(blessings, security.BlessingPattern("root")); err != nil {
+ vlog.Fatal("p.BlessingStore().Set() failed: ", err)
+ }
+
+ // Make it so that when users of the new context act as a server, they present
+ // their "root/<suffix>" blessing.
+ if err := p.BlessingStore().SetDefault(blessings); err != nil {
+ vlog.Fatal("p.BlessingStore().SetDefault() failed: ", err)
+ }
+
+ // Have users of the prepared context treat root's public key as an authority
+ // on all blessings rooted at "root".
+ if err := p.AddToRoots(blessings); err != nil {
+ vlog.Fatal("p.AddToRoots() failed: ", err)
+ }
+
+ resCtx, err := v23.WithPrincipal(ctx, p)
+ if err != nil {
+ vlog.Fatal("v23.WithPrincipal() failed: ", err)
+ }
+
+ return resCtx
+}
diff --git a/services/syncbase/testutil/v23util.go b/services/syncbase/testutil/v23util.go
new file mode 100644
index 0000000..ef2c3f6
--- /dev/null
+++ b/services/syncbase/testutil/v23util.go
@@ -0,0 +1,76 @@
+// 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 testutil
+
+import (
+ "bytes"
+ "io/ioutil"
+ "log"
+ "os"
+ "runtime/debug"
+ "syscall"
+
+ "v.io/x/ref/test/modules"
+ "v.io/x/ref/test/v23tests"
+)
+
+// StartSyncbased starts a syncbased process, intended to be accessed from an
+// integration test (run using --v23.tests). The returned cleanup function
+// should be called once the syncbased process is no longer needed.
+func StartSyncbased(t *v23tests.T, creds *modules.CustomCredentials, name, rootDir, permsLiteral string) (cleanup func()) {
+ syncbased := t.BuildV23Pkg("v.io/x/ref/services/syncbase/syncbased")
+ // Create root dir for the store.
+ rmRootDir := false
+ if rootDir == "" {
+ var err error
+ rootDir, err = ioutil.TempDir("", "syncbase_leveldb")
+ if err != nil {
+ V23Fatalf(t, "can't create temp dir: %v", err)
+ }
+ rmRootDir = true
+ }
+ // Start syncbased.
+ invocation := syncbased.WithStartOpts(syncbased.StartOpts().WithCustomCredentials(creds)).Start(
+ "--v23.tcp.address=127.0.0.1:0",
+ "--v23.permissions.literal", permsLiteral,
+ "--name="+name,
+ "--root-dir="+rootDir)
+ return func() {
+ // TODO(sadovsky): Something's broken here. If the syncbased invocation
+ // fails (e.g. if NewService returns an error), currently it's possible for
+ // the test to fail without the crash error getting logged. This makes
+ // debugging a challenge.
+ go invocation.Kill(syscall.SIGINT)
+ stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
+ if err := invocation.Shutdown(stdout, stderr); err != nil {
+ log.Printf("syncbased terminated with an error: %v\nstdout: %v\nstderr: %v\n", err, stdout, stderr)
+ }
+ if rmRootDir {
+ if err := os.RemoveAll(rootDir); err != nil {
+ V23Fatalf(t, "can't remove dir %v: %v", rootDir, err)
+ }
+ }
+ }
+}
+
+// RunClient runs modules.Program and waits until it terminates.
+func RunClient(t *v23tests.T, creds *modules.CustomCredentials, program modules.Program, args ...string) {
+ client, err := t.Shell().StartWithOpts(
+ t.Shell().DefaultStartOpts().WithCustomCredentials(creds),
+ nil,
+ program, args...)
+ if err != nil {
+ V23Fatalf(t, "unable to start the client: %v", err)
+ }
+ stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
+ if err := client.Shutdown(stdout, stderr); err != nil {
+ V23Fatalf(t, "client failed: %v\nstdout: %v\nstderr: %v\n", err, stdout, stderr)
+ }
+}
+
+func V23Fatalf(t *v23tests.T, format string, args ...interface{}) {
+ debug.PrintStack()
+ t.Fatalf(format, args...)
+}
diff --git a/services/syncbase/vsync/sync_state_test.go b/services/syncbase/vsync/sync_state_test.go
index 01315a7..b621fdc 100644
--- a/services/syncbase/vsync/sync_state_test.go
+++ b/services/syncbase/vsync/sync_state_test.go
@@ -141,7 +141,7 @@
//////////////////////////////
// Helpers
-// TODO(hpucha): Look into using v.io/v23/syncbase/testutil.Fatalf()
+// TODO(hpucha): Look into using v.io/x/ref/services/syncbase/testutil.Fatalf()
// for getting the stack trace. Right now cannot import the package due to a
// cycle.