knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/tracker/enqueue_test.go (about) 1 /* 2 Copyright 2018 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tracker 18 19 import ( 20 "regexp" 21 "testing" 22 "time" 23 24 corev1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/types" 27 "k8s.io/client-go/tools/cache" 28 29 "knative.dev/pkg/kmeta" 30 . "knative.dev/pkg/testing" 31 ) 32 33 func TestHappyPathsExact(t *testing.T) { 34 calls := 0 35 f := func(key types.NamespacedName) { 36 calls++ 37 } 38 39 trk := New(f, 100*time.Millisecond) 40 41 thing1 := &Resource{ 42 TypeMeta: metav1.TypeMeta{ 43 APIVersion: "ref.knative.dev/v1alpha1", 44 Kind: "Thing1", 45 }, 46 ObjectMeta: metav1.ObjectMeta{ 47 Namespace: "ns", 48 Name: "foo", 49 }, 50 } 51 or := kmeta.ObjectReference(thing1) 52 ref := Reference{ 53 APIVersion: or.APIVersion, 54 Kind: or.Kind, 55 Namespace: or.Namespace, 56 Name: or.Name, 57 } 58 59 thing2 := &Resource{ 60 TypeMeta: metav1.TypeMeta{ 61 APIVersion: "refer.knative.dev/v1alpha1", 62 Kind: "Thing2", 63 }, 64 ObjectMeta: metav1.ObjectMeta{ 65 Namespace: "default", 66 Name: "bar.baz.this-is-fine", 67 }, 68 } 69 70 // Not tracked yet 71 { 72 trk.OnChanged(thing1) 73 if got, want := calls, 0; got != want { 74 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 75 } 76 obs := trk.GetObservers(thing1) 77 if got, want := len(obs), 0; got != want { 78 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 79 } 80 } 81 82 // Tracked gets called 83 { 84 if err := trk.Track(ref.ObjectReference(), thing2); err != nil { 85 t.Fatal("Track() =", err) 86 } 87 // New registrations should result in an immediate callback. 88 if got, want := calls, 1; got != want { 89 t.Fatalf("Track() = %v, wanted %v", got, want) 90 } 91 92 trk.OnChanged(thing1) 93 if got, want := calls, 2; got != want { 94 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 95 } 96 obs := trk.GetObservers(thing1) 97 if got, want := len(obs), 1; got != want { 98 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 99 } 100 } 101 102 // Still gets called 103 { 104 trk.OnChanged(thing1) 105 if got, want := calls, 3; got != want { 106 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 107 } 108 } 109 110 // Check that after the sleep duration, we stop getting called. 111 time.Sleep(101 * time.Millisecond) 112 { 113 trk.OnChanged(thing1) 114 if got, want := calls, 3; got != want { 115 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 116 } 117 if _, stillThere := trk.(*impl).exact[ref]; stillThere { 118 t.Fatal("Timeout passed, but exact for objectReference is still there") 119 } 120 obs := trk.GetObservers(thing1) 121 if got, want := len(obs), 0; got != want { 122 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 123 } 124 } 125 126 // Starts getting called again 127 { 128 if err := trk.Track(ref.ObjectReference(), thing2); err != nil { 129 t.Fatal("Track() =", err) 130 } 131 // New registrations should result in an immediate callback. 132 if got, want := calls, 4; got != want { 133 t.Fatalf("Track() = %v, wanted %v", got, want) 134 } 135 136 trk.OnChanged(thing1) 137 if got, want := calls, 5; got != want { 138 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 139 } 140 obs := trk.GetObservers(thing1) 141 if got, want := len(obs), 1; got != want { 142 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 143 } 144 } 145 146 // OnChanged non-accessor 147 { 148 // Check that passing in a resource that doesn't implement 149 // accessor won't panic. 150 trk.OnChanged("not an accessor") 151 152 if got, want := calls, 5; got != want { 153 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 154 } 155 } 156 157 // OnChanged non-accessor in DeletedFinalStateUnknown 158 { 159 // Check that passing in a DeletedFinalStateUnknown instance 160 // with a resource that doesn't implement accessor won't get 161 // Tracked called, and won't panic. 162 trk.OnChanged(cache.DeletedFinalStateUnknown{ 163 Key: "ns/foo", 164 Obj: "not an accessor", 165 }) 166 167 if got, want := calls, 5; got != want { 168 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 169 } 170 } 171 172 // Tracked gets called by DeletedFinalStateUnknown 173 { 174 trk.OnChanged(cache.DeletedFinalStateUnknown{ 175 Key: "ns/foo", 176 Obj: thing1, 177 }) 178 if got, want := calls, 6; got != want { 179 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 180 } 181 } 182 183 // Stops tracking explicitly 184 { 185 trk.OnDeletedObserver(thing2) 186 trk.OnChanged(thing1) 187 if got, want := calls, 6; got != want { 188 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 189 } 190 obs := trk.GetObservers(thing1) 191 if got, want := len(obs), 0; got != want { 192 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 193 } 194 } 195 196 // Track bad object 197 { 198 if err := trk.Track(ref.ObjectReference(), struct{}{}); err == nil { 199 t.Fatal("Track() = nil, wanted error") 200 } 201 obs := trk.GetObservers(thing1) 202 if got, want := len(obs), 0; got != want { 203 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 204 } 205 } 206 } 207 208 func TestAllowedObjectReferences(t *testing.T) { 209 trk := New(func(key types.NamespacedName) {}, 10*time.Millisecond) 210 thing1 := &Resource{ 211 TypeMeta: metav1.TypeMeta{ 212 APIVersion: "ref.knative.dev/v1alpha1", 213 Kind: "Thing1", 214 }, 215 ObjectMeta: metav1.ObjectMeta{ 216 Namespace: "ns", 217 Name: "foo", 218 }, 219 } 220 tests := []struct { 221 name string 222 objRef corev1.ObjectReference 223 }{{ 224 name: "Pod", 225 objRef: corev1.ObjectReference{ 226 APIVersion: "v1", 227 Kind: "Pod", 228 Namespace: "default", 229 Name: "test", 230 }, 231 }, { 232 name: "Namespace", 233 objRef: corev1.ObjectReference{ 234 APIVersion: "v1", 235 Kind: "Namespace", 236 Name: "default", 237 }, 238 }, { 239 name: "Non-core resource", 240 objRef: corev1.ObjectReference{ 241 APIVersion: "custom.example.com/v1alpha17", 242 Kind: "Widget", 243 Namespace: "default", 244 Name: "test", 245 }, 246 }, { 247 name: "Complex Kind", 248 objRef: corev1.ObjectReference{ 249 APIVersion: "custom.example.com/v1alpha17", 250 Kind: "Widget_v3", 251 Namespace: "default", 252 Name: "test", 253 }, 254 }, { 255 name: "Dashed Namespace", 256 objRef: corev1.ObjectReference{ 257 APIVersion: "v1", 258 Kind: "ConfigMap", 259 Namespace: "not-default", 260 Name: "test", 261 }, 262 }, { 263 name: "Complex Name", 264 objRef: corev1.ObjectReference{ 265 APIVersion: "v1", 266 Kind: "ConfigMap", 267 Namespace: "default", 268 Name: "test.example.cluster.local", 269 }, 270 }} 271 272 for _, test := range tests { 273 t.Run(test.name, func(t *testing.T) { 274 if err := trk.Track(test.objRef, thing1); err != nil { 275 t.Fatalf("Track() on %v returned error: %v", test.objRef, err) 276 } 277 }) 278 } 279 } 280 281 func TestBadObjectReferences(t *testing.T) { 282 trk := New(func(key types.NamespacedName) {}, 10*time.Millisecond) 283 thing1 := &Resource{ 284 TypeMeta: metav1.TypeMeta{ 285 APIVersion: "ref.knative.dev/v1alpha1", 286 Kind: "Thing1", 287 }, 288 ObjectMeta: metav1.ObjectMeta{ 289 Namespace: "ns", 290 Name: "foo", 291 }, 292 } 293 294 tests := []struct { 295 name string 296 objRef corev1.ObjectReference 297 match string 298 }{{ 299 name: "Missing APIVersion", 300 objRef: corev1.ObjectReference{ 301 // APIVersion: "build.knative.dev/v1alpha1", 302 Kind: "Build", 303 Namespace: "default", 304 Name: "kaniko", 305 }, 306 match: "APIVersion", 307 }, { 308 name: "Bad char in APIVersion", 309 objRef: corev1.ObjectReference{ 310 APIVersion: "build.knative.dev%v1alpha1", 311 Kind: "Build", 312 Namespace: "default", 313 Name: "kaniko", 314 }, 315 match: "APIVersion", 316 }, { 317 name: "Extra slashes in APIVersion", 318 objRef: corev1.ObjectReference{ 319 APIVersion: "build.knative.dev/v1/alpha1", 320 Kind: "Build", 321 Namespace: "default", 322 Name: "kaniko", 323 }, 324 match: "APIVersion", 325 }, { 326 name: "Missing Kind", 327 objRef: corev1.ObjectReference{ 328 APIVersion: "build.knative.dev/v1alpha1", 329 // Kind: "Build", 330 Namespace: "default", 331 Name: "kaniko", 332 }, 333 match: "Kind", 334 }, { 335 name: "Invalid Kind", 336 objRef: corev1.ObjectReference{ 337 APIVersion: "build.knative.dev/v1alpha1", 338 Kind: "Build.1", 339 Namespace: "default", 340 Name: "kaniko", 341 }, 342 match: "Kind", 343 }, { 344 name: "Capital in Namespace", 345 objRef: corev1.ObjectReference{ 346 APIVersion: "build.knative.dev/v1alpha1", 347 Kind: "Build", 348 Namespace: "Default", 349 Name: "kaniko", 350 }, 351 match: "Namespace", 352 }, { 353 name: "Domain-separated Namespace", 354 objRef: corev1.ObjectReference{ 355 APIVersion: "build.knative.dev/v1alpha1", 356 Kind: "Build", 357 Namespace: "not.default", 358 Name: "kaniko", 359 }, 360 match: "Namespace", 361 }, { 362 name: "Missing Name", 363 objRef: corev1.ObjectReference{ 364 APIVersion: "build.knative.dev/v1alpha1", 365 Kind: "Build", 366 Namespace: "default", 367 // Name: "kaniko", 368 }, 369 match: "Name", 370 }, { 371 name: "Capital in Name", 372 objRef: corev1.ObjectReference{ 373 APIVersion: "build.knative.dev/v1alpha1", 374 Kind: "Build", 375 Namespace: "default", 376 Name: "Kaniko", 377 }, 378 match: "Name", 379 }, { 380 name: "Bad char in Name", 381 objRef: corev1.ObjectReference{ 382 APIVersion: "build.knative.dev/v1alpha1", 383 Kind: "Build", 384 Namespace: "default", 385 Name: "kaniko_small", 386 }, 387 match: "Name", 388 }, { 389 name: "Missing All", 390 objRef: corev1.ObjectReference{ 391 // APIVersion: "build.knative.dev/v1alpha1", 392 // Kind: "Build", 393 // Namespace: "default", 394 // Name: "kaniko", 395 }, 396 match: "\nAPIVersion:.*\nKind:", 397 }} 398 399 for _, test := range tests { 400 t.Run(test.name, func(t *testing.T) { 401 if err := trk.Track(test.objRef, thing1); err == nil { 402 t.Fatal("Track() = nil, wanted error") 403 } else { 404 match, e2 := regexp.MatchString(test.match, err.Error()) 405 if e2 != nil { 406 t.Fatalf("Failed to compile %q: %v", e2, test.match) 407 } else if !match { 408 t.Fatalf("Track() = %v, wanted match: %s", err, test.match) 409 } 410 } 411 }) 412 } 413 } 414 415 func TestBadReferences(t *testing.T) { 416 trk := New(func(key types.NamespacedName) {}, 10*time.Millisecond) 417 thing1 := &Resource{ 418 TypeMeta: metav1.TypeMeta{ 419 APIVersion: "ref.knative.dev/v1alpha1", 420 Kind: "Thing1", 421 }, 422 ObjectMeta: metav1.ObjectMeta{ 423 Namespace: "ns", 424 Name: "foo", 425 }, 426 } 427 428 tests := []struct { 429 name string 430 objRef Reference 431 match string 432 }{{ 433 name: "Missing All", 434 objRef: Reference{}, 435 match: "\nAPIVersion:.*\nKind:", 436 }, { 437 name: "Name and Selector", 438 objRef: Reference{ 439 APIVersion: "apps/v1", 440 Kind: "Deployment", 441 Namespace: "default", 442 Name: "foo", 443 Selector: &metav1.LabelSelector{}, 444 }, 445 match: "both Name and Selector", 446 }, { 447 name: "bad label key", 448 objRef: Reference{ 449 APIVersion: "apps/v1", 450 Kind: "Deployment", 451 Namespace: "default", 452 Selector: &metav1.LabelSelector{ 453 MatchLabels: map[string]string{ 454 "a bad key": "bar", 455 }, 456 }, 457 }, 458 match: "a bad key", 459 }} 460 461 for _, test := range tests { 462 t.Run(test.name, func(t *testing.T) { 463 if err := trk.TrackReference(test.objRef, thing1); err == nil { 464 t.Fatal("Track() = nil, wanted error") 465 } else { 466 match, e2 := regexp.MatchString(test.match, err.Error()) 467 if e2 != nil { 468 t.Fatalf("Failed to compile %q: %v", e2, test.match) 469 } else if !match { 470 t.Fatalf("Track() = %v, wanted match: %s", err, test.match) 471 } 472 } 473 }) 474 } 475 } 476 477 func TestHappyPathsInexact(t *testing.T) { 478 calls := 0 479 f := func(key types.NamespacedName) { 480 calls++ 481 } 482 483 trk := New(f, 100*time.Millisecond) 484 485 thing1 := &Resource{ 486 TypeMeta: metav1.TypeMeta{ 487 APIVersion: "ref.knative.dev/v1alpha1", 488 Kind: "Thing1", 489 }, 490 ObjectMeta: metav1.ObjectMeta{ 491 Namespace: "ns", 492 Name: "foo", 493 Labels: map[string]string{ 494 "foo": "bar", 495 // An extra label. 496 "baz": "blah", 497 }, 498 }, 499 } 500 or := kmeta.ObjectReference(thing1) 501 ref := Reference{ 502 APIVersion: or.APIVersion, 503 Kind: or.Kind, 504 Namespace: or.Namespace, 505 Selector: &metav1.LabelSelector{ 506 MatchLabels: map[string]string{ 507 "foo": "bar", 508 }, 509 }, 510 } 511 512 thing2 := &Resource{ 513 TypeMeta: metav1.TypeMeta{ 514 APIVersion: "refer.knative.dev/v1alpha1", 515 Kind: "Thing2", 516 }, 517 ObjectMeta: metav1.ObjectMeta{ 518 Namespace: "default", 519 Name: "bar.baz.this-is-fine", 520 }, 521 } 522 523 // Not tracked yet 524 { 525 trk.OnChanged(thing1) 526 if got, want := calls, 0; got != want { 527 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 528 } 529 obs := trk.GetObservers(thing1) 530 if got, want := len(obs), 0; got != want { 531 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 532 } 533 } 534 535 // Tracked gets called 536 { 537 if err := trk.TrackReference(ref, thing2); err != nil { 538 t.Fatal("Track() =", err) 539 } 540 // New registrations should result in an immediate callback. 541 if got, want := calls, 1; got != want { 542 t.Fatalf("Track() = %v, wanted %v", got, want) 543 } 544 545 trk.OnChanged(thing1) 546 if got, want := calls, 2; got != want { 547 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 548 } 549 obs := trk.GetObservers(thing1) 550 if got, want := len(obs), 1; got != want { 551 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 552 } 553 } 554 555 // Still gets called 556 { 557 trk.OnChanged(thing1) 558 if got, want := calls, 3; got != want { 559 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 560 } 561 } 562 563 // Check that after the sleep duration, we stop getting called. 564 time.Sleep(101 * time.Millisecond) 565 { 566 trk.OnChanged(thing1) 567 if got, want := calls, 3; got != want { 568 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 569 } 570 if _, stillThere := trk.(*impl).exact[ref]; stillThere { 571 t.Fatal("Timeout passed, but exact for objectReference is still there") 572 } 573 obs := trk.GetObservers(thing1) 574 if got, want := len(obs), 0; got != want { 575 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 576 } 577 } 578 579 // Starts getting called again 580 { 581 if err := trk.TrackReference(ref, thing2); err != nil { 582 t.Fatal("Track() =", err) 583 } 584 // New registrations should result in an immediate callback. 585 if got, want := calls, 4; got != want { 586 t.Fatalf("Track() = %v, wanted %v", got, want) 587 } 588 589 trk.OnChanged(thing1) 590 if got, want := calls, 5; got != want { 591 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 592 } 593 obs := trk.GetObservers(thing1) 594 if got, want := len(obs), 1; got != want { 595 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 596 } 597 } 598 599 // OnChanged non-accessor 600 { 601 // Check that passing in a resource that doesn't implement 602 // accessor won't panic. 603 trk.OnChanged("not an accessor") 604 605 if got, want := calls, 5; got != want { 606 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 607 } 608 } 609 610 // OnChanged non-accessor in DeletedFinalStateUnknown 611 { 612 // Check that passing in a DeletedFinalStateUnknown instance 613 // with a resource that doesn't implement accessor won't get 614 // Tracked called, and won't panic. 615 trk.OnChanged(cache.DeletedFinalStateUnknown{ 616 Key: "ns/foo", 617 Obj: "not an accessor", 618 }) 619 620 if got, want := calls, 5; got != want { 621 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 622 } 623 } 624 625 // Tracked gets called by DeletedFinalStateUnknown 626 { 627 trk.OnChanged(cache.DeletedFinalStateUnknown{ 628 Key: "ns/foo", 629 Obj: thing1, 630 }) 631 if got, want := calls, 6; got != want { 632 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 633 } 634 } 635 636 // Not called when something about the reference matching changes. 637 { 638 if err := trk.TrackReference(ref, thing2); err != nil { 639 t.Fatal("Track() =", err) 640 } 641 // New registrations should result in an immediate callback. 642 if got, want := calls, 6; got != want { 643 t.Fatalf("Track() = %v, wanted %v", got, want) 644 } 645 obs := trk.GetObservers(thing1) 646 if got, want := len(obs), 1; got != want { 647 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 648 } 649 650 thing1unlabeled := thing1.DeepCopy() 651 thing1unlabeled.Labels = nil 652 trk.OnChanged(thing1unlabeled) 653 if got, want := calls, 6; got != want { 654 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 655 } 656 657 thing1othernamespace := thing1.DeepCopy() 658 thing1othernamespace.Namespace = "another" 659 trk.OnChanged(thing1othernamespace) 660 if got, want := calls, 6; got != want { 661 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 662 } 663 664 thing1othergroup := thing1.DeepCopy() 665 thing1othergroup.APIVersion = "apps/v1" 666 trk.OnChanged(thing1othergroup) 667 if got, want := calls, 6; got != want { 668 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 669 } 670 671 thing1otherkind := thing1.DeepCopy() 672 thing1otherkind.Kind = "deployment" 673 trk.OnChanged(thing1otherkind) 674 if got, want := calls, 6; got != want { 675 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 676 } 677 678 // But with labels is still called 679 trk.OnChanged(thing1) 680 if got, want := calls, 7; got != want { 681 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 682 } 683 } 684 685 // Stops tracking explicitly 686 { 687 trk.OnDeletedObserver(thing2) 688 trk.OnChanged(thing1) 689 if got, want := calls, 7; got != want { 690 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 691 } 692 obs := trk.GetObservers(thing1) 693 if got, want := len(obs), 0; got != want { 694 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 695 } 696 } 697 698 // Track bad object 699 { 700 if err := trk.TrackReference(ref, struct{}{}); err == nil { 701 t.Fatal("Track() = nil, wanted error") 702 } 703 obs := trk.GetObservers(thing1) 704 if got, want := len(obs), 0; got != want { 705 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 706 } 707 } 708 } 709 710 func TestHappyPathsByBoth(t *testing.T) { 711 calls := 0 712 f := func(key types.NamespacedName) { 713 calls++ 714 } 715 716 trk := New(f, 100*time.Millisecond) 717 718 thing1 := &Resource{ 719 TypeMeta: metav1.TypeMeta{ 720 APIVersion: "ref.knative.dev/v1alpha1", 721 Kind: "Thing1", 722 }, 723 ObjectMeta: metav1.ObjectMeta{ 724 Namespace: "ns", 725 Name: "foo", 726 Labels: map[string]string{ 727 "foo": "bar", 728 // An extra label. 729 "baz": "blah", 730 }, 731 }, 732 } 733 or := kmeta.ObjectReference(thing1) 734 ref1 := Reference{ 735 APIVersion: or.APIVersion, 736 Kind: or.Kind, 737 Namespace: or.Namespace, 738 Name: or.Name, 739 } 740 ref2 := Reference{ 741 APIVersion: or.APIVersion, 742 Kind: or.Kind, 743 Namespace: or.Namespace, 744 Selector: &metav1.LabelSelector{ 745 MatchLabels: map[string]string{ 746 "foo": "bar", 747 }, 748 }, 749 } 750 751 thing2 := &Resource{ 752 TypeMeta: metav1.TypeMeta{ 753 APIVersion: "refer.knative.dev/v1alpha1", 754 Kind: "Thing2", 755 }, 756 ObjectMeta: metav1.ObjectMeta{ 757 Namespace: "default", 758 Name: "bar.baz.this-is-fine", 759 }, 760 } 761 762 // Not tracked yet 763 { 764 trk.OnChanged(thing1) 765 if got, want := calls, 0; got != want { 766 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 767 } 768 } 769 770 // Tracked gets called 771 { 772 if err := trk.TrackReference(ref1, thing2); err != nil { 773 t.Fatal("Track() =", err) 774 } 775 // New registrations should result in an immediate callback. 776 if got, want := calls, 1; got != want { 777 t.Fatalf("Track() = %v, wanted %v", got, want) 778 } 779 if got, want := len(trk.GetObservers(thing1)), 1; got != want { 780 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 781 } 782 783 if err := trk.TrackReference(ref2, thing2); err != nil { 784 t.Fatal("Track() =", err) 785 } 786 // New registrations should result in an immediate callback. 787 if got, want := calls, 2; got != want { 788 t.Fatalf("Track() = %v, wanted %v", got, want) 789 } 790 if got, want := len(trk.GetObservers(thing1)), 2; got != want { 791 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 792 } 793 794 // The callback should be called for each of the tracks (exact and inexact) 795 trk.OnChanged(thing1) 796 if got, want := calls, 4; got != want { 797 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 798 } 799 } 800 801 // Stops tracking explicitly 802 { 803 trk.OnDeletedObserver(thing2) 804 trk.OnChanged(thing1) 805 if got, want := calls, 4; got != want { 806 t.Fatalf("OnChanged() = %v, wanted %v", got, want) 807 } 808 if got, want := len(trk.GetObservers(thing1)), 0; got != want { 809 t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want) 810 } 811 if got, want := len(trk.(*impl).exact), 0; got != want { 812 t.Fatalf("OnDeletedObserver() did not clean up exact map: len = %v, wanted %v", got, want) 813 } 814 if got, want := len(trk.(*impl).inexact), 0; got != want { 815 t.Fatalf("OnDeletedObserver() did not clean up inexact map: len = %v, wanted %v", got, want) 816 } 817 } 818 819 // Verify inexact map cleanup on expiry when exact tracker is also present. 820 { 821 if err := trk.TrackReference(ref1, thing2); err != nil { 822 t.Fatal("Track() =", err) 823 } 824 if err := trk.TrackReference(ref2, thing2); err != nil { 825 t.Fatal("Track() =", err) 826 } 827 828 time.Sleep(101 * time.Millisecond) 829 830 trk.GetObservers(thing1) 831 832 if got, want := len(trk.(*impl).exact), 0; got != want { 833 t.Fatalf("Timeout passed, but exact map not cleaned up: len = %v, wanted %v", got, want) 834 } 835 if got, want := len(trk.(*impl).inexact), 0; got != want { 836 t.Fatalf("Timeout passed, but inexact map not cleaned up: len = %v, wanted %v", got, want) 837 } 838 } 839 }