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.
33 testFile
= "testdata/file"
37 type wantRange
struct {
38 start
, end
int64 // range [start,end)
41 var itoa
= strconv
.Itoa
43 var ServeFileRangeTests
= []struct {
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
) {
66 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
67 ServeFile(w
, r
, "testdata/file")
73 file
, err
:= ioutil
.ReadFile(testFile
)
75 t
.Fatal("reading file:", err
)
78 // set up the Request (re-used for all tests)
80 req
.Header
= make(Header
)
81 if req
.URL
, err
= url
.Parse(ts
.URL
); err
!= nil {
82 t
.Fatal("ParseURL:", err
)
87 _
, body
:= getBody(t
, "straight get", req
)
88 if !bytes
.Equal(body
, file
) {
89 t
.Fatalf("body mismatch: got %q, want %q", body
, file
)
94 for _
, rt
:= range ServeFileRangeTests
{
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
{
105 wantContentRange
:= ""
106 if len(rt
.ranges
) == 1 {
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 {
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
)
128 t
.Errorf("range=%q content-type = %q; %v", rt
.r
, ct
, err
)
131 if typ
!= "multipart/byteranges" {
132 t
.Errorf("range=%q content-type = %q; want multipart/byteranges", rt
.r
, typ
)
135 if params
["boundary"] == "" {
136 t
.Errorf("range=%q content-type = %q; lacks boundary", rt
.r
, ct
)
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
)
143 mr
:= multipart
.NewReader(bytes
.NewReader(body
), params
["boundary"])
144 for ri
, rng
:= range rt
.ranges
{
145 part
, err
:= mr
.NextPart()
147 t
.Errorf("range=%q, reading part index %d: %v", rt
.r
, ri
, err
)
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
)
156 t
.Errorf("range=%q, reading part index %d body: %v", rt
.r
, ri
, err
)
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()
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
) {
182 ts
:= httptest
.NewServer(StripPrefix("/test", FileServer(Dir("."))))
185 for _
, data
:= range fsRedirectTestData
{
186 res
, err
:= Get(ts
.URL
+ data
.original
)
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
) {
205 func TestFileServerCleans(t
*testing
.T
) {
207 ch
:= make(chan string, 1)
208 fs
:= FileServer(&testFileSystem
{func(name
string) (File
, error
) {
210 return nil, errors
.New("file does not exist")
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
) {
232 const dirListPrefix
= "<pre>\n"
233 const dirListSuffix
= "\n</pre>\n"
237 {`simple_name`, `<a href="simple_name">simple_name</a>`},
238 {`"'<>&`, `<a href="%22%27%3C%3E&">"'<>&</a>`},
239 {`?foo=bar#baz`, `<a href="%3Ffoo=bar%23baz">?foo=bar#baz</a>`},
240 {`<combo>?foo`, `<a href="%3Ccombo%3E%3Ffoo"><combo>?foo</a>`},
243 // We put each test file in its own directory in the fakeFS so we can look at it in isolation.
245 for i
, test
:= range tests
{
246 testFile
:= &fakeFileInfo
{basename
: test
.name
}
247 fs
[fmt
.Sprintf("/%d", i
)] = &fakeFileInfo
{
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
))
257 for i
, test
:= range tests
{
258 url
:= fmt
.Sprintf("%s/%d", ts
.URL
, i
)
261 t
.Fatalf("test %q: Get: %v", test
.name
, err
)
263 b
, err
:= ioutil
.ReadAll(res
.Body
)
265 t
.Fatalf("test %q: read Body: %v", test
.name
, err
)
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
)
278 func mustRemoveAll(dir
string) {
279 err
:= os
.RemoveAll(dir
)
285 func TestFileServerImplicitLeadingSlash(t
*testing
.T
) {
287 tempDir
, err
:= ioutil
.TempDir("", "")
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
))))
297 get
:= func(suffix
string) string {
298 res
, err
:= Get(ts
.URL
+ suffix
)
300 t
.Fatalf("Get %s: %v", suffix
, err
)
302 b
, err
:= ioutil
.ReadAll(res
.Body
)
304 t
.Fatalf("ReadAll %s: %v", suffix
, err
)
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")
323 t
.Skip("skipping test; no /etc/hosts file")
325 test
:= func(d Dir
, name
string) {
326 f
, err
:= d
.Open(name
)
328 t
.Fatalf("open of %s: %v", name
, err
)
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
) {
356 f
, err
:= d
.Open(name
)
358 t
.Fatalf("open of %s: %v", name
, err
)
367 func TestServeFileContentType(t
*testing
.T
) {
369 const ctype
= "icecream/chocolate"
370 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
371 switch r
.FormValue("override") {
373 w
.Header().Set("Content-Type", ctype
)
375 // Explicitly inhibit sniffing.
376 w
.Header()["Content-Type"] = []string{}
378 ServeFile(w
, r
, "testdata/file")
381 get
:= func(override
string, want
[]string) {
382 resp
, err
:= Get(ts
.URL
+ "?override=" + override
)
386 if h
:= resp
.Header
["Content-Type"]; !reflect
.DeepEqual(h
, want
) {
387 t
.Errorf("Content-Type mismatch: got %v, want %v", h
, want
)
391 get("0", []string{"text/plain; charset=utf-8"})
392 get("1", []string{ctype
})
396 func TestServeFileMimeType(t
*testing
.T
) {
398 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
399 ServeFile(w
, r
, "testdata/style.css")
402 resp
, err
:= Get(ts
.URL
)
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
) {
415 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
416 ServeFile(w
, r
, "fs_test.go")
419 r
, err
:= Get(ts
.URL
)
424 if r
.StatusCode
!= 200 {
425 t
.Fatalf("expected 200 OK, got %s", r
.Status
)
429 func TestServeFileWithContentEncoding(t
*testing
.T
) {
431 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
432 w
.Header().Set("Content-Encoding", "foo")
433 ServeFile(w
, r
, "testdata/file")
436 resp
, err
:= Get(ts
.URL
)
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
) {
448 const want
= "index.html says hello\n"
449 ts
:= httptest
.NewServer(FileServer(Dir(".")))
452 for _
, path
:= range []string{"/testdata/", "/testdata/index.html"} {
453 res
, err
:= Get(ts
.URL
+ path
)
457 b
, err
:= ioutil
.ReadAll(res
.Body
)
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
)
468 func TestFileServerZeroByte(t
*testing
.T
) {
470 ts
:= httptest
.NewServer(FileServer(Dir(".")))
473 res
, err
:= Get(ts
.URL
+ "/..\x00")
477 b
, err
:= ioutil
.ReadAll(res
.Body
)
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 {
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
{
501 return 0755 | os
.ModeDir
506 type fakeFile
struct {
509 path
string // as opened
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
) {
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 {
536 type fakeFS
map[string]*fakeFileInfo
538 func (fs fakeFS
) Open(name
string) (File
, error
) {
539 name
= path
.Clean(name
)
542 return nil, os
.ErrNotExist
544 return &fakeFile
{ReadSeeker
: strings
.NewReader(f
.contents
), fi
: f
, path
: name
}, nil
547 func TestDirectoryIfNotModified(t
*testing
.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",
556 contents
: indexContents
,
562 ents
: []*fakeFileInfo
{indexFile
},
564 "/index.html": indexFile
,
567 ts
:= httptest
.NewServer(FileServer(fs
))
570 res
, err
:= Get(ts
.URL
)
574 b
, err
:= ioutil
.ReadAll(res
.Body
)
578 if string(b
) != indexContents
{
579 t
.Fatalf("Got body %q; want %q", b
, indexContents
)
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
)
595 if res
.StatusCode
!= 304 {
596 t
.Fatalf("Code after If-Modified-Since request = %v; want 304", res
.StatusCode
)
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
)
607 if res
.StatusCode
!= 200 {
608 t
.Fatalf("Code after second If-Modified-Since request = %v; want 200; res is %#v", res
.StatusCode
, res
)
613 func mustStat(t
*testing
.T
, fileName
string) os
.FileInfo
{
614 fi
, err
:= os
.Stat(fileName
)
621 func TestServeContent(t
*testing
.T
) {
623 type serveParam
struct {
626 content io
.ReadSeeker
630 servec
:= make(chan serveParam
, 1)
631 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
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
)
643 type testCase
struct {
644 // One of file or content must be set:
646 content io
.ReadSeeker
649 serveETag
string // optional
650 serveContentType
string // optional
651 reqHeader
map[string]string
653 wantContentType
string
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",
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
),
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
),
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
),
687 "not_modified_etag": {
688 file
: "testdata/style.css",
690 reqHeader
: map[string]string{
691 "If-None-Match": `"foo"`,
695 "not_modified_etag_no_seek": {
696 content
: panicOnSeek
{nil}, // should never be called
698 reqHeader
: map[string]string{
699 "If-None-Match": `"foo"`,
704 file
: "testdata/style.css",
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.
715 file
: "testdata/style.css",
717 reqHeader
: map[string]string{
718 "Range": "bytes=0-4",
722 wantContentType
: "text/css; charset=utf-8",
725 for testName
, tt
:= range tests
{
726 var content io
.ReadSeeker
728 f
, err
:= os
.Open(tt
.file
)
730 t
.Fatalf("test %q: %v", testName
, err
)
738 servec
<- serveParam
{
739 name
: filepath
.Base(tt
.file
),
743 contentType
: tt
.serveContentType
,
745 req
, err
:= NewRequest("GET", ts
.URL
, nil)
749 for k
, v
:= range tt
.reqHeader
{
752 res
, err
:= DefaultClient
.Do(req
)
756 io
.Copy(ioutil
.Discard
, res
.Body
)
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
) {
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")
784 lnf
, err
:= ln
.(*net
.TCPListener
).File()
790 trace
:= "trace=sendfile"
791 if runtime
.GOARCH
!= "alpha" {
792 trace
= trace
+ ",sendfile64"
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()...)
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()))
807 t
.Fatalf("http client error: %v", err
)
809 _
, err
= io
.Copy(ioutil
.Discard
, res
.Body
)
811 t
.Fatalf("client body read error: %v", err
)
815 // Force child to exit cleanly.
816 Get(fmt
.Sprintf("http://%s/quit", ln
.Addr()))
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`)
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
)
830 t
.Fatalf("%s: for URL %q, send error: %v", testName
, req
.URL
.String(), err
)
832 b
, err
:= ioutil
.ReadAll(r
.Body
)
834 t
.Fatalf("%s: for URL %q, reading body: %v", testName
, req
.URL
.String(), err
)
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" {
846 fd3
:= os
.NewFile(3, "ephemeral-port-listener")
847 ln
, err
:= net
.FileListener(fd3
)
852 mux
.Handle("/", FileServer(Dir("testdata")))
853 mux
.HandleFunc("/quit", func(ResponseWriter
, *Request
) {
856 s
:= &Server
{Handler
: mux
}
863 type panicOnSeek
struct{ io
.ReadSeeker
}