From 92f4b3166f577330a5e1c65f1cb92c1fbc2d02d6 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Mon, 24 Oct 2022 12:33:20 -0700 Subject: [PATCH 1/4] add test for slice/array returned from jrpc calls as well as a cleaner shortcut jrpc.GetArray(). Also fix #633 --- jrpc/jrpcClient.go | 12 ++++++- jrpc/jrpc_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/jrpc/jrpcClient.go b/jrpc/jrpcClient.go index 557c6c2f1..f9965469e 100644 --- a/jrpc/jrpcClient.go +++ b/jrpc/jrpcClient.go @@ -120,6 +120,16 @@ func Get[Q any](url *Destination) (*Q, error) { return Fetch[Q](url, []byte{}) } +// GetArray fetches and deseializes the JSON returned by the Destination into a slice of +// Q struct (ie the response is a json array). +func GetArray[Q any](url *Destination) ([]Q, error) { + slicePtr, err := Fetch[[]Q](url, []byte{}) + if slicePtr == nil { + return nil, err + } + return *slicePtr, err +} + // GetURL is Get without additional options (default timeout and headers). func GetURL[Q any](url string) (*Q, error) { return Get[Q](NewDestination(url)) @@ -162,7 +172,7 @@ func Fetch[Q any](url *Destination, bytes []byte) (*Q, error) { if ok { return nil, err } - return nil, &FetchError{"deserialization error", code, err, bytes} + return nil, &FetchError{"non ok http result and deserialization error", code, err, bytes} } if !ok { // can still be "ok" for some callers, they can use the result object as it deserialized as expected. diff --git a/jrpc/jrpc_test.go b/jrpc/jrpc_test.go index 9619b3954..f2b0d82be 100644 --- a/jrpc/jrpc_test.go +++ b/jrpc/jrpc_test.go @@ -396,3 +396,84 @@ func TestSerializeServerReply(t *testing.T) { t.Errorf("expected %s, got %s", expected, str) } } + +// Testing slices + +type SliceRequest struct { + HowMany int +} + +type SliceOneResponse struct { + Index int + Data string +} + +func TestJPRCSlices(t *testing.T) { + mux, addr := fhttp.HTTPServer("test3", "0") + port := addr.(*net.TCPAddr).Port + mux.HandleFunc("/test-api-array", func(w http.ResponseWriter, r *http.Request) { + req, err := jrpc.HandleCall[SliceRequest](w, r) + if err != nil { + err = jrpc.ReplyError(w, "request error", err) + if err != nil { + t.Errorf("Error in replying error: %v", err) + } + return + } + n := req.HowMany + if n < 0 { + jrpc.ReplyError(w, "invalid negative count", nil) + return + } + if n == 0 { + n = 42 // for testing of GetArray + } + resp := make([]SliceOneResponse, n) + for i := 0; i < n; i++ { + resp[i] = SliceOneResponse{ + Index: i, + Data: fmt.Sprintf("data %d", i), + } + } + jrpc.ReplyOk(w, &resp) + }) + url := fmt.Sprintf("http://localhost:%d/test-api-array", port) + req := SliceRequest{10} + res, err := jrpc.CallURL[[]SliceOneResponse](url, &req) + if err != nil { + t.Errorf("failed Call: %v", err) + } + if res == nil { + t.Errorf("nil response") + return + } + slice := *res + if len(slice) != 10 { + t.Errorf("expected 10 results, got %d", len(slice)) + } + for i := 0; i < len(slice); i++ { + el := slice[i] + if el.Index != i { + t.Errorf("expected index %d, got %d", i, el.Index) + } + if el.Data != fmt.Sprintf("data %d", i) { + t.Errorf("expected data %d, got %s", i, el.Data) + } + } + slice, err = jrpc.GetArray[SliceOneResponse](jrpc.NewDestination(url)) + if err != nil { + t.Errorf("failed GetArray: %v", err) + } + if len(slice) != 42 { + t.Errorf("expected 42 results, got %d", len(slice)) + } + for i := 0; i < len(slice); i++ { + el := slice[i] + if el.Index != i { + t.Errorf("expected index %d, got %d", i, el.Index) + } + if el.Data != fmt.Sprintf("data %d", i) { + t.Errorf("expected data %d, got %s", i, el.Data) + } + } +} From 43d92cad83dfa4084945f2f3ebab6bf3e1c2277f Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Mon, 24 Oct 2022 12:44:20 -0700 Subject: [PATCH 2/4] update test accordingly to error message change --- jrpc/jrpc_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrpc/jrpc_test.go b/jrpc/jrpc_test.go index f2b0d82be..3c99e3fbd 100644 --- a/jrpc/jrpc_test.go +++ b/jrpc/jrpc_test.go @@ -270,7 +270,7 @@ func TestJPRC(t *testing.T) { if unwrap.Error() != expected { t.Errorf("unwrapped error expected to be %q, got %v", expected, unwrap.Error()) } - expected = "deserialization error, code 747: " + expected + " (raw reply: {bad})" + expected = "non ok http result and deserialization error, code 747: " + expected + " (raw reply: {bad})" if err.Error() != expected { t.Errorf("error string expected %q, got %q", expected, err.Error()) } From a980172d67072cceb6e5f3566eb69b66057d479a Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Mon, 24 Oct 2022 13:25:42 -0700 Subject: [PATCH 3/4] test the nil slice case --- jrpc/jrpc_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/jrpc/jrpc_test.go b/jrpc/jrpc_test.go index 3c99e3fbd..69c669a78 100644 --- a/jrpc/jrpc_test.go +++ b/jrpc/jrpc_test.go @@ -425,6 +425,10 @@ func TestJPRCSlices(t *testing.T) { jrpc.ReplyError(w, "invalid negative count", nil) return } + if r.FormValue("errror") != "" { + jrpc.ReplyError(w, "error requested", nil) + return + } if n == 0 { n = 42 // for testing of GetArray } @@ -476,4 +480,12 @@ func TestJPRCSlices(t *testing.T) { t.Errorf("expected data %d, got %s", i, el.Data) } } + // Empty slice/error + slice, err = jrpc.GetArray[SliceOneResponse](jrpc.NewDestination(url + "?errror=true")) + if err == nil { + t.Errorf("expected error, got nil") + } + if slice != nil { + t.Errorf("expected nil slice, got %v", slice) + } } From 3e8550b63713b6442265c0b856d03c8e47ef615e Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Mon, 24 Oct 2022 13:42:29 -0700 Subject: [PATCH 4/4] prep for 1.38.2, would in theory be 1.39.0 as there is a new api but this is a super minor addition --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1f8ffdf8a..10bde738c 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,13 @@ You can install from source: The [releases](https://github.com/fortio/fortio/releases) page has binaries for many OS/architecture combinations (see assets). ```shell -curl -L https://github.com/fortio/fortio/releases/download/v1.38.1/fortio-linux_amd64-1.38.1.tgz \ +curl -L https://github.com/fortio/fortio/releases/download/v1.38.2/fortio-linux_amd64-1.38.2.tgz \ | sudo tar -C / -xvzpf - # or the debian package -wget https://github.com/fortio/fortio/releases/download/v1.38.1/fortio_1.38.1_amd64.deb -dpkg -i fortio_1.38.1_amd64.deb +wget https://github.com/fortio/fortio/releases/download/v1.38.2/fortio_1.38.2_amd64.deb +dpkg -i fortio_1.38.2_amd64.deb # or the rpm -rpm -i https://github.com/fortio/fortio/releases/download/v1.38.1/fortio-1.38.1-1.x86_64.rpm +rpm -i https://github.com/fortio/fortio/releases/download/v1.38.2/fortio-1.38.2-1.x86_64.rpm # and more, see assets in release page ``` @@ -68,7 +68,7 @@ On a MacOS you can also install Fortio using [Homebrew](https://brew.sh/): brew install fortio ``` -On Windows, download https://github.com/fortio/fortio/releases/download/v1.38.1/fortio_win_1.38.1.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: +On Windows, download https://github.com/fortio/fortio/releases/download/v1.38.2/fortio_win_1.38.2.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: ``` fortio.exe server ``` @@ -116,7 +116,7 @@ Full list of command line flags (`fortio help`):
-Φορτίο 1.38.1 usage:
+Φορτίο 1.38.2 usage:
     fortio command [flags] target
 where command is one of: load (load testing), server (starts ui, rest api,
  http-echo, redirect, proxies, tcp-echo and grpc ping servers), tcp-echo (only