Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6e2c99c

Browse filesBrowse files
committed
http2: allow testing Transports with testSyncHooks
Change-Id: Icafc4860ef0691e5133221a0b53bb1d2158346cc Reviewed-on: https://go-review.googlesource.com/c/net/+/572378 Reviewed-by: Jonathan Amsterdam <jba@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 9e0498d commit 6e2c99c
Copy full SHA for 6e2c99c

File tree

Expand file treeCollapse file tree

3 files changed

+288
-250
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+288
-250
lines changed

‎http2/clientconn_test.go

Copy file name to clipboardExpand all lines: http2/clientconn_test.go
+158-44Lines changed: 158 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -99,62 +99,57 @@ type testClientConn struct {
9999

100100
roundtrips []*testRoundTrip
101101

102-
rerr error // returned by Read
103-
rbuf bytes.Buffer // sent to the test conn
104-
wbuf bytes.Buffer // sent by the test conn
102+
rerr error // returned by Read
103+
netConnClosed bool // set when the ClientConn closes the net.Conn
104+
rbuf bytes.Buffer // sent to the test conn
105+
wbuf bytes.Buffer // sent by the test conn
105106
}
106107

107-
func newTestClientConn(t *testing.T, opts ...func(*Transport)) *testClientConn {
108-
t.Helper()
109-
110-
tr := &Transport{}
111-
for _, o := range opts {
112-
o(tr)
113-
}
114-
108+
func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientConn {
115109
tc := &testClientConn{
116110
t: t,
117-
tr: tr,
118-
hooks: newTestSyncHooks(),
111+
tr: cc.t,
112+
cc: cc,
113+
hooks: cc.t.syncHooks,
119114
}
115+
cc.tconn = (*testClientConnNetConn)(tc)
120116
tc.enc = hpack.NewEncoder(&tc.encbuf)
121117
tc.fr = NewFramer(&tc.rbuf, &tc.wbuf)
122118
tc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
123119
tc.fr.SetMaxReadFrameSize(10 << 20)
124-
125120
t.Cleanup(func() {
126121
tc.sync()
127122
if tc.rerr == nil {
128123
tc.rerr = io.EOF
129124
}
130125
tc.sync()
131-
if tc.hooks.total != 0 {
132-
t.Errorf("%v goroutines still running after test completed", tc.hooks.total)
133-
}
134-
135126
})
127+
return tc
128+
}
136129

137-
tc.hooks.newclientconn = func(cc *ClientConn) {
138-
tc.cc = cc
139-
}
140-
const singleUse = false
141-
_, err := tc.tr.newClientConn((*testClientConnNetConn)(tc), singleUse, tc.hooks)
142-
if err != nil {
143-
t.Fatal(err)
144-
}
145-
tc.sync()
146-
tc.hooks.newclientconn = nil
147-
130+
func (tc *testClientConn) readClientPreface() {
131+
tc.t.Helper()
148132
// Read the client's HTTP/2 preface, sent prior to any HTTP/2 frames.
149133
buf := make([]byte, len(clientPreface))
150134
if _, err := io.ReadFull(&tc.wbuf, buf); err != nil {
151-
t.Fatalf("reading preface: %v", err)
135+
tc.t.Fatalf("reading preface: %v", err)
152136
}
153137
if !bytes.Equal(buf, clientPreface) {
154-
t.Fatalf("client preface: %q, want %q", buf, clientPreface)
138+
tc.t.Fatalf("client preface: %q, want %q", buf, clientPreface)
155139
}
140+
}
156141

157-
return tc
142+
func newTestClientConn(t *testing.T, opts ...func(*Transport)) *testClientConn {
143+
t.Helper()
144+
145+
tt := newTestTransport(t, opts...)
146+
const singleUse = false
147+
_, err := tt.tr.newClientConn(nil, singleUse, tt.tr.syncHooks)
148+
if err != nil {
149+
t.Fatalf("newClientConn: %v", err)
150+
}
151+
152+
return tt.getConn()
158153
}
159154

160155
// sync waits for the ClientConn under test to reach a stable state,
@@ -349,7 +344,7 @@ func (b *testRequestBody) closeWithError(err error) {
349344
// the request times out, or some other terminal condition is reached.)
350345
func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip {
351346
rt := &testRoundTrip{
352-
tc: tc,
347+
t: tc.t,
353348
donec: make(chan struct{}),
354349
}
355350
tc.roundtrips = append(tc.roundtrips, rt)
@@ -362,6 +357,9 @@ func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip {
362357
tc.hooks.newstream = nil
363358

364359
tc.t.Cleanup(func() {
360+
if !rt.done() {
361+
return
362+
}
365363
res, _ := rt.result()
366364
if res != nil {
367365
res.Body.Close()
@@ -460,6 +458,14 @@ func (tc *testClientConn) writeContinuation(streamID uint32, endHeaders bool, he
460458
tc.sync()
461459
}
462460

461+
func (tc *testClientConn) writeRSTStream(streamID uint32, code ErrCode) {
462+
tc.t.Helper()
463+
if err := tc.fr.WriteRSTStream(streamID, code); err != nil {
464+
tc.t.Fatal(err)
465+
}
466+
tc.sync()
467+
}
468+
463469
func (tc *testClientConn) writePing(ack bool, data [8]byte) {
464470
tc.t.Helper()
465471
if err := tc.fr.WritePing(ack, data); err != nil {
@@ -491,9 +497,25 @@ func (tc *testClientConn) closeWrite(err error) {
491497
tc.sync()
492498
}
493499

500+
// inflowWindow returns the amount of inbound flow control available for a stream,
501+
// or for the connection if streamID is 0.
502+
func (tc *testClientConn) inflowWindow(streamID uint32) int32 {
503+
tc.cc.mu.Lock()
504+
defer tc.cc.mu.Unlock()
505+
if streamID == 0 {
506+
return tc.cc.inflow.avail + tc.cc.inflow.unsent
507+
}
508+
cs := tc.cc.streams[streamID]
509+
if cs == nil {
510+
tc.t.Errorf("no stream with id %v", streamID)
511+
return -1
512+
}
513+
return cs.inflow.avail + cs.inflow.unsent
514+
}
515+
494516
// testRoundTrip manages a RoundTrip in progress.
495517
type testRoundTrip struct {
496-
tc *testClientConn
518+
t *testing.T
497519
resp *http.Response
498520
respErr error
499521
donec chan struct{}
@@ -502,6 +524,9 @@ type testRoundTrip struct {
502524

503525
// streamID returns the HTTP/2 stream ID of the request.
504526
func (rt *testRoundTrip) streamID() uint32 {
527+
if rt.cs == nil {
528+
panic("stream ID unknown")
529+
}
505530
return rt.cs.ID
506531
}
507532

@@ -517,20 +542,20 @@ func (rt *testRoundTrip) done() bool {
517542

518543
// result returns the result of the RoundTrip.
519544
func (rt *testRoundTrip) result() (*http.Response, error) {
520-
t := rt.tc.t
545+
t := rt.t
521546
t.Helper()
522547
select {
523548
case <-rt.donec:
524549
default:
525-
t.Fatalf("RoundTrip (stream %v) is not done; want it to be", rt.streamID())
550+
t.Fatalf("RoundTrip is not done; want it to be")
526551
}
527552
return rt.resp, rt.respErr
528553
}
529554

530555
// response returns the response of a successful RoundTrip.
531556
// If the RoundTrip unexpectedly failed, it calls t.Fatal.
532557
func (rt *testRoundTrip) response() *http.Response {
533-
t := rt.tc.t
558+
t := rt.t
534559
t.Helper()
535560
resp, err := rt.result()
536561
if err != nil {
@@ -544,15 +569,15 @@ func (rt *testRoundTrip) response() *http.Response {
544569

545570
// err returns the (possibly nil) error result of RoundTrip.
546571
func (rt *testRoundTrip) err() error {
547-
t := rt.tc.t
572+
t := rt.t
548573
t.Helper()
549574
_, err := rt.result()
550575
return err
551576
}
552577

553578
// wantStatus indicates the expected response StatusCode.
554579
func (rt *testRoundTrip) wantStatus(want int) {
555-
t := rt.tc.t
580+
t := rt.t
556581
t.Helper()
557582
if got := rt.response().StatusCode; got != want {
558583
t.Fatalf("got response status %v, want %v", got, want)
@@ -561,15 +586,15 @@ func (rt *testRoundTrip) wantStatus(want int) {
561586

562587
// body reads the contents of the response body.
563588
func (rt *testRoundTrip) readBody() ([]byte, error) {
564-
t := rt.tc.t
589+
t := rt.t
565590
t.Helper()
566591
return io.ReadAll(rt.response().Body)
567592
}
568593

569594
// wantBody indicates the expected response body.
570595
// (Note that this consumes the body.)
571596
func (rt *testRoundTrip) wantBody(want []byte) {
572-
t := rt.tc.t
597+
t := rt.t
573598
t.Helper()
574599
got, err := rt.readBody()
575600
if err != nil {
@@ -582,7 +607,7 @@ func (rt *testRoundTrip) wantBody(want []byte) {
582607

583608
// wantHeaders indicates the expected response headers.
584609
func (rt *testRoundTrip) wantHeaders(want http.Header) {
585-
t := rt.tc.t
610+
t := rt.t
586611
t.Helper()
587612
res := rt.response()
588613
if diff := diffHeaders(res.Header, want); diff != "" {
@@ -592,7 +617,7 @@ func (rt *testRoundTrip) wantHeaders(want http.Header) {
592617

593618
// wantTrailers indicates the expected response trailers.
594619
func (rt *testRoundTrip) wantTrailers(want http.Header) {
595-
t := rt.tc.t
620+
t := rt.t
596621
t.Helper()
597622
res := rt.response()
598623
if diff := diffHeaders(res.Trailer, want); diff != "" {
@@ -630,7 +655,8 @@ func (nc *testClientConnNetConn) Write(b []byte) (n int, err error) {
630655
return nc.wbuf.Write(b)
631656
}
632657

633-
func (*testClientConnNetConn) Close() error {
658+
func (nc *testClientConnNetConn) Close() error {
659+
nc.netConnClosed = true
634660
return nil
635661
}
636662

@@ -639,3 +665,91 @@ func (*testClientConnNetConn) RemoteAddr() (_ net.Addr) { return }
639665
func (*testClientConnNetConn) SetDeadline(t time.Time) error { return nil }
640666
func (*testClientConnNetConn) SetReadDeadline(t time.Time) error { return nil }
641667
func (*testClientConnNetConn) SetWriteDeadline(t time.Time) error { return nil }
668+
669+
// A testTransport allows testing Transport.RoundTrip against fake servers.
670+
// Tests that aren't specifically exercising RoundTrip's retry loop or connection pooling
671+
// should use testClientConn instead.
672+
type testTransport struct {
673+
t *testing.T
674+
tr *Transport
675+
676+
ccs []*testClientConn
677+
}
678+
679+
func newTestTransport(t *testing.T, opts ...func(*Transport)) *testTransport {
680+
tr := &Transport{
681+
syncHooks: newTestSyncHooks(),
682+
}
683+
for _, o := range opts {
684+
o(tr)
685+
}
686+
687+
tt := &testTransport{
688+
t: t,
689+
tr: tr,
690+
}
691+
tr.syncHooks.newclientconn = func(cc *ClientConn) {
692+
tt.ccs = append(tt.ccs, newTestClientConnFromClientConn(t, cc))
693+
}
694+
695+
t.Cleanup(func() {
696+
tt.sync()
697+
if len(tt.ccs) > 0 {
698+
t.Fatalf("%v test ClientConns created, but not examined by test", len(tt.ccs))
699+
}
700+
if tt.tr.syncHooks.total != 0 {
701+
t.Errorf("%v goroutines still running after test completed", tt.tr.syncHooks.total)
702+
}
703+
})
704+
705+
return tt
706+
}
707+
708+
func (tt *testTransport) sync() {
709+
tt.tr.syncHooks.waitInactive()
710+
}
711+
712+
func (tt *testTransport) advance(d time.Duration) {
713+
tt.tr.syncHooks.advance(d)
714+
tt.sync()
715+
}
716+
717+
func (tt *testTransport) hasConn() bool {
718+
return len(tt.ccs) > 0
719+
}
720+
721+
func (tt *testTransport) getConn() *testClientConn {
722+
tt.t.Helper()
723+
if len(tt.ccs) == 0 {
724+
tt.t.Fatalf("no new ClientConns created; wanted one")
725+
}
726+
tc := tt.ccs[0]
727+
tt.ccs = tt.ccs[1:]
728+
tc.sync()
729+
tc.readClientPreface()
730+
return tc
731+
}
732+
733+
func (tt *testTransport) roundTrip(req *http.Request) *testRoundTrip {
734+
rt := &testRoundTrip{
735+
t: tt.t,
736+
donec: make(chan struct{}),
737+
}
738+
tt.tr.syncHooks.goRun(func() {
739+
defer close(rt.donec)
740+
rt.resp, rt.respErr = tt.tr.RoundTrip(req)
741+
})
742+
tt.sync()
743+
744+
tt.t.Cleanup(func() {
745+
if !rt.done() {
746+
return
747+
}
748+
res, _ := rt.result()
749+
if res != nil {
750+
res.Body.Close()
751+
}
752+
})
753+
754+
return rt
755+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.