1/* 2 * 3 * Copyright 2019 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 19package edsbalancer 20 21import ( 22 "bytes" 23 "context" 24 "encoding/json" 25 "fmt" 26 "reflect" 27 "testing" 28 "time" 29 30 "github.com/golang/protobuf/jsonpb" 31 wrapperspb "github.com/golang/protobuf/ptypes/wrappers" 32 "github.com/google/go-cmp/cmp" 33 "google.golang.org/grpc/attributes" 34 xdsinternal "google.golang.org/grpc/xds/internal" 35 36 "google.golang.org/grpc/balancer" 37 "google.golang.org/grpc/connectivity" 38 "google.golang.org/grpc/internal/grpclog" 39 "google.golang.org/grpc/internal/grpctest" 40 scpb "google.golang.org/grpc/internal/proto/grpc_service_config" 41 "google.golang.org/grpc/internal/testutils" 42 "google.golang.org/grpc/resolver" 43 "google.golang.org/grpc/serviceconfig" 44 xdsclient "google.golang.org/grpc/xds/internal/client" 45 "google.golang.org/grpc/xds/internal/testutils/fakeclient" 46 47 _ "google.golang.org/grpc/xds/internal/client/v2" // V2 client registration. 48) 49 50const defaultTestTimeout = 1 * time.Second 51 52func init() { 53 balancer.Register(&edsBalancerBuilder{}) 54} 55 56func subConnFromPicker(p balancer.Picker) func() balancer.SubConn { 57 return func() balancer.SubConn { 58 scst, _ := p.Pick(balancer.PickInfo{}) 59 return scst.SubConn 60 } 61} 62 63type s struct { 64 grpctest.Tester 65} 66 67func Test(t *testing.T) { 68 grpctest.RunSubTests(t, s{}) 69} 70 71const testBalancerNameFooBar = "foo.bar" 72 73func newNoopTestClientConn() *noopTestClientConn { 74 return &noopTestClientConn{} 75} 76 77// noopTestClientConn is used in EDS balancer config update tests that only 78// cover the config update handling, but not SubConn/load-balancing. 79type noopTestClientConn struct { 80 balancer.ClientConn 81} 82 83func (t *noopTestClientConn) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) { 84 return nil, nil 85} 86 87func (noopTestClientConn) Target() string { return testServiceName } 88 89type scStateChange struct { 90 sc balancer.SubConn 91 state connectivity.State 92} 93 94type fakeEDSBalancer struct { 95 cc balancer.ClientConn 96 childPolicy *testutils.Channel 97 subconnStateChange *testutils.Channel 98 edsUpdate *testutils.Channel 99} 100 101func (f *fakeEDSBalancer) handleSubConnStateChange(sc balancer.SubConn, state connectivity.State) { 102 f.subconnStateChange.Send(&scStateChange{sc: sc, state: state}) 103} 104 105func (f *fakeEDSBalancer) handleChildPolicy(name string, config json.RawMessage) { 106 f.childPolicy.Send(&loadBalancingConfig{Name: name, Config: config}) 107} 108 109func (f *fakeEDSBalancer) handleEDSResponse(edsResp xdsclient.EndpointsUpdate) { 110 f.edsUpdate.Send(edsResp) 111} 112 113func (f *fakeEDSBalancer) updateState(priority priorityType, s balancer.State) {} 114 115func (f *fakeEDSBalancer) close() {} 116 117func (f *fakeEDSBalancer) waitForChildPolicy(wantPolicy *loadBalancingConfig) error { 118 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 119 defer cancel() 120 121 val, err := f.childPolicy.Receive(ctx) 122 if err != nil { 123 return fmt.Errorf("error waiting for childPolicy: %v", err) 124 } 125 gotPolicy := val.(*loadBalancingConfig) 126 if !cmp.Equal(gotPolicy, wantPolicy) { 127 return fmt.Errorf("got childPolicy %v, want %v", gotPolicy, wantPolicy) 128 } 129 return nil 130} 131 132func (f *fakeEDSBalancer) waitForSubConnStateChange(wantState *scStateChange) error { 133 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 134 defer cancel() 135 136 val, err := f.subconnStateChange.Receive(ctx) 137 if err != nil { 138 return fmt.Errorf("error waiting for subconnStateChange: %v", err) 139 } 140 gotState := val.(*scStateChange) 141 if !cmp.Equal(gotState, wantState, cmp.AllowUnexported(scStateChange{})) { 142 return fmt.Errorf("got subconnStateChange %v, want %v", gotState, wantState) 143 } 144 return nil 145} 146 147func (f *fakeEDSBalancer) waitForEDSResponse(wantUpdate xdsclient.EndpointsUpdate) error { 148 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 149 defer cancel() 150 151 val, err := f.edsUpdate.Receive(ctx) 152 if err != nil { 153 return fmt.Errorf("error waiting for edsUpdate: %v", err) 154 } 155 gotUpdate := val.(xdsclient.EndpointsUpdate) 156 if !reflect.DeepEqual(gotUpdate, wantUpdate) { 157 return fmt.Errorf("got edsUpdate %+v, want %+v", gotUpdate, wantUpdate) 158 } 159 return nil 160} 161 162func newFakeEDSBalancer(cc balancer.ClientConn) edsBalancerImplInterface { 163 return &fakeEDSBalancer{ 164 cc: cc, 165 childPolicy: testutils.NewChannelWithSize(10), 166 subconnStateChange: testutils.NewChannelWithSize(10), 167 edsUpdate: testutils.NewChannelWithSize(10), 168 } 169} 170 171type fakeSubConn struct{} 172 173func (*fakeSubConn) UpdateAddresses([]resolver.Address) { panic("implement me") } 174func (*fakeSubConn) Connect() { panic("implement me") } 175 176// waitForNewEDSLB makes sure that a new edsLB is created by the top-level 177// edsBalancer. 178func waitForNewEDSLB(t *testing.T, ch *testutils.Channel) *fakeEDSBalancer { 179 t.Helper() 180 181 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 182 defer cancel() 183 val, err := ch.Receive(ctx) 184 if err != nil { 185 t.Fatalf("error when waiting for a new edsLB: %v", err) 186 return nil 187 } 188 return val.(*fakeEDSBalancer) 189} 190 191// setup overrides the functions which are used to create the xdsClient and the 192// edsLB, creates fake version of them and makes them available on the provided 193// channels. The returned cancel function should be called by the test for 194// cleanup. 195func setup(edsLBCh *testutils.Channel) func() { 196 origNewEDSBalancer := newEDSBalancer 197 newEDSBalancer = func(cc balancer.ClientConn, enqueue func(priorityType, balancer.State), _ *xdsClientWrapper, logger *grpclog.PrefixLogger) edsBalancerImplInterface { 198 edsLB := newFakeEDSBalancer(cc) 199 defer func() { edsLBCh.Send(edsLB) }() 200 return edsLB 201 } 202 return func() { 203 newEDSBalancer = origNewEDSBalancer 204 } 205} 206 207const ( 208 fakeBalancerA = "fake_balancer_A" 209 fakeBalancerB = "fake_balancer_B" 210) 211 212// Install two fake balancers for service config update tests. 213// 214// ParseConfig only accepts the json if the balancer specified is registered. 215 216func init() { 217 balancer.Register(&fakeBalancerBuilder{name: fakeBalancerA}) 218 balancer.Register(&fakeBalancerBuilder{name: fakeBalancerB}) 219} 220 221type fakeBalancerBuilder struct { 222 name string 223} 224 225func (b *fakeBalancerBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer { 226 return &fakeBalancer{cc: cc} 227} 228 229func (b *fakeBalancerBuilder) Name() string { 230 return b.name 231} 232 233type fakeBalancer struct { 234 cc balancer.ClientConn 235} 236 237func (b *fakeBalancer) ResolverError(error) { 238 panic("implement me") 239} 240 241func (b *fakeBalancer) UpdateClientConnState(balancer.ClientConnState) error { 242 panic("implement me") 243} 244 245func (b *fakeBalancer) UpdateSubConnState(balancer.SubConn, balancer.SubConnState) { 246 panic("implement me") 247} 248 249func (b *fakeBalancer) Close() {} 250 251// TestXDSConnfigChildPolicyUpdate verifies scenarios where the childPolicy 252// section of the lbConfig is updated. 253// 254// The test does the following: 255// * Builds a new xds balancer. 256// * Pushes a new ClientConnState with a childPolicy set to fakeBalancerA. 257// Verifies that a new xdsClient is created. It then pushes a new edsUpdate 258// through the fakexds client. Verifies that a new edsLB is created and it 259// receives the expected childPolicy. 260// * Pushes a new ClientConnState with a childPolicy set to fakeBalancerB. 261// This time around, we expect no new xdsClient or edsLB to be created. 262// Instead, we expect the existing edsLB to receive the new child policy. 263func (s) TestXDSConnfigChildPolicyUpdate(t *testing.T) { 264 xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar) 265 edsLBCh := testutils.NewChannel() 266 cancel := setup(edsLBCh) 267 defer cancel() 268 269 builder := balancer.Get(edsName) 270 cc := newNoopTestClientConn() 271 edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testServiceName}}).(*edsBalancer) 272 if !ok { 273 t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB) 274 } 275 defer edsB.Close() 276 277 edsB.UpdateClientConnState(balancer.ClientConnState{ 278 ResolverState: resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)}, 279 BalancerConfig: &EDSConfig{ 280 ChildPolicy: &loadBalancingConfig{ 281 Name: fakeBalancerA, 282 Config: json.RawMessage("{}"), 283 }, 284 EDSServiceName: testEDSClusterName, 285 }, 286 }) 287 288 ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout) 289 defer ctxCancel() 290 if _, err := xdsC.WaitForWatchEDS(ctx); err != nil { 291 t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err) 292 } 293 xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil) 294 edsLB := waitForNewEDSLB(t, edsLBCh) 295 edsLB.waitForChildPolicy(&loadBalancingConfig{ 296 Name: string(fakeBalancerA), 297 Config: json.RawMessage(`{}`), 298 }) 299 300 edsB.UpdateClientConnState(balancer.ClientConnState{ 301 ResolverState: resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)}, 302 BalancerConfig: &EDSConfig{ 303 ChildPolicy: &loadBalancingConfig{ 304 Name: fakeBalancerB, 305 Config: json.RawMessage("{}"), 306 }, 307 EDSServiceName: testEDSClusterName, 308 }, 309 }) 310 edsLB.waitForChildPolicy(&loadBalancingConfig{ 311 Name: string(fakeBalancerA), 312 Config: json.RawMessage(`{}`), 313 }) 314} 315 316// TestXDSSubConnStateChange verifies if the top-level edsBalancer passes on 317// the subConnStateChange to appropriate child balancers. 318func (s) TestXDSSubConnStateChange(t *testing.T) { 319 xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar) 320 edsLBCh := testutils.NewChannel() 321 cancel := setup(edsLBCh) 322 defer cancel() 323 324 builder := balancer.Get(edsName) 325 cc := newNoopTestClientConn() 326 edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testEDSClusterName}}).(*edsBalancer) 327 if !ok { 328 t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB) 329 } 330 defer edsB.Close() 331 332 edsB.UpdateClientConnState(balancer.ClientConnState{ 333 ResolverState: resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)}, 334 BalancerConfig: &EDSConfig{EDSServiceName: testEDSClusterName}, 335 }) 336 337 ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout) 338 defer ctxCancel() 339 if _, err := xdsC.WaitForWatchEDS(ctx); err != nil { 340 t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err) 341 } 342 xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil) 343 edsLB := waitForNewEDSLB(t, edsLBCh) 344 345 fsc := &fakeSubConn{} 346 state := connectivity.Ready 347 edsB.UpdateSubConnState(fsc, balancer.SubConnState{ConnectivityState: state}) 348 edsLB.waitForSubConnStateChange(&scStateChange{sc: fsc, state: state}) 349} 350 351// TestErrorFromXDSClientUpdate verifies that errros from xdsClient update are 352// handled correctly. 353// 354// If it's resource-not-found, watch will NOT be canceled, the EDS impl will 355// receive an empty EDS update, and new RPCs will fail. 356// 357// If it's connection error, nothing will happen. This will need to change to 358// handle fallback. 359func (s) TestErrorFromXDSClientUpdate(t *testing.T) { 360 xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar) 361 edsLBCh := testutils.NewChannel() 362 cancel := setup(edsLBCh) 363 defer cancel() 364 365 builder := balancer.Get(edsName) 366 cc := newNoopTestClientConn() 367 edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testEDSClusterName}}).(*edsBalancer) 368 if !ok { 369 t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB) 370 } 371 defer edsB.Close() 372 373 if err := edsB.UpdateClientConnState(balancer.ClientConnState{ 374 ResolverState: resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)}, 375 BalancerConfig: &EDSConfig{EDSServiceName: testEDSClusterName}, 376 }); err != nil { 377 t.Fatal(err) 378 } 379 380 ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout) 381 defer ctxCancel() 382 if _, err := xdsC.WaitForWatchEDS(ctx); err != nil { 383 t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err) 384 } 385 xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil) 386 edsLB := waitForNewEDSLB(t, edsLBCh) 387 if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil { 388 t.Fatalf("EDS impl got unexpected EDS response: %v", err) 389 } 390 391 connectionErr := xdsclient.NewErrorf(xdsclient.ErrorTypeConnection, "connection error") 392 xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, connectionErr) 393 394 if err := xdsC.WaitForCancelEDSWatch(ctx); err == nil { 395 t.Fatal("watch was canceled, want not canceled (timeout error)") 396 } 397 if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err == nil { 398 t.Fatal("eds impl got EDS resp, want timeout error") 399 } 400 401 resourceErr := xdsclient.NewErrorf(xdsclient.ErrorTypeResourceNotFound, "edsBalancer resource not found error") 402 xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, resourceErr) 403 // Even if error is resource not found, watch shouldn't be canceled, because 404 // this is an EDS resource removed (and xds client actually never sends this 405 // error, but we still handles it). 406 ctx, ctxCancel = context.WithTimeout(context.Background(), defaultTestTimeout) 407 defer ctxCancel() 408 if err := xdsC.WaitForCancelEDSWatch(ctx); err == nil { 409 t.Fatal("watch was canceled, want not canceled (timeout error)") 410 } 411 if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil { 412 t.Fatalf("eds impl expecting empty update, got %v", err) 413 } 414} 415 416// TestErrorFromResolver verifies that resolver errors are handled correctly. 417// 418// If it's resource-not-found, watch will be canceled, the EDS impl will receive 419// an empty EDS update, and new RPCs will fail. 420// 421// If it's connection error, nothing will happen. This will need to change to 422// handle fallback. 423func (s) TestErrorFromResolver(t *testing.T) { 424 xdsC := fakeclient.NewClientWithName(testBalancerNameFooBar) 425 edsLBCh := testutils.NewChannel() 426 cancel := setup(edsLBCh) 427 defer cancel() 428 429 builder := balancer.Get(edsName) 430 cc := newNoopTestClientConn() 431 edsB, ok := builder.Build(cc, balancer.BuildOptions{Target: resolver.Target{Endpoint: testEDSClusterName}}).(*edsBalancer) 432 if !ok { 433 t.Fatalf("builder.Build(%s) returned type {%T}, want {*edsBalancer}", edsName, edsB) 434 } 435 defer edsB.Close() 436 437 if err := edsB.UpdateClientConnState(balancer.ClientConnState{ 438 ResolverState: resolver.State{Attributes: attributes.New(xdsinternal.XDSClientID, xdsC)}, 439 BalancerConfig: &EDSConfig{EDSServiceName: testEDSClusterName}, 440 }); err != nil { 441 t.Fatal(err) 442 } 443 444 ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout) 445 defer ctxCancel() 446 if _, err := xdsC.WaitForWatchEDS(ctx); err != nil { 447 t.Fatalf("xdsClient.WatchEndpoints failed with error: %v", err) 448 } 449 xdsC.InvokeWatchEDSCallback(xdsclient.EndpointsUpdate{}, nil) 450 edsLB := waitForNewEDSLB(t, edsLBCh) 451 if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil { 452 t.Fatalf("EDS impl got unexpected EDS response: %v", err) 453 } 454 455 connectionErr := xdsclient.NewErrorf(xdsclient.ErrorTypeConnection, "connection error") 456 edsB.ResolverError(connectionErr) 457 458 if err := xdsC.WaitForCancelEDSWatch(ctx); err == nil { 459 t.Fatal("watch was canceled, want not canceled (timeout error)") 460 } 461 if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err == nil { 462 t.Fatal("eds impl got EDS resp, want timeout error") 463 } 464 465 resourceErr := xdsclient.NewErrorf(xdsclient.ErrorTypeResourceNotFound, "edsBalancer resource not found error") 466 edsB.ResolverError(resourceErr) 467 ctx, ctxCancel = context.WithTimeout(context.Background(), defaultTestTimeout) 468 defer ctxCancel() 469 if err := xdsC.WaitForCancelEDSWatch(ctx); err != nil { 470 t.Fatalf("want watch to be canceled, waitForCancel failed: %v", err) 471 } 472 if err := edsLB.waitForEDSResponse(xdsclient.EndpointsUpdate{}); err != nil { 473 t.Fatalf("EDS impl got unexpected EDS response: %v", err) 474 } 475} 476 477func (s) TestXDSBalancerConfigParsing(t *testing.T) { 478 const testEDSName = "eds.service" 479 var testLRSName = "lrs.server" 480 b := bytes.NewBuffer(nil) 481 if err := (&jsonpb.Marshaler{}).Marshal(b, &scpb.XdsConfig{ 482 ChildPolicy: []*scpb.LoadBalancingConfig{ 483 {Policy: &scpb.LoadBalancingConfig_Xds{}}, 484 {Policy: &scpb.LoadBalancingConfig_RoundRobin{ 485 RoundRobin: &scpb.RoundRobinConfig{}, 486 }}, 487 }, 488 FallbackPolicy: []*scpb.LoadBalancingConfig{ 489 {Policy: &scpb.LoadBalancingConfig_Xds{}}, 490 {Policy: &scpb.LoadBalancingConfig_PickFirst{ 491 PickFirst: &scpb.PickFirstConfig{}, 492 }}, 493 }, 494 EdsServiceName: testEDSName, 495 LrsLoadReportingServerName: &wrapperspb.StringValue{Value: testLRSName}, 496 }); err != nil { 497 t.Fatalf("%v", err) 498 } 499 500 tests := []struct { 501 name string 502 js json.RawMessage 503 want serviceconfig.LoadBalancingConfig 504 wantErr bool 505 }{ 506 { 507 name: "jsonpb-generated", 508 js: b.Bytes(), 509 want: &EDSConfig{ 510 ChildPolicy: &loadBalancingConfig{ 511 Name: "round_robin", 512 Config: json.RawMessage("{}"), 513 }, 514 FallBackPolicy: &loadBalancingConfig{ 515 Name: "pick_first", 516 Config: json.RawMessage("{}"), 517 }, 518 EDSServiceName: testEDSName, 519 LrsLoadReportingServerName: &testLRSName, 520 }, 521 wantErr: false, 522 }, 523 { 524 // json with random balancers, and the first is not registered. 525 name: "manually-generated", 526 js: json.RawMessage(` 527{ 528 "childPolicy": [ 529 {"fake_balancer_C": {}}, 530 {"fake_balancer_A": {}}, 531 {"fake_balancer_B": {}} 532 ], 533 "fallbackPolicy": [ 534 {"fake_balancer_C": {}}, 535 {"fake_balancer_B": {}}, 536 {"fake_balancer_A": {}} 537 ], 538 "edsServiceName": "eds.service", 539 "lrsLoadReportingServerName": "lrs.server" 540}`), 541 want: &EDSConfig{ 542 ChildPolicy: &loadBalancingConfig{ 543 Name: "fake_balancer_A", 544 Config: json.RawMessage("{}"), 545 }, 546 FallBackPolicy: &loadBalancingConfig{ 547 Name: "fake_balancer_B", 548 Config: json.RawMessage("{}"), 549 }, 550 EDSServiceName: testEDSName, 551 LrsLoadReportingServerName: &testLRSName, 552 }, 553 wantErr: false, 554 }, 555 { 556 // json with no lrs server name, LrsLoadReportingServerName should 557 // be nil (not an empty string). 558 name: "no-lrs-server-name", 559 js: json.RawMessage(` 560{ 561 "edsServiceName": "eds.service" 562}`), 563 want: &EDSConfig{ 564 EDSServiceName: testEDSName, 565 LrsLoadReportingServerName: nil, 566 }, 567 wantErr: false, 568 }, 569 } 570 for _, tt := range tests { 571 t.Run(tt.name, func(t *testing.T) { 572 b := &edsBalancerBuilder{} 573 got, err := b.ParseConfig(tt.js) 574 if (err != nil) != tt.wantErr { 575 t.Errorf("edsBalancerBuilder.ParseConfig() error = %v, wantErr %v", err, tt.wantErr) 576 return 577 } 578 if !cmp.Equal(got, tt.want) { 579 t.Errorf(cmp.Diff(got, tt.want)) 580 } 581 }) 582 } 583} 584func (s) TestLoadbalancingConfigParsing(t *testing.T) { 585 tests := []struct { 586 name string 587 s string 588 want *EDSConfig 589 }{ 590 { 591 name: "empty", 592 s: "{}", 593 want: &EDSConfig{}, 594 }, 595 { 596 name: "success1", 597 s: `{"childPolicy":[{"pick_first":{}}]}`, 598 want: &EDSConfig{ 599 ChildPolicy: &loadBalancingConfig{ 600 Name: "pick_first", 601 Config: json.RawMessage(`{}`), 602 }, 603 }, 604 }, 605 { 606 name: "success2", 607 s: `{"childPolicy":[{"round_robin":{}},{"pick_first":{}}]}`, 608 want: &EDSConfig{ 609 ChildPolicy: &loadBalancingConfig{ 610 Name: "round_robin", 611 Config: json.RawMessage(`{}`), 612 }, 613 }, 614 }, 615 } 616 for _, tt := range tests { 617 t.Run(tt.name, func(t *testing.T) { 618 var cfg EDSConfig 619 if err := json.Unmarshal([]byte(tt.s), &cfg); err != nil || !cmp.Equal(&cfg, tt.want) { 620 t.Errorf("test name: %s, parseFullServiceConfig() = %+v, err: %v, want %+v, <nil>", tt.name, cfg, err, tt.want) 621 } 622 }) 623 } 624} 625 626func (s) TestEqualStringPointers(t *testing.T) { 627 var ( 628 ta1 = "test-a" 629 ta2 = "test-a" 630 tb = "test-b" 631 ) 632 tests := []struct { 633 name string 634 a *string 635 b *string 636 want bool 637 }{ 638 {"both-nil", nil, nil, true}, 639 {"a-non-nil", &ta1, nil, false}, 640 {"b-non-nil", nil, &tb, false}, 641 {"equal", &ta1, &ta2, true}, 642 {"different", &ta1, &tb, false}, 643 } 644 for _, tt := range tests { 645 t.Run(tt.name, func(t *testing.T) { 646 if got := equalStringPointers(tt.a, tt.b); got != tt.want { 647 t.Errorf("equalStringPointers() = %v, want %v", got, tt.want) 648 } 649 }) 650 } 651} 652