| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package prpc |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "io/ioutil" |
| 10 "net/http" |
| 11 "net/http/httptest" |
| 12 "strconv" |
| 13 "strings" |
| 14 "testing" |
| 15 "time" |
| 16 |
| 17 "github.com/golang/protobuf/proto" |
| 18 "golang.org/x/net/context" |
| 19 "google.golang.org/grpc" |
| 20 "google.golang.org/grpc/codes" |
| 21 |
| 22 "github.com/luci/luci-go/common/clock" |
| 23 "github.com/luci/luci-go/common/clock/testclock" |
| 24 "github.com/luci/luci-go/common/logging" |
| 25 "github.com/luci/luci-go/common/logging/memlogger" |
| 26 "github.com/luci/luci-go/common/retry" |
| 27 |
| 28 . "github.com/smartystreets/goconvey/convey" |
| 29 ) |
| 30 |
| 31 func sayHello(c C) http.HandlerFunc { |
| 32 return func(w http.ResponseWriter, r *http.Request) { |
| 33 c.So(r.Method, ShouldEqual, "POST") |
| 34 c.So(r.URL.Path, ShouldEqual, "/prpc/prpc.Greeter/SayHello") |
| 35 c.So(r.Header.Get("Accept"), ShouldEqual, "application/prpc") |
| 36 c.So(r.Header.Get("Content-Type"), ShouldEqual, "application/prp
c") |
| 37 c.So(r.Header.Get("User-Agent"), ShouldEqual, "prpc-test") |
| 38 |
| 39 if timeout := r.Header.Get(HeaderTimeout); timeout != "" { |
| 40 c.So(timeout, ShouldEqual, "10000000u") |
| 41 } |
| 42 |
| 43 reqBody, err := ioutil.ReadAll(r.Body) |
| 44 c.So(err, ShouldBeNil) |
| 45 |
| 46 var req HelloRequest |
| 47 err = proto.Unmarshal(reqBody, &req) |
| 48 c.So(err, ShouldBeNil) |
| 49 |
| 50 res := HelloReply{"Hello " + req.Name} |
| 51 buf, err := proto.Marshal(&res) |
| 52 c.So(err, ShouldBeNil) |
| 53 |
| 54 w.Header().Set(HeaderGRPCCode, strconv.Itoa(int(codes.OK))) |
| 55 _, err = w.Write(buf) |
| 56 c.So(err, ShouldBeNil) |
| 57 } |
| 58 } |
| 59 |
| 60 func doPanicHandler(w http.ResponseWriter, r *http.Request) { |
| 61 panic("test panic") |
| 62 } |
| 63 |
| 64 func transientErrors(count int, then http.Handler) http.HandlerFunc { |
| 65 return func(w http.ResponseWriter, r *http.Request) { |
| 66 if count > 0 { |
| 67 count-- |
| 68 w.Header().Set(HeaderGRPCCode, strconv.Itoa(int(codes.In
ternal))) |
| 69 w.WriteHeader(http.StatusInternalServerError) |
| 70 fmt.Fprintln(w, "Server misbehaved") |
| 71 return |
| 72 } |
| 73 then.ServeHTTP(w, r) |
| 74 } |
| 75 } |
| 76 |
| 77 func shouldHaveMessagesLike(actual interface{}, expected ...interface{}) string
{ |
| 78 log := actual.(*memlogger.MemLogger) |
| 79 msgs := log.Messages() |
| 80 |
| 81 So(msgs, ShouldHaveLength, len(expected)) |
| 82 for i, actual := range msgs { |
| 83 expected := expected[i].(memlogger.LogEntry) |
| 84 So(actual.Level, ShouldEqual, expected.Level) |
| 85 So(actual.Msg, ShouldContainSubstring, expected.Msg) |
| 86 } |
| 87 return "" |
| 88 } |
| 89 |
| 90 func TestClient(t *testing.T) { |
| 91 t.Parallel() |
| 92 |
| 93 Convey("Client", t, func() { |
| 94 setUp := func(h http.HandlerFunc) (*Client, *httptest.Server) { |
| 95 server := httptest.NewServer(h) |
| 96 client := &Client{ |
| 97 Host: strings.TrimPrefix(server.URL, "http://"), |
| 98 Options: &Options{ |
| 99 Retry: func() retry.Iterator { |
| 100 return &retry.Limited{ |
| 101 Retries: 3, |
| 102 Delay: 0, |
| 103 } |
| 104 }, |
| 105 Insecure: true, |
| 106 UserAgent: "prpc-test", |
| 107 }, |
| 108 } |
| 109 return client, server |
| 110 } |
| 111 |
| 112 ctx, _ := testclock.UseTime(context.Background(), testclock.Test
TimeLocal) |
| 113 ctx = memlogger.Use(ctx) |
| 114 log := logging.Get(ctx).(*memlogger.MemLogger) |
| 115 expectedCallLogEntry := func(c *Client) memlogger.LogEntry { |
| 116 return memlogger.LogEntry{ |
| 117 Level: logging.Debug, |
| 118 Msg: fmt.Sprintf("RPC %s/prpc.Greeter.SayHello
", c.Host), |
| 119 } |
| 120 } |
| 121 |
| 122 req := &HelloRequest{"John"} |
| 123 res := &HelloReply{} |
| 124 |
| 125 Convey("Call", func() { |
| 126 Convey("Works", func(c C) { |
| 127 client, server := setUp(sayHello(c)) |
| 128 defer server.Close() |
| 129 |
| 130 err := client.Call(ctx, "prpc.Greeter", "SayHell
o", req, res) |
| 131 So(err, ShouldBeNil) |
| 132 So(res.Message, ShouldEqual, "Hello John") |
| 133 |
| 134 So(log, shouldHaveMessagesLike, expectedCallLogE
ntry(client)) |
| 135 }) |
| 136 |
| 137 Convey("With a deadline <= now, does not execute.", func
(c C) { |
| 138 client, server := setUp(doPanicHandler) |
| 139 defer server.Close() |
| 140 |
| 141 ctx, _ = context.WithDeadline(ctx, clock.Now(ctx
)) |
| 142 err := client.Call(ctx, "prpc.Greeter", "SayHell
o", req, res) |
| 143 So(err, ShouldEqual, context.DeadlineExceeded) |
| 144 }) |
| 145 |
| 146 Convey("With a deadline in the future, sets the deadline
header.", func(c C) { |
| 147 client, server := setUp(sayHello(c)) |
| 148 defer server.Close() |
| 149 |
| 150 ctx, _ = clock.WithDeadline(ctx, clock.Now(ctx).
Add(10*time.Second)) |
| 151 err := client.Call(ctx, "prpc.Greeter", "SayHell
o", req, res) |
| 152 So(err, ShouldBeNil) |
| 153 So(res.Message, ShouldEqual, "Hello John") |
| 154 |
| 155 So(log, shouldHaveMessagesLike, expectedCallLogE
ntry(client)) |
| 156 }) |
| 157 |
| 158 Convey("HTTP 500 x2", func(c C) { |
| 159 client, server := setUp(transientErrors(2, sayHe
llo(c))) |
| 160 defer server.Close() |
| 161 |
| 162 err := client.Call(ctx, "prpc.Greeter", "SayHell
o", req, res) |
| 163 So(err, ShouldBeNil) |
| 164 So(res.Message, ShouldEqual, "Hello John") |
| 165 |
| 166 So(log, shouldHaveMessagesLike, |
| 167 expectedCallLogEntry(client), |
| 168 memlogger.LogEntry{Level: logging.Warnin
g, Msg: "RPC failed transiently. Will retry in 0"}, |
| 169 |
| 170 expectedCallLogEntry(client), |
| 171 memlogger.LogEntry{Level: logging.Warnin
g, Msg: "RPC failed transiently. Will retry in 0"}, |
| 172 |
| 173 expectedCallLogEntry(client), |
| 174 ) |
| 175 }) |
| 176 |
| 177 Convey("HTTP 500 many", func(c C) { |
| 178 client, server := setUp(transientErrors(10, sayH
ello(c))) |
| 179 defer server.Close() |
| 180 |
| 181 err := client.Call(ctx, "prpc.Greeter", "SayHell
o", req, res) |
| 182 So(grpc.Code(err), ShouldEqual, codes.Internal) |
| 183 So(grpc.ErrorDesc(err), ShouldEqual, "Server mis
behaved") |
| 184 |
| 185 So(log, shouldHaveMessagesLike, |
| 186 expectedCallLogEntry(client), |
| 187 memlogger.LogEntry{Level: logging.Warnin
g, Msg: "RPC failed transiently. Will retry in 0"}, |
| 188 |
| 189 expectedCallLogEntry(client), |
| 190 memlogger.LogEntry{Level: logging.Warnin
g, Msg: "RPC failed transiently. Will retry in 0"}, |
| 191 |
| 192 expectedCallLogEntry(client), |
| 193 memlogger.LogEntry{Level: logging.Warnin
g, Msg: "RPC failed transiently. Will retry in 0"}, |
| 194 |
| 195 expectedCallLogEntry(client), |
| 196 memlogger.LogEntry{Level: logging.Warnin
g, Msg: "RPC failed permanently"}, |
| 197 ) |
| 198 }) |
| 199 |
| 200 Convey("Forbidden", func(c C) { |
| 201 client, server := setUp(func(w http.ResponseWrit
er, r *http.Request) { |
| 202 w.Header().Set(HeaderGRPCCode, strconv.I
toa(int(codes.PermissionDenied))) |
| 203 w.WriteHeader(http.StatusForbidden) |
| 204 fmt.Fprintln(w, "Access denied") |
| 205 }) |
| 206 defer server.Close() |
| 207 |
| 208 err := client.Call(ctx, "prpc.Greeter", "SayHell
o", req, res) |
| 209 So(grpc.Code(err), ShouldEqual, codes.Permission
Denied) |
| 210 So(grpc.ErrorDesc(err), ShouldEqual, "Access den
ied") |
| 211 |
| 212 So(log, shouldHaveMessagesLike, |
| 213 expectedCallLogEntry(client), |
| 214 memlogger.LogEntry{Level: logging.Warnin
g, Msg: "RPC failed permanently"}, |
| 215 ) |
| 216 }) |
| 217 |
| 218 Convey(HeaderGRPCCode, func(c C) { |
| 219 client, server := setUp(func(w http.ResponseWrit
er, r *http.Request) { |
| 220 w.Header().Set(HeaderGRPCCode, strconv.I
toa(int(codes.Canceled))) |
| 221 w.WriteHeader(http.StatusBadRequest) |
| 222 }) |
| 223 defer server.Close() |
| 224 |
| 225 err := client.Call(ctx, "prpc.Greeter", "SayHell
o", req, res) |
| 226 So(grpc.Code(err), ShouldEqual, codes.Canceled) |
| 227 }) |
| 228 }) |
| 229 }) |
| 230 } |
| OLD | NEW |