From 112aad1ea74040d010f88a258e36bd519dfbc2af Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Sat, 13 May 2023 14:04:03 +0200 Subject: [PATCH] tests/robustness: Unify model test cases Signed-off-by: Marek Siarkowicz --- tests/robustness/model/deterministic_test.go | 727 +++++++++--------- .../model/non_deterministic_test.go | 438 ++--------- 2 files changed, 440 insertions(+), 725 deletions(-) diff --git a/tests/robustness/model/deterministic_test.go b/tests/robustness/model/deterministic_test.go index f2b11f5f4..673ab2e97 100644 --- a/tests/robustness/model/deterministic_test.go +++ b/tests/robustness/model/deterministic_test.go @@ -20,372 +20,16 @@ import ( "go.etcd.io/etcd/api/v3/mvccpb" ) -func TestModelBase(t *testing.T) { - type testOperation struct { - req EtcdRequest - resp EtcdResponse - failure bool - } - tcs := []struct { - name string - operations []testOperation - }{ - { - name: "First Get can start from non-empty value and non-zero revision", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 42, 42).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "1", 42, 42).EtcdResponse}, - }, - }, - { - name: "First Range can start from non-empty value and non-zero revision", - operations: []testOperation{ - {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, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse}, - {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse}, - }, - }, - { - name: "First Put can start from non-zero revision", - operations: []testOperation{ - {req: putRequest("key", "1"), resp: putResponse(42).EtcdResponse}, - }, - }, - { - name: "First delete can start from non-zero revision", - operations: []testOperation{ - {req: deleteRequest("key"), resp: deleteResponse(0, 42).EtcdResponse}, - }, - }, - { - name: "First Txn can start from non-zero revision", - operations: []testOperation{ - {req: compareRevisionAndPutRequest("key", 0, "42"), resp: compareRevisionAndPutResponse(false, 42).EtcdResponse}, - }, - }, - { - name: "Get response data should match put", - operations: []testOperation{ - {req: putRequest("key1", "11"), resp: putResponse(1).EtcdResponse}, - {req: putRequest("key2", "12"), resp: putResponse(2).EtcdResponse}, - {req: getRequest("key1"), resp: getResponse("key1", "11", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key1"), resp: getResponse("key1", "12", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key1"), resp: getResponse("key1", "12", 2, 2).EtcdResponse, failure: true}, - {req: getRequest("key1"), resp: getResponse("key1", "11", 1, 2).EtcdResponse}, - {req: getRequest("key2"), resp: getResponse("key2", "11", 2, 2).EtcdResponse, failure: true}, - {req: getRequest("key2"), resp: getResponse("key2", "12", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key2"), resp: getResponse("key2", "11", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key2"), resp: getResponse("key2", "12", 2, 2).EtcdResponse}, - }, - }, - { - name: "Range response data should match put", - operations: []testOperation{ - {req: putRequest("key1", "1"), resp: putResponse(1).EtcdResponse}, - {req: putRequest("key2", "2"), resp: putResponse(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, 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).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}, - }, - }, - { - name: "Range response data should match large put", - operations: []testOperation{ - {req: putRequest("key", "012345678901234567890"), resp: putResponse(1).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "123456789012345678901", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "012345678901234567890", 1, 1).EtcdResponse}, - {req: putRequest("key", "123456789012345678901"), resp: putResponse(2).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "123456789012345678901", 2, 2).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "012345678901234567890", 2, 2).EtcdResponse, failure: true}, - }, - }, - { - name: "Put must increase revision by 1", - operations: []testOperation{ - {req: getRequest("key"), resp: emptyGetResponse(1).EtcdResponse}, - {req: putRequest("key", "1"), resp: putResponse(1).EtcdResponse, failure: true}, - {req: putRequest("key", "1"), resp: putResponse(3).EtcdResponse, failure: true}, - {req: putRequest("key", "1"), resp: putResponse(2).EtcdResponse}, - }, - }, - { - name: "Delete only increases revision on success", - operations: []testOperation{ - {req: putRequest("key1", "11"), resp: putResponse(1).EtcdResponse}, - {req: putRequest("key2", "12"), resp: putResponse(2).EtcdResponse}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 2).EtcdResponse, failure: true}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 3).EtcdResponse}, - {req: deleteRequest("key1"), resp: deleteResponse(0, 4).EtcdResponse, failure: true}, - {req: deleteRequest("key1"), resp: deleteResponse(0, 3).EtcdResponse}, - }, - }, - { - name: "Delete not existing key", - operations: []testOperation{ - {req: getRequest("key"), resp: emptyGetResponse(1).EtcdResponse}, - {req: deleteRequest("key"), resp: deleteResponse(1, 2).EtcdResponse, failure: true}, - {req: deleteRequest("key"), resp: deleteResponse(0, 1).EtcdResponse}, - }, - }, - { - name: "Delete clears value", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse}, - {req: deleteRequest("key"), resp: deleteResponse(1, 2).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 2, 2).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 2).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: emptyGetResponse(2).EtcdResponse}, - }, - }, - { - name: "Txn executes onSuccess if revision matches expected", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(true, 1).EtcdResponse, failure: true}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(false, 2).EtcdResponse, failure: true}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(false, 1).EtcdResponse, failure: true}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(true, 2).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 2).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 2, 2).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 1, 1).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2).EtcdResponse}, - }, - }, - { - name: "Txn can expect on key not existing", - operations: []testOperation{ - {req: getRequest("key1"), resp: emptyGetResponse(1).EtcdResponse}, - {req: compareRevisionAndPutRequest("key1", 0, "2"), resp: compareRevisionAndPutResponse(true, 2).EtcdResponse}, - {req: compareRevisionAndPutRequest("key1", 0, "3"), resp: compareRevisionAndPutResponse(true, 3).EtcdResponse, failure: true}, - {req: txnRequestSingleOperation(compareRevision("key1", 0), putOperation("key1", "4"), putOperation("key1", "5")), resp: txnPutResponse(false, 3).EtcdResponse}, - {req: getRequest("key1"), resp: getResponse("key1", "5", 3, 3).EtcdResponse}, - {req: compareRevisionAndPutRequest("key2", 0, "6"), resp: compareRevisionAndPutResponse(true, 4).EtcdResponse}, - }, - }, - { - name: "Txn executes onFailure if revision doesn't match expected", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse}, - {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnPutResponse(false, 2).EtcdResponse, failure: true}, - {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnEmptyResponse(false, 2).EtcdResponse, failure: true}, - {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnEmptyResponse(true, 2).EtcdResponse, failure: true}, - {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnPutResponse(true, 1).EtcdResponse, failure: true}, - {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnEmptyResponse(true, 1).EtcdResponse}, - {req: txnRequestSingleOperation(compareRevision("key", 2), nil, putOperation("key", "2")), resp: txnPutResponse(false, 2).EtcdResponse}, - }, - }, - { - name: "Put with valid lease id should succeed. Put with invalid lease id should fail", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, - {req: putWithLeaseRequest("key", "3", 2), resp: putResponse(3).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2).EtcdResponse}, - }, - }, - { - name: "Put with valid lease id should succeed. Put with expired lease id should fail", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, - {req: putWithLeaseRequest("key", "4", 1), resp: putResponse(4).EtcdResponse, failure: true}, - {req: getRequest("key"), resp: emptyGetResponse(3).EtcdResponse}, - }, - }, - { - name: "Revoke should increment the revision", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, - {req: getRequest("key"), resp: emptyGetResponse(3).EtcdResponse}, - }, - }, - { - name: "Put following a PutWithLease will detach the key from the lease", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, - {req: putRequest("key", "3"), resp: putResponse(3).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "3", 3, 3).EtcdResponse}, - }, - }, - { - name: "Change lease. Revoking older lease should not increment revision", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: leaseGrantRequest(2), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, - {req: putWithLeaseRequest("key", "3", 2), resp: putResponse(3).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "3", 3, 3).EtcdResponse}, - {req: leaseRevokeRequest(2), resp: leaseRevokeResponse(4).EtcdResponse}, - {req: getRequest("key"), resp: emptyGetResponse(4).EtcdResponse}, - }, - }, - { - name: "Update key with same lease", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, - {req: putWithLeaseRequest("key", "3", 1), resp: putResponse(3).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "3", 3, 3).EtcdResponse}, - }, - }, - { - name: "Deleting a leased key - revoke should not increment revision", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, - {req: deleteRequest("key"), resp: deleteResponse(1, 3).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(4).EtcdResponse, failure: true}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, - }, - }, - { - name: "Lease a few keys - revoke should increment revision only once", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2).EtcdResponse}, - {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3).EtcdResponse}, - {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4).EtcdResponse}, - {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(6).EtcdResponse}, - }, - }, - { - name: "Lease some keys then delete some of them. Revoke should increment revision since some keys were still leased", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2).EtcdResponse}, - {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3).EtcdResponse}, - {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4).EtcdResponse}, - {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5).EtcdResponse}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 6).EtcdResponse}, - {req: deleteRequest("key3"), resp: deleteResponse(1, 7).EtcdResponse}, - {req: deleteRequest("key4"), resp: deleteResponse(1, 8).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(9).EtcdResponse}, - {req: deleteRequest("key2"), resp: deleteResponse(0, 9).EtcdResponse}, - {req: getRequest("key1"), resp: emptyGetResponse(9).EtcdResponse}, - {req: getRequest("key2"), resp: emptyGetResponse(9).EtcdResponse}, - {req: getRequest("key3"), resp: emptyGetResponse(9).EtcdResponse}, - {req: getRequest("key4"), resp: emptyGetResponse(9).EtcdResponse}, - }, - }, - { - name: "Lease some keys then delete all of them. Revoke should not increment", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2).EtcdResponse}, - {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3).EtcdResponse}, - {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4).EtcdResponse}, - {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5).EtcdResponse}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 6).EtcdResponse}, - {req: deleteRequest("key2"), resp: deleteResponse(1, 7).EtcdResponse}, - {req: deleteRequest("key3"), resp: deleteResponse(1, 8).EtcdResponse}, - {req: deleteRequest("key4"), resp: deleteResponse(1, 9).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(9).EtcdResponse}, - }, - }, - { - name: "All request types", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "1", 1), resp: putResponse(2).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, - {req: putRequest("key", "4"), resp: putResponse(4).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "4", 4, 4).EtcdResponse}, - {req: compareRevisionAndPutRequest("key", 4, "5"), resp: compareRevisionAndPutResponse(true, 5).EtcdResponse}, - {req: deleteRequest("key"), resp: deleteResponse(1, 6).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(6).EtcdResponse}, - }, - }, - { - name: "Defragment success between all other request types", - operations: []testOperation{ - {req: defragmentRequest(), resp: defragmentResponse(1).EtcdResponse}, - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(1).EtcdResponse}, - {req: putWithLeaseRequest("key", "1", 1), resp: putResponse(2).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(2).EtcdResponse}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(3).EtcdResponse}, - {req: putRequest("key", "4"), resp: putResponse(4).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(4).EtcdResponse}, - {req: getRequest("key"), resp: getResponse("key", "4", 4, 4).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(4).EtcdResponse}, - {req: compareRevisionAndPutRequest("key", 4, "5"), resp: compareRevisionAndPutResponse(true, 5).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(5).EtcdResponse}, - {req: deleteRequest("key"), resp: deleteResponse(1, 6).EtcdResponse}, - {req: defragmentRequest(), resp: defragmentResponse(6).EtcdResponse}, - }, - }, - } - for _, tc := range tcs { +func TestModelDeterministic(t *testing.T) { + for _, tc := range deterministicModelTestScenarios { + tc := tc t.Run(tc.name, func(t *testing.T) { state := DeterministicModel.Init() for _, op := range tc.operations { t.Logf("state: %v", state) ok, newState := DeterministicModel.Step(state, op.req, op.resp) - if op.failure == ok { - t.Errorf("Unexpected operation result, expect: %v, got: %v, operation: %s", !op.failure, ok, DeterministicModel.DescribeOperation(op.req, op.resp)) + if op.expectFailure == ok { + t.Errorf("Unexpected operation result, expect: %v, got: %v, operation: %s", !op.expectFailure, ok, DeterministicModel.DescribeOperation(op.req, op.resp)) break } if ok { @@ -395,3 +39,364 @@ func TestModelBase(t *testing.T) { }) } } + +type deterministicModelTest struct { + name string + operations []deterministicOperation +} + +type deterministicOperation struct { + req EtcdRequest + resp EtcdResponse + expectFailure bool +} + +var deterministicModelTestScenarios = []deterministicModelTest{ + { + name: "First Get can start from non-empty value and non-zero revision", + operations: []deterministicOperation{ + {req: getRequest("key"), resp: getResponse("key", "1", 42, 42).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "1", 42, 42).EtcdResponse}, + }, + }, + { + name: "First Range can start from non-empty value and non-zero revision", + operations: []deterministicOperation{ + {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: []deterministicOperation{ + {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse}, + {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 0, 1).EtcdResponse}, + }, + }, + { + name: "First Put can start from non-zero revision", + operations: []deterministicOperation{ + {req: putRequest("key", "1"), resp: putResponse(42).EtcdResponse}, + }, + }, + { + name: "First delete can start from non-zero revision", + operations: []deterministicOperation{ + {req: deleteRequest("key"), resp: deleteResponse(0, 42).EtcdResponse}, + }, + }, + { + name: "First Txn can start from non-zero revision", + operations: []deterministicOperation{ + {req: compareRevisionAndPutRequest("key", 0, "42"), resp: compareRevisionAndPutResponse(false, 42).EtcdResponse}, + }, + }, + { + name: "Get response data should match put", + operations: []deterministicOperation{ + {req: putRequest("key1", "11"), resp: putResponse(1).EtcdResponse}, + {req: putRequest("key2", "12"), resp: putResponse(2).EtcdResponse}, + {req: getRequest("key1"), resp: getResponse("key1", "11", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key1"), resp: getResponse("key1", "12", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key1"), resp: getResponse("key1", "12", 2, 2).EtcdResponse, expectFailure: true}, + {req: getRequest("key1"), resp: getResponse("key1", "11", 1, 2).EtcdResponse}, + {req: getRequest("key2"), resp: getResponse("key2", "11", 2, 2).EtcdResponse, expectFailure: true}, + {req: getRequest("key2"), resp: getResponse("key2", "12", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key2"), resp: getResponse("key2", "11", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key2"), resp: getResponse("key2", "12", 2, 2).EtcdResponse}, + }, + }, + { + name: "Range response data should match put", + operations: []deterministicOperation{ + {req: putRequest("key1", "1"), resp: putResponse(1).EtcdResponse}, + {req: putRequest("key2", "2"), resp: putResponse(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: []deterministicOperation{ + {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: []deterministicOperation{ + {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).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, expectFailure: 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, expectFailure: true}, + }, + }, + { + name: "Range response data should match large put", + operations: []deterministicOperation{ + {req: putRequest("key", "012345678901234567890"), resp: putResponse(1).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "123456789012345678901", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "012345678901234567890", 1, 1).EtcdResponse}, + {req: putRequest("key", "123456789012345678901"), resp: putResponse(2).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "123456789012345678901", 2, 2).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "012345678901234567890", 2, 2).EtcdResponse, expectFailure: true}, + }, + }, + { + name: "Put must increase revision by 1", + operations: []deterministicOperation{ + {req: getRequest("key"), resp: emptyGetResponse(1).EtcdResponse}, + {req: putRequest("key", "1"), resp: putResponse(1).EtcdResponse, expectFailure: true}, + {req: putRequest("key", "1"), resp: putResponse(3).EtcdResponse, expectFailure: true}, + {req: putRequest("key", "1"), resp: putResponse(2).EtcdResponse}, + }, + }, + { + name: "Delete only increases revision on success", + operations: []deterministicOperation{ + {req: putRequest("key1", "11"), resp: putResponse(1).EtcdResponse}, + {req: putRequest("key2", "12"), resp: putResponse(2).EtcdResponse}, + {req: deleteRequest("key1"), resp: deleteResponse(1, 2).EtcdResponse, expectFailure: true}, + {req: deleteRequest("key1"), resp: deleteResponse(1, 3).EtcdResponse}, + {req: deleteRequest("key1"), resp: deleteResponse(0, 4).EtcdResponse, expectFailure: true}, + {req: deleteRequest("key1"), resp: deleteResponse(0, 3).EtcdResponse}, + }, + }, + { + name: "Delete not existing key", + operations: []deterministicOperation{ + {req: getRequest("key"), resp: emptyGetResponse(1).EtcdResponse}, + {req: deleteRequest("key"), resp: deleteResponse(1, 2).EtcdResponse, expectFailure: true}, + {req: deleteRequest("key"), resp: deleteResponse(0, 1).EtcdResponse}, + }, + }, + { + name: "Delete clears value", + operations: []deterministicOperation{ + {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse}, + {req: deleteRequest("key"), resp: deleteResponse(1, 2).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "1", 2, 2).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "1", 1, 2).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: emptyGetResponse(2).EtcdResponse}, + }, + }, + { + name: "Txn executes onSuccess if revision matches expected", + operations: []deterministicOperation{ + {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse}, + {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(true, 1).EtcdResponse, expectFailure: true}, + {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(false, 2).EtcdResponse, expectFailure: true}, + {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(false, 1).EtcdResponse, expectFailure: true}, + {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(true, 2).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "1", 1, 2).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "1", 2, 2).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 1, 1).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 2, 2).EtcdResponse}, + }, + }, + { + name: "Txn can expect on key not existing", + operations: []deterministicOperation{ + {req: getRequest("key1"), resp: emptyGetResponse(1).EtcdResponse}, + {req: compareRevisionAndPutRequest("key1", 0, "2"), resp: compareRevisionAndPutResponse(true, 2).EtcdResponse}, + {req: compareRevisionAndPutRequest("key1", 0, "3"), resp: compareRevisionAndPutResponse(true, 3).EtcdResponse, expectFailure: true}, + {req: txnRequestSingleOperation(compareRevision("key1", 0), putOperation("key1", "4"), putOperation("key1", "5")), resp: txnPutResponse(false, 3).EtcdResponse}, + {req: getRequest("key1"), resp: getResponse("key1", "5", 3, 3).EtcdResponse}, + {req: compareRevisionAndPutRequest("key2", 0, "6"), resp: compareRevisionAndPutResponse(true, 4).EtcdResponse}, + }, + }, + { + name: "Txn executes onFailure if revision doesn't match expected", + operations: []deterministicOperation{ + {req: getRequest("key"), resp: getResponse("key", "1", 1, 1).EtcdResponse}, + {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnPutResponse(false, 2).EtcdResponse, expectFailure: true}, + {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnEmptyResponse(false, 2).EtcdResponse, expectFailure: true}, + {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnEmptyResponse(true, 2).EtcdResponse, expectFailure: true}, + {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnPutResponse(true, 1).EtcdResponse, expectFailure: true}, + {req: txnRequestSingleOperation(compareRevision("key", 1), nil, putOperation("key", "2")), resp: txnEmptyResponse(true, 1).EtcdResponse}, + {req: txnRequestSingleOperation(compareRevision("key", 2), nil, putOperation("key", "2")), resp: txnPutResponse(false, 2).EtcdResponse}, + }, + }, + { + name: "Put with valid lease id should succeed. Put with invalid lease id should fail", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, + {req: putWithLeaseRequest("key", "3", 2), resp: putResponse(3).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 2, 2).EtcdResponse}, + }, + }, + { + name: "Put with valid lease id should succeed. Put with expired lease id should fail", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "2", 2, 2).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, + {req: putWithLeaseRequest("key", "4", 1), resp: putResponse(4).EtcdResponse, expectFailure: true}, + {req: getRequest("key"), resp: emptyGetResponse(3).EtcdResponse}, + }, + }, + { + name: "Revoke should increment the revision", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, + {req: getRequest("key"), resp: emptyGetResponse(3).EtcdResponse}, + }, + }, + { + name: "Put following a PutWithLease will detach the key from the lease", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, + {req: putRequest("key", "3"), resp: putResponse(3).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "3", 3, 3).EtcdResponse}, + }, + }, + { + name: "Change lease. Revoking older lease should not increment revision", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: leaseGrantRequest(2), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, + {req: putWithLeaseRequest("key", "3", 2), resp: putResponse(3).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "3", 3, 3).EtcdResponse}, + {req: leaseRevokeRequest(2), resp: leaseRevokeResponse(4).EtcdResponse}, + {req: getRequest("key"), resp: emptyGetResponse(4).EtcdResponse}, + }, + }, + { + name: "Update key with same lease", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, + {req: putWithLeaseRequest("key", "3", 1), resp: putResponse(3).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "3", 3, 3).EtcdResponse}, + }, + }, + { + name: "Deleting a leased key - revoke should not increment revision", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2).EtcdResponse}, + {req: deleteRequest("key"), resp: deleteResponse(1, 3).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(4).EtcdResponse, expectFailure: true}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, + }, + }, + { + name: "Lease a few keys - revoke should increment revision only once", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2).EtcdResponse}, + {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3).EtcdResponse}, + {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4).EtcdResponse}, + {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(6).EtcdResponse}, + }, + }, + { + name: "Lease some keys then delete some of them. Revoke should increment revision since some keys were still leased", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2).EtcdResponse}, + {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3).EtcdResponse}, + {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4).EtcdResponse}, + {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5).EtcdResponse}, + {req: deleteRequest("key1"), resp: deleteResponse(1, 6).EtcdResponse}, + {req: deleteRequest("key3"), resp: deleteResponse(1, 7).EtcdResponse}, + {req: deleteRequest("key4"), resp: deleteResponse(1, 8).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(9).EtcdResponse}, + {req: deleteRequest("key2"), resp: deleteResponse(0, 9).EtcdResponse}, + {req: getRequest("key1"), resp: emptyGetResponse(9).EtcdResponse}, + {req: getRequest("key2"), resp: emptyGetResponse(9).EtcdResponse}, + {req: getRequest("key3"), resp: emptyGetResponse(9).EtcdResponse}, + {req: getRequest("key4"), resp: emptyGetResponse(9).EtcdResponse}, + }, + }, + { + name: "Lease some keys then delete all of them. Revoke should not increment", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2).EtcdResponse}, + {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3).EtcdResponse}, + {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4).EtcdResponse}, + {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5).EtcdResponse}, + {req: deleteRequest("key1"), resp: deleteResponse(1, 6).EtcdResponse}, + {req: deleteRequest("key2"), resp: deleteResponse(1, 7).EtcdResponse}, + {req: deleteRequest("key3"), resp: deleteResponse(1, 8).EtcdResponse}, + {req: deleteRequest("key4"), resp: deleteResponse(1, 9).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(9).EtcdResponse}, + }, + }, + { + name: "All request types", + operations: []deterministicOperation{ + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "1", 1), resp: putResponse(2).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, + {req: putRequest("key", "4"), resp: putResponse(4).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "4", 4, 4).EtcdResponse}, + {req: compareRevisionAndPutRequest("key", 4, "5"), resp: compareRevisionAndPutResponse(true, 5).EtcdResponse}, + {req: deleteRequest("key"), resp: deleteResponse(1, 6).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(6).EtcdResponse}, + }, + }, + { + name: "Defragment success between all other request types", + operations: []deterministicOperation{ + {req: defragmentRequest(), resp: defragmentResponse(1).EtcdResponse}, + {req: leaseGrantRequest(1), resp: leaseGrantResponse(1).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(1).EtcdResponse}, + {req: putWithLeaseRequest("key", "1", 1), resp: putResponse(2).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(2).EtcdResponse}, + {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(3).EtcdResponse}, + {req: putRequest("key", "4"), resp: putResponse(4).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(4).EtcdResponse}, + {req: getRequest("key"), resp: getResponse("key", "4", 4, 4).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(4).EtcdResponse}, + {req: compareRevisionAndPutRequest("key", 4, "5"), resp: compareRevisionAndPutResponse(true, 5).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(5).EtcdResponse}, + {req: deleteRequest("key"), resp: deleteResponse(1, 6).EtcdResponse}, + {req: defragmentRequest(), resp: defragmentResponse(6).EtcdResponse}, + }, + }, +} diff --git a/tests/robustness/model/non_deterministic_test.go b/tests/robustness/model/non_deterministic_test.go index 6342d0575..e51fd3625 100644 --- a/tests/robustness/model/non_deterministic_test.go +++ b/tests/robustness/model/non_deterministic_test.go @@ -19,127 +19,29 @@ import ( "testing" "github.com/stretchr/testify/assert" - - "go.etcd.io/etcd/api/v3/mvccpb" ) func TestModelNonDeterministic(t *testing.T) { - type testOperation struct { - req EtcdRequest - resp EtcdNonDeterministicResponse - failure bool + nonDeterministicTestScenarios := []nonDeterministicModelTest{} + for _, tc := range deterministicModelTestScenarios { + nonDeterministicTestScenarios = append(nonDeterministicTestScenarios, toNonDeterministicTest(tc)) } - tcs := []struct { - name string - operations []testOperation - }{ - { - name: "First Get can start from non-empty value and non-zero revision", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 42, 42)}, - {req: getRequest("key"), resp: getResponse("key", "1", 42, 42)}, - }, - }, - { - name: "First Range can start from non-empty value and non-zero revision", - operations: []testOperation{ - {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, 0), resp: rangeResponse(nil, 1, 1)}, - {req: rangeRequest("key", true, 0), resp: rangeResponse(nil, 1, 1)}, - }, - }, - { - name: "First Put can start from non-zero revision", - operations: []testOperation{ - {req: putRequest("key", "1"), resp: putResponse(42)}, - }, - }, - { - name: "First delete can start from non-zero revision", - operations: []testOperation{ - {req: deleteRequest("key"), resp: deleteResponse(0, 42)}, - }, - }, - { - name: "First Txn can start from non-zero revision", - operations: []testOperation{ - {req: compareRevisionAndPutRequest("key", 0, "42"), resp: compareRevisionAndPutResponse(false, 42)}, - }, - }, - { - name: "Get response data should match put", - operations: []testOperation{ - {req: putRequest("key1", "11"), resp: putResponse(1)}, - {req: putRequest("key2", "12"), resp: putResponse(2)}, - {req: getRequest("key1"), resp: getResponse("key1", "11", 1, 1), failure: true}, - {req: getRequest("key1"), resp: getResponse("key1", "12", 1, 1), failure: true}, - {req: getRequest("key1"), resp: getResponse("key1", "12", 2, 2), failure: true}, - {req: getRequest("key1"), resp: getResponse("key1", "11", 1, 2)}, - {req: getRequest("key2"), resp: getResponse("key2", "11", 2, 2), failure: true}, - {req: getRequest("key2"), resp: getResponse("key2", "12", 1, 1), failure: true}, - {req: getRequest("key2"), resp: getResponse("key2", "11", 1, 1), failure: true}, - {req: getRequest("key2"), resp: getResponse("key2", "12", 2, 2)}, - }, - }, - { - name: "Range response data should match put", - operations: []testOperation{ - {req: putRequest("key1", "1"), resp: putResponse(1)}, - {req: putRequest("key2", "2"), resp: putResponse(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, 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)}, - }, - }, - { - name: "Range response data should match large put", - operations: []testOperation{ - {req: putRequest("key", "012345678901234567890"), resp: putResponse(1)}, - {req: getRequest("key"), resp: getResponse("key", "123456789012345678901", 1, 1), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "012345678901234567890", 1, 1)}, - {req: putRequest("key", "123456789012345678901"), resp: putResponse(2)}, - {req: getRequest("key"), resp: getResponse("key", "123456789012345678901", 2, 2)}, - {req: getRequest("key"), resp: getResponse("key", "012345678901234567890", 2, 2), failure: true}, - }, - }, - { - name: "Put must increase revision by 1", - operations: []testOperation{ - {req: getRequest("key"), resp: emptyGetResponse(1)}, - {req: putRequest("key", "1"), resp: putResponse(1), failure: true}, - {req: putRequest("key", "1"), resp: putResponse(3), failure: true}, - {req: putRequest("key", "1"), resp: putResponse(2)}, - }, - }, + + nonDeterministicTestScenarios = append(nonDeterministicTestScenarios, []nonDeterministicModelTest{ { name: "Put can fail and be lost before get", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: putRequest("key", "1"), resp: putResponse(1)}, {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, - {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 1, 2), failure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "1", 1, 2), expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 1, 2), expectFailure: true}, }, }, { name: "Put can fail and be lost before put", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: getRequest("key"), resp: emptyGetResponse(1)}, {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, {req: putRequest("key", "3"), resp: putResponse(2)}, @@ -147,7 +49,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Put can fail and be lost before delete", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: deleteRequest("key"), resp: deleteResponse(0, 1)}, {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, {req: deleteRequest("key"), resp: deleteResponse(0, 1)}, @@ -155,7 +57,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Put can fail and be lost before txn", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // Txn failure {req: getRequest("key"), resp: emptyGetResponse(1)}, {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, @@ -168,17 +70,17 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Put can fail and be lost before txn success", - operations: []testOperation{}, + operations: []nonDeterministicOperation{}, }, { name: "Put can fail but be persisted and increase revision before get", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: putRequest("key", "1"), resp: putResponse(1)}, {req: putRequest("key", "2"), resp: failedResponse(errors.New("failed"))}, - {req: getRequest("key"), resp: getResponse("key", "3", 2, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "3", 1, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), failure: true}, + {req: getRequest("key"), resp: getResponse("key", "3", 2, 2), expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "3", 1, 2), expectFailure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), expectFailure: true}, {req: getRequest("key"), resp: getResponse("key", "2", 2, 2)}, // Two failed request, two persisted. {req: putRequest("key", "3"), resp: failedResponse(errors.New("failed"))}, @@ -188,12 +90,12 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Put can fail but be persisted and increase revision before delete", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: deleteRequest("key"), resp: deleteResponse(0, 1)}, {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, - {req: deleteRequest("key"), resp: deleteResponse(1, 1), failure: true}, - {req: deleteRequest("key"), resp: deleteResponse(1, 2), failure: true}, + {req: deleteRequest("key"), resp: deleteResponse(1, 1), expectFailure: true}, + {req: deleteRequest("key"), resp: deleteResponse(1, 2), expectFailure: true}, {req: deleteRequest("key"), resp: deleteResponse(1, 3)}, // Two failed request, two persisted. {req: putRequest("key", "4"), resp: putResponse(4)}, @@ -209,11 +111,11 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Put can fail but be persisted before txn", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // Txn success {req: getRequest("key"), resp: emptyGetResponse(1)}, {req: putRequest("key", "2"), resp: failedResponse(errors.New("failed"))}, - {req: compareRevisionAndPutRequest("key", 2, ""), resp: compareRevisionAndPutResponse(true, 2), failure: true}, + {req: compareRevisionAndPutRequest("key", 2, ""), resp: compareRevisionAndPutResponse(true, 2), expectFailure: true}, {req: compareRevisionAndPutRequest("key", 2, ""), resp: compareRevisionAndPutResponse(true, 3)}, // Txn failure {req: putRequest("key", "4"), resp: putResponse(4)}, @@ -222,59 +124,29 @@ func TestModelNonDeterministic(t *testing.T) { {req: getRequest("key"), resp: getResponse("key", "5", 5, 5)}, }, }, - { - name: "Delete only increases revision on success", - operations: []testOperation{ - {req: putRequest("key1", "11"), resp: putResponse(1)}, - {req: putRequest("key2", "12"), resp: putResponse(2)}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 2), failure: true}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 3)}, - {req: deleteRequest("key1"), resp: deleteResponse(0, 4), failure: true}, - {req: deleteRequest("key1"), resp: deleteResponse(0, 3)}, - }, - }, - { - name: "Delete not existing key", - operations: []testOperation{ - {req: getRequest("key"), resp: emptyGetResponse(1)}, - {req: deleteRequest("key"), resp: deleteResponse(1, 2), failure: true}, - {req: deleteRequest("key"), resp: deleteResponse(0, 1)}, - }, - }, - { - name: "Delete clears value", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, - {req: deleteRequest("key"), resp: deleteResponse(1, 2)}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 2, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 2), failure: true}, - {req: getRequest("key"), resp: emptyGetResponse(2)}, - }, - }, { name: "Delete can fail and be lost before get", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: putRequest("key", "1"), resp: putResponse(1)}, {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, - {req: getRequest("key"), resp: emptyGetResponse(2), failure: true}, - {req: getRequest("key"), resp: emptyGetResponse(2), failure: true}, - {req: getRequest("key"), resp: emptyGetResponse(1), failure: true}, + {req: getRequest("key"), resp: emptyGetResponse(2), expectFailure: true}, + {req: getRequest("key"), resp: emptyGetResponse(2), expectFailure: true}, + {req: getRequest("key"), resp: emptyGetResponse(1), expectFailure: true}, }, }, { name: "Delete can fail and be lost before delete", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: putRequest("key", "1"), resp: putResponse(1)}, {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, - {req: deleteRequest("key"), resp: deleteResponse(1, 1), failure: true}, + {req: deleteRequest("key"), resp: deleteResponse(1, 1), expectFailure: true}, {req: deleteRequest("key"), resp: deleteResponse(1, 2)}, }, }, { name: "Delete can fail and be lost before put", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: putRequest("key", "1"), resp: putResponse(1)}, {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, {req: putRequest("key", "1"), resp: putResponse(2)}, @@ -282,7 +154,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Delete can fail but be persisted before get", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: putRequest("key", "1"), resp: putResponse(1)}, {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, @@ -296,7 +168,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Delete can fail but be persisted before put", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: putRequest("key", "1"), resp: putResponse(1)}, {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, @@ -309,7 +181,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Delete can fail but be persisted before delete", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: putRequest("key", "1"), resp: putResponse(1)}, {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, @@ -323,7 +195,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Delete can fail but be persisted before txn", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // Txn success {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, @@ -334,58 +206,18 @@ func TestModelNonDeterministic(t *testing.T) { {req: compareRevisionAndPutRequest("key", 4, "5"), resp: compareRevisionAndPutResponse(false, 5)}, }, }, - { - name: "Txn sets new value if value matches expected", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(true, 1), failure: true}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(false, 2), failure: true}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(false, 1), failure: true}, - {req: compareRevisionAndPutRequest("key", 1, "2"), resp: compareRevisionAndPutResponse(true, 2)}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 2, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2)}, - }, - }, - { - name: "Txn can expect on empty key", - operations: []testOperation{ - {req: getRequest("key1"), resp: emptyGetResponse(1)}, - {req: compareRevisionAndPutRequest("key1", 0, "2"), resp: compareRevisionAndPutResponse(true, 2)}, - {req: compareRevisionAndPutRequest("key2", 0, "3"), resp: compareRevisionAndPutResponse(true, 3)}, - {req: compareRevisionAndPutRequest("key3", 4, "4"), resp: compareRevisionAndPutResponse(false, 4), failure: true}, - }, - }, - { - name: "Txn doesn't do anything if value doesn't match expected", - operations: []testOperation{ - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, - {req: compareRevisionAndPutRequest("key", 2, "3"), resp: compareRevisionAndPutResponse(true, 2), failure: true}, - {req: compareRevisionAndPutRequest("key", 2, "3"), resp: compareRevisionAndPutResponse(true, 1), failure: true}, - {req: compareRevisionAndPutRequest("key", 2, "3"), resp: compareRevisionAndPutResponse(false, 2), failure: true}, - {req: compareRevisionAndPutRequest("key", 2, "3"), resp: compareRevisionAndPutResponse(false, 1)}, - {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "3", 1, 1), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "3", 1, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "3", 2, 2), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, - }, - }, { name: "Txn can fail and be lost before get", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: compareRevisionAndPutRequest("key", 1, "2"), resp: failedResponse(errors.New("failed"))}, {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2), failure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 2, 2), expectFailure: true}, }, }, { name: "Txn can fail and be lost before delete", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: compareRevisionAndPutRequest("key", 1, "2"), resp: failedResponse(errors.New("failed"))}, {req: deleteRequest("key"), resp: deleteResponse(1, 2)}, @@ -393,7 +225,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Txn can fail and be lost before put", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: compareRevisionAndPutRequest("key", 1, "2"), resp: failedResponse(errors.New("failed"))}, {req: putRequest("key", "3"), resp: putResponse(2)}, @@ -401,11 +233,11 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Txn can fail but be persisted before get", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: compareRevisionAndPutRequest("key", 1, "2"), resp: failedResponse(errors.New("failed"))}, - {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), failure: true}, + {req: getRequest("key"), resp: getResponse("key", "2", 1, 1), expectFailure: true}, {req: getRequest("key"), resp: getResponse("key", "2", 2, 2)}, // Two failed request, two persisted. {req: putRequest("key", "3"), resp: putResponse(3)}, @@ -416,7 +248,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Txn can fail but be persisted before put", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: compareRevisionAndPutRequest("key", 1, "2"), resp: failedResponse(errors.New("failed"))}, @@ -430,7 +262,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Txn can fail but be persisted before delete", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted. {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: compareRevisionAndPutRequest("key", 1, "2"), resp: failedResponse(errors.New("failed"))}, @@ -444,7 +276,7 @@ func TestModelNonDeterministic(t *testing.T) { }, { name: "Txn can fail but be persisted before txn", - operations: []testOperation{ + operations: []nonDeterministicOperation{ // One failed request, one persisted with success. {req: getRequest("key"), resp: getResponse("key", "1", 1, 1)}, {req: compareRevisionAndPutRequest("key", 1, "2"), resp: failedResponse(errors.New("failed"))}, @@ -460,158 +292,9 @@ func TestModelNonDeterministic(t *testing.T) { {req: compareRevisionAndPutRequest("key", 8, "10"), resp: compareRevisionAndPutResponse(false, 9)}, }, }, - { - name: "Put with valid lease id should succeed. Put with invalid lease id should fail", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2)}, - {req: putWithLeaseRequest("key", "3", 2), resp: putResponse(3), failure: true}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2)}, - }, - }, - { - name: "Put with valid lease id should succeed. Put with expired lease id should fail", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2)}, - {req: getRequest("key"), resp: getResponse("key", "2", 2, 2)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3)}, - {req: putWithLeaseRequest("key", "4", 1), resp: putResponse(4), failure: true}, - {req: getRequest("key"), resp: emptyGetResponse(3)}, - }, - }, - { - name: "Revoke should increment the revision", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3)}, - {req: getRequest("key"), resp: emptyGetResponse(3)}, - }, - }, - { - name: "Put following a PutWithLease will detach the key from the lease", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2)}, - {req: putRequest("key", "3"), resp: putResponse(3)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3)}, - {req: getRequest("key"), resp: getResponse("key", "3", 3, 3)}, - }, - }, - { - name: "Change lease. Revoking older lease should not increment revision", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: leaseGrantRequest(2), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2)}, - {req: putWithLeaseRequest("key", "3", 2), resp: putResponse(3)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3)}, - {req: getRequest("key"), resp: getResponse("key", "3", 3, 3)}, - {req: leaseRevokeRequest(2), resp: leaseRevokeResponse(4)}, - {req: getRequest("key"), resp: emptyGetResponse(4)}, - }, - }, - { - name: "Update key with same lease", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2)}, - {req: putWithLeaseRequest("key", "3", 1), resp: putResponse(3)}, - {req: getRequest("key"), resp: getResponse("key", "3", 3, 3)}, - }, - }, - { - name: "Deleting a leased key - revoke should not increment revision", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "2", 1), resp: putResponse(2)}, - {req: deleteRequest("key"), resp: deleteResponse(1, 3)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(4), failure: true}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3)}, - }, - }, - { - name: "Lease a few keys - revoke should increment revision only once", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2)}, - {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3)}, - {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4)}, - {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(6)}, - }, - }, - { - name: "Lease some keys then delete some of them. Revoke should increment revision since some keys were still leased", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2)}, - {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3)}, - {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4)}, - {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5)}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 6)}, - {req: deleteRequest("key3"), resp: deleteResponse(1, 7)}, - {req: deleteRequest("key4"), resp: deleteResponse(1, 8)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(9)}, - {req: deleteRequest("key2"), resp: deleteResponse(0, 9)}, - {req: getRequest("key1"), resp: emptyGetResponse(9)}, - {req: getRequest("key2"), resp: emptyGetResponse(9)}, - {req: getRequest("key3"), resp: emptyGetResponse(9)}, - {req: getRequest("key4"), resp: emptyGetResponse(9)}, - }, - }, - { - name: "Lease some keys then delete all of them. Revoke should not increment", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key1", "1", 1), resp: putResponse(2)}, - {req: putWithLeaseRequest("key2", "2", 1), resp: putResponse(3)}, - {req: putWithLeaseRequest("key3", "3", 1), resp: putResponse(4)}, - {req: putWithLeaseRequest("key4", "4", 1), resp: putResponse(5)}, - {req: deleteRequest("key1"), resp: deleteResponse(1, 6)}, - {req: deleteRequest("key2"), resp: deleteResponse(1, 7)}, - {req: deleteRequest("key3"), resp: deleteResponse(1, 8)}, - {req: deleteRequest("key4"), resp: deleteResponse(1, 9)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(9)}, - }, - }, - { - name: "All request types", - operations: []testOperation{ - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: putWithLeaseRequest("key", "1", 1), resp: putResponse(2)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3)}, - {req: putRequest("key", "4"), resp: putResponse(4)}, - {req: getRequest("key"), resp: getResponse("key", "4", 4, 4)}, - {req: compareRevisionAndPutRequest("key", 4, "5"), resp: compareRevisionAndPutResponse(true, 5)}, - {req: deleteRequest("key"), resp: deleteResponse(1, 6)}, - {req: defragmentRequest(), resp: defragmentResponse(6)}, - }, - }, - { - name: "Defragment success between all other request types", - operations: []testOperation{ - {req: defragmentRequest(), resp: defragmentResponse(1)}, - {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, - {req: defragmentRequest(), resp: defragmentResponse(1)}, - {req: putWithLeaseRequest("key", "1", 1), resp: putResponse(2)}, - {req: defragmentRequest(), resp: defragmentResponse(2)}, - {req: leaseRevokeRequest(1), resp: leaseRevokeResponse(3)}, - {req: defragmentRequest(), resp: defragmentResponse(3)}, - {req: putRequest("key", "4"), resp: putResponse(4)}, - {req: defragmentRequest(), resp: defragmentResponse(4)}, - {req: getRequest("key"), resp: getResponse("key", "4", 4, 4)}, - {req: defragmentRequest(), resp: defragmentResponse(4)}, - {req: compareRevisionAndPutRequest("key", 4, "5"), resp: compareRevisionAndPutResponse(true, 5)}, - {req: defragmentRequest(), resp: defragmentResponse(5)}, - {req: deleteRequest("key"), resp: deleteResponse(1, 6)}, - {req: defragmentRequest(), resp: defragmentResponse(6)}, - }, - }, { name: "Defragment failures between all other request types", - operations: []testOperation{ + operations: []nonDeterministicOperation{ {req: defragmentRequest(), resp: failedResponse(errors.New("failed"))}, {req: leaseGrantRequest(1), resp: leaseGrantResponse(1)}, {req: defragmentRequest(), resp: failedResponse(errors.New("failed"))}, @@ -629,15 +312,16 @@ func TestModelNonDeterministic(t *testing.T) { {req: defragmentRequest(), resp: failedResponse(errors.New("failed"))}, }, }, - } - for _, tc := range tcs { + }...) + for _, tc := range nonDeterministicTestScenarios { + tc := tc t.Run(tc.name, func(t *testing.T) { state := NonDeterministicModel.Init() for _, op := range tc.operations { ok, newState := NonDeterministicModel.Step(state, op.req, op.resp) - if ok != !op.failure { + if ok != !op.expectFailure { t.Logf("state: %v", state) - t.Errorf("Unexpected operation result, expect: %v, got: %v, operation: %s", !op.failure, ok, NonDeterministicModel.DescribeOperation(op.req, op.resp)) + t.Errorf("Unexpected operation result, expect: %v, got: %v, operation: %s", !op.expectFailure, ok, NonDeterministicModel.DescribeOperation(op.req, op.resp)) } if ok { state = newState @@ -648,6 +332,32 @@ func TestModelNonDeterministic(t *testing.T) { } } +type nonDeterministicModelTest struct { + name string + operations []nonDeterministicOperation +} + +type nonDeterministicOperation struct { + req EtcdRequest + resp EtcdNonDeterministicResponse + expectFailure bool +} + +func toNonDeterministicTest(tc deterministicModelTest) nonDeterministicModelTest { + operations := []nonDeterministicOperation{} + for _, op := range tc.operations { + operations = append(operations, nonDeterministicOperation{ + req: op.req, + resp: EtcdNonDeterministicResponse{EtcdResponse: op.resp}, + expectFailure: op.expectFailure, + }) + } + return nonDeterministicModelTest{ + name: tc.name, + operations: operations, + } +} + func TestModelResponseMatch(t *testing.T) { tcs := []struct { resp1 EtcdNonDeterministicResponse