Skip to content

Commit

Permalink
Merge pull request #16113 from serathius/robustness-range
Browse files Browse the repository at this point in the history
tests/robustness: Implement proper range requests
  • Loading branch information
serathius authored Jun 22, 2023
2 parents 6fd77ac + 2e7cb77 commit f985890
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 134 deletions.
42 changes: 23 additions & 19 deletions tests/robustness/model/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func describeEtcdResponse(request EtcdRequest, response MaybeEtcdResponse) strin
func describeEtcdRequest(request EtcdRequest) string {
switch request.Type {
case Range:
return describeRangeRequest(request.Range.Key, request.Range.Revision, request.Range.RangeOptions)
return describeRangeRequest(request.Range.RangeOptions, request.Range.Revision)
case Txn:
onSuccess := describeEtcdOperations(request.Txn.OperationsOnSuccess)
if len(request.Txn.Conditions) != 0 {
Expand Down Expand Up @@ -105,52 +105,56 @@ func describeTxnResponse(request *TxnRequest, response *TxnResponse) string {
func describeEtcdOperation(op EtcdOperation) string {
switch op.Type {
case RangeOperation:
return describeRangeRequest(op.Key, 0, op.RangeOptions)
return describeRangeRequest(op.Range, 0)
case PutOperation:
if op.LeaseID != 0 {
return fmt.Sprintf("put(%q, %s, %d)", op.Key, describeValueOrHash(op.Value), op.LeaseID)
if op.Put.LeaseID != 0 {
return fmt.Sprintf("put(%q, %s, %d)", op.Put.Key, describeValueOrHash(op.Put.Value), op.Put.LeaseID)
}
return fmt.Sprintf("put(%q, %s)", op.Key, describeValueOrHash(op.Value))
return fmt.Sprintf("put(%q, %s)", op.Put.Key, describeValueOrHash(op.Put.Value))
case DeleteOperation:
return fmt.Sprintf("delete(%q)", op.Key)
return fmt.Sprintf("delete(%q)", op.Delete.Key)
default:
return fmt.Sprintf("<! unknown op: %q !>", op.Type)
}
}

func describeRangeRequest(key string, revision int64, opts RangeOptions) string {
func describeRangeRequest(opts RangeOptions, revision int64) string {
kwargs := []string{}
if revision != 0 {
kwargs = append(kwargs, fmt.Sprintf("rev=%d", revision))
}
if opts.Limit != 0 {
kwargs = append(kwargs, fmt.Sprintf("limit=%d", opts.Limit))
}
command := "get"
if opts.WithPrefix {
command = "range"
kwargsString := strings.Join(kwargs, ", ")
if kwargsString != "" {
kwargsString = ", " + kwargsString
}
if len(kwargs) == 0 {
return fmt.Sprintf("%s(%q)", command, key)
switch {
case opts.End == "":
return fmt.Sprintf("get(%q%s)", opts.Start, kwargsString)
case opts.End == prefixEnd(opts.Start):
return fmt.Sprintf("list(%q%s)", opts.Start, kwargsString)
default:
return fmt.Sprintf("range(%q..%q%s)", opts.Start, opts.End, kwargsString)
}
return fmt.Sprintf("%s(%q, %s)", command, key, strings.Join(kwargs, ", "))
}

func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult) string {
switch req.Type {
func describeEtcdOperationResponse(op EtcdOperation, resp EtcdOperationResult) string {
switch op.Type {
case RangeOperation:
return describeRangeResponse(req.RangeOptions, resp.RangeResponse)
return describeRangeResponse(op.Range, resp.RangeResponse)
case PutOperation:
return fmt.Sprintf("ok")
case DeleteOperation:
return fmt.Sprintf("deleted: %d", resp.Deleted)
default:
return fmt.Sprintf("<! unknown op: %q !>", req.Type)
return fmt.Sprintf("<! unknown op: %q !>", op.Type)
}
}

func describeRangeResponse(opts RangeOptions, response RangeResponse) string {
if opts.WithPrefix {
func describeRangeResponse(request RangeOptions, response RangeResponse) string {
if request.End != "" {
kvs := make([]string, len(response.KVs))
for i, kv := range response.KVs {
kvs[i] = describeValueOrHash(kv.Value)
Expand Down
45 changes: 30 additions & 15 deletions tests/robustness/model/describe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import (
"errors"
"testing"

"github.com/stretchr/testify/assert"

"go.etcd.io/etcd/api/v3/mvccpb"

"github.com/stretchr/testify/assert"
)

func TestModelDescribe(t *testing.T) {
Expand Down Expand Up @@ -95,17 +95,17 @@ func TestModelDescribe(t *testing.T) {
expectDescribe: `if(mod_rev(key9)==9).then(put("key9", "99")) -> err: "failed"`,
},
{
req: txnRequest([]EtcdCondition{{Key: "key9b", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9b", PutOptions: PutOptions{Value: ValueOrHash{Value: "991"}}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9b"}}),
req: txnRequest([]EtcdCondition{{Key: "key9b", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Put: PutOptions{Key: "key9b", Value: ValueOrHash{Value: "991"}}}}, []EtcdOperation{{Type: RangeOperation, Range: RangeOptions{Start: "key9b"}}}),
resp: txnResponse([]EtcdOperationResult{{}}, true, 10),
expectDescribe: `if(mod_rev(key9b)==9).then(put("key9b", "991")).else(get("key9b")) -> success(ok), rev: 10`,
},
{
req: txnRequest([]EtcdCondition{{Key: "key9c", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9c", PutOptions: PutOptions{Value: ValueOrHash{Value: "992"}}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9c"}}),
req: txnRequest([]EtcdCondition{{Key: "key9c", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Put: PutOptions{Key: "key9c", Value: ValueOrHash{Value: "992"}}}}, []EtcdOperation{{Type: RangeOperation, Range: RangeOptions{Start: "key9c"}}}),
resp: txnResponse([]EtcdOperationResult{{RangeResponse: RangeResponse{KVs: []KeyValue{{Key: "key9c", ValueRevision: ValueRevision{Value: ValueOrHash{Value: "993"}, ModRevision: 10}}}}}}, false, 10),
expectDescribe: `if(mod_rev(key9c)==9).then(put("key9c", "992")).else(get("key9c")) -> failure("993"), rev: 10`,
},
{
req: txnRequest(nil, []EtcdOperation{{Type: RangeOperation, Key: "10"}, {Type: PutOperation, Key: "11", PutOptions: PutOptions{Value: ValueOrHash{Value: "111"}}}, {Type: DeleteOperation, Key: "12"}}, nil),
req: txnRequest(nil, []EtcdOperation{{Type: RangeOperation, Range: RangeOptions{Start: "10"}}, {Type: PutOperation, Put: PutOptions{Key: "11", Value: ValueOrHash{Value: "111"}}}, {Type: DeleteOperation, Delete: DeleteOptions{Key: "12"}}}, nil),
resp: txnResponse([]EtcdOperationResult{{RangeResponse: RangeResponse{KVs: []KeyValue{{ValueRevision: ValueRevision{Value: ValueOrHash{Value: "110"}}}}}}, {}, {Deleted: 1}}, true, 10),
expectDescribe: `get("10"), put("11", "111"), delete("12") -> "110", ok, deleted: 1, rev: 10`,
},
Expand All @@ -115,29 +115,44 @@ func TestModelDescribe(t *testing.T) {
expectDescribe: `defragment() -> ok, rev: 10`,
},
{
req: rangeRequest("key11", true, 0),
req: listRequest("key11", 0),
resp: rangeResponse(nil, 0, 11),
expectDescribe: `range("key11") -> [], count: 0, rev: 11`,
expectDescribe: `list("key11") -> [], count: 0, rev: 11`,
},
{
req: rangeRequest("key12", true, 0),
req: listRequest("key12", 0),
resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("12")}}, 2, 12),
expectDescribe: `range("key12") -> ["12"], count: 2, rev: 12`,
expectDescribe: `list("key12") -> ["12"], count: 2, rev: 12`,
},
{
req: rangeRequest("key13", true, 0),
req: listRequest("key13", 0),
resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("01234567890123456789")}}, 1, 13),
expectDescribe: `range("key13") -> [hash: 2945867837], count: 1, rev: 13`,
expectDescribe: `list("key13") -> [hash: 2945867837], count: 1, rev: 13`,
},
{
req: rangeRequest("key14", true, 14),
req: listRequest("key14", 14),
resp: rangeResponse(nil, 0, 14),
expectDescribe: `range("key14", limit=14) -> [], count: 0, rev: 14`,
expectDescribe: `list("key14", limit=14) -> [], count: 0, rev: 14`,
},
{
req: staleRangeRequest("key15", true, 0, 15),
req: staleListRequest("key15", 0, 15),
resp: rangeResponse(nil, 0, 15),
expectDescribe: `range("key15", rev=15) -> [], count: 0, rev: 15`,
expectDescribe: `list("key15", rev=15) -> [], count: 0, rev: 15`,
},
{
req: staleListRequest("key15", 2, 15),
resp: rangeResponse(nil, 0, 15),
expectDescribe: `list("key15", rev=15, limit=2) -> [], count: 0, rev: 15`,
},
{
req: rangeRequest("key16", "key16b", 0),
resp: rangeResponse(nil, 0, 16),
expectDescribe: `range("key16".."key16b") -> [], count: 0, rev: 16`,
},
{
req: rangeRequest("key16", "key16b", 2),
resp: rangeResponse(nil, 0, 16),
expectDescribe: `range("key16".."key16b", limit=2) -> [], count: 0, rev: 16`,
},
}
for _, tc := range tcs {
Expand Down
52 changes: 28 additions & 24 deletions tests/robustness/model/deterministic.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"hash/fnv"
"reflect"
"sort"
"strings"

"github.com/anishathalye/porcupine"
)
Expand Down Expand Up @@ -95,7 +94,7 @@ func (s EtcdState) Step(request EtcdRequest) (EtcdState, MaybeEtcdResponse) {
switch request.Type {
case Range:
if request.Range.Revision == 0 || request.Range.Revision == s.Revision {
resp := s.getRange(request.Range.Key, request.Range.RangeOptions)
resp := s.getRange(request.Range.RangeOptions)
return s, MaybeEtcdResponse{EtcdResponse: EtcdResponse{Range: &resp, Revision: s.Revision}}
} else {
if request.Range.Revision > s.Revision {
Expand All @@ -121,27 +120,27 @@ func (s EtcdState) Step(request EtcdRequest) (EtcdState, MaybeEtcdResponse) {
switch op.Type {
case RangeOperation:
opResp[i] = EtcdOperationResult{
RangeResponse: s.getRange(op.Key, op.RangeOptions),
RangeResponse: s.getRange(op.Range),
}
case PutOperation:
_, leaseExists := s.Leases[op.LeaseID]
if op.LeaseID != 0 && !leaseExists {
_, leaseExists := s.Leases[op.Put.LeaseID]
if op.Put.LeaseID != 0 && !leaseExists {
break
}
s.KeyValues[op.Key] = ValueRevision{
Value: op.Value,
s.KeyValues[op.Put.Key] = ValueRevision{
Value: op.Put.Value,
ModRevision: s.Revision + 1,
}
increaseRevision = true
s = detachFromOldLease(s, op.Key)
s = detachFromOldLease(s, op.Put.Key)
if leaseExists {
s = attachToNewLease(s, op.LeaseID, op.Key)
s = attachToNewLease(s, op.Put.LeaseID, op.Put.Key)
}
case DeleteOperation:
if _, ok := s.KeyValues[op.Key]; ok {
delete(s.KeyValues, op.Key)
if _, ok := s.KeyValues[op.Delete.Key]; ok {
delete(s.KeyValues, op.Delete.Key)
increaseRevision = true
s = detachFromOldLease(s, op.Key)
s = detachFromOldLease(s, op.Delete.Key)
opResp[i].Deleted = 1
}
default:
Expand Down Expand Up @@ -185,14 +184,14 @@ func (s EtcdState) Step(request EtcdRequest) (EtcdState, MaybeEtcdResponse) {
}
}

func (s EtcdState) getRange(key string, options RangeOptions) RangeResponse {
func (s EtcdState) getRange(options RangeOptions) RangeResponse {
response := RangeResponse{
KVs: []KeyValue{},
}
if options.WithPrefix {
if options.End != "" {
var count int64
for k, v := range s.KeyValues {
if strings.HasPrefix(k, key) {
if k >= options.Start && k < options.End {
response.KVs = append(response.KVs, KeyValue{Key: k, ValueRevision: v})
count += 1
}
Expand All @@ -205,10 +204,10 @@ func (s EtcdState) getRange(key string, options RangeOptions) RangeResponse {
}
response.Count = count
} else {
value, ok := s.KeyValues[key]
value, ok := s.KeyValues[options.Start]
if ok {
response.KVs = append(response.KVs, KeyValue{
Key: key,
Key: options.Start,
ValueRevision: value,
})
response.Count = 1
Expand Down Expand Up @@ -251,21 +250,26 @@ type EtcdRequest struct {
}

type RangeRequest struct {
Key string
RangeOptions
Revision int64
}

type RangeOptions struct {
WithPrefix bool
Limit int64
Start string
End string
Limit int64
}

type PutOptions struct {
Key string
Value ValueOrHash
LeaseID int64
}

type DeleteOptions struct {
Key string
}

type TxnRequest struct {
Conditions []EtcdCondition
OperationsOnSuccess []EtcdOperation
Expand All @@ -278,10 +282,10 @@ type EtcdCondition struct {
}

type EtcdOperation struct {
Type OperationType
Key string
RangeOptions
PutOptions
Type OperationType
Range RangeOptions
Put PutOptions
Delete DeleteOptions
}

type OperationType string
Expand Down
20 changes: 10 additions & 10 deletions tests/robustness/model/deterministic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ var commonTestScenarios = []modelTestCase{
operations: []testOperation{
{req: putRequest("key1", "1"), resp: putResponse(2)},
{req: putRequest("key2", "2"), resp: putResponse(3)},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 3}}, 2, 3)},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 3}}, 2, 3)},
{req: listRequest("key", 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 3}}, 2, 3)},
{req: listRequest("key", 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 3}}, 2, 3)},
},
},
{
Expand All @@ -93,26 +93,26 @@ var commonTestScenarios = []modelTestCase{
{req: putRequest("key1", "1"), resp: putResponse(2)},
{req: putRequest("key2", "2"), resp: putResponse(3)},
{req: putRequest("key3", "3"), resp: putResponse(4)},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 3},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 4},
}, 3, 4)},
{req: rangeRequest("key", true, 4), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 4), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 3},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 4},
}, 3, 4)},
{req: rangeRequest("key", true, 3), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 3), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 3},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 4},
}, 3, 4)},
{req: rangeRequest("key", true, 2), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 2), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 3},
}, 3, 4)},
{req: rangeRequest("key", true, 1), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 1), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 2},
}, 3, 4)},
},
Expand All @@ -123,17 +123,17 @@ var commonTestScenarios = []modelTestCase{
{req: putRequest("key3", "3"), resp: putResponse(2)},
{req: putRequest("key2", "1"), resp: putResponse(3)},
{req: putRequest("key1", "2"), resp: putResponse(4)},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("2"), ModRevision: 4},
{Key: []byte("key2"), Value: []byte("1"), ModRevision: 3},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 2},
}, 3, 4)},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key2"), Value: []byte("1"), ModRevision: 3},
{Key: []byte("key1"), Value: []byte("2"), ModRevision: 4},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 2},
}, 3, 4), expectFailure: true},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{req: listRequest("key", 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 2},
{Key: []byte("key2"), Value: []byte("1"), ModRevision: 3},
{Key: []byte("key1"), Value: []byte("2"), ModRevision: 4},
Expand Down
Loading

0 comments on commit f985890

Please sign in to comment.