
This does not yet include support for the //go:embed directive added in this release. * Makefile.am (check-runtime): Don't create check-runtime-dir. (mostlyclean-local): Don't remove check-runtime-dir. (check-go-tool, check-vet): Copy in go.mod and modules.txt. (check-cgo-test, check-carchive-test): Add go.mod file. * Makefile.in: Regenerate. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/280172
333 lines
7.9 KiB
Go
333 lines
7.9 KiB
Go
// Copyright 2014 The Go 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 trace_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"internal/testenv"
|
|
"internal/trace"
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
. "runtime/trace"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"text/tabwriter"
|
|
"time"
|
|
)
|
|
|
|
// TestTraceSymbolize tests symbolization and that events has proper stacks.
|
|
// In particular that we strip bottom uninteresting frames like goexit,
|
|
// top uninteresting frames (runtime guts).
|
|
func TestTraceSymbolize(t *testing.T) {
|
|
skipTraceSymbolizeTestIfNecessary(t)
|
|
|
|
buf := new(bytes.Buffer)
|
|
if err := Start(buf); err != nil {
|
|
t.Fatalf("failed to start tracing: %v", err)
|
|
}
|
|
defer Stop() // in case of early return
|
|
|
|
// Now we will do a bunch of things for which we verify stacks later.
|
|
// It is impossible to ensure that a goroutine has actually blocked
|
|
// on a channel, in a select or otherwise. So we kick off goroutines
|
|
// that need to block first in the hope that while we are executing
|
|
// the rest of the test, they will block.
|
|
go func() { // func1
|
|
select {}
|
|
}()
|
|
go func() { // func2
|
|
var c chan int
|
|
c <- 0
|
|
}()
|
|
go func() { // func3
|
|
var c chan int
|
|
<-c
|
|
}()
|
|
done1 := make(chan bool)
|
|
go func() { // func4
|
|
<-done1
|
|
}()
|
|
done2 := make(chan bool)
|
|
go func() { // func5
|
|
done2 <- true
|
|
}()
|
|
c1 := make(chan int)
|
|
c2 := make(chan int)
|
|
go func() { // func6
|
|
select {
|
|
case <-c1:
|
|
case <-c2:
|
|
}
|
|
}()
|
|
var mu sync.Mutex
|
|
mu.Lock()
|
|
go func() { // func7
|
|
mu.Lock()
|
|
mu.Unlock()
|
|
}()
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() { // func8
|
|
wg.Wait()
|
|
}()
|
|
cv := sync.NewCond(&sync.Mutex{})
|
|
go func() { // func9
|
|
cv.L.Lock()
|
|
cv.Wait()
|
|
cv.L.Unlock()
|
|
}()
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("failed to listen: %v", err)
|
|
}
|
|
go func() { // func10
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
t.Errorf("failed to accept: %v", err)
|
|
return
|
|
}
|
|
c.Close()
|
|
}()
|
|
rp, wp, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("failed to create a pipe: %v", err)
|
|
}
|
|
defer rp.Close()
|
|
defer wp.Close()
|
|
pipeReadDone := make(chan bool)
|
|
go func() { // func11
|
|
var data [1]byte
|
|
rp.Read(data[:])
|
|
pipeReadDone <- true
|
|
}()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
runtime.GC()
|
|
runtime.Gosched()
|
|
time.Sleep(100 * time.Millisecond) // the last chance for the goroutines above to block
|
|
done1 <- true
|
|
<-done2
|
|
select {
|
|
case c1 <- 0:
|
|
case c2 <- 0:
|
|
}
|
|
mu.Unlock()
|
|
wg.Done()
|
|
cv.Signal()
|
|
c, err := net.Dial("tcp", ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("failed to dial: %v", err)
|
|
}
|
|
c.Close()
|
|
var data [1]byte
|
|
wp.Write(data[:])
|
|
<-pipeReadDone
|
|
|
|
oldGoMaxProcs := runtime.GOMAXPROCS(0)
|
|
runtime.GOMAXPROCS(oldGoMaxProcs + 1)
|
|
|
|
Stop()
|
|
|
|
runtime.GOMAXPROCS(oldGoMaxProcs)
|
|
|
|
events, _ := parseTrace(t, buf)
|
|
|
|
// Now check that the stacks are correct.
|
|
type eventDesc struct {
|
|
Type byte
|
|
Stk []frame
|
|
}
|
|
want := []eventDesc{
|
|
{trace.EvGCStart, []frame{
|
|
{"runtime.GC", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 0},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoStart, []frame{
|
|
{"runtime/trace_test.TestTraceSymbolize.func1", 0},
|
|
}},
|
|
{trace.EvGoSched, []frame{
|
|
{"runtime/trace_test.TestTraceSymbolize", 111},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoCreate, []frame{
|
|
{"runtime/trace_test.TestTraceSymbolize", 40},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoStop, []frame{
|
|
{"runtime.block", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func1", 0},
|
|
}},
|
|
{trace.EvGoStop, []frame{
|
|
{"runtime.chansend1", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func2", 0},
|
|
}},
|
|
{trace.EvGoStop, []frame{
|
|
{"runtime.chanrecv1", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func3", 0},
|
|
}},
|
|
{trace.EvGoBlockRecv, []frame{
|
|
{"runtime.chanrecv1", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func4", 0},
|
|
}},
|
|
{trace.EvGoUnblock, []frame{
|
|
{"runtime.chansend1", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 113},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoBlockSend, []frame{
|
|
{"runtime.chansend1", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func5", 0},
|
|
}},
|
|
{trace.EvGoUnblock, []frame{
|
|
{"runtime.chanrecv1", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 114},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoBlockSelect, []frame{
|
|
{"runtime.selectgo", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func6", 0},
|
|
}},
|
|
{trace.EvGoUnblock, []frame{
|
|
{"runtime.selectgo", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 115},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoBlockSync, []frame{
|
|
{"sync.(*Mutex).Lock", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func7", 0},
|
|
}},
|
|
{trace.EvGoUnblock, []frame{
|
|
{"sync.(*Mutex).Unlock", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 0},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoBlockSync, []frame{
|
|
{"sync.(*WaitGroup).Wait", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func8", 0},
|
|
}},
|
|
{trace.EvGoUnblock, []frame{
|
|
{"sync.(*WaitGroup).Add", 0},
|
|
{"sync.(*WaitGroup).Done", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 120},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoBlockCond, []frame{
|
|
{"sync.(*Cond).Wait", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func9", 0},
|
|
}},
|
|
{trace.EvGoUnblock, []frame{
|
|
{"sync.(*Cond).Signal", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 0},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGoSleep, []frame{
|
|
{"time.Sleep", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 0},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
{trace.EvGomaxprocs, []frame{
|
|
{"runtime.startTheWorld", 0}, // this is when the current gomaxprocs is logged.
|
|
{"runtime.startTheWorldGC", 0},
|
|
{"runtime.GOMAXPROCS", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize", 0},
|
|
{"testing.tRunner", 0},
|
|
}},
|
|
}
|
|
// Stacks for the following events are OS-dependent due to OS-specific code in net package.
|
|
if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
|
|
want = append(want, []eventDesc{
|
|
{trace.EvGoBlockNet, []frame{
|
|
{"internal/poll.(*FD).Accept", 0},
|
|
{"net.(*netFD).accept", 0},
|
|
{"net.(*TCPListener).accept", 0},
|
|
{"net.(*TCPListener).Accept", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func10", 0},
|
|
}},
|
|
{trace.EvGoSysCall, []frame{
|
|
{"syscall.read", 0},
|
|
{"syscall.Read", 0},
|
|
{"internal/poll.ignoringEINTRIO", 0},
|
|
{"internal/poll.(*FD).Read", 0},
|
|
{"os.(*File).read", 0},
|
|
{"os.(*File).Read", 0},
|
|
{"runtime/trace_test.TestTraceSymbolize.func11", 0},
|
|
}},
|
|
}...)
|
|
}
|
|
matched := make([]bool, len(want))
|
|
for _, ev := range events {
|
|
wantLoop:
|
|
for i, w := range want {
|
|
if matched[i] || w.Type != ev.Type || len(w.Stk) != len(ev.Stk) {
|
|
continue
|
|
}
|
|
|
|
for fi, f := range ev.Stk {
|
|
wf := w.Stk[fi]
|
|
if wf.Fn != f.Fn || wf.Line != 0 && wf.Line != f.Line {
|
|
continue wantLoop
|
|
}
|
|
}
|
|
matched[i] = true
|
|
}
|
|
}
|
|
for i, w := range want {
|
|
if matched[i] {
|
|
continue
|
|
}
|
|
seen, n := dumpEventStacks(w.Type, events)
|
|
t.Errorf("Did not match event %v with stack\n%s\nSeen %d events of the type\n%s",
|
|
trace.EventDescriptions[w.Type].Name, dumpFrames(w.Stk), n, seen)
|
|
}
|
|
}
|
|
|
|
func skipTraceSymbolizeTestIfNecessary(t *testing.T) {
|
|
testenv.MustHaveGoBuild(t)
|
|
if IsEnabled() {
|
|
t.Skip("skipping because -test.trace is set")
|
|
}
|
|
}
|
|
|
|
func dumpEventStacks(typ byte, events []*trace.Event) ([]byte, int) {
|
|
matched := 0
|
|
o := new(bytes.Buffer)
|
|
tw := tabwriter.NewWriter(o, 0, 8, 0, '\t', 0)
|
|
for _, ev := range events {
|
|
if ev.Type != typ {
|
|
continue
|
|
}
|
|
matched++
|
|
fmt.Fprintf(tw, "Offset %d\n", ev.Off)
|
|
for _, f := range ev.Stk {
|
|
fname := f.File
|
|
if idx := strings.Index(fname, "/go/src/"); idx > 0 {
|
|
fname = fname[idx:]
|
|
}
|
|
fmt.Fprintf(tw, " %v\t%s:%d\n", f.Fn, fname, f.Line)
|
|
}
|
|
}
|
|
tw.Flush()
|
|
return o.Bytes(), matched
|
|
}
|
|
|
|
type frame struct {
|
|
Fn string
|
|
Line int
|
|
}
|
|
|
|
func dumpFrames(frames []frame) []byte {
|
|
o := new(bytes.Buffer)
|
|
tw := tabwriter.NewWriter(o, 0, 8, 0, '\t', 0)
|
|
|
|
for _, f := range frames {
|
|
fmt.Fprintf(tw, " %v\t :%d\n", f.Fn, f.Line)
|
|
}
|
|
tw.Flush()
|
|
return o.Bytes()
|
|
}
|