github.com/onsi/gomega@v1.32.0/gleak/goroutine/goroutine_test.go (about) 1 package goroutine 2 3 import ( 4 "bufio" 5 "errors" 6 "io" 7 "reflect" 8 "strings" 9 "sync" 10 "testing/iotest" 11 "time" 12 13 . "github.com/onsi/ginkgo/v2" 14 . "github.com/onsi/gomega" 15 ) 16 17 var _ = Describe("goroutine", func() { 18 19 const stack = `runtime/debug.Stack() 20 /usr/local/go-faketime/src/runtime/debug/stack.go:24 +0x65 21 runtime/debug.PrintStack() 22 /usr/local/go-faketime/src/runtime/debug/stack.go:16 +0x19 23 main.main() 24 /tmp/sandbox3386995578/prog.go:10 +0x17 25 ` 26 const header = `goroutine 666 [running]: 27 ` 28 const nextStack = header + `main.hades() 29 /tmp/sandbox3386995578/prog.go:10 +0x17 30 ` 31 32 It("prints", func() { 33 Expect(Goroutine{ 34 ID: 1234, 35 State: "gone", 36 TopFunction: "gopher.hole", 37 }.String()).To(Equal( 38 "Goroutine ID: 1234, state: gone, top function: gopher.hole")) 39 40 Expect(Goroutine{ 41 ID: 1234, 42 State: "gone", 43 TopFunction: "gopher.hole", 44 CreatorFunction: "google", 45 BornAt: "/plan/10:2009", 46 }.String()).To(Equal( 47 "Goroutine ID: 1234, state: gone, top function: gopher.hole, created by: google, at: /plan/10:2009")) 48 49 Expect(Goroutine{ 50 ID: 1234, 51 State: "gone", 52 TopFunction: "gopher.hole", 53 CreatorFunction: "google", 54 BornAt: "/plan/10:2009", 55 }.GomegaString()).To(Equal( 56 "{ID: 1234, State: \"gone\", TopFunction: \"gopher.hole\", CreatorFunction: \"google\", BornAt: \"/plan/10:2009\"}")) 57 }) 58 59 Context("goroutine header", func() { 60 61 It("parses goroutine header", func() { 62 g := new(header) 63 Expect(g.ID).To(Equal(uint64(666))) 64 Expect(g.State).To(Equal("running")) 65 }) 66 67 It("panics on malformed goroutine header", func() { 68 Expect(func() { _ = new("a") }).To(PanicWith(MatchRegexp(`invalid stack header: .*`))) 69 Expect(func() { _ = new("a b") }).To(PanicWith(MatchRegexp(`invalid stack header: .*`))) 70 }) 71 72 It("panics on malformed goroutine ID", func() { 73 Expect(func() { _ = new("a b c:\n") }).To(PanicWith(MatchRegexp(`invalid stack header ID: "b", header: ".*"`))) 74 }) 75 76 }) 77 78 Context("goroutine backtrace", func() { 79 80 It("parses goroutine's backtrace", func() { 81 r := bufio.NewReader(strings.NewReader(stack)) 82 topF, backtrace := parseGoroutineBacktrace(r) 83 Expect(topF).To(Equal("runtime/debug.Stack")) 84 Expect(backtrace).To(Equal(stack)) 85 86 r.Reset(strings.NewReader(stack[:len(stack)-1])) 87 topF, backtrace = parseGoroutineBacktrace(r) 88 Expect(topF).To(Equal("runtime/debug.Stack")) 89 Expect(backtrace).To(Equal(stack[:len(stack)-1])) 90 }) 91 92 It("parses goroutine's backtrace until next goroutine header", func() { 93 r := bufio.NewReader(strings.NewReader(stack + nextStack)) 94 topF, backtrace := parseGoroutineBacktrace(r) 95 Expect(topF).To(Equal("runtime/debug.Stack")) 96 Expect(backtrace).To(Equal(stack)) 97 }) 98 99 It("panics on invalid function call stack entry", func() { 100 r := bufio.NewReader(strings.NewReader(`main.main 101 /somewhere/prog.go:123 +0x666 102 `)) 103 Expect(func() { parseGoroutineBacktrace(r) }).To(PanicWith(MatchRegexp(`invalid function call stack entry: "main.main"`))) 104 }) 105 106 It("panics on failing reader", func() { 107 Expect(func() { 108 parseGoroutineBacktrace(bufio.NewReader( 109 iotest.ErrReader(errors.New("foo failure")))) 110 }).To(PanicWith("parsing backtrace failed: foo failure")) 111 112 Expect(func() { 113 parseGoroutineBacktrace( 114 bufio.NewReaderSize( 115 iotest.TimeoutReader(strings.NewReader(strings.Repeat("x", 32))), 116 16)) 117 }).To(PanicWith("parsing backtrace failed: timeout")) 118 119 Expect(func() { 120 parseGoroutineBacktrace(bufio.NewReader( 121 iotest.ErrReader(io.ErrClosedPipe))) 122 }).To(PanicWith(MatchRegexp(`parsing backtrace failed: .*`))) 123 }) 124 125 It("parses goroutine information and stack", func() { 126 gs := parseStack([]byte(header + stack)) 127 Expect(gs).To(HaveLen(1)) 128 Expect(gs[0]).To(And( 129 HaveField("ID", uint64(666)), 130 HaveField("State", "running"), 131 HaveField("TopFunction", "runtime/debug.Stack"), 132 HaveField("Backtrace", stack))) 133 }) 134 135 It("finds its Creator", func() { 136 creator, location := findCreator(` 137 goroutine 42 [chan receive]: 138 main.foo.func1() 139 /home/foo/test.go:6 +0x28 140 created by main.foo 141 /home/foo/test.go:5 +0x64 142 `) 143 Expect(creator).To(Equal("main.foo")) 144 Expect(location).To(Equal("/home/foo/test.go:5")) 145 }) 146 147 It("handles missing or invalid creator information", func() { 148 creator, location := findCreator("") 149 Expect(creator).To(BeEmpty()) 150 Expect(location).To(BeEmpty()) 151 152 creator, location = findCreator(` 153 goroutine 42 [chan receive]: 154 main.foo.func1() 155 /home/foo/test.go:6 +0x28 156 created by`) 157 Expect(creator).To(BeEmpty()) 158 Expect(location).To(BeEmpty()) 159 160 creator, location = findCreator(` 161 goroutine 42 [chan receive]: 162 main.foo.func1() 163 /home/foo/test.go:6 +0x28 164 created by main.foo`) 165 Expect(creator).To(BeEmpty()) 166 Expect(location).To(BeEmpty()) 167 168 creator, location = findCreator(` 169 goroutine 42 [chan receive]: 170 main.foo.func1() 171 /home/foo/test.go:6 +0x28 172 created by main.foo 173 /home/foo/test.go:5 174 `) 175 Expect(creator).To(BeEmpty()) 176 Expect(location).To(BeEmpty()) 177 }) 178 179 }) 180 181 Context("live", func() { 182 183 It("discovers current goroutine information", func() { 184 type T struct{} 185 pkg := reflect.TypeOf(T{}).PkgPath() 186 gs := goroutines(false) 187 Expect(gs).To(HaveLen(1)) 188 Expect(gs[0]).To(And( 189 HaveField("ID", Not(BeZero())), 190 HaveField("State", "running"), 191 HaveField("TopFunction", pkg+".stacks"), 192 HaveField("Backtrace", MatchRegexp(pkg+`.stacks.* 193 `)))) 194 }) 195 196 It("discovers a goroutine's creator", func() { 197 ch := make(chan Goroutine) 198 go func() { 199 ch <- Current() 200 }() 201 g := <-ch 202 Expect(g.CreatorFunction).NotTo(BeEmpty(), "no creator: %s", g.Backtrace) 203 Expect(g.BornAt).NotTo(BeEmpty()) 204 }) 205 206 It("discovers all goroutine information", func() { 207 By("creating a chan receive canary goroutine") 208 done := make(chan struct{}) 209 go testWait(done) 210 once := sync.Once{} 211 cloze := func() { once.Do(func() { close(done) }) } 212 defer cloze() 213 214 By("getting all goroutines including canary") 215 type T struct{} 216 pkg := reflect.TypeOf(T{}).PkgPath() 217 Eventually(Goroutines). 218 WithTimeout(1 * time.Second).WithPolling(250 * time.Millisecond). 219 Should(ContainElements( 220 And( 221 HaveField("TopFunction", pkg+".stacks"), 222 HaveField("State", "running")), 223 And( 224 HaveField("TopFunction", pkg+".testWait"), 225 HaveField("State", "chan receive")), 226 )) 227 228 By("getting all goroutines after being done with the canary") 229 cloze() 230 Eventually(Goroutines). 231 WithTimeout(1 * time.Second).WithPolling(250 * time.Millisecond). 232 ShouldNot(ContainElement(HaveField("TopFunction", pkg+".testWait"))) 233 }) 234 235 }) 236 237 }) 238 239 func testWait(done <-chan struct{}) { 240 <-done 241 }