diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2014-07-19 08:53:52 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2014-07-19 08:53:52 +0000 |
commit | 00d86ac99f5dd6afa5bbd7c38ffe1c585edd2387 (patch) | |
tree | b988e32ea14a3dc1b4718b1fdfa47bab087ae96c /libgo/go/net/http/serve_test.go | |
parent | bcf2fc6ee0a7edbe7de4299f28b66527c07bb0a2 (diff) |
libgo: Update to Go 1.3 release.
From-SVN: r212837
Diffstat (limited to 'libgo/go/net/http/serve_test.go')
-rw-r--r-- | libgo/go/net/http/serve_test.go | 370 |
1 files changed, 323 insertions, 47 deletions
diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index e7a3e6ea75f..8371dd82f58 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -851,7 +851,9 @@ func TestTLSHandshakeTimeout(t *testing.T) { } defer afterTest(t) ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) + errc := make(chanWriter, 10) // but only expecting 1 ts.Config.ReadTimeout = 250 * time.Millisecond + ts.Config.ErrorLog = log.New(errc, "", 0) ts.StartTLS() defer ts.Close() conn, err := net.Dial("tcp", ts.Listener.Addr().String()) @@ -866,6 +868,14 @@ func TestTLSHandshakeTimeout(t *testing.T) { t.Errorf("Read = %d, %v; want an error and no bytes", n, err) } }) + select { + case v := <-errc: + if !strings.Contains(v, "timeout") && !strings.Contains(v, "TLS handshake") { + t.Errorf("expected a TLS handshake timeout error; got %q", v) + } + case <-time.After(5 * time.Second): + t.Errorf("timeout waiting for logged error") + } } func TestTLSServer(t *testing.T) { @@ -878,6 +888,7 @@ func TestTLSServer(t *testing.T) { } } })) + ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) defer ts.Close() // Connect an idle TCP connection to this server before we run @@ -922,31 +933,50 @@ func TestTLSServer(t *testing.T) { } type serverExpectTest struct { - contentLength int // of request body + contentLength int // of request body + chunked bool expectation string // e.g. "100-continue" readBody bool // whether handler should read the body (if false, sends StatusUnauthorized) expectedResponse string // expected substring in first line of http response } +func expectTest(contentLength int, expectation string, readBody bool, expectedResponse string) serverExpectTest { + return serverExpectTest{ + contentLength: contentLength, + expectation: expectation, + readBody: readBody, + expectedResponse: expectedResponse, + } +} + var serverExpectTests = []serverExpectTest{ // Normal 100-continues, case-insensitive. - {100, "100-continue", true, "100 Continue"}, - {100, "100-cOntInUE", true, "100 Continue"}, + expectTest(100, "100-continue", true, "100 Continue"), + expectTest(100, "100-cOntInUE", true, "100 Continue"), // No 100-continue. - {100, "", true, "200 OK"}, + expectTest(100, "", true, "200 OK"), // 100-continue but requesting client to deny us, // so it never reads the body. - {100, "100-continue", false, "401 Unauthorized"}, + expectTest(100, "100-continue", false, "401 Unauthorized"), // Likewise without 100-continue: - {100, "", false, "401 Unauthorized"}, + expectTest(100, "", false, "401 Unauthorized"), // Non-standard expectations are failures - {0, "a-pony", false, "417 Expectation Failed"}, + expectTest(0, "a-pony", false, "417 Expectation Failed"), - // Expect-100 requested but no body - {0, "100-continue", true, "400 Bad Request"}, + // Expect-100 requested but no body (is apparently okay: Issue 7625) + expectTest(0, "100-continue", true, "200 OK"), + // Expect-100 requested but handler doesn't read the body + expectTest(0, "100-continue", false, "401 Unauthorized"), + // Expect-100 continue with no body, but a chunked body. + { + expectation: "100-continue", + readBody: true, + chunked: true, + expectedResponse: "100 Continue", + }, } // Tests that the server responds to the "Expect" request header @@ -975,21 +1005,38 @@ func TestServerExpect(t *testing.T) { // Only send the body immediately if we're acting like an HTTP client // that doesn't send 100-continue expectations. - writeBody := test.contentLength > 0 && strings.ToLower(test.expectation) != "100-continue" + writeBody := test.contentLength != 0 && strings.ToLower(test.expectation) != "100-continue" go func() { + contentLen := fmt.Sprintf("Content-Length: %d", test.contentLength) + if test.chunked { + contentLen = "Transfer-Encoding: chunked" + } _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+ "Connection: close\r\n"+ - "Content-Length: %d\r\n"+ + "%s\r\n"+ "Expect: %s\r\nHost: foo\r\n\r\n", - test.readBody, test.contentLength, test.expectation) + test.readBody, contentLen, test.expectation) if err != nil { t.Errorf("On test %#v, error writing request headers: %v", test, err) return } if writeBody { + var targ io.WriteCloser = struct { + io.Writer + io.Closer + }{ + conn, + ioutil.NopCloser(nil), + } + if test.chunked { + targ = httputil.NewChunkedWriter(conn) + } body := strings.Repeat("A", test.contentLength) - _, err = fmt.Fprint(conn, body) + _, err = fmt.Fprint(targ, body) + if err == nil { + err = targ.Close() + } if err != nil { if !test.readBody { // Server likely already hung up on us. @@ -2097,7 +2144,7 @@ func TestCodesPreventingContentTypeAndBody(t *testing.T) { got := ht.rawResponse(req) wantStatus := fmt.Sprintf("%d %s", code, StatusText(code)) if !strings.Contains(got, wantStatus) { - t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, req, got) + t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, wantStatus, req, got) } else if strings.Contains(got, "Content-Length") { t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got) } else if strings.Contains(got, "stuff") { @@ -2107,6 +2154,21 @@ func TestCodesPreventingContentTypeAndBody(t *testing.T) { } } +func TestContentTypeOkayOn204(t *testing.T) { + ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("Content-Length", "123") // suppressed + w.Header().Set("Content-Type", "foo/bar") + w.WriteHeader(204) + })) + got := ht.rawResponse("GET / HTTP/1.1") + if !strings.Contains(got, "Content-Type: foo/bar") { + t.Errorf("Response = %q; want Content-Type: foo/bar", got) + } + if strings.Contains(got, "Content-Length: 123") { + t.Errorf("Response = %q; don't want a Content-Length", got) + } +} + // Issue 6995 // A server Handler can receive a Request, and then turn around and // give a copy of that Request.Body out to the Transport (e.g. any @@ -2225,8 +2287,8 @@ func TestResponseWriterWriteStringAllocs(t *testing.T) { w.Write([]byte("Hello world")) } })) - before := testing.AllocsPerRun(25, func() { ht.rawResponse("GET / HTTP/1.0") }) - after := testing.AllocsPerRun(25, func() { ht.rawResponse("GET /s HTTP/1.0") }) + before := testing.AllocsPerRun(50, func() { ht.rawResponse("GET / HTTP/1.0") }) + after := testing.AllocsPerRun(50, func() { ht.rawResponse("GET /s HTTP/1.0") }) if int(after) >= int(before) { t.Errorf("WriteString allocs of %v >= Write allocs of %v", after, before) } @@ -2245,6 +2307,230 @@ func TestAppendTime(t *testing.T) { } } +func TestServerConnState(t *testing.T) { + defer afterTest(t) + handler := map[string]func(w ResponseWriter, r *Request){ + "/": func(w ResponseWriter, r *Request) { + fmt.Fprintf(w, "Hello.") + }, + "/close": func(w ResponseWriter, r *Request) { + w.Header().Set("Connection", "close") + fmt.Fprintf(w, "Hello.") + }, + "/hijack": func(w ResponseWriter, r *Request) { + c, _, _ := w.(Hijacker).Hijack() + c.Write([]byte("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello.")) + c.Close() + }, + "/hijack-panic": func(w ResponseWriter, r *Request) { + c, _, _ := w.(Hijacker).Hijack() + c.Write([]byte("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello.")) + c.Close() + panic("intentional panic") + }, + } + ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { + handler[r.URL.Path](w, r) + })) + defer ts.Close() + + var mu sync.Mutex // guard stateLog and connID + var stateLog = map[int][]ConnState{} + var connID = map[net.Conn]int{} + + ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) + ts.Config.ConnState = func(c net.Conn, state ConnState) { + if c == nil { + t.Errorf("nil conn seen in state %s", state) + return + } + mu.Lock() + defer mu.Unlock() + id, ok := connID[c] + if !ok { + id = len(connID) + 1 + connID[c] = id + } + stateLog[id] = append(stateLog[id], state) + } + ts.Start() + + mustGet(t, ts.URL+"/") + mustGet(t, ts.URL+"/close") + + mustGet(t, ts.URL+"/") + mustGet(t, ts.URL+"/", "Connection", "close") + + mustGet(t, ts.URL+"/hijack") + mustGet(t, ts.URL+"/hijack-panic") + + // New->Closed + { + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + c.Close() + } + + // New->Active->Closed + { + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + if _, err := io.WriteString(c, "BOGUS REQUEST\r\n\r\n"); err != nil { + t.Fatal(err) + } + c.Close() + } + + // New->Idle->Closed + { + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + if _, err := io.WriteString(c, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"); err != nil { + t.Fatal(err) + } + res, err := ReadResponse(bufio.NewReader(c), nil) + if err != nil { + t.Fatal(err) + } + if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { + t.Fatal(err) + } + c.Close() + } + + want := map[int][]ConnState{ + 1: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, + 2: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, + 3: []ConnState{StateNew, StateActive, StateHijacked}, + 4: []ConnState{StateNew, StateActive, StateHijacked}, + 5: []ConnState{StateNew, StateClosed}, + 6: []ConnState{StateNew, StateActive, StateClosed}, + 7: []ConnState{StateNew, StateActive, StateIdle, StateClosed}, + } + logString := func(m map[int][]ConnState) string { + var b bytes.Buffer + for id, l := range m { + fmt.Fprintf(&b, "Conn %d: ", id) + for _, s := range l { + fmt.Fprintf(&b, "%s ", s) + } + b.WriteString("\n") + } + return b.String() + } + + for i := 0; i < 5; i++ { + time.Sleep(time.Duration(i) * 50 * time.Millisecond) + mu.Lock() + match := reflect.DeepEqual(stateLog, want) + mu.Unlock() + if match { + return + } + } + + mu.Lock() + t.Errorf("Unexpected events.\nGot log: %s\n Want: %s\n", logString(stateLog), logString(want)) + mu.Unlock() +} + +func mustGet(t *testing.T, url string, headers ...string) { + req, err := NewRequest("GET", url, nil) + if err != nil { + t.Fatal(err) + } + for len(headers) > 0 { + req.Header.Add(headers[0], headers[1]) + headers = headers[2:] + } + res, err := DefaultClient.Do(req) + if err != nil { + t.Errorf("Error fetching %s: %v", url, err) + return + } + _, err = ioutil.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + t.Errorf("Error reading %s: %v", url, err) + } +} + +func TestServerKeepAlivesEnabled(t *testing.T) { + defer afterTest(t) + ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) + ts.Config.SetKeepAlivesEnabled(false) + ts.Start() + defer ts.Close() + res, err := Get(ts.URL) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + if !res.Close { + t.Errorf("Body.Close == false; want true") + } +} + +// golang.org/issue/7856 +func TestServerEmptyBodyRace(t *testing.T) { + defer afterTest(t) + var n int32 + ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + atomic.AddInt32(&n, 1) + })) + defer ts.Close() + var wg sync.WaitGroup + const reqs = 20 + for i := 0; i < reqs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + res, err := Get(ts.URL) + if err != nil { + t.Error(err) + return + } + defer res.Body.Close() + _, err = io.Copy(ioutil.Discard, res.Body) + if err != nil { + t.Error(err) + return + } + }() + } + wg.Wait() + if got := atomic.LoadInt32(&n); got != reqs { + t.Errorf("handler ran %d times; want %d", got, reqs) + } +} + +func TestServerConnStateNew(t *testing.T) { + sawNew := false // if the test is buggy, we'll race on this variable. + srv := &Server{ + ConnState: func(c net.Conn, state ConnState) { + if state == StateNew { + sawNew = true // testing that this write isn't racy + } + }, + Handler: HandlerFunc(func(w ResponseWriter, r *Request) {}), // irrelevant + } + srv.Serve(&oneConnListener{ + conn: &rwTestConn{ + Reader: strings.NewReader("GET / HTTP/1.1\r\nHost: foo\r\n\r\n"), + Writer: ioutil.Discard, + }, + }) + if !sawNew { // testing that this read isn't racy + t.Error("StateNew not seen") + } +} + func BenchmarkClientServer(b *testing.B) { b.ReportAllocs() b.StopTimer() @@ -2259,7 +2545,6 @@ func BenchmarkClientServer(b *testing.B) { if err != nil { b.Fatal("Get:", err) } - defer res.Body.Close() all, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { @@ -2282,42 +2567,33 @@ func BenchmarkClientServerParallel64(b *testing.B) { benchmarkClientServerParallel(b, 64) } -func benchmarkClientServerParallel(b *testing.B, conc int) { +func benchmarkClientServerParallel(b *testing.B, parallelism int) { b.ReportAllocs() - b.StopTimer() ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { fmt.Fprintf(rw, "Hello world.\n") })) defer ts.Close() - b.StartTimer() - - numProcs := runtime.GOMAXPROCS(-1) * conc - var wg sync.WaitGroup - wg.Add(numProcs) - n := int32(b.N) - for p := 0; p < numProcs; p++ { - go func() { - for atomic.AddInt32(&n, -1) >= 0 { - res, err := Get(ts.URL) - if err != nil { - b.Logf("Get: %v", err) - continue - } - all, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - b.Logf("ReadAll: %v", err) - continue - } - body := string(all) - if body != "Hello world.\n" { - panic("Got body: " + body) - } + b.ResetTimer() + b.SetParallelism(parallelism) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + res, err := Get(ts.URL) + if err != nil { + b.Logf("Get: %v", err) + continue } - wg.Done() - }() - } - wg.Wait() + all, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + b.Logf("ReadAll: %v", err) + continue + } + body := string(all) + if body != "Hello world.\n" { + panic("Got body: " + body) + } + } + }) } // A benchmark for profiling the server without the HTTP client code. |