1// Copyright 2016, Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30package gax 31 32import ( 33 "context" 34 "errors" 35 "testing" 36 "time" 37) 38 39var canceledContext context.Context 40 41func init() { 42 ctx, cancel := context.WithCancel(context.Background()) 43 cancel() 44 canceledContext = ctx 45} 46 47// recordSleeper is a test implementation of sleeper. 48type recordSleeper int 49 50func (s *recordSleeper) sleep(ctx context.Context, _ time.Duration) error { 51 *s++ 52 return ctx.Err() 53} 54 55type boolRetryer bool 56 57func (r boolRetryer) Retry(err error) (time.Duration, bool) { return 0, bool(r) } 58 59func TestInvokeSuccess(t *testing.T) { 60 apiCall := func(context.Context, CallSettings) error { return nil } 61 var sp recordSleeper 62 err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep) 63 64 if err != nil { 65 t.Errorf("found error %s, want nil", err) 66 } 67 if sp != 0 { 68 t.Errorf("slept %d times, should not have slept since the call succeeded", int(sp)) 69 } 70} 71 72func TestInvokeNoRetry(t *testing.T) { 73 apiErr := errors.New("foo error") 74 apiCall := func(context.Context, CallSettings) error { return apiErr } 75 var sp recordSleeper 76 err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep) 77 78 if err != apiErr { 79 t.Errorf("found error %s, want %s", err, apiErr) 80 } 81 if sp != 0 { 82 t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp)) 83 } 84} 85 86func TestInvokeNilRetry(t *testing.T) { 87 apiErr := errors.New("foo error") 88 apiCall := func(context.Context, CallSettings) error { return apiErr } 89 var settings CallSettings 90 WithRetry(func() Retryer { return nil }).Resolve(&settings) 91 var sp recordSleeper 92 err := invoke(context.Background(), apiCall, settings, sp.sleep) 93 94 if err != apiErr { 95 t.Errorf("found error %s, want %s", err, apiErr) 96 } 97 if sp != 0 { 98 t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp)) 99 } 100} 101 102func TestInvokeNeverRetry(t *testing.T) { 103 apiErr := errors.New("foo error") 104 apiCall := func(context.Context, CallSettings) error { return apiErr } 105 var settings CallSettings 106 WithRetry(func() Retryer { return boolRetryer(false) }).Resolve(&settings) 107 var sp recordSleeper 108 err := invoke(context.Background(), apiCall, settings, sp.sleep) 109 110 if err != apiErr { 111 t.Errorf("found error %s, want %s", err, apiErr) 112 } 113 if sp != 0 { 114 t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp)) 115 } 116} 117 118func TestInvokeRetry(t *testing.T) { 119 const target = 3 120 121 retryNum := 0 122 apiErr := errors.New("foo error") 123 apiCall := func(context.Context, CallSettings) error { 124 retryNum++ 125 if retryNum < target { 126 return apiErr 127 } 128 return nil 129 } 130 var settings CallSettings 131 WithRetry(func() Retryer { return boolRetryer(true) }).Resolve(&settings) 132 var sp recordSleeper 133 err := invoke(context.Background(), apiCall, settings, sp.sleep) 134 135 if err != nil { 136 t.Errorf("found error %s, want nil, call should have succeeded after %d tries", err, target) 137 } 138 if sp != target-1 { 139 t.Errorf("retried %d times, want %d", int(sp), int(target-1)) 140 } 141} 142 143func TestInvokeRetryTimeout(t *testing.T) { 144 apiErr := errors.New("foo error") 145 apiCall := func(context.Context, CallSettings) error { return apiErr } 146 var settings CallSettings 147 WithRetry(func() Retryer { return boolRetryer(true) }).Resolve(&settings) 148 var sp recordSleeper 149 150 err := invoke(canceledContext, apiCall, settings, sp.sleep) 151 152 if err != context.Canceled { 153 t.Errorf("found error %s, want %s", err, context.Canceled) 154 } 155} 156