diff --git a/client/main_test.go b/client/main_test.go index 19c93043e..eead65da0 100644 --- a/client/main_test.go +++ b/client/main_test.go @@ -21,8 +21,8 @@ import ( "go.etcd.io/etcd/v3/pkg/testutil" ) -var exampleEndpoints []string -var exampleTransport *http.Transport +func exampleEndpoints() []string { return nil } +func exampleTransport() *http.Transport { return nil } func forUnitTestsRunInMockedContext(mocking func(), example func()) { mocking() diff --git a/pkg/testutil/leak.go b/pkg/testutil/leak.go index 133a13aef..85554bae8 100644 --- a/pkg/testutil/leak.go +++ b/pkg/testutil/leak.go @@ -133,6 +133,8 @@ func interestingGoroutines() (gs []string) { func MustCheckLeakedGoroutine() { http.DefaultTransport.(*http.Transport).CloseIdleConnections() + CheckAfterTest(5 * time.Second) + // Let the other goroutines finalize. runtime.Gosched() diff --git a/tests/integration/client/examples/example_keys_test.go b/tests/integration/client/examples/example_keys_test.go index bc7c3b3fa..1fb702104 100644 --- a/tests/integration/client/examples/example_keys_test.go +++ b/tests/integration/client/examples/example_keys_test.go @@ -34,8 +34,8 @@ func ExampleKeysAPI_directory() { mockKeysAPI_directory, func() { c, err := client.New(client.Config{ - Endpoints: exampleEndpoints, - Transport: exampleTransport, + Endpoints: exampleEndpoints(), + Transport: exampleTransport(), }) if err != nil { log.Fatal(err) @@ -85,8 +85,8 @@ func ExampleKeysAPI_setget() { mockKeysAPI_setget, func() { c, err := client.New(client.Config{ - Endpoints: exampleEndpoints, - Transport: exampleTransport, + Endpoints: exampleEndpoints(), + Transport: exampleTransport(), }) if err != nil { log.Fatal(err) diff --git a/tests/integration/client/examples/main_test.go b/tests/integration/client/examples/main_test.go index 3d6e98cc7..fcc1085a4 100644 --- a/tests/integration/client/examples/main_test.go +++ b/tests/integration/client/examples/main_test.go @@ -15,19 +15,18 @@ package client_test import ( - "fmt" "net/http" "os" "testing" - "time" "go.etcd.io/etcd/tests/v3/integration" "go.etcd.io/etcd/v3/pkg/testutil" - "go.etcd.io/etcd/v3/pkg/transport" ) -var exampleEndpoints []string -var exampleTransport *http.Transport +var lazyCluster = integration.NewLazyCluster() + +func exampleEndpoints() []string { return lazyCluster.EndpointsV2() } +func exampleTransport() *http.Transport { return lazyCluster.Transport() } func forUnitTestsRunInMockedContext(mocking func(), example func()) { // For integration tests runs in the provided environment @@ -36,22 +35,8 @@ func forUnitTestsRunInMockedContext(mocking func(), example func()) { // TestMain sets up an etcd cluster if running the examples. func TestMain(m *testing.M) { - 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) - } - + lazyCluster.Terminate() if v == 0 { testutil.MustCheckLeakedGoroutine() } diff --git a/tests/integration/lazy_cluster.go b/tests/integration/lazy_cluster.go new file mode 100644 index 000000000..0b5d7595b --- /dev/null +++ b/tests/integration/lazy_cluster.go @@ -0,0 +1,82 @@ +// Copyright 2020 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 integration + +import ( + "log" + "net/http" + "sync" + "time" + + "go.etcd.io/etcd/v3/pkg/transport" +) + +type LazyCluster interface { + // EndpointsV2 - call to this method might initialize the cluster. + EndpointsV2() []string + + // EndpointsV2 - call to this method might initialize the cluster. + EndpointsV3() []string + + // Transport - call to this method might initialize the cluster. + Transport() *http.Transport + + Terminate() +} + +type lazyCluster struct { + cfg ClusterConfig + cluster *ClusterV3 + transport *http.Transport + once sync.Once +} + +// 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}} +} + +func (lc *lazyCluster) mustLazyInit() { + lc.once.Do(func() { + var err error + lc.transport, err = transport.NewTransport(transport.TLSInfo{}, time.Second) + if err != nil { + log.Fatal(err) + } + lc.cluster = NewClusterV3(nil, &lc.cfg) + }) +} + +func (lc *lazyCluster) Terminate() { + if lc != nil { + lc.cluster.Terminate(nil) + } +} + +func (lc *lazyCluster) EndpointsV2() []string { + lc.mustLazyInit() + return []string{lc.cluster.Members[0].URL()} +} + +func (lc *lazyCluster) EndpointsV3() []string { + lc.mustLazyInit() + return []string{lc.cluster.Client(0).Endpoints()[0]} +} + +func (lc *lazyCluster) Transport() *http.Transport { + lc.mustLazyInit() + return lc.transport +}