Skip to content

Commit

Permalink
yandex#30: test base http provider
Browse files Browse the repository at this point in the history
  • Loading branch information
skipor committed Feb 9, 2017
1 parent 544f6e7 commit 6dd6224
Show file tree
Hide file tree
Showing 20 changed files with 520 additions and 183 deletions.
3 changes: 2 additions & 1 deletion ammo/ammo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import "context"
type Provider interface {
Start(context.Context) error
Source() <-chan Ammo
Release(Ammo) // return unused Ammo object to memory pool
// Release notifies that ammo usage is finished.
Release(Ammo)
}

type Ammo interface{}
Expand Down
18 changes: 4 additions & 14 deletions ammo/http_ammo.go → ammo/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
package ammo

import (
"io"
"net/http"

"github.com/yandex/pandora/aggregate"
)

//go:generate mockery -name=HTTP -case=underscore -outpkg=ammomocks

// HTTP ammo interface for http based guns.
// HTTP ammo providers should produce ammo that implements HTTP.
// HTTP guns should use convert ammo to HTTP interface, not to specific implementation.
// http ammo providers should produce ammo that implements HTTP.
// http guns should use convert ammo to HTTP, not to specific implementation.
type HTTP interface {
// TODO (skipor) instead of sample use some more usable interface.
Request() (*http.Request, *aggregate.Sample)
Expand All @@ -35,14 +36,3 @@ func (a *SimpleHTTP) Reset(req *http.Request, tag string) {
}

var _ HTTP = (*SimpleHTTP)(nil)

type ReadSeeker interface {
io.ReadSeeker
io.ReaderAt
}

// TODO (skipor): need it?
type HTTPDecoder interface {
Decode(r *http.Request) error
Reset() error
}
38 changes: 38 additions & 0 deletions ammo/mocks/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ammomocks

import "github.com/yandex/pandora/aggregate"
import "github.com/yandex/pandora/ammo"
import "net/http"
import "github.com/stretchr/testify/mock"

// HTTP is an autogenerated mock type for the HTTP type
type HTTP struct {
mock.Mock
}

// Request provides a mock function with given fields:
func (_m *HTTP) Request() (*http.Request, *aggregate.Sample) {
ret := _m.Called()

var r0 *http.Request
if rf, ok := ret.Get(0).(func() *http.Request); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*http.Request)
}
}

var r1 *aggregate.Sample
if rf, ok := ret.Get(1).(func() *aggregate.Sample); ok {
r1 = rf()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*aggregate.Sample)
}
}

return r0, r1
}

var _ ammo.HTTP = (*HTTP)(nil)
1 change: 1 addition & 0 deletions config/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/c2h5oh/datasize"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/yandex/pandora/plugin"
)

Expand Down
11 changes: 7 additions & 4 deletions gun/phttp/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/yandex/pandora/gun"
)

// TODO: inject logger
type Base struct {
Do func(r *http.Request) (*http.Response, error) // Required.
Connect func(ctx context.Context) error // Optional hook.
Expand All @@ -44,8 +45,10 @@ func (b *Base) Shoot(ctx context.Context, a ammo.Ammo) (err error) {
}
if b.Connect != nil {
err = b.Connect(ctx)
log.Printf("Connect error: %s\n", err)
return
if err != nil {
log.Printf("Connect error: %s\n", err)
return
}
}

ha := a.(ammo.HTTP)
Expand All @@ -63,14 +66,14 @@ func (b *Base) Shoot(ctx context.Context, a ammo.Ammo) (err error) {
log.Printf("Error performing a request: %s\n", err)
return
}
sample.SetProtoCode(res.StatusCode)
defer res.Body.Close()
// TODO: measure body read
// TODO: measure body read time
_, err = io.Copy(ioutil.Discard, res.Body)
if err != nil {
log.Printf("Error reading response body: %s\n", err)
return
}
sample.SetProtoCode(res.StatusCode)
// TODO: verbose logging
return
}
Expand Down
145 changes: 145 additions & 0 deletions gun/phttp/base_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package phttp

import (
"context"
"errors"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"

"github.com/yandex/pandora/aggregate"
"github.com/yandex/pandora/ammo/mocks"
)

var _ = Describe("Base", func() {
var (
base Base
ammo *ammomocks.HTTP
)
BeforeEach(func() {
base = Base{}
ammo = &ammomocks.HTTP{}
})

Context("BindResultTo", func() {
It("nil panics", func() {
Expect(func() {
base.BindResultsTo(nil)
}).To(Panic())
})
It("second time panics", func() {
res := make(chan<- *aggregate.Sample)
base.BindResultsTo(res)
Expect(base.Results).To(Equal(res))
Expect(func() {
base.BindResultsTo(make(chan<- *aggregate.Sample))
}).To(Panic())
})
})

It("Shoot before bind panics", func() {
base.Do = func(*http.Request) (_ *http.Response, _ error) {
Fail("should not be called")
return
}
am := &ammomocks.HTTP{}
am.On("Request").Return(nil, nil).Run(
func(mock.Arguments) {
Fail("should not be caled")
})
Expect(func() {
base.Shoot(context.Background(), am)
}).To(Panic())
}, 1)

Context("Shoot", func() {
var (
body io.ReadCloser

ctx context.Context
am *ammomocks.HTTP
req *http.Request
res *http.Response
sample *aggregate.Sample
results chan *aggregate.Sample
shootErr error
)
BeforeEach(func() {
ctx = context.Background()
am = &ammomocks.HTTP{}
req = httptest.NewRequest("GET", "/", nil)
sample = aggregate.AcquireSample("REQUEST")
am.On("Request").Return(req, sample)
results = make(chan *aggregate.Sample, 1) // Results buffered.
base.BindResultsTo(results)
})

JustBeforeEach(func() {
res = &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(body),
Request: req,
}
shootErr = base.Shoot(ctx, am)
})

Context("Do ok", func() {
BeforeEach(func() {
body = ioutil.NopCloser(strings.NewReader("aaaaaaa"))
base.Do = func(doReq *http.Request) (*http.Response, error) {
Expect(doReq).To(Equal(req))
return res, nil
}
})

It("ammo sample sent to results", func() {
var gotSample *aggregate.Sample
Eventually(results).Should(Receive(&gotSample))
Expect(gotSample).To(Equal(sample))
Expect(sample.ProtoCode()).To(Equal(res.StatusCode))
})
It("body read well", func() {
Expect(shootErr).To(BeNil())
_, err := body.Read([]byte{0})
Expect(err).To(Equal(io.EOF), "body should be read fully")
})
Context("Connect set", func() {
var connectCalled, doCalled bool
BeforeEach(func() {
base.Connect = func(ctx context.Context) error {
connectCalled = true
return nil
}
oldDo := base.Do
base.Do = func(r *http.Request) (*http.Response, error) {
doCalled = true
return oldDo(r)
}
})
It("Connect called", func() {
Expect(shootErr).To(BeNil())
Expect(connectCalled).To(BeTrue())
Expect(doCalled).To(BeTrue())
})
})
Context("Connect failed", func() {
connectErr := errors.New("connect error")
BeforeEach(func() {
base.Connect = func(ctx context.Context) error {
return connectErr
}
})
It("Shoot failed", func() {
Expect(shootErr).NotTo(BeNil())
Expect(shootErr).To(Equal(connectErr))
})
})
})
})
})
6 changes: 4 additions & 2 deletions gun/phttp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import (
"github.com/yandex/pandora/config"
)

//go:generate mockery -name=Client -case=underscore -inpkg -testonly

type Client interface {
Do(r *http.Request) (*http.Response, error)
}

type ClientConfig struct {
TransportConfig TransportConfig `config:",squash"`
DialerConfig DialerConfig `config:"dial"`
Transport TransportConfig `config:",squash"`
Dialer DialerConfig `config:"dial"`
}

func NewDefaultClientConfig() ClientConfig {
Expand Down
43 changes: 29 additions & 14 deletions gun/phttp/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,37 @@ import (
)

type HTTPGunConfig struct {
Target string `validate:"endpoint,required"`
SSL bool
ClientConfig `config:",squash"`
Target string `validate:"endpoint,required"`
SSL bool
}

func NewDefaultHTTPGunConfig() HTTPGunConfig {
return HTTPGunConfig{
SSL: false,
ClientConfig: NewDefaultClientConfig(),
}
}

func NewHTTPGun(conf HTTPGunConfig) *HTTPGun {
func NewHTTPGun(client Client, conf HTTPGunConfig) *HTTPGun {
scheme := "http"
if conf.SSL {
scheme = "https"
}
transport := NewTransport(conf.TransportConfig)
transport.DialContext = NewDialer(conf.DialerConfig).DialContext
var g HTTPGun
g = HTTPGun{
Base: Base{Do: g.Do},
scheme: scheme,
target: conf.Target,
client: &http.Client{Transport: transport},
client: client,
}
return &g
}

type HTTPGunClientConfig struct {
Gun HTTPGunConfig `config:",squash"`
Client ClientConfig `config:",squash"`
}

func NewHTTPGunClient(conf HTTPGunClientConfig) *HTTPGun {
transport := NewTransport(conf.Client.Transport)
transport.DialContext = NewDialer(conf.Client.Dialer).DialContext
client := &http.Client{Transport: transport}
return NewHTTPGun(client, conf.Gun)
}

type HTTPGun struct {
Base
scheme string
Expand All @@ -50,3 +52,16 @@ func (g *HTTPGun) Do(req *http.Request) (*http.Response, error) {
req.URL.Host = g.target
return g.client.Do(req)
}

func NewDefaultHTTPGunClientConfig() HTTPGunClientConfig {
return HTTPGunClientConfig{
Gun: NewDefaultHTTPGunConfig(),
Client: NewDefaultClientConfig(),
}
}

func NewDefaultHTTPGunConfig() HTTPGunConfig {
return HTTPGunConfig{
SSL: false,
}
}
Loading

0 comments on commit 6dd6224

Please sign in to comment.