diff --git a/tests/linearizability/client.go b/tests/linearizability/client.go index 5addf7297..ca2da56df 100644 --- a/tests/linearizability/client.go +++ b/tests/linearizability/client.go @@ -89,3 +89,23 @@ 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() + 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, err: err}, + Return: returnTime.UnixNano(), + }) + return nil +} diff --git a/tests/linearizability/model.go b/tests/linearizability/model.go index a126f7cf2..6c0617222 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,10 @@ func initState(request etcdRequest, response etcdResponse) EtcdState { } else { state.FailedWrites[request.putData] = struct{}{} } + case Delete: + if response.err != nil { + state.FailedWrites[""] = struct{}{} + } default: panic("Unknown operation") } @@ -151,3 +165,29 @@ 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[""] = struct{}{} + return true, state + } + deleteSucceeded := response.deleted != 0 + keySet := state.Value != "" + + //non-existent key cannot be deleted. + if deleteSucceeded != keySet { + return false, state + } + //if key was deleted, response revision should go up + if deleteSucceeded && state.LastRevision >= response.revision { + return false, state + } + //if key was not deleted, response revision should not change + if !deleteSucceeded && state.LastRevision != response.revision { + return false, state + } + + state.Value = "" + state.LastRevision = response.revision + return true, state +} diff --git a/tests/linearizability/model_test.go b/tests/linearizability/model_test.go index 61453310a..ca65a146b 100644 --- a/tests/linearizability/model_test.go +++ b/tests/linearizability/model_test.go @@ -104,6 +104,16 @@ func TestModel(t *testing.T) { {req: etcdRequest{op: Put, key: "key", putData: "3"}, resp: etcdResponse{revision: 4}}, }, }, + { + name: "Delete only increases revision on success", + operations: []testOperation{ + {req: etcdRequest{op: Put, key: "key", putData: "1"}, resp: etcdResponse{revision: 1}}, + {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: etcdRequest{op: Delete, key: "key"}, resp: etcdResponse{deleted: 0, revision: 3}, failure: true}, + {req: etcdRequest{op: Delete, key: "key"}, resp: etcdResponse{deleted: 0, revision: 2}}, + }, + }, } for _, tc := range tcs { var ok bool diff --git a/tests/linearizability/traffic.go b/tests/linearizability/traffic.go index 83c0e101c..f1507466e 100644 --- a/tests/linearizability/traffic.go +++ b/tests/linearizability/traffic.go @@ -24,7 +24,7 @@ import ( ) var ( - DefaultTraffic Traffic = readWriteSingleKey{key: "key", writes: []opChance{{operation: Put, chance: 100}}} + DefaultTraffic Traffic = readWriteSingleKey{key: "key", writes: []opChance{{operation: Put, chance: 90}, {operation: Delete, chance: 10}}} ) type Traffic interface { @@ -81,6 +81,8 @@ func (t readWriteSingleKey) Write(ctx context.Context, c *recordingClient, limit switch t.pickWriteOperation() { case Put: err = c.Put(putCtx, t.key, fmt.Sprintf("%d", id)) + case Delete: + err = c.Delete(putCtx, t.key) default: panic("invalid operation") }