k8s.io/kubernetes@v1.29.3/pkg/apis/core/v1/helper/helpers_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes 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 helper 18 19 import ( 20 "fmt" 21 "reflect" 22 "testing" 23 24 v1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/api/resource" 26 "k8s.io/apimachinery/pkg/labels" 27 ) 28 29 func TestIsNativeResource(t *testing.T) { 30 testCases := []struct { 31 resourceName v1.ResourceName 32 expectVal bool 33 }{ 34 { 35 resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", 36 expectVal: true, 37 }, 38 { 39 resourceName: "kubernetes.io/resource-foo", 40 expectVal: true, 41 }, 42 { 43 resourceName: "foo", 44 expectVal: true, 45 }, 46 { 47 resourceName: "a/b", 48 expectVal: false, 49 }, 50 { 51 resourceName: "", 52 expectVal: true, 53 }, 54 } 55 56 for _, tc := range testCases { 57 tc := tc 58 t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { 59 t.Parallel() 60 v := IsNativeResource(tc.resourceName) 61 if v != tc.expectVal { 62 t.Errorf("Got %v but expected %v", v, tc.expectVal) 63 } 64 }) 65 } 66 } 67 68 func TestHugePageSizeFromResourceName(t *testing.T) { 69 expected100m, _ := resource.ParseQuantity("100m") 70 testCases := []struct { 71 resourceName v1.ResourceName 72 expectVal resource.Quantity 73 expectErr bool 74 }{ 75 { 76 resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", 77 expectVal: resource.Quantity{}, 78 expectErr: true, 79 }, 80 { 81 resourceName: "hugepages-", 82 expectVal: resource.Quantity{}, 83 expectErr: true, 84 }, 85 { 86 resourceName: "hugepages-100m", 87 expectVal: expected100m, 88 expectErr: false, 89 }, 90 { 91 resourceName: "", 92 expectVal: resource.Quantity{}, 93 expectErr: true, 94 }, 95 } 96 97 for i, tc := range testCases { 98 i := i 99 tc := tc 100 t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { 101 t.Parallel() 102 v, err := HugePageSizeFromResourceName(tc.resourceName) 103 if err == nil && tc.expectErr { 104 t.Errorf("[%v]expected error but got none.", i) 105 } 106 if err != nil && !tc.expectErr { 107 t.Errorf("[%v]did not expect error but got: %v", i, err) 108 } 109 if v != tc.expectVal { 110 t.Errorf("Got %v but expected %v", v, tc.expectVal) 111 } 112 }) 113 } 114 } 115 116 func TestHugePageSizeFromMedium(t *testing.T) { 117 testCases := []struct { 118 description string 119 medium v1.StorageMedium 120 expectVal resource.Quantity 121 expectErr bool 122 }{ 123 { 124 description: "Invalid hugepages medium", 125 medium: "Memory", 126 expectVal: resource.Quantity{}, 127 expectErr: true, 128 }, 129 { 130 description: "Invalid hugepages medium", 131 medium: "Memory", 132 expectVal: resource.Quantity{}, 133 expectErr: true, 134 }, 135 { 136 description: "Invalid: HugePages without size", 137 medium: "HugePages", 138 expectVal: resource.Quantity{}, 139 expectErr: true, 140 }, 141 { 142 description: "Invalid: HugePages without size", 143 medium: "HugePages", 144 expectVal: resource.Quantity{}, 145 expectErr: true, 146 }, 147 { 148 description: "Valid: HugePages-1Gi", 149 medium: "HugePages-1Gi", 150 expectVal: resource.MustParse("1Gi"), 151 expectErr: false, 152 }, 153 { 154 description: "Valid: HugePages-2Mi", 155 medium: "HugePages-2Mi", 156 expectVal: resource.MustParse("2Mi"), 157 expectErr: false, 158 }, 159 { 160 description: "Valid: HugePages-64Ki", 161 medium: "HugePages-64Ki", 162 expectVal: resource.MustParse("64Ki"), 163 expectErr: false, 164 }, 165 } 166 for i, tc := range testCases { 167 i := i 168 tc := tc 169 t.Run(tc.description, func(t *testing.T) { 170 t.Parallel() 171 v, err := HugePageSizeFromMedium(tc.medium) 172 if err == nil && tc.expectErr { 173 t.Errorf("[%v]expected error but got none.", i) 174 } 175 if err != nil && !tc.expectErr { 176 t.Errorf("[%v]did not expect error but got: %v", i, err) 177 } 178 if v != tc.expectVal { 179 t.Errorf("Got %v but expected %v", v, tc.expectVal) 180 } 181 }) 182 } 183 } 184 185 func TestIsOvercommitAllowed(t *testing.T) { 186 testCases := []struct { 187 resourceName v1.ResourceName 188 expectVal bool 189 }{ 190 { 191 resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo", 192 expectVal: true, 193 }, 194 { 195 resourceName: "kubernetes.io/resource-foo", 196 expectVal: true, 197 }, 198 { 199 resourceName: "hugepages-100m", 200 expectVal: false, 201 }, 202 { 203 resourceName: "", 204 expectVal: true, 205 }, 206 } 207 208 for _, tc := range testCases { 209 tc := tc 210 t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) { 211 t.Parallel() 212 v := IsOvercommitAllowed(tc.resourceName) 213 if v != tc.expectVal { 214 t.Errorf("Got %v but expected %v", v, tc.expectVal) 215 } 216 }) 217 } 218 } 219 220 func TestGetAccessModesFromString(t *testing.T) { 221 modes := GetAccessModesFromString("ROX") 222 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 223 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 224 } 225 226 modes = GetAccessModesFromString("ROX,RWX") 227 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 228 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 229 } 230 if !ContainsAccessMode(modes, v1.ReadWriteMany) { 231 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) 232 } 233 234 modes = GetAccessModesFromString("RWO,ROX,RWX") 235 if !ContainsAccessMode(modes, v1.ReadWriteOnce) { 236 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes) 237 } 238 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 239 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 240 } 241 if !ContainsAccessMode(modes, v1.ReadWriteMany) { 242 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) 243 } 244 245 modes = GetAccessModesFromString("RWO,ROX,RWX,RWOP") 246 if !ContainsAccessMode(modes, v1.ReadWriteOnce) { 247 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes) 248 } 249 if !ContainsAccessMode(modes, v1.ReadOnlyMany) { 250 t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes) 251 } 252 if !ContainsAccessMode(modes, v1.ReadWriteMany) { 253 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes) 254 } 255 if !ContainsAccessMode(modes, v1.ReadWriteOncePod) { 256 t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOncePod, modes) 257 } 258 } 259 260 func TestRemoveDuplicateAccessModes(t *testing.T) { 261 modes := []v1.PersistentVolumeAccessMode{ 262 v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadOnlyMany, v1.ReadOnlyMany, 263 } 264 modes = removeDuplicateAccessModes(modes) 265 if len(modes) != 2 { 266 t.Errorf("Expected 2 distinct modes in set but found %v", len(modes)) 267 } 268 } 269 270 func TestTopologySelectorRequirementsAsSelector(t *testing.T) { 271 mustParse := func(s string) labels.Selector { 272 out, e := labels.Parse(s) 273 if e != nil { 274 panic(e) 275 } 276 return out 277 } 278 tc := []struct { 279 in []v1.TopologySelectorLabelRequirement 280 out labels.Selector 281 expectErr bool 282 }{ 283 {in: nil, out: labels.Nothing()}, 284 {in: []v1.TopologySelectorLabelRequirement{}, out: labels.Nothing()}, 285 { 286 in: []v1.TopologySelectorLabelRequirement{{ 287 Key: "foo", 288 Values: []string{"bar", "baz"}, 289 }}, 290 out: mustParse("foo in (baz,bar)"), 291 }, 292 { 293 in: []v1.TopologySelectorLabelRequirement{{ 294 Key: "foo", 295 Values: []string{}, 296 }}, 297 expectErr: true, 298 }, 299 { 300 in: []v1.TopologySelectorLabelRequirement{ 301 { 302 Key: "foo", 303 Values: []string{"bar", "baz"}, 304 }, 305 { 306 Key: "invalid", 307 Values: []string{}, 308 }, 309 }, 310 expectErr: true, 311 }, 312 { 313 in: []v1.TopologySelectorLabelRequirement{{ 314 Key: "/invalidkey", 315 Values: []string{"bar", "baz"}, 316 }}, 317 expectErr: true, 318 }, 319 } 320 321 for i, tc := range tc { 322 out, err := TopologySelectorRequirementsAsSelector(tc.in) 323 if err == nil && tc.expectErr { 324 t.Errorf("[%v]expected error but got none.", i) 325 } 326 if err != nil && !tc.expectErr { 327 t.Errorf("[%v]did not expect error but got: %v", i, err) 328 } 329 if !reflect.DeepEqual(out, tc.out) { 330 t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) 331 } 332 } 333 } 334 335 func TestMatchTopologySelectorTerms(t *testing.T) { 336 type args struct { 337 topologySelectorTerms []v1.TopologySelectorTerm 338 labels labels.Set 339 } 340 341 tests := []struct { 342 name string 343 args args 344 want bool 345 }{ 346 { 347 name: "nil term list", 348 args: args{ 349 topologySelectorTerms: nil, 350 labels: nil, 351 }, 352 want: true, 353 }, 354 { 355 name: "nil term", 356 args: args{ 357 topologySelectorTerms: []v1.TopologySelectorTerm{ 358 { 359 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{}, 360 }, 361 }, 362 labels: nil, 363 }, 364 want: false, 365 }, 366 { 367 name: "label matches MatchLabelExpressions terms", 368 args: args{ 369 topologySelectorTerms: []v1.TopologySelectorTerm{ 370 { 371 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 372 Key: "label_1", 373 Values: []string{"label_1_val"}, 374 }}, 375 }, 376 }, 377 labels: map[string]string{"label_1": "label_1_val"}, 378 }, 379 want: true, 380 }, 381 { 382 name: "label does not match MatchLabelExpressions terms", 383 args: args{ 384 topologySelectorTerms: []v1.TopologySelectorTerm{ 385 { 386 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 387 Key: "label_1", 388 Values: []string{"label_1_val"}, 389 }}, 390 }, 391 }, 392 labels: map[string]string{"label_1": "label_1_val-failed"}, 393 }, 394 want: false, 395 }, 396 { 397 name: "multi-values in one requirement, one matched", 398 args: args{ 399 topologySelectorTerms: []v1.TopologySelectorTerm{ 400 { 401 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 402 Key: "label_1", 403 Values: []string{"label_1_val1", "label_1_val2"}, 404 }}, 405 }, 406 }, 407 labels: map[string]string{"label_1": "label_1_val2"}, 408 }, 409 want: true, 410 }, 411 { 412 name: "multi-terms was set, one matched", 413 args: args{ 414 topologySelectorTerms: []v1.TopologySelectorTerm{ 415 { 416 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 417 Key: "label_1", 418 Values: []string{"label_1_val"}, 419 }}, 420 }, 421 { 422 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{ 423 Key: "label_2", 424 Values: []string{"label_2_val"}, 425 }}, 426 }, 427 }, 428 labels: map[string]string{ 429 "label_2": "label_2_val", 430 }, 431 }, 432 want: true, 433 }, 434 { 435 name: "multi-requirement in one term, fully matched", 436 args: args{ 437 topologySelectorTerms: []v1.TopologySelectorTerm{ 438 { 439 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ 440 { 441 Key: "label_1", 442 Values: []string{"label_1_val"}, 443 }, 444 { 445 Key: "label_2", 446 Values: []string{"label_2_val"}, 447 }, 448 }, 449 }, 450 }, 451 labels: map[string]string{ 452 "label_1": "label_1_val", 453 "label_2": "label_2_val", 454 }, 455 }, 456 want: true, 457 }, 458 { 459 name: "multi-requirement in one term, partial matched", 460 args: args{ 461 topologySelectorTerms: []v1.TopologySelectorTerm{ 462 { 463 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ 464 { 465 Key: "label_1", 466 Values: []string{"label_1_val"}, 467 }, 468 { 469 Key: "label_2", 470 Values: []string{"label_2_val"}, 471 }, 472 }, 473 }, 474 }, 475 labels: map[string]string{ 476 "label_1": "label_1_val-failed", 477 "label_2": "label_2_val", 478 }, 479 }, 480 want: false, 481 }, 482 } 483 484 for _, tt := range tests { 485 t.Run(tt.name, func(t *testing.T) { 486 if got := MatchTopologySelectorTerms(tt.args.topologySelectorTerms, tt.args.labels); got != tt.want { 487 t.Errorf("MatchTopologySelectorTermsORed() = %v, want %v", got, tt.want) 488 } 489 }) 490 } 491 } 492 493 func TestNodeSelectorRequirementKeyExistsInNodeSelectorTerms(t *testing.T) { 494 tests := []struct { 495 name string 496 reqs []v1.NodeSelectorRequirement 497 terms []v1.NodeSelectorTerm 498 exists bool 499 }{ 500 { 501 name: "empty set of keys in empty set of terms", 502 reqs: []v1.NodeSelectorRequirement{}, 503 terms: []v1.NodeSelectorTerm{}, 504 exists: false, 505 }, 506 { 507 name: "key existence in terms with all keys specified", 508 reqs: []v1.NodeSelectorRequirement{ 509 { 510 Key: "key1", 511 Operator: v1.NodeSelectorOpIn, 512 Values: []string{"test-value1"}, 513 }, 514 { 515 Key: "key2", 516 Operator: v1.NodeSelectorOpIn, 517 Values: []string{"test-value2"}, 518 }, 519 }, 520 terms: []v1.NodeSelectorTerm{ 521 { 522 MatchExpressions: []v1.NodeSelectorRequirement{ 523 { 524 Key: "key2", 525 Operator: v1.NodeSelectorOpIn, 526 Values: []string{"test-value2"}, 527 }, 528 { 529 Key: "key3", 530 Operator: v1.NodeSelectorOpIn, 531 Values: []string{"test-value3"}, 532 }, 533 }, 534 }, 535 { 536 MatchExpressions: []v1.NodeSelectorRequirement{ 537 { 538 Key: "key1", 539 Operator: v1.NodeSelectorOpIn, 540 Values: []string{"test-value11, test-value12"}, 541 }, 542 { 543 Key: "key4", 544 Operator: v1.NodeSelectorOpIn, 545 Values: []string{"test-value41, test-value42"}, 546 }, 547 }, 548 }, 549 }, 550 exists: true, 551 }, 552 { 553 name: "key existence in terms with one of the keys specified", 554 reqs: []v1.NodeSelectorRequirement{ 555 { 556 Key: "key1", 557 Operator: v1.NodeSelectorOpIn, 558 Values: []string{"test-value1"}, 559 }, 560 { 561 Key: "key2", 562 Operator: v1.NodeSelectorOpIn, 563 Values: []string{"test-value2"}, 564 }, 565 { 566 Key: "key3", 567 Operator: v1.NodeSelectorOpIn, 568 Values: []string{"test-value3"}, 569 }, 570 { 571 Key: "key6", 572 Operator: v1.NodeSelectorOpIn, 573 Values: []string{"test-value6"}, 574 }, 575 }, 576 terms: []v1.NodeSelectorTerm{ 577 { 578 MatchExpressions: []v1.NodeSelectorRequirement{ 579 { 580 Key: "key2", 581 Operator: v1.NodeSelectorOpIn, 582 Values: []string{"test-value2"}, 583 }, { 584 Key: "key4", 585 Operator: v1.NodeSelectorOpIn, 586 Values: []string{"test-value4"}, 587 }, 588 }, 589 }, 590 { 591 MatchExpressions: []v1.NodeSelectorRequirement{ 592 { 593 Key: "key5", 594 Operator: v1.NodeSelectorOpIn, 595 Values: []string{"test-value5"}, 596 }, 597 }, 598 }, 599 }, 600 exists: true, 601 }, 602 { 603 name: "key existence in terms without any of the keys specified", 604 reqs: []v1.NodeSelectorRequirement{ 605 { 606 Key: "key2", 607 Operator: v1.NodeSelectorOpIn, 608 Values: []string{"test-value2"}, 609 }, 610 { 611 Key: "key3", 612 Operator: v1.NodeSelectorOpIn, 613 Values: []string{"test-value3"}, 614 }, 615 }, 616 terms: []v1.NodeSelectorTerm{ 617 { 618 MatchExpressions: []v1.NodeSelectorRequirement{ 619 { 620 Key: "key4", 621 Operator: v1.NodeSelectorOpIn, 622 Values: []string{"test-value"}, 623 }, 624 { 625 Key: "key5", 626 Operator: v1.NodeSelectorOpIn, 627 Values: []string{"test-value"}, 628 }, 629 }, 630 }, 631 { 632 MatchExpressions: []v1.NodeSelectorRequirement{ 633 { 634 Key: "key6", 635 Operator: v1.NodeSelectorOpIn, 636 Values: []string{"test-value"}, 637 }, 638 }, 639 }, 640 { 641 MatchExpressions: []v1.NodeSelectorRequirement{ 642 { 643 Key: "key7", 644 Operator: v1.NodeSelectorOpIn, 645 Values: []string{"test-value"}, 646 }, 647 { 648 Key: "key8", 649 Operator: v1.NodeSelectorOpIn, 650 Values: []string{"test-value"}, 651 }, 652 }, 653 }, 654 }, 655 exists: false, 656 }, 657 { 658 name: "key existence in empty set of terms", 659 reqs: []v1.NodeSelectorRequirement{ 660 { 661 Key: "key2", 662 Operator: v1.NodeSelectorOpIn, 663 Values: []string{"test-value2"}, 664 }, 665 { 666 Key: "key3", 667 Operator: v1.NodeSelectorOpIn, 668 Values: []string{"test-value3"}, 669 }, 670 }, 671 terms: []v1.NodeSelectorTerm{}, 672 exists: false, 673 }, 674 } 675 for _, test := range tests { 676 keyExists := NodeSelectorRequirementKeysExistInNodeSelectorTerms(test.reqs, test.terms) 677 if test.exists != keyExists { 678 t.Errorf("test %s failed. Expected %v but got %v", test.name, test.exists, keyExists) 679 } 680 } 681 } 682 683 func TestHugePageUnitSizeFromByteSize(t *testing.T) { 684 tests := []struct { 685 size int64 686 expected string 687 wantErr bool 688 }{ 689 { 690 size: 1024, 691 expected: "1KB", 692 wantErr: false, 693 }, 694 { 695 size: 33554432, 696 expected: "32MB", 697 wantErr: false, 698 }, 699 { 700 size: 3221225472, 701 expected: "3GB", 702 wantErr: false, 703 }, 704 { 705 size: 1024 * 1024 * 1023 * 3, 706 expected: "3069MB", 707 wantErr: true, 708 }, 709 } 710 for _, test := range tests { 711 size := test.size 712 result, err := HugePageUnitSizeFromByteSize(size) 713 if err != nil { 714 if test.wantErr { 715 t.Logf("HugePageUnitSizeFromByteSize() expected error = %v", err) 716 } else { 717 t.Errorf("HugePageUnitSizeFromByteSize() error = %v, wantErr %v", err, test.wantErr) 718 } 719 continue 720 } 721 if test.expected != result { 722 t.Errorf("HugePageUnitSizeFromByteSize() expected %v but got %v", test.expected, result) 723 } 724 } 725 } 726 727 func TestLoadBalancerStatusEqual(t *testing.T) { 728 729 testCases := []struct { 730 left *v1.LoadBalancerStatus 731 right *v1.LoadBalancerStatus 732 name string 733 expectVal bool 734 }{{ 735 name: "left equals right", 736 left: &v1.LoadBalancerStatus{ 737 Ingress: []v1.LoadBalancerIngress{{ 738 IP: "1.1.1.1", 739 Hostname: "host1", 740 }}, 741 }, 742 right: &v1.LoadBalancerStatus{ 743 Ingress: []v1.LoadBalancerIngress{{ 744 IP: "1.1.1.1", 745 Hostname: "host1", 746 }}, 747 }, 748 expectVal: true, 749 }, { 750 name: "length of LoadBalancerIngress slice is not equal", 751 left: &v1.LoadBalancerStatus{ 752 Ingress: []v1.LoadBalancerIngress{{ 753 IP: "1.1.1.1", 754 Hostname: "host1", 755 }, { 756 IP: "1.1.1.2", 757 Hostname: "host1", 758 }}, 759 }, 760 right: &v1.LoadBalancerStatus{ 761 Ingress: []v1.LoadBalancerIngress{{ 762 IP: "1.1.1.1", 763 Hostname: "host1", 764 }}, 765 }, 766 expectVal: false, 767 }, { 768 name: "LoadBalancerIngress ip is not equal", 769 left: &v1.LoadBalancerStatus{ 770 Ingress: []v1.LoadBalancerIngress{{ 771 IP: "1.1.1.2", 772 Hostname: "host1", 773 }}, 774 }, 775 right: &v1.LoadBalancerStatus{ 776 Ingress: []v1.LoadBalancerIngress{{ 777 IP: "1.1.1.1", 778 Hostname: "host1", 779 }}, 780 }, 781 expectVal: false, 782 }, { 783 name: "LoadBalancerIngress hostname is not equal", 784 left: &v1.LoadBalancerStatus{ 785 Ingress: []v1.LoadBalancerIngress{{ 786 IP: "1.1.1.1", 787 Hostname: "host2", 788 }}, 789 }, 790 right: &v1.LoadBalancerStatus{ 791 Ingress: []v1.LoadBalancerIngress{{ 792 IP: "1.1.1.1", 793 Hostname: "host1", 794 }}, 795 }, 796 expectVal: false, 797 }} 798 799 for _, tc := range testCases { 800 t.Run(tc.name, func(t *testing.T) { 801 v := LoadBalancerStatusEqual(tc.left, tc.right) 802 if v != tc.expectVal { 803 t.Errorf("test %s failed. left input=%v, right input=%v, Got %v but expected %v", 804 tc.name, tc.left, tc.right, v, tc.expectVal) 805 } 806 }) 807 } 808 }