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