Redis: remove some direct pool usages (#64447)

We want to discourage direct usage of the Redis pool in favor of routing
all calls through the main `KeyValue` interface. This PR removes several
usages of `KeyValue.Pool`. To do so, it adds "PING" and "MGET" to the
`KeyValue` interface.
This commit is contained in:
Julie Tibshirani 2024-08-14 13:39:10 +03:00 committed by GitHub
parent d3a3d721d3
commit c222523fa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 319 additions and 110 deletions

View File

@ -36,7 +36,6 @@ go_library(
"//internal/version",
"//lib/errors",
"//lib/pointers",
"@com_github_gomodule_redigo//redis",
"@com_github_gorilla_mux//:mux",
"@com_github_khan_genqlient//graphql",
"@com_github_sourcegraph_log//:log",

View File

@ -6,7 +6,6 @@ import (
"net/http"
"strings"
"github.com/gomodule/redigo/redis"
"github.com/sourcegraph/log"
"github.com/sourcegraph/log/hook"
"github.com/sourcegraph/log/output"
@ -17,16 +16,16 @@ import (
"github.com/sourcegraph/sourcegraph/cmd/cody-gateway/internal/response"
"github.com/sourcegraph/sourcegraph/internal/authbearer"
"github.com/sourcegraph/sourcegraph/internal/instrumentation"
"github.com/sourcegraph/sourcegraph/internal/redispool"
sgtrace "github.com/sourcegraph/sourcegraph/internal/trace"
"github.com/sourcegraph/sourcegraph/internal/version"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
// NewDiagnosticsHandler creates a handler for diagnostic endpoints typically served
// on "/-/..." paths. It should be placed before any authentication middleware, since
// we do a simple auth on a static secret instead that is uniquely generated per
// deployment.
func NewDiagnosticsHandler(baseLogger log.Logger, next http.Handler, redisPool *redis.Pool, secret string, sources *actor.Sources) http.Handler {
func NewDiagnosticsHandler(baseLogger log.Logger, next http.Handler, redisCache redispool.KeyValue, secret string, sources *actor.Sources) http.Handler {
baseLogger = baseLogger.Scoped("diagnostics")
hasValidSecret := func(l log.Logger, w http.ResponseWriter, r *http.Request) (yes bool) {
@ -58,7 +57,7 @@ func NewDiagnosticsHandler(baseLogger log.Logger, next http.Handler, redisPool *
return
}
if err := healthz(r.Context(), redisPool); err != nil {
if err := healthz(r.Context(), redisCache); err != nil {
logger.Error("check failed", log.Error(err))
w.WriteHeader(http.StatusInternalServerError)
@ -110,21 +109,6 @@ func NewDiagnosticsHandler(baseLogger log.Logger, next http.Handler, redisPool *
})
}
func healthz(ctx context.Context, rpool *redis.Pool) error {
// Check redis health
rconn, err := rpool.GetContext(ctx)
if err != nil {
return errors.Wrap(err, "redis: failed to get conn")
}
defer rconn.Close()
data, err := rconn.Do("PING")
if err != nil {
return errors.Wrap(err, "redis: failed to ping")
}
if data != "PONG" {
return errors.New("redis: failed to ping: no pong received")
}
return nil
func healthz(ctx context.Context, cache redispool.KeyValue) error {
return cache.WithContext(ctx).Ping()
}

View File

@ -241,7 +241,7 @@ func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFu
return errors.Wrap(err, "httpapi.NewHandler")
}
// Diagnostic and Maintenance layers, exposing additional APIs and endpoints.
handler = httpapi.NewDiagnosticsHandler(obctx.Logger, handler, redisCache.Pool(), cfg.DiagnosticsSecret, sources)
handler = httpapi.NewDiagnosticsHandler(obctx.Logger, handler, redisCache, cfg.DiagnosticsSecret, sources)
handler = httpapi.NewMaintenanceHandler(obctx.Logger, handler, cfg, redisCache)
// Collect request client for downstream handlers. Outside of dev, we always set up

View File

@ -450,19 +450,6 @@ func GetInternalAddr() string {
return httpAddrInternal
}
func pingRedis(kv redispool.KeyValue) error {
conn := kv.Pool().Get()
defer conn.Close()
data, err := conn.Do("PING")
if err != nil {
return err
}
if data != "PONG" {
return errors.New("no pong received")
}
return nil
}
// waitForRedis waits up to a certain timeout for Redis to become reachable, to reduce the
// likelihood of the HTTP handlers starting to serve requests while Redis (and therefore session
// data) is still unavailable. After the timeout has elapsed, if Redis is still unreachable, it
@ -473,7 +460,7 @@ func waitForRedis(logger sglog.Logger, kv redispool.KeyValue) {
var err error
for {
time.Sleep(150 * time.Millisecond)
err = pingRedis(kv)
err = kv.Ping()
if err == nil {
return
}

View File

@ -12,7 +12,6 @@ go_library(
deps = [
"//internal/redispool",
"//lib/errors",
"@com_github_gomodule_redigo//redis",
"@com_github_prometheus_client_golang//prometheus",
"@com_github_prometheus_client_model//go",
"@com_github_prometheus_common//expfmt",

View File

@ -5,7 +5,6 @@ import (
"io"
"strings"
"github.com/gomodule/redigo/redis"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
@ -20,16 +19,6 @@ type Store interface {
prometheus.Gatherer
}
func NewDefaultStore() Store {
return &defaultStore{}
}
type defaultStore struct{}
func (*defaultStore) Gather() ([]*dto.MetricFamily, error) {
return prometheus.DefaultGatherer.Gather()
}
type DistributedStore interface {
Store
Ingest(instance string, mfs []*dto.MetricFamily) error
@ -48,13 +37,10 @@ type distributedStore struct {
}
func (d *distributedStore) Gather() ([]*dto.MetricFamily, error) {
pool := redispool.Cache.Pool()
reConn := pool.Get()
defer reConn.Close()
cache := redispool.Cache
// First, list all the keys for which we hold metrics.
keys, err := redis.Values(reConn.Do("KEYS", d.prefix+"*"))
keys, err := cache.Keys(d.prefix + "*")
if err != nil {
return nil, errors.Wrap(err, "listing entries from redis")
}
@ -64,7 +50,7 @@ func (d *distributedStore) Gather() ([]*dto.MetricFamily, error) {
}
// Then bulk retrieve all the metrics blobs for all the instances.
encodedMetrics, err := redis.Strings(reConn.Do("MGET", keys...))
encodedMetrics, err := cache.MGet(keys).Strings()
if err != nil {
return nil, errors.Wrap(err, "retrieving blobs from redis")
}
@ -92,7 +78,7 @@ func (d *distributedStore) Gather() ([]*dto.MetricFamily, error) {
}
func (d *distributedStore) Ingest(instance string, mfs []*dto.MetricFamily) error {
pool := redispool.Cache.Pool()
cache := redispool.Cache
// First, encode the metrics to text format so we can store them.
var enc bytes.Buffer
@ -106,13 +92,10 @@ func (d *distributedStore) Ingest(instance string, mfs []*dto.MetricFamily) erro
encodedMetrics := enc.String()
reConn := pool.Get()
defer reConn.Close()
// Store the metrics and set an expiry on the key, if we haven't retrieved
// an updated set of metric data, we consider the host down and prune it
// from the gatherer.
err := reConn.Send("SETEX", d.prefix+instance, d.expiry, encodedMetrics)
err := cache.SetEx(d.prefix+instance, d.expiry, encodedMetrics)
if err != nil {
return errors.Wrap(err, "writing metrics blob to redis")
}

View File

@ -499,15 +499,11 @@ func SetupForTest(t TB) {
t.Helper()
kvMock = redispool.NewTestKeyValue()
tokenBucketGlobalPrefix = "__test__" + t.Name()
c := kvMock.Pool().Get()
defer c.Close()
// If we are not on CI, skip the test if our redis connection fails.
if os.Getenv("CI") == "" {
_, err := c.Do("PING")
if err != nil {
if err := kvMock.Ping(); err != nil {
t.Skip("could not connect to redis", err)
}
}

View File

@ -250,13 +250,10 @@ func SetupForTest(t testing.TB) redispool.KeyValue {
})
globalPrefix = "__test__" + t.Name()
c := kvMock.Pool().Get()
defer c.Close()
// If we are not on CI, skip the test if our redis connection fails.
if os.Getenv("CI") == "" {
_, err := c.Do("PING")
if err != nil {
if err := kvMock.Ping(); err != nil {
t.Skip("could not connect to redis", err)
}
}

View File

@ -20,6 +20,8 @@ import (
type KeyValue interface {
Get(key string) Value
GetSet(key string, value any) Value
MGet(keys []string) Values
Set(key string, value any) error
SetEx(key string, ttlSeconds int, value any) error
SetNx(key string, value any) (bool, error)
@ -42,6 +44,9 @@ type KeyValue interface {
LLen(key string) (int, error)
LRange(key string, start, stop int) Values
// Ping checks the connection to the redis server.
Ping() error
// Keys returns all keys matching the glob pattern. NOTE: this command takes time
// linear in the number of keys, and should not be run over large keyspaces.
Keys(pattern string) ([]string, error)
@ -169,24 +174,33 @@ type redisKeyValue struct {
recorder *LatencyRecorder
}
func (r *redisKeyValue) Ping() error {
// The 'ping' command takes no arguments
return r.do("PING", []string{}, []any{}).err
}
func (r *redisKeyValue) Get(key string) Value {
return r.do("GET", key)
return r.doSimple("GET", key)
}
func (r *redisKeyValue) GetSet(key string, val any) Value {
return r.do("GETSET", key, val)
return r.doSimple("GETSET", key, val)
}
func (r *redisKeyValue) MGet(keys []string) Values {
return Values(r.do("MGET", keys, []any{}))
}
func (r *redisKeyValue) Set(key string, val any) error {
return r.do("SET", key, val).err
return r.doSimple("SET", key, val).err
}
func (r *redisKeyValue) SetEx(key string, ttlSeconds int, val any) error {
return r.do("SETEX", key, ttlSeconds, val).err
return r.doSimple("SETEX", key, ttlSeconds, val).err
}
func (r *redisKeyValue) SetNx(key string, val any) (bool, error) {
_, err := r.do("SET", key, val, "NX").String()
_, err := r.doSimple("SET", key, val, "NX").String()
if err == redis.ErrNil {
return false, nil
}
@ -194,61 +208,61 @@ func (r *redisKeyValue) SetNx(key string, val any) (bool, error) {
}
func (r *redisKeyValue) Incr(key string) (int, error) {
return r.do("INCR", key).Int()
return r.doSimple("INCR", key).Int()
}
func (r *redisKeyValue) Incrby(key string, value int) (int, error) {
return r.do("INCRBY", key, value).Int()
return r.doSimple("INCRBY", key, value).Int()
}
func (r *redisKeyValue) IncrByInt64(key string, value int64) (int64, error) {
return r.do("INCRBY", key, value).Int64()
return r.doSimple("INCRBY", key, value).Int64()
}
func (r *redisKeyValue) DecrByInt64(key string, value int64) (int64, error) {
return r.do("DECRBY", key, value).Int64()
return r.doSimple("DECRBY", key, value).Int64()
}
func (r *redisKeyValue) Del(key string) error {
return r.do("DEL", key).err
return r.doSimple("DEL", key).err
}
func (r *redisKeyValue) TTL(key string) (int, error) {
return r.do("TTL", key).Int()
return r.doSimple("TTL", key).Int()
}
func (r *redisKeyValue) Expire(key string, ttlSeconds int) error {
return r.do("EXPIRE", key, ttlSeconds).err
return r.doSimple("EXPIRE", key, ttlSeconds).err
}
func (r *redisKeyValue) HGet(key, field string) Value {
return r.do("HGET", key, field)
return r.doSimple("HGET", key, field)
}
func (r *redisKeyValue) HGetAll(key string) Values {
return Values(r.do("HGETALL", key))
return Values(r.doSimple("HGETALL", key))
}
func (r *redisKeyValue) HSet(key, field string, val any) error {
return r.do("HSET", key, field, val).err
return r.doSimple("HSET", key, field, val).err
}
func (r *redisKeyValue) HDel(key, field string) Value {
return r.do("HDEL", key, field)
return r.doSimple("HDEL", key, field)
}
func (r *redisKeyValue) LPush(key string, value any) error {
return r.do("LPUSH", key, value).err
return r.doSimple("LPUSH", key, value).err
}
func (r *redisKeyValue) LTrim(key string, start, stop int) error {
return r.do("LTRIM", key, start, stop).err
return r.doSimple("LTRIM", key, start, stop).err
}
func (r *redisKeyValue) LLen(key string) (int, error) {
raw := r.do("LLEN", key)
raw := r.doSimple("LLEN", key)
return redis.Int(raw.reply, raw.err)
}
func (r *redisKeyValue) LRange(key string, start, stop int) Values {
return Values(r.do("LRANGE", key, start, stop))
return Values(r.doSimple("LRANGE", key, start, stop))
}
func (r *redisKeyValue) WithContext(ctx context.Context) KeyValue {
@ -278,14 +292,18 @@ func (r *redisKeyValue) WithPrefix(prefix string) KeyValue {
}
func (r *redisKeyValue) Keys(pattern string) ([]string, error) {
return Values(r.do("KEYS", pattern)).Strings()
return Values(r.doSimple("KEYS", pattern)).Strings()
}
func (r *redisKeyValue) Pool() *redis.Pool {
return r.pool
}
func (r *redisKeyValue) do(commandName string, key string, args ...any) Value {
func (r *redisKeyValue) doSimple(commandName string, key string, args ...any) Value {
return r.do(commandName, []string{key}, args)
}
func (r *redisKeyValue) do(commandName string, keys []string, args []any) Value {
var c redis.Conn
if r.ctx != nil {
var err error
@ -293,19 +311,22 @@ func (r *redisKeyValue) do(commandName string, key string, args ...any) Value {
if err != nil {
return Value{err: err}
}
defer c.Close()
} else {
c = r.pool.Get()
defer c.Close()
}
defer c.Close()
var start time.Time
if r.recorder != nil {
start = time.Now()
}
prefixedKey := r.prefix + key
args = append([]any{prefixedKey}, args...)
reply, err := c.Do(commandName, args...)
prefixedKeys := make([]any, len(keys))
for i, key := range keys {
prefixedKeys[i] = r.prefix + key
}
reply, err := c.Do(commandName, append(prefixedKeys, args...)...)
if r.recorder != nil {
elapsed := time.Since(start)

View File

@ -189,7 +189,7 @@ func TestKeyValue(t *testing.T) {
require.Equal(kv.Get("empty-string"), "")
require.Equal(kv.Get("empty-bytes"), "")
// List group. Once empty we should be able to do a Get without a
// List group. Once empty we should be able to doSimple a Get without a
// wrongtype error.
require.Works(kv.LPush("empty-list", "here today gone tomorrow"))
require.Equal(kv.Get("empty-list"), errWrongType)
@ -353,6 +353,20 @@ func TestKeyValue(t *testing.T) {
require.Equal(kv.Get(k), "2")
}
})
t.Run("ping", func(t *testing.T) {
t.Parallel()
require := require{TB: t}
require.Works(kv.Ping())
brokenKv := redispool.NewKeyValue("nonexistent-redis-server:6379", &redis.Pool{
MaxIdle: 3,
IdleTimeout: 5 * time.Second,
})
if brokenKv.Ping() == nil {
t.Fatalf("ping: expected error, but did not receive one")
}
})
}
func TestKeyValueWithPrefix(t *testing.T) {
@ -378,16 +392,22 @@ func TestKeyValueWithPrefix(t *testing.T) {
require.Works(kv1.Set("other", "a"))
mget1, err := kv1.MGet([]string{"simple", "other"}).Strings()
require.Works(err)
if !reflect.DeepEqual(mget1, []string{"1", "a"}) {
t.Fatalf("mget mismatch: expected [1 a], got %v", mget1)
}
keys1, err := kv1.Keys("*")
require.Works(err)
if len(keys1) != 2 {
t.Fatalf("expected 2 keys, got %v", keys1)
t.Fatalf("keys mismatch: expected 2 keys, got %v", keys1)
}
keys2, err := kv2.Keys("*")
keys2, err := kv2.Keys("s*")
require.Works(err)
if len(keys2) != 1 {
t.Fatalf("expected 1 key, got %v", keys1)
t.Fatalf("keys mismatch: expected 1 key, got %v", keys1)
}
}
@ -399,13 +419,9 @@ func redisKeyValueForTest(t *testing.T) redispool.KeyValue {
kv := redispool.NewTestKeyValue()
prefix := "__test__" + t.Name()
c := kv.Pool().Get()
defer c.Close()
// If we are not on CI, skip the test if our redis connection fails.
if os.Getenv("CI") == "" {
_, err := c.Do("PING")
if err != nil {
if err := kv.Ping(); err != nil {
t.Skip("could not connect to redis", err)
}
}

View File

@ -68,6 +68,12 @@ type MockKeyValue struct {
// LTrimFunc is an instance of a mock function object controlling the
// behavior of the method LTrim.
LTrimFunc *KeyValueLTrimFunc
// MGetFunc is an instance of a mock function object controlling the
// behavior of the method MGet.
MGetFunc *KeyValueMGetFunc
// PingFunc is an instance of a mock function object controlling the
// behavior of the method Ping.
PingFunc *KeyValuePingFunc
// PoolFunc is an instance of a mock function object controlling the
// behavior of the method Pool.
PoolFunc *KeyValuePoolFunc
@ -180,6 +186,16 @@ func NewMockKeyValue() *MockKeyValue {
return
},
},
MGetFunc: &KeyValueMGetFunc{
defaultHook: func([]string) (r0 Values) {
return
},
},
PingFunc: &KeyValuePingFunc{
defaultHook: func() (r0 error) {
return
},
},
PoolFunc: &KeyValuePoolFunc{
defaultHook: func() (r0 *redis.Pool) {
return
@ -307,6 +323,16 @@ func NewStrictMockKeyValue() *MockKeyValue {
panic("unexpected invocation of MockKeyValue.LTrim")
},
},
MGetFunc: &KeyValueMGetFunc{
defaultHook: func([]string) Values {
panic("unexpected invocation of MockKeyValue.MGet")
},
},
PingFunc: &KeyValuePingFunc{
defaultHook: func() error {
panic("unexpected invocation of MockKeyValue.Ping")
},
},
PoolFunc: &KeyValuePoolFunc{
defaultHook: func() *redis.Pool {
panic("unexpected invocation of MockKeyValue.Pool")
@ -400,6 +426,12 @@ func NewMockKeyValueFrom(i KeyValue) *MockKeyValue {
LTrimFunc: &KeyValueLTrimFunc{
defaultHook: i.LTrim,
},
MGetFunc: &KeyValueMGetFunc{
defaultHook: i.MGet,
},
PingFunc: &KeyValuePingFunc{
defaultHook: i.Ping,
},
PoolFunc: &KeyValuePoolFunc{
defaultHook: i.Pool,
},
@ -2203,6 +2235,205 @@ func (c KeyValueLTrimFuncCall) Results() []interface{} {
return []interface{}{c.Result0}
}
// KeyValueMGetFunc describes the behavior when the MGet method of the
// parent MockKeyValue instance is invoked.
type KeyValueMGetFunc struct {
defaultHook func([]string) Values
hooks []func([]string) Values
history []KeyValueMGetFuncCall
mutex sync.Mutex
}
// MGet delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockKeyValue) MGet(v0 []string) Values {
r0 := m.MGetFunc.nextHook()(v0)
m.MGetFunc.appendCall(KeyValueMGetFuncCall{v0, r0})
return r0
}
// SetDefaultHook sets function that is called when the MGet method of the
// parent MockKeyValue instance is invoked and the hook queue is empty.
func (f *KeyValueMGetFunc) SetDefaultHook(hook func([]string) Values) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// MGet method of the parent MockKeyValue instance invokes the hook at the
// front of the queue and discards it. After the queue is empty, the default
// hook function is invoked for any future action.
func (f *KeyValueMGetFunc) PushHook(hook func([]string) Values) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *KeyValueMGetFunc) SetDefaultReturn(r0 Values) {
f.SetDefaultHook(func([]string) Values {
return r0
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *KeyValueMGetFunc) PushReturn(r0 Values) {
f.PushHook(func([]string) Values {
return r0
})
}
func (f *KeyValueMGetFunc) nextHook() func([]string) Values {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *KeyValueMGetFunc) appendCall(r0 KeyValueMGetFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of KeyValueMGetFuncCall objects describing the
// invocations of this function.
func (f *KeyValueMGetFunc) History() []KeyValueMGetFuncCall {
f.mutex.Lock()
history := make([]KeyValueMGetFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// KeyValueMGetFuncCall is an object that describes an invocation of method
// MGet on an instance of MockKeyValue.
type KeyValueMGetFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 []string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 Values
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c KeyValueMGetFuncCall) Args() []interface{} {
return []interface{}{c.Arg0}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c KeyValueMGetFuncCall) Results() []interface{} {
return []interface{}{c.Result0}
}
// KeyValuePingFunc describes the behavior when the Ping method of the
// parent MockKeyValue instance is invoked.
type KeyValuePingFunc struct {
defaultHook func() error
hooks []func() error
history []KeyValuePingFuncCall
mutex sync.Mutex
}
// Ping delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockKeyValue) Ping() error {
r0 := m.PingFunc.nextHook()()
m.PingFunc.appendCall(KeyValuePingFuncCall{r0})
return r0
}
// SetDefaultHook sets function that is called when the Ping method of the
// parent MockKeyValue instance is invoked and the hook queue is empty.
func (f *KeyValuePingFunc) SetDefaultHook(hook func() error) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// Ping method of the parent MockKeyValue instance invokes the hook at the
// front of the queue and discards it. After the queue is empty, the default
// hook function is invoked for any future action.
func (f *KeyValuePingFunc) PushHook(hook func() error) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *KeyValuePingFunc) SetDefaultReturn(r0 error) {
f.SetDefaultHook(func() error {
return r0
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *KeyValuePingFunc) PushReturn(r0 error) {
f.PushHook(func() error {
return r0
})
}
func (f *KeyValuePingFunc) nextHook() func() error {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *KeyValuePingFunc) appendCall(r0 KeyValuePingFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of KeyValuePingFuncCall objects describing the
// invocations of this function.
func (f *KeyValuePingFunc) History() []KeyValuePingFuncCall {
f.mutex.Lock()
history := make([]KeyValuePingFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// KeyValuePingFuncCall is an object that describes an invocation of method
// Ping on an instance of MockKeyValue.
type KeyValuePingFuncCall struct {
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c KeyValuePingFuncCall) Args() []interface{} {
return []interface{}{}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c KeyValuePingFuncCall) Results() []interface{} {
return []interface{}{c.Result0}
}
// KeyValuePoolFunc describes the behavior when the Pool method of the
// parent MockKeyValue instance is invoked.
type KeyValuePoolFunc struct {

View File

@ -40,13 +40,9 @@ func TestDeleteAllKeysWithPrefix(t *testing.T) {
kv := NewTestKeyValue()
c := kv.Pool().Get()
defer c.Close()
// If we are not on CI, skip the test if our redis connection fails.
if os.Getenv("CI") == "" {
_, err := c.Do("PING")
if err != nil {
if err := kv.Ping(); err != nil {
t.Skip("could not connect to redis", err)
}
}