...

Source file src/github.com/onsi/gomega/gleak/have_leaked_matcher_test.go

Documentation: github.com/onsi/gomega/gleak

     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  })
   279  

View as plain text