diff --git a/components/example/import/import_suite_test.go b/components/example/import/import_suite_test.go new file mode 100644 index 000000000..06002b770 --- /dev/null +++ b/components/example/import/import_suite_test.go @@ -0,0 +1,20 @@ +package example + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/yandex/pandora/lib/testutil" +) + +func TestImport(t *testing.T) { + testutil.RunSuite(t, "Import Suite") +} + +var _ = Describe("import", func() { + It("not panics", func() { + Expect(Import).NotTo(Panic()) + }) +}) diff --git a/components/phttp/client.go b/components/phttp/client.go index 0f72d2466..7ee02d409 100644 --- a/components/phttp/client.go +++ b/components/phttp/client.go @@ -6,7 +6,6 @@ package phttp import ( - "context" "crypto/tls" "net" "net/http" @@ -17,6 +16,7 @@ import ( "github.com/pkg/errors" "github.com/yandex/pandora/core/config" + "github.com/yandex/pandora/lib/netutil" ) //go:generate mockery -name=Client -case=underscore -inpkg -testonly @@ -43,6 +43,8 @@ func NewDefaultClientConfig() ClientConfig { // DialerConfig can be mapped on net.Dialer. // Set net.Dialer for details. type DialerConfig struct { + DNSCache bool `config:"dns-cache" map:"-"` + Timeout time.Duration `config:"timeout"` DualStack bool `config:"dual-stack"` @@ -54,25 +56,20 @@ type DialerConfig struct { func NewDefaultDialerConfig() DialerConfig { return DialerConfig{ + DNSCache: true, + DualStack: true, Timeout: 3 * time.Second, KeepAlive: 120 * time.Second, } } -type Dialer interface { - DialContext(ctx context.Context, network, address string) (net.Conn, error) -} - -type DialerFunc func(ctx context.Context, network, address string) (net.Conn, error) - -func (f DialerFunc) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - return f(ctx, network, address) -} - -func NewDialer(conf DialerConfig) *net.Dialer { +func NewDialer(conf DialerConfig) netutil.Dialer { d := &net.Dialer{} config.Map(d, conf) - return d + if !conf.DNSCache { + return d + } + return netutil.NewDNSCachingDialer(d, netutil.DefaultDNSCache) } // TransportConfig can be mapped on http.Transport. @@ -98,7 +95,7 @@ func NewDefaultTransportConfig() TransportConfig { } } -func NewTransport(conf TransportConfig, dial DialerFunc) *http.Transport { +func NewTransport(conf TransportConfig, dial netutil.DialerFunc) *http.Transport { tr := &http.Transport{} tr.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, // We should not spend time for this stuff. @@ -109,7 +106,7 @@ func NewTransport(conf TransportConfig, dial DialerFunc) *http.Transport { return tr } -func NewHTTP2Transport(conf TransportConfig, dial DialerFunc) *http.Transport { +func NewHTTP2Transport(conf TransportConfig, dial netutil.DialerFunc) *http.Transport { tr := NewTransport(conf, dial) err := http2.ConfigureTransport(tr) if err != nil { diff --git a/components/phttp/connect.go b/components/phttp/connect.go index 4ea48b289..a05a3d8ec 100644 --- a/components/phttp/connect.go +++ b/components/phttp/connect.go @@ -15,6 +15,7 @@ import ( "net/url" "github.com/pkg/errors" + "github.com/yandex/pandora/lib/netutil" ) type ConnectGunConfig struct { @@ -78,7 +79,7 @@ func newConnectClient(conf ConnectGunConfig) Client { return newClient(transport, conf.Client.Redirect) } -func newConnectDialFunc(target string, connectSSL bool, dialer Dialer) DialerFunc { +func newConnectDialFunc(target string, connectSSL bool, dialer netutil.Dialer) netutil.DialerFunc { return func(ctx context.Context, network, address string) (conn net.Conn, err error) { // TODO(skipor): make connect sample. // TODO(skipor): make httptrace callbacks called correctly. diff --git a/components/phttp/http.go b/components/phttp/http.go index 1b33c313a..f043a6d86 100644 --- a/components/phttp/http.go +++ b/components/phttp/http.go @@ -36,7 +36,7 @@ func NewHTTPGun(conf HTTPGunConfig) *HTTPGun { // NewHTTP2Gun return simple HTTP/2 gun that can shoot sequentially through one connection. func NewHTTP2Gun(conf HTTP2GunConfig) (*HTTPGun, error) { if !conf.Gun.SSL { - // Open issue on github if you need this feature. + // Open issue on github if you really need this feature. return nil, errors.New("HTTP/2.0 over TCP is not supported. Please leave SSL option true by default.") } transport := NewHTTP2Transport(conf.Client.Transport, NewDialer(conf.Client.Dialer).DialContext) diff --git a/components/phttp/import/import.go b/components/phttp/import/import.go index 5ea87f375..9733cf5bf 100644 --- a/components/phttp/import/import.go +++ b/components/phttp/import/import.go @@ -6,7 +6,10 @@ package phttp import ( + "net" + "github.com/spf13/afero" + "go.uber.org/zap" . "github.com/yandex/pandora/components/phttp" "github.com/yandex/pandora/components/phttp/ammo/simple/jsonline" @@ -14,6 +17,7 @@ import ( "github.com/yandex/pandora/components/phttp/ammo/simple/uri" "github.com/yandex/pandora/core" "github.com/yandex/pandora/core/register" + "github.com/yandex/pandora/lib/netutil" ) func Import(fs afero.Fs) { @@ -29,16 +33,56 @@ func Import(fs afero.Fs) { return raw.NewProvider(fs, conf) }) - register.Gun("http", func(conf HTTPGunConfig) core.Gun { - return WrapGun(NewHTTPGun(conf)) + register.Gun("http", func(conf HTTPGunConfig) func() core.Gun { + preResolveTargetAddr(&conf.Client, &conf.Gun.Target) + return func() core.Gun { return WrapGun(NewHTTPGun(conf)) } }, NewDefaultHTTPGunConfig) - register.Gun("http2", func(conf HTTP2GunConfig) (core.Gun, error) { - gun, err := NewHTTP2Gun(conf) - return WrapGun(gun), err + register.Gun("http2", func(conf HTTP2GunConfig) func() (core.Gun, error) { + preResolveTargetAddr(&conf.Client, &conf.Gun.Target) + return func() (core.Gun, error) { + gun, err := NewHTTP2Gun(conf) + return WrapGun(gun), err + } }, NewDefaultHTTP2GunConfig) - register.Gun("connect", func(conf ConnectGunConfig) core.Gun { - return WrapGun(NewConnectGun(conf)) + register.Gun("connect", func(conf ConnectGunConfig) func() core.Gun { + preResolveTargetAddr(&conf.Client, &conf.Target) + return func() core.Gun { + return WrapGun(NewConnectGun(conf)) + } }, NewDefaultConnectGunConfig) } + +// DNS resolve optimisation. +// When DNSCache turned off - do nothing extra, host will be resolved on every shoot. +// When using resolved target, don't use DNS caching logic - it is useless. +// If we can resolve accessible target addr - use it as target, not use caching. +// Otherwise just use DNS cache - we should not fail shooting, we should try to +// connect on every shoot. DNS cache will save resolved addr after first successful connect. +func preResolveTargetAddr(clientConf *ClientConfig, target *string) (err error) { + if !clientConf.Dialer.DNSCache { + return + } + if endpointIsResolved(*target) { + clientConf.Dialer.DNSCache = false + return + } + resolved, err := netutil.LookupReachable(*target) + if err != nil { + zap.L().Warn("DNS target pre resolve failed", + zap.String("target", *target), zap.Error(err)) + return + } + clientConf.Dialer.DNSCache = false + *target = resolved + return +} + +func endpointIsResolved(endpoint string) bool { + host, _, err := net.SplitHostPort(endpoint) + if err != nil { + return false + } + return net.ParseIP(host) != nil +} diff --git a/components/phttp/import/import_suite_test.go b/components/phttp/import/import_suite_test.go new file mode 100644 index 000000000..b8432e4c2 --- /dev/null +++ b/components/phttp/import/import_suite_test.go @@ -0,0 +1,72 @@ +package phttp + +import ( + "net" + "strconv" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/spf13/afero" + + . "github.com/yandex/pandora/components/phttp" + "github.com/yandex/pandora/lib/testutil" +) + +func TestImport(t *testing.T) { + testutil.RunSuite(t, "phttp Import Suite") +} + +var _ = Describe("import", func() { + It("not panics", func() { + Expect(func() { + Import(afero.NewOsFs()) + }).NotTo(Panic()) + }) +}) + +var _ = Describe("preResolveTargetAddr", func() { + It("host target", func() { + conf := &ClientConfig{} + conf.Dialer.DNSCache = true + + listener, err := net.ListenTCP("tcp6", nil) + defer listener.Close() + Expect(err).NotTo(HaveOccurred()) + + port := strconv.Itoa(listener.Addr().(*net.TCPAddr).Port) + target := "localhost:" + port + expectedResolved := "[::1]:" + port + + err = preResolveTargetAddr(conf, &target) + Expect(err).NotTo(HaveOccurred()) + Expect(conf.Dialer.DNSCache).To(BeFalse()) + + Expect(target).To(Equal(expectedResolved)) + }) + + It("ip target", func() { + conf := &ClientConfig{} + conf.Dialer.DNSCache = true + + const addr = "127.0.0.1:80" + target := addr + err := preResolveTargetAddr(conf, &target) + Expect(err).NotTo(HaveOccurred()) + Expect(conf.Dialer.DNSCache).To(BeFalse()) + Expect(target).To(Equal(addr)) + }) + + It("failed", func() { + conf := &ClientConfig{} + conf.Dialer.DNSCache = true + + const addr = "localhost:54321" + target := addr + err := preResolveTargetAddr(conf, &target) + Expect(err).To(HaveOccurred()) + Expect(conf.Dialer.DNSCache).To(BeTrue()) + Expect(target).To(Equal(addr)) + }) + +}) diff --git a/core/import/import_suite_test.go b/core/import/import_suite_test.go index cb41a2952..77a71b008 100644 --- a/core/import/import_suite_test.go +++ b/core/import/import_suite_test.go @@ -6,7 +6,6 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/format" "github.com/spf13/afero" "github.com/yandex/pandora/core" @@ -16,13 +15,8 @@ import ( ) func TestImport(t *testing.T) { - format.UseStringerRepresentation = true - RegisterFailHandler(Fail) - - testutil.ReplaceGlobalLogger() Import(afero.NewOsFs()) - - RunSpecs(t, "Import Suite") + testutil.RunSuite(t, "Import Suite") } var _ = Describe("plugin decode", func() { diff --git a/lib/netutil/dial.go b/lib/netutil/dial.go new file mode 100644 index 000000000..44c94b0e1 --- /dev/null +++ b/lib/netutil/dial.go @@ -0,0 +1,115 @@ +// Copyright (c) 2017 Yandex LLC. All rights reserved. +// Use of this source code is governed by a MPL 2.0 +// license that can be found in the LICENSE file. +// Author: Vladimir Skipor + +package netutil + +import ( + "context" + "net" + "sync" + + "github.com/pkg/errors" +) + +//go:generate mockery -name=Dialer -case=underscore -outpkg=netmock + +type Dialer interface { + DialContext(ctx context.Context, net, addr string) (net.Conn, error) +} + +var _ Dialer = &net.Dialer{} + +type DialerFunc func(ctx context.Context, network, address string) (net.Conn, error) + +func (f DialerFunc) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return f(ctx, network, address) +} + +// NewDNSCachingDialer returns dialer with primitive DNS caching logic +// that remembers remote address on first try, and use it in future. +func NewDNSCachingDialer(dialer Dialer, cache DNSCache) DialerFunc { + return func(ctx context.Context, network, addr string) (conn net.Conn, err error) { + resolved, ok := cache.Get(addr) + if ok { + return dialer.DialContext(ctx, network, resolved) + } + conn, err = dialer.DialContext(ctx, network, addr) + if err != nil { + return + } + remoteAddr := conn.RemoteAddr().(*net.TCPAddr) + _, port, err := net.SplitHostPort(addr) + if err != nil { + conn.Close() + return nil, errors.Wrap(err, "invalid address, but successful dial - should not happen") + } + cache.Add(addr, net.JoinHostPort(remoteAddr.IP.String(), port)) + return + } +} + +var DefaultDNSCache = &SimpleDNSCache{} + +// LookupReachable tries to resolve addr via connecting to it. +// This method has much more overhead, but get guaranteed reachable resolved addr. +// Example: host is resolved to IPv4 and IPv6, but IPv4 is not working on machine. +// LookupAccessible will return IPv6 in that case. +func LookupReachable(addr string) (string, error) { + d := net.Dialer{DualStack: true} + conn, err := d.Dial("tcp", addr) + if err != nil { + return "", err + } + defer conn.Close() + _, port, err := net.SplitHostPort(addr) + if err != nil { + return "", err + } + remoteAddr := conn.RemoteAddr().(*net.TCPAddr) + return net.JoinHostPort(remoteAddr.IP.String(), port), nil +} + +// WarmDNSCache tries connect to addr, and adds conn remote ip + addr port to cache. +func WarmDNSCache(c DNSCache, addr string) error { + var d net.Dialer + conn, err := NewDNSCachingDialer(&d, c).DialContext(context.Background(), "tcp", addr) + if err != nil { + return err + } + conn.Close() + return nil +} + +//go:generate mockery -name=DNSCache -case=underscore -outpkg=netmock + +type DNSCache interface { + Get(addr string) (string, bool) + Add(addr, resolved string) +} + +type SimpleDNSCache struct { + rw sync.RWMutex + hostToAddr map[string]string +} + +func (c *SimpleDNSCache) Get(addr string) (resolved string, ok bool) { + c.rw.RLock() + if c.hostToAddr == nil { + c.rw.RUnlock() + return + } + resolved, ok = c.hostToAddr[addr] + c.rw.RUnlock() + return +} + +func (c *SimpleDNSCache) Add(addr, resolved string) { + c.rw.Lock() + if c.hostToAddr == nil { + c.hostToAddr = make(map[string]string) + } + c.hostToAddr[addr] = resolved + c.rw.Unlock() +} diff --git a/lib/netutil/mocks/conn.go b/lib/netutil/mocks/conn.go new file mode 100644 index 000000000..7f10c79c0 --- /dev/null +++ b/lib/netutil/mocks/conn.go @@ -0,0 +1,142 @@ +// Code generated by mockery v1.0.0 +package netmock + +import "github.com/stretchr/testify/mock" +import "net" + +import "time" + +// Conn is an autogenerated mock type for the Conn type +type Conn struct { + mock.Mock +} + +// Close provides a mock function with given fields: +func (_m *Conn) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// LocalAddr provides a mock function with given fields: +func (_m *Conn) LocalAddr() net.Addr { + ret := _m.Called() + + var r0 net.Addr + if rf, ok := ret.Get(0).(func() net.Addr); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(net.Addr) + } + } + + return r0 +} + +// Read provides a mock function with given fields: b +func (_m *Conn) Read(b []byte) (int, error) { + ret := _m.Called(b) + + var r0 int + if rf, ok := ret.Get(0).(func([]byte) int); ok { + r0 = rf(b) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(b) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoteAddr provides a mock function with given fields: +func (_m *Conn) RemoteAddr() net.Addr { + ret := _m.Called() + + var r0 net.Addr + if rf, ok := ret.Get(0).(func() net.Addr); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(net.Addr) + } + } + + return r0 +} + +// SetDeadline provides a mock function with given fields: t +func (_m *Conn) SetDeadline(t time.Time) error { + ret := _m.Called(t) + + var r0 error + if rf, ok := ret.Get(0).(func(time.Time) error); ok { + r0 = rf(t) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetReadDeadline provides a mock function with given fields: t +func (_m *Conn) SetReadDeadline(t time.Time) error { + ret := _m.Called(t) + + var r0 error + if rf, ok := ret.Get(0).(func(time.Time) error); ok { + r0 = rf(t) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetWriteDeadline provides a mock function with given fields: t +func (_m *Conn) SetWriteDeadline(t time.Time) error { + ret := _m.Called(t) + + var r0 error + if rf, ok := ret.Get(0).(func(time.Time) error); ok { + r0 = rf(t) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Write provides a mock function with given fields: b +func (_m *Conn) Write(b []byte) (int, error) { + ret := _m.Called(b) + + var r0 int + if rf, ok := ret.Get(0).(func([]byte) int); ok { + r0 = rf(b) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(b) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/lib/netutil/mocks/dialer.go b/lib/netutil/mocks/dialer.go new file mode 100644 index 000000000..27e1ea755 --- /dev/null +++ b/lib/netutil/mocks/dialer.go @@ -0,0 +1,34 @@ +// Code generated by mockery v1.0.0 +package netmock + +import "context" +import "github.com/stretchr/testify/mock" +import "net" + +// Dialer is an autogenerated mock type for the Dialer type +type Dialer struct { + mock.Mock +} + +// DialContext provides a mock function with given fields: ctx, _a1, addr +func (_m *Dialer) DialContext(ctx context.Context, _a1 string, addr string) (net.Conn, error) { + ret := _m.Called(ctx, _a1, addr) + + var r0 net.Conn + if rf, ok := ret.Get(0).(func(context.Context, string, string) net.Conn); ok { + r0 = rf(ctx, _a1, addr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(net.Conn) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, _a1, addr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/lib/netutil/mocks/dns_cache.go b/lib/netutil/mocks/dns_cache.go new file mode 100644 index 000000000..ca9979966 --- /dev/null +++ b/lib/netutil/mocks/dns_cache.go @@ -0,0 +1,35 @@ +// Code generated by mockery v1.0.0 +package netmock + +import "github.com/stretchr/testify/mock" + +// DNSCache is an autogenerated mock type for the DNSCache type +type DNSCache struct { + mock.Mock +} + +// Add provides a mock function with given fields: addr, resolved +func (_m *DNSCache) Add(addr string, resolved string) { + _m.Called(addr, resolved) +} + +// Get provides a mock function with given fields: addr +func (_m *DNSCache) Get(addr string) (string, bool) { + ret := _m.Called(addr) + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(addr) + } else { + r0 = ret.Get(0).(string) + } + + var r1 bool + if rf, ok := ret.Get(1).(func(string) bool); ok { + r1 = rf(addr) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} diff --git a/lib/netutil/netutil_suite_test.go b/lib/netutil/netutil_suite_test.go new file mode 100644 index 000000000..462bb073c --- /dev/null +++ b/lib/netutil/netutil_suite_test.go @@ -0,0 +1,107 @@ +package netutil + +import ( + "context" + "net" + "strconv" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/pkg/errors" + "github.com/yandex/pandora/lib/netutil/mocks" + "github.com/yandex/pandora/lib/testutil" +) + +func TestNetutil(t *testing.T) { + testutil.RunSuite(t, "Netutil Suite") +} + +var _ = Describe("DNS", func() { + + It("lookup reachable", func() { + listener, err := net.ListenTCP("tcp6", nil) + defer listener.Close() + Expect(err).NotTo(HaveOccurred()) + + port := strconv.Itoa(listener.Addr().(*net.TCPAddr).Port) + addr := "localhost:" + port + expectedResolved := "[::1]:" + port + + resolved, err := LookupReachable(addr) + Expect(err).NotTo(HaveOccurred()) + Expect(resolved).To(Equal(expectedResolved)) + }) + + const ( + addr = "localhost:8888" + resolved = "[::1]:8888" + ) + + It("cache", func() { + cache := &SimpleDNSCache{} + got, ok := cache.Get(addr) + Expect(ok).To(BeFalse()) + Expect(got).To(BeEmpty()) + + cache.Add(addr, resolved) + got, ok = cache.Get(addr) + Expect(ok).To(BeTrue()) + Expect(got).To(Equal(resolved)) + }) + + It("Dialer cache miss", func() { + ctx := context.Background() + mockConn := &netmock.Conn{} + mockConn.On("RemoteAddr").Return(&net.TCPAddr{ + IP: net.IPv6loopback, + Port: 8888, + }) + cache := &netmock.DNSCache{} + cache.On("Get", addr).Return("", false) + cache.On("Add", addr, resolved) + dialer := &netmock.Dialer{} + dialer.On("DialContext", ctx, "tcp", addr).Return(mockConn, nil) + + testee := NewDNSCachingDialer(dialer, cache) + conn, err := testee.DialContext(ctx, "tcp", addr) + Expect(err).NotTo(HaveOccurred()) + Expect(conn).To(Equal(mockConn)) + + testutil.AssertExpectations(mockConn, cache, dialer) + }) + + It("Dialer cache hit", func() { + ctx := context.Background() + mockConn := &netmock.Conn{} + cache := &netmock.DNSCache{} + cache.On("Get", addr).Return(resolved, true) + dialer := &netmock.Dialer{} + dialer.On("DialContext", ctx, "tcp", resolved).Return(mockConn, nil) + + testee := NewDNSCachingDialer(dialer, cache) + conn, err := testee.DialContext(ctx, "tcp", addr) + Expect(err).NotTo(HaveOccurred()) + Expect(conn).To(Equal(mockConn)) + + testutil.AssertExpectations(mockConn, cache, dialer) + }) + + It("Dialer cache miss err", func() { + ctx := context.Background() + expectedErr := errors.New("dial failed") + cache := &netmock.DNSCache{} + cache.On("Get", addr).Return("", false) + dialer := &netmock.Dialer{} + dialer.On("DialContext", ctx, "tcp", addr).Return(nil, expectedErr) + + testee := NewDNSCachingDialer(dialer, cache) + conn, err := testee.DialContext(ctx, "tcp", addr) + Expect(err).To(Equal(expectedErr)) + Expect(conn).To(BeNil()) + + testutil.AssertExpectations(cache, dialer) + }) + +}) diff --git a/lib/testutil/ginkgo.go b/lib/testutil/ginkgo.go index b60339da6..888227f33 100644 --- a/lib/testutil/ginkgo.go +++ b/lib/testutil/ginkgo.go @@ -7,15 +7,24 @@ package testutil import ( "strings" + "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/onsi/gomega/format" "github.com/spf13/viper" "github.com/stretchr/testify/mock" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) +func RunSuite(t *testing.T, description string) { + format.UseStringerRepresentation = true + ReplaceGlobalLogger() + RegisterFailHandler(Fail) + RunSpecs(t, description) +} + func ReplaceGlobalLogger() *zap.Logger { log := NewLogger() zap.ReplaceGlobals(log) diff --git a/script/coverage.sh b/script/coverage.sh index 1a7fdf5c7..a40d4ae37 100755 --- a/script/coverage.sh +++ b/script/coverage.sh @@ -14,7 +14,7 @@ _cd_into_top_level() { } _generate_coverage_files() { - for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/vendor/*' -type d); do + for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/vendor/*' -not -path '*/mocks/*' -type d); do if ls $dir/*.go &>/dev/null ; then go test -covermode=count -coverprofile=$dir/profile.coverprofile $dir || fail=1 fi