Skip to content

Commit 022530c

Browse files
committed
http2: add a more full-featured test net.Conn
Add a net.Conn implementation that plays nicely with testsyncGroup, implements read/write timeouts, and gives control over buffering to let us write tests that cause writes to a Conn to block at specific points in time. Change-Id: I9d870b211ac9d938a8c4a221277981cdb821a6e4 Reviewed-on: https://go-review.googlesource.com/c/net/+/586246 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 410d19e commit 022530c

File tree

4 files changed

+376
-101
lines changed

4 files changed

+376
-101
lines changed

http2/clientconn_test.go

+17-99
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ package http2
1010
import (
1111
"bytes"
1212
"context"
13-
"errors"
1413
"fmt"
1514
"io"
16-
"net"
1715
"net/http"
16+
"os"
1817
"reflect"
1918
"runtime"
2019
"slices"
@@ -104,7 +103,7 @@ type testClientConn struct {
104103

105104
roundtrips []*testRoundTrip
106105

107-
netconn testClientConnNetConn
106+
netconn *synctestNetConn
108107
}
109108

110109
func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientConn {
@@ -114,22 +113,21 @@ func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientCo
114113
cc: cc,
115114
group: cc.t.transportTestHooks.group.(*synctestGroup),
116115
}
116+
cli, srv := synctestNetPipe(tc.group)
117+
srv.SetReadDeadline(tc.group.Now())
118+
tc.netconn = srv
117119
tc.enc = hpack.NewEncoder(&tc.encbuf)
118-
tc.netconn.gate = newGate()
119120

120121
// all writes and reads are finished.
121122
//
122123
// cli is the ClientConn's side, srv is the side controlled by the test.
123-
cc.tconn = &tc.netconn
124-
tc.fr = NewFramer(
125-
(*testClientConnNetConnWriteToClient)(&tc.netconn),
126-
(*testClientConnNetConnReadFromClient)(&tc.netconn),
127-
)
124+
cc.tconn = cli
125+
tc.fr = NewFramer(srv, srv)
128126

129127
tc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
130128
tc.fr.SetMaxReadFrameSize(10 << 20)
131129
t.Cleanup(func() {
132-
tc.closeWrite(io.EOF)
130+
tc.closeWrite()
133131
})
134132
return tc
135133
}
@@ -138,8 +136,7 @@ func (tc *testClientConn) readClientPreface() {
138136
tc.t.Helper()
139137
// Read the client's HTTP/2 preface, sent prior to any HTTP/2 frames.
140138
buf := make([]byte, len(clientPreface))
141-
r := (*testClientConnNetConnReadFromClient)(&tc.netconn)
142-
if _, err := io.ReadFull(r, buf); err != nil {
139+
if _, err := io.ReadFull(tc.netconn, buf); err != nil {
143140
tc.t.Fatalf("reading preface: %v", err)
144141
}
145142
if !bytes.Equal(buf, clientPreface) {
@@ -174,26 +171,23 @@ func (tc *testClientConn) advance(d time.Duration) {
174171

175172
// hasFrame reports whether a frame is available to be read.
176173
func (tc *testClientConn) hasFrame() bool {
177-
tc.netconn.lock()
178-
defer tc.netconn.unlock()
179-
return tc.netconn.fromConn.Len() > 0
174+
return len(tc.netconn.Peek()) > 0
180175
}
181176

182177
func (tc *testClientConn) isClosed() bool {
183-
tc.netconn.lock()
184-
defer tc.netconn.unlock()
185-
return tc.netconn.fromConnClosed
178+
return tc.netconn.IsClosedByPeer()
186179
}
187180

188181
// readFrame reads the next frame from the conn.
189182
func (tc *testClientConn) readFrame() Frame {
183+
tc.t.Helper()
190184
tc.sync()
191185
fr, err := tc.fr.ReadFrame()
192-
if err == io.EOF {
186+
if err == io.EOF || err == os.ErrDeadlineExceeded {
193187
return nil
194188
}
195189
if err != nil {
196-
return nil
190+
tc.t.Fatalf("ReadFrame: %v", err)
197191
}
198192
return fr
199193
}
@@ -597,10 +591,8 @@ func (tc *testClientConn) writeWindowUpdate(streamID, incr uint32) {
597591

598592
// closeWrite causes the net.Conn used by the ClientConn to return a error
599593
// from Read calls.
600-
func (tc *testClientConn) closeWrite(err error) {
601-
tc.netconn.lock()
602-
tc.netconn.toConnErr = err
603-
tc.netconn.unlock()
594+
func (tc *testClientConn) closeWrite() {
595+
tc.netconn.Close()
604596
tc.sync()
605597
}
606598

@@ -746,80 +738,6 @@ func diffHeaders(got, want http.Header) string {
746738
return fmt.Sprintf("got: %v\nwant: %v", got, want)
747739
}
748740

749-
// testClientConnNetConn implements net.Conn,
750-
// and is the Conn used by a ClientConn under test.
751-
type testClientConnNetConn struct {
752-
gate gate
753-
toConn bytes.Buffer
754-
toConnErr error
755-
fromConn bytes.Buffer
756-
fromConnClosed bool
757-
}
758-
759-
func (c *testClientConnNetConn) lock() {
760-
c.gate.lock()
761-
}
762-
763-
func (c *testClientConnNetConn) unlock() {
764-
c.gate.unlock(c.toConn.Len() > 0 || c.toConnErr != nil)
765-
}
766-
767-
func (c *testClientConnNetConn) Read(b []byte) (n int, err error) {
768-
if err := c.gate.waitAndLock(context.Background()); err != nil {
769-
return 0, err
770-
}
771-
defer c.unlock()
772-
if c.toConn.Len() == 0 && c.toConnErr != nil {
773-
return 0, c.toConnErr
774-
}
775-
return c.toConn.Read(b)
776-
}
777-
778-
func (c *testClientConnNetConn) Write(b []byte) (n int, err error) {
779-
c.lock()
780-
defer c.unlock()
781-
return c.fromConn.Write(b)
782-
}
783-
784-
func (c *testClientConnNetConn) Close() error {
785-
c.lock()
786-
defer c.unlock()
787-
c.fromConnClosed = true
788-
c.toConn.Reset()
789-
if c.toConnErr == nil {
790-
c.toConnErr = errors.New("connection closed")
791-
}
792-
return nil
793-
}
794-
795-
func (*testClientConnNetConn) LocalAddr() (_ net.Addr) { return }
796-
func (*testClientConnNetConn) RemoteAddr() (_ net.Addr) { return }
797-
func (*testClientConnNetConn) SetDeadline(t time.Time) error { return nil }
798-
func (*testClientConnNetConn) SetReadDeadline(t time.Time) error { return nil }
799-
func (*testClientConnNetConn) SetWriteDeadline(t time.Time) error { return nil }
800-
801-
// testClientConnNetConnWriteToClient is a view on a testClientConnNetConn
802-
// that implements an io.Writer that sends to the client conn under test.
803-
type testClientConnNetConnWriteToClient testClientConnNetConn
804-
805-
func (w *testClientConnNetConnWriteToClient) Write(b []byte) (n int, err error) {
806-
c := (*testClientConnNetConn)(w)
807-
c.gate.lock()
808-
defer c.unlock()
809-
return c.toConn.Write(b)
810-
}
811-
812-
// testClientConnNetConnReadFromClient is a view on a testClientConnNetConn
813-
// that implements an io.Reader that reads data sent by the client conn under test.
814-
type testClientConnNetConnReadFromClient testClientConnNetConn
815-
816-
func (w *testClientConnNetConnReadFromClient) Read(b []byte) (n int, err error) {
817-
c := (*testClientConnNetConn)(w)
818-
c.gate.lock()
819-
defer c.unlock()
820-
return c.fromConn.Read(b)
821-
}
822-
823741
// A testTransport allows testing Transport.RoundTrip against fake servers.
824742
// Tests that aren't specifically exercising RoundTrip's retry loop or connection pooling
825743
// should use testClientConn instead.
@@ -861,7 +779,7 @@ func newTestTransport(t *testing.T, opts ...func(*Transport)) *testTransport {
861779
buf := make([]byte, 16*1024)
862780
n := runtime.Stack(buf, true)
863781
t.Logf("stacks:\n%s", buf[:n])
864-
t.Fatalf("%v goroutines still running after test completed, expect 1", count-1)
782+
t.Fatalf("%v goroutines still running after test completed, expect 1", count)
865783
}
866784
})
867785

0 commit comments

Comments
 (0)