diff --git a/.gitignore b/.gitignore index a1338d6..c40eb23 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,10 @@ # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ + +# dep +vendor/ +Gopkg.lock + +# cover +coverage.txt diff --git a/.godocdown.tmpl b/.godocdown.tmpl new file mode 100644 index 0000000..6873edf --- /dev/null +++ b/.godocdown.tmpl @@ -0,0 +1,37 @@ +# {{ .Name }} + +[![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go) +[![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go) +[![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go) +[![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go) +[![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master) +[![Sourcegraph](https://sourcegraph.com/github.com/avast/retry-go/-/badge.svg)](https://sourcegraph.com/github.com/avast/retry-go?badge) + +{{ .EmitSynopsis }} + +{{ .EmitUsage }} + +## Contributing + +Contributions are very much welcome. + +### Makefile + +Makefile provides several handy rules, like README.md `generator` , `setup` for prepare build/dev environment, `test`, `cover`, etc... + +Try `make help` for more information. + +### Before pull request + +please try: +* run tests (`make test`) +* run linter (`make lint`) +* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`) + +### README + +README.md are generate from template [.godocdown.tmpl](.godocdown.tmpl) and code documentation via [godocdown](https://github.com/robertkrimen/godocdown). + +Never edit README.md direct, because your change will be lost. diff --git a/.travis.yml b/.travis.yml index 79cf2ba..54003a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,16 @@ language: go go: - - 1.2 -# - 1.3 - - 1.4 - - 1.5 - 1.6 - 1.7 - - tip + - 1.8 + - 1.9 install: - - go get -t -v -d ./... + - make setup script: - - ./test.sh + - make ci after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..cf8c9eb --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,3 @@ +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.1.4" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d36978e --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +SOURCE_FILES?=$$(go list ./... | grep -v /vendor/) +TEST_PATTERN?=. +TEST_OPTIONS?= +DEP?=$$(which dep) + +ifeq ($(OS),Windows_NT) + DEP_VERS=dep-windows-amd64 +else + DEP_VERS=dep-linux-amd64 +endif + +setup: ## Install all the build and lint dependencies + # fix of gopkg.in issue (https://github.com/niemeyer/gopkg/issues/50) + git config --global http.https://gopkg.in.followRedirects true + go get -u gopkg.in/alecthomas/gometalinter.v1 + go get -u github.com/pierrre/gotestcover + go get -u golang.org/x/tools/cmd/cover + go get -u github.com/robertkrimen/godocdown/godocdown + gometalinter.v1 --install + @if [ "$(DEP)" = "" ]; then\ + curl -L https://github.com/golang/dep/releases/download/v0.3.1/$(DEP_VERS) >| $$GOPATH/bin/dep;\ + chmod +x $$GOPATH/bin/dep;\ + fi + dep ensure + +generate: ## Generate README.md + godocdown >| README.md + +test: generate ## Run all the tests + gotestcover $(TEST_OPTIONS) -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m + +cover: test ## Run all the tests and opens the coverage report + go tool cover -html=coverage.txt + +fmt: ## gofmt and goimports all go files + find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done + +lint: ## Run all the linters + gometalinter.v1 --vendor --disable-all \ + --enable=deadcode \ + --enable=ineffassign \ + --enable=gosimple \ + --enable=staticcheck \ + --enable=gofmt \ + --enable=goimports \ + --enable=dupl \ + --enable=misspell \ + --enable=errcheck \ + --enable=vet \ + --deadline=10m \ + ./... + +ci: test lint ## Run all the tests and code checks + +build: + go build + +# Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.DEFAULT_GOAL := build diff --git a/README.md b/README.md index 966632c..9389257 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,183 @@ # retry -[![Linux Build Status](https://travis-ci.org/avast/retry-go.svg)](https://travis-ci.org/avast/retry-go) -[![Windows Build status](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go) -[![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go)](https://goreportcard.com/report/github.com/avast/retry-go) -[![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg)](http://godoc.org/github.com/avast/retry-go) +[![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go) +[![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go) +[![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go) +[![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go) +[![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master) [![Sourcegraph](https://sourcegraph.com/github.com/avast/retry-go/-/badge.svg)](https://sourcegraph.com/github.com/avast/retry-go?badge) -[![codecov.io](https://codecov.io/github/boennemann/badges/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master) Simple library for retry mechanism -slightly inspired by [Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry) +slightly inspired by +[Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry) -## INSTALL && USE -To get the package, execute: +### SYNOPSIS +http get with retry: + + url := "http://example.com" + var body []byte + + err := retry.Retry( + func() error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + return nil + }, + ) + + fmt.Println(body) + +[next examples](https://github.com/avast/retry-go/examples) + + +### SEE ALSO + +* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly +complicated interface. + +* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for +http calls with retries and backoff + +* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the +exponential backoff algorithm from Google's HTTP Client Library for Java. Really +complicated interface. + +## Usage + +#### func Retry + +```go +func Retry(retryableFunction Retryable) error +``` +Retry - simple retry + +#### func RetryCustom + +```go +func RetryCustom(retryableFunction Retryable, onRetryFunction OnRetry, opts RetryOpts) error ``` -go get gopkg.in/avast/retry-go.v0 +RetryCustom - the most customizable retry is possible set OnRetry function +callback which are called each retry + +#### func RetryWithOpts + +```go +func RetryWithOpts(retryableFunction Retryable, opts RetryOpts) error ``` +RetryWithOpts - customizable retry via RetryOpts -To import this package, add the following line to your code: +#### type Error ```go -import "gopkg.in/avast/retry-go.v0" +type Error []error ``` -### EXAMPLE +Error type represents list of errors in retry -http get with retry: +#### func (Error) Error ```go -url := "http://example.com" -var body []byte +func (e Error) Error() string +``` +Error method return string representation of Error It is an implementation of +error interface -err := retry.Retry( - func() error { - resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - body, err = ioutil.ReadAll(resp.Body) - if err != nil { - return err - } +#### func (Error) WrappedErrors - return nil - }, -) +```go +func (e Error) WrappedErrors() []error +``` +WrappedErrors returns the list of errors that this Error is wrapping. It is an +implementation of the errwrap.Wrapper interface so that multierror.Error can be +used with that library. -fmt.Println(body) +#### type OnRetry + +```go +type OnRetry func(n uint, err error) ``` -[next examples](examples) +Function signature of OnRetry function n = count of tries + +#### type RetryOpts + +```go +type RetryOpts struct { +} +``` + +Struct for configure retry tries - count of tries delay - waiting time units - +waiting time unit (for tests purpose) + +#### func NewRetryOpts + +```go +func NewRetryOpts() RetryOpts +``` +Create new RetryOpts struct with default values default tries are 10 default +delay are 1e5 default units are microsecond + +#### func (RetryOpts) Delay + +```go +func (opts RetryOpts) Delay(delay time.Duration) RetryOpts +``` +Delay setter + +#### func (RetryOpts) Tries + +```go +func (opts RetryOpts) Tries(tries uint) RetryOpts +``` +Tries setter + +#### func (RetryOpts) Units + +```go +func (opts RetryOpts) Units(timeUnit time.Duration) RetryOpts +``` +Units setter + +#### type Retryable + +```go +type Retryable func() error +``` + +Function signature of retryable function + +## Contributing + +Contributions are very much welcome. + +### Makefile + +Makefile provides several handy rules, like README.md `generator` , `setup` for prepare build/dev environment, `test`, `cover`, etc... + +Try `make help` for more information. + +### Before pull request + +please try: +* run tests (`make test`) +* run linter (`make lint`) +* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`) + +### README + +README.md are generate from template [.godocdown.tmpl](.godocdown.tmpl) and code documentation via [godocdown](https://github.com/robertkrimen/godocdown). -## SEE ALSO -* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly complicated interface. -* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for http calls with retries and backoff -* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the exponential backoff algorithm from Google's HTTP Client Library for Java. Really complicated interface. +Never edit README.md direct, because your change will be lost. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0d91a54 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.0 diff --git a/appveyor.yml b/appveyor.yml index 9807787..dc5234a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,14 +1,19 @@ version: "{build}" -clone_folder: c:\go\src\github.com\avast\retry-go +clone_folder: c:\Users\appveyor\go\src\github.com\avast\retry-go #os: Windows Server 2012 R2 platform: x64 install: + - copy c:\MinGW\bin\mingw32-make.exe c:\MinGW\bin\make.exe + - set GOPATH=C:\Users\appveyor\go + - set PATH=%PATH%;c:\MinGW\bin + - set PATH=%PATH%;%GOPATH%\bin;c:\go\bin + - set GOBIN=%GOPATH%\bin - go version - go env - - go get -v -t + - make setup build_script: - - go test -v ./... + - make ci diff --git a/examples/http_get_test.go b/examples/http_get_test.go index b89c57e..0112284 100644 --- a/examples/http_get_test.go +++ b/examples/http_get_test.go @@ -1,11 +1,12 @@ package retry_test import ( - "github.com/avast/retry-go" - "github.com/stretchr/testify/assert" "io/ioutil" "net/http" "testing" + + "github.com/avast/retry-go" + "github.com/stretchr/testify/assert" ) func TestGet(t *testing.T) { @@ -15,16 +16,17 @@ func TestGet(t *testing.T) { err := retry.Retry( func() error { resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - body, err = ioutil.ReadAll(resp.Body) - if err != nil { - return err + + if err == nil { + defer func() { + if err := resp.Body.Close(); err != nil { + panic(err) + } + }() + body, err = ioutil.ReadAll(resp.Body) } - return nil + return err }, ) diff --git a/retry.go b/retry.go index fd35526..25664c1 100644 --- a/retry.go +++ b/retry.go @@ -1,5 +1,49 @@ -// Simple library for retry mechanism -// slightly inspired by https://metacpan.org/pod/Try::Tiny::Retry +/* +Simple library for retry mechanism + +slightly inspired by [Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry) + +SYNOPSIS + +http get with retry: + + url := "http://example.com" + var body []byte + + err := retry.Retry( + func() error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + return nil + }, + ) + + fmt.Println(body) + +[next examples](https://github.com/avast/retry-go/examples) + + +SEE ALSO + +* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly complicated interface. + +* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for http calls with retries and backoff + +* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the exponential backoff algorithm from Google's HTTP Client Library for Java. Really complicated interface. + +* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - looks good, slightly similar as this package, don't have 'simple' `Retry` method + +* [matryer/try](https://github.com/matryer/try) - very popular package, nonintuitive interface (for me) + +*/ package retry import ( @@ -16,25 +60,6 @@ type Retryable func() error type OnRetry func(n uint, err error) // Retry - simple retry -// -// url := "http://example.com" -// var body []byte -// -// err := retry.Retry( -// func() error { -// resp, err := http.Get(url) -// if err != nil { -// return err -// } -// defer resp.Body.Close() -// body, err = ioutil.ReadAll(resp.Body) -// if err != nil { -// return err -// } -// -// return nil -// }, -// ) func Retry(retryableFunction Retryable) error { return RetryWithOpts(retryableFunction, NewRetryOpts()) } @@ -50,7 +75,7 @@ func RetryWithOpts(retryableFunction Retryable, opts RetryOpts) error { func RetryCustom(retryableFunction Retryable, onRetryFunction OnRetry, opts RetryOpts) error { var n uint - errorLog := make(errorLog, opts.tries) + errorLog := make(Error, opts.tries) for n < opts.tries { err := retryableFunction() @@ -68,16 +93,27 @@ func RetryCustom(retryableFunction Retryable, onRetryFunction OnRetry, opts Retr n++ } - return fmt.Errorf("All (%d) retries fail:\n%s", opts.tries, errorLog) + return errorLog } -type errorLog []error +// Error type represents list of errors in retry +type Error []error -func (log errorLog) String() string { - logWithNumber := make([]string, len(log)) - for i, l := range log { +// Error method return string representation of Error +// It is an implementation of error interface +func (e Error) Error() string { + logWithNumber := make([]string, len(e)) + for i, l := range e { logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error()) } - return strings.Join(logWithNumber, "\n") + return fmt.Sprintf("All retries fail:\n%s", strings.Join(logWithNumber, "\n")) +} + +// WrappedErrors returns the list of errors that this Error is wrapping. +// It is an implementation of the `errwrap.Wrapper` interface +// in package [errwrap](https://github.com/hashicorp/errwrap) so that +// `retry.Error` can be used with that library. +func (e Error) WrappedErrors() []error { + return e } diff --git a/retry_test.go b/retry_test.go index 772727e..e8b6ca6 100644 --- a/retry_test.go +++ b/retry_test.go @@ -2,9 +2,10 @@ package retry import ( "errors" - "github.com/stretchr/testify/assert" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestCustom(t *testing.T) { @@ -16,7 +17,7 @@ func TestCustom(t *testing.T) { ) assert.Error(t, err) - expectedErrorFormat := `All (10) retries fail: + expectedErrorFormat := `All retries fail: #1: test #2: test #3: test diff --git a/test.sh b/test.sh deleted file mode 100755 index 88c4e8b..0000000 --- a/test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e -echo "" > coverage.txt - -for d in $(go list ./... | grep -v vendor); do - go test -v -race -coverprofile=profile.out -covermode=atomic $d - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done