From 6877122dca63fc842753db5f0c0c50d1a3d83f32 Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Sat, 24 Dec 2022 19:08:50 +0100 Subject: [PATCH] tests: Rewrite etcd requests to use operations Signed-off-by: Marek Siarkowicz --- tests/linearizability/history.go | 64 ++++- tests/linearizability/model.go | 229 +++++++++------ tests/linearizability/model_test.go | 414 ++++++++++++++-------------- tests/linearizability/traffic.go | 4 +- 4 files changed, 412 insertions(+), 299 deletions(-) diff --git a/tests/linearizability/history.go b/tests/linearizability/history.go index 52981167d..4ec0f8161 100644 --- a/tests/linearizability/history.go +++ b/tests/linearizability/history.go @@ -46,17 +46,21 @@ func (h *appendableHistory) AppendGet(key string, start, end time.Time, resp *cl if len(resp.Kvs) == 1 { readData = string(resp.Kvs[0].Value) } + var revision int64 + if resp != nil && resp.Header != nil { + revision = resp.Header.Revision + } h.successful = append(h.successful, porcupine.Operation{ ClientId: h.id, - Input: EtcdRequest{Op: Get, Key: key}, + Input: getRequest(key), Call: start.UnixNano(), - Output: EtcdResponse{GetData: readData, Revision: resp.Header.Revision}, + Output: getResponse(readData, revision), Return: end.UnixNano(), }) } func (h *appendableHistory) AppendPut(key, value string, start, end time.Time, resp *clientv3.PutResponse, err error) { - request := EtcdRequest{Op: Put, Key: key, PutData: value} + request := putRequest(key, value) if err != nil { h.appendFailed(request, start, err) return @@ -67,15 +71,15 @@ func (h *appendableHistory) AppendPut(key, value string, start, end time.Time, r } h.successful = append(h.successful, porcupine.Operation{ ClientId: h.id, - Input: EtcdRequest{Op: Put, Key: key, PutData: value}, + Input: request, Call: start.UnixNano(), - Output: EtcdResponse{Err: err, Revision: revision}, + Output: putResponse(revision), Return: end.UnixNano(), }) } func (h *appendableHistory) AppendDelete(key string, start, end time.Time, resp *clientv3.DeleteResponse, err error) { - request := EtcdRequest{Op: Delete, Key: key} + request := deleteRequest(key) if err != nil { h.appendFailed(request, start, err) return @@ -90,13 +94,13 @@ func (h *appendableHistory) AppendDelete(key string, start, end time.Time, resp ClientId: h.id, Input: request, Call: start.UnixNano(), - Output: EtcdResponse{Revision: revision, Deleted: deleted, Err: err}, + Output: deleteResponse(deleted, revision), Return: end.UnixNano(), }) } func (h *appendableHistory) AppendTxn(key, expectValue, newValue string, start, end time.Time, resp *clientv3.TxnResponse, err error) { - request := EtcdRequest{Op: Txn, Key: key, TxnExpectData: expectValue, TxnNewData: newValue} + request := txnRequest(key, expectValue, newValue) if err != nil { h.appendFailed(request, start, err) return @@ -109,7 +113,7 @@ func (h *appendableHistory) AppendTxn(key, expectValue, newValue string, start, ClientId: h.id, Input: request, Call: start.UnixNano(), - Output: EtcdResponse{Err: err, Revision: revision, TxnSucceeded: resp.Succeeded}, + Output: txnResponse(resp.Succeeded, revision), Return: end.UnixNano(), }) } @@ -119,7 +123,7 @@ func (h *appendableHistory) appendFailed(request EtcdRequest, start time.Time, e ClientId: h.id, Input: request, Call: start.UnixNano(), - Output: EtcdResponse{Err: err}, + Output: failedResponse(err), Return: 0, // For failed writes we don't know when request has really finished. }) // Operations of single client needs to be sequential. @@ -127,6 +131,46 @@ func (h *appendableHistory) appendFailed(request EtcdRequest, start time.Time, e h.id = h.idProvider.ClientId() } +func getRequest(key string) EtcdRequest { + return EtcdRequest{Ops: []EtcdOperation{{Type: Get, Key: key}}} +} + +func getResponse(value string, revision int64) EtcdResponse { + return EtcdResponse{Result: []EtcdOperationResult{{Value: value}}, Revision: revision} +} + +func failedResponse(err error) EtcdResponse { + return EtcdResponse{Err: err} +} + +func putRequest(key, value string) EtcdRequest { + return EtcdRequest{Ops: []EtcdOperation{{Type: Put, Key: key, Value: value}}} +} + +func putResponse(revision int64) EtcdResponse { + return EtcdResponse{Result: []EtcdOperationResult{{}}, Revision: revision} +} + +func deleteRequest(key string) EtcdRequest { + return EtcdRequest{Ops: []EtcdOperation{{Type: Delete, Key: key}}} +} + +func deleteResponse(deleted int64, revision int64) EtcdResponse { + return EtcdResponse{Result: []EtcdOperationResult{{Deleted: deleted}}, Revision: revision} +} + +func txnRequest(key, expectValue, newValue string) EtcdRequest { + return EtcdRequest{Conds: []EtcdCondition{{Key: key, ExpectedValue: expectValue}}, Ops: []EtcdOperation{{Type: Put, Key: key, Value: newValue}}} +} + +func txnResponse(succeeded bool, revision int64) EtcdResponse { + var result []EtcdOperationResult + if succeeded { + result = []EtcdOperationResult{{}} + } + return EtcdResponse{Result: result, TxnFailure: !succeeded, Revision: revision} +} + type history struct { successful []porcupine.Operation // failed requests are kept separate as we don't know return time of failed operations. diff --git a/tests/linearizability/model.go b/tests/linearizability/model.go index b8a284483..6a01180a4 100644 --- a/tests/linearizability/model.go +++ b/tests/linearizability/model.go @@ -17,33 +17,47 @@ package linearizability import ( "encoding/json" "fmt" + "reflect" + "strings" "github.com/anishathalye/porcupine" ) -type Operation string +type OperationType string const ( - Get Operation = "get" - Put Operation = "put" - Delete Operation = "delete" - Txn Operation = "txn" + Get OperationType = "get" + Put OperationType = "put" + Delete OperationType = "delete" + Txn OperationType = "txn" ) type EtcdRequest struct { - Op Operation + Conds []EtcdCondition + Ops []EtcdOperation +} + +type EtcdCondition struct { Key string - PutData string - TxnExpectData string - TxnNewData string + ExpectedValue string +} + +type EtcdOperation struct { + Type OperationType + Key string + Value string } type EtcdResponse struct { - GetData string - Revision int64 - Deleted int64 - TxnSucceeded bool - Err error + Err error + Revision int64 + TxnFailure bool + Result []EtcdOperationResult +} + +type EtcdOperationResult struct { + Value string + Deleted int64 } type PossibleStates []EtcdState @@ -71,39 +85,83 @@ var etcdModel = porcupine.Model{ return ok, string(data) }, DescribeOperation: func(in, out interface{}) string { - request := in.(EtcdRequest) - response := out.(EtcdResponse) - switch request.Op { - case Get: - if response.Err != nil { - return fmt.Sprintf("get(%q) -> %q", request.Key, response.Err) - } else { - return fmt.Sprintf("get(%q) -> %q, rev: %d", request.Key, response.GetData, response.Revision) - } - case Put: - if response.Err != nil { - return fmt.Sprintf("put(%q, %q) -> %s", request.Key, request.PutData, response.Err) - } else { - return fmt.Sprintf("put(%q, %q) -> ok, rev: %d", request.Key, request.PutData, response.Revision) - } - case Delete: - if response.Err != nil { - return fmt.Sprintf("delete(%q) -> %s", request.Key, response.Err) - } else { - return fmt.Sprintf("delete(%q) -> ok, rev: %d deleted:%d", request.Key, response.Revision, response.Deleted) - } - case Txn: - if response.Err != nil { - return fmt.Sprintf("txn(if(value(%q)=%q).then(put(%q, %q)) -> %s", request.Key, request.TxnExpectData, request.Key, request.TxnNewData, response.Err) - } else { - return fmt.Sprintf("txn(if(value(%q)=%q).then(put(%q, %q)) -> %v, rev: %d", request.Key, request.TxnExpectData, request.Key, request.TxnNewData, response.TxnSucceeded, response.Revision) - } - default: - return "" - } + return describeEtcdRequestResponse(in.(EtcdRequest), out.(EtcdResponse)) }, } +func describeEtcdRequestResponse(request EtcdRequest, response EtcdResponse) string { + prefix := describeEtcdOperations(request.Ops) + if len(request.Conds) != 0 { + prefix = fmt.Sprintf("if(%s).then(%s)", describeEtcdConditions(request.Conds), prefix) + } + + return fmt.Sprintf("%s -> %s", prefix, describeEtcdResponse(request.Ops, response)) +} + +func describeEtcdConditions(conds []EtcdCondition) string { + opsDescription := make([]string, len(conds)) + for i := range conds { + opsDescription[i] = fmt.Sprintf("%s==%q", conds[i].Key, conds[i].ExpectedValue) + } + 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 describeEtcdResponse(ops []EtcdOperation, response EtcdResponse) string { + if response.Err != nil { + return fmt.Sprintf("err: %q", response.Err) + } + if response.TxnFailure { + return fmt.Sprintf("txn failed, rev: %d", response.Revision) + } + respDescription := make([]string, len(response.Result)) + for i := range response.Result { + respDescription[i] = describeEtcdOperationResponse(ops[i].Type, response.Result[i]) + } + respDescription = append(respDescription, fmt.Sprintf("rev: %d", response.Revision)) + return strings.Join(respDescription, ", ") +} + +func describeEtcdOperation(op EtcdOperation) string { + switch op.Type { + case Get: + return fmt.Sprintf("get(%q)", op.Key) + case Put: + return fmt.Sprintf("put(%q, %q)", op.Key, op.Value) + case Delete: + return fmt.Sprintf("delete(%q)", op.Key) + case Txn: + return "" + default: + return fmt.Sprintf("", op.Type) + } +} + +func describeEtcdOperationResponse(op OperationType, resp EtcdOperationResult) string { + switch op { + case Get: + if resp.Value == "" { + return "nil" + } + return fmt.Sprintf("%q", resp.Value) + case Put: + return fmt.Sprintf("ok") + case Delete: + return fmt.Sprintf("deleted: %d", resp.Deleted) + case Txn: + return "" + default: + return fmt.Sprintf("", op) + } +} + func step(states PossibleStates, request EtcdRequest, response EtcdResponse) (bool, PossibleStates) { if len(states) == 0 { // states were not initialized @@ -126,20 +184,22 @@ func initState(request EtcdRequest, response EtcdResponse) EtcdState { Revision: response.Revision, KeyValues: map[string]string{}, } - switch request.Op { - case Get: - if response.GetData != "" { - state.KeyValues[request.Key] = response.GetData + if response.TxnFailure { + return state + } + for i, op := range request.Ops { + opResp := response.Result[i] + switch op.Type { + case Get: + if opResp.Value != "" { + state.KeyValues[op.Key] = opResp.Value + } + case Put: + state.KeyValues[op.Key] = op.Value + case Delete: + default: + panic("Unknown operation") } - case Put: - state.KeyValues[request.Key] = request.PutData - case Delete: - case Txn: - if response.TxnSucceeded { - state.KeyValues[request.Key] = request.TxnNewData - } - default: - panic("Unknown operation") } return state } @@ -158,7 +218,7 @@ func applyRequest(states PossibleStates, request EtcdRequest, response EtcdRespo newStates := make(PossibleStates, 0, len(states)) for _, s := range states { newState, expectResponse := applyRequestToSingleState(s, request) - if expectResponse == response { + if reflect.DeepEqual(expectResponse, response) { newStates = append(newStates, newState) } } @@ -167,33 +227,42 @@ func applyRequest(states PossibleStates, request EtcdRequest, response EtcdRespo // applyRequestToSingleState handles a successful request, returning updated state and response it would generate. func applyRequestToSingleState(s EtcdState, request EtcdRequest) (EtcdState, EtcdResponse) { + success := true + for _, cond := range request.Conds { + if val := s.KeyValues[cond.Key]; val != cond.ExpectedValue { + success = false + break + } + } + if !success { + return s, EtcdResponse{Revision: s.Revision, TxnFailure: true} + } newKVs := map[string]string{} for k, v := range s.KeyValues { newKVs[k] = v } s.KeyValues = newKVs - resp := EtcdResponse{} - switch request.Op { - case Get: - resp.GetData = s.KeyValues[request.Key] - case Put: - s.KeyValues[request.Key] = request.PutData - s.Revision += 1 - case Delete: - if _, ok := s.KeyValues[request.Key]; ok { - delete(s.KeyValues, request.Key) - s.Revision += 1 - resp.Deleted = 1 + opResp := make([]EtcdOperationResult, len(request.Ops)) + increaseRevision := false + for i, op := range request.Ops { + switch op.Type { + case Get: + opResp[i].Value = s.KeyValues[op.Key] + case Put: + s.KeyValues[op.Key] = op.Value + increaseRevision = true + case Delete: + if _, ok := s.KeyValues[op.Key]; ok { + delete(s.KeyValues, op.Key) + increaseRevision = true + opResp[i].Deleted = 1 + } + default: + panic("unsupported operation") } - case Txn: - if val := s.KeyValues[request.Key]; val == request.TxnExpectData { - s.KeyValues[request.Key] = request.TxnNewData - s.Revision += 1 - resp.TxnSucceeded = true - } - default: - panic("unsupported operation") } - resp.Revision = s.Revision - return s, resp + if increaseRevision { + s.Revision += 1 + } + return s, EtcdResponse{Result: opResp, Revision: s.Revision} } diff --git a/tests/linearizability/model_test.go b/tests/linearizability/model_test.go index e45ad0a60..067046cbd 100644 --- a/tests/linearizability/model_test.go +++ b/tests/linearizability/model_test.go @@ -29,89 +29,89 @@ func TestModelStep(t *testing.T) { { name: "First Get can start from non-empty value and non-zero revision", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 42}}, + {req: getRequest("key"), resp: getResponse("", 42)}, }, }, { name: "First Put can start from non-zero revision", operations: []testOperation{ - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Revision: 42}}, + {req: putRequest("key", "1"), resp: putResponse(42)}, }, }, { name: "First delete can start from non-zero revision", operations: []testOperation{ - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Revision: 42}}, + {req: deleteRequest("key"), resp: deleteResponse(0, 42)}, }, }, { name: "First Txn can start from non-zero revision", operations: []testOperation{ - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "", TxnNewData: "42"}, resp: EtcdResponse{Revision: 42}}, + {req: txnRequest("key", "", "42"), resp: txnResponse(false, 42)}, }, }, { name: "Get response data should match put", operations: []testOperation{ - {req: EtcdRequest{Op: Put, Key: "key1", PutData: "11"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key2", PutData: "12"}, resp: EtcdResponse{Revision: 2}}, - {req: EtcdRequest{Op: Get, Key: "key1"}, resp: EtcdResponse{GetData: "11", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key1"}, resp: EtcdResponse{GetData: "12", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key1"}, resp: EtcdResponse{GetData: "12", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key1"}, resp: EtcdResponse{GetData: "11", Revision: 2}}, - {req: EtcdRequest{Op: Get, Key: "key2"}, resp: EtcdResponse{GetData: "11", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key2"}, resp: EtcdResponse{GetData: "12", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key2"}, resp: EtcdResponse{GetData: "11", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key2"}, resp: EtcdResponse{GetData: "12", Revision: 2}}, + {req: putRequest("key1", "11"), resp: putResponse(1)}, + {req: putRequest("key2", "12"), resp: putResponse(2)}, + {req: getRequest("key1"), resp: getResponse("11", 1), failure: true}, + {req: getRequest("key1"), resp: getResponse("12", 1), failure: true}, + {req: getRequest("key1"), resp: getResponse("12", 2), failure: true}, + {req: getRequest("key1"), resp: getResponse("11", 2)}, + {req: getRequest("key2"), resp: getResponse("11", 2), failure: true}, + {req: getRequest("key2"), resp: getResponse("12", 1), failure: true}, + {req: getRequest("key2"), resp: getResponse("11", 1), failure: true}, + {req: getRequest("key2"), resp: getResponse("12", 2)}, }, }, { name: "Put must increase revision by 1", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 3}, failure: true}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Revision: 2}}, + {req: getRequest("key"), resp: getResponse("", 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)}, }, }, { name: "Put can fail and be lost before get", operations: []testOperation{ - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 2}, failure: true}, + {req: putRequest("key", "1"), resp: putResponse(1)}, + {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: getRequest("key"), resp: getResponse("2", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("1", 2), failure: true}, + {req: getRequest("key"), resp: getResponse("2", 2), failure: true}, }, }, { name: "Put can fail and be lost before put", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Revision: 2}}, + {req: getRequest("key"), resp: getResponse("", 1)}, + {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "3"), resp: getResponse("", 2)}, }, }, { name: "Put can fail and be lost before delete", operations: []testOperation{ - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Revision: 1}}, + {req: deleteRequest("key"), resp: deleteResponse(0, 1)}, + {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(0, 1)}, }, }, { - name: "Put can fail and be lost before txn failed", + name: "Put can fail and be lost before txn", operations: []testOperation{ // Txn failure - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2", TxnNewData: "3"}, resp: EtcdResponse{Revision: 1}}, + {req: getRequest("key"), resp: getResponse("", 1)}, + {req: putRequest("key", "1"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "2", "3"), resp: txnResponse(false, 1)}, // Txn success - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Revision: 2}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2", TxnNewData: "3"}, resp: EtcdResponse{TxnSucceeded: true, Revision: 3}}, + {req: putRequest("key", "2"), resp: putResponse(2)}, + {req: putRequest("key", "4"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "2", "5"), resp: txnResponse(true, 3)}, }, }, { @@ -122,284 +122,284 @@ func TestModelStep(t *testing.T) { name: "Put can fail but be persisted and increase revision before get", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "3", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 2}}, + {req: putRequest("key", "1"), resp: putResponse(1)}, + {req: putRequest("key", "2"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("3", 2), failure: true}, + {req: getRequest("key"), resp: getResponse("2", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("2", 2)}, // Two failed request, two persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "4", Revision: 4}}, + {req: putRequest("key", "3"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "4"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("4", 4)}, }, }, { name: "Put can fail but be persisted and increase revision before delete", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 3}}, + {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, 3)}, // Two failed request, two persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Revision: 4}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "5"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "6"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 7}}, + {req: putRequest("key", "4"), resp: putResponse(4)}, + {req: putRequest("key", "5"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "6"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(1, 7)}, // Two failed request, one persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "8"}, resp: EtcdResponse{Revision: 8}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "9"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "10"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 10}}, + {req: putRequest("key", "8"), resp: putResponse(8)}, + {req: putRequest("key", "9"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "10"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(1, 10)}, }, }, { name: "Put can fail but be persisted before txn", operations: []testOperation{ // Txn success - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2"}, resp: EtcdResponse{TxnSucceeded: true, Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2"}, resp: EtcdResponse{TxnSucceeded: true, Revision: 3}}, + {req: getRequest("key"), resp: getResponse("", 1)}, + {req: putRequest("key", "2"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "2", ""), resp: txnResponse(true, 2), failure: true}, + {req: txnRequest("key", "2", ""), resp: txnResponse(true, 3)}, // Txn failure - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Revision: 4}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "5"}, resp: EtcdResponse{Revision: 4}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "5"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 5, GetData: "5"}}, + {req: putRequest("key", "4"), resp: putResponse(4)}, + {req: txnRequest("key", "5", ""), resp: txnResponse(false, 4)}, + {req: putRequest("key", "5"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("5", 5)}, }, }, { name: "Delete only increases revision on success", operations: []testOperation{ - {req: EtcdRequest{Op: Put, Key: "key1", PutData: "11"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Put, Key: "key2", PutData: "12"}, resp: EtcdResponse{Revision: 2}}, - {req: EtcdRequest{Op: Delete, Key: "key1"}, resp: EtcdResponse{Deleted: 1, Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Delete, Key: "key1"}, resp: EtcdResponse{Deleted: 1, Revision: 3}}, - {req: EtcdRequest{Op: Delete, Key: "key1"}, resp: EtcdResponse{Deleted: 0, Revision: 4}, failure: true}, - {req: EtcdRequest{Op: Delete, Key: "key1"}, resp: EtcdResponse{Deleted: 0, Revision: 3}}, + {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: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 0, Revision: 1}}, + {req: getRequest("key"), resp: getResponse("", 1)}, + {req: deleteRequest("key"), resp: deleteResponse(1, 2), failure: true}, + {req: deleteRequest("key"), resp: deleteResponse(0, 1)}, }, }, { name: "Delete clears value", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 2}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 2}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: deleteRequest("key"), resp: deleteResponse(1, 2)}, + {req: getRequest("key"), resp: getResponse("1", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("1", 2), failure: true}, + {req: getRequest("key"), resp: getResponse("", 2)}, }, }, { name: "Delete can fail and be lost before get", operations: []testOperation{ - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 2}, failure: true}, + {req: putRequest("key", "1"), resp: putResponse(1)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: getRequest("key"), resp: getResponse("", 2), failure: true}, }, }, { name: "Delete can fail and be lost before delete", operations: []testOperation{ - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 2}}, + {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, 2)}, }, }, { name: "Delete can fail and be lost before put", operations: []testOperation{ - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "2"}, resp: EtcdResponse{Revision: 2}}, + {req: putRequest("key", "1"), resp: putResponse(1)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "1"), resp: putResponse(2)}, }, }, { name: "Delete can fail but be persisted before get", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 2}}, + {req: putRequest("key", "1"), resp: putResponse(1)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("", 2)}, // Two failed request, one persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Revision: 3}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 4}}, + {req: putRequest("key", "3"), resp: putResponse(3)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("", 4)}, }, }, { name: "Delete can fail but be persisted before put", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Revision: 3}}, + {req: putRequest("key", "1"), resp: putResponse(1)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "3"), resp: putResponse(3)}, // Two failed request, one persisted. - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "5"}, resp: EtcdResponse{Revision: 5}}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "5"), resp: putResponse(5)}, }, }, { name: "Delete can fail but be persisted before delete", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Revision: 2}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Revision: 3}}, + {req: putRequest("key", "1"), resp: putResponse(1)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(0, 2)}, + {req: putRequest("key", "3"), resp: putResponse(3)}, // Two failed request, one persisted. - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Revision: 4}}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(0, 4)}, }, }, { name: "Delete can fail but be persisted before txn", operations: []testOperation{ // Txn success - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "", TxnNewData: "1"}, resp: EtcdResponse{TxnSucceeded: true, Revision: 3}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "", "3"), resp: txnResponse(true, 3)}, // Txn failure - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Revision: 4}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "4", TxnNewData: "5"}, resp: EtcdResponse{TxnSucceeded: false, Revision: 5}}, + {req: putRequest("key", "4"), resp: putResponse(4)}, + {req: deleteRequest("key"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "4", "5"), resp: txnResponse(false, 5)}, }, }, { name: "Txn sets new value if value matches expected", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Revision: 1, TxnSucceeded: true}, failure: true}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Revision: 2, TxnSucceeded: false}, failure: true}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Revision: 1, TxnSucceeded: false}, failure: true}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Revision: 2, TxnSucceeded: true}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 2}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: txnResponse(true, 1), failure: true}, + {req: txnRequest("key", "1", "2"), resp: txnResponse(false, 2), failure: true}, + {req: txnRequest("key", "1", "2"), resp: txnResponse(false, 1), failure: true}, + {req: txnRequest("key", "1", "2"), resp: txnResponse(true, 2)}, + {req: getRequest("key"), resp: getResponse("1", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("1", 2), failure: true}, + {req: getRequest("key"), resp: getResponse("2", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("2", 2)}, }, }, { name: "Txn can expect on empty key", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key1"}, resp: EtcdResponse{Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key1", TxnExpectData: "", TxnNewData: "2"}, resp: EtcdResponse{Revision: 2, TxnSucceeded: true}}, - {req: EtcdRequest{Op: Txn, Key: "key2", TxnExpectData: "", TxnNewData: "3"}, resp: EtcdResponse{Revision: 3, TxnSucceeded: true}}, - {req: EtcdRequest{Op: Txn, Key: "key3", TxnExpectData: "4", TxnNewData: "4"}, resp: EtcdResponse{Revision: 4}, failure: true}, + {req: getRequest("key1"), resp: getResponse("", 1)}, + {req: txnRequest("key1", "", "2"), resp: txnResponse(true, 2)}, + {req: txnRequest("key2", "", "3"), resp: txnResponse(true, 3)}, + {req: txnRequest("key3", "4", "4"), resp: txnResponse(false, 4), failure: true}, }, }, { name: "Txn doesn't do anything if value doesn't match expected", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2", TxnNewData: "3"}, resp: EtcdResponse{Revision: 2, TxnSucceeded: true}, failure: true}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2", TxnNewData: "3"}, resp: EtcdResponse{Revision: 1, TxnSucceeded: true}, failure: true}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2", TxnNewData: "3"}, resp: EtcdResponse{Revision: 2, TxnSucceeded: false}, failure: true}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2", TxnNewData: "3"}, resp: EtcdResponse{Revision: 1, TxnSucceeded: false}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "2", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "3", Revision: 1}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "3", Revision: 2}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "2", "3"), resp: txnResponse(true, 2), failure: true}, + {req: txnRequest("key", "2", "3"), resp: txnResponse(true, 1), failure: true}, + {req: txnRequest("key", "2", "3"), resp: txnResponse(false, 2), failure: true}, + {req: txnRequest("key", "2", "3"), resp: txnResponse(false, 1)}, + {req: getRequest("key"), resp: getResponse("2", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("2", 2), failure: true}, + {req: getRequest("key"), resp: getResponse("3", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("3", 2), failure: true}, + {req: getRequest("key"), resp: getResponse("1", 1)}, }, }, { name: "Txn can fail and be lost before get", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 2, GetData: "2"}, failure: true}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: getRequest("key"), resp: getResponse("2", 2), failure: true}, }, }, { name: "Txn can fail and be lost before delete", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 2}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(1, 2)}, }, }, { name: "Txn can fail and be lost before put", operations: []testOperation{ - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Revision: 2}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "3"), resp: putResponse(2)}, }, }, { name: "Txn can fail but be persisted before get", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 1, GetData: "2"}, failure: true}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 2, GetData: "2"}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("2", 1), failure: true}, + {req: getRequest("key"), resp: getResponse("2", 2)}, // Two failed request, two persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Revision: 3}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "3", TxnNewData: "4"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "4", TxnNewData: "5"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{Revision: 5, GetData: "5"}}, + {req: putRequest("key", "3"), resp: putResponse(3)}, + {req: txnRequest("key", "3", "4"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "4", "5"), resp: failedResponse(errors.New("failed"))}, + {req: getRequest("key"), resp: getResponse("5", 5)}, }, }, { name: "Txn can fail but be persisted before put", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "3"}, resp: EtcdResponse{Revision: 3}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "3"), resp: putResponse(3)}, // Two failed request, two persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Revision: 4}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "4", TxnNewData: "5"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "5", TxnNewData: "6"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Put, Key: "key", PutData: "7"}, resp: EtcdResponse{Revision: 7}}, + {req: putRequest("key", "4"), resp: putResponse(4)}, + {req: txnRequest("key", "4", "5"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "5", "6"), resp: failedResponse(errors.New("failed"))}, + {req: putRequest("key", "7"), resp: putResponse(7)}, }, }, { name: "Txn can fail but be persisted before delete", operations: []testOperation{ // One failed request, one persisted. - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 3}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(1, 3)}, // Two failed request, two persisted. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Revision: 4}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "4", TxnNewData: "5"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "5", TxnNewData: "6"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Delete, Key: "key"}, resp: EtcdResponse{Deleted: 1, Revision: 7}}, + {req: putRequest("key", "4"), resp: putResponse(4)}, + {req: txnRequest("key", "4", "5"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "5", "6"), resp: failedResponse(errors.New("failed"))}, + {req: deleteRequest("key"), resp: deleteResponse(1, 7)}, }, }, { name: "Txn can fail but be persisted before txn", operations: []testOperation{ // One failed request, one persisted with success. - {req: EtcdRequest{Op: Get, Key: "key"}, resp: EtcdResponse{GetData: "1", Revision: 1}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "1", TxnNewData: "2"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "2", TxnNewData: "3"}, resp: EtcdResponse{Revision: 3, TxnSucceeded: true}}, + {req: getRequest("key"), resp: getResponse("1", 1)}, + {req: txnRequest("key", "1", "2"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "2", "3"), resp: txnResponse(true, 3)}, // Two failed request, two persisted with success. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "4"}, resp: EtcdResponse{Revision: 4}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "4", TxnNewData: "5"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "5", TxnNewData: "6"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "6", TxnNewData: "7"}, resp: EtcdResponse{Revision: 7, TxnSucceeded: true}}, + {req: putRequest("key", "4"), resp: putResponse(4)}, + {req: txnRequest("key", "4", "5"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "5", "6"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "6", "7"), resp: txnResponse(true, 7)}, // One failed request, one persisted with failure. - {req: EtcdRequest{Op: Put, Key: "key", PutData: "8"}, resp: EtcdResponse{Revision: 8}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "8", TxnNewData: "9"}, resp: EtcdResponse{Err: errors.New("failed")}}, - {req: EtcdRequest{Op: Txn, Key: "key", TxnExpectData: "8", TxnNewData: "10"}, resp: EtcdResponse{Revision: 9}}, + {req: putRequest("key", "8"), resp: putResponse(8)}, + {req: txnRequest("key", "8", "9"), resp: failedResponse(errors.New("failed"))}, + {req: txnRequest("key", "8", "10"), resp: txnResponse(false, 9)}, }, }, } @@ -434,49 +434,49 @@ func TestModelDescribe(t *testing.T) { expectDescribe string }{ { - req: EtcdRequest{Op: Get, Key: "key1"}, - resp: EtcdResponse{Revision: 1}, - expectDescribe: `get("key1") -> "", rev: 1`, + req: getRequest("key1"), + resp: getResponse("", 1), + expectDescribe: `get("key1") -> nil, rev: 1`, }, { - req: EtcdRequest{Op: Get, Key: "key2"}, - resp: EtcdResponse{GetData: "2", Revision: 2}, + req: getRequest("key2"), + resp: getResponse("2", 2), expectDescribe: `get("key2") -> "2", rev: 2`, }, { - req: EtcdRequest{Op: Put, Key: "key3", PutData: "3"}, - resp: EtcdResponse{Revision: 3}, + req: putRequest("key3", "3"), + resp: putResponse(3), expectDescribe: `put("key3", "3") -> ok, rev: 3`, }, { - req: EtcdRequest{Op: Put, Key: "key4", PutData: "4"}, - resp: EtcdResponse{Err: errors.New("failed")}, - expectDescribe: `put("key4", "4") -> failed`, + req: putRequest("key4", "4"), + resp: failedResponse(errors.New("failed")), + expectDescribe: `put("key4", "4") -> err: "failed"`, }, { - req: EtcdRequest{Op: Delete, Key: "key5"}, - resp: EtcdResponse{Revision: 5, Deleted: 1}, - expectDescribe: `delete("key5") -> ok, rev: 5 deleted:1`, + req: deleteRequest("key5"), + resp: deleteResponse(1, 5), + expectDescribe: `delete("key5") -> deleted: 1, rev: 5`, }, { - req: EtcdRequest{Op: Delete, Key: "key6"}, - resp: EtcdResponse{Err: errors.New("failed")}, - expectDescribe: `delete("key6") -> failed`, + req: deleteRequest("key6"), + resp: failedResponse(errors.New("failed")), + expectDescribe: `delete("key6") -> err: "failed"`, }, { - req: EtcdRequest{Op: Txn, Key: "key7", TxnExpectData: "7", TxnNewData: "77"}, - resp: EtcdResponse{Revision: 7}, - expectDescribe: `txn(if(value("key7")="7").then(put("key7", "77")) -> false, rev: 7`, + req: txnRequest("key7", "7", "77"), + resp: txnResponse(false, 7), + expectDescribe: `if(key7=="7").then(put("key7", "77")) -> txn failed, rev: 7`, }, { - req: EtcdRequest{Op: Txn, Key: "key8", TxnExpectData: "8", TxnNewData: "88"}, - resp: EtcdResponse{TxnSucceeded: true, Revision: 8}, - expectDescribe: `txn(if(value("key8")="8").then(put("key8", "88")) -> true, rev: 8`, + req: txnRequest("key8", "8", "88"), + resp: txnResponse(true, 8), + expectDescribe: `if(key8=="8").then(put("key8", "88")) -> ok, rev: 8`, }, { - req: EtcdRequest{Op: Txn, Key: "key9", TxnExpectData: "9", TxnNewData: "99"}, - resp: EtcdResponse{Err: errors.New("failed")}, - expectDescribe: `txn(if(value("key9")="9").then(put("key9", "99")) -> failed`, + req: txnRequest("key9", "9", "99"), + resp: failedResponse(errors.New("failed")), + expectDescribe: `if(key9=="9").then(put("key9", "99")) -> err: "failed"`, }, } for _, tc := range tcs { diff --git a/tests/linearizability/traffic.go b/tests/linearizability/traffic.go index 020f82274..e4ab5410d 100644 --- a/tests/linearizability/traffic.go +++ b/tests/linearizability/traffic.go @@ -39,7 +39,7 @@ type readWriteSingleKey struct { } type opChance struct { - operation Operation + operation OperationType chance int } @@ -97,7 +97,7 @@ func (t readWriteSingleKey) Write(ctx context.Context, c *recordingClient, limit return err } -func (t readWriteSingleKey) pickWriteOperation() Operation { +func (t readWriteSingleKey) pickWriteOperation() OperationType { sum := 0 for _, op := range t.writes { sum += op.chance