Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1140)

Unified Diff: go/src/infra/libs/epclient/epclient_test.go

Issue 1153473008: A client/server helper wrapper for endpoints in Go. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: rename to better package Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: go/src/infra/libs/epclient/epclient_test.go
diff --git a/go/src/infra/libs/epclient/epclient_test.go b/go/src/infra/libs/epclient/epclient_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1b7fde910f389b506fb2bd2590aecde417e40733
--- /dev/null
+++ b/go/src/infra/libs/epclient/epclient_test.go
@@ -0,0 +1,235 @@
+// Copyright 2015 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 epclient
+
+import (
+ "bytes"
+ "fmt"
+ "golang.org/x/net/context"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ . "github.com/smartystreets/goconvey/convey"
+
+ "infra/gae/libs/endpoints"
+)
+
+func init() {
+ backoffSlot = time.Millisecond // doesn't matter, but it should be short.
+}
+
+func TestBackoff(t *testing.T) {
+ t.Parallel()
+
+ cases := []struct {
+ name string
+ failures uint
+ mult time.Duration // multiplier for slot
+ }{
+ {"zero", 0, 1},
+ {"one", 1, 2},
+ {"five", 5, 2 * 2 * 2 * 2 * 2},
+ {"lots", 500, 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2},
+ }
+
+ Convey("Backoff", t, func() {
+ for _, tc := range cases {
+ Convey(tc.name, func() {
+ So(Backoff(tc.failures), ShouldResemble, tc.mult*backoffSlot)
+ })
+ }
+ })
+}
+
+func TestDoer(t *testing.T) {
+ t.Parallel()
+
+ Convey("Test mkHttpDoer", t, func() {
+ serv := httptest.NewServer(http.HandlerFunc(func(rsp http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(rsp, "sup fool: %s", r.Method)
+ }))
+ defer serv.Close()
+
+ doer, closer := mkHttpDoer()
+ defer closer()
+
+ req, err := http.NewRequest("GET", serv.URL, nil)
+ So(err, ShouldBeNil)
+
+ rsp, err := doer(req)
+ So(err, ShouldBeNil)
+ defer rsp.Body.Close()
+
+ data, err := ioutil.ReadAll(rsp.Body)
+ So(err, ShouldBeNil)
+ So(data, ShouldResemble, []byte("sup fool: GET"))
+ })
+}
+
+type FakeService struct{}
+
+var FakeServiceMethodInfoMap = endpoints.MethodInfoMap{
+ "Get": {HTTPMethod: "GET"},
+}
+
+type CurResp struct{ Amt int }
+
+type AddReq struct {
+ CounterName string `endpoints:"required"`
+ Amt int
+}
+
+type GetReq struct {
+ CounterName string `endpoints:"required"`
+ Limit int
+ Flavor string
+}
+
+func (FakeService) Add(*http.Request, *AddReq) (*CurResp, error) { return nil, nil }
+func (FakeService) Get(*http.Request, *GetReq) (*CurResp, error) { return nil, nil }
+
+type fakeBody struct{ *bytes.Buffer }
+
+func (fakeBody) Close() error { return nil }
+
+type rsplet struct {
+ body string
+ statusCode int
+}
+
+func fakeDoer(retBody map[string][]interface{}) func() (func(*http.Request) (*http.Response, error), func()) {
+ return func() (func(*http.Request) (*http.Response, error), func()) {
+ return func(req *http.Request) (*http.Response, error) {
+ lookup := fmt.Sprintf("%s %s", req.Method, req.URL)
+ vals := retBody[lookup]
+ val := interface{}(nil)
+ if len(vals) > 0 {
+ val = vals[0]
+ retBody[lookup] = vals[1:]
+ }
+ if f, ok := val.(func() interface{}); ok {
+ val = f()
+ }
+ switch x := val.(type) {
+ case string:
+ return &http.Response{
+ Body: fakeBody{bytes.NewBufferString(x)},
+ StatusCode: 200,
+ }, nil
+ case rsplet:
+ return &http.Response{
+ Body: fakeBody{bytes.NewBufferString(x.body)},
+ StatusCode: x.statusCode,
+ }, nil
+ case error:
+ return nil, x
+ case *http.Response:
+ return x, nil
+ default:
+ return &http.Response{
+ Body: fakeBody{bytes.NewBufferString(
+ fmt.Sprintf(`{"error": {"message": "Not Found: %s"}}`, req.URL))},
+ StatusCode: 404,
+ }, nil
+ }
+ }, func() {}
+ }
+}
+
+func TestClient(t *testing.T) {
+ t.Parallel()
+
+ Convey("Test clients", t, func() {
+ srv, err := endpoints.Register(FakeService{}, nil, FakeServiceMethodInfoMap)
+ So(err, ShouldBeNil)
+ ctx, cancel := context.WithCancel(context.Background())
+ c := NewClient(ctx, "", srv)
+ So(c, ShouldNotBeNil)
+
+ Convey("essentially works", func() {
+ sc, err := c.ForService("FakeService")
+ So(err, ShouldBeNil)
+
+ c.(*client).mkHttpDoer = fakeDoer(map[string][]interface{}{
+ "POST /_ah/api/fakeservice/v1/add/foocounter": {
+ `{"Amt": 18}`,
+ rsplet{``, 500},
+ `{"Amt": 19}`,
+ },
+ // TODO(riannucci): verify that the quotes on cats is correct
+ `GET /_ah/api/fakeservice/v1/get/derpcounter?Limit=1&Flavor="cats"`: {
+ `{"Amt": 9001}`,
+ },
+ })
+
+ rsp := &CurResp{}
+ So(sc.DoWithRetries("Add", &AddReq{"foocounter", 19}, rsp, 1), ShouldBeNil)
+ So(rsp.Amt, ShouldEqual, 18)
+
+ So(sc.DoWithRetries("Get", &GetReq{"derpcounter", 1, "cats"}, rsp, 1), ShouldBeNil)
+ So(rsp.Amt, ShouldEqual, 9001)
+
+ // swallows the error and retries
+ So(sc.DoWithRetries("Add", &AddReq{"foocounter", 20}, rsp, 2), ShouldBeNil)
+ So(rsp.Amt, ShouldEqual, 19)
+ })
+
+ Convey("doesn't work for missing services", func() {
+ _, err := c.ForService("Wat")
+ So(err.Error(), ShouldContainSubstring, "no such service")
+
+ err = c.DoWithRetries("Wat", "fleem", nil, nil, 1)
+ So(err.Error(), ShouldContainSubstring, "no such service")
+ })
+
+ Convey("doesn't work for missing methods", func() {
+ sc, err := c.ForService("FakeService")
+ So(err, ShouldBeNil)
+
+ err = sc.DoWithRetries("fleem", nil, nil, 1)
+ So(err.Error(), ShouldContainSubstring, "no such method")
+ })
+
+ Convey("detects bad types", func() {
+ sc, err := c.ForService("FakeService")
+ So(err, ShouldBeNil)
+
+ err = sc.DoWithRetries("Add", "", "", 1)
+ So(err.Error(), ShouldContainSubstring, "mismatch for input")
+
+ err = sc.DoWithRetries("Add", &AddReq{}, "", 1)
+ So(err.Error(), ShouldContainSubstring, "mismatch for output")
+ })
+
+ Convey("can cancel it", func() {
+ sc, err := c.ForService("FakeService")
+ So(err, ShouldBeNil)
+
+ api := "POST /_ah/api/fakeservice/v1/add/foocounter"
+ fakeData := map[string][]interface{}{
+ api: {
+ nil, // force 404
+ func() interface{} {
+ cancel()
+ return nil
+ },
+ nil,
+ nil,
+ },
+ }
+ c.(*client).mkHttpDoer = fakeDoer(fakeData)
+
+ rsp := &CurResp{}
+ // 10 retries, but we'll never hit it
+ err = sc.DoWithRetries("Add", &AddReq{"foocounter", 19}, rsp, 10)
+ So(err.Error(), ShouldContainSubstring, "Not Found")
+ So(len(fakeData[api]), ShouldEqual, 2) // should have two 'nil's left
+ })
+
+ })
+}
« go/src/infra/libs/epclient/epclient.go ('K') | « go/src/infra/libs/epclient/epclient.infra_testing ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698