...

Source file src/github.com/onsi/gomega/gleak/goroutine/goroutine_test.go

Documentation: github.com/onsi/gomega/gleak/goroutine

     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  }
   242  

View as plain text