1// Copyright 2014 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
5package main
6
7import (
8	"bufio"
9	"bytes"
10	"internal/testenv"
11	"os"
12	"path/filepath"
13	"runtime"
14	"strings"
15	"sync"
16	"testing"
17)
18
19// TestMain executes the test binary as the addr2line command if
20// GO_ADDR2LINETEST_IS_ADDR2LINE is set, and runs the tests otherwise.
21func TestMain(m *testing.M) {
22	if os.Getenv("GO_ADDR2LINETEST_IS_ADDR2LINE") != "" {
23		main()
24		os.Exit(0)
25	}
26
27	os.Setenv("GO_ADDR2LINETEST_IS_ADDR2LINE", "1") // Set for subprocesses to inherit.
28	os.Exit(m.Run())
29}
30
31// addr2linePath returns the path to the "addr2line" binary to run.
32func addr2linePath(t testing.TB) string {
33	t.Helper()
34	testenv.MustHaveExec(t)
35
36	addr2linePathOnce.Do(func() {
37		addr2lineExePath, addr2linePathErr = os.Executable()
38	})
39	if addr2linePathErr != nil {
40		t.Fatal(addr2linePathErr)
41	}
42	return addr2lineExePath
43}
44
45var (
46	addr2linePathOnce sync.Once
47	addr2lineExePath  string
48	addr2linePathErr  error
49)
50
51func loadSyms(t *testing.T, dbgExePath string) map[string]string {
52	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", dbgExePath)
53	out, err := cmd.CombinedOutput()
54	if err != nil {
55		t.Fatalf("%v: %v\n%s", cmd, err, string(out))
56	}
57	syms := make(map[string]string)
58	scanner := bufio.NewScanner(bytes.NewReader(out))
59	for scanner.Scan() {
60		f := strings.Fields(scanner.Text())
61		if len(f) < 3 {
62			continue
63		}
64		syms[f[2]] = f[0]
65	}
66	if err := scanner.Err(); err != nil {
67		t.Fatalf("error reading symbols: %v", err)
68	}
69	return syms
70}
71
72func runAddr2Line(t *testing.T, dbgExePath, addr string) (funcname, path, lineno string) {
73	cmd := testenv.Command(t, addr2linePath(t), dbgExePath)
74	cmd.Stdin = strings.NewReader(addr)
75	out, err := cmd.CombinedOutput()
76	if err != nil {
77		t.Fatalf("go tool addr2line %v: %v\n%s", os.Args[0], err, string(out))
78	}
79	f := strings.Split(string(out), "\n")
80	if len(f) < 3 && f[2] == "" {
81		t.Fatal("addr2line output must have 2 lines")
82	}
83	funcname = f[0]
84	pathAndLineNo := f[1]
85	f = strings.Split(pathAndLineNo, ":")
86	if runtime.GOOS == "windows" && len(f) == 3 {
87		// Reattach drive letter.
88		f = []string{f[0] + ":" + f[1], f[2]}
89	}
90	if len(f) != 2 {
91		t.Fatalf("no line number found in %q", pathAndLineNo)
92	}
93	return funcname, f[0], f[1]
94}
95
96const symName = "cmd/addr2line.TestAddr2Line"
97
98func testAddr2Line(t *testing.T, dbgExePath, addr string) {
99	funcName, srcPath, srcLineNo := runAddr2Line(t, dbgExePath, addr)
100	if symName != funcName {
101		t.Fatalf("expected function name %v; got %v", symName, funcName)
102	}
103	fi1, err := os.Stat("addr2line_test.go")
104	if err != nil {
105		t.Fatalf("Stat failed: %v", err)
106	}
107
108	// Debug paths are stored slash-separated, so convert to system-native.
109	srcPath = filepath.FromSlash(srcPath)
110	fi2, err := os.Stat(srcPath)
111
112	if err != nil {
113		t.Fatalf("Stat failed: %v", err)
114	}
115	if !os.SameFile(fi1, fi2) {
116		t.Fatalf("addr2line_test.go and %s are not same file", srcPath)
117	}
118	if want := "124"; srcLineNo != want {
119		t.Fatalf("line number = %v; want %s", srcLineNo, want)
120	}
121}
122
123// This is line 123. The test depends on that.
124func TestAddr2Line(t *testing.T) {
125	testenv.MustHaveGoBuild(t)
126
127	tmpDir, err := os.MkdirTemp("", "TestAddr2Line")
128	if err != nil {
129		t.Fatal("TempDir failed: ", err)
130	}
131	defer os.RemoveAll(tmpDir)
132
133	// Build copy of test binary with debug symbols,
134	// since the one running now may not have them.
135	exepath := filepath.Join(tmpDir, "testaddr2line_test.exe")
136	out, err := testenv.Command(t, testenv.GoToolPath(t), "test", "-c", "-o", exepath, "cmd/addr2line").CombinedOutput()
137	if err != nil {
138		t.Fatalf("go test -c -o %v cmd/addr2line: %v\n%s", exepath, err, string(out))
139	}
140
141	syms := loadSyms(t, exepath)
142
143	testAddr2Line(t, exepath, syms[symName])
144	testAddr2Line(t, exepath, "0x"+syms[symName])
145}
146