blob: 7c40dba2daa7f5775c0ab04ca2cff0d5c02a2319 [file] [log] [blame]
Adam Sadovsky6a6214f2015-09-03 18:20:18 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
Adam Sadovsky1b055e42015-09-10 17:45:34 -07003// license that can be found in the LICENSE file.
Adam Sadovsky6a6214f2015-09-03 18:20:18 -07004
5package testutil
6
7import (
8 "fmt"
9 "io/ioutil"
10 "os"
11 "reflect"
12 "runtime/debug"
13 "testing"
14
15 "v.io/v23"
16 "v.io/v23/context"
17 "v.io/v23/security"
18 "v.io/v23/security/access"
19 wire "v.io/v23/services/syncbase/nosql"
20 "v.io/v23/syncbase"
21 "v.io/v23/syncbase/nosql"
22 "v.io/v23/syncbase/util"
23 "v.io/v23/vdl"
24 "v.io/v23/verror"
25 "v.io/x/lib/vlog"
26 "v.io/x/ref/lib/flags"
Adam Sadovsky6a6214f2015-09-03 18:20:18 -070027 "v.io/x/ref/services/syncbase/server"
Sergey Rogulenko2ddb9802015-09-29 11:59:33 -070028 "v.io/x/ref/services/syncbase/store"
Adam Sadovsky6a6214f2015-09-03 18:20:18 -070029 tsecurity "v.io/x/ref/test/testutil"
30)
31
32func Fatal(t *testing.T, args ...interface{}) {
33 debug.PrintStack()
34 t.Fatal(args...)
35}
36
37func Fatalf(t *testing.T, format string, args ...interface{}) {
38 debug.PrintStack()
39 t.Fatalf(format, args...)
40}
41
42func CreateApp(t *testing.T, ctx *context.T, s syncbase.Service, name string) syncbase.App {
43 a := s.App(name)
44 if err := a.Create(ctx, nil); err != nil {
45 Fatalf(t, "a.Create() failed: %v", err)
46 }
47 return a
48}
49
50func CreateNoSQLDatabase(t *testing.T, ctx *context.T, a syncbase.App, name string) nosql.Database {
51 d := a.NoSQLDatabase(name, nil)
52 if err := d.Create(ctx, nil); err != nil {
53 Fatalf(t, "d.Create() failed: %v", err)
54 }
55 return d
56}
57
58func CreateTable(t *testing.T, ctx *context.T, d nosql.Database, name string) nosql.Table {
Adam Sadovsky7e6bc0c2015-09-09 10:07:57 -070059 tb := d.Table(name)
60 if err := tb.Create(ctx, nil); err != nil {
61 Fatalf(t, "tb.Create() failed: %v", err)
Adam Sadovsky6a6214f2015-09-03 18:20:18 -070062 }
Adam Sadovsky7e6bc0c2015-09-09 10:07:57 -070063 return tb
Adam Sadovsky6a6214f2015-09-03 18:20:18 -070064}
65
66// TODO(sadovsky): Drop the 'perms' argument. The only client that passes
67// non-nil, syncgroup_test.go, should use SetupOrDieCustom instead.
68func SetupOrDie(perms access.Permissions) (clientCtx *context.T, serverName string, cleanup func()) {
69 _, clientCtx, serverName, _, cleanup = SetupOrDieCustom("client", "server", perms)
70 return
71}
72
73func SetupOrDieCustom(clientSuffix, serverSuffix string, perms access.Permissions) (ctx, clientCtx *context.T, serverName string, rootp security.Principal, cleanup func()) {
74 // TODO(mattr): Instead of SetDefaultHostPort the arguably more correct thing
75 // would be to call v.io/x/ref/test.Init() from the test packages that import
76 // the profile. Note you should only call that from the package that imports
77 // the profile, not from libraries like this. Also, it would be better if
78 // v23.Init was test.V23Init().
79 flags.SetDefaultHostPort("127.0.0.1:0")
80 ctx, shutdown := v23.Init()
81
82 rootp = tsecurity.NewPrincipal("root")
83 clientCtx, serverCtx := NewCtx(ctx, rootp, clientSuffix), NewCtx(ctx, rootp, serverSuffix)
84
85 if perms == nil {
86 perms = DefaultPerms(fmt.Sprintf("%s/%s", "root", clientSuffix))
87 }
88 serverName, stopServer := newServer(serverCtx, perms)
89 cleanup = func() {
90 stopServer()
91 shutdown()
92 }
93 return
94}
95
96func DefaultPerms(patterns ...string) access.Permissions {
97 perms := access.Permissions{}
98 for _, tag := range access.AllTypicalTags() {
99 for _, pattern := range patterns {
100 perms.Add(security.BlessingPattern(pattern), string(tag))
101 }
102 }
103 return perms
104}
105
106func ScanMatches(ctx *context.T, tb nosql.Table, r nosql.RowRange, wantKeys []string, wantValues []interface{}) error {
107 if len(wantKeys) != len(wantValues) {
108 return fmt.Errorf("bad input args")
109 }
110 it := tb.Scan(ctx, r)
111 gotKeys := []string{}
112 for it.Advance() {
113 gotKey := it.Key()
114 gotKeys = append(gotKeys, gotKey)
115 i := len(gotKeys) - 1
116 if i >= len(wantKeys) {
117 continue
118 }
119 // Check key.
120 wantKey := wantKeys[i]
121 if gotKey != wantKey {
122 return fmt.Errorf("Keys do not match: got %q, want %q", gotKey, wantKey)
123 }
124 // Check value.
125 wantValue := wantValues[i]
126 gotValue := reflect.Zero(reflect.TypeOf(wantValue)).Interface()
127 if err := it.Value(&gotValue); err != nil {
128 return fmt.Errorf("it.Value() failed: %v", err)
129 }
130 if !reflect.DeepEqual(gotValue, wantValue) {
131 return fmt.Errorf("Values do not match: got %v, want %v", gotValue, wantValue)
132 }
133 }
134 if err := it.Err(); err != nil {
135 return fmt.Errorf("tb.Scan() failed: %v", err)
136 }
137 if len(gotKeys) != len(wantKeys) {
138 return fmt.Errorf("Unmatched keys: got %v, want %v", gotKeys, wantKeys)
139 }
140 return nil
141}
142
143func CheckScan(t *testing.T, ctx *context.T, tb nosql.Table, r nosql.RowRange, wantKeys []string, wantValues []interface{}) {
144 if err := ScanMatches(ctx, tb, r, wantKeys, wantValues); err != nil {
145 Fatalf(t, err.Error())
146 }
147}
148
149func CheckExec(t *testing.T, ctx *context.T, db nosql.DatabaseHandle, q string, wantHeaders []string, wantResults [][]*vdl.Value) {
150 gotHeaders, it, err := db.Exec(ctx, q)
151 if err != nil {
152 t.Errorf("query %q: got %v, want nil", q, err)
153 }
154 if !reflect.DeepEqual(gotHeaders, wantHeaders) {
155 t.Errorf("query %q: got %v, want %v", q, gotHeaders, wantHeaders)
156 }
157 gotResults := [][]*vdl.Value{}
158 for it.Advance() {
159 gotResult := it.Result()
160 gotResults = append(gotResults, gotResult)
161 }
162 if it.Err() != nil {
163 t.Errorf("query %q: got %v, want nil", q, it.Err())
164 }
165 if !reflect.DeepEqual(gotResults, wantResults) {
166 t.Errorf("query %q: got %v, want %v", q, gotResults, wantResults)
167 }
168}
169
170func CheckExecError(t *testing.T, ctx *context.T, db nosql.DatabaseHandle, q string, wantErrorID verror.ID) {
171 _, rs, err := db.Exec(ctx, q)
172 if err == nil {
173 if rs.Advance() {
174 t.Errorf("query %q: got true, want false", q)
175 }
176 err = rs.Err()
177 }
178 if verror.ErrorID(err) != wantErrorID {
179 t.Errorf("%q", verror.DebugString(err))
180 t.Errorf("query %q: got %v, want: %v", q, verror.ErrorID(err), wantErrorID)
181 }
182}
183
184// CheckWatch checks that the sequence of elements from the watch stream starts
185// with the given slice of watch changes.
186func CheckWatch(t *testing.T, wstream nosql.WatchStream, changes []nosql.WatchChange) {
187 for _, want := range changes {
188 if !wstream.Advance() {
189 Fatalf(t, "wstream.Advance() reached the end: %v", wstream.Err())
190 }
191 if got := wstream.Change(); !reflect.DeepEqual(got, want) {
192 Fatalf(t, "unexpected watch change: got %v, want %v", got, want)
193 }
194 }
195}
196
197type MockSchemaUpgrader struct {
198 CallCount int
199}
200
201func (msu *MockSchemaUpgrader) Run(db nosql.Database, oldVersion, newVersion int32) error {
202 msu.CallCount++
203 return nil
204}
205
206var _ nosql.SchemaUpgrader = (*MockSchemaUpgrader)(nil)
207
208func DefaultSchema(version int32) *nosql.Schema {
209 return &nosql.Schema{
210 Metadata: wire.SchemaMetadata{
211 Version: version,
212 },
213 Upgrader: nosql.SchemaUpgrader(&MockSchemaUpgrader{}),
214 }
215}
216
217////////////////////////////////////////
218// Internal helpers
219
220func getPermsOrDie(t *testing.T, ctx *context.T, ac util.AccessController) access.Permissions {
221 perms, _, err := ac.GetPermissions(ctx)
222 if err != nil {
223 Fatalf(t, "GetPermissions failed: %v", err)
224 }
225 return perms
226}
227
228func newServer(serverCtx *context.T, perms access.Permissions) (string, func()) {
229 if perms == nil {
230 vlog.Fatal("perms must be specified")
231 }
232 rootDir, err := ioutil.TempDir("", "syncbase")
233 if err != nil {
234 vlog.Fatal("ioutil.TempDir() failed: ", err)
235 }
Matt Rosencrantz9bf68632015-09-30 17:13:46 -0700236 serverCtx, cancel := context.WithCancel(serverCtx)
Adam Sadovsky6a6214f2015-09-03 18:20:18 -0700237 service, err := server.NewService(serverCtx, nil, server.ServiceOptions{
238 Perms: perms,
239 RootDir: rootDir,
Sergey Rogulenko2ddb9802015-09-29 11:59:33 -0700240 Engine: store.EngineForTest,
Adam Sadovsky6a6214f2015-09-03 18:20:18 -0700241 })
242 if err != nil {
243 vlog.Fatal("server.NewService() failed: ", err)
244 }
Matt Rosencrantz53ac5852015-09-04 15:14:54 -0700245 serverCtx, s, err := v23.WithNewDispatchingServer(serverCtx, "", server.NewDispatcher(service))
Adam Sadovsky6a6214f2015-09-03 18:20:18 -0700246 if err != nil {
Matt Rosencrantz53ac5852015-09-04 15:14:54 -0700247 vlog.Fatal("v23.WithNewDispatchingServer() failed: ", err)
Adam Sadovsky6a6214f2015-09-03 18:20:18 -0700248 }
249 name := s.Status().Endpoints[0].Name()
250 return name, func() {
Matt Rosencrantz9bf68632015-09-30 17:13:46 -0700251 cancel()
252 <-s.Closed()
Adam Sadovsky6a6214f2015-09-03 18:20:18 -0700253 os.RemoveAll(rootDir)
254 }
255}
256
257// Creates a new context object with blessing "root/<suffix>", configured to
258// present this blessing when acting as a server as well as when acting as a
259// client and talking to a server that presents a blessing rooted at "root".
260func NewCtx(ctx *context.T, rootp security.Principal, suffix string) *context.T {
261 // Principal for the new context.
262 p := tsecurity.NewPrincipal(suffix)
263
264 // Bless the new principal as "root/<suffix>".
265 blessings, err := rootp.Bless(p.PublicKey(), rootp.BlessingStore().Default(), suffix, security.UnconstrainedUse())
266 if err != nil {
267 vlog.Fatal("rootp.Bless() failed: ", err)
268 }
269
270 // Make it so users of the new context present their "root/<suffix>" blessing
271 // when talking to servers with blessings rooted at "root".
272 if _, err := p.BlessingStore().Set(blessings, security.BlessingPattern("root")); err != nil {
273 vlog.Fatal("p.BlessingStore().Set() failed: ", err)
274 }
275
276 // Make it so that when users of the new context act as a server, they present
277 // their "root/<suffix>" blessing.
278 if err := p.BlessingStore().SetDefault(blessings); err != nil {
279 vlog.Fatal("p.BlessingStore().SetDefault() failed: ", err)
280 }
281
282 // Have users of the prepared context treat root's public key as an authority
283 // on all blessings rooted at "root".
284 if err := p.AddToRoots(blessings); err != nil {
285 vlog.Fatal("p.AddToRoots() failed: ", err)
286 }
287
288 resCtx, err := v23.WithPrincipal(ctx, p)
289 if err != nil {
290 vlog.Fatal("v23.WithPrincipal() failed: ", err)
291 }
292
293 return resCtx
294}