Skip to content

Commit 0d515a5

Browse files
committed
http2: factor out frame read/write test functions
Client and server tests both write frames to a test connection and read frames back. Frame reads are usually paired with test expectations. Unify the API used for frame reads/writes in tests. Introduce a testConnFramer type that implements a common set of read/write methods, and embed it in both client and server test types. Change-Id: I6927c43459ba24f150a21c058a92797754f82bf1 Reviewed-on: https://go-review.googlesource.com/c/net/+/586249 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 9f5b79b commit 0d515a5

File tree

5 files changed

+1015
-1258
lines changed

5 files changed

+1015
-1258
lines changed

http2/clientconn_test.go

+13-275
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ import (
1313
"fmt"
1414
"io"
1515
"net/http"
16-
"os"
1716
"reflect"
1817
"runtime"
19-
"slices"
2018
"sync/atomic"
2119
"testing"
2220
"time"
@@ -62,6 +60,7 @@ func TestTestClientConn(t *testing.T) {
6260
streamID: rt.streamID(),
6361
endStream: true,
6462
size: 10,
63+
multiple: true,
6564
})
6665

6766
// tc.writeHeaders sends a HEADERS frame back to the client.
@@ -97,6 +96,7 @@ type testClientConn struct {
9796
fr *Framer
9897
cc *ClientConn
9998
group *synctestGroup
99+
testConnFramer
100100

101101
encbuf bytes.Buffer
102102
enc *hpack.Encoder
@@ -115,6 +115,7 @@ func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientCo
115115
}
116116
cli, srv := synctestNetPipe(tc.group)
117117
srv.SetReadDeadline(tc.group.Now())
118+
srv.autoWait = true
118119
tc.netconn = srv
119120
tc.enc = hpack.NewEncoder(&tc.encbuf)
120121

@@ -123,8 +124,12 @@ func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientCo
123124
// cli is the ClientConn's side, srv is the side controlled by the test.
124125
cc.tconn = cli
125126
tc.fr = NewFramer(srv, srv)
127+
tc.testConnFramer = testConnFramer{
128+
t: t,
129+
fr: tc.fr,
130+
dec: hpack.NewDecoder(initialHeaderTableSize, nil),
131+
}
126132

127-
tc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
128133
tc.fr.SetMaxReadFrameSize(10 << 20)
129134
t.Cleanup(func() {
130135
tc.closeWrite()
@@ -174,169 +179,15 @@ func (tc *testClientConn) hasFrame() bool {
174179
return len(tc.netconn.Peek()) > 0
175180
}
176181

182+
// isClosed reports whether the peer has closed the connection.
177183
func (tc *testClientConn) isClosed() bool {
178184
return tc.netconn.IsClosedByPeer()
179185
}
180186

181-
// readFrame reads the next frame from the conn.
182-
func (tc *testClientConn) readFrame() Frame {
183-
tc.t.Helper()
184-
tc.sync()
185-
fr, err := tc.fr.ReadFrame()
186-
if err == io.EOF || err == os.ErrDeadlineExceeded {
187-
return nil
188-
}
189-
if err != nil {
190-
tc.t.Fatalf("ReadFrame: %v", err)
191-
}
192-
return fr
193-
}
194-
195-
// testClientConnReadFrame reads a frame of a specific type from the conn.
196-
func testClientConnReadFrame[T any](tc *testClientConn) T {
197-
tc.t.Helper()
198-
var v T
199-
fr := tc.readFrame()
200-
if fr == nil {
201-
tc.t.Fatalf("got no frame, want frame %T", v)
202-
}
203-
v, ok := fr.(T)
204-
if !ok {
205-
tc.t.Fatalf("got frame %T, want %T", fr, v)
206-
}
207-
return v
208-
}
209-
210-
// wantFrameType reads the next frame from the conn.
211-
// It produces an error if the frame type is not the expected value.
212-
func (tc *testClientConn) wantFrameType(want FrameType) {
213-
tc.t.Helper()
214-
fr := tc.readFrame()
215-
if fr == nil {
216-
tc.t.Fatalf("got no frame, want frame %v", want)
217-
}
218-
if got := fr.Header().Type; got != want {
219-
tc.t.Fatalf("got frame %v, want %v", got, want)
220-
}
221-
}
222-
223-
// wantUnorderedFrames reads frames from the conn until every condition in want has been satisfied.
224-
//
225-
// want is a list of func(*SomeFrame) bool.
226-
// wantUnorderedFrames will call each func with frames of the appropriate type
227-
// until the func returns true.
228-
// It calls t.Fatal if an unexpected frame is received (no func has that frame type,
229-
// or all funcs with that type have returned true), or if the conn runs out of frames
230-
// with unsatisfied funcs.
231-
//
232-
// Example:
233-
//
234-
// // Read a SETTINGS frame, and any number of DATA frames for a stream.
235-
// // The SETTINGS frame may appear anywhere in the sequence.
236-
// // The last DATA frame must indicate the end of the stream.
237-
// tc.wantUnorderedFrames(
238-
// func(f *SettingsFrame) bool {
239-
// return true
240-
// },
241-
// func(f *DataFrame) bool {
242-
// return f.StreamEnded()
243-
// },
244-
// )
245-
func (tc *testClientConn) wantUnorderedFrames(want ...any) {
246-
tc.t.Helper()
247-
want = slices.Clone(want)
248-
seen := 0
249-
frame:
250-
for seen < len(want) && !tc.t.Failed() {
251-
fr := tc.readFrame()
252-
if fr == nil {
253-
break
254-
}
255-
for i, f := range want {
256-
if f == nil {
257-
continue
258-
}
259-
typ := reflect.TypeOf(f)
260-
if typ.Kind() != reflect.Func ||
261-
typ.NumIn() != 1 ||
262-
typ.NumOut() != 1 ||
263-
typ.Out(0) != reflect.TypeOf(true) {
264-
tc.t.Fatalf("expected func(*SomeFrame) bool, got %T", f)
265-
}
266-
if typ.In(0) == reflect.TypeOf(fr) {
267-
out := reflect.ValueOf(f).Call([]reflect.Value{reflect.ValueOf(fr)})
268-
if out[0].Bool() {
269-
want[i] = nil
270-
seen++
271-
}
272-
continue frame
273-
}
274-
}
275-
tc.t.Errorf("got unexpected frame type %T", fr)
276-
}
277-
if seen < len(want) {
278-
for _, f := range want {
279-
if f == nil {
280-
continue
281-
}
282-
tc.t.Errorf("did not see expected frame: %v", reflect.TypeOf(f).In(0))
283-
}
284-
tc.t.Fatalf("did not see %v expected frame types", len(want)-seen)
285-
}
286-
}
287-
288-
type wantHeader struct {
289-
streamID uint32
290-
endStream bool
291-
header http.Header
292-
}
293-
294-
// wantHeaders reads a HEADERS frame and potential CONTINUATION frames,
295-
// and asserts that they contain the expected headers.
296-
func (tc *testClientConn) wantHeaders(want wantHeader) {
297-
tc.t.Helper()
298-
got := testClientConnReadFrame[*MetaHeadersFrame](tc)
299-
if got, want := got.StreamID, want.streamID; got != want {
300-
tc.t.Fatalf("got stream ID %v, want %v", got, want)
301-
}
302-
if got, want := got.StreamEnded(), want.endStream; got != want {
303-
tc.t.Fatalf("got stream ended %v, want %v", got, want)
304-
}
305-
gotHeader := make(http.Header)
306-
for _, f := range got.Fields {
307-
gotHeader[f.Name] = append(gotHeader[f.Name], f.Value)
308-
}
309-
for k, v := range want.header {
310-
if !reflect.DeepEqual(v, gotHeader[k]) {
311-
tc.t.Fatalf("got header %q = %q; want %q", k, v, gotHeader[k])
312-
}
313-
}
314-
}
315-
316-
type wantData struct {
317-
streamID uint32
318-
endStream bool
319-
size int
320-
}
321-
322-
// wantData reads zero or more DATA frames, and asserts that they match the expectation.
323-
func (tc *testClientConn) wantData(want wantData) {
324-
tc.t.Helper()
325-
gotSize := 0
326-
gotEndStream := false
327-
for tc.hasFrame() && !gotEndStream {
328-
data := testClientConnReadFrame[*DataFrame](tc)
329-
gotSize += len(data.Data())
330-
if data.StreamEnded() {
331-
gotEndStream = true
332-
}
333-
}
334-
if gotSize != want.size {
335-
tc.t.Fatalf("got %v bytes of DATA frames, want %v", gotSize, want.size)
336-
}
337-
if gotEndStream != want.endStream {
338-
tc.t.Fatalf("after %v bytes of DATA frames, got END_STREAM=%v; want %v", gotSize, gotEndStream, want.endStream)
339-
}
187+
// closeWrite causes the net.Conn used by the ClientConn to return a error
188+
// from Read calls.
189+
func (tc *testClientConn) closeWrite() {
190+
tc.netconn.Close()
340191
}
341192

342193
// testRequestBody is a Request.Body for use in tests.
@@ -468,38 +319,6 @@ func (tc *testClientConn) greet(settings ...Setting) {
468319
tc.wantFrameType(FrameSettings) // acknowledgement
469320
}
470321

471-
func (tc *testClientConn) writeSettings(settings ...Setting) {
472-
tc.t.Helper()
473-
if err := tc.fr.WriteSettings(settings...); err != nil {
474-
tc.t.Fatal(err)
475-
}
476-
tc.sync()
477-
}
478-
479-
func (tc *testClientConn) writeSettingsAck() {
480-
tc.t.Helper()
481-
if err := tc.fr.WriteSettingsAck(); err != nil {
482-
tc.t.Fatal(err)
483-
}
484-
tc.sync()
485-
}
486-
487-
func (tc *testClientConn) writeData(streamID uint32, endStream bool, data []byte) {
488-
tc.t.Helper()
489-
if err := tc.fr.WriteData(streamID, endStream, data); err != nil {
490-
tc.t.Fatal(err)
491-
}
492-
tc.sync()
493-
}
494-
495-
func (tc *testClientConn) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) {
496-
tc.t.Helper()
497-
if err := tc.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil {
498-
tc.t.Fatal(err)
499-
}
500-
tc.sync()
501-
}
502-
503322
// makeHeaderBlockFragment encodes headers in a form suitable for inclusion
504323
// in a HEADERS or CONTINUATION frame.
505324
//
@@ -515,87 +334,6 @@ func (tc *testClientConn) makeHeaderBlockFragment(s ...string) []byte {
515334
return tc.encbuf.Bytes()
516335
}
517336

518-
func (tc *testClientConn) writeHeaders(p HeadersFrameParam) {
519-
tc.t.Helper()
520-
if err := tc.fr.WriteHeaders(p); err != nil {
521-
tc.t.Fatal(err)
522-
}
523-
tc.sync()
524-
}
525-
526-
// writeHeadersMode writes header frames, as modified by mode:
527-
//
528-
// - noHeader: Don't write the header.
529-
// - oneHeader: Write a single HEADERS frame.
530-
// - splitHeader: Write a HEADERS frame and CONTINUATION frame.
531-
func (tc *testClientConn) writeHeadersMode(mode headerType, p HeadersFrameParam) {
532-
tc.t.Helper()
533-
switch mode {
534-
case noHeader:
535-
case oneHeader:
536-
tc.writeHeaders(p)
537-
case splitHeader:
538-
if len(p.BlockFragment) < 2 {
539-
panic("too small")
540-
}
541-
contData := p.BlockFragment[1:]
542-
contEnd := p.EndHeaders
543-
p.BlockFragment = p.BlockFragment[:1]
544-
p.EndHeaders = false
545-
tc.writeHeaders(p)
546-
tc.writeContinuation(p.StreamID, contEnd, contData)
547-
default:
548-
panic("bogus mode")
549-
}
550-
}
551-
552-
func (tc *testClientConn) writeContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) {
553-
tc.t.Helper()
554-
if err := tc.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil {
555-
tc.t.Fatal(err)
556-
}
557-
tc.sync()
558-
}
559-
560-
func (tc *testClientConn) writeRSTStream(streamID uint32, code ErrCode) {
561-
tc.t.Helper()
562-
if err := tc.fr.WriteRSTStream(streamID, code); err != nil {
563-
tc.t.Fatal(err)
564-
}
565-
tc.sync()
566-
}
567-
568-
func (tc *testClientConn) writePing(ack bool, data [8]byte) {
569-
tc.t.Helper()
570-
if err := tc.fr.WritePing(ack, data); err != nil {
571-
tc.t.Fatal(err)
572-
}
573-
tc.sync()
574-
}
575-
576-
func (tc *testClientConn) writeGoAway(maxStreamID uint32, code ErrCode, debugData []byte) {
577-
tc.t.Helper()
578-
if err := tc.fr.WriteGoAway(maxStreamID, code, debugData); err != nil {
579-
tc.t.Fatal(err)
580-
}
581-
tc.sync()
582-
}
583-
584-
func (tc *testClientConn) writeWindowUpdate(streamID, incr uint32) {
585-
tc.t.Helper()
586-
if err := tc.fr.WriteWindowUpdate(streamID, incr); err != nil {
587-
tc.t.Fatal(err)
588-
}
589-
tc.sync()
590-
}
591-
592-
// closeWrite causes the net.Conn used by the ClientConn to return a error
593-
// from Read calls.
594-
func (tc *testClientConn) closeWrite() {
595-
tc.netconn.Close()
596-
tc.sync()
597-
}
598-
599337
// inflowWindow returns the amount of inbound flow control available for a stream,
600338
// or for the connection if streamID is 0.
601339
func (tc *testClientConn) inflowWindow(streamID uint32) int32 {

0 commit comments

Comments
 (0)