google.golang.org/grpc@v1.74.2/internal/leakcheck/leakcheck_test.go (about) 1 /* 2 * 3 * Copyright 2017 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package leakcheck 20 21 import ( 22 "context" 23 "fmt" 24 "strings" 25 "sync" 26 "testing" 27 "time" 28 29 "google.golang.org/grpc/internal" 30 ) 31 32 type testLogger struct { 33 errorCount int 34 errors []string 35 } 36 37 func (e *testLogger) Logf(string, ...any) { 38 } 39 40 func (e *testLogger) Errorf(format string, args ...any) { 41 e.errors = append(e.errors, fmt.Sprintf(format, args...)) 42 e.errorCount++ 43 } 44 45 func TestCheck(t *testing.T) { 46 const leakCount = 3 47 ch := make(chan struct{}) 48 for i := 0; i < leakCount; i++ { 49 go func() { <-ch }() 50 } 51 if leaked := interestingGoroutines(); len(leaked) != leakCount { 52 t.Errorf("interestingGoroutines() = %d, want length %d", len(leaked), leakCount) 53 } 54 e := &testLogger{} 55 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 56 defer cancel() 57 if CheckGoroutines(ctx, e); e.errorCount < leakCount { 58 t.Errorf("CheckGoroutines() = %d, want count %d", e.errorCount, leakCount) 59 t.Logf("leaked goroutines:\n%v", strings.Join(e.errors, "\n")) 60 } 61 close(ch) 62 ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) 63 defer cancel() 64 CheckGoroutines(ctx, t) 65 } 66 67 func ignoredTestingLeak(d time.Duration) { 68 time.Sleep(d) 69 } 70 71 func TestCheckRegisterIgnore(t *testing.T) { 72 RegisterIgnoreGoroutine("ignoredTestingLeak") 73 go ignoredTestingLeak(3 * time.Second) 74 const leakCount = 3 75 ch := make(chan struct{}) 76 for i := 0; i < leakCount; i++ { 77 go func() { <-ch }() 78 } 79 if leaked := interestingGoroutines(); len(leaked) != leakCount { 80 t.Errorf("interestingGoroutines() = %d, want length %d", len(leaked), leakCount) 81 } 82 e := &testLogger{} 83 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 84 defer cancel() 85 if CheckGoroutines(ctx, e); e.errorCount < leakCount { 86 t.Errorf("CheckGoroutines() = %d, want count %d", e.errorCount, leakCount) 87 t.Logf("leaked goroutines:\n%v", strings.Join(e.errors, "\n")) 88 } 89 close(ch) 90 ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) 91 defer cancel() 92 CheckGoroutines(ctx, t) 93 } 94 95 // TestTrackTimers verifies that only leaked timers are reported and expired, 96 // stopped timers are ignored. 97 func TestTrackTimers(t *testing.T) { 98 TrackTimers() 99 const leakCount = 3 100 for i := 0; i < leakCount; i++ { 101 internal.TimeAfterFunc(2*time.Second, func() { 102 t.Logf("Timer %d fired.", i) 103 }) 104 } 105 wg := sync.WaitGroup{} 106 // Let a couple of timers expire. 107 for i := 0; i < 2; i++ { 108 wg.Add(1) 109 internal.TimeAfterFunc(time.Millisecond, func() { 110 wg.Done() 111 }) 112 } 113 wg.Wait() 114 115 // Stop a couple of timers. 116 for i := 0; i < leakCount; i++ { 117 t := internal.TimeAfterFunc(time.Hour, func() { 118 t.Error("Timer fired before test ended.") 119 }) 120 t.Stop() 121 } 122 123 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 124 defer cancel() 125 e := &testLogger{} 126 CheckTimers(ctx, e) 127 if e.errorCount != leakCount { 128 t.Errorf("CheckTimers found %v leaks, want %v leaks", e.errorCount, leakCount) 129 t.Logf("leaked timers:\n%v", strings.Join(e.errors, "\n")) 130 } 131 ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) 132 defer cancel() 133 CheckTimers(ctx, t) 134 }