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