You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

759 lines
20 KiB

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