gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/balancer/rls/internal/keys/builder_test.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package keys 20 21 import ( 22 "fmt" 23 "strings" 24 "testing" 25 26 rlspb "gitee.com/ks-custle/core-gm/grpc/internal/proto/grpc_lookup_v1" 27 "gitee.com/ks-custle/core-gm/grpc/metadata" 28 "github.com/google/go-cmp/cmp" 29 ) 30 31 var ( 32 goodKeyBuilder1 = &rlspb.GrpcKeyBuilder{ 33 Names: []*rlspb.GrpcKeyBuilder_Name{ 34 {Service: "gFoo"}, 35 }, 36 Headers: []*rlspb.NameMatcher{ 37 {Key: "k1", Names: []string{"n1"}}, 38 {Key: "k2", Names: []string{"n1"}}, 39 }, 40 ExtraKeys: &rlspb.GrpcKeyBuilder_ExtraKeys{ 41 Host: "host", 42 Service: "service", 43 Method: "method", 44 }, 45 ConstantKeys: map[string]string{ 46 "const-key-1": "const-val-1", 47 "const-key-2": "const-val-2", 48 }, 49 } 50 goodKeyBuilder2 = &rlspb.GrpcKeyBuilder{ 51 Names: []*rlspb.GrpcKeyBuilder_Name{ 52 {Service: "gBar", Method: "method1"}, 53 {Service: "gFoobar"}, 54 }, 55 Headers: []*rlspb.NameMatcher{ 56 {Key: "k1", Names: []string{"n1", "n2"}}, 57 }, 58 } 59 ) 60 61 func TestMakeBuilderMap(t *testing.T) { 62 gFooBuilder := builder{ 63 headerKeys: []matcher{{key: "k1", names: []string{"n1"}}, {key: "k2", names: []string{"n1"}}}, 64 constantKeys: map[string]string{ 65 "const-key-1": "const-val-1", 66 "const-key-2": "const-val-2", 67 }, 68 hostKey: "host", 69 serviceKey: "service", 70 methodKey: "method", 71 } 72 wantBuilderMap1 := map[string]builder{"/gFoo/": gFooBuilder} 73 wantBuilderMap2 := map[string]builder{ 74 "/gFoo/": gFooBuilder, 75 "/gBar/method1": {headerKeys: []matcher{{key: "k1", names: []string{"n1", "n2"}}}}, 76 "/gFoobar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1", "n2"}}}}, 77 } 78 79 tests := []struct { 80 desc string 81 cfg *rlspb.RouteLookupConfig 82 wantBuilderMap BuilderMap 83 }{ 84 { 85 desc: "One good GrpcKeyBuilder", 86 cfg: &rlspb.RouteLookupConfig{ 87 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1}, 88 }, 89 wantBuilderMap: wantBuilderMap1, 90 }, 91 { 92 desc: "Two good GrpcKeyBuilders", 93 cfg: &rlspb.RouteLookupConfig{ 94 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1, goodKeyBuilder2}, 95 }, 96 wantBuilderMap: wantBuilderMap2, 97 }, 98 } 99 100 for _, test := range tests { 101 t.Run(test.desc, func(t *testing.T) { 102 builderMap, err := MakeBuilderMap(test.cfg) 103 if err != nil || !builderMap.Equal(test.wantBuilderMap) { 104 t.Errorf("MakeBuilderMap(%+v) returned {%v, %v}, want: {%v, nil}", test.cfg, builderMap, err, test.wantBuilderMap) 105 } 106 }) 107 } 108 } 109 110 func TestMakeBuilderMapErrors(t *testing.T) { 111 tests := []struct { 112 desc string 113 cfg *rlspb.RouteLookupConfig 114 wantErrPrefix string 115 }{ 116 { 117 desc: "No GrpcKeyBuilder", 118 cfg: &rlspb.RouteLookupConfig{}, 119 wantErrPrefix: "rls: RouteLookupConfig does not contain any GrpcKeyBuilder", 120 }, 121 { 122 desc: "Two GrpcKeyBuilders with same Name", 123 cfg: &rlspb.RouteLookupConfig{ 124 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1, goodKeyBuilder1}, 125 }, 126 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated Name field", 127 }, 128 { 129 desc: "GrpcKeyBuilder with empty Service field", 130 cfg: &rlspb.RouteLookupConfig{ 131 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{ 132 { 133 Names: []*rlspb.GrpcKeyBuilder_Name{ 134 {Service: "bFoo", Method: "method1"}, 135 {Service: "bBar"}, 136 {Method: "method1"}, 137 }, 138 Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}}, 139 }, 140 goodKeyBuilder1, 141 }, 142 }, 143 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains a Name field with no Service", 144 }, 145 { 146 desc: "GrpcKeyBuilder with no Name", 147 cfg: &rlspb.RouteLookupConfig{ 148 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{{}, goodKeyBuilder1}, 149 }, 150 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig does not contain any Name", 151 }, 152 { 153 desc: "GrpcKeyBuilder with requiredMatch field set", 154 cfg: &rlspb.RouteLookupConfig{ 155 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{ 156 { 157 Names: []*rlspb.GrpcKeyBuilder_Name{{Service: "bFoo", Method: "method1"}}, 158 Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}, RequiredMatch: true}}, 159 }, 160 goodKeyBuilder1, 161 }, 162 }, 163 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig has required_match field set", 164 }, 165 { 166 desc: "GrpcKeyBuilder two headers with same key", 167 cfg: &rlspb.RouteLookupConfig{ 168 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{ 169 { 170 Names: []*rlspb.GrpcKeyBuilder_Name{ 171 {Service: "gBar", Method: "method1"}, 172 {Service: "gFoobar"}, 173 }, 174 Headers: []*rlspb.NameMatcher{ 175 {Key: "k1", Names: []string{"n1", "n2"}}, 176 {Key: "k1", Names: []string{"n1", "n2"}}, 177 }, 178 }, 179 goodKeyBuilder1, 180 }, 181 }, 182 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"k1\" across headers, constant_keys and extra_keys", 183 }, 184 { 185 desc: "GrpcKeyBuilder repeated keys across headers and constant_keys", 186 cfg: &rlspb.RouteLookupConfig{ 187 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{ 188 { 189 Names: []*rlspb.GrpcKeyBuilder_Name{ 190 {Service: "gBar", Method: "method1"}, 191 {Service: "gFoobar"}, 192 }, 193 Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}}, 194 ConstantKeys: map[string]string{"k1": "v1"}, 195 }, 196 goodKeyBuilder1, 197 }, 198 }, 199 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"k1\" across headers, constant_keys and extra_keys", 200 }, 201 { 202 desc: "GrpcKeyBuilder repeated keys across headers and extra_keys", 203 cfg: &rlspb.RouteLookupConfig{ 204 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{ 205 { 206 Names: []*rlspb.GrpcKeyBuilder_Name{ 207 {Service: "gBar", Method: "method1"}, 208 {Service: "gFoobar"}, 209 }, 210 Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}}, 211 ExtraKeys: &rlspb.GrpcKeyBuilder_ExtraKeys{Method: "k1"}, 212 }, 213 goodKeyBuilder1, 214 }, 215 }, 216 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"k1\" in extra_keys from constant_keys or headers", 217 }, 218 { 219 desc: "GrpcKeyBuilder repeated keys across constant_keys and extra_keys", 220 cfg: &rlspb.RouteLookupConfig{ 221 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{ 222 { 223 Names: []*rlspb.GrpcKeyBuilder_Name{ 224 {Service: "gBar", Method: "method1"}, 225 {Service: "gFoobar"}, 226 }, 227 Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}}, 228 ConstantKeys: map[string]string{"host": "v1"}, 229 ExtraKeys: &rlspb.GrpcKeyBuilder_ExtraKeys{Host: "host"}, 230 }, 231 goodKeyBuilder1, 232 }, 233 }, 234 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains repeated key \"host\" in extra_keys from constant_keys or headers", 235 }, 236 { 237 desc: "GrpcKeyBuilder with slash in method name", 238 cfg: &rlspb.RouteLookupConfig{ 239 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{ 240 { 241 Names: []*rlspb.GrpcKeyBuilder_Name{{Service: "gBar", Method: "method1/foo"}}, 242 Headers: []*rlspb.NameMatcher{{Key: "k1", Names: []string{"n1", "n2"}}}, 243 }, 244 }, 245 }, 246 wantErrPrefix: "rls: GrpcKeyBuilder in RouteLookupConfig contains a method with a slash", 247 }, 248 } 249 250 for _, test := range tests { 251 t.Run(test.desc, func(t *testing.T) { 252 builderMap, err := MakeBuilderMap(test.cfg) 253 if builderMap != nil || !strings.HasPrefix(fmt.Sprint(err), test.wantErrPrefix) { 254 t.Errorf("MakeBuilderMap(%+v) returned {%v, %v}, want: {nil, %v}", test.cfg, builderMap, err, test.wantErrPrefix) 255 } 256 }) 257 } 258 } 259 260 func TestRLSKey(t *testing.T) { 261 bm, err := MakeBuilderMap(&rlspb.RouteLookupConfig{ 262 GrpcKeybuilders: []*rlspb.GrpcKeyBuilder{goodKeyBuilder1, goodKeyBuilder2}, 263 }) 264 if err != nil { 265 t.Fatalf("MakeBuilderMap() failed: %v", err) 266 } 267 268 tests := []struct { 269 desc string 270 path string 271 md metadata.MD 272 wantKM KeyMap 273 }{ 274 { 275 // No keyBuilder is found for the provided service. 276 desc: "service not found in key builder map", 277 path: "/notFoundService/method", 278 md: metadata.Pairs("n1", "v1", "n2", "v2"), 279 wantKM: KeyMap{}, 280 }, 281 { 282 // No keyBuilder is found for the provided method. 283 desc: "method not found in key builder map", 284 path: "/gBar/notFoundMethod", 285 md: metadata.Pairs("n1", "v1", "n2", "v2"), 286 wantKM: KeyMap{}, 287 }, 288 { 289 // A keyBuilder is found, but none of the headers match. 290 desc: "directPathMatch-NoMatchingKey", 291 path: "/gBar/method1", 292 md: metadata.Pairs("notMatchingKey", "v1"), 293 wantKM: KeyMap{Map: map[string]string{}, Str: ""}, 294 }, 295 { 296 // A keyBuilder is found, and a single headers matches. 297 desc: "directPathMatch-SingleKey", 298 path: "/gBar/method1", 299 md: metadata.Pairs("n1", "v1"), 300 wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"}, 301 }, 302 { 303 // A keyBuilder is found, and multiple headers match, but the first 304 // match is chosen. 305 desc: "directPathMatch-FirstMatchingKey", 306 path: "/gBar/method1", 307 md: metadata.Pairs("n2", "v2", "n1", "v1"), 308 wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"}, 309 }, 310 { 311 // A keyBuilder is found as a wildcard match, but none of the 312 // headers match. 313 desc: "wildcardPathMatch-NoMatchingKey", 314 path: "/gFoobar/method1", 315 md: metadata.Pairs("notMatchingKey", "v1"), 316 wantKM: KeyMap{Map: map[string]string{}, Str: ""}, 317 }, 318 { 319 // A keyBuilder is found as a wildcard match, and a single headers 320 // matches. 321 desc: "wildcardPathMatch-SingleKey", 322 path: "/gFoobar/method1", 323 md: metadata.Pairs("n1", "v1"), 324 wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"}, 325 }, 326 { 327 // A keyBuilder is found as a wildcard match, and multiple headers 328 // match, but the first match is chosen. 329 desc: "wildcardPathMatch-FirstMatchingKey", 330 path: "/gFoobar/method1", 331 md: metadata.Pairs("n2", "v2", "n1", "v1"), 332 wantKM: KeyMap{Map: map[string]string{"k1": "v1"}, Str: "k1=v1"}, 333 }, 334 { 335 // Multiple headerKeys find hits in the provided request headers. 336 desc: "multipleMatchers", 337 path: "/gFoo/method1", 338 md: metadata.Pairs("n2", "v2", "n1", "v1"), 339 wantKM: KeyMap{ 340 Map: map[string]string{ 341 "const-key-1": "const-val-1", 342 "const-key-2": "const-val-2", 343 "host": "dummy-host", 344 "service": "/gFoo/", 345 "method": "method1", 346 "k1": "v1", 347 "k2": "v1", 348 }, 349 Str: "const-key-1=const-val-1,const-key-2=const-val-2,host=dummy-host,k1=v1,k2=v1,method=method1,service=/gFoo/", 350 }, 351 }, 352 { 353 // A match is found for a header which is specified multiple times. 354 // So, the values are joined with commas separating them. 355 desc: "commaSeparated", 356 path: "/gBar/method1", 357 md: metadata.Pairs("n1", "v1", "n1", "v2", "n1", "v3"), 358 wantKM: KeyMap{Map: map[string]string{"k1": "v1,v2,v3"}, Str: "k1=v1,v2,v3"}, 359 }, 360 } 361 362 for _, test := range tests { 363 t.Run(test.desc, func(t *testing.T) { 364 if gotKM := bm.RLSKey(test.md, "dummy-host", test.path); !cmp.Equal(gotKM, test.wantKM) { 365 t.Errorf("RLSKey(%+v, %s) = %+v, want %+v", test.md, test.path, gotKM, test.wantKM) 366 } 367 }) 368 } 369 } 370 371 func TestMapToString(t *testing.T) { 372 tests := []struct { 373 desc string 374 input map[string]string 375 wantStr string 376 }{ 377 { 378 desc: "empty map", 379 input: nil, 380 wantStr: "", 381 }, 382 { 383 desc: "one key", 384 input: map[string]string{ 385 "k1": "v1", 386 }, 387 wantStr: "k1=v1", 388 }, 389 { 390 desc: "sorted keys", 391 input: map[string]string{ 392 "k1": "v1", 393 "k2": "v2", 394 "k3": "v3", 395 }, 396 wantStr: "k1=v1,k2=v2,k3=v3", 397 }, 398 { 399 desc: "unsorted keys", 400 input: map[string]string{ 401 "k3": "v3", 402 "k1": "v1", 403 "k2": "v2", 404 }, 405 wantStr: "k1=v1,k2=v2,k3=v3", 406 }, 407 } 408 409 for _, test := range tests { 410 t.Run(test.desc, func(t *testing.T) { 411 if gotStr := mapToString(test.input); gotStr != test.wantStr { 412 t.Errorf("mapToString(%v) = %s, want %s", test.input, gotStr, test.wantStr) 413 } 414 }) 415 } 416 } 417 418 func TestBuilderMapEqual(t *testing.T) { 419 tests := []struct { 420 desc string 421 a BuilderMap 422 b BuilderMap 423 wantEqual bool 424 }{ 425 { 426 desc: "nil builder maps", 427 a: nil, 428 b: nil, 429 wantEqual: true, 430 }, 431 { 432 desc: "empty builder maps", 433 a: make(map[string]builder), 434 b: make(map[string]builder), 435 wantEqual: true, 436 }, 437 { 438 desc: "nil and non-nil builder maps", 439 a: nil, 440 b: map[string]builder{"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}}, 441 wantEqual: false, 442 }, 443 { 444 desc: "empty and non-empty builder maps", 445 a: make(map[string]builder), 446 b: map[string]builder{"/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}}, 447 wantEqual: false, 448 }, 449 { 450 desc: "different number of map keys", 451 a: map[string]builder{ 452 "/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 453 "/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 454 }, 455 b: map[string]builder{ 456 "/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 457 }, 458 wantEqual: false, 459 }, 460 { 461 desc: "different map keys", 462 a: map[string]builder{ 463 "/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 464 }, 465 b: map[string]builder{ 466 "/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 467 }, 468 wantEqual: false, 469 }, 470 { 471 desc: "equal keys different values", 472 a: map[string]builder{ 473 "/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 474 "/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1", "n2"}}}}, 475 }, 476 b: map[string]builder{ 477 "/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 478 "/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 479 }, 480 wantEqual: false, 481 }, 482 { 483 desc: "good match", 484 a: map[string]builder{ 485 "/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 486 "/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 487 }, 488 b: map[string]builder{ 489 "/gBar/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 490 "/gFoo/": {headerKeys: []matcher{{key: "k1", names: []string{"n1"}}}}, 491 }, 492 wantEqual: true, 493 }, 494 } 495 496 for _, test := range tests { 497 t.Run(test.desc, func(t *testing.T) { 498 if gotEqual := test.a.Equal(test.b); gotEqual != test.wantEqual { 499 t.Errorf("BuilderMap.Equal(%v, %v) = %v, want %v", test.a, test.b, gotEqual, test.wantEqual) 500 } 501 }) 502 } 503 } 504 505 func TestBuilderEqual(t *testing.T) { 506 tests := []struct { 507 desc string 508 a builder 509 b builder 510 wantEqual bool 511 }{ 512 { 513 desc: "nil builders", 514 a: builder{headerKeys: nil}, 515 b: builder{headerKeys: nil}, 516 wantEqual: true, 517 }, 518 { 519 desc: "empty builders", 520 a: builder{headerKeys: []matcher{}}, 521 b: builder{headerKeys: []matcher{}}, 522 wantEqual: true, 523 }, 524 { 525 desc: "empty and non-empty builders", 526 a: builder{headerKeys: []matcher{}}, 527 b: builder{headerKeys: []matcher{{key: "foo"}}}, 528 wantEqual: false, 529 }, 530 { 531 desc: "different number of headerKeys", 532 a: builder{headerKeys: []matcher{{key: "foo"}, {key: "bar"}}}, 533 b: builder{headerKeys: []matcher{{key: "foo"}}}, 534 wantEqual: false, 535 }, 536 { 537 desc: "equal number but differing headerKeys", 538 a: builder{headerKeys: []matcher{{key: "bar"}}}, 539 b: builder{headerKeys: []matcher{{key: "foo"}}}, 540 wantEqual: false, 541 }, 542 { 543 desc: "different number of constantKeys", 544 a: builder{constantKeys: map[string]string{"k1": "v1"}}, 545 b: builder{constantKeys: map[string]string{"k1": "v1", "k2": "v2"}}, 546 wantEqual: false, 547 }, 548 { 549 desc: "equal number but differing constantKeys", 550 a: builder{constantKeys: map[string]string{"k1": "v1"}}, 551 b: builder{constantKeys: map[string]string{"k2": "v2"}}, 552 wantEqual: false, 553 }, 554 { 555 desc: "different hostKey", 556 a: builder{hostKey: "host1"}, 557 b: builder{hostKey: "host2"}, 558 wantEqual: false, 559 }, 560 { 561 desc: "different serviceKey", 562 a: builder{hostKey: "service1"}, 563 b: builder{hostKey: "service2"}, 564 wantEqual: false, 565 }, 566 { 567 desc: "different methodKey", 568 a: builder{hostKey: "method1"}, 569 b: builder{hostKey: "method2"}, 570 wantEqual: false, 571 }, 572 { 573 desc: "equal", 574 a: builder{ 575 headerKeys: []matcher{{key: "foo"}}, 576 constantKeys: map[string]string{"k1": "v1"}, 577 hostKey: "host", 578 serviceKey: "/service/", 579 methodKey: "method", 580 }, 581 b: builder{ 582 headerKeys: []matcher{{key: "foo"}}, 583 constantKeys: map[string]string{"k1": "v1"}, 584 hostKey: "host", 585 serviceKey: "/service/", 586 methodKey: "method", 587 }, 588 wantEqual: true, 589 }, 590 } 591 592 for _, test := range tests { 593 t.Run(test.desc, func(t *testing.T) { 594 t.Run(test.desc, func(t *testing.T) { 595 if gotEqual := test.a.Equal(test.b); gotEqual != test.wantEqual { 596 t.Errorf("builder.Equal(%v, %v) = %v, want %v", test.a, test.b, gotEqual, test.wantEqual) 597 } 598 }) 599 }) 600 } 601 } 602 603 // matcher helps extract a key from request headers based on a given name. 604 func TestMatcherEqual(t *testing.T) { 605 tests := []struct { 606 desc string 607 a matcher 608 b matcher 609 wantEqual bool 610 }{ 611 { 612 desc: "different keys", 613 a: matcher{key: "foo"}, 614 b: matcher{key: "bar"}, 615 wantEqual: false, 616 }, 617 { 618 desc: "different number of names", 619 a: matcher{key: "foo", names: []string{"v1", "v2"}}, 620 b: matcher{key: "foo", names: []string{"v1"}}, 621 wantEqual: false, 622 }, 623 { 624 desc: "equal number but differing names", 625 a: matcher{key: "foo", names: []string{"v1", "v2"}}, 626 b: matcher{key: "foo", names: []string{"v1", "v22"}}, 627 wantEqual: false, 628 }, 629 { 630 desc: "same names in different order", 631 a: matcher{key: "foo", names: []string{"v2", "v1"}}, 632 b: matcher{key: "foo", names: []string{"v1", "v3"}}, 633 wantEqual: false, 634 }, 635 { 636 desc: "good match", 637 a: matcher{key: "foo", names: []string{"v1", "v2"}}, 638 b: matcher{key: "foo", names: []string{"v1", "v2"}}, 639 wantEqual: true, 640 }, 641 } 642 643 for _, test := range tests { 644 t.Run(test.desc, func(t *testing.T) { 645 if gotEqual := test.a.Equal(test.b); gotEqual != test.wantEqual { 646 t.Errorf("matcher.Equal(%v, %v) = %v, want %v", test.a, test.b, gotEqual, test.wantEqual) 647 } 648 }) 649 } 650 }