1 /* 2 package gleak complements the Gingko/Gomega testing and matchers framework with 3 matchers for Goroutine leakage detection. 4 5 # Basics of gleak 6 7 To start with, 8 9 Goroutines() 10 11 returns information about all (non-dead) goroutines at a particular moment. This 12 is useful to capture a known correct snapshot and then later taking a new 13 snapshot and comparing these two snapshots for leaked goroutines. 14 15 Next, the matcher 16 17 HaveLeaked(...) 18 19 filters out well-known and expected "non-leaky" goroutines from an actual list 20 of goroutines (passed from Eventually or Expect), hopefully ending up with an 21 empty list of leaked goroutines. If there are still goroutines left after 22 filtering, then HaveLeaked() will succeed ... which usually is actually 23 considered to be failure. So, this can be rather declared to be "suckcess" 24 because no one wants leaked goroutines. 25 26 A typical pattern to detect goroutines leaked in individual tests is as follows: 27 28 var ignoreGood []Goroutine 29 30 BeforeEach(func() { 31 ignoreGood = Goroutines() 32 }) 33 34 AfterEach(func() { 35 // Note: it's "Goroutines", but not "Goroutines()", when using with Eventually! 36 Eventually(Goroutines).ShouldNot(HaveLeaked(ignoreGood)) 37 }) 38 39 Using Eventually instead of Expect ensures that there is some time given for 40 temporary goroutines to finally wind down. Gomega's default values apply: the 1s 41 timeout and 10ms polling interval. 42 43 Please note that the form 44 45 HaveLeaked(ignoreGood) 46 47 is the same as the slightly longer, but also more expressive variant: 48 49 HaveLeaked(IgnoringGoroutines(ignoreGood)) 50 51 # Leak-Related Matchers 52 53 Depending on your tests and the dependencies used, you might need to identify 54 additional goroutines as not being leaks. The gleak packages comes with the 55 following predefined goroutine "filter" matchers that can be specified as 56 arguments to HaveLeaked(...): 57 58 IgnoringTopFunction("foo.bar") // exactly "foo.bar" 59 IgnoringTopFunction("foo.bar...") // top function name with prefix "foo.bar." (note the trailing dot!) 60 IgnoringTopFunction("foo.bar [chan receive]") // exactly "foo.bar" with state starting with "chan receive" 61 IgnoringGoroutines(expectedGoroutines) // ignore specified goroutines with these IDs 62 IgnoringInBacktrace("foo.bar.baz") // "foo.bar.baz" within the backtrace 63 IgnoringCreator("foo.bar") // exact creator function name "foo.bar" 64 IgnoringCreator("foo.bar...") // creator function name with prefix "foo.bar." 65 66 In addition, you can use any other GomegaMatcher, as long as it can work on a 67 (single) Goroutine. For instance, Gomega's HaveField and WithTransform 68 matchers are good foundations for writing project-specific gleak matchers. 69 70 # Leaked Goroutine Dump 71 72 By default, when gleak's HaveLeaked matcher finds one or more leaked 73 goroutines, it dumps the goroutine backtraces in a condensed format that uses 74 only a single line per call instead of two lines. Moreover, the backtraces 75 abbreviate the source file location in the form of package/source.go:lineno: 76 77 goroutine 42 [flabbergasted] 78 main.foo.func1() at foo/test.go:6 79 created by main.foo at foo/test.go:5 80 81 By setting gleak.ReportFilenameWithPath=true the leaky goroutine backtraces 82 will show full path names for each source file: 83 84 goroutine 42 [flabbergasted] 85 main.foo.func1() at /home/go/foo/test.go:6 86 created by main.foo at home/go/foo/test.go:5 87 88 # Acknowledgement 89 90 gleak has been heavily inspired by the Goroutine leak detector 91 github.com/uber-go/goleak. That's definitely a fine piece of work! 92 93 But then why another goroutine leak package? After a deep analysis of Uber's 94 goleak we decided against crunching goleak somehow half-assed into the Gomega 95 TDD matcher ecosystem. In particular, reusing and wrapping of the existing Uber 96 implementation would have become very awkward: goleak.Find combines all the 97 different elements of getting actual goroutines information, filtering them, 98 arriving at a leak conclusion, and even retrying multiple times all in just one 99 single exported function. Unfortunately, goleak makes gathering information 100 about all goroutines an internal matter, so we cannot reuse such functionality 101 elsewhere. 102 103 Users of the Gomega ecosystem are already experienced in arriving at conclusions 104 and retrying temporarily failing expectations: Gomega does it in form of 105 Eventually().ShouldNot(), and (without the trying aspect) with Expect().NotTo(). 106 So what is missing is only a goroutine leak detector in form of the HaveLeaked 107 matcher, as well as the ability to specify goroutine filters in order to sort 108 out the non-leaking (and therefore expected) goroutines, using a few filter 109 criteria. That is, a few new goroutine-related matchers. In this architecture, 110 even existing Gomega matchers can optionally be (re)used as the need arises. 111 112 # References 113 114 https://github.com/onsi/gomega and https://github.com/onsi/ginkgo. 115 */ 116 package gleak 117