go.uber.org/yarpc@v1.72.1/transport/http/transport_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 http 22 23 import ( 24 "context" 25 "errors" 26 "net" 27 "net/http" 28 "testing" 29 "time" 30 31 "github.com/golang/mock/gomock" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 "go.uber.org/yarpc/api/peer" 35 . "go.uber.org/yarpc/api/peer/peertest" 36 "go.uber.org/yarpc/internal/testtime" 37 ypeer "go.uber.org/yarpc/peer" 38 "go.uber.org/yarpc/peer/hostport" 39 ) 40 41 // NoJitter is a transport option only available in tests, to disable jitter 42 // between connection attempts. 43 func NoJitter() TransportOption { 44 return func(options *transportOptions) { 45 options.jitter = func(n int64) int64 { 46 return n 47 } 48 } 49 } 50 51 type peerExpectation struct { 52 id string 53 subscribers []string 54 } 55 56 func createPeerIdentifierMap(ids []string) map[string]peer.Identifier { 57 pids := make(map[string]peer.Identifier, len(ids)) 58 for _, id := range ids { 59 pids[id] = &testIdentifier{id} 60 } 61 return pids 62 } 63 64 func TestTransport(t *testing.T) { 65 type testStruct struct { 66 msg string 67 68 // identifiers defines all the Identifiers that will be used in 69 // the actions up from so they can be generated and passed as deps 70 identifiers []string 71 72 // subscriberDefs defines all the Subscribers that will be used in 73 // the actions up from so they can be generated and passed as deps 74 subscriberDefs []SubscriberDefinition 75 76 // actions are the actions that will be applied against the transport 77 actions []TransportAction 78 79 // expectedPeers are a list of peers (and those peer's subscribers) 80 // that are expected on the transport after the actions 81 expectedPeers []peerExpectation 82 } 83 tests := []testStruct{ 84 { 85 msg: "one retain", 86 identifiers: []string{"i1"}, 87 subscriberDefs: []SubscriberDefinition{ 88 {ID: "s1"}, 89 }, 90 actions: []TransportAction{ 91 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"}, 92 }, 93 expectedPeers: []peerExpectation{ 94 {id: "i1", subscribers: []string{"s1"}}, 95 }, 96 }, 97 { 98 msg: "one retain one release", 99 identifiers: []string{"i1"}, 100 subscriberDefs: []SubscriberDefinition{ 101 {ID: "s1"}, 102 }, 103 actions: []TransportAction{ 104 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"}, 105 ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s1"}, 106 }, 107 }, 108 { 109 msg: "three retains", 110 identifiers: []string{"i1"}, 111 subscriberDefs: []SubscriberDefinition{ 112 {ID: "s1"}, 113 {ID: "s2"}, 114 {ID: "s3"}, 115 }, 116 actions: []TransportAction{ 117 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"}, 118 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2", ExpectedPeerID: "i1"}, 119 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s3", ExpectedPeerID: "i1"}, 120 }, 121 expectedPeers: []peerExpectation{ 122 {id: "i1", subscribers: []string{"s1", "s2", "s3"}}, 123 }, 124 }, 125 { 126 msg: "three retains one release", 127 identifiers: []string{"i1"}, 128 subscriberDefs: []SubscriberDefinition{ 129 {ID: "s1"}, 130 {ID: "s2r"}, 131 {ID: "s3"}, 132 }, 133 actions: []TransportAction{ 134 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"}, 135 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2r", ExpectedPeerID: "i1"}, 136 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s3", ExpectedPeerID: "i1"}, 137 ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s2r"}, 138 }, 139 expectedPeers: []peerExpectation{ 140 {id: "i1", subscribers: []string{"s1", "s3"}}, 141 }, 142 }, 143 { 144 msg: "three retains, three release", 145 identifiers: []string{"i1"}, 146 subscriberDefs: []SubscriberDefinition{ 147 {ID: "s1"}, 148 {ID: "s2"}, 149 {ID: "s3"}, 150 }, 151 actions: []TransportAction{ 152 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"}, 153 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2", ExpectedPeerID: "i1"}, 154 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s3", ExpectedPeerID: "i1"}, 155 ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s1"}, 156 ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s2"}, 157 ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s3"}, 158 }, 159 }, 160 { 161 msg: "no retains one release", 162 identifiers: []string{"i1"}, 163 subscriberDefs: []SubscriberDefinition{ 164 {ID: "s1"}, 165 }, 166 actions: []TransportAction{ 167 ReleaseAction{ 168 InputIdentifierID: "i1", 169 InputSubscriberID: "s1", 170 ExpectedErrType: peer.ErrTransportHasNoReferenceToPeer{}, 171 }, 172 }, 173 }, 174 { 175 msg: "one retains, one release (from different subscriber)", 176 identifiers: []string{"i1"}, 177 subscriberDefs: []SubscriberDefinition{ 178 {ID: "s1"}, 179 {ID: "s2"}, 180 }, 181 actions: []TransportAction{ 182 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"}, 183 ReleaseAction{ 184 InputIdentifierID: "i1", 185 InputSubscriberID: "s2", 186 ExpectedErrType: peer.ErrPeerHasNoReferenceToSubscriber{}, 187 }, 188 }, 189 expectedPeers: []peerExpectation{ 190 {id: "i1", subscribers: []string{"s1"}}, 191 }, 192 }, 193 { 194 msg: "multi peer retain/release", 195 identifiers: []string{"i1", "i2", "i3", "i4r", "i5r"}, 196 subscriberDefs: []SubscriberDefinition{ 197 {ID: "s1"}, 198 {ID: "s2"}, 199 {ID: "s3"}, 200 {ID: "s4"}, 201 {ID: "s5rnd"}, 202 {ID: "s6rnd"}, 203 {ID: "s7rnd"}, 204 }, 205 actions: []TransportAction{ 206 // Retains/Releases of i1 (Retain/Release the random peers at the end) 207 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s5rnd", ExpectedPeerID: "i1"}, 208 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s6rnd", ExpectedPeerID: "i1"}, 209 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s1", ExpectedPeerID: "i1"}, 210 RetainAction{InputIdentifierID: "i1", InputSubscriberID: "s2", ExpectedPeerID: "i1"}, 211 ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s5rnd"}, 212 ReleaseAction{InputIdentifierID: "i1", InputSubscriberID: "s6rnd"}, 213 214 // Retains/Releases of i2 (Retain then Release then Retain again) 215 RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s2", ExpectedPeerID: "i2"}, 216 RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s3", ExpectedPeerID: "i2"}, 217 ReleaseAction{InputIdentifierID: "i2", InputSubscriberID: "s2"}, 218 ReleaseAction{InputIdentifierID: "i2", InputSubscriberID: "s3"}, 219 RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s2", ExpectedPeerID: "i2"}, 220 RetainAction{InputIdentifierID: "i2", InputSubscriberID: "s3", ExpectedPeerID: "i2"}, 221 222 // Retains/Releases of i3 (Retain/Release unrelated sub, then retain two) 223 RetainAction{InputIdentifierID: "i3", InputSubscriberID: "s7rnd", ExpectedPeerID: "i3"}, 224 ReleaseAction{InputIdentifierID: "i3", InputSubscriberID: "s7rnd"}, 225 RetainAction{InputIdentifierID: "i3", InputSubscriberID: "s3", ExpectedPeerID: "i3"}, 226 RetainAction{InputIdentifierID: "i3", InputSubscriberID: "s4", ExpectedPeerID: "i3"}, 227 228 // Retain/Release i4r on random subscriber 229 RetainAction{InputIdentifierID: "i4r", InputSubscriberID: "s5rnd", ExpectedPeerID: "i4r"}, 230 ReleaseAction{InputIdentifierID: "i4r", InputSubscriberID: "s5rnd"}, 231 232 // Retain/Release i5r on already used subscriber 233 RetainAction{InputIdentifierID: "i5r", InputSubscriberID: "s3", ExpectedPeerID: "i5r"}, 234 ReleaseAction{InputIdentifierID: "i5r", InputSubscriberID: "s3"}, 235 }, 236 expectedPeers: []peerExpectation{ 237 {id: "i1", subscribers: []string{"s1", "s2"}}, 238 {id: "i2", subscribers: []string{"s2", "s3"}}, 239 {id: "i3", subscribers: []string{"s3", "s4"}}, 240 }, 241 }, 242 } 243 244 for _, tt := range tests { 245 t.Run(tt.msg, func(t *testing.T) { 246 mockCtrl := gomock.NewController(t) 247 defer mockCtrl.Finish() 248 249 transport := NewTransport() 250 defer transport.Stop() 251 252 deps := TransportDeps{ 253 PeerIdentifiers: createPeerIdentifierMap(tt.identifiers), 254 Subscribers: CreateSubscriberMap(mockCtrl, tt.subscriberDefs), 255 } 256 ApplyTransportActions(t, transport, tt.actions, deps) 257 258 assert.Len(t, transport.peers, len(tt.expectedPeers)) 259 for _, expectedPeerNode := range tt.expectedPeers { 260 p, ok := transport.peers[expectedPeerNode.id] 261 assert.True(t, ok) 262 263 if assert.NotNil(t, p) { 264 assert.Equal(t, expectedPeerNode.id, p.Identifier()) 265 266 // We can't look at the hostport subscribers directly so we'll 267 // attempt to remove subscribers and be sure that it doesn't error 268 assert.Len(t, expectedPeerNode.subscribers, p.NumSubscribers()) 269 for _, sub := range expectedPeerNode.subscribers { 270 err := p.Unsubscribe(deps.Subscribers[sub]) 271 assert.NoError(t, err, "peer %s did not have reference to subscriber %s", p.Identifier(), sub) 272 } 273 } 274 } 275 }) 276 } 277 } 278 279 func TestTransportClient(t *testing.T) { 280 transport := NewTransport() 281 282 assert.NotNil(t, transport.client) 283 } 284 285 func TestTransportClientOpaqueOptions(t *testing.T) { 286 // Unfortunately the KeepAlive is obfuscated in the client, so we can't really 287 // assert this worked. 288 transport := NewTransport( 289 KeepAlive(testtime.Second), 290 MaxIdleConns(100), 291 MaxIdleConnsPerHost(10), 292 IdleConnTimeout(1*time.Second), 293 DisableCompression(), 294 DisableKeepAlives(), 295 ResponseHeaderTimeout(1*time.Second), 296 ) 297 298 assert.NotNil(t, transport.client) 299 } 300 301 func TestDialContext(t *testing.T) { 302 errMsg := "my custom dialer error message" 303 dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { 304 return nil, errors.New(errMsg) 305 } 306 307 transport := NewTransport(DialContext(dialContext)) 308 309 require.NoError(t, transport.Start()) 310 defer func() { assert.NoError(t, transport.Stop()) }() 311 312 req, err := http.NewRequest("GET", "http://foo.bar", nil) 313 require.NoError(t, err) 314 315 outbound := transport.NewOutbound(ypeer.NewSingle(hostport.Identify("foo"), transport)) 316 require.NoError(t, outbound.Start()) 317 defer func() { assert.NoError(t, outbound.Stop()) }() 318 319 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 320 defer cancel() 321 322 _, err = outbound.RoundTrip(req.WithContext(ctx)) 323 require.Error(t, err) 324 assert.Contains(t, err.Error(), errMsg) 325 } 326 327 type testIdentifier struct { 328 id string 329 } 330 331 func (i testIdentifier) Identifier() string { 332 return i.id 333 }