From cd4ffe6c960c712e9f18719d5aee91eba6686e82 Mon Sep 17 00:00:00 2001 From: J7mbo Date: Mon, 15 Apr 2019 20:04:39 +0200 Subject: [PATCH] [#21] - Added new "wasSuccessful" return value --- MethodCallRetrier.go | 16 ++--- README.md | 27 +++++---- Retrier_test.go | 138 +++++++++++++++++++++++++++++++------------ 3 files changed, 123 insertions(+), 58 deletions(-) diff --git a/MethodCallRetrier.go b/MethodCallRetrier.go index b1e9e71..732c9f7 100644 --- a/MethodCallRetrier.go +++ b/MethodCallRetrier.go @@ -48,7 +48,7 @@ ExecuteFuncWithRetry retries a function with a maximum number of retries and a w ExecuteWithRetry() but accepts a function to maintain type safety in userland instead and removes the requirement of a user type assertion. */ -func (r *MethodCallRetrier) ExecuteFuncWithRetry(function func() error) []error { +func (r *MethodCallRetrier) ExecuteFuncWithRetry(function func() error) (errs []error, wasSuccessful bool) { defer func() { r.resetCurrentRetries() r.resetErrorList() @@ -63,7 +63,7 @@ func (r *MethodCallRetrier) ExecuteFuncWithRetry(function func() error) []error }, ) - return r.errorList + return r.errorList, false } err := function() @@ -76,13 +76,13 @@ func (r *MethodCallRetrier) ExecuteFuncWithRetry(function func() error) []error return r.ExecuteFuncWithRetry(function) } - return r.errorList + return r.errorList, true } /* ExecuteWithRetry retries the call to object.methodName(...args) with a maximum number of retries and a wait time. */ func (r *MethodCallRetrier) ExecuteWithRetry( object interface{}, methodName string, args ...interface{}, -) ([]interface{}, []error) { +) ([]interface{}, []error, bool) { defer func() { r.resetCurrentRetries() r.resetErrorList() @@ -93,13 +93,13 @@ func (r *MethodCallRetrier) ExecuteWithRetry( r.errorList, &MaxRetriesError{methodName: methodName, waitTime: r.waitTime, maxRetries: r.maxRetries}, ) - return nil, r.errorList + return nil, r.errorList, false } returnValues, err := r.callMethodOnObject(object, methodName, args) if err != nil { - return nil, []error{err} + return nil, []error{err}, false } returnValueCount := len(returnValues) @@ -127,10 +127,10 @@ func (r *MethodCallRetrier) ExecuteWithRetry( results[i] = returnValues[i].Interface() } - return results, nil + return results, r.errorList, true } -/* callMethodOnObject calls a method dynamically on an object with arguments. */ +/* callMethodOnObject calls a method dynamically on an object with arguments - error returned here is our fault. */ func (r *MethodCallRetrier) callMethodOnObject( object interface{}, methodName string, diff --git a/README.md b/README.md index c7ca376..2b0d55d 100644 --- a/README.md +++ b/README.md @@ -17,48 +17,49 @@ be found [here](https://github.com/J7mbo/palmago-streetview). Installation - - -`go get github.com/j7mbo/MethodCallRetrier/v2` +```bash +go get github.com/j7mbo/MethodCallRetrier/v2 +``` Usage - Initialise the object with some options: -``` +```go MethodCallRetrier.New(waitTime time.Duration, maxRetries int64, exponent int64) ``` Call `ExecuteWithRetry` with your object and method you want to retry: -``` +```go ExecuteWithRetry( object interface{}, methodName string, args ...interface{}, -) ([]interface{}, []error) +) (results []interface{}, errs []error, wasSuccessful bool) ``` Alternatively, call `ExecuteFuncWithRetry` and pass in a function that returns `error` to retry. -``` -ExecuteFuncWithRetry(func() error) []error +```go +ExecuteFuncWithRetry(func() error) (errs []error, wasSuccessful bool) ``` You can use it as follows: -``` -results, errs := retrier.ExecuteWithRetry(yourObject, "MethodToCall", "Arg1", "Arg2", "etc") +```go +results, errs, wasSuccessful := retrier.ExecuteWithRetry(yourObject, "MethodToCall", "Arg1", "Arg2", "etc") ``` The results are an array of `interface{}` objects, (used for the dynamic method call), and an array of all errors. To use the results, you must typecast the result to the expected type. In the case of an `int64`, for example: -``` +```go myInt := results[0].(int64) ``` Or, to maintain type safety in userland, you can pass a function in instead and do all your retriable work in there: -``` +```go var json string functionToRetry := func() error { @@ -71,8 +72,8 @@ functionToRetry := func() error { return nil } -if errs := retrier.ExecuteFuncWithRetry(funcToRetry); len(errs) > 0 { - /* Do something because we failed 3 times */ +if wasSuccessful, errs := retrier.ExecuteFuncWithRetry(funcToRetry); !wasSuccesful { + /* Do something with errs because we failed 3 times */ return } diff --git a/Retrier_test.go b/Retrier_test.go index e4611c2..342f9c7 100644 --- a/Retrier_test.go +++ b/Retrier_test.go @@ -24,48 +24,48 @@ func TestRetrierTestSuite(t *testing.T) { func (s *RetrierTestSuite) TestRetrierWorksWithPointer() { arg := "TestArg" - results, _ := s.retrier.ExecuteWithRetry(&RetryObject{}, "MethodReturningString", arg) + results, _, _ := s.retrier.ExecuteWithRetry(&RetryObject{}, "MethodReturningString", arg) - s.Assert().EqualValues(results[0], arg) + s.EqualValues(results[0], arg) } func (s *RetrierTestSuite) TestRetrierWorksWithObject() { arg := "TestArg" - results, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningString", arg) + results, _, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningString", arg) - s.Assert().EqualValues(results[0], arg) + s.EqualValues(results[0], arg) } func (s *RetrierTestSuite) TestRetrierReturnsErrorOnInvalidMethod() { - results, errs := s.retrier.ExecuteWithRetry(RetryObject{}, "InvalidMethodName") + results, errs, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "InvalidMethodName") - s.Assert().Nil(results) - s.Assert().Error(errs[0]) + s.Nil(results) + s.Error(errs[0]) } func (s *RetrierTestSuite) TestRetrierThrowsErrorReturnsNilResults() { - results, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningError", "TestArg") + results, _, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningError", "TestArg") - s.Assert().Nil(results) + s.Nil(results) } func (s *RetrierTestSuite) TestRetrierThrowsErrorReturnsErrors() { - _, errs := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningError", "TestArg") + _, errs, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningError", "TestArg") - s.Assert().IsType(errors.New(""), errs[0]) + s.IsType(errors.New(""), errs[0]) } func (s *RetrierTestSuite) TestRetrierThrowsErrorReturnsCorrectNumberOfErrors() { - _, errs := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningError", "TestArg") + _, errs, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningError", "TestArg") - s.Assert().Len(errs, 2) + s.Len(errs, 2) } func (s *RetrierTestSuite) TestRetrierReturnsNilWhenGivenObjectWithNoReturnTypes() { - results, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningNoValues") + results, _, _ := s.retrier.ExecuteWithRetry(RetryObject{}, "MethodReturningNoValues") - s.Assert().Len(results, 0) + s.Len(results, 0) } func (s *RetrierTestSuite) TestRetrierRetriesCorrectNumberOfTimes() { @@ -74,7 +74,7 @@ func (s *RetrierTestSuite) TestRetrierRetriesCorrectNumberOfTimes() { testObj.On(methodName, "").Return(errors.New("")) - _, _ = New(0, 5, 1).ExecuteWithRetry(&testObj, methodName, "") + _, _, _ = New(0, 5, 1).ExecuteWithRetry(&testObj, methodName, "") testObj.AssertNumberOfCalls(s.T(), methodName, 5) @@ -84,17 +84,17 @@ func (s *RetrierTestSuite) TestRetrierRetriesCorrectNumberOfTimes() { func (s *RetrierTestSuite) TestRetrierWorksWithNegativeMaxRetries() { arg := "testArg" - results, _ := New(-1, -1, 1).ExecuteWithRetry(RetryObject{}, "MethodReturningString", arg) + results, _, _ := New(-1, -1, 1).ExecuteWithRetry(RetryObject{}, "MethodReturningString", arg) - s.Assert().EqualValues(results[0], arg) + s.EqualValues(results[0], arg) } func (s *RetrierTestSuite) TestRetrierDefaultsToOneRetryGivenZeroMaxRetries() { testObj := RetryMockObject{} - New(0, 0, 1).ExecuteFuncWithRetry(testObj.MethodToBeCalledToReturnResultAndError) + New(0, 0, 1).ExecuteFuncWithRetry(testObj.MethodToBeCalledToReturnErrorWithTimesCalledAvailable) - s.Assert().Equal(1, testObj.timesCalled) + s.Equal(1, testObj.timesCalled) } func (s *RetrierTestSuite) TestRetrierReturnsAllErrorsPlusOurError() { @@ -103,56 +103,110 @@ func (s *RetrierTestSuite) TestRetrierReturnsAllErrorsPlusOurError() { testObj.On(methodName, "").Return(errors.New("")) - _, errs := New(0, 5, 1).ExecuteWithRetry(&testObj, methodName, "") + _, errs, _ := New(0, 5, 1).ExecuteWithRetry(&testObj, methodName, "") - s.Assert().Len(errs, 6) + s.Len(errs, 6) } func (s *RetrierTestSuite) TestRetrierWorksWhenErrorIsNotLastReturnParamOnObject() { testObj := RetryObject{} methodName := "MethodReturningErrorInRandomPosition" - _, errs := New(1, 1, 1).ExecuteWithRetry(&testObj, methodName, "") + _, errs, _ := New(1, 1, 1).ExecuteWithRetry(&testObj, methodName, "") - s.Assert().IsType(errors.New(""), errs[0]) + s.IsType(errors.New(""), errs[0]) } func (s *RetrierTestSuite) TestRetrierWorksWhenMultipleReturnParamsAreErrors() { testObj := RetryObject{} methodName := "MethodReturningMultipleErrors" - _, errs := New(0, 5, 1).ExecuteWithRetry(&testObj, methodName, "") + _, errs, _ := New(0, 5, 1).ExecuteWithRetry(&testObj, methodName, "") - s.Assert().Len(errs, 11) + s.Len(errs, 11) } func (s *RetrierTestSuite) TestRetrierWorksWithUserFunction() { var num int - errs := New(0, 3, 1).ExecuteFuncWithRetry(func() error { + errs, _ := New(0, 3, 1).ExecuteFuncWithRetry(func() error { num = 42 return nil }) - s.Assert().Equal(42, num) - s.Assert().Len(errs, 0) + s.Equal(42, num) + s.Len(errs, 0) } func (s *RetrierTestSuite) TestRetrierWithUserFunctionReturnsCorrectNumberOfErrors() { - errs := New(0, 3, 1).ExecuteFuncWithRetry(func() error { + errs, _ := New(0, 3, 1).ExecuteFuncWithRetry(func() error { return errors.New("") }) - s.Assert().Equal(4, len(errs)) + s.Equal(4, len(errs)) } func (s *RetrierTestSuite) TestRetrierWorksWithUserFunctionCalledCorrectNumberOfTimes() { testObj := RetryMockObject{} - New(0, 3, 1).ExecuteFuncWithRetry(testObj.MethodToBeCalledToReturnResultAndError) + New(0, 3, 1).ExecuteFuncWithRetry(testObj.MethodToBeCalledToReturnErrorWithTimesCalledAvailable) - s.Assert().Equal(3, testObj.timesCalled) + s.Equal(3, testObj.timesCalled) +} + +func (s *RetrierTestSuite) TestRetrierWithUserFunctionReturnsFalseWhenAllFailed() { + testObj := RetryMockObject{} + + errs, wasSuccessful := New(0, 5, 1).ExecuteFuncWithRetry(testObj.MethodToBeCalledToReturnErrorWithTimesCalledAvailable) + + s.Equal(5, testObj.timesCalled) + s.Equal(6, len(errs)) + s.False(wasSuccessful) +} + +func (s *RetrierTestSuite) TestRetrierWithUserFunctionReturnsTrueWhenOneSucceededButOthersFailedFirst() { + testObj := RetryMockObject{} + resultStr := "" + + errs, wasSuccessful := New(0, 5, 1).ExecuteFuncWithRetry(func() error { + result, err := testObj.MethodToBeCalledToReturnSuccessOnFifthCall() + + if err != nil { + return err + } + + resultStr = result + + return nil + }) + + s.Equal("omg", resultStr) + s.Equal(5, testObj.timesCalled) + s.Equal(4, len(errs)) + s.True(wasSuccessful) +} + +func (s *RetrierTestSuite) TestRetrierWithMethodNameReturnsFalseWhenAllFailed() { + testObj := RetryMockObject{} + + results, errs, wasSuccessful := New(0, 5, 1).ExecuteWithRetry(&testObj, "MethodToBeCalledToReturnErrorWithTimesCalledAvailable") + + s.Nil(results) + s.Equal(5, testObj.timesCalled) + s.Equal(6, len(errs)) + s.False(wasSuccessful) +} + +func (s *RetrierTestSuite) TestRetrierWithMethodNameReturnsTrueWhenOneSucceededButOthersFailedFirst() { + testObj := RetryMockObject{} + + results, errs, wasSuccessful := New(0, 5, 1).ExecuteWithRetry(&testObj, "MethodToBeCalledToReturnSuccessOnFifthCall") + + s.Equal("omg", results[0]) + s.Equal(5, testObj.timesCalled) + s.Equal(4, len(errs)) + s.True(wasSuccessful) } /* This really only exists for coverage */ @@ -163,9 +217,9 @@ func (s *RetrierTestSuite) TestMaxRetriesError() { err := MaxRetriesError{methodName: methodName, waitTime: 42, maxRetries: 52} - s.Assert().Contains(err.Error(), methodName) - s.Assert().Contains(err.Error(), waitTime) - s.Assert().Contains(err.Error(), maxRetries) + s.Contains(err.Error(), methodName) + s.Contains(err.Error(), waitTime) + s.Contains(err.Error(), maxRetries) } type RetryObject struct{} @@ -198,8 +252,18 @@ func (m *RetryMockObject) MethodReturningError(anArgument string) error { return m.Called(anArgument).Error(0) } -func (m *RetryMockObject) MethodToBeCalledToReturnResultAndError() error { +func (m *RetryMockObject) MethodToBeCalledToReturnErrorWithTimesCalledAvailable() error { m.timesCalled += 1 return errors.New("") +} + +func (m *RetryMockObject) MethodToBeCalledToReturnSuccessOnFifthCall() (string, error) { + m.timesCalled += 1 + + if m.timesCalled < 5 { + return "", errors.New("ah crap") + } + + return "omg", nil } \ No newline at end of file