Commit b690d7e5 authored by Emmanuel T Odeke's avatar Emmanuel T Odeke Committed by Emmanuel Odeke

net/http/httputil: make TestDumpRequest idempotent

TestDumpRequest was failing with -count=2 or more
because for test cases that involved mustReadRequest,
the body was created as a *bufio.Reader. DumpRequest
and DumpRequestOut would then read the body until EOF
and would close it after use.
However, on re-runs of the test, the body would
be terminally exhausted and result in an unexpected
error "http: invalid Read on closed Body".

The update to the test cases adds an extra field "GetReq"
which allows us to construct requests per run of the tests
and hence make the test indefinitely re-runnable/idempotent.
"Req" or "GetReq" are mutually exclusive: either one of them
can be set or nil, but not both.

Fixes #26858

Change-Id: Ice3083dac1aa3249da4afc7075cd984eb159530d
Reviewed-on: https://go-review.googlesource.com/c/153377
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent af8f4062
...@@ -18,7 +18,10 @@ import ( ...@@ -18,7 +18,10 @@ import (
) )
type dumpTest struct { type dumpTest struct {
Req http.Request // Either Req or GetReq can be set/nil but not both.
Req *http.Request
GetReq func() *http.Request
Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
WantDump string WantDump string
...@@ -29,7 +32,7 @@ type dumpTest struct { ...@@ -29,7 +32,7 @@ type dumpTest struct {
var dumpTests = []dumpTest{ var dumpTests = []dumpTest{
// HTTP/1.1 => chunked coding; body; empty trailer // HTTP/1.1 => chunked coding; body; empty trailer
{ {
Req: http.Request{ Req: &http.Request{
Method: "GET", Method: "GET",
URL: &url.URL{ URL: &url.URL{
Scheme: "http", Scheme: "http",
...@@ -52,7 +55,7 @@ var dumpTests = []dumpTest{ ...@@ -52,7 +55,7 @@ var dumpTests = []dumpTest{
// Verify that DumpRequest preserves the HTTP version number, doesn't add a Host, // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
// and doesn't add a User-Agent. // and doesn't add a User-Agent.
{ {
Req: http.Request{ Req: &http.Request{
Method: "GET", Method: "GET",
URL: mustParseURL("/foo"), URL: mustParseURL("/foo"),
ProtoMajor: 1, ProtoMajor: 1,
...@@ -67,7 +70,7 @@ var dumpTests = []dumpTest{ ...@@ -67,7 +70,7 @@ var dumpTests = []dumpTest{
}, },
{ {
Req: *mustNewRequest("GET", "http://example.com/foo", nil), Req: mustNewRequest("GET", "http://example.com/foo", nil),
WantDumpOut: "GET /foo HTTP/1.1\r\n" + WantDumpOut: "GET /foo HTTP/1.1\r\n" +
"Host: example.com\r\n" + "Host: example.com\r\n" +
...@@ -79,8 +82,7 @@ var dumpTests = []dumpTest{ ...@@ -79,8 +82,7 @@ var dumpTests = []dumpTest{
// with a bytes.Buffer and hang with all goroutines not // with a bytes.Buffer and hang with all goroutines not
// runnable. // runnable.
{ {
Req: *mustNewRequest("GET", "https://example.com/foo", nil), Req: mustNewRequest("GET", "https://example.com/foo", nil),
WantDumpOut: "GET /foo HTTP/1.1\r\n" + WantDumpOut: "GET /foo HTTP/1.1\r\n" +
"Host: example.com\r\n" + "Host: example.com\r\n" +
"User-Agent: Go-http-client/1.1\r\n" + "User-Agent: Go-http-client/1.1\r\n" +
...@@ -89,7 +91,7 @@ var dumpTests = []dumpTest{ ...@@ -89,7 +91,7 @@ var dumpTests = []dumpTest{
// Request with Body, but Dump requested without it. // Request with Body, but Dump requested without it.
{ {
Req: http.Request{ Req: &http.Request{
Method: "POST", Method: "POST",
URL: &url.URL{ URL: &url.URL{
Scheme: "http", Scheme: "http",
...@@ -114,7 +116,7 @@ var dumpTests = []dumpTest{ ...@@ -114,7 +116,7 @@ var dumpTests = []dumpTest{
// Request with Body > 8196 (default buffer size) // Request with Body > 8196 (default buffer size)
{ {
Req: http.Request{ Req: &http.Request{
Method: "POST", Method: "POST",
URL: &url.URL{ URL: &url.URL{
Scheme: "http", Scheme: "http",
...@@ -145,8 +147,10 @@ var dumpTests = []dumpTest{ ...@@ -145,8 +147,10 @@ var dumpTests = []dumpTest{
}, },
{ {
Req: *mustReadRequest("GET http://foo.com/ HTTP/1.1\r\n" + GetReq: func() *http.Request {
"User-Agent: blah\r\n\r\n"), return mustReadRequest("GET http://foo.com/ HTTP/1.1\r\n" +
"User-Agent: blah\r\n\r\n")
},
NoBody: true, NoBody: true,
WantDump: "GET http://foo.com/ HTTP/1.1\r\n" + WantDump: "GET http://foo.com/ HTTP/1.1\r\n" +
"User-Agent: blah\r\n\r\n", "User-Agent: blah\r\n\r\n",
...@@ -154,22 +158,25 @@ var dumpTests = []dumpTest{ ...@@ -154,22 +158,25 @@ var dumpTests = []dumpTest{
// Issue #7215. DumpRequest should return the "Content-Length" when set // Issue #7215. DumpRequest should return the "Content-Length" when set
{ {
Req: *mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" + GetReq: func() *http.Request {
"Host: passport.myhost.com\r\n" + return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
"Content-Length: 3\r\n" + "Host: passport.myhost.com\r\n" +
"\r\nkey1=name1&key2=name2"), "Content-Length: 3\r\n" +
"\r\nkey1=name1&key2=name2")
},
WantDump: "POST /v2/api/?login HTTP/1.1\r\n" + WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
"Host: passport.myhost.com\r\n" + "Host: passport.myhost.com\r\n" +
"Content-Length: 3\r\n" + "Content-Length: 3\r\n" +
"\r\nkey", "\r\nkey",
}, },
// Issue #7215. DumpRequest should return the "Content-Length" in ReadRequest // Issue #7215. DumpRequest should return the "Content-Length" in ReadRequest
{ {
Req: *mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" + GetReq: func() *http.Request {
"Host: passport.myhost.com\r\n" + return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
"Content-Length: 0\r\n" + "Host: passport.myhost.com\r\n" +
"\r\nkey1=name1&key2=name2"), "Content-Length: 0\r\n" +
"\r\nkey1=name1&key2=name2")
},
WantDump: "POST /v2/api/?login HTTP/1.1\r\n" + WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
"Host: passport.myhost.com\r\n" + "Host: passport.myhost.com\r\n" +
"Content-Length: 0\r\n\r\n", "Content-Length: 0\r\n\r\n",
...@@ -177,9 +184,11 @@ var dumpTests = []dumpTest{ ...@@ -177,9 +184,11 @@ var dumpTests = []dumpTest{
// Issue #7215. DumpRequest should not return the "Content-Length" if unset // Issue #7215. DumpRequest should not return the "Content-Length" if unset
{ {
Req: *mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" + GetReq: func() *http.Request {
"Host: passport.myhost.com\r\n" + return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
"\r\nkey1=name1&key2=name2"), "Host: passport.myhost.com\r\n" +
"\r\nkey1=name1&key2=name2")
},
WantDump: "POST /v2/api/?login HTTP/1.1\r\n" + WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
"Host: passport.myhost.com\r\n\r\n", "Host: passport.myhost.com\r\n\r\n",
}, },
...@@ -187,8 +196,7 @@ var dumpTests = []dumpTest{ ...@@ -187,8 +196,7 @@ var dumpTests = []dumpTest{
// Issue 18506: make drainBody recognize NoBody. Otherwise // Issue 18506: make drainBody recognize NoBody. Otherwise
// this was turning into a chunked request. // this was turning into a chunked request.
{ {
Req: *mustNewRequest("POST", "http://example.com/foo", http.NoBody), Req: mustNewRequest("POST", "http://example.com/foo", http.NoBody),
WantDumpOut: "POST /foo HTTP/1.1\r\n" + WantDumpOut: "POST /foo HTTP/1.1\r\n" +
"Host: example.com\r\n" + "Host: example.com\r\n" +
"User-Agent: Go-http-client/1.1\r\n" + "User-Agent: Go-http-client/1.1\r\n" +
...@@ -200,28 +208,40 @@ var dumpTests = []dumpTest{ ...@@ -200,28 +208,40 @@ var dumpTests = []dumpTest{
func TestDumpRequest(t *testing.T) { func TestDumpRequest(t *testing.T) {
numg0 := runtime.NumGoroutine() numg0 := runtime.NumGoroutine()
for i, tt := range dumpTests { for i, tt := range dumpTests {
setBody := func() { if tt.Req != nil && tt.GetReq != nil || tt.Req == nil && tt.GetReq == nil {
if tt.Body == nil { t.Errorf("#%d: either .Req(%p) or .GetReq(%p) can be set/nil but not both", i, tt.Req, tt.GetReq)
return continue
}
freshReq := func(ti dumpTest) *http.Request {
req := ti.Req
if req == nil {
req = ti.GetReq()
} }
switch b := tt.Body.(type) {
if req.Header == nil {
req.Header = make(http.Header)
}
if ti.Body == nil {
return req
}
switch b := ti.Body.(type) {
case []byte: case []byte:
tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b)) req.Body = ioutil.NopCloser(bytes.NewReader(b))
case func() io.ReadCloser: case func() io.ReadCloser:
tt.Req.Body = b() req.Body = b()
default: default:
t.Fatalf("Test %d: unsupported Body of %T", i, tt.Body) t.Fatalf("Test %d: unsupported Body of %T", i, ti.Body)
} }
} return req
if tt.Req.Header == nil {
tt.Req.Header = make(http.Header)
} }
if tt.WantDump != "" { if tt.WantDump != "" {
setBody() req := freshReq(tt)
dump, err := DumpRequest(&tt.Req, !tt.NoBody) dump, err := DumpRequest(req, !tt.NoBody)
if err != nil { if err != nil {
t.Errorf("DumpRequest #%d: %s", i, err) t.Errorf("DumpRequest #%d: %s\nWantDump:\n%s", i, err, tt.WantDump)
continue continue
} }
if string(dump) != tt.WantDump { if string(dump) != tt.WantDump {
...@@ -231,8 +251,8 @@ func TestDumpRequest(t *testing.T) { ...@@ -231,8 +251,8 @@ func TestDumpRequest(t *testing.T) {
} }
if tt.WantDumpOut != "" { if tt.WantDumpOut != "" {
setBody() req := freshReq(tt)
dump, err := DumpRequestOut(&tt.Req, !tt.NoBody) dump, err := DumpRequestOut(req, !tt.NoBody)
if err != nil { if err != nil {
t.Errorf("DumpRequestOut #%d: %s", i, err) t.Errorf("DumpRequestOut #%d: %s", i, err)
continue continue
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment