http2: use synthetic time in TestIdleConnTimeout
Rewrite TestIdleConnTimeout to use the new synthetic time and
synchronization test facilities, rather than using real time
and sleeps.
Reduces the test time from 20 seconds to 0.
Reduces all package tests on my laptop from 32 seconds to 12.
Change-Id: I33838488168450a7acd6a462777b5a4caf7f5307
Reviewed-on: https://go-review.googlesource.com/c/net/+/572379
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/http2/transport.go b/http2/transport.go
index ba0956e..ce375c8 100644
--- a/http2/transport.go
+++ b/http2/transport.go
@@ -310,7 +310,7 @@
readerErr error // set before readerDone is closed
idleTimeout time.Duration // or 0 for never
- idleTimer *time.Timer
+ idleTimer timer
mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes
@@ -828,7 +828,7 @@
}
if d := t.idleConnTimeout(); d != 0 {
cc.idleTimeout = d
- cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
+ cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
}
if VerboseLogs {
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
diff --git a/http2/transport_test.go b/http2/transport_test.go
index 5226a61..18d4db3 100644
--- a/http2/transport_test.go
+++ b/http2/transport_test.go
@@ -97,63 +97,83 @@
func TestIdleConnTimeout(t *testing.T) {
for _, test := range []struct {
+ name string
idleConnTimeout time.Duration
wait time.Duration
baseTransport *http.Transport
- wantConns int32
+ wantNewConn bool
}{{
+ name: "NoExpiry",
idleConnTimeout: 2 * time.Second,
wait: 1 * time.Second,
baseTransport: nil,
- wantConns: 1,
+ wantNewConn: false,
}, {
+ name: "H2TransportTimeoutExpires",
idleConnTimeout: 1 * time.Second,
wait: 2 * time.Second,
baseTransport: nil,
- wantConns: 5,
+ wantNewConn: true,
}, {
+ name: "H1TransportTimeoutExpires",
idleConnTimeout: 0 * time.Second,
wait: 1 * time.Second,
baseTransport: &http.Transport{
IdleConnTimeout: 2 * time.Second,
},
- wantConns: 1,
+ wantNewConn: false,
}} {
- var gotConns int32
+ t.Run(test.name, func(t *testing.T) {
+ tt := newTestTransport(t, func(tr *Transport) {
+ tr.IdleConnTimeout = test.idleConnTimeout
+ })
+ var tc *testClientConn
+ for i := 0; i < 3; i++ {
+ req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
+ rt := tt.roundTrip(req)
- st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
- io.WriteString(w, r.RemoteAddr)
- }, optOnlyServer)
- defer st.Close()
+ // This request happens on a new conn if it's the first request
+ // (and there is no cached conn), or if the test timeout is long
+ // enough that old conns are being closed.
+ wantConn := i == 0 || test.wantNewConn
+ if has := tt.hasConn(); has != wantConn {
+ t.Fatalf("request %v: hasConn=%v, want %v", i, has, wantConn)
+ }
+ if wantConn {
+ tc = tt.getConn()
+ // Read client's SETTINGS and first WINDOW_UPDATE,
+ // send our SETTINGS.
+ tc.wantFrameType(FrameSettings)
+ tc.wantFrameType(FrameWindowUpdate)
+ tc.writeSettings()
+ }
+ if tt.hasConn() {
+ t.Fatalf("request %v: Transport has more than one conn", i)
+ }
- tr := &Transport{
- IdleConnTimeout: test.idleConnTimeout,
- TLSClientConfig: tlsConfigInsecure,
- }
- defer tr.CloseIdleConnections()
+ // Respond to the client's request.
+ hf := testClientConnReadFrame[*MetaHeadersFrame](tc)
+ tc.writeHeaders(HeadersFrameParam{
+ StreamID: hf.StreamID,
+ EndHeaders: true,
+ EndStream: true,
+ BlockFragment: tc.makeHeaderBlockFragment(
+ ":status", "200",
+ ),
+ })
+ rt.wantStatus(200)
- for i := 0; i < 5; i++ {
- req, _ := http.NewRequest("GET", st.ts.URL, http.NoBody)
- trace := &httptrace.ClientTrace{
- GotConn: func(connInfo httptrace.GotConnInfo) {
- if !connInfo.Reused {
- atomic.AddInt32(&gotConns, 1)
- }
- },
+ // If this was a newly-accepted conn, read the SETTINGS ACK.
+ if wantConn {
+ tc.wantFrameType(FrameSettings) // ACK to our settings
+ }
+
+ tt.advance(test.wait)
+ if got, want := tc.netConnClosed, test.wantNewConn; got != want {
+ t.Fatalf("after waiting %v, conn closed=%v; want %v", test.wait, got, want)
+ }
}
- req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
-
- _, err := tr.RoundTrip(req)
- if err != nil {
- t.Fatalf("%v", err)
- }
-
- <-time.After(test.wait)
- }
-
- if gotConns != test.wantConns {
- t.Errorf("incorrect gotConns: %d != %d", gotConns, test.wantConns)
- }
+ })
}
}