diff --git a/tests/robustness/model/describe.go b/tests/robustness/model/describe.go index 2d6d6ca07..f00386bbf 100644 --- a/tests/robustness/model/describe.go +++ b/tests/robustness/model/describe.go @@ -89,6 +89,9 @@ func describeEtcdOperation(op EtcdOperation) string { switch op.Type { case Range: 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("get(%q)", op.Key) @@ -112,7 +115,7 @@ func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult) for i, kv := range resp.KVs { kvs[i] = describeValueOrHash(kv.Value) } - return fmt.Sprintf("[%s]", strings.Join(kvs, ",")) + return fmt.Sprintf("[%s], count: %d", strings.Join(kvs, ","), resp.Count) } else { if len(resp.KVs) == 0 { return "nil" diff --git a/tests/robustness/model/describe_test.go b/tests/robustness/model/describe_test.go index 7482e2167..447e41c62 100644 --- a/tests/robustness/model/describe_test.go +++ b/tests/robustness/model/describe_test.go @@ -105,19 +105,24 @@ func TestModelDescribe(t *testing.T) { expectDescribe: `defragment() -> ok, rev: 10`, }, { - req: rangeRequest("key11", true), - resp: rangeResponse(nil, 11), - expectDescribe: `range("key11") -> [], rev: 11`, + req: rangeRequest("key11", true, 0), + resp: rangeResponse(nil, 0, 11), + expectDescribe: `range("key11") -> [], count: 0, rev: 11`, }, { - req: rangeRequest("key12", true), - resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("12")}}, 12), - expectDescribe: `range("key12") -> ["12"], rev: 12`, + req: rangeRequest("key12", true, 0), + resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("12")}}, 2, 12), + expectDescribe: `range("key12") -> ["12"], count: 2, rev: 12`, }, { - req: rangeRequest("key13", true), - resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("01234567890123456789")}}, 13), - expectDescribe: `range("key13") -> [hash: 2945867837], rev: 13`, + req: rangeRequest("key13", true, 0), + resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("01234567890123456789")}}, 1, 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 { diff --git a/tests/robustness/model/deterministic.go b/tests/robustness/model/deterministic.go index 3d13bd4c2..5ed694cce 100644 --- a/tests/robustness/model/deterministic.go +++ b/tests/robustness/model/deterministic.go @@ -146,14 +146,20 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { KVs: []KeyValue{}, } if op.WithPrefix { + var count int64 for k, v := range s.KeyValues { if strings.HasPrefix(k, op.Key) { opResp[i].KVs = append(opResp[i].KVs, KeyValue{Key: k, ValueRevision: v}) + count += 1 } } sort.Slice(opResp[i].KVs, func(j, k int) bool { 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 { value, ok := s.KeyValues[op.Key] if ok { @@ -161,6 +167,7 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { Key: op.Key, ValueRevision: value, }) + opResp[i].Count = 1 } } case Put: @@ -270,6 +277,7 @@ type EtcdOperation struct { Type OperationType Key string WithPrefix bool + Limit int64 Value ValueOrHash LeaseID int64 } @@ -303,6 +311,7 @@ type DefragmentResponse struct{} type EtcdOperationResult struct { KVs []KeyValue + Count int64 Deleted int64 } diff --git a/tests/robustness/model/deterministic_test.go b/tests/robustness/model/deterministic_test.go index 72c6aa04c..d0ecdf3ba 100644 --- a/tests/robustness/model/deterministic_test.go +++ b/tests/robustness/model/deterministic_test.go @@ -40,15 +40,15 @@ func TestModelBase(t *testing.T) { { name: "First Range can start from non-empty value and non-zero revision", operations: []testOperation{ - {req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("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}, + {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", operations: []testOperation{ - {req: rangeRequest("key", true), resp: rangeResponse(nil, 1).EtcdResponse}, - {req: rangeRequest("key", true), resp: rangeResponse(nil, 1).EtcdResponse}, + {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse}, + {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse}, }, }, { @@ -89,18 +89,55 @@ func TestModelBase(t *testing.T) { operations: []testOperation{ {req: putRequest("key1", "1"), resp: putResponse(1).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), 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, 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", 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("key2"), Value: []byte("1"), ModRevision: 2}, {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}, }, }, { diff --git a/tests/robustness/model/history.go b/tests/robustness/model/history.go index 01e2dbf4e..d34e5d08b 100644 --- a/tests/robustness/model/history.go +++ b/tests/robustness/model/history.go @@ -72,9 +72,9 @@ func (h *AppendableHistory) AppendRange(key string, withPrefix bool, start, end } h.successful = append(h.successful, porcupine.Operation{ ClientId: h.id, - Input: rangeRequest(key, withPrefix), + Input: rangeRequest(key, withPrefix, 0), Call: start.Nanoseconds(), - Output: rangeResponse(resp.Kvs, revision), + Output: rangeResponse(resp.Kvs, resp.Count, revision), Return: end.Nanoseconds(), }) } @@ -299,7 +299,8 @@ func toEtcdOperationResult(resp *etcdserverpb.ResponseOp) EtcdOperationResult { } } return EtcdOperationResult{ - KVs: kvs, + KVs: kvs, + Count: getResp.Count, } case resp.GetResponsePut() != nil: return EtcdOperationResult{} @@ -345,22 +346,22 @@ func (h *AppendableHistory) appendFailed(request EtcdRequest, start time.Duratio } func getRequest(key string) EtcdRequest { - return rangeRequest(key, false) + return rangeRequest(key, false, 0) } -func rangeRequest(key string, withPrefix bool) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{Ops: []EtcdOperation{{Type: Range, Key: key, WithPrefix: withPrefix}}}} +func rangeRequest(key string, withPrefix bool, limit int64) EtcdRequest { + return EtcdRequest{Type: Txn, Txn: &TxnRequest{Ops: []EtcdOperation{{Type: Range, Key: key, WithPrefix: withPrefix, Limit: limit}}}} } 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 { - 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))} for i, kv := range kvs { @@ -371,6 +372,7 @@ func rangeResponse(kvs []*mvccpb.KeyValue, revision int64) EtcdNonDeterministicR ModRevision: kv.ModRevision, }, } + result.Count = count } return EtcdNonDeterministicResponse{EtcdResponse: EtcdResponse{Txn: &TxnResponse{OpsResult: []EtcdOperationResult{result}}, Revision: revision}} } diff --git a/tests/robustness/model/non_deterministic_test.go b/tests/robustness/model/non_deterministic_test.go index 4a7183169..6342d0575 100644 --- a/tests/robustness/model/non_deterministic_test.go +++ b/tests/robustness/model/non_deterministic_test.go @@ -43,15 +43,15 @@ func TestModelNonDeterministic(t *testing.T) { { name: "First Range can start from non-empty value and non-zero revision", operations: []testOperation{ - {req: rangeRequest("key", true), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key"), Value: []byte("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)}, + {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", operations: []testOperation{ - {req: rangeRequest("key", true), resp: rangeResponse(nil, 1)}, - {req: rangeRequest("key", true), resp: rangeResponse(nil, 1)}, + {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 1, 1)}, + {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 1, 1)}, }, }, { @@ -92,18 +92,18 @@ func TestModelNonDeterministic(t *testing.T) { operations: []testOperation{ {req: putRequest("key1", "1"), resp: putResponse(1)}, {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), 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, 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", 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("key2"), Value: []byte("1"), ModRevision: 2}, {Key: []byte("key3"), Value: []byte("3"), ModRevision: 1}, - }, 3)}, + }, 3, 3)}, }, }, {