diff --git a/client/example_keys_test.go b/client/example_keys_test.go new file mode 120000 index 000000000..f1d6f144f --- /dev/null +++ b/client/example_keys_test.go @@ -0,0 +1 @@ +../tests/integration/client/examples/example_keys_test.go \ No newline at end of file diff --git a/client/main_test.go b/client/main_test.go new file mode 100644 index 000000000..19c93043e --- /dev/null +++ b/client/main_test.go @@ -0,0 +1,37 @@ +// 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 client_test + +import ( + "net/http" + "testing" + + "go.etcd.io/etcd/v3/pkg/testutil" +) + +var exampleEndpoints []string +var exampleTransport *http.Transport + +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/client/example/... +} + +func TestMain(m *testing.M) { + testutil.MustTestMainWithLeakDetection(m) +} diff --git a/pkg/testutil/leak.go b/pkg/testutil/leak.go index 51b119f22..133a13aef 100644 --- a/pkg/testutil/leak.go +++ b/pkg/testutil/leak.go @@ -130,18 +130,23 @@ func interestingGoroutines() (gs []string) { return gs } -// MustTestMainWithLeakDetection expands standard m.Run with leaked -// goroutines detection. -func MustTestMainWithLeakDetection(m *testing.M) { - v := m.Run() - +func MustCheckLeakedGoroutine() { http.DefaultTransport.(*http.Transport).CloseIdleConnections() // Let the other goroutines finalize. runtime.Gosched() - if v == 0 && CheckLeakedGoroutine() { + if CheckLeakedGoroutine() { os.Exit(1) } +} + +// MustTestMainWithLeakDetection expands standard m.Run with leaked +// goroutines detection. +func MustTestMainWithLeakDetection(m *testing.M) { + v := m.Run() + if v == 0 { + MustCheckLeakedGoroutine() + } os.Exit(v) } diff --git a/tests/integration/client/examples/example_keys_test.go b/tests/integration/client/examples/example_keys_test.go index 2dd69a27f..bc7c3b3fa 100644 --- a/tests/integration/client/examples/example_keys_test.go +++ b/tests/integration/client/examples/example_keys_test.go @@ -23,71 +23,89 @@ import ( "go.etcd.io/etcd/v3/client" ) +func mockKeysAPI_directory() { + // TODO: Replace with proper mocking + fmt.Println(`Key: "/myNodes/key1", Value: "value1"`) + fmt.Println(`Key: "/myNodes/key2", Value: "value2"`) +} + func ExampleKeysAPI_directory() { - c, err := client.New(client.Config{ - Endpoints: exampleEndpoints, - Transport: exampleTransport, - }) - if err != nil { - log.Fatal(err) - } - kapi := client.NewKeysAPI(c) + forUnitTestsRunInMockedContext( + mockKeysAPI_directory, + func() { + c, err := client.New(client.Config{ + Endpoints: exampleEndpoints, + Transport: exampleTransport, + }) + if err != nil { + log.Fatal(err) + } + kapi := client.NewKeysAPI(c) - // Setting '/myNodes' to create a directory that will hold some keys. - o := client.SetOptions{Dir: true} - resp, err := kapi.Set(context.Background(), "/myNodes", "", &o) - if err != nil { - log.Fatal(err) - } + // Setting '/myNodes' to create a directory that will hold some keys. + o := client.SetOptions{Dir: true} + resp, err := kapi.Set(context.Background(), "/myNodes", "", &o) + if err != nil { + log.Fatal(err) + } - // Add keys to /myNodes directory. - resp, err = kapi.Set(context.Background(), "/myNodes/key1", "value1", nil) - if err != nil { - log.Fatal(err) - } - resp, err = kapi.Set(context.Background(), "/myNodes/key2", "value2", nil) - if err != nil { - log.Fatal(err) - } + // Add keys to /myNodes directory. + resp, err = kapi.Set(context.Background(), "/myNodes/key1", "value1", nil) + if err != nil { + log.Fatal(err) + } + resp, err = kapi.Set(context.Background(), "/myNodes/key2", "value2", nil) + if err != nil { + log.Fatal(err) + } - // fetch directory - resp, err = kapi.Get(context.Background(), "/myNodes", nil) - if err != nil { - log.Fatal(err) - } - // print directory keys - sort.Sort(resp.Node.Nodes) - for _, n := range resp.Node.Nodes { - fmt.Printf("Key: %q, Value: %q\n", n.Key, n.Value) - } + // fetch directory + resp, err = kapi.Get(context.Background(), "/myNodes", nil) + if err != nil { + log.Fatal(err) + } + // print directory keys + sort.Sort(resp.Node.Nodes) + for _, n := range resp.Node.Nodes { + fmt.Printf("Key: %q, Value: %q\n", n.Key, n.Value) + } + }) // Output: // Key: "/myNodes/key1", Value: "value1" // Key: "/myNodes/key2", Value: "value2" } +func mockKeysAPI_setget() { + fmt.Println(`"/foo" key has "bar" value`) +} + func ExampleKeysAPI_setget() { - c, err := client.New(client.Config{ - Endpoints: exampleEndpoints, - Transport: exampleTransport, - }) - if err != nil { - log.Fatal(err) - } - kapi := client.NewKeysAPI(c) + forUnitTestsRunInMockedContext( + mockKeysAPI_setget, + func() { + c, err := client.New(client.Config{ + Endpoints: exampleEndpoints, + Transport: exampleTransport, + }) + if err != nil { + log.Fatal(err) + } + kapi := client.NewKeysAPI(c) - // Set key "/foo" to value "bar". - resp, err := kapi.Set(context.Background(), "/foo", "bar", nil) - if err != nil { - log.Fatal(err) - } - // Get key "/foo" - resp, err = kapi.Get(context.Background(), "/foo", nil) - if err != nil { - log.Fatal(err) - } + // Set key "/foo" to value "bar". + resp, err := kapi.Set(context.Background(), "/foo", "bar", nil) + if err != nil { + log.Fatal(err) + } + // Get key "/foo" + resp, err = kapi.Get(context.Background(), "/foo", nil) + if err != nil { + log.Fatal(err) + } - fmt.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value) + fmt.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value) + }) // Output: "/foo" key has "bar" value } diff --git a/tests/integration/client/examples/main_test.go b/tests/integration/client/examples/main_test.go index af6203cdc..3d6e98cc7 100644 --- a/tests/integration/client/examples/main_test.go +++ b/tests/integration/client/examples/main_test.go @@ -18,8 +18,6 @@ import ( "fmt" "net/http" "os" - "regexp" - "strings" "testing" "time" @@ -31,47 +29,31 @@ import ( var exampleEndpoints []string var exampleTransport *http.Transport +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] - match, err := regexp.MatchString(exp, "Example") - useCluster = (err == nil && match) || 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") - } - - var v int - if useCluster { - tr, trerr := transport.NewTransport(transport.TLSInfo{}, time.Second) - if trerr != nil { - fmt.Fprintf(os.Stderr, "%v", trerr) - os.Exit(1) - } - cfg := integration.ClusterConfig{Size: 1} - clus := integration.NewClusterV3(nil, &cfg) - exampleEndpoints = []string{clus.Members[0].URL()} - exampleTransport = tr - 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() { + tr, trerr := transport.NewTransport(transport.TLSInfo{}, time.Second) + if trerr != nil { + fmt.Fprintf(os.Stderr, "%v", trerr) os.Exit(1) } + cfg := integration.ClusterConfig{Size: 1} + clus := integration.NewClusterV3(nil, &cfg) + exampleEndpoints = []string{clus.Members[0].URL()} + exampleTransport = tr + v := m.Run() + clus.Terminate(nil) + if err := testutil.CheckAfterTest(time.Second); err != nil { + fmt.Fprintf(os.Stderr, "%v", err) + os.Exit(1) + } + + if v == 0 { + testutil.MustCheckLeakedGoroutine() + } os.Exit(v) }