| Index: common/prpc/client_test.go
|
| diff --git a/common/prpc/client_test.go b/common/prpc/client_test.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..82b773eaad37ec3b41a170d7efb7755e7e9b341d
|
| --- /dev/null
|
| +++ b/common/prpc/client_test.go
|
| @@ -0,0 +1,252 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package prpc
|
| +
|
| +import (
|
| + "fmt"
|
| + "io/ioutil"
|
| + "net/http"
|
| + "net/http/httptest"
|
| + "strconv"
|
| + "strings"
|
| + "testing"
|
| + "time"
|
| +
|
| + "github.com/golang/protobuf/proto"
|
| + "golang.org/x/net/context"
|
| + "google.golang.org/grpc"
|
| + "google.golang.org/grpc/codes"
|
| +
|
| + "github.com/luci/luci-go/common/clock"
|
| + "github.com/luci/luci-go/common/clock/testclock"
|
| + "github.com/luci/luci-go/common/logging"
|
| + "github.com/luci/luci-go/common/logging/memlogger"
|
| + "github.com/luci/luci-go/common/retry"
|
| +
|
| + . "github.com/smartystreets/goconvey/convey"
|
| +)
|
| +
|
| +func sayHello(c C) http.HandlerFunc {
|
| + return func(w http.ResponseWriter, r *http.Request) {
|
| + c.So(r.Method, ShouldEqual, "POST")
|
| + c.So(r.URL.Path, ShouldEqual, "/prpc/prpc.Greeter/SayHello")
|
| + c.So(r.Header.Get("Accept"), ShouldEqual, "application/prpc")
|
| + c.So(r.Header.Get("Content-Type"), ShouldEqual, "application/prpc")
|
| + c.So(r.Header.Get("User-Agent"), ShouldEqual, "prpc-test")
|
| +
|
| + if timeout := r.Header.Get(HeaderTimeout); timeout != "" {
|
| + c.So(timeout, ShouldEqual, "10000000u")
|
| + }
|
| +
|
| + reqBody, err := ioutil.ReadAll(r.Body)
|
| + c.So(err, ShouldBeNil)
|
| +
|
| + var req HelloRequest
|
| + err = proto.Unmarshal(reqBody, &req)
|
| + c.So(err, ShouldBeNil)
|
| +
|
| + if req.Name == "TOO BIG" {
|
| + w.Header().Set("Content-Length", "999999999999")
|
| + }
|
| +
|
| + res := HelloReply{"Hello " + req.Name}
|
| + buf, err := proto.Marshal(&res)
|
| + c.So(err, ShouldBeNil)
|
| +
|
| + w.Header().Set(HeaderGRPCCode, strconv.Itoa(int(codes.OK)))
|
| + _, err = w.Write(buf)
|
| + c.So(err, ShouldBeNil)
|
| + }
|
| +}
|
| +
|
| +func doPanicHandler(w http.ResponseWriter, r *http.Request) {
|
| + panic("test panic")
|
| +}
|
| +
|
| +func transientErrors(count int, then http.Handler) http.HandlerFunc {
|
| + return func(w http.ResponseWriter, r *http.Request) {
|
| + if count > 0 {
|
| + count--
|
| + w.Header().Set(HeaderGRPCCode, strconv.Itoa(int(codes.Internal)))
|
| + w.WriteHeader(http.StatusInternalServerError)
|
| + fmt.Fprintln(w, "Server misbehaved")
|
| + return
|
| + }
|
| + then.ServeHTTP(w, r)
|
| + }
|
| +}
|
| +
|
| +func shouldHaveMessagesLike(actual interface{}, expected ...interface{}) string {
|
| + log := actual.(*memlogger.MemLogger)
|
| + msgs := log.Messages()
|
| +
|
| + So(msgs, ShouldHaveLength, len(expected))
|
| + for i, actual := range msgs {
|
| + expected := expected[i].(memlogger.LogEntry)
|
| + So(actual.Level, ShouldEqual, expected.Level)
|
| + So(actual.Msg, ShouldContainSubstring, expected.Msg)
|
| + }
|
| + return ""
|
| +}
|
| +
|
| +func TestClient(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey("Client", t, func() {
|
| + setUp := func(h http.HandlerFunc) (*Client, *httptest.Server) {
|
| + server := httptest.NewServer(h)
|
| + client := &Client{
|
| + Host: strings.TrimPrefix(server.URL, "http://"),
|
| + Options: &Options{
|
| + Retry: func() retry.Iterator {
|
| + return &retry.Limited{
|
| + Retries: 3,
|
| + Delay: 0,
|
| + }
|
| + },
|
| + Insecure: true,
|
| + UserAgent: "prpc-test",
|
| + },
|
| + }
|
| + return client, server
|
| + }
|
| +
|
| + ctx, _ := testclock.UseTime(context.Background(), testclock.TestTimeLocal)
|
| + ctx = memlogger.Use(ctx)
|
| + log := logging.Get(ctx).(*memlogger.MemLogger)
|
| + expectedCallLogEntry := func(c *Client) memlogger.LogEntry {
|
| + return memlogger.LogEntry{
|
| + Level: logging.Debug,
|
| + Msg: fmt.Sprintf("RPC %s/prpc.Greeter.SayHello", c.Host),
|
| + }
|
| + }
|
| +
|
| + req := &HelloRequest{"John"}
|
| + res := &HelloReply{}
|
| +
|
| + Convey("Call", func() {
|
| + Convey("Works", func(c C) {
|
| + client, server := setUp(sayHello(c))
|
| + defer server.Close()
|
| +
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(err, ShouldBeNil)
|
| + So(res.Message, ShouldEqual, "Hello John")
|
| +
|
| + So(log, shouldHaveMessagesLike, expectedCallLogEntry(client))
|
| + })
|
| +
|
| + Convey("With a deadline <= now, does not execute.", func(c C) {
|
| + client, server := setUp(doPanicHandler)
|
| + defer server.Close()
|
| +
|
| + ctx, _ = context.WithDeadline(ctx, clock.Now(ctx))
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(err, ShouldEqual, context.DeadlineExceeded)
|
| + })
|
| +
|
| + Convey("With a deadline in the future, sets the deadline header.", func(c C) {
|
| + client, server := setUp(sayHello(c))
|
| + defer server.Close()
|
| +
|
| + ctx, _ = clock.WithDeadline(ctx, clock.Now(ctx).Add(10*time.Second))
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(err, ShouldBeNil)
|
| + So(res.Message, ShouldEqual, "Hello John")
|
| +
|
| + So(log, shouldHaveMessagesLike, expectedCallLogEntry(client))
|
| + })
|
| +
|
| + Convey(`With a maximum content length smaller than the response, returns "ErrResponseTooBig".`, func(c C) {
|
| + client, server := setUp(sayHello(c))
|
| + defer server.Close()
|
| +
|
| + client.MaxContentLength = 8
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(err, ShouldEqual, ErrResponseTooBig)
|
| + })
|
| +
|
| + Convey(`When the response returns a huge Content Length, returns "ErrResponseTooBig".`, func(c C) {
|
| + client, server := setUp(sayHello(c))
|
| + defer server.Close()
|
| +
|
| + req.Name = "TOO BIG"
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(err, ShouldEqual, ErrResponseTooBig)
|
| + })
|
| +
|
| + Convey("HTTP 500 x2", func(c C) {
|
| + client, server := setUp(transientErrors(2, sayHello(c)))
|
| + defer server.Close()
|
| +
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(err, ShouldBeNil)
|
| + So(res.Message, ShouldEqual, "Hello John")
|
| +
|
| + So(log, shouldHaveMessagesLike,
|
| + expectedCallLogEntry(client),
|
| + memlogger.LogEntry{Level: logging.Warning, Msg: "RPC failed transiently. Will retry in 0"},
|
| +
|
| + expectedCallLogEntry(client),
|
| + memlogger.LogEntry{Level: logging.Warning, Msg: "RPC failed transiently. Will retry in 0"},
|
| +
|
| + expectedCallLogEntry(client),
|
| + )
|
| + })
|
| +
|
| + Convey("HTTP 500 many", func(c C) {
|
| + client, server := setUp(transientErrors(10, sayHello(c)))
|
| + defer server.Close()
|
| +
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(grpc.Code(err), ShouldEqual, codes.Internal)
|
| + So(grpc.ErrorDesc(err), ShouldEqual, "Server misbehaved")
|
| +
|
| + So(log, shouldHaveMessagesLike,
|
| + expectedCallLogEntry(client),
|
| + memlogger.LogEntry{Level: logging.Warning, Msg: "RPC failed transiently. Will retry in 0"},
|
| +
|
| + expectedCallLogEntry(client),
|
| + memlogger.LogEntry{Level: logging.Warning, Msg: "RPC failed transiently. Will retry in 0"},
|
| +
|
| + expectedCallLogEntry(client),
|
| + memlogger.LogEntry{Level: logging.Warning, Msg: "RPC failed transiently. Will retry in 0"},
|
| +
|
| + expectedCallLogEntry(client),
|
| + memlogger.LogEntry{Level: logging.Warning, Msg: "RPC failed permanently"},
|
| + )
|
| + })
|
| +
|
| + Convey("Forbidden", func(c C) {
|
| + client, server := setUp(func(w http.ResponseWriter, r *http.Request) {
|
| + w.Header().Set(HeaderGRPCCode, strconv.Itoa(int(codes.PermissionDenied)))
|
| + w.WriteHeader(http.StatusForbidden)
|
| + fmt.Fprintln(w, "Access denied")
|
| + })
|
| + defer server.Close()
|
| +
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(grpc.Code(err), ShouldEqual, codes.PermissionDenied)
|
| + So(grpc.ErrorDesc(err), ShouldEqual, "Access denied")
|
| +
|
| + So(log, shouldHaveMessagesLike,
|
| + expectedCallLogEntry(client),
|
| + memlogger.LogEntry{Level: logging.Warning, Msg: "RPC failed permanently"},
|
| + )
|
| + })
|
| +
|
| + Convey(HeaderGRPCCode, func(c C) {
|
| + client, server := setUp(func(w http.ResponseWriter, r *http.Request) {
|
| + w.Header().Set(HeaderGRPCCode, strconv.Itoa(int(codes.Canceled)))
|
| + w.WriteHeader(http.StatusBadRequest)
|
| + })
|
| + defer server.Close()
|
| +
|
| + err := client.Call(ctx, "prpc.Greeter", "SayHello", req, res)
|
| + So(grpc.Code(err), ShouldEqual, codes.Canceled)
|
| + })
|
| + })
|
| + })
|
| +}
|
|
|