github.com/neatio-net/neatio@v1.7.3-0.20231114194659-f4d7a2226baa/network/rpc/client_test.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"net"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"reflect"
    12  	"runtime"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/davecgh/go-spew/spew"
    18  	"github.com/neatio-net/neatio/chain/log"
    19  )
    20  
    21  func TestClientRequest(t *testing.T) {
    22  	server := newTestServer()
    23  	defer server.Stop()
    24  	client := DialInProc(server)
    25  	defer client.Close()
    26  
    27  	var resp Result
    28  	if err := client.Call(&resp, "test_echo", "hello", 10, &Args{"world"}); err != nil {
    29  		t.Fatal(err)
    30  	}
    31  	if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) {
    32  		t.Errorf("incorrect result %#v", resp)
    33  	}
    34  }
    35  
    36  func TestClientErrorData(t *testing.T) {
    37  	server := newTestServer()
    38  	defer server.Stop()
    39  	client := DialInProc(server)
    40  	defer client.Close()
    41  
    42  	var resp interface{}
    43  	err := client.Call(&resp, "test_returnError")
    44  	if err == nil {
    45  		t.Fatal("expected error")
    46  	}
    47  
    48  	if e, ok := err.(Error); !ok {
    49  		t.Fatalf("client did not return rpc.Error, got %#v", e)
    50  	} else if e.ErrorCode() != (testError{}.ErrorCode()) {
    51  		t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode())
    52  	}
    53  	if e, ok := err.(DataError); !ok {
    54  		t.Fatalf("client did not return rpc.DataError, got %#v", e)
    55  	} else if e.ErrorData() != (testError{}.ErrorData()) {
    56  		t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData())
    57  	}
    58  }
    59  
    60  func TestClientBatchRequest(t *testing.T) {
    61  	server := newTestServer()
    62  	defer server.Stop()
    63  	client := DialInProc(server)
    64  	defer client.Close()
    65  
    66  	batch := []BatchElem{
    67  		{
    68  			Method: "test_echo",
    69  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    70  			Result: new(Result),
    71  		},
    72  		{
    73  			Method: "test_echo",
    74  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    75  			Result: new(Result),
    76  		},
    77  		{
    78  			Method: "no_such_method",
    79  			Args:   []interface{}{1, 2, 3},
    80  			Result: new(int),
    81  		},
    82  	}
    83  	if err := client.BatchCall(batch); err != nil {
    84  		t.Fatal(err)
    85  	}
    86  	wantResult := []BatchElem{
    87  		{
    88  			Method: "test_echo",
    89  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    90  			Result: &Result{"hello", 10, &Args{"world"}},
    91  		},
    92  		{
    93  			Method: "test_echo",
    94  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    95  			Result: &Result{"hello2", 11, &Args{"world"}},
    96  		},
    97  		{
    98  			Method: "no_such_method",
    99  			Args:   []interface{}{1, 2, 3},
   100  			Result: new(int),
   101  			Error:  &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"},
   102  		},
   103  	}
   104  	if !reflect.DeepEqual(batch, wantResult) {
   105  		t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult))
   106  	}
   107  }
   108  
   109  func TestClientNotify(t *testing.T) {
   110  	server := newTestServer()
   111  	defer server.Stop()
   112  	client := DialInProc(server)
   113  	defer client.Close()
   114  
   115  	if err := client.Notify(context.Background(), "test_echo", "hello", 10, &Args{"world"}); err != nil {
   116  		t.Fatal(err)
   117  	}
   118  }
   119  
   120  func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
   121  func TestClientCancelHTTP(t *testing.T)      { testClientCancel("http", t) }
   122  func TestClientCancelIPC(t *testing.T)       { testClientCancel("ipc", t) }
   123  
   124  func testClientCancel(transport string, t *testing.T) {
   125  	t.Parallel()
   126  
   127  	server := newTestServer()
   128  	defer server.Stop()
   129  
   130  	maxContextCancelTimeout := 300 * time.Millisecond
   131  	fl := &flakeyListener{
   132  		maxAcceptDelay: 1 * time.Second,
   133  		maxKillTimeout: 600 * time.Millisecond,
   134  	}
   135  
   136  	var client *Client
   137  	switch transport {
   138  	case "ws", "http":
   139  		c, hs := httpTestClient(server, transport, fl)
   140  		defer hs.Close()
   141  		client = c
   142  	case "ipc":
   143  		c, l := ipcTestClient(server, fl)
   144  		defer l.Close()
   145  		client = c
   146  	default:
   147  		panic("unknown transport: " + transport)
   148  	}
   149  
   150  	var (
   151  		wg       sync.WaitGroup
   152  		nreqs    = 10
   153  		ncallers = 6
   154  	)
   155  	caller := func(index int) {
   156  		defer wg.Done()
   157  		for i := 0; i < nreqs; i++ {
   158  			var (
   159  				ctx     context.Context
   160  				cancel  func()
   161  				timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout)))
   162  			)
   163  			if index < ncallers/2 {
   164  				ctx, cancel = context.WithCancel(context.Background())
   165  				time.AfterFunc(timeout, cancel)
   166  			} else {
   167  				ctx, cancel = context.WithTimeout(context.Background(), timeout)
   168  			}
   169  			sleepTime := maxContextCancelTimeout + 20*time.Millisecond
   170  			err := client.CallContext(ctx, nil, "test_sleep", sleepTime)
   171  			if err != nil {
   172  				log.Debug(fmt.Sprint("got expected error:", err))
   173  			} else {
   174  				t.Errorf("no error for call with %v wait time", timeout)
   175  			}
   176  			cancel()
   177  		}
   178  	}
   179  	wg.Add(ncallers)
   180  	for i := 0; i < ncallers; i++ {
   181  		go caller(i)
   182  	}
   183  	wg.Wait()
   184  }
   185  
   186  func TestClientSubscribeInvalidArg(t *testing.T) {
   187  	server := newTestServer()
   188  	defer server.Stop()
   189  	client := DialInProc(server)
   190  	defer client.Close()
   191  
   192  	check := func(shouldPanic bool, arg interface{}) {
   193  		defer func() {
   194  			err := recover()
   195  			if shouldPanic && err == nil {
   196  				t.Errorf("EthSubscribe should've panicked for %#v", arg)
   197  			}
   198  			if !shouldPanic && err != nil {
   199  				t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg)
   200  				buf := make([]byte, 1024*1024)
   201  				buf = buf[:runtime.Stack(buf, false)]
   202  				t.Error(err)
   203  				t.Error(string(buf))
   204  			}
   205  		}()
   206  		client.EthSubscribe(context.Background(), arg, "foo_bar")
   207  	}
   208  	check(true, nil)
   209  	check(true, 1)
   210  	check(true, (chan int)(nil))
   211  	check(true, make(<-chan int))
   212  	check(false, make(chan int))
   213  	check(false, make(chan<- int))
   214  }
   215  
   216  func TestClientSubscribe(t *testing.T) {
   217  	server := newTestServer()
   218  	defer server.Stop()
   219  	client := DialInProc(server)
   220  	defer client.Close()
   221  
   222  	nc := make(chan int)
   223  	count := 10
   224  	sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0)
   225  	if err != nil {
   226  		t.Fatal("can't subscribe:", err)
   227  	}
   228  	for i := 0; i < count; i++ {
   229  		if val := <-nc; val != i {
   230  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   231  		}
   232  	}
   233  
   234  	sub.Unsubscribe()
   235  	select {
   236  	case v := <-nc:
   237  		t.Fatal("received value after unsubscribe:", v)
   238  	case err := <-sub.Err():
   239  		if err != nil {
   240  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   241  		}
   242  	case <-time.After(1 * time.Second):
   243  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   244  	}
   245  }
   246  
   247  func TestClientSubscribeClose(t *testing.T) {
   248  	server := newTestServer()
   249  	service := &notificationTestService{
   250  		gotHangSubscriptionReq:  make(chan struct{}),
   251  		unblockHangSubscription: make(chan struct{}),
   252  	}
   253  	if err := server.RegisterName("nftest2", service); err != nil {
   254  		t.Fatal(err)
   255  	}
   256  
   257  	defer server.Stop()
   258  	client := DialInProc(server)
   259  	defer client.Close()
   260  
   261  	var (
   262  		nc   = make(chan int)
   263  		errc = make(chan error)
   264  		sub  *ClientSubscription
   265  		err  error
   266  	)
   267  	go func() {
   268  		sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999)
   269  		errc <- err
   270  	}()
   271  
   272  	<-service.gotHangSubscriptionReq
   273  	client.Close()
   274  	service.unblockHangSubscription <- struct{}{}
   275  
   276  	select {
   277  	case err := <-errc:
   278  		if err == nil {
   279  			t.Errorf("Subscribe returned nil error after Close")
   280  		}
   281  		if sub != nil {
   282  			t.Error("Subscribe returned non-nil subscription after Close")
   283  		}
   284  	case <-time.After(1 * time.Second):
   285  		t.Fatalf("Subscribe did not return within 1s after Close")
   286  	}
   287  }
   288  
   289  func TestClientCloseUnsubscribeRace(t *testing.T) {
   290  	server := newTestServer()
   291  	defer server.Stop()
   292  
   293  	for i := 0; i < 20; i++ {
   294  		client := DialInProc(server)
   295  		nc := make(chan int)
   296  		sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1)
   297  		if err != nil {
   298  			t.Fatal(err)
   299  		}
   300  		go client.Close()
   301  		go sub.Unsubscribe()
   302  		select {
   303  		case <-sub.Err():
   304  		case <-time.After(5 * time.Second):
   305  			t.Fatal("subscription not closed within timeout")
   306  		}
   307  	}
   308  }
   309  
   310  func TestClientNotificationStorm(t *testing.T) {
   311  	server := newTestServer()
   312  	defer server.Stop()
   313  
   314  	doTest := func(count int, wantError bool) {
   315  		client := DialInProc(server)
   316  		defer client.Close()
   317  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   318  		defer cancel()
   319  
   320  		nc := make(chan int)
   321  		sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0)
   322  		if err != nil {
   323  			t.Fatal("can't subscribe:", err)
   324  		}
   325  		defer sub.Unsubscribe()
   326  
   327  		for i := 0; i < count; i++ {
   328  			select {
   329  			case val := <-nc:
   330  				if val != i {
   331  					t.Fatalf("(%d/%d) unexpected value %d", i, count, val)
   332  				}
   333  			case err := <-sub.Err():
   334  				if wantError && err != ErrSubscriptionQueueOverflow {
   335  					t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow)
   336  				} else if !wantError {
   337  					t.Fatalf("(%d/%d) got unexpected error %q", i, count, err)
   338  				}
   339  				return
   340  			}
   341  			var r int
   342  			err := client.CallContext(ctx, &r, "nftest_echo", i)
   343  			if err != nil {
   344  				if !wantError {
   345  					t.Fatalf("(%d/%d) call error: %v", i, count, err)
   346  				}
   347  				return
   348  			}
   349  		}
   350  	}
   351  
   352  	doTest(8000, false)
   353  	doTest(10000, true)
   354  }
   355  
   356  func TestClientHTTP(t *testing.T) {
   357  	server := newTestServer()
   358  	defer server.Stop()
   359  
   360  	client, hs := httpTestClient(server, "http", nil)
   361  	defer hs.Close()
   362  	defer client.Close()
   363  
   364  	var (
   365  		results    = make([]Result, 100)
   366  		errc       = make(chan error)
   367  		wantResult = Result{"a", 1, new(Args)}
   368  	)
   369  	defer client.Close()
   370  	for i := range results {
   371  		i := i
   372  		go func() {
   373  			errc <- client.Call(&results[i], "test_echo",
   374  				wantResult.String, wantResult.Int, wantResult.Args)
   375  		}()
   376  	}
   377  
   378  	timeout := time.NewTimer(5 * time.Second)
   379  	defer timeout.Stop()
   380  	for i := range results {
   381  		select {
   382  		case err := <-errc:
   383  			if err != nil {
   384  				t.Fatal(err)
   385  			}
   386  		case <-timeout.C:
   387  			t.Fatalf("timeout (got %d/%d) results)", i+1, len(results))
   388  		}
   389  	}
   390  
   391  	for i := range results {
   392  		if !reflect.DeepEqual(results[i], wantResult) {
   393  			t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult)
   394  		}
   395  	}
   396  }
   397  
   398  func TestClientReconnect(t *testing.T) {
   399  	startServer := func(addr string) (*Server, net.Listener) {
   400  		srv := newTestServer()
   401  		l, err := net.Listen("tcp", addr)
   402  		if err != nil {
   403  			t.Fatal("can't listen:", err)
   404  		}
   405  		go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
   406  		return srv, l
   407  	}
   408  
   409  	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
   410  	defer cancel()
   411  
   412  	s1, l1 := startServer("127.0.0.1:0")
   413  	client, err := DialContext(ctx, "ws://"+l1.Addr().String())
   414  	if err != nil {
   415  		t.Fatal("can't dial", err)
   416  	}
   417  
   418  	var resp Result
   419  	if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
   420  		t.Fatal(err)
   421  	}
   422  
   423  	l1.Close()
   424  	s1.Stop()
   425  	time.Sleep(2 * time.Second)
   426  
   427  	if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil {
   428  		t.Error("successful call while the server is down")
   429  		t.Logf("resp: %#v", resp)
   430  	}
   431  
   432  	s2, l2 := startServer(l1.Addr().String())
   433  	defer l2.Close()
   434  	defer s2.Stop()
   435  
   436  	start := make(chan struct{})
   437  	errors := make(chan error, 20)
   438  	for i := 0; i < cap(errors); i++ {
   439  		go func() {
   440  			<-start
   441  			var resp Result
   442  			errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
   443  		}()
   444  	}
   445  	close(start)
   446  	errcount := 0
   447  	for i := 0; i < cap(errors); i++ {
   448  		if err = <-errors; err != nil {
   449  			errcount++
   450  		}
   451  	}
   452  	t.Logf("%d errors, last error: %v", errcount, err)
   453  	if errcount > 1 {
   454  		t.Errorf("expected one error after disconnect, got %d", errcount)
   455  	}
   456  }
   457  
   458  func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
   459  	var hs *httptest.Server
   460  	switch transport {
   461  	case "ws":
   462  		hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"}))
   463  	case "http":
   464  		hs = httptest.NewUnstartedServer(srv)
   465  	default:
   466  		panic("unknown HTTP transport: " + transport)
   467  	}
   468  	if fl != nil {
   469  		fl.Listener = hs.Listener
   470  		hs.Listener = fl
   471  	}
   472  	hs.Start()
   473  	client, err := Dial(transport + "://" + hs.Listener.Addr().String())
   474  	if err != nil {
   475  		panic(err)
   476  	}
   477  	return client, hs
   478  }
   479  
   480  func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) {
   481  	endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63())
   482  	if runtime.GOOS == "windows" {
   483  		endpoint = `\\.\pipe\` + endpoint
   484  	} else {
   485  		endpoint = os.TempDir() + "/" + endpoint
   486  	}
   487  	l, err := ipcListen(endpoint)
   488  	if err != nil {
   489  		panic(err)
   490  	}
   491  	if fl != nil {
   492  		fl.Listener = l
   493  		l = fl
   494  	}
   495  	go srv.ServeListener(l)
   496  	client, err := Dial(endpoint)
   497  	if err != nil {
   498  		panic(err)
   499  	}
   500  	return client, l
   501  }
   502  
   503  type flakeyListener struct {
   504  	net.Listener
   505  	maxKillTimeout time.Duration
   506  	maxAcceptDelay time.Duration
   507  }
   508  
   509  func (l *flakeyListener) Accept() (net.Conn, error) {
   510  	delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay)))
   511  	time.Sleep(delay)
   512  
   513  	c, err := l.Listener.Accept()
   514  	if err == nil {
   515  		timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout)))
   516  		time.AfterFunc(timeout, func() {
   517  			log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout))
   518  			c.Close()
   519  		})
   520  	}
   521  	return c, err
   522  }