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