Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(387)

Side by Side Diff: common/lhttp/client_test.go

Issue 2562293002: common/lhttp: close the Response.Body on non-200s (Closed)
Patch Set: Reorder struct fields Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « common/lhttp/client.go ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The LUCI Authors. All rights reserved. 1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package lhttp 5 package lhttp
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 "encoding/json" 9 "encoding/json"
10 "fmt" 10 "fmt"
11 "io" 11 "io"
12 "io/ioutil" 12 "io/ioutil"
13 "net/http" 13 "net/http"
14 "net/http/httptest" 14 "net/http/httptest"
15 "sync"
15 "testing" 16 "testing"
16 17
17 "golang.org/x/net/context" 18 "golang.org/x/net/context"
18 19
19 "github.com/luci/luci-go/common/errors"
20 "github.com/luci/luci-go/common/retry" 20 "github.com/luci/luci-go/common/retry"
21 21
22 "github.com/maruel/ut" 22 "github.com/maruel/ut"
23 ) 23 )
24 24
25 func httpReqGen(method, url string, body []byte) RequestGen { 25 func httpReqGen(method, url string, body []byte) RequestGen {
26 return func() (*http.Request, error) { 26 return func() (*http.Request, error) {
27 var bodyReader io.Reader 27 var bodyReader io.Reader
28 if body != nil { 28 if body != nil {
29 bodyReader = bytes.NewReader(body) 29 bodyReader = bytes.NewReader(body)
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
118 defer ts.Close() 118 defer ts.Close()
119 119
120 httpReq := httpReqGen("GET", ts.URL, nil) 120 httpReq := httpReqGen("GET", ts.URL, nil)
121 121
122 clientReq := NewRequest(ctx, http.DefaultClient, fast, httpReq, func(res p *http.Response) error { 122 clientReq := NewRequest(ctx, http.DefaultClient, fast, httpReq, func(res p *http.Response) error {
123 t.Fail() 123 t.Fail()
124 return nil 124 return nil
125 }) 125 })
126 126
127 status, err := clientReq() 127 status, err := clientReq()
128 » ut.AssertEqual(t, true, errors.IsTransient(err)) 128 » ut.AssertEqual(t, "http request failed: Internal Server Error (HTTP 500) (attempts: 4)", err.Error())
129 » ut.AssertEqual(t, "http request failed: Internal Server Error (HTTP 500) ", err.Error())
130 ut.AssertEqual(t, 500, status) 129 ut.AssertEqual(t, 500, status)
131 } 130 }
132 131
133 func TestGetJSON(t *testing.T) { 132 func TestGetJSON(t *testing.T) {
134 ctx := context.Background() 133 ctx := context.Background()
135 134
136 // First call returns HTTP 500, second succeeds. 135 // First call returns HTTP 500, second succeeds.
137 serverCalls := 0 136 serverCalls := 0
138 ts := httptest.NewServer(handlerJSON(t, func(body io.Reader) interface{} { 137 ts := httptest.NewServer(handlerJSON(t, func(body io.Reader) interface{} {
139 serverCalls++ 138 serverCalls++
(...skipping 22 matching lines...) Expand all
162 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 161 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
163 serverCalls++ 162 serverCalls++
164 w.Header().Set("Content-Type", jsonContentType) 163 w.Header().Set("Content-Type", jsonContentType)
165 _, err := io.WriteString(w, "yo") 164 _, err := io.WriteString(w, "yo")
166 ut.ExpectEqual(t, nil, err) 165 ut.ExpectEqual(t, nil, err)
167 })) 166 }))
168 defer ts.Close() 167 defer ts.Close()
169 168
170 actual := map[string]string{} 169 actual := map[string]string{}
171 status, err := GetJSON(ctx, fast, http.DefaultClient, ts.URL, &actual) 170 status, err := GetJSON(ctx, fast, http.DefaultClient, ts.URL, &actual)
172 » ut.AssertEqual(t, true, errors.IsTransient(err)) 171 » ut.AssertEqual(t, "bad response "+ts.URL+": invalid character 'y' lookin g for beginning of value (attempts: 4)", err.Error())
173 » ut.AssertEqual(t, "bad response "+ts.URL+": invalid character 'y' lookin g for beginning of value", err.Error())
174 ut.AssertEqual(t, 200, status) 172 ut.AssertEqual(t, 200, status)
175 ut.AssertEqual(t, map[string]string{}, actual) 173 ut.AssertEqual(t, map[string]string{}, actual)
176 } 174 }
177 175
178 func TestGetJSONBadResultIgnore(t *testing.T) { 176 func TestGetJSONBadResultIgnore(t *testing.T) {
179 ctx := context.Background() 177 ctx := context.Background()
180 178
181 serverCalls := 0 179 serverCalls := 0
182 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 180 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
183 serverCalls++ 181 serverCalls++
184 w.Header().Set("Content-Type", jsonContentType) 182 w.Header().Set("Content-Type", jsonContentType)
185 _, err := io.WriteString(w, "yo") 183 _, err := io.WriteString(w, "yo")
186 ut.ExpectEqual(t, nil, err) 184 ut.ExpectEqual(t, nil, err)
187 })) 185 }))
188 defer ts.Close() 186 defer ts.Close()
189 187
190 status, err := GetJSON(ctx, fast, http.DefaultClient, ts.URL, nil) 188 status, err := GetJSON(ctx, fast, http.DefaultClient, ts.URL, nil)
191 » ut.AssertEqual(t, true, errors.IsTransient(err)) 189 » ut.AssertEqual(t, "bad response "+ts.URL+": invalid character 'y' lookin g for beginning of value (attempts: 4)", err.Error())
192 » ut.AssertEqual(t, "bad response "+ts.URL+": invalid character 'y' lookin g for beginning of value", err.Error())
193 ut.AssertEqual(t, 200, status) 190 ut.AssertEqual(t, 200, status)
194 } 191 }
195 192
196 func TestGetJSONBadContentTypeIgnore(t *testing.T) { 193 func TestGetJSONBadContentTypeIgnore(t *testing.T) {
197 ctx := context.Background() 194 ctx := context.Background()
198 195
199 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 196 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
200 _, err := io.WriteString(w, "{}") 197 _, err := io.WriteString(w, "{}")
201 ut.ExpectEqual(t, nil, err) 198 ut.ExpectEqual(t, nil, err)
202 })) 199 }))
203 defer ts.Close() 200 defer ts.Close()
204 201
205 status, err := GetJSON(ctx, fast, http.DefaultClient, ts.URL, nil) 202 status, err := GetJSON(ctx, fast, http.DefaultClient, ts.URL, nil)
206 » ut.AssertEqual(t, "unexpected Content-Type, expected \"application/json\ ", got \"text/plain; charset=utf-8\"", err.Error()) 203 » ut.AssertEqual(t, "unexpected Content-Type, expected \"application/json\ ", got \"text/plain; charset=utf-8\" (attempts: 4)", err.Error())
207 ut.AssertEqual(t, 200, status) 204 ut.AssertEqual(t, 200, status)
208 } 205 }
209 206
210 func TestPostJSON(t *testing.T) { 207 func TestPostJSON(t *testing.T) {
211 ctx := context.Background() 208 ctx := context.Background()
212 209
213 // First call returns HTTP 500, second succeeds. 210 // First call returns HTTP 500, second succeeds.
214 serverCalls := 0 211 serverCalls := 0
215 ts := httptest.NewServer(handlerJSON(t, func(body io.Reader) interface{} { 212 ts := httptest.NewServer(handlerJSON(t, func(body io.Reader) interface{} {
216 serverCalls++ 213 serverCalls++
(...skipping 29 matching lines...) Expand all
246 serverCalls++ 243 serverCalls++
247 })) 244 }))
248 defer ts.Close() 245 defer ts.Close()
249 246
250 status, err := PostJSON(ctx, fast, http.DefaultClient, ts.URL, map[strin g]string{"key": "value"}, nil, nil) 247 status, err := PostJSON(ctx, fast, http.DefaultClient, ts.URL, map[strin g]string{"key": "value"}, nil, nil)
251 ut.AssertEqual(t, nil, err) 248 ut.AssertEqual(t, nil, err)
252 ut.AssertEqual(t, 200, status) 249 ut.AssertEqual(t, 200, status)
253 ut.AssertEqual(t, 1, serverCalls) 250 ut.AssertEqual(t, 1, serverCalls)
254 } 251 }
255 252
253 func TestNewRequestClosesBody(t *testing.T) {
254 ctx := context.Background()
255 serverCalls := 0
256
257 // Return a 500 for the first 2 requests.
258 ts := httptest.NewServer(
259 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
260 defer r.Body.Close()
261 serverCalls++
262 if serverCalls <= 2 {
263 w.WriteHeader(500)
264 }
265 fmt.Fprintf(w, "Hello World!\n")
266 }))
267 defer ts.Close()
268
269 rt := &trackingRoundTripper{RoundTripper: http.DefaultTransport}
270 hc := &http.Client{Transport: rt}
271 httpReq := httpReqGen("GET", ts.URL, nil)
272
273 clientCalls := 0
274 var lastResp *http.Response
275 req := NewRequest(ctx, hc, fast, httpReq, func(resp *http.Response) erro r {
276 clientCalls++
277 lastResp = resp
278 return resp.Body.Close()
279 })
280
281 status, err := req()
282 if err != nil {
283 t.Fatalf("req returned err %v, want nil", err)
284 }
285 if got, want := status, http.StatusOK; got != want {
286 t.Errorf("req returned status %d, want %d", got, want)
287 }
288
289 // We expect only one client call, but three requests through to the ser ver.
290 if got, want := clientCalls, 1; got != want {
291 t.Errorf("handler callback invoked %d times, want %d", got, want )
292 }
293 if got, want := len(rt.Responses), 3; got != want {
294 t.Errorf("len(Responses) = %d, want %d", got, want)
295 }
296
297 // Check that the last response is the one we handled, and that all the bodies
298 // were closed.
299 if got, want := lastResp, rt.Responses[2]; got != want {
300 t.Errorf("Last Response did not match Response in handler callba ck.\nGot: %v\nWant: %v", got, want)
301 }
302 for i, resp := range rt.Responses {
303 rc := resp.Body.(*trackingReadCloser)
304 if !rc.Closed {
305 t.Errorf("Reponses[%d].Body was not closed", i)
306 }
307 }
308 }
309
310 // trackingRoundTripper wraps an http.RoundTripper, keeping track of any
311 // returned Responses. Each response's Body, when set, is wrapped with a
312 // trackingReadCloser.
313 type trackingRoundTripper struct {
314 http.RoundTripper
315
316 mu sync.Mutex
317 Responses []*http.Response
318 }
319
320 func (t *trackingRoundTripper) RoundTrip(req *http.Request) (*http.Response, err or) {
321 resp, err := t.RoundTripper.RoundTrip(req)
322 if resp != nil && resp.Body != nil {
323 resp.Body = &trackingReadCloser{ReadCloser: resp.Body}
324 }
325 t.mu.Lock()
326 defer t.mu.Unlock()
327 t.Responses = append(t.Responses, resp)
328 return resp, err
329 }
330
331 // trackingReadCloser wraps an io.ReadCloser, keeping track of whether Closed wa s
332 // called.
333 type trackingReadCloser struct {
334 io.ReadCloser
335 Closed bool
336 }
337
338 func (t *trackingReadCloser) Close() error {
339 t.Closed = true
340 return t.ReadCloser.Close()
341 }
342
256 // Private details. 343 // Private details.
257 344
258 func fast() retry.Iterator { 345 func fast() retry.Iterator {
259 return &retry.Limited{Retries: 3} 346 return &retry.Limited{Retries: 3}
260 } 347 }
261 348
262 type jsonAPI func(body io.Reader) interface{} 349 type jsonAPI func(body io.Reader) interface{}
263 350
264 // handlerJSON converts a jsonAPI http handler to a proper http.Handler. 351 // handlerJSON converts a jsonAPI http handler to a proper http.Handler.
265 func handlerJSON(t *testing.T, handler jsonAPI) http.Handler { 352 func handlerJSON(t *testing.T, handler jsonAPI) http.Handler {
266 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 353 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
267 //ut.ExpectEqual(t, jsonContentType, r.Header.Get("Content-Type" )) 354 //ut.ExpectEqual(t, jsonContentType, r.Header.Get("Content-Type" ))
268 defer r.Body.Close() 355 defer r.Body.Close()
269 out := handler(r.Body) 356 out := handler(r.Body)
270 if out == nil { 357 if out == nil {
271 w.WriteHeader(500) 358 w.WriteHeader(500)
272 } else { 359 } else {
273 w.Header().Set("Content-Type", jsonContentType) 360 w.Header().Set("Content-Type", jsonContentType)
274 ut.ExpectEqual(t, nil, json.NewEncoder(w).Encode(out)) 361 ut.ExpectEqual(t, nil, json.NewEncoder(w).Encode(out))
275 } 362 }
276 }) 363 })
277 } 364 }
OLDNEW
« no previous file with comments | « common/lhttp/client.go ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698