Repository: gomodule/redigo
Pull request #482 (2020-05-18 08:06:37)

update godoc link to pkg.go.dev

stevenh: Thanks for PR but pkg.go.dev doesn't have feature parity with godoc yet, specifically it doesn't support master.

Once they address this, happy to look at merging.

Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
new file mode 100644
index 0000000..e59fd3c
--- /dev/null
+++ b/.github/workflows/golangci-lint.yml
@@ -0,0 +1,31 @@
+name: golangci-lint
+on:
+  push:
+    tags:
+      - v*
+    branches:
+      - master
+  pull_request:
+jobs:
+  golangci:
+    name: lint
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: golangci-lint
+        uses: golangci/golangci-lint-action@v2
+        with:
+          # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
+          version: v1.36
+
+          # Optional: working directory, useful for monorepos
+          # working-directory: somedir
+
+          # Optional: golangci-lint command line arguments.
+          # args: --issues-exit-code=0
+
+          # Optional: show only new issues if it's a pull request. The default value is `false`.
+          # only-new-issues: true
+
+          # Optional: if set to true then the action will use pre-installed Go.
+          # skip-go-installation: true
diff --git a/.travis.yml b/.travis.yml
index 6e672d1..0e281de 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,15 +3,13 @@ services:
   - redis-server
 
 go:
-  - 1.9.x
-  - 1.10.x
-  - 1.11.x
-  - 1.12.x
-  - tip
+  - 1.13.x
+  - 1.14.x
+  - master
 
 matrix:
   allow_failures:
-    - go: tip
+    - go: master
 
 script:
   - go get -t -v ./...
diff --git a/LICENSE b/LICENSE
index 67db858..f433b1a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -173,3 +173,5 @@
       defend, and hold each Contributor harmless for any liability
       incurred by, or claims asserted against, such Contributor by reason
       of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
diff --git a/README.markdown b/README.markdown
index 2a637ac..d418cbb 100644
--- a/README.markdown
+++ b/README.markdown
@@ -2,26 +2,26 @@ Redigo
 ======
 
 [![Build Status](https://travis-ci.org/gomodule/redigo.svg?branch=master)](https://travis-ci.org/gomodule/redigo)
-[![GoDoc](https://godoc.org/github.com/gomodule/redigo/redis?status.svg)](https://godoc.org/github.com/gomodule/redigo/redis)
+[![GoDoc](https://godoc.org/github.com/gomodule/redigo/redis?status.svg)](https://pkg.go.dev/github.com/gomodule/redigo/redis)
 
 Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database.
 
 Features
 -------
 
-* A [Print-like](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.
-* [Pipelining](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Pipelining), including pipelined transactions.
-* [Publish/Subscribe](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Publish_and_Subscribe).
-* [Connection pooling](http://godoc.org/github.com/gomodule/redigo/redis#Pool).
-* [Script helper type](http://godoc.org/github.com/gomodule/redigo/redis#Script) with optimistic use of EVALSHA.
-* [Helper functions](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Reply_Helpers) for working with command replies.
+* A [Print-like](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.
+* [Pipelining](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Pipelining), including pipelined transactions.
+* [Publish/Subscribe](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Publish_and_Subscribe).
+* [Connection pooling](https://pkg.go.dev/github.com/gomodule/redigo/redis#Pool).
+* [Script helper type](https://pkg.go.dev/github.com/gomodule/redigo/redis#Script) with optimistic use of EVALSHA.
+* [Helper functions](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Reply_Helpers) for working with command replies.
 
 Documentation
 -------------
 
-- [API Reference](http://godoc.org/github.com/gomodule/redigo/redis)
+- [API Reference](https://pkg.go.dev/github.com/gomodule/redigo/redis)
 - [FAQ](https://github.com/gomodule/redigo/wiki/FAQ)
-- [Examples](https://godoc.org/github.com/gomodule/redigo/redis#pkg-examples)
+- [Examples](https://pkg.go.dev/github.com/gomodule/redigo/redis#pkg-examples)
 
 Installation
 ------------
@@ -35,7 +35,7 @@ The Go distribution is Redigo's only dependency.
 Related Projects
 ----------------
 
-- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo.
+- [rafaeljusto/redigomock](https://pkg.go.dev/github.com/rafaeljusto/redigomock) - A mock library for Redigo.
 - [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation.
 - [FZambia/sentinel](https://github.com/FZambia/sentinel) - Redis Sentinel support for Redigo
 - [mna/redisc](https://github.com/mna/redisc) - Redis Cluster client built on top of Redigo
diff --git a/redis/conn.go b/redis/conn.go
index 962c295..1398b4d 100644
--- a/redis/conn.go
+++ b/redis/conn.go
@@ -17,6 +17,7 @@ package redis
 import (
 	"bufio"
 	"bytes"
+	"context"
 	"crypto/tls"
 	"errors"
 	"fmt"
@@ -74,16 +75,27 @@ type DialOption struct {
 }
 
 type dialOptions struct {
-	readTimeout  time.Duration
-	writeTimeout time.Duration
-	dialer       *net.Dialer
-	dial         func(network, addr string) (net.Conn, error)
-	db           int
-	password     string
-	clientName   string
-	useTLS       bool
-	skipVerify   bool
-	tlsConfig    *tls.Config
+	readTimeout         time.Duration
+	writeTimeout        time.Duration
+	tlsHandshakeTimeout time.Duration
+	dialer              *net.Dialer
+	dialContext         func(ctx context.Context, network, addr string) (net.Conn, error)
+	db                  int
+	username            string
+	password            string
+	clientName          string
+	useTLS              bool
+	skipVerify          bool
+	tlsConfig           *tls.Config
+}
+
+// DialTLSHandshakeTimeout specifies the maximum amount of time waiting to
+// wait for a TLS handshake. Zero means no timeout.
+// If no DialTLSHandshakeTimeout option is specified then the default is 30 seconds.
+func DialTLSHandshakeTimeout(d time.Duration) DialOption {
+	return DialOption{func(do *dialOptions) {
+		do.tlsHandshakeTimeout = d
+	}}
 }
 
 // DialReadTimeout specifies the timeout for reading a single command reply.
@@ -102,6 +114,7 @@ func DialWriteTimeout(d time.Duration) DialOption {
 
 // DialConnectTimeout specifies the timeout for connecting to the Redis server when
 // no DialNetDial option is specified.
+// If no DialConnectTimeout option is specified then the default is 30 seconds.
 func DialConnectTimeout(d time.Duration) DialOption {
 	return DialOption{func(do *dialOptions) {
 		do.dialer.Timeout = d
@@ -123,7 +136,18 @@ func DialKeepAlive(d time.Duration) DialOption {
 // DialNetDial overrides DialConnectTimeout and DialKeepAlive.
 func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
 	return DialOption{func(do *dialOptions) {
-		do.dial = dial
+		do.dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
+			return dial(network, addr)
+		}
+	}}
+}
+
+// DialContextFunc specifies a custom dial function with context for creating TCP
+// connections, otherwise a net.Dialer customized via the other options is used.
+// DialContextFunc overrides DialConnectTimeout and DialKeepAlive.
+func DialContextFunc(f func(ctx context.Context, network, addr string) (net.Conn, error)) DialOption {
+	return DialOption{func(do *dialOptions) {
+		do.dialContext = f
 	}}
 }
 
@@ -142,6 +166,14 @@ func DialPassword(password string) DialOption {
 	}}
 }
 
+// DialUsername specifies the username to use when connecting to
+// the Redis server when Redis ACLs are used.
+func DialUsername(username string) DialOption {
+	return DialOption{func(do *dialOptions) {
+		do.username = username
+	}}
+}
+
 // DialClientName specifies a client name to be used
 // by the Redis server connection.
 func DialClientName(name string) DialOption {
@@ -177,19 +209,33 @@ func DialUseTLS(useTLS bool) DialOption {
 // Dial connects to the Redis server at the given network and
 // address using the specified options.
 func Dial(network, address string, options ...DialOption) (Conn, error) {
+	return DialContext(context.Background(), network, address, options...)
+}
+
+type tlsHandshakeTimeoutError struct{}
+
+func (tlsHandshakeTimeoutError) Timeout() bool   { return true }
+func (tlsHandshakeTimeoutError) Temporary() bool { return true }
+func (tlsHandshakeTimeoutError) Error() string   { return "TLS handshake timeout" }
+
+// DialContext connects to the Redis server at the given network and
+// address using the specified options and context.
+func DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) {
 	do := dialOptions{
 		dialer: &net.Dialer{
+			Timeout:   time.Second * 30,
 			KeepAlive: time.Minute * 5,
 		},
+		tlsHandshakeTimeout: time.Second * 10,
 	}
 	for _, option := range options {
 		option.f(&do)
 	}
-	if do.dial == nil {
-		do.dial = do.dialer.Dial
+	if do.dialContext == nil {
+		do.dialContext = do.dialer.DialContext
 	}
 
-	netConn, err := do.dial(network, address)
+	netConn, err := do.dialContext(ctx, network, address)
 	if err != nil {
 		return nil, err
 	}
@@ -211,10 +257,22 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
 		}
 
 		tlsConn := tls.Client(netConn, tlsConfig)
-		if err := tlsConn.Handshake(); err != nil {
-			netConn.Close()
+		errc := make(chan error, 2) // buffered so we don't block timeout or Handshake
+		if d := do.tlsHandshakeTimeout; d != 0 {
+			timer := time.AfterFunc(d, func() {
+				errc <- tlsHandshakeTimeoutError{}
+			})
+			defer timer.Stop()
+		}
+		go func() {
+			errc <- tlsConn.Handshake()
+		}()
+		if err := <-errc; err != nil {
+			// Timeout or Handshake error.
+			netConn.Close() // nolint: errcheck
 			return nil, err
 		}
+
 		netConn = tlsConn
 	}
 
@@ -227,7 +285,12 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
 	}
 
 	if do.password != "" {
-		if _, err := c.Do("AUTH", do.password); err != nil {
+		authArgs := make([]interface{}, 0, 2)
+		if do.username != "" {
+			authArgs = append(authArgs, do.username)
+		}
+		authArgs = append(authArgs, do.password)
+		if _, err := c.Do("AUTH", authArgs...); err != nil {
 			netConn.Close()
 			return nil, err
 		}
@@ -285,7 +348,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) {
 	if u.User != nil {
 		password, isSet := u.User.Password()
 		if isSet {
-			options = append(options, DialPassword(password))
+			options = append(options, DialUsername(u.User.Username()), DialPassword(password))
 		}
 	}
 
@@ -369,15 +432,23 @@ func (c *conn) writeLen(prefix byte, n int) error {
 }
 
 func (c *conn) writeString(s string) error {
-	c.writeLen('$', len(s))
-	c.bw.WriteString(s)
+	if err := c.writeLen('$', len(s)); err != nil {
+		return err
+	}
+	if _, err := c.bw.WriteString(s); err != nil {
+		return err
+	}
 	_, err := c.bw.WriteString("\r\n")
 	return err
 }
 
 func (c *conn) writeBytes(p []byte) error {
-	c.writeLen('$', len(p))
-	c.bw.Write(p)
+	if err := c.writeLen('$', len(p)); err != nil {
+		return err
+	}
+	if _, err := c.bw.Write(p); err != nil {
+		return err
+	}
 	_, err := c.bw.WriteString("\r\n")
 	return err
 }
@@ -391,7 +462,9 @@ func (c *conn) writeFloat64(n float64) error {
 }
 
 func (c *conn) writeCommand(cmd string, args []interface{}) error {
-	c.writeLen('*', 1+len(args))
+	if err := c.writeLen('*', 1+len(args)); err != nil {
+		return err
+	}
 	if err := c.writeString(cmd); err != nil {
 		return err
 	}
@@ -552,7 +625,7 @@ func (c *conn) readReply() (interface{}, error) {
 			return string(line[1:]), nil
 		}
 	case '-':
-		return Error(string(line[1:])), nil
+		return Error(line[1:]), nil
 	case ':':
 		return parseInt(line[1:])
 	case '$':
@@ -593,7 +666,9 @@ func (c *conn) Send(cmd string, args ...interface{}) error {
 	c.pending += 1
 	c.mu.Unlock()
 	if c.writeTimeout != 0 {
-		c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+		if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {
+			return c.fatal(err)
+		}
 	}
 	if err := c.writeCommand(cmd, args); err != nil {
 		return c.fatal(err)
@@ -603,7 +678,9 @@ func (c *conn) Send(cmd string, args ...interface{}) error {
 
 func (c *conn) Flush() error {
 	if c.writeTimeout != 0 {
-		c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+		if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {
+			return c.fatal(err)
+		}
 	}
 	if err := c.bw.Flush(); err != nil {
 		return c.fatal(err)
@@ -620,7 +697,9 @@ func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err
 	if timeout != 0 {
 		deadline = time.Now().Add(timeout)
 	}
-	c.conn.SetReadDeadline(deadline)
+	if err := c.conn.SetReadDeadline(deadline); err != nil {
+		return nil, c.fatal(err)
+	}
 
 	if reply, err = c.readReply(); err != nil {
 		return nil, c.fatal(err)
@@ -658,7 +737,9 @@ func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...inte
 	}
 
 	if c.writeTimeout != 0 {
-		c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+		if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {
+			return nil, c.fatal(err)
+		}
 	}
 
 	if cmd != "" {
@@ -675,7 +756,9 @@ func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...inte
 	if readTimeout != 0 {
 		deadline = time.Now().Add(readTimeout)
 	}
-	c.conn.SetReadDeadline(deadline)
+	if err := c.conn.SetReadDeadline(deadline); err != nil {
+		return nil, c.fatal(err)
+	}
 
 	if cmd == "" {
 		reply := make([]interface{}, pending)
diff --git a/redis/log.go b/redis/log.go
index a06db9d..ef8cd7a 100644
--- a/redis/log.go
+++ b/redis/log.go
@@ -52,7 +52,7 @@ func (c *loggingConn) Close() error {
 	err := c.Conn.Close()
 	var buf bytes.Buffer
 	fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
-	c.logger.Output(2, buf.String())
+	c.logger.Output(2, buf.String()) // nolint: errcheck
 	return err
 }
 
@@ -112,7 +112,7 @@ func (c *loggingConn) print(method, commandName string, args []interface{}, repl
 		buf.WriteString(", ")
 	}
 	fmt.Fprintf(&buf, "%v)", err)
-	c.logger.Output(3, buf.String())
+	c.logger.Output(3, buf.String()) // nolint: errcheck
 }
 
 func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
diff --git a/redis/pool.go b/redis/pool.go
index 254c034..c7a2f19 100644
--- a/redis/pool.go
+++ b/redis/pool.go
@@ -23,7 +23,6 @@ import (
 	"io"
 	"strconv"
 	"sync"
-	"sync/atomic"
 	"time"
 )
 
@@ -40,7 +39,6 @@ var nowFunc = time.Now // for testing
 var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
 
 var (
-	errPoolClosed = errors.New("redigo: connection pool closed")
 	errConnClosed = errors.New("redigo: connection closed")
 )
 
@@ -161,11 +159,10 @@ type Pool struct {
 	// the pool does not close connections based on age.
 	MaxConnLifetime time.Duration
 
-	chInitialized uint32 // set to 1 when field ch is initialized
-
 	mu           sync.Mutex    // mu protects the following fields
 	closed       bool          // set to true when the pool is closed.
 	active       int           // the number of open connections in the pool
+	initOnce     sync.Once     // the init ch once func
 	ch           chan struct{} // limits open connections when p.Wait is true
 	idle         idleList      // idle connections
 	waitCount    int64         // total number of connections waited for.
@@ -199,27 +196,10 @@ func (p *Pool) Get() Conn {
 // If the function completes without error, then the application must close the
 // returned connection.
 func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
-	// Handle limit for p.Wait == true.
-	var waited time.Duration
-	if p.Wait && p.MaxActive > 0 {
-		p.lazyInit()
-
-		// wait indicates if we believe it will block so its not 100% accurate
-		// however for stats it should be good enough.
-		wait := len(p.ch) == 0
-		var start time.Time
-		if wait {
-			start = time.Now()
-		}
-		select {
-		case <-p.ch:
-		case <-ctx.Done():
-			err := ctx.Err()
-			return errorConn{err}, err
-		}
-		if wait {
-			waited = time.Since(start)
-		}
+	// Wait until there is a vacant connection in the pool.
+	waited, err := p.waitVacantConn(ctx)
+	if err != nil {
+		return errorConn{err}, err
 	}
 
 	p.mu.Lock()
@@ -273,7 +253,6 @@ func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
 	p.mu.Unlock()
 	c, err := p.dial(ctx)
 	if err != nil {
-		c = nil
 		p.mu.Lock()
 		p.active--
 		if p.ch != nil && !p.closed {
@@ -356,13 +335,7 @@ func (p *Pool) Close() error {
 }
 
 func (p *Pool) lazyInit() {
-	// Fast path.
-	if atomic.LoadUint32(&p.chInitialized) == 1 {
-		return
-	}
-	// Slow path.
-	p.mu.Lock()
-	if p.chInitialized == 0 {
+	p.initOnce.Do(func() {
 		p.ch = make(chan struct{}, p.MaxActive)
 		if p.closed {
 			close(p.ch)
@@ -371,9 +344,49 @@ func (p *Pool) lazyInit() {
 				p.ch <- struct{}{}
 			}
 		}
-		atomic.StoreUint32(&p.chInitialized, 1)
+	})
+}
+
+// waitVacantConn waits for a vacant connection in pool if waiting
+// is enabled and pool size is limited, otherwise returns instantly.
+// If ctx expires before that, an error is returned.
+//
+// If there were no vacant connection in the pool right away it returns the time spent waiting
+// for that connection to appear in the pool.
+func (p *Pool) waitVacantConn(ctx context.Context) (waited time.Duration, err error) {
+	if !p.Wait || p.MaxActive <= 0 {
+		// No wait or no connection limit.
+		return 0, nil
 	}
-	p.mu.Unlock()
+
+	p.lazyInit()
+
+	// wait indicates if we believe it will block so its not 100% accurate
+	// however for stats it should be good enough.
+	wait := len(p.ch) == 0
+	var start time.Time
+	if wait {
+		start = time.Now()
+	}
+
+	select {
+	case <-p.ch:
+		// Additionally check that context hasn't expired while we were waiting,
+		// because `select` picks a random `case` if several of them are "ready".
+		select {
+		case <-ctx.Done():
+			p.ch <- struct{}{}
+			return 0, ctx.Err()
+		default:
+		}
+	case <-ctx.Done():
+		return 0, ctx.Err()
+	}
+
+	if wait {
+		return time.Since(start), nil
+	}
+	return 0, nil
 }
 
 func (p *Pool) dial(ctx context.Context) (Conn, error) {
@@ -430,13 +443,22 @@ func initSentinel() {
 		sentinel = p
 	} else {
 		h := sha1.New()
-		io.WriteString(h, "Oops, rand failed. Use time instead.")
-		io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
+		io.WriteString(h, "Oops, rand failed. Use time instead.")       // nolint: errcheck
+		io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) // nolint: errcheck
 		sentinel = h.Sum(nil)
 	}
 }
 
-func (ac *activeConn) Close() error {
+func (ac *activeConn) firstError(errs ...error) error {
+	for _, err := range errs[:len(errs)-1] {
+		if err != nil {
+			return err
+		}
+	}
+	return errs[len(errs)-1]
+}
+
+func (ac *activeConn) Close() (err error) {
 	pc := ac.pc
 	if pc == nil {
 		return nil
@@ -444,23 +466,28 @@ func (ac *activeConn) Close() error {
 	ac.pc = nil
 
 	if ac.state&connectionMultiState != 0 {
-		pc.c.Send("DISCARD")
+		err = pc.c.Send("DISCARD")
 		ac.state &^= (connectionMultiState | connectionWatchState)
 	} else if ac.state&connectionWatchState != 0 {
-		pc.c.Send("UNWATCH")
+		err = pc.c.Send("UNWATCH")
 		ac.state &^= connectionWatchState
 	}
 	if ac.state&connectionSubscribeState != 0 {
-		pc.c.Send("UNSUBSCRIBE")
-		pc.c.Send("PUNSUBSCRIBE")
+		err = ac.firstError(err,
+			pc.c.Send("UNSUBSCRIBE"),
+			pc.c.Send("PUNSUBSCRIBE"),
+		)
 		// To detect the end of the message stream, ask the server to echo
 		// a sentinel value and read until we see that value.
 		sentinelOnce.Do(initSentinel)
-		pc.c.Send("ECHO", sentinel)
-		pc.c.Flush()
+		err = ac.firstError(err,
+			pc.c.Send("ECHO", sentinel),
+			pc.c.Flush(),
+		)
 		for {
-			p, err := pc.c.Receive()
-			if err != nil {
+			p, err2 := pc.c.Receive()
+			if err2 != nil {
+				err = ac.firstError(err, err2)
 				break
 			}
 			if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
@@ -469,9 +496,12 @@ func (ac *activeConn) Close() error {
 			}
 		}
 	}
-	pc.c.Do("")
-	ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil)
-	return nil
+	_, err2 := pc.c.Do("")
+	return ac.firstError(
+		err,
+		err2,
+		ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil),
+	)
 }
 
 func (ac *activeConn) Err() error {
@@ -579,7 +609,6 @@ func (l *idleList) pushFront(pc *poolConn) {
 	}
 	l.front = pc
 	l.count++
-	return
 }
 
 func (l *idleList) popFront() {
diff --git a/redis/pubsub.go b/redis/pubsub.go
index 2da6021..cc58575 100644
--- a/redis/pubsub.go
+++ b/redis/pubsub.go
@@ -60,27 +60,35 @@ func (c PubSubConn) Close() error {
 
 // Subscribe subscribes the connection to the specified channels.
 func (c PubSubConn) Subscribe(channel ...interface{}) error {
-	c.Conn.Send("SUBSCRIBE", channel...)
+	if err := c.Conn.Send("SUBSCRIBE", channel...); err != nil {
+		return err
+	}
 	return c.Conn.Flush()
 }
 
 // PSubscribe subscribes the connection to the given patterns.
 func (c PubSubConn) PSubscribe(channel ...interface{}) error {
-	c.Conn.Send("PSUBSCRIBE", channel...)
+	if err := c.Conn.Send("PSUBSCRIBE", channel...); err != nil {
+		return err
+	}
 	return c.Conn.Flush()
 }
 
 // Unsubscribe unsubscribes the connection from the given channels, or from all
 // of them if none is given.
 func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
-	c.Conn.Send("UNSUBSCRIBE", channel...)
+	if err := c.Conn.Send("UNSUBSCRIBE", channel...); err != nil {
+		return err
+	}
 	return c.Conn.Flush()
 }
 
 // PUnsubscribe unsubscribes the connection from the given patterns, or from all
 // of them if none is given.
 func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
-	c.Conn.Send("PUNSUBSCRIBE", channel...)
+	if err := c.Conn.Send("PUNSUBSCRIBE", channel...); err != nil {
+		return err
+	}
 	return c.Conn.Flush()
 }
 
@@ -89,7 +97,9 @@ func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
 // The connection must be subscribed to at least one channel or pattern when
 // calling this method.
 func (c PubSubConn) Ping(data string) error {
-	c.Conn.Send("PING", data)
+	if err := c.Conn.Send("PING", data); err != nil {
+		return err
+	}
 	return c.Conn.Flush()
 }
 
diff --git a/redis/reply.go b/redis/reply.go
index 251a788..dfe6aff 100644
--- a/redis/reply.go
+++ b/redis/reply.go
@@ -118,7 +118,7 @@ func Uint64(reply interface{}, err error) (uint64, error) {
 
 // Float64 is a helper that converts a command reply to 64 bit float. If err is
 // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
-// the reply to an int as follows:
+// the reply to a float64 as follows:
 //
 //  Reply type    Result
 //  bulk string   parsed reply, nil
@@ -348,7 +348,7 @@ func Int64s(reply interface{}, err error) ([]int64, error) {
 	return result, err
 }
 
-// Ints is a helper that converts an array command reply to a []in.
+// Ints is a helper that converts an array command reply to a []int.
 // If err is not equal to nil, then Ints returns nil, err. Nil array
 // items are stay nil. Ints returns an error if an array item is not a
 // bulk string or nil.
diff --git a/redis/scan.go b/redis/scan.go
index 289cfd9..379206e 100644
--- a/redis/scan.go
+++ b/redis/scan.go
@@ -23,6 +23,10 @@ import (
 	"sync"
 )
 
+var (
+	scannerType = reflect.TypeOf((*Scanner)(nil)).Elem()
+)
+
 func ensureLen(d reflect.Value, n int) {
 	if n > d.Cap() {
 		d.Set(reflect.MakeSlice(d.Type(), n, n))
@@ -352,34 +356,49 @@ func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
 }
 
 func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
+LOOP:
 	for i := 0; i < t.NumField(); i++ {
 		f := t.Field(i)
 		switch {
 		case f.PkgPath != "" && !f.Anonymous:
 			// Ignore unexported fields.
 		case f.Anonymous:
-			// TODO: Handle pointers. Requires change to decoder and
-			// protection against infinite recursion.
-			if f.Type.Kind() == reflect.Struct {
+			switch f.Type.Kind() {
+			case reflect.Struct:
 				compileStructSpec(f.Type, depth, append(index, i), ss)
+			case reflect.Ptr:
+				// TODO(steve): Protect against infinite recursion.
+				if f.Type.Elem().Kind() == reflect.Struct {
+					compileStructSpec(f.Type.Elem(), depth, append(index, i), ss)
+				}
 			}
 		default:
 			fs := &fieldSpec{name: f.Name}
 			tag := f.Tag.Get("redis")
-			p := strings.Split(tag, ",")
-			if len(p) > 0 {
-				if p[0] == "-" {
-					continue
+
+			var (
+				p string
+			)
+			first := true
+			for len(tag) > 0 {
+				i := strings.IndexByte(tag, ',')
+				if i < 0 {
+					p, tag = tag, ""
+				} else {
+					p, tag = tag[:i], tag[i+1:]
 				}
-				if len(p[0]) > 0 {
-					fs.name = p[0]
+				if p == "-" {
+					continue LOOP
 				}
-				for _, s := range p[1:] {
-					switch s {
+				if first && len(p) > 0 {
+					fs.name = p
+					first = false
+				} else {
+					switch p {
 					case "omitempty":
 						fs.omitEmpty = true
 					default:
-						panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name()))
+						panic(fmt.Errorf("redigo: unknown field tag %s for type %s", p, t.Name()))
 					}
 				}
 			}
@@ -412,9 +431,8 @@ func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *st
 }
 
 var (
-	structSpecMutex  sync.RWMutex
-	structSpecCache  = make(map[reflect.Type]*structSpec)
-	defaultFieldSpec = &fieldSpec{}
+	structSpecMutex sync.RWMutex
+	structSpecCache = make(map[reflect.Type]*structSpec)
 )
 
 func structSpecForType(t reflect.Type) *structSpec {
@@ -496,9 +514,13 @@ var (
 	errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
 )
 
-// ScanSlice scans src to the slice pointed to by dest. The elements the dest
-// slice must be integer, float, boolean, string, struct or pointer to struct
-// values.
+// ScanSlice scans src to the slice pointed to by dest.
+//
+// If the target is a slice of types which implement Scanner then the custom
+// RedisScan method is used otherwise the following rules apply:
+//
+// The elements in the dest slice must be integer, float, boolean, string, struct
+// or pointer to struct values.
 //
 // Struct fields must be integer, float, boolean or string values. All struct
 // fields are used unless a subset is specified using fieldNames.
@@ -514,12 +536,13 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error
 
 	isPtr := false
 	t := d.Type().Elem()
+	st := t
 	if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
 		isPtr = true
 		t = t.Elem()
 	}
 
-	if t.Kind() != reflect.Struct {
+	if t.Kind() != reflect.Struct || st.Implements(scannerType) {
 		ensureLen(d, len(src))
 		for i, s := range src {
 			if s == nil {
diff --git a/redis/script.go b/redis/script.go
index 0ef1c82..d0cec1e 100644
--- a/redis/script.go
+++ b/redis/script.go
@@ -36,7 +36,7 @@ type Script struct {
 // SendHash methods.
 func NewScript(keyCount int, src string) *Script {
 	h := sha1.New()
-	io.WriteString(h, src)
+	io.WriteString(h, src) // nolint: errcheck
 	return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
 }
 
diff --git a/redisx/commandinfo.go b/redisx/commandinfo.go
index b911cc4..45c7663 100644
--- a/redisx/commandinfo.go
+++ b/redisx/commandinfo.go
@@ -18,13 +18,6 @@ import (
 	"strings"
 )
 
-const (
-	connectionWatchState = 1 << iota
-	connectionMultiState
-	connectionSubscribeState
-	connectionMonitorState
-)
-
 type commandInfo struct {
 	notMuxable bool
 }
diff --git a/redisx/connmux.go b/redisx/connmux.go
index 6ae1f9d..41c7cd5 100644
--- a/redisx/connmux.go
+++ b/redisx/connmux.go
@@ -133,7 +133,7 @@ func (c *muxConn) Close() error {
 		return nil
 	}
 	c.Flush()
-	for _ = range c.ids {
+	for range c.ids {
 		_, err = c.Receive()
 	}
 	return err


Test patch:
diff --git a/redis/conn_test.go b/redis/conn_test.go
index 08da5fa..425c4f0 100644
--- a/redis/conn_test.go
+++ b/redis/conn_test.go
@@ -16,6 +16,7 @@ package redis_test
 
 import (
 	"bytes"
+	"context"
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
@@ -29,6 +30,7 @@ import (
 	"time"
 
 	"github.com/gomodule/redigo/redis"
+	"github.com/stretchr/testify/require"
 )
 
 type testConn struct {
@@ -70,10 +72,10 @@ func dialTestConnTLS(r string, w io.Writer) redis.DialOption {
 	return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
 		client, server := net.Pipe()
 		tlsServer := tls.Server(server, &serverTLSConfig)
-		go io.Copy(tlsServer, strings.NewReader(r))
+		go io.Copy(tlsServer, strings.NewReader(r)) // nolint: errcheck
 		done := make(chan struct{})
 		go func() {
-			io.Copy(w, tlsServer)
+			io.Copy(w, tlsServer) // nolint: errcheck
 			close(done)
 		}()
 		return &tlsTestConn{Conn: client, done: done}, nil
@@ -441,12 +443,12 @@ func TestRecvBeforeSend(t *testing.T) {
 	defer c.Close()
 	done := make(chan struct{})
 	go func() {
-		c.Receive()
+		c.Receive() // nolint: errcheck
 		close(done)
 	}()
 	time.Sleep(time.Millisecond)
-	c.Send("PING")
-	c.Flush()
+	require.NoError(t, c.Send("PING"))
+	require.NoError(t, c.Flush())
 	<-done
 	_, err = c.Do("")
 	if err != nil {
@@ -461,7 +463,8 @@ func TestError(t *testing.T) {
 	}
 	defer c.Close()
 
-	c.Do("SET", "key", "val")
+	_, err = c.Do("SET", "key", "val")
+	require.NoError(t, err)
 	_, err = c.Do("HSET", "key", "fld", "val")
 	if err == nil {
 		t.Errorf("Expected err for HSET on string key.")
@@ -490,7 +493,8 @@ func TestReadTimeout(t *testing.T) {
 			}
 			go func() {
 				time.Sleep(time.Second)
-				c.Write([]byte("+OK\r\n"))
+				_, err := c.Write([]byte("+OK\r\n"))
+				require.NoError(t, err)
 				c.Close()
 			}()
 		}
@@ -520,8 +524,8 @@ func TestReadTimeout(t *testing.T) {
 	}
 	defer c2.Close()
 
-	c2.Send("PING")
-	c2.Flush()
+	require.NoError(t, c2.Send("PING"))
+	require.NoError(t, c2.Flush())
 	_, err = c2.Receive()
 	if err == nil {
 		t.Fatalf("c2.Receive() returned nil, expect error")
@@ -531,6 +535,37 @@ func TestReadTimeout(t *testing.T) {
 	}
 }
 
+func TestDialContextFunc(t *testing.T) {
+	var isPassed bool
+	f := func(ctx context.Context, network, addr string) (net.Conn, error) {
+		isPassed = true
+		return &testConn{}, nil
+	}
+
+	_, err := redis.DialContext(context.Background(), "", "", redis.DialContextFunc(f))
+	if err != nil {
+		t.Fatalf("DialContext returned %v", err)
+	}
+
+	if !isPassed {
+		t.Fatal("DialContextFunc not passed")
+	}
+}
+
+func TestDialContext_CanceledContext(t *testing.T) {
+	addr, err := redis.DefaultServerAddr()
+	if err != nil {
+		t.Fatalf("redis.DefaultServerAddr returned %v", err)
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+
+	if _, err = redis.DialContext(ctx, "tcp", addr); err == nil {
+		t.Fatalf("DialContext returned nil, expect error")
+	}
+}
+
 var dialErrors = []struct {
 	rawurl        string
 	expectedError string
@@ -604,7 +639,9 @@ var dialURLTests = []struct {
 	r           string
 	w           string
 }{
-	{"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
+	{"password", "redis://:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
+	{"username and password", "redis://user:password@localhost", "+OK\r\n", "*3\r\n$4\r\nAUTH\r\n$4\r\nuser\r\n$8\r\npassword\r\n"},
+	{"username", "redis://x:@localhost", "+OK\r\n", ""},
 	{"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"},
 	{"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"},
 	{"no database", "redis://localhost/", "+OK\r\n", ""},
@@ -667,6 +704,45 @@ func TestDialUseTLS(t *testing.T) {
 	checkPingPong(t, &buf, c)
 }
 
+type blockedReader struct {
+	ch chan struct{}
+}
+
+func (b blockedReader) Read(p []byte) (n int, err error) {
+	<-b.ch
+	return 0, nil
+}
+
+func dialTestBlockedConn(ch chan struct{}, w io.Writer) redis.DialOption {
+	return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
+		return &testConn{Reader: blockedReader{ch: ch}, Writer: w}, nil
+	})
+}
+
+func TestDialTLSHandshakeTimeout(t *testing.T) {
+	var buf bytes.Buffer
+	ch := make(chan struct{})
+	var err error
+	go func() {
+		_, err = redis.Dial("tcp", "example.com:6379",
+			redis.DialTLSConfig(&clientTLSConfig),
+			redis.DialTLSHandshakeTimeout(time.Millisecond),
+			dialTestBlockedConn(ch, &buf),
+			redis.DialUseTLS(true))
+		close(ch)
+	}()
+	select {
+	case <-time.After(time.Second):
+		t.Fatal("dial didn't timeout")
+	case <-ch:
+		if err == nil {
+			t.Fatal("dial didn't error")
+		} else if err.Error() != "TLS handshake timeout" {
+			t.Fatal("dial unexpected error:", err)
+		}
+	}
+}
+
 func TestDialTLSSKipVerify(t *testing.T) {
 	var buf bytes.Buffer
 	c, err := redis.Dial("tcp", "example.com:6379",
@@ -679,6 +755,36 @@ func TestDialTLSSKipVerify(t *testing.T) {
 	checkPingPong(t, &buf, c)
 }
 
+func TestDialUseACL(t *testing.T) {
+	var buf bytes.Buffer
+	_, err := redis.Dial("tcp", "localhost:6379",
+		redis.DialUsername("user"),
+		redis.DialPassword("password"),
+		dialTestConn(pingResponse, &buf))
+	if err != nil {
+		t.Fatal("dial error:", err)
+	}
+	if err != nil {
+		t.Fatal("dial error:", err)
+	}
+	expected := "*3\r\n$4\r\nAUTH\r\n$4\r\nuser\r\n$8\r\npassword\r\n"
+	if w := buf.String(); w != expected {
+		t.Errorf("got %q, want %q", w, expected)
+	}
+}
+
+// Connect to an Redis instance using the Redis ACL system
+func ExampleDial_acl() {
+	c, err := redis.Dial("tcp", "localhost:6379",
+		redis.DialUsername("username"),
+		redis.DialPassword("password"),
+	)
+	if err != nil {
+		// handle error
+	}
+	defer c.Close()
+}
+
 func TestDialClientName(t *testing.T) {
 	var buf bytes.Buffer
 	_, err := redis.Dial("tcp", ":6379",
@@ -725,6 +831,16 @@ func ExampleDial() {
 	defer c.Close()
 }
 
+// Connect to local instance of Redis running on the default port using the provided context.
+func ExampleDialContext() {
+	ctx := context.Background()
+	c, err := redis.DialContext(ctx, "tcp", ":6379")
+	if err != nil {
+		// handle error
+	}
+	defer c.Close()
+}
+
 // Connect to remote instance of Redis using a URL.
 func ExampleDialURL() {
 	c, err := redis.DialURL(os.Getenv("REDIS_URL"))
@@ -746,11 +862,13 @@ func TestExecError(t *testing.T) {
 
 	// Execute commands that fail before EXEC is called.
 
-	c.Do("DEL", "k0")
-	c.Do("ZADD", "k0", 0, 0)
-	c.Send("MULTI")
-	c.Send("NOTACOMMAND", "k0", 0, 0)
-	c.Send("ZINCRBY", "k0", 0, 0)
+	_, err = c.Do("DEL", "k0")
+	require.NoError(t, err)
+	_, err = c.Do("ZADD", "k0", 0, 0)
+	require.NoError(t, err)
+	require.NoError(t, c.Send("MULTI"))
+	require.NoError(t, c.Send("NOTACOMMAND", "k0", 0, 0))
+	require.NoError(t, c.Send("ZINCRBY", "k0", 0, 0))
 	v, err := c.Do("EXEC")
 	if err == nil {
 		t.Fatalf("EXEC returned values %v, expected error", v)
@@ -759,11 +877,13 @@ func TestExecError(t *testing.T) {
 	// Execute commands that fail after EXEC is called. The first command
 	// returns an error.
 
-	c.Do("DEL", "k1")
-	c.Do("ZADD", "k1", 0, 0)
-	c.Send("MULTI")
-	c.Send("HSET", "k1", 0, 0)
-	c.Send("ZINCRBY", "k1", 0, 0)
+	_, err = c.Do("DEL", "k1")
+	require.NoError(t, err)
+	_, err = c.Do("ZADD", "k1", 0, 0)
+	require.NoError(t, err)
+	require.NoError(t, c.Send("MULTI"))
+	require.NoError(t, c.Send("HSET", "k1", 0, 0))
+	require.NoError(t, c.Send("ZINCRBY", "k1", 0, 0))
 	v, err = c.Do("EXEC")
 	if err != nil {
 		t.Fatalf("EXEC returned error %v", err)
@@ -789,10 +909,11 @@ func TestExecError(t *testing.T) {
 	// Execute commands that fail after EXEC is called. The second command
 	// returns an error.
 
-	c.Do("ZADD", "k2", 0, 0)
-	c.Send("MULTI")
-	c.Send("ZINCRBY", "k2", 0, 0)
-	c.Send("HSET", "k2", 0, 0)
+	_, err = c.Do("ZADD", "k2", 0, 0)
+	require.NoError(t, err)
+	require.NoError(t, c.Send("MULTI"))
+	require.NoError(t, c.Send("ZINCRBY", "k2", 0, 0))
+	require.NoError(t, c.Send("HSET", "k2", 0, 0))
 	v, err = c.Do("EXEC")
 	if err != nil {
 		t.Fatalf("EXEC returned error %v", err)
@@ -917,15 +1038,17 @@ func TestWithTimeout(t *testing.T) {
 				var minDeadline, maxDeadline time.Time
 
 				// Alternate between default and specified timeout.
+				var err error
 				if i%2 == 0 {
 					if defaultTimout != 0 {
 						minDeadline = time.Now().Add(defaultTimout)
 					}
 					if recv {
-						c.Receive()
+						_, err = c.Receive()
 					} else {
-						c.Do("PING")
+						_, err = c.Do("PING")
 					}
+					require.NoError(t, err)
 					if defaultTimout != 0 {
 						maxDeadline = time.Now().Add(defaultTimout)
 					}
@@ -933,10 +1056,11 @@ func TestWithTimeout(t *testing.T) {
 					timeout := 10 * time.Minute
 					minDeadline = time.Now().Add(timeout)
 					if recv {
-						redis.ReceiveWithTimeout(c, timeout)
+						_, err = redis.ReceiveWithTimeout(c, timeout)
 					} else {
-						redis.DoWithTimeout(c, timeout, "PING")
+						_, err = redis.DoWithTimeout(c, timeout, "PING")
 					}
+					require.NoError(t, err)
 					maxDeadline = time.Now().Add(timeout)
 				}
 
diff --git a/redis/pool_test.go b/redis/pool_test.go
index 3864d2a..f420b12 100644
--- a/redis/pool_test.go
+++ b/redis/pool_test.go
@@ -18,12 +18,14 @@ import (
 	"context"
 	"errors"
 	"io"
+	"net"
 	"reflect"
 	"sync"
 	"testing"
 	"time"
 
 	"github.com/gomodule/redigo/redis"
+	"github.com/stretchr/testify/require"
 )
 
 const (
@@ -141,11 +143,13 @@ func TestPoolReuse(t *testing.T) {
 
 	for i := 0; i < 10; i++ {
 		c1 := p.Get()
-		c1.Do("PING")
+		_, err := c1.Do("PING")
+		require.NoError(t, err)
 		c2 := p.Get()
-		c2.Do("PING")
-		c1.Close()
-		c2.Close()
+		_, err = c2.Do("PING")
+		require.NoError(t, err)
+		require.NoError(t, c1.Close())
+		require.NoError(t, c2.Close())
 	}
 
 	d.check("before close", p, 2, 2, 0)
@@ -163,14 +167,17 @@ func TestPoolMaxIdle(t *testing.T) {
 
 	for i := 0; i < 10; i++ {
 		c1 := p.Get()
-		c1.Do("PING")
+		_, err = c1.Do("PING")
+		require.NoError(t, err)
 		c2 := p.Get()
-		c2.Do("PING")
+		_, err = c2.Do("PING")
+		require.NoError(t, err)
 		c3 := p.Get()
-		c3.Do("PING")
-		c1.Close()
-		c2.Close()
-		c3.Close()
+		_, err = c3.Do("PING")
+		require.NoError(t, err)
+		require.NoError(t, c1.Close())
+		require.NoError(t, c2.Close())
+		require.NoError(t, c3.Close())
 	}
 	d.check("before close", p, 12, 2, 0)
 	p.Close()
@@ -186,14 +193,16 @@ func TestPoolError(t *testing.T) {
 	defer p.Close()
 
 	c := p.Get()
-	c.Do("ERR", io.EOF)
+	_, err := c.Do("ERR", io.EOF)
+	require.NoError(t, err)
 	if c.Err() == nil {
 		t.Errorf("expected c.Err() != nil")
 	}
 	c.Close()
 
 	c = p.Get()
-	c.Do("ERR", io.EOF)
+	_, err = c.Do("ERR", io.EOF)
+	require.NoError(t, err)
 	c.Close()
 
 	d.check(".", p, 2, 0, 0)
@@ -208,11 +217,14 @@ func TestPoolClose(t *testing.T) {
 	defer p.Close()
 
 	c1 := p.Get()
-	c1.Do("PING")
+	_, err := c1.Do("PING")
+	require.NoError(t, err)
 	c2 := p.Get()
-	c2.Do("PING")
+	_, err = c2.Do("PING")
+	require.NoError(t, err)
 	c3 := p.Get()
-	c3.Do("PING")
+	_, err = c3.Do("PING")
+	require.NoError(t, err)
 
 	c1.Close()
 	if _, err := c1.Do("PING"); err == nil {
@@ -284,7 +296,8 @@ func TestPoolIdleTimeout(t *testing.T) {
 	defer redis.SetNowFunc(time.Now)
 
 	c := p.Get()
-	c.Do("PING")
+	_, err := c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	d.check("1", p, 1, 1, 0)
@@ -292,7 +305,8 @@ func TestPoolIdleTimeout(t *testing.T) {
 	now = now.Add(p.IdleTimeout + 1)
 
 	c = p.Get()
-	c.Do("PING")
+	_, err = c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	d.check("2", p, 2, 1, 0)
@@ -312,7 +326,8 @@ func TestPoolMaxLifetime(t *testing.T) {
 	defer redis.SetNowFunc(time.Now)
 
 	c := p.Get()
-	c.Do("PING")
+	_, err := c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	d.check("1", p, 1, 1, 0)
@@ -320,7 +335,8 @@ func TestPoolMaxLifetime(t *testing.T) {
 	now = now.Add(p.MaxConnLifetime + 1)
 
 	c = p.Get()
-	c.Do("PING")
+	_, err = c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	d.check("2", p, 2, 1, 0)
@@ -338,7 +354,7 @@ func TestPoolConcurrenSendReceive(t *testing.T) {
 		_, err := c.Receive()
 		done <- err
 	}()
-	c.Send("PING")
+	require.NoError(t, c.Send("PING"))
 	c.Flush()
 	err := <-done
 	if err != nil {
@@ -362,7 +378,8 @@ func TestPoolBorrowCheck(t *testing.T) {
 
 	for i := 0; i < 10; i++ {
 		c := p.Get()
-		c.Do("PING")
+		_, err := c.Do("PING")
+		require.NoError(t, err)
 		c.Close()
 	}
 	d.check("1", p, 10, 1, 0)
@@ -378,9 +395,11 @@ func TestPoolMaxActive(t *testing.T) {
 	defer p.Close()
 
 	c1 := p.Get()
-	c1.Do("PING")
+	_, err := c1.Do("PING")
+	require.NoError(t, err)
 	c2 := p.Get()
-	c2.Do("PING")
+	_, err = c2.Do("PING")
+	require.NoError(t, err)
 
 	d.check("1", p, 2, 2, 2)
 
@@ -414,9 +433,11 @@ func TestPoolWaitStats(t *testing.T) {
 	defer p.Close()
 
 	c1 := p.Get()
-	c1.Do("PING")
+	_, err := c1.Do("PING")
+	require.NoError(t, err)
 	c2 := p.Get()
-	c2.Do("PING")
+	_, err = c2.Do("PING")
+	require.NoError(t, err)
 
 	d.checkAll("1", p, 2, 2, 2, 0, 0)
 
@@ -444,7 +465,7 @@ func TestPoolMonitorCleanup(t *testing.T) {
 	defer p.Close()
 
 	c := p.Get()
-	c.Send("MONITOR")
+	require.NoError(t, c.Send("MONITOR"))
 	c.Close()
 
 	d.check("", p, 1, 0, 0)
@@ -460,7 +481,7 @@ func TestPoolPubSubCleanup(t *testing.T) {
 	defer p.Close()
 
 	c := p.Get()
-	c.Send("SUBSCRIBE", "x")
+	require.NoError(t, c.Send("SUBSCRIBE", "x"))
 	c.Close()
 
 	want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
@@ -470,7 +491,7 @@ func TestPoolPubSubCleanup(t *testing.T) {
 	d.commands = nil
 
 	c = p.Get()
-	c.Send("PSUBSCRIBE", "x*")
+	require.NoError(t, c.Send("PSUBSCRIBE", "x*"))
 	c.Close()
 
 	want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
@@ -490,8 +511,10 @@ func TestPoolTransactionCleanup(t *testing.T) {
 	defer p.Close()
 
 	c := p.Get()
-	c.Do("WATCH", "key")
-	c.Do("PING")
+	_, err := c.Do("WATCH", "key")
+	require.NoError(t, err)
+	_, err = c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	want := []string{"WATCH", "PING", "UNWATCH"}
@@ -501,9 +524,12 @@ func TestPoolTransactionCleanup(t *testing.T) {
 	d.commands = nil
 
 	c = p.Get()
-	c.Do("WATCH", "key")
-	c.Do("UNWATCH")
-	c.Do("PING")
+	_, err = c.Do("WATCH", "key")
+	require.NoError(t, err)
+	_, err = c.Do("UNWATCH")
+	require.NoError(t, err)
+	_, err = c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	want = []string{"WATCH", "UNWATCH", "PING"}
@@ -513,9 +539,12 @@ func TestPoolTransactionCleanup(t *testing.T) {
 	d.commands = nil
 
 	c = p.Get()
-	c.Do("WATCH", "key")
-	c.Do("MULTI")
-	c.Do("PING")
+	_, err = c.Do("WATCH", "key")
+	require.NoError(t, err)
+	_, err = c.Do("MULTI")
+	require.NoError(t, err)
+	_, err = c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	want = []string{"WATCH", "MULTI", "PING", "DISCARD"}
@@ -525,10 +554,14 @@ func TestPoolTransactionCleanup(t *testing.T) {
 	d.commands = nil
 
 	c = p.Get()
-	c.Do("WATCH", "key")
-	c.Do("MULTI")
-	c.Do("DISCARD")
-	c.Do("PING")
+	_, err = c.Do("WATCH", "key")
+	require.NoError(t, err)
+	_, err = c.Do("MULTI")
+	require.NoError(t, err)
+	_, err = c.Do("DISCARD")
+	require.NoError(t, err)
+	_, err = c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	want = []string{"WATCH", "MULTI", "DISCARD", "PING"}
@@ -538,10 +571,14 @@ func TestPoolTransactionCleanup(t *testing.T) {
 	d.commands = nil
 
 	c = p.Get()
-	c.Do("WATCH", "key")
-	c.Do("MULTI")
-	c.Do("EXEC")
-	c.Do("PING")
+	_, err = c.Do("WATCH", "key")
+	require.NoError(t, err)
+	_, err = c.Do("MULTI")
+	require.NoError(t, err)
+	_, err = c.Do("EXEC")
+	require.NoError(t, err)
+	_, err = c.Do("PING")
+	require.NoError(t, err)
 	c.Close()
 
 	want = []string{"WATCH", "MULTI", "EXEC", "PING"}
@@ -824,6 +861,32 @@ func TestWaitPoolGetContext(t *testing.T) {
 	defer c.Close()
 }
 
+func TestWaitPoolGetContextIssue520(t *testing.T) {
+	d := poolDialer{t: t}
+	p := &redis.Pool{
+		MaxIdle:   1,
+		MaxActive: 1,
+		Dial:      d.dial,
+		Wait:      true,
+	}
+	defer p.Close()
+	ctx1, cancel1 := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+	defer cancel1()
+	c, err := p.GetContext(ctx1)
+	if err != context.DeadlineExceeded {
+		t.Fatalf("GetContext returned %v", err)
+	}
+	defer c.Close()
+
+	ctx2, cancel2 := context.WithCancel(context.Background())
+	defer cancel2()
+	c2, err := p.GetContext(ctx2)
+	if err != nil {
+		t.Fatalf("Get context returned %v", err)
+	}
+	defer c2.Close()
+}
+
 func TestWaitPoolGetContextWithDialContext(t *testing.T) {
 	d := poolDialer{t: t}
 	p := &redis.Pool{
@@ -840,6 +903,48 @@ func TestWaitPoolGetContextWithDialContext(t *testing.T) {
 	defer c.Close()
 }
 
+func TestPoolGetContext_DialContext(t *testing.T) {
+	var isPassed bool
+	f := func(ctx context.Context, network, addr string) (net.Conn, error) {
+		isPassed = true
+		return &testConn{}, nil
+	}
+
+	p := &redis.Pool{
+		DialContext: func(ctx context.Context) (redis.Conn, error) {
+			return redis.DialContext(ctx, "", "", redis.DialContextFunc(f))
+		},
+	}
+	defer p.Close()
+
+	if _, err := p.GetContext(context.Background()); err != nil {
+		t.Fatalf("GetContext returned %v", err)
+	}
+
+	if !isPassed {
+		t.Fatal("DialContextFunc not passed")
+	}
+}
+
+func TestPoolGetContext_DialContext_CanceledContext(t *testing.T) {
+	addr, err := redis.DefaultServerAddr()
+	if err != nil {
+		t.Fatalf("redis.DefaultServerAddr returned %v", err)
+	}
+
+	p := &redis.Pool{
+		DialContext: func(ctx context.Context) (redis.Conn, error) { return redis.DialContext(ctx, "tcp", addr) },
+	}
+	defer p.Close()
+
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+
+	if _, err := p.GetContext(ctx); err == nil {
+		t.Fatalf("GetContext returned nil, expect error")
+	}
+}
+
 func TestWaitPoolGetAfterClose(t *testing.T) {
 	d := poolDialer{t: t}
 	p := &redis.Pool{
@@ -856,20 +961,38 @@ func TestWaitPoolGetAfterClose(t *testing.T) {
 }
 
 func TestWaitPoolGetCanceledContext(t *testing.T) {
-	d := poolDialer{t: t}
-	p := &redis.Pool{
-		MaxIdle:   1,
-		MaxActive: 1,
-		Dial:      d.dial,
-		Wait:      true,
-	}
-	defer p.Close()
-	ctx, f := context.WithCancel(context.Background())
-	f()
-	c := p.Get()
-	defer c.Close()
-	_, err := p.GetContext(ctx)
-	if err != context.Canceled {
-		t.Fatalf("got error %v, want %v", err, context.Canceled)
-	}
+	t.Run("without vacant connection in the pool", func(t *testing.T) {
+		d := poolDialer{t: t}
+		p := &redis.Pool{
+			MaxIdle:   1,
+			MaxActive: 1,
+			Dial:      d.dial,
+			Wait:      true,
+		}
+		defer p.Close()
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		c := p.Get()
+		defer c.Close()
+		_, err := p.GetContext(ctx)
+		if err != context.Canceled {
+			t.Fatalf("got error %v, want %v", err, context.Canceled)
+		}
+	})
+	t.Run("with vacant connection in the pool", func(t *testing.T) {
+		d := poolDialer{t: t}
+		p := &redis.Pool{
+			MaxIdle:   1,
+			MaxActive: 1,
+			Dial:      d.dial,
+			Wait:      true,
+		}
+		defer p.Close()
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		_, err := p.GetContext(ctx)
+		if err != context.Canceled {
+			t.Fatalf("got error %v, want %v", err, context.Canceled)
+		}
+	})
 }
diff --git a/redis/pubsub_example_test.go b/redis/pubsub_example_test.go
index 39d0abf..4a5ec91 100644
--- a/redis/pubsub_example_test.go
+++ b/redis/pubsub_example_test.go
@@ -84,7 +84,7 @@ func listenPubSubChannels(ctx context.Context, redisServerAddr string,
 	ticker := time.NewTicker(healthCheckPeriod)
 	defer ticker.Stop()
 loop:
-	for err == nil {
+	for {
 		select {
 		case <-ticker.C:
 			// Send ping to test health of connection and server. If
@@ -102,7 +102,9 @@ loop:
 	}
 
 	// Signal the receiving goroutine to exit by unsubscribing from all channels.
-	psc.Unsubscribe()
+	if err := psc.Unsubscribe(); err != nil {
+		return err
+	}
 
 	// Wait for goroutine to complete.
 	return <-done
@@ -116,9 +118,18 @@ func publish() {
 	}
 	defer c.Close()
 
-	c.Do("PUBLISH", "c1", "hello")
-	c.Do("PUBLISH", "c2", "world")
-	c.Do("PUBLISH", "c1", "goodbye")
+	if _, err = c.Do("PUBLISH", "c1", "hello"); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if _, err = c.Do("PUBLISH", "c2", "world"); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if _, err = c.Do("PUBLISH", "c1", "goodbye"); err != nil {
+		fmt.Println(err)
+		return
+	}
 }
 
 // This example shows how receive pubsub notifications with cancelation and
diff --git a/redis/pubsub_test.go b/redis/pubsub_test.go
index 13f3f79..83b9158 100644
--- a/redis/pubsub_test.go
+++ b/redis/pubsub_test.go
@@ -20,6 +20,7 @@ import (
 	"time"
 
 	"github.com/gomodule/redigo/redis"
+	"github.com/stretchr/testify/require"
 )
 
 func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) {
@@ -44,29 +45,30 @@ func TestPushed(t *testing.T) {
 
 	c := redis.PubSubConn{Conn: sc}
 
-	c.Subscribe("c1")
+	require.NoError(t, c.Subscribe("c1"))
 	expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1})
-	c.Subscribe("c2")
+	require.NoError(t, c.Subscribe("c2"))
 	expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2})
-	c.PSubscribe("p1")
+	require.NoError(t, c.PSubscribe("p1"))
 	expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3})
-	c.PSubscribe("p2")
+	require.NoError(t, c.PSubscribe("p2"))
 	expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4})
-	c.PUnsubscribe()
+	require.NoError(t, c.PUnsubscribe())
 	expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3})
 	expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2})
 
-	pc.Do("PUBLISH", "c1", "hello")
+	_, err = pc.Do("PUBLISH", "c1", "hello")
+	require.NoError(t, err)
 	expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")})
 
-	c.Ping("hello")
+	require.NoError(t, c.Ping("hello"))
 	expectPushed(t, c, `Ping("hello")`, redis.Pong{Data: "hello"})
 
-	c.Conn.Send("PING")
+	require.NoError(t, c.Conn.Send("PING"))
 	c.Conn.Flush()
 	expectPushed(t, c, `Send("PING")`, redis.Pong{})
 
-	c.Ping("timeout")
+	require.NoError(t, c.Ping("timeout"))
 	got := c.ReceiveWithTimeout(time.Minute)
 	if want := (redis.Pong{Data: "timeout"}); want != got {
 		t.Errorf("recv /w timeout got %v, want %v", got, want)
diff --git a/redis/reply_test.go b/redis/reply_test.go
index 464045a..8993bad 100644
--- a/redis/reply_test.go
+++ b/redis/reply_test.go
@@ -238,8 +238,16 @@ func ExampleBool() {
 	}
 	defer c.Close()
 
-	c.Do("SET", "foo", 1)
-	exists, _ := redis.Bool(c.Do("EXISTS", "foo"))
+	if _, err = c.Do("SET", "foo", 1); err != nil {
+		fmt.Println(err)
+		return
+	}
+	exists, err := redis.Bool(c.Do("EXISTS", "foo"))
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
 	fmt.Printf("%#v\n", exists)
 	// Output:
 	// true
@@ -253,10 +261,22 @@ func ExampleInt() {
 	}
 	defer c.Close()
 
-	c.Do("SET", "k1", 1)
-	n, _ := redis.Int(c.Do("GET", "k1"))
+	_, err = c.Do("SET", "k1", 1)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	n, err := redis.Int(c.Do("GET", "k1"))
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
 	fmt.Printf("%#v\n", n)
-	n, _ = redis.Int(c.Do("INCR", "k1"))
+	n, err = redis.Int(c.Do("INCR", "k1"))
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
 	fmt.Printf("%#v\n", n)
 	// Output:
 	// 1
@@ -271,8 +291,16 @@ func ExampleInts() {
 	}
 	defer c.Close()
 
-	c.Do("SADD", "set_with_integers", 4, 5, 6)
-	ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers"))
+	_, err = c.Do("SADD", "set_with_integers", 4, 5, 6)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	ints, err := redis.Ints(c.Do("SMEMBERS", "set_with_integers"))
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
 	fmt.Printf("%#v\n", ints)
 	// Output:
 	// []int{4, 5, 6}
@@ -286,8 +314,16 @@ func ExampleString() {
 	}
 	defer c.Close()
 
-	c.Do("SET", "hello", "world")
+	_, err = c.Do("SET", "hello", "world")
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
 	s, err := redis.String(c.Do("GET", "hello"))
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
 	fmt.Printf("%#v\n", s)
 	// Output:
 	// "world"
diff --git a/redis/scan_test.go b/redis/scan_test.go
index 67b1aac..d43c372 100644
--- a/redis/scan_test.go
+++ b/redis/scan_test.go
@@ -115,7 +115,7 @@ func TestScanConversion(t *testing.T) {
 	for _, tt := range scanConversionTests {
 		values := []interface{}{tt.src}
 		dest := reflect.New(reflect.TypeOf(tt.dest))
-		values, err := redis.Scan(values, dest.Interface())
+		_, err := redis.Scan(values, dest.Interface())
 		if err != nil {
 			t.Errorf("Scan(%v) returned error %v", tt, err)
 			continue
@@ -144,7 +144,7 @@ func TestScanConversionError(t *testing.T) {
 	for _, tt := range scanConversionErrorTests {
 		values := []interface{}{tt.src}
 		dest := reflect.New(reflect.TypeOf(tt.dest))
-		values, err := redis.Scan(values, dest.Interface())
+		_, err := redis.Scan(values, dest.Interface())
 		if err == nil {
 			t.Errorf("Scan(%v) did not return error", tt)
 		}
@@ -159,12 +159,30 @@ func ExampleScan() {
 	}
 	defer c.Close()
 
-	c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
-	c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
-	c.Send("HMSET", "album:3", "title", "Beat")
-	c.Send("LPUSH", "albums", "1")
-	c.Send("LPUSH", "albums", "2")
-	c.Send("LPUSH", "albums", "3")
+	if err = c.Send("HMSET", "album:1", "title", "Red", "rating", 5); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("HMSET", "album:3", "title", "Beat"); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("LPUSH", "albums", "1"); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("LPUSH", "albums", "2"); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("LPUSH", "albums", "3"); err != nil {
+		fmt.Println(err)
+		return
+	}
 	values, err := redis.Values(c.Do("SORT", "albums",
 		"BY", "album:*->rating",
 		"GET", "album:*->title",
@@ -298,67 +316,90 @@ func TestBadScanStructArgs(t *testing.T) {
 	test(&v2)
 }
 
+type sliceScanner struct {
+	Field string
+}
+
+func (ss *sliceScanner) RedisScan(s interface{}) error {
+	v, ok := s.([]interface{})
+	if !ok {
+		return fmt.Errorf("invalid type %T", s)
+	}
+	return redis.ScanStruct(v, ss)
+}
+
 var scanSliceTests = []struct {
+	name       string
 	src        []interface{}
 	fieldNames []string
 	ok         bool
 	dest       interface{}
 }{
 	{
+		"scanner",
+		[]interface{}{[]interface{}{[]byte("Field"), []byte("1")}},
+		nil,
+		true,
+		[]*sliceScanner{{"1"}},
+	},
+	{
+		"int",
 		[]interface{}{[]byte("1"), nil, []byte("-1")},
 		nil,
 		true,
 		[]int{1, 0, -1},
 	},
 	{
+		"uint",
 		[]interface{}{[]byte("1"), nil, []byte("2")},
 		nil,
 		true,
 		[]uint{1, 0, 2},
 	},
 	{
+		"uint-error",
 		[]interface{}{[]byte("-1")},
 		nil,
 		false,
 		[]uint{1},
 	},
 	{
+		"[]byte",
 		[]interface{}{[]byte("hello"), nil, []byte("world")},
 		nil,
 		true,
 		[][]byte{[]byte("hello"), nil, []byte("world")},
 	},
 	{
+		"string",
 		[]interface{}{[]byte("hello"), nil, []byte("world")},
 		nil,
 		true,
 		[]string{"hello", "", "world"},
 	},
 	{
+		"struct",
 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
 		nil,
 		true,
 		[]struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
 	},
 	{
+		"struct-error",
 		[]interface{}{[]byte("a1"), []byte("b1")},
 		nil,
 		false,
 		[]struct{ A, B, C string }{{"a1", "b1", ""}},
 	},
 	{
-		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
-		nil,
-		true,
-		[]*struct{ A, B string }{{A: "a1", B: "b1"}, {A: "a2", B: "b2"}},
-	},
-	{
+		"struct-field-names",
 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
 		[]string{"A", "B"},
 		true,
 		[]struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
 	},
 	{
+		"struct-no-fields",
 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
 		nil,
 		false,
@@ -368,18 +409,18 @@ var scanSliceTests = []struct {
 
 func TestScanSlice(t *testing.T) {
 	for _, tt := range scanSliceTests {
+		t.Run(tt.name, func(t *testing.T) {
+			typ := reflect.ValueOf(tt.dest).Type()
+			dest := reflect.New(typ)
 
-		typ := reflect.ValueOf(tt.dest).Type()
-		dest := reflect.New(typ)
-
-		err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
-		if tt.ok != (err == nil) {
-			t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
-			continue
-		}
-		if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
-			t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
-		}
+			err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
+			if tt.ok != (err == nil) {
+				t.Fatalf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
+			}
+			if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
+				t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
+			}
+		})
 	}
 }
 
@@ -391,12 +432,30 @@ func ExampleScanSlice() {
 	}
 	defer c.Close()
 
-	c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
-	c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
-	c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
-	c.Send("LPUSH", "albums", "1")
-	c.Send("LPUSH", "albums", "2")
-	c.Send("LPUSH", "albums", "3")
+	if err = c.Send("HMSET", "album:1", "title", "Red", "rating", 5); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("HMSET", "album:3", "title", "Beat", "rating", 4); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("LPUSH", "albums", "1"); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("LPUSH", "albums", "2"); err != nil {
+		fmt.Println(err)
+		return
+	}
+	if err = c.Send("LPUSH", "albums", "3"); err != nil {
+		fmt.Println(err)
+		return
+	}
 	values, err := redis.Values(c.Do("SORT", "albums",
 		"BY", "album:*->rating",
 		"GET", "album:*->title",
@@ -419,7 +478,13 @@ func ExampleScanSlice() {
 	// [{Earthbound 1} {Beat 4} {Red 5}]
 }
 
-var now = time.Now()
+type Ed struct {
+	EdI int `redis:"edi"`
+}
+
+type Edp struct {
+	EdpI int `redis:"edpi"`
+}
 
 var argsTests = []struct {
 	title    string
@@ -470,6 +535,16 @@ var argsTests = []struct {
 		}),
 		redis.Args{},
 	},
+	{"struct-anonymous",
+		redis.Args{}.AddFlat(struct {
+			Ed
+			*Edp
+		}{
+			Ed{EdI: 2},
+			&Edp{EdpI: 3},
+		}),
+		redis.Args{"edi", 2, "edpi", 3},
+	},
 }
 
 func TestArgs(t *testing.T) {
diff --git a/redis/script_test.go b/redis/script_test.go
index 388e167..71424c3 100644
--- a/redis/script_test.go
+++ b/redis/script_test.go
@@ -78,6 +78,9 @@ func TestScript(t *testing.T) {
 	}
 
 	v, err = c.Receive()
+	if err != nil {
+		t.Errorf("s.Recieve() returned %v", err)
+	}
 	if !reflect.DeepEqual(v, reply) {
 		t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply)
 	}
@@ -93,6 +96,9 @@ func TestScript(t *testing.T) {
 	}
 
 	v, err = c.Receive()
+	if err != nil {
+		t.Errorf("s.Recieve() returned %v", err)
+	}
 	if !reflect.DeepEqual(v, reply) {
 		t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply)
 	}
diff --git a/redis/test_test.go b/redis/test_test.go
index dfa48bf..fcbb7bd 100644
--- a/redis/test_test.go
+++ b/redis/test_test.go
@@ -107,13 +107,17 @@ func (s *Server) watch(r io.Reader, ready chan error) {
 	if !listening {
 		ready <- fmt.Errorf("server exited: %s", text)
 	}
-	s.cmd.Wait()
+	if err := s.cmd.Wait(); err != nil {
+		if listening {
+			ready <- err
+		}
+	}
 	fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name)
 	close(s.done)
 }
 
 func (s *Server) Stop() {
-	s.cmd.Process.Signal(os.Interrupt)
+	s.cmd.Process.Signal(os.Interrupt) // nolint: errcheck
 	<-s.done
 }
 
@@ -156,7 +160,9 @@ func DialDefaultServer(options ...DialOption) (Conn, error) {
 	if err != nil {
 		return nil, err
 	}
-	c.Do("FLUSHDB")
+	if _, err = c.Do("FLUSHDB"); err != nil {
+		return nil, err
+	}
 	return c, nil
 }
 
diff --git a/redis/zpop_example_test.go b/redis/zpop_example_test.go
index f7702e0..3d1af52 100644
--- a/redis/zpop_example_test.go
+++ b/redis/zpop_example_test.go
@@ -26,7 +26,7 @@ func zpop(c redis.Conn, key string) (result string, err error) {
 	defer func() {
 		// Return connection to normal state on error.
 		if err != nil {
-			c.Do("DISCARD")
+			c.Do("DISCARD") // nolint: errcheck
 		}
 	}()
 
@@ -44,8 +44,12 @@ func zpop(c redis.Conn, key string) (result string, err error) {
 			return "", redis.ErrNil
 		}
 
-		c.Send("MULTI")
-		c.Send("ZREM", key, members[0])
+		if err = c.Send("MULTI"); err != nil {
+			return "", err
+		}
+		if err = c.Send("ZREM", key, members[0]); err != nil {
+			return "", err
+		}
 		queued, err := c.Do("EXEC")
 		if err != nil {
 			return "", err
@@ -83,8 +87,12 @@ func Example_zpop() {
 	// Add test data using a pipeline.
 
 	for i, member := range []string{"red", "blue", "green"} {
-		c.Send("ZADD", "zset", i, member)
+		if err = c.Send("ZADD", "zset", i, member); err != nil {
+			fmt.Println(err)
+			return
+		}
 	}
+
 	if _, err := c.Do(""); err != nil {
 		fmt.Println(err)
 		return
diff --git a/redisx/connmux_test.go b/redisx/connmux_test.go
index e035a59..a1d38ba 100644
--- a/redisx/connmux_test.go
+++ b/redisx/connmux_test.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/gomodule/redigo/redis"
 	"github.com/gomodule/redigo/redisx"
+	"github.com/stretchr/testify/require"
 )
 
 func TestConnMux(t *testing.T) {
@@ -33,8 +34,10 @@ func TestConnMux(t *testing.T) {
 
 	c1 := m.Get()
 	c2 := m.Get()
-	c1.Send("ECHO", "hello")
-	c2.Send("ECHO", "world")
+	err = c1.Send("ECHO", "hello")
+	require.NoError(t, err)
+	err = c2.Send("ECHO", "world")
+	require.NoError(t, err)
 	c1.Flush()
 	c2.Flush()
 	s, err := redis.String(c1.Receive())
@@ -172,9 +175,8 @@ func BenchmarkConnMuxConcurrent(b *testing.B) {
 			defer wg.Done()
 			for i := 0; i < b.N; i++ {
 				c := m.Get()
-				if _, err := c.Do("PING"); err != nil {
-					b.Fatal(err)
-				}
+				_, err := c.Do("PING")
+				require.NoError(b, err)
 				c.Close()
 			}
 		}()
@@ -211,9 +213,8 @@ func BenchmarkPoolConcurrent(b *testing.B) {
 			defer wg.Done()
 			for i := 0; i < b.N; i++ {
 				c := p.Get()
-				if _, err := c.Do("PING"); err != nil {
-					b.Fatal(err)
-				}
+				_, err := c.Do("PING")
+				require.NoError(b, err)
 				c.Close()
 			}
 		}()
@@ -242,14 +243,13 @@ func BenchmarkPipelineConcurrency(b *testing.B) {
 			for i := 0; i < b.N; i++ {
 				id := pipeline.Next()
 				pipeline.StartRequest(id)
-				c.Send("PING")
+				err = c.Send("PING")
+				require.NoError(b, err)
 				c.Flush()
 				pipeline.EndRequest(id)
 				pipeline.StartResponse(id)
 				_, err := c.Receive()
-				if err != nil {
-					b.Fatal(err)
-				}
+				require.NoError(b, err)
 				pipeline.EndResponse(id)
 			}
 		}()
diff --git a/redisx/db_test.go b/redisx/db_test.go
index ead6474..8b72eb1 100644
--- a/redisx/db_test.go
+++ b/redisx/db_test.go
@@ -42,7 +42,10 @@ func (t testConn) Close() error {
 // stomping on real data, DialTestDB fails if database 9 contains data. The
 // returned connection flushes database 9 on close.
 func DialTest() (redis.Conn, error) {
-	c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
+	c, err := redis.Dial("tcp", ":6379",
+		redis.DialReadTimeout(time.Second),
+		redis.DialWriteTimeout(time.Second),
+	)
 	if err != nil {
 		return nil, err
 	}