Rebase.
[official-gcc.git] / libgo / go / net / http / fs_test.go
blobc9a77c9f6aaf2187e0c2d4f0a9550ecff9cbc3a8
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.
5 package http_test
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 "reflect"
24 "regexp"
25 "runtime"
26 "strconv"
27 "strings"
28 "testing"
29 "time"
32 const (
33 testFile = "testdata/file"
34 testFileLen = 11
37 type wantRange struct {
38 start, end int64 // range [start,end)
41 var itoa = strconv.Itoa
43 var ServeFileRangeTests = []struct {
44 r string
45 code int
46 ranges []wantRange
48 {r: "", code: StatusOK},
49 {r: "bytes=0-4", code: StatusPartialContent, ranges: []wantRange{{0, 5}}},
50 {r: "bytes=2-", code: StatusPartialContent, ranges: []wantRange{{2, testFileLen}}},
51 {r: "bytes=-5", code: StatusPartialContent, ranges: []wantRange{{testFileLen - 5, testFileLen}}},
52 {r: "bytes=3-7", code: StatusPartialContent, ranges: []wantRange{{3, 8}}},
53 {r: "bytes=20-", code: StatusRequestedRangeNotSatisfiable},
54 {r: "bytes=0-0,-2", code: StatusPartialContent, ranges: []wantRange{{0, 1}, {testFileLen - 2, testFileLen}}},
55 {r: "bytes=0-1,5-8", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, 9}}},
56 {r: "bytes=0-1,5-", code: StatusPartialContent, ranges: []wantRange{{0, 2}, {5, testFileLen}}},
57 {r: "bytes=5-1000", code: StatusPartialContent, ranges: []wantRange{{5, testFileLen}}},
58 {r: "bytes=0-,1-,2-,3-,4-", code: StatusOK}, // ignore wasteful range request
59 {r: "bytes=0-" + itoa(testFileLen-2), code: StatusPartialContent, ranges: []wantRange{{0, testFileLen - 1}}},
60 {r: "bytes=0-" + itoa(testFileLen-1), code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}},
61 {r: "bytes=0-" + itoa(testFileLen), code: StatusPartialContent, ranges: []wantRange{{0, testFileLen}}},
64 func TestServeFile(t *testing.T) {
65 defer afterTest(t)
66 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
67 ServeFile(w, r, "testdata/file")
68 }))
69 defer ts.Close()
71 var err error
73 file, err := ioutil.ReadFile(testFile)
74 if err != nil {
75 t.Fatal("reading file:", err)
78 // set up the Request (re-used for all tests)
79 var req Request
80 req.Header = make(Header)
81 if req.URL, err = url.Parse(ts.URL); err != nil {
82 t.Fatal("ParseURL:", err)
84 req.Method = "GET"
86 // straight GET
87 _, body := getBody(t, "straight get", req)
88 if !bytes.Equal(body, file) {
89 t.Fatalf("body mismatch: got %q, want %q", body, file)
92 // Range tests
93 Cases:
94 for _, rt := range ServeFileRangeTests {
95 if rt.r != "" {
96 req.Header.Set("Range", rt.r)
98 resp, body := getBody(t, fmt.Sprintf("range test %q", rt.r), req)
99 if resp.StatusCode != rt.code {
100 t.Errorf("range=%q: StatusCode=%d, want %d", rt.r, resp.StatusCode, rt.code)
102 if rt.code == StatusRequestedRangeNotSatisfiable {
103 continue
105 wantContentRange := ""
106 if len(rt.ranges) == 1 {
107 rng := rt.ranges[0]
108 wantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen)
110 cr := resp.Header.Get("Content-Range")
111 if cr != wantContentRange {
112 t.Errorf("range=%q: Content-Range = %q, want %q", rt.r, cr, wantContentRange)
114 ct := resp.Header.Get("Content-Type")
115 if len(rt.ranges) == 1 {
116 rng := rt.ranges[0]
117 wantBody := file[rng.start:rng.end]
118 if !bytes.Equal(body, wantBody) {
119 t.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody)
121 if strings.HasPrefix(ct, "multipart/byteranges") {
122 t.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r, ct)
125 if len(rt.ranges) > 1 {
126 typ, params, err := mime.ParseMediaType(ct)
127 if err != nil {
128 t.Errorf("range=%q content-type = %q; %v", rt.r, ct, err)
129 continue
131 if typ != "multipart/byteranges" {
132 t.Errorf("range=%q content-type = %q; want multipart/byteranges", rt.r, typ)
133 continue
135 if params["boundary"] == "" {
136 t.Errorf("range=%q content-type = %q; lacks boundary", rt.r, ct)
137 continue
139 if g, w := resp.ContentLength, int64(len(body)); g != w {
140 t.Errorf("range=%q Content-Length = %d; want %d", rt.r, g, w)
141 continue
143 mr := multipart.NewReader(bytes.NewReader(body), params["boundary"])
144 for ri, rng := range rt.ranges {
145 part, err := mr.NextPart()
146 if err != nil {
147 t.Errorf("range=%q, reading part index %d: %v", rt.r, ri, err)
148 continue Cases
150 wantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen)
151 if g, w := part.Header.Get("Content-Range"), wantContentRange; g != w {
152 t.Errorf("range=%q: part Content-Range = %q; want %q", rt.r, g, w)
154 body, err := ioutil.ReadAll(part)
155 if err != nil {
156 t.Errorf("range=%q, reading part index %d body: %v", rt.r, ri, err)
157 continue Cases
159 wantBody := file[rng.start:rng.end]
160 if !bytes.Equal(body, wantBody) {
161 t.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody)
164 _, err = mr.NextPart()
165 if err != io.EOF {
166 t.Errorf("range=%q; expected final error io.EOF; got %v", rt.r, err)
172 var fsRedirectTestData = []struct {
173 original, redirect string
175 {"/test/index.html", "/test/"},
176 {"/test/testdata", "/test/testdata/"},
177 {"/test/testdata/file/", "/test/testdata/file"},
180 func TestFSRedirect(t *testing.T) {
181 defer afterTest(t)
182 ts := httptest.NewServer(StripPrefix("/test", FileServer(Dir("."))))
183 defer ts.Close()
185 for _, data := range fsRedirectTestData {
186 res, err := Get(ts.URL + data.original)
187 if err != nil {
188 t.Fatal(err)
190 res.Body.Close()
191 if g, e := res.Request.URL.Path, data.redirect; g != e {
192 t.Errorf("redirect from %s: got %s, want %s", data.original, g, e)
197 type testFileSystem struct {
198 open func(name string) (File, error)
201 func (fs *testFileSystem) Open(name string) (File, error) {
202 return fs.open(name)
205 func TestFileServerCleans(t *testing.T) {
206 defer afterTest(t)
207 ch := make(chan string, 1)
208 fs := FileServer(&testFileSystem{func(name string) (File, error) {
209 ch <- name
210 return nil, errors.New("file does not exist")
212 tests := []struct {
213 reqPath, openArg string
215 {"/foo.txt", "/foo.txt"},
216 {"//foo.txt", "/foo.txt"},
217 {"/../foo.txt", "/foo.txt"},
219 req, _ := NewRequest("GET", "http://example.com", nil)
220 for n, test := range tests {
221 rec := httptest.NewRecorder()
222 req.URL.Path = test.reqPath
223 fs.ServeHTTP(rec, req)
224 if got := <-ch; got != test.openArg {
225 t.Errorf("test %d: got %q, want %q", n, got, test.openArg)
230 func TestFileServerEscapesNames(t *testing.T) {
231 defer afterTest(t)
232 const dirListPrefix = "<pre>\n"
233 const dirListSuffix = "\n</pre>\n"
234 tests := []struct {
235 name, escaped string
237 {`simple_name`, `<a href="simple_name">simple_name</a>`},
238 {`"'<>&`, `<a href="%22%27%3C%3E&">&#34;&#39;&lt;&gt;&amp;</a>`},
239 {`?foo=bar#baz`, `<a href="%3Ffoo=bar%23baz">?foo=bar#baz</a>`},
240 {`<combo>?foo`, `<a href="%3Ccombo%3E%3Ffoo">&lt;combo&gt;?foo</a>`},
243 // We put each test file in its own directory in the fakeFS so we can look at it in isolation.
244 fs := make(fakeFS)
245 for i, test := range tests {
246 testFile := &fakeFileInfo{basename: test.name}
247 fs[fmt.Sprintf("/%d", i)] = &fakeFileInfo{
248 dir: true,
249 modtime: time.Unix(1000000000, 0).UTC(),
250 ents: []*fakeFileInfo{testFile},
252 fs[fmt.Sprintf("/%d/%s", i, test.name)] = testFile
255 ts := httptest.NewServer(FileServer(&fs))
256 defer ts.Close()
257 for i, test := range tests {
258 url := fmt.Sprintf("%s/%d", ts.URL, i)
259 res, err := Get(url)
260 if err != nil {
261 t.Fatalf("test %q: Get: %v", test.name, err)
263 b, err := ioutil.ReadAll(res.Body)
264 if err != nil {
265 t.Fatalf("test %q: read Body: %v", test.name, err)
267 s := string(b)
268 if !strings.HasPrefix(s, dirListPrefix) || !strings.HasSuffix(s, dirListSuffix) {
269 t.Errorf("test %q: listing dir, full output is %q, want prefix %q and suffix %q", test.name, s, dirListPrefix, dirListSuffix)
271 if trimmed := strings.TrimSuffix(strings.TrimPrefix(s, dirListPrefix), dirListSuffix); trimmed != test.escaped {
272 t.Errorf("test %q: listing dir, filename escaped to %q, want %q", test.name, trimmed, test.escaped)
274 res.Body.Close()
278 func mustRemoveAll(dir string) {
279 err := os.RemoveAll(dir)
280 if err != nil {
281 panic(err)
285 func TestFileServerImplicitLeadingSlash(t *testing.T) {
286 defer afterTest(t)
287 tempDir, err := ioutil.TempDir("", "")
288 if err != nil {
289 t.Fatalf("TempDir: %v", err)
291 defer mustRemoveAll(tempDir)
292 if err := ioutil.WriteFile(filepath.Join(tempDir, "foo.txt"), []byte("Hello world"), 0644); err != nil {
293 t.Fatalf("WriteFile: %v", err)
295 ts := httptest.NewServer(StripPrefix("/bar/", FileServer(Dir(tempDir))))
296 defer ts.Close()
297 get := func(suffix string) string {
298 res, err := Get(ts.URL + suffix)
299 if err != nil {
300 t.Fatalf("Get %s: %v", suffix, err)
302 b, err := ioutil.ReadAll(res.Body)
303 if err != nil {
304 t.Fatalf("ReadAll %s: %v", suffix, err)
306 res.Body.Close()
307 return string(b)
309 if s := get("/bar/"); !strings.Contains(s, ">foo.txt<") {
310 t.Logf("expected a directory listing with foo.txt, got %q", s)
312 if s := get("/bar/foo.txt"); s != "Hello world" {
313 t.Logf("expected %q, got %q", "Hello world", s)
317 func TestDirJoin(t *testing.T) {
318 if runtime.GOOS == "windows" {
319 t.Skip("skipping test on windows")
321 wfi, err := os.Stat("/etc/hosts")
322 if err != nil {
323 t.Skip("skipping test; no /etc/hosts file")
325 test := func(d Dir, name string) {
326 f, err := d.Open(name)
327 if err != nil {
328 t.Fatalf("open of %s: %v", name, err)
330 defer f.Close()
331 gfi, err := f.Stat()
332 if err != nil {
333 t.Fatalf("stat of %s: %v", name, err)
335 if !os.SameFile(gfi, wfi) {
336 t.Errorf("%s got different file", name)
339 test(Dir("/etc/"), "/hosts")
340 test(Dir("/etc/"), "hosts")
341 test(Dir("/etc/"), "../../../../hosts")
342 test(Dir("/etc"), "/hosts")
343 test(Dir("/etc"), "hosts")
344 test(Dir("/etc"), "../../../../hosts")
346 // Not really directories, but since we use this trick in
347 // ServeFile, test it:
348 test(Dir("/etc/hosts"), "")
349 test(Dir("/etc/hosts"), "/")
350 test(Dir("/etc/hosts"), "../")
353 func TestEmptyDirOpenCWD(t *testing.T) {
354 test := func(d Dir) {
355 name := "fs_test.go"
356 f, err := d.Open(name)
357 if err != nil {
358 t.Fatalf("open of %s: %v", name, err)
360 defer f.Close()
362 test(Dir(""))
363 test(Dir("."))
364 test(Dir("./"))
367 func TestServeFileContentType(t *testing.T) {
368 defer afterTest(t)
369 const ctype = "icecream/chocolate"
370 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
371 switch r.FormValue("override") {
372 case "1":
373 w.Header().Set("Content-Type", ctype)
374 case "2":
375 // Explicitly inhibit sniffing.
376 w.Header()["Content-Type"] = []string{}
378 ServeFile(w, r, "testdata/file")
380 defer ts.Close()
381 get := func(override string, want []string) {
382 resp, err := Get(ts.URL + "?override=" + override)
383 if err != nil {
384 t.Fatal(err)
386 if h := resp.Header["Content-Type"]; !reflect.DeepEqual(h, want) {
387 t.Errorf("Content-Type mismatch: got %v, want %v", h, want)
389 resp.Body.Close()
391 get("0", []string{"text/plain; charset=utf-8"})
392 get("1", []string{ctype})
393 get("2", nil)
396 func TestServeFileMimeType(t *testing.T) {
397 defer afterTest(t)
398 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
399 ServeFile(w, r, "testdata/style.css")
401 defer ts.Close()
402 resp, err := Get(ts.URL)
403 if err != nil {
404 t.Fatal(err)
406 resp.Body.Close()
407 want := "text/css; charset=utf-8"
408 if h := resp.Header.Get("Content-Type"); h != want {
409 t.Errorf("Content-Type mismatch: got %q, want %q", h, want)
413 func TestServeFileFromCWD(t *testing.T) {
414 defer afterTest(t)
415 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
416 ServeFile(w, r, "fs_test.go")
418 defer ts.Close()
419 r, err := Get(ts.URL)
420 if err != nil {
421 t.Fatal(err)
423 r.Body.Close()
424 if r.StatusCode != 200 {
425 t.Fatalf("expected 200 OK, got %s", r.Status)
429 func TestServeFileWithContentEncoding(t *testing.T) {
430 defer afterTest(t)
431 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
432 w.Header().Set("Content-Encoding", "foo")
433 ServeFile(w, r, "testdata/file")
435 defer ts.Close()
436 resp, err := Get(ts.URL)
437 if err != nil {
438 t.Fatal(err)
440 resp.Body.Close()
441 if g, e := resp.ContentLength, int64(-1); g != e {
442 t.Errorf("Content-Length mismatch: got %d, want %d", g, e)
446 func TestServeIndexHtml(t *testing.T) {
447 defer afterTest(t)
448 const want = "index.html says hello\n"
449 ts := httptest.NewServer(FileServer(Dir(".")))
450 defer ts.Close()
452 for _, path := range []string{"/testdata/", "/testdata/index.html"} {
453 res, err := Get(ts.URL + path)
454 if err != nil {
455 t.Fatal(err)
457 b, err := ioutil.ReadAll(res.Body)
458 if err != nil {
459 t.Fatal("reading Body:", err)
461 if s := string(b); s != want {
462 t.Errorf("for path %q got %q, want %q", path, s, want)
464 res.Body.Close()
468 func TestFileServerZeroByte(t *testing.T) {
469 defer afterTest(t)
470 ts := httptest.NewServer(FileServer(Dir(".")))
471 defer ts.Close()
473 res, err := Get(ts.URL + "/..\x00")
474 if err != nil {
475 t.Fatal(err)
477 b, err := ioutil.ReadAll(res.Body)
478 if err != nil {
479 t.Fatal("reading Body:", err)
481 if res.StatusCode == 200 {
482 t.Errorf("got status 200; want an error. Body is:\n%s", string(b))
486 type fakeFileInfo struct {
487 dir bool
488 basename string
489 modtime time.Time
490 ents []*fakeFileInfo
491 contents string
494 func (f *fakeFileInfo) Name() string { return f.basename }
495 func (f *fakeFileInfo) Sys() interface{} { return nil }
496 func (f *fakeFileInfo) ModTime() time.Time { return f.modtime }
497 func (f *fakeFileInfo) IsDir() bool { return f.dir }
498 func (f *fakeFileInfo) Size() int64 { return int64(len(f.contents)) }
499 func (f *fakeFileInfo) Mode() os.FileMode {
500 if f.dir {
501 return 0755 | os.ModeDir
503 return 0644
506 type fakeFile struct {
507 io.ReadSeeker
508 fi *fakeFileInfo
509 path string // as opened
510 entpos int
513 func (f *fakeFile) Close() error { return nil }
514 func (f *fakeFile) Stat() (os.FileInfo, error) { return f.fi, nil }
515 func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
516 if !f.fi.dir {
517 return nil, os.ErrInvalid
519 var fis []os.FileInfo
521 limit := f.entpos + count
522 if count <= 0 || limit > len(f.fi.ents) {
523 limit = len(f.fi.ents)
525 for ; f.entpos < limit; f.entpos++ {
526 fis = append(fis, f.fi.ents[f.entpos])
529 if len(fis) == 0 && count > 0 {
530 return fis, io.EOF
531 } else {
532 return fis, nil
536 type fakeFS map[string]*fakeFileInfo
538 func (fs fakeFS) Open(name string) (File, error) {
539 name = path.Clean(name)
540 f, ok := fs[name]
541 if !ok {
542 return nil, os.ErrNotExist
544 return &fakeFile{ReadSeeker: strings.NewReader(f.contents), fi: f, path: name}, nil
547 func TestDirectoryIfNotModified(t *testing.T) {
548 defer afterTest(t)
549 const indexContents = "I am a fake index.html file"
550 fileMod := time.Unix(1000000000, 0).UTC()
551 fileModStr := fileMod.Format(TimeFormat)
552 dirMod := time.Unix(123, 0).UTC()
553 indexFile := &fakeFileInfo{
554 basename: "index.html",
555 modtime: fileMod,
556 contents: indexContents,
558 fs := fakeFS{
559 "/": &fakeFileInfo{
560 dir: true,
561 modtime: dirMod,
562 ents: []*fakeFileInfo{indexFile},
564 "/index.html": indexFile,
567 ts := httptest.NewServer(FileServer(fs))
568 defer ts.Close()
570 res, err := Get(ts.URL)
571 if err != nil {
572 t.Fatal(err)
574 b, err := ioutil.ReadAll(res.Body)
575 if err != nil {
576 t.Fatal(err)
578 if string(b) != indexContents {
579 t.Fatalf("Got body %q; want %q", b, indexContents)
581 res.Body.Close()
583 lastMod := res.Header.Get("Last-Modified")
584 if lastMod != fileModStr {
585 t.Fatalf("initial Last-Modified = %q; want %q", lastMod, fileModStr)
588 req, _ := NewRequest("GET", ts.URL, nil)
589 req.Header.Set("If-Modified-Since", lastMod)
591 res, err = DefaultClient.Do(req)
592 if err != nil {
593 t.Fatal(err)
595 if res.StatusCode != 304 {
596 t.Fatalf("Code after If-Modified-Since request = %v; want 304", res.StatusCode)
598 res.Body.Close()
600 // Advance the index.html file's modtime, but not the directory's.
601 indexFile.modtime = indexFile.modtime.Add(1 * time.Hour)
603 res, err = DefaultClient.Do(req)
604 if err != nil {
605 t.Fatal(err)
607 if res.StatusCode != 200 {
608 t.Fatalf("Code after second If-Modified-Since request = %v; want 200; res is %#v", res.StatusCode, res)
610 res.Body.Close()
613 func mustStat(t *testing.T, fileName string) os.FileInfo {
614 fi, err := os.Stat(fileName)
615 if err != nil {
616 t.Fatal(err)
618 return fi
621 func TestServeContent(t *testing.T) {
622 defer afterTest(t)
623 type serveParam struct {
624 name string
625 modtime time.Time
626 content io.ReadSeeker
627 contentType string
628 etag string
630 servec := make(chan serveParam, 1)
631 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
632 p := <-servec
633 if p.etag != "" {
634 w.Header().Set("ETag", p.etag)
636 if p.contentType != "" {
637 w.Header().Set("Content-Type", p.contentType)
639 ServeContent(w, r, p.name, p.modtime, p.content)
641 defer ts.Close()
643 type testCase struct {
644 // One of file or content must be set:
645 file string
646 content io.ReadSeeker
648 modtime time.Time
649 serveETag string // optional
650 serveContentType string // optional
651 reqHeader map[string]string
652 wantLastMod string
653 wantContentType string
654 wantStatus int
656 htmlModTime := mustStat(t, "testdata/index.html").ModTime()
657 tests := map[string]testCase{
658 "no_last_modified": {
659 file: "testdata/style.css",
660 wantContentType: "text/css; charset=utf-8",
661 wantStatus: 200,
663 "with_last_modified": {
664 file: "testdata/index.html",
665 wantContentType: "text/html; charset=utf-8",
666 modtime: htmlModTime,
667 wantLastMod: htmlModTime.UTC().Format(TimeFormat),
668 wantStatus: 200,
670 "not_modified_modtime": {
671 file: "testdata/style.css",
672 modtime: htmlModTime,
673 reqHeader: map[string]string{
674 "If-Modified-Since": htmlModTime.UTC().Format(TimeFormat),
676 wantStatus: 304,
678 "not_modified_modtime_with_contenttype": {
679 file: "testdata/style.css",
680 serveContentType: "text/css", // explicit content type
681 modtime: htmlModTime,
682 reqHeader: map[string]string{
683 "If-Modified-Since": htmlModTime.UTC().Format(TimeFormat),
685 wantStatus: 304,
687 "not_modified_etag": {
688 file: "testdata/style.css",
689 serveETag: `"foo"`,
690 reqHeader: map[string]string{
691 "If-None-Match": `"foo"`,
693 wantStatus: 304,
695 "not_modified_etag_no_seek": {
696 content: panicOnSeek{nil}, // should never be called
697 serveETag: `"foo"`,
698 reqHeader: map[string]string{
699 "If-None-Match": `"foo"`,
701 wantStatus: 304,
703 "range_good": {
704 file: "testdata/style.css",
705 serveETag: `"A"`,
706 reqHeader: map[string]string{
707 "Range": "bytes=0-4",
709 wantStatus: StatusPartialContent,
710 wantContentType: "text/css; charset=utf-8",
712 // An If-Range resource for entity "A", but entity "B" is now current.
713 // The Range request should be ignored.
714 "range_no_match": {
715 file: "testdata/style.css",
716 serveETag: `"A"`,
717 reqHeader: map[string]string{
718 "Range": "bytes=0-4",
719 "If-Range": `"B"`,
721 wantStatus: 200,
722 wantContentType: "text/css; charset=utf-8",
725 for testName, tt := range tests {
726 var content io.ReadSeeker
727 if tt.file != "" {
728 f, err := os.Open(tt.file)
729 if err != nil {
730 t.Fatalf("test %q: %v", testName, err)
732 defer f.Close()
733 content = f
734 } else {
735 content = tt.content
738 servec <- serveParam{
739 name: filepath.Base(tt.file),
740 content: content,
741 modtime: tt.modtime,
742 etag: tt.serveETag,
743 contentType: tt.serveContentType,
745 req, err := NewRequest("GET", ts.URL, nil)
746 if err != nil {
747 t.Fatal(err)
749 for k, v := range tt.reqHeader {
750 req.Header.Set(k, v)
752 res, err := DefaultClient.Do(req)
753 if err != nil {
754 t.Fatal(err)
756 io.Copy(ioutil.Discard, res.Body)
757 res.Body.Close()
758 if res.StatusCode != tt.wantStatus {
759 t.Errorf("test %q: status = %d; want %d", testName, res.StatusCode, tt.wantStatus)
761 if g, e := res.Header.Get("Content-Type"), tt.wantContentType; g != e {
762 t.Errorf("test %q: content-type = %q, want %q", testName, g, e)
764 if g, e := res.Header.Get("Last-Modified"), tt.wantLastMod; g != e {
765 t.Errorf("test %q: last-modified = %q, want %q", testName, g, e)
770 // verifies that sendfile is being used on Linux
771 func TestLinuxSendfile(t *testing.T) {
772 defer afterTest(t)
773 if runtime.GOOS != "linux" {
774 t.Skip("skipping; linux-only test")
776 if _, err := exec.LookPath("strace"); err != nil {
777 t.Skip("skipping; strace not found in path")
780 ln, err := net.Listen("tcp", "127.0.0.1:0")
781 if err != nil {
782 t.Fatal(err)
784 lnf, err := ln.(*net.TCPListener).File()
785 if err != nil {
786 t.Fatal(err)
788 defer ln.Close()
790 trace := "trace=sendfile"
791 if runtime.GOARCH != "alpha" {
792 trace = trace + ",sendfile64"
795 var buf bytes.Buffer
796 child := exec.Command("strace", "-f", "-q", "-e", trace, os.Args[0], "-test.run=TestLinuxSendfileChild")
797 child.ExtraFiles = append(child.ExtraFiles, lnf)
798 child.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...)
799 child.Stdout = &buf
800 child.Stderr = &buf
801 if err := child.Start(); err != nil {
802 t.Skipf("skipping; failed to start straced child: %v", err)
805 res, err := Get(fmt.Sprintf("http://%s/", ln.Addr()))
806 if err != nil {
807 t.Fatalf("http client error: %v", err)
809 _, err = io.Copy(ioutil.Discard, res.Body)
810 if err != nil {
811 t.Fatalf("client body read error: %v", err)
813 res.Body.Close()
815 // Force child to exit cleanly.
816 Get(fmt.Sprintf("http://%s/quit", ln.Addr()))
817 child.Wait()
819 rx := regexp.MustCompile(`sendfile(64)?\(\d+,\s*\d+,\s*NULL,\s*\d+\)\s*=\s*\d+\s*\n`)
820 rxResume := regexp.MustCompile(`<\.\.\. sendfile(64)? resumed> \)\s*=\s*\d+\s*\n`)
821 out := buf.String()
822 if !rx.MatchString(out) && !rxResume.MatchString(out) {
823 t.Errorf("no sendfile system call found in:\n%s", out)
827 func getBody(t *testing.T, testName string, req Request) (*Response, []byte) {
828 r, err := DefaultClient.Do(&req)
829 if err != nil {
830 t.Fatalf("%s: for URL %q, send error: %v", testName, req.URL.String(), err)
832 b, err := ioutil.ReadAll(r.Body)
833 if err != nil {
834 t.Fatalf("%s: for URL %q, reading body: %v", testName, req.URL.String(), err)
836 return r, b
839 // TestLinuxSendfileChild isn't a real test. It's used as a helper process
840 // for TestLinuxSendfile.
841 func TestLinuxSendfileChild(*testing.T) {
842 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
843 return
845 defer os.Exit(0)
846 fd3 := os.NewFile(3, "ephemeral-port-listener")
847 ln, err := net.FileListener(fd3)
848 if err != nil {
849 panic(err)
851 mux := NewServeMux()
852 mux.Handle("/", FileServer(Dir("testdata")))
853 mux.HandleFunc("/quit", func(ResponseWriter, *Request) {
854 os.Exit(0)
856 s := &Server{Handler: mux}
857 err = s.Serve(ln)
858 if err != nil {
859 panic(err)
863 type panicOnSeek struct{ io.ReadSeeker }