| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package goroutines |
| |
| import ( |
| "bytes" |
| "runtime" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| ) |
| |
| const leakWaitTime = 250 * time.Millisecond |
| |
| func wrappedWaitForIt(wg *sync.WaitGroup, wait chan struct{}, n int64) { |
| if n == 0 { |
| waitForIt(wg, wait) |
| } else { |
| wrappedWaitForIt(wg, wait, n-1) |
| } |
| } |
| |
| func waitForIt(wg *sync.WaitGroup, wait chan struct{}) { |
| wg.Done() |
| <-wait |
| } |
| |
| func runGoA(wg *sync.WaitGroup, wait chan struct{}) { |
| go waitForIt(wg, wait) |
| } |
| |
| func runGoB(wg *sync.WaitGroup, wait chan struct{}) { |
| go wrappedWaitForIt(wg, wait, 3) |
| } |
| |
| func runGoC(wg *sync.WaitGroup, wait chan struct{}) { |
| go func() { |
| wg.Done() |
| <-wait |
| }() |
| } |
| |
| func TestGet(t *testing.T) { |
| defer NoLeaks(t, leakWaitTime)() |
| var wg sync.WaitGroup |
| wg.Add(3) |
| wait := make(chan struct{}) |
| runGoA(&wg, wait) |
| runGoB(&wg, wait) |
| runGoC(&wg, wait) |
| wg.Wait() |
| gs, err := Get() |
| if err != nil { |
| t.Fatal(err) |
| } |
| close(wait) |
| |
| if len(gs) < 4 { |
| t.Errorf("Got %d goroutines, expected at least 4", len(gs)) |
| } |
| bycreator := map[string]*Goroutine{} |
| for _, g := range gs { |
| key := "" |
| if g.Creator != nil { |
| key = g.Creator.Call |
| } |
| bycreator[key] = g |
| } |
| a := bycreator["v.io/x/ref/test/goroutines.runGoA"] |
| if a == nil { |
| t.Errorf("runGoA is missing") |
| } else if len(a.Stack) < 1 { |
| t.Errorf("got %d expected at least 1: %s", len(a.Stack), Format(a)) |
| } else if !strings.HasPrefix(a.Stack[0].Call, "v.io/x/ref/test/goroutines.waitForIt") { |
| t.Errorf("got %s, wanted it to start with v.io/x/ref/test/goroutines.waitForIt", |
| a.Stack[0].Call) |
| } |
| b := bycreator["v.io/x/ref/test/goroutines.runGoB"] |
| if b == nil { |
| t.Errorf("runGoB is missing") |
| } else if len(b.Stack) < 5 { |
| t.Errorf("got %d expected at least 5: %s", len(b.Stack), Format(b)) |
| } |
| c := bycreator["v.io/x/ref/test/goroutines.runGoC"] |
| if c == nil { |
| t.Errorf("runGoC is missing") |
| } else if len(c.Stack) < 1 { |
| t.Errorf("got %d expected at least 1: %s", len(c.Stack), Format(c)) |
| } |
| } |
| |
| func TestFormat(t *testing.T) { |
| defer NoLeaks(t, leakWaitTime)() |
| |
| var wg sync.WaitGroup |
| wg.Add(3) |
| wait := make(chan struct{}) |
| runGoA(&wg, wait) |
| runGoB(&wg, wait) |
| runGoC(&wg, wait) |
| wg.Wait() |
| |
| buf := make([]byte, 1<<20) |
| buf = buf[:runtime.Stack(buf, true)] |
| close(wait) |
| |
| gs, err := Parse(buf, false) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if formatted := Format(gs...); !bytes.Equal(buf, formatted) { |
| t.Errorf("got:\n%s\nwanted:\n%s\n", string(formatted), string(buf)) |
| } |
| } |
| |
| type fakeErrorReporter struct { |
| calls int |
| extra int |
| formatted string |
| } |
| |
| func (f *fakeErrorReporter) Errorf(format string, args ...interface{}) { |
| f.calls++ |
| f.extra = args[0].(int) |
| f.formatted = args[1].(string) |
| } |
| |
| func TestNoLeaks(t *testing.T) { |
| er := &fakeErrorReporter{} |
| f := NoLeaks(er, leakWaitTime) |
| |
| var wg sync.WaitGroup |
| wg.Add(3) |
| wait := make(chan struct{}) |
| runGoA(&wg, wait) |
| runGoB(&wg, wait) |
| runGoC(&wg, wait) |
| wg.Wait() |
| |
| f() |
| if er.calls != 1 { |
| t.Errorf("got %d, wanted 1: %s", er.calls, er.formatted) |
| } |
| if er.extra != 3 { |
| t.Errorf("got %d, wanted 3: %s", er.extra, er.formatted) |
| } |
| close(wait) |
| |
| *er = fakeErrorReporter{} |
| f() |
| if er.calls != 0 { |
| t.Errorf("got %d, wanted 0: %s", er.calls, er.formatted) |
| } |
| if er.extra != 0 { |
| t.Errorf("got %d, wanted 0: %s", er.extra, er.formatted) |
| } |
| } |