github.com/stafiprotocol/go-substrate-rpc-client@v1.4.7/pkg/gethrpc/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  	"fmt"
    22  	"math/rand"
    23  	"net"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"os"
    27  	"reflect"
    28  	"runtime"
    29  	"sync"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/davecgh/go-spew/spew"
    34  	"github.com/ethereum/go-ethereum/log"
    35  )
    36  
    37  func TestClientRequest(t *testing.T) {
    38  	server := newTestServer()
    39  	defer server.Stop()
    40  	client := DialInProc(server)
    41  	defer client.Close()
    42  
    43  	var resp Result
    44  	if err := client.Call(&resp, "test_echo", "hello", 10, &Args{"world"}); err != nil {
    45  		t.Fatal(err)
    46  	}
    47  	if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) {
    48  		t.Errorf("incorrect result %#v", resp)
    49  	}
    50  }
    51  
    52  func TestClientBatchRequest(t *testing.T) {
    53  	server := newTestServer()
    54  	defer server.Stop()
    55  	client := DialInProc(server)
    56  	defer client.Close()
    57  
    58  	batch := []BatchElem{
    59  		{
    60  			Method: "test_echo",
    61  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    62  			Result: new(Result),
    63  		},
    64  		{
    65  			Method: "test_echo",
    66  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    67  			Result: new(Result),
    68  		},
    69  		{
    70  			Method: "no_such_method",
    71  			Args:   []interface{}{1, 2, 3},
    72  			Result: new(int),
    73  		},
    74  	}
    75  	if err := client.BatchCall(batch); err != nil {
    76  		t.Fatal(err)
    77  	}
    78  	wantResult := []BatchElem{
    79  		{
    80  			Method: "test_echo",
    81  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    82  			Result: &Result{"hello", 10, &Args{"world"}},
    83  		},
    84  		{
    85  			Method: "test_echo",
    86  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    87  			Result: &Result{"hello2", 11, &Args{"world"}},
    88  		},
    89  		{
    90  			Method: "no_such_method",
    91  			Args:   []interface{}{1, 2, 3},
    92  			Result: new(int),
    93  			Error:  &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"},
    94  		},
    95  	}
    96  	if !reflect.DeepEqual(batch, wantResult) {
    97  		t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult))
    98  	}
    99  }
   100  
   101  func TestClientNotify(t *testing.T) {
   102  	server := newTestServer()
   103  	defer server.Stop()
   104  	client := DialInProc(server)
   105  	defer client.Close()
   106  
   107  	if err := client.Notify(context.Background(), "test_echo", "hello", 10, &Args{"world"}); err != nil {
   108  		t.Fatal(err)
   109  	}
   110  }
   111  
   112  // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) }
   113  func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
   114  func TestClientCancelHTTP(t *testing.T)      { testClientCancel("http", t) }
   115  func TestClientCancelIPC(t *testing.T)       { testClientCancel("ipc", t) }
   116  
   117  // This test checks that requests made through CallContext can be canceled by canceling
   118  // the context.
   119  func testClientCancel(transport string, t *testing.T) {
   120  	// These tests take a lot of time, run them all at once.
   121  	// You probably want to run with -parallel 1 or comment out
   122  	// the call to t.Parallel if you enable the logging.
   123  	t.Parallel()
   124  
   125  	server := newTestServer()
   126  	defer server.Stop()
   127  
   128  	// What we want to achieve is that the context gets canceled
   129  	// at various stages of request processing. The interesting cases
   130  	// are:
   131  	//  - cancel during dial
   132  	//  - cancel while performing a HTTP request
   133  	//  - cancel while waiting for a response
   134  	//
   135  	// To trigger those, the times are chosen such that connections
   136  	// are killed within the deadline for every other call (maxKillTimeout
   137  	// is 2x maxCancelTimeout).
   138  	//
   139  	// Once a connection is dead, there is a fair chance it won't connect
   140  	// successfully because the accept is delayed by 1s.
   141  	maxContextCancelTimeout := 300 * time.Millisecond
   142  	fl := &flakeyListener{
   143  		maxAcceptDelay: 1 * time.Second,
   144  		maxKillTimeout: 600 * time.Millisecond,
   145  	}
   146  
   147  	var client *Client
   148  	switch transport {
   149  	case "ws", "http":
   150  		c, hs := httpTestClient(server, transport, fl)
   151  		defer hs.Close()
   152  		client = c
   153  	case "ipc":
   154  		c, l := ipcTestClient(server, fl)
   155  		defer l.Close()
   156  		client = c
   157  	default:
   158  		panic("unknown transport: " + transport)
   159  	}
   160  
   161  	// The actual test starts here.
   162  	var (
   163  		wg       sync.WaitGroup
   164  		nreqs    = 10
   165  		ncallers = 6
   166  	)
   167  	caller := func(index int) {
   168  		defer wg.Done()
   169  		for i := 0; i < nreqs; i++ {
   170  			var (
   171  				ctx     context.Context
   172  				cancel  func()
   173  				timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout)))
   174  			)
   175  			if index < ncallers/2 {
   176  				// For half of the callers, create a context without deadline
   177  				// and cancel it later.
   178  				ctx, cancel = context.WithCancel(context.Background())
   179  				time.AfterFunc(timeout, cancel)
   180  			} else {
   181  				// For the other half, create a context with a deadline instead. This is
   182  				// different because the context deadline is used to set the socket write
   183  				// deadline.
   184  				ctx, cancel = context.WithTimeout(context.Background(), timeout)
   185  			}
   186  			// Now perform a call with the context.
   187  			// The key thing here is that no call will ever complete successfully.
   188  			sleepTime := maxContextCancelTimeout + 20*time.Millisecond
   189  			err := client.CallContext(ctx, nil, "test_sleep", sleepTime)
   190  			if err != nil {
   191  				log.Debug(fmt.Sprint("got expected error:", err))
   192  			} else {
   193  				t.Errorf("no error for call with %v wait time", timeout)
   194  			}
   195  			cancel()
   196  		}
   197  	}
   198  	wg.Add(ncallers)
   199  	for i := 0; i < ncallers; i++ {
   200  		go caller(i)
   201  	}
   202  	wg.Wait()
   203  }
   204  
   205  func TestClientHTTP(t *testing.T) {
   206  	server := newTestServer()
   207  	defer server.Stop()
   208  
   209  	client, hs := httpTestClient(server, "http", nil)
   210  	defer hs.Close()
   211  	defer client.Close()
   212  
   213  	// Launch concurrent requests.
   214  	var (
   215  		results    = make([]Result, 100)
   216  		errc       = make(chan error)
   217  		wantResult = Result{"a", 1, new(Args)}
   218  	)
   219  	defer client.Close()
   220  	for i := range results {
   221  		i := i
   222  		go func() {
   223  			errc <- client.Call(&results[i], "test_echo",
   224  				wantResult.String, wantResult.Int, wantResult.Args)
   225  		}()
   226  	}
   227  
   228  	// Wait for all of them to complete.
   229  	timeout := time.NewTimer(5 * time.Second)
   230  	defer timeout.Stop()
   231  	for i := range results {
   232  		select {
   233  		case err := <-errc:
   234  			if err != nil {
   235  				t.Fatal(err)
   236  			}
   237  		case <-timeout.C:
   238  			t.Fatalf("timeout (got %d/%d) results)", i+1, len(results))
   239  		}
   240  	}
   241  
   242  	// Check results.
   243  	for i := range results {
   244  		if !reflect.DeepEqual(results[i], wantResult) {
   245  			t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult)
   246  		}
   247  	}
   248  }
   249  
   250  func TestClientReconnect(t *testing.T) {
   251  	startServer := func(addr string) (*Server, net.Listener) {
   252  		srv := newTestServer()
   253  		l, err := net.Listen("tcp", addr)
   254  		if err != nil {
   255  			t.Fatal("can't listen:", err)
   256  		}
   257  		go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
   258  		return srv, l
   259  	}
   260  
   261  	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
   262  	defer cancel()
   263  
   264  	// Start a server and corresponding client.
   265  	s1, l1 := startServer("127.0.0.1:0")
   266  	client, err := DialContext(ctx, "ws://"+l1.Addr().String())
   267  	if err != nil {
   268  		t.Fatal("can't dial", err)
   269  	}
   270  
   271  	// Perform a call. This should work because the server is up.
   272  	var resp Result
   273  	if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
   274  		t.Fatal(err)
   275  	}
   276  
   277  	// Shut down the server and allow for some cool down time so we can listen on the same
   278  	// address again.
   279  	l1.Close()
   280  	s1.Stop()
   281  	time.Sleep(2 * time.Second)
   282  
   283  	// Try calling again. It shouldn't work.
   284  	if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil {
   285  		t.Error("successful call while the server is down")
   286  		t.Logf("resp: %#v", resp)
   287  	}
   288  
   289  	// Start it up again and call again. The connection should be reestablished.
   290  	// We spawn multiple calls here to check whether this hangs somehow.
   291  	s2, l2 := startServer(l1.Addr().String())
   292  	defer l2.Close()
   293  	defer s2.Stop()
   294  
   295  	start := make(chan struct{})
   296  	errors := make(chan error, 20)
   297  	for i := 0; i < cap(errors); i++ {
   298  		go func() {
   299  			<-start
   300  			var resp Result
   301  			errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
   302  		}()
   303  	}
   304  	close(start)
   305  	errcount := 0
   306  	for i := 0; i < cap(errors); i++ {
   307  		if err = <-errors; err != nil {
   308  			errcount++
   309  		}
   310  	}
   311  	t.Logf("%d errors, last error: %v", errcount, err)
   312  	if errcount > 1 {
   313  		t.Errorf("expected one error after disconnect, got %d", errcount)
   314  	}
   315  }
   316  
   317  func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
   318  	// Create the HTTP server.
   319  	var hs *httptest.Server
   320  	switch transport {
   321  	case "ws":
   322  		hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"}))
   323  	case "http":
   324  		hs = httptest.NewUnstartedServer(srv)
   325  	default:
   326  		panic("unknown HTTP transport: " + transport)
   327  	}
   328  	// Wrap the listener if required.
   329  	if fl != nil {
   330  		fl.Listener = hs.Listener
   331  		hs.Listener = fl
   332  	}
   333  	// Connect the client.
   334  	hs.Start()
   335  	client, err := Dial(transport + "://" + hs.Listener.Addr().String())
   336  	if err != nil {
   337  		panic(err)
   338  	}
   339  	return client, hs
   340  }
   341  
   342  func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) {
   343  	// Listen on a random endpoint.
   344  	endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63())
   345  	if runtime.GOOS == "windows" {
   346  		endpoint = `\\.\pipe\` + endpoint
   347  	} else {
   348  		endpoint = os.TempDir() + "/" + endpoint
   349  	}
   350  	l, err := ipcListen(endpoint)
   351  	if err != nil {
   352  		panic(err)
   353  	}
   354  	// Connect the listener to the server.
   355  	if fl != nil {
   356  		fl.Listener = l
   357  		l = fl
   358  	}
   359  	go srv.ServeListener(l)
   360  	// Connect the client.
   361  	client, err := Dial(endpoint)
   362  	if err != nil {
   363  		panic(err)
   364  	}
   365  	return client, l
   366  }
   367  
   368  // flakeyListener kills accepted connections after a random timeout.
   369  type flakeyListener struct {
   370  	net.Listener
   371  	maxKillTimeout time.Duration
   372  	maxAcceptDelay time.Duration
   373  }
   374  
   375  func (l *flakeyListener) Accept() (net.Conn, error) {
   376  	delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay)))
   377  	time.Sleep(delay)
   378  
   379  	c, err := l.Listener.Accept()
   380  	if err == nil {
   381  		timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout)))
   382  		time.AfterFunc(timeout, func() {
   383  			log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout))
   384  			c.Close()
   385  		})
   386  	}
   387  	return c, err
   388  }