github.com/simonmittag/ws@v1.1.0-rc.5.0.20210419231947-82b846128245/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/gobwas/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  		t.Run(test.name, func(t *testing.T) {
   453  			client, server := net.Pipe()
   454  			go func() {
   455  				// This routine is our fake web-server. It reads request after
   456  				// client wrote it. Then it optionally could send some frames
   457  				// set in test case.
   458  				req, err := http.ReadRequest(bufio.NewReader(client))
   459  				if err != nil {
   460  					t.Fatal(err)
   461  				}
   462  
   463  				switch test.accept {
   464  				case acceptInvalid:
   465  					k := make([]byte, nonceSize)
   466  					rand.Read(k)
   467  					nonce := string(k)
   468  					accept := makeAccept(strToBytes(nonce))
   469  					test.res.Header.Set(headerSecAccept, string(accept))
   470  				case acceptValid:
   471  					nonce := req.Header.Get(headerSecKey)
   472  					accept := makeAccept(strToBytes(nonce))
   473  					test.res.Header.Set(headerSecAccept, string(accept))
   474  				}
   475  
   476  				test.res.Request = req
   477  				bts := dumpResponse(test.res)
   478  
   479  				var buf bytes.Buffer
   480  				for _, f := range test.frames {
   481  					if err := WriteFrame(&buf, f); err != nil {
   482  						t.Fatal(err)
   483  					}
   484  					bts = append(bts, buf.Bytes()...)
   485  					buf.Reset()
   486  				}
   487  
   488  				client.Write(bts)
   489  				client.Close()
   490  			}()
   491  
   492  			conn := &stubConn{
   493  				read: func(p []byte) (int, error) {
   494  					return server.Read(p)
   495  				},
   496  				write: func(p []byte) (int, error) {
   497  					n, err := server.Write(p)
   498  					return n, err
   499  				},
   500  				close: func() error { return nil },
   501  			}
   502  
   503  			test.dialer.NetDial = func(_ context.Context, _, _ string) (net.Conn, error) {
   504  				return conn, nil
   505  			}
   506  			test.dialer.OnStatusError = func(status int, reason []byte, r io.Reader) {
   507  				res, err := http.ReadResponse(
   508  					bufio.NewReader(r),
   509  					nil,
   510  				)
   511  				if err != nil {
   512  					t.Errorf("read response inside OnStatusError error: %v", err)
   513  				}
   514  				if act, exp := dumpResponse(res), dumpResponse(test.res); !bytes.Equal(act, exp) {
   515  					t.Errorf(
   516  						"unexpected response from OnStatusError:\nact:\n%s\nexp:\n%s\n",
   517  						act, exp,
   518  					)
   519  				}
   520  			}
   521  
   522  			_, br, _, err := test.dialer.Dial(context.Background(), "ws://gobwas.com")
   523  			if test.err != err {
   524  				t.Fatalf("unexpected error: %v;\n\twant %v", err, test.err)
   525  			}
   526  
   527  			if (test.wantBuffer || len(test.frames) > 0) && br == nil {
   528  				t.Fatalf("Dial() returned empty bufio.Reader")
   529  			}
   530  			if !test.wantBuffer && br != nil {
   531  				t.Fatalf("Dial() returned non-empty bufio.Reader")
   532  			}
   533  			for i, exp := range test.frames {
   534  				act, err := ReadFrame(br)
   535  				if err != nil {
   536  					t.Fatalf("can not read %d-th frame: %v", i, err)
   537  				}
   538  				if act.Header != exp.Header {
   539  					t.Fatalf(
   540  						"unexpected %d-th frame header: %v; want %v",
   541  						i, act.Header, exp.Header,
   542  					)
   543  				}
   544  				if !bytes.Equal(act.Payload, exp.Payload) {
   545  					t.Fatalf(
   546  						"unexpected %d-th frame payload:\n%v\nwant:\n%v",
   547  						i, act.Payload, exp.Payload,
   548  					)
   549  				}
   550  			}
   551  		})
   552  	}
   553  }
   554  
   555  // Used to emulate net.Error behavior, which is usually returned when
   556  // connection deadline exceeds.
   557  type errTimeout struct {
   558  	error
   559  }
   560  
   561  func (errTimeout) Timeout() bool   { return true }
   562  func (errTimeout) Temporary() bool { return false }
   563  
   564  func TestDialerCancelation(t *testing.T) {
   565  	ioErrDeadline := errTimeout{
   566  		fmt.Errorf("stub: i/o timeout"),
   567  	}
   568  
   569  	for _, test := range []struct {
   570  		name           string
   571  		dialer         Dialer
   572  		dialDelay      time.Duration
   573  		ctxTimeout     time.Duration
   574  		ctxCancelAfter time.Duration
   575  		err            error
   576  	}{
   577  		{
   578  			ctxTimeout: time.Millisecond * 100,
   579  			err:        context.DeadlineExceeded,
   580  		},
   581  		{
   582  			ctxCancelAfter: time.Millisecond * 100,
   583  			err:            context.Canceled,
   584  		},
   585  		{
   586  			dialer: Dialer{
   587  				Timeout: time.Millisecond * 100,
   588  			},
   589  			ctxTimeout: time.Millisecond * 150,
   590  			err:        context.DeadlineExceeded,
   591  		},
   592  		{
   593  			ctxTimeout: time.Millisecond * 100,
   594  			dialDelay:  time.Millisecond * 200,
   595  			err:        context.DeadlineExceeded,
   596  		},
   597  		{
   598  			ctxCancelAfter: time.Millisecond * 100,
   599  			dialDelay:      time.Millisecond * 200,
   600  			err:            context.Canceled,
   601  		},
   602  	} {
   603  		t.Run(test.name, func(t *testing.T) {
   604  			var timer *time.Timer
   605  			deadline := make(chan error, 10)
   606  			conn := &stubConn{
   607  				setDeadline: func(t time.Time) error {
   608  					if timer != nil {
   609  						timer.Stop()
   610  					}
   611  					if t.IsZero() {
   612  						return nil
   613  					}
   614  					d := t.Sub(time.Now())
   615  					if d < 0 {
   616  						deadline <- ioErrDeadline
   617  					} else {
   618  						timer = time.AfterFunc(d, func() {
   619  							deadline <- ioErrDeadline
   620  						})
   621  					}
   622  
   623  					return nil
   624  				},
   625  				read: func(p []byte) (int, error) {
   626  					if err := <-deadline; err != nil {
   627  						return 0, err
   628  					}
   629  					return len(p), nil
   630  				},
   631  				write: func(p []byte) (int, error) {
   632  					if err := <-deadline; err != nil {
   633  						return 0, err
   634  					}
   635  					return len(p), nil
   636  				},
   637  			}
   638  
   639  			test.dialer.NetDial = func(ctx context.Context, _, _ string) (net.Conn, error) {
   640  				if t := test.dialDelay; t != 0 {
   641  					delay := time.After(t)
   642  					select {
   643  					case <-delay:
   644  					case <-ctx.Done():
   645  						return nil, ctx.Err()
   646  					}
   647  				}
   648  				return conn, nil
   649  			}
   650  
   651  			ctx := context.Background()
   652  			if t := test.ctxTimeout; t != 0 {
   653  				var cancel context.CancelFunc
   654  				ctx, cancel = context.WithTimeout(ctx, t)
   655  				defer cancel()
   656  			}
   657  			if t := test.ctxCancelAfter; t != 0 {
   658  				var cancel context.CancelFunc
   659  				ctx, cancel = context.WithCancel(ctx)
   660  				time.AfterFunc(t, cancel)
   661  			}
   662  
   663  			_, _, _, err := test.dialer.Dial(ctx, "ws://gobwas.com")
   664  			if err != test.err {
   665  				t.Fatalf("unexpected error: %q; want %q", err, test.err)
   666  			}
   667  		})
   668  	}
   669  }
   670  
   671  func BenchmarkDialer(b *testing.B) {
   672  	for _, test := range []struct {
   673  		dialer Dialer
   674  	}{
   675  		{
   676  			dialer: DefaultDialer,
   677  		},
   678  	} {
   679  		// We need to "mock" the rand.Read method used to generate nonce random
   680  		// bytes for Sec-WebSocket-Key header.
   681  		rand.Seed(0)
   682  		need := b.N * nonceKeySize
   683  		nonceBytes := make([]byte, need)
   684  		n, err := rand.Read(nonceBytes)
   685  		if err != nil {
   686  			b.Fatal(err)
   687  		}
   688  		if n != need {
   689  			b.Fatalf("not enough random nonce bytes: %d; want %d", n, need)
   690  		}
   691  		rand.Seed(0)
   692  
   693  		resp := &http.Response{
   694  			StatusCode: 101,
   695  			ProtoMajor: 1,
   696  			ProtoMinor: 1,
   697  			Header: http.Header{
   698  				headerConnection: []string{"Upgrade"},
   699  				headerUpgrade:    []string{"websocket"},
   700  				headerSecAccept:  []string{"fill it later"},
   701  			},
   702  		}
   703  		rs := make([][]byte, b.N)
   704  		for i := range rs {
   705  			nonce := make([]byte, nonceSize)
   706  			base64.StdEncoding.Encode(
   707  				nonce,
   708  				nonceBytes[i*nonceKeySize:i*nonceKeySize+nonceKeySize],
   709  			)
   710  			accept := makeAccept(nonce)
   711  			resp.Header[headerSecAccept] = []string{string(accept)}
   712  			rs[i] = dumpResponse(resp)
   713  		}
   714  
   715  		var i int
   716  		conn := stubConn{
   717  			read: func(p []byte) (int, error) {
   718  				bts := rs[i]
   719  				if len(p) < len(bts) {
   720  					b.Fatalf("short buffer")
   721  				}
   722  				return copy(p, bts), io.EOF
   723  			},
   724  			write: func(p []byte) (int, error) {
   725  				return len(p), nil
   726  			},
   727  		}
   728  		var nc net.Conn = conn
   729  		test.dialer.NetDial = func(_ context.Context, net, addr string) (net.Conn, error) {
   730  			return nc, nil
   731  		}
   732  
   733  		b.ResetTimer()
   734  		for i = 0; i < b.N; i++ {
   735  			_, _, _, err := test.dialer.Dial(context.Background(), "ws://example.org")
   736  			if err != nil {
   737  				b.Fatal(err)
   738  			}
   739  		}
   740  	}
   741  }
   742  
   743  func TestHostPort(t *testing.T) {
   744  	for _, test := range []struct {
   745  		name string
   746  		host string
   747  		port string
   748  
   749  		expHostname string
   750  		expHostport string
   751  	}{
   752  		{
   753  			host:        "foo",
   754  			port:        ":80",
   755  			expHostname: "foo",
   756  			expHostport: "foo:80",
   757  		},
   758  		{
   759  			host:        "foo:1234",
   760  			port:        ":80",
   761  			expHostname: "foo",
   762  			expHostport: "foo:1234",
   763  		},
   764  		{
   765  			name:        "ipv4",
   766  			host:        "127.0.0.1",
   767  			port:        ":80",
   768  			expHostname: "127.0.0.1",
   769  			expHostport: "127.0.0.1:80",
   770  		},
   771  		{
   772  			name:        "ipv4",
   773  			host:        "127.0.0.1:1234",
   774  			port:        ":80",
   775  			expHostname: "127.0.0.1",
   776  			expHostport: "127.0.0.1:1234",
   777  		},
   778  		{
   779  			name:        "ipv6",
   780  			host:        "[0:0:0:0:0:0:0:1]",
   781  			port:        ":80",
   782  			expHostname: "[0:0:0:0:0:0:0:1]",
   783  			expHostport: "[0:0:0:0:0:0:0:1]:80",
   784  		},
   785  		{
   786  			name:        "ipv6",
   787  			host:        "[0:0:0:0:0:0:0:1]:1234",
   788  			port:        ":80",
   789  			expHostname: "[0:0:0:0:0:0:0:1]",
   790  			expHostport: "[0:0:0:0:0:0:0:1]:1234",
   791  		},
   792  	} {
   793  		t.Run(test.name, func(t *testing.T) {
   794  			actHostname, actHostport := hostport(test.host, test.port)
   795  			if actHostname != test.expHostname {
   796  				t.Errorf(
   797  					"actual hostname = %q; want %q",
   798  					actHostname, test.expHostname,
   799  				)
   800  			}
   801  			if actHostport != test.expHostport {
   802  				t.Errorf(
   803  					"actual hostname = %q; want %q",
   804  					actHostport, test.expHostport,
   805  				)
   806  			}
   807  		})
   808  	}
   809  }
   810  
   811  type stubConn struct {
   812  	read             func([]byte) (int, error)
   813  	write            func([]byte) (int, error)
   814  	close            func() error
   815  	setDeadline      func(time.Time) error
   816  	setWriteDeadline func(time.Time) error
   817  	setReadDeadline  func(time.Time) error
   818  }
   819  
   820  func (s stubConn) Read(p []byte) (int, error)  { return s.read(p) }
   821  func (s stubConn) Write(p []byte) (int, error) { return s.write(p) }
   822  func (s stubConn) LocalAddr() net.Addr         { return nil }
   823  func (s stubConn) RemoteAddr() net.Addr        { return nil }
   824  func (s stubConn) Close() error {
   825  	if s.close != nil {
   826  		return s.close()
   827  	}
   828  	return nil
   829  }
   830  func (s stubConn) SetDeadline(t time.Time) error {
   831  	if s.setDeadline != nil {
   832  		return s.setDeadline(t)
   833  	}
   834  	return nil
   835  }
   836  func (s stubConn) SetReadDeadline(t time.Time) error {
   837  	if s.setReadDeadline != nil {
   838  		return s.setReadDeadline(t)
   839  	}
   840  	return nil
   841  }
   842  func (s stubConn) SetWriteDeadline(t time.Time) error {
   843  	if s.setWriteDeadline != nil {
   844  		return s.setWriteDeadline(t)
   845  	}
   846  	return nil
   847  }