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  }