[
  {
    "path": ".clog.toml",
    "content": "[clog]\nrepository = \"https://github.com/gomodule/redigo\"\nsubtitle = \"Release Notes\"\nfrom-latest-tag = true\n\n[sections]\n\"Refactors\" = [\"refactor\"]\n\"Chores\" = [\"chore\"]\n\"Continuous Integration\" = [\"ci\"]\n\"Improvements\" = [\"imp\", \"improvement\"]\n\"Features\" = [\"feat\", \"feature\"]\n\"Legacy\" = [\"legacy\"]\n\"QA\" = [\"qa\", \"test\"]\n\"Documentation\" = [\"doc\", \"docs\"]\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "Ask questions at\n[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).\n\n[Open an issue](https://github.com/gomodule/redigo/issues/new) to discuss your\nplans before doing any work on Redigo.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis\n"
  },
  {
    "path": ".github/workflows/go-test.yml",
    "content": "name: go-test\non:\n  push:\n    tags:\n      - v*\n    branches:\n      - master\n  pull_request:\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        go-version:\n        - '1.21'\n        - '1.22'\n        os:\n        - 'ubuntu-latest'\n        redis:\n        - '7.2'\n        - '7.0'\n        - '6.2'\n        - '6.0'\n    name: Test go ${{ matrix.go-version }} redis ${{ matrix.redis }} on ${{ matrix.os }}\n    steps:\n      - name: Setup redis\n        uses: shogo82148/actions-setup-redis@v1\n        with:\n          redis-version: ${{ matrix.redis }}\n\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go-version }}\n\n      - name: Go Test\n        run: go test -race ./...\n"
  },
  {
    "path": ".github/workflows/golangci-lint.yml",
    "content": "name: golangci-lint\non:\n  push:\n    tags:\n      - v*\n    branches:\n      - master\n  pull_request:\njobs:\n  golangci:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Setup golang\n        uses: actions/setup-go@v4\n        with:\n          go-version: '1.22'\n          cache: false # Handled by golangci-lint.\n\n      - name: Validate go mod\n        run: |\n          go mod tidy\n          git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]]\n\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v4\n        with:\n          version: v1.56.2\n          args: --out-format=colored-line-number\n"
  },
  {
    "path": ".github/workflows/release-build.yml",
    "content": "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  goreleaser:\n    name: Release Go Binary\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '1.22'\n\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v5\n        with:\n          distribution: goreleaser\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".goreleaser.yaml",
    "content": "# When adding options check the documentation at https://goreleaser.com\nbuilds:\n  - skip: true\nrelease:\n  header: |\n    <a name='{{.Tag}}'></a>\n    ### {{.Tag}} Release Notes ({{.Date}})\n  footer: |\n    [Full Changelog](https://{{ .ModulePath }}/compare/{{ .PreviousTag }}...{{ .Tag }})\nchangelog:\n  use: github\n  sort: asc\n  filters:\n    exclude:\n    - Merge pull request\n    - Merge remote-tracking branch\n    - Merge branch\n\n  # Group commits messages by given regex and title.\n  # Order value defines the order of the groups.\n  # Proving no regex means all commits will be grouped under the default group.\n  # Groups are disabled when using github-native, as it already groups things by itself.\n  # Matches are performed against strings of the form: \"<abbrev-commit>[:] <title-commit>\".\n  #\n  # Default is no groups.\n  groups:\n    - title: Features\n      regexp: '^.*?(feat|feature)(\\([[:word:]]+\\))??!?:.+$'\n      order: 0\n    - title: 'Bug fixes'\n      regexp: '^.*?fix(\\([[:word:]]+\\))??!?:.+$'\n      order: 1\n    - title: 'Chores'\n      regexp: '^.*?chore(\\([[:word:]]+\\))??!?:.+$'\n      order: 2\n    - title: 'Quality'\n      regexp: '^.*?(qa|test|tests)(\\([[:word:]]+\\))??!?:.+$'\n      order: 3\n    - title: 'Documentation'\n      regexp: '^.*?(doc|docs)(\\([[:word:]]+\\))??!?:.+$'\n      order: 4\n    - title: 'Continuous Integration'\n      regexp: '^.*?ci(\\([[:word:]]+\\))??!?:.+$'\n      order: 5\n    - title: Other\n      order: 999\n"
  },
  {
    "path": ".travis.yml",
    "content": "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: master\n\nscript:\n  - go get -t -v ./...\n  - diff -u <(echo -n) <(gofmt -d .)\n  - go vet $(go list ./... | grep -v /vendor/)\n  - go test -v -race ./...\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.markdown",
    "content": "Redigo\n======\n\n[![GoDoc](https://godoc.org/github.com/gomodule/redigo/redis?status.svg)](https://pkg.go.dev/github.com/gomodule/redigo/redis)\n\nRedigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) / [Valkey](https://github.com/valkey-io/valkey) database.\n\nFeatures\n-------\n\n* A [Print-like](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.\n* [Pipelining](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Pipelining), including pipelined transactions.\n* [Publish/Subscribe](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Publish_and_Subscribe).\n* [Connection pooling](https://pkg.go.dev/github.com/gomodule/redigo/redis#Pool).\n* [Script helper type](https://pkg.go.dev/github.com/gomodule/redigo/redis#Script) with optimistic use of EVALSHA.\n* [Helper functions](https://pkg.go.dev/github.com/gomodule/redigo/redis#hdr-Reply_Helpers) for working with command replies.\n\nDocumentation\n-------------\n\n- [API Reference](https://pkg.go.dev/github.com/gomodule/redigo/redis)\n- [FAQ](https://github.com/gomodule/redigo/wiki/FAQ)\n- [Examples](https://pkg.go.dev/github.com/gomodule/redigo/redis#pkg-examples)\n\nInstallation\n------------\n\nInstall Redigo using the \"go get\" command:\n\n    go get github.com/gomodule/redigo/redis\n\nThe Go distribution is Redigo's only dependency.\n\nRelated Projects\n----------------\n\n- [rafaeljusto/redigomock](https://pkg.go.dev/github.com/rafaeljusto/redigomock) - A mock library for Redigo.\n- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation.\n- [FZambia/sentinel](https://github.com/FZambia/sentinel) - Redis Sentinel support for Redigo\n- [mna/redisc](https://github.com/mna/redisc) - Redis Cluster client built on top of Redigo\n- [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\n\nContributing\n------------\n\nSee [CONTRIBUTING.md](https://github.com/gomodule/redigo/blob/master/.github/CONTRIBUTING.md).\n\nLicense\n-------\n\nRedigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).\n"
  },
  {
    "path": "go.mod",
    "content": "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-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nretract (\n\tv2.0.0+incompatible // Old development version not maintained or published.\n\tv1.8.10 // Incorrect version tag for feature.\n\tv0.0.0-do-not-use // Never used only present due to lack of retract.\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "redis/commandinfo.go",
    "content": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\tconnectionWatchState = 1 << iota\n\tconnectionMultiState\n\tconnectionSubscribeState\n\tconnectionMonitorState\n)\n\ntype commandInfo struct {\n\t// Set or Clear these states on connection.\n\tSet, Clear int\n}\n\nvar commandInfos = map[string]commandInfo{\n\t\"WATCH\":      {Set: connectionWatchState},\n\t\"UNWATCH\":    {Clear: connectionWatchState},\n\t\"MULTI\":      {Set: connectionMultiState},\n\t\"EXEC\":       {Clear: connectionWatchState | connectionMultiState},\n\t\"DISCARD\":    {Clear: connectionWatchState | connectionMultiState},\n\t\"PSUBSCRIBE\": {Set: connectionSubscribeState},\n\t\"SUBSCRIBE\":  {Set: connectionSubscribeState},\n\t\"MONITOR\":    {Set: connectionMonitorState},\n}\n\nfunc init() {\n\tfor n, ci := range commandInfos {\n\t\tcommandInfos[strings.ToLower(n)] = ci\n\t}\n}\n\nfunc lookupCommandInfo(commandName string) commandInfo {\n\tif ci, ok := commandInfos[commandName]; ok {\n\t\treturn ci\n\t}\n\treturn commandInfos[strings.ToUpper(commandName)]\n}\n"
  },
  {
    "path": "redis/commandinfo_test.go",
    "content": "package redis\n\nimport \"testing\"\n\nfunc TestLookupCommandInfo(t *testing.T) {\n\tfor _, n := range []string{\"watch\", \"WATCH\", \"wAtch\"} {\n\t\tif lookupCommandInfo(n) == (commandInfo{}) {\n\t\t\tt.Errorf(\"LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value\", n)\n\t\t}\n\t}\n}\n\nfunc benchmarkLookupCommandInfo(b *testing.B, names ...string) {\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, c := range names {\n\t\t\tlookupCommandInfo(c)\n\t\t}\n\t}\n}\n\nfunc BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {\n\tbenchmarkLookupCommandInfo(b, \"watch\", \"WATCH\", \"monitor\", \"MONITOR\")\n}\n\nfunc BenchmarkLookupCommandInfoMixedCase(b *testing.B) {\n\tbenchmarkLookupCommandInfo(b, \"wAtch\", \"WeTCH\", \"monItor\", \"MONiTOR\")\n}\n"
  },
  {
    "path": "redis/conn.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\t_ ConnWithTimeout = (*conn)(nil)\n)\n\n// conn is the low-level implementation of Conn\ntype conn struct {\n\t// Shared\n\tmu      sync.Mutex\n\tpending int\n\terr     error\n\tconn    net.Conn\n\n\t// Read\n\treadTimeout time.Duration\n\tbr          *bufio.Reader\n\n\t// Write\n\twriteTimeout time.Duration\n\tbw           *bufio.Writer\n\n\t// Scratch space for formatting argument length.\n\t// '*' or '$', length, \"\\r\\n\"\n\tlenScratch [32]byte\n\n\t// Scratch space for formatting integers and floats.\n\tnumScratch [40]byte\n}\n\n// DialTimeout acts like Dial but takes timeouts for establishing the\n// connection to the server, writing a command and reading a reply.\n//\n// Deprecated: Use Dial with options instead.\nfunc DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {\n\treturn Dial(network, address,\n\t\tDialConnectTimeout(connectTimeout),\n\t\tDialReadTimeout(readTimeout),\n\t\tDialWriteTimeout(writeTimeout))\n}\n\n// DialOption specifies an option for dialing a Redis server.\ntype DialOption struct {\n\tf func(*dialOptions)\n}\n\ntype dialOptions struct {\n\treadTimeout         time.Duration\n\twriteTimeout        time.Duration\n\ttlsHandshakeTimeout time.Duration\n\tdialer              *net.Dialer\n\tdialContext         func(ctx context.Context, network, addr string) (net.Conn, error)\n\tdb                  int\n\tusername            string\n\tpassword            string\n\tclientName          string\n\tuseTLS              bool\n\tskipVerify          bool\n\ttlsConfig           *tls.Config\n}\n\n// DialTLSHandshakeTimeout specifies the maximum amount of time waiting to\n// wait for a TLS handshake. Zero means no timeout.\n// If no DialTLSHandshakeTimeout option is specified then the default is 30 seconds.\nfunc DialTLSHandshakeTimeout(d time.Duration) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.tlsHandshakeTimeout = d\n\t}}\n}\n\n// DialReadTimeout specifies the timeout for reading a single command reply.\nfunc DialReadTimeout(d time.Duration) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.readTimeout = d\n\t}}\n}\n\n// DialWriteTimeout specifies the timeout for writing a single command.\nfunc DialWriteTimeout(d time.Duration) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.writeTimeout = d\n\t}}\n}\n\n// DialConnectTimeout specifies the timeout for connecting to the Redis server when\n// no DialNetDial option is specified.\n// If no DialConnectTimeout option is specified then the default is 30 seconds.\nfunc DialConnectTimeout(d time.Duration) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.dialer.Timeout = d\n\t}}\n}\n\n// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server\n// when no DialNetDial option is specified.\n// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then\n// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.\nfunc DialKeepAlive(d time.Duration) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.dialer.KeepAlive = d\n\t}}\n}\n\n// DialNetDial specifies a custom dial function for creating TCP\n// connections, otherwise a net.Dialer customized via the other options is used.\n// DialNetDial overrides DialConnectTimeout and DialKeepAlive.\nfunc DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\treturn dial(network, addr)\n\t\t}\n\t}}\n}\n\n// DialContextFunc specifies a custom dial function with context for creating TCP\n// connections, otherwise a net.Dialer customized via the other options is used.\n// DialContextFunc overrides DialConnectTimeout and DialKeepAlive.\nfunc DialContextFunc(f func(ctx context.Context, network, addr string) (net.Conn, error)) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.dialContext = f\n\t}}\n}\n\n// DialDatabase specifies the database to select when dialing a connection.\nfunc DialDatabase(db int) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.db = db\n\t}}\n}\n\n// DialPassword specifies the password to use when connecting to\n// the Redis server.\nfunc DialPassword(password string) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.password = password\n\t}}\n}\n\n// DialUsername specifies the username to use when connecting to\n// the Redis server when Redis ACLs are used.\n// A DialPassword must also be passed otherwise this option will have no effect.\nfunc DialUsername(username string) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.username = username\n\t}}\n}\n\n// DialClientName specifies a client name to be used\n// by the Redis server connection.\nfunc DialClientName(name string) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.clientName = name\n\t}}\n}\n\n// DialTLSConfig specifies the config to use when a TLS connection is dialed.\n// Has no effect when not dialing a TLS connection.\nfunc DialTLSConfig(c *tls.Config) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.tlsConfig = c\n\t}}\n}\n\n// DialTLSSkipVerify disables server name verification when connecting over\n// TLS. Has no effect when not dialing a TLS connection.\nfunc DialTLSSkipVerify(skip bool) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.skipVerify = skip\n\t}}\n}\n\n// DialUseTLS specifies whether TLS should be used when connecting to the\n// server. This option is ignore by DialURL.\nfunc DialUseTLS(useTLS bool) DialOption {\n\treturn DialOption{func(do *dialOptions) {\n\t\tdo.useTLS = useTLS\n\t}}\n}\n\n// Dial connects to the Redis server at the given network and\n// address using the specified options.\nfunc Dial(network, address string, options ...DialOption) (Conn, error) {\n\treturn DialContext(context.Background(), network, address, options...)\n}\n\ntype tlsHandshakeTimeoutError struct{}\n\nfunc (tlsHandshakeTimeoutError) Timeout() bool   { return true }\nfunc (tlsHandshakeTimeoutError) Temporary() bool { return true }\nfunc (tlsHandshakeTimeoutError) Error() string   { return \"TLS handshake timeout\" }\n\n// DialContext connects to the Redis server at the given network and\n// address using the specified options and context.\nfunc DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) {\n\tdo := dialOptions{\n\t\tdialer: &net.Dialer{\n\t\t\tTimeout:   time.Second * 30,\n\t\t\tKeepAlive: time.Minute * 5,\n\t\t},\n\t\ttlsHandshakeTimeout: time.Second * 10,\n\t}\n\tfor _, option := range options {\n\t\toption.f(&do)\n\t}\n\tif do.dialContext == nil {\n\t\tdo.dialContext = do.dialer.DialContext\n\t}\n\n\tnetConn, err := do.dialContext(ctx, network, address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif do.useTLS {\n\t\tvar tlsConfig *tls.Config\n\t\tif do.tlsConfig == nil {\n\t\t\ttlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify}\n\t\t} else {\n\t\t\ttlsConfig = do.tlsConfig.Clone()\n\t\t}\n\t\tif tlsConfig.ServerName == \"\" {\n\t\t\thost, _, err := net.SplitHostPort(address)\n\t\t\tif err != nil {\n\t\t\t\tnetConn.Close()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ttlsConfig.ServerName = host\n\t\t}\n\n\t\ttlsConn := tls.Client(netConn, tlsConfig)\n\t\terrc := make(chan error, 2) // buffered so we don't block timeout or Handshake\n\t\tif d := do.tlsHandshakeTimeout; d != 0 {\n\t\t\ttimer := time.AfterFunc(d, func() {\n\t\t\t\terrc <- tlsHandshakeTimeoutError{}\n\t\t\t})\n\t\t\tdefer timer.Stop()\n\t\t}\n\t\tgo func() {\n\t\t\terrc <- tlsConn.Handshake()\n\t\t}()\n\t\tif err := <-errc; err != nil {\n\t\t\t// Timeout or Handshake error.\n\t\t\tnetConn.Close() // nolint: errcheck\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnetConn = tlsConn\n\t}\n\n\tc := &conn{\n\t\tconn:         netConn,\n\t\tbw:           bufio.NewWriter(netConn),\n\t\tbr:           bufio.NewReader(netConn),\n\t\treadTimeout:  do.readTimeout,\n\t\twriteTimeout: do.writeTimeout,\n\t}\n\n\tif do.password != \"\" {\n\t\tauthArgs := make([]interface{}, 0, 2)\n\t\tif do.username != \"\" {\n\t\t\tauthArgs = append(authArgs, do.username)\n\t\t}\n\t\tauthArgs = append(authArgs, do.password)\n\t\tif _, err := c.DoContext(ctx, \"AUTH\", authArgs...); err != nil {\n\t\t\tnetConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif do.clientName != \"\" {\n\t\tif _, err := c.DoContext(ctx, \"CLIENT\", \"SETNAME\", do.clientName); err != nil {\n\t\t\tnetConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif do.db != 0 {\n\t\tif _, err := c.DoContext(ctx, \"SELECT\", do.db); err != nil {\n\t\t\tnetConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn c, nil\n}\n\nvar pathDBRegexp = regexp.MustCompile(`/(\\d*)\\z`)\n\n// DialURL wraps DialURLContext using context.Background.\nfunc DialURL(rawurl string, options ...DialOption) (Conn, error) {\n\tctx := context.Background()\n\n\treturn DialURLContext(ctx, rawurl, options...)\n}\n\n// DialURLContext connects to a Redis server at the given URL using the Redis\n// URI scheme. URLs should follow the draft IANA specification for the\n// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).\nfunc DialURLContext(ctx context.Context, rawurl string, options ...DialOption) (Conn, error) {\n\tu, err := url.Parse(rawurl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch u.Scheme {\n\tcase \"redis\", \"rediss\", \"valkey\", \"valkeys\":\n\t\t// valid scheme\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid redis URL scheme: %s\", u.Scheme)\n\t}\n\n\tif u.Opaque != \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid redis URL, url is opaque: %s\", rawurl)\n\t}\n\n\t// As per the IANA draft spec, the host defaults to localhost and\n\t// the port defaults to 6379.\n\thost, port, err := net.SplitHostPort(u.Host)\n\tif err != nil {\n\t\t// assume port is missing\n\t\thost = u.Host\n\t\tport = \"6379\"\n\t}\n\tif host == \"\" {\n\t\thost = \"localhost\"\n\t}\n\taddress := net.JoinHostPort(host, port)\n\n\tif u.User != nil {\n\t\tpassword, isSet := u.User.Password()\n\t\tusername := u.User.Username()\n\t\tif isSet {\n\t\t\tif username != \"\" {\n\t\t\t\t// ACL\n\t\t\t\toptions = append(options, DialUsername(username), DialPassword(password))\n\t\t\t} else {\n\t\t\t\t// requirepass - user-info username:password with blank username\n\t\t\t\toptions = append(options, DialPassword(password))\n\t\t\t}\n\t\t} else if username != \"\" {\n\t\t\t// requirepass - redis-cli compatibility which treats as single arg in user-info as a password\n\t\t\toptions = append(options, DialPassword(username))\n\t\t}\n\t}\n\n\tmatch := pathDBRegexp.FindStringSubmatch(u.Path)\n\tif len(match) == 2 {\n\t\tdb := 0\n\t\tif len(match[1]) > 0 {\n\t\t\tdb, err = strconv.Atoi(match[1])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid database: %s\", u.Path[1:])\n\t\t\t}\n\t\t}\n\t\tif db != 0 {\n\t\t\toptions = append(options, DialDatabase(db))\n\t\t}\n\t} else if u.Path != \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid database: %s\", u.Path[1:])\n\t}\n\n\toptions = append(options, DialUseTLS(u.Scheme == \"rediss\" || u.Scheme == \"valkeys\"))\n\n\treturn DialContext(ctx, \"tcp\", address, options...)\n}\n\n// NewConn returns a new Redigo connection for the given net connection.\nfunc NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {\n\treturn &conn{\n\t\tconn:         netConn,\n\t\tbw:           bufio.NewWriter(netConn),\n\t\tbr:           bufio.NewReader(netConn),\n\t\treadTimeout:  readTimeout,\n\t\twriteTimeout: writeTimeout,\n\t}\n}\n\nfunc (c *conn) Close() error {\n\tc.mu.Lock()\n\terr := c.err\n\tif c.err == nil {\n\t\tc.err = errors.New(\"redigo: closed\")\n\t\terr = c.conn.Close()\n\t}\n\tc.mu.Unlock()\n\treturn err\n}\n\nfunc (c *conn) fatal(err error) error {\n\tc.mu.Lock()\n\tif c.err == nil {\n\t\tc.err = err\n\t\t// Close connection to force errors on subsequent calls and to unblock\n\t\t// other reader or writer.\n\t\tc.conn.Close()\n\t}\n\tc.mu.Unlock()\n\treturn err\n}\n\nfunc (c *conn) Err() error {\n\tc.mu.Lock()\n\terr := c.err\n\tc.mu.Unlock()\n\treturn err\n}\n\nfunc (c *conn) writeLen(prefix byte, n int) error {\n\tc.lenScratch[len(c.lenScratch)-1] = '\\n'\n\tc.lenScratch[len(c.lenScratch)-2] = '\\r'\n\ti := len(c.lenScratch) - 3\n\tfor {\n\t\tc.lenScratch[i] = byte('0' + n%10)\n\t\ti -= 1\n\t\tn = n / 10\n\t\tif n == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\tc.lenScratch[i] = prefix\n\t_, err := c.bw.Write(c.lenScratch[i:])\n\treturn err\n}\n\nfunc (c *conn) writeString(s string) error {\n\tif err := c.writeLen('$', len(s)); err != nil {\n\t\treturn err\n\t}\n\tif _, err := c.bw.WriteString(s); err != nil {\n\t\treturn err\n\t}\n\t_, err := c.bw.WriteString(\"\\r\\n\")\n\treturn err\n}\n\nfunc (c *conn) writeBytes(p []byte) error {\n\tif err := c.writeLen('$', len(p)); err != nil {\n\t\treturn err\n\t}\n\tif _, err := c.bw.Write(p); err != nil {\n\t\treturn err\n\t}\n\t_, err := c.bw.WriteString(\"\\r\\n\")\n\treturn err\n}\n\nfunc (c *conn) writeInt64(n int64) error {\n\treturn c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))\n}\n\nfunc (c *conn) writeFloat64(n float64) error {\n\treturn c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))\n}\n\nfunc (c *conn) writeCommand(cmd string, args []interface{}) error {\n\tif err := c.writeLen('*', 1+len(args)); err != nil {\n\t\treturn err\n\t}\n\tif err := c.writeString(cmd); err != nil {\n\t\treturn err\n\t}\n\tfor _, arg := range args {\n\t\tif err := c.writeArg(arg, true); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {\n\tswitch arg := arg.(type) {\n\tcase string:\n\t\treturn c.writeString(arg)\n\tcase []byte:\n\t\treturn c.writeBytes(arg)\n\tcase int:\n\t\treturn c.writeInt64(int64(arg))\n\tcase int64:\n\t\treturn c.writeInt64(arg)\n\tcase float64:\n\t\treturn c.writeFloat64(arg)\n\tcase bool:\n\t\tif arg {\n\t\t\treturn c.writeString(\"1\")\n\t\t} else {\n\t\t\treturn c.writeString(\"0\")\n\t\t}\n\tcase nil:\n\t\treturn c.writeString(\"\")\n\tcase Argument:\n\t\tif argumentTypeOK {\n\t\t\treturn c.writeArg(arg.RedisArg(), false)\n\t\t}\n\t\t// See comment in default clause below.\n\t\tvar buf bytes.Buffer\n\t\tfmt.Fprint(&buf, arg)\n\t\treturn c.writeBytes(buf.Bytes())\n\tdefault:\n\t\t// This default clause is intended to handle builtin numeric types.\n\t\t// The function should return an error for other types, but this is not\n\t\t// done for compatibility with previous versions of the package.\n\t\tvar buf bytes.Buffer\n\t\tfmt.Fprint(&buf, arg)\n\t\treturn c.writeBytes(buf.Bytes())\n\t}\n}\n\ntype protocolError string\n\nfunc (pe protocolError) Error() string {\n\treturn fmt.Sprintf(\"redigo: %s (possible server error or unsupported concurrent read by application)\", string(pe))\n}\n\n// readLine reads a line of input from the RESP stream.\nfunc (c *conn) readLine() ([]byte, error) {\n\t// To avoid allocations, attempt to read the line using ReadSlice. This\n\t// call typically succeeds. The known case where the call fails is when\n\t// reading the output from the MONITOR command.\n\tp, err := c.br.ReadSlice('\\n')\n\tif err == bufio.ErrBufferFull {\n\t\t// The line does not fit in the bufio.Reader's buffer. Fall back to\n\t\t// allocating a buffer for the line.\n\t\tbuf := append([]byte{}, p...)\n\t\tfor err == bufio.ErrBufferFull {\n\t\t\tp, err = c.br.ReadSlice('\\n')\n\t\t\tbuf = append(buf, p...)\n\t\t}\n\t\tp = buf\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ti := len(p) - 2\n\tif i < 0 || p[i] != '\\r' {\n\t\treturn nil, protocolError(\"bad response line terminator\")\n\t}\n\treturn p[:i], nil\n}\n\n// parseLen parses bulk string and array lengths.\nfunc parseLen(p []byte) (int, error) {\n\tif len(p) == 0 {\n\t\treturn -1, protocolError(\"malformed length\")\n\t}\n\n\tif p[0] == '-' && len(p) == 2 && p[1] == '1' {\n\t\t// handle $-1 and $-1 null replies.\n\t\treturn -1, nil\n\t}\n\n\tvar n int\n\tfor _, b := range p {\n\t\tn *= 10\n\t\tif b < '0' || b > '9' {\n\t\t\treturn -1, protocolError(\"illegal bytes in length\")\n\t\t}\n\t\tn += int(b - '0')\n\t}\n\n\treturn n, nil\n}\n\n// parseInt parses an integer reply.\nfunc parseInt(p []byte) (interface{}, error) {\n\tif len(p) == 0 {\n\t\treturn 0, protocolError(\"malformed integer\")\n\t}\n\n\tvar negate bool\n\tif p[0] == '-' {\n\t\tnegate = true\n\t\tp = p[1:]\n\t\tif len(p) == 0 {\n\t\t\treturn 0, protocolError(\"malformed integer\")\n\t\t}\n\t}\n\n\tvar n int64\n\tfor _, b := range p {\n\t\tn *= 10\n\t\tif b < '0' || b > '9' {\n\t\t\treturn 0, protocolError(\"illegal bytes in length\")\n\t\t}\n\t\tn += int64(b - '0')\n\t}\n\n\tif negate {\n\t\tn = -n\n\t}\n\treturn n, nil\n}\n\nvar (\n\tokReply   interface{} = \"OK\"\n\tpongReply interface{} = \"PONG\"\n)\n\nfunc (c *conn) readReply() (interface{}, error) {\n\tline, err := c.readLine()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(line) == 0 {\n\t\treturn nil, protocolError(\"short response line\")\n\t}\n\tswitch line[0] {\n\tcase '+':\n\t\tswitch string(line[1:]) {\n\t\tcase \"OK\":\n\t\t\t// Avoid allocation for frequent \"+OK\" response.\n\t\t\treturn okReply, nil\n\t\tcase \"PONG\":\n\t\t\t// Avoid allocation in PING command benchmarks :)\n\t\t\treturn pongReply, nil\n\t\tdefault:\n\t\t\treturn string(line[1:]), nil\n\t\t}\n\tcase '-':\n\t\treturn Error(line[1:]), nil\n\tcase ':':\n\t\treturn parseInt(line[1:])\n\tcase '$':\n\t\tn, err := parseLen(line[1:])\n\t\tif n < 0 || err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tp := make([]byte, n)\n\t\t_, err = io.ReadFull(c.br, p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif line, err := c.readLine(); err != nil {\n\t\t\treturn nil, err\n\t\t} else if len(line) != 0 {\n\t\t\treturn nil, protocolError(\"bad bulk string format\")\n\t\t}\n\t\treturn p, nil\n\tcase '*':\n\t\tn, err := parseLen(line[1:])\n\t\tif n < 0 || err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr := make([]interface{}, n)\n\t\tfor i := range r {\n\t\t\tr[i], err = c.readReply()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn r, nil\n\t}\n\treturn nil, protocolError(\"unexpected response line\")\n}\n\nfunc (c *conn) Send(cmd string, args ...interface{}) error {\n\tc.mu.Lock()\n\tc.pending += 1\n\tc.mu.Unlock()\n\tif c.writeTimeout != 0 {\n\t\tif err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {\n\t\t\treturn c.fatal(err)\n\t\t}\n\t}\n\tif err := c.writeCommand(cmd, args); err != nil {\n\t\treturn c.fatal(err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) Flush() error {\n\tif c.writeTimeout != 0 {\n\t\tif err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {\n\t\t\treturn c.fatal(err)\n\t\t}\n\t}\n\tif err := c.bw.Flush(); err != nil {\n\t\treturn c.fatal(err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) Receive() (interface{}, error) {\n\treturn c.ReceiveWithTimeout(c.readTimeout)\n}\n\nfunc (c *conn) ReceiveContext(ctx context.Context) (interface{}, error) {\n\tvar realTimeout time.Duration\n\tif dl, ok := ctx.Deadline(); ok {\n\t\ttimeout := time.Until(dl)\n\t\tif timeout >= c.readTimeout && c.readTimeout != 0 {\n\t\t\trealTimeout = c.readTimeout\n\t\t} else if timeout <= 0 {\n\t\t\treturn nil, c.fatal(context.DeadlineExceeded)\n\t\t} else {\n\t\t\trealTimeout = timeout\n\t\t}\n\t} else {\n\t\trealTimeout = c.readTimeout\n\t}\n\tendch := make(chan struct{})\n\tvar r interface{}\n\tvar e error\n\tgo func() {\n\t\tdefer close(endch)\n\n\t\tr, e = c.ReceiveWithTimeout(realTimeout)\n\t}()\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, c.fatal(ctx.Err())\n\tcase <-endch:\n\t\treturn r, e\n\t}\n}\n\nfunc (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {\n\tvar deadline time.Time\n\tif timeout != 0 {\n\t\tdeadline = time.Now().Add(timeout)\n\t}\n\tif err := c.conn.SetReadDeadline(deadline); err != nil {\n\t\treturn nil, c.fatal(err)\n\t}\n\n\tif reply, err = c.readReply(); err != nil {\n\t\treturn nil, c.fatal(err)\n\t}\n\t// When using pub/sub, the number of receives can be greater than the\n\t// number of sends. To enable normal use of the connection after\n\t// unsubscribing from all channels, we do not decrement pending to a\n\t// negative value.\n\t//\n\t// The pending field is decremented after the reply is read to handle the\n\t// case where Receive is called before Send.\n\tc.mu.Lock()\n\tif c.pending > 0 {\n\t\tc.pending -= 1\n\t}\n\tc.mu.Unlock()\n\tif err, ok := reply.(Error); ok {\n\t\treturn nil, err\n\t}\n\treturn\n}\n\nfunc (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {\n\treturn c.DoWithTimeout(c.readTimeout, cmd, args...)\n}\n\nfunc (c *conn) DoContext(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) {\n\tvar realTimeout time.Duration\n\tif dl, ok := ctx.Deadline(); ok {\n\t\ttimeout := time.Until(dl)\n\t\tif timeout >= c.readTimeout && c.readTimeout != 0 {\n\t\t\trealTimeout = c.readTimeout\n\t\t} else if timeout <= 0 {\n\t\t\treturn nil, c.fatal(context.DeadlineExceeded)\n\t\t} else {\n\t\t\trealTimeout = timeout\n\t\t}\n\t} else {\n\t\trealTimeout = c.readTimeout\n\t}\n\tendch := make(chan struct{})\n\tvar r interface{}\n\tvar e error\n\tgo func() {\n\t\tdefer close(endch)\n\n\t\tr, e = c.DoWithTimeout(realTimeout, cmd, args...)\n\t}()\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, c.fatal(ctx.Err())\n\tcase <-endch:\n\t\treturn r, e\n\t}\n}\n\nfunc (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {\n\tc.mu.Lock()\n\tpending := c.pending\n\tc.pending = 0\n\tc.mu.Unlock()\n\n\tif cmd == \"\" && pending == 0 {\n\t\treturn nil, nil\n\t}\n\n\tif c.writeTimeout != 0 {\n\t\tif err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {\n\t\t\treturn nil, c.fatal(err)\n\t\t}\n\t}\n\n\tif cmd != \"\" {\n\t\tif err := c.writeCommand(cmd, args); err != nil {\n\t\t\treturn nil, c.fatal(err)\n\t\t}\n\t}\n\n\tif err := c.bw.Flush(); err != nil {\n\t\treturn nil, c.fatal(err)\n\t}\n\n\tvar deadline time.Time\n\tif readTimeout != 0 {\n\t\tdeadline = time.Now().Add(readTimeout)\n\t}\n\tif err := c.conn.SetReadDeadline(deadline); err != nil {\n\t\treturn nil, c.fatal(err)\n\t}\n\n\tif cmd == \"\" {\n\t\treply := make([]interface{}, pending)\n\t\tfor i := range reply {\n\t\t\tr, e := c.readReply()\n\t\t\tif e != nil {\n\t\t\t\treturn nil, c.fatal(e)\n\t\t\t}\n\t\t\treply[i] = r\n\t\t}\n\t\treturn reply, nil\n\t}\n\n\tvar err error\n\tvar reply interface{}\n\tfor i := 0; i <= pending; i++ {\n\t\tvar e error\n\t\tif reply, e = c.readReply(); e != nil {\n\t\t\treturn nil, c.fatal(e)\n\t\t}\n\t\tif e, ok := reply.(Error); ok && err == nil {\n\t\t\terr = e\n\t\t}\n\t}\n\treturn reply, err\n}\n"
  },
  {
    "path": "redis/conn_test.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net\"\n\t\"sync\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype testConn struct {\n\tio.Reader\n\tio.Writer\n\treadDeadline  time.Time\n\twriteDeadline time.Time\n}\n\nfunc (*testConn) Close() error         { return nil }\nfunc (*testConn) LocalAddr() net.Addr  { return nil }\nfunc (*testConn) RemoteAddr() net.Addr { return nil }\nfunc (c *testConn) SetDeadline(t time.Time) error {\n\tc.readDeadline = t\n\tc.writeDeadline = t\n\treturn nil\n}\nfunc (c *testConn) SetReadDeadline(t time.Time) error  { c.readDeadline = t; return nil }\nfunc (c *testConn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t; return nil }\n\nfunc dialTestConn(r string, w io.Writer) redis.DialOption {\n\treturn redis.DialNetDial(func(network, addr string) (net.Conn, error) {\n\t\treturn &testConn{Reader: strings.NewReader(r), Writer: w}, nil\n\t})\n}\n\ntype tlsTestConn struct {\n\tnet.Conn\n\tdone chan struct{}\n}\n\nfunc (c *tlsTestConn) Close() error {\n\tc.Conn.Close()\n\t<-c.done\n\treturn nil\n}\n\nfunc dialTestConnTLS(r string, w io.Writer) redis.DialOption {\n\treturn redis.DialNetDial(func(network, addr string) (net.Conn, error) {\n\t\tclient, server := net.Pipe()\n\t\ttlsServer := tls.Server(server, &serverTLSConfig)\n\t\tgo io.Copy(tlsServer, strings.NewReader(r)) // nolint: errcheck\n\t\tdone := make(chan struct{})\n\t\tgo func() {\n\t\t\tio.Copy(w, tlsServer) // nolint: errcheck\n\t\t\tclose(done)\n\t\t}()\n\t\treturn &tlsTestConn{Conn: client, done: done}, nil\n\t})\n}\n\ntype durationArg struct {\n\ttime.Duration\n}\n\nfunc (t durationArg) RedisArg() interface{} {\n\treturn t.Seconds()\n}\n\ntype recursiveArg int\n\nfunc (v recursiveArg) RedisArg() interface{} { return v }\n\nvar writeTests = []struct {\n\targs     []interface{}\n\texpected string\n}{\n\t{\n\t\t[]interface{}{\"SET\", \"key\", \"value\"},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$5\\r\\nvalue\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", \"value\"},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$5\\r\\nvalue\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", byte(100)},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$3\\r\\n100\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", 100},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$3\\r\\n100\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", int64(math.MinInt64)},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$20\\r\\n-9223372036854775808\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", float64(1349673917.939762)},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$21\\r\\n1.349673917939762e+09\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", \"\"},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$0\\r\\n\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", nil},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$0\\r\\n\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", durationArg{time.Minute}},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$2\\r\\n60\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"key\", recursiveArg(123)},\n\t\t\"*3\\r\\n$3\\r\\nSET\\r\\n$3\\r\\nkey\\r\\n$3\\r\\n123\\r\\n\",\n\t},\n\t{\n\t\t[]interface{}{\"ECHO\", true, false},\n\t\t\"*3\\r\\n$4\\r\\nECHO\\r\\n$1\\r\\n1\\r\\n$1\\r\\n0\\r\\n\",\n\t},\n}\n\nfunc TestWrite(t *testing.T) {\n\tfor _, tt := range writeTests {\n\t\tvar buf bytes.Buffer\n\t\tc, _ := redis.Dial(\"\", \"\", dialTestConn(\"\", &buf))\n\t\terr := c.Send(tt.args[0].(string), tt.args[1:]...)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Send(%v) returned error %v\", tt.args, err)\n\t\t\tcontinue\n\t\t}\n\t\tc.Flush()\n\t\tactual := buf.String()\n\t\tif actual != tt.expected {\n\t\t\tt.Errorf(\"Send(%v) = %q, want %q\", tt.args, actual, tt.expected)\n\t\t}\n\t}\n}\n\nvar errorSentinel = &struct{}{}\n\nvar readTests = []struct {\n\treply    string\n\texpected interface{}\n}{\n\t{\n\t\t\"+OK\\r\\n\",\n\t\t\"OK\",\n\t},\n\t{\n\t\t\"+PONG\\r\\n\",\n\t\t\"PONG\",\n\t},\n\t{\n\t\t\"+OK\\n\\n\", // no \\r\n\t\terrorSentinel,\n\t},\n\t{\n\t\t\"@OK\\r\\n\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t\"$6\\r\\nfoobar\\r\\n\",\n\t\t[]byte(\"foobar\"),\n\t},\n\t{\n\t\t\"$-1\\r\\n\",\n\t\tnil,\n\t},\n\t{\n\t\t\":1\\r\\n\",\n\t\tint64(1),\n\t},\n\t{\n\t\t\":-2\\r\\n\",\n\t\tint64(-2),\n\t},\n\t{\n\t\t\"*0\\r\\n\",\n\t\t[]interface{}{},\n\t},\n\t{\n\t\t\"*-1\\r\\n\",\n\t\tnil,\n\t},\n\t{\n\t\t\"*4\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n$5\\r\\nHello\\r\\n$5\\r\\nWorld\\r\\n\",\n\t\t[]interface{}{[]byte(\"foo\"), []byte(\"bar\"), []byte(\"Hello\"), []byte(\"World\")},\n\t},\n\t{\n\t\t\"*3\\r\\n$3\\r\\nfoo\\r\\n$-1\\r\\n$3\\r\\nbar\\r\\n\",\n\t\t[]interface{}{[]byte(\"foo\"), nil, []byte(\"bar\")},\n\t},\n\n\t{\n\t\t// \"\" is not a valid length\n\t\t\"$\\r\\nfoobar\\r\\n\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t// \"x\" is not a valid length\n\t\t\"$x\\r\\nfoobar\\r\\n\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t// -2 is not a valid length\n\t\t\"$-2\\r\\n\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t// \"\"  is not a valid integer\n\t\t\":\\r\\n\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t// \"x\"  is not a valid integer\n\t\t\":x\\r\\n\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t// missing \\r\\n following value\n\t\t\"$6\\r\\nfoobar\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t// short value\n\t\t\"$6\\r\\nxx\",\n\t\terrorSentinel,\n\t},\n\t{\n\t\t// long value\n\t\t\"$6\\r\\nfoobarx\\r\\n\",\n\t\terrorSentinel,\n\t},\n}\n\nfunc TestRead(t *testing.T) {\n\tfor _, tt := range readTests {\n\t\tc, _ := redis.Dial(\"\", \"\", dialTestConn(tt.reply, nil))\n\t\tactual, err := c.Receive()\n\t\tif tt.expected == errorSentinel {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"Receive(%q) did not return expected error\", tt.reply)\n\t\t\t}\n\t\t} else {\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Receive(%q) returned error %v\", tt.reply, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(actual, tt.expected) {\n\t\t\t\tt.Errorf(\"Receive(%q) = %v, want %v\", tt.reply, actual, tt.expected)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestReadString(t *testing.T) {\n\t// n is value of bufio.defaultBufSize\n\tconst n = 4096\n\n\t// Test read string lengths near bufio.Reader buffer boundaries.\n\ttestRanges := [][2]int{{0, 64}, {n - 64, n + 64}, {2*n - 64, 2*n + 64}}\n\n\tp := make([]byte, 2*n+64)\n\tfor i := range p {\n\t\tp[i] = byte('a' + i%26)\n\t}\n\ts := string(p)\n\n\tfor _, r := range testRanges {\n\t\tfor i := r[0]; i < r[1]; i++ {\n\t\t\tc, _ := redis.Dial(\"\", \"\", dialTestConn(\"+\"+s[:i]+\"\\r\\n\", nil))\n\t\t\tactual, err := c.Receive()\n\t\t\tif err != nil || actual != s[:i] {\n\t\t\t\tt.Fatalf(\"Receive(string len %d) -> err=%v, equal=%v\", i, err, actual != s[:i])\n\t\t\t}\n\t\t}\n\t}\n}\n\nvar testCommands = []struct {\n\targs     []interface{}\n\texpected interface{}\n}{\n\t{\n\t\t[]interface{}{\"PING\"},\n\t\t\"PONG\",\n\t},\n\t{\n\t\t[]interface{}{\"SET\", \"foo\", \"bar\"},\n\t\t\"OK\",\n\t},\n\t{\n\t\t[]interface{}{\"GET\", \"foo\"},\n\t\t[]byte(\"bar\"),\n\t},\n\t{\n\t\t[]interface{}{\"GET\", \"nokey\"},\n\t\tnil,\n\t},\n\t{\n\t\t[]interface{}{\"MGET\", \"nokey\", \"foo\"},\n\t\t[]interface{}{nil, []byte(\"bar\")},\n\t},\n\t{\n\t\t[]interface{}{\"INCR\", \"mycounter\"},\n\t\tint64(1),\n\t},\n\t{\n\t\t[]interface{}{\"LPUSH\", \"mylist\", \"foo\"},\n\t\tint64(1),\n\t},\n\t{\n\t\t[]interface{}{\"LPUSH\", \"mylist\", \"bar\"},\n\t\tint64(2),\n\t},\n\t{\n\t\t[]interface{}{\"LRANGE\", \"mylist\", 0, -1},\n\t\t[]interface{}{[]byte(\"bar\"), []byte(\"foo\")},\n\t},\n\t{\n\t\t[]interface{}{\"MULTI\"},\n\t\t\"OK\",\n\t},\n\t{\n\t\t[]interface{}{\"LRANGE\", \"mylist\", 0, -1},\n\t\t\"QUEUED\",\n\t},\n\t{\n\t\t[]interface{}{\"PING\"},\n\t\t\"QUEUED\",\n\t},\n\t{\n\t\t[]interface{}{\"EXEC\"},\n\t\t[]interface{}{\n\t\t\t[]interface{}{[]byte(\"bar\"), []byte(\"foo\")},\n\t\t\t\"PONG\",\n\t\t},\n\t},\n}\n\nfunc TestDoCommands(t *testing.T) {\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\tfor _, cmd := range testCommands {\n\t\tactual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Do(%v) returned error %v\", cmd.args, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !reflect.DeepEqual(actual, cmd.expected) {\n\t\t\tt.Errorf(\"Do(%v) = %v, want %v\", cmd.args, actual, cmd.expected)\n\t\t}\n\t}\n}\n\nfunc TestPipelineCommands(t *testing.T) {\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\tfor _, cmd := range testCommands {\n\t\tif err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {\n\t\t\tt.Fatalf(\"Send(%v) returned error %v\", cmd.args, err)\n\t\t}\n\t}\n\tif err := c.Flush(); err != nil {\n\t\tt.Errorf(\"Flush() returned error %v\", err)\n\t}\n\tfor _, cmd := range testCommands {\n\t\tactual, err := c.Receive()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Receive(%v) returned error %v\", cmd.args, err)\n\t\t}\n\t\tif !reflect.DeepEqual(actual, cmd.expected) {\n\t\t\tt.Errorf(\"Receive(%v) = %v, want %v\", cmd.args, actual, cmd.expected)\n\t\t}\n\t}\n}\n\nfunc TestBlankCommand(t *testing.T) {\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\tfor _, cmd := range testCommands {\n\t\tif err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {\n\t\t\tt.Fatalf(\"Send(%v) returned error %v\", cmd.args, err)\n\t\t}\n\t}\n\treply, err := redis.Values(c.Do(\"\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Do() returned error %v\", err)\n\t}\n\tif len(reply) != len(testCommands) {\n\t\tt.Fatalf(\"len(reply)=%d, want %d\", len(reply), len(testCommands))\n\t}\n\tfor i, cmd := range testCommands {\n\t\tactual := reply[i]\n\t\tif !reflect.DeepEqual(actual, cmd.expected) {\n\t\t\tt.Errorf(\"Receive(%v) = %v, want %v\", cmd.args, actual, cmd.expected)\n\t\t}\n\t}\n}\n\nfunc TestRecvBeforeSend(t *testing.T) {\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tc.Receive() // nolint: errcheck\n\t\tclose(done)\n\t}()\n\ttime.Sleep(time.Millisecond)\n\trequire.NoError(t, c.Send(\"PING\"))\n\trequire.NoError(t, c.Flush())\n\t<-done\n\t_, err = c.Do(\"\")\n\tif err != nil {\n\t\tt.Fatalf(\"error=%v\", err)\n\t}\n}\n\nfunc TestError(t *testing.T) {\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\t_, err = c.Do(\"SET\", \"key\", \"val\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"HSET\", \"key\", \"fld\", \"val\")\n\tif err == nil {\n\t\tt.Errorf(\"Expected err for HSET on string key.\")\n\t}\n\tif c.Err() != nil {\n\t\tt.Errorf(\"Conn has Err()=%v, expect nil\", c.Err())\n\t}\n\t_, err = c.Do(\"SET\", \"key\", \"val\")\n\tif err != nil {\n\t\tt.Errorf(\"Do(SET, key, val) returned error %v, expected nil.\", err)\n\t}\n}\n\nfunc TestReadTimeout(t *testing.T) {\n\tdone := make(chan struct{})\n\terrs := make(chan error, 2)\n\tdefer func() {\n\t\tclose(done)\n\t\tfor err := range errs {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}()\n\n\tvar wg sync.WaitGroup\n\tdefer func() {\n\t\twg.Wait()\n\t\tclose(errs)\n\t}()\n\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatalf(\"net.Listen returned %v\", err)\n\t}\n\tdefer l.Close()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tfor {\n\t\t\tc, err := l.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tto := time.NewTimer(time.Second)\n\t\t\t\tdefer to.Stop()\n\t\t\t\tselect {\n\t\t\t\tcase <-to.C:\n\t\t\t\tcase <-done:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_, err := c.Write([]byte(\"+OK\\r\\n\"))\n\t\t\t\terrs <- err\n\t\t\t\tc.Close()\n\t\t\t}()\n\t\t}\n\t}()\n\n\t// Do\n\n\tc1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))\n\tif err != nil {\n\t\tt.Fatalf(\"redis.Dial returned %v\", err)\n\t}\n\tdefer c1.Close()\n\n\t_, err = c1.Do(\"PING\")\n\tif err == nil {\n\t\tt.Fatalf(\"c1.Do() returned nil, expect error\")\n\t}\n\tif c1.Err() == nil {\n\t\tt.Fatalf(\"c1.Err() = nil, expect error\")\n\t}\n\n\t// Send/Flush/Receive\n\n\tc2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))\n\tif err != nil {\n\t\tt.Fatalf(\"redis.Dial returned %v\", err)\n\t}\n\tdefer c2.Close()\n\n\trequire.NoError(t, c2.Send(\"PING\"))\n\trequire.NoError(t, c2.Flush())\n\t_, err = c2.Receive()\n\tif err == nil {\n\t\tt.Fatalf(\"c2.Receive() returned nil, expect error\")\n\t}\n\tif c2.Err() == nil {\n\t\tt.Fatalf(\"c2.Err() = nil, expect error\")\n\t}\n}\n\nfunc TestDialContextFunc(t *testing.T) {\n\tvar isPassed bool\n\tf := func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\tisPassed = true\n\t\treturn &testConn{}, nil\n\t}\n\n\t_, err := redis.DialContext(context.Background(), \"\", \"\", redis.DialContextFunc(f))\n\tif err != nil {\n\t\tt.Fatalf(\"DialContext returned %v\", err)\n\t}\n\n\tif !isPassed {\n\t\tt.Fatal(\"DialContextFunc not passed\")\n\t}\n}\n\nfunc TestDialContext_CanceledContext(t *testing.T) {\n\taddr, err := redis.DefaultServerAddr()\n\tif err != nil {\n\t\tt.Fatalf(\"redis.DefaultServerAddr returned %v\", err)\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\n\tif _, err = redis.DialContext(ctx, \"tcp\", addr); err == nil {\n\t\tt.Fatalf(\"DialContext returned nil, expect error\")\n\t}\n}\n\nvar dialErrors = []struct {\n\trawurl        string\n\texpectedError string\n}{\n\t{\n\t\t\"localhost\",\n\t\t\"invalid redis URL scheme\",\n\t},\n\t// The error message for invalid hosts is different in different\n\t// versions of Go, so just check that there is an error message.\n\t{\n\t\t\"redis://weird url\",\n\t\t\"\",\n\t},\n\t{\n\t\t\"redis://foo:bar:baz\",\n\t\t\"\",\n\t},\n\t{\n\t\t\"http://www.google.com\",\n\t\t\"invalid redis URL scheme: http\",\n\t},\n\t{\n\t\t\"redis://localhost:6379/abc123\",\n\t\t\"invalid database: abc123\",\n\t},\n\t{\n\t\t\"redis:foo//localhost:6379\",\n\t\t\"invalid redis URL, url is opaque: redis:foo//localhost:6379\",\n\t},\n\t{\n\t\t\"valkey://localhost:6379/abc123\",\n\t\t\"invalid database: abc123\",\n\t},\n\t{\n\t\t\"valkeys://localhost:6379/abc123\",\n\t\t\"invalid database: abc123\",\n\t},\n\t{\n\t\t\"valkey:foo//localhost:6379\",\n\t\t\"invalid redis URL, url is opaque: valkey:foo//localhost:6379\",\n\t},\n\t{\n\t\t\"valkeys:foo//localhost:6379\",\n\t\t\"invalid redis URL, url is opaque: valkeys:foo//localhost:6379\",\n\t},\n}\n\nfunc TestDialURLErrors(t *testing.T) {\n\tfor _, d := range dialErrors {\n\t\t_, err := redis.DialURL(d.rawurl)\n\t\tif err == nil || !strings.Contains(err.Error(), d.expectedError) {\n\t\t\tt.Errorf(\"DialURL did not return expected error (expected %v to contain %s)\", err, d.expectedError)\n\t\t}\n\t}\n}\n\nfunc TestDialURLPort(t *testing.T) {\n\tcheckPort := func(network, address string) (net.Conn, error) {\n\t\tif address != \"localhost:6379\" {\n\t\t\tt.Errorf(\"DialURL did not set port to 6379 by default (got %v)\", address)\n\t\t}\n\t\treturn nil, nil\n\t}\n\t_, err := redis.DialURL(\"redis://localhost\", redis.DialNetDial(checkPort))\n\tif err != nil {\n\t\tt.Error(\"dial error:\", err)\n\t}\n}\n\nfunc TestDialURLHost(t *testing.T) {\n\tcheckHost := func(network, address string) (net.Conn, error) {\n\t\tif address != \"localhost:6379\" {\n\t\t\tt.Errorf(\"DialURL did not set host to localhost by default (got %v)\", address)\n\t\t}\n\t\treturn nil, nil\n\t}\n\t_, err := redis.DialURL(\"redis://:6379\", redis.DialNetDial(checkHost))\n\tif err != nil {\n\t\tt.Error(\"dial error:\", err)\n\t}\n}\n\nvar dialURLTests = []struct {\n\tdescription string\n\turl         string\n\tr           string\n\tw           string\n}{\n\t{\"password\", \"redis://:abc123@localhost\", \"+OK\\r\\n\", \"*2\\r\\n$4\\r\\nAUTH\\r\\n$6\\r\\nabc123\\r\\n\"},\n\t{\"password redis-cli compat\", \"redis://abc123@localhost\", \"+OK\\r\\n\", \"*2\\r\\n$4\\r\\nAUTH\\r\\n$6\\r\\nabc123\\r\\n\"},\n\t{\"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\"},\n\t{\"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\"},\n\t{\"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\"},\n\t{\"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\"},\n\t{\"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\"},\n\t{\"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\"},\n\t{\"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\"},\n\t{\"username\", \"redis://x:@localhost\", \"+OK\\r\\n\", \"\"},\n\t{\"database 3\", \"redis://localhost/3\", \"+OK\\r\\n\", \"*2\\r\\n$6\\r\\nSELECT\\r\\n$1\\r\\n3\\r\\n\"},\n\t{\"database 99\", \"redis://localhost/99\", \"+OK\\r\\n\", \"*2\\r\\n$6\\r\\nSELECT\\r\\n$2\\r\\n99\\r\\n\"},\n\t{\"no database\", \"redis://localhost/\", \"+OK\\r\\n\", \"\"},\n}\n\nfunc TestDialURL(t *testing.T) {\n\tfor _, tt := range dialURLTests {\n\t\tt.Run(tt.description, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\t// UseTLS should be ignored in all of these tests.\n\t\t\t_, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"%s dial error: %v, buf: %v\", tt.description, err, buf.String())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif w := buf.String(); w != tt.w {\n\t\t\t\tt.Errorf(\"%s commands = %q, want %q\", tt.description, w, tt.w)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) {\n\tresp, err := c.Do(\"PING\")\n\tif err != nil {\n\t\tt.Fatal(\"ping error:\", err)\n\t}\n\t// Close connection to ensure that writes to buf are complete.\n\tc.Close()\n\texpected := \"*1\\r\\n$4\\r\\nPING\\r\\n\"\n\tactual := buf.String()\n\tif actual != expected {\n\t\tt.Errorf(\"commands = %q, want %q\", actual, expected)\n\t}\n\tif resp != \"PONG\" {\n\t\tt.Errorf(\"resp = %v, want %v\", resp, \"PONG\")\n\t}\n}\n\nconst pingResponse = \"+PONG\\r\\n\"\n\nfunc TestDialURLTLS(t *testing.T) {\n\tvar buf bytes.Buffer\n\tc, err := redis.DialURL(\"rediss://example.com/\",\n\t\tredis.DialTLSConfig(&clientTLSConfig),\n\t\tdialTestConnTLS(pingResponse, &buf))\n\tif err != nil {\n\t\tt.Fatal(\"dial error:\", err)\n\t}\n\tcheckPingPong(t, &buf, c)\n}\n\nfunc TestDialUseTLS(t *testing.T) {\n\tvar buf bytes.Buffer\n\tc, err := redis.Dial(\"tcp\", \"example.com:6379\",\n\t\tredis.DialTLSConfig(&clientTLSConfig),\n\t\tdialTestConnTLS(pingResponse, &buf),\n\t\tredis.DialUseTLS(true))\n\tif err != nil {\n\t\tt.Fatal(\"dial error:\", err)\n\t}\n\tcheckPingPong(t, &buf, c)\n}\n\ntype blockedReader struct {\n\tch chan struct{}\n}\n\nfunc (b blockedReader) Read(p []byte) (n int, err error) {\n\t<-b.ch\n\treturn 0, io.EOF\n}\n\nfunc dialTestBlockedConn(ch chan struct{}, w io.Writer) redis.DialOption {\n\treturn redis.DialNetDial(func(network, addr string) (net.Conn, error) {\n\t\treturn &testConn{Reader: blockedReader{ch: ch}, Writer: w}, nil\n\t})\n}\n\nfunc TestDialTLSHandshakeTimeout(t *testing.T) {\n\tvar buf bytes.Buffer\n\tch := make(chan struct{})\n\tvar err error\n\tgo func() {\n\t\t_, err = redis.Dial(\"tcp\", \"example.com:6379\",\n\t\t\tredis.DialTLSConfig(&clientTLSConfig),\n\t\t\tredis.DialTLSHandshakeTimeout(time.Millisecond),\n\t\t\tdialTestBlockedConn(ch, &buf),\n\t\t\tredis.DialUseTLS(true))\n\t\tclose(ch)\n\t}()\n\tselect {\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"dial didn't timeout\")\n\tcase <-ch:\n\t\tif err == nil {\n\t\t\tt.Fatal(\"dial didn't error\")\n\t\t} else if err.Error() != \"TLS handshake timeout\" {\n\t\t\tt.Fatal(\"dial unexpected error:\", err)\n\t\t}\n\t}\n}\n\nfunc TestDialTLSSKipVerify(t *testing.T) {\n\tvar buf bytes.Buffer\n\tc, err := redis.Dial(\"tcp\", \"example.com:6379\",\n\t\tdialTestConnTLS(pingResponse, &buf),\n\t\tredis.DialTLSSkipVerify(true),\n\t\tredis.DialUseTLS(true))\n\tif err != nil {\n\t\tt.Fatal(\"dial error:\", err)\n\t}\n\tcheckPingPong(t, &buf, c)\n}\n\nfunc TestDialUseACL(t *testing.T) {\n\tvar buf bytes.Buffer\n\t_, err := redis.Dial(\"tcp\", \"localhost:6379\",\n\t\tredis.DialUsername(\"user\"),\n\t\tredis.DialPassword(\"password\"),\n\t\tdialTestConn(pingResponse, &buf))\n\tif err != nil {\n\t\tt.Fatal(\"dial error:\", err)\n\t}\n\tif err != nil {\n\t\tt.Fatal(\"dial error:\", err)\n\t}\n\texpected := \"*3\\r\\n$4\\r\\nAUTH\\r\\n$4\\r\\nuser\\r\\n$8\\r\\npassword\\r\\n\"\n\tif w := buf.String(); w != expected {\n\t\tt.Errorf(\"got %q, want %q\", w, expected)\n\t}\n}\n\n// Connect to an Redis instance using the Redis ACL system\nfunc ExampleDial_acl() {\n\tc, err := redis.Dial(\"tcp\", \"localhost:6379\",\n\t\tredis.DialUsername(\"username\"),\n\t\tredis.DialPassword(\"password\"),\n\t)\n\tif err != nil {\n\t\t// handle error\n\t}\n\tdefer c.Close()\n}\n\nfunc TestDialClientName(t *testing.T) {\n\tvar buf bytes.Buffer\n\t_, err := redis.Dial(\"tcp\", \":6379\",\n\t\tdialTestConn(pingResponse, &buf),\n\t\tredis.DialClientName(\"redis-connection\"),\n\t)\n\tif err != nil {\n\t\tt.Fatal(\"dial error:\", err)\n\t}\n\texpected := \"*3\\r\\n$6\\r\\nCLIENT\\r\\n$7\\r\\nSETNAME\\r\\n$16\\r\\nredis-connection\\r\\n\"\n\tif w := buf.String(); w != expected {\n\t\tt.Errorf(\"got %q, want %q\", w, expected)\n\t}\n\n\t// testing against a real server\n\tconnectionName := \"test-connection\"\n\tc, err := redis.DialDefaultServer(redis.DialClientName(connectionName))\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\tv, err := c.Do(\"CLIENT\", \"GETNAME\")\n\tif err != nil {\n\t\tt.Fatalf(\"CLIENT GETNAME returned error %v\", err)\n\t}\n\n\tvs, err := redis.String(v, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"String(v) returned error %v\", err)\n\t}\n\n\tif vs != connectionName {\n\t\tt.Fatalf(\"wrong connection name. Got '%s', expected '%s'\", vs, connectionName)\n\t}\n}\n\n// Connect to local instance of Redis running on the default port.\nfunc ExampleDial() {\n\tc, err := redis.Dial(\"tcp\", \":6379\")\n\tif err != nil {\n\t\t// handle error\n\t}\n\tdefer c.Close()\n}\n\n// Connect to local instance of Redis running on the default port using the provided context.\nfunc ExampleDialContext() {\n\tctx := context.Background()\n\tc, err := redis.DialContext(ctx, \"tcp\", \":6379\")\n\tif err != nil {\n\t\t// handle error\n\t}\n\tdefer c.Close()\n}\n\n// Connect to remote instance of Redis using a URL.\nfunc ExampleDialURL() {\n\tc, err := redis.DialURL(os.Getenv(\"REDIS_URL\"))\n\tif err != nil {\n\t\t// handle connection error\n\t}\n\tdefer c.Close()\n}\n\n// TestExecError tests handling of errors in a transaction. See\n// http://redis.io/topics/transactions for information on how Redis handles\n// errors in a transaction.\nfunc TestExecError(t *testing.T) {\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\t// Execute commands that fail before EXEC is called.\n\n\t_, err = c.Do(\"DEL\", \"k0\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"ZADD\", \"k0\", 0, 0)\n\trequire.NoError(t, err)\n\trequire.NoError(t, c.Send(\"MULTI\"))\n\trequire.NoError(t, c.Send(\"NOTACOMMAND\", \"k0\", 0, 0))\n\trequire.NoError(t, c.Send(\"ZINCRBY\", \"k0\", 0, 0))\n\tv, err := c.Do(\"EXEC\")\n\tif err == nil {\n\t\tt.Fatalf(\"EXEC returned values %v, expected error\", v)\n\t}\n\n\t// Execute commands that fail after EXEC is called. The first command\n\t// returns an error.\n\n\t_, err = c.Do(\"DEL\", \"k1\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"ZADD\", \"k1\", 0, 0)\n\trequire.NoError(t, err)\n\trequire.NoError(t, c.Send(\"MULTI\"))\n\trequire.NoError(t, c.Send(\"HSET\", \"k1\", 0, 0))\n\trequire.NoError(t, c.Send(\"ZINCRBY\", \"k1\", 0, 0))\n\tv, err = c.Do(\"EXEC\")\n\tif err != nil {\n\t\tt.Fatalf(\"EXEC returned error %v\", err)\n\t}\n\n\tvs, err := redis.Values(v, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Values(v) returned error %v\", err)\n\t}\n\n\tif len(vs) != 2 {\n\t\tt.Fatalf(\"len(vs) == %d, want 2\", len(vs))\n\t}\n\n\tif _, ok := vs[0].(error); !ok {\n\t\tt.Fatalf(\"first result is type %T, expected error\", vs[0])\n\t}\n\n\tif _, ok := vs[1].([]byte); !ok {\n\t\tt.Fatalf(\"second result is type %T, expected []byte\", vs[1])\n\t}\n\n\t// Execute commands that fail after EXEC is called. The second command\n\t// returns an error.\n\n\t_, err = c.Do(\"ZADD\", \"k2\", 0, 0)\n\trequire.NoError(t, err)\n\trequire.NoError(t, c.Send(\"MULTI\"))\n\trequire.NoError(t, c.Send(\"ZINCRBY\", \"k2\", 0, 0))\n\trequire.NoError(t, c.Send(\"HSET\", \"k2\", 0, 0))\n\tv, err = c.Do(\"EXEC\")\n\tif err != nil {\n\t\tt.Fatalf(\"EXEC returned error %v\", err)\n\t}\n\n\tvs, err = redis.Values(v, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Values(v) returned error %v\", err)\n\t}\n\n\tif len(vs) != 2 {\n\t\tt.Fatalf(\"len(vs) == %d, want 2\", len(vs))\n\t}\n\n\tif _, ok := vs[0].([]byte); !ok {\n\t\tt.Fatalf(\"first result is type %T, expected []byte\", vs[0])\n\t}\n\n\tif _, ok := vs[1].(error); !ok {\n\t\tt.Fatalf(\"second result is type %T, expected error\", vs[2])\n\t}\n}\n\nfunc BenchmarkDoEmpty(b *testing.B) {\n\tb.StopTimer()\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer c.Close()\n\tb.StartTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tif _, err := c.Do(\"\"); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkDoPing(b *testing.B) {\n\tb.StopTimer()\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer c.Close()\n\tb.StartTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tif _, err := c.Do(\"PING\"); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nvar clientTLSConfig, serverTLSConfig tls.Config\n\nfunc init() {\n\t// The certificate and key for testing TLS dial options was created\n\t// using the command\n\t//\n\t//   go run GOROOT/src/crypto/tls/generate_cert.go  \\\n\t//      --rsa-bits 1024 \\\n\t//      --host 127.0.0.1,::1,example.com --ca \\\n\t//      --start-date \"Jan 1 00:00:00 1970\" \\\n\t//      --duration=1000000h\n\t//\n\t// where GOROOT is the value of GOROOT reported by go env.\n\tlocalhostCert := []byte(`\n-----BEGIN CERTIFICATE-----\nMIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw\nEjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2\nMDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\ngYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+\nLGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+\nJaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw\nDgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF\nMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA\nAAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+\nach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9\n6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt\nrrKgNsltzMk=\n-----END CERTIFICATE-----`)\n\n\tlocalhostKey := []byte(`\n-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi\nbMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l\nSoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB\nAoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB\nEq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y\nHenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm\nKbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw\nKjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa\nm6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0\npDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci\nEo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH\ndiKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc\nBjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=\n-----END RSA PRIVATE KEY-----`)\n\n\tcert, err := tls.X509KeyPair(localhostCert, localhostKey)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"error creating key pair: %v\", err))\n\t}\n\tserverTLSConfig.Certificates = []tls.Certificate{cert}\n\n\tcertificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0])\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"error parsing x509 certificate: %v\", err))\n\t}\n\n\tclientTLSConfig.RootCAs = x509.NewCertPool()\n\tclientTLSConfig.RootCAs.AddCert(certificate)\n}\n\nfunc TestWithTimeout(t *testing.T) {\n\tfor _, recv := range []bool{true, false} {\n\t\tfor _, defaultTimout := range []time.Duration{0, time.Minute} {\n\t\t\tvar buf bytes.Buffer\n\t\t\tnc := &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}\n\t\t\tc, _ := redis.Dial(\"\", \"\", redis.DialReadTimeout(defaultTimout), redis.DialNetDial(func(network, addr string) (net.Conn, error) { return nc, nil }))\n\t\t\tfor i := 0; i < 4; i++ {\n\t\t\t\tvar minDeadline, maxDeadline time.Time\n\n\t\t\t\t// Alternate between default and specified timeout.\n\t\t\t\tvar err error\n\t\t\t\tif i%2 == 0 {\n\t\t\t\t\tif defaultTimout != 0 {\n\t\t\t\t\t\tminDeadline = time.Now().Add(defaultTimout)\n\t\t\t\t\t}\n\t\t\t\t\tif recv {\n\t\t\t\t\t\t_, err = c.Receive()\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_, err = c.Do(\"PING\")\n\t\t\t\t\t}\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tif defaultTimout != 0 {\n\t\t\t\t\t\tmaxDeadline = time.Now().Add(defaultTimout)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttimeout := 10 * time.Minute\n\t\t\t\t\tminDeadline = time.Now().Add(timeout)\n\t\t\t\t\tif recv {\n\t\t\t\t\t\t_, err = redis.ReceiveWithTimeout(c, timeout)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_, err = redis.DoWithTimeout(c, timeout, \"PING\")\n\t\t\t\t\t}\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tmaxDeadline = time.Now().Add(timeout)\n\t\t\t\t}\n\n\t\t\t\t// Expect set deadline in expected range.\n\t\t\t\tif nc.readDeadline.Before(minDeadline) || nc.readDeadline.After(maxDeadline) {\n\t\t\t\t\tt.Errorf(\"recv %v, %d: do deadline error: %v, %v, %v\", recv, i, minDeadline, nc.readDeadline, maxDeadline)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "redis/doc.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\n// Package redis is a client for the Redis database.\n//\n// The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more\n// documentation about this package.\n//\n// Connections\n//\n// The Conn interface is the primary interface for working with Redis.\n// Applications create connections by calling the Dial, DialWithTimeout or\n// NewConn functions. In the future, functions will be added for creating\n// sharded and other types of connections.\n//\n// The application must call the connection Close method when the application\n// is done with the connection.\n//\n// Executing Commands\n//\n// The Conn interface has a generic method for executing Redis commands:\n//\n//  Do(commandName string, args ...interface{}) (reply interface{}, err error)\n//\n// The Redis command reference (http://redis.io/commands) lists the available\n// commands. An example of using the Redis APPEND command is:\n//\n//  n, err := conn.Do(\"APPEND\", \"key\", \"value\")\n//\n// The Do method converts command arguments to bulk strings for transmission\n// to the server as follows:\n//\n//  Go Type                 Conversion\n//  []byte                  Sent as is\n//  string                  Sent as is\n//  int, int64              strconv.FormatInt(v)\n//  float64                 strconv.FormatFloat(v, 'g', -1, 64)\n//  bool                    true -> \"1\", false -> \"0\"\n//  nil                     \"\"\n//  all other types         fmt.Fprint(w, v)\n//\n// Redis command reply types are represented using the following Go types:\n//\n//  Redis type              Go type\n//  error                   redis.Error\n//  integer                 int64\n//  simple string           string\n//  bulk string             []byte or nil if value not present.\n//  array                   []interface{} or nil if value not present.\n//\n// Use type assertions or the reply helper functions to convert from\n// interface{} to the specific Go type for the command result.\n//\n// Pipelining\n//\n// Connections support pipelining using the Send, Flush and Receive methods.\n//\n//  Send(commandName string, args ...interface{}) error\n//  Flush() error\n//  Receive() (reply interface{}, err error)\n//\n// Send writes the command to the connection's output buffer. Flush flushes the\n// connection's output buffer to the server. Receive reads a single reply from\n// the server. The following example shows a simple pipeline.\n//\n//  c.Send(\"SET\", \"foo\", \"bar\")\n//  c.Send(\"GET\", \"foo\")\n//  c.Flush()\n//  c.Receive() // reply from SET\n//  v, err = c.Receive() // reply from GET\n//\n// The Do method combines the functionality of the Send, Flush and Receive\n// methods. The Do method starts by writing the command and flushing the output\n// buffer. Next, the Do method receives all pending replies including the reply\n// for the command just sent by Do. If any of the received replies is an error,\n// then Do returns the error. If there are no errors, then Do returns the last\n// reply. If the command argument to the Do method is \"\", then the Do method\n// will flush the output buffer and receive pending replies without sending a\n// command.\n//\n// Use the Send and Do methods to implement pipelined transactions.\n//\n//  c.Send(\"MULTI\")\n//  c.Send(\"INCR\", \"foo\")\n//  c.Send(\"INCR\", \"bar\")\n//  r, err := c.Do(\"EXEC\")\n//  fmt.Println(r) // prints [1, 1]\n//\n// Concurrency\n//\n// Connections support one concurrent caller to the Receive method and one\n// concurrent caller to the Send and Flush methods. No other concurrency is\n// supported including concurrent calls to the Do and Close methods.\n//\n// For full concurrent access to Redis, use the thread-safe Pool to get, use\n// and release a connection from within a goroutine. Connections returned from\n// a Pool have the concurrency restrictions described in the previous\n// paragraph.\n//\n// Publish and Subscribe\n//\n// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.\n//\n//  c.Send(\"SUBSCRIBE\", \"example\")\n//  c.Flush()\n//  for {\n//      reply, err := c.Receive()\n//      if err != nil {\n//          return err\n//      }\n//      // process pushed message\n//  }\n//\n// The PubSubConn type wraps a Conn with convenience methods for implementing\n// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods\n// send and flush a subscription management command. The receive method\n// converts a pushed message to convenient types for use in a type switch.\n//\n//  psc := redis.PubSubConn{Conn: c}\n//  psc.Subscribe(\"example\")\n//  for {\n//      switch v := psc.Receive().(type) {\n//      case redis.Message:\n//          fmt.Printf(\"%s: message: %s\\n\", v.Channel, v.Data)\n//      case redis.Subscription:\n//          fmt.Printf(\"%s: %s %d\\n\", v.Channel, v.Kind, v.Count)\n//      case error:\n//          return v\n//      }\n//  }\n//\n// Reply Helpers\n//\n// The Bool, Int, Bytes, String, Strings and Values functions convert a reply\n// to a value of a specific type. To allow convenient wrapping of calls to the\n// connection Do and Receive methods, the functions take a second argument of\n// type error.  If the error is non-nil, then the helper function returns the\n// error. If the error is nil, the function converts the reply to the specified\n// type:\n//\n//  exists, err := redis.Bool(c.Do(\"EXISTS\", \"foo\"))\n//  if err != nil {\n//      // handle error return from c.Do or type conversion error.\n//  }\n//\n// The Scan function converts elements of a array reply to Go types:\n//\n//  var value1 int\n//  var value2 string\n//  reply, err := redis.Values(c.Do(\"MGET\", \"key1\", \"key2\"))\n//  if err != nil {\n//      // handle error\n//  }\n//   if _, err := redis.Scan(reply, &value1, &value2); err != nil {\n//      // handle error\n//  }\n//\n// Errors\n//\n// Connection methods return error replies from the server as type redis.Error.\n//\n// Call the connection Err() method to determine if the connection encountered\n// non-recoverable error such as a network error or protocol parsing error. If\n// Err() returns a non-nil value, then the connection is not usable and should\n// be closed.\n//\n// Metrics\n//\n// Metrics regarding the connection pool and its connections are exposed via\n// the pool.Stats() method, which can then be used with your preferred metrics\n// library. \n// \n// The below code snippet demonstrates an example using the\n// \"github.com/prometheus/client_golang/prometheus\" library.\n// Note that all the metrics exposed are gauges, even though the wait count and\n// wait duration are being used as counters - this is required to \"set\" the\n// value rather than add/increment it.\n//\n//  func collectRedisPoolStats(pool *redis.Pool, maxConn float64) {\n//    ticker := time.NewTicker(5 * time.Second)\n//    redisPoolMax.Set(maxConn)\n//\n//    go func() {\n//      defer ticker.Stop()\n//\n//      for range ticker.C {\n//        stats := pool.Stats()\n//\n//        redisPoolOpen.Set(float64(stats.ActiveCount))\n//        redisPoolInUse.Set(float64(stats.ActiveCount - stats.IdleCount))\n//        redisPoolIdle.Set(float64(stats.IdleCount))\n//        redisPoolWaitCount.Set(float64(stats.WaitCount))\n//        redisPoolWaitDuration.Set(stats.WaitDuration.Seconds())\n//      }\n//    }()\n//  }\npackage redis\n"
  },
  {
    "path": "redis/list_test.go",
    "content": "// Copyright 2018 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\n// +build go1.9\n\npackage redis\n\nimport \"testing\"\n\nfunc TestPoolList(t *testing.T) {\n\tvar idle idleList\n\tvar a, b, c poolConn\n\n\tcheck := func(pcs ...*poolConn) {\n\t\tif idle.count != len(pcs) {\n\t\t\tt.Fatal(\"idle.count != len(pcs)\")\n\t\t}\n\t\tif len(pcs) == 0 {\n\t\t\tif idle.front != nil {\n\t\t\t\tt.Fatalf(\"front not nil\")\n\t\t\t}\n\t\t\tif idle.back != nil {\n\t\t\t\tt.Fatalf(\"back not nil\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif idle.front != pcs[0] {\n\t\t\tt.Fatal(\"front != pcs[0]\")\n\t\t}\n\t\tif idle.back != pcs[len(pcs)-1] {\n\t\t\tt.Fatal(\"back != pcs[len(pcs)-1]\")\n\t\t}\n\t\tif idle.front.prev != nil {\n\t\t\tt.Fatal(\"front.prev != nil\")\n\t\t}\n\t\tif idle.back.next != nil {\n\t\t\tt.Fatal(\"back.next != nil\")\n\t\t}\n\t\tfor i := 1; i < len(pcs)-1; i++ {\n\t\t\tif pcs[i-1].next != pcs[i] {\n\t\t\t\tt.Fatal(\"pcs[i-1].next != pcs[i]\")\n\t\t\t}\n\t\t\tif pcs[i+1].prev != pcs[i] {\n\t\t\t\tt.Fatal(\"pcs[i+1].prev != pcs[i]\")\n\t\t\t}\n\t\t}\n\t}\n\n\tidle.pushFront(&c)\n\tcheck(&c)\n\tidle.pushFront(&b)\n\tcheck(&b, &c)\n\tidle.pushFront(&a)\n\tcheck(&a, &b, &c)\n\tidle.popFront()\n\tcheck(&b, &c)\n\tidle.popFront()\n\tcheck(&c)\n\tidle.popFront()\n\tcheck()\n\n\tidle.pushFront(&c)\n\tcheck(&c)\n\tidle.pushFront(&b)\n\tcheck(&b, &c)\n\tidle.pushFront(&a)\n\tcheck(&a, &b, &c)\n\tidle.popBack()\n\tcheck(&a, &b)\n\tidle.popBack()\n\tcheck(&a)\n\tidle.popBack()\n\tcheck()\n}\n"
  },
  {
    "path": "redis/log.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n)\n\nvar (\n\t_ ConnWithTimeout = (*loggingConn)(nil)\n)\n\n// NewLoggingConn returns a logging wrapper around a connection.\nfunc NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {\n\tif prefix != \"\" {\n\t\tprefix = prefix + \".\"\n\t}\n\treturn &loggingConn{conn, logger, prefix, nil}\n}\n\n//NewLoggingConnFilter returns a logging wrapper around a connection and a filter function.\nfunc NewLoggingConnFilter(conn Conn, logger *log.Logger, prefix string, skip func(cmdName string) bool) Conn {\n\tif prefix != \"\" {\n\t\tprefix = prefix + \".\"\n\t}\n\treturn &loggingConn{conn, logger, prefix, skip}\n}\n\ntype loggingConn struct {\n\tConn\n\tlogger *log.Logger\n\tprefix string\n\tskip   func(cmdName string) bool\n}\n\nfunc (c *loggingConn) Close() error {\n\terr := c.Conn.Close()\n\tvar buf bytes.Buffer\n\tfmt.Fprintf(&buf, \"%sClose() -> (%v)\", c.prefix, err)\n\tc.logger.Output(2, buf.String()) // nolint: errcheck\n\treturn err\n}\n\nfunc (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {\n\tconst chop = 32\n\tswitch v := v.(type) {\n\tcase []byte:\n\t\tif len(v) > chop {\n\t\t\tfmt.Fprintf(buf, \"%q...\", v[:chop])\n\t\t} else {\n\t\t\tfmt.Fprintf(buf, \"%q\", v)\n\t\t}\n\tcase string:\n\t\tif len(v) > chop {\n\t\t\tfmt.Fprintf(buf, \"%q...\", v[:chop])\n\t\t} else {\n\t\t\tfmt.Fprintf(buf, \"%q\", v)\n\t\t}\n\tcase []interface{}:\n\t\tif len(v) == 0 {\n\t\t\tbuf.WriteString(\"[]\")\n\t\t} else {\n\t\t\tsep := \"[\"\n\t\t\tfin := \"]\"\n\t\t\tif len(v) > chop {\n\t\t\t\tv = v[:chop]\n\t\t\t\tfin = \"...]\"\n\t\t\t}\n\t\t\tfor _, vv := range v {\n\t\t\t\tbuf.WriteString(sep)\n\t\t\t\tc.printValue(buf, vv)\n\t\t\t\tsep = \", \"\n\t\t\t}\n\t\t\tbuf.WriteString(fin)\n\t\t}\n\tdefault:\n\t\tfmt.Fprint(buf, v)\n\t}\n}\n\nfunc (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {\n\tif c.skip != nil && c.skip(commandName) {\n\t\treturn\n\t}\n\tvar buf bytes.Buffer\n\tfmt.Fprintf(&buf, \"%s%s(\", c.prefix, method)\n\tif method != \"Receive\" {\n\t\tbuf.WriteString(commandName)\n\t\tfor _, arg := range args {\n\t\t\tbuf.WriteString(\", \")\n\t\t\tc.printValue(&buf, arg)\n\t\t}\n\t}\n\tbuf.WriteString(\") -> (\")\n\tif method != \"Send\" {\n\t\tc.printValue(&buf, reply)\n\t\tbuf.WriteString(\", \")\n\t}\n\tfmt.Fprintf(&buf, \"%v)\", err)\n\tc.logger.Output(3, buf.String()) // nolint: errcheck\n}\n\nfunc (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {\n\treply, err := c.Conn.Do(commandName, args...)\n\tc.print(\"Do\", commandName, args, reply, err)\n\treturn reply, err\n}\n\nfunc (c *loggingConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) {\n\treply, err := DoContext(c.Conn, ctx, commandName, args...)\n\tc.print(\"DoContext\", commandName, args, reply, err)\n\treturn reply, err\n}\n\nfunc (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) {\n\treply, err := DoWithTimeout(c.Conn, timeout, commandName, args...)\n\tc.print(\"DoWithTimeout\", commandName, args, reply, err)\n\treturn reply, err\n}\n\nfunc (c *loggingConn) Send(commandName string, args ...interface{}) error {\n\terr := c.Conn.Send(commandName, args...)\n\tc.print(\"Send\", commandName, args, nil, err)\n\treturn err\n}\n\nfunc (c *loggingConn) Receive() (interface{}, error) {\n\treply, err := c.Conn.Receive()\n\tc.print(\"Receive\", \"\", nil, reply, err)\n\treturn reply, err\n}\n\nfunc (c *loggingConn) ReceiveContext(ctx context.Context) (interface{}, error) {\n\treply, err := ReceiveContext(c.Conn, ctx)\n\tc.print(\"ReceiveContext\", \"\", nil, reply, err)\n\treturn reply, err\n}\n\nfunc (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {\n\treply, err := ReceiveWithTimeout(c.Conn, timeout)\n\tc.print(\"ReceiveWithTimeout\", \"\", nil, reply, err)\n\treturn reply, err\n}\n"
  },
  {
    "path": "redis/pool.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/sha1\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\t_ ConnWithTimeout = (*activeConn)(nil)\n\t_ ConnWithTimeout = (*errorConn)(nil)\n)\n\nvar nowFunc = time.Now // for testing\n\n// ErrPoolExhausted is returned from a pool connection method (Do, Send,\n// Receive, Flush, Err) when the maximum number of database connections in the\n// pool has been reached.\nvar ErrPoolExhausted = errors.New(\"redigo: connection pool exhausted\")\n\nvar (\n\terrConnClosed = errors.New(\"redigo: connection closed\")\n)\n\n// Pool maintains a pool of connections. The application calls the Get method\n// to get a connection from the pool and the connection's Close method to\n// return the connection's resources to the pool.\n//\n// The following example shows how to use a pool in a web application. The\n// application creates a pool at application startup and makes it available to\n// request handlers using a package level variable. The pool configuration used\n// here is an example, not a recommendation.\n//\n//  func newPool(addr string) *redis.Pool {\n//    return &redis.Pool{\n//      MaxIdle: 3,\n//      IdleTimeout: 240 * time.Second,\n//      // Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial.\n//      Dial: func () (redis.Conn, error) { return redis.Dial(\"tcp\", addr) },\n//    }\n//  }\n//\n//  var (\n//    pool *redis.Pool\n//    redisServer = flag.String(\"redisServer\", \":6379\", \"\")\n//  )\n//\n//  func main() {\n//    flag.Parse()\n//    pool = newPool(*redisServer)\n//    ...\n//  }\n//\n// A request handler gets a connection from the pool and closes the connection\n// when the handler is done:\n//\n//  func serveHome(w http.ResponseWriter, r *http.Request) {\n//      conn := pool.Get()\n//      defer conn.Close()\n//      ...\n//  }\n//\n// Use the Dial function to authenticate connections with the AUTH command or\n// select a database with the SELECT command:\n//\n//  pool := &redis.Pool{\n//    // Other pool configuration not shown in this example.\n//    Dial: func () (redis.Conn, error) {\n//      c, err := redis.Dial(\"tcp\", server)\n//      if err != nil {\n//        return nil, err\n//      }\n//      if _, err := c.Do(\"AUTH\", password); err != nil {\n//        c.Close()\n//        return nil, err\n//      }\n//      if _, err := c.Do(\"SELECT\", db); err != nil {\n//        c.Close()\n//        return nil, err\n//      }\n//      return c, nil\n//    },\n//  }\n//\n// Use the TestOnBorrow function to check the health of an idle connection\n// before the connection is returned to the application. This example PINGs\n// connections that have been idle more than a minute:\n//\n//  pool := &redis.Pool{\n//    // Other pool configuration not shown in this example.\n//    TestOnBorrow: func(c redis.Conn, t time.Time) error {\n//      if time.Since(t) < time.Minute {\n//        return nil\n//      }\n//      _, err := c.Do(\"PING\")\n//      return err\n//    },\n//  }\n//\ntype Pool struct {\n\t// Dial is an application supplied function for creating and configuring a\n\t// connection.\n\t//\n\t// The connection returned from Dial must not be in a special state\n\t// (subscribed to pubsub channel, transaction started, ...).\n\tDial func() (Conn, error)\n\n\t// DialContext is an application supplied function for creating and configuring a\n\t// connection with the given context.\n\t//\n\t// The connection returned from DialContext must not be in a special state\n\t// (subscribed to pubsub channel, transaction started, ...).\n\tDialContext func(ctx context.Context) (Conn, error)\n\n\t// TestOnBorrow is an optional application supplied function for checking\n\t// the health of an idle connection before the connection is used again by\n\t// the application. Argument lastUsed is the time when the connection was returned\n\t// to the pool. If the function returns an error, then the connection is\n\t// closed.\n\tTestOnBorrow func(c Conn, lastUsed time.Time) error\n\n\t// TestOnBorrowContext is an optional application supplied function\n\t// for checking the health of an idle connection with the given context\n\t// before the connection is used again by the application.\n\t// Argument lastUsed is the time when the connection was returned\n\t// to the pool. If the function returns an error, then the connection is\n\t// closed.\n\tTestOnBorrowContext func(ctx context.Context, c Conn, lastUsed time.Time) error\n\n\t// Maximum number of idle connections in the pool.\n\tMaxIdle int\n\n\t// Maximum number of connections allocated by the pool at a given time.\n\t// When zero, there is no limit on the number of connections in the pool.\n\tMaxActive int\n\n\t// Close connections after remaining idle for this duration. If the value\n\t// is zero, then idle connections are not closed. Applications should set\n\t// the timeout to a value less than the server's timeout.\n\tIdleTimeout time.Duration\n\n\t// If Wait is true and the pool is at the MaxActive limit, then Get() waits\n\t// for a connection to be returned to the pool before returning.\n\tWait bool\n\n\t// Close connections older than this duration. If the value is zero, then\n\t// the pool does not close connections based on age.\n\tMaxConnLifetime time.Duration\n\n\tmu           sync.Mutex    // mu protects the following fields\n\tclosed       bool          // set to true when the pool is closed.\n\tactive       int           // the number of open connections in the pool\n\tinitOnce     sync.Once     // the init ch once func\n\tch           chan struct{} // limits open connections when p.Wait is true\n\tidle         idleList      // idle connections\n\twaitCount    int64         // total number of connections waited for.\n\twaitDuration time.Duration // total time waited for new connections.\n}\n\n// NewPool creates a new pool.\n//\n// Deprecated: Initialize the Pool directly as shown in the example.\nfunc NewPool(newFn func() (Conn, error), maxIdle int) *Pool {\n\treturn &Pool{Dial: newFn, MaxIdle: maxIdle}\n}\n\n// Get gets a connection. The application must close the returned connection.\n// This method always returns a valid connection so that applications can defer\n// error handling to the first use of the connection. If there is an error\n// getting an underlying connection, then the connection Err, Do, Send, Flush\n// and Receive methods return that error.\nfunc (p *Pool) Get() Conn {\n\t// GetContext returns errorConn in the first argument when an error occurs.\n\tc, _ := p.GetContext(context.Background())\n\treturn c\n}\n\n// GetContext gets a connection using the provided context.\n//\n// The provided Context must be non-nil. If the context expires before the\n// connection is complete, an error is returned. Any expiration on the context\n// will not affect the returned connection.\n//\n// If the function completes without error, then the application must close the\n// returned connection.\nfunc (p *Pool) GetContext(ctx context.Context) (Conn, error) {\n\t// Wait until there is a vacant connection in the pool.\n\twaited, err := p.waitVacantConn(ctx)\n\tif err != nil {\n\t\treturn errorConn{err}, err\n\t}\n\n\tp.mu.Lock()\n\n\tif waited > 0 {\n\t\tp.waitCount++\n\t\tp.waitDuration += waited\n\t}\n\n\t// Prune stale connections at the back of the idle list.\n\tif p.IdleTimeout > 0 {\n\t\tn := p.idle.count\n\t\tfor i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ {\n\t\t\tpc := p.idle.back\n\t\t\tp.idle.popBack()\n\t\t\tp.mu.Unlock()\n\t\t\tpc.c.Close()\n\t\t\tp.mu.Lock()\n\t\t\tp.active--\n\t\t}\n\t}\n\n\t// Get idle connection from the front of idle list.\n\tfor p.idle.front != nil {\n\t\tpc := p.idle.front\n\t\tp.idle.popFront()\n\t\tp.mu.Unlock()\n\t\tif (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) &&\n\t\t\t(p.TestOnBorrowContext == nil || p.TestOnBorrowContext(ctx, pc.c, pc.t) == nil) &&\n\t\t\t(p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) {\n\t\t\treturn &activeConn{p: p, pc: pc}, nil\n\t\t}\n\t\tpc.c.Close()\n\t\tp.mu.Lock()\n\t\tp.active--\n\t}\n\n\t// Check for pool closed before dialing a new connection.\n\tif p.closed {\n\t\tp.mu.Unlock()\n\t\terr := errors.New(\"redigo: get on closed pool\")\n\t\treturn errorConn{err}, err\n\t}\n\n\t// Handle limit for p.Wait == false.\n\tif !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive {\n\t\tp.mu.Unlock()\n\t\treturn errorConn{ErrPoolExhausted}, ErrPoolExhausted\n\t}\n\n\tp.active++\n\tp.mu.Unlock()\n\tc, err := p.dial(ctx)\n\tif err != nil {\n\t\tp.mu.Lock()\n\t\tp.active--\n\t\tif p.ch != nil && !p.closed {\n\t\t\tp.ch <- struct{}{}\n\t\t}\n\t\tp.mu.Unlock()\n\t\treturn errorConn{err}, err\n\t}\n\treturn &activeConn{p: p, pc: &poolConn{c: c, created: nowFunc()}}, nil\n}\n\n// PoolStats contains pool statistics.\ntype PoolStats struct {\n\t// ActiveCount is the number of connections in the pool. The count includes\n\t// idle connections and connections in use.\n\tActiveCount int\n\t// IdleCount is the number of idle connections in the pool.\n\tIdleCount int\n\n\t// WaitCount is the total number of connections waited for.\n\t// This value is currently not guaranteed to be 100% accurate.\n\tWaitCount int64\n\n\t// WaitDuration is the total time blocked waiting for a new connection.\n\t// This value is currently not guaranteed to be 100% accurate.\n\tWaitDuration time.Duration\n}\n\n// Stats returns pool's statistics.\n// Example usage with metric libraries can be found in the package documentation.\nfunc (p *Pool) Stats() PoolStats {\n\tp.mu.Lock()\n\tstats := PoolStats{\n\t\tActiveCount:  p.active,\n\t\tIdleCount:    p.idle.count,\n\t\tWaitCount:    p.waitCount,\n\t\tWaitDuration: p.waitDuration,\n\t}\n\tp.mu.Unlock()\n\n\treturn stats\n}\n\n// ActiveCount returns the number of connections in the pool. The count\n// includes idle connections and connections in use.\nfunc (p *Pool) ActiveCount() int {\n\tp.mu.Lock()\n\tactive := p.active\n\tp.mu.Unlock()\n\treturn active\n}\n\n// IdleCount returns the number of idle connections in the pool.\nfunc (p *Pool) IdleCount() int {\n\tp.mu.Lock()\n\tidle := p.idle.count\n\tp.mu.Unlock()\n\treturn idle\n}\n\n// Close releases the resources used by the pool.\nfunc (p *Pool) Close() error {\n\tp.mu.Lock()\n\tif p.closed {\n\t\tp.mu.Unlock()\n\t\treturn nil\n\t}\n\tp.closed = true\n\tp.active -= p.idle.count\n\tpc := p.idle.front\n\tp.idle.count = 0\n\tp.idle.front, p.idle.back = nil, nil\n\tif p.ch != nil {\n\t\tclose(p.ch)\n\t}\n\tp.mu.Unlock()\n\tfor ; pc != nil; pc = pc.next {\n\t\tpc.c.Close()\n\t}\n\treturn nil\n}\n\nfunc (p *Pool) lazyInit() {\n\tp.initOnce.Do(func() {\n\t\tp.ch = make(chan struct{}, p.MaxActive)\n\t\tif p.closed {\n\t\t\tclose(p.ch)\n\t\t} else {\n\t\t\tfor i := 0; i < p.MaxActive; i++ {\n\t\t\t\tp.ch <- struct{}{}\n\t\t\t}\n\t\t}\n\t})\n}\n\n// waitVacantConn waits for a vacant connection in pool if waiting\n// is enabled and pool size is limited, otherwise returns instantly.\n// If ctx expires before that, an error is returned.\n//\n// If there were no vacant connection in the pool right away it returns the time spent waiting\n// for that connection to appear in the pool.\nfunc (p *Pool) waitVacantConn(ctx context.Context) (waited time.Duration, err error) {\n\tif !p.Wait || p.MaxActive <= 0 {\n\t\t// No wait or no connection limit.\n\t\treturn 0, nil\n\t}\n\n\tp.lazyInit()\n\n\t// wait indicates if we believe it will block so its not 100% accurate\n\t// however for stats it should be good enough.\n\twait := len(p.ch) == 0\n\tvar start time.Time\n\tif wait {\n\t\tstart = time.Now()\n\t}\n\n\tselect {\n\tcase <-p.ch:\n\t\t// Additionally check that context hasn't expired while we were waiting,\n\t\t// because `select` picks a random `case` if several of them are \"ready\".\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tp.ch <- struct{}{}\n\t\t\treturn 0, ctx.Err()\n\t\tdefault:\n\t\t}\n\tcase <-ctx.Done():\n\t\treturn 0, ctx.Err()\n\t}\n\n\tif wait {\n\t\treturn time.Since(start), nil\n\t}\n\treturn 0, nil\n}\n\nfunc (p *Pool) dial(ctx context.Context) (Conn, error) {\n\tif p.DialContext != nil {\n\t\treturn p.DialContext(ctx)\n\t}\n\tif p.Dial != nil {\n\t\treturn p.Dial()\n\t}\n\treturn nil, errors.New(\"redigo: must pass Dial or DialContext to pool\")\n}\n\nfunc (p *Pool) put(pc *poolConn, forceClose bool) error {\n\tp.mu.Lock()\n\tif !p.closed && !forceClose {\n\t\tpc.t = nowFunc()\n\t\tp.idle.pushFront(pc)\n\t\tif p.idle.count > p.MaxIdle {\n\t\t\tpc = p.idle.back\n\t\t\tp.idle.popBack()\n\t\t} else {\n\t\t\tpc = nil\n\t\t}\n\t}\n\n\tif pc != nil {\n\t\tp.mu.Unlock()\n\t\tpc.c.Close()\n\t\tp.mu.Lock()\n\t\tp.active--\n\t}\n\n\tif p.ch != nil && !p.closed {\n\t\tp.ch <- struct{}{}\n\t}\n\tp.mu.Unlock()\n\treturn nil\n}\n\ntype activeConn struct {\n\tp     *Pool\n\tpc    *poolConn\n\tstate int\n}\n\nvar (\n\tsentinel     []byte\n\tsentinelOnce sync.Once\n)\n\nfunc initSentinel() {\n\tp := make([]byte, 64)\n\tif _, err := rand.Read(p); err == nil {\n\t\tsentinel = p\n\t} else {\n\t\th := sha1.New()\n\t\tio.WriteString(h, \"Oops, rand failed. Use time instead.\")       // nolint: errcheck\n\t\tio.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) // nolint: errcheck\n\t\tsentinel = h.Sum(nil)\n\t}\n}\n\nfunc (ac *activeConn) firstError(errs ...error) error {\n\tfor _, err := range errs[:len(errs)-1] {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn errs[len(errs)-1]\n}\n\nfunc (ac *activeConn) Close() (err error) {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn nil\n\t}\n\tac.pc = nil\n\n\tif ac.state&connectionMultiState != 0 {\n\t\terr = pc.c.Send(\"DISCARD\")\n\t\tac.state &^= (connectionMultiState | connectionWatchState)\n\t} else if ac.state&connectionWatchState != 0 {\n\t\terr = pc.c.Send(\"UNWATCH\")\n\t\tac.state &^= connectionWatchState\n\t}\n\tif ac.state&connectionSubscribeState != 0 {\n\t\terr = ac.firstError(err,\n\t\t\tpc.c.Send(\"UNSUBSCRIBE\"),\n\t\t\tpc.c.Send(\"PUNSUBSCRIBE\"),\n\t\t)\n\t\t// To detect the end of the message stream, ask the server to echo\n\t\t// a sentinel value and read until we see that value.\n\t\tsentinelOnce.Do(initSentinel)\n\t\terr = ac.firstError(err,\n\t\t\tpc.c.Send(\"ECHO\", sentinel),\n\t\t\tpc.c.Flush(),\n\t\t)\n\t\tfor {\n\t\t\tp, err2 := pc.c.Receive()\n\t\t\tif err2 != nil {\n\t\t\t\terr = ac.firstError(err, err2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {\n\t\t\t\tac.state &^= connectionSubscribeState\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\t_, err2 := pc.c.Do(\"\")\n\treturn ac.firstError(\n\t\terr,\n\t\terr2,\n\t\tac.p.put(pc, ac.state != 0 || pc.c.Err() != nil),\n\t)\n}\n\nfunc (ac *activeConn) Err() error {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn errConnClosed\n\t}\n\treturn pc.c.Err()\n}\n\nfunc (ac *activeConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn nil, errConnClosed\n\t}\n\tcwt, ok := pc.c.(ConnWithContext)\n\tif !ok {\n\t\treturn nil, errContextNotSupported\n\t}\n\tci := lookupCommandInfo(commandName)\n\tac.state = (ac.state | ci.Set) &^ ci.Clear\n\treturn cwt.DoContext(ctx, commandName, args...)\n}\n\nfunc (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn nil, errConnClosed\n\t}\n\tci := lookupCommandInfo(commandName)\n\tac.state = (ac.state | ci.Set) &^ ci.Clear\n\treturn pc.c.Do(commandName, args...)\n}\n\nfunc (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn nil, errConnClosed\n\t}\n\tcwt, ok := pc.c.(ConnWithTimeout)\n\tif !ok {\n\t\treturn nil, errTimeoutNotSupported\n\t}\n\tci := lookupCommandInfo(commandName)\n\tac.state = (ac.state | ci.Set) &^ ci.Clear\n\treturn cwt.DoWithTimeout(timeout, commandName, args...)\n}\n\nfunc (ac *activeConn) Send(commandName string, args ...interface{}) error {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn errConnClosed\n\t}\n\tci := lookupCommandInfo(commandName)\n\tac.state = (ac.state | ci.Set) &^ ci.Clear\n\treturn pc.c.Send(commandName, args...)\n}\n\nfunc (ac *activeConn) Flush() error {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn errConnClosed\n\t}\n\treturn pc.c.Flush()\n}\n\nfunc (ac *activeConn) Receive() (reply interface{}, err error) {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn nil, errConnClosed\n\t}\n\treturn pc.c.Receive()\n}\n\nfunc (ac *activeConn) ReceiveContext(ctx context.Context) (reply interface{}, err error) {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn nil, errConnClosed\n\t}\n\tcwt, ok := pc.c.(ConnWithContext)\n\tif !ok {\n\t\treturn nil, errContextNotSupported\n\t}\n\treturn cwt.ReceiveContext(ctx)\n}\n\nfunc (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) {\n\tpc := ac.pc\n\tif pc == nil {\n\t\treturn nil, errConnClosed\n\t}\n\tcwt, ok := pc.c.(ConnWithTimeout)\n\tif !ok {\n\t\treturn nil, errTimeoutNotSupported\n\t}\n\treturn cwt.ReceiveWithTimeout(timeout)\n}\n\ntype errorConn struct{ err error }\n\nfunc (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }\nfunc (ec errorConn) DoContext(context.Context, string, ...interface{}) (interface{}, error) {\n\treturn nil, ec.err\n}\nfunc (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) {\n\treturn nil, ec.err\n}\nfunc (ec errorConn) Send(string, ...interface{}) error                     { return ec.err }\nfunc (ec errorConn) Err() error                                            { return ec.err }\nfunc (ec errorConn) Close() error                                          { return nil }\nfunc (ec errorConn) Flush() error                                          { return ec.err }\nfunc (ec errorConn) Receive() (interface{}, error)                         { return nil, ec.err }\nfunc (ec errorConn) ReceiveContext(context.Context) (interface{}, error)   { return nil, ec.err }\nfunc (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err }\n\ntype idleList struct {\n\tcount       int\n\tfront, back *poolConn\n}\n\ntype poolConn struct {\n\tc          Conn\n\tt          time.Time\n\tcreated    time.Time\n\tnext, prev *poolConn\n}\n\nfunc (l *idleList) pushFront(pc *poolConn) {\n\tpc.next = l.front\n\tpc.prev = nil\n\tif l.count == 0 {\n\t\tl.back = pc\n\t} else {\n\t\tl.front.prev = pc\n\t}\n\tl.front = pc\n\tl.count++\n}\n\nfunc (l *idleList) popFront() {\n\tpc := l.front\n\tl.count--\n\tif l.count == 0 {\n\t\tl.front, l.back = nil, nil\n\t} else {\n\t\tpc.next.prev = nil\n\t\tl.front = pc.next\n\t}\n\tpc.next, pc.prev = nil, nil\n}\n\nfunc (l *idleList) popBack() {\n\tpc := l.back\n\tl.count--\n\tif l.count == 0 {\n\t\tl.front, l.back = nil, nil\n\t} else {\n\t\tpc.prev.next = nil\n\t\tl.back = pc.prev\n\t}\n\tpc.next, pc.prev = nil, nil\n}\n"
  },
  {
    "path": "redis/pool_test.go",
    "content": "// Copyright 2011 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"reflect\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\ttestGoRoutines = 10\n)\n\ntype poolTestConn struct {\n\td   *poolDialer\n\terr error\n\tredis.Conn\n}\n\nfunc (c *poolTestConn) Close() error {\n\tc.d.mu.Lock()\n\tc.d.open -= 1\n\tc.d.mu.Unlock()\n\treturn c.Conn.Close()\n}\n\nfunc (c *poolTestConn) Err() error { return c.err }\n\nfunc (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) {\n\treturn c.do(c.Conn.Do, commandName, args...)\n}\n\nfunc (c *poolTestConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) {\n\tcwc, ok := c.Conn.(redis.ConnWithContext)\n\tif !ok {\n\t\treturn nil, errors.New(\"redis: connection does not support ConnWithContext\")\n\t}\n\treturn c.do(\n\t\tfunc(c string, a ...interface{}) (interface{}, error) {\n\t\t\treturn cwc.DoContext(ctx, c, a...)\n\t\t},\n\t\tcommandName, args)\n}\n\nfunc (c *poolTestConn) do(\n\tfn func(commandName string, args ...interface{}) (interface{}, error),\n\tcommandName string, args ...interface{},\n) (interface{}, error) {\n\tif commandName == \"ERR\" {\n\t\tc.err = args[0].(error)\n\t\tcommandName = \"PING\"\n\t}\n\tif commandName != \"\" {\n\t\tc.d.commands = append(c.d.commands, commandName)\n\t}\n\treturn fn(commandName, args...)\n}\n\nfunc (c *poolTestConn) Send(commandName string, args ...interface{}) error {\n\tc.d.commands = append(c.d.commands, commandName)\n\treturn c.Conn.Send(commandName, args...)\n}\n\nfunc (c *poolTestConn) ReceiveContext(ctx context.Context) (reply interface{}, err error) {\n\tcwc, ok := c.Conn.(redis.ConnWithContext)\n\tif !ok {\n\t\treturn nil, errors.New(\"redis: connection does not support ConnWithContext\")\n\t}\n\treturn cwc.ReceiveContext(ctx)\n}\n\ntype poolDialer struct {\n\tmu       sync.Mutex\n\tt        *testing.T\n\tdialed   int\n\topen     int\n\tcommands []string\n\tdialErr  error\n}\n\nfunc (d *poolDialer) dial() (redis.Conn, error) {\n\treturn d.dialContext(context.Background())\n}\n\nfunc (d *poolDialer) dialContext(ctx context.Context) (redis.Conn, error) {\n\td.mu.Lock()\n\td.dialed += 1\n\tdialErr := d.dialErr\n\td.mu.Unlock()\n\tif dialErr != nil {\n\t\treturn nil, d.dialErr\n\t}\n\tc, err := redis.DialDefaultServerContext(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\td.mu.Lock()\n\td.open += 1\n\td.mu.Unlock()\n\treturn &poolTestConn{d: d, Conn: c}, nil\n}\n\nfunc (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse int) {\n\td.t.Helper()\n\td.checkAll(message, p, dialed, open, inuse, 0, 0)\n}\n\nfunc (d *poolDialer) checkAll(message string, p *redis.Pool, dialed, open, inuse int, waitCountMax int64, waitDurationMax time.Duration) {\n\td.t.Helper()\n\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tif d.dialed != dialed {\n\t\td.t.Errorf(\"%s: dialed=%d, want %d\", message, d.dialed, dialed)\n\t}\n\tif d.open != open {\n\t\td.t.Errorf(\"%s: open=%d, want %d\", message, d.open, open)\n\t}\n\n\tstats := p.Stats()\n\n\tif stats.ActiveCount != open {\n\t\td.t.Errorf(\"%s: active=%d, want %d\", message, stats.ActiveCount, open)\n\t}\n\tif stats.IdleCount != open-inuse {\n\t\td.t.Errorf(\"%s: idle=%d, want %d\", message, stats.IdleCount, open-inuse)\n\t}\n\n\tif stats.WaitCount > waitCountMax {\n\t\td.t.Errorf(\"%s: unexpected wait=%d want at most %d\", message, stats.WaitCount, waitCountMax)\n\t}\n\n\tif waitCountMax == 0 {\n\t\tif stats.WaitDuration != 0 {\n\t\t\td.t.Errorf(\"%s: unexpected waitDuration=%v want %v\", message, stats.WaitDuration, 0)\n\t\t}\n\t\treturn\n\t}\n\n\tif stats.WaitDuration > waitDurationMax {\n\t\td.t.Errorf(\"%s: unexpected waitDuration=%v want < %v\", message, stats.WaitDuration, waitDurationMax)\n\t}\n}\n\nfunc TestPoolReuse(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle: 2,\n\t\tDial:    d.dial,\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tc1 := p.Get()\n\t\t_, err := c1.Do(\"PING\")\n\t\trequire.NoError(t, err)\n\t\tc2 := p.Get()\n\t\t_, err = c2.Do(\"PING\")\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, c1.Close())\n\t\trequire.NoError(t, c2.Close())\n\t}\n\n\td.check(\"before close\", p, 2, 2, 0)\n\tp.Close()\n\td.check(\"after close\", p, 2, 0, 0)\n}\n\nfunc TestPoolMaxIdle(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle: 2,\n\t\tDial:    d.dial,\n\t}\n\tdefer p.Close()\n\n\tfor i := 0; i < 10; i++ {\n\t\tc1 := p.Get()\n\t\t_, err = c1.Do(\"PING\")\n\t\trequire.NoError(t, err)\n\t\tc2 := p.Get()\n\t\t_, err = c2.Do(\"PING\")\n\t\trequire.NoError(t, err)\n\t\tc3 := p.Get()\n\t\t_, err = c3.Do(\"PING\")\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, c1.Close())\n\t\trequire.NoError(t, c2.Close())\n\t\trequire.NoError(t, c3.Close())\n\t}\n\td.check(\"before close\", p, 12, 2, 0)\n\tp.Close()\n\td.check(\"after close\", p, 12, 0, 0)\n}\n\nfunc TestPoolError(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle: 2,\n\t\tDial:    d.dial,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\t_, err := c.Do(\"ERR\", io.EOF)\n\trequire.NoError(t, err)\n\tif c.Err() == nil {\n\t\tt.Errorf(\"expected c.Err() != nil\")\n\t}\n\tc.Close()\n\n\tc = p.Get()\n\t_, err = c.Do(\"ERR\", io.EOF)\n\trequire.NoError(t, err)\n\tc.Close()\n\n\td.check(\".\", p, 2, 0, 0)\n}\n\nfunc TestPoolClose(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle: 2,\n\t\tDial:    d.dial,\n\t}\n\tdefer p.Close()\n\n\tc1 := p.Get()\n\t_, err := c1.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc2 := p.Get()\n\t_, err = c2.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc3 := p.Get()\n\t_, err = c3.Do(\"PING\")\n\trequire.NoError(t, err)\n\n\tc1.Close()\n\tif _, err := c1.Do(\"PING\"); err == nil {\n\t\tt.Errorf(\"expected error after connection closed\")\n\t}\n\n\tc2.Close()\n\tc2.Close()\n\n\tp.Close()\n\n\td.check(\"after pool close\", p, 3, 1, 1)\n\n\tif _, err := c1.Do(\"PING\"); err == nil {\n\t\tt.Errorf(\"expected error after connection and pool closed\")\n\t}\n\n\tc3.Close()\n\n\td.check(\"after conn close\", p, 3, 0, 0)\n\n\tc1 = p.Get()\n\tif _, err := c1.Do(\"PING\"); err == nil {\n\t\tt.Errorf(\"expected error after pool closed\")\n\t}\n}\n\nfunc TestPoolClosedConn(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:     2,\n\t\tIdleTimeout: 300 * time.Second,\n\t\tDial:        d.dial,\n\t}\n\tdefer p.Close()\n\tc := p.Get()\n\tif c.Err() != nil {\n\t\tt.Fatal(\"get failed\")\n\t}\n\tc.Close()\n\tif err := c.Err(); err == nil {\n\t\tt.Fatal(\"Err on closed connection did not return error\")\n\t}\n\tif _, err := c.Do(\"PING\"); err == nil {\n\t\tt.Fatal(\"Do on closed connection did not return error\")\n\t}\n\tif err := c.Send(\"PING\"); err == nil {\n\t\tt.Fatal(\"Send on closed connection did not return error\")\n\t}\n\tif err := c.Flush(); err == nil {\n\t\tt.Fatal(\"Flush on closed connection did not return error\")\n\t}\n\tif _, err := c.Receive(); err == nil {\n\t\tt.Fatal(\"Receive on closed connection did not return error\")\n\t}\n}\n\nfunc TestPoolIdleTimeout(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:     2,\n\t\tIdleTimeout: 300 * time.Second,\n\t\tDial:        d.dial,\n\t}\n\tdefer p.Close()\n\n\tnow := time.Now()\n\tredis.SetNowFunc(func() time.Time { return now })\n\tdefer redis.SetNowFunc(time.Now)\n\n\tc := p.Get()\n\t_, err := c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\td.check(\"1\", p, 1, 1, 0)\n\n\tnow = now.Add(p.IdleTimeout + 1)\n\n\tc = p.Get()\n\t_, err = c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\td.check(\"2\", p, 2, 1, 0)\n}\n\nfunc TestPoolMaxLifetime(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:         2,\n\t\tMaxConnLifetime: 300 * time.Second,\n\t\tDial:            d.dial,\n\t}\n\tdefer p.Close()\n\n\tnow := time.Now()\n\tredis.SetNowFunc(func() time.Time { return now })\n\tdefer redis.SetNowFunc(time.Now)\n\n\tc := p.Get()\n\t_, err := c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\td.check(\"1\", p, 1, 1, 0)\n\n\tnow = now.Add(p.MaxConnLifetime + 1)\n\n\tc = p.Get()\n\t_, err = c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\td.check(\"2\", p, 2, 1, 0)\n}\n\nfunc TestPoolConcurrenSendReceive(t *testing.T) {\n\tp := &redis.Pool{\n\t\tDial: func() (redis.Conn, error) { return redis.DialDefaultServer() },\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\tdone := make(chan error, 1)\n\tgo func() {\n\t\t_, err := c.Receive()\n\t\tdone <- err\n\t}()\n\trequire.NoError(t, c.Send(\"PING\"))\n\tc.Flush()\n\terr := <-done\n\tif err != nil {\n\t\tt.Fatalf(\"Receive() returned error %v\", err)\n\t}\n\t_, err = c.Do(\"\")\n\tif err != nil {\n\t\tt.Fatalf(\"Do() returned error %v\", err)\n\t}\n\tc.Close()\n}\n\nfunc TestPoolBorrowCheck(t *testing.T) {\n\tpingN := func(ctx context.Context, p *redis.Pool, n int) {\n\t\tfor i := 0; i < n; i++ {\n\t\t\tfunc() {\n\t\t\t\tc, err := p.GetContext(ctx)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer func() {\n\t\t\t\t\trequire.NoError(t, c.Close())\n\t\t\t\t}()\n\t\t\t\t_, err = redis.DoContext(c, ctx, \"PING\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}()\n\t\t}\n\t}\n\n\tcheckLastUsedTimes := func(lastUsedTimes []time.Time, startTime, endTime time.Time, wantLen int) {\n\t\trequire.Len(t, lastUsedTimes, wantLen)\n\t\tfor i, lastUsed := range lastUsedTimes {\n\t\t\tif i == 0 {\n\t\t\t\trequire.True(t, lastUsed.After(startTime))\n\t\t\t} else {\n\t\t\t\trequire.True(t, lastUsed.After(lastUsedTimes[i-1]))\n\t\t\t}\n\t\t\trequire.True(t, lastUsed.Before(endTime))\n\t\t}\n\t}\n\n\tt.Run(\"TestOnBorrow-error\", func(t *testing.T) {\n\t\td := poolDialer{t: t}\n\t\tp := &redis.Pool{\n\t\t\tMaxIdle:      2,\n\t\t\tDialContext:  d.dialContext,\n\t\t\tTestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error(\"BLAH\") },\n\t\t}\n\t\tdefer p.Close()\n\t\tpingN(context.Background(), p, 10)\n\t\td.check(\"1\", p, 10, 1, 0)\n\t})\n\n\tt.Run(\"TestOnBorrow-nil-error\", func(t *testing.T) {\n\t\td := poolDialer{t: t}\n\t\tvar borrowErrs []error\n\t\tvar lastUsedTimes []time.Time\n\t\tp := &redis.Pool{\n\t\t\tMaxIdle:     2,\n\t\t\tDialContext: d.dialContext,\n\t\t\tTestOnBorrow: func(c redis.Conn, lastUsed time.Time) error {\n\t\t\t\tlastUsedTimes = append(lastUsedTimes, lastUsed)\n\t\t\t\t_, err := c.Do(\"PING\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tborrowErrs = append(borrowErrs, err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t},\n\t\t}\n\t\tdefer p.Close()\n\n\t\tstartTime := time.Now()\n\t\tpingN(context.Background(), p, 10)\n\t\tendTime := time.Now()\n\n\t\trequire.Empty(t, borrowErrs)\n\t\tcheckLastUsedTimes(lastUsedTimes, startTime, endTime, 9)\n\t\td.check(\"1\", p, 1, 1, 0)\n\t})\n\n\tt.Run(\"TestOnBorrowContext-error\", func(t *testing.T) {\n\t\td := poolDialer{t: t}\n\t\tp := &redis.Pool{\n\t\t\tMaxIdle:             2,\n\t\t\tDialContext:         d.dialContext,\n\t\t\tTestOnBorrowContext: func(context.Context, redis.Conn, time.Time) error { return redis.Error(\"BLAH\") },\n\t\t}\n\t\tdefer p.Close()\n\t\tpingN(context.Background(), p, 10)\n\t\td.check(\"1\", p, 10, 1, 0)\n\t})\n\n\tt.Run(\"TestOnBorrowContext-nil-error\", func(t *testing.T) {\n\t\td := poolDialer{t: t}\n\t\tvar borrowErrs []error\n\t\tvar lastUsedTimes []time.Time\n\t\tp := &redis.Pool{\n\t\t\tMaxIdle:     2,\n\t\t\tDialContext: d.dialContext,\n\t\t\tTestOnBorrowContext: func(ctx context.Context, c redis.Conn, lastUsed time.Time) error {\n\t\t\t\tlastUsedTimes = append(lastUsedTimes, lastUsed)\n\t\t\t\t_, err := redis.DoContext(c, ctx, \"PING\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tborrowErrs = append(borrowErrs, err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t},\n\t\t}\n\t\tdefer p.Close()\n\n\t\tstartTime := time.Now()\n\t\tpingN(context.Background(), p, 10)\n\t\tendTime := time.Now()\n\n\t\trequire.Empty(t, borrowErrs)\n\t\tcheckLastUsedTimes(lastUsedTimes, startTime, endTime, 9)\n\t\td.check(\"1\", p, 1, 1, 0)\n\t})\n\n\tt.Run(\"TestOnBorrowContext-context.Canceled\", func(t *testing.T) {\n\t\td := poolDialer{t: t}\n\t\tvar borrowErrs []error\n\t\tp := &redis.Pool{\n\t\t\tMaxIdle:     2,\n\t\t\tDialContext: d.dialContext,\n\t\t\tTestOnBorrowContext: func(ctx context.Context, c redis.Conn, _ time.Time) error {\n\t\t\t\t_, err := redis.DoContext(c, ctx, \"PING\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tborrowErrs = append(borrowErrs, err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t},\n\t\t}\n\t\tdefer p.Close()\n\n\t\tctx, ctxCancel := context.WithCancel(context.Background())\n\t\tdefer ctxCancel()\n\n\t\tpingN(ctx, p, 2)\n\t\td.check(\"1\", p, 1, 1, 0)\n\t\trequire.Empty(t, borrowErrs)\n\n\t\tctxCancel()\n\n\t\t_, err := p.GetContext(ctx)\n\t\trequire.ErrorIs(t, err, context.Canceled)\n\n\t\td.check(\"1\", p, 2, 0, 0)\n\t\trequire.Len(t, borrowErrs, 1)\n\t\trequire.ErrorIs(t, borrowErrs[0], context.Canceled)\n\t})\n}\n\nfunc TestPoolMaxActive(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   2,\n\t\tMaxActive: 2,\n\t\tDial:      d.dial,\n\t}\n\tdefer p.Close()\n\n\tc1 := p.Get()\n\t_, err := c1.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc2 := p.Get()\n\t_, err = c2.Do(\"PING\")\n\trequire.NoError(t, err)\n\n\td.check(\"1\", p, 2, 2, 2)\n\n\tc3 := p.Get()\n\tif _, err := c3.Do(\"PING\"); err != redis.ErrPoolExhausted {\n\t\tt.Errorf(\"expected pool exhausted\")\n\t}\n\n\tc3.Close()\n\td.check(\"2\", p, 2, 2, 2)\n\tc2.Close()\n\td.check(\"3\", p, 2, 2, 1)\n\n\tc3 = p.Get()\n\tif _, err := c3.Do(\"PING\"); err != nil {\n\t\tt.Errorf(\"expected good channel, err=%v\", err)\n\t}\n\tc3.Close()\n\n\td.check(\"4\", p, 2, 2, 1)\n}\n\nfunc TestPoolWaitStats(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tWait:      true,\n\t\tMaxIdle:   2,\n\t\tMaxActive: 2,\n\t\tDial:      d.dial,\n\t}\n\tdefer p.Close()\n\n\tc1 := p.Get()\n\t_, err := c1.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc2 := p.Get()\n\t_, err = c2.Do(\"PING\")\n\trequire.NoError(t, err)\n\n\td.checkAll(\"1\", p, 2, 2, 2, 0, 0)\n\n\tstart := time.Now()\n\tgo func() {\n\t\ttime.Sleep(time.Millisecond * 100)\n\t\tc1.Close()\n\t}()\n\n\tc3 := p.Get()\n\td.checkAll(\"2\", p, 2, 2, 2, 1, time.Since(start))\n\n\tif _, err := c3.Do(\"PING\"); err != nil {\n\t\tt.Errorf(\"expected good channel, err=%v\", err)\n\t}\n}\n\nfunc TestPoolMonitorCleanup(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   2,\n\t\tMaxActive: 2,\n\t\tDial:      d.dial,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\trequire.NoError(t, c.Send(\"MONITOR\"))\n\tc.Close()\n\n\td.check(\"\", p, 1, 0, 0)\n}\n\nfunc TestPoolPubSubCleanup(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   2,\n\t\tMaxActive: 2,\n\t\tDial:      d.dial,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\trequire.NoError(t, c.Send(\"SUBSCRIBE\", \"x\"))\n\tc.Close()\n\n\twant := []string{\"SUBSCRIBE\", \"UNSUBSCRIBE\", \"PUNSUBSCRIBE\", \"ECHO\"}\n\tif !reflect.DeepEqual(d.commands, want) {\n\t\tt.Errorf(\"got commands %v, want %v\", d.commands, want)\n\t}\n\td.commands = nil\n\n\tc = p.Get()\n\trequire.NoError(t, c.Send(\"PSUBSCRIBE\", \"x*\"))\n\tc.Close()\n\n\twant = []string{\"PSUBSCRIBE\", \"UNSUBSCRIBE\", \"PUNSUBSCRIBE\", \"ECHO\"}\n\tif !reflect.DeepEqual(d.commands, want) {\n\t\tt.Errorf(\"got commands %v, want %v\", d.commands, want)\n\t}\n\td.commands = nil\n}\n\nfunc TestPoolTransactionCleanup(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   2,\n\t\tMaxActive: 2,\n\t\tDial:      d.dial,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\t_, err := c.Do(\"WATCH\", \"key\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\twant := []string{\"WATCH\", \"PING\", \"UNWATCH\"}\n\tif !reflect.DeepEqual(d.commands, want) {\n\t\tt.Errorf(\"got commands %v, want %v\", d.commands, want)\n\t}\n\td.commands = nil\n\n\tc = p.Get()\n\t_, err = c.Do(\"WATCH\", \"key\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"UNWATCH\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\twant = []string{\"WATCH\", \"UNWATCH\", \"PING\"}\n\tif !reflect.DeepEqual(d.commands, want) {\n\t\tt.Errorf(\"got commands %v, want %v\", d.commands, want)\n\t}\n\td.commands = nil\n\n\tc = p.Get()\n\t_, err = c.Do(\"WATCH\", \"key\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"MULTI\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\twant = []string{\"WATCH\", \"MULTI\", \"PING\", \"DISCARD\"}\n\tif !reflect.DeepEqual(d.commands, want) {\n\t\tt.Errorf(\"got commands %v, want %v\", d.commands, want)\n\t}\n\td.commands = nil\n\n\tc = p.Get()\n\t_, err = c.Do(\"WATCH\", \"key\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"MULTI\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"DISCARD\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\twant = []string{\"WATCH\", \"MULTI\", \"DISCARD\", \"PING\"}\n\tif !reflect.DeepEqual(d.commands, want) {\n\t\tt.Errorf(\"got commands %v, want %v\", d.commands, want)\n\t}\n\td.commands = nil\n\n\tc = p.Get()\n\t_, err = c.Do(\"WATCH\", \"key\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"MULTI\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"EXEC\")\n\trequire.NoError(t, err)\n\t_, err = c.Do(\"PING\")\n\trequire.NoError(t, err)\n\tc.Close()\n\n\twant = []string{\"WATCH\", \"MULTI\", \"EXEC\", \"PING\"}\n\tif !reflect.DeepEqual(d.commands, want) {\n\t\tt.Errorf(\"got commands %v, want %v\", d.commands, want)\n\t}\n\td.commands = nil\n}\n\nfunc startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error {\n\terrs := make(chan error, testGoRoutines)\n\tfor i := 0; i < cap(errs); i++ {\n\t\tgo func() {\n\t\t\tc := p.Get()\n\t\t\t_, err := c.Do(cmd, args...)\n\t\t\tc.Close()\n\t\t\terrs <- err\n\t\t}()\n\t}\n\n\treturn errs\n}\n\nfunc TestWaitPool(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   1,\n\t\tMaxActive: 1,\n\t\tDial:      d.dial,\n\t\tWait:      true,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\tstart := time.Now()\n\terrs := startGoroutines(p, \"PING\")\n\td.check(\"before close\", p, 1, 1, 1)\n\tc.Close()\n\ttimeout := time.After(2 * time.Second)\n\tfor i := 0; i < cap(errs); i++ {\n\t\tselect {\n\t\tcase err := <-errs:\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\tcase <-timeout:\n\t\t\tt.Fatalf(\"timeout waiting for blocked goroutine %d\", i)\n\t\t}\n\t}\n\td.checkAll(\"done\", p, 1, 1, 0, testGoRoutines, time.Since(start)*testGoRoutines)\n}\n\nfunc TestWaitPoolClose(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   1,\n\t\tMaxActive: 1,\n\t\tDial:      d.dial,\n\t\tWait:      true,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\tif _, err := c.Do(\"PING\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tstart := time.Now()\n\terrs := startGoroutines(p, \"PING\")\n\td.check(\"before close\", p, 1, 1, 1)\n\tp.Close()\n\ttimeout := time.After(2 * time.Second)\n\tfor i := 0; i < cap(errs); i++ {\n\t\tselect {\n\t\tcase err := <-errs:\n\t\t\tswitch err {\n\t\t\tcase nil:\n\t\t\t\tt.Fatal(\"blocked goroutine did not get error\")\n\t\t\tcase redis.ErrPoolExhausted:\n\t\t\t\tt.Fatal(\"blocked goroutine got pool exhausted error\")\n\t\t\t}\n\t\tcase <-timeout:\n\t\t\tt.Fatal(\"timeout waiting for blocked goroutine\")\n\t\t}\n\t}\n\tc.Close()\n\td.checkAll(\"done\", p, 1, 0, 0, testGoRoutines, time.Since(start)*testGoRoutines)\n}\n\nfunc TestWaitPoolCommandError(t *testing.T) {\n\ttestErr := errors.New(\"test\")\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   1,\n\t\tMaxActive: 1,\n\t\tDial:      d.dial,\n\t\tWait:      true,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\tstart := time.Now()\n\terrs := startGoroutines(p, \"ERR\", testErr)\n\td.check(\"before close\", p, 1, 1, 1)\n\tc.Close()\n\ttimeout := time.After(2 * time.Second)\n\tfor i := 0; i < cap(errs); i++ {\n\t\tselect {\n\t\tcase err := <-errs:\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\tcase <-timeout:\n\t\t\tt.Fatalf(\"timeout waiting for blocked goroutine %d\", i)\n\t\t}\n\t}\n\td.checkAll(\"done\", p, cap(errs), 0, 0, testGoRoutines, time.Since(start)*testGoRoutines)\n}\n\nfunc TestWaitPoolDialError(t *testing.T) {\n\ttestErr := errors.New(\"test\")\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   1,\n\t\tMaxActive: 1,\n\t\tDial:      d.dial,\n\t\tWait:      true,\n\t}\n\tdefer p.Close()\n\n\tc := p.Get()\n\tstart := time.Now()\n\terrs := startGoroutines(p, \"ERR\", testErr)\n\td.check(\"before close\", p, 1, 1, 1)\n\n\td.dialErr = errors.New(\"dial\")\n\tc.Close()\n\n\tnilCount := 0\n\terrCount := 0\n\ttimeout := time.After(2 * time.Second)\n\tfor i := 0; i < cap(errs); i++ {\n\t\tselect {\n\t\tcase err := <-errs:\n\t\t\tswitch err {\n\t\t\tcase nil:\n\t\t\t\tnilCount++\n\t\t\tcase d.dialErr:\n\t\t\t\terrCount++\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"expected dial error or nil, got %v\", err)\n\t\t\t}\n\t\tcase <-timeout:\n\t\t\tt.Fatalf(\"timeout waiting for blocked goroutine %d\", i)\n\t\t}\n\t}\n\tif nilCount != 1 {\n\t\tt.Errorf(\"expected one nil error, got %d\", nilCount)\n\t}\n\tif errCount != cap(errs)-1 {\n\t\tt.Errorf(\"expected %d dial errors, got %d\", cap(errs)-1, errCount)\n\t}\n\td.checkAll(\"done\", p, cap(errs), 0, 0, testGoRoutines, time.Since(start)*testGoRoutines)\n}\n\n// Borrowing requires us to iterate over the idle connections, unlock the pool,\n// and perform a blocking operation to check the connection still works. If\n// TestOnBorrow fails, we must reacquire the lock and continue iteration. This\n// test ensures that iteration will work correctly if multiple threads are\n// iterating simultaneously.\nfunc TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) {\n\tconst count = 100\n\n\t// First we'll Create a pool where the pilfering of idle connections fails.\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   count,\n\t\tMaxActive: count,\n\t\tDial:      d.dial,\n\t\tTestOnBorrow: func(redis.Conn, time.Time) error {\n\t\t\treturn errors.New(\"No way back into the real world.\")\n\t\t},\n\t}\n\tdefer p.Close()\n\n\t// Fill the pool with idle connections.\n\tconns := make([]redis.Conn, count)\n\tfor i := range conns {\n\t\tconns[i] = p.Get()\n\t}\n\tfor i := range conns {\n\t\tconns[i].Close()\n\t}\n\n\t// Spawn a bunch of goroutines to thrash the pool.\n\tvar wg sync.WaitGroup\n\twg.Add(count)\n\tfor i := 0; i < count; i++ {\n\t\tgo func() {\n\t\t\tc := p.Get()\n\t\t\tif c.Err() != nil {\n\t\t\t\tt.Errorf(\"pool get failed: %v\", c.Err())\n\t\t\t}\n\t\t\tc.Close()\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n\tif d.dialed != count*2 {\n\t\tt.Errorf(\"Expected %d dials, got %d\", count*2, d.dialed)\n\t}\n}\n\nfunc BenchmarkPoolGet(b *testing.B) {\n\tb.StopTimer()\n\tp := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2}\n\tc := p.Get()\n\tif err := c.Err(); err != nil {\n\t\tb.Fatal(err)\n\t}\n\tc.Close()\n\tdefer p.Close()\n\tb.StartTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tc = p.Get()\n\t\tc.Close()\n\t}\n}\n\nfunc BenchmarkPoolGetErr(b *testing.B) {\n\tb.StopTimer()\n\tp := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2}\n\tc := p.Get()\n\tif err := c.Err(); err != nil {\n\t\tb.Fatal(err)\n\t}\n\tc.Close()\n\tdefer p.Close()\n\tb.StartTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tc = p.Get()\n\t\tif err := c.Err(); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tc.Close()\n\t}\n}\n\nfunc BenchmarkPoolGetPing(b *testing.B) {\n\tb.StopTimer()\n\tp := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2}\n\tc := p.Get()\n\tif err := c.Err(); err != nil {\n\t\tb.Fatal(err)\n\t}\n\tc.Close()\n\tdefer p.Close()\n\tb.StartTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tc = p.Get()\n\t\tif _, err := c.Do(\"PING\"); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tc.Close()\n\t}\n}\n\nfunc TestWaitPoolGetContext(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   1,\n\t\tMaxActive: 1,\n\t\tDial:      d.dial,\n\t\tWait:      true,\n\t}\n\tdefer p.Close()\n\tc, err := p.GetContext(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"GetContext returned %v\", err)\n\t}\n\tdefer c.Close()\n}\n\nfunc TestWaitPoolGetContextIssue520(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   1,\n\t\tMaxActive: 1,\n\t\tDial:      d.dial,\n\t\tWait:      true,\n\t}\n\tdefer p.Close()\n\tctx1, cancel1 := context.WithTimeout(context.Background(), 1*time.Nanosecond)\n\tdefer cancel1()\n\tc, err := p.GetContext(ctx1)\n\tif err != context.DeadlineExceeded {\n\t\tt.Fatalf(\"GetContext returned %v\", err)\n\t}\n\tdefer c.Close()\n\n\tctx2, cancel2 := context.WithCancel(context.Background())\n\tdefer cancel2()\n\tc2, err := p.GetContext(ctx2)\n\tif err != nil {\n\t\tt.Fatalf(\"Get context returned %v\", err)\n\t}\n\tdefer c2.Close()\n}\n\nfunc TestWaitPoolGetContextWithDialContext(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:     1,\n\t\tMaxActive:   1,\n\t\tDialContext: d.dialContext,\n\t\tWait:        true,\n\t}\n\tdefer p.Close()\n\tc, err := p.GetContext(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"GetContext returned %v\", err)\n\t}\n\tdefer c.Close()\n}\n\nfunc TestPoolGetContext_DialContext(t *testing.T) {\n\tvar isPassed bool\n\tf := func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\tisPassed = true\n\t\treturn &testConn{}, nil\n\t}\n\n\tp := &redis.Pool{\n\t\tDialContext: func(ctx context.Context) (redis.Conn, error) {\n\t\t\treturn redis.DialContext(ctx, \"\", \"\", redis.DialContextFunc(f))\n\t\t},\n\t}\n\tdefer p.Close()\n\n\tif _, err := p.GetContext(context.Background()); err != nil {\n\t\tt.Fatalf(\"GetContext returned %v\", err)\n\t}\n\n\tif !isPassed {\n\t\tt.Fatal(\"DialContextFunc not passed\")\n\t}\n}\n\nfunc TestPoolGetContext_DialContext_CanceledContext(t *testing.T) {\n\taddr, err := redis.DefaultServerAddr()\n\tif err != nil {\n\t\tt.Fatalf(\"redis.DefaultServerAddr returned %v\", err)\n\t}\n\n\tp := &redis.Pool{\n\t\tDialContext: func(ctx context.Context) (redis.Conn, error) { return redis.DialContext(ctx, \"tcp\", addr) },\n\t}\n\tdefer p.Close()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\n\tif _, err := p.GetContext(ctx); err == nil {\n\t\tt.Fatalf(\"GetContext returned nil, expect error\")\n\t}\n}\n\nfunc TestWaitPoolGetAfterClose(t *testing.T) {\n\td := poolDialer{t: t}\n\tp := &redis.Pool{\n\t\tMaxIdle:   1,\n\t\tMaxActive: 1,\n\t\tDial:      d.dial,\n\t\tWait:      true,\n\t}\n\tp.Close()\n\t_, err := p.GetContext(context.Background())\n\tif err == nil {\n\t\tt.Fatal(\"expected error\")\n\t}\n}\n\nfunc TestWaitPoolGetCanceledContext(t *testing.T) {\n\tt.Run(\"without vacant connection in the pool\", func(t *testing.T) {\n\t\td := poolDialer{t: t}\n\t\tp := &redis.Pool{\n\t\t\tMaxIdle:   1,\n\t\t\tMaxActive: 1,\n\t\t\tDial:      d.dial,\n\t\t\tWait:      true,\n\t\t}\n\t\tdefer p.Close()\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tcancel()\n\t\tc := p.Get()\n\t\tdefer c.Close()\n\t\t_, err := p.GetContext(ctx)\n\t\tif err != context.Canceled {\n\t\t\tt.Fatalf(\"got error %v, want %v\", err, context.Canceled)\n\t\t}\n\t})\n\tt.Run(\"with vacant connection in the pool\", func(t *testing.T) {\n\t\td := poolDialer{t: t}\n\t\tp := &redis.Pool{\n\t\t\tMaxIdle:   1,\n\t\t\tMaxActive: 1,\n\t\t\tDial:      d.dial,\n\t\t\tWait:      true,\n\t\t}\n\t\tdefer p.Close()\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tcancel()\n\t\t_, err := p.GetContext(ctx)\n\t\tif err != context.Canceled {\n\t\t\tt.Fatalf(\"got error %v, want %v\", err, context.Canceled)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "redis/pubsub.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n)\n\n// Subscription represents a subscribe or unsubscribe notification.\ntype Subscription struct {\n\t// Kind is \"subscribe\", \"unsubscribe\", \"psubscribe\" or \"punsubscribe\"\n\tKind string\n\n\t// The channel that was changed.\n\tChannel string\n\n\t// The current number of subscriptions for connection.\n\tCount int\n}\n\n// Message represents a message notification.\ntype Message struct {\n\t// The originating channel.\n\tChannel string\n\n\t// The matched pattern, if any\n\tPattern string\n\n\t// The message data.\n\tData []byte\n}\n\n// Pong represents a pubsub pong notification.\ntype Pong struct {\n\tData string\n}\n\n// PubSubConn wraps a Conn with convenience methods for subscribers.\ntype PubSubConn struct {\n\tConn Conn\n}\n\n// Close closes the connection.\nfunc (c PubSubConn) Close() error {\n\treturn c.Conn.Close()\n}\n\n// Subscribe subscribes the connection to the specified channels.\nfunc (c PubSubConn) Subscribe(channel ...interface{}) error {\n\tif err := c.Conn.Send(\"SUBSCRIBE\", channel...); err != nil {\n\t\treturn err\n\t}\n\treturn c.Conn.Flush()\n}\n\n// PSubscribe subscribes the connection to the given patterns.\nfunc (c PubSubConn) PSubscribe(channel ...interface{}) error {\n\tif err := c.Conn.Send(\"PSUBSCRIBE\", channel...); err != nil {\n\t\treturn err\n\t}\n\treturn c.Conn.Flush()\n}\n\n// Unsubscribe unsubscribes the connection from the given channels, or from all\n// of them if none is given.\nfunc (c PubSubConn) Unsubscribe(channel ...interface{}) error {\n\tif err := c.Conn.Send(\"UNSUBSCRIBE\", channel...); err != nil {\n\t\treturn err\n\t}\n\treturn c.Conn.Flush()\n}\n\n// PUnsubscribe unsubscribes the connection from the given patterns, or from all\n// of them if none is given.\nfunc (c PubSubConn) PUnsubscribe(channel ...interface{}) error {\n\tif err := c.Conn.Send(\"PUNSUBSCRIBE\", channel...); err != nil {\n\t\treturn err\n\t}\n\treturn c.Conn.Flush()\n}\n\n// Ping sends a PING to the server with the specified data.\n//\n// The connection must be subscribed to at least one channel or pattern when\n// calling this method.\nfunc (c PubSubConn) Ping(data string) error {\n\tif err := c.Conn.Send(\"PING\", data); err != nil {\n\t\treturn err\n\t}\n\treturn c.Conn.Flush()\n}\n\n// Receive returns a pushed message as a Subscription, Message, Pong or error.\n// The return value is intended to be used directly in a type switch as\n// illustrated in the PubSubConn example.\nfunc (c PubSubConn) Receive() interface{} {\n\treturn c.receiveInternal(c.Conn.Receive())\n}\n\n// ReceiveWithTimeout is like Receive, but it allows the application to\n// override the connection's default timeout.\nfunc (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} {\n\treturn c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout))\n}\n\n// ReceiveContext is like Receive, but it allows termination of the receive\n// via a Context. If the call returns due to closure of the context's Done\n// channel the underlying Conn will have been closed.\nfunc (c PubSubConn) ReceiveContext(ctx context.Context) interface{} {\n\treturn c.receiveInternal(ReceiveContext(c.Conn, ctx))\n}\n\nfunc (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} {\n\treply, err := Values(replyArg, errArg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar kind string\n\treply, err = Scan(reply, &kind)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch kind {\n\tcase \"message\":\n\t\tvar m Message\n\t\tif _, err := Scan(reply, &m.Channel, &m.Data); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn m\n\tcase \"pmessage\":\n\t\tvar m Message\n\t\tif _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn m\n\tcase \"subscribe\", \"psubscribe\", \"unsubscribe\", \"punsubscribe\":\n\t\ts := Subscription{Kind: kind}\n\t\tif _, err := Scan(reply, &s.Channel, &s.Count); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn s\n\tcase \"pong\":\n\t\tvar p Pong\n\t\tif _, err := Scan(reply, &p.Data); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn p\n\t}\n\treturn errors.New(\"redigo: unknown pubsub notification\")\n}\n"
  },
  {
    "path": "redis/pubsub_example_test.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\n// +build go1.7\n\npackage redis_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n)\n\n// listenPubSubChannels listens for messages on Redis pubsub channels. The\n// onStart function is called after the channels are subscribed. The onMessage\n// function is called for each message.\nfunc listenPubSubChannels(ctx context.Context, redisServerAddr string,\n\tonStart func() error,\n\tonMessage func(channel string, data []byte) error,\n\tchannels ...string) error {\n\t// A ping is set to the server with this period to test for the health of\n\t// the connection and server.\n\tconst healthCheckPeriod = time.Minute\n\n\tc, err := redis.Dial(\"tcp\", redisServerAddr,\n\t\t// Read timeout on server should be greater than ping period.\n\t\tredis.DialReadTimeout(healthCheckPeriod+10*time.Second),\n\t\tredis.DialWriteTimeout(10*time.Second))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer c.Close()\n\n\tpsc := redis.PubSubConn{Conn: c}\n\n\tif err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil {\n\t\treturn err\n\t}\n\n\tdone := make(chan error, 1)\n\n\t// Start a goroutine to receive notifications from the server.\n\tgo func() {\n\t\tfor {\n\t\t\tswitch n := psc.Receive().(type) {\n\t\t\tcase error:\n\t\t\t\tdone <- n\n\t\t\t\treturn\n\t\t\tcase redis.Message:\n\t\t\t\tif err := onMessage(n.Channel, n.Data); err != nil {\n\t\t\t\t\tdone <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase redis.Subscription:\n\t\t\t\tswitch n.Count {\n\t\t\t\tcase len(channels):\n\t\t\t\t\t// Notify application when all channels are subscribed.\n\t\t\t\t\tif err := onStart(); err != nil {\n\t\t\t\t\t\tdone <- err\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\tcase 0:\n\t\t\t\t\t// Return from the goroutine when all channels are unsubscribed.\n\t\t\t\t\tdone <- nil\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tticker := time.NewTicker(healthCheckPeriod)\n\tdefer ticker.Stop()\nloop:\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\t// Send ping to test health of connection and server. If\n\t\t\t// corresponding pong is not received, then receive on the\n\t\t\t// connection will timeout and the receive goroutine will exit.\n\t\t\tif err = psc.Ping(\"\"); err != nil {\n\t\t\t\tbreak loop\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\tbreak loop\n\t\tcase err := <-done:\n\t\t\t// Return error from the receive goroutine.\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Signal the receiving goroutine to exit by unsubscribing from all channels.\n\tif err := psc.Unsubscribe(); err != nil {\n\t\treturn err\n\t}\n\n\t// Wait for goroutine to complete.\n\treturn <-done\n}\n\nfunc publish() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\tif _, err = c.Do(\"PUBLISH\", \"c1\", \"hello\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif _, err = c.Do(\"PUBLISH\", \"c2\", \"world\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif _, err = c.Do(\"PUBLISH\", \"c1\", \"goodbye\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n}\n\n// This example shows how receive pubsub notifications with cancelation and\n// health checks.\nfunc ExamplePubSubConn() {\n\tredisServerAddr, err := serverAddr()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\n\terr = listenPubSubChannels(ctx,\n\t\tredisServerAddr,\n\t\tfunc() error {\n\t\t\t// The start callback is a good place to backfill missed\n\t\t\t// notifications. For the purpose of this example, a goroutine is\n\t\t\t// started to send notifications.\n\t\t\tgo publish()\n\t\t\treturn nil\n\t\t},\n\t\tfunc(channel string, message []byte) error {\n\t\t\tfmt.Printf(\"channel: %s, message: %s\\n\", channel, message)\n\n\t\t\t// For the purpose of this example, cancel the listener's context\n\t\t\t// after receiving last message sent by publish().\n\t\t\tif string(message) == \"goodbye\" {\n\t\t\t\tcancel()\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\t\"c1\", \"c2\")\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\t// Output:\n\t// channel: c1, message: hello\n\t// channel: c2, message: world\n\t// channel: c1, message: goodbye\n}\n"
  },
  {
    "path": "redis/pubsub_test.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPushed(t *testing.T) {\n\tpc, err := redis.DialDefaultServer()\n\trequire.NoError(t, err)\n\tdefer pc.Close()\n\n\tsc, err := redis.DialDefaultServer()\n\trequire.NoError(t, err)\n\tdefer sc.Close()\n\n\tc := redis.PubSubConn{Conn: sc}\n\n\trequire.NoError(t, c.Subscribe(\"c1\"))\n\trequire.Equal(t, redis.Subscription{Kind: \"subscribe\", Channel: \"c1\", Count: 1}, c.Receive())\n\trequire.NoError(t, c.Subscribe(\"c2\"))\n\trequire.Equal(t, redis.Subscription{Kind: \"subscribe\", Channel: \"c2\", Count: 2}, c.Receive())\n\trequire.NoError(t, c.PSubscribe(\"p1\"))\n\trequire.Equal(t, redis.Subscription{Kind: \"psubscribe\", Channel: \"p1\", Count: 3}, c.Receive())\n\trequire.NoError(t, c.PSubscribe(\"p2\"))\n\trequire.Equal(t, redis.Subscription{Kind: \"psubscribe\", Channel: \"p2\", Count: 4}, c.Receive())\n\trequire.NoError(t, c.PUnsubscribe())\n\n\t// Response can return in any order.\n\tv := c.Receive()\n\trequire.IsType(t, redis.Subscription{}, v)\n\tu := v.(redis.Subscription)\n\texpected1 := redis.Subscription{Kind: \"punsubscribe\", Channel: \"p1\", Count: 3}\n\texpected2 := redis.Subscription{Kind: \"punsubscribe\", Channel: \"p2\", Count: 2}\n\tif u.Channel == \"p2\" {\n\t\t// Order reversed.\n\t\texpected1.Channel = \"p2\"\n\t\texpected2.Channel = \"p1\"\n\t}\n\trequire.Equal(t, expected1, u)\n\trequire.Equal(t, expected2, c.Receive())\n\n\t_, err = pc.Do(\"PUBLISH\", \"c1\", \"hello\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, redis.Message{Channel: \"c1\", Data: []byte(\"hello\")}, c.Receive())\n\n\trequire.NoError(t, c.Ping(\"hello\"))\n\trequire.Equal(t, redis.Pong{Data: \"hello\"}, c.Receive())\n\n\trequire.NoError(t, c.Conn.Send(\"PING\"))\n\tc.Conn.Flush()\n\trequire.Equal(t, redis.Pong{}, c.Receive())\n\n\trequire.NoError(t, c.Ping(\"timeout\"))\n\tgot := c.ReceiveWithTimeout(time.Minute)\n\tif want := (redis.Pong{Data: \"timeout\"}); want != got {\n\t\tt.Errorf(\"recv /w timeout got %v, want %v\", got, want)\n\t}\n}\n\nfunc TestPubSubReceiveContext(t *testing.T) {\n\tsc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer sc.Close()\n\n\tc := redis.PubSubConn{Conn: sc}\n\n\trequire.NoError(t, c.Subscribe(\"c1\"))\n\trequire.Equal(t, redis.Subscription{Kind: \"subscribe\", Channel: \"c1\", Count: 1}, c.Receive())\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\tgot := c.ReceiveContext(ctx)\n\tif err, ok := got.(error); !ok {\n\t\tt.Errorf(\"recv w/canceled expected Canceled got non-error type %T\", got)\n\t} else if !errors.Is(err, context.Canceled) {\n\t\tt.Errorf(\"recv w/canceled expected Canceled got %v\", err)\n\t}\n}\n"
  },
  {
    "path": "redis/redis.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n)\n\n// Error represents an error returned in a command reply.\ntype Error string\n\nfunc (err Error) Error() string { return string(err) }\n\n// Conn represents a connection to a Redis server.\ntype Conn interface {\n\t// Close closes the connection.\n\tClose() error\n\n\t// Err returns a non-nil value when the connection is not usable.\n\tErr() error\n\n\t// Do sends a command to the server and returns the received reply.\n\t// This function will use the timeout which was set when the connection is created\n\tDo(commandName string, args ...interface{}) (reply interface{}, err error)\n\n\t// Send writes the command to the client's output buffer.\n\tSend(commandName string, args ...interface{}) error\n\n\t// Flush flushes the output buffer to the Redis server.\n\tFlush() error\n\n\t// Receive receives a single reply from the Redis server\n\tReceive() (reply interface{}, err error)\n}\n\n// Argument is the interface implemented by an object which wants to control how\n// the object is converted to Redis bulk strings.\ntype Argument interface {\n\t// RedisArg returns a value to be encoded as a bulk string per the\n\t// conversions listed in the section 'Executing Commands'.\n\t// Implementations should typically return a []byte or string.\n\tRedisArg() interface{}\n}\n\n// Scanner is implemented by an object which wants to control its value is\n// interpreted when read from Redis.\ntype Scanner interface {\n\t// RedisScan assigns a value from a Redis value. The argument src is one of\n\t// the reply types listed in the section `Executing Commands`.\n\t//\n\t// An error should be returned if the value cannot be stored without\n\t// loss of information.\n\tRedisScan(src interface{}) error\n}\n\n// ConnWithTimeout is an optional interface that allows the caller to override\n// a connection's default read timeout. This interface is useful for executing\n// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the\n// server.\n//\n// A connection's default read timeout is set with the DialReadTimeout dial\n// option. Applications should rely on the default timeout for commands that do\n// not block at the server.\n//\n// All of the Conn implementations in this package satisfy the ConnWithTimeout\n// interface.\n//\n// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify\n// use of this interface.\ntype ConnWithTimeout interface {\n\tConn\n\n\t// DoWithTimeout sends a command to the server and returns the received reply.\n\t// The timeout overrides the readtimeout set when dialing the connection.\n\tDoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error)\n\n\t// ReceiveWithTimeout receives a single reply from the Redis server.\n\t// The timeout overrides the readtimeout set when dialing the connection.\n\tReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error)\n}\n\n// ConnWithContext is an optional interface that allows the caller to control the command's life with context.\ntype ConnWithContext interface {\n\tConn\n\n\t// DoContext sends a command to server and returns the received reply.\n\t// min(ctx,DialReadTimeout()) will be used as the deadline.\n\t// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.\n\t// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded).\n\t// ctx timeout return err context.DeadlineExceeded.\n\t// ctx canceled return err context.Canceled.\n\tDoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error)\n\n\t// ReceiveContext receives a single reply from the Redis server.\n\t// min(ctx,DialReadTimeout()) will be used as the deadline.\n\t// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.\n\t// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded).\n\t// ctx timeout return err context.DeadlineExceeded.\n\t// ctx canceled return err context.Canceled.\n\tReceiveContext(ctx context.Context) (reply interface{}, err error)\n}\n\nvar errTimeoutNotSupported = errors.New(\"redis: connection does not support ConnWithTimeout\")\nvar errContextNotSupported = errors.New(\"redis: connection does not support ConnWithContext\")\n\n// DoContext sends a command to server and returns the received reply.\n// min(ctx,DialReadTimeout()) will be used as the deadline.\n// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.\n// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded).\n// ctx timeout return err context.DeadlineExceeded.\n// ctx canceled return err context.Canceled.\nfunc DoContext(c Conn, ctx context.Context, cmd string, args ...interface{}) (interface{}, error) {\n\tcwt, ok := c.(ConnWithContext)\n\tif !ok {\n\t\treturn nil, errContextNotSupported\n\t}\n\treturn cwt.DoContext(ctx, cmd, args...)\n}\n\n// DoWithTimeout executes a Redis command with the specified read timeout. If\n// the connection does not satisfy the ConnWithTimeout interface, then an error\n// is returned.\nfunc DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {\n\tcwt, ok := c.(ConnWithTimeout)\n\tif !ok {\n\t\treturn nil, errTimeoutNotSupported\n\t}\n\treturn cwt.DoWithTimeout(timeout, cmd, args...)\n}\n\n// ReceiveContext receives a single reply from the Redis server.\n// min(ctx,DialReadTimeout()) will be used as the deadline.\n// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.\n// DialReadTimeout() timeout return err can be checked by strings.Contains(e.Error(), \"io/timeout\").\n// ctx timeout return err context.DeadlineExceeded.\n// ctx canceled return err context.Canceled.\nfunc ReceiveContext(c Conn, ctx context.Context) (interface{}, error) {\n\tcwt, ok := c.(ConnWithContext)\n\tif !ok {\n\t\treturn nil, errContextNotSupported\n\t}\n\treturn cwt.ReceiveContext(ctx)\n}\n\n// ReceiveWithTimeout receives a reply with the specified read timeout. If the\n// connection does not satisfy the ConnWithTimeout interface, then an error is\n// returned.\nfunc ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) {\n\tcwt, ok := c.(ConnWithTimeout)\n\tif !ok {\n\t\treturn nil, errTimeoutNotSupported\n\t}\n\treturn cwt.ReceiveWithTimeout(timeout)\n}\n\n// SlowLog represents a redis SlowLog\ntype SlowLog struct {\n\t// ID is a unique progressive identifier for every slow log entry.\n\tID int64\n\n\t// Time is the unix timestamp at which the logged command was processed.\n\tTime time.Time\n\n\t// ExecutationTime is the amount of time needed for the command execution.\n\tExecutionTime time.Duration\n\n\t// Args is the command name and arguments\n\tArgs []string\n\n\t// ClientAddr is the client IP address (4.0 only).\n\tClientAddr string\n\n\t// ClientName is the name set via the CLIENT SETNAME command (4.0 only).\n\tClientName string\n}\n\n// Latency represents a redis LATENCY LATEST.\ntype Latency struct {\n\t// Name of the latest latency spike event.\n\tName string\n\n\t// Time of the latest latency spike for the event.\n\tTime time.Time\n\n\t// Latest is the latest recorded latency for the named event.\n\tLatest time.Duration\n\n\t// Max is the maximum latency for the named event.\n\tMax time.Duration\n}\n\n// LatencyHistory represents a redis LATENCY HISTORY.\ntype LatencyHistory struct {\n\t// Time is the unix timestamp at which the event was processed.\n\tTime time.Time\n\n\t// ExecutationTime is the amount of time needed for the command execution.\n\tExecutionTime time.Duration\n}\n"
  },
  {
    "path": "redis/redis_test.go",
    "content": "// Copyright 2017 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n)\n\ntype timeoutTestConn int\n\nfunc (tc timeoutTestConn) Do(string, ...interface{}) (interface{}, error) {\n\treturn time.Duration(-1), nil\n}\n\nfunc (tc timeoutTestConn) DoWithTimeout(timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {\n\treturn timeout, nil\n}\n\nfunc (tc timeoutTestConn) Receive() (interface{}, error) {\n\treturn time.Duration(-1), nil\n}\n\nfunc (tc timeoutTestConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) {\n\treturn timeout, nil\n}\n\nfunc (tc timeoutTestConn) Send(string, ...interface{}) error { return nil }\nfunc (tc timeoutTestConn) Err() error                        { return nil }\nfunc (tc timeoutTestConn) Close() error                      { return nil }\nfunc (tc timeoutTestConn) Flush() error                      { return nil }\n\nfunc testTimeout(t *testing.T, c redis.Conn) {\n\tr, err := c.Do(\"PING\")\n\tif r != time.Duration(-1) || err != nil {\n\t\tt.Errorf(\"Do() = %v, %v, want %v, %v\", r, err, time.Duration(-1), nil)\n\t}\n\tr, err = redis.DoWithTimeout(c, time.Minute, \"PING\")\n\tif r != time.Minute || err != nil {\n\t\tt.Errorf(\"DoWithTimeout() = %v, %v, want %v, %v\", r, err, time.Minute, nil)\n\t}\n\tr, err = c.Receive()\n\tif r != time.Duration(-1) || err != nil {\n\t\tt.Errorf(\"Receive() = %v, %v, want %v, %v\", r, err, time.Duration(-1), nil)\n\t}\n\tr, err = redis.ReceiveWithTimeout(c, time.Minute)\n\tif r != time.Minute || err != nil {\n\t\tt.Errorf(\"ReceiveWithTimeout() = %v, %v, want %v, %v\", r, err, time.Minute, nil)\n\t}\n}\n\nfunc TestConnTimeout(t *testing.T) {\n\ttestTimeout(t, timeoutTestConn(0))\n}\n\nfunc TestPoolConnTimeout(t *testing.T) {\n\tp := &redis.Pool{Dial: func() (redis.Conn, error) { return timeoutTestConn(0), nil }}\n\ttestTimeout(t, p.Get())\n}\n\ntype contextDeadTestConn int\n\nfunc (cc contextDeadTestConn) Do(string, ...interface{}) (interface{}, error) {\n\treturn -1, nil\n}\nfunc (cc contextDeadTestConn) DoContext(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) {\n\treturn 1, nil\n}\nfunc (cc contextDeadTestConn) Receive() (interface{}, error) {\n\treturn -1, nil\n}\nfunc (cc contextDeadTestConn) ReceiveContext(ctx context.Context) (interface{}, error) {\n\treturn 1, nil\n}\nfunc (cc contextDeadTestConn) Send(string, ...interface{}) error { return nil }\nfunc (cc contextDeadTestConn) Err() error                        { return nil }\nfunc (cc contextDeadTestConn) Close() error                      { return nil }\nfunc (cc contextDeadTestConn) Flush() error                      { return nil }\n\nfunc testcontext(t *testing.T, c redis.Conn) {\n\tr, e := c.Do(\"PING\")\n\tif r != -1 || e != nil {\n\t\tt.Errorf(\"Do() = %v, %v, want %v, %v\", r, e, -1, nil)\n\t}\n\tctx, f := context.WithTimeout(context.Background(), time.Minute)\n\tdefer f()\n\tr, e = redis.DoContext(c, ctx, \"PING\")\n\tif r != 1 || e != nil {\n\t\tt.Errorf(\"DoContext() = %v, %v, want %v, %v\", r, e, 1, nil)\n\t}\n\tr, e = c.Receive()\n\tif r != -1 || e != nil {\n\t\tt.Errorf(\"Receive() = %v, %v, want %v, %v\", r, e, -1, nil)\n\t}\n\tr, e = redis.ReceiveContext(c, ctx)\n\tif r != 1 || e != nil {\n\t\tt.Errorf(\"ReceiveContext() = %v, %v, want %v, %v\", r, e, 1, nil)\n\t}\n}\n\nfunc TestConnContext(t *testing.T) {\n\ttestcontext(t, contextDeadTestConn(0))\n}\n\nfunc TestPoolConnContext(t *testing.T) {\n\tp := redis.Pool{Dial: func() (redis.Conn, error) { return contextDeadTestConn(0), nil }}\n\ttestcontext(t, p.Get())\n}\n"
  },
  {
    "path": "redis/reflect.go",
    "content": "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 two stack frames above.\nfunc methodName() string {\n\tpc, _, _, _ := runtime.Caller(2)\n\tf := runtime.FuncForPC(pc)\n\tif f == nil {\n\t\treturn \"unknown method\"\n\t}\n\treturn f.Name()\n}\n\n// mustBe panics if f's kind is not expected.\nfunc mustBe(v reflect.Value, expected reflect.Kind) {\n\tif v.Kind() != expected {\n\t\tpanic(&reflect.ValueError{Method: methodName(), Kind: v.Kind()})\n\t}\n}\n\n// fieldByIndexCreate returns the nested field corresponding\n// to index creating elements that are nil when stepping through.\n// It panics if v is not a struct.\nfunc fieldByIndexCreate(v reflect.Value, index []int) reflect.Value {\n\tif len(index) == 1 {\n\t\treturn v.Field(index[0])\n\t}\n\n\tmustBe(v, reflect.Struct)\n\tfor i, x := range index {\n\t\tif i > 0 {\n\t\t\tif v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {\n\t\t\t\tif v.IsNil() {\n\t\t\t\t\tv.Set(reflect.New(v.Type().Elem()))\n\t\t\t\t}\n\t\t\t\tv = v.Elem()\n\t\t\t}\n\t\t}\n\t\tv = v.Field(x)\n\t}\n\n\treturn v\n}\n"
  },
  {
    "path": "redis/reflect_go117.go",
    "content": "//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 nested field corresponding to index.\n// It returns an error if evaluation requires stepping through a nil\n// pointer, but panics if it must step through a field that\n// is not a struct.\nfunc fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) {\n\tif len(index) == 1 {\n\t\treturn v.Field(index[0]), nil\n\t}\n\n\tmustBe(v, reflect.Struct)\n\tfor i, x := range index {\n\t\tif i > 0 {\n\t\t\tif v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {\n\t\t\t\tif v.IsNil() {\n\t\t\t\t\treturn reflect.Value{}, errors.New(\"reflect: indirection through nil pointer to embedded struct field \" + v.Type().Elem().Name())\n\t\t\t\t}\n\t\t\t\tv = v.Elem()\n\t\t\t}\n\t\t}\n\t\tv = v.Field(x)\n\t}\n\n\treturn v, nil\n}\n"
  },
  {
    "path": "redis/reflect_go118.go",
    "content": "//go:build go1.18\n// +build go1.18\n\npackage redis\n\nimport (\n\t\"reflect\"\n)\n\n// fieldByIndexErr returns the nested field corresponding to index.\n// It returns an error if evaluation requires stepping through a nil\n// pointer, but panics if it must step through a field that\n// is not a struct.\nfunc fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) {\n\treturn v.FieldByIndexErr(index)\n}\n"
  },
  {
    "path": "redis/reply.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// ErrNil indicates that a reply value is nil.\nvar ErrNil = errors.New(\"redigo: nil returned\")\n\n// Int is a helper that converts a command reply to an integer. If err is not\n// equal to nil, then Int returns 0, err. Otherwise, Int converts the\n// reply to an int as follows:\n//\n//  Reply type    Result\n//  integer       int(reply), nil\n//  bulk string   parsed reply, nil\n//  nil           0, ErrNil\n//  other         0, error\nfunc Int(reply interface{}, err error) (int, error) {\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tswitch reply := reply.(type) {\n\tcase int64:\n\t\tx := int(reply)\n\t\tif int64(x) != reply {\n\t\t\treturn 0, strconv.ErrRange\n\t\t}\n\t\treturn x, nil\n\tcase []byte:\n\t\tn, err := strconv.ParseInt(string(reply), 10, 0)\n\t\treturn int(n), err\n\tcase nil:\n\t\treturn 0, ErrNil\n\tcase Error:\n\t\treturn 0, reply\n\t}\n\treturn 0, fmt.Errorf(\"redigo: unexpected type for Int, got type %T\", reply)\n}\n\n// Int64 is a helper that converts a command reply to 64 bit integer. If err is\n// not equal to nil, then Int64 returns 0, err. Otherwise, Int64 converts the\n// reply to an int64 as follows:\n//\n//  Reply type    Result\n//  integer       reply, nil\n//  bulk string   parsed reply, nil\n//  nil           0, ErrNil\n//  other         0, error\nfunc Int64(reply interface{}, err error) (int64, error) {\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tswitch reply := reply.(type) {\n\tcase int64:\n\t\treturn reply, nil\n\tcase []byte:\n\t\tn, err := strconv.ParseInt(string(reply), 10, 64)\n\t\treturn n, err\n\tcase nil:\n\t\treturn 0, ErrNil\n\tcase Error:\n\t\treturn 0, reply\n\t}\n\treturn 0, fmt.Errorf(\"redigo: unexpected type for Int64, got type %T\", reply)\n}\n\nfunc errNegativeInt(v int64) error {\n\treturn fmt.Errorf(\"redigo: unexpected negative value %v for Uint64\", v)\n}\n\n// Uint64 is a helper that converts a command reply to 64 bit unsigned integer.\n// If err is not equal to nil, then Uint64 returns 0, err. Otherwise, Uint64 converts the\n// reply to an uint64 as follows:\n//\n//  Reply type    Result\n//  +integer      reply, nil\n//  bulk string   parsed reply, nil\n//  nil           0, ErrNil\n//  other         0, error\nfunc Uint64(reply interface{}, err error) (uint64, error) {\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tswitch reply := reply.(type) {\n\tcase int64:\n\t\tif reply < 0 {\n\t\t\treturn 0, errNegativeInt(reply)\n\t\t}\n\t\treturn uint64(reply), nil\n\tcase []byte:\n\t\tn, err := strconv.ParseUint(string(reply), 10, 64)\n\t\treturn n, err\n\tcase nil:\n\t\treturn 0, ErrNil\n\tcase Error:\n\t\treturn 0, reply\n\t}\n\treturn 0, fmt.Errorf(\"redigo: unexpected type for Uint64, got type %T\", reply)\n}\n\n// Float64 is a helper that converts a command reply to 64 bit float. If err is\n// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts\n// the reply to a float64 as follows:\n//\n//  Reply type    Result\n//  bulk string   parsed reply, nil\n//  nil           0, ErrNil\n//  other         0, error\nfunc Float64(reply interface{}, err error) (float64, error) {\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tswitch reply := reply.(type) {\n\tcase []byte:\n\t\tn, err := strconv.ParseFloat(string(reply), 64)\n\t\treturn n, err\n\tcase nil:\n\t\treturn 0, ErrNil\n\tcase Error:\n\t\treturn 0, reply\n\t}\n\treturn 0, fmt.Errorf(\"redigo: unexpected type for Float64, got type %T\", reply)\n}\n\n// String is a helper that converts a command reply to a string. If err is not\n// equal to nil, then String returns \"\", err. Otherwise String converts the\n// reply to a string as follows:\n//\n//  Reply type      Result\n//  bulk string     string(reply), nil\n//  simple string   reply, nil\n//  nil             \"\",  ErrNil\n//  other           \"\",  error\nfunc String(reply interface{}, err error) (string, error) {\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tswitch reply := reply.(type) {\n\tcase []byte:\n\t\treturn string(reply), nil\n\tcase string:\n\t\treturn reply, nil\n\tcase nil:\n\t\treturn \"\", ErrNil\n\tcase Error:\n\t\treturn \"\", reply\n\t}\n\treturn \"\", fmt.Errorf(\"redigo: unexpected type for String, got type %T\", reply)\n}\n\n// Bytes is a helper that converts a command reply to a slice of bytes. If err\n// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts\n// the reply to a slice of bytes as follows:\n//\n//  Reply type      Result\n//  bulk string     reply, nil\n//  simple string   []byte(reply), nil\n//  nil             nil, ErrNil\n//  other           nil, error\nfunc Bytes(reply interface{}, err error) ([]byte, error) {\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch reply := reply.(type) {\n\tcase []byte:\n\t\treturn reply, nil\n\tcase string:\n\t\treturn []byte(reply), nil\n\tcase nil:\n\t\treturn nil, ErrNil\n\tcase Error:\n\t\treturn nil, reply\n\t}\n\treturn nil, fmt.Errorf(\"redigo: unexpected type for Bytes, got type %T\", reply)\n}\n\n// Bool is a helper that converts a command reply to a boolean. If err is not\n// equal to nil, then Bool returns false, err. Otherwise Bool converts the\n// reply to boolean as follows:\n//\n//  Reply type      Result\n//  integer         value != 0, nil\n//  bulk string     strconv.ParseBool(reply)\n//  nil             false, ErrNil\n//  other           false, error\nfunc Bool(reply interface{}, err error) (bool, error) {\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tswitch reply := reply.(type) {\n\tcase int64:\n\t\treturn reply != 0, nil\n\tcase []byte:\n\t\treturn strconv.ParseBool(string(reply))\n\tcase nil:\n\t\treturn false, ErrNil\n\tcase Error:\n\t\treturn false, reply\n\t}\n\treturn false, fmt.Errorf(\"redigo: unexpected type for Bool, got type %T\", reply)\n}\n\n// MultiBulk is a helper that converts an array command reply to a []interface{}.\n//\n// Deprecated: Use Values instead.\nfunc MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }\n\n// Values is a helper that converts an array command reply to a []interface{}.\n// If err is not equal to nil, then Values returns nil, err. Otherwise, Values\n// converts the reply as follows:\n//\n//  Reply type      Result\n//  array           reply, nil\n//  nil             nil, ErrNil\n//  other           nil, error\nfunc Values(reply interface{}, err error) ([]interface{}, error) {\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch reply := reply.(type) {\n\tcase []interface{}:\n\t\treturn reply, nil\n\tcase nil:\n\t\treturn nil, ErrNil\n\tcase Error:\n\t\treturn nil, reply\n\t}\n\treturn nil, fmt.Errorf(\"redigo: unexpected type for Values, got type %T\", reply)\n}\n\nfunc sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch reply := reply.(type) {\n\tcase []interface{}:\n\t\tmakeSlice(len(reply))\n\t\tfor i := range reply {\n\t\t\tif reply[i] == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := assign(i, reply[i]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\tcase nil:\n\t\treturn ErrNil\n\tcase Error:\n\t\treturn reply\n\t}\n\treturn fmt.Errorf(\"redigo: unexpected type for %s, got type %T\", name, reply)\n}\n\n// Float64s is a helper that converts an array command reply to a []float64. If\n// err is not equal to nil, then Float64s returns nil, err. Nil array items are\n// converted to 0 in the output slice. Floats64 returns an error if an array\n// item is not a bulk string or nil.\nfunc Float64s(reply interface{}, err error) ([]float64, error) {\n\tvar result []float64\n\terr = sliceHelper(reply, err, \"Float64s\", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {\n\t\tswitch v := v.(type) {\n\t\tcase []byte:\n\t\t\tf, err := strconv.ParseFloat(string(v), 64)\n\t\t\tresult[i] = f\n\t\t\treturn err\n\t\tcase Error:\n\t\t\treturn v\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"redigo: unexpected element type for Float64s, got type %T\", v)\n\t\t}\n\t})\n\treturn result, err\n}\n\n// Strings is a helper that converts an array command reply to a []string. If\n// err is not equal to nil, then Strings returns nil, err. Nil array items are\n// converted to \"\" in the output slice. Strings returns an error if an array\n// item is not a bulk string or nil.\nfunc Strings(reply interface{}, err error) ([]string, error) {\n\tvar result []string\n\terr = sliceHelper(reply, err, \"Strings\", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {\n\t\tswitch v := v.(type) {\n\t\tcase string:\n\t\t\tresult[i] = v\n\t\t\treturn nil\n\t\tcase []byte:\n\t\t\tresult[i] = string(v)\n\t\t\treturn nil\n\t\tcase Error:\n\t\t\treturn v\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"redigo: unexpected element type for Strings, got type %T\", v)\n\t\t}\n\t})\n\treturn result, err\n}\n\n// ByteSlices is a helper that converts an array command reply to a [][]byte.\n// If err is not equal to nil, then ByteSlices returns nil, err. Nil array\n// items are stay nil. ByteSlices returns an error if an array item is not a\n// bulk string or nil.\nfunc ByteSlices(reply interface{}, err error) ([][]byte, error) {\n\tvar result [][]byte\n\terr = sliceHelper(reply, err, \"ByteSlices\", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {\n\t\tswitch v := v.(type) {\n\t\tcase []byte:\n\t\t\tresult[i] = v\n\t\t\treturn nil\n\t\tcase Error:\n\t\t\treturn v\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"redigo: unexpected element type for ByteSlices, got type %T\", v)\n\t\t}\n\t})\n\treturn result, err\n}\n\n// Int64s is a helper that converts an array command reply to a []int64.\n// If err is not equal to nil, then Int64s returns nil, err. Nil array\n// items are stay nil. Int64s returns an error if an array item is not a\n// bulk string or nil.\nfunc Int64s(reply interface{}, err error) ([]int64, error) {\n\tvar result []int64\n\terr = sliceHelper(reply, err, \"Int64s\", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {\n\t\tswitch v := v.(type) {\n\t\tcase int64:\n\t\t\tresult[i] = v\n\t\t\treturn nil\n\t\tcase []byte:\n\t\t\tn, err := strconv.ParseInt(string(v), 10, 64)\n\t\t\tresult[i] = n\n\t\t\treturn err\n\t\tcase Error:\n\t\t\treturn v\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"redigo: unexpected element type for Int64s, got type %T\", v)\n\t\t}\n\t})\n\treturn result, err\n}\n\n// Ints is a helper that converts an array command reply to a []int.\n// If err is not equal to nil, then Ints returns nil, err. Nil array\n// items are stay nil. Ints returns an error if an array item is not a\n// bulk string or nil.\nfunc Ints(reply interface{}, err error) ([]int, error) {\n\tvar result []int\n\terr = sliceHelper(reply, err, \"Ints\", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {\n\t\tswitch v := v.(type) {\n\t\tcase int64:\n\t\t\tn := int(v)\n\t\t\tif int64(n) != v {\n\t\t\t\treturn strconv.ErrRange\n\t\t\t}\n\t\t\tresult[i] = n\n\t\t\treturn nil\n\t\tcase []byte:\n\t\t\tn, err := strconv.Atoi(string(v))\n\t\t\tresult[i] = n\n\t\t\treturn err\n\t\tcase Error:\n\t\t\treturn v\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"redigo: unexpected element type for Ints, got type %T\", v)\n\t\t}\n\t})\n\treturn result, err\n}\n\n// mapHelper builds a map from the data in reply.\nfunc mapHelper(reply interface{}, err error, name string, makeMap func(int), assign func(key string, value interface{}) error) error {\n\tvalues, err := Values(reply, err)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(values)%2 != 0 {\n\t\treturn fmt.Errorf(\"redigo: %s expects even number of values result, got %d\", name, len(values))\n\t}\n\n\tmakeMap(len(values) / 2)\n\tfor i := 0; i < len(values); i += 2 {\n\t\tkey, ok := values[i].([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"redigo: %s key[%d] not a bulk string value, got %T\", name, i, values[i])\n\t\t}\n\n\t\tif err := assign(string(key), values[i+1]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// StringMap is a helper that converts an array of strings (alternating key, value)\n// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.\n// Requires an even number of values in result.\nfunc StringMap(reply interface{}, err error) (map[string]string, error) {\n\tvar result map[string]string\n\terr = mapHelper(reply, err, \"StringMap\",\n\t\tfunc(n int) {\n\t\t\tresult = make(map[string]string, n)\n\t\t}, func(key string, v interface{}) error {\n\t\t\tvalue, ok := v.([]byte)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"redigo: StringMap for %q not a bulk string value, got %T\", key, v)\n\t\t\t}\n\n\t\t\tresult[key] = string(value)\n\n\t\t\treturn nil\n\t\t},\n\t)\n\n\treturn result, err\n}\n\n// IntMap is a helper that converts an array of strings (alternating key, value)\n// into a map[string]int. The HGETALL commands return replies in this format.\n// Requires an even number of values in result.\nfunc IntMap(result interface{}, err error) (map[string]int, error) {\n\tvar m map[string]int\n\terr = mapHelper(result, err, \"IntMap\",\n\t\tfunc(n int) {\n\t\t\tm = make(map[string]int, n)\n\t\t}, func(key string, v interface{}) error {\n\t\t\tvalue, err := Int(v, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tm[key] = value\n\n\t\t\treturn nil\n\t\t},\n\t)\n\n\treturn m, err\n}\n\n// Int64Map is a helper that converts an array of strings (alternating key, value)\n// into a map[string]int64. The HGETALL commands return replies in this format.\n// Requires an even number of values in result.\nfunc Int64Map(result interface{}, err error) (map[string]int64, error) {\n\tvar m map[string]int64\n\terr = mapHelper(result, err, \"Int64Map\",\n\t\tfunc(n int) {\n\t\t\tm = make(map[string]int64, n)\n\t\t}, func(key string, v interface{}) error {\n\t\t\tvalue, err := Int64(v, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tm[key] = value\n\n\t\t\treturn nil\n\t\t},\n\t)\n\n\treturn m, err\n}\n\n// Float64Map is a helper that converts an array of strings (alternating key, value)\n// into a map[string]float64. The HGETALL commands return replies in this format.\n// Requires an even number of values in result.\nfunc Float64Map(result interface{}, err error) (map[string]float64, error) {\n\tvar m map[string]float64\n\terr = mapHelper(result, err, \"Float64Map\",\n\t\tfunc(n int) {\n\t\t\tm = make(map[string]float64, n)\n\t\t}, func(key string, v interface{}) error {\n\t\t\tvalue, err := Float64(v, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tm[key] = value\n\n\t\t\treturn nil\n\t\t},\n\t)\n\n\treturn m, err\n}\n\n// Positions is a helper that converts an array of positions (lat, long)\n// into a [][2]float64. The GEOPOS command returns replies in this format.\nfunc Positions(result interface{}, err error) ([]*[2]float64, error) {\n\tvalues, err := Values(result, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpositions := make([]*[2]float64, len(values))\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tp, ok := values[i].([]interface{})\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: unexpected element type for interface slice, got type %T\", values[i])\n\t\t}\n\n\t\tif len(p) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"redigo: unexpected number of values for a member position, got %d\", len(p))\n\t\t}\n\n\t\tlat, err := Float64(p[0], nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlong, err := Float64(p[1], nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpositions[i] = &[2]float64{lat, long}\n\t}\n\treturn positions, nil\n}\n\n// Uint64s is a helper that converts an array command reply to a []uint64.\n// If err is not equal to nil, then Uint64s returns nil, err. Nil array\n// items are stay nil. Uint64s returns an error if an array item is not a\n// bulk string or nil.\nfunc Uint64s(reply interface{}, err error) ([]uint64, error) {\n\tvar result []uint64\n\terr = sliceHelper(reply, err, \"Uint64s\", func(n int) { result = make([]uint64, n) }, func(i int, v interface{}) error {\n\t\tswitch v := v.(type) {\n\t\tcase uint64:\n\t\t\tresult[i] = v\n\t\t\treturn nil\n\t\tcase []byte:\n\t\t\tn, err := strconv.ParseUint(string(v), 10, 64)\n\t\t\tresult[i] = n\n\t\t\treturn err\n\t\tcase Error:\n\t\t\treturn v\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"redigo: unexpected element type for Uint64s, got type %T\", v)\n\t\t}\n\t})\n\treturn result, err\n}\n\n// Uint64Map is a helper that converts an array of strings (alternating key, value)\n// into a map[string]uint64. The HGETALL commands return replies in this format.\n// Requires an even number of values in result.\nfunc Uint64Map(result interface{}, err error) (map[string]uint64, error) {\n\tvar m map[string]uint64\n\terr = mapHelper(result, err, \"Uint64Map\",\n\t\tfunc(n int) {\n\t\t\tm = make(map[string]uint64, n)\n\t\t}, func(key string, v interface{}) error {\n\t\t\tvalue, err := Uint64(v, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tm[key] = value\n\n\t\t\treturn nil\n\t\t},\n\t)\n\n\treturn m, err\n}\n\n// SlowLogs is a helper that parse the SLOWLOG GET command output and\n// return the array of SlowLog\nfunc SlowLogs(result interface{}, err error) ([]SlowLog, error) {\n\trawLogs, err := Values(result, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogs := make([]SlowLog, len(rawLogs))\n\tfor i, e := range rawLogs {\n\t\trawLog, ok := e.([]interface{})\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element is not an array, got %T\", e)\n\t\t}\n\n\t\tvar log SlowLog\n\t\tif len(rawLog) < 4 {\n\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element has %d elements, expected at least 4\", len(rawLog))\n\t\t}\n\n\t\tlog.ID, ok = rawLog[0].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element[0] not an int64, got %T\", rawLog[0])\n\t\t}\n\n\t\ttimestamp, ok := rawLog[1].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element[1] not an int64, got %T\", rawLog[1])\n\t\t}\n\n\t\tlog.Time = time.Unix(timestamp, 0)\n\t\tduration, ok := rawLog[2].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element[2] not an int64, got %T\", rawLog[2])\n\t\t}\n\n\t\tlog.ExecutionTime = time.Duration(duration) * time.Microsecond\n\n\t\tlog.Args, err = Strings(rawLog[3], nil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element[3] is not array of strings: %w\", err)\n\t\t}\n\n\t\tif len(rawLog) >= 6 {\n\t\t\tlog.ClientAddr, err = String(rawLog[4], nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element[4] is not a string: %w\", err)\n\t\t\t}\n\n\t\t\tlog.ClientName, err = String(rawLog[5], nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"redigo: slowlog element[5] is not a string: %w\", err)\n\t\t\t}\n\t\t}\n\t\tlogs[i] = log\n\t}\n\treturn logs, nil\n}\n\n// Latencies is a helper that parses the LATENCY LATEST command output and\n// return the slice of Latency values.\nfunc Latencies(result interface{}, err error) ([]Latency, error) {\n\trawLatencies, err := Values(result, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlatencies := make([]Latency, len(rawLatencies))\n\tfor i, e := range rawLatencies {\n\t\trawLatency, ok := e.([]interface{})\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latencies element is not slice, got %T\", e)\n\t\t}\n\n\t\tvar event Latency\n\t\tif len(rawLatency) != 4 {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latencies element has %d elements, expected 4\", len(rawLatency))\n\t\t}\n\n\t\tevent.Name, err = String(rawLatency[0], nil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latencies element[0] is not a string: %w\", err)\n\t\t}\n\n\t\ttimestamp, ok := rawLatency[1].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latencies element[1] not an int64, got %T\", rawLatency[1])\n\t\t}\n\n\t\tevent.Time = time.Unix(timestamp, 0)\n\n\t\tlatestDuration, ok := rawLatency[2].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latencies element[2] not an int64, got %T\", rawLatency[2])\n\t\t}\n\n\t\tevent.Latest = time.Duration(latestDuration) * time.Millisecond\n\n\t\tmaxDuration, ok := rawLatency[3].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latencies element[3] not an int64, got %T\", rawLatency[3])\n\t\t}\n\n\t\tevent.Max = time.Duration(maxDuration) * time.Millisecond\n\n\t\tlatencies[i] = event\n\t}\n\n\treturn latencies, nil\n}\n\n// LatencyHistories is a helper that parse the LATENCY HISTORY command output and\n// returns a LatencyHistory slice.\nfunc LatencyHistories(result interface{}, err error) ([]LatencyHistory, error) {\n\trawLogs, err := Values(result, err)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlatencyHistories := make([]LatencyHistory, len(rawLogs))\n\tfor i, e := range rawLogs {\n\t\trawLog, ok := e.([]interface{})\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latency history element is not an slice, got %T\", e)\n\t\t}\n\n\t\tvar event LatencyHistory\n\t\ttimestamp, ok := rawLog[0].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latency history element[0] not an int64, got %T\", rawLog[0])\n\t\t}\n\n\t\tevent.Time = time.Unix(timestamp, 0)\n\n\t\tduration, ok := rawLog[1].(int64)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"redigo: latency history element[1] not an int64, got %T\", rawLog[1])\n\t\t}\n\n\t\tevent.ExecutionTime = time.Duration(duration) * time.Millisecond\n\n\t\tlatencyHistories[i] = event\n\t}\n\n\treturn latencyHistories, nil\n}\n"
  },
  {
    "path": "redis/reply_test.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\tmaxUint64Str = strconv.FormatUint(math.MaxUint64, 10)\n)\n\ntype valueError struct {\n\tv   interface{}\n\terr error\n}\n\nfunc ve(v interface{}, err error) valueError {\n\treturn valueError{v, err}\n}\n\nvar replyTests = []struct {\n\tname     interface{}\n\tactual   valueError\n\texpected valueError\n}{\n\t{\n\t\t\"ints([[]byte, []byte])\",\n\t\tve(redis.Ints([]interface{}{[]byte(\"4\"), []byte(\"5\")}, nil)),\n\t\tve([]int{4, 5}, nil),\n\t},\n\t{\n\t\t\"ints([nt64, int64])\",\n\t\tve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)),\n\t\tve([]int{4, 5}, nil),\n\t},\n\t{\n\t\t\"ints([[]byte, nil, []byte])\",\n\t\tve(redis.Ints([]interface{}{[]byte(\"4\"), nil, []byte(\"5\")}, nil)),\n\t\tve([]int{4, 0, 5}, nil),\n\t},\n\t{\n\t\t\"ints(nil)\",\n\t\tve(redis.Ints(nil, nil)),\n\t\tve([]int(nil), redis.ErrNil),\n\t},\n\t{\n\t\t\"int64s([[]byte, []byte])\",\n\t\tve(redis.Int64s([]interface{}{[]byte(\"4\"), []byte(\"5\")}, nil)),\n\t\tve([]int64{4, 5}, nil),\n\t},\n\t{\n\t\t\"int64s([int64, int64])\",\n\t\tve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)),\n\t\tve([]int64{4, 5}, nil),\n\t},\n\t{\n\t\t\"uint64s([[]byte, []byte])\",\n\t\tve(redis.Uint64s([]interface{}{[]byte(maxUint64Str), []byte(\"5\")}, nil)),\n\t\tve([]uint64{math.MaxUint64, 5}, nil),\n\t},\n\t{\n\t\t\"Uint64Map([[]byte, []byte])\",\n\t\tve(redis.Uint64Map([]interface{}{[]byte(\"key1\"), []byte(maxUint64Str), []byte(\"key2\"), []byte(\"5\")}, nil)),\n\t\tve(map[string]uint64{\"key1\": math.MaxUint64, \"key2\": 5}, nil),\n\t},\n\t{\n\t\t\"strings([[]byte, []byte])\",\n\t\tve(redis.Strings([]interface{}{[]byte(\"v1\"), []byte(\"v2\")}, nil)),\n\t\tve([]string{\"v1\", \"v2\"}, nil),\n\t},\n\t{\n\t\t\"strings([string, string])\",\n\t\tve(redis.Strings([]interface{}{\"v1\", \"v2\"}, nil)),\n\t\tve([]string{\"v1\", \"v2\"}, nil),\n\t},\n\t{\n\t\t\"byteslices([v1, v2])\",\n\t\tve(redis.ByteSlices([]interface{}{[]byte(\"v1\"), []byte(\"v2\")}, nil)),\n\t\tve([][]byte{[]byte(\"v1\"), []byte(\"v2\")}, nil),\n\t},\n\t{\n\t\t\"float64s([v1, v2])\",\n\t\tve(redis.Float64s([]interface{}{[]byte(\"1.234\"), []byte(\"5.678\")}, nil)),\n\t\tve([]float64{1.234, 5.678}, nil),\n\t},\n\t{\n\t\t\"values([v1, v2])\",\n\t\tve(redis.Values([]interface{}{[]byte(\"v1\"), []byte(\"v2\")}, nil)),\n\t\tve([]interface{}{[]byte(\"v1\"), []byte(\"v2\")}, nil),\n\t},\n\t{\n\t\t\"values(nil)\",\n\t\tve(redis.Values(nil, nil)),\n\t\tve([]interface{}(nil), redis.ErrNil),\n\t},\n\t{\n\t\t\"float64(1.0)\",\n\t\tve(redis.Float64([]byte(\"1.0\"), nil)),\n\t\tve(float64(1.0), nil),\n\t},\n\t{\n\t\t\"float64(nil)\",\n\t\tve(redis.Float64(nil, nil)),\n\t\tve(float64(0.0), redis.ErrNil),\n\t},\n\t{\n\t\t\"float64Map([[]byte, []byte])\",\n\t\tve(redis.Float64Map([]interface{}{[]byte(\"key1\"), []byte(\"1.234\"), []byte(\"key2\"), []byte(\"5.678\")}, nil)),\n\t\tve(map[string]float64{\"key1\": 1.234, \"key2\": 5.678}, nil),\n\t},\n\t{\n\t\t\"uint64(1)\",\n\t\tve(redis.Uint64(int64(1), nil)),\n\t\tve(uint64(1), nil),\n\t},\n\t{\n\t\t\"uint64(-1)\",\n\t\tve(redis.Uint64(int64(-1), nil)),\n\t\tve(uint64(0), redis.ErrNegativeInt(-1)),\n\t},\n\t{\n\t\t\"positions([[1, 2], nil, [3, 4]])\",\n\t\tve(redis.Positions([]interface{}{[]interface{}{[]byte(\"1\"), []byte(\"2\")}, nil, []interface{}{[]byte(\"3\"), []byte(\"4\")}}, nil)),\n\t\tve([]*[2]float64{{1.0, 2.0}, nil, {3.0, 4.0}}, nil),\n\t},\n\t{\n\t\t\"SlowLogs(1, 1579625870, 3, {set, x, y}, localhost:1234, testClient\",\n\t\tve(getSlowLog()),\n\t\tve(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),\n\t},\n}\n\nfunc getSlowLog() (redis.SlowLog, error) {\n\tslowLogs, _ := redis.SlowLogs([]interface{}{[]interface{}{int64(1), int64(1579625870), int64(3), []interface{}{\"set\", \"x\", \"y\"}, \"localhost:1234\", \"testClient\"}}, nil)\n\tif err != nil {\n\t\treturn redis.SlowLog{}, err\n\t}\n\treturn slowLogs[0], nil\n}\n\nfunc TestReply(t *testing.T) {\n\tfor _, rt := range replyTests {\n\t\tif rt.actual.err != rt.expected.err && rt.actual.err.Error() != rt.expected.err.Error() {\n\t\t\tt.Errorf(\"%s returned err %v, want %v\", rt.name, rt.actual.err, rt.expected.err)\n\t\t\tcontinue\n\t\t}\n\t\tif !reflect.DeepEqual(rt.actual.v, rt.expected.v) {\n\t\t\tt.Errorf(\"%s=%+v, want %+v\", rt.name, rt.actual.v, rt.expected.v)\n\t\t}\n\t}\n}\n\nfunc TestSlowLog(t *testing.T) {\n\tc, err := dial()\n\tif err != nil {\n\t\tt.Errorf(\"TestSlowLog failed during dial with error \" + err.Error())\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\tresultStr, err := redis.Strings(c.Do(\"CONFIG\", \"GET\", \"slowlog-log-slower-than\"))\n\tif err != nil {\n\t\tt.Errorf(\"TestSlowLog failed during CONFIG GET slowlog-log-slower-than with error \" + err.Error())\n\t\treturn\n\t}\n\t// in case of older verion < 2.2.12 where SLOWLOG command is not supported\n\t// don't run the test\n\tif len(resultStr) == 0 {\n\t\treturn\n\t}\n\tslowLogSlowerThanOldCfg, err := strconv.Atoi(resultStr[1])\n\tif err != nil {\n\t\tt.Errorf(\"TestSlowLog failed during strconv.Atoi with error \" + err.Error())\n\t\treturn\n\t}\n\tresult, err := c.Do(\"CONFIG\", \"SET\", \"slowlog-log-slower-than\", \"0\")\n\tif err != nil && result != \"OK\" {\n\t\tt.Errorf(\"TestSlowLog failed during CONFIG SET with error \" + err.Error())\n\t\treturn\n\t}\n\tresult, err = c.Do(\"SLOWLOG\", \"GET\")\n\tif err != nil {\n\t\tt.Errorf(\"TestSlowLog failed during SLOWLOG GET with error \" + err.Error())\n\t\treturn\n\t}\n\tslowLogs, err := redis.SlowLogs(result, err)\n\tif err != nil {\n\t\tt.Errorf(\"TestSlowLog failed during redis.SlowLogs with error \" + err.Error())\n\t\treturn\n\t}\n\tslowLog := slowLogs[0]\n\tif slowLog.Args[0] != \"CONFIG\" ||\n\t\tslowLog.Args[1] != \"SET\" ||\n\t\tslowLog.Args[2] != \"slowlog-log-slower-than\" ||\n\t\tslowLog.Args[3] != \"0\" {\n\t\tt.Errorf(\"%s=%+v, want %+v\", \"TestSlowLog test failed : \",\n\t\t\tslowLog.Args[0]+\" \"+slowLog.Args[1]+\" \"+slowLog.Args[2]+\" \"+\n\t\t\t\tslowLog.Args[3], \"CONFIG SET slowlog-log-slower-than 0\")\n\t}\n\t// reset the old configuration after test\n\tresult, err = c.Do(\"CONFIG\", \"SET\", \"slowlog-log-slower-than\", slowLogSlowerThanOldCfg)\n\tif err != nil && result != \"OK\" {\n\t\tt.Errorf(\"TestSlowLog failed during CONFIG SET with error \" + err.Error())\n\t\treturn\n\t}\n}\n\nfunc TestLatency(t *testing.T) {\n\tc, err := dial()\n\trequire.NoError(t, err)\n\tdefer c.Close()\n\n\tresultStr, err := redis.Strings(c.Do(\"CONFIG\", \"GET\", \"latency-monitor-threshold\"))\n\trequire.NoError(t, err)\n\t// LATENCY commands were added in 2.8.13 so might not be supported.\n\tif len(resultStr) == 0 {\n\t\tt.Skip(\"Latency commands not supported\")\n\t}\n\tlatencyMonitorThresholdOldCfg, err := strconv.Atoi(resultStr[1])\n\trequire.NoError(t, err)\n\t// Enable latency monitoring for events that take 1ms or longer.\n\tresult, err := c.Do(\"CONFIG\", \"SET\", \"latency-monitor-threshold\", \"1\")\n\t// reset the old configuration after test.\n\tdefer func() {\n\t\tres, err := c.Do(\"CONFIG\", \"SET\", \"latency-monitor-threshold\", latencyMonitorThresholdOldCfg)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"OK\", res)\n\t}()\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"OK\", result)\n\n\t// Sleep for 1ms to register a slow event.\n\t_, err = c.Do(\"DEBUG\", \"SLEEP\", 0.001)\n\trequire.NoError(t, err)\n\n\tresult, err = c.Do(\"LATENCY\", \"LATEST\")\n\trequire.NoError(t, err)\n\n\tlatestLatencies, err := redis.Latencies(result, err)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, 1, len(latestLatencies))\n\n\tlatencyEvent := latestLatencies[0]\n\n\t// The actual latency might be longer than 1ms\n\trequire.GreaterOrEqual(t, latencyEvent.Latest, time.Millisecond)\n\trequire.GreaterOrEqual(t, latencyEvent.Max, time.Millisecond)\n\texpected := redis.Latency{\n\t\tName:   \"command\",\n\t\tLatest: latencyEvent.Latest,\n\t\tMax:    latencyEvent.Max,\n\t\tTime:   latencyEvent.Time,\n\t}\n\trequire.Equal(t, expected, latencyEvent)\n}\n\nfunc TestLatencyHistories(t *testing.T) {\n\tc, err := dial()\n\trequire.NoError(t, err)\n\tdefer c.Close()\n\n\tres, err := redis.Strings(c.Do(\"CONFIG\", \"GET\", \"latency-monitor-threshold\"))\n\trequire.NoError(t, err)\n\n\t// LATENCY commands were added in 2.8.13 so might not be supported.\n\tif len(res) == 0 {\n\t\tt.Skip(\"Latency commands not supported\")\n\t}\n\tlatencyMonitorThresholdOldCfg, err := strconv.Atoi(res[1])\n\trequire.NoError(t, err)\n\n\t// Reset so we're compatible with -count=X\n\t_, err = c.Do(\"LATENCY\", \"RESET\", \"command\")\n\trequire.NoError(t, err)\n\n\t// Enable latency monitoring for events that take 1ms or longer\n\tresult, err := c.Do(\"CONFIG\", \"SET\", \"latency-monitor-threshold\", \"1\")\n\t// reset the old configuration after test.\n\tdefer func() {\n\t\tres, err := c.Do(\"CONFIG\", \"SET\", \"latency-monitor-threshold\", latencyMonitorThresholdOldCfg)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"OK\", res)\n\t}()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"OK\", result)\n\n\t// Sleep for 1ms to register a slow event\n\t_, err = c.Do(\"DEBUG\", \"SLEEP\", 0.001)\n\trequire.NoError(t, err)\n\n\tresult, err = c.Do(\"LATENCY\", \"HISTORY\", \"command\")\n\trequire.NoError(t, err)\n\n\tlatencyHistory, err := redis.LatencyHistories(result, err)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, latencyHistory, 1)\n\tlatencyEvent := latencyHistory[0]\n\t// The actual latency might be longer than 1ms\n\trequire.GreaterOrEqual(t, latencyEvent.ExecutionTime, time.Millisecond)\n}\n\n// dial wraps DialDefaultServer() with a more suitable function name for examples.\nfunc dial() (redis.Conn, error) {\n\treturn redis.DialDefaultServer()\n}\n\n// serverAddr wraps DefaultServerAddr() with a more suitable function name for examples.\nfunc serverAddr() (string, error) {\n\treturn redis.DefaultServerAddr()\n}\n\nfunc ExampleBool() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\tif _, err = c.Do(\"SET\", \"foo\", 1); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\texists, err := redis.Bool(c.Do(\"EXISTS\", \"foo\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"%#v\\n\", exists)\n\t// Output:\n\t// true\n}\n\nfunc ExampleInt() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\t_, err = c.Do(\"SET\", \"k1\", 1)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tn, err := redis.Int(c.Do(\"GET\", \"k1\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Printf(\"%#v\\n\", n)\n\tn, err = redis.Int(c.Do(\"INCR\", \"k1\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Printf(\"%#v\\n\", n)\n\t// Output:\n\t// 1\n\t// 2\n}\n\nfunc ExampleInts() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\t_, err = c.Do(\"SADD\", \"set_with_integers\", 4, 5, 6)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tints, err := redis.Ints(c.Do(\"SMEMBERS\", \"set_with_integers\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Printf(\"%#v\\n\", ints)\n\t// Output:\n\t// []int{4, 5, 6}\n}\n\nfunc ExampleString() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\t_, err = c.Do(\"SET\", \"hello\", \"world\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\ts, err := redis.String(c.Do(\"GET\", \"hello\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Printf(\"%#v\\n\", s)\n\t// Output:\n\t// \"world\"\n}\n"
  },
  {
    "path": "redis/scan.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar (\n\tscannerType = reflect.TypeOf((*Scanner)(nil)).Elem()\n)\n\nfunc ensureLen(d reflect.Value, n int) {\n\tif n > d.Cap() {\n\t\td.Set(reflect.MakeSlice(d.Type(), n, n))\n\t} else {\n\t\td.SetLen(n)\n\t}\n}\n\nfunc cannotConvert(d reflect.Value, s interface{}) error {\n\tvar sname string\n\tswitch s.(type) {\n\tcase string:\n\t\tsname = \"Redis simple string\"\n\tcase Error:\n\t\tsname = \"Redis error\"\n\tcase int64:\n\t\tsname = \"Redis integer\"\n\tcase []byte:\n\t\tsname = \"Redis bulk string\"\n\tcase []interface{}:\n\t\tsname = \"Redis array\"\n\tcase nil:\n\t\tsname = \"Redis nil\"\n\tdefault:\n\t\tsname = reflect.TypeOf(s).String()\n\t}\n\treturn fmt.Errorf(\"cannot convert from %s to %s\", sname, d.Type())\n}\n\nfunc convertAssignNil(d reflect.Value) (err error) {\n\tswitch d.Type().Kind() {\n\tcase reflect.Slice, reflect.Interface:\n\t\td.Set(reflect.Zero(d.Type()))\n\tdefault:\n\t\terr = cannotConvert(d, nil)\n\t}\n\treturn err\n}\n\nfunc convertAssignError(d reflect.Value, s Error) (err error) {\n\tif d.Kind() == reflect.String {\n\t\td.SetString(string(s))\n\t} else if d.Kind() == reflect.Slice && d.Type().Elem().Kind() == reflect.Uint8 {\n\t\td.SetBytes([]byte(s))\n\t} else {\n\t\terr = cannotConvert(d, s)\n\t}\n\treturn\n}\n\nfunc convertAssignString(d reflect.Value, s string) (err error) {\n\tswitch d.Type().Kind() {\n\tcase reflect.Float32, reflect.Float64:\n\t\tvar x float64\n\t\tx, err = strconv.ParseFloat(s, d.Type().Bits())\n\t\td.SetFloat(x)\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\tvar x int64\n\t\tx, err = strconv.ParseInt(s, 10, d.Type().Bits())\n\t\td.SetInt(x)\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\tvar x uint64\n\t\tx, err = strconv.ParseUint(s, 10, d.Type().Bits())\n\t\td.SetUint(x)\n\tcase reflect.Bool:\n\t\tvar x bool\n\t\tx, err = strconv.ParseBool(s)\n\t\td.SetBool(x)\n\tcase reflect.String:\n\t\td.SetString(s)\n\tcase reflect.Slice:\n\t\tif d.Type().Elem().Kind() == reflect.Uint8 {\n\t\t\td.SetBytes([]byte(s))\n\t\t} else {\n\t\t\terr = cannotConvert(d, s)\n\t\t}\n\tcase reflect.Ptr:\n\t\terr = convertAssignString(d.Elem(), s)\n\tdefault:\n\t\terr = cannotConvert(d, s)\n\t}\n\treturn\n}\n\nfunc convertAssignBulkString(d reflect.Value, s []byte) (err error) {\n\tswitch d.Type().Kind() {\n\tcase reflect.Slice:\n\t\t// Handle []byte destination here to avoid unnecessary\n\t\t// []byte -> string -> []byte converion.\n\t\tif d.Type().Elem().Kind() == reflect.Uint8 {\n\t\t\td.SetBytes(s)\n\t\t} else {\n\t\t\terr = cannotConvert(d, s)\n\t\t}\n\tcase reflect.Ptr:\n\t\tif d.CanInterface() && d.CanSet() {\n\t\t\tif s == nil {\n\t\t\t\tif d.IsNil() {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\td.Set(reflect.Zero(d.Type()))\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif d.IsNil() {\n\t\t\t\td.Set(reflect.New(d.Type().Elem()))\n\t\t\t}\n\n\t\t\tif sc, ok := d.Interface().(Scanner); ok {\n\t\t\t\treturn sc.RedisScan(s)\n\t\t\t}\n\t\t}\n\t\terr = convertAssignString(d, string(s))\n\tdefault:\n\t\terr = convertAssignString(d, string(s))\n\t}\n\treturn err\n}\n\nfunc convertAssignInt(d reflect.Value, s int64) (err error) {\n\tswitch d.Type().Kind() {\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\td.SetInt(s)\n\t\tif d.Int() != s {\n\t\t\terr = strconv.ErrRange\n\t\t\td.SetInt(0)\n\t\t}\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\tif s < 0 {\n\t\t\terr = strconv.ErrRange\n\t\t} else {\n\t\t\tx := uint64(s)\n\t\t\td.SetUint(x)\n\t\t\tif d.Uint() != x {\n\t\t\t\terr = strconv.ErrRange\n\t\t\t\td.SetUint(0)\n\t\t\t}\n\t\t}\n\tcase reflect.Bool:\n\t\td.SetBool(s != 0)\n\tdefault:\n\t\terr = cannotConvert(d, s)\n\t}\n\treturn\n}\n\nfunc convertAssignValue(d reflect.Value, s interface{}) (err error) {\n\tif d.Kind() != reflect.Ptr {\n\t\tif d.CanAddr() {\n\t\t\td2 := d.Addr()\n\t\t\tif d2.CanInterface() {\n\t\t\t\tif scanner, ok := d2.Interface().(Scanner); ok {\n\t\t\t\t\treturn scanner.RedisScan(s)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if d.CanInterface() {\n\t\t// Already a reflect.Ptr\n\t\tif d.IsNil() {\n\t\t\td.Set(reflect.New(d.Type().Elem()))\n\t\t}\n\t\tif scanner, ok := d.Interface().(Scanner); ok {\n\t\t\treturn scanner.RedisScan(s)\n\t\t}\n\t}\n\n\tswitch s := s.(type) {\n\tcase nil:\n\t\terr = convertAssignNil(d)\n\tcase []byte:\n\t\terr = convertAssignBulkString(d, s)\n\tcase int64:\n\t\terr = convertAssignInt(d, s)\n\tcase string:\n\t\terr = convertAssignString(d, s)\n\tcase Error:\n\t\terr = convertAssignError(d, s)\n\tdefault:\n\t\terr = cannotConvert(d, s)\n\t}\n\treturn err\n}\n\nfunc convertAssignArray(d reflect.Value, s []interface{}) error {\n\tif d.Type().Kind() != reflect.Slice {\n\t\treturn cannotConvert(d, s)\n\t}\n\tensureLen(d, len(s))\n\tfor i := 0; i < len(s); i++ {\n\t\tif err := convertAssignValue(d.Index(i), s[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc convertAssign(d interface{}, s interface{}) (err error) {\n\tif scanner, ok := d.(Scanner); ok {\n\t\treturn scanner.RedisScan(s)\n\t}\n\n\t// Handle the most common destination types using type switches and\n\t// fall back to reflection for all other types.\n\tswitch s := s.(type) {\n\tcase nil:\n\t\t// ignore\n\tcase []byte:\n\t\tswitch d := d.(type) {\n\t\tcase *string:\n\t\t\t*d = string(s)\n\t\tcase *int:\n\t\t\t*d, err = strconv.Atoi(string(s))\n\t\tcase *bool:\n\t\t\t*d, err = strconv.ParseBool(string(s))\n\t\tcase *[]byte:\n\t\t\t*d = s\n\t\tcase *interface{}:\n\t\t\t*d = s\n\t\tcase nil:\n\t\t\t// skip value\n\t\tdefault:\n\t\t\tif d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {\n\t\t\t\terr = cannotConvert(d, s)\n\t\t\t} else {\n\t\t\t\terr = convertAssignBulkString(d.Elem(), s)\n\t\t\t}\n\t\t}\n\tcase int64:\n\t\tswitch d := d.(type) {\n\t\tcase *int:\n\t\t\tx := int(s)\n\t\t\tif int64(x) != s {\n\t\t\t\terr = strconv.ErrRange\n\t\t\t\tx = 0\n\t\t\t}\n\t\t\t*d = x\n\t\tcase *bool:\n\t\t\t*d = s != 0\n\t\tcase *interface{}:\n\t\t\t*d = s\n\t\tcase nil:\n\t\t\t// skip value\n\t\tdefault:\n\t\t\tif d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {\n\t\t\t\terr = cannotConvert(d, s)\n\t\t\t} else {\n\t\t\t\terr = convertAssignInt(d.Elem(), s)\n\t\t\t}\n\t\t}\n\tcase string:\n\t\tswitch d := d.(type) {\n\t\tcase *string:\n\t\t\t*d = s\n\t\tcase *interface{}:\n\t\t\t*d = s\n\t\tcase nil:\n\t\t\t// skip value\n\t\tdefault:\n\t\t\terr = cannotConvert(reflect.ValueOf(d), s)\n\t\t}\n\tcase []interface{}:\n\t\tswitch d := d.(type) {\n\t\tcase *[]interface{}:\n\t\t\t*d = s\n\t\tcase *interface{}:\n\t\t\t*d = s\n\t\tcase nil:\n\t\t\t// skip value\n\t\tdefault:\n\t\t\tif d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {\n\t\t\t\terr = cannotConvert(d, s)\n\t\t\t} else {\n\t\t\t\terr = convertAssignArray(d.Elem(), s)\n\t\t\t}\n\t\t}\n\tcase Error:\n\t\terr = s\n\tdefault:\n\t\terr = cannotConvert(reflect.ValueOf(d), s)\n\t}\n\treturn\n}\n\n// Scan copies from src to the values pointed at by dest.\n//\n// Scan uses RedisScan if available otherwise:\n//\n// The values pointed at by dest must be an integer, float, boolean, string,\n// []byte, interface{} or slices of these types. Scan uses the standard strconv\n// package to convert bulk strings to numeric and boolean types.\n//\n// If a dest value is nil, then the corresponding src value is skipped.\n//\n// If a src element is nil, then the corresponding dest value is not modified.\n//\n// To enable easy use of Scan in a loop, Scan returns the slice of src\n// following the copied values.\nfunc Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {\n\tif len(src) < len(dest) {\n\t\treturn nil, errors.New(\"redigo.Scan: array short\")\n\t}\n\tvar err error\n\tfor i, d := range dest {\n\t\terr = convertAssign(d, src[i])\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"redigo.Scan: cannot assign to dest %d: %v\", i, err)\n\t\t\tbreak\n\t\t}\n\t}\n\treturn src[len(dest):], err\n}\n\ntype fieldSpec struct {\n\tname      string\n\tindex     []int\n\tomitEmpty bool\n}\n\ntype structSpec struct {\n\tm map[string]*fieldSpec\n\tl []*fieldSpec\n}\n\nfunc (ss *structSpec) fieldSpec(name []byte) *fieldSpec {\n\treturn ss.m[string(name)]\n}\n\nfunc compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec, seen map[reflect.Type]struct{}) error {\n\tif _, ok := seen[t]; ok {\n\t\t// Protect against infinite recursion.\n\t\treturn fmt.Errorf(\"recursive struct definition for %v\", t)\n\t}\n\n\tseen[t] = struct{}{}\nLOOP:\n\tfor i := 0; i < t.NumField(); i++ {\n\t\tf := t.Field(i)\n\t\tswitch {\n\t\tcase f.PkgPath != \"\" && !f.Anonymous:\n\t\t\t// Ignore unexported fields.\n\t\tcase f.Anonymous:\n\t\t\tswitch f.Type.Kind() {\n\t\t\tcase reflect.Struct:\n\t\t\t\tif err := compileStructSpec(f.Type, depth, append(index, i), ss, seen); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase reflect.Ptr:\n\t\t\t\tif f.Type.Elem().Kind() == reflect.Struct {\n\t\t\t\t\tif err := compileStructSpec(f.Type.Elem(), depth, append(index, i), ss, seen); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tfs := &fieldSpec{name: f.Name}\n\t\t\ttag := f.Tag.Get(\"redis\")\n\n\t\t\tvar p string\n\t\t\tfirst := true\n\t\t\tfor len(tag) > 0 {\n\t\t\t\ti := strings.IndexByte(tag, ',')\n\t\t\t\tif i < 0 {\n\t\t\t\t\tp, tag = tag, \"\"\n\t\t\t\t} else {\n\t\t\t\t\tp, tag = tag[:i], tag[i+1:]\n\t\t\t\t}\n\t\t\t\tif p == \"-\" {\n\t\t\t\t\tcontinue LOOP\n\t\t\t\t}\n\t\t\t\tif first && len(p) > 0 {\n\t\t\t\t\tfs.name = p\n\t\t\t\t\tfirst = false\n\t\t\t\t} else {\n\t\t\t\t\tswitch p {\n\t\t\t\t\tcase \"omitempty\":\n\t\t\t\t\t\tfs.omitEmpty = true\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tpanic(fmt.Errorf(\"redigo: unknown field tag %s for type %s\", p, t.Name()))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\td, found := depth[fs.name]\n\t\t\tif !found {\n\t\t\t\td = 1 << 30\n\t\t\t}\n\n\t\t\tswitch {\n\t\t\tcase len(index) == d:\n\t\t\t\t// At same depth, remove from result.\n\t\t\t\tdelete(ss.m, fs.name)\n\t\t\t\tj := 0\n\t\t\t\tfor i := 0; i < len(ss.l); i++ {\n\t\t\t\t\tif fs.name != ss.l[i].name {\n\t\t\t\t\t\tss.l[j] = ss.l[i]\n\t\t\t\t\t\tj += 1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tss.l = ss.l[:j]\n\t\t\tcase len(index) < d:\n\t\t\t\tfs.index = make([]int, len(index)+1)\n\t\t\t\tcopy(fs.index, index)\n\t\t\t\tfs.index[len(index)] = i\n\t\t\t\tdepth[fs.name] = len(index)\n\t\t\t\tss.m[fs.name] = fs\n\t\t\t\tss.l = append(ss.l, fs)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nvar (\n\tstructSpecMutex sync.RWMutex\n\tstructSpecCache = make(map[reflect.Type]*structSpec)\n)\n\nfunc structSpecForType(t reflect.Type) (*structSpec, error) {\n\tstructSpecMutex.RLock()\n\tss, found := structSpecCache[t]\n\tstructSpecMutex.RUnlock()\n\tif found {\n\t\treturn ss, nil\n\t}\n\n\tstructSpecMutex.Lock()\n\tdefer structSpecMutex.Unlock()\n\tss, found = structSpecCache[t]\n\tif found {\n\t\treturn ss, nil\n\t}\n\n\tss = &structSpec{m: make(map[string]*fieldSpec)}\n\tif err := compileStructSpec(t, make(map[string]int), nil, ss, make(map[reflect.Type]struct{})); err != nil {\n\t\treturn nil, fmt.Errorf(\"compile struct: %s: %w\", t, err)\n\t}\n\tstructSpecCache[t] = ss\n\treturn ss, nil\n}\n\nvar errScanStructValue = errors.New(\"redigo.ScanStruct: value must be non-nil pointer to a struct\")\n\n// ScanStruct scans alternating names and values from src to a struct. The\n// HGETALL and CONFIG GET commands return replies in this format.\n//\n// ScanStruct uses exported field names to match values in the response. Use\n// 'redis' field tag to override the name:\n//\n//\tField int `redis:\"myName\"`\n//\n// Fields with the tag redis:\"-\" are ignored.\n//\n// Each field uses RedisScan if available otherwise:\n// Integer, float, boolean, string and []byte fields are supported. Scan uses the\n// standard strconv package to convert bulk string values to numeric and\n// boolean types.\n//\n// If a src element is nil, then the corresponding field is not modified.\nfunc ScanStruct(src []interface{}, dest interface{}) error {\n\td := reflect.ValueOf(dest)\n\tif d.Kind() != reflect.Ptr || d.IsNil() {\n\t\treturn errScanStructValue\n\t}\n\n\td = d.Elem()\n\tif d.Kind() != reflect.Struct {\n\t\treturn errScanStructValue\n\t}\n\n\tif len(src)%2 != 0 {\n\t\treturn errors.New(\"redigo.ScanStruct: number of values not a multiple of 2\")\n\t}\n\n\tss, err := structSpecForType(d.Type())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"redigo.ScanStruct: %w\", err)\n\t}\n\n\tfor i := 0; i < len(src); i += 2 {\n\t\ts := src[i+1]\n\t\tif s == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tname, ok := convertToBulk(src[i])\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"redigo.ScanStruct: key %d not a bulk string value got type: %T\", i, src[i])\n\t\t}\n\n\t\tfs := ss.fieldSpec(name)\n\t\tif fs == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := convertAssignValue(fieldByIndexCreate(d, fs.index), s); err != nil {\n\t\t\treturn fmt.Errorf(\"redigo.ScanStruct: cannot assign field %s: %v\", fs.name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// convertToBulk converts src to a []byte if src is a string or bulk string\n// and returns true. Otherwise nil and false is returned.\nfunc convertToBulk(src interface{}) ([]byte, bool) {\n\tswitch v := src.(type) {\n\tcase []byte:\n\t\treturn v, true\n\tcase string:\n\t\treturn []byte(v), true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nvar (\n\terrScanSliceValue = errors.New(\"redigo.ScanSlice: dest must be non-nil pointer to a struct\")\n)\n\n// ScanSlice scans src to the slice pointed to by dest.\n//\n// If the target is a slice of types which implement Scanner then the custom\n// RedisScan method is used otherwise the following rules apply:\n//\n// The elements in the dest slice must be integer, float, boolean, string, struct\n// or pointer to struct values.\n//\n// Struct fields must be integer, float, boolean or string values. All struct\n// fields are used unless a subset is specified using fieldNames.\nfunc ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {\n\td := reflect.ValueOf(dest)\n\tif d.Kind() != reflect.Ptr || d.IsNil() {\n\t\treturn errScanSliceValue\n\t}\n\td = d.Elem()\n\tif d.Kind() != reflect.Slice {\n\t\treturn errScanSliceValue\n\t}\n\n\tisPtr := false\n\tt := d.Type().Elem()\n\tst := t\n\tif t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {\n\t\tisPtr = true\n\t\tt = t.Elem()\n\t}\n\n\tif t.Kind() != reflect.Struct || st.Implements(scannerType) {\n\t\tensureLen(d, len(src))\n\t\tfor i, s := range src {\n\t\t\tif s == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := convertAssignValue(d.Index(i), s); err != nil {\n\t\t\t\treturn fmt.Errorf(\"redigo.ScanSlice: cannot assign element %d: %v\", i, err)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tss, err := structSpecForType(t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"redigo.ScanSlice: %w\", err)\n\t}\n\n\tfss := ss.l\n\tif len(fieldNames) > 0 {\n\t\tfss = make([]*fieldSpec, len(fieldNames))\n\t\tfor i, name := range fieldNames {\n\t\t\tfss[i] = ss.m[name]\n\t\t\tif fss[i] == nil {\n\t\t\t\treturn fmt.Errorf(\"redigo.ScanSlice: ScanSlice bad field name %s\", name)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(fss) == 0 {\n\t\treturn errors.New(\"redigo.ScanSlice: no struct fields\")\n\t}\n\n\tn := len(src) / len(fss)\n\tif n*len(fss) != len(src) {\n\t\treturn errors.New(\"redigo.ScanSlice: length not a multiple of struct field count\")\n\t}\n\n\tensureLen(d, n)\n\tfor i := 0; i < n; i++ {\n\t\td := d.Index(i)\n\t\tif isPtr {\n\t\t\tif d.IsNil() {\n\t\t\t\td.Set(reflect.New(t))\n\t\t\t}\n\t\t\td = d.Elem()\n\t\t}\n\t\tfor j, fs := range fss {\n\t\t\ts := src[i*len(fss)+j]\n\t\t\tif s == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {\n\t\t\t\treturn fmt.Errorf(\"redigo.ScanSlice: cannot assign element %d to field %s: %v\", i*len(fss)+j, fs.name, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Args is a helper for constructing command arguments from structured values.\ntype Args []interface{}\n\n// Add returns the result of appending value to args.\nfunc (args Args) Add(value ...interface{}) Args {\n\treturn append(args, value...)\n}\n\n// AddFlat returns the result of appending the flattened value of v to args.\n//\n// Maps are flattened by appending the alternating keys and map values to args.\n//\n// Slices are flattened by appending the slice elements to args.\n//\n// Structs are flattened by appending the alternating names and values of\n// exported fields to args. If v is a nil struct pointer, then nothing is\n// appended. The 'redis' field tag overrides struct field names. See ScanStruct\n// for more information on the use of the 'redis' field tag.\n//\n// Other types are appended to args as is.\n// panics if v includes a recursive anonymous struct.\nfunc (args Args) AddFlat(v interface{}) Args {\n\trv := reflect.ValueOf(v)\n\tswitch rv.Kind() {\n\tcase reflect.Struct:\n\t\targs = flattenStruct(args, rv)\n\tcase reflect.Slice:\n\t\tfor i := 0; i < rv.Len(); i++ {\n\t\t\targs = append(args, rv.Index(i).Interface())\n\t\t}\n\tcase reflect.Map:\n\t\tfor _, k := range rv.MapKeys() {\n\t\t\targs = append(args, k.Interface(), rv.MapIndex(k).Interface())\n\t\t}\n\tcase reflect.Ptr:\n\t\tif rv.Type().Elem().Kind() == reflect.Struct {\n\t\t\tif !rv.IsNil() {\n\t\t\t\targs = flattenStruct(args, rv.Elem())\n\t\t\t}\n\t\t} else {\n\t\t\targs = append(args, v)\n\t\t}\n\tdefault:\n\t\targs = append(args, v)\n\t}\n\treturn args\n}\n\nfunc flattenStruct(args Args, v reflect.Value) Args {\n\tss, err := structSpecForType(v.Type())\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"redigo.AddFlat: %w\", err))\n\t}\n\n\tfor _, fs := range ss.l {\n\t\tfv, err := fieldByIndexErr(v, fs.index)\n\t\tif err != nil {\n\t\t\t// Nil item ignore.\n\t\t\tcontinue\n\t\t}\n\t\tif fs.omitEmpty {\n\t\t\tvar empty = false\n\t\t\tswitch fv.Kind() {\n\t\t\tcase reflect.Array, reflect.Map, reflect.Slice, reflect.String:\n\t\t\t\tempty = fv.Len() == 0\n\t\t\tcase reflect.Bool:\n\t\t\t\tempty = !fv.Bool()\n\t\t\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\t\t\tempty = fv.Int() == 0\n\t\t\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\t\t\tempty = fv.Uint() == 0\n\t\t\tcase reflect.Float32, reflect.Float64:\n\t\t\t\tempty = fv.Float() == 0\n\t\t\tcase reflect.Interface, reflect.Ptr:\n\t\t\t\tempty = fv.IsNil()\n\t\t\t}\n\t\t\tif empty {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif arg, ok := fv.Interface().(Argument); ok {\n\t\t\targs = append(args, fs.name, arg.RedisArg())\n\t\t} else if fv.Kind() == reflect.Ptr {\n\t\t\tif !fv.IsNil() {\n\t\t\t\targs = append(args, fs.name, fv.Elem().Interface())\n\t\t\t}\n\t\t} else {\n\t\t\targs = append(args, fs.name, fv.Interface())\n\t\t}\n\t}\n\treturn args\n}\n"
  },
  {
    "path": "redis/scan_test.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype durationScan struct {\n\ttime.Duration `redis:\"sd\"`\n}\n\nfunc (t *durationScan) RedisScan(src interface{}) (err error) {\n\tif t == nil {\n\t\treturn fmt.Errorf(\"nil pointer\")\n\t}\n\tswitch src := src.(type) {\n\tcase string:\n\t\tt.Duration, err = time.ParseDuration(src)\n\tcase []byte:\n\t\tt.Duration, err = time.ParseDuration(string(src))\n\tcase int64:\n\t\tt.Duration = time.Duration(src)\n\tdefault:\n\t\terr = fmt.Errorf(\"cannot convert from %T to %T\", src, t)\n\t}\n\treturn err\n}\n\nvar scanConversionTests = []struct {\n\tsrc  interface{}\n\tdest interface{}\n}{\n\t{[]byte(\"-inf\"), math.Inf(-1)},\n\t{[]byte(\"+inf\"), math.Inf(1)},\n\t{[]byte(\"0\"), float64(0)},\n\t{[]byte(\"3.14159\"), float64(3.14159)},\n\t{[]byte(\"3.14\"), float32(3.14)},\n\t{[]byte(\"-100\"), int(-100)},\n\t{[]byte(\"101\"), int(101)},\n\t{int64(102), int(102)},\n\t{[]byte(\"103\"), uint(103)},\n\t{int64(104), uint(104)},\n\t{[]byte(\"105\"), int8(105)},\n\t{int64(106), int8(106)},\n\t{[]byte(\"107\"), uint8(107)},\n\t{int64(108), uint8(108)},\n\t{[]byte(\"0\"), false},\n\t{int64(0), false},\n\t{[]byte(\"f\"), false},\n\t{[]byte(\"1\"), true},\n\t{int64(1), true},\n\t{[]byte(\"t\"), true},\n\t{\"hello\", \"hello\"},\n\t{[]byte(\"hello\"), \"hello\"},\n\t{[]byte(\"world\"), []byte(\"world\")},\n\t{nil, \"\"},\n\t{nil, []byte(nil)},\n\n\t{[]interface{}{[]byte(\"b1\")}, []interface{}{[]byte(\"b1\")}},\n\t{[]interface{}{[]byte(\"b2\")}, []string{\"b2\"}},\n\t{[]interface{}{[]byte(\"b3\"), []byte(\"b4\")}, []string{\"b3\", \"b4\"}},\n\t{[]interface{}{[]byte(\"b5\")}, [][]byte{[]byte(\"b5\")}},\n\t{[]interface{}{[]byte(\"1\")}, []int{1}},\n\t{[]interface{}{[]byte(\"1\"), []byte(\"2\")}, []int{1, 2}},\n\t{[]interface{}{[]byte(\"1\"), []byte(\"2\")}, []float64{1, 2}},\n\t{[]interface{}{[]byte(\"1\")}, []byte{1}},\n\t{[]interface{}{[]byte(\"1\")}, []bool{true}},\n\n\t{[]interface{}{\"s1\"}, []interface{}{\"s1\"}},\n\t{[]interface{}{\"s2\"}, [][]byte{[]byte(\"s2\")}},\n\t{[]interface{}{\"s3\", \"s4\"}, []string{\"s3\", \"s4\"}},\n\t{[]interface{}{\"s5\"}, [][]byte{[]byte(\"s5\")}},\n\t{[]interface{}{\"1\"}, []int{1}},\n\t{[]interface{}{\"1\", \"2\"}, []int{1, 2}},\n\t{[]interface{}{\"1\", \"2\"}, []float64{1, 2}},\n\t{[]interface{}{\"1\"}, []byte{1}},\n\t{[]interface{}{\"1\"}, []bool{true}},\n\n\t{[]interface{}{nil, \"2\"}, []interface{}{nil, \"2\"}},\n\t{[]interface{}{nil, []byte(\"2\")}, [][]byte{nil, []byte(\"2\")}},\n\n\t{[]interface{}{redis.Error(\"e1\")}, []interface{}{redis.Error(\"e1\")}},\n\t{[]interface{}{redis.Error(\"e2\")}, [][]byte{[]byte(\"e2\")}},\n\t{[]interface{}{redis.Error(\"e3\")}, []string{\"e3\"}},\n\n\t{\"1m\", durationScan{Duration: time.Minute}},\n\t{[]byte(\"1m\"), durationScan{Duration: time.Minute}},\n\t{time.Minute.Nanoseconds(), durationScan{Duration: time.Minute}},\n\t{[]interface{}{[]byte(\"1m\")}, []durationScan{{Duration: time.Minute}}},\n\t{[]interface{}{[]byte(\"1m\")}, []*durationScan{{Duration: time.Minute}}},\n}\n\nfunc TestScanConversion(t *testing.T) {\n\tfor _, tt := range scanConversionTests {\n\t\tvalues := []interface{}{tt.src}\n\t\tdest := reflect.New(reflect.TypeOf(tt.dest))\n\t\t_, err := redis.Scan(values, dest.Interface())\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Scan(%v) returned error %v\", tt, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {\n\t\t\tt.Errorf(\"Scan(%v) returned %v, want %v\", tt, dest.Elem().Interface(), tt.dest)\n\t\t}\n\t}\n}\n\nvar scanConversionErrorTests = []struct {\n\tsrc  interface{}\n\tdest interface{}\n}{\n\t{[]byte(\"1234\"), byte(0)},\n\t{int64(1234), byte(0)},\n\t{[]byte(\"-1\"), byte(0)},\n\t{int64(-1), byte(0)},\n\t{[]byte(\"junk\"), false},\n\t{redis.Error(\"blah\"), false},\n\t{redis.Error(\"blah\"), durationScan{Duration: time.Minute}},\n\t{\"invalid\", durationScan{Duration: time.Minute}},\n}\n\nfunc TestScanConversionError(t *testing.T) {\n\tfor _, tt := range scanConversionErrorTests {\n\t\tvalues := []interface{}{tt.src}\n\t\tdest := reflect.New(reflect.TypeOf(tt.dest))\n\t\t_, err := redis.Scan(values, dest.Interface())\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Scan(%v) did not return error\", tt)\n\t\t}\n\t}\n}\n\nfunc ExampleScan() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\tif err = c.Send(\"HMSET\", \"album:1\", \"title\", \"Red\", \"rating\", 5); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"HMSET\", \"album:2\", \"title\", \"Earthbound\", \"rating\", 1); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"HMSET\", \"album:3\", \"title\", \"Beat\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"LPUSH\", \"albums\", \"1\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"LPUSH\", \"albums\", \"2\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"LPUSH\", \"albums\", \"3\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tvalues, err := redis.Values(c.Do(\"SORT\", \"albums\",\n\t\t\"BY\", \"album:*->rating\",\n\t\t\"GET\", \"album:*->title\",\n\t\t\"GET\", \"album:*->rating\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tfor len(values) > 0 {\n\t\tvar title string\n\t\trating := -1 // initialize to illegal value to detect nil.\n\t\tvalues, err = redis.Scan(values, &title, &rating)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\t\tif rating == -1 {\n\t\t\tfmt.Println(title, \"not-rated\")\n\t\t} else {\n\t\t\tfmt.Println(title, rating)\n\t\t}\n\t}\n\t// Output:\n\t// Beat not-rated\n\t// Earthbound 1\n\t// Red 5\n}\n\ntype s0 struct {\n\tX  int\n\tY  int `redis:\"y\"`\n\tBt bool\n}\n\ntype s1 struct {\n\tX    int    `redis:\"-\"`\n\tI    int    `redis:\"i\"`\n\tU    uint   `redis:\"u\"`\n\tS    string `redis:\"s\"`\n\tP    []byte `redis:\"p\"`\n\tB    bool   `redis:\"b\"`\n\tBt   bool\n\tBf   bool\n\tPtrB *bool\n\ts0\n\tSd  durationScan  `redis:\"sd\"`\n\tSdp *durationScan `redis:\"sdp\"`\n}\n\nvar (\n\tboolTrue = true\n\tint5     = 5\n)\n\nvar scanStructTests = []struct {\n\tname     string\n\treply    []string\n\texpected interface{}\n}{\n\t{\"basic\",\n\t\t[]string{\n\t\t\t\"i\", \"-1234\",\n\t\t\t\"u\", \"5678\",\n\t\t\t\"s\", \"hello\",\n\t\t\t\"p\", \"world\",\n\t\t\t\"b\", \"t\",\n\t\t\t\"Bt\", \"1\",\n\t\t\t\"Bf\", \"0\",\n\t\t\t\"PtrB\", \"1\",\n\t\t\t\"X\", \"123\",\n\t\t\t\"y\", \"456\",\n\t\t\t\"sd\", \"1m\",\n\t\t\t\"sdp\", \"1m\",\n\t\t},\n\t\t&s1{\n\t\t\tI:    -1234,\n\t\t\tU:    5678,\n\t\t\tS:    \"hello\",\n\t\t\tP:    []byte(\"world\"),\n\t\t\tB:    true,\n\t\t\tBt:   true,\n\t\t\tBf:   false,\n\t\t\tPtrB: &boolTrue,\n\t\t\ts0:   s0{X: 123, Y: 456},\n\t\t\tSd:   durationScan{Duration: time.Minute},\n\t\t\tSdp:  &durationScan{Duration: time.Minute},\n\t\t},\n\t},\n\t{\"absent values\",\n\t\t[]string{},\n\t\t&s1{},\n\t},\n\t{\"struct-anonymous-nil\",\n\t\t[]string{\"edi\", \"2\"},\n\t\t&struct {\n\t\t\tEd\n\t\t\t*Edp\n\t\t}{\n\t\t\tEd: Ed{EdI: 2},\n\t\t},\n\t},\n\t{\"struct-anonymous-multi-nil-early\",\n\t\t[]string{\"edi\", \"2\"},\n\t\t&struct {\n\t\t\tEd\n\t\t\t*Ed2\n\t\t}{\n\t\t\tEd: Ed{EdI: 2},\n\t\t},\n\t},\n\t{\"struct-anonymous-multi-nil-late\",\n\t\t[]string{\"edi\", \"2\", \"ed2i\", \"3\", \"edp2i\", \"4\"},\n\t\t&struct {\n\t\t\tEd\n\t\t\t*Ed2\n\t\t}{\n\t\t\tEd: Ed{EdI: 2},\n\t\t\tEd2: &Ed2{\n\t\t\t\tEd2I: 3,\n\t\t\t\tEdp2: &Edp2{\n\t\t\t\t\tEdp2I: 4,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n}\n\nfunc TestScanStruct(t *testing.T) {\n\tfor _, tt := range scanStructTests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treply := make([]interface{}, len(tt.reply))\n\t\t\tfor i, v := range tt.reply {\n\t\t\t\treply[i] = []byte(v)\n\t\t\t}\n\n\t\t\tvalue := reflect.New(reflect.ValueOf(tt.expected).Type().Elem()).Interface()\n\t\t\terr := redis.ScanStruct(reply, value)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tt.expected, value)\n\t\t})\n\t}\n}\n\nfunc TestScanStructStringKeys(t *testing.T) {\n\treply := []interface{}{\"simple\", []byte(\"value\"), \"number\", []byte(\"123\")}\n\texpected := &struct {\n\t\tSimple string `redis:\"simple\"`\n\t\tNumber int    `redis:\"number\"`\n\t}{\n\t\tSimple: \"value\",\n\t\tNumber: 123,\n\t}\n\n\tvalue := reflect.New(reflect.ValueOf(expected).Type().Elem()).Interface()\n\terr := redis.ScanStruct(reply, value)\n\trequire.NoError(t, err)\n\trequire.Equal(t, expected, value)\n}\n\nfunc TestBadScanStructArgs(t *testing.T) {\n\tx := []interface{}{\"A\", \"b\"}\n\ttest := func(v interface{}) {\n\t\tif err := redis.ScanStruct(x, v); err == nil {\n\t\t\tt.Errorf(\"Expect error for ScanStruct(%T, %T)\", x, v)\n\t\t}\n\t}\n\n\ttest(nil)\n\n\tvar v0 *struct{}\n\ttest(v0)\n\n\tvar v1 int\n\ttest(&v1)\n\n\tx = x[:1]\n\tv2 := struct{ A string }{}\n\ttest(&v2)\n}\n\ntype sliceScanner struct {\n\tField string\n}\n\nfunc (ss *sliceScanner) RedisScan(s interface{}) error {\n\tv, ok := s.([]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid type %T\", s)\n\t}\n\treturn redis.ScanStruct(v, ss)\n}\n\nvar scanSliceTests = []struct {\n\tname       string\n\tsrc        []interface{}\n\tfieldNames []string\n\tok         bool\n\tdest       interface{}\n}{\n\t{\n\t\t\"scanner\",\n\t\t[]interface{}{[]interface{}{[]byte(\"Field\"), []byte(\"1\")}},\n\t\tnil,\n\t\ttrue,\n\t\t[]*sliceScanner{{\"1\"}},\n\t},\n\t{\n\t\t\"int\",\n\t\t[]interface{}{[]byte(\"1\"), nil, []byte(\"-1\")},\n\t\tnil,\n\t\ttrue,\n\t\t[]int{1, 0, -1},\n\t},\n\t{\n\t\t\"uint\",\n\t\t[]interface{}{[]byte(\"1\"), nil, []byte(\"2\")},\n\t\tnil,\n\t\ttrue,\n\t\t[]uint{1, 0, 2},\n\t},\n\t{\n\t\t\"uint-error\",\n\t\t[]interface{}{[]byte(\"-1\")},\n\t\tnil,\n\t\tfalse,\n\t\t[]uint{1},\n\t},\n\t{\n\t\t\"[]byte\",\n\t\t[]interface{}{[]byte(\"hello\"), nil, []byte(\"world\")},\n\t\tnil,\n\t\ttrue,\n\t\t[][]byte{[]byte(\"hello\"), nil, []byte(\"world\")},\n\t},\n\t{\n\t\t\"string\",\n\t\t[]interface{}{[]byte(\"hello\"), nil, []byte(\"world\")},\n\t\tnil,\n\t\ttrue,\n\t\t[]string{\"hello\", \"\", \"world\"},\n\t},\n\t{\n\t\t\"struct\",\n\t\t[]interface{}{[]byte(\"a1\"), []byte(\"b1\"), []byte(\"a2\"), []byte(\"b2\")},\n\t\tnil,\n\t\ttrue,\n\t\t[]struct{ A, B string }{{\"a1\", \"b1\"}, {\"a2\", \"b2\"}},\n\t},\n\t{\n\t\t\"struct-error\",\n\t\t[]interface{}{[]byte(\"a1\"), []byte(\"b1\")},\n\t\tnil,\n\t\tfalse,\n\t\t[]struct{ A, B, C string }{{\"a1\", \"b1\", \"\"}},\n\t},\n\t{\n\t\t\"struct-field-names\",\n\t\t[]interface{}{[]byte(\"a1\"), []byte(\"b1\"), []byte(\"a2\"), []byte(\"b2\")},\n\t\t[]string{\"A\", \"B\"},\n\t\ttrue,\n\t\t[]struct{ A, C, B string }{{\"a1\", \"\", \"b1\"}, {\"a2\", \"\", \"b2\"}},\n\t},\n\t{\n\t\t\"struct-no-fields\",\n\t\t[]interface{}{[]byte(\"a1\"), []byte(\"b1\"), []byte(\"a2\"), []byte(\"b2\")},\n\t\tnil,\n\t\tfalse,\n\t\t[]struct{}{},\n\t},\n}\n\nfunc TestScanSlice(t *testing.T) {\n\tfor _, tt := range scanSliceTests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttyp := reflect.ValueOf(tt.dest).Type()\n\t\t\tdest := reflect.New(typ)\n\n\t\t\terr := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)\n\t\t\tif tt.ok != (err == nil) {\n\t\t\t\tt.Fatalf(\"ScanSlice(%v, []%s, %v) returned error %v\", tt.src, typ, tt.fieldNames, err)\n\t\t\t}\n\t\t\tif tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {\n\t\t\t\tt.Errorf(\"ScanSlice(src, []%s) returned %#v, want %#v\", typ, dest.Elem().Interface(), tt.dest)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc ExampleScanSlice() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\tif err = c.Send(\"HMSET\", \"album:1\", \"title\", \"Red\", \"rating\", 5); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"HMSET\", \"album:2\", \"title\", \"Earthbound\", \"rating\", 1); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"HMSET\", \"album:3\", \"title\", \"Beat\", \"rating\", 4); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"LPUSH\", \"albums\", \"1\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"LPUSH\", \"albums\", \"2\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tif err = c.Send(\"LPUSH\", \"albums\", \"3\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tvalues, err := redis.Values(c.Do(\"SORT\", \"albums\",\n\t\t\"BY\", \"album:*->rating\",\n\t\t\"GET\", \"album:*->title\",\n\t\t\"GET\", \"album:*->rating\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tvar albums []struct {\n\t\tTitle  string\n\t\tRating int\n\t}\n\tif err := redis.ScanSlice(values, &albums); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Printf(\"%v\\n\", albums)\n\t// Output:\n\t// [{Earthbound 1} {Beat 4} {Red 5}]\n}\n\ntype Ed struct {\n\tEdI int `redis:\"edi\"`\n}\n\ntype Edp struct {\n\tEdpI int `redis:\"edpi\"`\n}\n\ntype Ed2 struct {\n\tEd2I int `redis:\"ed2i\"`\n\t*Edp2\n}\n\ntype Edp2 struct {\n\tEdp2I int `redis:\"edp2i\"`\n\t*Edp\n}\n\ntype Edpr1 struct {\n\tEdpr1I int `redis:\"edpr1i\"`\n\t*Edpr2\n}\n\ntype Edpr2 struct {\n\tEdpr2I int `redis:\"edpr2i\"`\n\t*Edpr1\n}\n\nvar argsTests = []struct {\n\ttitle    string\n\tfn       func() redis.Args\n\texpected redis.Args\n\tpanics   bool\n}{\n\t{\"struct-ptr\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(&struct {\n\t\t\t\tI     int               `redis:\"i\"`\n\t\t\t\tU     uint              `redis:\"u\"`\n\t\t\t\tS     string            `redis:\"s\"`\n\t\t\t\tP     []byte            `redis:\"p\"`\n\t\t\t\tM     map[string]string `redis:\"m\"`\n\t\t\t\tBt    bool\n\t\t\t\tBf    bool\n\t\t\t\tPtrB  *bool\n\t\t\t\tPtrI  *int\n\t\t\t\tPtrI2 *int\n\t\t\t}{\n\t\t\t\t-1234, 5678, \"hello\", []byte(\"world\"), map[string]string{\"hello\": \"world\"}, true, false, &boolTrue, nil, &int5,\n\t\t\t})\n\t\t},\n\t\tredis.Args{\"i\", int(-1234), \"u\", uint(5678), \"s\", \"hello\", \"p\", []byte(\"world\"), \"m\", map[string]string{\"hello\": \"world\"}, \"Bt\", true, \"Bf\", false, \"PtrB\", true, \"PtrI2\", 5},\n\t\tfalse,\n\t},\n\t{\"struct\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct{ I int }{123})\n\t\t},\n\t\tredis.Args{\"I\", 123},\n\t\tfalse,\n\t},\n\t{\"struct-with-RedisArg-direct\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct{ T CustomTime }{CustomTime{Time: time.Unix(1573231058, 0)}})\n\t\t},\n\t\tredis.Args{\"T\", int64(1573231058)},\n\t\tfalse,\n\t},\n\t{\"struct-with-RedisArg-direct-ptr\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct{ T *CustomTime }{&CustomTime{Time: time.Unix(1573231058, 0)}})\n\t\t},\n\t\tredis.Args{\"T\", int64(1573231058)},\n\t\tfalse,\n\t},\n\t{\"struct-with-RedisArg-ptr\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct{ T *CustomTimePtr }{&CustomTimePtr{Time: time.Unix(1573231058, 0)}})\n\t\t},\n\t\tredis.Args{\"T\", int64(1573231058)},\n\t\tfalse,\n\t},\n\t{\"slice\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.Add(1).AddFlat([]string{\"a\", \"b\", \"c\"}).Add(2)\n\t\t},\n\t\tredis.Args{1, \"a\", \"b\", \"c\", 2},\n\t\tfalse,\n\t},\n\t{\"struct-omitempty\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(&struct {\n\t\t\t\tSdp *durationArg `redis:\"Sdp,omitempty\"`\n\t\t\t}{\n\t\t\t\tnil,\n\t\t\t})\n\t\t},\n\t\tredis.Args{},\n\t\tfalse,\n\t},\n\t{\"struct-anonymous\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct {\n\t\t\t\tEd\n\t\t\t\t*Edp\n\t\t\t}{\n\t\t\t\tEd{EdI: 2},\n\t\t\t\t&Edp{EdpI: 3},\n\t\t\t})\n\t\t},\n\t\tredis.Args{\"edi\", 2, \"edpi\", 3},\n\t\tfalse,\n\t},\n\t{\"struct-anonymous-nil\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct {\n\t\t\t\tEd\n\t\t\t\t*Edp\n\t\t\t}{\n\t\t\t\tEd: Ed{EdI: 2},\n\t\t\t})\n\t\t},\n\t\tredis.Args{\"edi\", 2},\n\t\tfalse,\n\t},\n\t{\"struct-anonymous-multi-nil-early\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct {\n\t\t\t\tEd\n\t\t\t\t*Ed2\n\t\t\t}{\n\t\t\t\tEd: Ed{EdI: 2},\n\t\t\t})\n\t\t},\n\t\tredis.Args{\"edi\", 2},\n\t\tfalse,\n\t},\n\t{\"struct-anonymous-multi-nil-late\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct {\n\t\t\t\tEd\n\t\t\t\t*Ed2\n\t\t\t}{\n\t\t\t\tEd: Ed{EdI: 2},\n\t\t\t\tEd2: &Ed2{\n\t\t\t\t\tEd2I: 3,\n\t\t\t\t\tEdp2: &Edp2{\n\t\t\t\t\t\tEdp2I: 4,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t},\n\t\tredis.Args{\"edi\", 2, \"ed2i\", 3, \"edp2i\", 4},\n\t\tfalse,\n\t},\n\t{\"struct-recursive-ptr\",\n\t\tfunc() redis.Args {\n\t\t\treturn redis.Args{}.AddFlat(struct {\n\t\t\t\tEdpr1\n\t\t\t}{\n\t\t\t\tEdpr1: Edpr1{\n\t\t\t\t\tEdpr1I: 1,\n\t\t\t\t\tEdpr2: &Edpr2{\n\t\t\t\t\t\tEdpr2I: 2,\n\t\t\t\t\t\tEdpr1: &Edpr1{\n\t\t\t\t\t\t\tEdpr1I: 10,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t},\n\t\tredis.Args{},\n\t\ttrue,\n\t},\n}\n\nfunc TestArgs(t *testing.T) {\n\tfor _, tt := range argsTests {\n\t\tt.Run(tt.title, func(t *testing.T) {\n\t\t\tif tt.panics {\n\t\t\t\trequire.Panics(t, func() { tt.fn() })\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.Equal(t, tt.expected, tt.fn())\n\t\t})\n\t}\n}\n\ntype CustomTimePtr struct {\n\ttime.Time\n}\n\nfunc (t *CustomTimePtr) RedisArg() interface{} {\n\treturn t.Unix()\n}\n\ntype CustomTime struct {\n\ttime.Time\n}\n\nfunc (t CustomTime) RedisArg() interface{} {\n\treturn t.Unix()\n}\n\ntype InnerStruct struct {\n\tFoo int64\n}\n\nfunc (f *InnerStruct) RedisScan(src interface{}) (err error) {\n\tswitch s := src.(type) {\n\tcase []byte:\n\t\tf.Foo, err = strconv.ParseInt(string(s), 10, 64)\n\tcase string:\n\t\tf.Foo, err = strconv.ParseInt(s, 10, 64)\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid type %T\", src)\n\t}\n\treturn err\n}\n\ntype OuterStruct struct {\n\tInner *InnerStruct\n}\n\nfunc TestScanPtrRedisScan(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tsrc      []interface{}\n\t\tdest     OuterStruct\n\t\texpected OuterStruct\n\t}{\n\t\t{\n\t\t\tname:     \"value-to-nil\",\n\t\t\tsrc:      []interface{}{[]byte(\"1234\"), nil},\n\t\t\tdest:     OuterStruct{&InnerStruct{}},\n\t\t\texpected: OuterStruct{Inner: &InnerStruct{Foo: 1234}},\n\t\t},\n\t\t{\n\t\t\tname:     \"nil-to-nil\",\n\t\t\tsrc:      []interface{}{[]byte(nil), nil},\n\t\t\tdest:     OuterStruct{},\n\t\t\texpected: OuterStruct{},\n\t\t},\n\t\t{\n\t\t\tname:     \"value-to-value\",\n\t\t\tsrc:      []interface{}{[]byte(\"1234\"), nil},\n\t\t\tdest:     OuterStruct{Inner: &InnerStruct{Foo: 5678}},\n\t\t\texpected: OuterStruct{Inner: &InnerStruct{Foo: 1234}},\n\t\t},\n\t\t{\n\t\t\tname:     \"nil-to-value\",\n\t\t\tsrc:      []interface{}{[]byte(nil), nil},\n\t\t\tdest:     OuterStruct{Inner: &InnerStruct{Foo: 1234}},\n\t\t\texpected: OuterStruct{},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t_, err := redis.Scan(tc.src, &tc.dest.Inner)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.expected, tc.dest)\n\t\t})\n\t}\n}\n\nfunc ExampleArgs() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\tvar p1, p2 struct {\n\t\tTitle  string `redis:\"title\"`\n\t\tAuthor string `redis:\"author\"`\n\t\tBody   string `redis:\"body\"`\n\t}\n\n\tp1.Title = \"Example\"\n\tp1.Author = \"Gary\"\n\tp1.Body = \"Hello\"\n\n\tif _, err := c.Do(\"HMSET\", redis.Args{}.Add(\"id1\").AddFlat(&p1)...); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tm := map[string]string{\n\t\t\"title\":  \"Example2\",\n\t\t\"author\": \"Steve\",\n\t\t\"body\":   \"Map\",\n\t}\n\n\tif _, err := c.Do(\"HMSET\", redis.Args{}.Add(\"id2\").AddFlat(m)...); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tfor _, id := range []string{\"id1\", \"id2\"} {\n\n\t\tv, err := redis.Values(c.Do(\"HGETALL\", id))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\n\t\tif err := redis.ScanStruct(v, &p2); err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(\"%+v\\n\", p2)\n\t}\n\n\t// Output:\n\t// {Title:Example Author:Gary Body:Hello}\n\t// {Title:Example2 Author:Steve Body:Map}\n}\n"
  },
  {
    "path": "redis/script.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"context\"\n\t\"crypto/sha1\"\n\t\"encoding/hex\"\n\t\"io\"\n\t\"strings\"\n)\n\n// Script encapsulates the source, hash and key count for a Lua script. See\n// http://redis.io/commands/eval for information on scripts in Redis.\ntype Script struct {\n\tkeyCount int\n\tsrc      string\n\thash     string\n}\n\n// NewScript returns a new script object. If keyCount is greater than or equal\n// to zero, then the count is automatically inserted in the EVAL command\n// argument list. If keyCount is less than zero, then the application supplies\n// the count as the first value in the keysAndArgs argument to the Do, Send and\n// SendHash methods.\nfunc NewScript(keyCount int, src string) *Script {\n\th := sha1.New()\n\tio.WriteString(h, src) // nolint: errcheck\n\treturn &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}\n}\n\nfunc (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {\n\tvar args []interface{}\n\tif s.keyCount < 0 {\n\t\targs = make([]interface{}, 1+len(keysAndArgs))\n\t\targs[0] = spec\n\t\tcopy(args[1:], keysAndArgs)\n\t} else {\n\t\targs = make([]interface{}, 2+len(keysAndArgs))\n\t\targs[0] = spec\n\t\targs[1] = s.keyCount\n\t\tcopy(args[2:], keysAndArgs)\n\t}\n\treturn args\n}\n\n// Hash returns the script hash.\nfunc (s *Script) Hash() string {\n\treturn s.hash\n}\n\nfunc (s *Script) DoContext(ctx context.Context, c Conn, keysAndArgs ...interface{}) (interface{}, error) {\n\tcwt, ok := c.(ConnWithContext)\n\tif !ok {\n\t\treturn nil, errContextNotSupported\n\t}\n\tv, err := cwt.DoContext(ctx, \"EVALSHA\", s.args(s.hash, keysAndArgs)...)\n\tif e, ok := err.(Error); ok && strings.HasPrefix(string(e), \"NOSCRIPT \") {\n\t\tv, err = cwt.DoContext(ctx, \"EVAL\", s.args(s.src, keysAndArgs)...)\n\t}\n\treturn v, err\n}\n\n// Do evaluates the script. Under the covers, Do optimistically evaluates the\n// script using the EVALSHA command. If the command fails because the script is\n// not loaded, then Do evaluates the script using the EVAL command (thus\n// causing the script to load).\nfunc (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {\n\tv, err := c.Do(\"EVALSHA\", s.args(s.hash, keysAndArgs)...)\n\tif err != nil && strings.HasPrefix(err.Error(), \"NOSCRIPT \") {\n\t\tv, err = c.Do(\"EVAL\", s.args(s.src, keysAndArgs)...)\n\t}\n\treturn v, err\n}\n\n// SendHash evaluates the script without waiting for the reply. The script is\n// evaluated with the EVALSHA command. The application must ensure that the\n// script is loaded by a previous call to Send, Do or Load methods.\nfunc (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {\n\treturn c.Send(\"EVALSHA\", s.args(s.hash, keysAndArgs)...)\n}\n\n// Send evaluates the script without waiting for the reply.\nfunc (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {\n\treturn c.Send(\"EVAL\", s.args(s.src, keysAndArgs)...)\n}\n\n// Load loads the script without evaluating it.\nfunc (s *Script) Load(c Conn) error {\n\t_, err := c.Do(\"SCRIPT\", \"LOAD\", s.src)\n\treturn err\n}\n"
  },
  {
    "path": "redis/script_test.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n)\n\nvar (\n\t// These variables are declared at package level to remove distracting\n\t// details from the examples.\n\tc     redis.Conn\n\treply interface{}\n\terr   error\n)\n\nfunc ExampleScript() {\n\t// Initialize a package-level variable with a script.\n\tvar getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`)\n\n\t// In a function, use the script Do method to evaluate the script. The Do\n\t// method optimistically uses the EVALSHA command. If the script is not\n\t// loaded, then the Do method falls back to the EVAL command.\n\treply, err = getScript.Do(c, \"foo\")\n}\n\nfunc TestScript(t *testing.T) {\n\tc, err := redis.DialDefaultServer()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\t// To test fall back in Do, we make script unique by adding comment with current time.\n\tscript := fmt.Sprintf(\"--%d\\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\", time.Now().UnixNano())\n\ts := redis.NewScript(2, script)\n\treply := []interface{}{[]byte(\"key1\"), []byte(\"key2\"), []byte(\"arg1\"), []byte(\"arg2\")}\n\n\tv, err := s.Do(c, \"key1\", \"key2\", \"arg1\", \"arg2\")\n\tif err != nil {\n\t\tt.Errorf(\"s.Do(c, ...) returned %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(v, reply) {\n\t\tt.Errorf(\"s.Do(c, ..); = %v, want %v\", v, reply)\n\t}\n\n\terr = s.Load(c)\n\tif err != nil {\n\t\tt.Errorf(\"s.Load(c) returned %v\", err)\n\t}\n\n\terr = s.SendHash(c, \"key1\", \"key2\", \"arg1\", \"arg2\")\n\tif err != nil {\n\t\tt.Errorf(\"s.SendHash(c, ...) returned %v\", err)\n\t}\n\n\terr = c.Flush()\n\tif err != nil {\n\t\tt.Errorf(\"c.Flush() returned %v\", err)\n\t}\n\n\tv, err = c.Receive()\n\tif err != nil {\n\t\tt.Errorf(\"s.Recieve() returned %v\", err)\n\t}\n\tif !reflect.DeepEqual(v, reply) {\n\t\tt.Errorf(\"s.SendHash(c, ..); c.Receive() = %v, want %v\", v, reply)\n\t}\n\n\terr = s.Send(c, \"key1\", \"key2\", \"arg1\", \"arg2\")\n\tif err != nil {\n\t\tt.Errorf(\"s.Send(c, ...) returned %v\", err)\n\t}\n\n\terr = c.Flush()\n\tif err != nil {\n\t\tt.Errorf(\"c.Flush() returned %v\", err)\n\t}\n\n\tv, err = c.Receive()\n\tif err != nil {\n\t\tt.Errorf(\"s.Recieve() returned %v\", err)\n\t}\n\tif !reflect.DeepEqual(v, reply) {\n\t\tt.Errorf(\"s.Send(c, ..); c.Receive() = %v, want %v\", v, reply)\n\t}\n\n}\n"
  },
  {
    "path": "redis/test_test.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc SetNowFunc(f func() time.Time) {\n\tnowFunc = f\n}\n\nvar (\n\tErrNegativeInt = errNegativeInt\n\n\tserverPath     = flag.String(\"redis-server\", \"redis-server\", \"Path to redis server binary\")\n\tserverAddress  = flag.String(\"redis-address\", \"127.0.0.1\", \"The address of the server\")\n\tserverBasePort = flag.Int(\"redis-port\", 16379, \"Beginning of port range for test servers\")\n\tserverLogName  = flag.String(\"redis-log\", \"\", \"Write Redis server logs to `filename`\")\n\tserverLog      = ioutil.Discard\n\n\tdefaultServerMu  sync.Mutex\n\tdefaultServer    *Server\n\tdefaultServerErr error\n)\n\ntype Server struct {\n\tname string\n\tcmd  *exec.Cmd\n\tdone chan struct{}\n}\n\ntype version struct {\n\tmajor int\n\tminor int\n\tpatch int\n}\n\nfunc redisServerVersion() (*version, error) {\n\tout, err := exec.Command(*serverPath, \"--version\").Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"server version: %w\", err)\n\t}\n\n\tver := string(out)\n\tre := regexp.MustCompile(`v=(\\d+)\\.(\\d+)\\.(\\d+)`)\n\tmatch := re.FindStringSubmatch(ver)\n\tif len(match) != 4 {\n\t\treturn nil, fmt.Errorf(\"no server version found in %q\", ver)\n\t}\n\n\tvar v version\n\tif v.major, err = strconv.Atoi(match[1]); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid major version %q\", match[1])\n\t}\n\n\tif v.minor, err = strconv.Atoi(match[2]); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid minor version %q\", match[2])\n\t}\n\n\tif v.patch, err = strconv.Atoi(match[3]); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid patch version %q\", match[3])\n\t}\n\n\treturn &v, nil\n}\n\nfunc NewServer(name string, args ...string) (*Server, error) {\n\tversion, err := redisServerVersion()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif version.major >= 7 {\n\t\targs = append(args, \"--enable-debug-command\", \"local\")\n\t}\n\n\ts := &Server{\n\t\tname: name,\n\t\tcmd:  exec.Command(*serverPath, args...),\n\t\tdone: make(chan struct{}),\n\t}\n\n\tr, err := s.cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = s.cmd.Start()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tready := make(chan error, 1)\n\tgo s.watch(r, ready)\n\n\tselect {\n\tcase err = <-ready:\n\tcase <-time.After(time.Second * 10):\n\t\terr = errors.New(\"timeout waiting for server to start\")\n\t}\n\n\tif err != nil {\n\t\ts.Stop()\n\t\treturn nil, err\n\t}\n\n\treturn s, nil\n}\n\nfunc (s *Server) watch(r io.Reader, ready chan error) {\n\tfmt.Fprintf(serverLog, \"%d START %s \\n\", s.cmd.Process.Pid, s.name)\n\tvar listening bool\n\tvar text string\n\tscn := bufio.NewScanner(r)\n\tfor scn.Scan() {\n\t\ttext = scn.Text()\n\t\tfmt.Fprintf(serverLog, \"%s\\n\", text)\n\t\tif !listening {\n\t\t\tif strings.Contains(text, \" * Ready to accept connections\") ||\n\t\t\t\tstrings.Contains(text, \" * The server is now ready to accept connections on port\") {\n\t\t\t\tlistening = true\n\t\t\t\tready <- nil\n\t\t\t}\n\t\t}\n\t}\n\tif !listening {\n\t\tready <- fmt.Errorf(\"server exited: %s\", text)\n\t}\n\tif err := s.cmd.Wait(); err != nil {\n\t\tif listening {\n\t\t\tready <- err\n\t\t}\n\t}\n\tfmt.Fprintf(serverLog, \"%d STOP %s \\n\", s.cmd.Process.Pid, s.name)\n\tclose(s.done)\n}\n\nfunc (s *Server) Stop() {\n\ts.cmd.Process.Signal(os.Interrupt) // nolint: errcheck\n\t<-s.done\n}\n\n// stopDefaultServer stops the server created by DialDefaultServer.\nfunc stopDefaultServer() {\n\tdefaultServerMu.Lock()\n\tdefer defaultServerMu.Unlock()\n\tif defaultServer != nil {\n\t\tdefaultServer.Stop()\n\t\tdefaultServer = nil\n\t}\n}\n\n// DefaultServerAddr starts the test server if not already started and returns\n// the address of that server.\nfunc DefaultServerAddr() (string, error) {\n\tdefaultServerMu.Lock()\n\tdefer defaultServerMu.Unlock()\n\taddr := fmt.Sprintf(\"%v:%d\", *serverAddress, *serverBasePort)\n\tif defaultServer != nil || defaultServerErr != nil {\n\t\treturn addr, defaultServerErr\n\t}\n\tdefaultServer, defaultServerErr = NewServer(\n\t\t\"default\",\n\t\t\"--port\", strconv.Itoa(*serverBasePort),\n\t\t\"--bind\", *serverAddress,\n\t\t\"--save\", \"\",\n\t\t\"--appendonly\", \"no\")\n\treturn addr, defaultServerErr\n}\n\n// DialDefaultServer starts the test server if not already started and dials a\n// connection to the server.\nfunc DialDefaultServer(options ...DialOption) (Conn, error) {\n\treturn DialDefaultServerContext(context.Background(), options...)\n}\n\n// DialDefaultServerContext starts the test server if not already started and\n// dials a connection to the server with the given context.\nfunc DialDefaultServerContext(ctx context.Context, options ...DialOption) (Conn, error) {\n\taddr, err := DefaultServerAddr()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc, err := DialContext(ctx, \"tcp\", addr, append([]DialOption{DialReadTimeout(1 * time.Second), DialWriteTimeout(1 * time.Second)}, options...)...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err = DoContext(c, ctx, \"FLUSHDB\"); err != nil {\n\t\treturn nil, err\n\t}\n\treturn c, nil\n}\n\nfunc TestMain(m *testing.M) {\n\tos.Exit(func() int {\n\t\tflag.Parse()\n\n\t\tvar f *os.File\n\t\tif *serverLogName != \"\" {\n\t\t\tvar err error\n\t\t\tf, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Error opening redis-log: %v\\n\", err)\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\tserverLog = f\n\t\t}\n\n\t\tdefer stopDefaultServer()\n\n\t\treturn m.Run()\n\t}())\n}\n"
  },
  {
    "path": "redis/zpop_example_test.go",
    "content": "// Copyright 2013 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redis_test\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gomodule/redigo/redis\"\n)\n\n// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.\nfunc zpop(c redis.Conn, key string) (result string, err error) {\n\n\tdefer func() {\n\t\t// Return connection to normal state on error.\n\t\tif err != nil {\n\t\t\tc.Do(\"DISCARD\") // nolint: errcheck\n\t\t}\n\t}()\n\n\t// Loop until transaction is successful.\n\tfor {\n\t\tif _, err := c.Do(\"WATCH\", key); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tmembers, err := redis.Strings(c.Do(\"ZRANGE\", key, 0, 0))\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif len(members) != 1 {\n\t\t\treturn \"\", redis.ErrNil\n\t\t}\n\n\t\tif err = c.Send(\"MULTI\"); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif err = c.Send(\"ZREM\", key, members[0]); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tqueued, err := c.Do(\"EXEC\")\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif queued != nil {\n\t\t\tresult = members[0]\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// zpopScript pops a value from a ZSET.\nvar zpopScript = redis.NewScript(1, `\n    local r = redis.call('ZRANGE', KEYS[1], 0, 0)\n    if r ~= nil then\n        r = r[1]\n        redis.call('ZREM', KEYS[1], r)\n    end\n    return r\n`)\n\n// This example implements ZPOP as described at\n// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.\nfunc Example_zpop() {\n\tc, err := dial()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer c.Close()\n\n\t// Add test data using a pipeline.\n\n\tfor i, member := range []string{\"red\", \"blue\", \"green\"} {\n\t\tif err = c.Send(\"ZADD\", \"zset\", i, member); err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif _, err := c.Do(\"\"); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\t// Pop using WATCH/MULTI/EXEC\n\n\tv, err := zpop(c, \"zset\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Println(v)\n\n\t// Pop using a script.\n\n\tv, err = redis.String(zpopScript.Do(c, \"zset\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Println(v)\n\n\t// Output:\n\t// red\n\t// blue\n}\n"
  },
  {
    "path": "redisx/commandinfo.go",
    "content": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redisx\n\nimport (\n\t\"strings\"\n)\n\ntype commandInfo struct {\n\tnotMuxable bool\n}\n\nvar commandInfos = map[string]commandInfo{\n\t\"WATCH\":      {notMuxable: true},\n\t\"UNWATCH\":    {notMuxable: true},\n\t\"MULTI\":      {notMuxable: true},\n\t\"EXEC\":       {notMuxable: true},\n\t\"DISCARD\":    {notMuxable: true},\n\t\"PSUBSCRIBE\": {notMuxable: true},\n\t\"SUBSCRIBE\":  {notMuxable: true},\n\t\"MONITOR\":    {notMuxable: true},\n}\n\nfunc init() {\n\tfor n, ci := range commandInfos {\n\t\tcommandInfos[strings.ToLower(n)] = ci\n\t}\n}\n\nfunc lookupCommandInfo(commandName string) commandInfo {\n\tif ci, ok := commandInfos[commandName]; ok {\n\t\treturn ci\n\t}\n\treturn commandInfos[strings.ToUpper(commandName)]\n}\n"
  },
  {
    "path": "redisx/commandinfo_test.go",
    "content": "package redisx\n\nimport \"testing\"\n\nfunc TestLookupCommandInfo(t *testing.T) {\n\tfor _, n := range []string{\"watch\", \"WATCH\", \"wAtch\"} {\n\t\tif lookupCommandInfo(n) == (commandInfo{}) {\n\t\t\tt.Errorf(\"LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value\", n)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "redisx/connmux.go",
    "content": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redisx\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/gomodule/redigo/redis\"\n)\n\n// ConnMux multiplexes one or more connections to a single underlying\n// connection. The ConnMux connections do not support concurrency, commands\n// that associate server side state with the connection or commands that put\n// the connection in a special mode.\ntype ConnMux struct {\n\tc redis.Conn\n\n\tsendMu sync.Mutex\n\tsendID uint\n\n\trecvMu   sync.Mutex\n\trecvID   uint\n\trecvWait map[uint]chan struct{}\n}\n\nfunc NewConnMux(c redis.Conn) *ConnMux {\n\treturn &ConnMux{c: c, recvWait: make(map[uint]chan struct{})}\n}\n\n// Get gets a connection. The application must close the returned connection.\nfunc (p *ConnMux) Get() redis.Conn {\n\tc := &muxConn{p: p}\n\tc.ids = c.buf[:0]\n\treturn c\n}\n\n// Close closes the underlying connection.\nfunc (p *ConnMux) Close() error {\n\treturn p.c.Close()\n}\n\ntype muxConn struct {\n\tp   *ConnMux\n\tids []uint\n\tbuf [8]uint\n}\n\nfunc (c *muxConn) send(flush bool, cmd string, args ...interface{}) error {\n\tif lookupCommandInfo(cmd).notMuxable {\n\t\treturn errors.New(\"command not supported by mux pool\")\n\t}\n\tp := c.p\n\tp.sendMu.Lock()\n\tid := p.sendID\n\tc.ids = append(c.ids, id)\n\tp.sendID++\n\terr := p.c.Send(cmd, args...)\n\tif flush {\n\t\terr = p.c.Flush()\n\t}\n\tp.sendMu.Unlock()\n\treturn err\n}\n\nfunc (c *muxConn) Send(cmd string, args ...interface{}) error {\n\treturn c.send(false, cmd, args...)\n}\n\nfunc (c *muxConn) Flush() error {\n\tp := c.p\n\tp.sendMu.Lock()\n\terr := p.c.Flush()\n\tp.sendMu.Unlock()\n\treturn err\n}\n\nfunc (c *muxConn) Receive() (interface{}, error) {\n\tif len(c.ids) == 0 {\n\t\treturn nil, errors.New(\"mux pool underflow\")\n\t}\n\n\tid := c.ids[0]\n\tc.ids = c.ids[1:]\n\tif len(c.ids) == 0 {\n\t\tc.ids = c.buf[:0]\n\t}\n\n\tp := c.p\n\tp.recvMu.Lock()\n\tif p.recvID != id {\n\t\tch := make(chan struct{})\n\t\tp.recvWait[id] = ch\n\t\tp.recvMu.Unlock()\n\t\t<-ch\n\t\tp.recvMu.Lock()\n\t\tif p.recvID != id {\n\t\t\tpanic(\"out of sync\")\n\t\t}\n\t}\n\n\tv, err := p.c.Receive()\n\n\tid++\n\tp.recvID = id\n\tch, ok := p.recvWait[id]\n\tif ok {\n\t\tdelete(p.recvWait, id)\n\t}\n\tp.recvMu.Unlock()\n\tif ok {\n\t\tch <- struct{}{}\n\t}\n\n\treturn v, err\n}\n\nfunc (c *muxConn) Close() error {\n\tvar err error\n\tif len(c.ids) == 0 {\n\t\treturn nil\n\t}\n\tc.Flush()\n\tfor range c.ids {\n\t\t_, err = c.Receive()\n\t}\n\treturn err\n}\n\nfunc (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, error) {\n\tif err := c.send(true, cmd, args...); err != nil {\n\t\treturn nil, err\n\t}\n\treturn c.Receive()\n}\n\nfunc (c *muxConn) Err() error {\n\treturn c.p.c.Err()\n}\n"
  },
  {
    "path": "redisx/connmux_test.go",
    "content": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\npackage redisx_test\n\nimport (\n\t\"net/textproto\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/gomodule/redigo/redis\"\n\t\"github.com/gomodule/redigo/redisx\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConnMux(t *testing.T) {\n\tc, err := redisx.DialTest()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tm := redisx.NewConnMux(c)\n\tdefer m.Close()\n\n\tc1 := m.Get()\n\tc2 := m.Get()\n\terr = c1.Send(\"ECHO\", \"hello\")\n\trequire.NoError(t, err)\n\terr = c2.Send(\"ECHO\", \"world\")\n\trequire.NoError(t, err)\n\tc1.Flush()\n\tc2.Flush()\n\ts, err := redis.String(c1.Receive())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif s != \"hello\" {\n\t\tt.Fatalf(\"echo returned %q, want %q\", s, \"hello\")\n\t}\n\ts, err = redis.String(c2.Receive())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif s != \"world\" {\n\t\tt.Fatalf(\"echo returned %q, want %q\", s, \"world\")\n\t}\n\tc1.Close()\n\tc2.Close()\n}\n\nfunc TestConnMuxClose(t *testing.T) {\n\tc, err := redisx.DialTest()\n\tif err != nil {\n\t\tt.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tm := redisx.NewConnMux(c)\n\tdefer m.Close()\n\n\tc1 := m.Get()\n\tc2 := m.Get()\n\n\tif err := c1.Send(\"ECHO\", \"hello\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := c1.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := c2.Send(\"ECHO\", \"world\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := c2.Flush(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ts, err := redis.String(c2.Receive())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif s != \"world\" {\n\t\tt.Fatalf(\"echo returned %q, want %q\", s, \"world\")\n\t}\n\tc2.Close()\n}\n\nfunc BenchmarkConn(b *testing.B) {\n\tb.StopTimer()\n\tc, err := redisx.DialTest()\n\tif err != nil {\n\t\tb.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\tb.StartTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tif _, err := c.Do(\"PING\"); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkConnMux(b *testing.B) {\n\tb.StopTimer()\n\tc, err := redisx.DialTest()\n\tif err != nil {\n\t\tb.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tm := redisx.NewConnMux(c)\n\tdefer m.Close()\n\n\tb.StartTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tc := m.Get()\n\t\tif _, err := c.Do(\"PING\"); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tc.Close()\n\t}\n}\n\nfunc BenchmarkPool(b *testing.B) {\n\tb.StopTimer()\n\n\tp := redis.Pool{Dial: redisx.DialTest, MaxIdle: 1}\n\tdefer p.Close()\n\n\t// Fill the pool.\n\tc := p.Get()\n\tif err := c.Err(); err != nil {\n\t\tb.Fatal(err)\n\t}\n\tc.Close()\n\n\tb.StartTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tc := p.Get()\n\t\tif _, err := c.Do(\"PING\"); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tc.Close()\n\t}\n}\n\nconst numConcurrent = 10\n\nfunc BenchmarkConnMuxConcurrent(b *testing.B) {\n\tb.StopTimer()\n\tc, err := redisx.DialTest()\n\tif err != nil {\n\t\tb.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\tm := redisx.NewConnMux(c)\n\n\tvar wg sync.WaitGroup\n\twg.Add(numConcurrent)\n\n\tb.StartTimer()\n\n\tfor i := 0; i < numConcurrent; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tc := m.Get()\n\t\t\t\t_, err := c.Do(\"PING\")\n\t\t\t\trequire.NoError(b, err)\n\t\t\t\tc.Close()\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc BenchmarkPoolConcurrent(b *testing.B) {\n\tb.StopTimer()\n\n\tp := redis.Pool{Dial: redisx.DialTest, MaxIdle: numConcurrent}\n\tdefer p.Close()\n\n\t// Fill the pool.\n\tconns := make([]redis.Conn, numConcurrent)\n\tfor i := range conns {\n\t\tc := p.Get()\n\t\tif err := c.Err(); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tconns[i] = c\n\t}\n\tfor _, c := range conns {\n\t\tc.Close()\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(numConcurrent)\n\n\tb.StartTimer()\n\n\tfor i := 0; i < numConcurrent; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tc := p.Get()\n\t\t\t\t_, err := c.Do(\"PING\")\n\t\t\t\trequire.NoError(b, err)\n\t\t\t\tc.Close()\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc BenchmarkPipelineConcurrency(b *testing.B) {\n\tb.StopTimer()\n\tc, err := redisx.DialTest()\n\tif err != nil {\n\t\tb.Fatalf(\"error connection to database, %v\", err)\n\t}\n\tdefer c.Close()\n\n\tvar wg sync.WaitGroup\n\twg.Add(numConcurrent)\n\n\tvar pipeline textproto.Pipeline\n\n\tb.StartTimer()\n\n\tfor i := 0; i < numConcurrent; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tid := pipeline.Next()\n\t\t\t\tpipeline.StartRequest(id)\n\t\t\t\terr = c.Send(\"PING\")\n\t\t\t\trequire.NoError(b, err)\n\t\t\t\tc.Flush()\n\t\t\t\tpipeline.EndRequest(id)\n\t\t\t\tpipeline.StartResponse(id)\n\t\t\t\t_, err := c.Receive()\n\t\t\t\trequire.NoError(b, err)\n\t\t\t\tpipeline.EndResponse(id)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n}\n"
  },
  {
    "path": "redisx/db_test.go",
    "content": "// Copyright 2014 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\n// Package redistest contains utilities for writing Redigo tests.\npackage redisx\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/gomodule/redigo/redis\"\n)\n\ntype testConn struct {\n\tredis.Conn\n}\n\nfunc (t testConn) Close() error {\n\t_, err := t.Conn.Do(\"SELECT\", \"9\")\n\tif err != nil {\n\t\treturn nil\n\t}\n\t_, err = t.Conn.Do(\"FLUSHDB\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn t.Conn.Close()\n}\n\n// Dial dials the local Redis server and selects database 9. To prevent\n// stomping on real data, DialTestDB fails if database 9 contains data. The\n// returned connection flushes database 9 on close.\nfunc DialTest() (redis.Conn, error) {\n\tc, err := redis.Dial(\"tcp\", \":6379\",\n\t\tredis.DialReadTimeout(time.Second),\n\t\tredis.DialWriteTimeout(time.Second),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = c.Do(\"SELECT\", \"9\")\n\tif err != nil {\n\t\tc.Close()\n\t\treturn nil, err\n\t}\n\n\tn, err := redis.Int(c.Do(\"DBSIZE\"))\n\tif err != nil {\n\t\tc.Close()\n\t\treturn nil, err\n\t}\n\n\tif n != 0 {\n\t\tc.Close()\n\t\treturn nil, errors.New(\"database #9 is not empty, test can not continue\")\n\t}\n\n\treturn testConn{c}, nil\n}\n"
  },
  {
    "path": "redisx/doc.go",
    "content": "// Copyright 2012 Gary Burd\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\"): you may\n// not use this file except in compliance with the License. You may obtain\n// a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n// License for the specific language governing permissions and limitations\n// under the License.\n\n// Package redisx contains experimental features for Redigo. Features in this\n// package may be modified or deleted at any time.\npackage redisx\n"
  }
]