github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/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/quickchainproject/quickchain/log"
    19  )
    20  
    21  func TestClientRequest(t *testing.T) {
    22  	server := newTestServer("service", new(Service))
    23  	defer server.Stop()
    24  	client := DialInProc(server)
    25  	defer client.Close()
    26  
    27  	var resp Result
    28  	if err := client.Call(&resp, "service_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 TestClientBatchRequest(t *testing.T) {
    37  	server := newTestServer("service", new(Service))
    38  	defer server.Stop()
    39  	client := DialInProc(server)
    40  	defer client.Close()
    41  
    42  	batch := []BatchElem{
    43  		{
    44  			Method: "service_echo",
    45  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    46  			Result: new(Result),
    47  		},
    48  		{
    49  			Method: "service_echo",
    50  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    51  			Result: new(Result),
    52  		},
    53  		{
    54  			Method: "no_such_method",
    55  			Args:   []interface{}{1, 2, 3},
    56  			Result: new(int),
    57  		},
    58  	}
    59  	if err := client.BatchCall(batch); err != nil {
    60  		t.Fatal(err)
    61  	}
    62  	wantResult := []BatchElem{
    63  		{
    64  			Method: "service_echo",
    65  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    66  			Result: &Result{"hello", 10, &Args{"world"}},
    67  		},
    68  		{
    69  			Method: "service_echo",
    70  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    71  			Result: &Result{"hello2", 11, &Args{"world"}},
    72  		},
    73  		{
    74  			Method: "no_such_method",
    75  			Args:   []interface{}{1, 2, 3},
    76  			Result: new(int),
    77  			Error:  &jsonError{Code: -32601, Message: "The method no_such_method_ does not exist/is not available"},
    78  		},
    79  	}
    80  	if !reflect.DeepEqual(batch, wantResult) {
    81  		t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult))
    82  	}
    83  }
    84  
    85  // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) }
    86  func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
    87  func TestClientCancelHTTP(t *testing.T)      { testClientCancel("http", t) }
    88  func TestClientCancelIPC(t *testing.T)       { testClientCancel("ipc", t) }
    89  
    90  // This test checks that requests made through CallContext can be canceled by canceling
    91  // the context.
    92  func testClientCancel(transport string, t *testing.T) {
    93  	server := newTestServer("service", new(Service))
    94  	defer server.Stop()
    95  
    96  	// What we want to achieve is that the context gets canceled
    97  	// at various stages of request processing. The interesting cases
    98  	// are:
    99  	//  - cancel during dial
   100  	//  - cancel while performing a HTTP request
   101  	//  - cancel while waiting for a response
   102  	//
   103  	// To trigger those, the times are chosen such that connections
   104  	// are killed within the deadline for every other call (maxKillTimeout
   105  	// is 2x maxCancelTimeout).
   106  	//
   107  	// Once a connection is dead, there is a fair chance it won't connect
   108  	// successfully because the accept is delayed by 1s.
   109  	maxContextCancelTimeout := 300 * time.Millisecond
   110  	fl := &flakeyListener{
   111  		maxAcceptDelay: 1 * time.Second,
   112  		maxKillTimeout: 600 * time.Millisecond,
   113  	}
   114  
   115  	var client *Client
   116  	switch transport {
   117  	case "ws", "http":
   118  		c, hs := httpTestClient(server, transport, fl)
   119  		defer hs.Close()
   120  		client = c
   121  	case "ipc":
   122  		c, l := ipcTestClient(server, fl)
   123  		defer l.Close()
   124  		client = c
   125  	default:
   126  		panic("unknown transport: " + transport)
   127  	}
   128  
   129  	// These tests take a lot of time, run them all at once.
   130  	// You probably want to run with -parallel 1 or comment out
   131  	// the call to t.Parallel if you enable the logging.
   132  	t.Parallel()
   133  
   134  	// The actual test starts here.
   135  	var (
   136  		wg       sync.WaitGroup
   137  		nreqs    = 10
   138  		ncallers = 6
   139  	)
   140  	caller := func(index int) {
   141  		defer wg.Done()
   142  		for i := 0; i < nreqs; i++ {
   143  			var (
   144  				ctx     context.Context
   145  				cancel  func()
   146  				timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout)))
   147  			)
   148  			if index < ncallers/2 {
   149  				// For half of the callers, create a context without deadline
   150  				// and cancel it later.
   151  				ctx, cancel = context.WithCancel(context.Background())
   152  				time.AfterFunc(timeout, cancel)
   153  			} else {
   154  				// For the other half, create a context with a deadline instead. This is
   155  				// different because the context deadline is used to set the socket write
   156  				// deadline.
   157  				ctx, cancel = context.WithTimeout(context.Background(), timeout)
   158  			}
   159  			// Now perform a call with the context.
   160  			// The key thing here is that no call will ever complete successfully.
   161  			err := client.CallContext(ctx, nil, "service_sleep", 2*maxContextCancelTimeout)
   162  			if err != nil {
   163  				log.Debug(fmt.Sprint("got expected error:", err))
   164  			} else {
   165  				t.Errorf("no error for call with %v wait time", timeout)
   166  			}
   167  			cancel()
   168  		}
   169  	}
   170  	wg.Add(ncallers)
   171  	for i := 0; i < ncallers; i++ {
   172  		go caller(i)
   173  	}
   174  	wg.Wait()
   175  }
   176  
   177  func TestClientSubscribeInvalidArg(t *testing.T) {
   178  	server := newTestServer("service", new(Service))
   179  	defer server.Stop()
   180  	client := DialInProc(server)
   181  	defer client.Close()
   182  
   183  	check := func(shouldPanic bool, arg interface{}) {
   184  		defer func() {
   185  			err := recover()
   186  			if shouldPanic && err == nil {
   187  				t.Errorf("EthSubscribe should've panicked for %#v", arg)
   188  			}
   189  			if !shouldPanic && err != nil {
   190  				t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg)
   191  				buf := make([]byte, 1024*1024)
   192  				buf = buf[:runtime.Stack(buf, false)]
   193  				t.Error(err)
   194  				t.Error(string(buf))
   195  			}
   196  		}()
   197  		client.EthSubscribe(context.Background(), arg, "foo_bar")
   198  	}
   199  	check(true, nil)
   200  	check(true, 1)
   201  	check(true, (chan int)(nil))
   202  	check(true, make(<-chan int))
   203  	check(false, make(chan int))
   204  	check(false, make(chan<- int))
   205  }
   206  
   207  func TestClientSubscribe(t *testing.T) {
   208  	server := newTestServer("eth", new(NotificationTestService))
   209  	defer server.Stop()
   210  	client := DialInProc(server)
   211  	defer client.Close()
   212  
   213  	nc := make(chan int)
   214  	count := 10
   215  	sub, err := client.EthSubscribe(context.Background(), nc, "someSubscription", count, 0)
   216  	if err != nil {
   217  		t.Fatal("can't subscribe:", err)
   218  	}
   219  	for i := 0; i < count; i++ {
   220  		if val := <-nc; val != i {
   221  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   222  		}
   223  	}
   224  
   225  	sub.Unsubscribe()
   226  	select {
   227  	case v := <-nc:
   228  		t.Fatal("received value after unsubscribe:", v)
   229  	case err := <-sub.Err():
   230  		if err != nil {
   231  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   232  		}
   233  	case <-time.After(1 * time.Second):
   234  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   235  	}
   236  }
   237  
   238  func TestClientSubscribeCustomNamespace(t *testing.T) {
   239  	namespace := "custom"
   240  	server := newTestServer(namespace, new(NotificationTestService))
   241  	defer server.Stop()
   242  	client := DialInProc(server)
   243  	defer client.Close()
   244  
   245  	nc := make(chan int)
   246  	count := 10
   247  	sub, err := client.Subscribe(context.Background(), namespace, nc, "someSubscription", count, 0)
   248  	if err != nil {
   249  		t.Fatal("can't subscribe:", err)
   250  	}
   251  	for i := 0; i < count; i++ {
   252  		if val := <-nc; val != i {
   253  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   254  		}
   255  	}
   256  
   257  	sub.Unsubscribe()
   258  	select {
   259  	case v := <-nc:
   260  		t.Fatal("received value after unsubscribe:", v)
   261  	case err := <-sub.Err():
   262  		if err != nil {
   263  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   264  		}
   265  	case <-time.After(1 * time.Second):
   266  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   267  	}
   268  }
   269  
   270  // In this test, the connection drops while EthSubscribe is
   271  // waiting for a response.
   272  func TestClientSubscribeClose(t *testing.T) {
   273  	service := &NotificationTestService{
   274  		gotHangSubscriptionReq:  make(chan struct{}),
   275  		unblockHangSubscription: make(chan struct{}),
   276  	}
   277  	server := newTestServer("eth", service)
   278  	defer server.Stop()
   279  	client := DialInProc(server)
   280  	defer client.Close()
   281  
   282  	var (
   283  		nc   = make(chan int)
   284  		errc = make(chan error)
   285  		sub  *ClientSubscription
   286  		err  error
   287  	)
   288  	go func() {
   289  		sub, err = client.EthSubscribe(context.Background(), nc, "hangSubscription", 999)
   290  		errc <- err
   291  	}()
   292  
   293  	<-service.gotHangSubscriptionReq
   294  	client.Close()
   295  	service.unblockHangSubscription <- struct{}{}
   296  
   297  	select {
   298  	case err := <-errc:
   299  		if err == nil {
   300  			t.Errorf("EthSubscribe returned nil error after Close")
   301  		}
   302  		if sub != nil {
   303  			t.Error("EthSubscribe returned non-nil subscription after Close")
   304  		}
   305  	case <-time.After(1 * time.Second):
   306  		t.Fatalf("EthSubscribe did not return within 1s after Close")
   307  	}
   308  }
   309  
   310  // This test checks that Client doesn't lock up when a single subscriber
   311  // doesn't read subscription events.
   312  func TestClientNotificationStorm(t *testing.T) {
   313  	server := newTestServer("eth", new(NotificationTestService))
   314  	defer server.Stop()
   315  
   316  	doTest := func(count int, wantError bool) {
   317  		client := DialInProc(server)
   318  		defer client.Close()
   319  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   320  		defer cancel()
   321  
   322  		// Subscribe on the server. It will start sending many notifications
   323  		// very quickly.
   324  		nc := make(chan int)
   325  		sub, err := client.EthSubscribe(ctx, nc, "someSubscription", count, 0)
   326  		if err != nil {
   327  			t.Fatal("can't subscribe:", err)
   328  		}
   329  		defer sub.Unsubscribe()
   330  
   331  		// Process each notification, try to run a call in between each of them.
   332  		for i := 0; i < count; i++ {
   333  			select {
   334  			case val := <-nc:
   335  				if val != i {
   336  					t.Fatalf("(%d/%d) unexpected value %d", i, count, val)
   337  				}
   338  			case err := <-sub.Err():
   339  				if wantError && err != ErrSubscriptionQueueOverflow {
   340  					t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow)
   341  				} else if !wantError {
   342  					t.Fatalf("(%d/%d) got unexpected error %q", i, count, err)
   343  				}
   344  				return
   345  			}
   346  			var r int
   347  			err := client.CallContext(ctx, &r, "qct_echo", i)
   348  			if err != nil {
   349  				if !wantError {
   350  					t.Fatalf("(%d/%d) call error: %v", i, count, err)
   351  				}
   352  				return
   353  			}
   354  		}
   355  	}
   356  
   357  	doTest(8000, false)
   358  	doTest(10000, true)
   359  }
   360  
   361  func TestClientHTTP(t *testing.T) {
   362  	server := newTestServer("service", new(Service))
   363  	defer server.Stop()
   364  
   365  	client, hs := httpTestClient(server, "http", nil)
   366  	defer hs.Close()
   367  	defer client.Close()
   368  
   369  	// Launch concurrent requests.
   370  	var (
   371  		results    = make([]Result, 100)
   372  		errc       = make(chan error)
   373  		wantResult = Result{"a", 1, new(Args)}
   374  	)
   375  	defer client.Close()
   376  	for i := range results {
   377  		i := i
   378  		go func() {
   379  			errc <- client.Call(&results[i], "service_echo",
   380  				wantResult.String, wantResult.Int, wantResult.Args)
   381  		}()
   382  	}
   383  
   384  	// Wait for all of them to complete.
   385  	timeout := time.NewTimer(5 * time.Second)
   386  	defer timeout.Stop()
   387  	for i := range results {
   388  		select {
   389  		case err := <-errc:
   390  			if err != nil {
   391  				t.Fatal(err)
   392  			}
   393  		case <-timeout.C:
   394  			t.Fatalf("timeout (got %d/%d) results)", i+1, len(results))
   395  		}
   396  	}
   397  
   398  	// Check results.
   399  	for i := range results {
   400  		if !reflect.DeepEqual(results[i], wantResult) {
   401  			t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult)
   402  		}
   403  	}
   404  }
   405  
   406  func TestClientReconnect(t *testing.T) {
   407  	startServer := func(addr string) (*Server, net.Listener) {
   408  		srv := newTestServer("service", new(Service))
   409  		l, err := net.Listen("tcp", addr)
   410  		if err != nil {
   411  			t.Fatal(err)
   412  		}
   413  		go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
   414  		return srv, l
   415  	}
   416  
   417  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   418  	defer cancel()
   419  
   420  	// Start a server and corresponding client.
   421  	s1, l1 := startServer("127.0.0.1:0")
   422  	client, err := DialContext(ctx, "ws://"+l1.Addr().String())
   423  	if err != nil {
   424  		t.Fatal("can't dial", err)
   425  	}
   426  
   427  	// Perform a call. This should work because the server is up.
   428  	var resp Result
   429  	if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil {
   430  		t.Fatal(err)
   431  	}
   432  
   433  	// Shut down the server and try calling again. It shouldn't work.
   434  	l1.Close()
   435  	s1.Stop()
   436  	if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil {
   437  		t.Error("successful call while the server is down")
   438  		t.Logf("resp: %#v", resp)
   439  	}
   440  
   441  	// Allow for some cool down time so we can listen on the same address again.
   442  	time.Sleep(2 * time.Second)
   443  
   444  	// Start it up again and call again. The connection should be reestablished.
   445  	// We spawn multiple calls here to check whether this hangs somehow.
   446  	s2, l2 := startServer(l1.Addr().String())
   447  	defer l2.Close()
   448  	defer s2.Stop()
   449  
   450  	start := make(chan struct{})
   451  	errors := make(chan error, 20)
   452  	for i := 0; i < cap(errors); i++ {
   453  		go func() {
   454  			<-start
   455  			var resp Result
   456  			errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil)
   457  		}()
   458  	}
   459  	close(start)
   460  	errcount := 0
   461  	for i := 0; i < cap(errors); i++ {
   462  		if err = <-errors; err != nil {
   463  			errcount++
   464  		}
   465  	}
   466  	t.Log("err:", err)
   467  	if errcount > 1 {
   468  		t.Errorf("expected one error after disconnect, got %d", errcount)
   469  	}
   470  }
   471  
   472  func newTestServer(serviceName string, service interface{}) *Server {
   473  	server := NewServer()
   474  	if err := server.RegisterName(serviceName, service); err != nil {
   475  		panic(err)
   476  	}
   477  	return server
   478  }
   479  
   480  func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
   481  	// Create the HTTP server.
   482  	var hs *httptest.Server
   483  	switch transport {
   484  	case "ws":
   485  		hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"}))
   486  	case "http":
   487  		hs = httptest.NewUnstartedServer(srv)
   488  	default:
   489  		panic("unknown HTTP transport: " + transport)
   490  	}
   491  	// Wrap the listener if required.
   492  	if fl != nil {
   493  		fl.Listener = hs.Listener
   494  		hs.Listener = fl
   495  	}
   496  	// Connect the client.
   497  	hs.Start()
   498  	client, err := Dial(transport + "://" + hs.Listener.Addr().String())
   499  	if err != nil {
   500  		panic(err)
   501  	}
   502  	return client, hs
   503  }
   504  
   505  func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) {
   506  	// Listen on a random endpoint.
   507  	endpoint := fmt.Sprintf("quickchain-test-ipc-%d-%d", os.Getpid(), rand.Int63())
   508  	if runtime.GOOS == "windows" {
   509  		endpoint = `\\.\pipe\` + endpoint
   510  	} else {
   511  		endpoint = os.TempDir() + "/" + endpoint
   512  	}
   513  	l, err := ipcListen(endpoint)
   514  	if err != nil {
   515  		panic(err)
   516  	}
   517  	// Connect the listener to the server.
   518  	if fl != nil {
   519  		fl.Listener = l
   520  		l = fl
   521  	}
   522  	go srv.ServeListener(l)
   523  	// Connect the client.
   524  	client, err := Dial(endpoint)
   525  	if err != nil {
   526  		panic(err)
   527  	}
   528  	return client, l
   529  }
   530  
   531  // flakeyListener kills accepted connections after a random timeout.
   532  type flakeyListener struct {
   533  	net.Listener
   534  	maxKillTimeout time.Duration
   535  	maxAcceptDelay time.Duration
   536  }
   537  
   538  func (l *flakeyListener) Accept() (net.Conn, error) {
   539  	delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay)))
   540  	time.Sleep(delay)
   541  
   542  	c, err := l.Listener.Accept()
   543  	if err == nil {
   544  		timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout)))
   545  		time.AfterFunc(timeout, func() {
   546  			log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout))
   547  			c.Close()
   548  		})
   549  	}
   550  	return c, err
   551  }