go.uber.org/yarpc@v1.72.1/yarpcconfig/spec_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package yarpcconfig 22 23 import ( 24 "errors" 25 "fmt" 26 "reflect" 27 "testing" 28 29 "github.com/golang/mock/gomock" 30 "github.com/stretchr/testify/assert" 31 "go.uber.org/yarpc/api/peer" 32 "go.uber.org/yarpc/api/transport" 33 "go.uber.org/yarpc/internal/config" 34 ) 35 36 var _typeOfEmptyStruct = reflect.TypeOf(struct{}{}) 37 38 func TestCompileTransportSpec(t *testing.T) { 39 type phoneCall struct{ Message string } 40 type cavalry struct{ Horses int } 41 type debt struct{ Amount int64 } 42 43 tests := []struct { 44 desc string 45 spec TransportSpec 46 47 supportsUnary bool 48 supportsOneway bool 49 supportsStream bool 50 51 transportInput reflect.Type 52 inboundInput reflect.Type 53 unaryOutboundInput reflect.Type 54 onewayOutboundInput reflect.Type 55 streamOutboundInput reflect.Type 56 57 wantErr []string 58 }{ 59 { 60 desc: "missing name", 61 wantErr: []string{"field Name is required"}, 62 }, 63 { 64 desc: "reserved name", 65 spec: TransportSpec{Name: "Unary"}, 66 wantErr: []string{`transport name cannot be "Unary"`}, 67 }, 68 { 69 desc: "reserved name 2", 70 spec: TransportSpec{Name: "Oneway"}, 71 wantErr: []string{`transport name cannot be "Oneway"`}, 72 }, 73 { 74 desc: "reserved name 3", 75 spec: TransportSpec{Name: "Stream"}, 76 wantErr: []string{`transport name cannot be "Stream"`}, 77 }, 78 { 79 desc: "missing BuildTransport", 80 spec: TransportSpec{Name: "foo"}, 81 wantErr: []string{"BuildTransport is required"}, 82 }, 83 { 84 desc: "great sadness", 85 spec: TransportSpec{ 86 Name: "foo", 87 BuildTransport: func(struct{}, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 88 BuildInbound: func(transport.Transport) (transport.UnaryOutbound, error) { panic("kthxbye") }, 89 BuildUnaryOutbound: func(struct{}, transport.Inbound, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 90 BuildOnewayOutbound: func(struct{}) (transport.OnewayOutbound, error) { panic("kthxbye") }, 91 BuildStreamOutbound: func(struct{}) (transport.StreamOutbound, error) { panic("kthxbye") }, 92 }, 93 wantErr: []string{ 94 "invalid BuildTransport func(struct {}, *yarpcconfig.Kit) (transport.Inbound, error): " + 95 "must return a transport.Transport as its first result, found transport.Inbound", 96 "invalid BuildInbound: must accept exactly three arguments, found 1", 97 "invalid BuildUnaryOutbound: must accept a transport.Transport as its second argument, found transport.Inbound", 98 "invalid BuildOnewayOutbound: must accept exactly three arguments, found 1", 99 "invalid BuildStreamOutbound: must accept exactly three arguments, found 1", 100 }, 101 }, 102 { 103 desc: "inbound only", 104 spec: TransportSpec{ 105 Name: "what-good-is-a-phone-call-when-you-are-unable-to-speak", 106 BuildTransport: func(struct{}, *Kit) (transport.Transport, error) { panic("kthxbye") }, 107 BuildInbound: func(*phoneCall, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 108 }, 109 transportInput: _typeOfEmptyStruct, 110 inboundInput: reflect.TypeOf(&phoneCall{}), 111 }, 112 { 113 desc: "unary outbound only", 114 spec: TransportSpec{ 115 Name: "tyrion", 116 BuildTransport: func(**struct{}, *Kit) (transport.Transport, error) { panic("kthxbye") }, 117 BuildUnaryOutbound: func(debt, transport.Transport, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 118 }, 119 transportInput: reflect.PtrTo(reflect.TypeOf(&struct{}{})), 120 supportsUnary: true, 121 unaryOutboundInput: reflect.TypeOf(debt{}), 122 }, 123 { 124 desc: "oneway outbound only", 125 spec: TransportSpec{ 126 Name: "arise-riders-of-theoden", 127 BuildTransport: func(*cavalry, *Kit) (transport.Transport, error) { panic("kthxbye") }, 128 BuildOnewayOutbound: func(struct{}, transport.Transport, *Kit) (transport.OnewayOutbound, error) { panic("kthxbye") }, 129 }, 130 transportInput: reflect.TypeOf(&cavalry{}), 131 supportsOneway: true, 132 onewayOutboundInput: _typeOfEmptyStruct, 133 }, 134 { 135 desc: "stream outbound only", 136 spec: TransportSpec{ 137 Name: "arise-riders-of-theoden", 138 BuildTransport: func(*cavalry, *Kit) (transport.Transport, error) { panic("kthxbye") }, 139 BuildStreamOutbound: func(struct{}, transport.Transport, *Kit) (transport.StreamOutbound, error) { panic("kthxbye") }, 140 }, 141 transportInput: reflect.TypeOf(&cavalry{}), 142 supportsStream: true, 143 streamOutboundInput: _typeOfEmptyStruct, 144 }, 145 { 146 desc: "bad peer chooser preset", 147 spec: TransportSpec{ 148 Name: "foo", 149 BuildTransport: func(struct{}, *Kit) (transport.Transport, error) { panic("kthxbye") }, 150 BuildUnaryOutbound: func(struct{}, transport.Transport, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 151 PeerChooserPresets: []PeerChooserPreset{ 152 { 153 Name: "fake", 154 BuildPeerChooser: func(transport.Transport, *Kit) (peer.Chooser, error) { panic("kthxbye") }, 155 }, 156 }, 157 }, 158 wantErr: []string{ 159 `failed to compile preset for transport "foo":`, 160 "invalid BuildPeerChooser func(transport.Transport, *yarpcconfig.Kit) (peer.Chooser, error):", 161 "must accept a peer.Transport as its first argument, found transport.Transport", 162 }, 163 }, 164 { 165 desc: "peer chooser preset collision", 166 spec: TransportSpec{ 167 Name: "foo", 168 BuildTransport: func(struct{}, *Kit) (transport.Transport, error) { panic("kthxbye") }, 169 BuildUnaryOutbound: func(struct{}, transport.Transport, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 170 PeerChooserPresets: []PeerChooserPreset{ 171 { 172 Name: "fake", 173 BuildPeerChooser: func(peer.Transport, *Kit) (peer.Chooser, error) { panic("kthxbye") }, 174 }, 175 { 176 Name: "fake", 177 BuildPeerChooser: func(peer.Transport, *Kit) (peer.Chooser, error) { panic("kthxbye") }, 178 }, 179 }, 180 }, 181 wantErr: []string{ 182 `found multiple peer lists with the name "fake" under transport "foo"`, 183 }, 184 }, 185 } 186 187 for _, tt := range tests { 188 t.Run(tt.desc, func(t *testing.T) { 189 ts, err := compileTransportSpec(&tt.spec) 190 191 if len(tt.wantErr) > 0 { 192 if assert.Error(t, err, "expected failure") { 193 for _, msg := range tt.wantErr { 194 assert.Contains(t, err.Error(), msg) 195 } 196 for _, msg := range tt.wantErr { 197 assert.Contains(t, fmt.Sprintf("%+v", err), msg) 198 } 199 } 200 return 201 } 202 203 if !assert.NoError(t, err) { 204 return 205 } 206 207 assert.Equal(t, tt.transportInput, ts.Transport.inputType) 208 assert.Equal(t, tt.supportsUnary, ts.SupportsUnaryOutbound()) 209 assert.Equal(t, tt.supportsOneway, ts.SupportsOnewayOutbound()) 210 assert.Equal(t, tt.supportsStream, ts.SupportsStreamOutbound()) 211 212 if ts.Inbound != nil { 213 assert.Equal(t, tt.inboundInput, ts.Inbound.inputType) 214 } 215 if ts.UnaryOutbound != nil { 216 assert.Equal(t, tt.unaryOutboundInput, ts.UnaryOutbound.inputType) 217 } 218 if ts.OnewayOutbound != nil { 219 assert.Equal(t, tt.onewayOutboundInput, ts.OnewayOutbound.inputType) 220 } 221 if ts.StreamOutbound != nil { 222 assert.Equal(t, tt.streamOutboundInput, ts.StreamOutbound.inputType) 223 } 224 }) 225 } 226 } 227 228 func TestConfigSpecDecode(t *testing.T) { 229 type item struct{ Key, Value string } 230 231 someItem := item{"key", "value"} 232 ptrToSomeItem := &someItem 233 234 tests := []struct { 235 desc string 236 237 // Build funcction to compile 238 build interface{} 239 240 // Compile function to use (compileTransportConfig, 241 // compileInboundConfig, etc.) 242 compiler func(interface{}) (*configSpec, error) 243 244 // Attributes to decode 245 attrs config.AttributeMap 246 247 // Whether we want a specific value decoded or an error message 248 want interface{} 249 wantErr []string 250 }{ 251 { 252 desc: "decode failure", 253 build: func(struct{}, *Kit) (transport.Transport, error) { panic("kthxbye") }, 254 compiler: compileTransportConfig, 255 attrs: config.AttributeMap{"unexpected": 42}, 256 wantErr: []string{ 257 "failed to decode struct {}", 258 "has invalid keys: unexpected", 259 }, 260 }, 261 { 262 desc: "decode struct{}", 263 build: func(struct{}, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 264 compiler: compileInboundConfig, 265 attrs: config.AttributeMap{}, 266 want: struct{}{}, 267 }, 268 { 269 desc: "decode item", 270 build: func(item, transport.Transport, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 271 compiler: compileUnaryOutboundConfig, 272 attrs: config.AttributeMap{"key": "key", "value": "value"}, 273 want: someItem, 274 }, 275 { 276 desc: "decode *item", 277 build: func(*item, transport.Transport, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 278 compiler: compileUnaryOutboundConfig, 279 attrs: config.AttributeMap{"key": "key", "value": "value"}, 280 want: ptrToSomeItem, 281 }, 282 { 283 desc: "decode **item", 284 build: func(**item, transport.Transport, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 285 compiler: compileUnaryOutboundConfig, 286 attrs: config.AttributeMap{"key": "key", "value": "value"}, 287 want: &ptrToSomeItem, 288 }, 289 } 290 291 for _, tt := range tests { 292 t.Run(tt.desc, func(t *testing.T) { 293 spec, err := tt.compiler(tt.build) 294 if !assert.NoError(t, err, "failed to compile config") { 295 return 296 } 297 298 got, err := spec.Decode(tt.attrs) 299 if len(tt.wantErr) == 0 { 300 if assert.NoError(t, err) { 301 assert.Equal(t, tt.want, got.inputData.Interface()) 302 } 303 return 304 } 305 306 if assert.Error(t, err, "expected failure") { 307 for _, msg := range tt.wantErr { 308 assert.Contains(t, err.Error(), msg) 309 } 310 } 311 }) 312 } 313 } 314 315 // mockValueBuilder is a simple callable that records and verifies its calls using 316 // a gomock controller. 317 // 318 // mockValueBuilder.Build is a valid factory function for buildable. 319 type mockValueBuilder struct{ ctrl *gomock.Controller } 320 321 func newMockValueBuilder(ctrl *gomock.Controller) *mockValueBuilder { 322 return &mockValueBuilder{ctrl: ctrl} 323 } 324 325 func (m *mockValueBuilder) ExpectBuild(args ...interface{}) *gomock.Call { 326 return m.ctrl.RecordCall(m, "Build", args...) 327 } 328 329 func (m *mockValueBuilder) Build(args ...interface{}) (interface{}, error) { 330 ret := m.ctrl.Call(m, "Build", args...) 331 err, _ := ret[1].(error) 332 return ret[0], err 333 } 334 335 func TestBuildableBuild(t *testing.T) { 336 type item struct{ Key, Value string } 337 338 tests := []struct { 339 desc string 340 341 // Configuration data and arguments for the build function 342 data interface{} 343 args []interface{} 344 345 // Expect a Build(..) call with the given arguments 346 wantArgs []interface{} 347 err error 348 }{ 349 { 350 desc: "success, no args", 351 data: struct{}{}, 352 wantArgs: []interface{}{struct{}{}}, 353 }, 354 { 355 desc: "success with args", 356 data: 1, 357 args: []interface{}{2, 3}, 358 wantArgs: []interface{}{1, 2, 3}, 359 }, 360 { 361 desc: "success with Value args", 362 data: &item{Key: "key", Value: "value"}, 363 args: []interface{}{ 364 "foo", 365 reflect.ValueOf("bar"), 366 "baz", 367 }, 368 wantArgs: []interface{}{ 369 &item{Key: "key", Value: "value"}, 370 "foo", 371 "bar", 372 "baz", 373 }, 374 }, 375 { 376 desc: "nil everything", 377 data: (*item)(nil), 378 wantArgs: []interface{}{nil}, 379 }, 380 { 381 desc: "error no args", 382 data: 42, 383 wantArgs: []interface{}{42}, 384 err: errors.New("great sadness"), 385 }, 386 { 387 desc: "error with args", 388 data: item{}, 389 args: []interface{}{ 390 (*string)(nil), 391 reflect.Zero(_typeOfTransport), 392 }, 393 wantArgs: []interface{}{item{}, nil, nil}, 394 err: errors.New("transport is required"), 395 }, 396 } 397 398 for _, tt := range tests { 399 t.Run(tt.desc, func(t *testing.T) { 400 mockCtrl := gomock.NewController(t) 401 defer mockCtrl.Finish() 402 403 builder := newMockValueBuilder(mockCtrl) 404 builder.ExpectBuild(tt.wantArgs...).Return("some result", tt.err) 405 406 cv := &buildable{ 407 inputData: reflect.ValueOf(tt.data), 408 factory: reflect.ValueOf(builder.Build), 409 } 410 411 result, err := cv.Build(tt.args...) 412 assert.Equal(t, tt.err, err) 413 assert.Equal(t, "some result", result) 414 }) 415 } 416 } 417 418 func TestCompileTransportConfig(t *testing.T) { 419 tests := []struct { 420 desc string 421 build interface{} 422 423 wantInputType reflect.Type 424 wantErr string 425 }{ 426 { 427 desc: "not a function", 428 build: 42, 429 wantErr: "must be a function", 430 }, 431 { 432 desc: "wrong number of arguments", 433 build: func(struct{}, struct{}, struct{}) (transport.Transport, error) { panic("kthxbye") }, 434 wantErr: "must accept exactly two arguments, found 3", 435 }, 436 { 437 desc: "incorrect input type", 438 build: func(int, *Kit) (transport.Transport, error) { panic("kthxbye") }, 439 wantErr: "must accept a struct or struct pointer as its first argument, found int", 440 }, 441 { 442 desc: "wrong number of results", 443 build: func(struct{}, *Kit) transport.Transport { panic("kthxbye") }, 444 wantErr: "must return exactly two results, found 1", 445 }, 446 { 447 desc: "wrong output type", 448 build: func(struct{}, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 449 wantErr: "must return a transport.Transport as its first result, found transport.Inbound", 450 }, 451 { 452 desc: "incorrect second result", 453 build: func(struct{}, *Kit) (transport.Transport, string) { panic("kthxbye") }, 454 wantErr: "must return an error as its second result, found string", 455 }, 456 { 457 desc: "valid: struct{}", 458 build: func(struct{}, *Kit) (transport.Transport, error) { panic("kthxbye") }, 459 wantInputType: _typeOfEmptyStruct, 460 }, 461 { 462 desc: "valid: *struct{}", 463 build: func(*struct{}, *Kit) (transport.Transport, error) { panic("kthxbye") }, 464 wantInputType: reflect.PtrTo(_typeOfEmptyStruct), 465 }, 466 } 467 468 for _, tt := range tests { 469 t.Run(tt.desc, func(t *testing.T) { 470 cs, err := compileTransportConfig(tt.build) 471 472 if tt.wantErr == "" { 473 assert.Equal(t, tt.wantInputType, cs.inputType, "input type mismatch") 474 assert.NoError(t, err, "expected success") 475 return 476 } 477 478 if assert.Error(t, err, "expected failure") { 479 assert.Contains(t, err.Error(), tt.wantErr) 480 } 481 }) 482 } 483 } 484 485 func TestCompileInboundConfig(t *testing.T) { 486 tests := []struct { 487 desc string 488 build interface{} 489 wantInputType reflect.Type 490 wantErr string 491 }{ 492 { 493 desc: "reserved field: Type", 494 build: func(struct{ Type string }, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 495 wantErr: "inbound configurations must not have a Type field", 496 }, 497 { 498 desc: "reserved field: Disabled", 499 build: func(struct{ Disabled string }, transport.Transport, *Kit) (transport.Inbound, error) { 500 panic("kthxbye") 501 }, 502 wantErr: "inbound configurations must not have a Disabled field", 503 }, 504 { 505 desc: "incorrect return type", 506 build: func(struct{}, transport.Transport, *Kit) (transport.Outbound, error) { panic("kthxbye") }, 507 wantErr: "invalid BuildInbound: must return a transport.Inbound as its first result, found transport.Outbound", 508 }, 509 { 510 desc: "valid: struct{}", 511 build: func(struct{}, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 512 wantInputType: _typeOfEmptyStruct, 513 }, 514 } 515 516 for _, tt := range tests { 517 t.Run(tt.desc, func(t *testing.T) { 518 cs, err := compileInboundConfig(tt.build) 519 520 if tt.wantErr == "" { 521 assert.Equal(t, tt.wantInputType, cs.inputType, "input type mismatch") 522 assert.NoError(t, err, "expected success") 523 return 524 } 525 526 if assert.Error(t, err, "expected failure") { 527 assert.Contains(t, err.Error(), tt.wantErr) 528 } 529 }) 530 } 531 } 532 533 func TestCompileUnaryOutboundConfig(t *testing.T) { 534 tests := []struct { 535 desc string 536 build interface{} 537 wantInputType reflect.Type 538 wantErr string 539 }{ 540 { 541 desc: "incorrect return type", 542 build: func(struct{}, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 543 wantErr: "invalid BuildUnaryOutbound: must return a transport.UnaryOutbound as its first result, found transport.Inbound", 544 }, 545 { 546 desc: "valid: struct{}", 547 build: func(struct{}, transport.Transport, *Kit) (transport.UnaryOutbound, error) { panic("kthxbye") }, 548 wantInputType: _typeOfEmptyStruct, 549 }, 550 } 551 552 for _, tt := range tests { 553 t.Run(tt.desc, func(t *testing.T) { 554 cs, err := compileUnaryOutboundConfig(tt.build) 555 556 if tt.wantErr == "" { 557 assert.Equal(t, tt.wantInputType, cs.inputType, "input type mismatch") 558 assert.NoError(t, err, "expected success") 559 return 560 } 561 562 if assert.Error(t, err, "expected failure") { 563 assert.Contains(t, err.Error(), tt.wantErr) 564 } 565 }) 566 } 567 } 568 569 func TestCompileOnewayOutboundConfig(t *testing.T) { 570 tests := []struct { 571 desc string 572 build interface{} 573 wantInputType reflect.Type 574 wantErr string 575 }{ 576 { 577 desc: "incorrect return type", 578 build: func(struct{}, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 579 wantErr: "invalid BuildOnewayOutbound: must return a transport.OnewayOutbound as its first result, found transport.Inbound", 580 }, 581 { 582 desc: "valid: struct{}", 583 build: func(struct{}, transport.Transport, *Kit) (transport.OnewayOutbound, error) { panic("kthxbye") }, 584 wantInputType: _typeOfEmptyStruct, 585 }, 586 } 587 588 for _, tt := range tests { 589 t.Run(tt.desc, func(t *testing.T) { 590 cs, err := compileOnewayOutboundConfig(tt.build) 591 592 if tt.wantErr == "" { 593 assert.Equal(t, tt.wantInputType, cs.inputType, "input type mismatch") 594 assert.NoError(t, err, "expected success") 595 return 596 } 597 598 if assert.Error(t, err, "expected failure") { 599 assert.Contains(t, err.Error(), tt.wantErr) 600 } 601 }) 602 } 603 } 604 605 func TestCompilePeerChooserSpec(t *testing.T) { 606 tests := []struct { 607 desc string 608 spec PeerChooserSpec 609 wantName string 610 wantErr string 611 }{ 612 { 613 desc: "missing name", 614 wantErr: "field Name is required", 615 }, 616 { 617 desc: "missing BuildPeerChooser", 618 spec: PeerChooserSpec{ 619 Name: "random", 620 }, 621 wantErr: "field BuildPeerChooser is required", 622 }, 623 { 624 desc: "not a function", 625 spec: PeerChooserSpec{ 626 Name: "much sadness", 627 BuildPeerChooser: 10, 628 }, 629 wantErr: "invalid BuildPeerChooser int: must be a function", 630 }, 631 { 632 desc: "too many arguments", 633 spec: PeerChooserSpec{ 634 Name: "much sadness", 635 BuildPeerChooser: func(a, b, c, d int) {}, 636 }, 637 wantErr: "invalid BuildPeerChooser func(int, int, int, int): must accept exactly three arguments, found 4", 638 }, 639 { 640 desc: "wrong kind of first argument", 641 spec: PeerChooserSpec{ 642 Name: "much sadness", 643 BuildPeerChooser: func(a, b, c int) {}, 644 }, 645 wantErr: "invalid BuildPeerChooser func(int, int, int): must accept a struct or struct pointer as its first argument, found int", 646 }, 647 { 648 desc: "wrong kind of second argument", 649 spec: PeerChooserSpec{ 650 Name: "much sadness", 651 BuildPeerChooser: func(c struct{}, t int, k *Kit) {}, 652 }, 653 wantErr: "invalid BuildPeerChooser func(struct {}, int, *yarpcconfig.Kit): must accept a peer.Transport as its second argument, found int", 654 }, 655 { 656 desc: "wrong kind of third argument", 657 spec: PeerChooserSpec{ 658 Name: "much sadness", 659 BuildPeerChooser: func(c struct{}, t peer.Transport, k int) {}, 660 }, 661 wantErr: "invalid BuildPeerChooser func(struct {}, peer.Transport, int): must accept a *yarpcconfig.Kit as its third argument, found int", 662 }, 663 { 664 desc: "wrong number of returns", 665 spec: PeerChooserSpec{ 666 Name: "much sadness", 667 BuildPeerChooser: func(c struct{}, t peer.Transport, k *Kit) {}, 668 }, 669 wantErr: "invalid BuildPeerChooser func(struct {}, peer.Transport, *yarpcconfig.Kit): must return exactly two results, found 0", 670 }, 671 { 672 desc: "wrong type of first return", 673 spec: PeerChooserSpec{ 674 Name: "much sadness", 675 BuildPeerChooser: func(c struct{}, t peer.Transport, b *Kit) (int, error) { 676 return 0, nil 677 }, 678 }, 679 wantErr: "invalid BuildPeerChooser func(struct {}, peer.Transport, *yarpcconfig.Kit) (int, error): must return a peer.Chooser as its first result, found int", 680 }, 681 { 682 desc: "wrong type of second return", 683 spec: PeerChooserSpec{ 684 Name: "much sadness", 685 BuildPeerChooser: func(c struct{}, t peer.Transport, k *Kit) (peer.Chooser, int) { 686 return nil, 0 687 }, 688 }, 689 wantErr: "invalid BuildPeerChooser func(struct {}, peer.Transport, *yarpcconfig.Kit) (peer.Chooser, int): must return an error as its second result, found int", 690 }, 691 { 692 desc: "such gladness", 693 spec: PeerChooserSpec{ 694 Name: "such gladness", 695 BuildPeerChooser: func(c struct{}, t peer.Transport, k *Kit) (peer.Chooser, error) { 696 return nil, nil 697 }, 698 }, 699 wantName: "such gladness", 700 }, 701 } 702 703 for _, tt := range tests { 704 t.Run(tt.desc, func(t *testing.T) { 705 s, err := compilePeerChooserSpec(&tt.spec) 706 if err != nil { 707 assert.Equal(t, tt.wantErr, err.Error(), "expected error") 708 } else { 709 assert.Equal(t, tt.wantName, s.Name, "expected name") 710 } 711 }) 712 } 713 } 714 715 func TestCompileStreamOutboundConfig(t *testing.T) { 716 tests := []struct { 717 desc string 718 build interface{} 719 wantInputType reflect.Type 720 wantErr string 721 }{ 722 { 723 desc: "incorrect return type", 724 build: func(struct{}, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 725 wantErr: "invalid BuildStreamOutbound: must return a transport.StreamOutbound as its first result, found transport.Inbound", 726 }, 727 { 728 desc: "valid: struct{}", 729 build: func(struct{}, transport.Transport, *Kit) (transport.StreamOutbound, error) { panic("kthxbye") }, 730 wantInputType: _typeOfEmptyStruct, 731 }, 732 } 733 for _, tt := range tests { 734 t.Run(tt.desc, func(t *testing.T) { 735 cs, err := compileStreamOutboundConfig(tt.build) 736 737 if tt.wantErr == "" { 738 assert.Equal(t, tt.wantInputType, cs.inputType, "input type mismatch") 739 assert.NoError(t, err, "expected success") 740 return 741 } 742 743 if assert.Error(t, err, "expected failure") { 744 assert.Contains(t, err.Error(), tt.wantErr) 745 } 746 }) 747 } 748 } 749 750 func TestCompilePeerListSpec(t *testing.T) { 751 tests := []struct { 752 desc string 753 spec PeerListSpec 754 wantName string 755 wantErr string 756 }{ 757 { 758 desc: "missing name", 759 wantErr: "field Name is required", 760 }, 761 { 762 desc: "missing BuildPeerList", 763 spec: PeerListSpec{ 764 Name: "random", 765 }, 766 wantErr: "field BuildPeerList is required", 767 }, 768 { 769 desc: "not a function", 770 spec: PeerListSpec{ 771 Name: "much sadness", 772 BuildPeerList: 10, 773 }, 774 wantErr: "invalid BuildPeerList int: must be a function", 775 }, 776 { 777 desc: "too many arguments", 778 spec: PeerListSpec{ 779 Name: "much sadness", 780 BuildPeerList: func(a, b, c, d int) {}, 781 }, 782 wantErr: "invalid BuildPeerList func(int, int, int, int): must accept exactly three arguments, found 4", 783 }, 784 { 785 desc: "wrong kind of first argument", 786 spec: PeerListSpec{ 787 Name: "much sadness", 788 BuildPeerList: func(a, b, c int) {}, 789 }, 790 wantErr: "invalid BuildPeerList func(int, int, int): must accept a struct or struct pointer as its first argument, found int", 791 }, 792 { 793 desc: "wrong kind of second argument", 794 spec: PeerListSpec{ 795 Name: "much sadness", 796 BuildPeerList: func(c struct{}, t int, k *Kit) {}, 797 }, 798 wantErr: "invalid BuildPeerList func(struct {}, int, *yarpcconfig.Kit): must accept a peer.Transport as its second argument, found int", 799 }, 800 { 801 desc: "wrong kind of third argument", 802 spec: PeerListSpec{ 803 Name: "much sadness", 804 BuildPeerList: func(c struct{}, t peer.Transport, k int) {}, 805 }, 806 wantErr: "invalid BuildPeerList func(struct {}, peer.Transport, int): must accept a *yarpcconfig.Kit as its third argument, found int", 807 }, 808 { 809 desc: "wrong number of returns", 810 spec: PeerListSpec{ 811 Name: "much sadness", 812 BuildPeerList: func(c struct{}, t peer.Transport, k *Kit) {}, 813 }, 814 wantErr: "invalid BuildPeerList func(struct {}, peer.Transport, *yarpcconfig.Kit): must return exactly two results, found 0", 815 }, 816 { 817 desc: "wrong type of first return", 818 spec: PeerListSpec{ 819 Name: "much sadness", 820 BuildPeerList: func(c struct{}, t peer.Transport, b *Kit) (int, error) { 821 return 0, nil 822 }, 823 }, 824 wantErr: "invalid BuildPeerList func(struct {}, peer.Transport, *yarpcconfig.Kit) (int, error): must return a peer.ChooserList as its first result, found int", 825 }, 826 { 827 desc: "wrong type of second return", 828 spec: PeerListSpec{ 829 Name: "much sadness", 830 BuildPeerList: func(c struct{}, t peer.Transport, k *Kit) (peer.ChooserList, int) { 831 return nil, 0 832 }, 833 }, 834 wantErr: "invalid BuildPeerList func(struct {}, peer.Transport, *yarpcconfig.Kit) (peer.ChooserList, int): must return an error as its second result, found int", 835 }, 836 { 837 desc: "such gladness", 838 spec: PeerListSpec{ 839 Name: "such gladness", 840 BuildPeerList: func(c struct{}, t peer.Transport, k *Kit) (peer.ChooserList, error) { 841 return nil, nil 842 }, 843 }, 844 wantName: "such gladness", 845 }, 846 } 847 848 for _, tt := range tests { 849 t.Run(tt.desc, func(t *testing.T) { 850 s, err := compilePeerListSpec(&tt.spec) 851 if err != nil { 852 assert.Equal(t, tt.wantErr, err.Error(), "expected error") 853 } else { 854 assert.Equal(t, tt.wantName, s.Name, "expected name") 855 } 856 }) 857 } 858 } 859 860 func TestCompilePeerListUpdaterSpec(t *testing.T) { 861 tests := []struct { 862 desc string 863 spec PeerListUpdaterSpec 864 wantName string 865 wantErr string 866 }{ 867 { 868 desc: "missing name", 869 wantErr: "field Name is required", 870 }, 871 { 872 desc: "missing BuildPeerListUpdater", 873 spec: PeerListUpdaterSpec{ 874 Name: "random", 875 }, 876 wantErr: "field BuildPeerListUpdater is required", 877 }, 878 { 879 desc: "not a function", 880 spec: PeerListUpdaterSpec{ 881 Name: "much sadness", 882 BuildPeerListUpdater: 10, 883 }, 884 wantErr: "invalid BuildPeerListUpdater int: must be a function", 885 }, 886 { 887 desc: "too many arguments", 888 spec: PeerListUpdaterSpec{ 889 Name: "much sadness", 890 BuildPeerListUpdater: func(a, b, c int) {}, 891 }, 892 wantErr: "invalid BuildPeerListUpdater func(int, int, int): must accept exactly two arguments, found 3", 893 }, 894 { 895 desc: "wrong kind of first argument", 896 spec: PeerListUpdaterSpec{ 897 Name: "much sadness", 898 BuildPeerListUpdater: func(a, b int) {}, 899 }, 900 wantErr: "invalid BuildPeerListUpdater func(int, int): must accept a struct or struct pointer as its first argument, found int", 901 }, 902 { 903 desc: "wrong kind of second argument", 904 spec: PeerListUpdaterSpec{ 905 Name: "much sadness", 906 BuildPeerListUpdater: func(a struct{}, b int) {}, 907 }, 908 wantErr: "invalid BuildPeerListUpdater func(struct {}, int): must accept a *yarpcconfig.Kit as its second argument, found int", 909 }, 910 { 911 desc: "wrong number of returns", 912 spec: PeerListUpdaterSpec{ 913 Name: "much sadness", 914 BuildPeerListUpdater: func(a struct{}, b *Kit) {}, 915 }, 916 wantErr: "invalid BuildPeerListUpdater func(struct {}, *yarpcconfig.Kit): must return exactly two results, found 0", 917 }, 918 { 919 desc: "wrong type of first return", 920 spec: PeerListUpdaterSpec{ 921 Name: "much sadness", 922 BuildPeerListUpdater: func(a struct{}, b *Kit) (int, error) { 923 return 0, nil 924 }, 925 }, 926 wantErr: "invalid BuildPeerListUpdater func(struct {}, *yarpcconfig.Kit) (int, error): must return a peer.Binder as its first result, found int", 927 }, 928 { 929 desc: "wrong type of second return", 930 spec: PeerListUpdaterSpec{ 931 Name: "much sadness", 932 BuildPeerListUpdater: func(a struct{}, b *Kit) (peer.Binder, int) { 933 return nil, 0 934 }, 935 }, 936 wantErr: "invalid BuildPeerListUpdater func(struct {}, *yarpcconfig.Kit) (peer.Binder, int): must return an error as its second result, found int", 937 }, 938 { 939 desc: "such gladness", 940 spec: PeerListUpdaterSpec{ 941 Name: "such gladness", 942 BuildPeerListUpdater: func(a struct{}, b *Kit) (peer.Binder, error) { 943 return nil, nil 944 }, 945 }, 946 wantName: "such gladness", 947 }, 948 } 949 950 for _, tt := range tests { 951 t.Run(tt.desc, func(t *testing.T) { 952 s, err := compilePeerListUpdaterSpec(&tt.spec) 953 if err != nil { 954 assert.Equal(t, tt.wantErr, err.Error(), "expected error") 955 } else { 956 assert.Equal(t, tt.wantName, s.Name, "expected name") 957 } 958 }) 959 } 960 } 961 962 func TestCompilePeerChooserPreset(t *testing.T) { 963 tests := []struct { 964 desc string 965 spec PeerChooserPreset 966 wantName string 967 wantErr string 968 }{ 969 { 970 desc: "missing name", 971 wantErr: "field Name is required", 972 }, 973 { 974 desc: "missing BuildPeerChooser", 975 spec: PeerChooserPreset{ 976 Name: "random", 977 }, 978 wantErr: "field BuildPeerChooser is required", 979 }, 980 { 981 desc: "not a function", 982 spec: PeerChooserPreset{ 983 Name: "much sadness", 984 BuildPeerChooser: 10, 985 }, 986 wantErr: "invalid BuildPeerChooser int: must be a function", 987 }, 988 { 989 desc: "too many arguments", 990 spec: PeerChooserPreset{ 991 Name: "much sadness", 992 BuildPeerChooser: func(a, b, c, d int) {}, 993 }, 994 wantErr: "invalid BuildPeerChooser func(int, int, int, int): must accept exactly two arguments, found 4", 995 }, 996 { 997 desc: "wrong kind of first argument", 998 spec: PeerChooserPreset{ 999 Name: "much sadness", 1000 BuildPeerChooser: func(a, b int) {}, 1001 }, 1002 wantErr: "invalid BuildPeerChooser func(int, int): must accept a peer.Transport as its first argument, found int", 1003 }, 1004 { 1005 desc: "wrong kind of second", 1006 spec: PeerChooserPreset{ 1007 Name: "much sadness", 1008 BuildPeerChooser: func(peer.Transport, int) {}, 1009 }, 1010 wantErr: "invalid BuildPeerChooser func(peer.Transport, int): must accept a *yarpcconfig.Kit as its second argument, found int", 1011 }, 1012 { 1013 desc: "wrong number of returns", 1014 spec: PeerChooserPreset{ 1015 Name: "much sadness", 1016 BuildPeerChooser: func(t peer.Transport, k *Kit) {}, 1017 }, 1018 wantErr: "invalid BuildPeerChooser func(peer.Transport, *yarpcconfig.Kit): must return exactly two results, found 0", 1019 }, 1020 { 1021 desc: "wrong type of first return", 1022 spec: PeerChooserPreset{ 1023 Name: "much sadness", 1024 BuildPeerChooser: func(t peer.Transport, b *Kit) (int, error) { 1025 return 0, nil 1026 }, 1027 }, 1028 wantErr: "invalid BuildPeerChooser func(peer.Transport, *yarpcconfig.Kit) (int, error): must return a peer.Chooser as its first result, found int", 1029 }, 1030 { 1031 desc: "wrong type of second return", 1032 spec: PeerChooserPreset{ 1033 Name: "much sadness", 1034 BuildPeerChooser: func(t peer.Transport, k *Kit) (peer.Chooser, int) { 1035 return nil, 0 1036 }, 1037 }, 1038 wantErr: "invalid BuildPeerChooser func(peer.Transport, *yarpcconfig.Kit) (peer.Chooser, int): must return an error as its second result, found int", 1039 }, 1040 { 1041 desc: "such gladness", 1042 spec: PeerChooserPreset{ 1043 Name: "such gladness", 1044 BuildPeerChooser: func(t peer.Transport, k *Kit) (peer.Chooser, error) { 1045 return nil, nil 1046 }, 1047 }, 1048 wantName: "such gladness", 1049 }, 1050 } 1051 1052 for _, tt := range tests { 1053 t.Run(tt.desc, func(t *testing.T) { 1054 s, err := compilePeerChooserPreset(tt.spec) 1055 if err != nil { 1056 assert.Equal(t, tt.wantErr, err.Error(), "expected error") 1057 } else { 1058 assert.Equal(t, tt.wantName, s.name, "expected name") 1059 } 1060 }) 1061 } 1062 } 1063 1064 func TestValidateConfigFunc(t *testing.T) { 1065 tests := []struct { 1066 desc string 1067 1068 // Build function. We'll use its type for the test. 1069 build interface{} 1070 1071 // Type of output expected from the function 1072 outputType reflect.Type 1073 1074 // If non-empty, we expect an error 1075 wantErr string 1076 }{ 1077 { 1078 desc: "not a function", 1079 build: 42, 1080 outputType: _typeOfEmptyStruct, 1081 wantErr: "must be a function", 1082 }, 1083 { 1084 desc: "wrong number of arguments", 1085 build: func(struct{}) (transport.Inbound, error) { panic("kthxbye") }, 1086 outputType: _typeOfInbound, 1087 wantErr: "must accept exactly three arguments, found 1", 1088 }, 1089 { 1090 desc: "incorrect input type", 1091 build: func(int, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 1092 outputType: _typeOfInbound, 1093 wantErr: "must accept a struct or struct pointer as its first argument, found int", 1094 }, 1095 { 1096 desc: "incorrect second argument", 1097 build: func(struct{}, int, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 1098 outputType: _typeOfInbound, 1099 wantErr: "must accept a transport.Transport as its second argument, found int", 1100 }, 1101 { 1102 desc: "wrong number of results", 1103 build: func(struct{}, transport.Transport, *Kit) transport.Inbound { panic("kthxbye") }, 1104 outputType: _typeOfInbound, 1105 wantErr: "must return exactly two results, found 1", 1106 }, 1107 { 1108 desc: "wrong output type", 1109 build: func(struct{}, transport.Transport, *Kit) (transport.Inbound, error) { panic("kthxbye") }, 1110 outputType: _typeOfUnaryOutbound, 1111 wantErr: "must return a transport.UnaryOutbound as its first result, found transport.Inbound", 1112 }, 1113 { 1114 desc: "incorrect second result", 1115 build: func(struct{}, transport.Transport, *Kit) (transport.Inbound, string) { panic("kthxbye") }, 1116 outputType: _typeOfInbound, 1117 wantErr: "must return an error as its second result, found string", 1118 }, 1119 { 1120 desc: "valid", 1121 build: func(struct{}, transport.Transport, *Kit) (struct{}, error) { panic("kthxbye") }, 1122 outputType: _typeOfEmptyStruct, 1123 }, 1124 } 1125 1126 for _, tt := range tests { 1127 t.Run(tt.desc, func(t *testing.T) { 1128 funcType := reflect.TypeOf(tt.build) 1129 err := validateConfigFunc(funcType, tt.outputType) 1130 1131 if tt.wantErr == "" { 1132 assert.NoError(t, err, "expected success") 1133 return 1134 } 1135 1136 if assert.Error(t, err, "expected failure") { 1137 assert.Contains(t, err.Error(), tt.wantErr) 1138 } 1139 }) 1140 } 1141 } 1142 1143 func TestFieldNames(t *testing.T) { 1144 tests := []struct { 1145 give reflect.Type 1146 want []string 1147 }{ 1148 {give: _typeOfEmptyStruct}, 1149 {give: _typeOfError}, 1150 { 1151 give: reflect.TypeOf(struct { 1152 Name string 1153 Value string 1154 hidden int64 1155 }{}), 1156 want: []string{"Name", "Value"}, 1157 }, 1158 } 1159 1160 for _, tt := range tests { 1161 t.Run(fmt.Sprint(tt.give), func(t *testing.T) { 1162 want := make(map[string]struct{}) 1163 for _, f := range tt.want { 1164 want[f] = struct{}{} 1165 } 1166 1167 if len(want) == 0 { 1168 // play nicely with nil/empty 1169 assert.Empty(t, fieldNames(tt.give)) 1170 } else { 1171 assert.Equal(t, want, fieldNames(tt.give)) 1172 } 1173 }) 1174 } 1175 } 1176 1177 func TestIsDecodable(t *testing.T) { 1178 tests := []struct { 1179 give reflect.Type 1180 want bool 1181 }{ 1182 {give: _typeOfError, want: false}, 1183 {give: reflect.PtrTo(_typeOfError), want: false}, 1184 {give: _typeOfEmptyStruct, want: true}, 1185 {give: reflect.PtrTo(_typeOfEmptyStruct), want: true}, 1186 {give: reflect.PtrTo(reflect.PtrTo(_typeOfEmptyStruct)), want: true}, 1187 } 1188 1189 for _, tt := range tests { 1190 t.Run(fmt.Sprint(tt.give), func(t *testing.T) { 1191 assert.Equal(t, tt.want, isDecodable(tt.give)) 1192 }) 1193 } 1194 }