|
| 1 | +/* |
| 2 | +Copyright 2025 The KCP Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package framework |
| 18 | + |
| 19 | +import ( |
| 20 | + "testing" |
| 21 | + "time" |
| 22 | + |
| 23 | + "go.uber.org/goleak" |
| 24 | +) |
| 25 | + |
| 26 | +var ( |
| 27 | + KnownGoroutineLeaks = []goleak.Option{ |
| 28 | + // context |
| 29 | + // created by: context.(*cancelCtx).propagateCancel |
| 30 | + // came up ~12 times, likely a result of some of the following |
| 31 | + // leaks |
| 32 | + goleak.IgnoreTopFunction("context.(*cancelCtx).propagateCancel.func2"), |
| 33 | + |
| 34 | + // grpc |
| 35 | + // created by: google.golang.org/grpc.(*acBalancerWrapper).Connect |
| 36 | + goleak.IgnoreTopFunction("google.golang.org/grpc.(*addrConn).resetTransportAndUnlock"), |
| 37 | + // created by: go.etcd.io/etcd/client/v3.(*watcher).newWatcherGrpcStream |
| 38 | + goleak.IgnoreTopFunction("google.golang.org/grpc.(*pickerWrapper).pick"), |
| 39 | + // created by: google.golang.org/grpc/internal/grpcsync.NewCallbackSerializer |
| 40 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/grpcsync.(*CallbackSerializer).run"), |
| 41 | + // created by: google.golang.org/grpc.newClientStreamWithParams |
| 42 | + goleak.IgnoreTopFunction("google.golang.org/grpc.newClientStreamWithParams.func4"), |
| 43 | + // created by: google.golang.org/grpc/internal/transport.(*serverHandlerTransport).HandleStreams |
| 44 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/transport.(*serverHandlerTransport).HandleStreams.func1"), |
| 45 | + // created by: google.golang.org/grpc.newClientStreamWithParams |
| 46 | + goleak.IgnoreTopFunction("google.golang.org/grpc.newClientStreamWithParams.func4"), |
| 47 | + // created by: google.golang.org/grpc/internal/transport.NewHTTP2Client |
| 48 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/transport.(*controlBuffer).get"), |
| 49 | + // created by: google.golang.org/grpc/internal/transport.NewHTTP2Client |
| 50 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/transport.(*http2Client).keepalive"), |
| 51 | + // created by: go.etcd.io/etcd/server/v3/etcdserver/api/v3rpc.(*watchServer).Watch |
| 52 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/transport.(*recvBufferReader).readMessageHeader"), |
| 53 | + // created by: go.etcd.io/etcd/client/v3.(*watchGrpcStream).newWatchClient |
| 54 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/transport.(*recvBufferReader).readMessageHeaderClient"), |
| 55 | + // created by: google.golang.org/grpc/internal/transport.(*serverHandlerTransport).HandleStreams |
| 56 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/transport.(*serverHandlerTransport).HandleStreams.func1"), |
| 57 | + // created by: golang.org/x/net/http2.(*serverConn).scheduleHandler |
| 58 | + goleak.IgnoreTopFunction("google.golang.org/grpc/internal/transport.(*serverHandlerTransport).runStream"), |
| 59 | + // created by: google.golang.org/grpc/internal/transport.NewHTTP2Client |
| 60 | + goleak.IgnoreTopFunction("internal/poll.runtime_pollWait"), |
| 61 | + // created by: google.golang.org/grpc/internal/transport.(*serverHandlerTransport).HandleStreams |
| 62 | + goleak.IgnoreTopFunction("sync.runtime_notifyListWait"), |
| 63 | + |
| 64 | + // etcd |
| 65 | + // created by: go.etcd.io/etcd/client/v3.(*watchGrpcStream).waitCancelSubstreams.func1 |
| 66 | + goleak.IgnoreTopFunction("go.etcd.io/etcd/client/v3.(*watchGrpcStream).waitCancelSubstreams.func1.1"), |
| 67 | + // created by: go.etcd.io/etcd/client/v3.(*watchGrpcStream).newWatchClient |
| 68 | + goleak.IgnoreTopFunction("go.etcd.io/etcd/client/v3.(*watchGrpcStream).serveSubstream.func1"), |
| 69 | + |
| 70 | + // kcp / kube |
| 71 | + // created by k8s.io/apiserver/pkg/registry/generic/registry.(*Store).startObservingCount |
| 72 | + goleak.IgnoreTopFunction("k8s.io/apimachinery/pkg/util/wait.BackoffUntil"), |
| 73 | + // created by: github.com/kcp-dev/kcp/pkg/informer.NewGenericDiscoveringDynamicSharedInformerFactory[...] |
| 74 | + goleak.IgnoreTopFunction("github.com/kcp-dev/kcp/pkg/informer.NewGenericDiscoveringDynamicSharedInformerFactory[...].func3"), |
| 75 | + // created by: k8s.io/apiserver/pkg/storage/storagebackend/factory.newETCD3Check |
| 76 | + goleak.IgnoreTopFunction("k8s.io/apiserver/pkg/storage/storagebackend/factory.newETCD3Check.func2"), |
| 77 | + |
| 78 | + // Known from kcp-dev/kcp#3350 |
| 79 | + // created by: k8s.io/client-go/util/workqueue.newDelayingQueue[...] |
| 80 | + goleak.IgnoreTopFunction("k8s.io/client-go/util/workqueue.(*delayingType[...]).waitingLoop"), |
| 81 | + // created by: k8s.io/client-go/util/workqueue.newQueue[...] |
| 82 | + goleak.IgnoreTopFunction("k8s.io/client-go/util/workqueue.(*Typed[...]).updateUnfinishedWorkLoop"), |
| 83 | + |
| 84 | + // unknown |
| 85 | + // created by: net/http.(*Transport).dialConn |
| 86 | + goleak.IgnoreTopFunction("net/http.(*persistConn).writeLoop"), |
| 87 | + // created by: net/http.(*Server).Serve |
| 88 | + goleak.IgnoreTopFunction("golang.org/x/net/http2.(*serverConn).serve"), |
| 89 | + // created by: gopkg.in/natefinch/lumberjack%2ev2.(*Logger).mill.func1 |
| 90 | + goleak.IgnoreTopFunction("gopkg.in/natefinch/lumberjack%2ev2.(*Logger).millRun"), |
| 91 | + // created by: golang.org/x/net/http2.(*serverConn).serve |
| 92 | + goleak.IgnoreTopFunction("internal/poll.runtime_pollWait"), |
| 93 | + } |
| 94 | + |
| 95 | + // 14s as etcd sets a client request timeout of up to 7 seconds when |
| 96 | + // shutting down the server and then starts shutting down everything |
| 97 | + // else. |
| 98 | + // A shorter timestan d would lead to false positives in the tests. |
| 99 | + WaitTime = 14 * time.Second |
| 100 | +) |
| 101 | + |
| 102 | +// GoleakWithDefaults verifies that there are no goroutine leaks. |
| 103 | +// Goleak tests cannot be run in parallelized tests. |
| 104 | +func GoleakWithDefaults(tb testing.TB, in ...goleak.Option) { |
| 105 | + tb.Helper() |
| 106 | + |
| 107 | + tb.Logf("waiting %v for goroutines to finish", WaitTime) |
| 108 | + time.Sleep(WaitTime) |
| 109 | + |
| 110 | + opts := append(KnownGoroutineLeaks, in...) |
| 111 | + goleak.VerifyNone(tb, opts...) |
| 112 | +} |
0 commit comments