retry_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package gensupport
  2. import (
  3. "errors"
  4. "io"
  5. "net"
  6. "net/http"
  7. "testing"
  8. "time"
  9. "golang.org/x/net/context"
  10. )
  11. func TestRetry(t *testing.T) {
  12. testCases := []struct {
  13. desc string
  14. respStatus []int // HTTP status codes returned (length indicates number of calls we expect).
  15. maxRetry int // Max number of calls allowed by the BackoffStrategy.
  16. wantStatus int // StatusCode of returned response.
  17. }{
  18. {
  19. desc: "First call successful",
  20. respStatus: []int{200},
  21. maxRetry: 3,
  22. wantStatus: 200,
  23. },
  24. {
  25. desc: "Retry before success",
  26. respStatus: []int{500, 500, 500, 200},
  27. maxRetry: 3,
  28. wantStatus: 200,
  29. },
  30. {
  31. desc: "Backoff strategy abandons after 3 retries",
  32. respStatus: []int{500, 500, 500, 500},
  33. maxRetry: 3,
  34. wantStatus: 500,
  35. },
  36. {
  37. desc: "Backoff strategy abandons after 2 retries",
  38. respStatus: []int{500, 500, 500},
  39. maxRetry: 2,
  40. wantStatus: 500,
  41. },
  42. }
  43. for _, tt := range testCases {
  44. // Function consumes tt.respStatus
  45. f := func() (*http.Response, error) {
  46. if len(tt.respStatus) == 0 {
  47. return nil, errors.New("too many requests to function")
  48. }
  49. resp := &http.Response{StatusCode: tt.respStatus[0]}
  50. tt.respStatus = tt.respStatus[1:]
  51. return resp, nil
  52. }
  53. backoff := &LimitRetryStrategy{
  54. Max: tt.maxRetry,
  55. Strategy: NoPauseStrategy,
  56. }
  57. resp, err := Retry(nil, f, backoff)
  58. if err != nil {
  59. t.Errorf("%s: Retry returned err %v", tt.desc, err)
  60. }
  61. if got := resp.StatusCode; got != tt.wantStatus {
  62. t.Errorf("%s: Retry returned response with StatusCode=%d; want %d", got, tt.wantStatus)
  63. }
  64. if len(tt.respStatus) != 0 {
  65. t.Errorf("%s: f was not called enough; status codes remaining: %v", tt.desc, tt.respStatus)
  66. }
  67. }
  68. }
  69. type checkCloseReader struct {
  70. closed bool
  71. }
  72. func (c *checkCloseReader) Read(p []byte) (n int, err error) { return 0, io.EOF }
  73. func (c *checkCloseReader) Close() error {
  74. c.closed = true
  75. return nil
  76. }
  77. func TestRetryClosesBody(t *testing.T) {
  78. var i int
  79. responses := []*http.Response{
  80. {StatusCode: 500, Body: &checkCloseReader{}},
  81. {StatusCode: 500, Body: &checkCloseReader{}},
  82. {StatusCode: 200, Body: &checkCloseReader{}},
  83. }
  84. f := func() (*http.Response, error) {
  85. resp := responses[i]
  86. i++
  87. return resp, nil
  88. }
  89. resp, err := Retry(nil, f, NoPauseStrategy)
  90. if err != nil {
  91. t.Fatalf("Retry returned error: %v", err)
  92. }
  93. if resp != responses[2] {
  94. t.Errorf("Retry returned %v; want %v", resp, responses[2])
  95. }
  96. for i, resp := range responses {
  97. want := i != 2 // Only the last response should not be closed.
  98. got := resp.Body.(*checkCloseReader).closed
  99. if got != want {
  100. t.Errorf("response[%d].Body closed = %t, want %t", got, want)
  101. }
  102. }
  103. }
  104. func RetryReturnsOnContextCancel(t *testing.T) {
  105. f := func() (*http.Response, error) {
  106. return nil, io.ErrUnexpectedEOF
  107. }
  108. backoff := UniformPauseStrategy(time.Hour)
  109. ctx, cancel := context.WithCancel(context.Background())
  110. errc := make(chan error, 1)
  111. go func() {
  112. _, err := Retry(ctx, f, backoff)
  113. errc <- err
  114. }()
  115. cancel()
  116. select {
  117. case err := <-errc:
  118. if err != ctx.Err() {
  119. t.Errorf("Retry returned err: %v, want %v", err, ctx.Err())
  120. }
  121. case <-time.After(5 * time.Second):
  122. t.Errorf("Timed out waiting for Retry to return")
  123. }
  124. }
  125. func TestShouldRetry(t *testing.T) {
  126. testCases := []struct {
  127. status int
  128. err error
  129. want bool
  130. }{
  131. {status: 200, want: false},
  132. {status: 308, want: false},
  133. {status: 403, want: false},
  134. {status: 429, want: true},
  135. {status: 500, want: true},
  136. {status: 503, want: true},
  137. {status: 600, want: false},
  138. {err: io.EOF, want: false},
  139. {err: errors.New("random badness"), want: false},
  140. {err: io.ErrUnexpectedEOF, want: true},
  141. {err: &net.AddrError{}, want: false}, // Not temporary.
  142. {err: &net.DNSError{IsTimeout: true}, want: true}, // Temporary.
  143. }
  144. for _, tt := range testCases {
  145. if got := shouldRetry(tt.status, tt.err); got != tt.want {
  146. t.Errorf("shouldRetry(%d, %v) = %t; want %t", tt.status, tt.err, got, tt.want)
  147. }
  148. }
  149. }