Source file
src/net/http/http1_transport_test.go
1
2
3
4
5 package http_test
6
7 import (
8 "bufio"
9 "context"
10 "errors"
11 "internal/nettest"
12 "net"
13 "net/http"
14 "slices"
15 "sync"
16 "testing"
17 "testing/synctest"
18 )
19
20
21 func TestHTTP1TransportTest(t *testing.T) {
22 synctest.Test(t, func(t *testing.T) {
23 tt := newHTTP1TransportTest(t)
24
25
26
27 sentReq, _ := http.NewRequest("GET", "http://example.tld/request/path", nil)
28 rt := tt.roundTrip(sentReq)
29 if rt.done() {
30 t.Fatalf("RoundTrip unexpectedly returned before reading response")
31 }
32
33
34
35 dial := tt.wantDial("tcp", "example.tld:80")
36 conn := dial.connect()
37
38
39 req := conn.readRequest()
40 if got, want := req.URL.Path, sentReq.URL.Path; got != want {
41 t.Fatalf("read request path %q, want %q", got, want)
42 }
43
44
45 conn.writeMessage(
46 "HTTP/1.1 200 OK",
47 "Content-Length: 0",
48 "",
49 )
50 rt.wantStatus(200)
51 })
52 }
53
54
55
56 type http1TransportTest struct {
57 t *testing.T
58 tr *http.Transport
59
60 dialsMu sync.Mutex
61 dials []*http1TestDial
62 }
63
64 func newHTTP1TransportTest(t *testing.T) *http1TransportTest {
65 tt := &http1TransportTest{
66 t: t,
67 tr: &http.Transport{},
68 }
69 tt.tr.DialContext = (*http1TransportTestDialer)(tt).dialContext
70 return tt
71 }
72
73 func (tt *http1TransportTest) roundTrip(req *http.Request) *testRoundTrip {
74 return newTestRoundTrip(tt.t, tt.tr, req)
75 }
76
77 func newTestRoundTrip(t *testing.T, roundTripper http.RoundTripper, req *http.Request) *testRoundTrip {
78 ctx, cancel := context.WithCancel(req.Context())
79 req = req.WithContext(ctx)
80 rt := &testRoundTrip{
81 t: t,
82 donec: make(chan struct{}),
83 cancel: cancel,
84 }
85 go func() {
86 defer close(rt.donec)
87 rt.resp, rt.respErr = roundTripper.RoundTrip(req)
88 }()
89 synctest.Wait()
90
91 t.Cleanup(func() {
92 if !rt.done() {
93 return
94 }
95 res, _ := rt.result()
96 if res != nil {
97 res.Body.Close()
98 }
99 })
100
101 return rt
102 }
103
104 func (tt *http1TransportTest) newClientConn(scheme, address string) (*http.ClientConn, *http1TestConn) {
105 t := tt.t
106 t.Helper()
107
108 var (
109 clientConn *http.ClientConn
110 err = errors.New("still running")
111 )
112 go func() {
113 clientConn, err = tt.tr.NewClientConn(t.Context(), scheme, address)
114 }()
115 synctest.Wait()
116 netConn := tt.wantDial("tcp", address).connect()
117 synctest.Wait()
118 if err != nil {
119 t.Fatalf("NewClientConn: %v (want success)", err)
120 }
121 t.Cleanup(func() {
122 netConn.conn.Close()
123 clientConn.Close()
124 })
125 return clientConn, netConn
126 }
127
128 func (tt *http1TransportTest) wantDial(network, address string) *http1TestDial {
129 tt.t.Helper()
130 synctest.Wait()
131 tt.dialsMu.Lock()
132 defer tt.dialsMu.Unlock()
133 for i, dial := range tt.dials {
134 if dial.network == network && dial.address == address {
135 tt.dials = slices.Delete(tt.dials, i, i+1)
136 return dial
137 }
138 }
139 if len(tt.dials) == 0 {
140 tt.t.Fatalf("want dial for %q, %q; got none", network, address)
141 } else {
142 tt.t.Fatalf("want dial for %q, %q; got %q, %q", network, address, tt.dials[0].network, tt.dials[0].address)
143 }
144 return nil
145 }
146
147 type connOrError struct {
148 conn net.Conn
149 err error
150 }
151
152 type http1TestDial struct {
153 t *testing.T
154 network string
155 address string
156 resultc chan connOrError
157 }
158
159 func (dial *http1TestDial) connect() *http1TestConn {
160 cliConn, srvConn := nettest.NewConnPair()
161 dial.t.Cleanup(func() {
162 srvConn.Close()
163 })
164 dial.resultc <- connOrError{conn: cliConn}
165 srvConn.SetReadError(errWouldBlock)
166 return &http1TestConn{
167 t: dial.t,
168 conn: srvConn,
169 bufr: bufio.NewReader(srvConn),
170 }
171 }
172
173 type http1TransportTestDialer http1TransportTest
174
175 func (tt *http1TransportTestDialer) dialContext(ctx context.Context, network, address string) (net.Conn, error) {
176 dial := &http1TestDial{
177 t: tt.t,
178 network: network,
179 address: address,
180 resultc: make(chan connOrError, 1),
181 }
182 tt.dialsMu.Lock()
183 tt.dials = append(tt.dials, dial)
184 tt.dialsMu.Unlock()
185 select {
186 case res := <-dial.resultc:
187 return res.conn, res.err
188 case <-tt.t.Context().Done():
189 return nil, errors.New("test ended")
190 }
191 }
192
193
194 type testRoundTrip struct {
195 t *testing.T
196 resp *http.Response
197 respErr error
198 donec chan struct{}
199 cancel context.CancelFunc
200 }
201
202
203 func (rt *testRoundTrip) done() bool {
204 synctest.Wait()
205 select {
206 case <-rt.donec:
207 return true
208 default:
209 return false
210 }
211 }
212
213
214 func (rt *testRoundTrip) result() (*http.Response, error) {
215 t := rt.t
216 t.Helper()
217 synctest.Wait()
218 select {
219 case <-rt.donec:
220 default:
221 t.Fatalf("RoundTrip is not done; want it to be")
222 }
223 return rt.resp, rt.respErr
224 }
225
226
227
228 func (rt *testRoundTrip) response() *http.Response {
229 t := rt.t
230 t.Helper()
231 resp, err := rt.result()
232 if err != nil {
233 t.Fatalf("RoundTrip returned unexpected error: %v", rt.respErr)
234 }
235 if resp == nil {
236 t.Fatalf("RoundTrip returned nil *Response and nil error")
237 }
238 return resp
239 }
240
241
242 func (rt *testRoundTrip) err() error {
243 t := rt.t
244 t.Helper()
245 _, err := rt.result()
246 return err
247 }
248
249
250 func (rt *testRoundTrip) wantStatus(want int) {
251 t := rt.t
252 t.Helper()
253 if got := rt.response().StatusCode; got != want {
254 t.Fatalf("got response status %v, want %v", got, want)
255 }
256 }
257
View as plain text