Skip to content

Commit

Permalink
Handle function panics
Browse files Browse the repository at this point in the history
add support for go 1.11 and above
  • Loading branch information
arsham committed Oct 16, 2019
1 parent 745d9e5 commit cd2b6e4
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ env:
- GO111MODULE=on

go:
- 1.11.x
- 1.12.x
- 1.13.x

script:
- go test -failfast -v -coverprofile=coverage.txt -covermode=atomic ./...
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This library has a few helpers for using in production code and
[go-sqlmock][go-sqlmock] tests. There is also a `Mocha` inspired reporter for
[spec BDD library][spec].

This library supports Go 1.11 and above.

1. [Transaction](#transaction)
* [WithTransaction](#withtransaction)
* [Retry](#retry)
Expand Down
29 changes: 23 additions & 6 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,26 @@ import (
"github.com/pkg/errors"
)

var errPanic = errors.New("function caused a panic")

// WithTransaction creates a transaction on db and uses it to call fn functions
// one by one. The first function that returns an error will cause the loop to
// stop and transaction to be rolled back.
// one by one. The first function that returns an error or panics will cause the
// loop to stop and transaction to be rolled back.
func WithTransaction(db *sql.DB, fn ...func(*sql.Tx) error) error {
tx, err := db.Begin()
if err != nil {
return errors.Wrap(err, "starting transaction")
}
for _, f := range fn {
err := f(tx)
var err error
func() {
defer func() {
if r := recover(); r != nil {
err = errors.Wrapf(errPanic, "%v", r)
}
}()
err = f(tx)
}()
if err != nil {
e := errors.Wrap(tx.Rollback(), "rolling back transaction")
return multierror.Append(err, e).ErrorOrNil()
Expand All @@ -29,12 +39,19 @@ func WithTransaction(db *sql.DB, fn ...func(*sql.Tx) error) error {
}

// Retry calls fn for retries times until it returns nil. If retries is zero fn
// would not be called. It delays and retries if the function returns any
// errors. The fn function receives the current iteration as its argument.
// would not be called. It delays and retries if the function returns any errors
// or panics. The fn function receives the current iteration as its argument.
func Retry(retries int, delay time.Duration, fn func(int) error) error {
var err error
for i := 0; i < retries; i++ {
err = fn(i)
func() {
defer func() {
if r := recover(); r != nil {
err = errors.Wrapf(errPanic, "%v", r)
}
}()
err = fn(i)
}()
switch err {
case io.EOF, nil:
return nil
Expand Down
39 changes: 39 additions & 0 deletions db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestWithTransaction(t *testing.T) {
t.Run("RollbackFirst", testWithTransactionRollbackFirst)
t.Run("RollbackSecond", testWithTransactionRollbackSecond)
t.Run("Commit", testWithTransactionCommit)
t.Run("FunctionPanic", testWithTransactionFunctionPanic)
}

func testWithTransactionBeginCommit(t *testing.T) {
Expand Down Expand Up @@ -132,6 +133,29 @@ func testWithTransactionCommit(t *testing.T) {
assert.Equal(t, assert.AnError, errors.Cause(err))
}

func testWithTransactionFunctionPanic(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New()
require.NoError(t, err)
defer db.Close()
defer func() {
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}()
mock.ExpectBegin()
mock.ExpectRollback()
require.NotPanics(t, func() {
err := dbtools.WithTransaction(db, func(*sql.Tx) error {
panic("for some reason")
}, func(*sql.Tx) error {
t.Error("didn't expect to be called")
return nil
})
assert.Error(t, err)
})
}

func ExampleWithTransaction() {
// For this example we are using sqlmock, but you can use an actual
// connection with this function.
Expand Down Expand Up @@ -198,6 +222,7 @@ func ExampleWithTransaction_two() {
func TestRetry(t *testing.T) {
t.Run("Delay", testRetryDelay)
t.Run("Retries", testRetryRetries)
t.Run("FunctionPanic", testRetryFunctionPanic)
}

func testRetryDelay(t *testing.T) {
Expand Down Expand Up @@ -234,6 +259,20 @@ func testRetryRetries(t *testing.T) {
assert.Equal(t, 20, count)
}

func testRetryFunctionPanic(t *testing.T) {
t.Parallel()
retries := 100
count := 0
assert.NotPanics(t, func() {
err := dbtools.Retry(retries, time.Nanosecond, func(int) error {
count++
panic("for some reason")
})
assert.Error(t, err)
})
assert.Equal(t, retries, count)
}

func ExampleRetry() {
err := dbtools.Retry(100, time.Nanosecond, func(i int) error {
fmt.Printf("Running iteration %d.\n", i+1)
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module github.com/arsham/dbtools

go 1.12
go 1.11

require (
github.com/DATA-DOG/go-sqlmock v1.3.3
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/go-multierror v1.0.0
github.com/pkg/errors v0.8.1
github.com/sclevine/spec v1.2.0
github.com/stretchr/testify v1.3.0
github.com/sclevine/spec v1.3.0
github.com/stretchr/testify v1.4.0
)
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/sclevine/spec v1.3.0 h1:iTB51CYlnju5oRh0/l67fg1+RlQ2nqmFecwdvN+5TrI=
github.com/sclevine/spec v1.3.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 comments on commit cd2b6e4

Please sign in to comment.