1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package driver
16
17 import (
18 "crypto/ecdsa"
19 "crypto/elliptic"
20 "crypto/rand"
21 "crypto/tls"
22 "crypto/x509"
23 "encoding/pem"
24 "fmt"
25 "math/big"
26 "net"
27 "net/http"
28 "os"
29 "path/filepath"
30 "reflect"
31 "regexp"
32 "runtime"
33 "strings"
34 "testing"
35 "time"
36
37 "github.com/google/pprof/internal/binutils"
38 "github.com/google/pprof/internal/plugin"
39 "github.com/google/pprof/internal/proftest"
40 "github.com/google/pprof/internal/symbolizer"
41 "github.com/google/pprof/internal/transport"
42 "github.com/google/pprof/profile"
43 )
44
45 func TestSymbolizationPath(t *testing.T) {
46 if runtime.GOOS == "windows" {
47 t.Skip("test assumes Unix paths")
48 }
49
50
51 saveHome := os.Getenv(homeEnv())
52 savePath := os.Getenv("PPROF_BINARY_PATH")
53
54 tempdir, err := os.MkdirTemp("", "home")
55 if err != nil {
56 t.Fatal("creating temp dir: ", err)
57 }
58 defer os.RemoveAll(tempdir)
59 os.MkdirAll(filepath.Join(tempdir, "pprof", "binaries", "abcde10001"), 0700)
60 os.Create(filepath.Join(tempdir, "pprof", "binaries", "abcde10001", "binary"))
61
62 os.MkdirAll(filepath.Join(tempdir, "pprof", "binaries", "fg"), 0700)
63 os.Create(filepath.Join(tempdir, "pprof", "binaries", "fg", "hij10001.debug"))
64
65 obj := testObj{tempdir}
66 os.Setenv(homeEnv(), tempdir)
67 for _, tc := range []struct {
68 env, file, buildID, want string
69 msgCount int
70 }{
71 {"", "/usr/bin/binary", "", "/usr/bin/binary", 0},
72 {"", "/usr/bin/binary", "fedcb10000", "/usr/bin/binary", 0},
73 {"/usr", "/bin/binary", "", "/usr/bin/binary", 0},
74 {"", "/prod/path/binary", "abcde10001", filepath.Join(tempdir, "pprof/binaries/abcde10001/binary"), 0},
75 {"/alternate/architecture", "/usr/bin/binary", "", "/alternate/architecture/binary", 0},
76 {"/alternate/architecture", "/usr/bin/binary", "abcde10001", "/alternate/architecture/binary", 0},
77 {"", "", "fghij10001", filepath.Join(tempdir, "pprof/binaries/fg/hij10001.debug"), 0},
78 {"/nowhere:/alternate/architecture", "/usr/bin/binary", "fedcb10000", "/usr/bin/binary", 1},
79 {"/nowhere:/alternate/architecture", "/usr/bin/binary", "abcde10002", "/usr/bin/binary", 1},
80 } {
81 os.Setenv("PPROF_BINARY_PATH", tc.env)
82 p := &profile.Profile{
83 Mapping: []*profile.Mapping{
84 {
85 File: tc.file,
86 BuildID: tc.buildID,
87 },
88 },
89 }
90 s := &source{}
91 locateBinaries(p, s, obj, &proftest.TestUI{T: t, Ignore: tc.msgCount})
92 if file := p.Mapping[0].File; file != tc.want {
93 t.Errorf("%s:%s:%s, want %s, got %s", tc.env, tc.file, tc.buildID, tc.want, file)
94 }
95 }
96 os.Setenv(homeEnv(), saveHome)
97 os.Setenv("PPROF_BINARY_PATH", savePath)
98 }
99
100 func TestCollectMappingSources(t *testing.T) {
101 const startAddress uint64 = 0x40000
102 const url = "http://example.com"
103 for _, tc := range []struct {
104 file, buildID string
105 want plugin.MappingSources
106 }{
107 {"/usr/bin/binary", "buildId", mappingSources("buildId", url, startAddress)},
108 {"/usr/bin/binary", "", mappingSources("/usr/bin/binary", url, startAddress)},
109 {"", "", mappingSources(url, url, startAddress)},
110 } {
111 p := &profile.Profile{
112 Mapping: []*profile.Mapping{
113 {
114 File: tc.file,
115 BuildID: tc.buildID,
116 Start: startAddress,
117 },
118 },
119 }
120 got := collectMappingSources(p, url)
121 if !reflect.DeepEqual(got, tc.want) {
122 t.Errorf("%s:%s, want %v, got %v", tc.file, tc.buildID, tc.want, got)
123 }
124 }
125 }
126
127 func TestUnsourceMappings(t *testing.T) {
128 for _, tc := range []struct {
129 os, file, buildID, want string
130 }{
131 {"any", "/usr/bin/binary", "buildId", "/usr/bin/binary"},
132 {"any", "http://example.com", "", ""},
133 {"windows", `C:\example.exe`, "", `C:\example.exe`},
134 {"windows", `c:/example.exe`, "", `c:/example.exe`},
135 } {
136 t.Run(tc.file+"-"+tc.os, func(t *testing.T) {
137 if tc.os != "any" && tc.os != runtime.GOOS {
138 t.Skipf("%s only test", tc.os)
139 }
140
141 p := &profile.Profile{
142 Mapping: []*profile.Mapping{
143 {
144 File: tc.file,
145 BuildID: tc.buildID,
146 },
147 },
148 }
149 unsourceMappings(p)
150 if got := p.Mapping[0].File; got != tc.want {
151 t.Errorf("%s:%s, want %s, got %s", tc.file, tc.buildID, tc.want, got)
152 }
153 })
154 }
155 }
156
157 type testObj struct {
158 home string
159 }
160
161 func (o testObj) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {
162 switch file {
163 case "/alternate/architecture/binary":
164 return testFile{file, "abcde10001"}, nil
165 case "/usr/bin/binary":
166 return testFile{file, "fedcb10000"}, nil
167 case filepath.Join(o.home, "pprof/binaries/abcde10001/binary"):
168 return testFile{file, "abcde10001"}, nil
169 case filepath.Join(o.home, "pprof/binaries/fg/hij10001.debug"):
170 return testFile{file, "fghij10001"}, nil
171 }
172 return nil, fmt.Errorf("not found: %s", file)
173 }
174 func (testObj) Demangler(_ string) func(names []string) (map[string]string, error) {
175 return func(names []string) (map[string]string, error) { return nil, nil }
176 }
177 func (testObj) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
178 return nil, nil
179 }
180
181 type testFile struct{ name, buildID string }
182
183 func (f testFile) Name() string { return f.name }
184 func (testFile) ObjAddr(addr uint64) (uint64, error) { return addr, nil }
185 func (f testFile) BuildID() string { return f.buildID }
186 func (testFile) SourceLine(addr uint64) ([]plugin.Frame, error) { return nil, nil }
187 func (testFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { return nil, nil }
188 func (testFile) Close() error { return nil }
189
190 func TestFetch(t *testing.T) {
191 const path = "testdata/"
192 type testcase struct {
193 source, execName string
194 }
195 ts := []testcase{
196 {path + "go.crc32.cpu", ""},
197 {path + "go.nomappings.crash", "/bin/gotest.exe"},
198 {"http://localhost/profile?file=cppbench.cpu", ""},
199 }
200
201
202
203 if runtime.GOOS != "windows" {
204 src := filepath.Join(path, "go.crc32.cpu")
205 dst := filepath.Join(t.TempDir(), "go.crc32.cpu_2023-11-11_01:02:03")
206 data, err := os.ReadFile(src)
207 if err != nil {
208 t.Fatalf("read src file %s failed: %#v", src, err)
209 }
210 err = os.WriteFile(dst, data, 0644)
211 if err != nil {
212 t.Fatalf("create dst file %s failed: %#v", dst, err)
213 }
214 ts = append(ts, testcase{dst, ""})
215 }
216 for _, tc := range ts {
217 p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t}, &httpTransport{})
218 if err != nil {
219 t.Fatalf("%s: %s", tc.source, err)
220 }
221 if len(p.Sample) == 0 {
222 t.Errorf("%s: want non-zero samples", tc.source)
223 }
224 if e := tc.execName; e != "" {
225 switch {
226 case len(p.Mapping) == 0 || p.Mapping[0] == nil:
227 t.Errorf("%s: want mapping[0].execName == %s, got no mappings", tc.source, e)
228 case p.Mapping[0].File != e:
229 t.Errorf("%s: want mapping[0].execName == %s, got %s", tc.source, e, p.Mapping[0].File)
230 }
231 }
232 }
233 }
234
235 func TestFetchWithBase(t *testing.T) {
236 baseConfig := currentConfig()
237 defer setCurrentConfig(baseConfig)
238
239 type WantSample struct {
240 values []int64
241 labels map[string][]string
242 }
243
244 const path = "testdata/"
245 type testcase struct {
246 desc string
247 sources []string
248 bases []string
249 diffBases []string
250 normalize bool
251 wantSamples []WantSample
252 wantParseErrorMsg string
253 wantFetchErrorMsg string
254 }
255
256 testcases := []testcase{
257 {
258 "not normalized base is same as source",
259 []string{path + "cppbench.contention"},
260 []string{path + "cppbench.contention"},
261 nil,
262 false,
263 nil,
264 "",
265 "",
266 },
267 {
268 "not normalized base is same as source",
269 []string{path + "cppbench.contention"},
270 []string{path + "cppbench.contention"},
271 nil,
272 false,
273 nil,
274 "",
275 "",
276 },
277 {
278 "not normalized single source, multiple base (all profiles same)",
279 []string{path + "cppbench.contention"},
280 []string{path + "cppbench.contention", path + "cppbench.contention"},
281 nil,
282 false,
283 []WantSample{
284 {
285 values: []int64{-2700, -608881724},
286 labels: map[string][]string{},
287 },
288 {
289 values: []int64{-100, -23992},
290 labels: map[string][]string{},
291 },
292 {
293 values: []int64{-200, -179943},
294 labels: map[string][]string{},
295 },
296 {
297 values: []int64{-100, -17778444},
298 labels: map[string][]string{},
299 },
300 {
301 values: []int64{-100, -75976},
302 labels: map[string][]string{},
303 },
304 {
305 values: []int64{-300, -63568134},
306 labels: map[string][]string{},
307 },
308 },
309 "",
310 "",
311 },
312 {
313 "not normalized, different base and source",
314 []string{path + "cppbench.contention"},
315 []string{path + "cppbench.small.contention"},
316 nil,
317 false,
318 []WantSample{
319 {
320 values: []int64{1700, 608878600},
321 labels: map[string][]string{},
322 },
323 {
324 values: []int64{100, 23992},
325 labels: map[string][]string{},
326 },
327 {
328 values: []int64{200, 179943},
329 labels: map[string][]string{},
330 },
331 {
332 values: []int64{100, 17778444},
333 labels: map[string][]string{},
334 },
335 {
336 values: []int64{100, 75976},
337 labels: map[string][]string{},
338 },
339 {
340 values: []int64{300, 63568134},
341 labels: map[string][]string{},
342 },
343 },
344 "",
345 "",
346 },
347 {
348 "normalized base is same as source",
349 []string{path + "cppbench.contention"},
350 []string{path + "cppbench.contention"},
351 nil,
352 true,
353 nil,
354 "",
355 "",
356 },
357 {
358 "normalized single source, multiple base (all profiles same)",
359 []string{path + "cppbench.contention"},
360 []string{path + "cppbench.contention", path + "cppbench.contention"},
361 nil,
362 true,
363 nil,
364 "",
365 "",
366 },
367 {
368 "normalized different base and source",
369 []string{path + "cppbench.contention"},
370 []string{path + "cppbench.small.contention"},
371 nil,
372 true,
373 []WantSample{
374 {
375 values: []int64{-229, -369},
376 labels: map[string][]string{},
377 },
378 {
379 values: []int64{29, 0},
380 labels: map[string][]string{},
381 },
382 {
383 values: []int64{57, 1},
384 labels: map[string][]string{},
385 },
386 {
387 values: []int64{29, 80},
388 labels: map[string][]string{},
389 },
390 {
391 values: []int64{29, 0},
392 labels: map[string][]string{},
393 },
394 {
395 values: []int64{86, 288},
396 labels: map[string][]string{},
397 },
398 },
399 "",
400 "",
401 },
402 {
403 "not normalized diff base is same as source",
404 []string{path + "cppbench.contention"},
405 nil,
406 []string{path + "cppbench.contention"},
407 false,
408 []WantSample{
409 {
410 values: []int64{2700, 608881724},
411 labels: map[string][]string{},
412 },
413 {
414 values: []int64{100, 23992},
415 labels: map[string][]string{},
416 },
417 {
418 values: []int64{200, 179943},
419 labels: map[string][]string{},
420 },
421 {
422 values: []int64{100, 17778444},
423 labels: map[string][]string{},
424 },
425 {
426 values: []int64{100, 75976},
427 labels: map[string][]string{},
428 },
429 {
430 values: []int64{300, 63568134},
431 labels: map[string][]string{},
432 },
433 {
434 values: []int64{-2700, -608881724},
435 labels: map[string][]string{"pprof::base": {"true"}},
436 },
437 {
438 values: []int64{-100, -23992},
439 labels: map[string][]string{"pprof::base": {"true"}},
440 },
441 {
442 values: []int64{-200, -179943},
443 labels: map[string][]string{"pprof::base": {"true"}},
444 },
445 {
446 values: []int64{-100, -17778444},
447 labels: map[string][]string{"pprof::base": {"true"}},
448 },
449 {
450 values: []int64{-100, -75976},
451 labels: map[string][]string{"pprof::base": {"true"}},
452 },
453 {
454 values: []int64{-300, -63568134},
455 labels: map[string][]string{"pprof::base": {"true"}},
456 },
457 },
458 "",
459 "",
460 },
461 {
462 "diff_base and base both specified",
463 []string{path + "cppbench.contention"},
464 []string{path + "cppbench.contention"},
465 []string{path + "cppbench.contention"},
466 false,
467 nil,
468 "-base and -diff_base flags cannot both be specified",
469 "",
470 },
471 {
472 "input profiles with different sample types (non empty intersection)",
473 []string{path + "cppbench.cpu", path + "cppbench.cpu_no_samples_type"},
474 []string{path + "cppbench.cpu", path + "cppbench.cpu_no_samples_type"},
475 nil,
476 false,
477 nil,
478 "",
479 "",
480 },
481 {
482 "input profiles with different sample types (empty intersection)",
483 []string{path + "cppbench.cpu", path + "cppbench.contention"},
484 []string{path + "cppbench.cpu", path + "cppbench.contention"},
485 nil,
486 false,
487 nil,
488 "",
489 "problem fetching source profiles: profiles have empty common sample type list",
490 },
491 }
492
493 for _, tc := range testcases {
494 t.Run(tc.desc, func(t *testing.T) {
495 setCurrentConfig(baseConfig)
496 f := testFlags{
497 stringLists: map[string][]string{
498 "base": tc.bases,
499 "diff_base": tc.diffBases,
500 },
501 bools: map[string]bool{
502 "normalize": tc.normalize,
503 },
504 }
505 f.args = tc.sources
506
507 o := setDefaults(&plugin.Options{
508 UI: &proftest.TestUI{T: t, AllowRx: "Local symbolization failed|Some binary filenames not available"},
509 Flagset: f,
510 HTTPTransport: transport.New(nil),
511 })
512 src, _, err := parseFlags(o)
513
514 if tc.wantParseErrorMsg != "" {
515 if err == nil {
516 t.Fatalf("got nil, want error %q", tc.wantParseErrorMsg)
517 }
518
519 if gotErrMsg := err.Error(); gotErrMsg != tc.wantParseErrorMsg {
520 t.Fatalf("got error %q, want error %q", gotErrMsg, tc.wantParseErrorMsg)
521 }
522 return
523 }
524
525 if err != nil {
526 t.Fatalf("got error %q, want no error", err)
527 }
528
529 p, err := fetchProfiles(src, o)
530
531 if tc.wantFetchErrorMsg != "" {
532 if err == nil {
533 t.Fatalf("got nil, want error %q", tc.wantFetchErrorMsg)
534 }
535
536 if gotErrMsg := err.Error(); gotErrMsg != tc.wantFetchErrorMsg {
537 t.Fatalf("got error %q, want error %q", gotErrMsg, tc.wantFetchErrorMsg)
538 }
539 return
540 }
541
542 if err != nil {
543 t.Fatalf("got error %q, want no error", err)
544 }
545
546 if got, want := len(p.Sample), len(tc.wantSamples); got != want {
547 t.Fatalf("got %d samples want %d", got, want)
548 }
549
550 for i, sample := range p.Sample {
551 if !reflect.DeepEqual(tc.wantSamples[i].values, sample.Value) {
552 t.Errorf("for sample %d got values %v, want %v", i, sample.Value, tc.wantSamples[i])
553 }
554 if !reflect.DeepEqual(tc.wantSamples[i].labels, sample.Label) {
555 t.Errorf("for sample %d got labels %v, want %v", i, sample.Label, tc.wantSamples[i].labels)
556 }
557 }
558 })
559 }
560 }
561
562
563 func mappingSources(key, source string, start uint64) plugin.MappingSources {
564 return plugin.MappingSources{
565 key: []struct {
566 Source string
567 Start uint64
568 }{
569 {Source: source, Start: start},
570 },
571 }
572 }
573
574 type httpTransport struct{}
575
576 func (tr *httpTransport) RoundTrip(req *http.Request) (*http.Response, error) {
577 values := req.URL.Query()
578 file := values.Get("file")
579
580 if file == "" {
581 return nil, fmt.Errorf("want .../file?profile, got %s", req.URL.String())
582 }
583
584 t := &http.Transport{}
585 t.RegisterProtocol("file", http.NewFileTransport(http.Dir("testdata/")))
586
587 c := &http.Client{Transport: t}
588 return c.Get("file:///" + file)
589 }
590
591 func closedError() string {
592 if runtime.GOOS == "plan9" {
593 return "listen hungup"
594 }
595 return "use of closed"
596 }
597
598 func TestHTTPSInsecure(t *testing.T) {
599 if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
600 t.Skip("test assumes tcp available")
601 }
602 saveHome := os.Getenv(homeEnv())
603 tempdir, err := os.MkdirTemp("", "home")
604 if err != nil {
605 t.Fatal("creating temp dir: ", err)
606 }
607 defer os.RemoveAll(tempdir)
608
609
610
611
612 os.Setenv(homeEnv(), tempdir)
613 defer os.Setenv(homeEnv(), saveHome)
614
615 baseConfig := currentConfig()
616 defer setCurrentConfig(baseConfig)
617
618 tlsCert, _, _ := selfSignedCert(t, "")
619 tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}
620
621 l, err := tls.Listen("tcp", "localhost:0", tlsConfig)
622 if err != nil {
623 t.Fatalf("net.Listen: got error %v, want no error", err)
624 }
625
626 donec := make(chan error, 1)
627 go func(donec chan<- error) {
628 donec <- http.Serve(l, nil)
629 }(donec)
630 defer func() {
631 if got, want := <-donec, closedError(); !strings.Contains(got.Error(), want) {
632 t.Fatalf("Serve got error %v, want %q", got, want)
633 }
634 }()
635 defer l.Close()
636
637 outputTempFile, err := os.CreateTemp("", "profile_output")
638 if err != nil {
639 t.Fatalf("Failed to create tempfile: %v", err)
640 }
641 defer os.Remove(outputTempFile.Name())
642 defer outputTempFile.Close()
643
644 address := "https+insecure://" + l.Addr().String() + "/debug/pprof/goroutine"
645 s := &source{
646 Sources: []string{address},
647 Timeout: 10,
648 Symbolize: "remote",
649 }
650 o := &plugin.Options{
651 Obj: &binutils.Binutils{},
652 UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"},
653 HTTPTransport: transport.New(nil),
654 }
655 o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI}
656 p, err := fetchProfiles(s, o)
657 if err != nil {
658 t.Fatal(err)
659 }
660 if len(p.SampleType) == 0 {
661 t.Fatalf("fetchProfiles(%s) got empty profile: len(p.SampleType)==0", address)
662 }
663 if len(p.Function) == 0 {
664 t.Fatalf("fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0", address)
665 }
666 if err := checkProfileHasFunction(p, "TestHTTPSInsecure"); err != nil {
667 t.Fatalf("fetchProfiles(%s) %v", address, err)
668 }
669 }
670
671 func TestHTTPSWithServerCertFetch(t *testing.T) {
672 if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
673 t.Skip("test assumes tcp available")
674 }
675 saveHome := os.Getenv(homeEnv())
676 tempdir, err := os.MkdirTemp("", "home")
677 if err != nil {
678 t.Fatal("creating temp dir: ", err)
679 }
680 defer os.RemoveAll(tempdir)
681
682
683
684
685 os.Setenv(homeEnv(), tempdir)
686 defer os.Setenv(homeEnv(), saveHome)
687
688 baseConfig := currentConfig()
689 defer setCurrentConfig(baseConfig)
690
691 cert, certBytes, keyBytes := selfSignedCert(t, "localhost")
692 cas := x509.NewCertPool()
693 cas.AppendCertsFromPEM(certBytes)
694
695 tlsConfig := &tls.Config{
696 RootCAs: cas,
697 Certificates: []tls.Certificate{cert},
698 ClientAuth: tls.RequireAndVerifyClientCert,
699 ClientCAs: cas,
700 }
701
702 l, err := tls.Listen("tcp", "localhost:0", tlsConfig)
703 if err != nil {
704 t.Fatalf("net.Listen: got error %v, want no error", err)
705 }
706
707 donec := make(chan error, 1)
708 go func(donec chan<- error) {
709 donec <- http.Serve(l, nil)
710 }(donec)
711 defer func() {
712 if got, want := <-donec, closedError(); !strings.Contains(got.Error(), want) {
713 t.Fatalf("Serve got error %v, want %q", got, want)
714 }
715 }()
716 defer l.Close()
717
718 outputTempFile, err := os.CreateTemp("", "profile_output")
719 if err != nil {
720 t.Fatalf("Failed to create tempfile: %v", err)
721 }
722 defer os.Remove(outputTempFile.Name())
723 defer outputTempFile.Close()
724
725
726
727 _, portStr, err := net.SplitHostPort(l.Addr().String())
728 if err != nil {
729 t.Fatalf("cannot get port from URL: %v", err)
730 }
731 address := "https://" + "localhost:" + portStr + "/debug/pprof/goroutine"
732 s := &source{
733 Sources: []string{address},
734 Timeout: 10,
735 Symbolize: "remote",
736 }
737
738 certTempFile, err := os.CreateTemp("", "cert_output")
739 if err != nil {
740 t.Errorf("cannot create cert tempfile: %v", err)
741 }
742 defer os.Remove(certTempFile.Name())
743 defer certTempFile.Close()
744 certTempFile.Write(certBytes)
745
746 keyTempFile, err := os.CreateTemp("", "key_output")
747 if err != nil {
748 t.Errorf("cannot create key tempfile: %v", err)
749 }
750 defer os.Remove(keyTempFile.Name())
751 defer keyTempFile.Close()
752 keyTempFile.Write(keyBytes)
753
754 f := &testFlags{
755 strings: map[string]string{
756 "tls_cert": certTempFile.Name(),
757 "tls_key": keyTempFile.Name(),
758 "tls_ca": certTempFile.Name(),
759 },
760 }
761 o := &plugin.Options{
762 Obj: &binutils.Binutils{},
763 UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"},
764 Flagset: f,
765 HTTPTransport: transport.New(f),
766 }
767
768 o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI, Transport: o.HTTPTransport}
769 p, err := fetchProfiles(s, o)
770 if err != nil {
771 t.Fatal(err)
772 }
773 if len(p.SampleType) == 0 {
774 t.Fatalf("fetchProfiles(%s) got empty profile: len(p.SampleType)==0", address)
775 }
776 if len(p.Function) == 0 {
777 t.Fatalf("fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0", address)
778 }
779 if err := checkProfileHasFunction(p, "TestHTTPSWithServerCertFetch"); err != nil {
780 t.Fatalf("fetchProfiles(%s) %v", address, err)
781 }
782 }
783
784 func checkProfileHasFunction(p *profile.Profile, fname string) error {
785 for _, f := range p.Function {
786 if strings.Contains(f.Name, fname) {
787 return nil
788 }
789 }
790 return fmt.Errorf("got %s, want function %q", p.String(), fname)
791 }
792
793
794
795
796 func selfSignedCert(t *testing.T, host string) (tls.Certificate, []byte, []byte) {
797 privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
798 if err != nil {
799 t.Fatalf("failed to generate private key: %v", err)
800 }
801 b, err := x509.MarshalECPrivateKey(privKey)
802 if err != nil {
803 t.Fatalf("failed to marshal private key: %v", err)
804 }
805 bk := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
806
807 tmpl := x509.Certificate{
808 SerialNumber: big.NewInt(1),
809 NotBefore: time.Now(),
810 NotAfter: time.Now().Add(10 * time.Minute),
811 IsCA: true,
812 DNSNames: []string{host},
813 }
814
815 b, err = x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privKey.Public(), privKey)
816 if err != nil {
817 t.Fatalf("failed to create cert: %v", err)
818 }
819 bc := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: b})
820
821 cert, err := tls.X509KeyPair(bc, bk)
822 if err != nil {
823 t.Fatalf("failed to create TLS key pair: %v", err)
824 }
825 return cert, bc, bk
826 }
827
View as plain text