tests/robustness: Implement Range limit and count

Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
This commit is contained in:
Marek Siarkowicz 2023-05-07 09:31:47 +02:00
parent 249c0d71d4
commit 7c68be4cf3
6 changed files with 91 additions and 35 deletions

View File

@ -89,6 +89,9 @@ func describeEtcdOperation(op EtcdOperation) string {
switch op.Type { switch op.Type {
case Range: case Range:
if op.WithPrefix { if op.WithPrefix {
if op.Limit != 0 {
return fmt.Sprintf("range(%q, limit=%d)", op.Key, op.Limit)
}
return fmt.Sprintf("range(%q)", op.Key) return fmt.Sprintf("range(%q)", op.Key)
} }
return fmt.Sprintf("get(%q)", op.Key) return fmt.Sprintf("get(%q)", op.Key)
@ -112,7 +115,7 @@ func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult)
for i, kv := range resp.KVs { for i, kv := range resp.KVs {
kvs[i] = describeValueOrHash(kv.Value) kvs[i] = describeValueOrHash(kv.Value)
} }
return fmt.Sprintf("[%s]", strings.Join(kvs, ",")) return fmt.Sprintf("[%s], count: %d", strings.Join(kvs, ","), resp.Count)
} else { } else {
if len(resp.KVs) == 0 { if len(resp.KVs) == 0 {
return "nil" return "nil"

View File

@ -105,19 +105,24 @@ func TestModelDescribe(t *testing.T) {
expectDescribe: `defragment() -> ok, rev: 10`, expectDescribe: `defragment() -> ok, rev: 10`,
}, },
{ {
req: rangeRequest("key11", true), req: rangeRequest("key11", true, 0),
resp: rangeResponse(nil, 11), resp: rangeResponse(nil, 0, 11),
expectDescribe: `range("key11") -> [], rev: 11`, expectDescribe: `range("key11") -> [], count: 0, rev: 11`,
}, },
{ {
req: rangeRequest("key12", true), req: rangeRequest("key12", true, 0),
resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("12")}}, 12), resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("12")}}, 2, 12),
expectDescribe: `range("key12") -> ["12"], rev: 12`, expectDescribe: `range("key12") -> ["12"], count: 2, rev: 12`,
}, },
{ {
req: rangeRequest("key13", true), req: rangeRequest("key13", true, 0),
resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("01234567890123456789")}}, 13), resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("01234567890123456789")}}, 1, 13),
expectDescribe: `range("key13") -> [hash: 2945867837], rev: 13`, expectDescribe: `range("key13") -> [hash: 2945867837], count: 1, rev: 13`,
},
{
req: rangeRequest("key14", true, 14),
resp: rangeResponse(nil, 0, 14),
expectDescribe: `range("key14", limit=14) -> [], count: 0, rev: 14`,
}, },
} }
for _, tc := range tcs { for _, tc := range tcs {

View File

@ -146,14 +146,20 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) {
KVs: []KeyValue{}, KVs: []KeyValue{},
} }
if op.WithPrefix { if op.WithPrefix {
var count int64
for k, v := range s.KeyValues { for k, v := range s.KeyValues {
if strings.HasPrefix(k, op.Key) { if strings.HasPrefix(k, op.Key) {
opResp[i].KVs = append(opResp[i].KVs, KeyValue{Key: k, ValueRevision: v}) opResp[i].KVs = append(opResp[i].KVs, KeyValue{Key: k, ValueRevision: v})
count += 1
} }
} }
sort.Slice(opResp[i].KVs, func(j, k int) bool { sort.Slice(opResp[i].KVs, func(j, k int) bool {
return opResp[i].KVs[j].Key < opResp[i].KVs[k].Key return opResp[i].KVs[j].Key < opResp[i].KVs[k].Key
}) })
if op.Limit != 0 && count > op.Limit {
opResp[i].KVs = opResp[i].KVs[:op.Limit]
}
opResp[i].Count = count
} else { } else {
value, ok := s.KeyValues[op.Key] value, ok := s.KeyValues[op.Key]
if ok { if ok {
@ -161,6 +167,7 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) {
Key: op.Key, Key: op.Key,
ValueRevision: value, ValueRevision: value,
}) })
opResp[i].Count = 1
} }
} }
case Put: case Put:
@ -270,6 +277,7 @@ type EtcdOperation struct {
Type OperationType Type OperationType
Key string Key string
WithPrefix bool WithPrefix bool
Limit int64
Value ValueOrHash Value ValueOrHash
LeaseID int64 LeaseID int64
} }
@ -303,6 +311,7 @@ type DefragmentResponse struct{}
type EtcdOperationResult struct { type EtcdOperationResult struct {
KVs []KeyValue KVs []KeyValue
Count int64
Deleted int64 Deleted int64
} }

View File

@ -40,15 +40,15 @@ func TestModelBase(t *testing.T) {
{ {
name: "First Range can start from non-empty value and non-zero revision", name: "First Range can start from non-empty value and non-zero revision",
operations: []testOperation{ operations: []testOperation{
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 42).EtcdResponse}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 1, 42).EtcdResponse},
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 42).EtcdResponse}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 1, 42).EtcdResponse},
}, },
}, },
{ {
name: "First Range can start from non-zero revision", name: "First Range can start from non-zero revision",
operations: []testOperation{ operations: []testOperation{
{req: rangeRequest("key", true), resp: rangeResponse(nil, 1).EtcdResponse}, {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse},
{req: rangeRequest("key", true), resp: rangeResponse(nil, 1).EtcdResponse}, {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse},
}, },
}, },
{ {
@ -89,18 +89,55 @@ func TestModelBase(t *testing.T) {
operations: []testOperation{ operations: []testOperation{
{req: putRequest("key1", "1"), resp: putResponse(1).EtcdResponse}, {req: putRequest("key1", "1"), resp: putResponse(1).EtcdResponse},
{req: putRequest("key2", "2"), resp: putResponse(2).EtcdResponse}, {req: putRequest("key2", "2"), resp: putResponse(2).EtcdResponse},
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2).EtcdResponse}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2, 2).EtcdResponse},
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2).EtcdResponse}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2, 2).EtcdResponse},
},
},
{
name: "Range limit should reduce number of kvs, but maintain count",
operations: []testOperation{
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 2},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 3},
}, 3, 3).EtcdResponse},
{req: rangeRequest("key", true, 4), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 2},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 3},
}, 3, 3).EtcdResponse},
{req: rangeRequest("key", true, 3), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 2},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 3},
}, 3, 3).EtcdResponse},
{req: rangeRequest("key", true, 2), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1},
{Key: []byte("key2"), Value: []byte("2"), ModRevision: 2},
}, 3, 3).EtcdResponse},
{req: rangeRequest("key", true, 1), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1},
}, 3, 3).EtcdResponse},
}, },
}, },
{ {
name: "Range response should be ordered by key", name: "Range response should be ordered by key",
operations: []testOperation{ operations: []testOperation{
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{ {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("2"), ModRevision: 3}, {Key: []byte("key1"), Value: []byte("2"), ModRevision: 3},
{Key: []byte("key2"), Value: []byte("1"), ModRevision: 2}, {Key: []byte("key2"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 1}, {Key: []byte("key3"), Value: []byte("3"), ModRevision: 1},
}, 3).EtcdResponse}, }, 3, 3).EtcdResponse},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key2"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key1"), Value: []byte("2"), ModRevision: 3},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 1},
}, 3, 3).EtcdResponse, failure: true},
{req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 1},
{Key: []byte("key2"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key1"), Value: []byte("2"), ModRevision: 3},
}, 3, 3).EtcdResponse, failure: true},
}, },
}, },
{ {

View File

@ -72,9 +72,9 @@ func (h *AppendableHistory) AppendRange(key string, withPrefix bool, start, end
} }
h.successful = append(h.successful, porcupine.Operation{ h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id, ClientId: h.id,
Input: rangeRequest(key, withPrefix), Input: rangeRequest(key, withPrefix, 0),
Call: start.Nanoseconds(), Call: start.Nanoseconds(),
Output: rangeResponse(resp.Kvs, revision), Output: rangeResponse(resp.Kvs, resp.Count, revision),
Return: end.Nanoseconds(), Return: end.Nanoseconds(),
}) })
} }
@ -299,7 +299,8 @@ func toEtcdOperationResult(resp *etcdserverpb.ResponseOp) EtcdOperationResult {
} }
} }
return EtcdOperationResult{ return EtcdOperationResult{
KVs: kvs, KVs: kvs,
Count: getResp.Count,
} }
case resp.GetResponsePut() != nil: case resp.GetResponsePut() != nil:
return EtcdOperationResult{} return EtcdOperationResult{}
@ -345,22 +346,22 @@ func (h *AppendableHistory) appendFailed(request EtcdRequest, start time.Duratio
} }
func getRequest(key string) EtcdRequest { func getRequest(key string) EtcdRequest {
return rangeRequest(key, false) return rangeRequest(key, false, 0)
} }
func rangeRequest(key string, withPrefix bool) EtcdRequest { func rangeRequest(key string, withPrefix bool, limit int64) EtcdRequest {
return EtcdRequest{Type: Txn, Txn: &TxnRequest{Ops: []EtcdOperation{{Type: Range, Key: key, WithPrefix: withPrefix}}}} return EtcdRequest{Type: Txn, Txn: &TxnRequest{Ops: []EtcdOperation{{Type: Range, Key: key, WithPrefix: withPrefix, Limit: limit}}}}
} }
func emptyGetResponse(revision int64) EtcdNonDeterministicResponse { func emptyGetResponse(revision int64) EtcdNonDeterministicResponse {
return rangeResponse([]*mvccpb.KeyValue{}, revision) return rangeResponse([]*mvccpb.KeyValue{}, 0, revision)
} }
func getResponse(key, value string, modRevision, revision int64) EtcdNonDeterministicResponse { func getResponse(key, value string, modRevision, revision int64) EtcdNonDeterministicResponse {
return rangeResponse([]*mvccpb.KeyValue{{Key: []byte(key), Value: []byte(value), ModRevision: modRevision}}, revision) return rangeResponse([]*mvccpb.KeyValue{{Key: []byte(key), Value: []byte(value), ModRevision: modRevision}}, 1, revision)
} }
func rangeResponse(kvs []*mvccpb.KeyValue, revision int64) EtcdNonDeterministicResponse { func rangeResponse(kvs []*mvccpb.KeyValue, count int64, revision int64) EtcdNonDeterministicResponse {
result := EtcdOperationResult{KVs: make([]KeyValue, len(kvs))} result := EtcdOperationResult{KVs: make([]KeyValue, len(kvs))}
for i, kv := range kvs { for i, kv := range kvs {
@ -371,6 +372,7 @@ func rangeResponse(kvs []*mvccpb.KeyValue, revision int64) EtcdNonDeterministicR
ModRevision: kv.ModRevision, ModRevision: kv.ModRevision,
}, },
} }
result.Count = count
} }
return EtcdNonDeterministicResponse{EtcdResponse: EtcdResponse{Txn: &TxnResponse{OpsResult: []EtcdOperationResult{result}}, Revision: revision}} return EtcdNonDeterministicResponse{EtcdResponse: EtcdResponse{Txn: &TxnResponse{OpsResult: []EtcdOperationResult{result}}, Revision: revision}}
} }

View File

@ -43,15 +43,15 @@ func TestModelNonDeterministic(t *testing.T) {
{ {
name: "First Range can start from non-empty value and non-zero revision", name: "First Range can start from non-empty value and non-zero revision",
operations: []testOperation{ operations: []testOperation{
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 42)}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 1, 42)},
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 42)}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("1")}}, 1, 42)},
}, },
}, },
{ {
name: "First Range can start from non-zero revision", name: "First Range can start from non-zero revision",
operations: []testOperation{ operations: []testOperation{
{req: rangeRequest("key", true), resp: rangeResponse(nil, 1)}, {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 1, 1)},
{req: rangeRequest("key", true), resp: rangeResponse(nil, 1)}, {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 1, 1)},
}, },
}, },
{ {
@ -92,18 +92,18 @@ func TestModelNonDeterministic(t *testing.T) {
operations: []testOperation{ operations: []testOperation{
{req: putRequest("key1", "1"), resp: putResponse(1)}, {req: putRequest("key1", "1"), resp: putResponse(1)},
{req: putRequest("key2", "2"), resp: putResponse(2)}, {req: putRequest("key2", "2"), resp: putResponse(2)},
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2)}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2, 2)},
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2)}, {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key1"), Value: []byte("1"), ModRevision: 1}, {Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 2, 2)},
}, },
}, },
{ {
name: "Range response should be ordered by key", name: "Range response should be ordered by key",
operations: []testOperation{ operations: []testOperation{
{req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{ {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{
{Key: []byte("key1"), Value: []byte("2"), ModRevision: 3}, {Key: []byte("key1"), Value: []byte("2"), ModRevision: 3},
{Key: []byte("key2"), Value: []byte("1"), ModRevision: 2}, {Key: []byte("key2"), Value: []byte("1"), ModRevision: 2},
{Key: []byte("key3"), Value: []byte("3"), ModRevision: 1}, {Key: []byte("key3"), Value: []byte("3"), ModRevision: 1},
}, 3)}, }, 3, 3)},
}, },
}, },
{ {