From 2e7cb772b222d87bc47ad23f3fa965ffef8c2a53 Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Tue, 20 Jun 2023 09:12:22 +0200 Subject: [PATCH] tests/robustness: Implement proper range requests Signed-off-by: Marek Siarkowicz --- tests/robustness/model/describe.go | 20 +++++---- tests/robustness/model/describe_test.go | 41 +++++++++++------ tests/robustness/model/deterministic.go | 15 +++---- tests/robustness/model/deterministic_test.go | 20 ++++----- tests/robustness/model/history.go | 44 +++++++++++++++---- .../model/non_deterministic_test.go | 4 +- 6 files changed, 95 insertions(+), 49 deletions(-) diff --git a/tests/robustness/model/describe.go b/tests/robustness/model/describe.go index 39c60005a..2057f94e2 100644 --- a/tests/robustness/model/describe.go +++ b/tests/robustness/model/describe.go @@ -126,14 +126,18 @@ func describeRangeRequest(opts RangeOptions, revision int64) string { 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, opts.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, opts.Key, strings.Join(kwargs, ", ")) } func describeEtcdOperationResponse(op EtcdOperation, resp EtcdOperationResult) string { @@ -149,8 +153,8 @@ func describeEtcdOperationResponse(op EtcdOperation, resp EtcdOperationResult) s } } -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) diff --git a/tests/robustness/model/describe_test.go b/tests/robustness/model/describe_test.go index 3e356692f..262fb9542 100644 --- a/tests/robustness/model/describe_test.go +++ b/tests/robustness/model/describe_test.go @@ -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, Put: PutOptions{Key: "key9b", Value: ValueOrHash{Value: "991"}}}}, []EtcdOperation{{Type: RangeOperation, Range: RangeOptions{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, Put: PutOptions{Key: "key9c", Value: ValueOrHash{Value: "992"}}}}, []EtcdOperation{{Type: RangeOperation, Range: RangeOptions{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, Range: RangeOptions{Key: "10"}}, {Type: PutOperation, Put: PutOptions{Key: "11", Value: ValueOrHash{Value: "111"}}}, {Type: DeleteOperation, Delete: DeleteOptions{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`, }, @@ -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 { diff --git a/tests/robustness/model/deterministic.go b/tests/robustness/model/deterministic.go index a064e5133..55bdcc043 100644 --- a/tests/robustness/model/deterministic.go +++ b/tests/robustness/model/deterministic.go @@ -21,7 +21,6 @@ import ( "hash/fnv" "reflect" "sort" - "strings" "github.com/anishathalye/porcupine" ) @@ -189,10 +188,10 @@ 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, options.Key) { + if k >= options.Start && k < options.End { response.KVs = append(response.KVs, KeyValue{Key: k, ValueRevision: v}) count += 1 } @@ -205,10 +204,10 @@ func (s EtcdState) getRange(options RangeOptions) RangeResponse { } response.Count = count } else { - value, ok := s.KeyValues[options.Key] + value, ok := s.KeyValues[options.Start] if ok { response.KVs = append(response.KVs, KeyValue{ - Key: options.Key, + Key: options.Start, ValueRevision: value, }) response.Count = 1 @@ -256,9 +255,9 @@ type RangeRequest struct { } type RangeOptions struct { - Key string - WithPrefix bool - Limit int64 + Start string + End string + Limit int64 } type PutOptions struct { diff --git a/tests/robustness/model/deterministic_test.go b/tests/robustness/model/deterministic_test.go index b8ae14e67..c41683dc6 100644 --- a/tests/robustness/model/deterministic_test.go +++ b/tests/robustness/model/deterministic_test.go @@ -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)}, }, }, { @@ -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)}, }, @@ -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}, diff --git a/tests/robustness/model/history.go b/tests/robustness/model/history.go index 1408f232e..98f4818ed 100644 --- a/tests/robustness/model/history.go +++ b/tests/robustness/model/history.go @@ -59,9 +59,13 @@ func (h *AppendableHistory) AppendRange(key string, withPrefix bool, revision in if resp != nil && resp.Header != nil { respRevision = resp.Header.Revision } + var keyEnd string + if withPrefix { + keyEnd = prefixEnd(key) + } h.appendSuccessful(porcupine.Operation{ ClientId: h.streamId, - Input: staleRangeRequest(key, withPrefix, 0, revision), + Input: staleRangeRequest(key, keyEnd, 0, revision), Call: start.Nanoseconds(), Output: rangeResponse(resp.Kvs, resp.Count, respRevision), Return: end.Nanoseconds(), @@ -244,7 +248,8 @@ func toEtcdOperation(option clientv3.Op) (op EtcdOperation) { case option.IsGet(): op.Type = RangeOperation op.Range = RangeOptions{ - Key: string(option.KeyBytes()), + Start: string(option.KeyBytes()), + End: string(option.RangeBytes()), } case option.IsPut(): op.Type = PutOperation @@ -342,19 +347,42 @@ func (h *AppendableHistory) appendFailed(request EtcdRequest, call int64, err er } func getRequest(key string) EtcdRequest { - return rangeRequest(key, false, 0) + return rangeRequest(key, "", 0) } func staleGetRequest(key string, revision int64) EtcdRequest { - return staleRangeRequest(key, false, 0, revision) + return staleRangeRequest(key, "", 0, revision) } -func rangeRequest(key string, withPrefix bool, limit int64) EtcdRequest { - return staleRangeRequest(key, withPrefix, limit, 0) +func rangeRequest(start, end string, limit int64) EtcdRequest { + return staleRangeRequest(start, end, limit, 0) } -func staleRangeRequest(key string, withPrefix bool, limit, revision int64) EtcdRequest { - return EtcdRequest{Type: Range, Range: &RangeRequest{RangeOptions: RangeOptions{Key: key, WithPrefix: withPrefix, Limit: limit}, Revision: revision}} +func listRequest(key string, limit int64) EtcdRequest { + return staleListRequest(key, limit, 0) +} + +func staleListRequest(key string, limit, revision int64) EtcdRequest { + return staleRangeRequest(key, prefixEnd(key), limit, revision) +} + +// prefixEnd gets the range end of the prefix. +// Notice: Keep in sync with /client/v3/op.go getPrefix function. +func prefixEnd(key string) string { + end := make([]byte, len(key)) + copy(end, key) + for i := len(end) - 1; i >= 0; i-- { + if end[i] < 0xff { + end[i] = end[i] + 1 + end = end[:i+1] + return string(end) + } + } + return "\x00" +} + +func staleRangeRequest(start, end string, limit, revision int64) EtcdRequest { + return EtcdRequest{Type: Range, Range: &RangeRequest{RangeOptions: RangeOptions{Start: start, End: end, Limit: limit}, Revision: revision}} } func emptyGetResponse(revision int64) MaybeEtcdResponse { diff --git a/tests/robustness/model/non_deterministic_test.go b/tests/robustness/model/non_deterministic_test.go index 014d4b39d..da771add8 100644 --- a/tests/robustness/model/non_deterministic_test.go +++ b/tests/robustness/model/non_deterministic_test.go @@ -32,7 +32,7 @@ func TestModelNonDeterministic(t *testing.T) { operations: []testOperation{ {req: putRequest("key1", "1"), resp: failedResponse(errors.New("failed"))}, {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: 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)}, }, }, { @@ -40,7 +40,7 @@ func TestModelNonDeterministic(t *testing.T) { operations: []testOperation{ {req: putRequest("key1", "1"), resp: failedResponse(errors.New("failed"))}, {req: putRequest("key2", "2"), resp: putResponse(2)}, - {req: rangeRequest("key", true, 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 1, 2)}, + {req: listRequest("key", 0), resp: rangeResponse([]*mvccpb.KeyValue{{Key: []byte("key2"), Value: []byte("2"), ModRevision: 2}}, 1, 2)}, }, }, {