Add retryupdate task
This commit is contained in:
parent
a5854da086
commit
dac5b0b7f8
8 changed files with 509 additions and 0 deletions
3
go.mod
3
go.mod
|
@ -4,11 +4,14 @@ go 1.13
|
|||
|
||||
require (
|
||||
github.com/go-resty/resty/v2 v2.1.0
|
||||
github.com/gofrs/uuid v3.2.0+incompatible
|
||||
github.com/golang/mock v1.4.1
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.uber.org/goleak v1.0.0
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
|
||||
golang.org/x/perf v0.0.0-20191209155426-36b577b0eb03
|
||||
golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -15,6 +15,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||
github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE=
|
||||
github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/mock v1.4.1 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc=
|
||||
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg=
|
||||
|
@ -77,8 +81,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825 h1:aNQeSIHKi0RWpKA5NO0CqyLjx6Beh5l0LLUEnndEjz0=
|
||||
golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
|
@ -96,3 +102,5 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
30
retryupdate/README.md
Normal file
30
retryupdate/README.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# retryupdate
|
||||
|
||||
В этой задаче, вам нужно познакомиться с паттернами обработки ошибок в golang.
|
||||
|
||||
Вам дан клиент вымышленного сервиса. Сервис хранит отображение `key -> (value, version)`,
|
||||
где `key` и `value` - это обычные строки. А `version` - уникальный идентификатор, который сгенерировал
|
||||
клиент.
|
||||
|
||||
Вам доступны два метода - `Get` и `Set`. Поведение методов описано в пакете `kvapi`.
|
||||
|
||||
Методы могут завершаться с различными ошибками. Ошибки описаны в том же пакете.
|
||||
|
||||
Вам нужно реализовать функцию `UpdateValue`, которая принимает ключ и функцию
|
||||
для "обновления" значения. Функция должна обновить значение по ключу, правильно обрабатывая
|
||||
ошибки, возникающие в процессе работы.
|
||||
|
||||
- Если ключ не нашёлся, нужно передать в `updateFn` значение `nil`.
|
||||
- Если произошла ошибка доступа, то нужно сразу выходить.
|
||||
- Если произошла ошибка внутри updateFn, то нужно сразу выходить.
|
||||
- Если случился конфликт при записи, и значение изменилось с момента последнего чтения,
|
||||
то нужно прочитать новое значение и попробовать заново.
|
||||
- Любые другие ошибки из `api` нужно "ретраить" бесконечно.
|
||||
|
||||
Функция должна минимизировать число запросов к API.
|
||||
|
||||
Для генерации нового `uuid` на клиенте, используйте вызов `uuid.Must(uuid.NewV4())`.
|
||||
|
||||
Большую цепочку `if`-ов удобнее писать через `switch`.
|
||||
|
||||
Для выхода из вложенных циклов вам помогут [loop labels](https://golang.org/doc/effective_go.html#switch).
|
36
retryupdate/kvapi/api.go
Normal file
36
retryupdate/kvapi/api.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
// +build !change
|
||||
|
||||
package kvapi
|
||||
|
||||
import "github.com/gofrs/uuid"
|
||||
|
||||
type (
|
||||
Client interface {
|
||||
// Get the value of key.
|
||||
Get(req *GetRequest) (*GetResponse, error)
|
||||
|
||||
// Set key to hold given value.
|
||||
Set(rsp *SetRequest) (*SetResponse, error)
|
||||
}
|
||||
|
||||
GetRequest struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
GetResponse struct {
|
||||
Value string
|
||||
Version uuid.UUID
|
||||
}
|
||||
|
||||
SetRequest struct {
|
||||
Key, Value string
|
||||
|
||||
// OldVersion field is checked before updating value.
|
||||
//
|
||||
// When updating old value, OldVersion must specify uuid of currently stored value.
|
||||
// When creating new value, OldVersion must hold zero value of UUID.
|
||||
OldVersion, NewVersion uuid.UUID
|
||||
}
|
||||
|
||||
SetResponse struct{}
|
||||
)
|
56
retryupdate/kvapi/errors.go
Normal file
56
retryupdate/kvapi/errors.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
// +build !change
|
||||
|
||||
package kvapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
_ error = (*ApiError)(nil)
|
||||
_ error = (*ConflictError)(nil)
|
||||
_ error = (*AuthError)(nil)
|
||||
_ error = (*NotFoundError)(nil)
|
||||
)
|
||||
|
||||
type (
|
||||
ApiError struct {
|
||||
Method string
|
||||
|
||||
Err error
|
||||
}
|
||||
|
||||
ConflictError struct {
|
||||
ProvidedVersion, ExpectedVersion uuid.UUID
|
||||
}
|
||||
|
||||
AuthError struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
NotFoundError struct {
|
||||
Key string
|
||||
}
|
||||
)
|
||||
|
||||
func (a *ApiError) Error() string {
|
||||
return fmt.Sprintf("api: %q error: %v", a.Method, a.Err)
|
||||
}
|
||||
|
||||
func (a *ApiError) Unwrap() error {
|
||||
return a.Err
|
||||
}
|
||||
|
||||
func (a *ConflictError) Error() string {
|
||||
return fmt.Sprintf("api: conflict: expected_version=%d, provided_version=%d", a.ExpectedVersion, a.ProvidedVersion)
|
||||
}
|
||||
|
||||
func (a *AuthError) Error() string {
|
||||
return fmt.Sprintf("api: auth: %s", a.Msg)
|
||||
}
|
||||
|
||||
func (a *NotFoundError) Error() string {
|
||||
return fmt.Sprintf("api: key %q is not found", a.Key)
|
||||
}
|
64
retryupdate/mock_test.go
Normal file
64
retryupdate/mock_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: gitlab.com/slon/shad-go/retryupdate/kvapi (interfaces: Client)
|
||||
|
||||
// Package retryupdate_test is a generated GoMock package.
|
||||
package retryupdate_test
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
kvapi "gitlab.com/slon/shad-go/retryupdate/kvapi"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface
|
||||
type MockClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockClientMockRecorder
|
||||
}
|
||||
|
||||
// MockClientMockRecorder is the mock recorder for MockClient
|
||||
type MockClientMockRecorder struct {
|
||||
mock *MockClient
|
||||
}
|
||||
|
||||
// NewMockClient creates a new mock instance
|
||||
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
||||
mock := &MockClient{ctrl: ctrl}
|
||||
mock.recorder = &MockClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockClient) Get(arg0 *kvapi.GetRequest) (*kvapi.GetResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0)
|
||||
ret0, _ := ret[0].(*kvapi.GetResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockClientMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), arg0)
|
||||
}
|
||||
|
||||
// Set mocks base method
|
||||
func (m *MockClient) Set(arg0 *kvapi.SetRequest) (*kvapi.SetResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Set", arg0)
|
||||
ret0, _ := ret[0].(*kvapi.SetResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set
|
||||
func (mr *MockClientMockRecorder) Set(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockClient)(nil).Set), arg0)
|
||||
}
|
9
retryupdate/update.go
Normal file
9
retryupdate/update.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build !solution
|
||||
|
||||
package retryupdate
|
||||
|
||||
import "gitlab.com/slon/shad-go/retryupdate/kvapi"
|
||||
|
||||
func UpdateValue(c kvapi.Client, key string, updateFn func(oldValue *string) (newValue string, err error)) error {
|
||||
panic("implement me")
|
||||
}
|
303
retryupdate/update_test.go
Normal file
303
retryupdate/update_test.go
Normal file
|
@ -0,0 +1,303 @@
|
|||
package retryupdate_test
|
||||
|
||||
//go:generate mockgen -destination mock_test.go -package retryupdate_test gitlab.com/slon/shad-go/retryupdate/kvapi Client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitlab.com/slon/shad-go/retryupdate"
|
||||
"gitlab.com/slon/shad-go/retryupdate/kvapi"
|
||||
)
|
||||
|
||||
var (
|
||||
K0 = "K0"
|
||||
V0 = "V0"
|
||||
V1 = "V1"
|
||||
V2 = "V2"
|
||||
V3 = "V3"
|
||||
|
||||
UUID0 = uuid.Must(uuid.NewV4())
|
||||
UUID1 = uuid.Must(uuid.NewV4())
|
||||
UUID2 = uuid.Must(uuid.NewV4())
|
||||
|
||||
errUpdate = errors.New("update error")
|
||||
|
||||
errGetAuth = &kvapi.ApiError{Method: "get", Err: &kvapi.AuthError{Msg: "token expired"}}
|
||||
errSetAuth = &kvapi.ApiError{Method: "set", Err: &kvapi.AuthError{Msg: "token expired"}}
|
||||
|
||||
errGetNoKey = &kvapi.ApiError{Method: "get", Err: &kvapi.NotFoundError{Key: K0}}
|
||||
errSetNoKey = &kvapi.ApiError{Method: "set", Err: &kvapi.NotFoundError{Key: K0}}
|
||||
|
||||
errGetTemporary = &kvapi.ApiError{Method: "get", Err: errors.New("unavailable")}
|
||||
errSetTemporary = &kvapi.ApiError{Method: "set", Err: errors.New("unavailable")}
|
||||
)
|
||||
|
||||
type setMatcher struct {
|
||||
kvapi.SetRequest
|
||||
|
||||
save *uuid.UUID
|
||||
}
|
||||
|
||||
func (m setMatcher) Matches(x interface{}) bool {
|
||||
if arg, ok := x.(*kvapi.SetRequest); ok {
|
||||
if m.save != nil {
|
||||
*m.save = arg.NewVersion
|
||||
}
|
||||
|
||||
return arg.Key == m.Key && arg.Value == m.Value && arg.OldVersion == m.OldVersion
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m setMatcher) String() string {
|
||||
return fmt.Sprintf("%v", m.SetRequest)
|
||||
}
|
||||
|
||||
func SetRequest(k, v string, oldVersion uuid.UUID, saveUUID ...*uuid.UUID) gomock.Matcher {
|
||||
m := setMatcher{
|
||||
SetRequest: kvapi.SetRequest{
|
||||
Key: k,
|
||||
Value: v,
|
||||
OldVersion: oldVersion,
|
||||
},
|
||||
}
|
||||
|
||||
if len(saveUUID) > 1 {
|
||||
panic("error")
|
||||
}
|
||||
|
||||
if len(saveUUID) == 1 {
|
||||
m.save = saveUUID[0]
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func updateFn(oldValue *string) (string, error) {
|
||||
switch {
|
||||
case oldValue == nil:
|
||||
return V0, nil
|
||||
case *oldValue == V0:
|
||||
return V1, nil
|
||||
case *oldValue == V1:
|
||||
return V2, nil
|
||||
case *oldValue == V2:
|
||||
return V3, nil
|
||||
default:
|
||||
return "", errUpdate
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleUpdate(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0)).
|
||||
Return(&kvapi.SetResponse{}, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestUpdateFnError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V3, Version: UUID0}, nil),
|
||||
)
|
||||
|
||||
require.Equal(t, errUpdate, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
func TestCreateKey(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(nil, errGetNoKey),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V0, uuid.UUID{})).
|
||||
Return(&kvapi.SetResponse{}, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestKeyVanished(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V2, Version: UUID0}, nil),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V3, UUID0)).
|
||||
Return(nil, errSetNoKey),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V0, uuid.UUID{})).
|
||||
Return(&kvapi.SetResponse{}, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestFailOnAuthErrorInGet(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(nil, errGetAuth),
|
||||
)
|
||||
|
||||
require.Equal(t, errGetAuth, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestFailOnAuthErrorInSet(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(nil, errGetNoKey),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V0, uuid.UUID{})).
|
||||
Return(nil, errSetAuth),
|
||||
)
|
||||
|
||||
require.Equal(t, errSetAuth, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestRetryGetError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(nil, errGetTemporary),
|
||||
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(nil, errGetTemporary),
|
||||
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0)).
|
||||
Return(&kvapi.SetResponse{}, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestRetrySetError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0)).
|
||||
Return(nil, errSetTemporary),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0)).
|
||||
Return(&kvapi.SetResponse{}, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestRetrySetConflict(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0)).
|
||||
Return(nil, errSetTemporary),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0)).
|
||||
Return(nil, &kvapi.ApiError{Method: "set", Err: &kvapi.ConflictError{ExpectedVersion: UUID1, ProvidedVersion: UUID0}}),
|
||||
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V2, Version: UUID1}, nil),
|
||||
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V3, UUID1)).
|
||||
Return(&kvapi.SetResponse{}, nil),
|
||||
)
|
||||
|
||||
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
||||
|
||||
func TestRetrySetFalseConflict(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
conflictErr := &kvapi.ConflictError{ProvidedVersion: UUID0}
|
||||
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
Get(&kvapi.GetRequest{Key: K0}).
|
||||
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
||||
|
||||
// first Set updates key state, but returns an error.
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0, &conflictErr.ExpectedVersion)).
|
||||
Return(nil, errSetTemporary),
|
||||
|
||||
// second Set returns conflict with ExpectedVersion == OldVersion from previous request.
|
||||
c.EXPECT().
|
||||
Set(SetRequest(K0, V1, UUID0)).
|
||||
Return(nil, &kvapi.ApiError{Method: "set", Err: conflictErr}),
|
||||
)
|
||||
|
||||
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
||||
}
|
Loading…
Reference in a new issue