blob: 3b3f725ef5a9602034f86838dbbd3acf07215f33 [file] [log] [blame]
// 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)
}
}