github.com/ezoic/ws@v1.0.4-0.20220713205711-5c1d69e074c5/dialer_test.go (about)

     1  package ws
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"encoding/base64"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"math/rand"
    12  	"net"
    13  	"net/http"
    14  	"net/url"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/ezoic/httphead"
    19  )
    20  
    21  func TestDialerRequest(t *testing.T) {
    22  	for _, test := range []struct {
    23  		dialer Dialer
    24  		url    string
    25  		exp    *http.Request
    26  		err    bool
    27  	}{
    28  		{
    29  			url: "wss://example.org/chat",
    30  			exp: setProto(1, 1,
    31  				mustMakeRequest("GET", "wss://example.org/chat", http.Header{
    32  					headerUpgrade:    []string{"websocket"},
    33  					headerConnection: []string{"Upgrade"},
    34  					headerSecVersion: []string{"13"},
    35  					headerSecKey:     []string{"some key"},
    36  				}),
    37  			),
    38  		},
    39  		{
    40  			dialer: Dialer{
    41  				Protocols: []string{"foo", "bar"},
    42  				Extensions: []httphead.Option{
    43  					httphead.NewOption("foo", map[string]string{
    44  						"bar": "1",
    45  					}),
    46  					httphead.NewOption("baz", nil),
    47  				},
    48  				Header: HandshakeHeaderHTTP(http.Header{
    49  					"Origin": []string{"who knows"},
    50  				}),
    51  			},
    52  			url: "wss://example.org/chat",
    53  			exp: setProto(1, 1,
    54  				mustMakeRequest("GET", "wss://example.org/chat", http.Header{
    55  					headerUpgrade:    []string{"websocket"},
    56  					headerConnection: []string{"Upgrade"},
    57  					headerSecVersion: []string{"13"},
    58  					headerSecKey:     []string{"some key"},
    59  
    60  					headerSecProtocol:   []string{"foo, bar"},
    61  					headerSecExtensions: []string{"foo;bar=1,baz"},
    62  
    63  					"Origin": []string{"who knows"},
    64  				}),
    65  			),
    66  		},
    67  	} {
    68  		t.Run("", func(t *testing.T) {
    69  			u, err := url.ParseRequestURI(test.url)
    70  			if err != nil {
    71  				t.Fatal(err)
    72  			}
    73  
    74  			var buf bytes.Buffer
    75  			conn := struct {
    76  				io.Reader
    77  				io.Writer
    78  			}{io.LimitReader(&buf, 0), &buf}
    79  
    80  			_, _, err = test.dialer.Upgrade(&conn, u)
    81  			if err == io.EOF {
    82  				err = nil
    83  			}
    84  			if test.err && err == nil {
    85  				t.Errorf("expected error; got nil")
    86  			}
    87  			if !test.err && err != nil {
    88  				t.Errorf("unexpected error: %s", err)
    89  			}
    90  			if test.err {
    91  				return
    92  			}
    93  
    94  			act := buf.Bytes()
    95  			exp := dumpRequest(test.exp)
    96  
    97  			act = sortHeaders(maskHeader(act, headerSecKey, "<masked>"))
    98  			exp = sortHeaders(maskHeader(exp, headerSecKey, "<masked>"))
    99  
   100  			if !bytes.Equal(act, exp) {
   101  				t.Errorf("unexpected request:\nact:\n%s\nexp:\n%s\n", act, exp)
   102  			}
   103  			if _, err := http.ReadRequest(bufio.NewReader(&buf)); err != nil {
   104  				t.Errorf("read request error: %s", err)
   105  				return
   106  			}
   107  		})
   108  	}
   109  }
   110  
   111  func makeAccept(nonce []byte) []byte {
   112  	accept := make([]byte, acceptSize)
   113  	initAcceptFromNonce(accept, nonce)
   114  	return accept
   115  }
   116  
   117  func BenchmarkPutAccept(b *testing.B) {
   118  	nonce := make([]byte, nonceSize)
   119  	_, err := rand.Read(nonce[:])
   120  	if err != nil {
   121  		b.Fatal(err)
   122  	}
   123  	p := make([]byte, acceptSize)
   124  	b.StartTimer()
   125  	for i := 0; i < b.N; i++ {
   126  		initAcceptFromNonce(p, nonce)
   127  	}
   128  }
   129  
   130  func BenchmarkCheckNonce(b *testing.B) {
   131  	nonce := make([]byte, nonceSize)
   132  	_, err := rand.Read(nonce[:])
   133  	if err != nil {
   134  		b.Fatal(err)
   135  	}
   136  
   137  	accept := makeAccept(nonce)
   138  
   139  	b.ResetTimer()
   140  	for i := 0; i < b.N; i++ {
   141  		_ = checkAcceptFromNonce(nonce, accept)
   142  	}
   143  }
   144  
   145  func TestDialerHandshake(t *testing.T) {
   146  	const (
   147  		acceptNo = iota
   148  		acceptInvalid
   149  		acceptValid
   150  	)
   151  	for _, test := range []struct {
   152  		name       string
   153  		dialer     Dialer
   154  		res        *http.Response
   155  		frames     []Frame
   156  		accept     int
   157  		err        error
   158  		wantBuffer bool
   159  	}{
   160  		{
   161  			res: &http.Response{
   162  				StatusCode: 101,
   163  				ProtoMajor: 1,
   164  				ProtoMinor: 1,
   165  				Header: http.Header{
   166  					headerConnection: []string{"Upgrade"},
   167  					headerUpgrade:    []string{"websocket"},
   168  				},
   169  			},
   170  			accept: acceptValid,
   171  		},
   172  		{
   173  			dialer: Dialer{
   174  				Protocols: []string{"xml", "json", "soap"},
   175  			},
   176  			res: &http.Response{
   177  				StatusCode: 101,
   178  				ProtoMajor: 1,
   179  				ProtoMinor: 1,
   180  				Header: http.Header{
   181  					headerConnection:  []string{"Upgrade"},
   182  					headerUpgrade:     []string{"websocket"},
   183  					headerSecProtocol: []string{"json"},
   184  				},
   185  			},
   186  			accept: acceptValid,
   187  		},
   188  		{
   189  			dialer: Dialer{
   190  				Protocols: []string{"xml", "json", "soap"},
   191  			},
   192  			res: &http.Response{
   193  				StatusCode: 101,
   194  				ProtoMajor: 1,
   195  				ProtoMinor: 1,
   196  				Header: http.Header{
   197  					headerConnection: []string{"Upgrade"},
   198  					headerUpgrade:    []string{"websocket"},
   199  				},
   200  			},
   201  			accept: acceptValid,
   202  		},
   203  		{
   204  			dialer: Dialer{
   205  				Extensions: []httphead.Option{
   206  					httphead.NewOption("foo", map[string]string{
   207  						"bar": "1",
   208  					}),
   209  					httphead.NewOption("baz", nil),
   210  				},
   211  			},
   212  			res: &http.Response{
   213  				StatusCode: 101,
   214  				ProtoMajor: 1,
   215  				ProtoMinor: 1,
   216  				Header: http.Header{
   217  					headerConnection:    []string{"Upgrade"},
   218  					headerUpgrade:       []string{"websocket"},
   219  					headerSecExtensions: []string{"foo;bar=1"},
   220  				},
   221  			},
   222  			accept: acceptValid,
   223  		},
   224  		{
   225  			dialer: Dialer{
   226  				Extensions: []httphead.Option{
   227  					httphead.NewOption("foo", map[string]string{
   228  						"bar": "1",
   229  					}),
   230  					httphead.NewOption("baz", nil),
   231  				},
   232  			},
   233  			res: &http.Response{
   234  				StatusCode: 101,
   235  				ProtoMajor: 1,
   236  				ProtoMinor: 1,
   237  				Header: http.Header{
   238  					headerConnection: []string{"Upgrade"},
   239  					headerUpgrade:    []string{"websocket"},
   240  				},
   241  			},
   242  			accept: acceptValid,
   243  		},
   244  		{
   245  			dialer: Dialer{
   246  				Protocols: []string{"xml", "json", "soap"},
   247  				Extensions: []httphead.Option{
   248  					httphead.NewOption("foo", map[string]string{
   249  						"bar": "1",
   250  					}),
   251  					httphead.NewOption("baz", nil),
   252  				},
   253  			},
   254  			res: &http.Response{
   255  				StatusCode: 101,
   256  				ProtoMajor: 1,
   257  				ProtoMinor: 1,
   258  				Header: http.Header{
   259  					headerConnection:    []string{"Upgrade"},
   260  					headerUpgrade:       []string{"websocket"},
   261  					headerSecProtocol:   []string{"json"},
   262  					headerSecExtensions: []string{"foo;bar=1"},
   263  				},
   264  			},
   265  			accept: acceptValid,
   266  		},
   267  		{
   268  			name: "resp with frames",
   269  			res: &http.Response{
   270  				StatusCode: 101,
   271  				ProtoMajor: 1,
   272  				ProtoMinor: 1,
   273  				Header: http.Header{
   274  					headerConnection: []string{"Upgrade"},
   275  					headerUpgrade:    []string{"websocket"},
   276  				},
   277  			},
   278  			accept: acceptValid,
   279  			frames: []Frame{
   280  				NewTextFrame([]byte("hello, gopherizer!")),
   281  			},
   282  			wantBuffer: true,
   283  		},
   284  		{
   285  			name: "resp with body",
   286  			res: &http.Response{
   287  				StatusCode: 101,
   288  				ProtoMajor: 1,
   289  				ProtoMinor: 1,
   290  				Header: http.Header{
   291  					headerConnection: []string{"Upgrade"},
   292  					headerUpgrade:    []string{"websocket"},
   293  				},
   294  				Body:          ioutil.NopCloser(bytes.NewReader([]byte(`hello, gopher!`))),
   295  				ContentLength: 14,
   296  			},
   297  			accept:     acceptValid,
   298  			wantBuffer: true,
   299  		},
   300  
   301  		// Error cases.
   302  
   303  		{
   304  			name: "bad proto",
   305  			res: &http.Response{
   306  				StatusCode: 101,
   307  				ProtoMajor: 2,
   308  				ProtoMinor: 1,
   309  				Header:     make(http.Header),
   310  			},
   311  			err: ErrHandshakeBadProtocol,
   312  		},
   313  		{
   314  			name: "bad status",
   315  			res: &http.Response{
   316  				StatusCode: 400,
   317  				ProtoMajor: 1,
   318  				ProtoMinor: 1,
   319  				Header:     make(http.Header),
   320  			},
   321  			err:        StatusError(400),
   322  			wantBuffer: false,
   323  		},
   324  		{
   325  			name: "bad status with body",
   326  			res: &http.Response{
   327  				StatusCode: 400,
   328  				ProtoMajor: 1,
   329  				ProtoMinor: 1,
   330  				Header:     make(http.Header),
   331  				Body: ioutil.NopCloser(bytes.NewReader(
   332  					[]byte(`<error description here>`),
   333  				)),
   334  				ContentLength: 24,
   335  			},
   336  			err:        StatusError(400),
   337  			wantBuffer: false,
   338  		},
   339  		{
   340  			name: "bad upgrade",
   341  			res: &http.Response{
   342  				StatusCode: 101,
   343  				ProtoMajor: 1,
   344  				ProtoMinor: 1,
   345  				Header: http.Header{
   346  					headerConnection: []string{"Upgrade"},
   347  				},
   348  			},
   349  			accept: acceptValid,
   350  			err:    ErrHandshakeBadUpgrade,
   351  		},
   352  		{
   353  			name: "bad upgrade",
   354  			res: &http.Response{
   355  				StatusCode: 101,
   356  				ProtoMajor: 1,
   357  				ProtoMinor: 1,
   358  				Header: http.Header{
   359  					headerConnection: []string{"Upgrade"},
   360  					headerUpgrade:    []string{"oops"},
   361  				},
   362  			},
   363  			accept: acceptValid,
   364  			err:    ErrHandshakeBadUpgrade,
   365  		},
   366  		{
   367  			name: "bad connection",
   368  			res: &http.Response{
   369  				StatusCode: 101,
   370  				ProtoMajor: 1,
   371  				ProtoMinor: 1,
   372  				Header: http.Header{
   373  					headerUpgrade: []string{"websocket"},
   374  				},
   375  			},
   376  			accept: acceptValid,
   377  			err:    ErrHandshakeBadConnection,
   378  		},
   379  		{
   380  			name: "bad connection",
   381  			res: &http.Response{
   382  				StatusCode: 101,
   383  				ProtoMajor: 1,
   384  				ProtoMinor: 1,
   385  				Header: http.Header{
   386  					headerConnection: []string{"oops!"},
   387  					headerUpgrade:    []string{"websocket"},
   388  				},
   389  			},
   390  			accept: acceptValid,
   391  			err:    ErrHandshakeBadConnection,
   392  		},
   393  		{
   394  			name: "bad accept",
   395  			res: &http.Response{
   396  				StatusCode: 101,
   397  				ProtoMajor: 1,
   398  				ProtoMinor: 1,
   399  				Header: http.Header{
   400  					headerConnection: []string{"Upgrade"},
   401  					headerUpgrade:    []string{"websocket"},
   402  				},
   403  			},
   404  			accept: acceptInvalid,
   405  			err:    ErrHandshakeBadSecAccept,
   406  		},
   407  		{
   408  			name: "bad accept",
   409  			res: &http.Response{
   410  				StatusCode: 101,
   411  				ProtoMajor: 1,
   412  				ProtoMinor: 1,
   413  				Header: http.Header{
   414  					headerConnection: []string{"Upgrade"},
   415  					headerUpgrade:    []string{"websocket"},
   416  				},
   417  			},
   418  			accept: acceptNo,
   419  			err:    ErrHandshakeBadSecAccept,
   420  		},
   421  		{
   422  			name: "bad subprotocol",
   423  			res: &http.Response{
   424  				StatusCode: 101,
   425  				ProtoMajor: 1,
   426  				ProtoMinor: 1,
   427  				Header: http.Header{
   428  					headerConnection:  []string{"Upgrade"},
   429  					headerUpgrade:     []string{"websocket"},
   430  					headerSecProtocol: []string{"oops!"},
   431  				},
   432  			},
   433  			accept: acceptValid,
   434  			err:    ErrHandshakeBadSubProtocol,
   435  		},
   436  		{
   437  			name: "bad extensions",
   438  			res: &http.Response{
   439  				StatusCode: 101,
   440  				ProtoMajor: 1,
   441  				ProtoMinor: 1,
   442  				Header: http.Header{
   443  					headerConnection:    []string{"Upgrade"},
   444  					headerUpgrade:       []string{"websocket"},
   445  					headerSecExtensions: []string{"foo,bar;baz=1"},
   446  				},
   447  			},
   448  			accept: acceptValid,
   449  			err:    ErrHandshakeBadExtensions,
   450  		},
   451  		{
   452  			name: "bad extensions",
   453  			dialer: Dialer{
   454  				Extensions: []httphead.Option{
   455  					httphead.NewOption("foo", map[string]string{
   456  						"bar": "1",
   457  					}),
   458  				},
   459  			},
   460  			res: &http.Response{
   461  				StatusCode: 101,
   462  				ProtoMajor: 1,
   463  				ProtoMinor: 1,
   464  				Header: http.Header{
   465  					headerConnection:    []string{"Upgrade"},
   466  					headerUpgrade:       []string{"websocket"},
   467  					headerSecExtensions: []string{"foo;bar=2"},
   468  				},
   469  			},
   470  			accept: acceptValid,
   471  			err:    ErrHandshakeBadExtensions,
   472  		},
   473  	} {
   474  		t.Run(test.name, func(t *testing.T) {
   475  			client, server := net.Pipe()
   476  			go func() {
   477  				// This routine is our fake web-server. It reads request after
   478  				// client wrote it. Then it optionally could send some frames
   479  				// set in test case.
   480  				req, err := http.ReadRequest(bufio.NewReader(client))
   481  				if err != nil {
   482  					t.Fatal(err)
   483  				}
   484  
   485  				switch test.accept {
   486  				case acceptInvalid:
   487  					k := make([]byte, nonceSize)
   488  					rand.Read(k)
   489  					nonce := string(k)
   490  					accept := makeAccept(strToBytes(nonce))
   491  					test.res.Header.Set(headerSecAccept, string(accept))
   492  				case acceptValid:
   493  					nonce := req.Header.Get(headerSecKey)
   494  					accept := makeAccept(strToBytes(nonce))
   495  					test.res.Header.Set(headerSecAccept, string(accept))
   496  				}
   497  
   498  				test.res.Request = req
   499  				bts := dumpResponse(test.res)
   500  
   501  				var buf bytes.Buffer
   502  				for _, f := range test.frames {
   503  					if err := WriteFrame(&buf, f); err != nil {
   504  						t.Fatal(err)
   505  					}
   506  					bts = append(bts, buf.Bytes()...)
   507  					buf.Reset()
   508  				}
   509  
   510  				client.Write(bts)
   511  				client.Close()
   512  			}()
   513  
   514  			conn := &stubConn{
   515  				read: func(p []byte) (int, error) {
   516  					return server.Read(p)
   517  				},
   518  				write: func(p []byte) (int, error) {
   519  					n, err := server.Write(p)
   520  					return n, err
   521  				},
   522  				close: func() error { return nil },
   523  			}
   524  
   525  			test.dialer.NetDial = func(_ context.Context, _, _ string) (net.Conn, error) {
   526  				return conn, nil
   527  			}
   528  			test.dialer.OnStatusError = func(status int, reason []byte, r io.Reader) {
   529  				res, err := http.ReadResponse(
   530  					bufio.NewReader(r),
   531  					nil,
   532  				)
   533  				if err != nil {
   534  					t.Errorf("read response inside OnStatusError error: %v", err)
   535  				}
   536  				if act, exp := dumpResponse(res), dumpResponse(test.res); !bytes.Equal(act, exp) {
   537  					t.Errorf(
   538  						"unexpected response from OnStatusError:\nact:\n%s\nexp:\n%s\n",
   539  						act, exp,
   540  					)
   541  				}
   542  			}
   543  
   544  			_, br, _, err := test.dialer.Dial(context.Background(), "ws://ezoic.com")
   545  			if test.err != err {
   546  				t.Fatalf("unexpected error: %v;\n\twant %v", err, test.err)
   547  			}
   548  
   549  			if (test.wantBuffer || len(test.frames) > 0) && br == nil {
   550  				t.Fatalf("Dial() returned empty bufio.Reader")
   551  			}
   552  			if !test.wantBuffer && br != nil {
   553  				t.Fatalf("Dial() returned non-empty bufio.Reader")
   554  			}
   555  			for i, exp := range test.frames {
   556  				act, err := ReadFrame(br)
   557  				if err != nil {
   558  					t.Fatalf("can not read %d-th frame: %v", i, err)
   559  				}
   560  				if act.Header != exp.Header {
   561  					t.Fatalf(
   562  						"unexpected %d-th frame header: %v; want %v",
   563  						i, act.Header, exp.Header,
   564  					)
   565  				}
   566  				if !bytes.Equal(act.Payload, exp.Payload) {
   567  					t.Fatalf(
   568  						"unexpected %d-th frame payload:\n%v\nwant:\n%v",
   569  						i, act.Payload, exp.Payload,
   570  					)
   571  				}
   572  			}
   573  		})
   574  	}
   575  }
   576  
   577  // Used to emulate net.Error behavior, which is usually returned when
   578  // connection deadline exceeds.
   579  type errTimeout struct {
   580  	error
   581  }
   582  
   583  func (errTimeout) Timeout() bool   { return true }
   584  func (errTimeout) Temporary() bool { return false }
   585  
   586  func TestDialerCancelation(t *testing.T) {
   587  	ioErrDeadline := errTimeout{
   588  		fmt.Errorf("stub: i/o timeout"),
   589  	}
   590  
   591  	for _, test := range []struct {
   592  		name           string
   593  		dialer         Dialer
   594  		dialDelay      time.Duration
   595  		ctxTimeout     time.Duration
   596  		ctxCancelAfter time.Duration
   597  		err            error
   598  	}{
   599  		{
   600  			ctxTimeout: time.Millisecond * 100,
   601  			err:        context.DeadlineExceeded,
   602  		},
   603  		{
   604  			ctxCancelAfter: time.Millisecond * 100,
   605  			err:            context.Canceled,
   606  		},
   607  		{
   608  			dialer: Dialer{
   609  				Timeout: time.Millisecond * 100,
   610  			},
   611  			ctxTimeout: time.Millisecond * 150,
   612  			err:        context.DeadlineExceeded,
   613  		},
   614  		{
   615  			ctxTimeout: time.Millisecond * 100,
   616  			dialDelay:  time.Millisecond * 200,
   617  			err:        context.DeadlineExceeded,
   618  		},
   619  		{
   620  			ctxCancelAfter: time.Millisecond * 100,
   621  			dialDelay:      time.Millisecond * 200,
   622  			err:            context.Canceled,
   623  		},
   624  	} {
   625  		t.Run(test.name, func(t *testing.T) {
   626  			var timer *time.Timer
   627  			deadline := make(chan error, 10)
   628  			conn := &stubConn{
   629  				setDeadline: func(t time.Time) error {
   630  					if timer != nil {
   631  						timer.Stop()
   632  					}
   633  					if t.IsZero() {
   634  						return nil
   635  					}
   636  					d := t.Sub(time.Now())
   637  					if d < 0 {
   638  						deadline <- ioErrDeadline
   639  					} else {
   640  						timer = time.AfterFunc(d, func() {
   641  							deadline <- ioErrDeadline
   642  						})
   643  					}
   644  
   645  					return nil
   646  				},
   647  				read: func(p []byte) (int, error) {
   648  					if err := <-deadline; err != nil {
   649  						return 0, err
   650  					}
   651  					return len(p), nil
   652  				},
   653  				write: func(p []byte) (int, error) {
   654  					if err := <-deadline; err != nil {
   655  						return 0, err
   656  					}
   657  					return len(p), nil
   658  				},
   659  			}
   660  
   661  			test.dialer.NetDial = func(ctx context.Context, _, _ string) (net.Conn, error) {
   662  				if t := test.dialDelay; t != 0 {
   663  					delay := time.After(t)
   664  					select {
   665  					case <-delay:
   666  					case <-ctx.Done():
   667  						return nil, ctx.Err()
   668  					}
   669  				}
   670  				return conn, nil
   671  			}
   672  
   673  			ctx := context.Background()
   674  			if t := test.ctxTimeout; t != 0 {
   675  				var cancel context.CancelFunc
   676  				ctx, cancel = context.WithTimeout(ctx, t)
   677  				defer cancel()
   678  			}
   679  			if t := test.ctxCancelAfter; t != 0 {
   680  				var cancel context.CancelFunc
   681  				ctx, cancel = context.WithCancel(ctx)
   682  				time.AfterFunc(t, cancel)
   683  			}
   684  
   685  			_, _, _, err := test.dialer.Dial(ctx, "ws://ezoic.com")
   686  			if err != test.err {
   687  				t.Fatalf("unexpected error: %q; want %q", err, test.err)
   688  			}
   689  		})
   690  	}
   691  }
   692  
   693  func BenchmarkDialer(b *testing.B) {
   694  	for _, test := range []struct {
   695  		dialer Dialer
   696  	}{
   697  		{
   698  			dialer: DefaultDialer,
   699  		},
   700  	} {
   701  		// We need to "mock" the rand.Read method used to generate nonce random
   702  		// bytes for Sec-WebSocket-Key header.
   703  		rand.Seed(0)
   704  		need := b.N * nonceKeySize
   705  		nonceBytes := make([]byte, need)
   706  		n, err := rand.Read(nonceBytes)
   707  		if err != nil {
   708  			b.Fatal(err)
   709  		}
   710  		if n != need {
   711  			b.Fatalf("not enough random nonce bytes: %d; want %d", n, need)
   712  		}
   713  		rand.Seed(0)
   714  
   715  		resp := &http.Response{
   716  			StatusCode: 101,
   717  			ProtoMajor: 1,
   718  			ProtoMinor: 1,
   719  			Header: http.Header{
   720  				headerConnection: []string{"Upgrade"},
   721  				headerUpgrade:    []string{"websocket"},
   722  				headerSecAccept:  []string{"fill it later"},
   723  			},
   724  		}
   725  		rs := make([][]byte, b.N)
   726  		for i := range rs {
   727  			nonce := make([]byte, nonceSize)
   728  			base64.StdEncoding.Encode(
   729  				nonce,
   730  				nonceBytes[i*nonceKeySize:i*nonceKeySize+nonceKeySize],
   731  			)
   732  			accept := makeAccept(nonce)
   733  			resp.Header[headerSecAccept] = []string{string(accept)}
   734  			rs[i] = dumpResponse(resp)
   735  		}
   736  
   737  		var i int
   738  		conn := stubConn{
   739  			read: func(p []byte) (int, error) {
   740  				bts := rs[i]
   741  				if len(p) < len(bts) {
   742  					b.Fatalf("short buffer")
   743  				}
   744  				return copy(p, bts), io.EOF
   745  			},
   746  			write: func(p []byte) (int, error) {
   747  				return len(p), nil
   748  			},
   749  		}
   750  		var nc net.Conn = conn
   751  		test.dialer.NetDial = func(_ context.Context, net, addr string) (net.Conn, error) {
   752  			return nc, nil
   753  		}
   754  
   755  		b.ResetTimer()
   756  		for i = 0; i < b.N; i++ {
   757  			_, _, _, err := test.dialer.Dial(context.Background(), "ws://example.org")
   758  			if err != nil {
   759  				b.Fatal(err)
   760  			}
   761  		}
   762  	}
   763  }
   764  
   765  func TestHostPort(t *testing.T) {
   766  	for _, test := range []struct {
   767  		name string
   768  		host string
   769  		port string
   770  
   771  		expHostname string
   772  		expHostport string
   773  	}{
   774  		{
   775  			host:        "foo",
   776  			port:        ":80",
   777  			expHostname: "foo",
   778  			expHostport: "foo:80",
   779  		},
   780  		{
   781  			host:        "foo:1234",
   782  			port:        ":80",
   783  			expHostname: "foo",
   784  			expHostport: "foo:1234",
   785  		},
   786  		{
   787  			name:        "ipv4",
   788  			host:        "127.0.0.1",
   789  			port:        ":80",
   790  			expHostname: "127.0.0.1",
   791  			expHostport: "127.0.0.1:80",
   792  		},
   793  		{
   794  			name:        "ipv4",
   795  			host:        "127.0.0.1:1234",
   796  			port:        ":80",
   797  			expHostname: "127.0.0.1",
   798  			expHostport: "127.0.0.1:1234",
   799  		},
   800  		{
   801  			name:        "ipv6",
   802  			host:        "[0:0:0:0:0:0:0:1]",
   803  			port:        ":80",
   804  			expHostname: "[0:0:0:0:0:0:0:1]",
   805  			expHostport: "[0:0:0:0:0:0:0:1]:80",
   806  		},
   807  		{
   808  			name:        "ipv6",
   809  			host:        "[0:0:0:0:0:0:0:1]:1234",
   810  			port:        ":80",
   811  			expHostname: "[0:0:0:0:0:0:0:1]",
   812  			expHostport: "[0:0:0:0:0:0:0:1]:1234",
   813  		},
   814  	} {
   815  		t.Run(test.name, func(t *testing.T) {
   816  			actHostname, actHostport := hostport(test.host, test.port)
   817  			if actHostname != test.expHostname {
   818  				t.Errorf(
   819  					"actual hostname = %q; want %q",
   820  					actHostname, test.expHostname,
   821  				)
   822  			}
   823  			if actHostport != test.expHostport {
   824  				t.Errorf(
   825  					"actual hostname = %q; want %q",
   826  					actHostport, test.expHostport,
   827  				)
   828  			}
   829  		})
   830  	}
   831  }
   832  
   833  type stubConn struct {
   834  	read             func([]byte) (int, error)
   835  	write            func([]byte) (int, error)
   836  	close            func() error
   837  	setDeadline      func(time.Time) error
   838  	setWriteDeadline func(time.Time) error
   839  	setReadDeadline  func(time.Time) error
   840  }
   841  
   842  func (s stubConn) Read(p []byte) (int, error)  { return s.read(p) }
   843  func (s stubConn) Write(p []byte) (int, error) { return s.write(p) }
   844  func (s stubConn) LocalAddr() net.Addr         { return nil }
   845  func (s stubConn) RemoteAddr() net.Addr        { return nil }
   846  func (s stubConn) Close() error {
   847  	if s.close != nil {
   848  		return s.close()
   849  	}
   850  	return nil
   851  }
   852  func (s stubConn) SetDeadline(t time.Time) error {
   853  	if s.setDeadline != nil {
   854  		return s.setDeadline(t)
   855  	}
   856  	return nil
   857  }
   858  func (s stubConn) SetReadDeadline(t time.Time) error {
   859  	if s.setReadDeadline != nil {
   860  		return s.setReadDeadline(t)
   861  	}
   862  	return nil
   863  }
   864  func (s stubConn) SetWriteDeadline(t time.Time) error {
   865  	if s.setWriteDeadline != nil {
   866  		return s.setWriteDeadline(t)
   867  	}
   868  	return nil
   869  }