diff --git a/tests/common/kv_test.go b/tests/common/kv_test.go index 68f3205b3823..2a4eccacc913 100644 --- a/tests/common/kv_test.go +++ b/tests/common/kv_test.go @@ -163,3 +163,106 @@ func TestKVGet(t *testing.T) { }) } } + +func TestKVDelete(t *testing.T) { + testRunner.BeforeTest(t) + tcs := []struct { + name string + config config.ClusterConfig + }{ + { + name: "NoTLS", + config: config.ClusterConfig{ClusterSize: 1}, + }, + { + name: "PeerTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS}, + }, + { + name: "PeerAutoTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS}, + }, + { + name: "ClientTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS}, + }, + { + name: "ClientAutoTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS}, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, tc.config) + defer clus.Close() + cc := clus.Client() + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + kvs := []string{"a", "b", "c", "c/abc", "d"} + tests := []struct { + deleteKey string + options config.DeleteOptions + + wantDeleted int + wantKeys []string + }{ + { // delete all keys + deleteKey: "", + options: config.DeleteOptions{Prefix: true}, + wantDeleted: 5, + }, + { // delete all keys + deleteKey: "", + options: config.DeleteOptions{FromKey: true}, + wantDeleted: 5, + }, + { + deleteKey: "a", + options: config.DeleteOptions{End: "c"}, + wantDeleted: 2, + wantKeys: []string{"c", "c/abc", "d"}, + }, + { + deleteKey: "c", + wantDeleted: 1, + wantKeys: []string{"a", "b", "c/abc", "d"}, + }, + { + deleteKey: "c", + options: config.DeleteOptions{Prefix: true}, + wantDeleted: 2, + wantKeys: []string{"a", "b", "d"}, + }, + { + deleteKey: "c", + options: config.DeleteOptions{FromKey: true}, + wantDeleted: 3, + wantKeys: []string{"a", "b"}, + }, + { + deleteKey: "e", + wantDeleted: 0, + wantKeys: kvs, + }, + } + for _, tt := range tests { + for i := range kvs { + if err := cc.Put(kvs[i], "bar"); err != nil { + t.Fatalf("count not put key %q, err: %s", kvs[i], err) + } + } + del, err := cc.Delete(tt.deleteKey, tt.options) + if err != nil { + t.Fatalf("count not get key %q, err: %s", tt.deleteKey, err) + } + assert.Equal(t, tt.wantDeleted, int(del.Deleted)) + get, err := cc.Get("", config.GetOptions{Prefix: true}) + if err != nil { + t.Fatalf("count not get key, err: %s", err) + } + kvs := testutils.KeysFromGetResponse(get) + assert.Equal(t, tt.wantKeys, kvs) + } + }) + }) + } +} diff --git a/tests/e2e/ctl_v3_kv_test.go b/tests/e2e/ctl_v3_kv_test.go index ae83ee1272d9..e23d02612490 100644 --- a/tests/e2e/ctl_v3_kv_test.go +++ b/tests/e2e/ctl_v3_kv_test.go @@ -36,11 +36,7 @@ func TestCtlV3GetRev(t *testing.T) { testCtl(t, getRevTest) } func TestCtlV3GetKeysOnly(t *testing.T) { testCtl(t, getKeysOnlyTest) } func TestCtlV3GetCountOnly(t *testing.T) { testCtl(t, getCountOnlyTest) } -func TestCtlV3Del(t *testing.T) { testCtl(t, delTest) } -func TestCtlV3DelNoTLS(t *testing.T) { testCtl(t, delTest, withCfg(*e2e.NewConfigNoTLS())) } -func TestCtlV3DelClientTLS(t *testing.T) { testCtl(t, delTest, withCfg(*e2e.NewConfigClientTLS())) } -func TestCtlV3DelPeerTLS(t *testing.T) { testCtl(t, delTest, withCfg(*e2e.NewConfigPeerTLS())) } -func TestCtlV3DelTimeout(t *testing.T) { testCtl(t, delTest, withDialTimeout(0)) } +func TestCtlV3DelTimeout(t *testing.T) { testCtl(t, delTest, withDialTimeout(0)) } func TestCtlV3GetRevokedCRL(t *testing.T) { cfg := e2e.EtcdProcessClusterConfig{ diff --git a/tests/framework/config/client.go b/tests/framework/config/client.go index 47c7ba13864c..e4ad1a46cd58 100644 --- a/tests/framework/config/client.go +++ b/tests/framework/config/client.go @@ -27,3 +27,9 @@ type GetOptions struct { Order clientv3.SortOrder SortBy clientv3.SortTarget } + +type DeleteOptions struct { + Prefix bool + FromKey bool + End string +} diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index 5d79bbfbba5f..3846b79a5bd8 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -98,7 +98,7 @@ func (ctl *EtcdctlV3) Get(key string, o config.GetOptions) (*clientv3.GetRespons _, err := cmd.Expect("Count") return &resp, err } - line, err := cmd.Expect("kvs") + line, err := cmd.Expect("header") if err != nil { return nil, err } @@ -110,6 +110,31 @@ func (ctl *EtcdctlV3) Put(key, value string) error { return SpawnWithExpect(ctl.cmdArgs("put", key, value), "OK") } +func (ctl *EtcdctlV3) Delete(key string, o config.DeleteOptions) (*clientv3.DeleteResponse, error) { + args := ctl.cmdArgs() + args = append(args, "del", key, "-w", "json") + if o.End != "" { + args = append(args, o.End) + } + if o.Prefix { + args = append(args, "--prefix") + } + if o.FromKey { + args = append(args, "--from-key") + } + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp clientv3.DeleteResponse + line, err := cmd.Expect("header") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return &resp, err +} + func (ctl *EtcdctlV3) cmdArgs(args ...string) []string { cmdArgs := []string{CtlBinPath + "3"} for k, v := range ctl.flags() { diff --git a/tests/framework/integration.go b/tests/framework/integration.go index d484fe580462..baf260f932e0 100644 --- a/tests/framework/integration.go +++ b/tests/framework/integration.go @@ -127,3 +127,17 @@ func (c integrationClient) Put(key, value string) error { _, err := c.Client.Put(context.Background(), key, value) return err } + +func (c integrationClient) Delete(key string, o config.DeleteOptions) (*clientv3.DeleteResponse, error) { + clientOpts := []clientv3.OpOption{} + if o.Prefix { + clientOpts = append(clientOpts, clientv3.WithPrefix()) + } + if o.FromKey { + clientOpts = append(clientOpts, clientv3.WithFromKey()) + } + if o.End != "" { + clientOpts = append(clientOpts, clientv3.WithRange(o.End)) + } + return c.Client.Delete(context.Background(), key, clientOpts...) +} diff --git a/tests/framework/interface.go b/tests/framework/interface.go index fb57b92ec267..4a5225dc176d 100644 --- a/tests/framework/interface.go +++ b/tests/framework/interface.go @@ -35,4 +35,5 @@ type Cluster interface { type Client interface { Put(key, value string) error Get(key string, opts config.GetOptions) (*clientv3.GetResponse, error) + Delete(key string, opts config.DeleteOptions) (*clientv3.DeleteResponse, error) } diff --git a/tests/integration/clientv3/kv_test.go b/tests/integration/clientv3/kv_test.go index 9d164760cd6c..ddb8db99e551 100644 --- a/tests/integration/clientv3/kv_test.go +++ b/tests/integration/clientv3/kv_test.go @@ -358,27 +358,6 @@ func TestKVDeleteRange(t *testing.T) { wkeys []string }{ - // [a, c) - { - key: "a", - opts: []clientv3.OpOption{clientv3.WithRange("c")}, - - wkeys: []string{"c", "c/abc", "d"}, - }, - // >= c - { - key: "c", - opts: []clientv3.OpOption{clientv3.WithFromKey()}, - - wkeys: []string{"a", "b"}, - }, - // c* - { - key: "c", - opts: []clientv3.OpOption{clientv3.WithPrefix()}, - - wkeys: []string{"a", "b", "d"}, - }, // * { key: "\x00", @@ -415,38 +394,6 @@ func TestKVDeleteRange(t *testing.T) { } } -func TestKVDelete(t *testing.T) { - integration2.BeforeTest(t) - - clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3}) - defer clus.Terminate(t) - - kv := clus.RandClient() - ctx := context.TODO() - - presp, err := kv.Put(ctx, "foo", "") - if err != nil { - t.Fatalf("couldn't put 'foo' (%v)", err) - } - if presp.Header.Revision != 2 { - t.Fatalf("presp.Header.Revision got %d, want %d", presp.Header.Revision, 2) - } - resp, err := kv.Delete(ctx, "foo") - if err != nil { - t.Fatalf("couldn't delete key (%v)", err) - } - if resp.Header.Revision != 3 { - t.Fatalf("resp.Header.Revision got %d, want %d", resp.Header.Revision, 3) - } - gresp, err := kv.Get(ctx, "foo") - if err != nil { - t.Fatalf("couldn't get key (%v)", err) - } - if len(gresp.Kvs) > 0 { - t.Fatalf("gresp.Kvs got %+v, want none", gresp.Kvs) - } -} - func TestKVCompactError(t *testing.T) { integration2.BeforeTest(t)