From f67956cb7a7cfa3eb229793c62017bed60228df9 Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Tue, 6 Oct 2020 22:36:16 +0200 Subject: [PATCH] clientv3: Expose clientv3/examples close to the code. Many of the tests had missing '// Output:' comment, so were not runnable. They required fining. --- .words | 1 + clientv3/example_auth_test.go | 1 + clientv3/example_cluster_test.go | 1 + clientv3/example_kv_test.go | 1 + clientv3/example_lease_test.go | 1 + clientv3/example_maintenance_test.go | 1 + clientv3/example_metrics_test.go | 1 + clientv3/example_test.go | 1 + clientv3/example_watch_test.go | 1 + clientv3/main_test.go | 41 ++ etcdserver/v3_server.go | 2 + test | 3 - .../clientv3/examples/example_auth_test.go | 163 +++--- .../clientv3/examples/example_cluster_test.go | 219 +++++--- .../clientv3/examples/example_kv_test.go | 497 ++++++++++-------- .../clientv3/examples/example_lease_test.go | 226 ++++---- .../examples/example_maintenance_test.go | 69 +-- .../clientv3/examples/example_metrics_test.go | 104 ++-- .../clientv3/examples/example_test.go | 80 +-- .../clientv3/examples/example_watch_test.go | 177 ++++--- .../clientv3/examples/main_test.go | 70 +-- tests/integration/cluster.go | 15 +- tests/integration/lazy_cluster.go | 37 +- 23 files changed, 995 insertions(+), 717 deletions(-) create mode 120000 clientv3/example_auth_test.go create mode 120000 clientv3/example_cluster_test.go create mode 120000 clientv3/example_kv_test.go create mode 120000 clientv3/example_lease_test.go create mode 120000 clientv3/example_maintenance_test.go create mode 120000 clientv3/example_metrics_test.go create mode 120000 clientv3/example_test.go create mode 120000 clientv3/example_watch_test.go create mode 100644 clientv3/main_test.go diff --git a/.words b/.words index 836433936..da36ba44b 100644 --- a/.words +++ b/.words @@ -108,6 +108,7 @@ uncontended unfreed unlisting unprefixed +WatchProgressNotifyInterval WAL WithBackoff WithDialer diff --git a/clientv3/example_auth_test.go b/clientv3/example_auth_test.go new file mode 120000 index 000000000..d01e01cbb --- /dev/null +++ b/clientv3/example_auth_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_auth_test.go \ No newline at end of file diff --git a/clientv3/example_cluster_test.go b/clientv3/example_cluster_test.go new file mode 120000 index 000000000..303ab5f09 --- /dev/null +++ b/clientv3/example_cluster_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_cluster_test.go \ No newline at end of file diff --git a/clientv3/example_kv_test.go b/clientv3/example_kv_test.go new file mode 120000 index 000000000..db886dbbd --- /dev/null +++ b/clientv3/example_kv_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_kv_test.go \ No newline at end of file diff --git a/clientv3/example_lease_test.go b/clientv3/example_lease_test.go new file mode 120000 index 000000000..c7b6c90d2 --- /dev/null +++ b/clientv3/example_lease_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_lease_test.go \ No newline at end of file diff --git a/clientv3/example_maintenance_test.go b/clientv3/example_maintenance_test.go new file mode 120000 index 000000000..c2c4035c4 --- /dev/null +++ b/clientv3/example_maintenance_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_maintenance_test.go \ No newline at end of file diff --git a/clientv3/example_metrics_test.go b/clientv3/example_metrics_test.go new file mode 120000 index 000000000..868acb33e --- /dev/null +++ b/clientv3/example_metrics_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_metrics_test.go \ No newline at end of file diff --git a/clientv3/example_test.go b/clientv3/example_test.go new file mode 120000 index 000000000..2c85ca955 --- /dev/null +++ b/clientv3/example_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_test.go \ No newline at end of file diff --git a/clientv3/example_watch_test.go b/clientv3/example_watch_test.go new file mode 120000 index 000000000..662d28083 --- /dev/null +++ b/clientv3/example_watch_test.go @@ -0,0 +1 @@ +../tests/integration/clientv3/examples/example_watch_test.go \ No newline at end of file diff --git a/clientv3/main_test.go b/clientv3/main_test.go new file mode 100644 index 000000000..b10e4d334 --- /dev/null +++ b/clientv3/main_test.go @@ -0,0 +1,41 @@ +// Copyright 2017 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 clientv3_test + +import ( + "testing" + "time" + + "go.etcd.io/etcd/v3/pkg/testutil" +) + +const ( + dialTimeout = 5 * time.Second + requestTimeout = 10 * time.Second +) + +func exampleEndpoints() []string { return nil } + +func forUnitTestsRunInMockedContext(mocking func(), example func()) { + mocking() + // TODO: Call 'example' when mocking() provides realistic mocking of transport. + + // The real testing logic of examples gets executed + // as part of ./tests/integration/clientv3/integration/... +} + +func TestMain(m *testing.M) { + testutil.MustTestMainWithLeakDetection(m) +} diff --git a/etcdserver/v3_server.go b/etcdserver/v3_server.go index 8c3881b33..4be2b519f 100644 --- a/etcdserver/v3_server.go +++ b/etcdserver/v3_server.go @@ -303,6 +303,8 @@ func (s *EtcdServer) LeaseRenew(ctx context.Context, id lease.LeaseID) (int64, e return ttl, err } } + // Throttle in case of e.g. connection problems. + time.Sleep(50 * time.Millisecond) } if cctx.Err() == context.DeadlineExceeded { diff --git a/test b/test index cd5f1622f..9c38ca0e6 100755 --- a/test +++ b/test @@ -94,9 +94,6 @@ function unit_pass { function integration_extra { if [ -z "${PKG}" ] ; then - if [[ -z "${RUN_ARG[*]}" ]]; then - run_for_module "tests" go_test "./integration/..." "keep_going" : -timeout="${TIMEOUT:-5m}" "${COMMON_TEST_FLAGS[@]}" --run=Example "$@" || return $? - fi run_for_module "." go_test "./contrib/raftexample" "keep_going" : -timeout="${TIMEOUT:-5m}" "${RUN_ARG[@]}" "${COMMON_TEST_FLAGS[@]}" "$@" || return $? run_for_module "tests" go_test "./integration/v2store/..." "keep_going" : -tags v2v3 -timeout="${TIMEOUT:-5m}" "${RUN_ARG[@]}" "${COMMON_TEST_FLAGS[@]}" "$@" || return $? else diff --git a/tests/integration/clientv3/examples/example_auth_test.go b/tests/integration/clientv3/examples/example_auth_test.go index 4bbb4492f..afb35736b 100644 --- a/tests/integration/clientv3/examples/example_auth_test.go +++ b/tests/integration/clientv3/examples/example_auth_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" @@ -22,92 +22,101 @@ import ( "go.etcd.io/etcd/v3/clientv3" ) +func mockAuth() { + fmt.Println(`etcdserver: permission denied`) + fmt.Println(`user u permission: key "foo", range end "zoo"`) +} + func ExampleAuth() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() + forUnitTestsRunInMockedContext( + mockAuth, + func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() - if _, err = cli.RoleAdd(context.TODO(), "root"); err != nil { - log.Fatal(err) - } - if _, err = cli.UserAdd(context.TODO(), "root", "123"); err != nil { - log.Fatal(err) - } - if _, err = cli.UserGrantRole(context.TODO(), "root", "root"); err != nil { - log.Fatal(err) - } + if _, err = cli.RoleAdd(context.TODO(), "root"); err != nil { + log.Fatal(err) + } + if _, err = cli.UserAdd(context.TODO(), "root", "123"); err != nil { + log.Fatal(err) + } + if _, err = cli.UserGrantRole(context.TODO(), "root", "root"); err != nil { + log.Fatal(err) + } - if _, err = cli.RoleAdd(context.TODO(), "r"); err != nil { - log.Fatal(err) - } + if _, err = cli.RoleAdd(context.TODO(), "r"); err != nil { + log.Fatal(err) + } - if _, err = cli.RoleGrantPermission( - context.TODO(), - "r", // role name - "foo", // key - "zoo", // range end - clientv3.PermissionType(clientv3.PermReadWrite), - ); err != nil { - log.Fatal(err) - } - if _, err = cli.UserAdd(context.TODO(), "u", "123"); err != nil { - log.Fatal(err) - } - if _, err = cli.UserGrantRole(context.TODO(), "u", "r"); err != nil { - log.Fatal(err) - } - if _, err = cli.AuthEnable(context.TODO()); err != nil { - log.Fatal(err) - } + if _, err = cli.RoleGrantPermission( + context.TODO(), + "r", // role name + "foo", // key + "zoo", // range end + clientv3.PermissionType(clientv3.PermReadWrite), + ); err != nil { + log.Fatal(err) + } + if _, err = cli.UserAdd(context.TODO(), "u", "123"); err != nil { + log.Fatal(err) + } + if _, err = cli.UserGrantRole(context.TODO(), "u", "r"); err != nil { + log.Fatal(err) + } + if _, err = cli.AuthEnable(context.TODO()); err != nil { + log.Fatal(err) + } - cliAuth, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - Username: "u", - Password: "123", - }) - if err != nil { - log.Fatal(err) - } - defer cliAuth.Close() + cliAuth, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + Username: "u", + Password: "123", + }) + if err != nil { + log.Fatal(err) + } + defer cliAuth.Close() - if _, err = cliAuth.Put(context.TODO(), "foo1", "bar"); err != nil { - log.Fatal(err) - } + if _, err = cliAuth.Put(context.TODO(), "foo1", "bar"); err != nil { + log.Fatal(err) + } - _, err = cliAuth.Txn(context.TODO()). - If(clientv3.Compare(clientv3.Value("zoo1"), ">", "abc")). - Then(clientv3.OpPut("zoo1", "XYZ")). - Else(clientv3.OpPut("zoo1", "ABC")). - Commit() - fmt.Println(err) + _, err = cliAuth.Txn(context.TODO()). + If(clientv3.Compare(clientv3.Value("zoo1"), ">", "abc")). + Then(clientv3.OpPut("zoo1", "XYZ")). + Else(clientv3.OpPut("zoo1", "ABC")). + Commit() + fmt.Println(err) - // now check the permission with the root account - rootCli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - Username: "root", - Password: "123", - }) - if err != nil { - log.Fatal(err) - } - defer rootCli.Close() + // now check the permission with the root account + rootCli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + Username: "root", + Password: "123", + }) + if err != nil { + log.Fatal(err) + } + defer rootCli.Close() - resp, err := rootCli.RoleGet(context.TODO(), "r") - if err != nil { - log.Fatal(err) - } - fmt.Printf("user u permission: key %q, range end %q\n", resp.Perm[0].Key, resp.Perm[0].RangeEnd) + resp, err := rootCli.RoleGet(context.TODO(), "r") + if err != nil { + log.Fatal(err) + } + fmt.Printf("user u permission: key %q, range end %q\n", resp.Perm[0].Key, resp.Perm[0].RangeEnd) - if _, err = rootCli.AuthDisable(context.TODO()); err != nil { - log.Fatal(err) - } + if _, err = rootCli.AuthDisable(context.TODO()); err != nil { + log.Fatal(err) + } + }) // Output: etcdserver: permission denied // user u permission: key "foo", range end "zoo" } diff --git a/tests/integration/clientv3/examples/example_cluster_test.go b/tests/integration/clientv3/examples/example_cluster_test.go index 0a5bf78f0..e7c4f22a8 100644 --- a/tests/integration/clientv3/examples/example_cluster_test.go +++ b/tests/integration/clientv3/examples/example_cluster_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" @@ -22,103 +22,160 @@ import ( "go.etcd.io/etcd/v3/clientv3" ) -func ExampleCluster_memberList() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() +func mockCluster_memberList() { + fmt.Println("members: 3") +} - resp, err := cli.MemberList(context.Background()) - if err != nil { - log.Fatal(err) - } - fmt.Println("members:", len(resp.Members)) +func ExampleCluster_memberList() { + forUnitTestsRunInMockedContext(mockCluster_memberList, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + resp, err := cli.MemberList(context.Background()) + if err != nil { + log.Fatal(err) + } + fmt.Println("members:", len(resp.Members)) + }) // Output: members: 3 } -func ExampleCluster_memberAdd() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints[:2], - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() +func mockCluster_memberAdd() { + fmt.Println("added member.PeerURLs: [http://localhost:32380]") + fmt.Println("members count: 4") +} - peerURLs := endpoints[2:] - mresp, err := cli.MemberAdd(context.Background(), peerURLs) - if err != nil { - log.Fatal(err) - } - fmt.Println("added member.PeerURLs:", mresp.Member.PeerURLs) +func ExampleCluster_memberAdd() { + forUnitTestsRunInMockedContext(mockCluster_memberAdd, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + // Add member 1: + mresp, err := cli.MemberAdd(context.Background(), []string{"http://localhost:32380"}) + if err != nil { + log.Fatal(err) + } + fmt.Println("added member.PeerURLs:", mresp.Member.PeerURLs) + fmt.Println("members count:", len(mresp.Members)) + + // Restore original cluster state + _, err = cli.MemberRemove(context.Background(), mresp.Member.ID) + if err != nil { + log.Fatal(err) + } + }) + // Output: // added member.PeerURLs: [http://localhost:32380] + // members count: 4 +} + +func mockCluster_memberAddAsLearner() { + fmt.Println("members count: 4") + fmt.Println("added member.IsLearner: true") } func ExampleCluster_memberAddAsLearner() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints[:2], - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() + forUnitTestsRunInMockedContext(mockCluster_memberAddAsLearner, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() - peerURLs := endpoints[2:] - mresp, err := cli.MemberAddAsLearner(context.Background(), peerURLs) - if err != nil { - log.Fatal(err) - } - fmt.Println("added member.PeerURLs:", mresp.Member.PeerURLs) - fmt.Println("added member.IsLearner:", mresp.Member.IsLearner) - // added member.PeerURLs: [http://localhost:32380] + mresp, err := cli.MemberAddAsLearner(context.Background(), []string{"http://localhost:32381"}) + if err != nil { + log.Fatal(err) + } + + // Restore original cluster state + _, err = cli.MemberRemove(context.Background(), mresp.Member.ID) + if err != nil { + log.Fatal(err) + } + + fmt.Println("members count:", len(mresp.Members)) + fmt.Println("added member.IsLearner:", mresp.Member.IsLearner) + }) + // Output: + // members count: 4 // added member.IsLearner: true } +func mockCluster_memberRemove() {} + func ExampleCluster_memberRemove() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints[1:], - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockCluster_memberRemove, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + resp, err := cli.MemberList(context.Background()) + if err != nil { + log.Fatal(err) + } + + _, err = cli.MemberRemove(context.Background(), resp.Members[0].ID) + if err != nil { + log.Fatal(err) + } + + // Restore original cluster: + _, err = cli.MemberAdd(context.Background(), resp.Members[0].PeerURLs) + if err != nil { + log.Fatal(err) + } }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - resp, err := cli.MemberList(context.Background()) - if err != nil { - log.Fatal(err) - } - - _, err = cli.MemberRemove(context.Background(), resp.Members[0].ID) - if err != nil { - log.Fatal(err) - } } +func mockCluster_memberUpdate() {} + func ExampleCluster_memberUpdate() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockCluster_memberUpdate, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + resp, err := cli.MemberList(context.Background()) + if err != nil { + log.Fatal(err) + } + + peerURLs := []string{"http://localhost:12380"} + _, err = cli.MemberUpdate(context.Background(), resp.Members[0].ID, peerURLs) + if err != nil { + log.Fatal(err) + } + + // Restore to mitigate impact on other tests: + _, err = cli.MemberUpdate(context.Background(), resp.Members[0].ID, resp.Members[0].PeerURLs) + if err != nil { + log.Fatal(err) + } }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - resp, err := cli.MemberList(context.Background()) - if err != nil { - log.Fatal(err) - } - - peerURLs := []string{"http://localhost:12380"} - _, err = cli.MemberUpdate(context.Background(), resp.Members[0].ID, peerURLs) - if err != nil { - log.Fatal(err) - } + // Output: } diff --git a/tests/integration/clientv3/examples/example_kv_test.go b/tests/integration/clientv3/examples/example_kv_test.go index 98e34c29f..23ba4a968 100644 --- a/tests/integration/clientv3/examples/example_kv_test.go +++ b/tests/integration/clientv3/examples/example_kv_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" @@ -23,259 +23,312 @@ import ( "go.etcd.io/etcd/v3/clientv3" ) +func mockKV_put() {} + func ExampleKV_put() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - _, err = cli.Put(ctx, "sample_key", "sample_value") - cancel() - if err != nil { - log.Fatal(err) - } -} - -func ExampleKV_putErrorHandling() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - _, err = cli.Put(ctx, "", "sample_value") - cancel() - if err != nil { - switch err { - case context.Canceled: - fmt.Printf("ctx is canceled by another routine: %v\n", err) - case context.DeadlineExceeded: - fmt.Printf("ctx is attached with a deadline is exceeded: %v\n", err) - case rpctypes.ErrEmptyKey: - fmt.Printf("client-side error: %v\n", err) - default: - fmt.Printf("bad cluster endpoints, which are not etcd servers: %v\n", err) + forUnitTestsRunInMockedContext(mockKV_put, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) } - } - // Output: client-side error: etcdserver: key is not provided -} + defer cli.Close() -func ExampleKV_get() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - _, err = cli.Put(context.TODO(), "foo", "bar") - if err != nil { - log.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - resp, err := cli.Get(ctx, "foo") - cancel() - if err != nil { - log.Fatal(err) - } - for _, ev := range resp.Kvs { - fmt.Printf("%s : %s\n", ev.Key, ev.Value) - } - // Output: foo : bar -} - -func ExampleKV_getWithRev() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - presp, err := cli.Put(context.TODO(), "foo", "bar1") - if err != nil { - log.Fatal(err) - } - _, err = cli.Put(context.TODO(), "foo", "bar2") - if err != nil { - log.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - resp, err := cli.Get(ctx, "foo", clientv3.WithRev(presp.Header.Revision)) - cancel() - if err != nil { - log.Fatal(err) - } - for _, ev := range resp.Kvs { - fmt.Printf("%s : %s\n", ev.Key, ev.Value) - } - // Output: foo : bar1 -} - -func ExampleKV_getSortedPrefix() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - for i := range make([]int, 3) { ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - _, err = cli.Put(ctx, fmt.Sprintf("key_%d", i), "value") + _, err = cli.Put(ctx, "sample_key", "sample_value") cancel() if err != nil { log.Fatal(err) } - } + }) + // Output: +} - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - resp, err := cli.Get(ctx, "key", clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortDescend)) - cancel() - if err != nil { - log.Fatal(err) - } - for _, ev := range resp.Kvs { - fmt.Printf("%s : %s\n", ev.Key, ev.Value) - } +func mockKV_putErrorHandling() { + fmt.Println("client-side error: etcdserver: key is not provided") +} + +func ExampleKV_putErrorHandling() { + forUnitTestsRunInMockedContext(mockKV_putErrorHandling, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + _, err = cli.Put(ctx, "", "sample_value") + cancel() + if err != nil { + switch err { + case context.Canceled: + fmt.Printf("ctx is canceled by another routine: %v\n", err) + case context.DeadlineExceeded: + fmt.Printf("ctx is attached with a deadline is exceeded: %v\n", err) + case rpctypes.ErrEmptyKey: + fmt.Printf("client-side error: %v\n", err) + default: + fmt.Printf("bad cluster endpoints, which are not etcd servers: %v\n", err) + } + } + }) + // Output: client-side error: etcdserver: key is not provided +} + +func mockKV_get() { + fmt.Println("foo : bar") +} + +func ExampleKV_get() { + forUnitTestsRunInMockedContext(mockKV_get, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + _, err = cli.Put(context.TODO(), "foo", "bar") + if err != nil { + log.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + resp, err := cli.Get(ctx, "foo") + cancel() + if err != nil { + log.Fatal(err) + } + for _, ev := range resp.Kvs { + fmt.Printf("%s : %s\n", ev.Key, ev.Value) + } + }) + // Output: foo : bar +} + +func mockKV_getWithRev() { + fmt.Println("foo : bar1") +} + +func ExampleKV_getWithRev() { + forUnitTestsRunInMockedContext(mockKV_getWithRev, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + presp, err := cli.Put(context.TODO(), "foo", "bar1") + if err != nil { + log.Fatal(err) + } + _, err = cli.Put(context.TODO(), "foo", "bar2") + if err != nil { + log.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + resp, err := cli.Get(ctx, "foo", clientv3.WithRev(presp.Header.Revision)) + cancel() + if err != nil { + log.Fatal(err) + } + for _, ev := range resp.Kvs { + fmt.Printf("%s : %s\n", ev.Key, ev.Value) + } + }) + // Output: foo : bar1 +} + +func mockKV_getSortedPrefix() { + fmt.Println(`key_2 : value`) + fmt.Println(`key_1 : value`) + fmt.Println(`key_0 : value`) +} + +func ExampleKV_getSortedPrefix() { + forUnitTestsRunInMockedContext(mockKV_getSortedPrefix, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + for i := range make([]int, 3) { + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + _, err = cli.Put(ctx, fmt.Sprintf("key_%d", i), "value") + cancel() + if err != nil { + log.Fatal(err) + } + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + resp, err := cli.Get(ctx, "key", clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortDescend)) + cancel() + if err != nil { + log.Fatal(err) + } + for _, ev := range resp.Kvs { + fmt.Printf("%s : %s\n", ev.Key, ev.Value) + } + }) // Output: // key_2 : value // key_1 : value // key_0 : value } +func mockKV_delete() { + fmt.Println("Deleted all keys: true") +} + func ExampleKV_delete() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockKV_delete, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + defer cancel() + + // count keys about to be deleted + gresp, err := cli.Get(ctx, "key", clientv3.WithPrefix()) + if err != nil { + log.Fatal(err) + } + + // delete the keys + dresp, err := cli.Delete(ctx, "key", clientv3.WithPrefix()) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Deleted all keys:", int64(len(gresp.Kvs)) == dresp.Deleted) }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - defer cancel() - - // count keys about to be deleted - gresp, err := cli.Get(ctx, "key", clientv3.WithPrefix()) - if err != nil { - log.Fatal(err) - } - - // delete the keys - dresp, err := cli.Delete(ctx, "key", clientv3.WithPrefix()) - if err != nil { - log.Fatal(err) - } - - fmt.Println("Deleted all keys:", int64(len(gresp.Kvs)) == dresp.Deleted) // Output: // Deleted all keys: true } +func mockKV_compact() {} + func ExampleKV_compact() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockKV_compact, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + resp, err := cli.Get(ctx, "foo") + cancel() + if err != nil { + log.Fatal(err) + } + compRev := resp.Header.Revision // specify compact revision of your choice + + ctx, cancel = context.WithTimeout(context.Background(), requestTimeout) + _, err = cli.Compact(ctx, compRev) + cancel() + if err != nil { + log.Fatal(err) + } }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() + // Output: +} - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - resp, err := cli.Get(ctx, "foo") - cancel() - if err != nil { - log.Fatal(err) - } - compRev := resp.Header.Revision // specify compact revision of your choice - - ctx, cancel = context.WithTimeout(context.Background(), requestTimeout) - _, err = cli.Compact(ctx, compRev) - cancel() - if err != nil { - log.Fatal(err) - } +func mockKV_txn() { + fmt.Println("key : XYZ") } func ExampleKV_txn() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockKV_txn, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + kvc := clientv3.NewKV(cli) + + _, err = kvc.Put(context.TODO(), "key", "xyz") + if err != nil { + log.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + _, err = kvc.Txn(ctx). + // txn value comparisons are lexical + If(clientv3.Compare(clientv3.Value("key"), ">", "abc")). + // the "Then" runs, since "xyz" > "abc" + Then(clientv3.OpPut("key", "XYZ")). + // the "Else" does not run + Else(clientv3.OpPut("key", "ABC")). + Commit() + cancel() + if err != nil { + log.Fatal(err) + } + + gresp, err := kvc.Get(context.TODO(), "key") + if err != nil { + log.Fatal(err) + } + for _, ev := range gresp.Kvs { + fmt.Printf("%s : %s\n", ev.Key, ev.Value) + } }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - kvc := clientv3.NewKV(cli) - - _, err = kvc.Put(context.TODO(), "key", "xyz") - if err != nil { - log.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - _, err = kvc.Txn(ctx). - // txn value comparisons are lexical - If(clientv3.Compare(clientv3.Value("key"), ">", "abc")). - // the "Then" runs, since "xyz" > "abc" - Then(clientv3.OpPut("key", "XYZ")). - // the "Else" does not run - Else(clientv3.OpPut("key", "ABC")). - Commit() - cancel() - if err != nil { - log.Fatal(err) - } - - gresp, err := kvc.Get(context.TODO(), "key") - if err != nil { - log.Fatal(err) - } - for _, ev := range gresp.Kvs { - fmt.Printf("%s : %s\n", ev.Key, ev.Value) - } // Output: key : XYZ } +func mockKV_do() {} + func ExampleKV_do() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - ops := []clientv3.Op{ - clientv3.OpPut("put-key", "123"), - clientv3.OpGet("put-key"), - clientv3.OpPut("put-key", "456")} - - for _, op := range ops { - if _, err := cli.Do(context.TODO(), op); err != nil { + forUnitTestsRunInMockedContext(mockKV_do, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { log.Fatal(err) } - } + defer cli.Close() + + ops := []clientv3.Op{ + clientv3.OpPut("put-key", "123"), + clientv3.OpGet("put-key"), + clientv3.OpPut("put-key", "456")} + + for _, op := range ops { + if _, err := cli.Do(context.TODO(), op); err != nil { + log.Fatal(err) + } + } + }) + // Output: } diff --git a/tests/integration/clientv3/examples/example_lease_test.go b/tests/integration/clientv3/examples/example_lease_test.go index 4d76c09ed..63346762c 100644 --- a/tests/integration/clientv3/examples/example_lease_test.go +++ b/tests/integration/clientv3/examples/example_lease_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" @@ -22,120 +22,148 @@ import ( "go.etcd.io/etcd/v3/clientv3" ) +func mockLease_grant() { +} + func ExampleLease_grant() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockLease_grant, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + // minimum lease TTL is 5-second + resp, err := cli.Grant(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + // after 5 seconds, the key 'foo' will be removed + _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) + if err != nil { + log.Fatal(err) + } }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() + //Output: +} - // minimum lease TTL is 5-second - resp, err := cli.Grant(context.TODO(), 5) - if err != nil { - log.Fatal(err) - } - - // after 5 seconds, the key 'foo' will be removed - _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) - if err != nil { - log.Fatal(err) - } +func mockLease_revoke() { + fmt.Println("number of keys: 0") } func ExampleLease_revoke() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockLease_revoke, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + resp, err := cli.Grant(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) + if err != nil { + log.Fatal(err) + } + + // revoking lease expires the key attached to its lease ID + _, err = cli.Revoke(context.TODO(), resp.ID) + if err != nil { + log.Fatal(err) + } + + gresp, err := cli.Get(context.TODO(), "foo") + if err != nil { + log.Fatal(err) + } + fmt.Println("number of keys:", len(gresp.Kvs)) }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - resp, err := cli.Grant(context.TODO(), 5) - if err != nil { - log.Fatal(err) - } - - _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) - if err != nil { - log.Fatal(err) - } - - // revoking lease expires the key attached to its lease ID - _, err = cli.Revoke(context.TODO(), resp.ID) - if err != nil { - log.Fatal(err) - } - - gresp, err := cli.Get(context.TODO(), "foo") - if err != nil { - log.Fatal(err) - } - fmt.Println("number of keys:", len(gresp.Kvs)) // Output: number of keys: 0 } +func mockLease_keepAlive() { + fmt.Println("ttl: 5") +} + func ExampleLease_keepAlive() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockLease_keepAlive, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + resp, err := cli.Grant(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) + if err != nil { + log.Fatal(err) + } + + // the key 'foo' will be kept forever + ch, kaerr := cli.KeepAlive(context.TODO(), resp.ID) + if kaerr != nil { + log.Fatal(kaerr) + } + + ka := <-ch + if ka != nil { + fmt.Println("ttl:", ka.TTL) + } else { + fmt.Println("Unexpected NULL") + } }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - resp, err := cli.Grant(context.TODO(), 5) - if err != nil { - log.Fatal(err) - } - - _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) - if err != nil { - log.Fatal(err) - } - - // the key 'foo' will be kept forever - ch, kaerr := cli.KeepAlive(context.TODO(), resp.ID) - if kaerr != nil { - log.Fatal(kaerr) - } - - ka := <-ch - fmt.Println("ttl:", ka.TTL) // Output: ttl: 5 } +func mockLease_keepAliveOnce() { + fmt.Println("ttl: 5") +} + func ExampleLease_keepAliveOnce() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, + forUnitTestsRunInMockedContext(mockLease_keepAliveOnce, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + resp, err := cli.Grant(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) + if err != nil { + log.Fatal(err) + } + + // to renew the lease only once + ka, kaerr := cli.KeepAliveOnce(context.TODO(), resp.ID) + if kaerr != nil { + log.Fatal(kaerr) + } + + fmt.Println("ttl:", ka.TTL) }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - resp, err := cli.Grant(context.TODO(), 5) - if err != nil { - log.Fatal(err) - } - - _, err = cli.Put(context.TODO(), "foo", "bar", clientv3.WithLease(resp.ID)) - if err != nil { - log.Fatal(err) - } - - // to renew the lease only once - ka, kaerr := cli.KeepAliveOnce(context.TODO(), resp.ID) - if kaerr != nil { - log.Fatal(kaerr) - } - - fmt.Println("ttl:", ka.TTL) // Output: ttl: 5 } diff --git a/tests/integration/clientv3/examples/example_maintenance_test.go b/tests/integration/clientv3/examples/example_maintenance_test.go index e9445556b..80759a37f 100644 --- a/tests/integration/clientv3/examples/example_maintenance_test.go +++ b/tests/integration/clientv3/examples/example_maintenance_test.go @@ -12,51 +12,56 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" - "fmt" "log" "go.etcd.io/etcd/v3/clientv3" ) -func ExampleMaintenance_status() { - for _, ep := range endpoints { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: []string{ep}, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() +func mockMaintenance_status() {} - resp, err := cli.Status(context.Background(), ep) - if err != nil { - log.Fatal(err) +func ExampleMaintenance_status() { + forUnitTestsRunInMockedContext(mockMaintenance_status, func() { + for _, ep := range exampleEndpoints() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: []string{ep}, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() + + _, err = cli.Status(context.Background(), ep) + if err != nil { + log.Fatal(err) + } } - fmt.Printf("endpoint: %s / Leader: %v\n", ep, resp.Header.MemberId == resp.Leader) - } - // endpoint: localhost:2379 / Leader: false - // endpoint: localhost:22379 / Leader: false - // endpoint: localhost:32379 / Leader: true + }) + // Output: } +func mockMaintenance_defragment() {} + func ExampleMaintenance_defragment() { - for _, ep := range endpoints { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: []string{ep}, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() + forUnitTestsRunInMockedContext(mockMaintenance_defragment, func() { + for _, ep := range exampleEndpoints() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: []string{ep}, + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() - if _, err = cli.Defragment(context.TODO(), ep); err != nil { - log.Fatal(err) + if _, err = cli.Defragment(context.TODO(), ep); err != nil { + log.Fatal(err) + } } - } + }) + // Output: } diff --git a/tests/integration/clientv3/examples/example_metrics_test.go b/tests/integration/clientv3/examples/example_metrics_test.go index 237d59dc2..10d2e06b3 100644 --- a/tests/integration/clientv3/examples/example_metrics_test.go +++ b/tests/integration/clientv3/examples/example_metrics_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" @@ -30,56 +30,62 @@ import ( "google.golang.org/grpc" ) +func mockClient_metrics() { + fmt.Println(`grpc_client_started_total{grpc_method="Range",grpc_service="etcdserverpb.KV",grpc_type="unary"} 1`) +} + func ExampleClient_metrics() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialOptions: []grpc.DialOption{ - grpc.WithUnaryInterceptor(grpcprom.UnaryClientInterceptor), - grpc.WithStreamInterceptor(grpcprom.StreamClientInterceptor), - }, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - // get a key so it shows up in the metrics as a range RPC - cli.Get(context.TODO(), "test_key") - - // listen for all Prometheus metrics - ln, err := net.Listen("tcp", ":0") - if err != nil { - log.Fatal(err) - } - donec := make(chan struct{}) - go func() { - defer close(donec) - http.Serve(ln, promhttp.Handler()) - }() - defer func() { - ln.Close() - <-donec - }() - - // make an http request to fetch all Prometheus metrics - url := "http://" + ln.Addr().String() + "/metrics" - resp, err := http.Get(url) - if err != nil { - log.Fatalf("fetch error: %v", err) - } - b, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - log.Fatalf("fetch error: reading %s: %v", url, err) - } - - // confirm range request in metrics - for _, l := range strings.Split(string(b), "\n") { - if strings.Contains(l, `grpc_client_started_total{grpc_method="Range"`) { - fmt.Println(l) - break + forUnitTestsRunInMockedContext(mockClient_metrics, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialOptions: []grpc.DialOption{ + grpc.WithUnaryInterceptor(grpcprom.UnaryClientInterceptor), + grpc.WithStreamInterceptor(grpcprom.StreamClientInterceptor), + }, + }) + if err != nil { + log.Fatal(err) } - } + defer cli.Close() + + // get a key so it shows up in the metrics as a range RPC + cli.Get(context.TODO(), "test_key") + + // listen for all Prometheus metrics + ln, err := net.Listen("tcp", ":0") + if err != nil { + log.Fatal(err) + } + donec := make(chan struct{}) + go func() { + defer close(donec) + http.Serve(ln, promhttp.Handler()) + }() + defer func() { + ln.Close() + <-donec + }() + + // make an http request to fetch all Prometheus metrics + url := "http://" + ln.Addr().String() + "/metrics" + resp, err := http.Get(url) + if err != nil { + log.Fatalf("fetch error: %v", err) + } + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatalf("fetch error: reading %s: %v", url, err) + } + + // confirm range request in metrics + for _, l := range strings.Split(string(b), "\n") { + if strings.Contains(l, `grpc_client_started_total{grpc_method="Range"`) { + fmt.Println(l) + break + } + } + }) // Output: // grpc_client_started_total{grpc_method="Range",grpc_service="etcdserverpb.KV",grpc_type="unary"} 1 } diff --git a/tests/integration/clientv3/examples/example_test.go b/tests/integration/clientv3/examples/example_test.go index 8ec6f340a..bac3092cc 100644 --- a/tests/integration/clientv3/examples/example_test.go +++ b/tests/integration/clientv3/examples/example_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" @@ -21,50 +21,58 @@ import ( "log" ) -func ExampleConfig_insecure() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() // make sure to close the client +func mockConfig_insecure() {} - _, err = cli.Put(context.TODO(), "foo", "bar") - if err != nil { - log.Fatal(err) - } +func ExampleConfig_insecure() { + forUnitTestsRunInMockedContext(mockConfig_insecure, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() // make sure to close the client + + _, err = cli.Put(context.TODO(), "foo", "bar") + if err != nil { + log.Fatal(err) + } + }) // Without the line below the test is not being executed // Output: } +func mockConfig_withTLS() {} + func ExampleConfig_withTLS() { - tlsInfo := transport.TLSInfo{ - CertFile: "/tmp/test-certs/test-name-1.pem", - KeyFile: "/tmp/test-certs/test-name-1-key.pem", - TrustedCAFile: "/tmp/test-certs/trusted-ca.pem", - } - tlsConfig, err := tlsInfo.ClientConfig() - if err != nil { - log.Fatal(err) - } - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - TLS: tlsConfig, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() // make sure to close the client + forUnitTestsRunInMockedContext(mockConfig_withTLS, func() { + tlsInfo := transport.TLSInfo{ + CertFile: "/tmp/test-certs/test-name-1.pem", + KeyFile: "/tmp/test-certs/test-name-1-key.pem", + TrustedCAFile: "/tmp/test-certs/trusted-ca.pem", + } + tlsConfig, err := tlsInfo.ClientConfig() + if err != nil { + log.Fatal(err) + } + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + TLS: tlsConfig, + }) + if err != nil { + log.Fatal(err) + } + defer cli.Close() // make sure to close the client - _, err = cli.Put(context.TODO(), "foo", "bar") - if err != nil { - log.Fatal(err) - } + _, err = cli.Put(context.TODO(), "foo", "bar") + if err != nil { + log.Fatal(err) + } + }) // Without the line below the test is not being executed // Output: } diff --git a/tests/integration/clientv3/examples/example_watch_test.go b/tests/integration/clientv3/examples/example_watch_test.go index 752e5b501..f60286ede 100644 --- a/tests/integration/clientv3/examples/example_watch_test.go +++ b/tests/integration/clientv3/examples/example_watch_test.go @@ -12,89 +12,148 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( "context" "fmt" "log" + "time" "go.etcd.io/etcd/v3/clientv3" ) -func ExampleWatcher_watch() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() +func mockWatcher_watch() { + fmt.Println(`PUT "foo" : "bar"`) +} - rch := cli.Watch(context.Background(), "foo") - for wresp := range rch { - for _, ev := range wresp.Events { - fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) +func ExampleWatcher_watch() { + forUnitTestsRunInMockedContext(mockWatcher_watch, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) } - } + defer cli.Close() + + rch := cli.Watch(context.Background(), "foo") + for wresp := range rch { + for _, ev := range wresp.Events { + fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + } + } + }) // PUT "foo" : "bar" } -func ExampleWatcher_watchWithPrefix() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() +func mockWatcher_watchWithPrefix() { + fmt.Println(`PUT "foo1" : "bar"`) +} - rch := cli.Watch(context.Background(), "foo", clientv3.WithPrefix()) - for wresp := range rch { - for _, ev := range wresp.Events { - fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) +func ExampleWatcher_watchWithPrefix() { + forUnitTestsRunInMockedContext(mockWatcher_watchWithPrefix, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) } - } + defer cli.Close() + + rch := cli.Watch(context.Background(), "foo", clientv3.WithPrefix()) + for wresp := range rch { + for _, ev := range wresp.Events { + fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + } + } + }) // PUT "foo1" : "bar" } +func mockWatcher_watchWithRange() { + fmt.Println(`PUT "foo1" : "bar1"`) + fmt.Println(`PUT "foo2" : "bar2"`) + fmt.Println(`PUT "foo3" : "bar3"`) +} + func ExampleWatcher_watchWithRange() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } - defer cli.Close() - - // watches within ['foo1', 'foo4'), in lexicographical order - rch := cli.Watch(context.Background(), "foo1", clientv3.WithRange("foo4")) - for wresp := range rch { - for _, ev := range wresp.Events { - fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + forUnitTestsRunInMockedContext(mockWatcher_watchWithRange, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) } - } - // PUT "foo1" : "bar" - // PUT "foo2" : "bar" - // PUT "foo3" : "bar" + defer cli.Close() + + // watches within ['foo1', 'foo4'), in lexicographical order + rch := cli.Watch(context.Background(), "foo1", clientv3.WithRange("foo4")) + + go func() { + cli.Put(context.Background(), "foo1", "bar1") + cli.Put(context.Background(), "foo5", "bar5") + cli.Put(context.Background(), "foo2", "bar2") + cli.Put(context.Background(), "foo3", "bar3") + }() + + i := 0 + for wresp := range rch { + for _, ev := range wresp.Events { + fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + i++ + if i == 3 { + // After 3 messages we are done. + cli.Delete(context.Background(), "foo", clientv3.WithPrefix()) + cli.Close() + return + } + } + } + }) + + // Output: + // PUT "foo1" : "bar1" + // PUT "foo2" : "bar2" + // PUT "foo3" : "bar3" +} + +func mockWatcher_watchWithProgressNotify() { + fmt.Println(`wresp.IsProgressNotify: true`) } func ExampleWatcher_watchWithProgressNotify() { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: endpoints, - DialTimeout: dialTimeout, - }) - if err != nil { - log.Fatal(err) - } + forUnitTestsRunInMockedContext(mockWatcher_watchWithProgressNotify, func() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: exampleEndpoints(), + DialTimeout: dialTimeout, + }) + if err != nil { + log.Fatal(err) + } - rch := cli.Watch(context.Background(), "foo", clientv3.WithProgressNotify()) - wresp := <-rch - fmt.Printf("wresp.Header.Revision: %d\n", wresp.Header.Revision) - fmt.Println("wresp.IsProgressNotify:", wresp.IsProgressNotify()) - // wresp.Header.Revision: 0 + rch := cli.Watch(context.Background(), "foo", clientv3.WithProgressNotify()) + closedch := make(chan bool) + go func() { + // This assumes that cluster is configured with frequent WatchProgressNotifyInterval + // e.g. WatchProgressNotifyInterval: 200 * time.Millisecond. + time.Sleep(time.Second) + err := cli.Close() + if err != nil { + log.Fatal(err) + } + close(closedch) + }() + wresp := <-rch + fmt.Println("wresp.IsProgressNotify:", wresp.IsProgressNotify()) + <-closedch + }) + + // TODO: Rather wresp.IsProgressNotify: true should be expected + + // Output: // wresp.IsProgressNotify: true } diff --git a/tests/integration/clientv3/examples/main_test.go b/tests/integration/clientv3/examples/main_test.go index f97c7efef..90f36b264 100644 --- a/tests/integration/clientv3/examples/main_test.go +++ b/tests/integration/clientv3/examples/main_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 The etcd Authors +// Copyright 2017 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. @@ -12,67 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -package clientv3test +package clientv3_test import ( - "fmt" - "go.etcd.io/etcd/tests/v3/integration" - "go.etcd.io/etcd/v3/clientv3" - "go.etcd.io/etcd/v3/pkg/testutil" - "google.golang.org/grpc/grpclog" "os" - "strings" "testing" "time" + + "go.etcd.io/etcd/tests/v3/integration" + "go.etcd.io/etcd/v3/pkg/testutil" ) -var ( +const ( dialTimeout = 5 * time.Second requestTimeout = 10 * time.Second - endpoints = []string{"localhost:2379", "localhost:22379", "localhost:32379"} ) +var lazyCluster = integration.NewLazyClusterWithConfig( + integration.ClusterConfig{ + Size: 3, + WatchProgressNotifyInterval: 200 * time.Millisecond}) + +func exampleEndpoints() []string { return lazyCluster.EndpointsV3() } + +func forUnitTestsRunInMockedContext(mocking func(), example func()) { + // For integration tests runs in the provided environment + example() +} + // TestMain sets up an etcd cluster if running the examples. func TestMain(m *testing.M) { - useCluster, hasRunArg := false, false // default to running only Test* - for _, arg := range os.Args { - if strings.HasPrefix(arg, "-test.run=") { - exp := strings.Split(arg, "=")[1] - useCluster = strings.Contains(exp, "Example") - hasRunArg = true - break - } - } - if !hasRunArg { - // force only running Test* if no args given to avoid leak false - // positives from having a long-running cluster for the examples. - os.Args = append(os.Args, "-test.run=Test") - } + v := m.Run() + lazyCluster.Terminate() - var v int - if useCluster { - // Redirecting outputs to Stderr, such that they not interleave with examples outputs. - // Setting it once and before running any of the test such that it not data-races - // between HTTP servers running in different tests. - clientv3.SetLogger(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr)) - - cfg := integration.ClusterConfig{Size: 3} - clus := integration.NewClusterV3(nil, &cfg) - endpoints = make([]string, 3) - for i := range endpoints { - endpoints[i] = clus.Client(i).Endpoints()[0] - } - v = m.Run() - clus.Terminate(nil) - if err := testutil.CheckAfterTest(time.Second); err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(1) - } - } else { - v = m.Run() - } - if v == 0 && testutil.CheckLeakedGoroutine() { - os.Exit(1) + if v == 0 { + testutil.MustCheckLeakedGoroutine() } os.Exit(v) } diff --git a/tests/integration/cluster.go b/tests/integration/cluster.go index 0020ccd76..8910a6cf7 100644 --- a/tests/integration/cluster.go +++ b/tests/integration/cluster.go @@ -312,7 +312,8 @@ func (c *cluster) mustNewMember(t testing.TB) *member { return m } -func (c *cluster) addMember(t testing.TB) { +// addMember return PeerURLs of the added member. +func (c *cluster) addMember(t testing.TB) types.URLs { m := c.mustNewMember(t) scheme := schemeFromTLSInfo(c.cfg.PeerTLS) @@ -327,7 +328,11 @@ func (c *cluster) addMember(t testing.TB) { } } if err != nil { - t.Fatalf("add member failed on all members error: %v", err) + if t != nil { + t.Fatalf("add member failed on all members error: %v", err) + } else { + log.Fatalf("add member failed on all members error: %v", err) + } } m.InitialPeerURLsMap = types.URLsMap{} @@ -342,6 +347,7 @@ func (c *cluster) addMember(t testing.TB) { c.Members = append(c.Members, m) // wait cluster to be stable to receive future client requests c.waitMembersMatch(t, c.HTTPMembers()) + return m.PeerURLs } func (c *cluster) addMemberByURL(t testing.TB, clientURL, peerURL string) error { @@ -360,8 +366,9 @@ func (c *cluster) addMemberByURL(t testing.TB, clientURL, peerURL string) error return nil } -func (c *cluster) AddMember(t testing.TB) { - c.addMember(t) +// AddMember return PeerURLs of the added member. +func (c *cluster) AddMember(t testing.TB) types.URLs { + return c.addMember(t) } func (c *cluster) RemoveMember(t testing.TB, id uint64) { diff --git a/tests/integration/lazy_cluster.go b/tests/integration/lazy_cluster.go index 0b5d7595b..8d7384cb7 100644 --- a/tests/integration/lazy_cluster.go +++ b/tests/integration/lazy_cluster.go @@ -23,13 +23,26 @@ import ( "go.etcd.io/etcd/v3/pkg/transport" ) +// Infrastructure to provision a single shared cluster for tests - only +// when its needed. +// +// See ./tests/integration/clientv3/examples/main_test.go for canonical usage. +// Please notice that the shared (LazyCluster's) state is preserved between +// testcases, so left-over state might has cross-testcase effects. +// Prefer dedicated clusters for substancial test-cases. + type LazyCluster interface { - // EndpointsV2 - call to this method might initialize the cluster. + // EndpointsV2 - exposes connection points for client v2. + // Calls to this method might initialize the cluster. EndpointsV2() []string - // EndpointsV2 - call to this method might initialize the cluster. + // EndpointsV3 - exposes connection points for client v3. + // Calls to this method might initialize the cluster. EndpointsV3() []string + // Cluster - calls to this method might initialize the cluster. + Cluster() *ClusterV3 + // Transport - call to this method might initialize the cluster. Transport() *http.Transport @@ -46,7 +59,13 @@ type lazyCluster struct { // NewLazyCluster returns a new test cluster handler that gets created on the // first call to GetEndpoints() or GetTransport() func NewLazyCluster() LazyCluster { - return &lazyCluster{cfg: ClusterConfig{Size: 1}} + return NewLazyClusterWithConfig(ClusterConfig{Size: 1}) +} + +// NewLazyClusterWithConfig returns a new test cluster handler that gets created +// on the first call to GetEndpoints() or GetTransport() +func NewLazyClusterWithConfig(cfg ClusterConfig) LazyCluster { + return &lazyCluster{cfg: cfg} } func (lc *lazyCluster) mustLazyInit() { @@ -61,19 +80,23 @@ func (lc *lazyCluster) mustLazyInit() { } func (lc *lazyCluster) Terminate() { - if lc != nil { + if lc != nil && lc.cluster != nil { lc.cluster.Terminate(nil) + lc.cluster = nil } } func (lc *lazyCluster) EndpointsV2() []string { - lc.mustLazyInit() - return []string{lc.cluster.Members[0].URL()} + return []string{lc.Cluster().Members[0].URL()} } func (lc *lazyCluster) EndpointsV3() []string { + return lc.Cluster().Client(0).Endpoints() +} + +func (lc *lazyCluster) Cluster() *ClusterV3 { lc.mustLazyInit() - return []string{lc.cluster.Client(0).Endpoints()[0]} + return lc.cluster } func (lc *lazyCluster) Transport() *http.Transport {