Skip to content

Commit

Permalink
Merge pull request #15819 from serathius/robustness-non-deterministic
Browse files Browse the repository at this point in the history
tests/robustness: Split model code into deterministic and non-deterministic
  • Loading branch information
serathius authored May 5, 2023
2 parents 08d25b2 + 92366a5 commit 79eabc1
Show file tree
Hide file tree
Showing 9 changed files with 929 additions and 450 deletions.
2 changes: 1 addition & 1 deletion tests/robustness/linearizability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func runScenario(ctx context.Context, t *testing.T, lg *zap.Logger, clus *e2e.Et
func operationsMaxRevision(operations []porcupine.Operation) int64 {
var maxRevision int64
for _, op := range operations {
revision := op.Output.(model.EtcdResponse).Revision
revision := op.Output.(model.EtcdNonDeterministicResponse).Revision
if revision > maxRevision {
maxRevision = revision
}
Expand Down
140 changes: 140 additions & 0 deletions tests/robustness/model/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2023 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package model

import (
"fmt"
"strings"
)

func describeEtcdNonDeterministicResponse(request EtcdRequest, response EtcdNonDeterministicResponse) string {
if response.Err != nil {
return fmt.Sprintf("err: %q", response.Err)
}
if response.ResultUnknown {
return fmt.Sprintf("unknown, rev: %d", response.Revision)
}
return describeEtcdResponse(request, response.EtcdResponse)
}

func describeEtcdResponse(request EtcdRequest, response EtcdResponse) string {
if request.Type == Txn {
return fmt.Sprintf("%s, rev: %d", describeTxnResponse(request.Txn, response.Txn), response.Revision)
}
if response.Revision == 0 {
return "ok"
}
return fmt.Sprintf("ok, rev: %d", response.Revision)
}

func describeEtcdRequest(request EtcdRequest) string {
switch request.Type {
case Txn:
describeOperations := describeEtcdOperations(request.Txn.Ops)
if len(request.Txn.Conds) != 0 {
return fmt.Sprintf("if(%s).then(%s)", describeEtcdConditions(request.Txn.Conds), describeOperations)
}
return describeOperations
case LeaseGrant:
return fmt.Sprintf("leaseGrant(%d)", request.LeaseGrant.LeaseID)
case LeaseRevoke:
return fmt.Sprintf("leaseRevoke(%d)", request.LeaseRevoke.LeaseID)
case Defragment:
return fmt.Sprintf("defragment()")
default:
return fmt.Sprintf("<! unknown request type: %q !>", request.Type)
}
}

func describeEtcdConditions(conds []EtcdCondition) string {
opsDescription := make([]string, len(conds))
for i := range conds {
opsDescription[i] = fmt.Sprintf("mod_rev(%s)==%d", conds[i].Key, conds[i].ExpectedRevision)
}
return strings.Join(opsDescription, " && ")
}

func describeEtcdOperations(ops []EtcdOperation) string {
opsDescription := make([]string, len(ops))
for i := range ops {
opsDescription[i] = describeEtcdOperation(ops[i])
}
return strings.Join(opsDescription, ", ")
}

func describeTxnResponse(request *TxnRequest, response *TxnResponse) string {
if response.TxnResult {
return fmt.Sprintf("txn failed")
}
respDescription := make([]string, len(response.OpsResult))
for i := range response.OpsResult {
respDescription[i] = describeEtcdOperationResponse(request.Ops[i], response.OpsResult[i])
}
return strings.Join(respDescription, ", ")
}

func describeEtcdOperation(op EtcdOperation) string {
switch op.Type {
case Range:
if op.WithPrefix {
return fmt.Sprintf("range(%q)", op.Key)
}
return fmt.Sprintf("get(%q)", op.Key)
case Put:
if op.LeaseID != 0 {
return fmt.Sprintf("put(%q, %s, %d)", op.Key, describeValueOrHash(op.Value), op.LeaseID)
}
return fmt.Sprintf("put(%q, %s)", op.Key, describeValueOrHash(op.Value))
case Delete:
return fmt.Sprintf("delete(%q)", op.Key)
default:
return fmt.Sprintf("<! unknown op: %q !>", op.Type)
}
}

func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult) string {
switch req.Type {
case Range:
if req.WithPrefix {
kvs := make([]string, len(resp.KVs))
for i, kv := range resp.KVs {
kvs[i] = describeValueOrHash(kv.Value)
}
return fmt.Sprintf("[%s]", strings.Join(kvs, ","))
} else {
if len(resp.KVs) == 0 {
return "nil"
} else {
return describeValueOrHash(resp.KVs[0].Value)
}
}
case Put:
return fmt.Sprintf("ok")
case Delete:
return fmt.Sprintf("deleted: %d", resp.Deleted)
default:
return fmt.Sprintf("<! unknown op: %q !>", req.Type)
}
}

func describeValueOrHash(value ValueOrHash) string {
if value.Hash != 0 {
return fmt.Sprintf("hash: %d", value.Hash)
}
if value.Value == "" {
return "nil"
}
return fmt.Sprintf("%q", value.Value)
}
126 changes: 126 additions & 0 deletions tests/robustness/model/describe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2023 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package model

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"

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

func TestModelDescribe(t *testing.T) {
tcs := []struct {
req EtcdRequest
resp EtcdNonDeterministicResponse
expectDescribe string
}{
{
req: getRequest("key1"),
resp: emptyGetResponse(1),
expectDescribe: `get("key1") -> nil, rev: 1`,
},
{
req: getRequest("key2"),
resp: getResponse("key", "2", 2, 2),
expectDescribe: `get("key2") -> "2", rev: 2`,
},
{
req: getRequest("key2b"),
resp: getResponse("key2b", "01234567890123456789", 2, 2),
expectDescribe: `get("key2b") -> hash: 2945867837, rev: 2`,
},
{
req: putRequest("key3", "3"),
resp: putResponse(3),
expectDescribe: `put("key3", "3") -> ok, rev: 3`,
},
{
req: putWithLeaseRequest("key3b", "3b", 3),
resp: putResponse(3),
expectDescribe: `put("key3b", "3b", 3) -> ok, rev: 3`,
},
{
req: putRequest("key3c", "01234567890123456789"),
resp: putResponse(3),
expectDescribe: `put("key3c", hash: 2945867837) -> ok, rev: 3`,
},
{
req: putRequest("key4", "4"),
resp: failedResponse(errors.New("failed")),
expectDescribe: `put("key4", "4") -> err: "failed"`,
},
{
req: putRequest("key4b", "4b"),
resp: unknownResponse(42),
expectDescribe: `put("key4b", "4b") -> unknown, rev: 42`,
},
{
req: deleteRequest("key5"),
resp: deleteResponse(1, 5),
expectDescribe: `delete("key5") -> deleted: 1, rev: 5`,
},
{
req: deleteRequest("key6"),
resp: failedResponse(errors.New("failed")),
expectDescribe: `delete("key6") -> err: "failed"`,
},
{
req: compareAndSetRequest("key7", 7, "77"),
resp: compareAndSetResponse(false, 7),
expectDescribe: `if(mod_rev(key7)==7).then(put("key7", "77")) -> txn failed, rev: 7`,
},
{
req: compareAndSetRequest("key8", 8, "88"),
resp: compareAndSetResponse(true, 8),
expectDescribe: `if(mod_rev(key8)==8).then(put("key8", "88")) -> ok, rev: 8`,
},
{
req: compareAndSetRequest("key9", 9, "99"),
resp: failedResponse(errors.New("failed")),
expectDescribe: `if(mod_rev(key9)==9).then(put("key9", "99")) -> err: "failed"`,
},
{
req: txnRequest(nil, []EtcdOperation{{Type: Range, Key: "10"}, {Type: Put, Key: "11", Value: ValueOrHash{Value: "111"}}, {Type: Delete, Key: "12"}}),
resp: txnResponse([]EtcdOperationResult{{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`,
},
{
req: defragmentRequest(),
resp: defragmentResponse(10),
expectDescribe: `defragment() -> ok, rev: 10`,
},
{
req: rangeRequest("key11", true),
resp: rangeResponse(nil, 11),
expectDescribe: `range("key11") -> [], rev: 11`,
},
{
req: rangeRequest("key12", true),
resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("12")}}, 12),
expectDescribe: `range("key12") -> ["12"], rev: 12`,
},
{
req: rangeRequest("key13", true),
resp: rangeResponse([]*mvccpb.KeyValue{{Value: []byte("01234567890123456789")}}, 13),
expectDescribe: `range("key13") -> [hash: 2945867837], rev: 13`,
},
}
for _, tc := range tcs {
assert.Equal(t, tc.expectDescribe, NonDeterministicModel.DescribeOperation(tc.req, tc.resp))
}
}
Loading

0 comments on commit 79eabc1

Please sign in to comment.