github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/rpc/client_test.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package rpc
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"math/rand"
    24  	"net"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"os"
    28  	"reflect"
    29  	"runtime"
    30  	"strings"
    31  	"sync"
    32  	"testing"
    33  	"time"
    34  
    35  	"github.com/davecgh/go-spew/spew"
    36  
    37  	"github.com/ethereum/go-ethereum/log"
    38  )
    39  
    40  func TestClientRequest(t *testing.T) {
    41  	server := newTestServer()
    42  	defer server.Stop()
    43  
    44  	client := DialInProc(server)
    45  	defer client.Close()
    46  
    47  	var resp echoResult
    48  	if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
    49  		t.Fatal(err)
    50  	}
    51  
    52  	if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) {
    53  		t.Errorf("incorrect result %#v", resp)
    54  	}
    55  }
    56  
    57  func TestClientResponseType(t *testing.T) {
    58  	server := newTestServer()
    59  	defer server.Stop()
    60  	client := DialInProc(server)
    61  	defer client.Close()
    62  
    63  	if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
    64  		t.Errorf("Passing nil as result should be fine, but got an error: %v", err)
    65  	}
    66  	var resultVar echoResult
    67  	// Note: passing the var, not a ref
    68  	err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"})
    69  	if err == nil {
    70  		t.Error("Passing a var as result should be an error")
    71  	}
    72  }
    73  
    74  // This test checks that server-returned errors with code and data come out of Client.Call.
    75  func TestClientErrorData(t *testing.T) {
    76  	server := newTestServer()
    77  	defer server.Stop()
    78  	client := DialInProc(server)
    79  	defer client.Close()
    80  
    81  	var resp interface{}
    82  	err := client.Call(&resp, "test_returnError")
    83  	if err == nil {
    84  		t.Fatal("expected error")
    85  	}
    86  
    87  	// Check code.
    88  	if e, ok := err.(Error); !ok {
    89  		t.Fatalf("client did not return rpc.Error, got %#v", e)
    90  	} else if e.ErrorCode() != (testError{}.ErrorCode()) {
    91  		t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode())
    92  	}
    93  	// Check data.
    94  	if e, ok := err.(DataError); !ok {
    95  		t.Fatalf("client did not return rpc.DataError, got %#v", e)
    96  	} else if e.ErrorData() != (testError{}.ErrorData()) {
    97  		t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData())
    98  	}
    99  }
   100  
   101  func TestClientBatchRequest(t *testing.T) {
   102  	server := newTestServer()
   103  	defer server.Stop()
   104  	client := DialInProc(server)
   105  	defer client.Close()
   106  
   107  	batch := []BatchElem{
   108  		{
   109  			Method: "test_echo",
   110  			Args:   []interface{}{"hello", 10, &echoArgs{"world"}},
   111  			Result: new(echoResult),
   112  		},
   113  		{
   114  			Method: "test_echo",
   115  			Args:   []interface{}{"hello2", 11, &echoArgs{"world"}},
   116  			Result: new(echoResult),
   117  		},
   118  		{
   119  			Method: "no_such_method",
   120  			Args:   []interface{}{1, 2, 3},
   121  			Result: new(int),
   122  		},
   123  	}
   124  	if err := client.BatchCall(batch); err != nil {
   125  		t.Fatal(err)
   126  	}
   127  	wantResult := []BatchElem{
   128  		{
   129  			Method: "test_echo",
   130  			Args:   []interface{}{"hello", 10, &echoArgs{"world"}},
   131  			Result: &echoResult{"hello", 10, &echoArgs{"world"}},
   132  		},
   133  		{
   134  			Method: "test_echo",
   135  			Args:   []interface{}{"hello2", 11, &echoArgs{"world"}},
   136  			Result: &echoResult{"hello2", 11, &echoArgs{"world"}},
   137  		},
   138  		{
   139  			Method: "no_such_method",
   140  			Args:   []interface{}{1, 2, 3},
   141  			Result: new(int),
   142  			Error:  &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"},
   143  		},
   144  	}
   145  	if !reflect.DeepEqual(batch, wantResult) {
   146  		t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult))
   147  	}
   148  }
   149  
   150  func TestClientNotify(t *testing.T) {
   151  	server := newTestServer()
   152  	defer server.Stop()
   153  	client := DialInProc(server)
   154  	defer client.Close()
   155  
   156  	if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
   157  		t.Fatal(err)
   158  	}
   159  }
   160  
   161  // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) }
   162  func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
   163  func TestClientCancelHTTP(t *testing.T)      { testClientCancel("http", t) }
   164  func TestClientCancelIPC(t *testing.T)       { testClientCancel("ipc", t) }
   165  
   166  // This test checks that requests made through CallContext can be canceled by canceling
   167  // the context.
   168  func testClientCancel(transport string, t *testing.T) {
   169  	// These tests take a lot of time, run them all at once.
   170  	// You probably want to run with -parallel 1 or comment out
   171  	// the call to t.Parallel if you enable the logging.
   172  	t.Parallel()
   173  
   174  	server := newTestServer()
   175  	defer server.Stop()
   176  
   177  	// What we want to achieve is that the context gets canceled
   178  	// at various stages of request processing. The interesting cases
   179  	// are:
   180  	//  - cancel during dial
   181  	//  - cancel while performing a HTTP request
   182  	//  - cancel while waiting for a response
   183  	//
   184  	// To trigger those, the times are chosen such that connections
   185  	// are killed within the deadline for every other call (maxKillTimeout
   186  	// is 2x maxCancelTimeout).
   187  	//
   188  	// Once a connection is dead, there is a fair chance it won't connect
   189  	// successfully because the accept is delayed by 1s.
   190  	maxContextCancelTimeout := 300 * time.Millisecond
   191  	fl := &flakeyListener{
   192  		maxAcceptDelay: 1 * time.Second,
   193  		maxKillTimeout: 600 * time.Millisecond,
   194  	}
   195  
   196  	var client *Client
   197  	switch transport {
   198  	case "ws", "http":
   199  		c, hs := httpTestClient(server, transport, fl)
   200  		defer hs.Close()
   201  		client = c
   202  	case "ipc":
   203  		c, l := ipcTestClient(server, fl)
   204  		defer l.Close()
   205  		client = c
   206  	default:
   207  		panic("unknown transport: " + transport)
   208  	}
   209  
   210  	// The actual test starts here.
   211  	var (
   212  		wg       sync.WaitGroup
   213  		nreqs    = 10
   214  		ncallers = 10
   215  	)
   216  	caller := func(index int) {
   217  		defer wg.Done()
   218  		for i := 0; i < nreqs; i++ {
   219  			var (
   220  				ctx     context.Context
   221  				cancel  func()
   222  				timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout)))
   223  			)
   224  			if index < ncallers/2 {
   225  				// For half of the callers, create a context without deadline
   226  				// and cancel it later.
   227  				ctx, cancel = context.WithCancel(context.Background())
   228  				time.AfterFunc(timeout, cancel)
   229  			} else {
   230  				// For the other half, create a context with a deadline instead. This is
   231  				// different because the context deadline is used to set the socket write
   232  				// deadline.
   233  				ctx, cancel = context.WithTimeout(context.Background(), timeout)
   234  			}
   235  
   236  			// Now perform a call with the context.
   237  			// The key thing here is that no call will ever complete successfully.
   238  			err := client.CallContext(ctx, nil, "test_block")
   239  			switch {
   240  			case err == nil:
   241  				_, hasDeadline := ctx.Deadline()
   242  				t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline)
   243  				// default:
   244  				// 	t.Logf("got expected error with %v wait time: %v", timeout, err)
   245  			}
   246  			cancel()
   247  		}
   248  	}
   249  	wg.Add(ncallers)
   250  	for i := 0; i < ncallers; i++ {
   251  		go caller(i)
   252  	}
   253  	wg.Wait()
   254  }
   255  
   256  func TestClientSubscribeInvalidArg(t *testing.T) {
   257  	server := newTestServer()
   258  	defer server.Stop()
   259  	client := DialInProc(server)
   260  	defer client.Close()
   261  
   262  	check := func(shouldPanic bool, arg interface{}) {
   263  		defer func() {
   264  			err := recover()
   265  			if shouldPanic && err == nil {
   266  				t.Errorf("EthSubscribe should've panicked for %#v", arg)
   267  			}
   268  			if !shouldPanic && err != nil {
   269  				t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg)
   270  				buf := make([]byte, 1024*1024)
   271  				buf = buf[:runtime.Stack(buf, false)]
   272  				t.Error(err)
   273  				t.Error(string(buf))
   274  			}
   275  		}()
   276  		client.EthSubscribe(context.Background(), arg, "foo_bar")
   277  	}
   278  	check(true, nil)
   279  	check(true, 1)
   280  	check(true, (chan int)(nil))
   281  	check(true, make(<-chan int))
   282  	check(false, make(chan int))
   283  	check(false, make(chan<- int))
   284  }
   285  
   286  func TestClientSubscribe(t *testing.T) {
   287  	server := newTestServer()
   288  	defer server.Stop()
   289  	client := DialInProc(server)
   290  	defer client.Close()
   291  
   292  	nc := make(chan int)
   293  	count := 10
   294  	sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0)
   295  	if err != nil {
   296  		t.Fatal("can't subscribe:", err)
   297  	}
   298  	for i := 0; i < count; i++ {
   299  		if val := <-nc; val != i {
   300  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   301  		}
   302  	}
   303  
   304  	sub.Unsubscribe()
   305  	select {
   306  	case v := <-nc:
   307  		t.Fatal("received value after unsubscribe:", v)
   308  	case err := <-sub.Err():
   309  		if err != nil {
   310  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   311  		}
   312  	case <-time.After(1 * time.Second):
   313  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   314  	}
   315  }
   316  
   317  // In this test, the connection drops while Subscribe is waiting for a response.
   318  func TestClientSubscribeClose(t *testing.T) {
   319  	server := newTestServer()
   320  	service := &notificationTestService{
   321  		gotHangSubscriptionReq:  make(chan struct{}),
   322  		unblockHangSubscription: make(chan struct{}),
   323  	}
   324  	if err := server.RegisterName("nftest2", service); err != nil {
   325  		t.Fatal(err)
   326  	}
   327  
   328  	defer server.Stop()
   329  	client := DialInProc(server)
   330  	defer client.Close()
   331  
   332  	var (
   333  		nc   = make(chan int)
   334  		errc = make(chan error, 1)
   335  		sub  *ClientSubscription
   336  		err  error
   337  	)
   338  	go func() {
   339  		sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999)
   340  		errc <- err
   341  	}()
   342  
   343  	<-service.gotHangSubscriptionReq
   344  	client.Close()
   345  	service.unblockHangSubscription <- struct{}{}
   346  
   347  	select {
   348  	case err := <-errc:
   349  		if err == nil {
   350  			t.Errorf("Subscribe returned nil error after Close")
   351  		}
   352  		if sub != nil {
   353  			t.Error("Subscribe returned non-nil subscription after Close")
   354  		}
   355  	case <-time.After(1 * time.Second):
   356  		t.Fatalf("Subscribe did not return within 1s after Close")
   357  	}
   358  }
   359  
   360  // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the
   361  // client hangs during shutdown when Unsubscribe races with Client.Close.
   362  func TestClientCloseUnsubscribeRace(t *testing.T) {
   363  	server := newTestServer()
   364  	defer server.Stop()
   365  
   366  	for i := 0; i < 20; i++ {
   367  		client := DialInProc(server)
   368  		nc := make(chan int)
   369  		sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1)
   370  		if err != nil {
   371  			t.Fatal(err)
   372  		}
   373  		go client.Close()
   374  		go sub.Unsubscribe()
   375  		select {
   376  		case <-sub.Err():
   377  		case <-time.After(5 * time.Second):
   378  			t.Fatal("subscription not closed within timeout")
   379  		}
   380  	}
   381  }
   382  
   383  // unsubscribeRecorder collects the subscription IDs of *_unsubscribe calls.
   384  type unsubscribeRecorder struct {
   385  	ServerCodec
   386  	unsubscribes map[string]bool
   387  }
   388  
   389  func (r *unsubscribeRecorder) readBatch() ([]*jsonrpcMessage, bool, error) {
   390  	if r.unsubscribes == nil {
   391  		r.unsubscribes = make(map[string]bool)
   392  	}
   393  
   394  	msgs, batch, err := r.ServerCodec.readBatch()
   395  	for _, msg := range msgs {
   396  		if msg.isUnsubscribe() {
   397  			var params []string
   398  			if err := json.Unmarshal(msg.Params, &params); err != nil {
   399  				panic("unsubscribe decode error: " + err.Error())
   400  			}
   401  			r.unsubscribes[params[0]] = true
   402  		}
   403  	}
   404  	return msgs, batch, err
   405  }
   406  
   407  // This checks that Client calls the _unsubscribe method on the server when Unsubscribe is
   408  // called on a subscription.
   409  func TestClientSubscriptionUnsubscribeServer(t *testing.T) {
   410  	t.Parallel()
   411  
   412  	// Create the server.
   413  	srv := NewServer(0, 0)
   414  	srv.RegisterName("nftest", new(notificationTestService))
   415  	p1, p2 := net.Pipe()
   416  	recorder := &unsubscribeRecorder{ServerCodec: NewCodec(p1)}
   417  	go srv.ServeCodec(recorder, OptionMethodInvocation|OptionSubscriptions)
   418  	defer srv.Stop()
   419  
   420  	// Create the client on the other end of the pipe.
   421  	client, _ := newClient(context.Background(), func(context.Context) (ServerCodec, error) {
   422  		return NewCodec(p2), nil
   423  	})
   424  	defer client.Close()
   425  
   426  	// Create the subscription.
   427  	ch := make(chan int)
   428  	sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 1, 1)
   429  	if err != nil {
   430  		t.Fatal(err)
   431  	}
   432  
   433  	// Unsubscribe and check that unsubscribe was called.
   434  	sub.Unsubscribe()
   435  	if !recorder.unsubscribes[sub.subid] {
   436  		t.Fatal("client did not call unsubscribe method")
   437  	}
   438  	if _, open := <-sub.Err(); open {
   439  		t.Fatal("subscription error channel not closed after unsubscribe")
   440  	}
   441  }
   442  
   443  // This checks that the subscribed channel can be closed after Unsubscribe.
   444  // It is the reproducer for https://github.com/ethereum/go-ethereum/issues/22322
   445  func TestClientSubscriptionChannelClose(t *testing.T) {
   446  	t.Parallel()
   447  
   448  	var (
   449  		srv     = NewServer(0, 0)
   450  		httpsrv = httptest.NewServer(srv.WebsocketHandler(nil))
   451  		wsURL   = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:")
   452  	)
   453  	defer srv.Stop()
   454  	defer httpsrv.Close()
   455  
   456  	srv.RegisterName("nftest", new(notificationTestService))
   457  	client, _ := Dial(wsURL)
   458  
   459  	for i := 0; i < 100; i++ {
   460  		ch := make(chan int, 100)
   461  		sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1)
   462  		if err != nil {
   463  			t.Fatal(err)
   464  		}
   465  		sub.Unsubscribe()
   466  		close(ch)
   467  	}
   468  }
   469  
   470  // This test checks that Client doesn't lock up when a single subscriber
   471  // doesn't read subscription events.
   472  func TestClientNotificationStorm(t *testing.T) {
   473  	server := newTestServer()
   474  	defer server.Stop()
   475  
   476  	doTest := func(count int, wantError bool) {
   477  		client := DialInProc(server)
   478  		defer client.Close()
   479  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   480  		defer cancel()
   481  
   482  		// Subscribe on the server. It will start sending many notifications
   483  		// very quickly.
   484  		nc := make(chan int)
   485  		sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0)
   486  		if err != nil {
   487  			t.Fatal("can't subscribe:", err)
   488  		}
   489  		defer sub.Unsubscribe()
   490  
   491  		// Process each notification, try to run a call in between each of them.
   492  		for i := 0; i < count; i++ {
   493  			select {
   494  			case val := <-nc:
   495  				if val != i {
   496  					t.Fatalf("(%d/%d) unexpected value %d", i, count, val)
   497  				}
   498  			case err := <-sub.Err():
   499  				if wantError && err != ErrSubscriptionQueueOverflow {
   500  					t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow)
   501  				} else if !wantError {
   502  					t.Fatalf("(%d/%d) got unexpected error %q", i, count, err)
   503  				}
   504  				return
   505  			}
   506  			var r int
   507  			err := client.CallContext(ctx, &r, "nftest_echo", i)
   508  			if err != nil {
   509  				if !wantError {
   510  					t.Fatalf("(%d/%d) call error: %v", i, count, err)
   511  				}
   512  				return
   513  			}
   514  		}
   515  		if wantError {
   516  			t.Fatalf("didn't get expected error")
   517  		}
   518  	}
   519  
   520  	doTest(8000, false)
   521  	doTest(24000, true)
   522  }
   523  
   524  func TestClientSetHeader(t *testing.T) {
   525  	var gotHeader bool
   526  	srv := newTestServer()
   527  	httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   528  		if r.Header.Get("test") == "ok" {
   529  			gotHeader = true
   530  		}
   531  		srv.ServeHTTP(w, r)
   532  	}))
   533  	defer httpsrv.Close()
   534  	defer srv.Stop()
   535  
   536  	client, err := Dial(httpsrv.URL)
   537  	if err != nil {
   538  		t.Fatal(err)
   539  	}
   540  	defer client.Close()
   541  
   542  	client.SetHeader("test", "ok")
   543  	if _, err := client.SupportedModules(); err != nil {
   544  		t.Fatal(err)
   545  	}
   546  	if !gotHeader {
   547  		t.Fatal("client did not set custom header")
   548  	}
   549  
   550  	// Check that Content-Type can be replaced.
   551  	client.SetHeader("content-type", "application/x-garbage")
   552  	_, err = client.SupportedModules()
   553  	if err == nil {
   554  		t.Fatal("no error for invalid content-type header")
   555  	} else if !strings.Contains(err.Error(), "Unsupported Media Type") {
   556  		t.Fatalf("error is not related to content-type: %q", err)
   557  	}
   558  }
   559  
   560  func TestClientHTTP(t *testing.T) {
   561  	server := newTestServer()
   562  	defer server.Stop()
   563  
   564  	client, hs := httpTestClient(server, "http", nil)
   565  	defer hs.Close()
   566  	defer client.Close()
   567  
   568  	// Launch concurrent requests.
   569  	var (
   570  		results    = make([]echoResult, 100)
   571  		errc       = make(chan error, len(results))
   572  		wantResult = echoResult{"a", 1, new(echoArgs)}
   573  	)
   574  	defer client.Close()
   575  	for i := range results {
   576  		i := i
   577  		go func() {
   578  			errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args)
   579  		}()
   580  	}
   581  
   582  	// Wait for all of them to complete.
   583  	timeout := time.NewTimer(5 * time.Second)
   584  	defer timeout.Stop()
   585  	for i := range results {
   586  		select {
   587  		case err := <-errc:
   588  			if err != nil {
   589  				t.Fatal(err)
   590  			}
   591  		case <-timeout.C:
   592  			t.Fatalf("timeout (got %d/%d) results)", i+1, len(results))
   593  		}
   594  	}
   595  
   596  	// Check results.
   597  	for i := range results {
   598  		if !reflect.DeepEqual(results[i], wantResult) {
   599  			t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult)
   600  		}
   601  	}
   602  }
   603  
   604  func TestClientReconnect(t *testing.T) {
   605  	startServer := func(addr string) (*Server, net.Listener) {
   606  		srv := newTestServer()
   607  		l, err := net.Listen("tcp", addr)
   608  		if err != nil {
   609  			t.Fatal("can't listen:", err)
   610  		}
   611  		go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
   612  		return srv, l
   613  	}
   614  
   615  	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
   616  	defer cancel()
   617  
   618  	// Start a server and corresponding client.
   619  	s1, l1 := startServer("127.0.0.1:0")
   620  	client, err := DialContext(ctx, "ws://"+l1.Addr().String())
   621  	defer client.Close()
   622  	if err != nil {
   623  		t.Fatal("can't dial", err)
   624  	}
   625  
   626  	// Perform a call. This should work because the server is up.
   627  	var resp echoResult
   628  	if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
   629  		t.Fatal(err)
   630  	}
   631  
   632  	// Shut down the server and allow for some cool down time so we can listen on the same
   633  	// address again.
   634  	l1.Close()
   635  	s1.Stop()
   636  	time.Sleep(2 * time.Second)
   637  
   638  	// Try calling again. It shouldn't work.
   639  	if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil {
   640  		t.Error("successful call while the server is down")
   641  		t.Logf("resp: %#v", resp)
   642  	}
   643  
   644  	// Start it up again and call again. The connection should be reestablished.
   645  	// We spawn multiple calls here to check whether this hangs somehow.
   646  	s2, l2 := startServer(l1.Addr().String())
   647  	defer l2.Close()
   648  	defer s2.Stop()
   649  
   650  	start := make(chan struct{})
   651  	errors := make(chan error, 20)
   652  	for i := 0; i < cap(errors); i++ {
   653  		go func() {
   654  			<-start
   655  			var resp echoResult
   656  			errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
   657  		}()
   658  	}
   659  	close(start)
   660  	errcount := 0
   661  	for i := 0; i < cap(errors); i++ {
   662  		if err = <-errors; err != nil {
   663  			errcount++
   664  		}
   665  	}
   666  	t.Logf("%d errors, last error: %v", errcount, err)
   667  	if errcount > 1 {
   668  		t.Errorf("expected one error after disconnect, got %d", errcount)
   669  	}
   670  }
   671  
   672  func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
   673  	// Create the HTTP server.
   674  	var hs *httptest.Server
   675  	switch transport {
   676  	case "ws":
   677  		hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"}))
   678  	case "http":
   679  		hs = httptest.NewUnstartedServer(srv)
   680  	default:
   681  		panic("unknown HTTP transport: " + transport)
   682  	}
   683  	// Wrap the listener if required.
   684  	if fl != nil {
   685  		fl.Listener = hs.Listener
   686  		hs.Listener = fl
   687  	}
   688  	// Connect the client.
   689  	hs.Start()
   690  	client, err := Dial(transport + "://" + hs.Listener.Addr().String())
   691  	if err != nil {
   692  		panic(err)
   693  	}
   694  	return client, hs
   695  }
   696  
   697  func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) {
   698  	// Listen on a random endpoint.
   699  	endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63())
   700  	if runtime.GOOS == "windows" {
   701  		endpoint = `\\.\pipe\` + endpoint
   702  	} else {
   703  		endpoint = os.TempDir() + "/" + endpoint
   704  	}
   705  	l, err := ipcListen(endpoint)
   706  	if err != nil {
   707  		panic(err)
   708  	}
   709  	// Connect the listener to the server.
   710  	if fl != nil {
   711  		fl.Listener = l
   712  		l = fl
   713  	}
   714  	go srv.ServeListener(l)
   715  	// Connect the client.
   716  	client, err := Dial(endpoint)
   717  	if err != nil {
   718  		panic(err)
   719  	}
   720  	return client, l
   721  }
   722  
   723  // flakeyListener kills accepted connections after a random timeout.
   724  type flakeyListener struct {
   725  	net.Listener
   726  	maxKillTimeout time.Duration
   727  	maxAcceptDelay time.Duration
   728  }
   729  
   730  func (l *flakeyListener) Accept() (net.Conn, error) {
   731  	delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay)))
   732  	time.Sleep(delay)
   733  
   734  	c, err := l.Listener.Accept()
   735  	if err == nil {
   736  		timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout)))
   737  		time.AfterFunc(timeout, func() {
   738  			log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout))
   739  			c.Close()
   740  		})
   741  	}
   742  	return c, err
   743  }