Full Code of gomodule/redigo for AI

master 57c8b99ccb90 cached
43 files
243.3 KB
73.2k tokens
420 symbols
1 requests
Download .txt
Showing preview only (256K chars total). Download the full file or copy to clipboard to get everything.
Repository: gomodule/redigo
Branch: master
Commit: 57c8b99ccb90
Files: 43
Total size: 243.3 KB

Directory structure:
gitextract_abjfnhty/

├── .clog.toml
├── .github/
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── go-test.yml
│       ├── golangci-lint.yml
│       └── release-build.yml
├── .goreleaser.yaml
├── .travis.yml
├── LICENSE
├── README.markdown
├── go.mod
├── go.sum
├── redis/
│   ├── commandinfo.go
│   ├── commandinfo_test.go
│   ├── conn.go
│   ├── conn_test.go
│   ├── doc.go
│   ├── list_test.go
│   ├── log.go
│   ├── pool.go
│   ├── pool_test.go
│   ├── pubsub.go
│   ├── pubsub_example_test.go
│   ├── pubsub_test.go
│   ├── redis.go
│   ├── redis_test.go
│   ├── reflect.go
│   ├── reflect_go117.go
│   ├── reflect_go118.go
│   ├── reply.go
│   ├── reply_test.go
│   ├── scan.go
│   ├── scan_test.go
│   ├── script.go
│   ├── script_test.go
│   ├── test_test.go
│   └── zpop_example_test.go
└── redisx/
    ├── commandinfo.go
    ├── commandinfo_test.go
    ├── connmux.go
    ├── connmux_test.go
    ├── db_test.go
    └── doc.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .clog.toml
================================================
[clog]
repository = "https://github.com/gomodule/redigo"
subtitle = "Release Notes"
from-latest-tag = true

[sections]
"Refactors" = ["refactor"]
"Chores" = ["chore"]
"Continuous Integration" = ["ci"]
"Improvements" = ["imp", "improvement"]
"Features" = ["feat", "feature"]
"Legacy" = ["legacy"]
"QA" = ["qa", "test"]
"Documentation" = ["doc", "docs"]


================================================
FILE: .github/CONTRIBUTING.md
================================================
Ask questions at
[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).

[Open an issue](https://github.com/gomodule/redigo/issues/new) to discuss your
plans before doing any work on Redigo.


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis


================================================
FILE: .github/workflows/go-test.yml
================================================
name: go-test
on:
  push:
    tags:
      - v*
    branches:
      - master
  pull_request:
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        go-version:
        - '1.21'
        - '1.22'
        os:
        - 'ubuntu-latest'
        redis:
        - '7.2'
        - '7.0'
        - '6.2'
        - '6.0'
    name: Test go ${{ matrix.go-version }} redis ${{ matrix.redis }} on ${{ matrix.os }}
    steps:
      - name: Setup redis
        uses: shogo82148/actions-setup-redis@v1
        with:
          redis-version: ${{ matrix.redis }}

      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go-version }}

      - name: Go Test
        run: go test -race ./...


================================================
FILE: .github/workflows/golangci-lint.yml
================================================
name: golangci-lint
on:
  push:
    tags:
      - v*
    branches:
      - master
  pull_request:
jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup golang
        uses: actions/setup-go@v4
        with:
          go-version: '1.22'
          cache: false # Handled by golangci-lint.

      - name: Validate go mod
        run: |
          go mod tidy
          git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]]

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v4
        with:
          version: v1.56.2
          args: --out-format=colored-line-number


================================================
FILE: .github/workflows/release-build.yml
================================================
name: Build Release

on:
  push:
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+*'

permissions:
  contents: write

jobs:
  goreleaser:
    name: Release Go Binary
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v5
        with:
          distribution: goreleaser
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .goreleaser.yaml
================================================
# When adding options check the documentation at https://goreleaser.com
builds:
  - skip: true
release:
  header: |
    <a name='{{.Tag}}'></a>
    ### {{.Tag}} Release Notes ({{.Date}})
  footer: |
    [Full Changelog](https://{{ .ModulePath }}/compare/{{ .PreviousTag }}...{{ .Tag }})
changelog:
  use: github
  sort: asc
  filters:
    exclude:
    - Merge pull request
    - Merge remote-tracking branch
    - Merge branch

  # Group commits messages by given regex and title.
  # Order value defines the order of the groups.
  # Proving no regex means all commits will be grouped under the default group.
  # Groups are disabled when using github-native, as it already groups things by itself.
  # Matches are performed against strings of the form: "<abbrev-commit>[:] <title-commit>".
  #
  # Default is no groups.
  groups:
    - title: Features
      regexp: '^.*?(feat|feature)(\([[:word:]]+\))??!?:.+$'
      order: 0
    - title: 'Bug fixes'
      regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
      order: 1
    - title: 'Chores'
      regexp: '^.*?chore(\([[:word:]]+\))??!?:.+$'
      order: 2
    - title: 'Quality'
      regexp: '^.*?(qa|test|tests)(\([[:word:]]+\))??!?:.+$'
      order: 3
    - title: 'Documentation'
      regexp: '^.*?(doc|docs)(\([[:word:]]+\))??!?:.+$'
      order: 4
    - title: 'Continuous Integration'
      regexp: '^.*?ci(\([[:word:]]+\))??!?:.+$'
      order: 5
    - title: Other
      order: 999


================================================
FILE: .travis.yml
================================================
language: go
services:
  - redis-server

go:
  - 1.13.x
  - 1.14.x
  - master

matrix:
  allow_failures:
    - go: master

script:
  - go get -t -v ./...
  - diff -u <(echo -n) <(gofmt -d .)
  - go vet $(go list ./... | grep -v /vendor/)
  - go test -v -race ./...


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      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


================================================
FILE: README.markdown
================================================
Redigo
======

[![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/) / [Valkey](https://github.com/valkey-io/valkey) database.

Features
-------

* 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](https://pkg.go.dev/github.com/gomodule/redigo/redis)
- [FAQ](https://github.com/gomodule/redigo/wiki/FAQ)
- [Examples](https://pkg.go.dev/github.com/gomodule/redigo/redis#pkg-examples)

Installation
------------

Install Redigo using the "go get" command:

    go get github.com/gomodule/redigo/redis

The Go distribution is Redigo's only dependency.

Related Projects
----------------

- [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
- [Alibaba/opentelemetry-go-auto-instrumentation](https://github.com/alibaba/opentelemetry-go-auto-instrumentation) - Get trace and metrics in OpenTelemetry format generated by the Redigo framework without changing any code

Contributing
------------

See [CONTRIBUTING.md](https://github.com/gomodule/redigo/blob/master/.github/CONTRIBUTING.md).

License
-------

Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).


================================================
FILE: go.mod
================================================
module github.com/gomodule/redigo

go 1.17

require github.com/stretchr/testify v1.8.4

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)

retract (
	v2.0.0+incompatible // Old development version not maintained or published.
	v1.8.10 // Incorrect version tag for feature.
	v0.0.0-do-not-use // Never used only present due to lack of retract.
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: redis/commandinfo.go
================================================
// Copyright 2014 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"strings"
)

const (
	connectionWatchState = 1 << iota
	connectionMultiState
	connectionSubscribeState
	connectionMonitorState
)

type commandInfo struct {
	// Set or Clear these states on connection.
	Set, Clear int
}

var commandInfos = map[string]commandInfo{
	"WATCH":      {Set: connectionWatchState},
	"UNWATCH":    {Clear: connectionWatchState},
	"MULTI":      {Set: connectionMultiState},
	"EXEC":       {Clear: connectionWatchState | connectionMultiState},
	"DISCARD":    {Clear: connectionWatchState | connectionMultiState},
	"PSUBSCRIBE": {Set: connectionSubscribeState},
	"SUBSCRIBE":  {Set: connectionSubscribeState},
	"MONITOR":    {Set: connectionMonitorState},
}

func init() {
	for n, ci := range commandInfos {
		commandInfos[strings.ToLower(n)] = ci
	}
}

func lookupCommandInfo(commandName string) commandInfo {
	if ci, ok := commandInfos[commandName]; ok {
		return ci
	}
	return commandInfos[strings.ToUpper(commandName)]
}


================================================
FILE: redis/commandinfo_test.go
================================================
package redis

import "testing"

func TestLookupCommandInfo(t *testing.T) {
	for _, n := range []string{"watch", "WATCH", "wAtch"} {
		if lookupCommandInfo(n) == (commandInfo{}) {
			t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n)
		}
	}
}

func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
	for i := 0; i < b.N; i++ {
		for _, c := range names {
			lookupCommandInfo(c)
		}
	}
}

func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
	benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR")
}

func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {
	benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR")
}


================================================
FILE: redis/conn.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"bufio"
	"bytes"
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"net/url"
	"regexp"
	"strconv"
	"sync"
	"time"
)

var (
	_ ConnWithTimeout = (*conn)(nil)
)

// conn is the low-level implementation of Conn
type conn struct {
	// Shared
	mu      sync.Mutex
	pending int
	err     error
	conn    net.Conn

	// Read
	readTimeout time.Duration
	br          *bufio.Reader

	// Write
	writeTimeout time.Duration
	bw           *bufio.Writer

	// Scratch space for formatting argument length.
	// '*' or '$', length, "\r\n"
	lenScratch [32]byte

	// Scratch space for formatting integers and floats.
	numScratch [40]byte
}

// DialTimeout acts like Dial but takes timeouts for establishing the
// connection to the server, writing a command and reading a reply.
//
// Deprecated: Use Dial with options instead.
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
	return Dial(network, address,
		DialConnectTimeout(connectTimeout),
		DialReadTimeout(readTimeout),
		DialWriteTimeout(writeTimeout))
}

// DialOption specifies an option for dialing a Redis server.
type DialOption struct {
	f func(*dialOptions)
}

type dialOptions struct {
	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.
func DialReadTimeout(d time.Duration) DialOption {
	return DialOption{func(do *dialOptions) {
		do.readTimeout = d
	}}
}

// DialWriteTimeout specifies the timeout for writing a single command.
func DialWriteTimeout(d time.Duration) DialOption {
	return DialOption{func(do *dialOptions) {
		do.writeTimeout = d
	}}
}

// 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
	}}
}

// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server
// when no DialNetDial option is specified.
// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then
// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.
func DialKeepAlive(d time.Duration) DialOption {
	return DialOption{func(do *dialOptions) {
		do.dialer.KeepAlive = d
	}}
}

// DialNetDial specifies a custom dial function for creating TCP
// connections, otherwise a net.Dialer customized via the other options is used.
// DialNetDial overrides DialConnectTimeout and DialKeepAlive.
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
	return DialOption{func(do *dialOptions) {
		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
	}}
}

// DialDatabase specifies the database to select when dialing a connection.
func DialDatabase(db int) DialOption {
	return DialOption{func(do *dialOptions) {
		do.db = db
	}}
}

// DialPassword specifies the password to use when connecting to
// the Redis server.
func DialPassword(password string) DialOption {
	return DialOption{func(do *dialOptions) {
		do.password = password
	}}
}

// DialUsername specifies the username to use when connecting to
// the Redis server when Redis ACLs are used.
// A DialPassword must also be passed otherwise this option will have no effect.
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 {
	return DialOption{func(do *dialOptions) {
		do.clientName = name
	}}
}

// DialTLSConfig specifies the config to use when a TLS connection is dialed.
// Has no effect when not dialing a TLS connection.
func DialTLSConfig(c *tls.Config) DialOption {
	return DialOption{func(do *dialOptions) {
		do.tlsConfig = c
	}}
}

// DialTLSSkipVerify disables server name verification when connecting over
// TLS. Has no effect when not dialing a TLS connection.
func DialTLSSkipVerify(skip bool) DialOption {
	return DialOption{func(do *dialOptions) {
		do.skipVerify = skip
	}}
}

// DialUseTLS specifies whether TLS should be used when connecting to the
// server. This option is ignore by DialURL.
func DialUseTLS(useTLS bool) DialOption {
	return DialOption{func(do *dialOptions) {
		do.useTLS = useTLS
	}}
}

// 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.dialContext == nil {
		do.dialContext = do.dialer.DialContext
	}

	netConn, err := do.dialContext(ctx, network, address)
	if err != nil {
		return nil, err
	}

	if do.useTLS {
		var tlsConfig *tls.Config
		if do.tlsConfig == nil {
			tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify}
		} else {
			tlsConfig = do.tlsConfig.Clone()
		}
		if tlsConfig.ServerName == "" {
			host, _, err := net.SplitHostPort(address)
			if err != nil {
				netConn.Close()
				return nil, err
			}
			tlsConfig.ServerName = host
		}

		tlsConn := tls.Client(netConn, tlsConfig)
		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
	}

	c := &conn{
		conn:         netConn,
		bw:           bufio.NewWriter(netConn),
		br:           bufio.NewReader(netConn),
		readTimeout:  do.readTimeout,
		writeTimeout: do.writeTimeout,
	}

	if do.password != "" {
		authArgs := make([]interface{}, 0, 2)
		if do.username != "" {
			authArgs = append(authArgs, do.username)
		}
		authArgs = append(authArgs, do.password)
		if _, err := c.DoContext(ctx, "AUTH", authArgs...); err != nil {
			netConn.Close()
			return nil, err
		}
	}

	if do.clientName != "" {
		if _, err := c.DoContext(ctx, "CLIENT", "SETNAME", do.clientName); err != nil {
			netConn.Close()
			return nil, err
		}
	}

	if do.db != 0 {
		if _, err := c.DoContext(ctx, "SELECT", do.db); err != nil {
			netConn.Close()
			return nil, err
		}
	}

	return c, nil
}

var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)

// DialURL wraps DialURLContext using context.Background.
func DialURL(rawurl string, options ...DialOption) (Conn, error) {
	ctx := context.Background()

	return DialURLContext(ctx, rawurl, options...)
}

// DialURLContext connects to a Redis server at the given URL using the Redis
// URI scheme. URLs should follow the draft IANA specification for the
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
func DialURLContext(ctx context.Context, rawurl string, options ...DialOption) (Conn, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}

	switch u.Scheme {
	case "redis", "rediss", "valkey", "valkeys":
		// valid scheme
	default:
		return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
	}

	if u.Opaque != "" {
		return nil, fmt.Errorf("invalid redis URL, url is opaque: %s", rawurl)
	}

	// As per the IANA draft spec, the host defaults to localhost and
	// the port defaults to 6379.
	host, port, err := net.SplitHostPort(u.Host)
	if err != nil {
		// assume port is missing
		host = u.Host
		port = "6379"
	}
	if host == "" {
		host = "localhost"
	}
	address := net.JoinHostPort(host, port)

	if u.User != nil {
		password, isSet := u.User.Password()
		username := u.User.Username()
		if isSet {
			if username != "" {
				// ACL
				options = append(options, DialUsername(username), DialPassword(password))
			} else {
				// requirepass - user-info username:password with blank username
				options = append(options, DialPassword(password))
			}
		} else if username != "" {
			// requirepass - redis-cli compatibility which treats as single arg in user-info as a password
			options = append(options, DialPassword(username))
		}
	}

	match := pathDBRegexp.FindStringSubmatch(u.Path)
	if len(match) == 2 {
		db := 0
		if len(match[1]) > 0 {
			db, err = strconv.Atoi(match[1])
			if err != nil {
				return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
			}
		}
		if db != 0 {
			options = append(options, DialDatabase(db))
		}
	} else if u.Path != "" {
		return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
	}

	options = append(options, DialUseTLS(u.Scheme == "rediss" || u.Scheme == "valkeys"))

	return DialContext(ctx, "tcp", address, options...)
}

// NewConn returns a new Redigo connection for the given net connection.
func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
	return &conn{
		conn:         netConn,
		bw:           bufio.NewWriter(netConn),
		br:           bufio.NewReader(netConn),
		readTimeout:  readTimeout,
		writeTimeout: writeTimeout,
	}
}

func (c *conn) Close() error {
	c.mu.Lock()
	err := c.err
	if c.err == nil {
		c.err = errors.New("redigo: closed")
		err = c.conn.Close()
	}
	c.mu.Unlock()
	return err
}

func (c *conn) fatal(err error) error {
	c.mu.Lock()
	if c.err == nil {
		c.err = err
		// Close connection to force errors on subsequent calls and to unblock
		// other reader or writer.
		c.conn.Close()
	}
	c.mu.Unlock()
	return err
}

func (c *conn) Err() error {
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()
	return err
}

func (c *conn) writeLen(prefix byte, n int) error {
	c.lenScratch[len(c.lenScratch)-1] = '\n'
	c.lenScratch[len(c.lenScratch)-2] = '\r'
	i := len(c.lenScratch) - 3
	for {
		c.lenScratch[i] = byte('0' + n%10)
		i -= 1
		n = n / 10
		if n == 0 {
			break
		}
	}
	c.lenScratch[i] = prefix
	_, err := c.bw.Write(c.lenScratch[i:])
	return err
}

func (c *conn) writeString(s string) error {
	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 {
	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
}

func (c *conn) writeInt64(n int64) error {
	return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
}

func (c *conn) writeFloat64(n float64) error {
	return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
}

func (c *conn) writeCommand(cmd string, args []interface{}) error {
	if err := c.writeLen('*', 1+len(args)); err != nil {
		return err
	}
	if err := c.writeString(cmd); err != nil {
		return err
	}
	for _, arg := range args {
		if err := c.writeArg(arg, true); err != nil {
			return err
		}
	}
	return nil
}

func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
	switch arg := arg.(type) {
	case string:
		return c.writeString(arg)
	case []byte:
		return c.writeBytes(arg)
	case int:
		return c.writeInt64(int64(arg))
	case int64:
		return c.writeInt64(arg)
	case float64:
		return c.writeFloat64(arg)
	case bool:
		if arg {
			return c.writeString("1")
		} else {
			return c.writeString("0")
		}
	case nil:
		return c.writeString("")
	case Argument:
		if argumentTypeOK {
			return c.writeArg(arg.RedisArg(), false)
		}
		// See comment in default clause below.
		var buf bytes.Buffer
		fmt.Fprint(&buf, arg)
		return c.writeBytes(buf.Bytes())
	default:
		// This default clause is intended to handle builtin numeric types.
		// The function should return an error for other types, but this is not
		// done for compatibility with previous versions of the package.
		var buf bytes.Buffer
		fmt.Fprint(&buf, arg)
		return c.writeBytes(buf.Bytes())
	}
}

type protocolError string

func (pe protocolError) Error() string {
	return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
}

// readLine reads a line of input from the RESP stream.
func (c *conn) readLine() ([]byte, error) {
	// To avoid allocations, attempt to read the line using ReadSlice. This
	// call typically succeeds. The known case where the call fails is when
	// reading the output from the MONITOR command.
	p, err := c.br.ReadSlice('\n')
	if err == bufio.ErrBufferFull {
		// The line does not fit in the bufio.Reader's buffer. Fall back to
		// allocating a buffer for the line.
		buf := append([]byte{}, p...)
		for err == bufio.ErrBufferFull {
			p, err = c.br.ReadSlice('\n')
			buf = append(buf, p...)
		}
		p = buf
	}
	if err != nil {
		return nil, err
	}
	i := len(p) - 2
	if i < 0 || p[i] != '\r' {
		return nil, protocolError("bad response line terminator")
	}
	return p[:i], nil
}

// parseLen parses bulk string and array lengths.
func parseLen(p []byte) (int, error) {
	if len(p) == 0 {
		return -1, protocolError("malformed length")
	}

	if p[0] == '-' && len(p) == 2 && p[1] == '1' {
		// handle $-1 and $-1 null replies.
		return -1, nil
	}

	var n int
	for _, b := range p {
		n *= 10
		if b < '0' || b > '9' {
			return -1, protocolError("illegal bytes in length")
		}
		n += int(b - '0')
	}

	return n, nil
}

// parseInt parses an integer reply.
func parseInt(p []byte) (interface{}, error) {
	if len(p) == 0 {
		return 0, protocolError("malformed integer")
	}

	var negate bool
	if p[0] == '-' {
		negate = true
		p = p[1:]
		if len(p) == 0 {
			return 0, protocolError("malformed integer")
		}
	}

	var n int64
	for _, b := range p {
		n *= 10
		if b < '0' || b > '9' {
			return 0, protocolError("illegal bytes in length")
		}
		n += int64(b - '0')
	}

	if negate {
		n = -n
	}
	return n, nil
}

var (
	okReply   interface{} = "OK"
	pongReply interface{} = "PONG"
)

func (c *conn) readReply() (interface{}, error) {
	line, err := c.readLine()
	if err != nil {
		return nil, err
	}
	if len(line) == 0 {
		return nil, protocolError("short response line")
	}
	switch line[0] {
	case '+':
		switch string(line[1:]) {
		case "OK":
			// Avoid allocation for frequent "+OK" response.
			return okReply, nil
		case "PONG":
			// Avoid allocation in PING command benchmarks :)
			return pongReply, nil
		default:
			return string(line[1:]), nil
		}
	case '-':
		return Error(line[1:]), nil
	case ':':
		return parseInt(line[1:])
	case '$':
		n, err := parseLen(line[1:])
		if n < 0 || err != nil {
			return nil, err
		}
		p := make([]byte, n)
		_, err = io.ReadFull(c.br, p)
		if err != nil {
			return nil, err
		}
		if line, err := c.readLine(); err != nil {
			return nil, err
		} else if len(line) != 0 {
			return nil, protocolError("bad bulk string format")
		}
		return p, nil
	case '*':
		n, err := parseLen(line[1:])
		if n < 0 || err != nil {
			return nil, err
		}
		r := make([]interface{}, n)
		for i := range r {
			r[i], err = c.readReply()
			if err != nil {
				return nil, err
			}
		}
		return r, nil
	}
	return nil, protocolError("unexpected response line")
}

func (c *conn) Send(cmd string, args ...interface{}) error {
	c.mu.Lock()
	c.pending += 1
	c.mu.Unlock()
	if c.writeTimeout != 0 {
		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)
	}
	return nil
}

func (c *conn) Flush() error {
	if c.writeTimeout != 0 {
		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)
	}
	return nil
}

func (c *conn) Receive() (interface{}, error) {
	return c.ReceiveWithTimeout(c.readTimeout)
}

func (c *conn) ReceiveContext(ctx context.Context) (interface{}, error) {
	var realTimeout time.Duration
	if dl, ok := ctx.Deadline(); ok {
		timeout := time.Until(dl)
		if timeout >= c.readTimeout && c.readTimeout != 0 {
			realTimeout = c.readTimeout
		} else if timeout <= 0 {
			return nil, c.fatal(context.DeadlineExceeded)
		} else {
			realTimeout = timeout
		}
	} else {
		realTimeout = c.readTimeout
	}
	endch := make(chan struct{})
	var r interface{}
	var e error
	go func() {
		defer close(endch)

		r, e = c.ReceiveWithTimeout(realTimeout)
	}()
	select {
	case <-ctx.Done():
		return nil, c.fatal(ctx.Err())
	case <-endch:
		return r, e
	}
}

func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
	var deadline time.Time
	if timeout != 0 {
		deadline = time.Now().Add(timeout)
	}
	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)
	}
	// When using pub/sub, the number of receives can be greater than the
	// number of sends. To enable normal use of the connection after
	// unsubscribing from all channels, we do not decrement pending to a
	// negative value.
	//
	// The pending field is decremented after the reply is read to handle the
	// case where Receive is called before Send.
	c.mu.Lock()
	if c.pending > 0 {
		c.pending -= 1
	}
	c.mu.Unlock()
	if err, ok := reply.(Error); ok {
		return nil, err
	}
	return
}

func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
	return c.DoWithTimeout(c.readTimeout, cmd, args...)
}

func (c *conn) DoContext(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) {
	var realTimeout time.Duration
	if dl, ok := ctx.Deadline(); ok {
		timeout := time.Until(dl)
		if timeout >= c.readTimeout && c.readTimeout != 0 {
			realTimeout = c.readTimeout
		} else if timeout <= 0 {
			return nil, c.fatal(context.DeadlineExceeded)
		} else {
			realTimeout = timeout
		}
	} else {
		realTimeout = c.readTimeout
	}
	endch := make(chan struct{})
	var r interface{}
	var e error
	go func() {
		defer close(endch)

		r, e = c.DoWithTimeout(realTimeout, cmd, args...)
	}()
	select {
	case <-ctx.Done():
		return nil, c.fatal(ctx.Err())
	case <-endch:
		return r, e
	}
}

func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
	c.mu.Lock()
	pending := c.pending
	c.pending = 0
	c.mu.Unlock()

	if cmd == "" && pending == 0 {
		return nil, nil
	}

	if c.writeTimeout != 0 {
		if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {
			return nil, c.fatal(err)
		}
	}

	if cmd != "" {
		if err := c.writeCommand(cmd, args); err != nil {
			return nil, c.fatal(err)
		}
	}

	if err := c.bw.Flush(); err != nil {
		return nil, c.fatal(err)
	}

	var deadline time.Time
	if readTimeout != 0 {
		deadline = time.Now().Add(readTimeout)
	}
	if err := c.conn.SetReadDeadline(deadline); err != nil {
		return nil, c.fatal(err)
	}

	if cmd == "" {
		reply := make([]interface{}, pending)
		for i := range reply {
			r, e := c.readReply()
			if e != nil {
				return nil, c.fatal(e)
			}
			reply[i] = r
		}
		return reply, nil
	}

	var err error
	var reply interface{}
	for i := 0; i <= pending; i++ {
		var e error
		if reply, e = c.readReply(); e != nil {
			return nil, c.fatal(e)
		}
		if e, ok := reply.(Error); ok && err == nil {
			err = e
		}
	}
	return reply, err
}


================================================
FILE: redis/conn_test.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis_test

import (
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"math"
	"net"
	"sync"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/gomodule/redigo/redis"
	"github.com/stretchr/testify/require"
)

type testConn struct {
	io.Reader
	io.Writer
	readDeadline  time.Time
	writeDeadline time.Time
}

func (*testConn) Close() error         { return nil }
func (*testConn) LocalAddr() net.Addr  { return nil }
func (*testConn) RemoteAddr() net.Addr { return nil }
func (c *testConn) SetDeadline(t time.Time) error {
	c.readDeadline = t
	c.writeDeadline = t
	return nil
}
func (c *testConn) SetReadDeadline(t time.Time) error  { c.readDeadline = t; return nil }
func (c *testConn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t; return nil }

func dialTestConn(r string, w io.Writer) redis.DialOption {
	return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
		return &testConn{Reader: strings.NewReader(r), Writer: w}, nil
	})
}

type tlsTestConn struct {
	net.Conn
	done chan struct{}
}

func (c *tlsTestConn) Close() error {
	c.Conn.Close()
	<-c.done
	return nil
}

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)) // nolint: errcheck
		done := make(chan struct{})
		go func() {
			io.Copy(w, tlsServer) // nolint: errcheck
			close(done)
		}()
		return &tlsTestConn{Conn: client, done: done}, nil
	})
}

type durationArg struct {
	time.Duration
}

func (t durationArg) RedisArg() interface{} {
	return t.Seconds()
}

type recursiveArg int

func (v recursiveArg) RedisArg() interface{} { return v }

var writeTests = []struct {
	args     []interface{}
	expected string
}{
	{
		[]interface{}{"SET", "key", "value"},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
	},
	{
		[]interface{}{"SET", "key", "value"},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
	},
	{
		[]interface{}{"SET", "key", byte(100)},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
	},
	{
		[]interface{}{"SET", "key", 100},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
	},
	{
		[]interface{}{"SET", "key", int64(math.MinInt64)},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n",
	},
	{
		[]interface{}{"SET", "key", float64(1349673917.939762)},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n",
	},
	{
		[]interface{}{"SET", "key", ""},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
	},
	{
		[]interface{}{"SET", "key", nil},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
	},
	{
		[]interface{}{"SET", "key", durationArg{time.Minute}},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$2\r\n60\r\n",
	},
	{
		[]interface{}{"SET", "key", recursiveArg(123)},
		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n123\r\n",
	},
	{
		[]interface{}{"ECHO", true, false},
		"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
	},
}

func TestWrite(t *testing.T) {
	for _, tt := range writeTests {
		var buf bytes.Buffer
		c, _ := redis.Dial("", "", dialTestConn("", &buf))
		err := c.Send(tt.args[0].(string), tt.args[1:]...)
		if err != nil {
			t.Errorf("Send(%v) returned error %v", tt.args, err)
			continue
		}
		c.Flush()
		actual := buf.String()
		if actual != tt.expected {
			t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected)
		}
	}
}

var errorSentinel = &struct{}{}

var readTests = []struct {
	reply    string
	expected interface{}
}{
	{
		"+OK\r\n",
		"OK",
	},
	{
		"+PONG\r\n",
		"PONG",
	},
	{
		"+OK\n\n", // no \r
		errorSentinel,
	},
	{
		"@OK\r\n",
		errorSentinel,
	},
	{
		"$6\r\nfoobar\r\n",
		[]byte("foobar"),
	},
	{
		"$-1\r\n",
		nil,
	},
	{
		":1\r\n",
		int64(1),
	},
	{
		":-2\r\n",
		int64(-2),
	},
	{
		"*0\r\n",
		[]interface{}{},
	},
	{
		"*-1\r\n",
		nil,
	},
	{
		"*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n",
		[]interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")},
	},
	{
		"*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n",
		[]interface{}{[]byte("foo"), nil, []byte("bar")},
	},

	{
		// "" is not a valid length
		"$\r\nfoobar\r\n",
		errorSentinel,
	},
	{
		// "x" is not a valid length
		"$x\r\nfoobar\r\n",
		errorSentinel,
	},
	{
		// -2 is not a valid length
		"$-2\r\n",
		errorSentinel,
	},
	{
		// ""  is not a valid integer
		":\r\n",
		errorSentinel,
	},
	{
		// "x"  is not a valid integer
		":x\r\n",
		errorSentinel,
	},
	{
		// missing \r\n following value
		"$6\r\nfoobar",
		errorSentinel,
	},
	{
		// short value
		"$6\r\nxx",
		errorSentinel,
	},
	{
		// long value
		"$6\r\nfoobarx\r\n",
		errorSentinel,
	},
}

func TestRead(t *testing.T) {
	for _, tt := range readTests {
		c, _ := redis.Dial("", "", dialTestConn(tt.reply, nil))
		actual, err := c.Receive()
		if tt.expected == errorSentinel {
			if err == nil {
				t.Errorf("Receive(%q) did not return expected error", tt.reply)
			}
		} else {
			if err != nil {
				t.Errorf("Receive(%q) returned error %v", tt.reply, err)
				continue
			}
			if !reflect.DeepEqual(actual, tt.expected) {
				t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected)
			}
		}
	}
}

func TestReadString(t *testing.T) {
	// n is value of bufio.defaultBufSize
	const n = 4096

	// Test read string lengths near bufio.Reader buffer boundaries.
	testRanges := [][2]int{{0, 64}, {n - 64, n + 64}, {2*n - 64, 2*n + 64}}

	p := make([]byte, 2*n+64)
	for i := range p {
		p[i] = byte('a' + i%26)
	}
	s := string(p)

	for _, r := range testRanges {
		for i := r[0]; i < r[1]; i++ {
			c, _ := redis.Dial("", "", dialTestConn("+"+s[:i]+"\r\n", nil))
			actual, err := c.Receive()
			if err != nil || actual != s[:i] {
				t.Fatalf("Receive(string len %d) -> err=%v, equal=%v", i, err, actual != s[:i])
			}
		}
	}
}

var testCommands = []struct {
	args     []interface{}
	expected interface{}
}{
	{
		[]interface{}{"PING"},
		"PONG",
	},
	{
		[]interface{}{"SET", "foo", "bar"},
		"OK",
	},
	{
		[]interface{}{"GET", "foo"},
		[]byte("bar"),
	},
	{
		[]interface{}{"GET", "nokey"},
		nil,
	},
	{
		[]interface{}{"MGET", "nokey", "foo"},
		[]interface{}{nil, []byte("bar")},
	},
	{
		[]interface{}{"INCR", "mycounter"},
		int64(1),
	},
	{
		[]interface{}{"LPUSH", "mylist", "foo"},
		int64(1),
	},
	{
		[]interface{}{"LPUSH", "mylist", "bar"},
		int64(2),
	},
	{
		[]interface{}{"LRANGE", "mylist", 0, -1},
		[]interface{}{[]byte("bar"), []byte("foo")},
	},
	{
		[]interface{}{"MULTI"},
		"OK",
	},
	{
		[]interface{}{"LRANGE", "mylist", 0, -1},
		"QUEUED",
	},
	{
		[]interface{}{"PING"},
		"QUEUED",
	},
	{
		[]interface{}{"EXEC"},
		[]interface{}{
			[]interface{}{[]byte("bar"), []byte("foo")},
			"PONG",
		},
	},
}

func TestDoCommands(t *testing.T) {
	c, err := redis.DialDefaultServer()
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer c.Close()

	for _, cmd := range testCommands {
		actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...)
		if err != nil {
			t.Errorf("Do(%v) returned error %v", cmd.args, err)
			continue
		}
		if !reflect.DeepEqual(actual, cmd.expected) {
			t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected)
		}
	}
}

func TestPipelineCommands(t *testing.T) {
	c, err := redis.DialDefaultServer()
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer c.Close()

	for _, cmd := range testCommands {
		if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
			t.Fatalf("Send(%v) returned error %v", cmd.args, err)
		}
	}
	if err := c.Flush(); err != nil {
		t.Errorf("Flush() returned error %v", err)
	}
	for _, cmd := range testCommands {
		actual, err := c.Receive()
		if err != nil {
			t.Fatalf("Receive(%v) returned error %v", cmd.args, err)
		}
		if !reflect.DeepEqual(actual, cmd.expected) {
			t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
		}
	}
}

func TestBlankCommand(t *testing.T) {
	c, err := redis.DialDefaultServer()
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer c.Close()

	for _, cmd := range testCommands {
		if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
			t.Fatalf("Send(%v) returned error %v", cmd.args, err)
		}
	}
	reply, err := redis.Values(c.Do(""))
	if err != nil {
		t.Fatalf("Do() returned error %v", err)
	}
	if len(reply) != len(testCommands) {
		t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands))
	}
	for i, cmd := range testCommands {
		actual := reply[i]
		if !reflect.DeepEqual(actual, cmd.expected) {
			t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
		}
	}
}

func TestRecvBeforeSend(t *testing.T) {
	c, err := redis.DialDefaultServer()
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer c.Close()
	done := make(chan struct{})
	go func() {
		c.Receive() // nolint: errcheck
		close(done)
	}()
	time.Sleep(time.Millisecond)
	require.NoError(t, c.Send("PING"))
	require.NoError(t, c.Flush())
	<-done
	_, err = c.Do("")
	if err != nil {
		t.Fatalf("error=%v", err)
	}
}

func TestError(t *testing.T) {
	c, err := redis.DialDefaultServer()
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer c.Close()

	_, 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.")
	}
	if c.Err() != nil {
		t.Errorf("Conn has Err()=%v, expect nil", c.Err())
	}
	_, err = c.Do("SET", "key", "val")
	if err != nil {
		t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err)
	}
}

func TestReadTimeout(t *testing.T) {
	done := make(chan struct{})
	errs := make(chan error, 2)
	defer func() {
		close(done)
		for err := range errs {
			require.NoError(t, err)
		}
	}()

	var wg sync.WaitGroup
	defer func() {
		wg.Wait()
		close(errs)
	}()

	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("net.Listen returned %v", err)
	}
	defer l.Close()

	wg.Add(1)
	go func() {
		defer wg.Done()

		for {
			c, err := l.Accept()
			if err != nil {
				return
			}
			wg.Add(1)
			go func() {
				defer wg.Done()

				to := time.NewTimer(time.Second)
				defer to.Stop()
				select {
				case <-to.C:
				case <-done:
					return
				}
				_, err := c.Write([]byte("+OK\r\n"))
				errs <- err
				c.Close()
			}()
		}
	}()

	// Do

	c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
	if err != nil {
		t.Fatalf("redis.Dial returned %v", err)
	}
	defer c1.Close()

	_, err = c1.Do("PING")
	if err == nil {
		t.Fatalf("c1.Do() returned nil, expect error")
	}
	if c1.Err() == nil {
		t.Fatalf("c1.Err() = nil, expect error")
	}

	// Send/Flush/Receive

	c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
	if err != nil {
		t.Fatalf("redis.Dial returned %v", err)
	}
	defer c2.Close()

	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")
	}
	if c2.Err() == nil {
		t.Fatalf("c2.Err() = nil, expect error")
	}
}

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
}{
	{
		"localhost",
		"invalid redis URL scheme",
	},
	// The error message for invalid hosts is different in different
	// versions of Go, so just check that there is an error message.
	{
		"redis://weird url",
		"",
	},
	{
		"redis://foo:bar:baz",
		"",
	},
	{
		"http://www.google.com",
		"invalid redis URL scheme: http",
	},
	{
		"redis://localhost:6379/abc123",
		"invalid database: abc123",
	},
	{
		"redis:foo//localhost:6379",
		"invalid redis URL, url is opaque: redis:foo//localhost:6379",
	},
	{
		"valkey://localhost:6379/abc123",
		"invalid database: abc123",
	},
	{
		"valkeys://localhost:6379/abc123",
		"invalid database: abc123",
	},
	{
		"valkey:foo//localhost:6379",
		"invalid redis URL, url is opaque: valkey:foo//localhost:6379",
	},
	{
		"valkeys:foo//localhost:6379",
		"invalid redis URL, url is opaque: valkeys:foo//localhost:6379",
	},
}

func TestDialURLErrors(t *testing.T) {
	for _, d := range dialErrors {
		_, err := redis.DialURL(d.rawurl)
		if err == nil || !strings.Contains(err.Error(), d.expectedError) {
			t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError)
		}
	}
}

func TestDialURLPort(t *testing.T) {
	checkPort := func(network, address string) (net.Conn, error) {
		if address != "localhost:6379" {
			t.Errorf("DialURL did not set port to 6379 by default (got %v)", address)
		}
		return nil, nil
	}
	_, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort))
	if err != nil {
		t.Error("dial error:", err)
	}
}

func TestDialURLHost(t *testing.T) {
	checkHost := func(network, address string) (net.Conn, error) {
		if address != "localhost:6379" {
			t.Errorf("DialURL did not set host to localhost by default (got %v)", address)
		}
		return nil, nil
	}
	_, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost))
	if err != nil {
		t.Error("dial error:", err)
	}
}

var dialURLTests = []struct {
	description string
	url         string
	r           string
	w           string
}{
	{"password", "redis://:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
	{"password redis-cli compat", "redis://abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
	{"password db1", "redis://:abc123@localhost/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n"},
	{"password db1 redis-cli compat", "redis://abc123@localhost/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n"},
	{"password no host db0", "redis://:abc123@/0", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
	{"password no host db0 redis-cli compat", "redis://abc123@/0", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
	{"password no host db1", "redis://:abc123@/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n"},
	{"password no host db1 redis-cli compat", "redis://abc123@/1", "+OK\r\n+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n*2\r\n$6\r\nSELECT\r\n$1\r\n1\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", ""},
}

func TestDialURL(t *testing.T) {
	for _, tt := range dialURLTests {
		t.Run(tt.description, func(t *testing.T) {
			var buf bytes.Buffer
			// UseTLS should be ignored in all of these tests.
			_, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true))
			if err != nil {
				t.Errorf("%s dial error: %v, buf: %v", tt.description, err, buf.String())
				return
			}
			if w := buf.String(); w != tt.w {
				t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w)
			}
		})
	}
}

func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) {
	resp, err := c.Do("PING")
	if err != nil {
		t.Fatal("ping error:", err)
	}
	// Close connection to ensure that writes to buf are complete.
	c.Close()
	expected := "*1\r\n$4\r\nPING\r\n"
	actual := buf.String()
	if actual != expected {
		t.Errorf("commands = %q, want %q", actual, expected)
	}
	if resp != "PONG" {
		t.Errorf("resp = %v, want %v", resp, "PONG")
	}
}

const pingResponse = "+PONG\r\n"

func TestDialURLTLS(t *testing.T) {
	var buf bytes.Buffer
	c, err := redis.DialURL("rediss://example.com/",
		redis.DialTLSConfig(&clientTLSConfig),
		dialTestConnTLS(pingResponse, &buf))
	if err != nil {
		t.Fatal("dial error:", err)
	}
	checkPingPong(t, &buf, c)
}

func TestDialUseTLS(t *testing.T) {
	var buf bytes.Buffer
	c, err := redis.Dial("tcp", "example.com:6379",
		redis.DialTLSConfig(&clientTLSConfig),
		dialTestConnTLS(pingResponse, &buf),
		redis.DialUseTLS(true))
	if err != nil {
		t.Fatal("dial error:", err)
	}
	checkPingPong(t, &buf, c)
}

type blockedReader struct {
	ch chan struct{}
}

func (b blockedReader) Read(p []byte) (n int, err error) {
	<-b.ch
	return 0, io.EOF
}

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",
		dialTestConnTLS(pingResponse, &buf),
		redis.DialTLSSkipVerify(true),
		redis.DialUseTLS(true))
	if err != nil {
		t.Fatal("dial error:", err)
	}
	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",
		dialTestConn(pingResponse, &buf),
		redis.DialClientName("redis-connection"),
	)
	if err != nil {
		t.Fatal("dial error:", err)
	}
	expected := "*3\r\n$6\r\nCLIENT\r\n$7\r\nSETNAME\r\n$16\r\nredis-connection\r\n"
	if w := buf.String(); w != expected {
		t.Errorf("got %q, want %q", w, expected)
	}

	// testing against a real server
	connectionName := "test-connection"
	c, err := redis.DialDefaultServer(redis.DialClientName(connectionName))
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer c.Close()

	v, err := c.Do("CLIENT", "GETNAME")
	if err != nil {
		t.Fatalf("CLIENT GETNAME returned error %v", err)
	}

	vs, err := redis.String(v, nil)
	if err != nil {
		t.Fatalf("String(v) returned error %v", err)
	}

	if vs != connectionName {
		t.Fatalf("wrong connection name. Got '%s', expected '%s'", vs, connectionName)
	}
}

// Connect to local instance of Redis running on the default port.
func ExampleDial() {
	c, err := redis.Dial("tcp", ":6379")
	if err != nil {
		// handle error
	}
	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"))
	if err != nil {
		// handle connection error
	}
	defer c.Close()
}

// TestExecError tests handling of errors in a transaction. See
// http://redis.io/topics/transactions for information on how Redis handles
// errors in a transaction.
func TestExecError(t *testing.T) {
	c, err := redis.DialDefaultServer()
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer c.Close()

	// Execute commands that fail before EXEC is called.

	_, 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)
	}

	// Execute commands that fail after EXEC is called. The first command
	// returns an error.

	_, 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)
	}

	vs, err := redis.Values(v, nil)
	if err != nil {
		t.Fatalf("Values(v) returned error %v", err)
	}

	if len(vs) != 2 {
		t.Fatalf("len(vs) == %d, want 2", len(vs))
	}

	if _, ok := vs[0].(error); !ok {
		t.Fatalf("first result is type %T, expected error", vs[0])
	}

	if _, ok := vs[1].([]byte); !ok {
		t.Fatalf("second result is type %T, expected []byte", vs[1])
	}

	// Execute commands that fail after EXEC is called. The second command
	// returns an error.

	_, 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)
	}

	vs, err = redis.Values(v, nil)
	if err != nil {
		t.Fatalf("Values(v) returned error %v", err)
	}

	if len(vs) != 2 {
		t.Fatalf("len(vs) == %d, want 2", len(vs))
	}

	if _, ok := vs[0].([]byte); !ok {
		t.Fatalf("first result is type %T, expected []byte", vs[0])
	}

	if _, ok := vs[1].(error); !ok {
		t.Fatalf("second result is type %T, expected error", vs[2])
	}
}

func BenchmarkDoEmpty(b *testing.B) {
	b.StopTimer()
	c, err := redis.DialDefaultServer()
	if err != nil {
		b.Fatal(err)
	}
	defer c.Close()
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		if _, err := c.Do(""); err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkDoPing(b *testing.B) {
	b.StopTimer()
	c, err := redis.DialDefaultServer()
	if err != nil {
		b.Fatal(err)
	}
	defer c.Close()
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		if _, err := c.Do("PING"); err != nil {
			b.Fatal(err)
		}
	}
}

var clientTLSConfig, serverTLSConfig tls.Config

func init() {
	// The certificate and key for testing TLS dial options was created
	// using the command
	//
	//   go run GOROOT/src/crypto/tls/generate_cert.go  \
	//      --rsa-bits 1024 \
	//      --host 127.0.0.1,::1,example.com --ca \
	//      --start-date "Jan 1 00:00:00 1970" \
	//      --duration=1000000h
	//
	// where GOROOT is the value of GOROOT reported by go env.
	localhostCert := []byte(`
-----BEGIN CERTIFICATE-----
MIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+
LGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+
JaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw
DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+
ach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9
6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt
rrKgNsltzMk=
-----END CERTIFICATE-----`)

	localhostKey := []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi
bMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l
SoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB
AoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB
Eq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y
HenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm
KbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw
KjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa
m6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0
pDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci
Eo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH
diKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc
Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=
-----END RSA PRIVATE KEY-----`)

	cert, err := tls.X509KeyPair(localhostCert, localhostKey)
	if err != nil {
		panic(fmt.Sprintf("error creating key pair: %v", err))
	}
	serverTLSConfig.Certificates = []tls.Certificate{cert}

	certificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0])
	if err != nil {
		panic(fmt.Sprintf("error parsing x509 certificate: %v", err))
	}

	clientTLSConfig.RootCAs = x509.NewCertPool()
	clientTLSConfig.RootCAs.AddCert(certificate)
}

func TestWithTimeout(t *testing.T) {
	for _, recv := range []bool{true, false} {
		for _, defaultTimout := range []time.Duration{0, time.Minute} {
			var buf bytes.Buffer
			nc := &testConn{Reader: strings.NewReader("+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n"), Writer: &buf}
			c, _ := redis.Dial("", "", redis.DialReadTimeout(defaultTimout), redis.DialNetDial(func(network, addr string) (net.Conn, error) { return nc, nil }))
			for i := 0; i < 4; i++ {
				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 {
						_, err = c.Receive()
					} else {
						_, err = c.Do("PING")
					}
					require.NoError(t, err)
					if defaultTimout != 0 {
						maxDeadline = time.Now().Add(defaultTimout)
					}
				} else {
					timeout := 10 * time.Minute
					minDeadline = time.Now().Add(timeout)
					if recv {
						_, err = redis.ReceiveWithTimeout(c, timeout)
					} else {
						_, err = redis.DoWithTimeout(c, timeout, "PING")
					}
					require.NoError(t, err)
					maxDeadline = time.Now().Add(timeout)
				}

				// Expect set deadline in expected range.
				if nc.readDeadline.Before(minDeadline) || nc.readDeadline.After(maxDeadline) {
					t.Errorf("recv %v, %d: do deadline error: %v, %v, %v", recv, i, minDeadline, nc.readDeadline, maxDeadline)
				}
			}
		}
	}
}


================================================
FILE: redis/doc.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// Package redis is a client for the Redis database.
//
// The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more
// documentation about this package.
//
// Connections
//
// The Conn interface is the primary interface for working with Redis.
// Applications create connections by calling the Dial, DialWithTimeout or
// NewConn functions. In the future, functions will be added for creating
// sharded and other types of connections.
//
// The application must call the connection Close method when the application
// is done with the connection.
//
// Executing Commands
//
// The Conn interface has a generic method for executing Redis commands:
//
//  Do(commandName string, args ...interface{}) (reply interface{}, err error)
//
// The Redis command reference (http://redis.io/commands) lists the available
// commands. An example of using the Redis APPEND command is:
//
//  n, err := conn.Do("APPEND", "key", "value")
//
// The Do method converts command arguments to bulk strings for transmission
// to the server as follows:
//
//  Go Type                 Conversion
//  []byte                  Sent as is
//  string                  Sent as is
//  int, int64              strconv.FormatInt(v)
//  float64                 strconv.FormatFloat(v, 'g', -1, 64)
//  bool                    true -> "1", false -> "0"
//  nil                     ""
//  all other types         fmt.Fprint(w, v)
//
// Redis command reply types are represented using the following Go types:
//
//  Redis type              Go type
//  error                   redis.Error
//  integer                 int64
//  simple string           string
//  bulk string             []byte or nil if value not present.
//  array                   []interface{} or nil if value not present.
//
// Use type assertions or the reply helper functions to convert from
// interface{} to the specific Go type for the command result.
//
// Pipelining
//
// Connections support pipelining using the Send, Flush and Receive methods.
//
//  Send(commandName string, args ...interface{}) error
//  Flush() error
//  Receive() (reply interface{}, err error)
//
// Send writes the command to the connection's output buffer. Flush flushes the
// connection's output buffer to the server. Receive reads a single reply from
// the server. The following example shows a simple pipeline.
//
//  c.Send("SET", "foo", "bar")
//  c.Send("GET", "foo")
//  c.Flush()
//  c.Receive() // reply from SET
//  v, err = c.Receive() // reply from GET
//
// The Do method combines the functionality of the Send, Flush and Receive
// methods. The Do method starts by writing the command and flushing the output
// buffer. Next, the Do method receives all pending replies including the reply
// for the command just sent by Do. If any of the received replies is an error,
// then Do returns the error. If there are no errors, then Do returns the last
// reply. If the command argument to the Do method is "", then the Do method
// will flush the output buffer and receive pending replies without sending a
// command.
//
// Use the Send and Do methods to implement pipelined transactions.
//
//  c.Send("MULTI")
//  c.Send("INCR", "foo")
//  c.Send("INCR", "bar")
//  r, err := c.Do("EXEC")
//  fmt.Println(r) // prints [1, 1]
//
// Concurrency
//
// Connections support one concurrent caller to the Receive method and one
// concurrent caller to the Send and Flush methods. No other concurrency is
// supported including concurrent calls to the Do and Close methods.
//
// For full concurrent access to Redis, use the thread-safe Pool to get, use
// and release a connection from within a goroutine. Connections returned from
// a Pool have the concurrency restrictions described in the previous
// paragraph.
//
// Publish and Subscribe
//
// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
//
//  c.Send("SUBSCRIBE", "example")
//  c.Flush()
//  for {
//      reply, err := c.Receive()
//      if err != nil {
//          return err
//      }
//      // process pushed message
//  }
//
// The PubSubConn type wraps a Conn with convenience methods for implementing
// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
// send and flush a subscription management command. The receive method
// converts a pushed message to convenient types for use in a type switch.
//
//  psc := redis.PubSubConn{Conn: c}
//  psc.Subscribe("example")
//  for {
//      switch v := psc.Receive().(type) {
//      case redis.Message:
//          fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
//      case redis.Subscription:
//          fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
//      case error:
//          return v
//      }
//  }
//
// Reply Helpers
//
// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
// to a value of a specific type. To allow convenient wrapping of calls to the
// connection Do and Receive methods, the functions take a second argument of
// type error.  If the error is non-nil, then the helper function returns the
// error. If the error is nil, the function converts the reply to the specified
// type:
//
//  exists, err := redis.Bool(c.Do("EXISTS", "foo"))
//  if err != nil {
//      // handle error return from c.Do or type conversion error.
//  }
//
// The Scan function converts elements of a array reply to Go types:
//
//  var value1 int
//  var value2 string
//  reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
//  if err != nil {
//      // handle error
//  }
//   if _, err := redis.Scan(reply, &value1, &value2); err != nil {
//      // handle error
//  }
//
// Errors
//
// Connection methods return error replies from the server as type redis.Error.
//
// Call the connection Err() method to determine if the connection encountered
// non-recoverable error such as a network error or protocol parsing error. If
// Err() returns a non-nil value, then the connection is not usable and should
// be closed.
//
// Metrics
//
// Metrics regarding the connection pool and its connections are exposed via
// the pool.Stats() method, which can then be used with your preferred metrics
// library. 
// 
// The below code snippet demonstrates an example using the
// "github.com/prometheus/client_golang/prometheus" library.
// Note that all the metrics exposed are gauges, even though the wait count and
// wait duration are being used as counters - this is required to "set" the
// value rather than add/increment it.
//
//  func collectRedisPoolStats(pool *redis.Pool, maxConn float64) {
//    ticker := time.NewTicker(5 * time.Second)
//    redisPoolMax.Set(maxConn)
//
//    go func() {
//      defer ticker.Stop()
//
//      for range ticker.C {
//        stats := pool.Stats()
//
//        redisPoolOpen.Set(float64(stats.ActiveCount))
//        redisPoolInUse.Set(float64(stats.ActiveCount - stats.IdleCount))
//        redisPoolIdle.Set(float64(stats.IdleCount))
//        redisPoolWaitCount.Set(float64(stats.WaitCount))
//        redisPoolWaitDuration.Set(stats.WaitDuration.Seconds())
//      }
//    }()
//  }
package redis


================================================
FILE: redis/list_test.go
================================================
// Copyright 2018 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// +build go1.9

package redis

import "testing"

func TestPoolList(t *testing.T) {
	var idle idleList
	var a, b, c poolConn

	check := func(pcs ...*poolConn) {
		if idle.count != len(pcs) {
			t.Fatal("idle.count != len(pcs)")
		}
		if len(pcs) == 0 {
			if idle.front != nil {
				t.Fatalf("front not nil")
			}
			if idle.back != nil {
				t.Fatalf("back not nil")
			}
			return
		}
		if idle.front != pcs[0] {
			t.Fatal("front != pcs[0]")
		}
		if idle.back != pcs[len(pcs)-1] {
			t.Fatal("back != pcs[len(pcs)-1]")
		}
		if idle.front.prev != nil {
			t.Fatal("front.prev != nil")
		}
		if idle.back.next != nil {
			t.Fatal("back.next != nil")
		}
		for i := 1; i < len(pcs)-1; i++ {
			if pcs[i-1].next != pcs[i] {
				t.Fatal("pcs[i-1].next != pcs[i]")
			}
			if pcs[i+1].prev != pcs[i] {
				t.Fatal("pcs[i+1].prev != pcs[i]")
			}
		}
	}

	idle.pushFront(&c)
	check(&c)
	idle.pushFront(&b)
	check(&b, &c)
	idle.pushFront(&a)
	check(&a, &b, &c)
	idle.popFront()
	check(&b, &c)
	idle.popFront()
	check(&c)
	idle.popFront()
	check()

	idle.pushFront(&c)
	check(&c)
	idle.pushFront(&b)
	check(&b, &c)
	idle.pushFront(&a)
	check(&a, &b, &c)
	idle.popBack()
	check(&a, &b)
	idle.popBack()
	check(&a)
	idle.popBack()
	check()
}


================================================
FILE: redis/log.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"time"
)

var (
	_ ConnWithTimeout = (*loggingConn)(nil)
)

// NewLoggingConn returns a logging wrapper around a connection.
func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
	if prefix != "" {
		prefix = prefix + "."
	}
	return &loggingConn{conn, logger, prefix, nil}
}

//NewLoggingConnFilter returns a logging wrapper around a connection and a filter function.
func NewLoggingConnFilter(conn Conn, logger *log.Logger, prefix string, skip func(cmdName string) bool) Conn {
	if prefix != "" {
		prefix = prefix + "."
	}
	return &loggingConn{conn, logger, prefix, skip}
}

type loggingConn struct {
	Conn
	logger *log.Logger
	prefix string
	skip   func(cmdName string) bool
}

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()) // nolint: errcheck
	return err
}

func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
	const chop = 32
	switch v := v.(type) {
	case []byte:
		if len(v) > chop {
			fmt.Fprintf(buf, "%q...", v[:chop])
		} else {
			fmt.Fprintf(buf, "%q", v)
		}
	case string:
		if len(v) > chop {
			fmt.Fprintf(buf, "%q...", v[:chop])
		} else {
			fmt.Fprintf(buf, "%q", v)
		}
	case []interface{}:
		if len(v) == 0 {
			buf.WriteString("[]")
		} else {
			sep := "["
			fin := "]"
			if len(v) > chop {
				v = v[:chop]
				fin = "...]"
			}
			for _, vv := range v {
				buf.WriteString(sep)
				c.printValue(buf, vv)
				sep = ", "
			}
			buf.WriteString(fin)
		}
	default:
		fmt.Fprint(buf, v)
	}
}

func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
	if c.skip != nil && c.skip(commandName) {
		return
	}
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
	if method != "Receive" {
		buf.WriteString(commandName)
		for _, arg := range args {
			buf.WriteString(", ")
			c.printValue(&buf, arg)
		}
	}
	buf.WriteString(") -> (")
	if method != "Send" {
		c.printValue(&buf, reply)
		buf.WriteString(", ")
	}
	fmt.Fprintf(&buf, "%v)", err)
	c.logger.Output(3, buf.String()) // nolint: errcheck
}

func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
	reply, err := c.Conn.Do(commandName, args...)
	c.print("Do", commandName, args, reply, err)
	return reply, err
}

func (c *loggingConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) {
	reply, err := DoContext(c.Conn, ctx, commandName, args...)
	c.print("DoContext", commandName, args, reply, err)
	return reply, err
}

func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) {
	reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...)
	c.print("DoWithTimeout", commandName, args, reply, err)
	return reply, err
}

func (c *loggingConn) Send(commandName string, args ...interface{}) error {
	err := c.Conn.Send(commandName, args...)
	c.print("Send", commandName, args, nil, err)
	return err
}

func (c *loggingConn) Receive() (interface{}, error) {
	reply, err := c.Conn.Receive()
	c.print("Receive", "", nil, reply, err)
	return reply, err
}

func (c *loggingConn) ReceiveContext(ctx context.Context) (interface{}, error) {
	reply, err := ReceiveContext(c.Conn, ctx)
	c.print("ReceiveContext", "", nil, reply, err)
	return reply, err
}

func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {
	reply, err := ReceiveWithTimeout(c.Conn, timeout)
	c.print("ReceiveWithTimeout", "", nil, reply, err)
	return reply, err
}


================================================
FILE: redis/pool.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/sha1"
	"errors"
	"io"
	"strconv"
	"sync"
	"time"
)

var (
	_ ConnWithTimeout = (*activeConn)(nil)
	_ ConnWithTimeout = (*errorConn)(nil)
)

var nowFunc = time.Now // for testing

// ErrPoolExhausted is returned from a pool connection method (Do, Send,
// Receive, Flush, Err) when the maximum number of database connections in the
// pool has been reached.
var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")

var (
	errConnClosed = errors.New("redigo: connection closed")
)

// Pool maintains a pool of connections. The application calls the Get method
// to get a connection from the pool and the connection's Close method to
// return the connection's resources to the pool.
//
// The following example shows how to use a pool in a web application. The
// application creates a pool at application startup and makes it available to
// request handlers using a package level variable. The pool configuration used
// here is an example, not a recommendation.
//
//  func newPool(addr string) *redis.Pool {
//    return &redis.Pool{
//      MaxIdle: 3,
//      IdleTimeout: 240 * time.Second,
//      // Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial.
//      Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
//    }
//  }
//
//  var (
//    pool *redis.Pool
//    redisServer = flag.String("redisServer", ":6379", "")
//  )
//
//  func main() {
//    flag.Parse()
//    pool = newPool(*redisServer)
//    ...
//  }
//
// A request handler gets a connection from the pool and closes the connection
// when the handler is done:
//
//  func serveHome(w http.ResponseWriter, r *http.Request) {
//      conn := pool.Get()
//      defer conn.Close()
//      ...
//  }
//
// Use the Dial function to authenticate connections with the AUTH command or
// select a database with the SELECT command:
//
//  pool := &redis.Pool{
//    // Other pool configuration not shown in this example.
//    Dial: func () (redis.Conn, error) {
//      c, err := redis.Dial("tcp", server)
//      if err != nil {
//        return nil, err
//      }
//      if _, err := c.Do("AUTH", password); err != nil {
//        c.Close()
//        return nil, err
//      }
//      if _, err := c.Do("SELECT", db); err != nil {
//        c.Close()
//        return nil, err
//      }
//      return c, nil
//    },
//  }
//
// Use the TestOnBorrow function to check the health of an idle connection
// before the connection is returned to the application. This example PINGs
// connections that have been idle more than a minute:
//
//  pool := &redis.Pool{
//    // Other pool configuration not shown in this example.
//    TestOnBorrow: func(c redis.Conn, t time.Time) error {
//      if time.Since(t) < time.Minute {
//        return nil
//      }
//      _, err := c.Do("PING")
//      return err
//    },
//  }
//
type Pool struct {
	// Dial is an application supplied function for creating and configuring a
	// connection.
	//
	// The connection returned from Dial must not be in a special state
	// (subscribed to pubsub channel, transaction started, ...).
	Dial func() (Conn, error)

	// DialContext is an application supplied function for creating and configuring a
	// connection with the given context.
	//
	// The connection returned from DialContext must not be in a special state
	// (subscribed to pubsub channel, transaction started, ...).
	DialContext func(ctx context.Context) (Conn, error)

	// TestOnBorrow is an optional application supplied function for checking
	// the health of an idle connection before the connection is used again by
	// the application. Argument lastUsed is the time when the connection was returned
	// to the pool. If the function returns an error, then the connection is
	// closed.
	TestOnBorrow func(c Conn, lastUsed time.Time) error

	// TestOnBorrowContext is an optional application supplied function
	// for checking the health of an idle connection with the given context
	// before the connection is used again by the application.
	// Argument lastUsed is the time when the connection was returned
	// to the pool. If the function returns an error, then the connection is
	// closed.
	TestOnBorrowContext func(ctx context.Context, c Conn, lastUsed time.Time) error

	// Maximum number of idle connections in the pool.
	MaxIdle int

	// Maximum number of connections allocated by the pool at a given time.
	// When zero, there is no limit on the number of connections in the pool.
	MaxActive int

	// Close connections after remaining idle for this duration. If the value
	// is zero, then idle connections are not closed. Applications should set
	// the timeout to a value less than the server's timeout.
	IdleTimeout time.Duration

	// If Wait is true and the pool is at the MaxActive limit, then Get() waits
	// for a connection to be returned to the pool before returning.
	Wait bool

	// Close connections older than this duration. If the value is zero, then
	// the pool does not close connections based on age.
	MaxConnLifetime time.Duration

	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.
	waitDuration time.Duration // total time waited for new connections.
}

// NewPool creates a new pool.
//
// Deprecated: Initialize the Pool directly as shown in the example.
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
	return &Pool{Dial: newFn, MaxIdle: maxIdle}
}

// Get gets a connection. The application must close the returned connection.
// This method always returns a valid connection so that applications can defer
// error handling to the first use of the connection. If there is an error
// getting an underlying connection, then the connection Err, Do, Send, Flush
// and Receive methods return that error.
func (p *Pool) Get() Conn {
	// GetContext returns errorConn in the first argument when an error occurs.
	c, _ := p.GetContext(context.Background())
	return c
}

// GetContext gets a connection using the provided context.
//
// The provided Context must be non-nil. If the context expires before the
// connection is complete, an error is returned. Any expiration on the context
// will not affect the returned connection.
//
// If the function completes without error, then the application must close the
// returned connection.
func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
	// 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()

	if waited > 0 {
		p.waitCount++
		p.waitDuration += waited
	}

	// Prune stale connections at the back of the idle list.
	if p.IdleTimeout > 0 {
		n := p.idle.count
		for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ {
			pc := p.idle.back
			p.idle.popBack()
			p.mu.Unlock()
			pc.c.Close()
			p.mu.Lock()
			p.active--
		}
	}

	// Get idle connection from the front of idle list.
	for p.idle.front != nil {
		pc := p.idle.front
		p.idle.popFront()
		p.mu.Unlock()
		if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) &&
			(p.TestOnBorrowContext == nil || p.TestOnBorrowContext(ctx, pc.c, pc.t) == nil) &&
			(p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) {
			return &activeConn{p: p, pc: pc}, nil
		}
		pc.c.Close()
		p.mu.Lock()
		p.active--
	}

	// Check for pool closed before dialing a new connection.
	if p.closed {
		p.mu.Unlock()
		err := errors.New("redigo: get on closed pool")
		return errorConn{err}, err
	}

	// Handle limit for p.Wait == false.
	if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive {
		p.mu.Unlock()
		return errorConn{ErrPoolExhausted}, ErrPoolExhausted
	}

	p.active++
	p.mu.Unlock()
	c, err := p.dial(ctx)
	if err != nil {
		p.mu.Lock()
		p.active--
		if p.ch != nil && !p.closed {
			p.ch <- struct{}{}
		}
		p.mu.Unlock()
		return errorConn{err}, err
	}
	return &activeConn{p: p, pc: &poolConn{c: c, created: nowFunc()}}, nil
}

// PoolStats contains pool statistics.
type PoolStats struct {
	// ActiveCount is the number of connections in the pool. The count includes
	// idle connections and connections in use.
	ActiveCount int
	// IdleCount is the number of idle connections in the pool.
	IdleCount int

	// WaitCount is the total number of connections waited for.
	// This value is currently not guaranteed to be 100% accurate.
	WaitCount int64

	// WaitDuration is the total time blocked waiting for a new connection.
	// This value is currently not guaranteed to be 100% accurate.
	WaitDuration time.Duration
}

// Stats returns pool's statistics.
// Example usage with metric libraries can be found in the package documentation.
func (p *Pool) Stats() PoolStats {
	p.mu.Lock()
	stats := PoolStats{
		ActiveCount:  p.active,
		IdleCount:    p.idle.count,
		WaitCount:    p.waitCount,
		WaitDuration: p.waitDuration,
	}
	p.mu.Unlock()

	return stats
}

// ActiveCount returns the number of connections in the pool. The count
// includes idle connections and connections in use.
func (p *Pool) ActiveCount() int {
	p.mu.Lock()
	active := p.active
	p.mu.Unlock()
	return active
}

// IdleCount returns the number of idle connections in the pool.
func (p *Pool) IdleCount() int {
	p.mu.Lock()
	idle := p.idle.count
	p.mu.Unlock()
	return idle
}

// Close releases the resources used by the pool.
func (p *Pool) Close() error {
	p.mu.Lock()
	if p.closed {
		p.mu.Unlock()
		return nil
	}
	p.closed = true
	p.active -= p.idle.count
	pc := p.idle.front
	p.idle.count = 0
	p.idle.front, p.idle.back = nil, nil
	if p.ch != nil {
		close(p.ch)
	}
	p.mu.Unlock()
	for ; pc != nil; pc = pc.next {
		pc.c.Close()
	}
	return nil
}

func (p *Pool) lazyInit() {
	p.initOnce.Do(func() {
		p.ch = make(chan struct{}, p.MaxActive)
		if p.closed {
			close(p.ch)
		} else {
			for i := 0; i < p.MaxActive; i++ {
				p.ch <- struct{}{}
			}
		}
	})
}

// 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.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) {
	if p.DialContext != nil {
		return p.DialContext(ctx)
	}
	if p.Dial != nil {
		return p.Dial()
	}
	return nil, errors.New("redigo: must pass Dial or DialContext to pool")
}

func (p *Pool) put(pc *poolConn, forceClose bool) error {
	p.mu.Lock()
	if !p.closed && !forceClose {
		pc.t = nowFunc()
		p.idle.pushFront(pc)
		if p.idle.count > p.MaxIdle {
			pc = p.idle.back
			p.idle.popBack()
		} else {
			pc = nil
		}
	}

	if pc != nil {
		p.mu.Unlock()
		pc.c.Close()
		p.mu.Lock()
		p.active--
	}

	if p.ch != nil && !p.closed {
		p.ch <- struct{}{}
	}
	p.mu.Unlock()
	return nil
}

type activeConn struct {
	p     *Pool
	pc    *poolConn
	state int
}

var (
	sentinel     []byte
	sentinelOnce sync.Once
)

func initSentinel() {
	p := make([]byte, 64)
	if _, err := rand.Read(p); err == nil {
		sentinel = p
	} else {
		h := sha1.New()
		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) 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
	}
	ac.pc = nil

	if ac.state&connectionMultiState != 0 {
		err = pc.c.Send("DISCARD")
		ac.state &^= (connectionMultiState | connectionWatchState)
	} else if ac.state&connectionWatchState != 0 {
		err = pc.c.Send("UNWATCH")
		ac.state &^= connectionWatchState
	}
	if ac.state&connectionSubscribeState != 0 {
		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)
		err = ac.firstError(err,
			pc.c.Send("ECHO", sentinel),
			pc.c.Flush(),
		)
		for {
			p, err2 := pc.c.Receive()
			if err2 != nil {
				err = ac.firstError(err, err2)
				break
			}
			if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
				ac.state &^= connectionSubscribeState
				break
			}
		}
	}
	_, 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 {
	pc := ac.pc
	if pc == nil {
		return errConnClosed
	}
	return pc.c.Err()
}

func (ac *activeConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) {
	pc := ac.pc
	if pc == nil {
		return nil, errConnClosed
	}
	cwt, ok := pc.c.(ConnWithContext)
	if !ok {
		return nil, errContextNotSupported
	}
	ci := lookupCommandInfo(commandName)
	ac.state = (ac.state | ci.Set) &^ ci.Clear
	return cwt.DoContext(ctx, commandName, args...)
}

func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
	pc := ac.pc
	if pc == nil {
		return nil, errConnClosed
	}
	ci := lookupCommandInfo(commandName)
	ac.state = (ac.state | ci.Set) &^ ci.Clear
	return pc.c.Do(commandName, args...)
}

func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) {
	pc := ac.pc
	if pc == nil {
		return nil, errConnClosed
	}
	cwt, ok := pc.c.(ConnWithTimeout)
	if !ok {
		return nil, errTimeoutNotSupported
	}
	ci := lookupCommandInfo(commandName)
	ac.state = (ac.state | ci.Set) &^ ci.Clear
	return cwt.DoWithTimeout(timeout, commandName, args...)
}

func (ac *activeConn) Send(commandName string, args ...interface{}) error {
	pc := ac.pc
	if pc == nil {
		return errConnClosed
	}
	ci := lookupCommandInfo(commandName)
	ac.state = (ac.state | ci.Set) &^ ci.Clear
	return pc.c.Send(commandName, args...)
}

func (ac *activeConn) Flush() error {
	pc := ac.pc
	if pc == nil {
		return errConnClosed
	}
	return pc.c.Flush()
}

func (ac *activeConn) Receive() (reply interface{}, err error) {
	pc := ac.pc
	if pc == nil {
		return nil, errConnClosed
	}
	return pc.c.Receive()
}

func (ac *activeConn) ReceiveContext(ctx context.Context) (reply interface{}, err error) {
	pc := ac.pc
	if pc == nil {
		return nil, errConnClosed
	}
	cwt, ok := pc.c.(ConnWithContext)
	if !ok {
		return nil, errContextNotSupported
	}
	return cwt.ReceiveContext(ctx)
}

func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {
	pc := ac.pc
	if pc == nil {
		return nil, errConnClosed
	}
	cwt, ok := pc.c.(ConnWithTimeout)
	if !ok {
		return nil, errTimeoutNotSupported
	}
	return cwt.ReceiveWithTimeout(timeout)
}

type errorConn struct{ err error }

func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
func (ec errorConn) DoContext(context.Context, string, ...interface{}) (interface{}, error) {
	return nil, ec.err
}
func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) {
	return nil, ec.err
}
func (ec errorConn) Send(string, ...interface{}) error                     { return ec.err }
func (ec errorConn) Err() error                                            { return ec.err }
func (ec errorConn) Close() error                                          { return nil }
func (ec errorConn) Flush() error                                          { return ec.err }
func (ec errorConn) Receive() (interface{}, error)                         { return nil, ec.err }
func (ec errorConn) ReceiveContext(context.Context) (interface{}, error)   { return nil, ec.err }
func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err }

type idleList struct {
	count       int
	front, back *poolConn
}

type poolConn struct {
	c          Conn
	t          time.Time
	created    time.Time
	next, prev *poolConn
}

func (l *idleList) pushFront(pc *poolConn) {
	pc.next = l.front
	pc.prev = nil
	if l.count == 0 {
		l.back = pc
	} else {
		l.front.prev = pc
	}
	l.front = pc
	l.count++
}

func (l *idleList) popFront() {
	pc := l.front
	l.count--
	if l.count == 0 {
		l.front, l.back = nil, nil
	} else {
		pc.next.prev = nil
		l.front = pc.next
	}
	pc.next, pc.prev = nil, nil
}

func (l *idleList) popBack() {
	pc := l.back
	l.count--
	if l.count == 0 {
		l.front, l.back = nil, nil
	} else {
		pc.prev.next = nil
		l.back = pc.prev
	}
	pc.next, pc.prev = nil, nil
}


================================================
FILE: redis/pool_test.go
================================================
// Copyright 2011 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis_test

import (
	"context"
	"errors"
	"io"
	"net"
	"reflect"
	"sync"
	"testing"
	"time"

	"github.com/gomodule/redigo/redis"
	"github.com/stretchr/testify/require"
)

const (
	testGoRoutines = 10
)

type poolTestConn struct {
	d   *poolDialer
	err error
	redis.Conn
}

func (c *poolTestConn) Close() error {
	c.d.mu.Lock()
	c.d.open -= 1
	c.d.mu.Unlock()
	return c.Conn.Close()
}

func (c *poolTestConn) Err() error { return c.err }

func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) {
	return c.do(c.Conn.Do, commandName, args...)
}

func (c *poolTestConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) {
	cwc, ok := c.Conn.(redis.ConnWithContext)
	if !ok {
		return nil, errors.New("redis: connection does not support ConnWithContext")
	}
	return c.do(
		func(c string, a ...interface{}) (interface{}, error) {
			return cwc.DoContext(ctx, c, a...)
		},
		commandName, args)
}

func (c *poolTestConn) do(
	fn func(commandName string, args ...interface{}) (interface{}, error),
	commandName string, args ...interface{},
) (interface{}, error) {
	if commandName == "ERR" {
		c.err = args[0].(error)
		commandName = "PING"
	}
	if commandName != "" {
		c.d.commands = append(c.d.commands, commandName)
	}
	return fn(commandName, args...)
}

func (c *poolTestConn) Send(commandName string, args ...interface{}) error {
	c.d.commands = append(c.d.commands, commandName)
	return c.Conn.Send(commandName, args...)
}

func (c *poolTestConn) ReceiveContext(ctx context.Context) (reply interface{}, err error) {
	cwc, ok := c.Conn.(redis.ConnWithContext)
	if !ok {
		return nil, errors.New("redis: connection does not support ConnWithContext")
	}
	return cwc.ReceiveContext(ctx)
}

type poolDialer struct {
	mu       sync.Mutex
	t        *testing.T
	dialed   int
	open     int
	commands []string
	dialErr  error
}

func (d *poolDialer) dial() (redis.Conn, error) {
	return d.dialContext(context.Background())
}

func (d *poolDialer) dialContext(ctx context.Context) (redis.Conn, error) {
	d.mu.Lock()
	d.dialed += 1
	dialErr := d.dialErr
	d.mu.Unlock()
	if dialErr != nil {
		return nil, d.dialErr
	}
	c, err := redis.DialDefaultServerContext(ctx)
	if err != nil {
		return nil, err
	}
	d.mu.Lock()
	d.open += 1
	d.mu.Unlock()
	return &poolTestConn{d: d, Conn: c}, nil
}

func (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse int) {
	d.t.Helper()
	d.checkAll(message, p, dialed, open, inuse, 0, 0)
}

func (d *poolDialer) checkAll(message string, p *redis.Pool, dialed, open, inuse int, waitCountMax int64, waitDurationMax time.Duration) {
	d.t.Helper()

	d.mu.Lock()
	defer d.mu.Unlock()

	if d.dialed != dialed {
		d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed)
	}
	if d.open != open {
		d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
	}

	stats := p.Stats()

	if stats.ActiveCount != open {
		d.t.Errorf("%s: active=%d, want %d", message, stats.ActiveCount, open)
	}
	if stats.IdleCount != open-inuse {
		d.t.Errorf("%s: idle=%d, want %d", message, stats.IdleCount, open-inuse)
	}

	if stats.WaitCount > waitCountMax {
		d.t.Errorf("%s: unexpected wait=%d want at most %d", message, stats.WaitCount, waitCountMax)
	}

	if waitCountMax == 0 {
		if stats.WaitDuration != 0 {
			d.t.Errorf("%s: unexpected waitDuration=%v want %v", message, stats.WaitDuration, 0)
		}
		return
	}

	if stats.WaitDuration > waitDurationMax {
		d.t.Errorf("%s: unexpected waitDuration=%v want < %v", message, stats.WaitDuration, waitDurationMax)
	}
}

func TestPoolReuse(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle: 2,
		Dial:    d.dial,
	}

	for i := 0; i < 10; i++ {
		c1 := p.Get()
		_, err := c1.Do("PING")
		require.NoError(t, err)
		c2 := p.Get()
		_, 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)
	p.Close()
	d.check("after close", p, 2, 0, 0)
}

func TestPoolMaxIdle(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle: 2,
		Dial:    d.dial,
	}
	defer p.Close()

	for i := 0; i < 10; i++ {
		c1 := p.Get()
		_, err = c1.Do("PING")
		require.NoError(t, err)
		c2 := p.Get()
		_, err = c2.Do("PING")
		require.NoError(t, err)
		c3 := p.Get()
		_, 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()
	d.check("after close", p, 12, 0, 0)
}

func TestPoolError(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle: 2,
		Dial:    d.dial,
	}
	defer p.Close()

	c := p.Get()
	_, 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()
	_, err = c.Do("ERR", io.EOF)
	require.NoError(t, err)
	c.Close()

	d.check(".", p, 2, 0, 0)
}

func TestPoolClose(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle: 2,
		Dial:    d.dial,
	}
	defer p.Close()

	c1 := p.Get()
	_, err := c1.Do("PING")
	require.NoError(t, err)
	c2 := p.Get()
	_, err = c2.Do("PING")
	require.NoError(t, err)
	c3 := p.Get()
	_, err = c3.Do("PING")
	require.NoError(t, err)

	c1.Close()
	if _, err := c1.Do("PING"); err == nil {
		t.Errorf("expected error after connection closed")
	}

	c2.Close()
	c2.Close()

	p.Close()

	d.check("after pool close", p, 3, 1, 1)

	if _, err := c1.Do("PING"); err == nil {
		t.Errorf("expected error after connection and pool closed")
	}

	c3.Close()

	d.check("after conn close", p, 3, 0, 0)

	c1 = p.Get()
	if _, err := c1.Do("PING"); err == nil {
		t.Errorf("expected error after pool closed")
	}
}

func TestPoolClosedConn(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:     2,
		IdleTimeout: 300 * time.Second,
		Dial:        d.dial,
	}
	defer p.Close()
	c := p.Get()
	if c.Err() != nil {
		t.Fatal("get failed")
	}
	c.Close()
	if err := c.Err(); err == nil {
		t.Fatal("Err on closed connection did not return error")
	}
	if _, err := c.Do("PING"); err == nil {
		t.Fatal("Do on closed connection did not return error")
	}
	if err := c.Send("PING"); err == nil {
		t.Fatal("Send on closed connection did not return error")
	}
	if err := c.Flush(); err == nil {
		t.Fatal("Flush on closed connection did not return error")
	}
	if _, err := c.Receive(); err == nil {
		t.Fatal("Receive on closed connection did not return error")
	}
}

func TestPoolIdleTimeout(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:     2,
		IdleTimeout: 300 * time.Second,
		Dial:        d.dial,
	}
	defer p.Close()

	now := time.Now()
	redis.SetNowFunc(func() time.Time { return now })
	defer redis.SetNowFunc(time.Now)

	c := p.Get()
	_, err := c.Do("PING")
	require.NoError(t, err)
	c.Close()

	d.check("1", p, 1, 1, 0)

	now = now.Add(p.IdleTimeout + 1)

	c = p.Get()
	_, err = c.Do("PING")
	require.NoError(t, err)
	c.Close()

	d.check("2", p, 2, 1, 0)
}

func TestPoolMaxLifetime(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:         2,
		MaxConnLifetime: 300 * time.Second,
		Dial:            d.dial,
	}
	defer p.Close()

	now := time.Now()
	redis.SetNowFunc(func() time.Time { return now })
	defer redis.SetNowFunc(time.Now)

	c := p.Get()
	_, err := c.Do("PING")
	require.NoError(t, err)
	c.Close()

	d.check("1", p, 1, 1, 0)

	now = now.Add(p.MaxConnLifetime + 1)

	c = p.Get()
	_, err = c.Do("PING")
	require.NoError(t, err)
	c.Close()

	d.check("2", p, 2, 1, 0)
}

func TestPoolConcurrenSendReceive(t *testing.T) {
	p := &redis.Pool{
		Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() },
	}
	defer p.Close()

	c := p.Get()
	done := make(chan error, 1)
	go func() {
		_, err := c.Receive()
		done <- err
	}()
	require.NoError(t, c.Send("PING"))
	c.Flush()
	err := <-done
	if err != nil {
		t.Fatalf("Receive() returned error %v", err)
	}
	_, err = c.Do("")
	if err != nil {
		t.Fatalf("Do() returned error %v", err)
	}
	c.Close()
}

func TestPoolBorrowCheck(t *testing.T) {
	pingN := func(ctx context.Context, p *redis.Pool, n int) {
		for i := 0; i < n; i++ {
			func() {
				c, err := p.GetContext(ctx)
				require.NoError(t, err)
				defer func() {
					require.NoError(t, c.Close())
				}()
				_, err = redis.DoContext(c, ctx, "PING")
				require.NoError(t, err)
			}()
		}
	}

	checkLastUsedTimes := func(lastUsedTimes []time.Time, startTime, endTime time.Time, wantLen int) {
		require.Len(t, lastUsedTimes, wantLen)
		for i, lastUsed := range lastUsedTimes {
			if i == 0 {
				require.True(t, lastUsed.After(startTime))
			} else {
				require.True(t, lastUsed.After(lastUsedTimes[i-1]))
			}
			require.True(t, lastUsed.Before(endTime))
		}
	}

	t.Run("TestOnBorrow-error", func(t *testing.T) {
		d := poolDialer{t: t}
		p := &redis.Pool{
			MaxIdle:      2,
			DialContext:  d.dialContext,
			TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") },
		}
		defer p.Close()
		pingN(context.Background(), p, 10)
		d.check("1", p, 10, 1, 0)
	})

	t.Run("TestOnBorrow-nil-error", func(t *testing.T) {
		d := poolDialer{t: t}
		var borrowErrs []error
		var lastUsedTimes []time.Time
		p := &redis.Pool{
			MaxIdle:     2,
			DialContext: d.dialContext,
			TestOnBorrow: func(c redis.Conn, lastUsed time.Time) error {
				lastUsedTimes = append(lastUsedTimes, lastUsed)
				_, err := c.Do("PING")
				if err != nil {
					borrowErrs = append(borrowErrs, err)
				}
				return err
			},
		}
		defer p.Close()

		startTime := time.Now()
		pingN(context.Background(), p, 10)
		endTime := time.Now()

		require.Empty(t, borrowErrs)
		checkLastUsedTimes(lastUsedTimes, startTime, endTime, 9)
		d.check("1", p, 1, 1, 0)
	})

	t.Run("TestOnBorrowContext-error", func(t *testing.T) {
		d := poolDialer{t: t}
		p := &redis.Pool{
			MaxIdle:             2,
			DialContext:         d.dialContext,
			TestOnBorrowContext: func(context.Context, redis.Conn, time.Time) error { return redis.Error("BLAH") },
		}
		defer p.Close()
		pingN(context.Background(), p, 10)
		d.check("1", p, 10, 1, 0)
	})

	t.Run("TestOnBorrowContext-nil-error", func(t *testing.T) {
		d := poolDialer{t: t}
		var borrowErrs []error
		var lastUsedTimes []time.Time
		p := &redis.Pool{
			MaxIdle:     2,
			DialContext: d.dialContext,
			TestOnBorrowContext: func(ctx context.Context, c redis.Conn, lastUsed time.Time) error {
				lastUsedTimes = append(lastUsedTimes, lastUsed)
				_, err := redis.DoContext(c, ctx, "PING")
				if err != nil {
					borrowErrs = append(borrowErrs, err)
				}
				return err
			},
		}
		defer p.Close()

		startTime := time.Now()
		pingN(context.Background(), p, 10)
		endTime := time.Now()

		require.Empty(t, borrowErrs)
		checkLastUsedTimes(lastUsedTimes, startTime, endTime, 9)
		d.check("1", p, 1, 1, 0)
	})

	t.Run("TestOnBorrowContext-context.Canceled", func(t *testing.T) {
		d := poolDialer{t: t}
		var borrowErrs []error
		p := &redis.Pool{
			MaxIdle:     2,
			DialContext: d.dialContext,
			TestOnBorrowContext: func(ctx context.Context, c redis.Conn, _ time.Time) error {
				_, err := redis.DoContext(c, ctx, "PING")
				if err != nil {
					borrowErrs = append(borrowErrs, err)
				}
				return err
			},
		}
		defer p.Close()

		ctx, ctxCancel := context.WithCancel(context.Background())
		defer ctxCancel()

		pingN(ctx, p, 2)
		d.check("1", p, 1, 1, 0)
		require.Empty(t, borrowErrs)

		ctxCancel()

		_, err := p.GetContext(ctx)
		require.ErrorIs(t, err, context.Canceled)

		d.check("1", p, 2, 0, 0)
		require.Len(t, borrowErrs, 1)
		require.ErrorIs(t, borrowErrs[0], context.Canceled)
	})
}

func TestPoolMaxActive(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   2,
		MaxActive: 2,
		Dial:      d.dial,
	}
	defer p.Close()

	c1 := p.Get()
	_, err := c1.Do("PING")
	require.NoError(t, err)
	c2 := p.Get()
	_, err = c2.Do("PING")
	require.NoError(t, err)

	d.check("1", p, 2, 2, 2)

	c3 := p.Get()
	if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted {
		t.Errorf("expected pool exhausted")
	}

	c3.Close()
	d.check("2", p, 2, 2, 2)
	c2.Close()
	d.check("3", p, 2, 2, 1)

	c3 = p.Get()
	if _, err := c3.Do("PING"); err != nil {
		t.Errorf("expected good channel, err=%v", err)
	}
	c3.Close()

	d.check("4", p, 2, 2, 1)
}

func TestPoolWaitStats(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		Wait:      true,
		MaxIdle:   2,
		MaxActive: 2,
		Dial:      d.dial,
	}
	defer p.Close()

	c1 := p.Get()
	_, err := c1.Do("PING")
	require.NoError(t, err)
	c2 := p.Get()
	_, err = c2.Do("PING")
	require.NoError(t, err)

	d.checkAll("1", p, 2, 2, 2, 0, 0)

	start := time.Now()
	go func() {
		time.Sleep(time.Millisecond * 100)
		c1.Close()
	}()

	c3 := p.Get()
	d.checkAll("2", p, 2, 2, 2, 1, time.Since(start))

	if _, err := c3.Do("PING"); err != nil {
		t.Errorf("expected good channel, err=%v", err)
	}
}

func TestPoolMonitorCleanup(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   2,
		MaxActive: 2,
		Dial:      d.dial,
	}
	defer p.Close()

	c := p.Get()
	require.NoError(t, c.Send("MONITOR"))
	c.Close()

	d.check("", p, 1, 0, 0)
}

func TestPoolPubSubCleanup(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   2,
		MaxActive: 2,
		Dial:      d.dial,
	}
	defer p.Close()

	c := p.Get()
	require.NoError(t, c.Send("SUBSCRIBE", "x"))
	c.Close()

	want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
	if !reflect.DeepEqual(d.commands, want) {
		t.Errorf("got commands %v, want %v", d.commands, want)
	}
	d.commands = nil

	c = p.Get()
	require.NoError(t, c.Send("PSUBSCRIBE", "x*"))
	c.Close()

	want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
	if !reflect.DeepEqual(d.commands, want) {
		t.Errorf("got commands %v, want %v", d.commands, want)
	}
	d.commands = nil
}

func TestPoolTransactionCleanup(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   2,
		MaxActive: 2,
		Dial:      d.dial,
	}
	defer p.Close()

	c := p.Get()
	_, err := c.Do("WATCH", "key")
	require.NoError(t, err)
	_, err = c.Do("PING")
	require.NoError(t, err)
	c.Close()

	want := []string{"WATCH", "PING", "UNWATCH"}
	if !reflect.DeepEqual(d.commands, want) {
		t.Errorf("got commands %v, want %v", d.commands, want)
	}
	d.commands = nil

	c = p.Get()
	_, 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"}
	if !reflect.DeepEqual(d.commands, want) {
		t.Errorf("got commands %v, want %v", d.commands, want)
	}
	d.commands = nil

	c = p.Get()
	_, 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"}
	if !reflect.DeepEqual(d.commands, want) {
		t.Errorf("got commands %v, want %v", d.commands, want)
	}
	d.commands = nil

	c = p.Get()
	_, 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"}
	if !reflect.DeepEqual(d.commands, want) {
		t.Errorf("got commands %v, want %v", d.commands, want)
	}
	d.commands = nil

	c = p.Get()
	_, 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"}
	if !reflect.DeepEqual(d.commands, want) {
		t.Errorf("got commands %v, want %v", d.commands, want)
	}
	d.commands = nil
}

func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error {
	errs := make(chan error, testGoRoutines)
	for i := 0; i < cap(errs); i++ {
		go func() {
			c := p.Get()
			_, err := c.Do(cmd, args...)
			c.Close()
			errs <- err
		}()
	}

	return errs
}

func TestWaitPool(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   1,
		MaxActive: 1,
		Dial:      d.dial,
		Wait:      true,
	}
	defer p.Close()

	c := p.Get()
	start := time.Now()
	errs := startGoroutines(p, "PING")
	d.check("before close", p, 1, 1, 1)
	c.Close()
	timeout := time.After(2 * time.Second)
	for i := 0; i < cap(errs); i++ {
		select {
		case err := <-errs:
			if err != nil {
				t.Fatal(err)
			}
		case <-timeout:
			t.Fatalf("timeout waiting for blocked goroutine %d", i)
		}
	}
	d.checkAll("done", p, 1, 1, 0, testGoRoutines, time.Since(start)*testGoRoutines)
}

func TestWaitPoolClose(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   1,
		MaxActive: 1,
		Dial:      d.dial,
		Wait:      true,
	}
	defer p.Close()

	c := p.Get()
	if _, err := c.Do("PING"); err != nil {
		t.Fatal(err)
	}
	start := time.Now()
	errs := startGoroutines(p, "PING")
	d.check("before close", p, 1, 1, 1)
	p.Close()
	timeout := time.After(2 * time.Second)
	for i := 0; i < cap(errs); i++ {
		select {
		case err := <-errs:
			switch err {
			case nil:
				t.Fatal("blocked goroutine did not get error")
			case redis.ErrPoolExhausted:
				t.Fatal("blocked goroutine got pool exhausted error")
			}
		case <-timeout:
			t.Fatal("timeout waiting for blocked goroutine")
		}
	}
	c.Close()
	d.checkAll("done", p, 1, 0, 0, testGoRoutines, time.Since(start)*testGoRoutines)
}

func TestWaitPoolCommandError(t *testing.T) {
	testErr := errors.New("test")
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   1,
		MaxActive: 1,
		Dial:      d.dial,
		Wait:      true,
	}
	defer p.Close()

	c := p.Get()
	start := time.Now()
	errs := startGoroutines(p, "ERR", testErr)
	d.check("before close", p, 1, 1, 1)
	c.Close()
	timeout := time.After(2 * time.Second)
	for i := 0; i < cap(errs); i++ {
		select {
		case err := <-errs:
			if err != nil {
				t.Fatal(err)
			}
		case <-timeout:
			t.Fatalf("timeout waiting for blocked goroutine %d", i)
		}
	}
	d.checkAll("done", p, cap(errs), 0, 0, testGoRoutines, time.Since(start)*testGoRoutines)
}

func TestWaitPoolDialError(t *testing.T) {
	testErr := errors.New("test")
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   1,
		MaxActive: 1,
		Dial:      d.dial,
		Wait:      true,
	}
	defer p.Close()

	c := p.Get()
	start := time.Now()
	errs := startGoroutines(p, "ERR", testErr)
	d.check("before close", p, 1, 1, 1)

	d.dialErr = errors.New("dial")
	c.Close()

	nilCount := 0
	errCount := 0
	timeout := time.After(2 * time.Second)
	for i := 0; i < cap(errs); i++ {
		select {
		case err := <-errs:
			switch err {
			case nil:
				nilCount++
			case d.dialErr:
				errCount++
			default:
				t.Fatalf("expected dial error or nil, got %v", err)
			}
		case <-timeout:
			t.Fatalf("timeout waiting for blocked goroutine %d", i)
		}
	}
	if nilCount != 1 {
		t.Errorf("expected one nil error, got %d", nilCount)
	}
	if errCount != cap(errs)-1 {
		t.Errorf("expected %d dial errors, got %d", cap(errs)-1, errCount)
	}
	d.checkAll("done", p, cap(errs), 0, 0, testGoRoutines, time.Since(start)*testGoRoutines)
}

// Borrowing requires us to iterate over the idle connections, unlock the pool,
// and perform a blocking operation to check the connection still works. If
// TestOnBorrow fails, we must reacquire the lock and continue iteration. This
// test ensures that iteration will work correctly if multiple threads are
// iterating simultaneously.
func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) {
	const count = 100

	// First we'll Create a pool where the pilfering of idle connections fails.
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   count,
		MaxActive: count,
		Dial:      d.dial,
		TestOnBorrow: func(redis.Conn, time.Time) error {
			return errors.New("No way back into the real world.")
		},
	}
	defer p.Close()

	// Fill the pool with idle connections.
	conns := make([]redis.Conn, count)
	for i := range conns {
		conns[i] = p.Get()
	}
	for i := range conns {
		conns[i].Close()
	}

	// Spawn a bunch of goroutines to thrash the pool.
	var wg sync.WaitGroup
	wg.Add(count)
	for i := 0; i < count; i++ {
		go func() {
			c := p.Get()
			if c.Err() != nil {
				t.Errorf("pool get failed: %v", c.Err())
			}
			c.Close()
			wg.Done()
		}()
	}
	wg.Wait()
	if d.dialed != count*2 {
		t.Errorf("Expected %d dials, got %d", count*2, d.dialed)
	}
}

func BenchmarkPoolGet(b *testing.B) {
	b.StopTimer()
	p := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2}
	c := p.Get()
	if err := c.Err(); err != nil {
		b.Fatal(err)
	}
	c.Close()
	defer p.Close()
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		c = p.Get()
		c.Close()
	}
}

func BenchmarkPoolGetErr(b *testing.B) {
	b.StopTimer()
	p := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2}
	c := p.Get()
	if err := c.Err(); err != nil {
		b.Fatal(err)
	}
	c.Close()
	defer p.Close()
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		c = p.Get()
		if err := c.Err(); err != nil {
			b.Fatal(err)
		}
		c.Close()
	}
}

func BenchmarkPoolGetPing(b *testing.B) {
	b.StopTimer()
	p := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2}
	c := p.Get()
	if err := c.Err(); err != nil {
		b.Fatal(err)
	}
	c.Close()
	defer p.Close()
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		c = p.Get()
		if _, err := c.Do("PING"); err != nil {
			b.Fatal(err)
		}
		c.Close()
	}
}

func TestWaitPoolGetContext(t *testing.T) {
	d := poolDialer{t: t}
	p := &redis.Pool{
		MaxIdle:   1,
		MaxActive: 1,
		Dial:      d.dial,
		Wait:      true,
	}
	defer p.Close()
	c, err := p.GetContext(context.Background())
	if err != nil {
		t.Fatalf("GetContext returned %v", err)
	}
	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{
		MaxIdle:     1,
		MaxActive:   1,
		DialContext: d.dialContext,
		Wait:        true,
	}
	defer p.Close()
	c, err := p.GetContext(context.Background())
	if err != nil {
		t.Fatalf("GetContext returned %v", err)
	}
	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{
		MaxIdle:   1,
		MaxActive: 1,
		Dial:      d.dial,
		Wait:      true,
	}
	p.Close()
	_, err := p.GetContext(context.Background())
	if err == nil {
		t.Fatal("expected error")
	}
}

func TestWaitPoolGetCanceledContext(t *testing.T) {
	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)
		}
	})
}


================================================
FILE: redis/pubsub.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"context"
	"errors"
	"time"
)

// Subscription represents a subscribe or unsubscribe notification.
type Subscription struct {
	// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
	Kind string

	// The channel that was changed.
	Channel string

	// The current number of subscriptions for connection.
	Count int
}

// Message represents a message notification.
type Message struct {
	// The originating channel.
	Channel string

	// The matched pattern, if any
	Pattern string

	// The message data.
	Data []byte
}

// Pong represents a pubsub pong notification.
type Pong struct {
	Data string
}

// PubSubConn wraps a Conn with convenience methods for subscribers.
type PubSubConn struct {
	Conn Conn
}

// Close closes the connection.
func (c PubSubConn) Close() error {
	return c.Conn.Close()
}

// Subscribe subscribes the connection to the specified channels.
func (c PubSubConn) Subscribe(channel ...interface{}) error {
	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 {
	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 {
	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 {
	if err := c.Conn.Send("PUNSUBSCRIBE", channel...); err != nil {
		return err
	}
	return c.Conn.Flush()
}

// Ping sends a PING to the server with the specified data.
//
// The connection must be subscribed to at least one channel or pattern when
// calling this method.
func (c PubSubConn) Ping(data string) error {
	if err := c.Conn.Send("PING", data); err != nil {
		return err
	}
	return c.Conn.Flush()
}

// Receive returns a pushed message as a Subscription, Message, Pong or error.
// The return value is intended to be used directly in a type switch as
// illustrated in the PubSubConn example.
func (c PubSubConn) Receive() interface{} {
	return c.receiveInternal(c.Conn.Receive())
}

// ReceiveWithTimeout is like Receive, but it allows the application to
// override the connection's default timeout.
func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} {
	return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout))
}

// ReceiveContext is like Receive, but it allows termination of the receive
// via a Context. If the call returns due to closure of the context's Done
// channel the underlying Conn will have been closed.
func (c PubSubConn) ReceiveContext(ctx context.Context) interface{} {
	return c.receiveInternal(ReceiveContext(c.Conn, ctx))
}

func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} {
	reply, err := Values(replyArg, errArg)
	if err != nil {
		return err
	}

	var kind string
	reply, err = Scan(reply, &kind)
	if err != nil {
		return err
	}

	switch kind {
	case "message":
		var m Message
		if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
			return err
		}
		return m
	case "pmessage":
		var m Message
		if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil {
			return err
		}
		return m
	case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
		s := Subscription{Kind: kind}
		if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
			return err
		}
		return s
	case "pong":
		var p Pong
		if _, err := Scan(reply, &p.Data); err != nil {
			return err
		}
		return p
	}
	return errors.New("redigo: unknown pubsub notification")
}


================================================
FILE: redis/pubsub_example_test.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// +build go1.7

package redis_test

import (
	"context"
	"fmt"
	"time"

	"github.com/gomodule/redigo/redis"
)

// listenPubSubChannels listens for messages on Redis pubsub channels. The
// onStart function is called after the channels are subscribed. The onMessage
// function is called for each message.
func listenPubSubChannels(ctx context.Context, redisServerAddr string,
	onStart func() error,
	onMessage func(channel string, data []byte) error,
	channels ...string) error {
	// A ping is set to the server with this period to test for the health of
	// the connection and server.
	const healthCheckPeriod = time.Minute

	c, err := redis.Dial("tcp", redisServerAddr,
		// Read timeout on server should be greater than ping period.
		redis.DialReadTimeout(healthCheckPeriod+10*time.Second),
		redis.DialWriteTimeout(10*time.Second))
	if err != nil {
		return err
	}
	defer c.Close()

	psc := redis.PubSubConn{Conn: c}

	if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil {
		return err
	}

	done := make(chan error, 1)

	// Start a goroutine to receive notifications from the server.
	go func() {
		for {
			switch n := psc.Receive().(type) {
			case error:
				done <- n
				return
			case redis.Message:
				if err := onMessage(n.Channel, n.Data); err != nil {
					done <- err
					return
				}
			case redis.Subscription:
				switch n.Count {
				case len(channels):
					// Notify application when all channels are subscribed.
					if err := onStart(); err != nil {
						done <- err
						return
					}
				case 0:
					// Return from the goroutine when all channels are unsubscribed.
					done <- nil
					return
				}
			}
		}
	}()

	ticker := time.NewTicker(healthCheckPeriod)
	defer ticker.Stop()
loop:
	for {
		select {
		case <-ticker.C:
			// Send ping to test health of connection and server. If
			// corresponding pong is not received, then receive on the
			// connection will timeout and the receive goroutine will exit.
			if err = psc.Ping(""); err != nil {
				break loop
			}
		case <-ctx.Done():
			break loop
		case err := <-done:
			// Return error from the receive goroutine.
			return err
		}
	}

	// Signal the receiving goroutine to exit by unsubscribing from all channels.
	if err := psc.Unsubscribe(); err != nil {
		return err
	}

	// Wait for goroutine to complete.
	return <-done
}

func publish() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	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
// health checks.
func ExamplePubSubConn() {
	redisServerAddr, err := serverAddr()
	if err != nil {
		fmt.Println(err)
		return
	}

	ctx, cancel := context.WithCancel(context.Background())

	err = listenPubSubChannels(ctx,
		redisServerAddr,
		func() error {
			// The start callback is a good place to backfill missed
			// notifications. For the purpose of this example, a goroutine is
			// started to send notifications.
			go publish()
			return nil
		},
		func(channel string, message []byte) error {
			fmt.Printf("channel: %s, message: %s\n", channel, message)

			// For the purpose of this example, cancel the listener's context
			// after receiving last message sent by publish().
			if string(message) == "goodbye" {
				cancel()
			}
			return nil
		},
		"c1", "c2")

	if err != nil {
		fmt.Println(err)
		return
	}

	// Output:
	// channel: c1, message: hello
	// channel: c2, message: world
	// channel: c1, message: goodbye
}


================================================
FILE: redis/pubsub_test.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis_test

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/gomodule/redigo/redis"
	"github.com/stretchr/testify/require"
)

func TestPushed(t *testing.T) {
	pc, err := redis.DialDefaultServer()
	require.NoError(t, err)
	defer pc.Close()

	sc, err := redis.DialDefaultServer()
	require.NoError(t, err)
	defer sc.Close()

	c := redis.PubSubConn{Conn: sc}

	require.NoError(t, c.Subscribe("c1"))
	require.Equal(t, redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}, c.Receive())
	require.NoError(t, c.Subscribe("c2"))
	require.Equal(t, redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2}, c.Receive())
	require.NoError(t, c.PSubscribe("p1"))
	require.Equal(t, redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3}, c.Receive())
	require.NoError(t, c.PSubscribe("p2"))
	require.Equal(t, redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4}, c.Receive())
	require.NoError(t, c.PUnsubscribe())

	// Response can return in any order.
	v := c.Receive()
	require.IsType(t, redis.Subscription{}, v)
	u := v.(redis.Subscription)
	expected1 := redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3}
	expected2 := redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2}
	if u.Channel == "p2" {
		// Order reversed.
		expected1.Channel = "p2"
		expected2.Channel = "p1"
	}
	require.Equal(t, expected1, u)
	require.Equal(t, expected2, c.Receive())

	_, err = pc.Do("PUBLISH", "c1", "hello")
	require.NoError(t, err)
	require.Equal(t, redis.Message{Channel: "c1", Data: []byte("hello")}, c.Receive())

	require.NoError(t, c.Ping("hello"))
	require.Equal(t, redis.Pong{Data: "hello"}, c.Receive())

	require.NoError(t, c.Conn.Send("PING"))
	c.Conn.Flush()
	require.Equal(t, redis.Pong{}, c.Receive())

	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)
	}
}

func TestPubSubReceiveContext(t *testing.T) {
	sc, err := redis.DialDefaultServer()
	if err != nil {
		t.Fatalf("error connection to database, %v", err)
	}
	defer sc.Close()

	c := redis.PubSubConn{Conn: sc}

	require.NoError(t, c.Subscribe("c1"))
	require.Equal(t, redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}, c.Receive())

	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	got := c.ReceiveContext(ctx)
	if err, ok := got.(error); !ok {
		t.Errorf("recv w/canceled expected Canceled got non-error type %T", got)
	} else if !errors.Is(err, context.Canceled) {
		t.Errorf("recv w/canceled expected Canceled got %v", err)
	}
}


================================================
FILE: redis/redis.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"context"
	"errors"
	"time"
)

// Error represents an error returned in a command reply.
type Error string

func (err Error) Error() string { return string(err) }

// Conn represents a connection to a Redis server.
type Conn interface {
	// Close closes the connection.
	Close() error

	// Err returns a non-nil value when the connection is not usable.
	Err() error

	// Do sends a command to the server and returns the received reply.
	// This function will use the timeout which was set when the connection is created
	Do(commandName string, args ...interface{}) (reply interface{}, err error)

	// Send writes the command to the client's output buffer.
	Send(commandName string, args ...interface{}) error

	// Flush flushes the output buffer to the Redis server.
	Flush() error

	// Receive receives a single reply from the Redis server
	Receive() (reply interface{}, err error)
}

// Argument is the interface implemented by an object which wants to control how
// the object is converted to Redis bulk strings.
type Argument interface {
	// RedisArg returns a value to be encoded as a bulk string per the
	// conversions listed in the section 'Executing Commands'.
	// Implementations should typically return a []byte or string.
	RedisArg() interface{}
}

// Scanner is implemented by an object which wants to control its value is
// interpreted when read from Redis.
type Scanner interface {
	// RedisScan assigns a value from a Redis value. The argument src is one of
	// the reply types listed in the section `Executing Commands`.
	//
	// An error should be returned if the value cannot be stored without
	// loss of information.
	RedisScan(src interface{}) error
}

// ConnWithTimeout is an optional interface that allows the caller to override
// a connection's default read timeout. This interface is useful for executing
// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the
// server.
//
// A connection's default read timeout is set with the DialReadTimeout dial
// option. Applications should rely on the default timeout for commands that do
// not block at the server.
//
// All of the Conn implementations in this package satisfy the ConnWithTimeout
// interface.
//
// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify
// use of this interface.
type ConnWithTimeout interface {
	Conn

	// DoWithTimeout sends a command to the server and returns the received reply.
	// The timeout overrides the readtimeout set when dialing the connection.
	DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error)

	// ReceiveWithTimeout receives a single reply from the Redis server.
	// The timeout overrides the readtimeout set when dialing the connection.
	ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error)
}

// ConnWithContext is an optional interface that allows the caller to control the command's life with context.
type ConnWithContext interface {
	Conn

	// DoContext sends a command to server and returns the received reply.
	// min(ctx,DialReadTimeout()) will be used as the deadline.
	// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.
	// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded).
	// ctx timeout return err context.DeadlineExceeded.
	// ctx canceled return err context.Canceled.
	DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error)

	// ReceiveContext receives a single reply from the Redis server.
	// min(ctx,DialReadTimeout()) will be used as the deadline.
	// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.
	// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded).
	// ctx timeout return err context.DeadlineExceeded.
	// ctx canceled return err context.Canceled.
	ReceiveContext(ctx context.Context) (reply interface{}, err error)
}

var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout")
var errContextNotSupported = errors.New("redis: connection does not support ConnWithContext")

// DoContext sends a command to server and returns the received reply.
// min(ctx,DialReadTimeout()) will be used as the deadline.
// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.
// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded).
// ctx timeout return err context.DeadlineExceeded.
// ctx canceled return err context.Canceled.
func DoContext(c Conn, ctx context.Context, cmd string, args ...interface{}) (interface{}, error) {
	cwt, ok := c.(ConnWithContext)
	if !ok {
		return nil, errContextNotSupported
	}
	return cwt.DoContext(ctx, cmd, args...)
}

// DoWithTimeout executes a Redis command with the specified read timeout. If
// the connection does not satisfy the ConnWithTimeout interface, then an error
// is returned.
func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
	cwt, ok := c.(ConnWithTimeout)
	if !ok {
		return nil, errTimeoutNotSupported
	}
	return cwt.DoWithTimeout(timeout, cmd, args...)
}

// ReceiveContext receives a single reply from the Redis server.
// min(ctx,DialReadTimeout()) will be used as the deadline.
// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.
// DialReadTimeout() timeout return err can be checked by strings.Contains(e.Error(), "io/timeout").
// ctx timeout return err context.DeadlineExceeded.
// ctx canceled return err context.Canceled.
func ReceiveContext(c Conn, ctx context.Context) (interface{}, error) {
	cwt, ok := c.(ConnWithContext)
	if !ok {
		return nil, errContextNotSupported
	}
	return cwt.ReceiveContext(ctx)
}

// ReceiveWithTimeout receives a reply with the specified read timeout. If the
// connection does not satisfy the ConnWithTimeout interface, then an error is
// returned.
func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) {
	cwt, ok := c.(ConnWithTimeout)
	if !ok {
		return nil, errTimeoutNotSupported
	}
	return cwt.ReceiveWithTimeout(timeout)
}

// SlowLog represents a redis SlowLog
type SlowLog struct {
	// ID is a unique progressive identifier for every slow log entry.
	ID int64

	// Time is the unix timestamp at which the logged command was processed.
	Time time.Time

	// ExecutationTime is the amount of time needed for the command execution.
	ExecutionTime time.Duration

	// Args is the command name and arguments
	Args []string

	// ClientAddr is the client IP address (4.0 only).
	ClientAddr string

	// ClientName is the name set via the CLIENT SETNAME command (4.0 only).
	ClientName string
}

// Latency represents a redis LATENCY LATEST.
type Latency struct {
	// Name of the latest latency spike event.
	Name string

	// Time of the latest latency spike for the event.
	Time time.Time

	// Latest is the latest recorded latency for the named event.
	Latest time.Duration

	// Max is the maximum latency for the named event.
	Max time.Duration
}

// LatencyHistory represents a redis LATENCY HISTORY.
type LatencyHistory struct {
	// Time is the unix timestamp at which the event was processed.
	Time time.Time

	// ExecutationTime is the amount of time needed for the command execution.
	ExecutionTime time.Duration
}


================================================
FILE: redis/redis_test.go
================================================
// Copyright 2017 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis_test

import (
	"context"
	"testing"
	"time"

	"github.com/gomodule/redigo/redis"
)

type timeoutTestConn int

func (tc timeoutTestConn) Do(string, ...interface{}) (interface{}, error) {
	return time.Duration(-1), nil
}

func (tc timeoutTestConn) DoWithTimeout(timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
	return timeout, nil
}

func (tc timeoutTestConn) Receive() (interface{}, error) {
	return time.Duration(-1), nil
}

func (tc timeoutTestConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {
	return timeout, nil
}

func (tc timeoutTestConn) Send(string, ...interface{}) error { return nil }
func (tc timeoutTestConn) Err() error                        { return nil }
func (tc timeoutTestConn) Close() error                      { return nil }
func (tc timeoutTestConn) Flush() error                      { return nil }

func testTimeout(t *testing.T, c redis.Conn) {
	r, err := c.Do("PING")
	if r != time.Duration(-1) || err != nil {
		t.Errorf("Do() = %v, %v, want %v, %v", r, err, time.Duration(-1), nil)
	}
	r, err = redis.DoWithTimeout(c, time.Minute, "PING")
	if r != time.Minute || err != nil {
		t.Errorf("DoWithTimeout() = %v, %v, want %v, %v", r, err, time.Minute, nil)
	}
	r, err = c.Receive()
	if r != time.Duration(-1) || err != nil {
		t.Errorf("Receive() = %v, %v, want %v, %v", r, err, time.Duration(-1), nil)
	}
	r, err = redis.ReceiveWithTimeout(c, time.Minute)
	if r != time.Minute || err != nil {
		t.Errorf("ReceiveWithTimeout() = %v, %v, want %v, %v", r, err, time.Minute, nil)
	}
}

func TestConnTimeout(t *testing.T) {
	testTimeout(t, timeoutTestConn(0))
}

func TestPoolConnTimeout(t *testing.T) {
	p := &redis.Pool{Dial: func() (redis.Conn, error) { return timeoutTestConn(0), nil }}
	testTimeout(t, p.Get())
}

type contextDeadTestConn int

func (cc contextDeadTestConn) Do(string, ...interface{}) (interface{}, error) {
	return -1, nil
}
func (cc contextDeadTestConn) DoContext(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) {
	return 1, nil
}
func (cc contextDeadTestConn) Receive() (interface{}, error) {
	return -1, nil
}
func (cc contextDeadTestConn) ReceiveContext(ctx context.Context) (interface{}, error) {
	return 1, nil
}
func (cc contextDeadTestConn) Send(string, ...interface{}) error { return nil }
func (cc contextDeadTestConn) Err() error                        { return nil }
func (cc contextDeadTestConn) Close() error                      { return nil }
func (cc contextDeadTestConn) Flush() error                      { return nil }

func testcontext(t *testing.T, c redis.Conn) {
	r, e := c.Do("PING")
	if r != -1 || e != nil {
		t.Errorf("Do() = %v, %v, want %v, %v", r, e, -1, nil)
	}
	ctx, f := context.WithTimeout(context.Background(), time.Minute)
	defer f()
	r, e = redis.DoContext(c, ctx, "PING")
	if r != 1 || e != nil {
		t.Errorf("DoContext() = %v, %v, want %v, %v", r, e, 1, nil)
	}
	r, e = c.Receive()
	if r != -1 || e != nil {
		t.Errorf("Receive() = %v, %v, want %v, %v", r, e, -1, nil)
	}
	r, e = redis.ReceiveContext(c, ctx)
	if r != 1 || e != nil {
		t.Errorf("ReceiveContext() = %v, %v, want %v, %v", r, e, 1, nil)
	}
}

func TestConnContext(t *testing.T) {
	testcontext(t, contextDeadTestConn(0))
}

func TestPoolConnContext(t *testing.T) {
	p := redis.Pool{Dial: func() (redis.Conn, error) { return contextDeadTestConn(0), nil }}
	testcontext(t, p.Get())
}


================================================
FILE: redis/reflect.go
================================================
package redis

import (
	"reflect"
	"runtime"
)

// methodName returns the name of the calling method,
// assumed to be two stack frames above.
func methodName() string {
	pc, _, _, _ := runtime.Caller(2)
	f := runtime.FuncForPC(pc)
	if f == nil {
		return "unknown method"
	}
	return f.Name()
}

// mustBe panics if f's kind is not expected.
func mustBe(v reflect.Value, expected reflect.Kind) {
	if v.Kind() != expected {
		panic(&reflect.ValueError{Method: methodName(), Kind: v.Kind()})
	}
}

// fieldByIndexCreate returns the nested field corresponding
// to index creating elements that are nil when stepping through.
// It panics if v is not a struct.
func fieldByIndexCreate(v reflect.Value, index []int) reflect.Value {
	if len(index) == 1 {
		return v.Field(index[0])
	}

	mustBe(v, reflect.Struct)
	for i, x := range index {
		if i > 0 {
			if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
				if v.IsNil() {
					v.Set(reflect.New(v.Type().Elem()))
				}
				v = v.Elem()
			}
		}
		v = v.Field(x)
	}

	return v
}


================================================
FILE: redis/reflect_go117.go
================================================
//go:build !go1.18
// +build !go1.18

package redis

import (
	"errors"
	"reflect"
)

// fieldByIndexErr returns the nested field corresponding to index.
// It returns an error if evaluation requires stepping through a nil
// pointer, but panics if it must step through a field that
// is not a struct.
func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) {
	if len(index) == 1 {
		return v.Field(index[0]), nil
	}

	mustBe(v, reflect.Struct)
	for i, x := range index {
		if i > 0 {
			if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
				if v.IsNil() {
					return reflect.Value{}, errors.New("reflect: indirection through nil pointer to embedded struct field " + v.Type().Elem().Name())
				}
				v = v.Elem()
			}
		}
		v = v.Field(x)
	}

	return v, nil
}


================================================
FILE: redis/reflect_go118.go
================================================
//go:build go1.18
// +build go1.18

package redis

import (
	"reflect"
)

// fieldByIndexErr returns the nested field corresponding to index.
// It returns an error if evaluation requires stepping through a nil
// pointer, but panics if it must step through a field that
// is not a struct.
func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) {
	return v.FieldByIndexErr(index)
}


================================================
FILE: redis/reply.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"errors"
	"fmt"
	"strconv"
	"time"
)

// ErrNil indicates that a reply value is nil.
var ErrNil = errors.New("redigo: nil returned")

// Int is a helper that converts a command reply to an integer. If err is not
// equal to nil, then Int returns 0, err. Otherwise, Int converts the
// reply to an int as follows:
//
//  Reply type    Result
//  integer       int(reply), nil
//  bulk string   parsed reply, nil
//  nil           0, ErrNil
//  other         0, error
func Int(reply interface{}, err error) (int, error) {
	if err != nil {
		return 0, err
	}
	switch reply := reply.(type) {
	case int64:
		x := int(reply)
		if int64(x) != reply {
			return 0, strconv.ErrRange
		}
		return x, nil
	case []byte:
		n, err := strconv.ParseInt(string(reply), 10, 0)
		return int(n), err
	case nil:
		return 0, ErrNil
	case Error:
		return 0, reply
	}
	return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
}

// Int64 is a helper that converts a command reply to 64 bit integer. If err is
// not equal to nil, then Int64 returns 0, err. Otherwise, Int64 converts the
// reply to an int64 as follows:
//
//  Reply type    Result
//  integer       reply, nil
//  bulk string   parsed reply, nil
//  nil           0, ErrNil
//  other         0, error
func Int64(reply interface{}, err error) (int64, error) {
	if err != nil {
		return 0, err
	}
	switch reply := reply.(type) {
	case int64:
		return reply, nil
	case []byte:
		n, err := strconv.ParseInt(string(reply), 10, 64)
		return n, err
	case nil:
		return 0, ErrNil
	case Error:
		return 0, reply
	}
	return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
}

func errNegativeInt(v int64) error {
	return fmt.Errorf("redigo: unexpected negative value %v for Uint64", v)
}

// Uint64 is a helper that converts a command reply to 64 bit unsigned integer.
// If err is not equal to nil, then Uint64 returns 0, err. Otherwise, Uint64 converts the
// reply to an uint64 as follows:
//
//  Reply type    Result
//  +integer      reply, nil
//  bulk string   parsed reply, nil
//  nil           0, ErrNil
//  other         0, error
func Uint64(reply interface{}, err error) (uint64, error) {
	if err != nil {
		return 0, err
	}
	switch reply := reply.(type) {
	case int64:
		if reply < 0 {
			return 0, errNegativeInt(reply)
		}
		return uint64(reply), nil
	case []byte:
		n, err := strconv.ParseUint(string(reply), 10, 64)
		return n, err
	case nil:
		return 0, ErrNil
	case Error:
		return 0, reply
	}
	return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
}

// 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 a float64 as follows:
//
//  Reply type    Result
//  bulk string   parsed reply, nil
//  nil           0, ErrNil
//  other         0, error
func Float64(reply interface{}, err error) (float64, error) {
	if err != nil {
		return 0, err
	}
	switch reply := reply.(type) {
	case []byte:
		n, err := strconv.ParseFloat(string(reply), 64)
		return n, err
	case nil:
		return 0, ErrNil
	case Error:
		return 0, reply
	}
	return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
}

// String is a helper that converts a command reply to a string. If err is not
// equal to nil, then String returns "", err. Otherwise String converts the
// reply to a string as follows:
//
//  Reply type      Result
//  bulk string     string(reply), nil
//  simple string   reply, nil
//  nil             "",  ErrNil
//  other           "",  error
func String(reply interface{}, err error) (string, error) {
	if err != nil {
		return "", err
	}
	switch reply := reply.(type) {
	case []byte:
		return string(reply), nil
	case string:
		return reply, nil
	case nil:
		return "", ErrNil
	case Error:
		return "", reply
	}
	return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
}

// Bytes is a helper that converts a command reply to a slice of bytes. If err
// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
// the reply to a slice of bytes as follows:
//
//  Reply type      Result
//  bulk string     reply, nil
//  simple string   []byte(reply), nil
//  nil             nil, ErrNil
//  other           nil, error
func Bytes(reply interface{}, err error) ([]byte, error) {
	if err != nil {
		return nil, err
	}
	switch reply := reply.(type) {
	case []byte:
		return reply, nil
	case string:
		return []byte(reply), nil
	case nil:
		return nil, ErrNil
	case Error:
		return nil, reply
	}
	return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
}

// Bool is a helper that converts a command reply to a boolean. If err is not
// equal to nil, then Bool returns false, err. Otherwise Bool converts the
// reply to boolean as follows:
//
//  Reply type      Result
//  integer         value != 0, nil
//  bulk string     strconv.ParseBool(reply)
//  nil             false, ErrNil
//  other           false, error
func Bool(reply interface{}, err error) (bool, error) {
	if err != nil {
		return false, err
	}
	switch reply := reply.(type) {
	case int64:
		return reply != 0, nil
	case []byte:
		return strconv.ParseBool(string(reply))
	case nil:
		return false, ErrNil
	case Error:
		return false, reply
	}
	return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
}

// MultiBulk is a helper that converts an array command reply to a []interface{}.
//
// Deprecated: Use Values instead.
func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }

// Values is a helper that converts an array command reply to a []interface{}.
// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
// converts the reply as follows:
//
//  Reply type      Result
//  array           reply, nil
//  nil             nil, ErrNil
//  other           nil, error
func Values(reply interface{}, err error) ([]interface{}, error) {
	if err != nil {
		return nil, err
	}
	switch reply := reply.(type) {
	case []interface{}:
		return reply, nil
	case nil:
		return nil, ErrNil
	case Error:
		return nil, reply
	}
	return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
}

func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
	if err != nil {
		return err
	}
	switch reply := reply.(type) {
	case []interface{}:
		makeSlice(len(reply))
		for i := range reply {
			if reply[i] == nil {
				continue
			}
			if err := assign(i, reply[i]); err != nil {
				return err
			}
		}
		return nil
	case nil:
		return ErrNil
	case Error:
		return reply
	}
	return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
}

// Float64s is a helper that converts an array command reply to a []float64. If
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
// converted to 0 in the output slice. Floats64 returns an error if an array
// item is not a bulk string or nil.
func Float64s(reply interface{}, err error) ([]float64, error) {
	var result []float64
	err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
		switch v := v.(type) {
		case []byte:
			f, err := strconv.ParseFloat(string(v), 64)
			result[i] = f
			return err
		case Error:
			return v
		default:
			return fmt.Errorf("redigo: unexpected element type for Float64s, got type %T", v)
		}
	})
	return result, err
}

// Strings is a helper that converts an array command reply to a []string. If
// err is not equal to nil, then Strings returns nil, err. Nil array items are
// converted to "" in the output slice. Strings returns an error if an array
// item is not a bulk string or nil.
func Strings(reply interface{}, err error) ([]string, error) {
	var result []string
	err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
		switch v := v.(type) {
		case string:
			result[i] = v
			return nil
		case []byte:
			result[i] = string(v)
			return nil
		case Error:
			return v
		default:
			return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
		}
	})
	return result, err
}

// ByteSlices is a helper that converts an array command reply to a [][]byte.
// If err is not equal to nil, then ByteSlices returns nil, err. Nil array
// items are stay nil. ByteSlices returns an error if an array item is not a
// bulk string or nil.
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
	var result [][]byte
	err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
		switch v := v.(type) {
		case []byte:
			result[i] = v
			return nil
		case Error:
			return v
		default:
			return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
		}
	})
	return result, err
}

// Int64s is a helper that converts an array command reply to a []int64.
// If err is not equal to nil, then Int64s returns nil, err. Nil array
// items are stay nil. Int64s returns an error if an array item is not a
// bulk string or nil.
func Int64s(reply interface{}, err error) ([]int64, error) {
	var result []int64
	err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
		switch v := v.(type) {
		case int64:
			result[i] = v
			return nil
		case []byte:
			n, err := strconv.ParseInt(string(v), 10, 64)
			result[i] = n
			return err
		case Error:
			return v
		default:
			return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
		}
	})
	return result, err
}

// 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.
func Ints(reply interface{}, err error) ([]int, error) {
	var result []int
	err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
		switch v := v.(type) {
		case int64:
			n := int(v)
			if int64(n) != v {
				return strconv.ErrRange
			}
			result[i] = n
			return nil
		case []byte:
			n, err := strconv.Atoi(string(v))
			result[i] = n
			return err
		case Error:
			return v
		default:
			return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
		}
	})
	return result, err
}

// mapHelper builds a map from the data in reply.
func mapHelper(reply interface{}, err error, name string, makeMap func(int), assign func(key string, value interface{}) error) error {
	values, err := Values(reply, err)
	if err != nil {
		return err
	}

	if len(values)%2 != 0 {
		return fmt.Errorf("redigo: %s expects even number of values result, got %d", name, len(values))
	}

	makeMap(len(values) / 2)
	for i := 0; i < len(values); i += 2 {
		key, ok := values[i].([]byte)
		if !ok {
			return fmt.Errorf("redigo: %s key[%d] not a bulk string value, got %T", name, i, values[i])
		}

		if err := assign(string(key), values[i+1]); err != nil {
			return err
		}
	}

	return nil
}

// StringMap is a helper that converts an array of strings (alternating key, value)
// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
// Requires an even number of values in result.
func StringMap(reply interface{}, err error) (map[string]string, error) {
	var result map[string]string
	err = mapHelper(reply, err, "StringMap",
		func(n int) {
			result = make(map[string]string, n)
		}, func(key string, v interface{}) error {
			value, ok := v.([]byte)
			if !ok {
				return fmt.Errorf("redigo: StringMap for %q not a bulk string value, got %T", key, v)
			}

			result[key] = string(value)

			return nil
		},
	)

	return result, err
}

// IntMap is a helper that converts an array of strings (alternating key, value)
// into a map[string]int. The HGETALL commands return replies in this format.
// Requires an even number of values in result.
func IntMap(result interface{}, err error) (map[string]int, error) {
	var m map[string]int
	err = mapHelper(result, err, "IntMap",
		func(n int) {
			m = make(map[string]int, n)
		}, func(key string, v interface{}) error {
			value, err := Int(v, nil)
			if err != nil {
				return err
			}

			m[key] = value

			return nil
		},
	)

	return m, err
}

// Int64Map is a helper that converts an array of strings (alternating key, value)
// into a map[string]int64. The HGETALL commands return replies in this format.
// Requires an even number of values in result.
func Int64Map(result interface{}, err error) (map[string]int64, error) {
	var m map[string]int64
	err = mapHelper(result, err, "Int64Map",
		func(n int) {
			m = make(map[string]int64, n)
		}, func(key string, v interface{}) error {
			value, err := Int64(v, nil)
			if err != nil {
				return err
			}

			m[key] = value

			return nil
		},
	)

	return m, err
}

// Float64Map is a helper that converts an array of strings (alternating key, value)
// into a map[string]float64. The HGETALL commands return replies in this format.
// Requires an even number of values in result.
func Float64Map(result interface{}, err error) (map[string]float64, error) {
	var m map[string]float64
	err = mapHelper(result, err, "Float64Map",
		func(n int) {
			m = make(map[string]float64, n)
		}, func(key string, v interface{}) error {
			value, err := Float64(v, nil)
			if err != nil {
				return err
			}

			m[key] = value

			return nil
		},
	)

	return m, err
}

// Positions is a helper that converts an array of positions (lat, long)
// into a [][2]float64. The GEOPOS command returns replies in this format.
func Positions(result interface{}, err error) ([]*[2]float64, error) {
	values, err := Values(result, err)
	if err != nil {
		return nil, err
	}
	positions := make([]*[2]float64, len(values))
	for i := range values {
		if values[i] == nil {
			continue
		}

		p, ok := values[i].([]interface{})
		if !ok {
			return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i])
		}

		if len(p) != 2 {
			return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p))
		}

		lat, err := Float64(p[0], nil)
		if err != nil {
			return nil, err
		}

		long, err := Float64(p[1], nil)
		if err != nil {
			return nil, err
		}

		positions[i] = &[2]float64{lat, long}
	}
	return positions, nil
}

// Uint64s is a helper that converts an array command reply to a []uint64.
// If err is not equal to nil, then Uint64s returns nil, err. Nil array
// items are stay nil. Uint64s returns an error if an array item is not a
// bulk string or nil.
func Uint64s(reply interface{}, err error) ([]uint64, error) {
	var result []uint64
	err = sliceHelper(reply, err, "Uint64s", func(n int) { result = make([]uint64, n) }, func(i int, v interface{}) error {
		switch v := v.(type) {
		case uint64:
			result[i] = v
			return nil
		case []byte:
			n, err := strconv.ParseUint(string(v), 10, 64)
			result[i] = n
			return err
		case Error:
			return v
		default:
			return fmt.Errorf("redigo: unexpected element type for Uint64s, got type %T", v)
		}
	})
	return result, err
}

// Uint64Map is a helper that converts an array of strings (alternating key, value)
// into a map[string]uint64. The HGETALL commands return replies in this format.
// Requires an even number of values in result.
func Uint64Map(result interface{}, err error) (map[string]uint64, error) {
	var m map[string]uint64
	err = mapHelper(result, err, "Uint64Map",
		func(n int) {
			m = make(map[string]uint64, n)
		}, func(key string, v interface{}) error {
			value, err := Uint64(v, nil)
			if err != nil {
				return err
			}

			m[key] = value

			return nil
		},
	)

	return m, err
}

// SlowLogs is a helper that parse the SLOWLOG GET command output and
// return the array of SlowLog
func SlowLogs(result interface{}, err error) ([]SlowLog, error) {
	rawLogs, err := Values(result, err)
	if err != nil {
		return nil, err
	}
	logs := make([]SlowLog, len(rawLogs))
	for i, e := range rawLogs {
		rawLog, ok := e.([]interface{})
		if !ok {
			return nil, fmt.Errorf("redigo: slowlog element is not an array, got %T", e)
		}

		var log SlowLog
		if len(rawLog) < 4 {
			return nil, fmt.Errorf("redigo: slowlog element has %d elements, expected at least 4", len(rawLog))
		}

		log.ID, ok = rawLog[0].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: slowlog element[0] not an int64, got %T", rawLog[0])
		}

		timestamp, ok := rawLog[1].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: slowlog element[1] not an int64, got %T", rawLog[1])
		}

		log.Time = time.Unix(timestamp, 0)
		duration, ok := rawLog[2].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: slowlog element[2] not an int64, got %T", rawLog[2])
		}

		log.ExecutionTime = time.Duration(duration) * time.Microsecond

		log.Args, err = Strings(rawLog[3], nil)
		if err != nil {
			return nil, fmt.Errorf("redigo: slowlog element[3] is not array of strings: %w", err)
		}

		if len(rawLog) >= 6 {
			log.ClientAddr, err = String(rawLog[4], nil)
			if err != nil {
				return nil, fmt.Errorf("redigo: slowlog element[4] is not a string: %w", err)
			}

			log.ClientName, err = String(rawLog[5], nil)
			if err != nil {
				return nil, fmt.Errorf("redigo: slowlog element[5] is not a string: %w", err)
			}
		}
		logs[i] = log
	}
	return logs, nil
}

// Latencies is a helper that parses the LATENCY LATEST command output and
// return the slice of Latency values.
func Latencies(result interface{}, err error) ([]Latency, error) {
	rawLatencies, err := Values(result, err)
	if err != nil {
		return nil, err
	}

	latencies := make([]Latency, len(rawLatencies))
	for i, e := range rawLatencies {
		rawLatency, ok := e.([]interface{})
		if !ok {
			return nil, fmt.Errorf("redigo: latencies element is not slice, got %T", e)
		}

		var event Latency
		if len(rawLatency) != 4 {
			return nil, fmt.Errorf("redigo: latencies element has %d elements, expected 4", len(rawLatency))
		}

		event.Name, err = String(rawLatency[0], nil)
		if err != nil {
			return nil, fmt.Errorf("redigo: latencies element[0] is not a string: %w", err)
		}

		timestamp, ok := rawLatency[1].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: latencies element[1] not an int64, got %T", rawLatency[1])
		}

		event.Time = time.Unix(timestamp, 0)

		latestDuration, ok := rawLatency[2].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: latencies element[2] not an int64, got %T", rawLatency[2])
		}

		event.Latest = time.Duration(latestDuration) * time.Millisecond

		maxDuration, ok := rawLatency[3].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: latencies element[3] not an int64, got %T", rawLatency[3])
		}

		event.Max = time.Duration(maxDuration) * time.Millisecond

		latencies[i] = event
	}

	return latencies, nil
}

// LatencyHistories is a helper that parse the LATENCY HISTORY command output and
// returns a LatencyHistory slice.
func LatencyHistories(result interface{}, err error) ([]LatencyHistory, error) {
	rawLogs, err := Values(result, err)
	if err != nil {
		return nil, err
	}

	latencyHistories := make([]LatencyHistory, len(rawLogs))
	for i, e := range rawLogs {
		rawLog, ok := e.([]interface{})
		if !ok {
			return nil, fmt.Errorf("redigo: latency history element is not an slice, got %T", e)
		}

		var event LatencyHistory
		timestamp, ok := rawLog[0].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: latency history element[0] not an int64, got %T", rawLog[0])
		}

		event.Time = time.Unix(timestamp, 0)

		duration, ok := rawLog[1].(int64)
		if !ok {
			return nil, fmt.Errorf("redigo: latency history element[1] not an int64, got %T", rawLog[1])
		}

		event.ExecutionTime = time.Duration(duration) * time.Millisecond

		latencyHistories[i] = event
	}

	return latencyHistories, nil
}


================================================
FILE: redis/reply_test.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis_test

import (
	"fmt"
	"math"
	"reflect"
	"strconv"
	"testing"
	"time"

	"github.com/gomodule/redigo/redis"
	"github.com/stretchr/testify/require"
)

var (
	maxUint64Str = strconv.FormatUint(math.MaxUint64, 10)
)

type valueError struct {
	v   interface{}
	err error
}

func ve(v interface{}, err error) valueError {
	return valueError{v, err}
}

var replyTests = []struct {
	name     interface{}
	actual   valueError
	expected valueError
}{
	{
		"ints([[]byte, []byte])",
		ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
		ve([]int{4, 5}, nil),
	},
	{
		"ints([nt64, int64])",
		ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)),
		ve([]int{4, 5}, nil),
	},
	{
		"ints([[]byte, nil, []byte])",
		ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)),
		ve([]int{4, 0, 5}, nil),
	},
	{
		"ints(nil)",
		ve(redis.Ints(nil, nil)),
		ve([]int(nil), redis.ErrNil),
	},
	{
		"int64s([[]byte, []byte])",
		ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)),
		ve([]int64{4, 5}, nil),
	},
	{
		"int64s([int64, int64])",
		ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)),
		ve([]int64{4, 5}, nil),
	},
	{
		"uint64s([[]byte, []byte])",
		ve(redis.Uint64s([]interface{}{[]byte(maxUint64Str), []byte("5")}, nil)),
		ve([]uint64{math.MaxUint64, 5}, nil),
	},
	{
		"Uint64Map([[]byte, []byte])",
		ve(redis.Uint64Map([]interface{}{[]byte("key1"), []byte(maxUint64Str), []byte("key2"), []byte("5")}, nil)),
		ve(map[string]uint64{"key1": math.MaxUint64, "key2": 5}, nil),
	},
	{
		"strings([[]byte, []byte])",
		ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
		ve([]string{"v1", "v2"}, nil),
	},
	{
		"strings([string, string])",
		ve(redis.Strings([]interface{}{"v1", "v2"}, nil)),
		ve([]string{"v1", "v2"}, nil),
	},
	{
		"byteslices([v1, v2])",
		ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
		ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
	},
	{
		"float64s([v1, v2])",
		ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)),
		ve([]float64{1.234, 5.678}, nil),
	},
	{
		"values([v1, v2])",
		ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
		ve([]interface{}{[]byte("v1"), []byte("v2")}, nil),
	},
	{
		"values(nil)",
		ve(redis.Values(nil, nil)),
		ve([]interface{}(nil), redis.ErrNil),
	},
	{
		"float64(1.0)",
		ve(redis.Float64([]byte("1.0"), nil)),
		ve(float64(1.0), nil),
	},
	{
		"float64(nil)",
		ve(redis.Float64(nil, nil)),
		ve(float64(0.0), redis.ErrNil),
	},
	{
		"float64Map([[]byte, []byte])",
		ve(redis.Float64Map([]interface{}{[]byte("key1"), []byte("1.234"), []byte("key2"), []byte("5.678")}, nil)),
		ve(map[string]float64{"key1": 1.234, "key2": 5.678}, nil),
	},
	{
		"uint64(1)",
		ve(redis.Uint64(int64(1), nil)),
		ve(uint64(1), nil),
	},
	{
		"uint64(-1)",
		ve(redis.Uint64(int64(-1), nil)),
		ve(uint64(0), redis.ErrNegativeInt(-1)),
	},
	{
		"positions([[1, 2], nil, [3, 4]])",
		ve(redis.Positions([]interface{}{[]interface{}{[]byte("1"), []byte("2")}, nil, []interface{}{[]byte("3"), []byte("4")}}, nil)),
		ve([]*[2]float64{{1.0, 2.0}, nil, {3.0, 4.0}}, nil),
	},
	{
		"SlowLogs(1, 1579625870, 3, {set, x, y}, localhost:1234, testClient",
		ve(getSlowLog()),
		ve(redis.SlowLog{ID: 1, Time: time.Unix(1579625870, 0), ExecutionTime: time.Duration(3) * time.Microsecond, Args: []string{"set", "x", "y"}, ClientAddr: "localhost:1234", ClientName: "testClient"}, nil),
	},
}

func getSlowLog() (redis.SlowLog, error) {
	slowLogs, _ := redis.SlowLogs([]interface{}{[]interface{}{int64(1), int64(1579625870), int64(3), []interface{}{"set", "x", "y"}, "localhost:1234", "testClient"}}, nil)
	if err != nil {
		return redis.SlowLog{}, err
	}
	return slowLogs[0], nil
}

func TestReply(t *testing.T) {
	for _, rt := range replyTests {
		if rt.actual.err != rt.expected.err && rt.actual.err.Error() != rt.expected.err.Error() {
			t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err)
			continue
		}
		if !reflect.DeepEqual(rt.actual.v, rt.expected.v) {
			t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v)
		}
	}
}

func TestSlowLog(t *testing.T) {
	c, err := dial()
	if err != nil {
		t.Errorf("TestSlowLog failed during dial with error " + err.Error())
		return
	}
	defer c.Close()

	resultStr, err := redis.Strings(c.Do("CONFIG", "GET", "slowlog-log-slower-than"))
	if err != nil {
		t.Errorf("TestSlowLog failed during CONFIG GET slowlog-log-slower-than with error " + err.Error())
		return
	}
	// in case of older verion < 2.2.12 where SLOWLOG command is not supported
	// don't run the test
	if len(resultStr) == 0 {
		return
	}
	slowLogSlowerThanOldCfg, err := strconv.Atoi(resultStr[1])
	if err != nil {
		t.Errorf("TestSlowLog failed during strconv.Atoi with error " + err.Error())
		return
	}
	result, err := c.Do("CONFIG", "SET", "slowlog-log-slower-than", "0")
	if err != nil && result != "OK" {
		t.Errorf("TestSlowLog failed during CONFIG SET with error " + err.Error())
		return
	}
	result, err = c.Do("SLOWLOG", "GET")
	if err != nil {
		t.Errorf("TestSlowLog failed during SLOWLOG GET with error " + err.Error())
		return
	}
	slowLogs, err := redis.SlowLogs(result, err)
	if err != nil {
		t.Errorf("TestSlowLog failed during redis.SlowLogs with error " + err.Error())
		return
	}
	slowLog := slowLogs[0]
	if slowLog.Args[0] != "CONFIG" ||
		slowLog.Args[1] != "SET" ||
		slowLog.Args[2] != "slowlog-log-slower-than" ||
		slowLog.Args[3] != "0" {
		t.Errorf("%s=%+v, want %+v", "TestSlowLog test failed : ",
			slowLog.Args[0]+" "+slowLog.Args[1]+" "+slowLog.Args[2]+" "+
				slowLog.Args[3], "CONFIG SET slowlog-log-slower-than 0")
	}
	// reset the old configuration after test
	result, err = c.Do("CONFIG", "SET", "slowlog-log-slower-than", slowLogSlowerThanOldCfg)
	if err != nil && result != "OK" {
		t.Errorf("TestSlowLog failed during CONFIG SET with error " + err.Error())
		return
	}
}

func TestLatency(t *testing.T) {
	c, err := dial()
	require.NoError(t, err)
	defer c.Close()

	resultStr, err := redis.Strings(c.Do("CONFIG", "GET", "latency-monitor-threshold"))
	require.NoError(t, err)
	// LATENCY commands were added in 2.8.13 so might not be supported.
	if len(resultStr) == 0 {
		t.Skip("Latency commands not supported")
	}
	latencyMonitorThresholdOldCfg, err := strconv.Atoi(resultStr[1])
	require.NoError(t, err)
	// Enable latency monitoring for events that take 1ms or longer.
	result, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", "1")
	// reset the old configuration after test.
	defer func() {
		res, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", latencyMonitorThresholdOldCfg)
		require.NoError(t, err)
		require.Equal(t, "OK", res)
	}()

	require.NoError(t, err)
	require.Equal(t, "OK", result)

	// Sleep for 1ms to register a slow event.
	_, err = c.Do("DEBUG", "SLEEP", 0.001)
	require.NoError(t, err)

	result, err = c.Do("LATENCY", "LATEST")
	require.NoError(t, err)

	latestLatencies, err := redis.Latencies(result, err)
	require.NoError(t, err)

	require.Equal(t, 1, len(latestLatencies))

	latencyEvent := latestLatencies[0]

	// The actual latency might be longer than 1ms
	require.GreaterOrEqual(t, latencyEvent.Latest, time.Millisecond)
	require.GreaterOrEqual(t, latencyEvent.Max, time.Millisecond)
	expected := redis.Latency{
		Name:   "command",
		Latest: latencyEvent.Latest,
		Max:    latencyEvent.Max,
		Time:   latencyEvent.Time,
	}
	require.Equal(t, expected, latencyEvent)
}

func TestLatencyHistories(t *testing.T) {
	c, err := dial()
	require.NoError(t, err)
	defer c.Close()

	res, err := redis.Strings(c.Do("CONFIG", "GET", "latency-monitor-threshold"))
	require.NoError(t, err)

	// LATENCY commands were added in 2.8.13 so might not be supported.
	if len(res) == 0 {
		t.Skip("Latency commands not supported")
	}
	latencyMonitorThresholdOldCfg, err := strconv.Atoi(res[1])
	require.NoError(t, err)

	// Reset so we're compatible with -count=X
	_, err = c.Do("LATENCY", "RESET", "command")
	require.NoError(t, err)

	// Enable latency monitoring for events that take 1ms or longer
	result, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", "1")
	// reset the old configuration after test.
	defer func() {
		res, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", latencyMonitorThresholdOldCfg)
		require.NoError(t, err)
		require.Equal(t, "OK", res)
	}()
	require.NoError(t, err)
	require.Equal(t, "OK", result)

	// Sleep for 1ms to register a slow event
	_, err = c.Do("DEBUG", "SLEEP", 0.001)
	require.NoError(t, err)

	result, err = c.Do("LATENCY", "HISTORY", "command")
	require.NoError(t, err)

	latencyHistory, err := redis.LatencyHistories(result, err)
	require.NoError(t, err)

	require.Len(t, latencyHistory, 1)
	latencyEvent := latencyHistory[0]
	// The actual latency might be longer than 1ms
	require.GreaterOrEqual(t, latencyEvent.ExecutionTime, time.Millisecond)
}

// dial wraps DialDefaultServer() with a more suitable function name for examples.
func dial() (redis.Conn, error) {
	return redis.DialDefaultServer()
}

// serverAddr wraps DefaultServerAddr() with a more suitable function name for examples.
func serverAddr() (string, error) {
	return redis.DefaultServerAddr()
}

func ExampleBool() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	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
}

func ExampleInt() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	_, 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, err = redis.Int(c.Do("INCR", "k1"))
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%#v\n", n)
	// Output:
	// 1
	// 2
}

func ExampleInts() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	_, 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}
}

func ExampleString() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	_, 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"
}


================================================
FILE: redis/scan.go
================================================
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package redis

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"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))
	} else {
		d.SetLen(n)
	}
}

func cannotConvert(d reflect.Value, s interface{}) error {
	var sname string
	switch s.(type) {
	case string:
		sname = "Redis simple string"
	case Error:
		sname = "Redis error"
	case int64:
		sname = "Redis integer"
	case []byte:
		sname = "Redis bulk string"
	case []interface{}:
		sname = "Redis array"
	case nil:
		sname = "Redis nil"
	default:
		sname = reflect.TypeOf(s).String()
	}
	return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
}

func convertAssignNil(d reflect.Value) (err error) {
	switch d.Type().Kind() {
	case reflect.Slice, reflect.Interface:
		d.Set(reflect.Zero(d.Type()))
	default:
		err = cannotConvert(d, nil)
	}
	return err
}

func convertAssignError(d reflect.Value, s Error) (err error) {
	if d.Kind() == reflect.String {
		d.SetString(string(s))
	} else if d.Kind() == reflect.Slice && d.Type().Elem().Kind() == reflect.Uint8 {
		d.SetBytes([]byte(s))
	} else {
		err = cannotConvert(d, s)
	}
	return
}

func convertAssignString(d reflect.Value, s string) (err error) {
	switch d.Type().Kind() {
	case reflect.Float32, reflect.Float64:
		var x float64
		x, err = strconv.ParseFloat(s, d.Type().Bits())
		d.SetFloat(x)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		var x int64
		x, err = strconv.ParseInt(s, 10, d.Type().Bits())
		d.SetInt(x)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		var x uint64
		x, err = strconv.ParseUint(s, 10, d.Type().Bits())
		d.SetUint(x)
	case reflect.Bool:
		var x bool
		x, err = strconv.ParseBool(s)
		d.SetBool(x)
	case reflect.String:
		d.SetString(s)
	case reflect.Slice:
		if d.Type().Elem().Kind() == reflect.Uint8 {
			d.SetBytes([]byte(s))
		} else {
			err = cannotConvert(d, s)
		}
	case reflect.Ptr:
		err = convertAssignString(d.Elem(), s)
	default:
		err = cannotConvert(d, s)
	}
	return
}

func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
	switch d.Type().Kind() {
	case reflect.Slice:
		// Handle []byte destination here to avoid unnecessary
		// []byte -> string -> []byte converion.
		if d.Type().Elem().Kind() == reflect.Uint8 {
			d.SetBytes(s)
		} else {
			err = cannotConvert(d, s)
		}
	case reflect.Ptr:
		if d.CanInterface() && d.CanSet() {
			if s == nil {
				if d.IsNil() {
					return nil
				}

				d.Set(reflect.Zero(d.Type()))
				return nil
			}

			if d.IsNil() {
				d.Set(reflect.New(d.Type().Elem()))
			}

			if sc, ok := d.Interface().(Scanner); ok {
				return sc.RedisScan(s)
			}
		}
		err = convertAssignString(d, string(s))
	default:
		err = convertAssignString(d, string(s))
	}
	return err
}

func convertAssignInt(d reflect.Value, s int64) (err error) {
	switch d.Type().Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		d.SetInt(s)
		if d.Int() != s {
			err = strconv.ErrRange
			d.SetInt(0)
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if s < 0 {
			err = strconv.ErrRange
		} else {
			x := uint64(s)
			d.SetUint(x)
			if d.Uint() != x {
				err = strconv.ErrRange
				d.SetUint(0)
			}
		}
	case reflect.Bool:
		d.SetBool(s != 0)
	default:
		err = cannotConvert(d, s)
	}
	return
}

func convertAssignValue(d reflect.Value, s interface{}) (err error) {
	if d.Kind() != reflect.Ptr {
		if d.CanAddr() {
			d2 := d.Addr()
			if d2.CanInterface() {
				if scanner, ok := d2.Interface().(Scanner); ok {
					return scanner.RedisScan(s)
				}
			}
		}
	} else if d.CanInterface() {
		// Already a reflect.Ptr
		if d.IsNil() {
			d.Set(reflect.New(d.Type().Elem()))
		}
		if scanner, ok := d.Interface().(Scanner); ok {
			return scanner.RedisScan(s)
		}
	}

	switch s := s.(type) {
	case nil:
		err = convertAssignNil(d)
	case []byte:
		err = convertAssignBulkString(d, s)
	case int64:
		err = convertAssignInt(d, s)
	case string:
		err = convertAssignString(d, s)
	case Error:
		err = convertAssignError(d, s)
	default:
		err = cannotConvert(d, s)
	}
	return err
}

func convertAssignArray(d reflect.Value, s []interface{}) error {
	if d.Type().Kind() != reflect.Slice {
		return cannotConvert(d, s)
	}
	ensureLen(d, len(s))
	for i := 0; i < len(s); i++ {
		if err := convertAssignValue(d.Index(i), s[i]); err != nil {
			return err
		}
	}
	return nil
}

func convertAssign(d interface{}, s interface{}) (err error) {
	if scanner, ok := d.(Scanner); ok {
		return scanner.RedisScan(s)
	}

	// Handle the most common destination types using type switches and
	// fall back to reflection for all other types.
	switch s := s.(type) {
	case nil:
		// ignore
	case []byte:
		switch d := d.(type) {
		case *string:
			*d = string(s)
		case *int:
			*d, err = strconv.Atoi(string(s))
		case *bool:
			*d, err = strconv.ParseBool(string(s))
		case *[]byte:
			*d = s
		case *interface{}:
			*d = s
		case nil:
			// skip value
		default:
			if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
				err = cannotConvert(d, s)
			} else {
				err = convertAssignBulkString(d.Elem(), s)
			}
		}
	case int64:
		switch d := d.(type) {
		case *int:
			x := int(s)
			if int64(x) != s {
				err = strconv.ErrRange
				x = 0
			}
			*d = x
		case *bool:
			*d = s != 0
		case *interface{}:
			*d = s
		case nil:
			// skip value
		default:
			if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
				err = cannotConvert(d, s)
			} else {
				err = convertAssignInt(d.Elem(), s)
			}
		}
	case string:
		switch d := d.(type) {
		case *string:
			*d = s
		case *interface{}:
			*d = s
		case nil:
			// skip value
		default:
			err = cannotConvert(reflect.ValueOf(d), s)
		}
	case []interface{}:
		switch d := d.(type) {
		case *[]interface{}:
			*d = s
		case *interface{}:
			*d = s
		case nil:
			// skip value
		default:
			if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
				err = cannotConvert(d, s)
			} else {
				err = convertAssignArray(d.Elem(), s)
			}
		}
	case Error:
		err = s
	default:
		err = cannotConvert(reflect.ValueOf(d), s)
	}
	return
}

// Scan copies from src to the values pointed at by dest.
//
// Scan uses RedisScan if available otherwise:
//
// The values pointed at by dest must be an integer, float, boolean, string,
// []byte, interface{} or slices of these types. Scan uses the standard strconv
// package to convert bulk strings to numeric and boolean types.
//
// If a dest value is nil, then the corresponding src value is skipped.
//
// If a src element is nil, then the corresponding dest value is not modified.
//
// To enable easy use of Scan in a loop, Scan returns the slice of src
// following the copied values.
func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
	if len(src) < len(dest) {
		return nil, errors.New("redigo.Scan: array short")
	}
	var err error
	for i, d := range dest {
		err = convertAssign(d, src[i])
		if err != nil {
			err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err)
			break
		}
	}
	return src[len(dest):], err
}

type fieldSpec struct {
	name      string
	index     []int
	omitEmpty bool
}

type structSpec struct {
	m map[string]*fieldSpec
	l []*fieldSpec
}

func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
	return ss.m[string(name)]
}

func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec, seen map[reflect.Type]struct{}) error {
	if _, ok := seen[t]; ok {
		// Protect against infinite recursion.
		return fmt.Errorf("recursive struct definition for %v", t)
	}

	seen[t] = struct{}{}
LOOP:
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		switch {
		case f.PkgPath != "" && !f.Anonymous:
			// Ignore unexported fields.
		case f.Anonymous:
			switch f.Type.Kind() {
			case reflect.Struct:
				if err := compileStructSpec(f.Type, depth, append(index, i), ss, seen); err != nil {
					return err
				}
			case reflect.Ptr:
				if f.Type.Elem().Kind() == reflect.Struct {
					if err := compileStructSpec(f.Type.Elem(), depth, append(index, i), ss, seen); err != nil {
						return err
					}
				}
			}
		default:
			fs := &fieldSpec{name:
Download .txt
gitextract_abjfnhty/

├── .clog.toml
├── .github/
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── go-test.yml
│       ├── golangci-lint.yml
│       └── release-build.yml
├── .goreleaser.yaml
├── .travis.yml
├── LICENSE
├── README.markdown
├── go.mod
├── go.sum
├── redis/
│   ├── commandinfo.go
│   ├── commandinfo_test.go
│   ├── conn.go
│   ├── conn_test.go
│   ├── doc.go
│   ├── list_test.go
│   ├── log.go
│   ├── pool.go
│   ├── pool_test.go
│   ├── pubsub.go
│   ├── pubsub_example_test.go
│   ├── pubsub_test.go
│   ├── redis.go
│   ├── redis_test.go
│   ├── reflect.go
│   ├── reflect_go117.go
│   ├── reflect_go118.go
│   ├── reply.go
│   ├── reply_test.go
│   ├── scan.go
│   ├── scan_test.go
│   ├── script.go
│   ├── script_test.go
│   ├── test_test.go
│   └── zpop_example_test.go
└── redisx/
    ├── commandinfo.go
    ├── commandinfo_test.go
    ├── connmux.go
    ├── connmux_test.go
    ├── db_test.go
    └── doc.go
Download .txt
SYMBOL INDEX (420 symbols across 29 files)

FILE: redis/commandinfo.go
  constant connectionWatchState (line 22) | connectionWatchState = 1 << iota
  constant connectionMultiState (line 23) | connectionMultiState
  constant connectionSubscribeState (line 24) | connectionSubscribeState
  constant connectionMonitorState (line 25) | connectionMonitorState
  type commandInfo (line 28) | type commandInfo struct
  function init (line 44) | func init() {
  function lookupCommandInfo (line 50) | func lookupCommandInfo(commandName string) commandInfo {

FILE: redis/commandinfo_test.go
  function TestLookupCommandInfo (line 5) | func TestLookupCommandInfo(t *testing.T) {
  function benchmarkLookupCommandInfo (line 13) | func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
  function BenchmarkLookupCommandInfoCorrectCase (line 21) | func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
  function BenchmarkLookupCommandInfoMixedCase (line 25) | func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {

FILE: redis/conn.go
  type conn (line 38) | type conn struct
    method Close (line 408) | func (c *conn) Close() error {
    method fatal (line 419) | func (c *conn) fatal(err error) error {
    method Err (line 431) | func (c *conn) Err() error {
    method writeLen (line 438) | func (c *conn) writeLen(prefix byte, n int) error {
    method writeString (line 455) | func (c *conn) writeString(s string) error {
    method writeBytes (line 466) | func (c *conn) writeBytes(p []byte) error {
    method writeInt64 (line 477) | func (c *conn) writeInt64(n int64) error {
    method writeFloat64 (line 481) | func (c *conn) writeFloat64(n float64) error {
    method writeCommand (line 485) | func (c *conn) writeCommand(cmd string, args []interface{}) error {
    method writeArg (line 500) | func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err err...
    method readLine (line 545) | func (c *conn) readLine() ([]byte, error) {
    method readReply (line 628) | func (c *conn) readReply() (interface{}, error) {
    method Send (line 685) | func (c *conn) Send(cmd string, args ...interface{}) error {
    method Flush (line 700) | func (c *conn) Flush() error {
    method Receive (line 712) | func (c *conn) Receive() (interface{}, error) {
    method ReceiveContext (line 716) | func (c *conn) ReceiveContext(ctx context.Context) (interface{}, error) {
    method ReceiveWithTimeout (line 746) | func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interf...
    method Do (line 776) | func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
    method DoContext (line 780) | func (c *conn) DoContext(ctx context.Context, cmd string, args ...inte...
    method DoWithTimeout (line 810) | func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, ar...
  function DialTimeout (line 65) | func DialTimeout(network, address string, connectTimeout, readTimeout, w...
  type DialOption (line 73) | type DialOption struct
  type dialOptions (line 77) | type dialOptions struct
  function DialTLSHandshakeTimeout (line 95) | func DialTLSHandshakeTimeout(d time.Duration) DialOption {
  function DialReadTimeout (line 102) | func DialReadTimeout(d time.Duration) DialOption {
  function DialWriteTimeout (line 109) | func DialWriteTimeout(d time.Duration) DialOption {
  function DialConnectTimeout (line 118) | func DialConnectTimeout(d time.Duration) DialOption {
  function DialKeepAlive (line 128) | func DialKeepAlive(d time.Duration) DialOption {
  function DialNetDial (line 137) | func DialNetDial(dial func(network, addr string) (net.Conn, error)) Dial...
  function DialContextFunc (line 148) | func DialContextFunc(f func(ctx context.Context, network, addr string) (...
  function DialDatabase (line 155) | func DialDatabase(db int) DialOption {
  function DialPassword (line 163) | func DialPassword(password string) DialOption {
  function DialUsername (line 172) | func DialUsername(username string) DialOption {
  function DialClientName (line 180) | func DialClientName(name string) DialOption {
  function DialTLSConfig (line 188) | func DialTLSConfig(c *tls.Config) DialOption {
  function DialTLSSkipVerify (line 196) | func DialTLSSkipVerify(skip bool) DialOption {
  function DialUseTLS (line 204) | func DialUseTLS(useTLS bool) DialOption {
  function Dial (line 212) | func Dial(network, address string, options ...DialOption) (Conn, error) {
  type tlsHandshakeTimeoutError (line 216) | type tlsHandshakeTimeoutError struct
    method Timeout (line 218) | func (tlsHandshakeTimeoutError) Timeout() bool   { return true }
    method Temporary (line 219) | func (tlsHandshakeTimeoutError) Temporary() bool { return true }
    method Error (line 220) | func (tlsHandshakeTimeoutError) Error() string   { return "TLS handsha...
  function DialContext (line 224) | func DialContext(ctx context.Context, network, address string, options ....
  function DialURL (line 320) | func DialURL(rawurl string, options ...DialOption) (Conn, error) {
  function DialURLContext (line 329) | func DialURLContext(ctx context.Context, rawurl string, options ...DialO...
  function NewConn (line 398) | func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) ...
  type protocolError (line 538) | type protocolError
    method Error (line 540) | func (pe protocolError) Error() string {
  function parseLen (line 571) | func parseLen(p []byte) (int, error) {
  function parseInt (line 594) | func parseInt(p []byte) (interface{}, error) {

FILE: redis/conn_test.go
  type testConn (line 37) | type testConn struct
    method Close (line 44) | func (*testConn) Close() error         { return nil }
    method LocalAddr (line 45) | func (*testConn) LocalAddr() net.Addr  { return nil }
    method RemoteAddr (line 46) | func (*testConn) RemoteAddr() net.Addr { return nil }
    method SetDeadline (line 47) | func (c *testConn) SetDeadline(t time.Time) error {
    method SetReadDeadline (line 52) | func (c *testConn) SetReadDeadline(t time.Time) error  { c.readDeadlin...
    method SetWriteDeadline (line 53) | func (c *testConn) SetWriteDeadline(t time.Time) error { c.writeDeadli...
  function dialTestConn (line 55) | func dialTestConn(r string, w io.Writer) redis.DialOption {
  type tlsTestConn (line 61) | type tlsTestConn struct
    method Close (line 66) | func (c *tlsTestConn) Close() error {
  function dialTestConnTLS (line 72) | func dialTestConnTLS(r string, w io.Writer) redis.DialOption {
  type durationArg (line 86) | type durationArg struct
    method RedisArg (line 90) | func (t durationArg) RedisArg() interface{} {
  type recursiveArg (line 94) | type recursiveArg
    method RedisArg (line 96) | func (v recursiveArg) RedisArg() interface{} { return v }
  function TestWrite (line 148) | func TestWrite(t *testing.T) {
  function TestRead (line 262) | func TestRead(t *testing.T) {
  function TestReadString (line 282) | func TestReadString(t *testing.T) {
  function TestDoCommands (line 367) | func TestDoCommands(t *testing.T) {
  function TestPipelineCommands (line 386) | func TestPipelineCommands(t *testing.T) {
  function TestBlankCommand (line 412) | func TestBlankCommand(t *testing.T) {
  function TestRecvBeforeSend (line 439) | func TestRecvBeforeSend(t *testing.T) {
  function TestError (line 460) | func TestError(t *testing.T) {
  function TestReadTimeout (line 482) | func TestReadTimeout(t *testing.T) {
  function TestDialContextFunc (line 566) | func TestDialContextFunc(t *testing.T) {
  function TestDialContext_CanceledContext (line 583) | func TestDialContext_CanceledContext(t *testing.T) {
  function TestDialURLErrors (line 645) | func TestDialURLErrors(t *testing.T) {
  function TestDialURLPort (line 654) | func TestDialURLPort(t *testing.T) {
  function TestDialURLHost (line 667) | func TestDialURLHost(t *testing.T) {
  function TestDialURL (line 701) | func TestDialURL(t *testing.T) {
  function checkPingPong (line 718) | func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) {
  constant pingResponse (line 735) | pingResponse = "+PONG\r\n"
  function TestDialURLTLS (line 737) | func TestDialURLTLS(t *testing.T) {
  function TestDialUseTLS (line 748) | func TestDialUseTLS(t *testing.T) {
  type blockedReader (line 760) | type blockedReader struct
    method Read (line 764) | func (b blockedReader) Read(p []byte) (n int, err error) {
  function dialTestBlockedConn (line 769) | func dialTestBlockedConn(ch chan struct{}, w io.Writer) redis.DialOption {
  function TestDialTLSHandshakeTimeout (line 775) | func TestDialTLSHandshakeTimeout(t *testing.T) {
  function TestDialTLSSKipVerify (line 799) | func TestDialTLSSKipVerify(t *testing.T) {
  function TestDialUseACL (line 811) | func TestDialUseACL(t *testing.T) {
  function ExampleDial_acl (line 830) | func ExampleDial_acl() {
  function TestDialClientName (line 841) | func TestDialClientName(t *testing.T) {
  function ExampleDial (line 879) | func ExampleDial() {
  function ExampleDialContext (line 888) | func ExampleDialContext() {
  function ExampleDialURL (line 898) | func ExampleDialURL() {
  function TestExecError (line 909) | func TestExecError(t *testing.T) {
  function BenchmarkDoEmpty (line 993) | func BenchmarkDoEmpty(b *testing.B) {
  function BenchmarkDoPing (line 1008) | func BenchmarkDoPing(b *testing.B) {
  function init (line 1025) | func init() {
  function TestWithTimeout (line 1084) | func TestWithTimeout(t *testing.T) {

FILE: redis/list_test.go
  function TestPoolList (line 21) | func TestPoolList(t *testing.T) {

FILE: redis/log.go
  function NewLoggingConn (line 30) | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
  function NewLoggingConnFilter (line 38) | func NewLoggingConnFilter(conn Conn, logger *log.Logger, prefix string, ...
  type loggingConn (line 45) | type loggingConn struct
    method Close (line 52) | func (c *loggingConn) Close() error {
    method printValue (line 60) | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
    method print (line 97) | func (c *loggingConn) print(method, commandName string, args []interfa...
    method Do (line 119) | func (c *loggingConn) Do(commandName string, args ...interface{}) (int...
    method DoContext (line 125) | func (c *loggingConn) DoContext(ctx context.Context, commandName strin...
    method DoWithTimeout (line 131) | func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName...
    method Send (line 137) | func (c *loggingConn) Send(commandName string, args ...interface{}) er...
    method Receive (line 143) | func (c *loggingConn) Receive() (interface{}, error) {
    method ReceiveContext (line 149) | func (c *loggingConn) ReceiveContext(ctx context.Context) (interface{}...
    method ReceiveWithTimeout (line 155) | func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (inter...

FILE: redis/pool.go
  type Pool (line 120) | type Pool struct
    method Get (line 192) | func (p *Pool) Get() Conn {
    method GetContext (line 206) | func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
    method Stats (line 295) | func (p *Pool) Stats() PoolStats {
    method ActiveCount (line 310) | func (p *Pool) ActiveCount() int {
    method IdleCount (line 318) | func (p *Pool) IdleCount() int {
    method Close (line 326) | func (p *Pool) Close() error {
    method lazyInit (line 347) | func (p *Pool) lazyInit() {
    method waitVacantConn (line 366) | func (p *Pool) waitVacantConn(ctx context.Context) (waited time.Durati...
    method dial (line 402) | func (p *Pool) dial(ctx context.Context) (Conn, error) {
    method put (line 412) | func (p *Pool) put(pc *poolConn, forceClose bool) error {
  function NewPool (line 183) | func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
  type PoolStats (line 277) | type PoolStats struct
  type activeConn (line 439) | type activeConn struct
    method firstError (line 462) | func (ac *activeConn) firstError(errs ...error) error {
    method Close (line 471) | func (ac *activeConn) Close() (err error) {
    method Err (line 517) | func (ac *activeConn) Err() error {
    method DoContext (line 525) | func (ac *activeConn) DoContext(ctx context.Context, commandName strin...
    method Do (line 539) | func (ac *activeConn) Do(commandName string, args ...interface{}) (rep...
    method DoWithTimeout (line 549) | func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName...
    method Send (line 563) | func (ac *activeConn) Send(commandName string, args ...interface{}) er...
    method Flush (line 573) | func (ac *activeConn) Flush() error {
    method Receive (line 581) | func (ac *activeConn) Receive() (reply interface{}, err error) {
    method ReceiveContext (line 589) | func (ac *activeConn) ReceiveContext(ctx context.Context) (reply inter...
    method ReceiveWithTimeout (line 601) | func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply...
  function initSentinel (line 450) | func initSentinel() {
  type errorConn (line 613) | type errorConn struct
    method Do (line 615) | func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { ...
    method DoContext (line 616) | func (ec errorConn) DoContext(context.Context, string, ...interface{})...
    method DoWithTimeout (line 619) | func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{...
    method Send (line 622) | func (ec errorConn) Send(string, ...interface{}) error                ...
    method Err (line 623) | func (ec errorConn) Err() error                                       ...
    method Close (line 624) | func (ec errorConn) Close() error                                     ...
    method Flush (line 625) | func (ec errorConn) Flush() error                                     ...
    method Receive (line 626) | func (ec errorConn) Receive() (interface{}, error)                    ...
    method ReceiveContext (line 627) | func (ec errorConn) ReceiveContext(context.Context) (interface{}, erro...
    method ReceiveWithTimeout (line 628) | func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, er...
  type idleList (line 630) | type idleList struct
    method pushFront (line 642) | func (l *idleList) pushFront(pc *poolConn) {
    method popFront (line 654) | func (l *idleList) popFront() {
    method popBack (line 666) | func (l *idleList) popBack() {
  type poolConn (line 635) | type poolConn struct

FILE: redis/pool_test.go
  constant testGoRoutines (line 32) | testGoRoutines = 10
  type poolTestConn (line 35) | type poolTestConn struct
    method Close (line 41) | func (c *poolTestConn) Close() error {
    method Err (line 48) | func (c *poolTestConn) Err() error { return c.err }
    method Do (line 50) | func (c *poolTestConn) Do(commandName string, args ...interface{}) (in...
    method DoContext (line 54) | func (c *poolTestConn) DoContext(ctx context.Context, commandName stri...
    method do (line 66) | func (c *poolTestConn) do(
    method Send (line 80) | func (c *poolTestConn) Send(commandName string, args ...interface{}) e...
    method ReceiveContext (line 85) | func (c *poolTestConn) ReceiveContext(ctx context.Context) (reply inte...
  type poolDialer (line 93) | type poolDialer struct
    method dial (line 102) | func (d *poolDialer) dial() (redis.Conn, error) {
    method dialContext (line 106) | func (d *poolDialer) dialContext(ctx context.Context) (redis.Conn, err...
    method check (line 124) | func (d *poolDialer) check(message string, p *redis.Pool, dialed, open...
    method checkAll (line 129) | func (d *poolDialer) checkAll(message string, p *redis.Pool, dialed, o...
  function TestPoolReuse (line 167) | func TestPoolReuse(t *testing.T) {
  function TestPoolMaxIdle (line 190) | func TestPoolMaxIdle(t *testing.T) {
  function TestPoolError (line 217) | func TestPoolError(t *testing.T) {
  function TestPoolClose (line 241) | func TestPoolClose(t *testing.T) {
  function TestPoolClosedConn (line 285) | func TestPoolClosedConn(t *testing.T) {
  function TestPoolIdleTimeout (line 315) | func TestPoolIdleTimeout(t *testing.T) {
  function TestPoolMaxLifetime (line 345) | func TestPoolMaxLifetime(t *testing.T) {
  function TestPoolConcurrenSendReceive (line 375) | func TestPoolConcurrenSendReceive(t *testing.T) {
  function TestPoolBorrowCheck (line 400) | func TestPoolBorrowCheck(t *testing.T) {
  function TestPoolMaxActive (line 539) | func TestPoolMaxActive(t *testing.T) {
  function TestPoolWaitStats (line 576) | func TestPoolWaitStats(t *testing.T) {
  function TestPoolMonitorCleanup (line 609) | func TestPoolMonitorCleanup(t *testing.T) {
  function TestPoolPubSubCleanup (line 625) | func TestPoolPubSubCleanup(t *testing.T) {
  function TestPoolTransactionCleanup (line 655) | func TestPoolTransactionCleanup(t *testing.T) {
  function startGoroutines (line 742) | func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) cha...
  function TestWaitPool (line 756) | func TestWaitPool(t *testing.T) {
  function TestWaitPoolClose (line 785) | func TestWaitPoolClose(t *testing.T) {
  function TestWaitPoolCommandError (line 821) | func TestWaitPoolCommandError(t *testing.T) {
  function TestWaitPoolDialError (line 851) | func TestWaitPoolDialError(t *testing.T) {
  function TestLocking_TestOnBorrowFails_PoolDoesntCrash (line 902) | func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) {
  function BenchmarkPoolGet (line 945) | func BenchmarkPoolGet(b *testing.B) {
  function BenchmarkPoolGetErr (line 961) | func BenchmarkPoolGetErr(b *testing.B) {
  function BenchmarkPoolGetPing (line 980) | func BenchmarkPoolGetPing(b *testing.B) {
  function TestWaitPoolGetContext (line 999) | func TestWaitPoolGetContext(t *testing.T) {
  function TestWaitPoolGetContextIssue520 (line 1015) | func TestWaitPoolGetContextIssue520(t *testing.T) {
  function TestWaitPoolGetContextWithDialContext (line 1041) | func TestWaitPoolGetContextWithDialContext(t *testing.T) {
  function TestPoolGetContext_DialContext (line 1057) | func TestPoolGetContext_DialContext(t *testing.T) {
  function TestPoolGetContext_DialContext_CanceledContext (line 1080) | func TestPoolGetContext_DialContext_CanceledContext(t *testing.T) {
  function TestWaitPoolGetAfterClose (line 1099) | func TestWaitPoolGetAfterClose(t *testing.T) {
  function TestWaitPoolGetCanceledContext (line 1114) | func TestWaitPoolGetCanceledContext(t *testing.T) {

FILE: redis/pubsub.go
  type Subscription (line 24) | type Subscription struct
  type Message (line 36) | type Message struct
  type Pong (line 48) | type Pong struct
  type PubSubConn (line 53) | type PubSubConn struct
    method Close (line 58) | func (c PubSubConn) Close() error {
    method Subscribe (line 63) | func (c PubSubConn) Subscribe(channel ...interface{}) error {
    method PSubscribe (line 71) | func (c PubSubConn) PSubscribe(channel ...interface{}) error {
    method Unsubscribe (line 80) | func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
    method PUnsubscribe (line 89) | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
    method Ping (line 100) | func (c PubSubConn) Ping(data string) error {
    method Receive (line 110) | func (c PubSubConn) Receive() interface{} {
    method ReceiveWithTimeout (line 116) | func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interfac...
    method ReceiveContext (line 123) | func (c PubSubConn) ReceiveContext(ctx context.Context) interface{} {
    method receiveInternal (line 127) | func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error...

FILE: redis/pubsub_example_test.go
  function listenPubSubChannels (line 30) | func listenPubSubChannels(ctx context.Context, redisServerAddr string,
  function publish (line 113) | func publish() {
  function ExamplePubSubConn (line 137) | func ExamplePubSubConn() {

FILE: redis/pubsub_test.go
  function TestPushed (line 27) | func TestPushed(t *testing.T) {
  function TestPubSubReceiveContext (line 80) | func TestPubSubReceiveContext(t *testing.T) {

FILE: redis/redis.go
  type Error (line 24) | type Error
    method Error (line 26) | func (err Error) Error() string { return string(err) }
  type Conn (line 29) | type Conn interface
  type Argument (line 52) | type Argument interface
  type Scanner (line 61) | type Scanner interface
  type ConnWithTimeout (line 84) | type ConnWithTimeout interface
  type ConnWithContext (line 97) | type ConnWithContext interface
  function DoContext (line 126) | func DoContext(c Conn, ctx context.Context, cmd string, args ...interfac...
  function DoWithTimeout (line 137) | func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...in...
  function ReceiveContext (line 151) | func ReceiveContext(c Conn, ctx context.Context) (interface{}, error) {
  function ReceiveWithTimeout (line 162) | func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, err...
  type SlowLog (line 171) | type SlowLog struct
  type Latency (line 192) | type Latency struct
  type LatencyHistory (line 207) | type LatencyHistory struct

FILE: redis/redis_test.go
  type timeoutTestConn (line 25) | type timeoutTestConn
    method Do (line 27) | func (tc timeoutTestConn) Do(string, ...interface{}) (interface{}, err...
    method DoWithTimeout (line 31) | func (tc timeoutTestConn) DoWithTimeout(timeout time.Duration, cmd str...
    method Receive (line 35) | func (tc timeoutTestConn) Receive() (interface{}, error) {
    method ReceiveWithTimeout (line 39) | func (tc timeoutTestConn) ReceiveWithTimeout(timeout time.Duration) (i...
    method Send (line 43) | func (tc timeoutTestConn) Send(string, ...interface{}) error { return ...
    method Err (line 44) | func (tc timeoutTestConn) Err() error                        { return ...
    method Close (line 45) | func (tc timeoutTestConn) Close() error                      { return ...
    method Flush (line 46) | func (tc timeoutTestConn) Flush() error                      { return ...
  function testTimeout (line 48) | func testTimeout(t *testing.T, c redis.Conn) {
  function TestConnTimeout (line 67) | func TestConnTimeout(t *testing.T) {
  function TestPoolConnTimeout (line 71) | func TestPoolConnTimeout(t *testing.T) {
  type contextDeadTestConn (line 76) | type contextDeadTestConn
    method Do (line 78) | func (cc contextDeadTestConn) Do(string, ...interface{}) (interface{},...
    method DoContext (line 81) | func (cc contextDeadTestConn) DoContext(ctx context.Context, cmd strin...
    method Receive (line 84) | func (cc contextDeadTestConn) Receive() (interface{}, error) {
    method ReceiveContext (line 87) | func (cc contextDeadTestConn) ReceiveContext(ctx context.Context) (int...
    method Send (line 90) | func (cc contextDeadTestConn) Send(string, ...interface{}) error { ret...
    method Err (line 91) | func (cc contextDeadTestConn) Err() error                        { ret...
    method Close (line 92) | func (cc contextDeadTestConn) Close() error                      { ret...
    method Flush (line 93) | func (cc contextDeadTestConn) Flush() error                      { ret...
  function testcontext (line 95) | func testcontext(t *testing.T, c redis.Conn) {
  function TestConnContext (line 116) | func TestConnContext(t *testing.T) {
  function TestPoolConnContext (line 120) | func TestPoolConnContext(t *testing.T) {

FILE: redis/reflect.go
  function methodName (line 10) | func methodName() string {
  function mustBe (line 20) | func mustBe(v reflect.Value, expected reflect.Kind) {
  function fieldByIndexCreate (line 29) | func fieldByIndexCreate(v reflect.Value, index []int) reflect.Value {

FILE: redis/reflect_go117.go
  function fieldByIndexErr (line 15) | func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) {

FILE: redis/reflect_go118.go
  function fieldByIndexErr (line 14) | func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) {

FILE: redis/reply.go
  function Int (line 36) | func Int(reply interface{}, err error) (int, error) {
  function Int64 (line 67) | func Int64(reply interface{}, err error) (int64, error) {
  function errNegativeInt (line 85) | func errNegativeInt(v int64) error {
  function Uint64 (line 98) | func Uint64(reply interface{}, err error) (uint64, error) {
  function Float64 (line 127) | func Float64(reply interface{}, err error) (float64, error) {
  function String (line 152) | func String(reply interface{}, err error) (string, error) {
  function Bytes (line 178) | func Bytes(reply interface{}, err error) ([]byte, error) {
  function Bool (line 204) | func Bool(reply interface{}, err error) (bool, error) {
  function MultiBulk (line 224) | func MultiBulk(reply interface{}, err error) ([]interface{}, error) { re...
  function Values (line 234) | func Values(reply interface{}, err error) ([]interface{}, error) {
  function sliceHelper (line 249) | func sliceHelper(reply interface{}, err error, name string, makeSlice fu...
  function Float64s (line 277) | func Float64s(reply interface{}, err error) ([]float64, error) {
  function Strings (line 298) | func Strings(reply interface{}, err error) ([]string, error) {
  function ByteSlices (line 321) | func ByteSlices(reply interface{}, err error) ([][]byte, error) {
  function Int64s (line 341) | func Int64s(reply interface{}, err error) ([]int64, error) {
  function Ints (line 365) | func Ints(reply interface{}, err error) ([]int, error) {
  function mapHelper (line 390) | func mapHelper(reply interface{}, err error, name string, makeMap func(i...
  function StringMap (line 418) | func StringMap(reply interface{}, err error) (map[string]string, error) {
  function IntMap (line 441) | func IntMap(result interface{}, err error) (map[string]int, error) {
  function Int64Map (line 464) | func Int64Map(result interface{}, err error) (map[string]int64, error) {
  function Float64Map (line 487) | func Float64Map(result interface{}, err error) (map[string]float64, erro...
  function Positions (line 509) | func Positions(result interface{}, err error) ([]*[2]float64, error) {
  function Uint64s (line 548) | func Uint64s(reply interface{}, err error) ([]uint64, error) {
  function Uint64Map (line 571) | func Uint64Map(result interface{}, err error) (map[string]uint64, error) {
  function SlowLogs (line 593) | func SlowLogs(result interface{}, err error) ([]SlowLog, error) {
  function Latencies (line 651) | func Latencies(result interface{}, err error) ([]Latency, error) {
  function LatencyHistories (line 703) | func LatencyHistories(result interface{}, err error) ([]LatencyHistory, ...

FILE: redis/reply_test.go
  type valueError (line 33) | type valueError struct
  function ve (line 38) | func ve(v interface{}, err error) valueError {
  function getSlowLog (line 154) | func getSlowLog() (redis.SlowLog, error) {
  function TestReply (line 162) | func TestReply(t *testing.T) {
  function TestSlowLog (line 174) | func TestSlowLog(t *testing.T) {
  function TestLatency (line 229) | func TestLatency(t *testing.T) {
  function TestLatencyHistories (line 280) | func TestLatencyHistories(t *testing.T) {
  function dial (line 327) | func dial() (redis.Conn, error) {
  function serverAddr (line 332) | func serverAddr() (string, error) {
  function ExampleBool (line 336) | func ExampleBool() {
  function ExampleInt (line 359) | func ExampleInt() {
  function ExampleInts (line 389) | func ExampleInts() {
  function ExampleString (line 412) | func ExampleString() {

FILE: redis/scan.go
  function ensureLen (line 30) | func ensureLen(d reflect.Value, n int) {
  function cannotConvert (line 38) | func cannotConvert(d reflect.Value, s interface{}) error {
  function convertAssignNil (line 59) | func convertAssignNil(d reflect.Value) (err error) {
  function convertAssignError (line 69) | func convertAssignError(d reflect.Value, s Error) (err error) {
  function convertAssignString (line 80) | func convertAssignString(d reflect.Value, s string) (err error) {
  function convertAssignBulkString (line 114) | func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
  function convertAssignInt (line 150) | func convertAssignInt(d reflect.Value, s int64) (err error) {
  function convertAssignValue (line 177) | func convertAssignValue(d reflect.Value, s interface{}) (err error) {
  function convertAssignArray (line 214) | func convertAssignArray(d reflect.Value, s []interface{}) error {
  function convertAssign (line 227) | func convertAssign(d interface{}, s interface{}) (err error) {
  function Scan (line 328) | func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
  type fieldSpec (line 343) | type fieldSpec struct
  type structSpec (line 349) | type structSpec struct
    method fieldSpec (line 354) | func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
  function compileStructSpec (line 358) | func compileStructSpec(t reflect.Type, depth map[string]int, index []int...
  function structSpecForType (line 449) | func structSpecForType(t reflect.Type) (*structSpec, error) {
  function ScanStruct (line 490) | func ScanStruct(src []interface{}, dest interface{}) error {
  function convertToBulk (line 535) | func convertToBulk(src interface{}) ([]byte, bool) {
  function ScanSlice (line 560) | func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string...
  type Args (line 639) | type Args
    method Add (line 642) | func (args Args) Add(value ...interface{}) Args {
    method AddFlat (line 659) | func (args Args) AddFlat(v interface{}) Args {
  function flattenStruct (line 686) | func flattenStruct(args Args, v reflect.Value) Args {

FILE: redis/scan_test.go
  type durationScan (line 29) | type durationScan struct
    method RedisScan (line 33) | func (t *durationScan) RedisScan(src interface{}) (err error) {
  function TestScanConversion (line 114) | func TestScanConversion(t *testing.T) {
  function TestScanConversionError (line 143) | func TestScanConversionError(t *testing.T) {
  function ExampleScan (line 154) | func ExampleScan() {
  type s0 (line 215) | type s0 struct
  type s1 (line 221) | type s1 struct
  function TestScanStruct (line 314) | func TestScanStruct(t *testing.T) {
  function TestScanStructStringKeys (line 330) | func TestScanStructStringKeys(t *testing.T) {
  function TestBadScanStructArgs (line 346) | func TestBadScanStructArgs(t *testing.T) {
  type sliceScanner (line 367) | type sliceScanner struct
    method RedisScan (line 371) | func (ss *sliceScanner) RedisScan(s interface{}) error {
  function TestScanSlice (line 458) | func TestScanSlice(t *testing.T) {
  function ExampleScanSlice (line 475) | func ExampleScanSlice() {
  type Ed (line 529) | type Ed struct
  type Edp (line 533) | type Edp struct
  type Ed2 (line 537) | type Ed2 struct
  type Edp2 (line 542) | type Edp2 struct
  type Edpr1 (line 547) | type Edpr1 struct
  type Edpr2 (line 552) | type Edpr2 struct
  function TestArgs (line 705) | func TestArgs(t *testing.T) {
  type CustomTimePtr (line 717) | type CustomTimePtr struct
    method RedisArg (line 721) | func (t *CustomTimePtr) RedisArg() interface{} {
  type CustomTime (line 725) | type CustomTime struct
    method RedisArg (line 729) | func (t CustomTime) RedisArg() interface{} {
  type InnerStruct (line 733) | type InnerStruct struct
    method RedisScan (line 737) | func (f *InnerStruct) RedisScan(src interface{}) (err error) {
  type OuterStruct (line 749) | type OuterStruct struct
  function TestScanPtrRedisScan (line 753) | func TestScanPtrRedisScan(t *testing.T) {
  function ExampleArgs (line 795) | func ExampleArgs() {

FILE: redis/script.go
  type Script (line 27) | type Script struct
    method args (line 44) | func (s *Script) args(spec string, keysAndArgs []interface{}) []interf...
    method Hash (line 60) | func (s *Script) Hash() string {
    method DoContext (line 64) | func (s *Script) DoContext(ctx context.Context, c Conn, keysAndArgs .....
    method Do (line 80) | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, ...
    method SendHash (line 91) | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
    method Send (line 96) | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
    method Load (line 101) | func (s *Script) Load(c Conn) error {
  function NewScript (line 38) | func NewScript(keyCount int, src string) *Script {

FILE: redis/script_test.go
  function ExampleScript (line 34) | func ExampleScript() {
  function TestScript (line 44) | func TestScript(t *testing.T) {

FILE: redis/test_test.go
  function SetNowFunc (line 35) | func SetNowFunc(f func() time.Time) {
  type Server (line 53) | type Server struct
    method watch (line 137) | func (s *Server) watch(r io.Reader, ready chan error) {
    method Stop (line 165) | func (s *Server) Stop() {
  type version (line 59) | type version struct
  function redisServerVersion (line 65) | func redisServerVersion() (*version, error) {
  function NewServer (line 94) | func NewServer(name string, args ...string) (*Server, error) {
  function stopDefaultServer (line 171) | func stopDefaultServer() {
  function DefaultServerAddr (line 182) | func DefaultServerAddr() (string, error) {
  function DialDefaultServer (line 200) | func DialDefaultServer(options ...DialOption) (Conn, error) {
  function DialDefaultServerContext (line 206) | func DialDefaultServerContext(ctx context.Context, options ...DialOption...
  function TestMain (line 221) | func TestMain(m *testing.M) {

FILE: redis/zpop_example_test.go
  function zpop (line 24) | func zpop(c redis.Conn, key string) (result string, err error) {
  function Example_zpop (line 79) | func Example_zpop() {

FILE: redisx/commandinfo.go
  type commandInfo (line 21) | type commandInfo struct
  function init (line 36) | func init() {
  function lookupCommandInfo (line 42) | func lookupCommandInfo(commandName string) commandInfo {

FILE: redisx/commandinfo_test.go
  function TestLookupCommandInfo (line 5) | func TestLookupCommandInfo(t *testing.T) {

FILE: redisx/connmux.go
  type ConnMux (line 28) | type ConnMux struct
    method Get (line 44) | func (p *ConnMux) Get() redis.Conn {
    method Close (line 51) | func (p *ConnMux) Close() error {
  function NewConnMux (line 39) | func NewConnMux(c redis.Conn) *ConnMux {
  type muxConn (line 55) | type muxConn struct
    method send (line 61) | func (c *muxConn) send(flush bool, cmd string, args ...interface{}) er...
    method Send (line 78) | func (c *muxConn) Send(cmd string, args ...interface{}) error {
    method Flush (line 82) | func (c *muxConn) Flush() error {
    method Receive (line 90) | func (c *muxConn) Receive() (interface{}, error) {
    method Close (line 130) | func (c *muxConn) Close() error {
    method Do (line 142) | func (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, er...
    method Err (line 149) | func (c *muxConn) Err() error {

FILE: redisx/connmux_test.go
  function TestConnMux (line 27) | func TestConnMux(t *testing.T) {
  function TestConnMuxClose (line 61) | func TestConnMuxClose(t *testing.T) {
  function BenchmarkConn (line 96) | func BenchmarkConn(b *testing.B) {
  function BenchmarkConnMux (line 112) | func BenchmarkConnMux(b *testing.B) {
  function BenchmarkPool (line 132) | func BenchmarkPool(b *testing.B) {
  constant numConcurrent (line 156) | numConcurrent = 10
  function BenchmarkConnMuxConcurrent (line 158) | func BenchmarkConnMuxConcurrent(b *testing.B) {
  function BenchmarkPoolConcurrent (line 187) | func BenchmarkPoolConcurrent(b *testing.B) {
  function BenchmarkPipelineConcurrency (line 225) | func BenchmarkPipelineConcurrency(b *testing.B) {

FILE: redisx/db_test.go
  type testConn (line 25) | type testConn struct
    method Close (line 29) | func (t testConn) Close() error {
  function DialTest (line 44) | func DialTest() (redis.Conn, error) {
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (275K chars).
[
  {
    "path": ".clog.toml",
    "chars": 352,
    "preview": "[clog]\nrepository = \"https://github.com/gomodule/redigo\"\nsubtitle = \"Release Notes\"\nfrom-latest-tag = true\n\n[sections]\n\""
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 208,
    "preview": "Ask questions at\n[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).\n\n[Open an issue](https://github"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 71,
    "preview": "Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis\n"
  },
  {
    "path": ".github/workflows/go-test.yml",
    "chars": 809,
    "preview": "name: go-test\non:\n  push:\n    tags:\n      - v*\n    branches:\n      - master\n  pull_request:\njobs:\n  build:\n    runs-on: "
  },
  {
    "path": ".github/workflows/golangci-lint.yml",
    "chars": 700,
    "preview": "name: golangci-lint\non:\n  push:\n    tags:\n      - v*\n    branches:\n      - master\n  pull_request:\njobs:\n  golangci:\n    "
  },
  {
    "path": ".github/workflows/release-build.yml",
    "chars": 640,
    "preview": "name: Build Release\n\non:\n  push:\n    tags:\n      - 'v[0-9]+.[0-9]+.[0-9]+*'\n\npermissions:\n  contents: write\n\njobs:\n  gor"
  },
  {
    "path": ".goreleaser.yaml",
    "chars": 1441,
    "preview": "# When adding options check the documentation at https://goreleaser.com\nbuilds:\n  - skip: true\nrelease:\n  header: |\n    "
  },
  {
    "path": ".travis.yml",
    "chars": 265,
    "preview": "language: go\nservices:\n  - redis-server\n\ngo:\n  - 1.13.x\n  - 1.14.x\n  - master\n\nmatrix:\n  allow_failures:\n    - go: maste"
  },
  {
    "path": "LICENSE",
    "chars": 10174,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.markdown",
    "chars": 2294,
    "preview": "Redigo\n======\n\n[![GoDoc](https://godoc.org/github.com/gomodule/redigo/redis?status.svg)](https://pkg.go.dev/github.com/g"
  },
  {
    "path": "go.mod",
    "chars": 441,
    "preview": "module github.com/gomodule/redigo\n\ngo 1.17\n\nrequire github.com/stretchr/testify v1.8.4\n\nrequire (\n\tgithub.com/davecgh/go"
  },
  {
    "path": "go.sum",
    "chars": 1518,
    "preview": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1"
  },
  {
    "path": "redis/commandinfo.go",
    "chars": 1558,
    "preview": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/commandinfo_test.go",
    "chars": 689,
    "preview": "package redis\n\nimport \"testing\"\n\nfunc TestLookupCommandInfo(t *testing.T) {\n\tfor _, n := range []string{\"watch\", \"WATCH\""
  },
  {
    "path": "redis/conn.go",
    "chars": 21727,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/conn_test.go",
    "chars": 28054,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/doc.go",
    "chars": 7701,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/list_test.go",
    "chars": 1822,
    "preview": "// Copyright 2018 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/log.go",
    "chars": 4255,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/pool.go",
    "chars": 18419,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/pool_test.go",
    "chars": 25581,
    "preview": "// Copyright 2011 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/pubsub.go",
    "chars": 4510,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/pubsub_example_test.go",
    "chars": 4321,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/pubsub_test.go",
    "chars": 3224,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/redis.go",
    "chars": 8162,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/redis_test.go",
    "chars": 4009,
    "preview": "// Copyright 2017 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/reflect.go",
    "chars": 1050,
    "preview": "package redis\n\nimport (\n\t\"reflect\"\n\t\"runtime\"\n)\n\n// methodName returns the name of the calling method,\n// assumed to be "
  },
  {
    "path": "redis/reflect_go117.go",
    "chars": 804,
    "preview": "//go:build !go1.18\n// +build !go1.18\n\npackage redis\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n)\n\n// fieldByIndexErr returns the nes"
  },
  {
    "path": "redis/reflect_go118.go",
    "chars": 402,
    "preview": "//go:build go1.18\n// +build go1.18\n\npackage redis\n\nimport (\n\t\"reflect\"\n)\n\n// fieldByIndexErr returns the nested field co"
  },
  {
    "path": "redis/reply.go",
    "chars": 20628,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/reply_test.go",
    "chars": 11290,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/scan.go",
    "chars": 17625,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/scan_test.go",
    "chars": 17971,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/script.go",
    "chars": 3498,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/script_test.go",
    "chars": 2790,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/test_test.go",
    "chars": 5792,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redis/zpop_example_test.go",
    "chars": 2523,
    "preview": "// Copyright 2013 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redisx/commandinfo.go",
    "chars": 1264,
    "preview": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redisx/commandinfo_test.go",
    "chars": 271,
    "preview": "package redisx\n\nimport \"testing\"\n\nfunc TestLookupCommandInfo(t *testing.T) {\n\tfor _, n := range []string{\"watch\", \"WATCH"
  },
  {
    "path": "redisx/connmux.go",
    "chars": 3053,
    "preview": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redisx/connmux_test.go",
    "chars": 4865,
    "preview": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redisx/db_test.go",
    "chars": 1663,
    "preview": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  },
  {
    "path": "redisx/doc.go",
    "chars": 731,
    "preview": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use thi"
  }
]

About this extraction

This page contains the full source code of the gomodule/redigo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (243.3 KB), approximately 73.2k tokens, and a symbol index with 420 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!