github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/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 if runtime.GOOS == "windows" { 263 t.Skip("skipping test on windows") 264 } 265 wfi, err := os.Stat("/etc/hosts") 266 if err != nil { 267 t.Skip("skipping test; no /etc/hosts file") 268 } 269 test := func(d Dir, name string) { 270 f, err := d.Open(name) 271 if err != nil { 272 t.Fatalf("open of %s: %v", name, err) 273 } 274 defer f.Close() 275 gfi, err := f.Stat() 276 if err != nil { 277 t.Fatalf("stat of %s: %v", name, err) 278 } 279 if !os.SameFile(gfi, wfi) { 280 t.Errorf("%s got different file", name) 281 } 282 } 283 test(Dir("/etc/"), "/hosts") 284 test(Dir("/etc/"), "hosts") 285 test(Dir("/etc/"), "../../../../hosts") 286 test(Dir("/etc"), "/hosts") 287 test(Dir("/etc"), "hosts") 288 test(Dir("/etc"), "../../../../hosts") 289 290 // Not really directories, but since we use this trick in 291 // ServeFile, test it: 292 test(Dir("/etc/hosts"), "") 293 test(Dir("/etc/hosts"), "/") 294 test(Dir("/etc/hosts"), "../") 295 } 296 297 func TestEmptyDirOpenCWD(t *testing.T) { 298 test := func(d Dir) { 299 name := "fs_test.go" 300 f, err := d.Open(name) 301 if err != nil { 302 t.Fatalf("open of %s: %v", name, err) 303 } 304 defer f.Close() 305 } 306 test(Dir("")) 307 test(Dir(".")) 308 test(Dir("./")) 309 } 310 311 func TestServeFileContentType(t *testing.T) { 312 defer afterTest(t) 313 const ctype = "icecream/chocolate" 314 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 315 if r.FormValue("override") == "1" { 316 w.Header().Set("Content-Type", ctype) 317 } 318 ServeFile(w, r, "testdata/file") 319 })) 320 defer ts.Close() 321 get := func(override, want string) { 322 resp, err := Get(ts.URL + "?override=" + override) 323 if err != nil { 324 t.Fatal(err) 325 } 326 if h := resp.Header.Get("Content-Type"); h != want { 327 t.Errorf("Content-Type mismatch: got %q, want %q", h, want) 328 } 329 resp.Body.Close() 330 } 331 get("0", "text/plain; charset=utf-8") 332 get("1", ctype) 333 } 334 335 func TestServeFileMimeType(t *testing.T) { 336 defer afterTest(t) 337 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 338 ServeFile(w, r, "testdata/style.css") 339 })) 340 defer ts.Close() 341 resp, err := Get(ts.URL) 342 if err != nil { 343 t.Fatal(err) 344 } 345 resp.Body.Close() 346 want := "text/css; charset=utf-8" 347 if h := resp.Header.Get("Content-Type"); h != want { 348 t.Errorf("Content-Type mismatch: got %q, want %q", h, want) 349 } 350 } 351 352 func TestServeFileFromCWD(t *testing.T) { 353 defer afterTest(t) 354 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 355 ServeFile(w, r, "fs_test.go") 356 })) 357 defer ts.Close() 358 r, err := Get(ts.URL) 359 if err != nil { 360 t.Fatal(err) 361 } 362 r.Body.Close() 363 if r.StatusCode != 200 { 364 t.Fatalf("expected 200 OK, got %s", r.Status) 365 } 366 } 367 368 func TestServeFileWithContentEncoding(t *testing.T) { 369 defer afterTest(t) 370 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 371 w.Header().Set("Content-Encoding", "foo") 372 ServeFile(w, r, "testdata/file") 373 })) 374 defer ts.Close() 375 resp, err := Get(ts.URL) 376 if err != nil { 377 t.Fatal(err) 378 } 379 resp.Body.Close() 380 if g, e := resp.ContentLength, int64(-1); g != e { 381 t.Errorf("Content-Length mismatch: got %d, want %d", g, e) 382 } 383 } 384 385 func TestServeIndexHtml(t *testing.T) { 386 defer afterTest(t) 387 const want = "index.html says hello\n" 388 ts := httptest.NewServer(FileServer(Dir("."))) 389 defer ts.Close() 390 391 for _, path := range []string{"/testdata/", "/testdata/index.html"} { 392 res, err := Get(ts.URL + path) 393 if err != nil { 394 t.Fatal(err) 395 } 396 b, err := ioutil.ReadAll(res.Body) 397 if err != nil { 398 t.Fatal("reading Body:", err) 399 } 400 if s := string(b); s != want { 401 t.Errorf("for path %q got %q, want %q", path, s, want) 402 } 403 res.Body.Close() 404 } 405 } 406 407 func TestFileServerZeroByte(t *testing.T) { 408 defer afterTest(t) 409 ts := httptest.NewServer(FileServer(Dir("."))) 410 defer ts.Close() 411 412 res, err := Get(ts.URL + "/..\x00") 413 if err != nil { 414 t.Fatal(err) 415 } 416 b, err := ioutil.ReadAll(res.Body) 417 if err != nil { 418 t.Fatal("reading Body:", err) 419 } 420 if res.StatusCode == 200 { 421 t.Errorf("got status 200; want an error. Body is:\n%s", string(b)) 422 } 423 } 424 425 type fakeFileInfo struct { 426 dir bool 427 basename string 428 modtime time.Time 429 ents []*fakeFileInfo 430 contents string 431 } 432 433 func (f *fakeFileInfo) Name() string { return f.basename } 434 func (f *fakeFileInfo) Sys() interface{} { return nil } 435 func (f *fakeFileInfo) ModTime() time.Time { return f.modtime } 436 func (f *fakeFileInfo) IsDir() bool { return f.dir } 437 func (f *fakeFileInfo) Size() int64 { return int64(len(f.contents)) } 438 func (f *fakeFileInfo) Mode() os.FileMode { 439 if f.dir { 440 return 0755 | os.ModeDir 441 } 442 return 0644 443 } 444 445 type fakeFile struct { 446 io.ReadSeeker 447 fi *fakeFileInfo 448 path string // as opened 449 } 450 451 func (f *fakeFile) Close() error { return nil } 452 func (f *fakeFile) Stat() (os.FileInfo, error) { return f.fi, nil } 453 func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) { 454 if !f.fi.dir { 455 return nil, os.ErrInvalid 456 } 457 var fis []os.FileInfo 458 for _, fi := range f.fi.ents { 459 fis = append(fis, fi) 460 } 461 return fis, nil 462 } 463 464 type fakeFS map[string]*fakeFileInfo 465 466 func (fs fakeFS) Open(name string) (File, error) { 467 name = path.Clean(name) 468 f, ok := fs[name] 469 if !ok { 470 println("fake filesystem didn't find file", name) 471 return nil, os.ErrNotExist 472 } 473 return &fakeFile{ReadSeeker: strings.NewReader(f.contents), fi: f, path: name}, nil 474 } 475 476 func TestDirectoryIfNotModified(t *testing.T) { 477 defer afterTest(t) 478 const indexContents = "I am a fake index.html file" 479 fileMod := time.Unix(1000000000, 0).UTC() 480 fileModStr := fileMod.Format(TimeFormat) 481 dirMod := time.Unix(123, 0).UTC() 482 indexFile := &fakeFileInfo{ 483 basename: "index.html", 484 modtime: fileMod, 485 contents: indexContents, 486 } 487 fs := fakeFS{ 488 "/": &fakeFileInfo{ 489 dir: true, 490 modtime: dirMod, 491 ents: []*fakeFileInfo{indexFile}, 492 }, 493 "/index.html": indexFile, 494 } 495 496 ts := httptest.NewServer(FileServer(fs)) 497 defer ts.Close() 498 499 res, err := Get(ts.URL) 500 if err != nil { 501 t.Fatal(err) 502 } 503 b, err := ioutil.ReadAll(res.Body) 504 if err != nil { 505 t.Fatal(err) 506 } 507 if string(b) != indexContents { 508 t.Fatalf("Got body %q; want %q", b, indexContents) 509 } 510 res.Body.Close() 511 512 lastMod := res.Header.Get("Last-Modified") 513 if lastMod != fileModStr { 514 t.Fatalf("initial Last-Modified = %q; want %q", lastMod, fileModStr) 515 } 516 517 req, _ := NewRequest("GET", ts.URL, nil) 518 req.Header.Set("If-Modified-Since", lastMod) 519 520 res, err = DefaultClient.Do(req) 521 if err != nil { 522 t.Fatal(err) 523 } 524 if res.StatusCode != 304 { 525 t.Fatalf("Code after If-Modified-Since request = %v; want 304", res.StatusCode) 526 } 527 res.Body.Close() 528 529 // Advance the index.html file's modtime, but not the directory's. 530 indexFile.modtime = indexFile.modtime.Add(1 * time.Hour) 531 532 res, err = DefaultClient.Do(req) 533 if err != nil { 534 t.Fatal(err) 535 } 536 if res.StatusCode != 200 { 537 t.Fatalf("Code after second If-Modified-Since request = %v; want 200; res is %#v", res.StatusCode, res) 538 } 539 res.Body.Close() 540 } 541 542 func mustStat(t *testing.T, fileName string) os.FileInfo { 543 fi, err := os.Stat(fileName) 544 if err != nil { 545 t.Fatal(err) 546 } 547 return fi 548 } 549 550 func TestServeContent(t *testing.T) { 551 defer afterTest(t) 552 type serveParam struct { 553 name string 554 modtime time.Time 555 content io.ReadSeeker 556 contentType string 557 etag string 558 } 559 servec := make(chan serveParam, 1) 560 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { 561 p := <-servec 562 if p.etag != "" { 563 w.Header().Set("ETag", p.etag) 564 } 565 if p.contentType != "" { 566 w.Header().Set("Content-Type", p.contentType) 567 } 568 ServeContent(w, r, p.name, p.modtime, p.content) 569 })) 570 defer ts.Close() 571 572 type testCase struct { 573 // One of file or content must be set: 574 file string 575 content io.ReadSeeker 576 577 modtime time.Time 578 serveETag string // optional 579 serveContentType string // optional 580 reqHeader map[string]string 581 wantLastMod string 582 wantContentType string 583 wantStatus int 584 } 585 htmlModTime := mustStat(t, "testdata/index.html").ModTime() 586 tests := map[string]testCase{ 587 "no_last_modified": { 588 file: "testdata/style.css", 589 wantContentType: "text/css; charset=utf-8", 590 wantStatus: 200, 591 }, 592 "with_last_modified": { 593 file: "testdata/index.html", 594 wantContentType: "text/html; charset=utf-8", 595 modtime: htmlModTime, 596 wantLastMod: htmlModTime.UTC().Format(TimeFormat), 597 wantStatus: 200, 598 }, 599 "not_modified_modtime": { 600 file: "testdata/style.css", 601 modtime: htmlModTime, 602 reqHeader: map[string]string{ 603 "If-Modified-Since": htmlModTime.UTC().Format(TimeFormat), 604 }, 605 wantStatus: 304, 606 }, 607 "not_modified_modtime_with_contenttype": { 608 file: "testdata/style.css", 609 serveContentType: "text/css", // explicit content type 610 modtime: htmlModTime, 611 reqHeader: map[string]string{ 612 "If-Modified-Since": htmlModTime.UTC().Format(TimeFormat), 613 }, 614 wantStatus: 304, 615 }, 616 "not_modified_etag": { 617 file: "testdata/style.css", 618 serveETag: `"foo"`, 619 reqHeader: map[string]string{ 620 "If-None-Match": `"foo"`, 621 }, 622 wantStatus: 304, 623 }, 624 "not_modified_etag_no_seek": { 625 content: panicOnSeek{nil}, // should never be called 626 serveETag: `"foo"`, 627 reqHeader: map[string]string{ 628 "If-None-Match": `"foo"`, 629 }, 630 wantStatus: 304, 631 }, 632 "range_good": { 633 file: "testdata/style.css", 634 serveETag: `"A"`, 635 reqHeader: map[string]string{ 636 "Range": "bytes=0-4", 637 }, 638 wantStatus: StatusPartialContent, 639 wantContentType: "text/css; charset=utf-8", 640 }, 641 // An If-Range resource for entity "A", but entity "B" is now current. 642 // The Range request should be ignored. 643 "range_no_match": { 644 file: "testdata/style.css", 645 serveETag: `"A"`, 646 reqHeader: map[string]string{ 647 "Range": "bytes=0-4", 648 "If-Range": `"B"`, 649 }, 650 wantStatus: 200, 651 wantContentType: "text/css; charset=utf-8", 652 }, 653 } 654 for testName, tt := range tests { 655 var content io.ReadSeeker 656 if tt.file != "" { 657 f, err := os.Open(tt.file) 658 if err != nil { 659 t.Fatalf("test %q: %v", testName, err) 660 } 661 defer f.Close() 662 content = f 663 } else { 664 content = tt.content 665 } 666 667 servec <- serveParam{ 668 name: filepath.Base(tt.file), 669 content: content, 670 modtime: tt.modtime, 671 etag: tt.serveETag, 672 contentType: tt.serveContentType, 673 } 674 req, err := NewRequest("GET", ts.URL, nil) 675 if err != nil { 676 t.Fatal(err) 677 } 678 for k, v := range tt.reqHeader { 679 req.Header.Set(k, v) 680 } 681 res, err := DefaultClient.Do(req) 682 if err != nil { 683 t.Fatal(err) 684 } 685 io.Copy(ioutil.Discard, res.Body) 686 res.Body.Close() 687 if res.StatusCode != tt.wantStatus { 688 t.Errorf("test %q: status = %d; want %d", testName, res.StatusCode, tt.wantStatus) 689 } 690 if g, e := res.Header.Get("Content-Type"), tt.wantContentType; g != e { 691 t.Errorf("test %q: content-type = %q, want %q", testName, g, e) 692 } 693 if g, e := res.Header.Get("Last-Modified"), tt.wantLastMod; g != e { 694 t.Errorf("test %q: last-modified = %q, want %q", testName, g, e) 695 } 696 } 697 } 698 699 // verifies that sendfile is being used on Linux 700 func TestLinuxSendfile(t *testing.T) { 701 defer afterTest(t) 702 if runtime.GOOS != "linux" { 703 t.Skip("skipping; linux-only test") 704 } 705 if _, err := exec.LookPath("strace"); err != nil { 706 t.Skip("skipping; strace not found in path") 707 } 708 709 ln, err := net.Listen("tcp", "127.0.0.1:0") 710 if err != nil { 711 t.Fatal(err) 712 } 713 lnf, err := ln.(*net.TCPListener).File() 714 if err != nil { 715 t.Fatal(err) 716 } 717 defer ln.Close() 718 719 var buf bytes.Buffer 720 child := exec.Command("strace", "-f", "-q", "-e", "trace=sendfile,sendfile64", os.Args[0], "-test.run=TestLinuxSendfileChild") 721 child.ExtraFiles = append(child.ExtraFiles, lnf) 722 child.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...) 723 child.Stdout = &buf 724 child.Stderr = &buf 725 if err := child.Start(); err != nil { 726 t.Skipf("skipping; failed to start straced child: %v", err) 727 } 728 729 res, err := Get(fmt.Sprintf("http://%s/", ln.Addr())) 730 if err != nil { 731 t.Fatalf("http client error: %v", err) 732 } 733 _, err = io.Copy(ioutil.Discard, res.Body) 734 if err != nil { 735 t.Fatalf("client body read error: %v", err) 736 } 737 res.Body.Close() 738 739 // Force child to exit cleanly. 740 Get(fmt.Sprintf("http://%s/quit", ln.Addr())) 741 child.Wait() 742 743 rx := regexp.MustCompile(`sendfile(64)?\(\d+,\s*\d+,\s*NULL,\s*\d+\)\s*=\s*\d+\s*\n`) 744 rxResume := regexp.MustCompile(`<\.\.\. sendfile(64)? resumed> \)\s*=\s*\d+\s*\n`) 745 out := buf.String() 746 if !rx.MatchString(out) && !rxResume.MatchString(out) { 747 t.Errorf("no sendfile system call found in:\n%s", out) 748 } 749 } 750 751 func getBody(t *testing.T, testName string, req Request) (*Response, []byte) { 752 r, err := DefaultClient.Do(&req) 753 if err != nil { 754 t.Fatalf("%s: for URL %q, send error: %v", testName, req.URL.String(), err) 755 } 756 b, err := ioutil.ReadAll(r.Body) 757 if err != nil { 758 t.Fatalf("%s: for URL %q, reading body: %v", testName, req.URL.String(), err) 759 } 760 return r, b 761 } 762 763 // TestLinuxSendfileChild isn't a real test. It's used as a helper process 764 // for TestLinuxSendfile. 765 func TestLinuxSendfileChild(*testing.T) { 766 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 767 return 768 } 769 defer os.Exit(0) 770 fd3 := os.NewFile(3, "ephemeral-port-listener") 771 ln, err := net.FileListener(fd3) 772 if err != nil { 773 panic(err) 774 } 775 mux := NewServeMux() 776 mux.Handle("/", FileServer(Dir("testdata"))) 777 mux.HandleFunc("/quit", func(ResponseWriter, *Request) { 778 os.Exit(0) 779 }) 780 s := &Server{Handler: mux} 781 err = s.Serve(ln) 782 if err != nil { 783 panic(err) 784 } 785 } 786 787 type panicOnSeek struct{ io.ReadSeeker }