| // Copyright 2016 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 pprof | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"encoding/json" | 
 | 	"fmt" | 
 | 	"internal/profile" | 
 | 	"internal/testenv" | 
 | 	"io/ioutil" | 
 | 	"os" | 
 | 	"os/exec" | 
 | 	"reflect" | 
 | 	"runtime" | 
 | 	"strings" | 
 | 	"testing" | 
 | ) | 
 |  | 
 | // translateCPUProfile parses binary CPU profiling stack trace data | 
 | // generated by runtime.CPUProfile() into a profile struct. | 
 | // This is only used for testing. Real conversions stream the | 
 | // data into the profileBuilder as it becomes available. | 
 | func translateCPUProfile(data []uint64) (*profile.Profile, error) { | 
 | 	var buf bytes.Buffer | 
 | 	b := newProfileBuilder(&buf) | 
 | 	if err := b.addCPUData(data, nil); err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	b.build() | 
 | 	return profile.Parse(&buf) | 
 | } | 
 |  | 
 | // fmtJSON returns a pretty-printed JSON form for x. | 
 | // It works reasonbly well for printing protocol-buffer | 
 | // data structures like profile.Profile. | 
 | func fmtJSON(x interface{}) string { | 
 | 	js, _ := json.MarshalIndent(x, "", "\t") | 
 | 	return string(js) | 
 | } | 
 |  | 
 | func TestConvertCPUProfileEmpty(t *testing.T) { | 
 | 	// A test server with mock cpu profile data. | 
 | 	var buf bytes.Buffer | 
 |  | 
 | 	b := []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period) | 
 | 	p, err := translateCPUProfile(b) | 
 | 	if err != nil { | 
 | 		t.Fatalf("translateCPUProfile: %v", err) | 
 | 	} | 
 | 	if err := p.Write(&buf); err != nil { | 
 | 		t.Fatalf("writing profile: %v", err) | 
 | 	} | 
 |  | 
 | 	p, err = profile.Parse(&buf) | 
 | 	if err != nil { | 
 | 		t.Fatalf("profile.Parse: %v", err) | 
 | 	} | 
 |  | 
 | 	// Expected PeriodType and SampleType. | 
 | 	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} | 
 | 	sampleType := []*profile.ValueType{ | 
 | 		{Type: "samples", Unit: "count"}, | 
 | 		{Type: "cpu", Unit: "nanoseconds"}, | 
 | 	} | 
 |  | 
 | 	checkProfile(t, p, 2000*1000, periodType, sampleType, nil, "") | 
 | } | 
 |  | 
 | func f1() { f1() } | 
 | func f2() { f2() } | 
 |  | 
 | // testPCs returns two PCs and two corresponding memory mappings | 
 | // to use in test profiles. | 
 | func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) { | 
 | 	switch runtime.GOOS { | 
 | 	case "linux", "android", "netbsd": | 
 | 		// Figure out two addresses from /proc/self/maps. | 
 | 		mmap, err := ioutil.ReadFile("/proc/self/maps") | 
 | 		if err != nil { | 
 | 			t.Fatal(err) | 
 | 		} | 
 | 		mprof := &profile.Profile{} | 
 | 		if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil { | 
 | 			t.Fatalf("parsing /proc/self/maps: %v", err) | 
 | 		} | 
 | 		if len(mprof.Mapping) < 2 { | 
 | 			// It is possible for a binary to only have 1 executable | 
 | 			// region of memory. | 
 | 			t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping)) | 
 | 		} | 
 | 		addr1 = mprof.Mapping[0].Start | 
 | 		map1 = mprof.Mapping[0] | 
 | 		map1.BuildID, _ = elfBuildID(map1.File) | 
 | 		addr2 = mprof.Mapping[1].Start | 
 | 		map2 = mprof.Mapping[1] | 
 | 		map2.BuildID, _ = elfBuildID(map2.File) | 
 | 	case "js": | 
 | 		addr1 = uint64(funcPC(f1)) | 
 | 		addr2 = uint64(funcPC(f2)) | 
 | 	default: | 
 | 		addr1 = uint64(funcPC(f1)) | 
 | 		addr2 = uint64(funcPC(f2)) | 
 | 		// Fake mapping - HasFunctions will be true because two PCs from Go | 
 | 		// will be fully symbolized. | 
 | 		fake := &profile.Mapping{ID: 1, HasFunctions: true} | 
 | 		map1, map2 = fake, fake | 
 | 	} | 
 | 	return | 
 | } | 
 |  | 
 | func TestConvertCPUProfile(t *testing.T) { | 
 | 	addr1, addr2, map1, map2 := testPCs(t) | 
 |  | 
 | 	b := []uint64{ | 
 | 		3, 0, 500, // hz = 500 | 
 | 		5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2), // 10 samples in addr1 | 
 | 		5, 0, 40, uint64(addr2 + 1), uint64(addr2 + 2), // 40 samples in addr2 | 
 | 		5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2), // 10 samples in addr1 | 
 | 	} | 
 | 	p, err := translateCPUProfile(b) | 
 | 	if err != nil { | 
 | 		t.Fatalf("translating profile: %v", err) | 
 | 	} | 
 | 	period := int64(2000 * 1000) | 
 | 	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"} | 
 | 	sampleType := []*profile.ValueType{ | 
 | 		{Type: "samples", Unit: "count"}, | 
 | 		{Type: "cpu", Unit: "nanoseconds"}, | 
 | 	} | 
 | 	samples := []*profile.Sample{ | 
 | 		{Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{ | 
 | 			{ID: 1, Mapping: map1, Address: addr1}, | 
 | 			{ID: 2, Mapping: map1, Address: addr1 + 1}, | 
 | 		}}, | 
 | 		{Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{ | 
 | 			{ID: 3, Mapping: map2, Address: addr2}, | 
 | 			{ID: 4, Mapping: map2, Address: addr2 + 1}, | 
 | 		}}, | 
 | 	} | 
 | 	checkProfile(t, p, period, periodType, sampleType, samples, "") | 
 | } | 
 |  | 
 | func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample, defaultSampleType string) { | 
 | 	t.Helper() | 
 |  | 
 | 	if p.Period != period { | 
 | 		t.Errorf("p.Period = %d, want %d", p.Period, period) | 
 | 	} | 
 | 	if !reflect.DeepEqual(p.PeriodType, periodType) { | 
 | 		t.Errorf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType)) | 
 | 	} | 
 | 	if !reflect.DeepEqual(p.SampleType, sampleType) { | 
 | 		t.Errorf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType)) | 
 | 	} | 
 | 	if defaultSampleType != p.DefaultSampleType { | 
 | 		t.Errorf("p.DefaultSampleType = %v\nwant = %v", p.DefaultSampleType, defaultSampleType) | 
 | 	} | 
 | 	// Clear line info since it is not in the expected samples. | 
 | 	// If we used f1 and f2 above, then the samples will have line info. | 
 | 	for _, s := range p.Sample { | 
 | 		for _, l := range s.Location { | 
 | 			l.Line = nil | 
 | 		} | 
 | 	} | 
 | 	if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields | 
 | 		if len(p.Sample) == len(samples) { | 
 | 			for i := range p.Sample { | 
 | 				if !reflect.DeepEqual(p.Sample[i], samples[i]) { | 
 | 					t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i])) | 
 | 				} | 
 | 			} | 
 | 			if t.Failed() { | 
 | 				t.FailNow() | 
 | 			} | 
 | 		} | 
 | 		t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples)) | 
 | 	} | 
 | } | 
 |  | 
 | var profSelfMapsTests = ` | 
 | 00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat | 
 | 0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat | 
 | 0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat | 
 | 014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap] | 
 | 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive | 
 | 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0 | 
 | 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0 | 
 | 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0 | 
 | 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0 | 
 | 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack] | 
 | 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso] | 
 | ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0                  [vsyscall] | 
 | -> | 
 | 00400000 0040b000 00000000 /bin/cat | 
 | 7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7ffc34343000 7ffc34345000 00000000 [vdso] | 
 | ffffffffff600000 ffffffffff601000 00000090 [vsyscall] | 
 |  | 
 | 00400000-07000000 r-xp 00000000 00:00 0 | 
 | 07000000-07093000 r-xp 06c00000 00:2e 536754                             /path/to/gobench_server_main | 
 | 07093000-0722d000 rw-p 06c92000 00:2e 536754                             /path/to/gobench_server_main | 
 | 0722d000-07b21000 rw-p 00000000 00:00 0 | 
 | c000000000-c000036000 rw-p 00000000 00:00 0 | 
 | -> | 
 | 07000000 07093000 06c00000 /path/to/gobench_server_main | 
 | ` | 
 |  | 
 | var profSelfMapsTestsWithDeleted = ` | 
 | 00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat (deleted) | 
 | 0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat (deleted) | 
 | 0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat (deleted) | 
 | 014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap] | 
 | 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive | 
 | 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0 | 
 | 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0 | 
 | 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0 | 
 | 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0 | 
 | 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack] | 
 | 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso] | 
 | ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0                  [vsyscall] | 
 | -> | 
 | 00400000 0040b000 00000000 /bin/cat | 
 | 7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7ffc34343000 7ffc34345000 00000000 [vdso] | 
 | ffffffffff600000 ffffffffff601000 00000090 [vsyscall] | 
 |  | 
 | 00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat with space | 
 | 0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat with space | 
 | 0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat with space | 
 | 014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap] | 
 | 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive | 
 | 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0 | 
 | 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0 | 
 | 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0 | 
 | 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0 | 
 | 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack] | 
 | 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso] | 
 | ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0                  [vsyscall] | 
 | -> | 
 | 00400000 0040b000 00000000 /bin/cat with space | 
 | 7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so | 
 | 7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so | 
 | 7ffc34343000 7ffc34345000 00000000 [vdso] | 
 | ffffffffff600000 ffffffffff601000 00000090 [vsyscall] | 
 | ` | 
 |  | 
 | func TestProcSelfMaps(t *testing.T) { | 
 |  | 
 | 	f := func(t *testing.T, input string) { | 
 | 		for tx, tt := range strings.Split(input, "\n\n") { | 
 | 			i := strings.Index(tt, "->\n") | 
 | 			if i < 0 { | 
 | 				t.Fatal("malformed test case") | 
 | 			} | 
 | 			in, out := tt[:i], tt[i+len("->\n"):] | 
 | 			if len(out) > 0 && out[len(out)-1] != '\n' { | 
 | 				out += "\n" | 
 | 			} | 
 | 			var buf bytes.Buffer | 
 | 			parseProcSelfMaps([]byte(in), func(lo, hi, offset uint64, file, buildID string) { | 
 | 				fmt.Fprintf(&buf, "%08x %08x %08x %s\n", lo, hi, offset, file) | 
 | 			}) | 
 | 			if buf.String() != out { | 
 | 				t.Errorf("#%d: have:\n%s\nwant:\n%s\n%q\n%q", tx, buf.String(), out, buf.String(), out) | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	t.Run("Normal", func(t *testing.T) { | 
 | 		f(t, profSelfMapsTests) | 
 | 	}) | 
 |  | 
 | 	t.Run("WithDeletedFile", func(t *testing.T) { | 
 | 		f(t, profSelfMapsTestsWithDeleted) | 
 | 	}) | 
 | } | 
 |  | 
 | // TestMapping checks the mapping section of CPU profiles | 
 | // has the HasFunctions field set correctly. If all PCs included | 
 | // in the samples are successfully symbolized, the corresponding | 
 | // mapping entry (in this test case, only one entry) should have | 
 | // its HasFunctions field set true. | 
 | // The test generates a CPU profile that includes PCs from C side | 
 | // that the runtime can't symbolize. See ./testdata/mappingtest. | 
 | func TestMapping(t *testing.T) { | 
 | 	testenv.MustHaveGoRun(t) | 
 | 	testenv.MustHaveCGO(t) | 
 |  | 
 | 	prog := "./testdata/mappingtest/main.go" | 
 |  | 
 | 	// GoOnly includes only Go symbols that runtime will symbolize. | 
 | 	// Go+C includes C symbols that runtime will not symbolize. | 
 | 	for _, traceback := range []string{"GoOnly", "Go+C"} { | 
 | 		t.Run("traceback"+traceback, func(t *testing.T) { | 
 | 			cmd := exec.Command(testenv.GoToolPath(t), "run", prog) | 
 | 			if traceback != "GoOnly" { | 
 | 				cmd.Env = append(os.Environ(), "SETCGOTRACEBACK=1") | 
 | 			} | 
 | 			cmd.Stderr = new(bytes.Buffer) | 
 |  | 
 | 			out, err := cmd.Output() | 
 | 			if err != nil { | 
 | 				t.Fatalf("failed to run the test program %q: %v\n%v", prog, err, cmd.Stderr) | 
 | 			} | 
 |  | 
 | 			prof, err := profile.Parse(bytes.NewReader(out)) | 
 | 			if err != nil { | 
 | 				t.Fatalf("failed to parse the generated profile data: %v", err) | 
 | 			} | 
 | 			t.Logf("Profile: %s", prof) | 
 |  | 
 | 			hit := make(map[*profile.Mapping]bool) | 
 | 			miss := make(map[*profile.Mapping]bool) | 
 | 			for _, loc := range prof.Location { | 
 | 				if symbolized(loc) { | 
 | 					hit[loc.Mapping] = true | 
 | 				} else { | 
 | 					miss[loc.Mapping] = true | 
 | 				} | 
 | 			} | 
 | 			if len(miss) == 0 { | 
 | 				t.Log("no location with missing symbol info was sampled") | 
 | 			} | 
 |  | 
 | 			for _, m := range prof.Mapping { | 
 | 				if miss[m] && m.HasFunctions { | 
 | 					t.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m) | 
 | 					continue | 
 | 				} | 
 | 				if !miss[m] && hit[m] && !m.HasFunctions { | 
 | 					t.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m) | 
 | 					continue | 
 | 				} | 
 | 			} | 
 |  | 
 | 			if traceback == "Go+C" { | 
 | 				// The test code was arranged to have PCs from C and | 
 | 				// they are not symbolized. | 
 | 				// Check no Location containing those unsymbolized PCs contains multiple lines. | 
 | 				for i, loc := range prof.Location { | 
 | 					if !symbolized(loc) && len(loc.Line) > 1 { | 
 | 						t.Errorf("Location[%d] contains unsymbolized PCs and multiple lines: %v", i, loc) | 
 | 					} | 
 | 				} | 
 | 			} | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func symbolized(loc *profile.Location) bool { | 
 | 	if len(loc.Line) == 0 { | 
 | 		return false | 
 | 	} | 
 | 	l := loc.Line[0] | 
 | 	f := l.Function | 
 | 	if l.Line == 0 || f == nil || f.Name == "" || f.Filename == "" { | 
 | 		return false | 
 | 	} | 
 | 	return true | 
 | } | 
 |  | 
 | // TestFakeMapping tests if at least one mapping exists | 
 | // (including a fake mapping), and their HasFunctions bits | 
 | // are set correctly. | 
 | func TestFakeMapping(t *testing.T) { | 
 | 	var buf bytes.Buffer | 
 | 	if err := Lookup("heap").WriteTo(&buf, 0); err != nil { | 
 | 		t.Fatalf("failed to write heap profile: %v", err) | 
 | 	} | 
 | 	prof, err := profile.Parse(&buf) | 
 | 	if err != nil { | 
 | 		t.Fatalf("failed to parse the generated profile data: %v", err) | 
 | 	} | 
 | 	t.Logf("Profile: %s", prof) | 
 | 	if len(prof.Mapping) == 0 { | 
 | 		t.Fatal("want profile with at least one mapping entry, got 0 mapping") | 
 | 	} | 
 |  | 
 | 	hit := make(map[*profile.Mapping]bool) | 
 | 	miss := make(map[*profile.Mapping]bool) | 
 | 	for _, loc := range prof.Location { | 
 | 		if symbolized(loc) { | 
 | 			hit[loc.Mapping] = true | 
 | 		} else { | 
 | 			miss[loc.Mapping] = true | 
 | 		} | 
 | 	} | 
 | 	for _, m := range prof.Mapping { | 
 | 		if miss[m] && m.HasFunctions { | 
 | 			t.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m) | 
 | 			continue | 
 | 		} | 
 | 		if !miss[m] && hit[m] && !m.HasFunctions { | 
 | 			t.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m) | 
 | 			continue | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | // Make sure the profiler can handle an empty stack trace. | 
 | // See issue 37967. | 
 | func TestEmptyStack(t *testing.T) { | 
 | 	b := []uint64{ | 
 | 		3, 0, 500, // hz = 500 | 
 | 		3, 0, 10, // 10 samples with an empty stack trace | 
 | 	} | 
 | 	_, err := translateCPUProfile(b) | 
 | 	if err != nil { | 
 | 		t.Fatalf("translating profile: %v", err) | 
 | 	} | 
 | } |