Skip to content

Commit cd24012

Browse files
committed
ktesting: tone down warning about leaked test goroutine
Not terminating immediately is fairly normal: many controllers log "terminating" messages while they shut down, which often is right after test completion, if that is when the test cancels the context and then doesn't wait for goroutines (which is often not possible). Therefore the warning now only gets printed if that happens after more than 10 seconds since test completion, and then the warning is only printed as an info message, not an error.
1 parent 2ee202a commit cd24012

File tree

2 files changed

+36
-9
lines changed

2 files changed

+36
-9
lines changed

ktesting/testinglogger.go

+26-4
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,19 @@ type tloggerShared struct {
217217
// t gets cleared when the test is completed.
218218
t TL
219219

220-
// We warn once when a leaked goroutine is detected because
221-
// it logs after test completion.
220+
// The time when the test completed.
221+
stopTime time.Time
222+
223+
// We warn once when a leaked goroutine logs after test completion.
224+
//
225+
// Not terminating immediately is fairly normal: many controllers
226+
// log "terminating" messages while they shut down, which often is
227+
// right after test completion, if that is when the test cancels the
228+
// context and then doesn't wait for goroutines (which is often
229+
// not possible).
230+
//
231+
// Therefore there is the [stopGracePeriod] during which messages get
232+
// logged to the global logger without the warning.
222233
goroutineWarningDone bool
223234

224235
formatter serialize.Formatter
@@ -228,10 +239,15 @@ type tloggerShared struct {
228239
callDepth int
229240
}
230241

242+
// Log output of a leaked goroutine during this period after test completion
243+
// does not trigger the warning.
244+
const stopGracePeriod = 10 * time.Second
245+
231246
func (ls *tloggerShared) stop() {
232247
ls.mutex.Lock()
233248
defer ls.mutex.Unlock()
234249
ls.t = nil
250+
ls.stopTime = time.Now()
235251
}
236252

237253
// tlogger is the actual LogSink implementation.
@@ -241,6 +257,8 @@ type tlogger struct {
241257
values []interface{}
242258
}
243259

260+
// fallbackLogger is called while l.shared.mutex is locked and after it has
261+
// been determined that the original testing.TB is no longer usable.
244262
func (l tlogger) fallbackLogger() logr.Logger {
245263
logger := klog.Background().WithValues(l.values...).WithName(l.shared.testName + " leaked goroutine")
246264
if l.prefix != "" {
@@ -250,8 +268,12 @@ func (l tlogger) fallbackLogger() logr.Logger {
250268
logger = logger.WithCallDepth(l.shared.callDepth + 1)
251269

252270
if !l.shared.goroutineWarningDone {
253-
logger.WithCallDepth(1).Error(nil, "WARNING: test kept at least one goroutine running after test completion", "callstack", string(dbg.Stacks(false)))
254-
l.shared.goroutineWarningDone = true
271+
duration := time.Since(l.shared.stopTime)
272+
if duration > stopGracePeriod {
273+
274+
logger.WithCallDepth(1).Info("WARNING: test kept at least one goroutine running after test completion", "timeSinceCompletion", duration.Round(time.Second), "callstack", string(dbg.Stacks(false)))
275+
l.shared.goroutineWarningDone = true
276+
}
255277
}
256278
return logger
257279
}

ktesting/testinglogger_test.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"runtime"
1717
"sync"
1818
"testing"
19+
"time"
1920

2021
"k8s.io/klog/v2"
2122
"k8s.io/klog/v2/internal/test/require"
@@ -198,6 +199,7 @@ func TestStop(t *testing.T) {
198199
_, _, line, _ = runtime.Caller(0)
199200
logger.Info("simple info message")
200201
logger.Error(nil, "error message")
202+
time.Sleep(15 * time.Second)
201203
logger.WithName("me").WithValues("completed", true).Info("complex info message", "anotherValue", 1)
202204
}()
203205
})
@@ -212,11 +214,14 @@ func TestStop(t *testing.T) {
212214
// Strip time and pid prefix.
213215
actual = regexp.MustCompile(`(?m)^.* testinglogger_test.go:`).ReplaceAllString(actual, `testinglogger_test.go:`)
214216

217+
// Strip duration.
218+
actual = regexp.MustCompile(`timeSinceCompletion="\d+s"`).ReplaceAllString(actual, `timeSinceCompletion="<...>s"`)
219+
215220
// All lines from the callstack get stripped. We can be sure that it was non-empty because otherwise we wouldn't
216221
// have the < > markers.
217222
//
218223
// Full output:
219-
// testinglogger_test.go:194] "WARNING: test kept at least one goroutine running after test completion" logger="TestStop/Sub leaked goroutine" callstack=<
224+
// testinglogger_test.go:194] "WARNING: test kept at least one goroutine running after test completion" logger="TestStop/Sub leaked goroutine.me" completed=true timeSinceCompletion="15s" callstack=<
220225
// goroutine 23 [running]:
221226
// k8s.io/klog/v2/internal/dbg.Stacks(0x0)
222227
// /nvme/gopath/src/k8s.io/klog/internal/dbg/dbg.go:34 +0x8a
@@ -233,13 +238,13 @@ func TestStop(t *testing.T) {
233238
// >
234239
actual = regexp.MustCompile(`(?m)^\t.*?\n`).ReplaceAllString(actual, ``)
235240

236-
expected := fmt.Sprintf(`testinglogger_test.go:%d] "WARNING: test kept at least one goroutine running after test completion" logger="TestStop/Sub leaked goroutine" callstack=<
237-
>
238-
testinglogger_test.go:%d] "simple info message" logger="TestStop/Sub leaked goroutine"
241+
expected := fmt.Sprintf(`testinglogger_test.go:%d] "simple info message" logger="TestStop/Sub leaked goroutine"
239242
testinglogger_test.go:%d] "error message" logger="TestStop/Sub leaked goroutine"
243+
testinglogger_test.go:%d] "WARNING: test kept at least one goroutine running after test completion" logger="TestStop/Sub leaked goroutine.me" completed=true timeSinceCompletion="<...>s" callstack=<
244+
>
240245
testinglogger_test.go:%d] "complex info message" logger="TestStop/Sub leaked goroutine.me" completed=true anotherValue=1
241246
`,
242-
line+1, line+1, line+2, line+3)
247+
line+1, line+2, line+4, line+4)
243248
if actual != expected {
244249
t.Errorf("Output does not match. Expected:\n%s\nActual:\n%s\n", expected, actual)
245250
}

0 commit comments

Comments
 (0)