github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/rpc/jsonrpc/jsonrpc_test.go (about)

     1  package jsonrpc
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	crand "crypto/rand"
     7  	"encoding/json"
     8  	"fmt"
     9  	mrand "math/rand"
    10  	"net/http"
    11  	"os"
    12  	"os/exec"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/go-kit/kit/log/term"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  
    20  	tmbytes "github.com/lazyledger/lazyledger-core/libs/bytes"
    21  	"github.com/lazyledger/lazyledger-core/libs/log"
    22  	client "github.com/lazyledger/lazyledger-core/rpc/jsonrpc/client"
    23  	server "github.com/lazyledger/lazyledger-core/rpc/jsonrpc/server"
    24  	types "github.com/lazyledger/lazyledger-core/rpc/jsonrpc/types"
    25  )
    26  
    27  // Client and Server should work over tcp or unix sockets
    28  const (
    29  	tcpAddr = "tcp://127.0.0.1:47768"
    30  
    31  	unixSocket = "/tmp/rpc_test.sock"
    32  	unixAddr   = "unix://" + unixSocket
    33  
    34  	websocketEndpoint = "/websocket/endpoint"
    35  
    36  	testVal = "acbd"
    37  )
    38  
    39  var (
    40  	ctx = context.Background()
    41  )
    42  
    43  type ResultEcho struct {
    44  	Value string `json:"value"`
    45  }
    46  
    47  type ResultEchoInt struct {
    48  	Value int `json:"value"`
    49  }
    50  
    51  type ResultEchoBytes struct {
    52  	Value []byte `json:"value"`
    53  }
    54  
    55  type ResultEchoDataBytes struct {
    56  	Value tmbytes.HexBytes `json:"value"`
    57  }
    58  
    59  // Define some routes
    60  var Routes = map[string]*server.RPCFunc{
    61  	"echo":            server.NewRPCFunc(EchoResult, "arg"),
    62  	"echo_ws":         server.NewWSRPCFunc(EchoWSResult, "arg"),
    63  	"echo_bytes":      server.NewRPCFunc(EchoBytesResult, "arg"),
    64  	"echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult, "arg"),
    65  	"echo_int":        server.NewRPCFunc(EchoIntResult, "arg"),
    66  }
    67  
    68  func EchoResult(ctx *types.Context, v string) (*ResultEcho, error) {
    69  	return &ResultEcho{v}, nil
    70  }
    71  
    72  func EchoWSResult(ctx *types.Context, v string) (*ResultEcho, error) {
    73  	return &ResultEcho{v}, nil
    74  }
    75  
    76  func EchoIntResult(ctx *types.Context, v int) (*ResultEchoInt, error) {
    77  	return &ResultEchoInt{v}, nil
    78  }
    79  
    80  func EchoBytesResult(ctx *types.Context, v []byte) (*ResultEchoBytes, error) {
    81  	return &ResultEchoBytes{v}, nil
    82  }
    83  
    84  func EchoDataBytesResult(ctx *types.Context, v tmbytes.HexBytes) (*ResultEchoDataBytes, error) {
    85  	return &ResultEchoDataBytes{v}, nil
    86  }
    87  
    88  func TestMain(m *testing.M) {
    89  	setup()
    90  	code := m.Run()
    91  	os.Exit(code)
    92  }
    93  
    94  var colorFn = func(keyvals ...interface{}) term.FgBgColor {
    95  	for i := 0; i < len(keyvals)-1; i += 2 {
    96  		if keyvals[i] == "socket" {
    97  			if keyvals[i+1] == "tcp" {
    98  				return term.FgBgColor{Fg: term.DarkBlue}
    99  			} else if keyvals[i+1] == "unix" {
   100  				return term.FgBgColor{Fg: term.DarkCyan}
   101  			}
   102  		}
   103  	}
   104  	return term.FgBgColor{}
   105  }
   106  
   107  // launch unix and tcp servers
   108  func setup() {
   109  	logger := log.NewTMLoggerWithColorFn(log.NewSyncWriter(os.Stdout), colorFn)
   110  
   111  	cmd := exec.Command("rm", "-f", unixSocket)
   112  	err := cmd.Start()
   113  	if err != nil {
   114  		panic(err)
   115  	}
   116  	if err = cmd.Wait(); err != nil {
   117  		panic(err)
   118  	}
   119  
   120  	tcpLogger := logger.With("socket", "tcp")
   121  	mux := http.NewServeMux()
   122  	server.RegisterRPCFuncs(mux, Routes, tcpLogger)
   123  	wm := server.NewWebsocketManager(Routes, server.ReadWait(5*time.Second), server.PingPeriod(1*time.Second))
   124  	wm.SetLogger(tcpLogger)
   125  	mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
   126  	config := server.DefaultConfig()
   127  	listener1, err := server.Listen(tcpAddr, config)
   128  	if err != nil {
   129  		panic(err)
   130  	}
   131  	go func() {
   132  		if err := server.Serve(listener1, mux, tcpLogger, config); err != nil {
   133  			panic(err)
   134  		}
   135  	}()
   136  
   137  	unixLogger := logger.With("socket", "unix")
   138  	mux2 := http.NewServeMux()
   139  	server.RegisterRPCFuncs(mux2, Routes, unixLogger)
   140  	wm = server.NewWebsocketManager(Routes)
   141  	wm.SetLogger(unixLogger)
   142  	mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
   143  	listener2, err := server.Listen(unixAddr, config)
   144  	if err != nil {
   145  		panic(err)
   146  	}
   147  	go func() {
   148  		if err := server.Serve(listener2, mux2, unixLogger, config); err != nil {
   149  			panic(err)
   150  		}
   151  	}()
   152  
   153  	// wait for servers to start
   154  	time.Sleep(time.Second * 2)
   155  }
   156  
   157  func echoViaHTTP(cl client.Caller, val string) (string, error) {
   158  	params := map[string]interface{}{
   159  		"arg": val,
   160  	}
   161  	result := new(ResultEcho)
   162  	if _, err := cl.Call(ctx, "echo", params, result); err != nil {
   163  		return "", err
   164  	}
   165  	return result.Value, nil
   166  }
   167  
   168  func echoIntViaHTTP(cl client.Caller, val int) (int, error) {
   169  	params := map[string]interface{}{
   170  		"arg": val,
   171  	}
   172  	result := new(ResultEchoInt)
   173  	if _, err := cl.Call(ctx, "echo_int", params, result); err != nil {
   174  		return 0, err
   175  	}
   176  	return result.Value, nil
   177  }
   178  
   179  func echoBytesViaHTTP(cl client.Caller, bytes []byte) ([]byte, error) {
   180  	params := map[string]interface{}{
   181  		"arg": bytes,
   182  	}
   183  	result := new(ResultEchoBytes)
   184  	if _, err := cl.Call(ctx, "echo_bytes", params, result); err != nil {
   185  		return []byte{}, err
   186  	}
   187  	return result.Value, nil
   188  }
   189  
   190  func echoDataBytesViaHTTP(cl client.Caller, bytes tmbytes.HexBytes) (tmbytes.HexBytes, error) {
   191  	params := map[string]interface{}{
   192  		"arg": bytes,
   193  	}
   194  	result := new(ResultEchoDataBytes)
   195  	if _, err := cl.Call(ctx, "echo_data_bytes", params, result); err != nil {
   196  		return []byte{}, err
   197  	}
   198  	return result.Value, nil
   199  }
   200  
   201  func testWithHTTPClient(t *testing.T, cl client.HTTPClient) {
   202  	val := testVal
   203  	got, err := echoViaHTTP(cl, val)
   204  	require.Nil(t, err)
   205  	assert.Equal(t, got, val)
   206  
   207  	val2 := randBytes(t)
   208  	got2, err := echoBytesViaHTTP(cl, val2)
   209  	require.Nil(t, err)
   210  	assert.Equal(t, got2, val2)
   211  
   212  	val3 := tmbytes.HexBytes(randBytes(t))
   213  	got3, err := echoDataBytesViaHTTP(cl, val3)
   214  	require.Nil(t, err)
   215  	assert.Equal(t, got3, val3)
   216  
   217  	val4 := mrand.Intn(10000)
   218  	got4, err := echoIntViaHTTP(cl, val4)
   219  	require.Nil(t, err)
   220  	assert.Equal(t, got4, val4)
   221  }
   222  
   223  func echoViaWS(cl *client.WSClient, val string) (string, error) {
   224  	params := map[string]interface{}{
   225  		"arg": val,
   226  	}
   227  	err := cl.Call(context.Background(), "echo", params)
   228  	if err != nil {
   229  		return "", err
   230  	}
   231  
   232  	msg := <-cl.ResponsesCh
   233  	if msg.Error != nil {
   234  		return "", err
   235  
   236  	}
   237  	result := new(ResultEcho)
   238  	err = json.Unmarshal(msg.Result, result)
   239  	if err != nil {
   240  		return "", nil
   241  	}
   242  	return result.Value, nil
   243  }
   244  
   245  func echoBytesViaWS(cl *client.WSClient, bytes []byte) ([]byte, error) {
   246  	params := map[string]interface{}{
   247  		"arg": bytes,
   248  	}
   249  	err := cl.Call(context.Background(), "echo_bytes", params)
   250  	if err != nil {
   251  		return []byte{}, err
   252  	}
   253  
   254  	msg := <-cl.ResponsesCh
   255  	if msg.Error != nil {
   256  		return []byte{}, msg.Error
   257  
   258  	}
   259  	result := new(ResultEchoBytes)
   260  	err = json.Unmarshal(msg.Result, result)
   261  	if err != nil {
   262  		return []byte{}, nil
   263  	}
   264  	return result.Value, nil
   265  }
   266  
   267  func testWithWSClient(t *testing.T, cl *client.WSClient) {
   268  	val := testVal
   269  	got, err := echoViaWS(cl, val)
   270  	require.Nil(t, err)
   271  	assert.Equal(t, got, val)
   272  
   273  	val2 := randBytes(t)
   274  	got2, err := echoBytesViaWS(cl, val2)
   275  	require.Nil(t, err)
   276  	assert.Equal(t, got2, val2)
   277  }
   278  
   279  //-------------
   280  
   281  func TestServersAndClientsBasic(t *testing.T) {
   282  	serverAddrs := [...]string{tcpAddr, unixAddr}
   283  	for _, addr := range serverAddrs {
   284  		cl1, err := client.NewURI(addr)
   285  		require.Nil(t, err)
   286  		fmt.Printf("=== testing server on %s using URI client", addr)
   287  		testWithHTTPClient(t, cl1)
   288  
   289  		cl2, err := client.New(addr)
   290  		require.Nil(t, err)
   291  		fmt.Printf("=== testing server on %s using JSONRPC client", addr)
   292  		testWithHTTPClient(t, cl2)
   293  
   294  		cl3, err := client.NewWS(addr, websocketEndpoint)
   295  		require.Nil(t, err)
   296  		cl3.SetLogger(log.TestingLogger())
   297  		err = cl3.Start()
   298  		require.Nil(t, err)
   299  		fmt.Printf("=== testing server on %s using WS client", addr)
   300  		testWithWSClient(t, cl3)
   301  		err = cl3.Stop()
   302  		require.NoError(t, err)
   303  	}
   304  }
   305  
   306  func TestHexStringArg(t *testing.T) {
   307  	cl, err := client.NewURI(tcpAddr)
   308  	require.Nil(t, err)
   309  	// should NOT be handled as hex
   310  	val := "0xabc"
   311  	got, err := echoViaHTTP(cl, val)
   312  	require.Nil(t, err)
   313  	assert.Equal(t, got, val)
   314  }
   315  
   316  func TestQuotedStringArg(t *testing.T) {
   317  	cl, err := client.NewURI(tcpAddr)
   318  	require.Nil(t, err)
   319  	// should NOT be unquoted
   320  	val := "\"abc\""
   321  	got, err := echoViaHTTP(cl, val)
   322  	require.Nil(t, err)
   323  	assert.Equal(t, got, val)
   324  }
   325  
   326  func TestWSNewWSRPCFunc(t *testing.T) {
   327  	cl, err := client.NewWS(tcpAddr, websocketEndpoint)
   328  	require.Nil(t, err)
   329  	cl.SetLogger(log.TestingLogger())
   330  	err = cl.Start()
   331  	require.Nil(t, err)
   332  	t.Cleanup(func() {
   333  		if err := cl.Stop(); err != nil {
   334  			t.Error(err)
   335  		}
   336  	})
   337  
   338  	val := testVal
   339  	params := map[string]interface{}{
   340  		"arg": val,
   341  	}
   342  	err = cl.Call(context.Background(), "echo_ws", params)
   343  	require.Nil(t, err)
   344  
   345  	msg := <-cl.ResponsesCh
   346  	if msg.Error != nil {
   347  		t.Fatal(err)
   348  	}
   349  	result := new(ResultEcho)
   350  	err = json.Unmarshal(msg.Result, result)
   351  	require.Nil(t, err)
   352  	got := result.Value
   353  	assert.Equal(t, got, val)
   354  }
   355  
   356  func TestWSHandlesArrayParams(t *testing.T) {
   357  	cl, err := client.NewWS(tcpAddr, websocketEndpoint)
   358  	require.Nil(t, err)
   359  	cl.SetLogger(log.TestingLogger())
   360  	err = cl.Start()
   361  	require.Nil(t, err)
   362  	t.Cleanup(func() {
   363  		if err := cl.Stop(); err != nil {
   364  			t.Error(err)
   365  		}
   366  	})
   367  
   368  	val := testVal
   369  	params := []interface{}{val}
   370  	err = cl.CallWithArrayParams(context.Background(), "echo_ws", params)
   371  	require.Nil(t, err)
   372  
   373  	msg := <-cl.ResponsesCh
   374  	if msg.Error != nil {
   375  		t.Fatalf("%+v", err)
   376  	}
   377  	result := new(ResultEcho)
   378  	err = json.Unmarshal(msg.Result, result)
   379  	require.Nil(t, err)
   380  	got := result.Value
   381  	assert.Equal(t, got, val)
   382  }
   383  
   384  // TestWSClientPingPong checks that a client & server exchange pings
   385  // & pongs so connection stays alive.
   386  func TestWSClientPingPong(t *testing.T) {
   387  	cl, err := client.NewWS(tcpAddr, websocketEndpoint)
   388  	require.Nil(t, err)
   389  	cl.SetLogger(log.TestingLogger())
   390  	err = cl.Start()
   391  	require.Nil(t, err)
   392  	t.Cleanup(func() {
   393  		if err := cl.Stop(); err != nil {
   394  			t.Error(err)
   395  		}
   396  	})
   397  
   398  	time.Sleep(6 * time.Second)
   399  }
   400  
   401  func randBytes(t *testing.T) []byte {
   402  	n := mrand.Intn(10) + 2
   403  	buf := make([]byte, n)
   404  	_, err := crand.Read(buf)
   405  	require.Nil(t, err)
   406  	return bytes.ReplaceAll(buf, []byte("="), []byte{100})
   407  }