diff --git a/http2/clientconn_test.go b/http2/clientconn_test.go index 36f080b9b..6c83e0a78 100644 --- a/http2/clientconn_test.go +++ b/http2/clientconn_test.go @@ -13,10 +13,8 @@ import ( "fmt" "io" "net/http" - "os" "reflect" "runtime" - "slices" "sync/atomic" "testing" "time" @@ -62,6 +60,7 @@ func TestTestClientConn(t *testing.T) { streamID: rt.streamID(), endStream: true, size: 10, + multiple: true, }) // tc.writeHeaders sends a HEADERS frame back to the client. @@ -97,6 +96,7 @@ type testClientConn struct { fr *Framer cc *ClientConn group *synctestGroup + testConnFramer encbuf bytes.Buffer enc *hpack.Encoder @@ -115,6 +115,7 @@ func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientCo } cli, srv := synctestNetPipe(tc.group) srv.SetReadDeadline(tc.group.Now()) + srv.autoWait = true tc.netconn = srv tc.enc = hpack.NewEncoder(&tc.encbuf) @@ -123,8 +124,12 @@ func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientCo // cli is the ClientConn's side, srv is the side controlled by the test. cc.tconn = cli tc.fr = NewFramer(srv, srv) + tc.testConnFramer = testConnFramer{ + t: t, + fr: tc.fr, + dec: hpack.NewDecoder(initialHeaderTableSize, nil), + } - tc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil) tc.fr.SetMaxReadFrameSize(10 << 20) t.Cleanup(func() { tc.closeWrite() @@ -174,169 +179,15 @@ func (tc *testClientConn) hasFrame() bool { return len(tc.netconn.Peek()) > 0 } +// isClosed reports whether the peer has closed the connection. func (tc *testClientConn) isClosed() bool { return tc.netconn.IsClosedByPeer() } -// readFrame reads the next frame from the conn. -func (tc *testClientConn) readFrame() Frame { - tc.t.Helper() - tc.sync() - fr, err := tc.fr.ReadFrame() - if err == io.EOF || err == os.ErrDeadlineExceeded { - return nil - } - if err != nil { - tc.t.Fatalf("ReadFrame: %v", err) - } - return fr -} - -// testClientConnReadFrame reads a frame of a specific type from the conn. -func testClientConnReadFrame[T any](tc *testClientConn) T { - tc.t.Helper() - var v T - fr := tc.readFrame() - if fr == nil { - tc.t.Fatalf("got no frame, want frame %T", v) - } - v, ok := fr.(T) - if !ok { - tc.t.Fatalf("got frame %T, want %T", fr, v) - } - return v -} - -// wantFrameType reads the next frame from the conn. -// It produces an error if the frame type is not the expected value. -func (tc *testClientConn) wantFrameType(want FrameType) { - tc.t.Helper() - fr := tc.readFrame() - if fr == nil { - tc.t.Fatalf("got no frame, want frame %v", want) - } - if got := fr.Header().Type; got != want { - tc.t.Fatalf("got frame %v, want %v", got, want) - } -} - -// wantUnorderedFrames reads frames from the conn until every condition in want has been satisfied. -// -// want is a list of func(*SomeFrame) bool. -// wantUnorderedFrames will call each func with frames of the appropriate type -// until the func returns true. -// It calls t.Fatal if an unexpected frame is received (no func has that frame type, -// or all funcs with that type have returned true), or if the conn runs out of frames -// with unsatisfied funcs. -// -// Example: -// -// // Read a SETTINGS frame, and any number of DATA frames for a stream. -// // The SETTINGS frame may appear anywhere in the sequence. -// // The last DATA frame must indicate the end of the stream. -// tc.wantUnorderedFrames( -// func(f *SettingsFrame) bool { -// return true -// }, -// func(f *DataFrame) bool { -// return f.StreamEnded() -// }, -// ) -func (tc *testClientConn) wantUnorderedFrames(want ...any) { - tc.t.Helper() - want = slices.Clone(want) - seen := 0 -frame: - for seen < len(want) && !tc.t.Failed() { - fr := tc.readFrame() - if fr == nil { - break - } - for i, f := range want { - if f == nil { - continue - } - typ := reflect.TypeOf(f) - if typ.Kind() != reflect.Func || - typ.NumIn() != 1 || - typ.NumOut() != 1 || - typ.Out(0) != reflect.TypeOf(true) { - tc.t.Fatalf("expected func(*SomeFrame) bool, got %T", f) - } - if typ.In(0) == reflect.TypeOf(fr) { - out := reflect.ValueOf(f).Call([]reflect.Value{reflect.ValueOf(fr)}) - if out[0].Bool() { - want[i] = nil - seen++ - } - continue frame - } - } - tc.t.Errorf("got unexpected frame type %T", fr) - } - if seen < len(want) { - for _, f := range want { - if f == nil { - continue - } - tc.t.Errorf("did not see expected frame: %v", reflect.TypeOf(f).In(0)) - } - tc.t.Fatalf("did not see %v expected frame types", len(want)-seen) - } -} - -type wantHeader struct { - streamID uint32 - endStream bool - header http.Header -} - -// wantHeaders reads a HEADERS frame and potential CONTINUATION frames, -// and asserts that they contain the expected headers. -func (tc *testClientConn) wantHeaders(want wantHeader) { - tc.t.Helper() - got := testClientConnReadFrame[*MetaHeadersFrame](tc) - if got, want := got.StreamID, want.streamID; got != want { - tc.t.Fatalf("got stream ID %v, want %v", got, want) - } - if got, want := got.StreamEnded(), want.endStream; got != want { - tc.t.Fatalf("got stream ended %v, want %v", got, want) - } - gotHeader := make(http.Header) - for _, f := range got.Fields { - gotHeader[f.Name] = append(gotHeader[f.Name], f.Value) - } - for k, v := range want.header { - if !reflect.DeepEqual(v, gotHeader[k]) { - tc.t.Fatalf("got header %q = %q; want %q", k, v, gotHeader[k]) - } - } -} - -type wantData struct { - streamID uint32 - endStream bool - size int -} - -// wantData reads zero or more DATA frames, and asserts that they match the expectation. -func (tc *testClientConn) wantData(want wantData) { - tc.t.Helper() - gotSize := 0 - gotEndStream := false - for tc.hasFrame() && !gotEndStream { - data := testClientConnReadFrame[*DataFrame](tc) - gotSize += len(data.Data()) - if data.StreamEnded() { - gotEndStream = true - } - } - if gotSize != want.size { - tc.t.Fatalf("got %v bytes of DATA frames, want %v", gotSize, want.size) - } - if gotEndStream != want.endStream { - tc.t.Fatalf("after %v bytes of DATA frames, got END_STREAM=%v; want %v", gotSize, gotEndStream, want.endStream) - } +// closeWrite causes the net.Conn used by the ClientConn to return a error +// from Read calls. +func (tc *testClientConn) closeWrite() { + tc.netconn.Close() } // testRequestBody is a Request.Body for use in tests. @@ -468,38 +319,6 @@ func (tc *testClientConn) greet(settings ...Setting) { tc.wantFrameType(FrameSettings) // acknowledgement } -func (tc *testClientConn) writeSettings(settings ...Setting) { - tc.t.Helper() - if err := tc.fr.WriteSettings(settings...); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeSettingsAck() { - tc.t.Helper() - if err := tc.fr.WriteSettingsAck(); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeData(streamID uint32, endStream bool, data []byte) { - tc.t.Helper() - if err := tc.fr.WriteData(streamID, endStream, data); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) { - tc.t.Helper() - if err := tc.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - // makeHeaderBlockFragment encodes headers in a form suitable for inclusion // in a HEADERS or CONTINUATION frame. // @@ -515,87 +334,6 @@ func (tc *testClientConn) makeHeaderBlockFragment(s ...string) []byte { return tc.encbuf.Bytes() } -func (tc *testClientConn) writeHeaders(p HeadersFrameParam) { - tc.t.Helper() - if err := tc.fr.WriteHeaders(p); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -// writeHeadersMode writes header frames, as modified by mode: -// -// - noHeader: Don't write the header. -// - oneHeader: Write a single HEADERS frame. -// - splitHeader: Write a HEADERS frame and CONTINUATION frame. -func (tc *testClientConn) writeHeadersMode(mode headerType, p HeadersFrameParam) { - tc.t.Helper() - switch mode { - case noHeader: - case oneHeader: - tc.writeHeaders(p) - case splitHeader: - if len(p.BlockFragment) < 2 { - panic("too small") - } - contData := p.BlockFragment[1:] - contEnd := p.EndHeaders - p.BlockFragment = p.BlockFragment[:1] - p.EndHeaders = false - tc.writeHeaders(p) - tc.writeContinuation(p.StreamID, contEnd, contData) - default: - panic("bogus mode") - } -} - -func (tc *testClientConn) writeContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) { - tc.t.Helper() - if err := tc.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeRSTStream(streamID uint32, code ErrCode) { - tc.t.Helper() - if err := tc.fr.WriteRSTStream(streamID, code); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writePing(ack bool, data [8]byte) { - tc.t.Helper() - if err := tc.fr.WritePing(ack, data); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeGoAway(maxStreamID uint32, code ErrCode, debugData []byte) { - tc.t.Helper() - if err := tc.fr.WriteGoAway(maxStreamID, code, debugData); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeWindowUpdate(streamID, incr uint32) { - tc.t.Helper() - if err := tc.fr.WriteWindowUpdate(streamID, incr); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -// closeWrite causes the net.Conn used by the ClientConn to return a error -// from Read calls. -func (tc *testClientConn) closeWrite() { - tc.netconn.Close() - tc.sync() -} - // inflowWindow returns the amount of inbound flow control available for a stream, // or for the connection if streamID is 0. func (tc *testClientConn) inflowWindow(streamID uint32) int32 { diff --git a/http2/connframes_test.go b/http2/connframes_test.go new file mode 100644 index 000000000..7db8b74e2 --- /dev/null +++ b/http2/connframes_test.go @@ -0,0 +1,414 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "bytes" + "context" + "io" + "net/http" + "os" + "reflect" + "slices" + "testing" + + "golang.org/x/net/http2/hpack" +) + +type testConnFramer struct { + t testing.TB + fr *Framer + dec *hpack.Decoder +} + +// readFrame reads the next frame. +// It returns nil if the conn is closed or no frames are available. +func (tf *testConnFramer) readFrame() Frame { + tf.t.Helper() + fr, err := tf.fr.ReadFrame() + if err == io.EOF || err == os.ErrDeadlineExceeded { + return nil + } + if err != nil { + tf.t.Fatalf("ReadFrame: %v", err) + } + return fr +} + +type readFramer interface { + readFrame() Frame +} + +// readFrame reads a frame of a specific type. +func readFrame[T any](t testing.TB, framer readFramer) T { + t.Helper() + var v T + fr := framer.readFrame() + if fr == nil { + t.Fatalf("got no frame, want frame %T", v) + } + v, ok := fr.(T) + if !ok { + t.Fatalf("got frame %T, want %T", fr, v) + } + return v +} + +// wantFrameType reads the next frame. +// It produces an error if the frame type is not the expected value. +func (tf *testConnFramer) wantFrameType(want FrameType) { + tf.t.Helper() + fr := tf.readFrame() + if fr == nil { + tf.t.Fatalf("got no frame, want frame %v", want) + } + if got := fr.Header().Type; got != want { + tf.t.Fatalf("got frame %v, want %v", got, want) + } +} + +// wantUnorderedFrames reads frames until every condition in want has been satisfied. +// +// want is a list of func(*SomeFrame) bool. +// wantUnorderedFrames will call each func with frames of the appropriate type +// until the func returns true. +// It calls t.Fatal if an unexpected frame is received (no func has that frame type, +// or all funcs with that type have returned true), or if the framer runs out of frames +// with unsatisfied funcs. +// +// Example: +// +// // Read a SETTINGS frame, and any number of DATA frames for a stream. +// // The SETTINGS frame may appear anywhere in the sequence. +// // The last DATA frame must indicate the end of the stream. +// tf.wantUnorderedFrames( +// func(f *SettingsFrame) bool { +// return true +// }, +// func(f *DataFrame) bool { +// return f.StreamEnded() +// }, +// ) +func (tf *testConnFramer) wantUnorderedFrames(want ...any) { + tf.t.Helper() + want = slices.Clone(want) + seen := 0 +frame: + for seen < len(want) && !tf.t.Failed() { + fr := tf.readFrame() + if fr == nil { + break + } + for i, f := range want { + if f == nil { + continue + } + typ := reflect.TypeOf(f) + if typ.Kind() != reflect.Func || + typ.NumIn() != 1 || + typ.NumOut() != 1 || + typ.Out(0) != reflect.TypeOf(true) { + tf.t.Fatalf("expected func(*SomeFrame) bool, got %T", f) + } + if typ.In(0) == reflect.TypeOf(fr) { + out := reflect.ValueOf(f).Call([]reflect.Value{reflect.ValueOf(fr)}) + if out[0].Bool() { + want[i] = nil + seen++ + } + continue frame + } + } + tf.t.Errorf("got unexpected frame type %T", fr) + } + if seen < len(want) { + for _, f := range want { + if f == nil { + continue + } + tf.t.Errorf("did not see expected frame: %v", reflect.TypeOf(f).In(0)) + } + tf.t.Fatalf("did not see %v expected frame types", len(want)-seen) + } +} + +type wantHeader struct { + streamID uint32 + endStream bool + header http.Header +} + +// wantHeaders reads a HEADERS frame and potential CONTINUATION frames, +// and asserts that they contain the expected headers. +func (tf *testConnFramer) wantHeaders(want wantHeader) { + tf.t.Helper() + + hf := readFrame[*HeadersFrame](tf.t, tf) + if got, want := hf.StreamID, want.streamID; got != want { + tf.t.Fatalf("got stream ID %v, want %v", got, want) + } + if got, want := hf.StreamEnded(), want.endStream; got != want { + tf.t.Fatalf("got stream ended %v, want %v", got, want) + } + + gotHeader := make(http.Header) + tf.dec.SetEmitFunc(func(hf hpack.HeaderField) { + gotHeader[hf.Name] = append(gotHeader[hf.Name], hf.Value) + }) + defer tf.dec.SetEmitFunc(nil) + if _, err := tf.dec.Write(hf.HeaderBlockFragment()); err != nil { + tf.t.Fatalf("decoding HEADERS frame: %v", err) + } + headersEnded := hf.HeadersEnded() + for !headersEnded { + cf := readFrame[*ContinuationFrame](tf.t, tf) + if cf == nil { + tf.t.Fatalf("got end of frames, want CONTINUATION") + } + if _, err := tf.dec.Write(cf.HeaderBlockFragment()); err != nil { + tf.t.Fatalf("decoding CONTINUATION frame: %v", err) + } + headersEnded = cf.HeadersEnded() + } + if err := tf.dec.Close(); err != nil { + tf.t.Fatalf("hpack decoding error: %v", err) + } + + for k, v := range want.header { + if !reflect.DeepEqual(v, gotHeader[k]) { + tf.t.Fatalf("got header %q = %q; want %q", k, v, gotHeader[k]) + } + } +} + +// decodeHeader supports some older server tests. +// TODO: rewrite those tests to use newer, more convenient test APIs. +func (tf *testConnFramer) decodeHeader(headerBlock []byte) (pairs [][2]string) { + tf.dec.SetEmitFunc(func(hf hpack.HeaderField) { + if hf.Name == "date" { + return + } + pairs = append(pairs, [2]string{hf.Name, hf.Value}) + }) + defer tf.dec.SetEmitFunc(nil) + if _, err := tf.dec.Write(headerBlock); err != nil { + tf.t.Fatalf("hpack decoding error: %v", err) + } + if err := tf.dec.Close(); err != nil { + tf.t.Fatalf("hpack decoding error: %v", err) + } + return pairs +} + +type wantData struct { + streamID uint32 + endStream bool + size int + data []byte + multiple bool // data may be spread across multiple DATA frames +} + +// wantData reads zero or more DATA frames, and asserts that they match the expectation. +func (tf *testConnFramer) wantData(want wantData) { + tf.t.Helper() + gotSize := 0 + gotEndStream := false + if want.data != nil { + want.size = len(want.data) + } + var gotData []byte + for { + fr := tf.readFrame() + if fr == nil { + break + } + data, ok := fr.(*DataFrame) + if !ok { + tf.t.Fatalf("got frame %T, want DataFrame", fr) + } + if want.data != nil { + gotData = append(gotData, data.Data()...) + } + gotSize += len(data.Data()) + if data.StreamEnded() { + gotEndStream = true + break + } + if !want.endStream && gotSize >= want.size { + break + } + if !want.multiple { + break + } + } + if gotSize != want.size { + tf.t.Fatalf("got %v bytes of DATA frames, want %v", gotSize, want.size) + } + if gotEndStream != want.endStream { + tf.t.Fatalf("after %v bytes of DATA frames, got END_STREAM=%v; want %v", gotSize, gotEndStream, want.endStream) + } + if want.data != nil && !bytes.Equal(gotData, want.data) { + tf.t.Fatalf("got data %q, want %q", gotData, want.data) + } +} + +func (tf *testConnFramer) wantRSTStream(streamID uint32, code ErrCode) { + tf.t.Helper() + fr := readFrame[*RSTStreamFrame](tf.t, tf) + if fr.StreamID != streamID || fr.ErrCode != code { + tf.t.Fatalf("got %v, want RST_STREAM StreamID=%v, code=%v", summarizeFrame(fr), streamID, code) + } +} + +func (tf *testConnFramer) wantSettingsAck() { + tf.t.Helper() + fr := readFrame[*SettingsFrame](tf.t, tf) + if !fr.Header().Flags.Has(FlagSettingsAck) { + tf.t.Fatal("Settings Frame didn't have ACK set") + } +} + +func (tf *testConnFramer) wantGoAway(maxStreamID uint32, code ErrCode) { + tf.t.Helper() + fr := readFrame[*GoAwayFrame](tf.t, tf) + if fr.LastStreamID != maxStreamID || fr.ErrCode != code { + tf.t.Fatalf("got %v, want GOAWAY LastStreamID=%v, code=%v", summarizeFrame(fr), maxStreamID, code) + } +} + +func (tf *testConnFramer) wantWindowUpdate(streamID, incr uint32) { + tf.t.Helper() + wu := readFrame[*WindowUpdateFrame](tf.t, tf) + if wu.FrameHeader.StreamID != streamID { + tf.t.Fatalf("WindowUpdate StreamID = %d; want %d", wu.FrameHeader.StreamID, streamID) + } + if wu.Increment != incr { + tf.t.Fatalf("WindowUpdate increment = %d; want %d", wu.Increment, incr) + } +} + +func (tf *testConnFramer) wantClosed() { + tf.t.Helper() + fr, err := tf.fr.ReadFrame() + if err == nil { + tf.t.Fatalf("got unexpected frame (want closed connection): %v", fr) + } + if err == context.DeadlineExceeded { + tf.t.Fatalf("connection is not closed; want it to be") + } +} + +func (tf *testConnFramer) wantIdle() { + tf.t.Helper() + fr, err := tf.fr.ReadFrame() + if err == nil { + tf.t.Fatalf("got unexpected frame (want idle connection): %v", fr) + } + if err != context.DeadlineExceeded { + tf.t.Fatalf("got unexpected frame error (want idle connection): %v", err) + } +} + +func (tf *testConnFramer) writeSettings(settings ...Setting) { + tf.t.Helper() + if err := tf.fr.WriteSettings(settings...); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeSettingsAck() { + tf.t.Helper() + if err := tf.fr.WriteSettingsAck(); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeData(streamID uint32, endStream bool, data []byte) { + tf.t.Helper() + if err := tf.fr.WriteData(streamID, endStream, data); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) { + tf.t.Helper() + if err := tf.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeHeaders(p HeadersFrameParam) { + tf.t.Helper() + if err := tf.fr.WriteHeaders(p); err != nil { + tf.t.Fatal(err) + } +} + +// writeHeadersMode writes header frames, as modified by mode: +// +// - noHeader: Don't write the header. +// - oneHeader: Write a single HEADERS frame. +// - splitHeader: Write a HEADERS frame and CONTINUATION frame. +func (tf *testConnFramer) writeHeadersMode(mode headerType, p HeadersFrameParam) { + tf.t.Helper() + switch mode { + case noHeader: + case oneHeader: + tf.writeHeaders(p) + case splitHeader: + if len(p.BlockFragment) < 2 { + panic("too small") + } + contData := p.BlockFragment[1:] + contEnd := p.EndHeaders + p.BlockFragment = p.BlockFragment[:1] + p.EndHeaders = false + tf.writeHeaders(p) + tf.writeContinuation(p.StreamID, contEnd, contData) + default: + panic("bogus mode") + } +} + +func (tf *testConnFramer) writeContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) { + tf.t.Helper() + if err := tf.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writePriority(id uint32, p PriorityParam) { + if err := tf.fr.WritePriority(id, p); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeRSTStream(streamID uint32, code ErrCode) { + tf.t.Helper() + if err := tf.fr.WriteRSTStream(streamID, code); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writePing(ack bool, data [8]byte) { + tf.t.Helper() + if err := tf.fr.WritePing(ack, data); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeGoAway(maxStreamID uint32, code ErrCode, debugData []byte) { + tf.t.Helper() + if err := tf.fr.WriteGoAway(maxStreamID, code, debugData); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeWindowUpdate(streamID, incr uint32) { + tf.t.Helper() + if err := tf.fr.WriteWindowUpdate(streamID, incr); err != nil { + tf.t.Fatal(err) + } +} diff --git a/http2/server_push_test.go b/http2/server_push_test.go index 97b00e85a..69e4c3b12 100644 --- a/http2/server_push_test.go +++ b/http2/server_push_test.go @@ -218,12 +218,12 @@ func TestServer_Push_Success(t *testing.T) { consumed := map[uint32]int{} for k := 0; len(expected) > 0; k++ { - f, err := st.readFrame() - if err != nil { + f := st.readFrame() + if f == nil { for id, left := range expected { t.Errorf("stream %d: missing %d frames", id, len(left)) } - t.Fatalf("readFrame %d: %v", k, err) + break } id := f.Header().StreamID label := fmt.Sprintf("stream %d, frame %d", id, consumed[id]) @@ -339,10 +339,10 @@ func testServer_Push_RejectSingleRequest(t *testing.T, doPush func(http.Pusher, t.Error(err) } // Should not get a PUSH_PROMISE frame. - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Error("stream should end after headers") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) } func TestServer_Push_RejectIfDisabled(t *testing.T) { @@ -459,7 +459,7 @@ func TestServer_Push_StateTransitions(t *testing.T) { } getSlash(st) // After the PUSH_PROMISE is sent, the stream should be stateHalfClosedRemote. - st.wantPushPromise() + _ = readFrame[*PushPromiseFrame](t, st) if got, want := st.streamState(2), stateHalfClosedRemote; got != want { t.Fatalf("streamState(2)=%v, want %v", got, want) } @@ -468,10 +468,10 @@ func TestServer_Push_StateTransitions(t *testing.T) { // the stream before we check st.streamState(2) -- should that happen, we'll // see stateClosed and fail the above check. close(gotPromise) - st.wantHeaders() - if df := st.wantData(); !df.StreamEnded() { - t.Fatal("expected END_STREAM flag on DATA") - } + st.wantHeaders(wantHeader{ + streamID: 2, + endStream: false, + }) if got, want := st.streamState(2), stateClosed; got != want { t.Fatalf("streamState(2)=%v, want %v", got, want) } @@ -554,9 +554,9 @@ func TestServer_Push_Underflow(t *testing.T) { numPushPromises := 0 numHeaders := 0 for numHeaders < numRequests*2 || numPushPromises < numRequests { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) + f := st.readFrame() + if f == nil { + st.t.Fatal("conn is idle, want frame") } switch f := f.(type) { case *HeadersFrame: diff --git a/http2/server_test.go b/http2/server_test.go index 506c51643..daa752eb9 100644 --- a/http2/server_test.go +++ b/http2/server_test.go @@ -65,18 +65,16 @@ func (sb *safeBuffer) Len() int { } type serverTester struct { - cc net.Conn // client conn - t testing.TB - group *synctestGroup - h1server *http.Server - h2server *Server - fr *Framer - serverLogBuf safeBuffer // logger for httptest.Server - logFilter []string // substrings to filter out - scMu sync.Mutex // guards sc - sc *serverConn - hpackDec *hpack.Decoder - decodedHeaders [][2]string + cc net.Conn // client conn + t testing.TB + group *synctestGroup + h1server *http.Server + h2server *Server + serverLogBuf safeBuffer // logger for httptest.Server + logFilter []string // substrings to filter out + scMu sync.Mutex // guards sc + sc *serverConn + testConnFramer // If http2debug!=2, then we capture Frame debug logs that will be written // to t.Log after a test fails. The read and write logs use separate locks @@ -187,7 +185,6 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} h2server: h2server, } st.hpackEnc = hpack.NewEncoder(&st.headerBuf) - st.hpackDec = hpack.NewDecoder(initialHeaderTableSize, st.onHeaderField) if h1server.ErrorLog == nil { h1server.ErrorLog = log.New(io.MultiWriter(stderrv(), twriter{t: t, st: st}, &st.serverLogBuf), "", log.LstdFlags) } @@ -212,6 +209,11 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} st.sc = <-connc st.fr = NewFramer(st.cc, st.cc) + st.testConnFramer = testConnFramer{ + t: t, + fr: NewFramer(st.cc, st.cc), + dec: hpack.NewDecoder(initialHeaderTableSize, nil), + } g.Wait() return st } @@ -276,7 +278,6 @@ func newServerTesterWithRealConn(t testing.TB, handler http.HandlerFunc, opts .. t: t, } st.hpackEnc = hpack.NewEncoder(&st.headerBuf) - st.hpackDec = hpack.NewDecoder(initialHeaderTableSize, st.onHeaderField) ts.TLS = ts.Config.TLSConfig // the httptest.Server has its own copy of this TLS config if ts.Config.ErrorLog == nil { @@ -298,7 +299,11 @@ func newServerTesterWithRealConn(t testing.TB, handler http.HandlerFunc, opts .. t.Fatal(err) } st.cc = cc - st.fr = NewFramer(cc, cc) + st.testConnFramer = testConnFramer{ + t: t, + fr: NewFramer(st.cc, st.cc), + dec: hpack.NewDecoder(initialHeaderTableSize, nil), + } if framerReuseFrames { st.fr.SetReuseFrames() } @@ -419,13 +424,16 @@ func (st *serverTester) Close() { // greet initiates the client's HTTP/2 connection into a state where // frames may be sent. func (st *serverTester) greet() { + st.t.Helper() st.greetAndCheckSettings(func(Setting) error { return nil }) } func (st *serverTester) greetAndCheckSettings(checkSetting func(s Setting) error) { + st.t.Helper() st.writePreface() - st.writeInitialSettings() - st.wantSettings().ForeachSetting(checkSetting) + st.writeSettings() + st.sync() + readFrame[*SettingsFrame](st.t, st).ForeachSetting(checkSetting) st.writeSettingsAck() // The initial WINDOW_UPDATE and SETTINGS ACK can come in any order. @@ -433,9 +441,9 @@ func (st *serverTester) greetAndCheckSettings(checkSetting func(s Setting) error var gotWindowUpdate bool for i := 0; i < 2; i++ { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) + f := st.readFrame() + if f == nil { + st.t.Fatal("wanted a settings ACK and window update, got none") } switch f := f.(type) { case *SettingsFrame: @@ -477,34 +485,6 @@ func (st *serverTester) writePreface() { } } -func (st *serverTester) writeInitialSettings() { - if err := st.fr.WriteSettings(); err != nil { - if runtime.GOOS == "openbsd" && strings.HasSuffix(err.Error(), "write: broken pipe") { - st.t.Logf("Error writing initial SETTINGS frame from client to server: %v", err) - st.t.Skipf("Skipping test with known OpenBSD failure mode. (See https://go.dev/issue/52208.)") - } - st.t.Fatalf("Error writing initial SETTINGS frame from client to server: %v", err) - } -} - -func (st *serverTester) writeSettingsAck() { - if err := st.fr.WriteSettingsAck(); err != nil { - st.t.Fatalf("Error writing ACK of server's SETTINGS: %v", err) - } -} - -func (st *serverTester) writeHeaders(p HeadersFrameParam) { - if err := st.fr.WriteHeaders(p); err != nil { - st.t.Fatalf("Error writing HEADERS: %v", err) - } -} - -func (st *serverTester) writePriority(id uint32, p PriorityParam) { - if err := st.fr.WritePriority(id, p); err != nil { - st.t.Fatalf("Error writing PRIORITY: %v", err) - } -} - func (st *serverTester) encodeHeaderField(k, v string) { err := st.hpackEnc.WriteField(hpack.HeaderField{Name: k, Value: v}) if err != nil { @@ -603,144 +583,6 @@ func (st *serverTester) bodylessReq1(headers ...string) { }) } -func (st *serverTester) writeData(streamID uint32, endStream bool, data []byte) { - if err := st.fr.WriteData(streamID, endStream, data); err != nil { - st.t.Fatalf("Error writing DATA: %v", err) - } -} - -func (st *serverTester) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) { - if err := st.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil { - st.t.Fatalf("Error writing DATA: %v", err) - } -} - -// writeReadPing sends a PING and immediately reads the PING ACK. -// It will fail if any other unread data was pending on the connection. -func (st *serverTester) writeReadPing() { - data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} - if err := st.fr.WritePing(false, data); err != nil { - st.t.Fatalf("Error writing PING: %v", err) - } - p := st.wantPing() - if p.Flags&FlagPingAck == 0 { - st.t.Fatalf("got a PING, want a PING ACK") - } - if p.Data != data { - st.t.Fatalf("got PING data = %x, want %x", p.Data, data) - } -} - -func (st *serverTester) readFrame() (Frame, error) { - return st.fr.ReadFrame() -} - -func (st *serverTester) wantHeaders() *HeadersFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a HEADERS frame: %v", err) - } - hf, ok := f.(*HeadersFrame) - if !ok { - st.t.Fatalf("got a %T; want *HeadersFrame", f) - } - return hf -} - -func (st *serverTester) wantContinuation() *ContinuationFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a CONTINUATION frame: %v", err) - } - cf, ok := f.(*ContinuationFrame) - if !ok { - st.t.Fatalf("got a %T; want *ContinuationFrame", f) - } - return cf -} - -func (st *serverTester) wantData() *DataFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a DATA frame: %v", err) - } - df, ok := f.(*DataFrame) - if !ok { - st.t.Fatalf("got a %T; want *DataFrame", f) - } - return df -} - -func (st *serverTester) wantSettings() *SettingsFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a SETTINGS frame: %v", err) - } - sf, ok := f.(*SettingsFrame) - if !ok { - st.t.Fatalf("got a %T; want *SettingsFrame", f) - } - return sf -} - -func (st *serverTester) wantPing() *PingFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a PING frame: %v", err) - } - pf, ok := f.(*PingFrame) - if !ok { - st.t.Fatalf("got a %T; want *PingFrame", f) - } - return pf -} - -func (st *serverTester) wantGoAway() *GoAwayFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a GOAWAY frame: %v", err) - } - gf, ok := f.(*GoAwayFrame) - if !ok { - st.t.Fatalf("got a %T; want *GoAwayFrame", f) - } - return gf -} - -func (st *serverTester) wantRSTStream(streamID uint32, errCode ErrCode) { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting an RSTStream frame: %v", err) - } - rs, ok := f.(*RSTStreamFrame) - if !ok { - st.t.Fatalf("got a %T; want *RSTStreamFrame", f) - } - if rs.FrameHeader.StreamID != streamID { - st.t.Fatalf("RSTStream StreamID = %d; want %d", rs.FrameHeader.StreamID, streamID) - } - if rs.ErrCode != errCode { - st.t.Fatalf("RSTStream ErrCode = %d (%s); want %d (%s)", rs.ErrCode, rs.ErrCode, errCode, errCode) - } -} - -func (st *serverTester) wantWindowUpdate(streamID, incr uint32) { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a WINDOW_UPDATE frame: %v", err) - } - wu, ok := f.(*WindowUpdateFrame) - if !ok { - st.t.Fatalf("got a %T; want *WindowUpdateFrame", f) - } - if wu.FrameHeader.StreamID != streamID { - st.t.Fatalf("WindowUpdate StreamID = %d; want %d", wu.FrameHeader.StreamID, streamID) - } - if wu.Increment != incr { - st.t.Fatalf("WindowUpdate increment = %d; want %d", wu.Increment, incr) - } -} - func (st *serverTester) wantFlowControlConsumed(streamID, consumed int32) { var initial int32 if streamID == 0 { @@ -763,32 +605,6 @@ func (st *serverTester) wantFlowControlConsumed(streamID, consumed int32) { <-donec } -func (st *serverTester) wantSettingsAck() { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } - sf, ok := f.(*SettingsFrame) - if !ok { - st.t.Fatalf("Wanting a settings ACK, received a %T", f) - } - if !sf.Header().Flags.Has(FlagSettingsAck) { - st.t.Fatal("Settings Frame didn't have ACK set") - } -} - -func (st *serverTester) wantPushPromise() *PushPromiseFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } - ppf, ok := f.(*PushPromiseFrame) - if !ok { - st.t.Fatalf("Wanted PushPromise, received %T", ppf) - } - return ppf -} - func TestServer(t *testing.T) { gotReq := make(chan bool, 1) st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { @@ -1218,37 +1034,32 @@ func testRejectRequest(t *testing.T, send func(*serverTester)) { st.wantRSTStream(1, ErrCodeProtocol) } -func testRejectRequestWithProtocolError(t *testing.T, send func(*serverTester)) { +func newServerTesterForError(t *testing.T) *serverTester { + t.Helper() st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { t.Error("server request made it to handler; should've been rejected") }, optQuiet) - defer st.Close() - st.greet() - send(st) - gf := st.wantGoAway() - if gf.ErrCode != ErrCodeProtocol { - t.Errorf("err code = %v; want %v", gf.ErrCode, ErrCodeProtocol) - } + return st } // Section 5.1, on idle connections: "Receiving any frame other than // HEADERS or PRIORITY on a stream in this state MUST be treated as a // connection error (Section 5.4.1) of type PROTOCOL_ERROR." func TestRejectFrameOnIdle_WindowUpdate(t *testing.T) { - testRejectRequestWithProtocolError(t, func(st *serverTester) { - st.fr.WriteWindowUpdate(123, 456) - }) + st := newServerTesterForError(t) + st.fr.WriteWindowUpdate(123, 456) + st.wantGoAway(123, ErrCodeProtocol) } func TestRejectFrameOnIdle_Data(t *testing.T) { - testRejectRequestWithProtocolError(t, func(st *serverTester) { - st.fr.WriteData(123, true, nil) - }) + st := newServerTesterForError(t) + st.fr.WriteData(123, true, nil) + st.wantGoAway(123, ErrCodeProtocol) } func TestRejectFrameOnIdle_RSTStream(t *testing.T) { - testRejectRequestWithProtocolError(t, func(st *serverTester) { - st.fr.WriteRSTStream(123, ErrCodeCancel) - }) + st := newServerTesterForError(t) + st.fr.WriteRSTStream(123, ErrCodeCancel) + st.wantGoAway(123, ErrCodeProtocol) } func TestServer_Request_Connect(t *testing.T) { @@ -1322,7 +1133,7 @@ func TestServer_Ping(t *testing.T) { t.Fatal(err) } - pf := st.wantPing() + pf := readFrame[*PingFrame](t, st) if !pf.Flags.Has(FlagPingAck) { t.Error("response ping doesn't have ACK set") } @@ -1366,11 +1177,15 @@ func TestServer_MaxQueuedControlFrames(t *testing.T) { // Unblock the server. // It should have closed the connection after exceeding the control frame limit. st.cc.(*synctestNetConn).SetReadBufferSize(math.MaxInt) - fr, err := st.readFrame() - if err != nil { - return + + st.advance(goAwayTimeout) + // Some frames may have persisted in the server's buffers. + for i := 0; i < 10; i++ { + if st.readFrame() == nil { + break + } } - t.Errorf("unexpected frame after exceeding maxQueuedControlFrames; want closed conn\n%v", fr) + st.wantClosed() } func TestServer_RejectsLargeFrames(t *testing.T) { @@ -1386,15 +1201,9 @@ func TestServer_RejectsLargeFrames(t *testing.T) { // will only read the first 9 bytes (the headre) and then disconnect. st.fr.WriteRawFrame(0xff, 0, 0, make([]byte, defaultMaxReadFrameSize+1)) - gf := st.wantGoAway() - if gf.ErrCode != ErrCodeFrameSize { - t.Errorf("GOAWAY err = %v; want %v", gf.ErrCode, ErrCodeFrameSize) - } - if st.serverLogBuf.Len() != 0 { - // Previously we spun here for a bit until the GOAWAY disconnect - // timer fired, logging while we fired. - t.Errorf("unexpected server output: %.500s\n", st.serverLogBuf.Bytes()) - } + st.wantGoAway(0, ErrCodeFrameSize) + st.advance(goAwayTimeout) + st.wantClosed() } func TestServer_Handler_Sends_WindowUpdate(t *testing.T) { @@ -1420,7 +1229,6 @@ func TestServer_Handler_Sends_WindowUpdate(t *testing.T) { EndStream: false, // data coming EndHeaders: true, }) - st.writeReadPing() // Write less than half the max window of data and consume it. // The server doesn't return flow control yet, buffering the 1024 bytes to @@ -1428,20 +1236,17 @@ func TestServer_Handler_Sends_WindowUpdate(t *testing.T) { data := make([]byte, windowSize) st.writeData(1, false, data[:1024]) puppet.do(readBodyHandler(t, string(data[:1024]))) - st.writeReadPing() // Write up to the window limit. // The server returns the buffered credit. st.writeData(1, false, data[1024:]) st.wantWindowUpdate(0, 1024) st.wantWindowUpdate(1, 1024) - st.writeReadPing() // The handler consumes the data and the server returns credit. puppet.do(readBodyHandler(t, string(data[1024:]))) st.wantWindowUpdate(0, windowSize-1024) st.wantWindowUpdate(1, windowSize-1024) - st.writeReadPing() } // the version of the TestServer_Handler_Sends_WindowUpdate with padding. @@ -1465,7 +1270,6 @@ func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) { EndStream: false, EndHeaders: true, }) - st.writeReadPing() // Write half a window of data, with some padding. // The server doesn't return the padding yet, buffering the 5 bytes to combine @@ -1473,7 +1277,6 @@ func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) { data := make([]byte, windowSize/2) pad := make([]byte, 4) st.writeDataPadded(1, false, data, pad) - st.writeReadPing() // The handler consumes the body. // The server returns flow control for the body and padding @@ -1490,13 +1293,7 @@ func TestServer_Send_GoAway_After_Bogus_WindowUpdate(t *testing.T) { if err := st.fr.WriteWindowUpdate(0, 1<<31-1); err != nil { t.Fatal(err) } - gf := st.wantGoAway() - if gf.ErrCode != ErrCodeFlowControl { - t.Errorf("GOAWAY err = %v; want %v", gf.ErrCode, ErrCodeFlowControl) - } - if gf.LastStreamID != 0 { - t.Errorf("GOAWAY last stream ID = %v; want %v", gf.LastStreamID, 0) - } + st.wantGoAway(0, ErrCodeFlowControl) } func TestServer_Send_RstStream_After_Bogus_WindowUpdate(t *testing.T) { @@ -1703,10 +1500,10 @@ func TestServer_StateTransitions(t *testing.T) { st.writeData(1, true, nil) leaveHandler <- true - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Fatal("expected END_STREAM flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) if got, want := st.streamState(1), stateClosed; got != want { t.Errorf("at end, state is %v; want %v", got, want) @@ -1718,97 +1515,101 @@ func TestServer_StateTransitions(t *testing.T) { // test HEADERS w/o EndHeaders + another HEADERS (should get rejected) func TestServer_Rejects_HeadersNoEnd_Then_Headers(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: false, - }) - st.writeHeaders(HeadersFrameParam{ // Not a continuation. - StreamID: 3, // different stream. - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: true, - }) + st := newServerTesterForError(t) + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: false, + }) + st.writeHeaders(HeadersFrameParam{ // Not a continuation. + StreamID: 3, // different stream. + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, }) + st.wantGoAway(0, ErrCodeProtocol) } // test HEADERS w/o EndHeaders + PING (should get rejected) func TestServer_Rejects_HeadersNoEnd_Then_Ping(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: false, - }) - if err := st.fr.WritePing(false, [8]byte{}); err != nil { - t.Fatal(err) - } + st := newServerTesterForError(t) + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: false, }) + if err := st.fr.WritePing(false, [8]byte{}); err != nil { + t.Fatal(err) + } + st.wantGoAway(0, ErrCodeProtocol) } // test HEADERS w/ EndHeaders + a continuation HEADERS (should get rejected) func TestServer_Rejects_HeadersEnd_Then_Continuation(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: true, - }) - st.wantHeaders() - if err := st.fr.WriteContinuation(1, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { - t.Fatal(err) - } + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, optQuiet) + st.greet() + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, }) + if err := st.fr.WriteContinuation(1, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { + t.Fatal(err) + } + st.wantGoAway(1, ErrCodeProtocol) } // test HEADERS w/o EndHeaders + a continuation HEADERS on wrong stream ID func TestServer_Rejects_HeadersNoEnd_Then_ContinuationWrongStream(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: false, - }) - if err := st.fr.WriteContinuation(3, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { - t.Fatal(err) - } + st := newServerTesterForError(t) + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: false, }) + if err := st.fr.WriteContinuation(3, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { + t.Fatal(err) + } + st.wantGoAway(0, ErrCodeProtocol) } // No HEADERS on stream 0. func TestServer_Rejects_Headers0(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.fr.AllowIllegalWrites = true - st.writeHeaders(HeadersFrameParam{ - StreamID: 0, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: true, - }) + st := newServerTesterForError(t) + st.fr.AllowIllegalWrites = true + st.writeHeaders(HeadersFrameParam{ + StreamID: 0, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, }) + st.wantGoAway(0, ErrCodeProtocol) } // No CONTINUATION on stream 0. func TestServer_Rejects_Continuation0(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.fr.AllowIllegalWrites = true - if err := st.fr.WriteContinuation(0, true, st.encodeHeader()); err != nil { - t.Fatal(err) - } - }) + st := newServerTesterForError(t) + st.fr.AllowIllegalWrites = true + if err := st.fr.WriteContinuation(0, true, st.encodeHeader()); err != nil { + t.Fatal(err) + } + st.wantGoAway(0, ErrCodeProtocol) } // No PRIORITY on stream 0. func TestServer_Rejects_Priority0(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.fr.AllowIllegalWrites = true - st.writePriority(0, PriorityParam{StreamDep: 1}) - }) + st := newServerTesterForError(t) + st.fr.AllowIllegalWrites = true + st.writePriority(0, PriorityParam{StreamDep: 1}) + st.wantGoAway(0, ErrCodeProtocol) } // No HEADERS frame with a self-dependence. @@ -1834,37 +1635,15 @@ func TestServer_Rejects_PrioritySelfDependence(t *testing.T) { } func TestServer_Rejects_PushPromise(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - pp := PushPromiseParam{ - StreamID: 1, - PromiseID: 3, - } - if err := st.fr.WritePushPromise(pp); err != nil { - t.Fatal(err) - } - }) -} - -// testServerRejectsConn tests that the server hangs up with a GOAWAY -// frame and a server close after the client does something -// deserving a CONNECTION_ERROR. -func testServerRejectsConn(t *testing.T, writeReq func(*serverTester)) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}) - st.addLogFilter("connection error: PROTOCOL_ERROR") - defer st.Close() - st.greet() - writeReq(st) - - st.wantGoAway() - st.advance(goAwayTimeout) - - fr, err := st.fr.ReadFrame() - if err == nil { - t.Errorf("ReadFrame got frame of type %T; want io.EOF", fr) + st := newServerTesterForError(t) + pp := PushPromiseParam{ + StreamID: 1, + PromiseID: 3, } - if err != io.EOF { - t.Errorf("ReadFrame = %v; want io.EOF", err) + if err := st.fr.WritePushPromise(pp); err != nil { + t.Fatal(err) } + st.wantGoAway(1, ErrCodeProtocol) } // testServerRejectsStream tests that the server sends a RST_STREAM with the provided @@ -1904,13 +1683,10 @@ func TestServer_Response_NoData(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Fatal("want END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) }) } @@ -1920,22 +1696,15 @@ func TestServer_Response_NoData_Header_FooBar(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Fatal("want END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"foo-bar", "some-value"}, - {"content-length", "0"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "foo-bar": []string{"some-value"}, + "content-length": []string{"0"}, + }, + }) }) } @@ -1980,15 +1749,14 @@ func TestServerIgnoresContentLengthSignWhenWritingChunks(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-length", tt.wantCL}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("For case %q, value %q, got = %q; want %q", tt.name, tt.cl, goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "content-length": []string{tt.wantCL}, + }, + }) }) } } @@ -2058,29 +1826,20 @@ func TestServer_Response_Data_Sniff_DoesntOverride(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("don't want END_STREAM, expecting data") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "foo/bar"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } - df := st.wantData() - if !df.StreamEnded() { - t.Error("expected DATA to have END_STREAM flag") - } - if got := string(df.Data()); got != msg { - t.Errorf("got DATA %q; want %q", got, msg) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"foo/bar"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(msg), + }) }) } @@ -2092,16 +1851,15 @@ func TestServer_Response_TransferEncoding_chunked(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) }) } @@ -2114,22 +1872,15 @@ func TestServer_Response_Data_IgnoreHeaderAfterWrite_After(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) }) } @@ -2143,23 +1894,16 @@ func TestServer_Response_Data_IgnoreHeaderAfterWrite_Overwrite(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"foo", "proper value"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "foo": []string{"proper value"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) }) } @@ -2170,29 +1914,20 @@ func TestServer_Response_Data_SniffLenType(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("don't want END_STREAM, expecting data") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } - df := st.wantData() - if !df.StreamEnded() { - t.Error("expected DATA to have END_STREAM flag") - } - if got := string(df.Data()); got != msg { - t.Errorf("got DATA %q; want %q", got, msg) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(msg), + }) }) } @@ -2206,40 +1941,25 @@ func TestServer_Response_Header_Flush_MidWrite(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/html; charset=utf-8"}, // sniffed - // and no content-length - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } - { - df := st.wantData() - if df.StreamEnded() { - t.Error("unexpected END_STREAM flag") - } - if got := string(df.Data()); got != msg { - t.Errorf("got DATA %q; want %q", got, msg) - } - } - { - df := st.wantData() - if !df.StreamEnded() { - t.Error("wanted END_STREAM flag on last data chunk") - } - if got := string(df.Data()); got != msg2 { - t.Errorf("got DATA %q; want %q", got, msg2) - } - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/html; charset=utf-8"}, // sniffed + // and no content-length + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: false, + data: []byte(msg), + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(msg2), + }) }) } @@ -2275,25 +1995,18 @@ func TestServer_Response_LargeWrite(t *testing.T) { if err := st.fr.WriteWindowUpdate(0, size); err != nil { t.Fatal(err) } - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, // sniffed - // and no content-length - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, // sniffed + // and no content-length + }, + }) var bytes, frames int for { - df := st.wantData() + df := readFrame[*DataFrame](t, st) bytes += len(df.Data()) frames++ for _, b := range df.Data() { @@ -2344,27 +2057,26 @@ func TestServer_Response_LargeWrite_FlowControlled(t *testing.T) { getSlash(st) // make the single request - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) - df := st.wantData() - if got := len(df.Data()); got != reads[0] { - t.Fatalf("Initial window size = %d but got DATA with %d bytes", reads[0], got) - } + st.wantData(wantData{ + streamID: 1, + endStream: false, + size: reads[0], + }) - for _, quota := range reads[1:] { + for i, quota := range reads[1:] { if err := st.fr.WriteWindowUpdate(1, uint32(quota)); err != nil { t.Fatal(err) } - df := st.wantData() - if int(quota) != len(df.Data()) { - t.Fatalf("read %d bytes after giving %d quota", len(df.Data()), quota) - } + st.wantData(wantData{ + streamID: 1, + endStream: i == len(reads[1:])-1, + size: quota, + }) } }) } @@ -2391,13 +2103,10 @@ func TestServer_Response_RST_Unblocks_LargeWrite(t *testing.T) { getSlash(st) // make the single request - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil { t.Fatal(err) @@ -2419,21 +2128,16 @@ func TestServer_Response_Empty_Data_Not_FlowControlled(t *testing.T) { getSlash(st) // make the single request - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) - df := st.wantData() - if got := len(df.Data()); got != 0 { - t.Fatalf("unexpected %d DATA bytes; want 0", got) - } - if !df.StreamEnded() { - t.Fatal("DATA didn't have END_STREAM") - } + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) }) } @@ -2458,49 +2162,33 @@ func TestServer_Response_Automatic100Continue(t *testing.T) { EndStream: false, EndHeaders: true, }) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "100"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Fatalf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"100"}, + }, + }) // Okay, they sent status 100, so we can send our // gigantic and/or sensitive "foo" payload now. st.writeData(1, true, []byte(msg)) - hf = st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("expected data to follow") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", strconv.Itoa(len(reply))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(reply))}, + }, + }) - df := st.wantData() - if string(df.Data()) != reply { - t.Errorf("Client read %q; want %q", df.Data(), reply) - } - if !df.StreamEnded() { - t.Errorf("expect data stream end") - } + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(reply), + }) }) } @@ -2522,13 +2210,10 @@ func TestServer_HandlerWriteErrorOnDisconnect(t *testing.T) { EndStream: false, EndHeaders: true, }) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) // Close the connection and wait for the handler to (hopefully) notice. st.cc.Close() _ = <-errc @@ -2549,6 +2234,11 @@ func TestServer_Rejects_Too_Many_Streams(t *testing.T) { <-leaveHandler }) defer st.Close() + + // Automatically syncing after every write / before every read + // slows this test down substantially. + st.cc.(*synctestNetConn).autoWait = false + st.greet() nextStreamID := uint32(1) streamID := func() uint32 { @@ -2588,11 +2278,16 @@ func TestServer_Rejects_Too_Many_Streams(t *testing.T) { if err := st.fr.WriteContinuation(rejectID, true, frag2); err != nil { t.Fatal(err) } + st.sync() st.wantRSTStream(rejectID, ErrCodeProtocol) // But let a handler finish: leaveHandler <- true - st.wantHeaders() + st.sync() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) // And now another stream should be able to start: goodID := streamID() @@ -2612,14 +2307,14 @@ func TestServer_Response_ManyHeaders_With_Continuation(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() + hf := readFrame[*HeadersFrame](t, st) if hf.HeadersEnded() { t.Fatal("got unwanted END_HEADERS flag") } n := 0 for { n++ - cf := st.wantContinuation() + cf := readFrame[*ContinuationFrame](t, st) if cf.HeadersEnded() { break } @@ -2648,10 +2343,10 @@ func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) { EndStream: false, // DATA is coming EndHeaders: true, }) - hf := st.wantHeaders() - if !hf.HeadersEnded() || !hf.StreamEnded() { - t.Fatalf("want END_HEADERS+END_STREAM, got %v", hf) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) // Sent when the a Handler closes while a client has // indicated it's still sending DATA: @@ -2714,10 +2409,7 @@ func testRejectTLS(t *testing.T, version uint16) { state.Version = version }) defer st.Close() - gf := st.wantGoAway() - if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want { - t.Errorf("Got error code %v; want %v", got, want) - } + st.wantGoAway(0, ErrCodeInadequateSecurity) } func TestServer_Rejects_TLSBadCipher(t *testing.T) { @@ -2726,10 +2418,7 @@ func TestServer_Rejects_TLSBadCipher(t *testing.T) { state.CipherSuite = tls.TLS_RSA_WITH_RC4_128_SHA }) defer st.Close() - gf := st.wantGoAway() - if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want { - t.Errorf("Got error code %v; want %v", got, want) - } + st.wantGoAway(0, ErrCodeInadequateSecurity) } func TestServer_Advertises_Common_Cipher(t *testing.T) { @@ -2759,24 +2448,6 @@ func TestServer_Advertises_Common_Cipher(t *testing.T) { res.Body.Close() } -func (st *serverTester) onHeaderField(f hpack.HeaderField) { - if f.Name == "date" { - return - } - st.decodedHeaders = append(st.decodedHeaders, [2]string{f.Name, f.Value}) -} - -func (st *serverTester) decodeHeader(headerBlock []byte) (pairs [][2]string) { - st.decodedHeaders = nil - if _, err := st.hpackDec.Write(headerBlock); err != nil { - st.t.Fatalf("hpack decoding error: %v", err) - } - if err := st.hpackDec.Close(); err != nil { - st.t.Fatalf("hpack decoding error: %v", err) - } - return st.decodedHeaders -} - // testServerResponse sets up an idle HTTP/2 connection. The client function should // write a single request that must be handled by the handler. func testServerResponse(t testing.TB, @@ -2918,19 +2589,15 @@ func TestServerDoS_MaxHeaderListSize(t *testing.T) { st.fr.WriteContinuation(1, len(b) == 0, chunk) } - h := st.wantHeaders() - if !h.HeadersEnded() { - t.Fatalf("Got HEADERS without END_HEADERS set: %v", h) - } - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "431"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", "63"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"431"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{"63"}, + }, + }) } func TestServer_Response_Stream_With_Missing_Trailer(t *testing.T) { @@ -2939,17 +2606,15 @@ func TestServer_Response_Stream_With_Missing_Trailer(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - df := st.wantData() - if len(df.data) != 0 { - t.Fatal("did not want data") - } - if !df.StreamEnded() { - t.Fatal("want END_STREAM flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) }) } @@ -2987,20 +2652,16 @@ func TestCompressionErrorOnWrite(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - if !h.HeadersEnded() { - t.Fatalf("Got HEADERS without END_HEADERS set: %v", h) - } - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "431"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", "63"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } - df := st.wantData() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"431"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{"63"}, + }, + }) + df := readFrame[*DataFrame](t, st) if !strings.Contains(string(df.Data()), "HTTP Error 431") { t.Errorf("Unexpected data body: %q", df.Data()) } @@ -3016,10 +2677,7 @@ func TestCompressionErrorOnWrite(t *testing.T) { EndStream: true, EndHeaders: true, }) - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeCompression { - t.Errorf("GOAWAY err = %v; want ErrCodeCompression", ga.ErrCode) - } + st.wantGoAway(3, ErrCodeCompression) } func TestCompressionErrorOnClose(t *testing.T) { @@ -3038,10 +2696,7 @@ func TestCompressionErrorOnClose(t *testing.T) { EndStream: true, EndHeaders: true, }) - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeCompression { - t.Errorf("GOAWAY err = %v; want ErrCodeCompression", ga.ErrCode) - } + st.wantGoAway(1, ErrCodeCompression) } // test that a server handler can read trailers from a client @@ -3131,50 +2786,36 @@ func testServerWritesTrailers(t *testing.T, withFlush bool) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("response HEADERS had END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("response HEADERS didn't have END_HEADERS") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"foo", "Bar"}, - {"trailer", "Server-Trailer-A, Server-Trailer-B"}, - {"trailer", "Server-Trailer-C"}, - {"trailer", "Transfer-Encoding, Content-Length, Trailer"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", "5"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Header mismatch.\n got: %v\nwant: %v", goth, wanth) - } - df := st.wantData() - if string(df.Data()) != "Hello" { - t.Fatalf("Client read %q; want Hello", df.Data()) - } - if df.StreamEnded() { - t.Fatalf("data frame had STREAM_ENDED") - } - tf := st.wantHeaders() // for the trailers - if !tf.StreamEnded() { - t.Fatalf("trailers HEADERS lacked END_STREAM") - } - if !tf.HeadersEnded() { - t.Fatalf("trailers HEADERS lacked END_HEADERS") - } - wanth = [][2]string{ - {"post-header-trailer", "hi1"}, - {"post-header-trailer2", "hi2"}, - {"server-trailer-a", "valuea"}, - {"server-trailer-c", "valuec"}, - } - goth = st.decodeHeader(tf.HeaderBlockFragment()) - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Header mismatch.\n got: %v\nwant: %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "foo": []string{"Bar"}, + "trailer": []string{ + "Server-Trailer-A, Server-Trailer-B", + "Server-Trailer-C", + "Transfer-Encoding, Content-Length, Trailer", + }, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{"5"}, + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: false, + data: []byte("Hello"), + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + "post-header-trailer": []string{"hi1"}, + "post-header-trailer2": []string{"hi2"}, + "server-trailer-a": []string{"valuea"}, + "server-trailer-c": []string{"valuec"}, + }, + }) }) } @@ -3212,22 +2853,15 @@ func TestServerDoesntWriteInvalidHeaders(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Error("response HEADERS lacked END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("response HEADERS didn't have END_HEADERS") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"ok1", "x"}, - {"content-length", "0"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Header mismatch.\n got: %v\nwant: %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "ok1": []string{"x"}, + "content-length": []string{"0"}, + }, + }) }) } @@ -3255,11 +2889,15 @@ func BenchmarkServerGets(b *testing.B) { EndStream: true, EndHeaders: true, }) - st.wantHeaders() - df := st.wantData() - if !df.StreamEnded() { - b.Fatalf("DATA didn't have END_STREAM; got %v", df) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) } } @@ -3294,11 +2932,15 @@ func BenchmarkServerPosts(b *testing.B) { EndHeaders: true, }) st.writeData(id, true, nil) - st.wantHeaders() - df := st.wantData() - if !df.StreamEnded() { - b.Fatalf("DATA didn't have END_STREAM; got %v", df) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) } } @@ -3357,18 +2999,22 @@ func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { }) st.writeData(id, true, nil) - st.wantHeaders() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) var pendingWindowUpdate = uint32(0) for i := 0; i < b.N; i += 1 { expected := nextMsg(i) - df := st.wantData() - if bytes.Compare(expected, df.data) != 0 { - b.Fatalf("Bad message received; want %v; got %v", expected, df.data) - } + st.wantData(wantData{ + streamID: 1, + endStream: false, + data: expected, + }) // try to send infrequent but large window updates so they don't overwhelm the test - pendingWindowUpdate += uint32(len(df.data)) + pendingWindowUpdate += uint32(len(expected)) if pendingWindowUpdate >= windowSize/2 { if err := st.fr.WriteWindowUpdate(0, pendingWindowUpdate); err != nil { b.Fatal(err) @@ -3379,10 +3025,10 @@ func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { pendingWindowUpdate = 0 } } - df := st.wantData() - if !df.StreamEnded() { - b.Fatalf("DATA didn't have END_STREAM; got %v", df) - } + st.wantData(wantData{ + streamID: 1, + endStream: true, + }) } // go-fuzz bug, originally reported at https://github.com/bradfitz/http2/issues/53 @@ -3546,14 +3192,13 @@ func TestServerNoAutoContentLengthOnHead(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "200"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + }, + }) } // golang.org/issue/13495 @@ -3570,16 +3215,15 @@ func TestServerNoDuplicateContentType(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "200"}, - {"content-type", ""}, - {"content-length", "41"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{""}, + "content-length": []string{"41"}, + }, + }) } func TestServerContentLengthCanBeDisabled(t *testing.T) { @@ -3595,15 +3239,14 @@ func TestServerContentLengthCanBeDisabled(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + }, + }) } func disableGoroutineTracking(t testing.TB) { @@ -3639,8 +3282,14 @@ func BenchmarkServer_GetRequest(b *testing.B) { EndStream: true, EndHeaders: true, }) - st.wantHeaders() - st.wantData() + st.wantHeaders(wantHeader{ + streamID: streamID, + endStream: false, + }) + st.wantData(wantData{ + streamID: streamID, + endStream: true, + }) } } @@ -3671,8 +3320,14 @@ func BenchmarkServer_PostRequest(b *testing.B) { EndHeaders: true, }) st.writeData(streamID, true, nil) - st.wantHeaders() - st.wantData() + st.wantHeaders(wantHeader{ + streamID: streamID, + endStream: false, + }) + st.wantData(wantData{ + streamID: streamID, + endStream: true, + }) } } @@ -3757,17 +3412,16 @@ func TestServer_Rejects_ConnHeaders(t *testing.T) { defer st.Close() st.greet() st.bodylessReq1("connection", "foo") - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "400"}, - {"content-type", "text/plain; charset=utf-8"}, - {"x-content-type-options", "nosniff"}, - {"content-length", "51"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"400"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "x-content-type-options": []string{"nosniff"}, + "content-length": []string{"51"}, + }, + }) } type hpackEncoder struct { @@ -3972,12 +3626,18 @@ func TestServerReturnsStreamAndConnFlowControlOnBodyClose(t *testing.T) { BlockFragment: st.encodeHeader(), EndHeaders: true, }) - st.wantHeaders() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) const size = inflowMinRefresh // enough to trigger flow control return st.writeData(1, false, make([]byte, size)) st.wantWindowUpdate(0, size) // conn-level flow control is returned unblockHandler <- struct{}{} - st.wantData() + st.wantData(wantData{ + streamID: 1, + endStream: true, + }) } func TestServerIdleTimeout(t *testing.T) { @@ -3993,10 +3653,7 @@ func TestServerIdleTimeout(t *testing.T) { st.greet() st.advance(500 * time.Millisecond) - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeNo { - t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode) - } + st.wantGoAway(0, ErrCodeNo) } func TestServerIdleTimeout_AfterRequest(t *testing.T) { @@ -4022,15 +3679,15 @@ func TestServerIdleTimeout_AfterRequest(t *testing.T) { // idle timeout doesn't fire while we're in a request: st.bodylessReq1() st.advance(requestTimeout) - st.wantHeaders() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) // But the idle timeout should be rearmed after the request // is done: st.advance(idleTimeout) - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeNo { - t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode) - } + st.wantGoAway(1, ErrCodeNo) } // grpc-go closes the Request.Body currently with a Read. @@ -4132,13 +3789,10 @@ func TestServerHandlerConnectionClose(t *testing.T) { var sawRes bool var sawWindowUpdate bool for { - f, err := st.readFrame() - if err == io.EOF { + f := st.readFrame() + if f == nil { break } - if err != nil { - t.Fatal(err) - } switch f := f.(type) { case *GoAwayFrame: sawGoAway = true @@ -4270,27 +3924,20 @@ func TestServerGracefulShutdown(t *testing.T) { st.sync() st.h1server.Shutdown(context.Background()) - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeNo { - t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode) - } - if ga.LastStreamID != 1 { - t.Errorf("GOAWAY LastStreamID = %v; want 1", ga.LastStreamID) - } + st.wantGoAway(1, ErrCodeNo) close(handlerDone) st.sync() - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"x-foo", "bar"}, - {"content-length", "0"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "x-foo": []string{"bar"}, + "content-length": []string{"0"}, + }, + }) n, err := st.cc.Read([]byte{0}) if n != 0 || err == nil { @@ -4441,13 +4088,10 @@ func TestServerWindowUpdateOnBodyClose(t *testing.T) { // Wait for flow control credit for the portion of the request written so far. increments := windowSize / 2 for { - f, err := st.readFrame() - if err == io.EOF { + f := st.readFrame() + if f == nil { break } - if err != nil { - t.Fatal(err) - } if wu, ok := f.(*WindowUpdateFrame); ok && wu.StreamID == 0 { increments -= int(wu.Increment) if increments == 0 { @@ -4481,21 +4125,14 @@ func TestNoErrorLoggedOnPostAfterGOAWAY(t *testing.T) { EndStream: false, EndHeaders: true, }) - st.wantHeaders() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) st.sc.startGracefulShutdown() - for { - f, err := st.readFrame() - if err == io.EOF { - st.t.Fatal("got a EOF; want *GoAwayFrame") - } - if err != nil { - t.Fatal(err) - } - if gf, ok := f.(*GoAwayFrame); ok && gf.StreamID == 0 { - break - } - } + st.wantRSTStream(1, ErrCodeNo) + st.wantGoAway(1, ErrCodeNo) st.writeData(1, true, []byte(content)) st.Close() @@ -4513,27 +4150,22 @@ func TestServerSendsProcessing(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "102"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } - - hf = st.wantHeaders() - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", "5"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"102"}, + }, + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{"5"}, + }, + }) }) } @@ -4553,45 +4185,43 @@ func TestServerSendsEarlyHints(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "103"}, - {"link", "; rel=preload; as=style"}, - {"link", "; rel=preload; as=script"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } - - hf = st.wantHeaders() - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "103"}, - {"link", "; rel=preload; as=style"}, - {"link", "; rel=preload; as=script"}, - {"link", "; rel=preload; as=script"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } - - hf = st.wantHeaders() - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "200"}, - {"link", "; rel=preload; as=style"}, - {"link", "; rel=preload; as=script"}, - {"link", "; rel=preload; as=script"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", "123"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"103"}, + "link": []string{ + "; rel=preload; as=style", + "; rel=preload; as=script", + }, + }, + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"103"}, + "link": []string{ + "; rel=preload; as=style", + "; rel=preload; as=script", + "; rel=preload; as=script", + }, + }, + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "link": []string{ + "; rel=preload; as=style", + "; rel=preload; as=script", + "; rel=preload; as=script", + }, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{"123"}, + }, + }) }) } @@ -4613,7 +4243,6 @@ func TestProtocolErrorAfterGoAway(t *testing.T) { EndHeaders: true, }) st.writeData(1, false, []byte(content[:5])) - st.writeReadPing() // Send a GOAWAY with ErrCodeNo, followed by a bogus window update. // The server should close the connection. @@ -4625,14 +4254,8 @@ func TestProtocolErrorAfterGoAway(t *testing.T) { } st.advance(goAwayTimeout) - for { - if _, err := st.readFrame(); err != nil { - if err != io.EOF { - t.Errorf("unexpected readFrame error: %v", err) - } - break - } - } + st.wantGoAway(1, ErrCodeNo) + st.wantClosed() } func TestServerInitialFlowControlWindow(t *testing.T) { @@ -4653,9 +4276,9 @@ func TestServerInitialFlowControlWindow(t *testing.T) { }, func(s *Server) { s.MaxUploadBufferPerConnection = want }) - defer st.Close() st.writePreface() - st.writeInitialSettings() + st.writeSettings() + _ = readFrame[*SettingsFrame](t, st) st.writeSettingsAck() st.writeHeaders(HeadersFrameParam{ StreamID: 1, @@ -4666,10 +4289,7 @@ func TestServerInitialFlowControlWindow(t *testing.T) { window := 65535 Frames: for { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } + f := st.readFrame() switch f := f.(type) { case *WindowUpdateFrame: if f.FrameHeader.StreamID != 0 { @@ -4679,6 +4299,8 @@ func TestServerInitialFlowControlWindow(t *testing.T) { window += int(f.Increment) case *HeadersFrame: break Frames + case nil: + break Frames default: } } @@ -4814,9 +4436,7 @@ func TestServerMaxHandlerGoroutines(t *testing.T) { }) defer st.Close() - st.writePreface() - st.writeInitialSettings() - st.writeSettingsAck() + st.greet() // Make maxHandlers concurrent requests. // Reset them all, but only after the handler goroutines have started. @@ -4883,20 +4503,9 @@ func TestServerMaxHandlerGoroutines(t *testing.T) { st.fr.WriteRSTStream(streamID, ErrCodeCancel) streamID += 2 } -Frames: - for { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } - switch f := f.(type) { - case *GoAwayFrame: - if f.ErrCode != ErrCodeEnhanceYourCalm { - t.Errorf("err code = %v; want %v", f.ErrCode, ErrCodeEnhanceYourCalm) - } - break Frames - default: - } + fr := readFrame[*GoAwayFrame](t, st) + if fr.ErrCode != ErrCodeEnhanceYourCalm { + t.Errorf("err code = %v; want %v", fr.ErrCode, ErrCodeEnhanceYourCalm) } for _, s := range stops { @@ -4912,9 +4521,7 @@ func TestServerContinuationFlood(t *testing.T) { }) defer st.Close() - st.writePreface() - st.writeInitialSettings() - st.writeSettingsAck() + st.greet() st.writeHeaders(HeadersFrameParam{ StreamID: 1, @@ -4931,8 +4538,8 @@ func TestServerContinuationFlood(t *testing.T) { )) for { - f, err := st.readFrame() - if err != nil { + f := st.readFrame() + if f == nil { break } switch f := f.(type) { @@ -4964,9 +4571,7 @@ func TestServerContinuationAfterInvalidHeader(t *testing.T) { }) defer st.Close() - st.writePreface() - st.writeInitialSettings() - st.writeSettingsAck() + st.greet() st.writeHeaders(HeadersFrameParam{ StreamID: 1, @@ -4982,8 +4587,8 @@ func TestServerContinuationAfterInvalidHeader(t *testing.T) { var sawGoAway bool for { - f, err := st.readFrame() - if err != nil { + f := st.readFrame() + if f == nil { break } switch f.(type) { @@ -5029,9 +4634,7 @@ func TestServerRequestCancelOnError(t *testing.T) { }) defer st.Close() - st.writePreface() - st.writeInitialSettings() - st.writeSettingsAck() + st.greet() // Client sends request headers, handler starts. st.writeHeaders(HeadersFrameParam{ diff --git a/http2/transport_test.go b/http2/transport_test.go index 261368f54..ddeaf6137 100644 --- a/http2/transport_test.go +++ b/http2/transport_test.go @@ -151,7 +151,7 @@ func TestIdleConnTimeout(t *testing.T) { } // Respond to the client's request. - hf := testClientConnReadFrame[*MetaHeadersFrame](tc) + hf := readFrame[*HeadersFrame](t, tc) tc.writeHeaders(HeadersFrameParam{ StreamID: hf.StreamID, EndHeaders: true, @@ -865,6 +865,7 @@ func testTransportReqBodyAfterResponse(t *testing.T, status int) { streamID: rt.streamID(), endStream: true, size: bodySize / 2, + multiple: true, }) } else { // After a 403 response, client gives up and resets the stream. @@ -1870,6 +1871,7 @@ func testTransportResponseHeaderTimeout(t *testing.T, body bool) { tc.wantData(wantData{ endStream: true, size: bodySize, + multiple: true, }) } @@ -2604,7 +2606,7 @@ func TestTransportAdjustsFlowControl(t *testing.T) { gotBytes := int64(0) for { - f := testClientConnReadFrame[*DataFrame](tc) + f := readFrame[*DataFrame](t, tc) gotBytes += int64(len(f.Data())) // After we've got half the client's initial flow control window's worth // of request body data, give it just enough flow control to finish. @@ -2700,7 +2702,7 @@ func TestTransportReturnsErrorOnBadResponseHeaders(t *testing.T) { t.Fatalf("RoundTrip error = %#v; want %#v", err, want) } - fr := testClientConnReadFrame[*RSTStreamFrame](tc) + fr := readFrame[*RSTStreamFrame](t, tc) if fr.StreamID != 1 || fr.ErrCode != ErrCodeProtocol { t.Errorf("Frame = %v; want RST_STREAM for stream 1 with ErrCodeProtocol", summarizeFrame(fr)) } @@ -3071,7 +3073,7 @@ func TestTransportPingWhenReadingMultiplePings(t *testing.T) { // ...ping now. tc.advance(1 * time.Millisecond) - f := testClientConnReadFrame[*PingFrame](tc) + f := readFrame[*PingFrame](t, tc) tc.writePing(true, f.Data) } @@ -3375,7 +3377,7 @@ func TestTransportMaxFrameReadSize(t *testing.T) { tr.MaxReadFrameSize = test.maxReadFrameSize }) - fr := testClientConnReadFrame[*SettingsFrame](tc) + fr := readFrame[*SettingsFrame](t, tc) got, ok := fr.Value(SettingMaxFrameSize) if !ok { t.Errorf("Transport.MaxReadFrameSize = %v; server got no setting, want %v", test.maxReadFrameSize, test.want) @@ -3518,7 +3520,7 @@ func TestTransportMaxDecoderHeaderTableSize(t *testing.T) { tr.MaxDecoderHeaderTableSize = reqSize }) - fr := testClientConnReadFrame[*SettingsFrame](tc) + fr := readFrame[*SettingsFrame](t, tc) if v, ok := fr.Value(SettingHeaderTableSize); !ok { t.Fatalf("missing SETTINGS_HEADER_TABLE_SIZE setting") } else if v != reqSize { @@ -4135,7 +4137,7 @@ func TestTransportBodyEagerEndStream(t *testing.T) { tc.roundTrip(req) tc.wantFrameType(FrameHeaders) - f := testClientConnReadFrame[*DataFrame](tc) + f := readFrame[*DataFrame](t, tc) if !f.StreamEnded() { t.Fatalf("data frame without END_STREAM %v", f) }