http_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*
  2. Copyright 2014 CoreOS, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package client
  14. import (
  15. "errors"
  16. "io/ioutil"
  17. "net/http"
  18. "net/url"
  19. "reflect"
  20. "strings"
  21. "testing"
  22. "time"
  23. "github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context"
  24. )
  25. type staticHTTPClient struct {
  26. resp http.Response
  27. err error
  28. }
  29. func (s *staticHTTPClient) Do(context.Context, HTTPAction) (*http.Response, []byte, error) {
  30. return &s.resp, nil, s.err
  31. }
  32. type fakeTransport struct {
  33. respchan chan *http.Response
  34. errchan chan error
  35. startCancel chan struct{}
  36. finishCancel chan struct{}
  37. }
  38. func newFakeTransport() *fakeTransport {
  39. return &fakeTransport{
  40. respchan: make(chan *http.Response, 1),
  41. errchan: make(chan error, 1),
  42. startCancel: make(chan struct{}, 1),
  43. finishCancel: make(chan struct{}, 1),
  44. }
  45. }
  46. func (t *fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
  47. select {
  48. case resp := <-t.respchan:
  49. return resp, nil
  50. case err := <-t.errchan:
  51. return nil, err
  52. case <-t.startCancel:
  53. // wait on finishCancel to simulate taking some amount of
  54. // time while calling CancelRequest
  55. <-t.finishCancel
  56. return nil, errors.New("cancelled")
  57. }
  58. }
  59. func (t *fakeTransport) CancelRequest(*http.Request) {
  60. t.startCancel <- struct{}{}
  61. }
  62. type fakeAction struct{}
  63. func (a *fakeAction) HTTPRequest(url.URL) *http.Request {
  64. return &http.Request{}
  65. }
  66. func TestHTTPClientDoSuccess(t *testing.T) {
  67. tr := newFakeTransport()
  68. c := &httpClient{transport: tr}
  69. tr.respchan <- &http.Response{
  70. StatusCode: http.StatusTeapot,
  71. Body: ioutil.NopCloser(strings.NewReader("foo")),
  72. }
  73. resp, body, err := c.Do(context.Background(), &fakeAction{})
  74. if err != nil {
  75. t.Fatalf("incorrect error value: want=nil got=%v", err)
  76. }
  77. wantCode := http.StatusTeapot
  78. if wantCode != resp.StatusCode {
  79. t.Fatalf("invalid response code: want=%d got=%d", wantCode, resp.StatusCode)
  80. }
  81. wantBody := []byte("foo")
  82. if !reflect.DeepEqual(wantBody, body) {
  83. t.Fatalf("invalid response body: want=%q got=%q", wantBody, body)
  84. }
  85. }
  86. func TestHTTPClientDoError(t *testing.T) {
  87. tr := newFakeTransport()
  88. c := &httpClient{transport: tr}
  89. tr.errchan <- errors.New("fixture")
  90. _, _, err := c.Do(context.Background(), &fakeAction{})
  91. if err == nil {
  92. t.Fatalf("expected non-nil error, got nil")
  93. }
  94. }
  95. func TestHTTPClientDoCancelContext(t *testing.T) {
  96. tr := newFakeTransport()
  97. c := &httpClient{transport: tr}
  98. tr.startCancel <- struct{}{}
  99. tr.finishCancel <- struct{}{}
  100. _, _, err := c.Do(context.Background(), &fakeAction{})
  101. if err == nil {
  102. t.Fatalf("expected non-nil error, got nil")
  103. }
  104. }
  105. func TestHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) {
  106. tr := newFakeTransport()
  107. c := &httpClient{transport: tr}
  108. donechan := make(chan struct{})
  109. ctx, cancel := context.WithCancel(context.Background())
  110. go func() {
  111. c.Do(ctx, &fakeAction{})
  112. close(donechan)
  113. }()
  114. // This should call CancelRequest and begin the cancellation process
  115. cancel()
  116. select {
  117. case <-donechan:
  118. t.Fatalf("httpClient.do should not have exited yet")
  119. default:
  120. }
  121. tr.finishCancel <- struct{}{}
  122. select {
  123. case <-donechan:
  124. //expected behavior
  125. return
  126. case <-time.After(time.Second):
  127. t.Fatalf("httpClient.do did not exit within 1s")
  128. }
  129. }
  130. func TestHTTPClusterClientDo(t *testing.T) {
  131. fakeErr := errors.New("fake!")
  132. tests := []struct {
  133. client *httpClusterClient
  134. wantCode int
  135. wantErr error
  136. }{
  137. // first good response short-circuits Do
  138. {
  139. client: &httpClusterClient{
  140. endpoints: []HTTPClient{
  141. &staticHTTPClient{resp: http.Response{StatusCode: http.StatusTeapot}},
  142. &staticHTTPClient{err: fakeErr},
  143. },
  144. },
  145. wantCode: http.StatusTeapot,
  146. },
  147. // fall through to good endpoint if err is arbitrary
  148. {
  149. client: &httpClusterClient{
  150. endpoints: []HTTPClient{
  151. &staticHTTPClient{err: fakeErr},
  152. &staticHTTPClient{resp: http.Response{StatusCode: http.StatusTeapot}},
  153. },
  154. },
  155. wantCode: http.StatusTeapot,
  156. },
  157. // ErrTimeout short-circuits Do
  158. {
  159. client: &httpClusterClient{
  160. endpoints: []HTTPClient{
  161. &staticHTTPClient{err: ErrTimeout},
  162. &staticHTTPClient{resp: http.Response{StatusCode: http.StatusTeapot}},
  163. },
  164. },
  165. wantErr: ErrTimeout,
  166. },
  167. // ErrCanceled short-circuits Do
  168. {
  169. client: &httpClusterClient{
  170. endpoints: []HTTPClient{
  171. &staticHTTPClient{err: ErrCanceled},
  172. &staticHTTPClient{resp: http.Response{StatusCode: http.StatusTeapot}},
  173. },
  174. },
  175. wantErr: ErrCanceled,
  176. },
  177. // return err if all endpoints return arbitrary errors
  178. {
  179. client: &httpClusterClient{
  180. endpoints: []HTTPClient{
  181. &staticHTTPClient{err: fakeErr},
  182. &staticHTTPClient{err: fakeErr},
  183. },
  184. },
  185. wantErr: fakeErr,
  186. },
  187. // 500-level errors cause Do to fallthrough to next endpoint
  188. {
  189. client: &httpClusterClient{
  190. endpoints: []HTTPClient{
  191. &staticHTTPClient{resp: http.Response{StatusCode: http.StatusBadGateway}},
  192. &staticHTTPClient{resp: http.Response{StatusCode: http.StatusTeapot}},
  193. },
  194. },
  195. wantCode: http.StatusTeapot,
  196. },
  197. }
  198. for i, tt := range tests {
  199. resp, _, err := tt.client.Do(context.Background(), nil)
  200. if !reflect.DeepEqual(tt.wantErr, err) {
  201. t.Errorf("#%d: got err=%v, want=%v", i, err, tt.wantErr)
  202. continue
  203. }
  204. if resp == nil {
  205. if tt.wantCode != 0 {
  206. t.Errorf("#%d: resp is nil, want=%d", i, tt.wantCode)
  207. }
  208. continue
  209. }
  210. if resp.StatusCode != tt.wantCode {
  211. t.Errorf("#%d: resp code=%d, want=%d", i, resp.StatusCode, tt.wantCode)
  212. continue
  213. }
  214. }
  215. }