Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
Adam Sadovsky | 1b055e4 | 2015-09-10 17:45:34 -0700 | [diff] [blame] | 3 | // license that can be found in the LICENSE file. |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 4 | |
| 5 | package testutil |
| 6 | |
| 7 | import ( |
| 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 Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 27 | "v.io/x/ref/services/syncbase/server" |
Sergey Rogulenko | 2ddb980 | 2015-09-29 11:59:33 -0700 | [diff] [blame] | 28 | "v.io/x/ref/services/syncbase/store" |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 29 | tsecurity "v.io/x/ref/test/testutil" |
| 30 | ) |
| 31 | |
| 32 | func Fatal(t *testing.T, args ...interface{}) { |
| 33 | debug.PrintStack() |
| 34 | t.Fatal(args...) |
| 35 | } |
| 36 | |
| 37 | func Fatalf(t *testing.T, format string, args ...interface{}) { |
| 38 | debug.PrintStack() |
| 39 | t.Fatalf(format, args...) |
| 40 | } |
| 41 | |
| 42 | func 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 | |
| 50 | func 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 | |
| 58 | func CreateTable(t *testing.T, ctx *context.T, d nosql.Database, name string) nosql.Table { |
Adam Sadovsky | 7e6bc0c | 2015-09-09 10:07:57 -0700 | [diff] [blame] | 59 | tb := d.Table(name) |
| 60 | if err := tb.Create(ctx, nil); err != nil { |
| 61 | Fatalf(t, "tb.Create() failed: %v", err) |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 62 | } |
Adam Sadovsky | 7e6bc0c | 2015-09-09 10:07:57 -0700 | [diff] [blame] | 63 | return tb |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 64 | } |
| 65 | |
| 66 | // TODO(sadovsky): Drop the 'perms' argument. The only client that passes |
| 67 | // non-nil, syncgroup_test.go, should use SetupOrDieCustom instead. |
| 68 | func SetupOrDie(perms access.Permissions) (clientCtx *context.T, serverName string, cleanup func()) { |
| 69 | _, clientCtx, serverName, _, cleanup = SetupOrDieCustom("client", "server", perms) |
| 70 | return |
| 71 | } |
| 72 | |
| 73 | func 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 | |
| 96 | func 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 | |
| 106 | func 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 | |
| 143 | func 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 | |
| 149 | func 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 | |
| 170 | func 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. |
| 186 | func 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 | |
| 197 | type MockSchemaUpgrader struct { |
| 198 | CallCount int |
| 199 | } |
| 200 | |
| 201 | func (msu *MockSchemaUpgrader) Run(db nosql.Database, oldVersion, newVersion int32) error { |
| 202 | msu.CallCount++ |
| 203 | return nil |
| 204 | } |
| 205 | |
| 206 | var _ nosql.SchemaUpgrader = (*MockSchemaUpgrader)(nil) |
| 207 | |
| 208 | func 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 | |
| 220 | func 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 | |
| 228 | func 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 Rosencrantz | 9bf6863 | 2015-09-30 17:13:46 -0700 | [diff] [blame^] | 236 | serverCtx, cancel := context.WithCancel(serverCtx) |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 237 | service, err := server.NewService(serverCtx, nil, server.ServiceOptions{ |
| 238 | Perms: perms, |
| 239 | RootDir: rootDir, |
Sergey Rogulenko | 2ddb980 | 2015-09-29 11:59:33 -0700 | [diff] [blame] | 240 | Engine: store.EngineForTest, |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 241 | }) |
| 242 | if err != nil { |
| 243 | vlog.Fatal("server.NewService() failed: ", err) |
| 244 | } |
Matt Rosencrantz | 53ac585 | 2015-09-04 15:14:54 -0700 | [diff] [blame] | 245 | serverCtx, s, err := v23.WithNewDispatchingServer(serverCtx, "", server.NewDispatcher(service)) |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 246 | if err != nil { |
Matt Rosencrantz | 53ac585 | 2015-09-04 15:14:54 -0700 | [diff] [blame] | 247 | vlog.Fatal("v23.WithNewDispatchingServer() failed: ", err) |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 248 | } |
| 249 | name := s.Status().Endpoints[0].Name() |
| 250 | return name, func() { |
Matt Rosencrantz | 9bf6863 | 2015-09-30 17:13:46 -0700 | [diff] [blame^] | 251 | cancel() |
| 252 | <-s.Closed() |
Adam Sadovsky | 6a6214f | 2015-09-03 18:20:18 -0700 | [diff] [blame] | 253 | 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". |
| 260 | func 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 | } |