github.com/onsi/gomega@v1.32.0/gleak/have_leaked_matcher_test.go (about) 1 package gleak 2 3 import ( 4 "os" 5 "os/signal" 6 "sync" 7 "time" 8 9 . "github.com/onsi/ginkgo/v2" 10 . "github.com/onsi/gomega" 11 ) 12 13 // Note: Go's stack dumps (backtraces) always contain forward slashes, even on 14 // Windows. The following tests thus work the same both on *nix and Windows. 15 16 var _ = Describe("HaveLeaked", func() { 17 18 It("renders indented goroutine information including (malformed) backtrace", func() { 19 gs := []Goroutine{ 20 { 21 ID: 42, 22 State: "stoned", 23 Backtrace: `main.foo.func1() 24 /home/foo/test.go:6 +0x28 25 created by main.foo 26 /home/foo/test.go:5 +0x64 27 `, 28 }, 29 } 30 m := HaveLeaked().(*HaveLeakedMatcher) 31 Expect(m.listGoroutines(gs, 1)).To(Equal(` goroutine 42 [stoned] 32 main.foo.func1() at foo/test.go:6 33 created by main.foo at foo/test.go:5`)) 34 35 gs = []Goroutine{ 36 { 37 ID: 42, 38 State: "stoned", 39 Backtrace: `main.foo.func1() 40 /home/foo/test.go:6 +0x28 41 created by main.foo 42 /home/foo/test.go:5 +0x64`, 43 }, 44 } 45 Expect(m.listGoroutines(gs, 1)).To(Equal(` goroutine 42 [stoned] 46 main.foo.func1() at foo/test.go:6 47 created by main.foo at foo/test.go:5`)) 48 49 gs = []Goroutine{ 50 { 51 ID: 42, 52 State: "stoned", 53 Backtrace: `main.foo.func1() 54 /home/foo/test.go:6 +0x28 55 created by main.foo 56 /home/foo/test.go:5`, 57 }, 58 } 59 Expect(m.listGoroutines(gs, 1)).To(Equal(` goroutine 42 [stoned] 60 main.foo.func1() at foo/test.go:6 61 created by main.foo at foo/test.go:5`)) 62 63 gs = []Goroutine{ 64 { 65 ID: 42, 66 State: "stoned", 67 Backtrace: `main.foo.func1() 68 /home/foo/test.go:6 +0x28 69 created by main.foo`, 70 }, 71 } 72 Expect(m.listGoroutines(gs, 1)).To(Equal(` goroutine 42 [stoned] 73 main.foo.func1() at foo/test.go:6 74 created by main.foo`)) 75 }) 76 77 It("considers testing and runtime goroutines not to be leaks", func() { 78 Eventually(Goroutines).WithTimeout(2*time.Second).WithPolling(250*time.Millisecond). 79 ShouldNot(HaveLeaked(), "should not find any leaks by default") 80 }) 81 82 When("using signals", func() { 83 84 It("doesn't find leaks", func() { 85 c := make(chan os.Signal, 1) 86 signal.Notify(c, os.Interrupt) 87 Eventually(Goroutines).WithTimeout(2*time.Second).WithPolling(250*time.Millisecond). 88 ShouldNot(HaveLeaked(), "found signal.Notify leaks") 89 90 signal.Reset(os.Interrupt) 91 Eventually(Goroutines).WithTimeout(2*time.Second).WithPolling(250*time.Millisecond). 92 ShouldNot(HaveLeaked(), "found signal.Reset leaks") 93 }) 94 95 }) 96 97 It("checks against list of expected goroutines", func() { 98 By("taking a snapshot") 99 gs := Goroutines() 100 m := HaveLeaked(gs) 101 102 By("starting a goroutine") 103 done := make(chan struct{}) 104 var once sync.Once 105 go func() { 106 <-done 107 }() 108 defer once.Do(func() { close(done) }) 109 110 By("detecting the goroutine") 111 Expect(m.Match(Goroutines())).To(BeTrue()) 112 113 By("terminating the goroutine and ensuring it has terminated") 114 once.Do(func() { close(done) }) 115 Eventually(func() (bool, error) { 116 return m.Match(Goroutines()) 117 }).Should(BeFalse()) 118 }) 119 120 Context("failure messages", func() { 121 122 var snapshot []Goroutine 123 124 BeforeEach(func() { 125 snapshot = Goroutines() 126 done := make(chan struct{}) 127 go func() { 128 <-done 129 }() 130 DeferCleanup(func() { 131 close(done) 132 Eventually(Goroutines).ShouldNot(HaveLeaked(snapshot)) 133 }) 134 }) 135 136 It("returns a failure message", func() { 137 m := HaveLeaked(snapshot) 138 gs := Goroutines() 139 Expect(m.Match(gs)).To(BeTrue()) 140 Expect(m.FailureMessage(gs)).To(MatchRegexp(`Expected to leak 1 goroutines: 141 goroutine \d+ \[.+\] 142 .* at .*:\d+ 143 created by .* at .*:\d+`)) 144 }) 145 146 It("returns a negated failure message", func() { 147 m := HaveLeaked(snapshot) 148 gs := Goroutines() 149 Expect(m.Match(gs)).To(BeTrue()) 150 Expect(m.NegatedFailureMessage(gs)).To(MatchRegexp(`Expected not to leak 1 goroutines: 151 goroutine \d+ \[.+\] 152 .* at .*:\d+ 153 created by .* at .*:\d+`)) 154 }) 155 156 When("things go wrong", func() { 157 158 It("rejects unsupported filter args types", func() { 159 Expect(func() { _ = HaveLeaked(42) }).To(PanicWith( 160 "HaveLeaked expected a string, []Goroutine, or GomegaMatcher, but got:\n <int>: 42")) 161 }) 162 163 It("accepts plain strings as filters", func() { 164 m := HaveLeaked("foo.bar") 165 Expect(m.Match([]Goroutine{ 166 {TopFunction: "foo.bar"}, 167 })).To(BeFalse()) 168 }) 169 170 It("expects actual to be a slice of Goroutine", func() { 171 m := HaveLeaked() 172 Expect(m.Match(nil)).Error().To(MatchError( 173 "HaveLeaked matcher expects an array or slice of goroutines. Got:\n <nil>: nil")) 174 Expect(m.Match("foo!")).Error().To(MatchError( 175 "HaveLeaked matcher expects an array or slice of goroutines. Got:\n <string>: foo!")) 176 Expect(m.Match([]string{"foo!"})).Error().To(MatchError( 177 "HaveLeaked matcher expects an array or slice of goroutines. Got:\n <[]string | len:1, cap:1>: [\"foo!\"]")) 178 }) 179 180 It("handles filter matcher errors", func() { 181 m := HaveLeaked(HaveField("foobar", BeNil())) 182 Expect(m.Match([]Goroutine{ 183 {ID: 0}, 184 })).Error().To(HaveOccurred()) 185 }) 186 187 }) 188 189 }) 190 191 Context("wrapped around test nodes", func() { 192 193 var snapshot []Goroutine 194 195 When("not leaking", func() { 196 197 BeforeEach(func() { 198 snapshot = Goroutines() 199 }) 200 201 AfterEach(func() { 202 Eventually(Goroutines).ShouldNot(HaveLeaked(snapshot)) 203 }) 204 205 It("doesn't leak in test", func() { 206 // nothing 207 }) 208 209 }) 210 211 When("leaking", func() { 212 213 done := make(chan struct{}) 214 215 BeforeEach(func() { 216 snapshot = Goroutines() 217 }) 218 219 AfterEach(func() { 220 Expect(Goroutines()).To(HaveLeaked(snapshot)) 221 close(done) 222 Eventually(Goroutines).ShouldNot(HaveLeaked(snapshot)) 223 }) 224 225 It("leaks in test", func() { 226 go func() { 227 <-done 228 }() 229 }) 230 231 }) 232 233 }) 234 235 Context("handling file names and paths in backtraces", func() { 236 237 When("ReportFilenameWithPath is true", Ordered, func() { 238 239 var oldState bool 240 241 BeforeAll(func() { 242 oldState = ReportFilenameWithPath 243 ReportFilenameWithPath = true 244 DeferCleanup(func() { 245 ReportFilenameWithPath = oldState 246 }) 247 }) 248 249 It("doesn't shorten filenames", func() { 250 Expect(formatFilename("/home/foo/bar/baz.go")).To(Equal("/home/foo/bar/baz.go")) 251 }) 252 253 }) 254 255 When("ReportFilenameWithPath is false", Ordered, func() { 256 257 var oldState bool 258 259 BeforeAll(func() { 260 oldState = ReportFilenameWithPath 261 ReportFilenameWithPath = false 262 DeferCleanup(func() { 263 ReportFilenameWithPath = oldState 264 }) 265 }) 266 267 It("does return only package and filename, but no path", func() { 268 Expect(formatFilename("/home/foo/bar/baz.go")).To(Equal("bar/baz.go")) 269 Expect(formatFilename("/bar/baz.go")).To(Equal("bar/baz.go")) 270 Expect(formatFilename("/baz.go")).To(Equal("baz.go")) 271 Expect(formatFilename("/")).To(Equal("/")) 272 }) 273 274 }) 275 276 }) 277 278 })