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