Source file src/runtime/trace_cgo_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build cgo
     6  
     7  package runtime_test
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"internal/trace"
    14  	"io"
    15  	"os"
    16  	"runtime"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  // TestTraceUnwindCGO verifies that trace events emitted in cgo callbacks
    22  // produce the same stack traces and don't cause any crashes regardless of
    23  // tracefpunwindoff being set to 0 or 1.
    24  func TestTraceUnwindCGO(t *testing.T) {
    25  	if *flagQuick {
    26  		t.Skip("-quick")
    27  	}
    28  	testenv.MustHaveGoBuild(t)
    29  	t.Parallel()
    30  
    31  	exe, err := buildTestProg(t, "testprogcgo")
    32  	if err != nil {
    33  		t.Fatal(err)
    34  	}
    35  
    36  	wantLogs := []string{
    37  		"goCalledFromC",
    38  		"goCalledFromCThread",
    39  	}
    40  	logs := make(map[string]*trace.Event)
    41  	for _, category := range wantLogs {
    42  		logs[category] = nil
    43  	}
    44  	for _, tracefpunwindoff := range []int{1, 0} {
    45  		env := fmt.Sprintf("GODEBUG=tracefpunwindoff=%d", tracefpunwindoff)
    46  		got := runBuiltTestProg(t, exe, "Trace", env)
    47  		prefix, tracePath, found := strings.Cut(got, ":")
    48  		if !found || prefix != "trace path" {
    49  			t.Fatalf("unexpected output:\n%s\n", got)
    50  		}
    51  		defer os.Remove(tracePath)
    52  
    53  		traceData, err := os.ReadFile(tracePath)
    54  		if err != nil {
    55  			t.Fatalf("failed to read trace: %s", err)
    56  		}
    57  		for category := range logs {
    58  			event := mustFindLogV2(t, bytes.NewReader(traceData), category)
    59  			if wantEvent := logs[category]; wantEvent == nil {
    60  				logs[category] = &event
    61  			} else if got, want := dumpStackV2(&event), dumpStackV2(wantEvent); got != want {
    62  				t.Errorf("%q: got stack:\n%s\nwant stack:\n%s\n", category, got, want)
    63  			}
    64  		}
    65  	}
    66  }
    67  
    68  func mustFindLogV2(t *testing.T, trc io.Reader, category string) trace.Event {
    69  	r, err := trace.NewReader(trc)
    70  	if err != nil {
    71  		t.Fatalf("bad trace: %v", err)
    72  	}
    73  	var candidates []trace.Event
    74  	for {
    75  		ev, err := r.ReadEvent()
    76  		if err == io.EOF {
    77  			break
    78  		}
    79  		if err != nil {
    80  			t.Fatalf("failed to parse trace: %v", err)
    81  		}
    82  		if ev.Kind() == trace.EventLog && ev.Log().Category == category {
    83  			candidates = append(candidates, ev)
    84  		}
    85  	}
    86  	if len(candidates) == 0 {
    87  		t.Fatalf("could not find log with category: %q", category)
    88  	} else if len(candidates) > 1 {
    89  		t.Fatalf("found more than one log with category: %q", category)
    90  	}
    91  	return candidates[0]
    92  }
    93  
    94  // dumpStack returns e.Stack() as a string.
    95  func dumpStackV2(e *trace.Event) string {
    96  	var buf bytes.Buffer
    97  	for f := range e.Stack().Frames() {
    98  		file := strings.TrimPrefix(f.File, runtime.GOROOT())
    99  		fmt.Fprintf(&buf, "%s\n\t%s:%d\n", f.Func, file, f.Line)
   100  	}
   101  	return buf.String()
   102  }
   103  

View as plain text