diff --git a/scripts/test.sh b/scripts/test.sh index e8047ccb5..687b32433 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -107,14 +107,15 @@ function integration_extra { } function integration_pass { - local pkgs=${USERPKG:-"./integration/..."} - run_for_module "tests" go_test "${pkgs}" "parallel" : -timeout="${TIMEOUT:-15m}" "${COMMON_TEST_FLAGS[@]}" "${RUN_ARG[@]}" "$@" || return $? + run_for_module "tests" go_test "./integration/..." "parallel" : -timeout="${TIMEOUT:-15m}" "${COMMON_TEST_FLAGS[@]}" "${RUN_ARG[@]}" "$@" || return $? + run_for_module "tests" go_test "./common/..." "parallel" : --tags=integration -timeout="${TIMEOUT:-15m}" "${COMMON_TEST_FLAGS[@]}" "${RUN_ARG[@]}" "$@" || return $? integration_extra "$@" } function e2e_pass { # e2e tests are running pre-build binary. Settings like --race,-cover,-cpu does not have any impact. run_for_module "tests" go_test "./e2e/..." "keep_going" : -timeout="${TIMEOUT:-30m}" "${RUN_ARG[@]}" "$@" + run_for_module "tests" go_test "./common/..." "keep_going" : --tags=e2e -timeout="${TIMEOUT:-30m}" "${RUN_ARG[@]}" "$@" } function integration_e2e_pass { diff --git a/tests/common/e2e_test.go b/tests/common/e2e_test.go new file mode 100644 index 000000000..e0f29ce39 --- /dev/null +++ b/tests/common/e2e_test.go @@ -0,0 +1,24 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build e2e +// +build e2e + +package common + +import "go.etcd.io/etcd/tests/v3/framework" + +func init() { + testFramework = framework.RunE2eTests +} diff --git a/tests/common/integration_test.go b/tests/common/integration_test.go new file mode 100644 index 000000000..7c7ccc224 --- /dev/null +++ b/tests/common/integration_test.go @@ -0,0 +1,26 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build integration +// +build integration + +package common + +import ( + "go.etcd.io/etcd/tests/v3/framework" +) + +func init() { + testFramework = framework.RunIntegrationTests +} diff --git a/tests/common/kv_test.go b/tests/common/kv_test.go new file mode 100644 index 000000000..868fa3f3e --- /dev/null +++ b/tests/common/kv_test.go @@ -0,0 +1,50 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "testing" + "time" + + "go.etcd.io/etcd/tests/v3/framework/testutils" +) + +func TestKVPut(t *testing.T) { + testFramework.BeforeTest(t) + clus := testFramework.NewCluster(t) + defer clus.Close() + cc := clus.Client() + + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + key, value := "foo", "bar" + + if err := cc.Put(key, value); err != nil { + t.Fatalf("count not put key %q, err: %s", key, err) + } + resp, err := cc.Get(key, testutils.WithSerializable()) + if err != nil { + t.Fatalf("count not get key %q, err: %s", key, err) + } + if len(resp.Kvs) != 1 { + t.Errorf("Unexpected lenth of response, got %d", len(resp.Kvs)) + } + if string(resp.Kvs[0].Key) != key { + t.Errorf("Unexpected key, want %q, got %q", key, resp.Kvs[0].Key) + } + if string(resp.Kvs[0].Value) != value { + t.Errorf("Unexpected value, want %q, got %q", value, resp.Kvs[0].Value) + } + }) +} diff --git a/tests/common/main_test.go b/tests/common/main_test.go new file mode 100644 index 000000000..b3987e1b5 --- /dev/null +++ b/tests/common/main_test.go @@ -0,0 +1,27 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "testing" + + "go.etcd.io/etcd/tests/v3/framework" +) + +var testFramework = framework.TestFramework + +func TestMain(m *testing.M) { + testFramework.TestMain(m) +} diff --git a/tests/e2e/cluster_downgrade_test.go b/tests/e2e/cluster_downgrade_test.go index 93a54884b..06031ea48 100644 --- a/tests/e2e/cluster_downgrade_test.go +++ b/tests/e2e/cluster_downgrade_test.go @@ -23,6 +23,7 @@ import ( "go.etcd.io/etcd/api/v3/version" "go.etcd.io/etcd/client/pkg/v3/fileutil" "go.etcd.io/etcd/tests/v3/framework/e2e" + "go.etcd.io/etcd/tests/v3/framework/testutils" ) func TestDowngradeUpgrade(t *testing.T) { @@ -78,7 +79,7 @@ func startEtcd(t *testing.T, execPath, dataDirPath string) *e2e.EtcdProcessClust func downgradeEnable(t *testing.T, epc *e2e.EtcdProcessCluster, ver semver.Version) { t.Log("etcdctl downgrade...") c := e2e.NewEtcdctl(epc.Cfg, epc.EndpointsV3()) - e2e.ExecuteWithTimeout(t, 20*time.Second, func() { + testutils.ExecuteWithTimeout(t, 20*time.Second, func() { err := c.DowngradeEnable(ver.String()) if err != nil { t.Fatal(err) @@ -96,7 +97,7 @@ func stopEtcd(t *testing.T, epc *e2e.EtcdProcessCluster) { func validateVersion(t *testing.T, epc *e2e.EtcdProcessCluster, expect version.Versions) { t.Log("Validate version") // Two separate calls to expect as it doesn't support multiple matches on the same line - e2e.ExecuteWithTimeout(t, 20*time.Second, func() { + testutils.ExecuteWithTimeout(t, 20*time.Second, func() { if expect.Server != "" { err := e2e.SpawnWithExpects(e2e.CURLPrefixArgs(epc, "GET", e2e.CURLReq{Endpoint: "/version"}), nil, `"etcdserver":"`+expect.Server) if err != nil { @@ -114,7 +115,7 @@ func validateVersion(t *testing.T, epc *e2e.EtcdProcessCluster, expect version.V func expectLog(t *testing.T, epc *e2e.EtcdProcessCluster, expectLog string) { t.Helper() - e2e.ExecuteWithTimeout(t, 30*time.Second, func() { + testutils.ExecuteWithTimeout(t, 30*time.Second, func() { _, err := epc.Procs[0].Logs().Expect(expectLog) if err != nil { t.Fatal(err) diff --git a/tests/e2e/ctl_v3_grpc_test.go b/tests/e2e/ctl_v3_grpc_test.go index d3cc101a6..8c8be2c69 100644 --- a/tests/e2e/ctl_v3_grpc_test.go +++ b/tests/e2e/ctl_v3_grpc_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "go.etcd.io/etcd/tests/v3/framework/e2e" + "go.etcd.io/etcd/tests/v3/framework/testutils" ) func TestAuthority(t *testing.T) { @@ -104,7 +105,7 @@ func TestAuthority(t *testing.T) { t.Fatal(err) } - e2e.ExecuteWithTimeout(t, 5*time.Second, func() { + testutils.ExecuteWithTimeout(t, 5*time.Second, func() { assertAuthority(t, fmt.Sprintf(tc.expectAuthorityPattern, 20000), epc) }) }) diff --git a/tests/e2e/ctl_v3_kv_test.go b/tests/e2e/ctl_v3_kv_test.go index 69f639be7..d74434ab2 100644 --- a/tests/e2e/ctl_v3_kv_test.go +++ b/tests/e2e/ctl_v3_kv_test.go @@ -18,12 +18,10 @@ import ( "fmt" "strings" "testing" - "time" "go.etcd.io/etcd/tests/v3/framework/e2e" ) -func TestCtlV3Put(t *testing.T) { testCtl(t, putTest, withDialTimeout(7*time.Second)) } func TestCtlV3PutNoTLS(t *testing.T) { testCtl(t, putTest, withCfg(*e2e.NewConfigNoTLS())) } func TestCtlV3PutClientTLS(t *testing.T) { testCtl(t, putTest, withCfg(*e2e.NewConfigClientTLS())) } func TestCtlV3PutClientAutoTLS(t *testing.T) { diff --git a/tests/framework/default.go b/tests/framework/default.go new file mode 100644 index 000000000..d34dc6462 --- /dev/null +++ b/tests/framework/default.go @@ -0,0 +1,44 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package framework + +import ( + "flag" + "fmt" + "os" + "testing" + + "go.etcd.io/etcd/client/pkg/v3/testutil" +) + +type noFrameworkSelected struct{} + +var _ testFramework = (*noFrameworkSelected)(nil) + +func (e noFrameworkSelected) TestMain(m *testing.M) { + flag.Parse() + if !testing.Short() { + fmt.Println(`No test mode selected, please selected either e2e mode with "--tags e2e" or integration mode with "--tags integration"`) + os.Exit(1) + } +} + +func (e noFrameworkSelected) BeforeTest(t testing.TB) { + testutil.SkipTestIfShortMode(t, "Cannot create clusters in --short tests") +} + +func (e noFrameworkSelected) NewCluster(t testing.TB) Cluster { + return nil +} diff --git a/tests/framework/e2e.go b/tests/framework/e2e.go new file mode 100644 index 000000000..7dfc4b611 --- /dev/null +++ b/tests/framework/e2e.go @@ -0,0 +1,68 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package framework + +import ( + "os" + "testing" + + "go.etcd.io/etcd/client/pkg/v3/testutil" + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/framework/e2e" + "go.etcd.io/etcd/tests/v3/framework/testutils" +) + +type e2eFramework struct{} + +func (e e2eFramework) TestMain(m *testing.M) { + e2e.InitFlags() + v := m.Run() + if v == 0 && testutil.CheckLeakedGoroutine() { + os.Exit(1) + } + os.Exit(v) +} + +func (e e2eFramework) BeforeTest(t testing.TB) { + e2e.BeforeTest(t) +} + +func (e e2eFramework) NewCluster(t testing.TB) Cluster { + epc, err := e2e.NewEtcdProcessCluster(t, e2e.ConfigStandalone(*e2e.NewConfigAutoTLS())) + if err != nil { + t.Fatalf("could not start etcd integrationCluster: %s", err) + } + return &e2eCluster{*epc} +} + +type e2eCluster struct { + e2e.EtcdProcessCluster +} + +func (c *e2eCluster) Client() Client { + return e2eClient{e2e.NewEtcdctl(c.Cfg, c.EndpointsV3())} +} + +type e2eClient struct { + *e2e.EtcdctlV3 +} + +func (c e2eClient) Get(key string, opts ...testutils.GetOption) (*clientv3.GetResponse, error) { + o := testutils.GetOptions{} + for _, opt := range opts { + opt(&o) + } + return c.EtcdctlV3.Get(key, o.Serializable) +} diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index 86940eee7..68e1e438e 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -15,31 +15,52 @@ package e2e import ( + "encoding/json" "fmt" "strings" + + clientv3 "go.etcd.io/etcd/client/v3" ) -type etcdctlV3 struct { +type EtcdctlV3 struct { cfg *EtcdProcessClusterConfig endpoints []string } -func NewEtcdctl(cfg *EtcdProcessClusterConfig, endpoints []string) *etcdctlV3 { - return &etcdctlV3{ +func NewEtcdctl(cfg *EtcdProcessClusterConfig, endpoints []string) *EtcdctlV3 { + return &EtcdctlV3{ cfg: cfg, endpoints: endpoints, } } -func (ctl *etcdctlV3) Put(key, value string) error { - return SpawnWithExpect(ctl.cmdArgs("put", key, value), "OK") -} - -func (ctl *etcdctlV3) DowngradeEnable(version string) error { +func (ctl *EtcdctlV3) DowngradeEnable(version string) error { return SpawnWithExpect(ctl.cmdArgs("downgrade", "enable", version), "Downgrade enable success") } -func (ctl *etcdctlV3) cmdArgs(args ...string) []string { +func (ctl *EtcdctlV3) Get(key string, serializable bool) (*clientv3.GetResponse, error) { + args := ctl.cmdArgs() + if serializable { + args = append(args, "--consistency", "s") + } + cmd, err := SpawnCmd(append(args, "get", key, "-w", "json"), nil) + if err != nil { + return nil, err + } + line, err := cmd.Expect("kvs") + if err != nil { + return nil, err + } + var resp clientv3.GetResponse + err = json.Unmarshal([]byte(line), &resp) + return &resp, err +} + +func (ctl *EtcdctlV3) Put(key, value string) error { + return SpawnWithExpect(ctl.cmdArgs("put", key, value), "OK") +} + +func (ctl *EtcdctlV3) cmdArgs(args ...string) []string { cmdArgs := []string{CtlBinPath + "3"} for k, v := range ctl.flags() { cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", k, v)) @@ -47,7 +68,7 @@ func (ctl *etcdctlV3) cmdArgs(args ...string) []string { return append(cmdArgs, args...) } -func (ctl *etcdctlV3) flags() map[string]string { +func (ctl *EtcdctlV3) flags() map[string]string { fmap := make(map[string]string) if ctl.cfg.ClientTLS == ClientTLS { if ctl.cfg.IsClientAutoTLS { diff --git a/tests/framework/e2e/util.go b/tests/framework/e2e/util.go index 8ea76ee40..7d997e08a 100644 --- a/tests/framework/e2e/util.go +++ b/tests/framework/e2e/util.go @@ -119,20 +119,6 @@ func SkipInShortMode(t testing.TB) { testutil.SkipTestIfShortMode(t, "e2e tests are not running in --short mode") } -func ExecuteWithTimeout(t *testing.T, timeout time.Duration, f func()) { - donec := make(chan struct{}) - go func() { - defer close(donec) - f() - }() - - select { - case <-time.After(timeout): - testutil.FatalStack(t, fmt.Sprintf("test timed out after %v", timeout)) - case <-donec: - } -} - func mergeEnvVariables(envVars map[string]string) []string { var env []string // Environment variables are passed as parameter have higher priority diff --git a/tests/framework/framework.go b/tests/framework/framework.go new file mode 100644 index 000000000..d07968e73 --- /dev/null +++ b/tests/framework/framework.go @@ -0,0 +1,21 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package framework + +var ( + TestFramework testFramework = noFrameworkSelected{} + RunE2eTests testFramework = e2eFramework{} + RunIntegrationTests testFramework = integrationFramework{} +) diff --git a/tests/framework/integration.go b/tests/framework/integration.go new file mode 100644 index 000000000..46eb83d83 --- /dev/null +++ b/tests/framework/integration.go @@ -0,0 +1,81 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package framework + +import ( + "context" + "testing" + + "go.etcd.io/etcd/client/pkg/v3/testutil" + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/framework/integration" + "go.etcd.io/etcd/tests/v3/framework/testutils" +) + +type integrationFramework struct{} + +func (e integrationFramework) TestMain(m *testing.M) { + testutil.MustTestMainWithLeakDetection(m) +} + +func (e integrationFramework) BeforeTest(t testing.TB) { + integration.BeforeTest(t) +} + +func (e integrationFramework) NewCluster(t testing.TB) Cluster { + return &integrationCluster{ + Cluster: integration.NewCluster(t, &integration.ClusterConfig{Size: 1}), + t: t, + } +} + +type integrationCluster struct { + *integration.Cluster + t testing.TB +} + +func (c *integrationCluster) Close() error { + c.Terminate(c.t) + return nil +} + +func (c *integrationCluster) Client() Client { + cc, err := c.ClusterClient() + if err != nil { + c.t.Fatal(err) + } + return &integrationClient{cc} +} + +type integrationClient struct { + *clientv3.Client +} + +func (c integrationClient) Get(key string, opts ...testutils.GetOption) (*clientv3.GetResponse, error) { + o := testutils.GetOptions{} + for _, opt := range opts { + opt(&o) + } + clientOpts := []clientv3.OpOption{} + if o.Serializable { + clientOpts = append(clientOpts, clientv3.WithSerializable()) + } + return c.Client.Get(context.Background(), key, clientOpts...) +} + +func (c integrationClient) Put(key, value string) error { + _, err := c.Client.Put(context.Background(), key, value) + return err +} diff --git a/tests/framework/interface.go b/tests/framework/interface.go new file mode 100644 index 000000000..273ed4532 --- /dev/null +++ b/tests/framework/interface.go @@ -0,0 +1,38 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package framework + +import ( + "testing" + + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/framework/testutils" +) + +type testFramework interface { + TestMain(m *testing.M) + BeforeTest(testing.TB) + NewCluster(testing.TB) Cluster +} + +type Cluster interface { + Close() error + Client() Client +} + +type Client interface { + Put(key, value string) error + Get(key string, opts ...testutils.GetOption) (*clientv3.GetResponse, error) +} diff --git a/tests/framework/testutils/options.go b/tests/framework/testutils/options.go new file mode 100644 index 000000000..49cfb8d09 --- /dev/null +++ b/tests/framework/testutils/options.go @@ -0,0 +1,27 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testutils + +type GetOptions struct { + Serializable bool +} + +type GetOption func(*GetOptions) + +func WithSerializable() GetOption { + return func(options *GetOptions) { + options.Serializable = true + } +} diff --git a/tests/framework/testutils/util.go b/tests/framework/testutils/util.go new file mode 100644 index 000000000..2c6564afd --- /dev/null +++ b/tests/framework/testutils/util.go @@ -0,0 +1,37 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testutils + +import ( + "fmt" + "testing" + "time" + + "go.etcd.io/etcd/client/pkg/v3/testutil" +) + +func ExecuteWithTimeout(t *testing.T, timeout time.Duration, f func()) { + donec := make(chan struct{}) + go func() { + defer close(donec) + f() + }() + + select { + case <-time.After(timeout): + testutil.FatalStack(t, fmt.Sprintf("test timed out after %v", timeout)) + case <-donec: + } +} diff --git a/tests/integration/clientv3/kv_test.go b/tests/integration/clientv3/kv_test.go index 27c7d9329..fe0b5a2c4 100644 --- a/tests/integration/clientv3/kv_test.go +++ b/tests/integration/clientv3/kv_test.go @@ -71,7 +71,7 @@ func TestKVPutError(t *testing.T) { } } -func TestKVPut(t *testing.T) { +func TestKVPutWithLease(t *testing.T) { integration2.BeforeTest(t) clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3}) @@ -82,36 +82,28 @@ func TestKVPut(t *testing.T) { kv := clus.RandClient() ctx := context.TODO() - resp, err := lapi.Grant(context.Background(), 10) + lease, err := lapi.Grant(context.Background(), 10) if err != nil { t.Fatalf("failed to create lease %v", err) } - tests := []struct { - key, val string - leaseID clientv3.LeaseID - }{ - {"foo", "bar", clientv3.NoLease}, - {"hello", "world", resp.ID}, + key := "hello" + val := "world" + if _, err := kv.Put(ctx, key, val, clientv3.WithLease(lease.ID)); err != nil { + t.Fatalf("couldn't put %q (%v)", key, err) } - - for i, tt := range tests { - if _, err := kv.Put(ctx, tt.key, tt.val, clientv3.WithLease(tt.leaseID)); err != nil { - t.Fatalf("#%d: couldn't put %q (%v)", i, tt.key, err) - } - resp, err := kv.Get(ctx, tt.key) - if err != nil { - t.Fatalf("#%d: couldn't get key (%v)", i, err) - } - if len(resp.Kvs) != 1 { - t.Fatalf("#%d: expected 1 key, got %d", i, len(resp.Kvs)) - } - if !bytes.Equal([]byte(tt.val), resp.Kvs[0].Value) { - t.Errorf("#%d: val = %s, want %s", i, tt.val, resp.Kvs[0].Value) - } - if tt.leaseID != clientv3.LeaseID(resp.Kvs[0].Lease) { - t.Errorf("#%d: val = %d, want %d", i, tt.leaseID, resp.Kvs[0].Lease) - } + resp, err := kv.Get(ctx, key) + if err != nil { + t.Fatalf("couldn't get key (%v)", err) + } + if len(resp.Kvs) != 1 { + t.Fatalf("expected 1 key, got %d", len(resp.Kvs)) + } + if !bytes.Equal([]byte(val), resp.Kvs[0].Value) { + t.Errorf("val = %s, want %s", val, resp.Kvs[0].Value) + } + if lease.ID != clientv3.LeaseID(resp.Kvs[0].Lease) { + t.Errorf("val = %d, want %d", lease.ID, resp.Kvs[0].Lease) } }