diff --git a/Makefile b/Makefile index a2e3bea7f..0937558a8 100644 --- a/Makefile +++ b/Makefile @@ -11,5 +11,8 @@ test: get_deps: @echo "--> Running go get" @go get -v -d $(PACKAGES) + @go list -f '{{join .TestImports "\n"}}' ./... | \ + grep -v /vendor/ | sort | uniq | \ + xargs go get -v -d .PHONY: all test get_deps diff --git a/README.md b/README.md index cb869d517..79dd9692e 100644 --- a/README.md +++ b/README.md @@ -125,3 +125,4 @@ IMPROVEMENTS: - added `HTTPClient` interface, which can be used for both `ClientURI` and `ClientJSONRPC` - all params are now optional (Golang's default will be used if some param is missing) +- added `Call` method to `WSClient` (see method's doc for details) diff --git a/client/http_client.go b/client/http_client.go index 96bae9d9b..f4a2a6d7e 100644 --- a/client/http_client.go +++ b/client/http_client.go @@ -72,7 +72,6 @@ func (c *JSONRPCClient) Call(method string, params map[string]interface{}, resul // (handlers.go:176) on the server side encodedParams := make(map[string]interface{}) for k, v := range params { - // log.Printf("%s: %v (%s)\n", k, v, string(wire.JSONBytes(v))) bytes := json.RawMessage(wire.JSONBytes(v)) encodedParams[k] = &bytes } diff --git a/client/ws_client.go b/client/ws_client.go index 9933ab073..6c34232e0 100644 --- a/client/ws_client.go +++ b/client/ws_client.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" cmn "github.com/tendermint/go-common" types "github.com/tendermint/go-rpc/types" + wire "github.com/tendermint/go-wire" ) const ( @@ -120,7 +121,8 @@ func (wsc *WSClient) receiveEventsRoutine() { close(wsc.ErrorsCh) } -// subscribe to an event +// Subscribe to an event. Note the server must have a "subscribe" route +// defined. func (wsc *WSClient) Subscribe(eventid string) error { err := wsc.WriteJSON(types.RPCRequest{ JSONRPC: "2.0", @@ -131,7 +133,8 @@ func (wsc *WSClient) Subscribe(eventid string) error { return err } -// unsubscribe from an event +// Unsubscribe from an event. Note the server must have a "unsubscribe" route +// defined. func (wsc *WSClient) Unsubscribe(eventid string) error { err := wsc.WriteJSON(types.RPCRequest{ JSONRPC: "2.0", @@ -141,3 +144,22 @@ func (wsc *WSClient) Unsubscribe(eventid string) error { }) return err } + +// Call asynchronously calls a given method by sending an RPCRequest to the +// server. Results will be available on ResultsCh, errors, if any, on ErrorsCh. +func (wsc *WSClient) Call(method string, params map[string]interface{}) error { + // we need this step because we attempt to decode values using `go-wire` + // (handlers.go:470) on the server side + encodedParams := make(map[string]interface{}) + for k, v := range params { + bytes := json.RawMessage(wire.JSONBytes(v)) + encodedParams[k] = &bytes + } + err := wsc.WriteJSON(types.RPCRequest{ + JSONRPC: "2.0", + Method: method, + Params: encodedParams, + ID: "", + }) + return err +} diff --git a/rpc_test.go b/rpc_test.go index b1ae2566d..56b8ade32 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -3,12 +3,15 @@ package rpc import ( "bytes" crand "crypto/rand" + "fmt" "math/rand" "net/http" "os/exec" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" client "github.com/tendermint/go-rpc/client" server "github.com/tendermint/go-rpc/server" types "github.com/tendermint/go-rpc/types" @@ -28,38 +31,37 @@ const ( // Define a type for results and register concrete versions type Result interface{} -type ResultStatus struct { +type ResultEcho struct { Value string } -type ResultBytes struct { +type ResultEchoBytes struct { Value []byte } var _ = wire.RegisterInterface( struct{ Result }{}, - wire.ConcreteType{&ResultStatus{}, 0x1}, - wire.ConcreteType{&ResultBytes{}, 0x2}, + wire.ConcreteType{&ResultEcho{}, 0x1}, + wire.ConcreteType{&ResultEchoBytes{}, 0x2}, ) // Define some routes var Routes = map[string]*server.RPCFunc{ - "status": server.NewRPCFunc(StatusResult, "arg"), - "status_ws": server.NewWSRPCFunc(StatusWSResult, "arg"), - "bytes": server.NewRPCFunc(BytesResult, "arg"), + "echo": server.NewRPCFunc(EchoResult, "arg"), + "echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"), + "echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"), } -// an rpc function -func StatusResult(v string) (Result, error) { - return &ResultStatus{v}, nil +func EchoResult(v string) (Result, error) { + return &ResultEcho{v}, nil } -func StatusWSResult(wsCtx types.WSRPCContext, v string) (Result, error) { - return &ResultStatus{v}, nil +func EchoWSResult(wsCtx types.WSRPCContext, v string) (Result, error) { + return &ResultEcho{v}, nil } -func BytesResult(v []byte) (Result, error) { - return &ResultBytes{v}, nil +func EchoBytesResult(v []byte) (Result, error) { + return &ResultEchoBytes{v}, nil } // launch unix and tcp servers @@ -69,7 +71,9 @@ func init() { if err != nil { panic(err) } - err = cmd.Wait() + if err = cmd.Wait(); err != nil { + panic(err) + } mux := http.NewServeMux() server.RegisterRPCFuncs(mux, Routes) @@ -95,54 +99,49 @@ func init() { // wait for servers to start time.Sleep(time.Second * 2) - } -func testURI(t *testing.T, cl *client.URIClient) { - val := "acbd" +func echoViaHTTP(cl client.HTTPClient, val string) (string, error) { params := map[string]interface{}{ "arg": val, } var result Result - _, err := cl.Call("status", params, &result) - if err != nil { - t.Fatal(err) - } - got := result.(*ResultStatus).Value - if got != val { - t.Fatalf("Got: %v .... Expected: %v \n", got, val) + if _, err := cl.Call("echo", params, &result); err != nil { + return "", err } + return result.(*ResultEcho).Value, nil } -func testJSONRPC(t *testing.T, cl *client.JSONRPCClient) { - val := "acbd" +func echoBytesViaHTTP(cl client.HTTPClient, bytes []byte) ([]byte, error) { params := map[string]interface{}{ - "arg": val, + "arg": bytes, } var result Result - _, err := cl.Call("status", params, &result) - if err != nil { - t.Fatal(err) - } - got := result.(*ResultStatus).Value - if got != val { - t.Fatalf("Got: %v .... Expected: %v \n", got, val) + if _, err := cl.Call("echo_bytes", params, &result); err != nil { + return []byte{}, err } + return result.(*ResultEchoBytes).Value, nil } -func testWS(t *testing.T, cl *client.WSClient) { +func testWithHTTPClient(t *testing.T, cl client.HTTPClient) { val := "acbd" + got, err := echoViaHTTP(cl, val) + require.Nil(t, err) + assert.Equal(t, got, val) + + val2 := randBytes(t) + got2, err := echoBytesViaHTTP(cl, val2) + require.Nil(t, err) + assert.Equal(t, got2, val2) +} + +func echoViaWS(cl *client.WSClient, val string) (string, error) { params := map[string]interface{}{ "arg": val, } - err := cl.WriteJSON(types.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "status", - Params: params, - }) + err := cl.Call("echo", params) if err != nil { - t.Fatal(err) + return "", err } select { @@ -150,127 +149,92 @@ func testWS(t *testing.T, cl *client.WSClient) { result := new(Result) wire.ReadJSONPtr(result, msg, &err) if err != nil { - t.Fatal(err) - } - got := (*result).(*ResultStatus).Value - if got != val { - t.Fatalf("Got: %v .... Expected: %v \n", got, val) + return "", nil } + return (*result).(*ResultEcho).Value, nil case err := <-cl.ErrorsCh: - t.Fatal(err) + return "", err } } -//------------- - -func TestURI_TCP(t *testing.T) { - cl := client.NewURIClient(tcpAddr) - testURI(t, cl) -} - -func TestURI_UNIX(t *testing.T) { - cl := client.NewURIClient(unixAddr) - testURI(t, cl) -} +func echoBytesViaWS(cl *client.WSClient, bytes []byte) ([]byte, error) { + params := map[string]interface{}{ + "arg": bytes, + } + err := cl.Call("echo_bytes", params) + if err != nil { + return []byte{}, err + } -func TestJSONRPC_TCP(t *testing.T) { - cl := client.NewJSONRPCClient(tcpAddr) - testJSONRPC(t, cl) + select { + case msg := <-cl.ResultsCh: + result := new(Result) + wire.ReadJSONPtr(result, msg, &err) + if err != nil { + return []byte{}, nil + } + return (*result).(*ResultEchoBytes).Value, nil + case err := <-cl.ErrorsCh: + return []byte{}, err + } } -func TestJSONRPC_UNIX(t *testing.T) { - cl := client.NewJSONRPCClient(unixAddr) - testJSONRPC(t, cl) +func testWithWSClient(t *testing.T, cl *client.WSClient) { + val := "acbd" + got, err := echoViaWS(cl, val) + require.Nil(t, err) + assert.Equal(t, got, val) + + val2 := randBytes(t) + got2, err := echoBytesViaWS(cl, val2) + require.Nil(t, err) + assert.Equal(t, got2, val2) } -func TestWS_TCP(t *testing.T) { - cl := client.NewWSClient(tcpAddr, websocketEndpoint) - _, err := cl.Start() - if err != nil { - t.Fatal(err) - } - testWS(t, cl) -} +//------------- -func TestWS_UNIX(t *testing.T) { - cl := client.NewWSClient(unixAddr, websocketEndpoint) - _, err := cl.Start() - if err != nil { - t.Fatal(err) +func TestServersAndClientsBasic(t *testing.T) { + serverAddrs := [...]string{tcpAddr, unixAddr} + for _, addr := range serverAddrs { + cl1 := client.NewURIClient(addr) + fmt.Printf("=== testing server on %s using %v client", addr, cl1) + testWithHTTPClient(t, cl1) + + cl2 := client.NewJSONRPCClient(tcpAddr) + fmt.Printf("=== testing server on %s using %v client", addr, cl2) + testWithHTTPClient(t, cl2) + + cl3 := client.NewWSClient(tcpAddr, websocketEndpoint) + _, err := cl3.Start() + require.Nil(t, err) + fmt.Printf("=== testing server on %s using %v client", addr, cl3) + testWithWSClient(t, cl3) + cl3.Stop() } - testWS(t, cl) } func TestHexStringArg(t *testing.T) { cl := client.NewURIClient(tcpAddr) // should NOT be handled as hex val := "0xabc" - params := map[string]interface{}{ - "arg": val, - } - var result Result - _, err := cl.Call("status", params, &result) - if err != nil { - t.Fatal(err) - } - got := result.(*ResultStatus).Value - if got != val { - t.Fatalf("Got: %v .... Expected: %v \n", got, val) - } + got, err := echoViaHTTP(cl, val) + require.Nil(t, err) + assert.Equal(t, got, val) } func TestQuotedStringArg(t *testing.T) { cl := client.NewURIClient(tcpAddr) // should NOT be unquoted val := "\"abc\"" - params := map[string]interface{}{ - "arg": val, - } - var result Result - _, err := cl.Call("status", params, &result) - if err != nil { - t.Fatal(err) - } - got := result.(*ResultStatus).Value - if got != val { - t.Fatalf("Got: %v .... Expected: %v \n", got, val) - } -} - -func randBytes(t *testing.T) []byte { - n := rand.Intn(10) + 2 - buf := make([]byte, n) - _, err := crand.Read(buf) - if err != nil { - t.Fatal(err) - } - return bytes.Replace(buf, []byte("="), []byte{100}, -1) -} - -func TestByteSliceViaJSONRPC(t *testing.T) { - cl := client.NewJSONRPCClient(unixAddr) - - val := randBytes(t) - params := map[string]interface{}{ - "arg": val, - } - var result Result - _, err := cl.Call("bytes", params, &result) - if err != nil { - t.Fatal(err) - } - got := result.(*ResultBytes).Value - if bytes.Compare(got, val) != 0 { - t.Fatalf("Got: %v .... Expected: %v \n", got, val) - } + got, err := echoViaHTTP(cl, val) + require.Nil(t, err) + assert.Equal(t, got, val) } func TestWSNewWSRPCFunc(t *testing.T) { - cl := client.NewWSClient(unixAddr, websocketEndpoint) + cl := client.NewWSClient(tcpAddr, websocketEndpoint) _, err := cl.Start() - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) defer cl.Stop() val := "acbd" @@ -280,25 +244,27 @@ func TestWSNewWSRPCFunc(t *testing.T) { err = cl.WriteJSON(types.RPCRequest{ JSONRPC: "2.0", ID: "", - Method: "status_ws", + Method: "echo_ws", Params: params, }) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) select { case msg := <-cl.ResultsCh: result := new(Result) wire.ReadJSONPtr(result, msg, &err) - if err != nil { - t.Fatal(err) - } - got := (*result).(*ResultStatus).Value - if got != val { - t.Fatalf("Got: %v .... Expected: %v \n", got, val) - } + require.Nil(t, err) + got := (*result).(*ResultEcho).Value + assert.Equal(t, got, val) case err := <-cl.ErrorsCh: t.Fatal(err) } } + +func randBytes(t *testing.T) []byte { + n := rand.Intn(10) + 2 + buf := make([]byte, n) + _, err := crand.Read(buf) + require.Nil(t, err) + return bytes.Replace(buf, []byte("="), []byte{100}, -1) +}