github.com/thiagoyeds/go-cloud@v0.26.0/runtimevar/runtimevar_test.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 runtimevar contains tests that exercises the runtimevar APIs. It does not test 16 // driver implementations. 17 package runtimevar 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/gob" 23 "encoding/json" 24 "errors" 25 "fmt" 26 "net/url" 27 "reflect" 28 "sync" 29 "testing" 30 "time" 31 32 "github.com/google/go-cmp/cmp" 33 "gocloud.dev/gcerrors" 34 "gocloud.dev/internal/gcerr" 35 "gocloud.dev/runtimevar/driver" 36 "gocloud.dev/secrets/localsecrets" 37 ) 38 39 // How long we wait on a call that is expected to block forever before cancelling it. 40 const blockingCheckDelay = 25 * time.Millisecond 41 42 // state implements driver.State. 43 type state struct { 44 val string 45 updateTime time.Time 46 err error 47 } 48 49 func (s *state) Value() (interface{}, error) { return s.val, s.err } 50 func (s *state) UpdateTime() time.Time { return s.updateTime } 51 func (s *state) As(i interface{}) bool { return false } 52 53 // fakeWatcher is a fake implementation of driver.Watcher that returns a set *state. 54 type fakeWatcher struct { 55 driver.Watcher 56 57 mu sync.Mutex 58 state *state 59 newval bool // true iff WatchVariable should return state 60 } 61 62 func (w *fakeWatcher) Set(s *state) { 63 w.mu.Lock() 64 defer w.mu.Unlock() 65 w.state = s 66 w.newval = true 67 } 68 69 func (w *fakeWatcher) WatchVariable(ctx context.Context, prev driver.State) (driver.State, time.Duration) { 70 if err := ctx.Err(); err != nil { 71 w.Set(&state{err: err}) 72 } 73 w.mu.Lock() 74 defer w.mu.Unlock() 75 if !w.newval { 76 return nil, 1 * time.Millisecond // to avoid spinning 77 } 78 w.newval = false 79 return w.state, 0 80 } 81 82 func (*fakeWatcher) Close() error { return nil } 83 func (*fakeWatcher) ErrorCode(error) gcerrors.ErrorCode { return gcerrors.Internal } 84 85 func TestVariable_Watch(t *testing.T) { 86 fake := &fakeWatcher{} 87 v := New(fake) 88 89 ctx := context.Background() 90 91 // Watch should block when there's no value yet. 92 ctx2, cancel := context.WithTimeout(ctx, blockingCheckDelay) 93 defer cancel() 94 if _, err := v.Watch(ctx2); err == nil { 95 t.Errorf("Watch with no value yet should block: got nil err, want err") 96 } 97 if ctx2.Err() == nil { 98 t.Error("Watch with no value yet should block") 99 } 100 101 // Setting an error value makes Watch return an error. 102 fake.Set(&state{err: errFake}) 103 if _, err := v.Watch(ctx); err == nil { 104 t.Fatal("Watch returned non-nil error, want error") 105 } 106 // But calling Watch again blocks. 107 ctx2, cancel = context.WithTimeout(ctx, blockingCheckDelay) 108 defer cancel() 109 if _, err := v.Watch(ctx2); err == nil { 110 t.Errorf("Watch called again with error value should block: got nil err, want err") 111 } 112 if ctx2.Err() == nil { 113 t.Error("Watch called again with error value should block") 114 } 115 116 // Setting a different error makes Watch return again. 117 fake.Set(&state{err: errors.New("another fake error")}) 118 if _, err := v.Watch(ctx); err == nil { 119 t.Fatal("Watch returned non-nil error, want error") 120 } 121 122 // Setting a value makes Watch return again. 123 fake.Set(&state{val: "hello"}) 124 if _, err := v.Watch(ctx); err != nil { 125 t.Fatalf("Watch returned error %v, want nil", err) 126 } 127 128 // Make a few updates. Each of these will try to write to the nextWatchCh, 129 // but we should only keep the latest one. 130 fake.Set(&state{val: "hello1"}) 131 fake.Set(&state{val: "hello2"}) 132 fake.Set(&state{val: "hello3"}) 133 fake.Set(&state{val: "hello4"}) 134 fake.Set(&state{val: "hello5"}) 135 // Wait until we're sure the last one has been received. 136 for { 137 snap, err := v.Latest(ctx) 138 if err != nil { 139 t.Errorf("got unexpected error from Latest: %v", err) 140 } 141 if snap.Value == "hello5" { 142 break 143 } 144 } 145 146 // Watch should get the last one, hello5. 147 if snap, err := v.Watch(ctx); err != nil { 148 t.Fatalf("Watch returned error %v, want nil", err) 149 } else if snap.Value != "hello5" { 150 t.Errorf("Watch got %v, want hello5", snap.Value) 151 } 152 153 // And the next call should block. 154 ctx2, cancel = context.WithTimeout(ctx, blockingCheckDelay) 155 defer cancel() 156 if _, err := v.Watch(ctx2); err == nil { 157 t.Errorf("Watch after no change in good value should block: got nil err, want err") 158 } 159 if ctx2.Err() == nil { 160 t.Error("Watch after no change in good value should block") 161 } 162 163 // Ensure a blocking Watch returns when a new value arrives. 164 // Wait blockingCheckDelay to give some time to be blocking in Watch. 165 // There's no guarantee it will get there, but if Watch doesn't handle 166 // cancelation properly, then the test will fail whenever it does get there, 167 // so at least we'll observe a flaky test. 168 time.AfterFunc(blockingCheckDelay, func() { fake.Set(&state{val: "hello6"}) }) 169 if snap, err := v.Watch(ctx); err != nil { 170 t.Errorf("Watch interrupted by new value returned %v, want nil", err) 171 } else if snap.Value != "hello6" { 172 t.Errorf("Watch got %v, want hello6", snap.Value) 173 } 174 175 // Similarly, ensure a blocking Watch is interrupted by Close. 176 time.AfterFunc(blockingCheckDelay, func() { 177 if err := v.Close(); err != nil { 178 t.Error(err) 179 } 180 }) 181 if _, err := v.Watch(ctx); err != ErrClosed { 182 t.Errorf("Watch interrupted by Close returned %v, want ErrClosed", err) 183 } 184 185 // Watch should now return ErrClosed. 186 if _, err := v.Watch(ctx); err != ErrClosed { 187 t.Errorf("Watch after Close returned %v, want ErrClosed", err) 188 } 189 } 190 191 func TestVariable_Latest(t *testing.T) { 192 const content1, content2 = "foo", "bar" 193 const numGoroutines = 10 194 ctx := context.Background() 195 196 fake := &fakeWatcher{} 197 v := New(fake) 198 199 // Not healthy at startup. 200 if v.CheckHealth() == nil { 201 t.Error("got nil from CheckHealth, want error") 202 } 203 204 // Latest should block until the context is done, as there's no value. 205 ctx2, cancel := context.WithTimeout(ctx, blockingCheckDelay) 206 defer cancel() 207 if _, err := v.Latest(ctx2); err == nil { 208 t.Errorf("Latest with no value yet should block: got nil err, want err") 209 } 210 if ctx2.Err() == nil { 211 t.Error("Latest with no value yet should block") 212 } 213 // And we're not healthy. 214 if v.CheckHealth() == nil { 215 t.Error("got nil from CheckHealth, want error") 216 } 217 218 // Call Latest concurrently. There's still no value. 219 var wg sync.WaitGroup 220 wg.Add(numGoroutines) 221 for i := 0; i < numGoroutines; i++ { 222 go func() { 223 ctx2, cancel := context.WithTimeout(ctx, blockingCheckDelay) 224 cancel() 225 if _, err := v.Latest(ctx2); err == nil { 226 t.Errorf("Latest with no value yet: got nil err, want err") 227 } 228 wg.Done() 229 }() 230 } 231 wg.Wait() 232 233 // Set an error value. Latest should still block. 234 fake.Set(&state{err: errFake}) 235 ctx2, cancel = context.WithTimeout(ctx, blockingCheckDelay) 236 defer cancel() 237 if _, err := v.Latest(ctx2); err == nil { 238 t.Errorf("Latest with error value should block: got nil err, want err") 239 } 240 if ctx2.Err() == nil { 241 t.Error("Latest with error value should block") 242 } 243 // And we're still not healthy. 244 if v.CheckHealth() == nil { 245 t.Error("got nil from CheckHealth, want error") 246 } 247 248 // Call Latest concurrently, only exiting each goroutine when they 249 // see the content1 value. 250 wg.Add(numGoroutines) 251 for i := 0; i < numGoroutines; i++ { 252 go func() { 253 for { 254 val, err := v.Latest(ctx) 255 if err != nil { 256 continue 257 } 258 if val.Value != content1 { 259 t.Errorf("got %v want %s", val, content1) 260 } 261 wg.Done() 262 return 263 } 264 }() 265 } 266 // Set a good value, after a small delay to give the goroutines a chance 267 // to get into Latest. 268 time.Sleep(blockingCheckDelay) 269 fake.Set(&state{val: content1}) 270 wg.Wait() 271 // And now we're healthy. 272 if err := v.CheckHealth(); err != nil { 273 t.Errorf("got %v from CheckHealth, want nil", err) 274 } 275 276 // Set a different value. At some point after this, Latest should start 277 // returning a Snapshot with Value set to content2. 278 fake.Set(&state{val: content2}) 279 280 // Call Latest concurrently, only exiting each goroutine when they 281 // see the content2 value. 282 wg.Add(numGoroutines) 283 for i := 0; i < numGoroutines; i++ { 284 go func() { 285 for { 286 val, err := v.Latest(ctx) 287 if err != nil { 288 // Errors are unexpected at this point. 289 t.Error(err) 290 } 291 if val.Value == content1 { 292 // Still seeing the old value. 293 continue 294 } 295 if val.Value != content2 { 296 t.Errorf("got %v want %s", val, content2) 297 } 298 wg.Done() 299 return 300 } 301 }() 302 } 303 wg.Wait() 304 305 // Set an error value. Latest should still return content2. 306 fake.Set(&state{err: errFake}) 307 308 // Call Latest concurrently. The test will be flaky if some of them 309 // start getting errors. 310 wg.Add(numGoroutines) 311 for i := 0; i < numGoroutines; i++ { 312 go func() { 313 for { 314 val, err := v.Latest(ctx) 315 if err != nil { 316 // Errors are unexpected at this point. 317 t.Error(err) 318 } else if val.Value != content2 { 319 t.Errorf("got %v want %s", val.Value, content2) 320 } 321 wg.Done() 322 return 323 } 324 }() 325 } 326 wg.Wait() 327 328 // Still healthy. 329 if err := v.CheckHealth(); err != nil { 330 t.Errorf("got %v from CheckHealth, want nil", err) 331 } 332 333 // Close the variable. 334 if err := v.Close(); err != nil { 335 t.Error(err) 336 } 337 338 // Latest should now return ErrClosed. 339 if _, err := v.Latest(ctx); err != ErrClosed { 340 t.Errorf("Latest after close returned %v, want ErrClosed", err) 341 } 342 // Unhealthy now. 343 if err := v.CheckHealth(); err != ErrClosed { 344 t.Errorf("got %v from CheckHealth, want ErrClosed", err) 345 } 346 } 347 348 // Tests that Latest is interrupted by Close. 349 func TestVariable_LatestBlockedDuringClose(t *testing.T) { 350 fake := &fakeWatcher{} 351 v := New(fake) 352 353 ctx := context.Background() 354 355 // Wait blockingCheckDelay to give some time to be blocking in Latest. 356 // There's no guarantee it will get there, but if Latest doesn't handle 357 // cancelation properly, then the test will fail whenever it does get there, 358 // so at least we'll observe a flaky test. 359 time.AfterFunc(blockingCheckDelay, func() { 360 if err := v.Close(); err != nil { 361 t.Error(err) 362 } 363 }) 364 if _, err := v.Latest(ctx); err != ErrClosed { 365 t.Errorf("Latest interrupted by Close got %v, want ErrClosed", err) 366 } 367 368 // Calling Close again should return ErrClosed. 369 if err := v.Close(); err != ErrClosed { 370 t.Errorf("calling Close 2x returned %v, want ErrClosed", err) 371 } 372 } 373 374 var errFake = errors.New("fake") 375 376 // erroringWatcher implements driver.Watcher. 377 // WatchVariable always returns a state with errFake, and Close 378 // always returns errFake. 379 type erroringWatcher struct { 380 driver.Watcher 381 } 382 383 func (b *erroringWatcher) WatchVariable(ctx context.Context, prev driver.State) (driver.State, time.Duration) { 384 return &state{err: errFake}, 0 385 } 386 387 func (b *erroringWatcher) Close() error { 388 return errFake 389 } 390 391 func (b *erroringWatcher) ErrorCode(err error) gcerrors.ErrorCode { 392 return gcerrors.Internal 393 } 394 395 // TestErrorsAreWrapped tests that all errors returned from the driver are 396 // wrapped exactly once by the portable type. 397 func TestErrorsAreWrapped(t *testing.T) { 398 ctx := context.Background() 399 v := New(&erroringWatcher{}) 400 401 // verifyWrap ensures that err is wrapped exactly once. 402 verifyWrap := func(description string, err error) { 403 if unwrapped, ok := err.(*gcerr.Error); !ok { 404 t.Errorf("%s: not wrapped: %v", description, err) 405 } else if du, ok := unwrapped.Unwrap().(*gcerr.Error); ok { 406 t.Errorf("%s: double wrapped: %v", description, du) 407 } 408 } 409 410 _, err := v.Watch(ctx) 411 verifyWrap("Watch", err) 412 413 err = v.Close() 414 verifyWrap("Close", err) 415 } 416 417 var ( 418 testOpenOnce sync.Once 419 testOpenGot *url.URL 420 ) 421 422 func TestURLMux(t *testing.T) { 423 ctx := context.Background() 424 425 mux := new(URLMux) 426 fake := &fakeOpener{} 427 mux.RegisterVariable("foo", fake) 428 mux.RegisterVariable("err", fake) 429 430 if diff := cmp.Diff(mux.VariableSchemes(), []string{"err", "foo"}); diff != "" { 431 t.Errorf("Schemes: %s", diff) 432 } 433 if !mux.ValidVariableScheme("foo") || !mux.ValidVariableScheme("err") { 434 t.Errorf("ValidVariableScheme didn't return true for valid scheme") 435 } 436 if mux.ValidVariableScheme("foo2") || mux.ValidVariableScheme("http") { 437 t.Errorf("ValidVariableScheme didn't return false for invalid scheme") 438 } 439 440 for _, tc := range []struct { 441 name string 442 url string 443 wantErr bool 444 }{ 445 { 446 name: "empty URL", 447 wantErr: true, 448 }, 449 { 450 name: "invalid URL", 451 url: ":foo", 452 wantErr: true, 453 }, 454 { 455 name: "invalid URL no scheme", 456 url: "foo", 457 wantErr: true, 458 }, 459 { 460 name: "unregistered scheme", 461 url: "bar://myvar", 462 wantErr: true, 463 }, 464 { 465 name: "func returns error", 466 url: "err://myvar", 467 wantErr: true, 468 }, 469 { 470 name: "no query options", 471 url: "foo://myvar", 472 }, 473 { 474 name: "empty query options", 475 url: "foo://myvar?", 476 }, 477 { 478 name: "query options", 479 url: "foo://myvar?aAa=bBb&cCc=dDd", 480 }, 481 { 482 name: "multiple query options", 483 url: "foo://myvar?x=a&x=b&x=c", 484 }, 485 { 486 name: "fancy var name", 487 url: "foo:///foo/bar/baz", 488 }, 489 { 490 name: "using api scheme prefix", 491 url: "runtimevar+foo:///foo/bar/baz", 492 }, 493 { 494 name: "using api+type scheme prefix", 495 url: "runtimevar+variable+foo:///foo/bar/baz", 496 }, 497 } { 498 t.Run(tc.name, func(t *testing.T) { 499 _, gotErr := mux.OpenVariable(ctx, tc.url) 500 if (gotErr != nil) != tc.wantErr { 501 t.Fatalf("got err %v, want error %v", gotErr, tc.wantErr) 502 } 503 if gotErr != nil { 504 return 505 } 506 if got := fake.u.String(); got != tc.url { 507 t.Errorf("got %q want %q", got, tc.url) 508 } 509 // Repeat with OpenVariableURL. 510 parsed, err := url.Parse(tc.url) 511 if err != nil { 512 t.Fatal(err) 513 } 514 _, gotErr = mux.OpenVariableURL(ctx, parsed) 515 if gotErr != nil { 516 t.Fatalf("got err %v, want nil", gotErr) 517 } 518 if got := fake.u.String(); got != tc.url { 519 t.Errorf("got %q want %q", got, tc.url) 520 } 521 }) 522 } 523 } 524 525 type fakeOpener struct { 526 u *url.URL // last url passed to OpenVariableURL 527 } 528 529 func (o *fakeOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*Variable, error) { 530 if u.Scheme == "err" { 531 return nil, errors.New("fail") 532 } 533 o.u = u 534 return nil, nil 535 } 536 537 func TestDecoder(t *testing.T) { 538 type Struct struct { 539 FieldA string 540 FieldB map[string]interface{} 541 } 542 543 num := 4321 544 numptr := &num 545 str := "boring string" 546 strptr := &str 547 548 inputs := []interface{}{ 549 str, 550 strptr, 551 num, 552 numptr, 553 100.1, 554 Struct{ 555 FieldA: "hello", 556 FieldB: map[string]interface{}{ 557 "hello": "world", 558 }, 559 }, 560 &Struct{ 561 FieldA: "world", 562 }, 563 map[string]string{ 564 "slice": "pizza", 565 }, 566 &map[string]interface{}{}, 567 []string{"hello", "world"}, 568 &[]int{1, 0, 1}, 569 [...]float64{3.1415}, 570 &[...]int64{4, 5, 6}, 571 } 572 573 for _, tc := range []struct { 574 desc string 575 encodeFn func(interface{}) ([]byte, error) 576 decodeFn Decode 577 }{ 578 { 579 desc: "JSON", 580 encodeFn: json.Marshal, 581 decodeFn: JSONDecode, 582 }, 583 { 584 desc: "Gob", 585 encodeFn: gobMarshal, 586 decodeFn: GobDecode, 587 }, 588 } { 589 for i, input := range inputs { 590 t.Run(fmt.Sprintf("%s_%d", tc.desc, i), func(t *testing.T) { 591 decoder := NewDecoder(input, tc.decodeFn) 592 b, err := tc.encodeFn(input) 593 if err != nil { 594 t.Fatalf("marshal error %v", err) 595 } 596 got, err := decoder.Decode(context.Background(), b) 597 if err != nil { 598 t.Fatalf("parse input\n%s\nerror: %v", string(b), err) 599 } 600 if reflect.TypeOf(got) != reflect.TypeOf(input) { 601 t.Errorf("type mismatch got %T, want %T", got, input) 602 } 603 if diff := cmp.Diff(got, input); diff != "" { 604 t.Errorf("value diff:\n%v", diff) 605 } 606 }) 607 } 608 } 609 } 610 611 func gobMarshal(v interface{}) ([]byte, error) { 612 var buf bytes.Buffer 613 if err := gob.NewEncoder(&buf).Encode(v); err != nil { 614 return nil, err 615 } 616 return buf.Bytes(), nil 617 } 618 619 func TestStringDecoder(t *testing.T) { 620 input := "hello world" 621 got, err := StringDecoder.Decode(context.Background(), []byte(input)) 622 if err != nil { 623 t.Fatalf("error: %v", err) 624 } 625 if input != got.(string) { 626 t.Errorf("output got %v, want %q", got, input) 627 } 628 } 629 630 func TestBytesDecoder(t *testing.T) { 631 input := []byte("hello world") 632 got, err := BytesDecoder.Decode(context.Background(), input) 633 if err != nil { 634 t.Fatalf("error: %v", err) 635 } 636 if diff := cmp.Diff(got, input); diff != "" { 637 t.Errorf("output got %v, want %q", got, input) 638 } 639 } 640 641 func TestDecryptDecoder(t *testing.T) { 642 ctx := context.Background() 643 secretKey, err := localsecrets.NewRandomKey() 644 if err != nil { 645 t.Fatal(err) 646 } 647 keeper := localsecrets.NewKeeper(secretKey) 648 649 tests := []struct { 650 desc string 651 in interface{} 652 encodeFn func(interface{}) ([]byte, error) 653 postDecFn Decode 654 }{ 655 { 656 desc: "Bytes", 657 in: []byte("hello world"), 658 encodeFn: func(obj interface{}) ([]byte, error) { return obj.([]byte), nil }, 659 }, 660 { 661 desc: "String", 662 in: "hello world", 663 encodeFn: func(obj interface{}) ([]byte, error) { return []byte(obj.(string)), nil }, 664 postDecFn: StringDecode, 665 }, 666 { 667 desc: "JSON", 668 in: map[string]string{ 669 "slice": "pizza", 670 }, 671 encodeFn: json.Marshal, 672 postDecFn: JSONDecode, 673 }, 674 } 675 for _, tc := range tests { 676 t.Run(tc.desc, func(t *testing.T) { 677 decoder := NewDecoder(tc.in, DecryptDecode(keeper, tc.postDecFn)) 678 679 b, err := tc.encodeFn(tc.in) 680 if err != nil { 681 t.Fatalf("encode error %v", err) 682 } 683 encrypted, err := keeper.Encrypt(ctx, b) 684 if err != nil { 685 t.Fatalf("encrypt error: %v", err) 686 } 687 688 got, err := decoder.Decode(ctx, encrypted) 689 if err != nil { 690 t.Fatalf("parse input\n%s\nerror: %v", string(b), err) 691 } 692 if reflect.TypeOf(got) != reflect.TypeOf(tc.in) { 693 t.Errorf("type mismatch got %T, want %T", got, tc.in) 694 } 695 if diff := cmp.Diff(got, tc.in); diff != "" { 696 t.Errorf("value diff:\n%v", diff) 697 } 698 }) 699 } 700 }