github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/net/http/fs_test.go (about) 1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package http_test 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "mime" 14 "mime/multipart" 15 "net" 16 . "net/http" 17 "net/http/httptest" 18 "net/url" 19 "os" 20 "os/exec" 21 "path" 22 "path/filepath" 23 "regexp" 24 "runtime" 25 "strings" 26 "testing" 27 "time" 28 ) 29 30 const ( 31 testFile = "testdata/file" 32 testFileLen = 11 33 ) 34 35 type wantRange struct { 36 start, end int64 // range [start,end) 37 } 38 39 var ServeFileRangeTests = []struct { 40 r string 41 code int 42 ranges []wantRange 43 }{ 44 {r: "", code: StatusOK}, 45 {r: "bytes=0-4", code: StatusPartialContent, ranges: []wantRange{{0, 5}}}, 46 {r: "bytes=2-", code: StatusPartialContent, ranges: []wantRange{{2, testFileLen}}}, 47 {r: "bytes=-5", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 5, testFileLen}}}, 48 {r: "bytes=3-7", code: StatusPartialContent, ranges: []wantRange{{3, 8}}}, 49 {r: "bytes=20-", code: StatusRequestedRangeNotSatisfiable}, 50 {r: "bytes=0-0,-2", code: StatusPartialContent, ranges: []wantRange{{0, 1}, {testFileLen - 2, testFileLen}}}, 51 {r: "bytes=0-1,5-8", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, 9}}}, 52 {r: "bytes=0-1,5-", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, testFileLen}}}, 53 {r: "bytes=0-,1-,2-,3-,4-", code: StatusOK}, // ignore wasteful range request 54 } 55 56 func TestServeFile(t *testing.T) { 57 defer afterTest(t) 58 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 59 ServeFile(w, r, "testdata/file") 60 })) 61 defer ts.Close() 62 63 var err error 64 65 file, err := ioutil.ReadFile(testFile) 66 if err != nil { 67 t.Fatal("reading file:", err) 68 } 69 70 // set up the Request (re-used for all tests) 71 var req Request 72 req.Header = make(Header) 73 if req.URL, err = url.Parse(ts.URL); err != nil { 74 t.Fatal("ParseURL:", err) 75 } 76 req.Method = "GET" 77 78 // straight GET 79 _, body := getBody(t, "straight get", req) 80 if !bytes.Equal(body, file) { 81 t.Fatalf("body mismatch: got %q, want %q", body, file) 82 } 83 84 // Range tests 85 Cases: 86 for _, rt := range ServeFileRangeTests { 87 if rt.r != "" { 88 req.Header.Set("Range", rt.r) 89 } 90 resp, body := getBody(t, fmt.Sprintf("range test %q", rt.r), req) 91 if resp.StatusCode != rt.code { 92 t.Errorf("range=%q: StatusCode=%d, want %d", rt.r, resp.StatusCode, rt.code) 93 } 94 if rt.code == StatusRequestedRangeNotSatisfiable { 95 continue 96 } 97 wantContentRange := "" 98 if len(rt.ranges) == 1 { 99 rng := rt.ranges[0] 100 wantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen) 101 } 102 cr := resp.Header.Get("Content-Range") 103 if cr != wantContentRange { 104 t.Errorf("range=%q: Content-Range = %q, want %q", rt.r, cr, wantContentRange) 105 } 106 ct := resp.Header.Get("Content-Type") 107 if len(rt.ranges) == 1 { 108 rng := rt.ranges[0] 109 wantBody := file[rng.start:rng.end] 110 if !bytes.Equal(body, wantBody) { 111 t.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody) 112 } 113 if strings.HasPrefix(ct, "multipart/byteranges") { 114 t.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r, ct) 115 } 116 } 117 if len(rt.ranges) > 1 { 118 typ, params, err := mime.ParseMediaType(ct) 119 if err != nil { 120 t.Errorf("range=%q content-type = %q; %v", rt.r, ct, err) 121 continue 122 } 123 if typ != "multipart/byteranges" { 124 t.Errorf("range=%q content-type = %q; want multipart/byteranges", rt.r, typ) 125 continue 126 } 127 if params["boundary"] == "" { 128 t.Errorf("range=%q content-type = %q; lacks boundary", rt.r, ct) 129 continue 130 } 131 if g, w := resp.ContentLength, int64(len(body)); g != w { 132 t.Errorf("range=%q Content-Length = %d; want %d", rt.r, g, w) 133 continue 134 } 135 mr := multipart.NewReader(bytes.NewReader(body), params["boundary"]) 136 for ri, rng := range rt.ranges { 137 part, err := mr.NextPart() 138 if err != nil { 139 t.Errorf("range=%q, reading part index %d: %v", rt.r, ri, err) 140 continue Cases 141 } 142 wantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen) 143 if g, w := part.Header.Get("Content-Range"), wantContentRange; g != w { 144 t.Errorf("range=%q: part Content-Range = %q; want %q", rt.r, g, w) 145 } 146 body, err := ioutil.ReadAll(part) 147 if err != nil { 148 t.Errorf("range=%q, reading part index %d body: %v", rt.r, ri, err) 149 continue Cases 150 } 151 wantBody := file[rng.start:rng.end] 152 if !bytes.Equal(body, wantBody) { 153 t.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody) 154 } 155 } 156 _, err = mr.NextPart() 157 if err != io.EOF { 158 t.Errorf("range=%q; expected final error io.EOF; got %v", rt.r, err) 159 } 160 } 161 } 162 } 163 164 var fsRedirectTestData = []struct { 165 original, redirect string 166 }{ 167 {"/test/index.html", "/test/"}, 168 {"/test/testdata", "/test/testdata/"}, 169 {"/test/testdata/file/", "/test/testdata/file"}, 170 } 171 172 func TestFSRedirect(t *testing.T) { 173 defer afterTest(t) 174 ts := httptest.NewServer(StripPrefix("/test", FileServer(Dir(".")))) 175 defer ts.Close() 176 177 for _, data := range fsRedirectTestData { 178 res, err := Get(ts.URL + data.original) 179 if err != nil { 180 t.Fatal(err) 181 } 182 res.Body.Close() 183 if g, e := res.Request.URL.Path, data.redirect; g != e { 184 t.Errorf("redirect from %s: got %s, want %s", data.original, g, e) 185 } 186 } 187 } 188 189 type testFileSystem struct { 190 open func(name string) (File, error) 191 } 192 193 func (fs *testFileSystem) Open(name string) (File, error) { 194 return fs.open(name) 195 } 196 197 func TestFileServerCleans(t *testing.T) { 198 defer afterTest(t) 199 ch := make(chan string, 1) 200 fs := FileServer(&testFileSystem{func(name string) (File, error) { 201 ch <- name 202 return nil, errors.New("file does not exist") 203 }}) 204 tests := []struct { 205 reqPath, openArg string 206 }{ 207 {"/foo.txt", "/foo.txt"}, 208 {"//foo.txt", "/foo.txt"}, 209 {"/../foo.txt", "/foo.txt"}, 210 } 211 req, _ := NewRequest("GET", "http://example.com", nil) 212 for n, test := range tests { 213 rec := httptest.NewRecorder() 214 req.URL.Path = test.reqPath 215 fs.ServeHTTP(rec, req) 216 if got := <-ch; got != test.openArg { 217 t.Errorf("test %d: got %q, want %q", n, got, test.openArg) 218 } 219 } 220 } 221 222 func mustRemoveAll(dir string) { 223 err := os.RemoveAll(dir) 224 if err != nil { 225 panic(err) 226 } 227 } 228 229 func TestFileServerImplicitLeadingSlash(t *testing.T) { 230 defer afterTest(t) 231 tempDir, err := ioutil.TempDir("", "") 232 if err != nil { 233 t.Fatalf("TempDir: %v", err) 234 } 235 defer mustRemoveAll(tempDir) 236 if err := ioutil.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("Hello world"), 0644); err != nil { 237 t.Fatalf("WriteFile: %v", err) 238 } 239 ts := httptest.NewServer(StripPrefix("/bar/", FileServer(Dir(tempDir)))) 240 defer ts.Close() 241 get := func(suffix string) string { 242 res, err := Get(ts.URL + suffix) 243 if err != nil { 244 t.Fatalf("Get %s: %v", suffix, err) 245 } 246 b, err := ioutil.ReadAll(res.Body) 247 if err != nil { 248 t.Fatalf("ReadAll %s: %v", suffix, err) 249 } 250 res.Body.Close() 251 return string(b) 252 } 253 if s := get("/bar/"); !strings.Contains(s, ">foo.txt<") { 254 t.Logf("expected a directory listing with foo.txt, got %q", s) 255 } 256 if s := get("/bar/foo.txt"); s != "Hello world" { 257 t.Logf("expected %q, got %q", "Hello world", s) 258 } 259 } 260 261 func TestDirJoin(t *testing.T) { 262 wfi, err := os.Stat("/etc/hosts") 263 if err != nil { 264 t.Skip("skipping test; no /etc/hosts file") 265 } 266 test := func(d Dir, name string) { 267 f, err := d.Open(name) 268 if err != nil { 269 t.Fatalf("open of %s: %v", name, err) 270 } 271 defer f.Close() 272 gfi, err := f.Stat() 273 if err != nil { 274 t.Fatalf("stat of %s: %v", name, err) 275 } 276 if !os.SameFile(gfi, wfi) { 277 t.Errorf("%s got different file", name) 278 } 279 } 280 test(Dir("/etc/"), "/hosts") 281 test(Dir("/etc/"), "hosts") 282 test(Dir("/etc/"), "../../../../hosts") 283 test(Dir("/etc"), "/hosts") 284 test(Dir("/etc"), "hosts") 285 test(Dir("/etc"), "../../../../hosts") 286 287 // Not really directories, but since we use this trick in 288 // ServeFile, test it: 289 test(Dir("/etc/hosts"), "") 290 test(Dir("/etc/hosts"), "/") 291 test(Dir("/etc/hosts"), "../") 292 } 293 294 func TestEmptyDirOpenCWD(t *testing.T) { 295 test := func(d Dir) { 296 name := "fs_test.go" 297 f, err := d.Open(name) 298 if err != nil { 299 t.Fatalf("open of %s: %v", name, err) 300 } 301 defer f.Close() 302 } 303 test(Dir("")) 304 test(Dir(".")) 305 test(Dir("./")) 306 } 307 308 func TestServeFileContentType(t *testing.T) { 309 defer afterTest(t) 310 const ctype = "icecream/chocolate" 311 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 312 if r.FormValue("override") == "1" { 313 w.Header().Set("Content-Type", ctype) 314 } 315 ServeFile(w, r, "testdata/file") 316 })) 317 defer ts.Close() 318 get := func(override, want string) { 319 resp, err := Get(ts.URL + "?override=" + override) 320 if err != nil { 321 t.Fatal(err) 322 } 323 if h := resp.Header.Get("Content-Type"); h != want { 324 t.Errorf("Content-Type mismatch: got %q, want %q", h, want) 325 } 326 resp.Body.Close() 327 } 328 get("0", "text/plain; charset=utf-8") 329 get("1", ctype) 330 } 331 332 func TestServeFileMimeType(t *testing.T) { 333 defer afterTest(t) 334 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 335 ServeFile(w, r, "testdata/style.css") 336 })) 337 defer ts.Close() 338 resp, err := Get(ts.URL) 339 if err != nil { 340 t.Fatal(err) 341 } 342 resp.Body.Close() 343 want := "text/css; charset=utf-8" 344 if h := resp.Header.Get("Content-Type"); h != want { 345 t.Errorf("Content-Type mismatch: got %q, want %q", h, want) 346 } 347 } 348 349 func TestServeFileFromCWD(t *testing.T) { 350 defer afterTest(t) 351 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 352 ServeFile(w, r, "fs_test.go") 353 })) 354 defer ts.Close() 355 r, err := Get(ts.URL) 356 if err != nil { 357 t.Fatal(err) 358 } 359 r.Body.Close() 360 if r.StatusCode != 200 { 361 t.Fatalf("expected 200 OK, got %s", r.Status) 362 } 363 } 364 365 func TestServeFileWithContentEncoding(t *testing.T) { 366 defer afterTest(t) 367 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 368 w.Header().Set("Content-Encoding", "foo") 369 ServeFile(w, r, "testdata/file") 370 })) 371 defer ts.Close() 372 resp, err := Get(ts.URL) 373 if err != nil { 374 t.Fatal(err) 375 } 376 resp.Body.Close() 377 if g, e := resp.ContentLength, int64(-1); g != e { 378 t.Errorf("Content-Length mismatch: got %d, want %d", g, e) 379 } 380 } 381 382 func TestServeIndexHtml(t *testing.T) { 383 defer afterTest(t) 384 const want = "index.html says hello\n" 385 ts := httptest.NewServer(FileServer(Dir("."))) 386 defer ts.Close() 387 388 for _, path := range []string{"/testdata/", "/testdata/index.html"} { 389 res, err := Get(ts.URL + path) 390 if err != nil { 391 t.Fatal(err) 392 } 393 b, err := ioutil.ReadAll(res.Body) 394 if err != nil { 395 t.Fatal("reading Body:", err) 396 } 397 if s := string(b); s != want { 398 t.Errorf("for path %q got %q, want %q", path, s, want) 399 } 400 res.Body.Close() 401 } 402 } 403 404 func TestFileServerZeroByte(t *testing.T) { 405 defer afterTest(t) 406 ts := httptest.NewServer(FileServer(Dir("."))) 407 defer ts.Close() 408 409 res, err := Get(ts.URL + "/..\x00") 410 if err != nil { 411 t.Fatal(err) 412 } 413 b, err := ioutil.ReadAll(res.Body) 414 if err != nil { 415 t.Fatal("reading Body:", err) 416 } 417 if res.StatusCode == 200 { 418 t.Errorf("got status 200; want an error. Body is:\n%s", string(b)) 419 } 420 } 421 422 type fakeFileInfo struct { 423 dir bool 424 basename string 425 modtime time.Time 426 ents []*fakeFileInfo 427 contents string 428 } 429 430 func (f *fakeFileInfo) Name() string { return f.basename } 431 func (f *fakeFileInfo) Sys() interface{} { return nil } 432 func (f *fakeFileInfo) ModTime() time.Time { return f.modtime } 433 func (f *fakeFileInfo) IsDir() bool { return f.dir } 434 func (f *fakeFileInfo) Size() int64 { return int64(len(f.contents)) } 435 func (f *fakeFileInfo) Mode() os.FileMode { 436 if f.dir { 437 return 0755 | os.ModeDir 438 } 439 return 0644 440 } 441 442 type fakeFile struct { 443 io.ReadSeeker 444 fi *fakeFileInfo 445 path string // as opened 446 } 447 448 func (f *fakeFile) Close() error { return nil } 449 func (f *fakeFile) Stat() (os.FileInfo, error) { return f.fi, nil } 450 func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) { 451 if !f.fi.dir { 452 return nil, os.ErrInvalid 453 } 454 var fis []os.FileInfo 455 for _, fi := range f.fi.ents { 456 fis = append(fis, fi) 457 } 458 return fis, nil 459 } 460 461 type fakeFS map[string]*fakeFileInfo 462 463 func (fs fakeFS) Open(name string) (File, error) { 464 name = path.Clean(name) 465 f, ok := fs[name] 466 if !ok { 467 println("fake filesystem didn't find file", name) 468 return nil, os.ErrNotExist 469 } 470 return &fakeFile{ReadSeeker: strings.NewReader(f.contents), fi: f, path: name}, nil 471 } 472 473 func TestDirectoryIfNotModified(t *testing.T) { 474 defer afterTest(t) 475 const indexContents = "I am a fake index.html file" 476 fileMod := time.Unix(1000000000, 0).UTC() 477 fileModStr := fileMod.Format(TimeFormat) 478 dirMod := time.Unix(123, 0).UTC() 479 indexFile := &fakeFileInfo{ 480 basename: "index.html", 481 modtime: fileMod, 482 contents: indexContents, 483 } 484 fs := fakeFS{ 485 "/": &fakeFileInfo{ 486 dir: true, 487 modtime: dirMod, 488 ents: []*fakeFileInfo{indexFile}, 489 }, 490 "/index.html": indexFile, 491 } 492 493 ts := httptest.NewServer(FileServer(fs)) 494 defer ts.Close() 495 496 res, err := Get(ts.URL) 497 if err != nil { 498 t.Fatal(err) 499 } 500 b, err := ioutil.ReadAll(res.Body) 501 if err != nil { 502 t.Fatal(err) 503 } 504 if string(b) != indexContents { 505 t.Fatalf("Got body %q; want %q", b, indexContents) 506 } 507 res.Body.Close() 508 509 lastMod := res.Header.Get("Last-Modified") 510 if lastMod != fileModStr { 511 t.Fatalf("initial Last-Modified = %q; want %q", lastMod, fileModStr) 512 } 513 514 req, _ := NewRequest("GET", ts.URL, nil) 515 req.Header.Set("If-Modified-Since", lastMod) 516 517 res, err = DefaultClient.Do(req) 518 if err != nil { 519 t.Fatal(err) 520 } 521 if res.StatusCode != 304 { 522 t.Fatalf("Code after If-Modified-Since request = %v; want 304", res.StatusCode) 523 } 524 res.Body.Close() 525 526 // Advance the index.html file's modtime, but not the directory's. 527 indexFile.modtime = indexFile.modtime.Add(1 * time.Hour) 528 529 res, err = DefaultClient.Do(req) 530 if err != nil { 531 t.Fatal(err) 532 } 533 if res.StatusCode != 200 { 534 t.Fatalf("Code after second If-Modified-Since request = %v; want 200; res is %#v", res.StatusCode, res) 535 } 536 res.Body.Close() 537 } 538 539 func mustStat(t *testing.T, fileName string) os.FileInfo { 540 fi, err := os.Stat(fileName) 541 if err != nil { 542 t.Fatal(err) 543 } 544 return fi 545 } 546 547 func TestServeContent(t *testing.T) { 548 defer afterTest(t) 549 type serveParam struct { 550 name string 551 modtime time.Time 552 content io.ReadSeeker 553 contentType string 554 etag string 555 } 556 servec := make(chan serveParam, 1) 557 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 558 p := <-servec 559 if p.etag != "" { 560 w.Header().Set("ETag", p.etag) 561 } 562 if p.contentType != "" { 563 w.Header().Set("Content-Type", p.contentType) 564 } 565 ServeContent(w, r, p.name, p.modtime, p.content) 566 })) 567 defer ts.Close() 568 569 type testCase struct { 570 file string 571 modtime time.Time 572 serveETag string // optional 573 serveContentType string // optional 574 reqHeader map[string]string 575 wantLastMod string 576 wantContentType string 577 wantStatus int 578 } 579 htmlModTime := mustStat(t, "testdata/index.html").ModTime() 580 tests := map[string]testCase{ 581 "no_last_modified": { 582 file: "testdata/style.css", 583 wantContentType: "text/css; charset=utf-8", 584 wantStatus: 200, 585 }, 586 "with_last_modified": { 587 file: "testdata/index.html", 588 wantContentType: "text/html; charset=utf-8", 589 modtime: htmlModTime, 590 wantLastMod: htmlModTime.UTC().Format(TimeFormat), 591 wantStatus: 200, 592 }, 593 "not_modified_modtime": { 594 file: "testdata/style.css", 595 modtime: htmlModTime, 596 reqHeader: map[string]string{ 597 "If-Modified-Since": htmlModTime.UTC().Format(TimeFormat), 598 }, 599 wantStatus: 304, 600 }, 601 "not_modified_modtime_with_contenttype": { 602 file: "testdata/style.css", 603 serveContentType: "text/css", // explicit content type 604 modtime: htmlModTime, 605 reqHeader: map[string]string{ 606 "If-Modified-Since": htmlModTime.UTC().Format(TimeFormat), 607 }, 608 wantStatus: 304, 609 }, 610 "not_modified_etag": { 611 file: "testdata/style.css", 612 serveETag: `"foo"`, 613 reqHeader: map[string]string{ 614 "If-None-Match": `"foo"`, 615 }, 616 wantStatus: 304, 617 }, 618 "range_good": { 619 file: "testdata/style.css", 620 serveETag: `"A"`, 621 reqHeader: map[string]string{ 622 "Range": "bytes=0-4", 623 }, 624 wantStatus: StatusPartialContent, 625 wantContentType: "text/css; charset=utf-8", 626 }, 627 // An If-Range resource for entity "A", but entity "B" is now current. 628 // The Range request should be ignored. 629 "range_no_match": { 630 file: "testdata/style.css", 631 serveETag: `"A"`, 632 reqHeader: map[string]string{ 633 "Range": "bytes=0-4", 634 "If-Range": `"B"`, 635 }, 636 wantStatus: 200, 637 wantContentType: "text/css; charset=utf-8", 638 }, 639 } 640 for testName, tt := range tests { 641 f, err := os.Open(tt.file) 642 if err != nil { 643 t.Fatalf("test %q: %v", testName, err) 644 } 645 defer f.Close() 646 647 servec <- serveParam{ 648 name: filepath.Base(tt.file), 649 content: f, 650 modtime: tt.modtime, 651 etag: tt.serveETag, 652 contentType: tt.serveContentType, 653 } 654 req, err := NewRequest("GET", ts.URL, nil) 655 if err != nil { 656 t.Fatal(err) 657 } 658 for k, v := range tt.reqHeader { 659 req.Header.Set(k, v) 660 } 661 res, err := DefaultClient.Do(req) 662 if err != nil { 663 t.Fatal(err) 664 } 665 io.Copy(ioutil.Discard, res.Body) 666 res.Body.Close() 667 if res.StatusCode != tt.wantStatus { 668 t.Errorf("test %q: status = %d; want %d", testName, res.StatusCode, tt.wantStatus) 669 } 670 if g, e := res.Header.Get("Content-Type"), tt.wantContentType; g != e { 671 t.Errorf("test %q: content-type = %q, want %q", testName, g, e) 672 } 673 if g, e := res.Header.Get("Last-Modified"), tt.wantLastMod; g != e { 674 t.Errorf("test %q: last-modified = %q, want %q", testName, g, e) 675 } 676 } 677 } 678 679 // verifies that sendfile is being used on Linux 680 func TestLinuxSendfile(t *testing.T) { 681 defer afterTest(t) 682 if runtime.GOOS != "linux" { 683 t.Skip("skipping; linux-only test") 684 } 685 if _, err := exec.LookPath("strace"); err != nil { 686 t.Skip("skipping; strace not found in path") 687 } 688 689 ln, err := net.Listen("tcp", "127.0.0.1:0") 690 if err != nil { 691 t.Fatal(err) 692 } 693 lnf, err := ln.(*net.TCPListener).File() 694 if err != nil { 695 t.Fatal(err) 696 } 697 defer ln.Close() 698 699 var buf bytes.Buffer 700 child := exec.Command("strace", "-f", "-q", "-e", "trace=sendfile,sendfile64", os.Args[0], "-test.run=TestLinuxSendfileChild") 701 child.ExtraFiles = append(child.ExtraFiles, lnf) 702 child.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...) 703 child.Stdout = &buf 704 child.Stderr = &buf 705 if err := child.Start(); err != nil { 706 t.Skipf("skipping; failed to start straced child: %v", err) 707 } 708 709 res, err := Get(fmt.Sprintf("http://%s/", ln.Addr())) 710 if err != nil { 711 t.Fatalf("http client error: %v", err) 712 } 713 _, err = io.Copy(ioutil.Discard, res.Body) 714 if err != nil { 715 t.Fatalf("client body read error: %v", err) 716 } 717 res.Body.Close() 718 719 // Force child to exit cleanly. 720 Get(fmt.Sprintf("http://%s/quit", ln.Addr())) 721 child.Wait() 722 723 rx := regexp.MustCompile(`sendfile(64)?\(\d+,\s*\d+,\s*NULL,\s*\d+\)\s*=\s*\d+\s*\n`) 724 rxResume := regexp.MustCompile(`<\.\.\. sendfile(64)? resumed> \)\s*=\s*\d+\s*\n`) 725 out := buf.String() 726 if !rx.MatchString(out) && !rxResume.MatchString(out) { 727 t.Errorf("no sendfile system call found in:\n%s", out) 728 } 729 } 730 731 func getBody(t *testing.T, testName string, req Request) (*Response, []byte) { 732 r, err := DefaultClient.Do(&req) 733 if err != nil { 734 t.Fatalf("%s: for URL %q, send error: %v", testName, req.URL.String(), err) 735 } 736 b, err := ioutil.ReadAll(r.Body) 737 if err != nil { 738 t.Fatalf("%s: for URL %q, reading body: %v", testName, req.URL.String(), err) 739 } 740 return r, b 741 } 742 743 // TestLinuxSendfileChild isn't a real test. It's used as a helper process 744 // for TestLinuxSendfile. 745 func TestLinuxSendfileChild(*testing.T) { 746 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 747 return 748 } 749 defer os.Exit(0) 750 fd3 := os.NewFile(3, "ephemeral-port-listener") 751 ln, err := net.FileListener(fd3) 752 if err != nil { 753 panic(err) 754 } 755 mux := NewServeMux() 756 mux.Handle("/", FileServer(Dir("testdata"))) 757 mux.HandleFunc("/quit", func(ResponseWriter, *Request) { 758 os.Exit(0) 759 }) 760 s := &Server{Handler: mux} 761 err = s.Serve(ln) 762 if err != nil { 763 panic(err) 764 } 765 }