github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/runtimevar/drivertest/drivertest.go (about) 1 // Copyright 2018 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package drivertest provides a conformance test for implementations of 16 // runtimevar. 17 package drivertest // import "gocloud.dev/runtimevar/drivertest" 18 19 import ( 20 "context" 21 "errors" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 "gocloud.dev/gcerrors" 28 "gocloud.dev/internal/testing/setup" 29 "gocloud.dev/runtimevar" 30 "gocloud.dev/runtimevar/driver" 31 ) 32 33 // Harness describes the functionality test harnesses must provide to run conformance tests. 34 type Harness interface { 35 // MakeWatcher creates a driver.Watcher to watch the given variable. 36 MakeWatcher(ctx context.Context, name string, decoder *runtimevar.Decoder) (driver.Watcher, error) 37 // CreateVariable creates the variable with the given contents. 38 CreateVariable(ctx context.Context, name string, val []byte) error 39 // UpdateVariable updates an existing variable to have the given contents. 40 UpdateVariable(ctx context.Context, name string, val []byte) error 41 // DeleteVariable deletes an existing variable. 42 DeleteVariable(ctx context.Context, name string) error 43 // Close is called when the test is complete. 44 Close() 45 // Mutable returns true iff the driver supports UpdateVariable/DeleteVariable. 46 // If false, those functions should return errors, and the conformance tests 47 // will skip and/or ignore errors for tests that require them. 48 Mutable() bool 49 } 50 51 // HarnessMaker describes functions that construct a harness for running tests. 52 // It is called exactly once per test; Harness.Close() will be called when the test is complete. 53 type HarnessMaker func(t *testing.T) (Harness, error) 54 55 // AsTest represents a test of As functionality. 56 // The conformance test: 57 // 1. Reads a Snapshot of the variable before it exists. 58 // 2. Calls ErrorCheck. 59 // 3. Creates the variable and reads a Snapshot of it. 60 // 4. Calls SnapshotCheck. 61 type AsTest interface { 62 // Name should return a descriptive name for the test. 63 Name() string 64 // SnapshotCheck will be called to allow verification of Snapshot.As. 65 SnapshotCheck(s *runtimevar.Snapshot) error 66 // ErrorCheck will be called to allow verification of Variable.ErrorAs. 67 // driver is provided so that errors other than err can be checked; 68 // Variable.ErrorAs won't work since it expects driver errors to be wrapped. 69 ErrorCheck(v *runtimevar.Variable, err error) error 70 } 71 72 type verifyAsFailsOnNil struct{} 73 74 func (verifyAsFailsOnNil) Name() string { 75 return "verify As returns false when passed nil" 76 } 77 78 func (verifyAsFailsOnNil) SnapshotCheck(v *runtimevar.Snapshot) error { 79 if v.As(nil) { 80 return errors.New("want Snapshot.As to return false when passed nil") 81 } 82 return nil 83 } 84 85 func (verifyAsFailsOnNil) ErrorCheck(v *runtimevar.Variable, err error) (ret error) { 86 defer func() { 87 if recover() == nil { 88 ret = errors.New("want ErrorAs to panic when passed nil") 89 } 90 }() 91 v.ErrorAs(err, nil) 92 return nil 93 } 94 95 // RunConformanceTests runs conformance tests for driver implementations 96 // of runtimevar. 97 func RunConformanceTests(t *testing.T, newHarness HarnessMaker, asTests []AsTest) { 98 t.Run("TestNonExistentVariable", func(t *testing.T) { 99 testNonExistentVariable(t, newHarness) 100 }) 101 t.Run("TestString", func(t *testing.T) { 102 testString(t, newHarness) 103 }) 104 t.Run("TestJSON", func(t *testing.T) { 105 testJSON(t, newHarness) 106 }) 107 t.Run("TestInvalidJSON", func(t *testing.T) { 108 testInvalidJSON(t, newHarness) 109 }) 110 t.Run("TestUpdate", func(t *testing.T) { 111 testUpdate(t, newHarness) 112 }) 113 t.Run("TestDelete", func(t *testing.T) { 114 testDelete(t, newHarness) 115 }) 116 t.Run("TestUpdateWithErrors", func(t *testing.T) { 117 testUpdateWithErrors(t, newHarness) 118 }) 119 asTests = append(asTests, verifyAsFailsOnNil{}) 120 t.Run("TestAs", func(t *testing.T) { 121 for _, st := range asTests { 122 if st.Name() == "" { 123 t.Fatalf("AsTest.Name is required") 124 } 125 t.Run(st.Name(), func(t *testing.T) { 126 testAs(t, newHarness, st) 127 }) 128 } 129 }) 130 } 131 132 // deadlineExceeded returns true if err represents a context exceeded error. 133 // It can either be a true context.DeadlineExceeded, or an RPC aborted due to 134 // ctx cancellation; we don't have a good way of checking for the latter 135 // explicitly so we check the Error() string. 136 func deadlineExceeded(err error) bool { 137 return err == context.DeadlineExceeded || strings.Contains(err.Error(), "context deadline exceeded") 138 } 139 140 // waitTimeForBlockingCheck returns a duration to wait when verifying that a 141 // call blocks. When in replay mode, it can be quite short to make tests run 142 // quickly. When in record mode, it has to be long enough that RPCs can 143 // consistently finish. 144 func waitTimeForBlockingCheck() time.Duration { 145 if *setup.Record { 146 return 5 * time.Second 147 } 148 return 10 * time.Millisecond 149 } 150 151 func testNonExistentVariable(t *testing.T, newHarness HarnessMaker) { 152 h, err := newHarness(t) 153 if err != nil { 154 t.Fatal(err) 155 } 156 defer h.Close() 157 ctx := context.Background() 158 159 drv, err := h.MakeWatcher(ctx, "does-not-exist", runtimevar.StringDecoder) 160 if err != nil { 161 t.Fatal(err) 162 } 163 v := runtimevar.New(drv) 164 defer func() { 165 if err := v.Close(); err != nil { 166 t.Error(err) 167 } 168 }() 169 got, err := v.Watch(ctx) 170 if err == nil { 171 t.Errorf("got %v expected not-found error", got.Value) 172 } else if gcerrors.Code(err) != gcerrors.NotFound { 173 t.Error("got IsNotExist false, expected true") 174 } 175 } 176 177 func testString(t *testing.T, newHarness HarnessMaker) { 178 const ( 179 name = "test-config-variable" 180 content = "hello world" 181 ) 182 183 h, err := newHarness(t) 184 if err != nil { 185 t.Fatal(err) 186 } 187 defer h.Close() 188 ctx := context.Background() 189 190 if err := h.CreateVariable(ctx, name, []byte(content)); err != nil { 191 t.Fatal(err) 192 } 193 if h.Mutable() { 194 defer func() { 195 if err := h.DeleteVariable(ctx, name); err != nil { 196 t.Fatal(err) 197 } 198 }() 199 } 200 201 drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder) 202 if err != nil { 203 t.Fatal(err) 204 } 205 v := runtimevar.New(drv) 206 defer func() { 207 if err := v.Close(); err != nil { 208 t.Error(err) 209 } 210 }() 211 got, err := v.Watch(ctx) 212 if err != nil { 213 t.Fatal(err) 214 } 215 // The variable is decoded to a string and matches the expected content. 216 if gotS, ok := got.Value.(string); !ok { 217 t.Fatalf("got value of type %T expected string", got.Value) 218 } else if gotS != content { 219 t.Errorf("got %q want %q", got.Value, content) 220 } 221 222 // A second watch should block forever since the value hasn't changed. 223 // A short wait here doesn't guarantee that this is working, but will catch 224 // most problems. 225 tCtx, cancel := context.WithTimeout(ctx, waitTimeForBlockingCheck()) 226 defer cancel() 227 got, err = v.Watch(tCtx) 228 if err == nil { 229 t.Errorf("got %v want error", got) 230 } 231 // tCtx should be cancelled. However, tests using record/replay mode can 232 // be in the middle of an RPC when that happens, and save the resulting 233 // RPC error during record. During replay, that error can be returned 234 // immediately (before tCtx is cancelled). So, we accept deadline exceeded 235 // errors as well. 236 if tCtx.Err() == nil && !deadlineExceeded(err) { 237 t.Errorf("got err %v; want Watch to have blocked until context was Done, or for the error to be deadline exceeded", err) 238 } 239 } 240 241 // Message is used as a target for JSON decoding. 242 type Message struct { 243 Name, Text string 244 } 245 246 func testJSON(t *testing.T, newHarness HarnessMaker) { 247 const ( 248 name = "test-config-variable" 249 jsonContent = `[ 250 {"Name": "Ed", "Text": "Knock knock."}, 251 {"Name": "Sam", "Text": "Who's there?"} 252 ]` 253 ) 254 want := []*Message{{Name: "Ed", Text: "Knock knock."}, {Name: "Sam", Text: "Who's there?"}} 255 256 h, err := newHarness(t) 257 if err != nil { 258 t.Fatal(err) 259 } 260 defer h.Close() 261 ctx := context.Background() 262 263 if err := h.CreateVariable(ctx, name, []byte(jsonContent)); err != nil { 264 t.Fatal(err) 265 } 266 if h.Mutable() { 267 defer func() { 268 if err := h.DeleteVariable(ctx, name); err != nil { 269 t.Fatal(err) 270 } 271 }() 272 } 273 274 var jsonData []*Message 275 drv, err := h.MakeWatcher(ctx, name, runtimevar.NewDecoder(jsonData, runtimevar.JSONDecode)) 276 if err != nil { 277 t.Fatal(err) 278 } 279 v := runtimevar.New(drv) 280 defer func() { 281 if err := v.Close(); err != nil { 282 t.Error(err) 283 } 284 }() 285 got, err := v.Watch(ctx) 286 if err != nil { 287 t.Fatal(err) 288 } 289 // The variable is decoded to a []*Message and matches the expected content. 290 if gotSlice, ok := got.Value.([]*Message); !ok { 291 t.Fatalf("got value of type %T expected []*Message", got.Value) 292 } else if !cmp.Equal(gotSlice, want) { 293 t.Errorf("got %v want %v", gotSlice, want) 294 } 295 } 296 297 func testInvalidJSON(t *testing.T, newHarness HarnessMaker) { 298 const ( 299 name = "test-config-variable" 300 content = "not-json" 301 ) 302 303 h, err := newHarness(t) 304 if err != nil { 305 t.Fatal(err) 306 } 307 defer h.Close() 308 ctx := context.Background() 309 310 if err := h.CreateVariable(ctx, name, []byte(content)); err != nil { 311 t.Fatal(err) 312 } 313 if h.Mutable() { 314 defer func() { 315 if err := h.DeleteVariable(ctx, name); err != nil { 316 t.Fatal(err) 317 } 318 }() 319 } 320 321 var jsonData []*Message 322 drv, err := h.MakeWatcher(ctx, name, runtimevar.NewDecoder(jsonData, runtimevar.JSONDecode)) 323 if err != nil { 324 t.Fatal(err) 325 } 326 v := runtimevar.New(drv) 327 defer func() { 328 if err := v.Close(); err != nil { 329 t.Error(err) 330 } 331 }() 332 got, err := v.Watch(ctx) 333 if err == nil { 334 t.Errorf("got %v wanted invalid-json error", got.Value) 335 } 336 } 337 338 func testUpdate(t *testing.T, newHarness HarnessMaker) { 339 const ( 340 name = "test-config-variable" 341 content1 = "hello world" 342 content2 = "goodbye world" 343 ) 344 345 h, err := newHarness(t) 346 if err != nil { 347 t.Fatal(err) 348 } 349 defer h.Close() 350 if !h.Mutable() { 351 return 352 } 353 ctx := context.Background() 354 355 // Create the variable and verify WatchVariable sees the value. 356 if err := h.CreateVariable(ctx, name, []byte(content1)); err != nil { 357 t.Fatal(err) 358 } 359 defer func() { 360 if err := h.DeleteVariable(ctx, name); err != nil { 361 t.Fatal(err) 362 } 363 }() 364 365 drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder) 366 if err != nil { 367 t.Fatal(err) 368 } 369 defer func() { 370 if err := drv.Close(); err != nil { 371 t.Error(err) 372 } 373 }() 374 state, _ := drv.WatchVariable(ctx, nil) 375 if state == nil { 376 t.Fatalf("got nil state, want a non-nil state with a value") 377 } 378 got, err := state.Value() 379 if err != nil { 380 t.Fatal(err) 381 } 382 if gotS, ok := got.(string); !ok { 383 t.Fatalf("got value of type %T expected string", got) 384 } else if gotS != content1 { 385 t.Errorf("got %q want %q", got, content1) 386 } 387 388 // The variable hasn't changed, so drv.WatchVariable should either 389 // return nil or block. 390 cancelCtx, cancel := context.WithTimeout(ctx, waitTimeForBlockingCheck()) 391 defer cancel() 392 unchangedState, _ := drv.WatchVariable(cancelCtx, state) 393 if unchangedState == nil { 394 // OK 395 } else { 396 got, err = unchangedState.Value() 397 if err != context.DeadlineExceeded { 398 t.Fatalf("got state %v/%v/%v, wanted nil or nil/DeadlineExceeded after no change", got, err, gcerrors.Code(err)) 399 } 400 } 401 402 // Update the variable and verify WatchVariable sees the updated value. 403 if err := h.UpdateVariable(ctx, name, []byte(content2)); err != nil { 404 t.Fatal(err) 405 } 406 state, _ = drv.WatchVariable(ctx, state) 407 if state == nil { 408 t.Fatalf("got nil state, want a non-nil state with a value") 409 } 410 got, err = state.Value() 411 if err != nil { 412 t.Fatal(err) 413 } 414 if gotS, ok := got.(string); !ok { 415 t.Fatalf("got value of type %T expected string", got) 416 } else if gotS != content2 { 417 t.Errorf("got %q want %q", got, content2) 418 } 419 } 420 421 func testDelete(t *testing.T, newHarness HarnessMaker) { 422 const ( 423 name = "test-config-variable" 424 content1 = "hello world" 425 content2 = "goodbye world" 426 ) 427 428 h, err := newHarness(t) 429 if err != nil { 430 t.Fatal(err) 431 } 432 defer h.Close() 433 if !h.Mutable() { 434 return 435 } 436 ctx := context.Background() 437 438 // Create the variable and verify WatchVariable sees the value. 439 if err := h.CreateVariable(ctx, name, []byte(content1)); err != nil { 440 t.Fatal(err) 441 } 442 needToDelete := true 443 defer func() { 444 if needToDelete { 445 if err := h.DeleteVariable(ctx, name); err != nil { 446 t.Fatal(err) 447 } 448 } 449 }() 450 451 drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder) 452 if err != nil { 453 t.Fatal(err) 454 } 455 defer func() { 456 if err := drv.Close(); err != nil { 457 t.Error(err) 458 } 459 }() 460 state, _ := drv.WatchVariable(ctx, nil) 461 if state == nil { 462 t.Fatalf("got nil state, want a non-nil state with a value") 463 } 464 got, err := state.Value() 465 if err != nil { 466 t.Fatal(err) 467 } 468 if gotS, ok := got.(string); !ok { 469 t.Fatalf("got value of type %T expected string", got) 470 } else if gotS != content1 { 471 t.Errorf("got %q want %q", got, content1) 472 } 473 prev := state 474 475 // Delete the variable. 476 if err := h.DeleteVariable(ctx, name); err != nil { 477 t.Fatal(err) 478 } 479 needToDelete = false 480 481 // WatchVariable should return a state with an error now. 482 state, _ = drv.WatchVariable(ctx, state) 483 if state == nil { 484 t.Fatalf("got nil state, want a non-nil state with an error") 485 } 486 got, err = state.Value() 487 if err == nil { 488 t.Fatalf("got %v want error because variable is deleted", got) 489 } 490 491 // Reset the variable with new content and verify via WatchVariable. 492 if err := h.CreateVariable(ctx, name, []byte(content2)); err != nil { 493 t.Fatal(err) 494 } 495 needToDelete = true 496 state, _ = drv.WatchVariable(ctx, state) 497 if state == nil { 498 t.Fatalf("got nil state, want a non-nil state with a value") 499 } 500 got, err = state.Value() 501 if err != nil { 502 t.Fatal(err) 503 } 504 if gotS, ok := got.(string); !ok { 505 t.Fatalf("got value of type %T expected string", got) 506 } else if gotS != content2 { 507 t.Errorf("got %q want %q", got, content2) 508 } 509 if state.UpdateTime().Before(prev.UpdateTime()) { 510 t.Errorf("got UpdateTime %v < previous %v, want >=", state.UpdateTime(), prev.UpdateTime()) 511 } 512 } 513 514 func testUpdateWithErrors(t *testing.T, newHarness HarnessMaker) { 515 const ( 516 name = "test-updating-variable-to-error" 517 content1 = `[{"Name": "Foo", "Text": "Bar"}]` 518 content2 = "invalid-json" 519 content3 = "invalid-json2" 520 ) 521 want := []*Message{{Name: "Foo", Text: "Bar"}} 522 523 h, err := newHarness(t) 524 if err != nil { 525 t.Fatal(err) 526 } 527 defer h.Close() 528 if !h.Mutable() { 529 return 530 } 531 ctx := context.Background() 532 533 // Create the variable and verify WatchVariable sees the value. 534 if err := h.CreateVariable(ctx, name, []byte(content1)); err != nil { 535 t.Fatal(err) 536 } 537 defer func() { 538 if err := h.DeleteVariable(ctx, name); err != nil { 539 t.Fatal(err) 540 } 541 }() 542 543 var jsonData []*Message 544 drv, err := h.MakeWatcher(ctx, name, runtimevar.NewDecoder(jsonData, runtimevar.JSONDecode)) 545 if err != nil { 546 t.Fatal(err) 547 } 548 defer func() { 549 if err := drv.Close(); err != nil { 550 t.Error(err) 551 } 552 }() 553 state, _ := drv.WatchVariable(ctx, nil) 554 if state == nil { 555 t.Fatal("got nil state, want a non-nil state with a value") 556 } 557 got, err := state.Value() 558 if err != nil { 559 t.Fatal(err) 560 } 561 if gotSlice, ok := got.([]*Message); !ok { 562 t.Fatalf("got value of type %T expected []*Message", got) 563 } else if !cmp.Equal(gotSlice, want) { 564 t.Errorf("got %v want %v", gotSlice, want) 565 } 566 567 // Update the variable to invalid JSON and verify WatchVariable returns an error. 568 if err := h.UpdateVariable(ctx, name, []byte(content2)); err != nil { 569 t.Fatal(err) 570 } 571 state, _ = drv.WatchVariable(ctx, state) 572 if state == nil { 573 t.Fatal("got nil state, want a non-nil state with an error") 574 } 575 _, err = state.Value() 576 if err == nil { 577 t.Fatal("got nil err want invalid JSON error") 578 } 579 580 // Update the variable again, with different invalid JSON. 581 // WatchVariable should block or return nil since it's the same error as before. 582 if err := h.UpdateVariable(ctx, name, []byte(content3)); err != nil { 583 t.Fatal(err) 584 } 585 tCtx, cancel := context.WithTimeout(ctx, waitTimeForBlockingCheck()) 586 defer cancel() 587 state, _ = drv.WatchVariable(tCtx, state) 588 if state == nil { 589 // OK: nil indicates no change. 590 } else { 591 // WatchVariable should have blocked until tCtx was cancelled, and we 592 // should have gotten that error back. 593 got, err := state.Value() 594 if err == nil { 595 t.Fatalf("got %v and nil error, want non-nil error", got) 596 } 597 // tCtx should be cancelled. However, tests using record/replay mode can 598 // be in the middle of an RPC when that happens, and save the resulting 599 // RPC error during record. During replay, that error can be returned 600 // immediately (before tCtx is cancelled). So, we accept deadline exceeded 601 // errors as well. 602 if tCtx.Err() == nil && !deadlineExceeded(err) { 603 t.Errorf("got err %v; want Watch to have blocked until context was Done, or for the error to be deadline exceeded", err) 604 } 605 } 606 } 607 608 // testAs tests the various As functions, using AsTest. 609 func testAs(t *testing.T, newHarness HarnessMaker, st AsTest) { 610 const ( 611 name = "variable-for-as" 612 content = "hello world" 613 ) 614 615 h, err := newHarness(t) 616 if err != nil { 617 t.Fatal(err) 618 } 619 defer h.Close() 620 ctx := context.Background() 621 622 // Try to read the variable before it exists. 623 drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder) 624 if err != nil { 625 t.Fatal(err) 626 } 627 v := runtimevar.New(drv) 628 s, gotErr := v.Watch(ctx) 629 if gotErr == nil { 630 t.Fatalf("got nil error and %v, expected non-nil error", v) 631 } 632 if err := st.ErrorCheck(v, gotErr); err != nil { 633 t.Error(err) 634 } 635 var dummy string 636 if s.As(&dummy) { 637 t.Error(errors.New("want Snapshot.As to return false when Snapshot is zero value")) 638 } 639 if err := v.Close(); err != nil { 640 t.Error(err) 641 } 642 643 // Create the variable and verify WatchVariable sees the value. 644 if err := h.CreateVariable(ctx, name, []byte(content)); err != nil { 645 t.Fatal(err) 646 } 647 if h.Mutable() { 648 defer func() { 649 if err := h.DeleteVariable(ctx, name); err != nil { 650 t.Fatal(err) 651 } 652 }() 653 } 654 655 drv, err = h.MakeWatcher(ctx, name, runtimevar.StringDecoder) 656 if err != nil { 657 t.Fatal(err) 658 } 659 v = runtimevar.New(drv) 660 defer func() { 661 if err := v.Close(); err != nil { 662 t.Error(err) 663 } 664 }() 665 s, err = v.Watch(ctx) 666 if err != nil { 667 t.Fatal(err) 668 } 669 if err := st.SnapshotCheck(&s); err != nil { 670 t.Error(err) 671 } 672 }