github.com/evdatsion/aphelion-dpos-bft@v0.32.1/rpc/lib/rpc_test.go (about)

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