mirror of https://github.com/dapr/kit.git
277 lines
7.1 KiB
Go
277 lines
7.1 KiB
Go
/*
|
|
Copyright 2021 The Dapr Authors
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package retry_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cenkalti/backoff/v4"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/dapr/kit/retry"
|
|
)
|
|
|
|
var errRetry = errors.New("Testing")
|
|
|
|
func TestDecode(t *testing.T) {
|
|
tests := map[string]struct {
|
|
config interface{}
|
|
overrides func(config *retry.Config)
|
|
err string
|
|
}{
|
|
"invalid policy type": {
|
|
config: map[string]interface{}{
|
|
"backOffPolicy": "invalid",
|
|
},
|
|
overrides: nil,
|
|
err: "1 error(s) decoding:\n\n* error decoding 'policy': invalid PolicyType \"invalid\": unexpected back off policy type: invalid",
|
|
},
|
|
"default": {
|
|
config: map[string]interface{}{},
|
|
overrides: nil,
|
|
err: "",
|
|
},
|
|
"constant default": {
|
|
config: map[string]interface{}{
|
|
"backOffPolicy": "constant",
|
|
},
|
|
overrides: nil,
|
|
err: "",
|
|
},
|
|
"constant with duraction": {
|
|
config: map[string]interface{}{
|
|
"backOffPolicy": "constant",
|
|
"backOffDuration": "10s",
|
|
},
|
|
overrides: func(config *retry.Config) {
|
|
config.Duration = 10 * time.Second
|
|
},
|
|
err: "",
|
|
},
|
|
"exponential default": {
|
|
config: map[string]interface{}{
|
|
"backOffPolicy": "exponential",
|
|
},
|
|
overrides: func(config *retry.Config) {
|
|
config.Policy = retry.PolicyExponential
|
|
},
|
|
err: "",
|
|
},
|
|
"exponential with string settings": {
|
|
config: map[string]interface{}{
|
|
"backOffPolicy": "exponential",
|
|
"backOffInitialInterval": "1000", // 1s
|
|
"backOffRandomizationFactor": "1.0",
|
|
"backOffMultiplier": "2.0",
|
|
"backOffMaxInterval": "120000", // 2m
|
|
"backOffMaxElapsedTime": "1800000", // 30m
|
|
},
|
|
overrides: func(config *retry.Config) {
|
|
config.Policy = retry.PolicyExponential
|
|
config.InitialInterval = 1 * time.Second
|
|
config.RandomizationFactor = 1.0
|
|
config.Multiplier = 2.0
|
|
config.MaxInterval = 2 * time.Minute
|
|
config.MaxElapsedTime = 30 * time.Minute
|
|
},
|
|
err: "",
|
|
},
|
|
"exponential with typed settings": {
|
|
config: map[string]interface{}{
|
|
"backOffPolicy": "exponential",
|
|
"backOffInitialInterval": "1000ms", // 1s
|
|
"backOffRandomizationFactor": 1.0,
|
|
"backOffMultiplier": 2.0,
|
|
"backOffMaxInterval": "120s", // 2m
|
|
"backOffMaxElapsedTime": "30m", // 30m
|
|
},
|
|
overrides: func(config *retry.Config) {
|
|
config.Policy = retry.PolicyExponential
|
|
config.InitialInterval = 1 * time.Second
|
|
config.RandomizationFactor = 1.0
|
|
config.Multiplier = 2.0
|
|
config.MaxInterval = 2 * time.Minute
|
|
config.MaxElapsedTime = 30 * time.Minute
|
|
},
|
|
err: "",
|
|
},
|
|
"map[string]string settings": {
|
|
config: map[string]string{
|
|
"backOffPolicy": "exponential",
|
|
"backOffInitialInterval": "1000ms", // 1s
|
|
"backOffRandomizationFactor": "1.0",
|
|
"backOffMultiplier": "2.0",
|
|
"backOffMaxInterval": "120s", // 2m
|
|
"backOffMaxElapsedTime": "30m", // 30m
|
|
},
|
|
overrides: func(config *retry.Config) {
|
|
config.Policy = retry.PolicyExponential
|
|
config.InitialInterval = 1 * time.Second
|
|
config.RandomizationFactor = 1.0
|
|
config.Multiplier = 2.0
|
|
config.MaxInterval = 2 * time.Minute
|
|
config.MaxElapsedTime = 30 * time.Minute
|
|
},
|
|
err: "",
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
var actual retry.Config
|
|
err := retry.DecodeConfigWithPrefix(&actual, tc.config, "backOff")
|
|
if tc.err != "" {
|
|
if assert.Error(t, err) {
|
|
assert.Equal(t, tc.err, err.Error())
|
|
}
|
|
} else {
|
|
b := actual.NewBackOff()
|
|
config := retry.DefaultConfig()
|
|
if tc.overrides != nil {
|
|
tc.overrides(&config)
|
|
}
|
|
assert.Equal(t, config, actual, "unexpected decoded configuration")
|
|
if actual.Policy == retry.PolicyConstant {
|
|
_, ok := b.(*backoff.ConstantBackOff)
|
|
assert.True(t, ok)
|
|
} else if actual.Policy == retry.PolicyExponential {
|
|
_, ok := b.(*backoff.ExponentialBackOff)
|
|
assert.True(t, ok)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRetryNotifyRecoverNoetries(t *testing.T) {
|
|
config := retry.DefaultConfigWithNoRetry()
|
|
config.Duration = 1
|
|
|
|
var operationCalls, notifyCalls, recoveryCalls int
|
|
|
|
b := config.NewBackOff()
|
|
err := retry.NotifyRecover(func() error {
|
|
operationCalls++
|
|
|
|
return errRetry
|
|
}, b, func(err error, d time.Duration) {
|
|
notifyCalls++
|
|
}, func() {
|
|
recoveryCalls++
|
|
})
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, errRetry, err)
|
|
assert.Equal(t, 1, operationCalls)
|
|
assert.Equal(t, 0, notifyCalls)
|
|
assert.Equal(t, 0, recoveryCalls)
|
|
}
|
|
|
|
func TestRetryNotifyRecoverMaxRetries(t *testing.T) {
|
|
config := retry.DefaultConfig()
|
|
config.MaxRetries = 3
|
|
config.Duration = 1
|
|
|
|
var operationCalls, notifyCalls, recoveryCalls int
|
|
|
|
b := config.NewBackOff()
|
|
err := retry.NotifyRecover(func() error {
|
|
operationCalls++
|
|
|
|
return errRetry
|
|
}, b, func(err error, d time.Duration) {
|
|
notifyCalls++
|
|
}, func() {
|
|
recoveryCalls++
|
|
})
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, errRetry, err)
|
|
assert.Equal(t, 4, operationCalls)
|
|
assert.Equal(t, 1, notifyCalls)
|
|
assert.Equal(t, 0, recoveryCalls)
|
|
}
|
|
|
|
func TestRetryNotifyRecoverRecovery(t *testing.T) {
|
|
config := retry.DefaultConfig()
|
|
config.MaxRetries = 3
|
|
config.Duration = 1
|
|
|
|
var operationCalls, notifyCalls, recoveryCalls int
|
|
|
|
b := config.NewBackOff()
|
|
err := retry.NotifyRecover(func() error {
|
|
operationCalls++
|
|
|
|
if operationCalls >= 2 {
|
|
return nil
|
|
}
|
|
|
|
return errRetry
|
|
}, b, func(err error, d time.Duration) {
|
|
notifyCalls++
|
|
}, func() {
|
|
recoveryCalls++
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, operationCalls)
|
|
assert.Equal(t, 1, notifyCalls)
|
|
assert.Equal(t, 1, recoveryCalls)
|
|
}
|
|
|
|
func TestRetryNotifyRecoverCancel(t *testing.T) {
|
|
config := retry.DefaultConfig()
|
|
config.Policy = retry.PolicyConstant
|
|
config.Duration = 1 * time.Minute
|
|
|
|
var notifyCalls, recoveryCalls int
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
b := config.NewBackOffWithContext(ctx)
|
|
errC := make(chan error, 1)
|
|
startedC := make(chan struct{}, 100)
|
|
|
|
go func() {
|
|
errC <- retry.NotifyRecover(func() error {
|
|
return errRetry
|
|
}, b, func(err error, d time.Duration) {
|
|
notifyCalls++
|
|
startedC <- struct{}{}
|
|
}, func() {
|
|
recoveryCalls++
|
|
})
|
|
}()
|
|
|
|
<-startedC
|
|
cancel()
|
|
|
|
err := <-errC
|
|
assert.Error(t, err)
|
|
assert.True(t, errors.Is(err, context.Canceled))
|
|
assert.Equal(t, 1, notifyCalls)
|
|
assert.Equal(t, 0, recoveryCalls)
|
|
}
|
|
|
|
func TestCheckEmptyConfig(t *testing.T) {
|
|
var config retry.Config
|
|
err := retry.DecodeConfig(&config, map[string]interface{}{})
|
|
assert.NoError(t, err)
|
|
defaultConfig := retry.DefaultConfig()
|
|
assert.Equal(t, config, defaultConfig)
|
|
}
|