File
Blob: spec/chord/retry_test.go
| 1 | package chord |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "testing" |
| 6 | "time" |
| 7 | |
| 8 | "github.com/stretchr/testify/require" |
| 9 | ) |
| 10 | |
| 11 | type retryTestNode struct { |
| 12 | VNode |
| 13 | get func(context.Context, []byte) ([]byte, error) |
| 14 | } |
| 15 | |
| 16 | func (n *retryTestNode) Get(ctx context.Context, key []byte) ([]byte, error) { |
| 17 | return n.get(ctx, key) |
| 18 | } |
| 19 | |
| 20 | func TestRetryKVPreservesAttemptsValuesAndErrors(t *testing.T) { |
| 21 | for _, tc := range []struct { |
| 22 | name string |
| 23 | results []error |
| 24 | wantErr error |
| 25 | }{ |
| 26 | {name: "retry stale ownership", results: []error{ErrKVStaleOwnership, nil}}, |
| 27 | {name: "stop on conflict", results: []error{ErrKVSimpleConflict}, wantErr: ErrKVSimpleConflict}, |
| 28 | {name: "return final error", results: []error{ErrKVStaleOwnership, ErrKVPendingTransfer, ErrKVPendingTransfer}, wantErr: ErrKVPendingTransfer}, |
| 29 | } { |
| 30 | t.Run(tc.name, func(t *testing.T) { |
| 31 | calls := 0 |
| 32 | node := &retryTestNode{get: func(ctx context.Context, key []byte) ([]byte, error) { |
| 33 | require.Equal(t, []byte("key"), key) |
| 34 | require.NoError(t, ctx.Err()) |
| 35 | if calls >= len(tc.results) { |
| 36 | t.Fatal("retried after the expected final attempt") |
| 37 | } |
| 38 | err := tc.results[calls] |
| 39 | calls++ |
| 40 | return []byte{}, err |
| 41 | }} |
| 42 | value, err := WrapRetryKV(node, time.Nanosecond, 3).Get(t.Context(), []byte("key")) |
| 43 | require.Equal(t, tc.wantErr, err, "errors must remain directly comparable") |
| 44 | require.Equal(t, len(tc.results), calls) |
| 45 | if err == nil { |
| 46 | require.NotNil(t, value, "successful empty values must stay distinct from missing values") |
| 47 | require.Empty(t, value) |
| 48 | } |
| 49 | }) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | func TestRetryKVCancellationStopsBackoff(t *testing.T) { |
| 54 | ctx, cancel := context.WithCancel(t.Context()) |
| 55 | defer cancel() |
| 56 | calls := 0 |
| 57 | node := &retryTestNode{get: func(context.Context, []byte) ([]byte, error) { |
| 58 | calls++ |
| 59 | cancel() |
| 60 | return nil, ErrKVStaleOwnership |
| 61 | }} |
| 62 | _, err := WrapRetryKV(node, time.Hour, 3).Get(ctx, []byte("key")) |
| 63 | require.ErrorIs(t, err, context.Canceled) |
| 64 | require.Equal(t, 1, calls) |
| 65 | } |