diff --git a/tests/linearizability/client.go b/tests/linearizability/client.go index 5addf7297..fe55436ff 100644 --- a/tests/linearizability/client.go +++ b/tests/linearizability/client.go @@ -89,3 +89,26 @@ func (c *recordingClient) Put(ctx context.Context, key, value string) error { }) return nil } + +func (c *recordingClient) Delete(ctx context.Context, key string) error { + callTime := time.Now() + resp, err := c.client.Delete(ctx, key) + returnTime := time.Now() + if err != nil { + return err + } + var revision int64 + var deleted int64 + if resp != nil && resp.Header != nil { + revision = resp.Header.Revision + deleted = resp.Deleted + } + c.operations = append(c.operations, porcupine.Operation{ + ClientId: c.id, + Input: etcdRequest{op: Delete, key: key}, + Call: callTime.UnixNano(), + Output: etcdResponse{revision: revision, deleted: deleted}, + Return: returnTime.UnixNano(), + }) + return nil +} diff --git a/tests/linearizability/model.go b/tests/linearizability/model.go index a126f7cf2..fc825c014 100644 --- a/tests/linearizability/model.go +++ b/tests/linearizability/model.go @@ -24,8 +24,9 @@ import ( type Operation string const ( - Get Operation = "get" - Put Operation = "put" + Get Operation = "get" + Put Operation = "put" + Delete Operation = "delete" ) type etcdRequest struct { @@ -37,6 +38,7 @@ type etcdRequest struct { type etcdResponse struct { getData string revision int64 + deleted int64 err error } @@ -78,6 +80,12 @@ var etcdModel = porcupine.Model{ } 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) + } default: return "" } @@ -99,6 +107,8 @@ func step(state EtcdState, request etcdRequest, response etcdResponse) (bool, Et return stepGet(state, request, response) case Put: return stepPut(state, request, response) + case Delete: + return stepDelete(state, request, response) default: panic("Unknown operation") } @@ -119,6 +129,9 @@ func initState(request etcdRequest, response etcdResponse) EtcdState { } else { state.FailedWrites[request.putData] = struct{}{} } + case Delete: + //TODO should state have 'deleted' field? + state.Value = "" default: panic("Unknown operation") } @@ -151,3 +164,15 @@ func stepPut(state EtcdState, request etcdRequest, response etcdResponse) (bool, state.LastRevision = response.revision return true, state } + +func stepDelete(state EtcdState, request etcdRequest, response etcdResponse) (bool, EtcdState) { + if response.err != nil { + state.FailedWrites[request.putData] = struct{}{} + return true, state + } + if response.deleted == 1 && state.LastRevision >= response.revision { + return false, state + } + state.LastRevision = response.revision + return true, state +} diff --git a/tests/linearizability/model_test.go b/tests/linearizability/model_test.go index 61453310a..8d1a82a8c 100644 --- a/tests/linearizability/model_test.go +++ b/tests/linearizability/model_test.go @@ -104,6 +104,18 @@ func TestModel(t *testing.T) { {req: etcdRequest{op: Put, key: "key", putData: "3"}, resp: etcdResponse{revision: 4}}, }, }, + { + name: "Deleting non existent key does not change revision", + operations: []testOperation{ + {req: etcdRequest{op: Delete, key: "NotThere"}, resp: etcdResponse{deleted: 0, revision: 4}}, + }, + }, + { + name: "Deleting existent key bumps up revision", + operations: []testOperation{ + {req: etcdRequest{op: Delete, key: "key"}, resp: etcdResponse{deleted: 1, revision: 5}}, + }, + }, } for _, tc := range tcs { var ok bool