pax_global_header00006660000000000000000000000064134737127470014531gustar00rootroot0000000000000052 comment=9101933c84d11f08794466ad0903467958e014ba redis-rb-4.1.2/000077500000000000000000000000001347371274700132445ustar00rootroot00000000000000redis-rb-4.1.2/.gitignore000066400000000000000000000003211347371274700152300ustar00rootroot00000000000000*.rdb *.swp Gemfile.lock *.gem /tmp/ /.idea /.yardoc /.bundle /coverage/* /doc/ /examples/sentinel/sentinel.conf /nohup.out /pkg/* /rdsrv /redis/* /test/db /test/test.conf appendonly.aof temp-rewriteaof-*.aof redis-rb-4.1.2/.travis.yml000066400000000000000000000017271347371274700153640ustar00rootroot00000000000000language: ruby cache: directories: - tmp/cache before_install: - gem update --system 3.0.3 - gem --version script: make rvm: - 2.3.8 - 2.4.5 - 2.5.3 - jruby-9.1.17.0 sudo: false before_script: - if (ruby -e "exit RUBY_VERSION.to_f >= 2.4"); then export RUBYOPT="--enable-frozen-string-literal"; fi; echo $RUBYOPT env: global: - VERBOSE=true - TIMEOUT=30 - LOW_TIMEOUT=0.01 matrix: - DRIVER=ruby REDIS_BRANCH=5.0 - DRIVER=hiredis REDIS_BRANCH=5.0 - DRIVER=synchrony REDIS_BRANCH=5.0 branches: only: - staging - trying - master matrix: exclude: - rvm: jruby-9.1.17.0 include: - rvm: jruby-9.1.17.0 env: DRIVER=ruby REDIS_BRANCH=5.0 LOW_TIMEOUT=0.3 - rvm: 2.3.8 env: DRIVER=ruby REDIS_BRANCH=3.0 - rvm: 2.3.8 env: DRIVER=ruby REDIS_BRANCH=3.2 - rvm: 2.3.8 env: DRIVER=ruby REDIS_BRANCH=4.0 notifications: irc: - irc.freenode.net#redis-rb email: false redis-rb-4.1.2/.yardopts000066400000000000000000000001041347371274700151050ustar00rootroot00000000000000--exclude redis/connection --exclude redis/compat --markup markdown redis-rb-4.1.2/CHANGELOG.md000066400000000000000000000311621347371274700150600ustar00rootroot00000000000000# Unreleased # 4.1.2 * Fix the client hanging forever when connecting with SSL to a non-SSL server. See #835. * Fix several authentication problems with sentinel. See #850 and #856. * Explicitly drop Ruby 2.2 support. # 4.1.1 * Fix error handling in multi blocks. See #754. * Fix geoadd to accept arrays like georadius and georadiusbymember. See #841. * Fix georadius command failing when long == lat. See #841. * Fix timeout error in xread block: 0. See #837. * Fix incompatibility issue with redis-objects. See #834. * Properly handle Errno::EADDRNOTAVAIL on connect. * Fix password authentication to sentinel instances. See #813. # 4.1.0 * Add Redis Cluster support. See #716. * Add streams support. See #799 and #811. * Add ZPOP* support. See #812. * Fix issues with integer-like objects as BPOP timeout # 4.0.3 * Fix raising command error for first command in pipeline. See #788. * Fix the gemspec to stop exposing a `build` executable. See #785. * Add `:reconnect_delay` and `:reconnect_delay_max` options. See #778. # 4.0.2 * Added `Redis#unlink`. See #766. * `Redis.new` now accept a custom connector via `:connector`. See #591. * `Redis#multi` no longer perform empty transactions. See #747. * `Redis#hdel` now accepts hash keys as multiple arguments like `#del`. See #755. * Allow to skip SSL verification. See #745. * Add Geo commands: `geoadd`, `geohash`, `georadius`, `georadiusbymember`, `geopos`, `geodist`. See #730. # 4.0.1 * `Redis::Distributed` now supports `mget` and `mapped_mget`. See #687. * `Redis::Distributed` now supports `sscan` and `sscan_each`. See #572. * `Redis#connection` returns a hash with connection information. You shouldn't need to call `Redis#_client`, ever. * `Redis#flushdb` and `Redis#flushall` now support the `:async` option. See #706. # 4.0 * Removed `Redis.connect`. Use `Redis.new`. * Removed `Redis#[]` and `Redis#[]=` aliases. * Added support for `CLIENT` commands. The lower-level client can be accessed via `Redis#_client`. * Dropped official support for Ruby < 2.2.2. # 3.3.5 * Fixed Ruby 1.8 compatibility after backporting `Redis#connection`. See #719. # 3.3.4 (yanked) * `Redis#connection` returns a hash with connection information. You shouldn't need to call `Redis#_client`, ever. # 3.3.3 * Improved timeout handling after dropping Timeout module. # 3.3.2 * Added support for `SPOP` with COUNT. See #628. * Fixed connection glitches when using SSL. See #644. # 3.3.1 * Remove usage of Timeout::timeout, refactor into using low level non-blocking writes. This fixes a memory leak due to Timeout creating threads on each invocation. # 3.3.0 * Added support for SSL/TLS. Redis doesn't support SSL natively, so you still need to run a terminating proxy on Redis' side. See #496. * Added `read_timeout` and `write_timeout` options. See #437, #482. * Added support for pub/sub with timeouts. See #329. * Added `Redis#call`, `Redis#queue` and `Redis#commit` as a more minimal API to the client. * Deprecated `Redis#disconnect!` in favor of `Redis#close`. # 3.2.2 * Added support for `ZADD` options `NX`, `XX`, `CH`, `INCR`. See #547. * Added support for sentinel commands. See #556. * New `:id` option allows you to identify the client against Redis. See #510. * `Redis::Distributed` will raise when adding two nodes with the same ID. See #354. # 3.2.1 * Added support for `PUBSUB` command. * More low-level socket errors are now raised as `CannotConnectError`. * Added `:connect_timeout` option. * Added support for `:limit` option for `ZREVRANGEBYLEX`. * Fixed an issue where connections become inconsistent when using Ruby's Timeout module outside of the client (see #501, #502). * Added `Redis#disconnect!` as a public-API way of disconnecting the client (without needing to use `QUIT`). See #506. * Fixed Sentinel support with Hiredis. * Fixed Sentinel support when using authentication and databases. * Improved resilience when trying to contact sentinels. # 3.2.0 * Redis Sentinel support. # 3.1.0 * Added debug log sanitization (#428). * Added support for HyperLogLog commands (Redis 2.8.9, #432). * Added support for `BITPOS` command (Redis 2.9.11, #412). * The client will now automatically reconnect after a fork (#414). * If you want to disable the fork-safety check and prefer to share the connection across child processes, you can now pass the `inherit_socket` option (#409). * If you want the client to attempt to reconnect more than once, you can now pass the `reconnect_attempts` option (#347) # 3.0.7 * Added method `Redis#dup` to duplicate a Redis connection. * IPv6 support. # 3.0.6 * Added support for `SCAN` and variants. # 3.0.5 * Fix calling #select from a pipeline (#309). * Added method `Redis#connected?`. * Added support for `MIGRATE` (Redis 2.6). * Support extended SET command (#343, thanks to @benubois). # 3.0.4 * Ensure #watch without a block returns "OK" (#332). * Make futures identifiable (#330). * Fix an issue preventing STORE in a SORT with multiple GETs (#328). # 3.0.3 * Blocking list commands (`BLPOP`, `BRPOP`, `BRPOPLPUSH`) use a socket timeout equal to the sum of the command's timeout and the Redis client's timeout, instead of disabling socket timeout altogether. * Ruby 2.0 compatibility. * Added support for `DUMP` and `RESTORE` (Redis 2.6). * Added support for `BITCOUNT` and `BITOP` (Redis 2.6). * Call `#to_s` on value argument for `SET`, `SETEX`, `PSETEX`, `GETSET`, `SETNX`, and `SETRANGE`. # 3.0.2 * Unescape CGI escaped password in URL. * Fix test to check availability of `UNIXSocket`. * Fix handling of score = +/- infinity for sorted set commands. * Replace array splats with concatenation where possible. * Raise if `EXEC` returns an error. * Passing a nil value in options hash no longer overwrites the default. * Allow string keys in options hash passed to `Redis.new` or `Redis.connect`. * Fix uncaught error triggering unrelated error (synchrony driver). See f7ffd5f1a628029691084de69e5b46699bb8b96d and #248. # 3.0.1 * Fix reconnect logic not kicking in on a write error. See 427dbd52928af452f35aa0a57b621bee56cdcb18 and #238. # 3.0.0 ### Upgrading from 2.x to 3.0 The following items are the most important changes to review when upgrading from redis-rb 2.x. A full list of changes can be found below. * The methods for the following commands have changed the arguments they take, their return value, or both. * `BLPOP`, `BRPOP`, `BRPOPLPUSH` * `SORT` * `MSETNX` * `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`, `ZREVRANGEBYSCORE` * `ZINCRBY`, `ZSCORE` * The return value from `#pipelined` and `#multi` no longer contains unprocessed replies, but the same replies that would be returned if the command had not been executed in these blocks. * The client raises custom errors on connection errors, instead of `RuntimeError` and errors in the `Errno` family. ### Changes * Added support for scripting commands (Redis 2.6). Scripts can be executed using `#eval` and `#evalsha`. Both can commands can either take two arrays to specify `KEYS` and `ARGV`, or take a hash containing `:keys` and `:argv` to specify `KEYS` and `ARGV`. ```ruby redis.eval("return ARGV[1] * ARGV[2]", :argv => [2, 3]) # => 6 ``` Subcommands of the `SCRIPT` command can be executed via the `#script` method. For example: ```ruby redis.script(:load, "return ARGV[1] * ARGV[2]") # => "58db5d365a1922f32e7aa717722141ea9c2b0cf3" redis.script(:exists, "58db5d365a1922f32e7aa717722141ea9c2b0cf3") # => true redis.script(:flush) # => "OK" ``` * The repository now lives at [https://github.com/redis/redis-rb](https://github.com/redis/redis-rb). Thanks, Ezra! * Added support for `PEXPIRE`, `PEXPIREAT`, `PTTL`, `PSETEX`, `INCRYBYFLOAT`, `HINCRYBYFLOAT` and `TIME` (Redis 2.6). * `Redis.current` is now thread unsafe, because the client itself is thread safe. In the future you'll be able to do something like: ```ruby Redis.current = Redis::Pool.connect ``` This makes `Redis.current` actually usable in multi-threaded environments, while not affecting those running a single thread. * Change API for `BLPOP`, `BRPOP` and `BRPOPLPUSH`. Both `BLPOP` and `BRPOP` now take a single argument equal to a string key, or an array with string keys, followed by an optional hash with a `:timeout` key. When not specified, the timeout defaults to `0` to not time out. ```ruby redis.blpop(["list1", "list2"], :timeout => 1.0) ``` `BRPOPLPUSH` also takes an optional hash with a `:timeout` key as last argument for consistency. When not specified, the timeout defaults to `0` to not time out. ```ruby redis.brpoplpush("some_list", "another_list", :timeout => 1.0) ``` * When `SORT` is passed multiple key patterns to get via the `:get` option, it now returns an array per result element, holding all `GET` substitutions. * The `MSETNX` command now returns a boolean. * The `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE` and `ZREVRANGEBYSCORE` commands now return an array containing `[String, Float]` pairs when `:with_scores => true` is passed. For example: ```ruby redis.zrange("zset", 0, -1, :with_scores => true) # => [["foo", 1.0], ["bar", 2.0]] ``` * The `ZINCRBY` and `ZSCORE` commands now return a `Float` score instead of a string holding a representation of the score. * The client now raises custom exceptions where it makes sense. If by any chance you were rescuing low-level exceptions (`Errno::*`), you should now rescue as follows: Errno::ECONNRESET -> Redis::ConnectionError Errno::EPIPE -> Redis::ConnectionError Errno::ECONNABORTED -> Redis::ConnectionError Errno::EBADF -> Redis::ConnectionError Errno::EINVAL -> Redis::ConnectionError Errno::EAGAIN -> Redis::TimeoutError Errno::ECONNREFUSED -> Redis::CannotConnectError * Always raise exceptions originating from erroneous command invocation inside pipelines and MULTI/EXEC blocks. The old behavior (swallowing exceptions) could cause application bugs to go unnoticed. * Implement futures for assigning values inside pipelines and MULTI/EXEC blocks. Futures are assigned their value after the pipeline or MULTI/EXEC block has executed. ```ruby $redis.pipelined do @future = $redis.get "key" end puts @future.value ``` * Ruby 1.8.6 is officially not supported. * Support `ZCOUNT` in `Redis::Distributed` (Michael Dungan). * Pipelined commands now return the same replies as when called outside a pipeline. In the past, pipelined replies were returned without post-processing. * Support `SLOWLOG` command (Michael Bernstein). * Calling `SHUTDOWN` effectively disconnects the client (Stefan Kaes). * Basic support for mapping commands so that they can be renamed on the server. * Connecting using a URL now checks that a host is given. It's just a small sanity check, cf. #126 * Support variadic commands introduced in Redis 2.4. # 2.2.2 * Added method `Redis::Distributed#hsetnx`. # 2.2.1 * Internal API: Client#call and family are now called with a single array argument, since splatting a large number of arguments (100K+) results in a stack overflow on 1.9.2. * The `INFO` command can optionally take a subcommand. When the subcommand is `COMMANDSTATS`, the client will properly format the returned statistics per command. Subcommands for `INFO` are available since Redis v2.3.0 (unstable). * Change `IO#syswrite` back to the buffered `IO#write` since some Rubies do short writes for large (1MB+) buffers and some don't (see issue #108). # 2.2.0 * Added method `Redis#without_reconnect` that ensures the client will not try to reconnect when running the code inside the specified block. * Thread-safe by default. Thread safety can be explicitly disabled by passing `:thread_safe => false` as argument. * Commands called inside a MULTI/EXEC no longer raise error replies, since a successful EXEC means the commands inside the block were executed. * MULTI/EXEC blocks are pipelined. * Don't disconnect on error replies. * Use `IO#syswrite` instead of `IO#write` because write buffering is not necessary. * Connect to a unix socket by passing the `:path` option as argument. * The timeout value is coerced into a float, allowing sub-second timeouts. * Accept both `:with_scores` _and_ `:withscores` as argument to sorted set commands. * Use [hiredis](https://github.com/pietern/hiredis-rb) (v0.3 or higher) by requiring "redis/connection/hiredis". * Use [em-synchrony](https://github.com/igrigorik/em-synchrony) by requiring "redis/connection/synchrony". # 2.1.1 See commit log. redis-rb-4.1.2/Gemfile000066400000000000000000000003421347371274700145360ustar00rootroot00000000000000source 'https://rubygems.org' gemspec gem 'rake' gem 'minitest' # Using jruby-openssl 0.10.0, we get NPEs in jruby tests: https://github.com/redis/redis-rb/issues/756 platform :jruby do gem 'jruby-openssl', '<0.10.0' end redis-rb-4.1.2/LICENSE000066400000000000000000000020441347371274700142510ustar00rootroot00000000000000Copyright (c) 2009 Ezra Zygmuntowicz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.redis-rb-4.1.2/README.md000066400000000000000000000331511347371274700145260ustar00rootroot00000000000000# redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link] A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still providing an idiomatic interface. ## Getting started Install with: ``` $ gem install redis ``` You can connect to Redis by instantiating the `Redis` class: ```ruby require "redis" redis = Redis.new ``` This assumes Redis was started with a default configuration, and is listening on `localhost`, port 6379. If you need to connect to a remote server or a different port, try: ```ruby redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15) ``` You can also specify connection options as a [`redis://` URL][redis-url]: ```ruby redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15") ``` By default, the client will try to read the `REDIS_URL` environment variable and use that as URL to connect to. The above statement is therefore equivalent to setting this environment variable and calling `Redis.new` without arguments. To connect to Redis listening on a Unix socket, try: ```ruby redis = Redis.new(path: "/tmp/redis.sock") ``` To connect to a password protected Redis instance, use: ```ruby redis = Redis.new(password: "mysecret") ``` The Redis class exports methods that are named identical to the commands they execute. The arguments these methods accept are often identical to the arguments specified on the [Redis website][redis-commands]. For instance, the `SET` and `GET` commands can be called like this: ```ruby redis.set("mykey", "hello world") # => "OK" redis.get("mykey") # => "hello world" ``` All commands, their arguments, and return values are documented and available on [RubyDoc.info][rubydoc]. ## Sentinel support The client is able to perform automatic failover by using [Redis Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+ if you want to use this feature. To connect using Sentinel, use: ```ruby SENTINELS = [{ host: "127.0.0.1", port: 26380 }, { host: "127.0.0.1", port: 26381 }] redis = Redis.new(url: "redis://mymaster", sentinels: SENTINELS, role: :master) ``` * The master name identifies a group of Redis instances composed of a master and one or more slaves (`mymaster` in the example). * It is possible to optionally provide a role. The allowed roles are `master` and `slave`. When the role is `slave`, the client will try to connect to a random slave of the specified master. If a role is not specified, the client will connect to the master. * When using the Sentinel support you need to specify a list of sentinels to connect to. The list does not need to enumerate all your Sentinel instances, but a few so that if one is down the client will try the next one. The client is able to remember the last Sentinel that was able to reply correctly and will use it for the next requests. If you want to [authenticate](https://redis.io/topics/sentinel#configuring-sentinel-instances-with-authentication) Sentinel itself, you must specify the `password` option per instance. ```ruby SENTINELS = [{ host: '127.0.0.1', port: 26380, password: 'mysecret' }, { host: '127.0.0.1', port: 26381, password: 'mysecret' }] redis = Redis.new(host: 'mymaster', sentinels: SENTINELS, role: :master) ``` ## Cluster support `redis-rb` supports [clustering](https://redis.io/topics/cluster-spec). ```ruby # Nodes can be passed to the client as an array of connection URLs. nodes = (7000..7005).map { |port| "redis://127.0.0.1:#{port}" } redis = Redis.new(cluster: nodes) # You can also specify the options as a Hash. The options are the same as for a single server connection. (7000..7005).map { |port| { host: '127.0.0.1', port: port } } ``` You can also specify only a subset of the nodes, and the client will discover the missing ones using the [CLUSTER NODES](https://redis.io/commands/cluster-nodes) command. ```ruby Redis.new(cluster: %w[redis://127.0.0.1:7000]) ``` If you want [the connection to be able to read from any replica](https://redis.io/commands/readonly), you must pass the `replica: true`. Note that this connection won't be usable to write keys. ```ruby Redis.new(cluster: nodes, replica: true) ``` The calling code is responsible for [avoiding cross slot commands](https://redis.io/topics/cluster-spec#keys-distribution-model). ```ruby redis = Redis.new(cluster: %w[redis://127.0.0.1:7000]) redis.mget('key1', 'key2') #=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot) redis.mget('{key}1', '{key}2') #=> [nil, nil] ``` * The client automatically reconnects after a failover occurred, but the caller is responsible for handling errors while it is happening. * The client supports `MOVED` and `ASK` redirections transparently. ## Storing objects Redis only stores strings as values. If you want to store an object, you can use a serialization mechanism such as JSON: ```ruby require "json" redis.set "foo", [1, 2, 3].to_json # => OK JSON.parse(redis.get("foo")) # => [1, 2, 3] ``` ## Pipelining When multiple commands are executed sequentially, but are not dependent, the calls can be *pipelined*. This means that the client doesn't wait for reply of the first command before sending the next command. The advantage is that multiple commands are sent at once, resulting in faster overall execution. The client can be instructed to pipeline commands by using the `#pipelined` method. After the block is executed, the client sends all commands to Redis and gathers their replies. These replies are returned by the `#pipelined` method. ```ruby redis.pipelined do redis.set "foo", "bar" redis.incr "baz" end # => ["OK", 1] ``` ### Executing commands atomically You can use `MULTI/EXEC` to run a number of commands in an atomic fashion. This is similar to executing a pipeline, but the commands are preceded by a call to `MULTI`, and followed by a call to `EXEC`. Like the regular pipeline, the replies to the commands are returned by the `#multi` method. ```ruby redis.multi do redis.set "foo", "bar" redis.incr "baz" end # => ["OK", 1] ``` ### Futures Replies to commands in a pipeline can be accessed via the *futures* they emit (since redis-rb 3.0). All calls inside a pipeline block return a `Future` object, which responds to the `#value` method. When the pipeline has successfully executed, all futures are assigned their respective replies and can be used. ```ruby redis.pipelined do @set = redis.set "foo", "bar" @incr = redis.incr "baz" end @set.value # => "OK" @incr.value # => 1 ``` ## Error Handling In general, if something goes wrong you'll get an exception. For example, if it can't connect to the server a `Redis::CannotConnectError` error will be raised. ```ruby begin redis.ping rescue StandardError => e e.inspect # => # e.message # => Timed out connecting to Redis on 10.0.1.1:6380 end ``` See lib/redis/errors.rb for information about what exceptions are possible. ## Timeouts The client allows you to configure connect, read, and write timeouts. Passing a single `timeout` option will set all three values: ```ruby Redis.new(:timeout => 1) ``` But you can use specific values for each of them: ```ruby Redis.new( :connect_timeout => 0.2, :read_timeout => 1.0, :write_timeout => 0.5 ) ``` All timeout values are specified in seconds. When using pub/sub, you can subscribe to a channel using a timeout as well: ```ruby redis.subscribe_with_timeout(5, "news") do |on| on.message do |channel, message| # ... end end ``` If no message is received after 5 seconds, the client will unsubscribe. ## Reconnections The client allows you to configure how many `reconnect_attempts` it should complete before declaring a connection as failed. Furthermore, you may want to control the maximum duration between reconnection attempts with `reconnect_delay` and `reconnect_delay_max`. ```ruby Redis.new( :reconnect_attempts => 10, :reconnect_delay => 1.5, :reconnect_delay_max => 10.0, ) ``` The delay values are specified in seconds. With the above configuration, the client would attempt 10 reconnections, exponentially increasing the duration between each attempt but it never waits longer than `reconnect_delay_max`. This is the retry algorithm: ```ruby attempt_wait_time = [(reconnect_delay * 2**(attempt-1)), reconnect_delay_max].min ``` **By default**, this gem will only **retry a connection once** and then fail, but with the above configuration the reconnection attempt would look like this: #|Attempt wait time|Total wait time :-:|:-:|:-: 1|1.5s|1.5s 2|3.0s|4.5s 3|6.0s|10.5s 4|10.0s|20.5s 5|10.0s|30.5s 6|10.0s|40.5s 7|10.0s|50.5s 8|10.0s|60.5s 9|10.0s|70.5s 10|10.0s|80.5s So if the reconnection attempt #10 succeeds 70 seconds have elapsed trying to reconnect, this is likely fine in long-running background processes, but if you use Redis to drive your website you might want to have a lower `reconnect_delay_max` or have less `reconnect_attempts`. ## SSL/TLS Support This library supports natively terminating client side SSL/TLS connections when talking to Redis via a server-side proxy such as [stunnel], [hitch], or [ghostunnel]. To enable SSL support, pass the `:ssl => :true` option when configuring the Redis client, or pass in `:url => "rediss://..."` (like HTTPS for Redis). You will also need to pass in an `:ssl_params => { ... }` hash used to configure the `OpenSSL::SSL::SSLContext` object used for the connection: ```ruby redis = Redis.new( :url => "rediss://:p4ssw0rd@10.0.1.1:6381/15", :ssl_params => { :ca_file => "/path/to/ca.crt" } ) ``` The options given to `:ssl_params` are passed directly to the `OpenSSL::SSL::SSLContext#set_params` method and can be any valid attribute of the SSL context. Please see the [OpenSSL::SSL::SSLContext documentation] for all of the available attributes. Here is an example of passing in params that can be used for SSL client certificate authentication (a.k.a. mutual TLS): ```ruby redis = Redis.new( :url => "rediss://:p4ssw0rd@10.0.1.1:6381/15", :ssl_params => { :ca_file => "/path/to/ca.crt", :cert => OpenSSL::X509::Certificate.new(File.read("client.crt")), :key => OpenSSL::PKey::RSA.new(File.read("client.key")) } ) ``` [stunnel]: https://www.stunnel.org/ [hitch]: https://hitch-tls.org/ [ghostunnel]: https://github.com/square/ghostunnel [OpenSSL::SSL::SSLContext documentation]: http://ruby-doc.org/stdlib-2.3.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html *NOTE:* SSL is only supported by the default "Ruby" driver ## Expert-Mode Options - `inherit_socket: true`: disable safety check that prevents a forked child from sharing a socket with its parent; this is potentially useful in order to mitigate connection churn when: - many short-lived forked children of one process need to talk to redis, AND - your own code prevents the parent process from using the redis connection while a child is alive Improper use of `inherit_socket` will result in corrupted and/or incorrect responses. ## Alternate drivers By default, redis-rb uses Ruby's socket library to talk with Redis. To use an alternative connection driver it should be specified as option when instantiating the client object. These instructions are only valid for **redis-rb 3.0**. For instructions on how to use alternate drivers from **redis-rb 2.2**, please refer to an [older README][readme-2.2.2]. [readme-2.2.2]: https://github.com/redis/redis-rb/blob/v2.2.2/README.md ### hiredis The hiredis driver uses the connection facility of hiredis-rb. In turn, hiredis-rb is a binding to the official hiredis client library. It optimizes for speed, at the cost of portability. Because it is a C extension, JRuby is not supported (by default). It is best to use hiredis when you have large replies (for example: `LRANGE`, `SMEMBERS`, `ZRANGE`, etc.) and/or use big pipelines. In your Gemfile, include hiredis: ```ruby gem "redis", "~> 3.0.1" gem "hiredis", "~> 0.4.5" ``` When instantiating the client object, specify hiredis: ```ruby redis = Redis.new(:driver => :hiredis) ``` ### synchrony The synchrony driver adds support for [em-synchrony][em-synchrony]. This makes redis-rb work with EventMachine's asynchronous I/O, while not changing the exposed API. The hiredis gem needs to be available as well, because the synchrony driver uses hiredis for parsing the Redis protocol. [em-synchrony]: https://github.com/igrigorik/em-synchrony In your Gemfile, include em-synchrony and hiredis: ```ruby gem "redis", "~> 3.0.1" gem "hiredis", "~> 0.4.5" gem "em-synchrony" ``` When instantiating the client object, specify synchrony: ```ruby redis = Redis.new(:driver => :synchrony) ``` ## Testing This library is tested against recent Ruby and Redis versions. Check [Travis][travis-link] for the exact versions supported. ## Contributors Several people contributed to redis-rb, but we would like to especially mention Ezra Zygmuntowicz. Ezra introduced the Ruby community to many new cool technologies, like Redis. He wrote the first version of this client and evangelized Redis in Rubyland. Thank you, Ezra. ## Contributing [Fork the project](https://github.com/redis/redis-rb) and send pull requests. You can also ask for help at `#redis-rb` on Freenode. [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg [inchpages-link]: https://inch-ci.org/github/redis/redis-rb [redis-commands]: https://redis.io/commands [redis-home]: https://redis.io [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis [travis-home]: https://travis-ci.org/ [travis-image]: https://secure.travis-ci.org/redis/redis-rb.svg?branch=master [travis-link]: https://travis-ci.org/redis/redis-rb [rubydoc]: http://www.rubydoc.info/gems/redis redis-rb-4.1.2/Rakefile000066400000000000000000000007101347371274700147070ustar00rootroot00000000000000require 'rake/testtask' Rake::TestTask.new 'test' do |t| if ENV['SOCKET_PATH'].nil? sock_file = Dir.glob("#{__dir__}/**/redis.sock").first if sock_file.nil? puts '`SOCKET_PATH` environment variable required' exit 1 end ENV['SOCKET_PATH'] = sock_file end t.libs = %w(lib test) if ARGV.size == 1 t.pattern = 'test/*_test.rb' else t.test_files = ARGV[1..-1] end t.options = '-v' end task default: :test redis-rb-4.1.2/benchmarking/000077500000000000000000000000001347371274700156745ustar00rootroot00000000000000redis-rb-4.1.2/benchmarking/cluster.rb000066400000000000000000000055031347371274700177050ustar00rootroot00000000000000# frozen_string_literal: true # Execute the following commands before execution # # `$ make start` # `$ make start_cluster` # `$ make create_cluster` require 'redis' require 'benchmark' HOST = '127.0.0.1' STANDALONE_PORT = 6381 CLUSTER_PORT = 7000 N = (ARGV.first || 100000).to_i rn = Redis.new(host: HOST, port: STANDALONE_PORT) rc = Redis.new(host: HOST, port: CLUSTER_PORT) rm = Redis.new(cluster: %W[redis://#{HOST}:#{CLUSTER_PORT}]) rs = Redis.new(cluster: %W[redis://#{HOST}:#{CLUSTER_PORT}], replica: true) Benchmark.bmbm do |bm| bm.report('client: normal, server: standalone, command: SET, key: fixed') do N.times { rn.set('foo', '42') } end bm.report('client: normal, server: standalone, command: GET, key: fixed') do N.times { rn.get('foo') } end bm.report('client: normal, server: cluster, command: SET, key: fixed') do N.times { rc.set('bar', '42') } end bm.report('client: normal, server: cluster, command: GET, key: fixed') do N.times { rc.get('bar') } end bm.report('client: cluster, server: cluster, command: SET, key: fixed') do N.times { rm.set('baz', '42') } end rm.wait(1, 0) bm.report('client: cluster, server: cluster, command: GET, key: fixed') do N.times { rm.get('baz') } end bm.report('client: cluster, server: cluster, command: SET, key: fixed, replica: true') do N.times { rs.set('zap', '42') } end rs.wait(1, 0) bm.report('client: cluster, server: cluster, command: GET, key: fixed, replica: true') do N.times { rs.get('zap') } end bm.report('client: normal, server: standalone, command: SET, key: variable') do N.times { |i| rn.set("foo:#{i}", '42') } end bm.report('client: normal, server: standalone, command: GET, key: variable') do N.times { |i| rn.get("foo:#{i}") } end bm.report('client: cluster, server: cluster, command: SET, key: variable') do N.times { |i| rm.set("bar:#{i}", '42') } end rm.wait(1, 0) bm.report('client: cluster, server: cluster, command: GET, key: variable') do N.times { |i| rm.get("bar:#{i}") } end bm.report('client: cluster, server: cluster, command: SET, key: variable, replica: true') do N.times { |i| rs.set("baz:#{i}", '42') } end rs.wait(1, 0) bm.report('client: cluster, server: cluster, command: GET, key: variable, replica: true') do N.times { |i| rs.get("baz:#{i}") } end rn.set('bar', 0) bm.report('client: normal, server: standalone, command: INCR, key: fixed') do N.times { rn.incr('bar') } end rc.set('bar', 0) bm.report('client: normal, server: cluster, command: INCR, key: fixed') do N.times { rc.incr('bar') } end rm.set('bar', 0) bm.report('client: cluster, server: cluster, command: INCR, key: fixed') do N.times { rm.incr('bar') } end end redis-rb-4.1.2/benchmarking/logging.rb000066400000000000000000000024161347371274700176520ustar00rootroot00000000000000# Run with # # $ ruby -Ilib benchmarking/logging.rb # begin require "bench" rescue LoadError $stderr.puts "`gem install bench` and try again." exit 1 end require "redis" require "logger" def log(level, namespace = nil) logger = (namespace || Kernel).const_get(:Logger).new("/dev/null") logger.level = (namespace || Logger).const_get(level) logger end def stress(redis) redis.flushdb n = (ARGV.shift || 2000).to_i n.times do |i| key = "foo:#{i}" redis.set key, i redis.get key end end default = Redis.new logging_redises = [ Redis.new(:logger => log(:DEBUG)), Redis.new(:logger => log(:INFO)), ] begin require "log4r" logging_redises += [ Redis.new(:logger => log(:DEBUG, Log4r)), Redis.new(:logger => log(:INFO, Log4r)), ] rescue LoadError $stderr.puts "Log4r not installed. `gem install log4r` if you want to compare it against Ruby's Logger (spoiler: it's much faster)." end benchmark "Default options (no logger)" do stress(default) end logging_redises.each do |redis| logger = redis._client.logger case logger when Logger level = Logger::SEV_LABEL[logger.level] when Log4r::Logger level = logger.levels[logger.level] end benchmark "#{logger.class} on #{level}" do stress(redis) end end run 10 redis-rb-4.1.2/benchmarking/pipeline.rb000066400000000000000000000015551347371274700200340ustar00rootroot00000000000000require "benchmark" $:.push File.join(File.dirname(__FILE__), 'lib') require 'redis' ITERATIONS = 10000 @r = Redis.new Benchmark.bmbm do |benchmark| benchmark.report("set") do @r.flushdb ITERATIONS.times do |i| @r.set("foo#{i}", "Hello world!") @r.get("foo#{i}") end end benchmark.report("set (pipelined)") do @r.flushdb @r.pipelined do ITERATIONS.times do |i| @r.set("foo#{i}", "Hello world!") @r.get("foo#{i}") end end end benchmark.report("lpush+ltrim") do @r.flushdb ITERATIONS.times do |i| @r.lpush "lpush#{i}", i @r.ltrim "ltrim#{i}", 0, 30 end end benchmark.report("lpush+ltrim (pipelined)") do @r.flushdb @r.pipelined do ITERATIONS.times do |i| @r.lpush "lpush#{i}", i @r.ltrim "ltrim#{i}", 0, 30 end end end end redis-rb-4.1.2/benchmarking/speed.rb000066400000000000000000000004721347371274700173240ustar00rootroot00000000000000# Run with # # $ ruby -Ilib benchmarking/speed.rb # require "benchmark" require "redis" r = Redis.new n = (ARGV.shift || 20000).to_i elapsed = Benchmark.realtime do # n sets, n gets n.times do |i| key = "foo#{i}" r[key] = key * 10 r[key] end end puts '%.2f Kops' % (2 * n / 1000 / elapsed) redis-rb-4.1.2/benchmarking/suite.rb000066400000000000000000000012631347371274700173540ustar00rootroot00000000000000require 'fileutils' def run_in_background(command) fork { system command } end def with_all_segments(&block) 0.upto(9) do |segment_number| block_size = 100000 start_index = segment_number * block_size end_index = start_index + block_size - 1 block.call(start_index, end_index) end end #with_all_segments do |start_index, end_index| # puts "Initializing keys from #{start_index} to #{end_index}" # system "ruby worker.rb initialize #{start_index} #{end_index} 0" #end with_all_segments do |start_index, end_index| run_in_background "ruby worker.rb write #{start_index} #{end_index} 10" run_in_background "ruby worker.rb read #{start_index} #{end_index} 1" end redis-rb-4.1.2/benchmarking/worker.rb000066400000000000000000000026761347371274700175450ustar00rootroot00000000000000BENCHMARK_ROOT = File.dirname(__FILE__) REDIS_ROOT = File.join(BENCHMARK_ROOT, "..", "lib") $: << REDIS_ROOT require 'redis' require 'benchmark' def show_usage puts <<-EOL Usage: worker.rb [read:write] EOL end def shift_from_argv value = ARGV.shift unless value show_usage exit -1 end value end operation = shift_from_argv.to_sym start_index = shift_from_argv.to_i end_index = shift_from_argv.to_i sleep_msec = shift_from_argv.to_i sleep_duration = sleep_msec/1000.0 redis = Redis.new case operation when :initialize start_index.upto(end_index) do |i| redis[i] = 0 end when :clear start_index.upto(end_index) do |i| redis.delete(i) end when :read, :write puts "Starting to #{operation} at segment #{end_index + 1}" loop do t1 = Time.now start_index.upto(end_index) do |i| case operation when :read redis.get(i) when :write redis.incr(i) else raise "Unknown operation: #{operation}" end sleep sleep_duration end t2 = Time.now requests_processed = end_index - start_index time = t2 - t1 puts "#{t2.strftime("%H:%M")} [segment #{end_index + 1}] : Processed #{requests_processed} requests in #{time} seconds - #{(requests_processed/time).round} requests/sec" end else raise "Unknown operation: #{operation}" end redis-rb-4.1.2/bin/000077500000000000000000000000001347371274700140145ustar00rootroot00000000000000redis-rb-4.1.2/bin/build000077500000000000000000000030321347371274700150370ustar00rootroot00000000000000#!/usr/bin/env ruby TARBALL = ARGV[0] require 'digest/sha1' require 'fileutils' class Builder TARBALL_CACHE_EXPIRATION = 60 * 10 def initialize(redis_branch, tmp_dir) @redis_branch = redis_branch @tmp_dir = tmp_dir @build_dir = File.join(@tmp_dir, "cache", "redis-#{redis_branch}") end def run download_tarball_if_needed if old_checkum != checksum build update_checksum end 0 end private def download_tarball_if_needed return if File.exist?(tarball_path) && File.mtime(tarball_path) > Time.now - TARBALL_CACHE_EXPIRATION command!('wget', '-q', tarball_url, '-O', tarball_path) end def tarball_path File.join(@tmp_dir, "redis-#{@redis_branch}.tar.gz") end def tarball_url "https://github.com/antirez/redis/archive/#{@redis_branch}.tar.gz" end def build FileUtils.rm_rf(@build_dir) FileUtils.mkdir_p(@build_dir) command!('tar', 'xf', tarball_path, '-C', File.expand_path('../', @build_dir)) Dir.chdir(@build_dir) do command!('make') end end def update_checksum File.write(checksum_path, checksum) end def old_checkum File.read(checksum_path) rescue Errno::ENOENT nil end def checksum_path File.join(@build_dir, 'build.checksum') end def checksum @checksum ||= Digest::SHA1.file(tarball_path).hexdigest end def command!(*args) puts "$ #{args.join(' ')}" unless system(*args) raise "Command failed with status #{$?.exitstatus}" end end end exit Builder.new(ARGV[0], ARGV[1]).run redis-rb-4.1.2/bin/cluster_creator000077500000000000000000000004101347371274700171350ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require_relative '../test/support/cluster/orchestrator' urls = ARGV.map { |host_port| "redis://#{host_port}" } orchestrator = ClusterOrchestrator.new(urls, timeout: 30.0) orchestrator.rebuild orchestrator.close redis-rb-4.1.2/bin/console000077500000000000000000000001721347371274700154040ustar00rootroot00000000000000#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) require 'redis' require 'irb' IRB.start redis-rb-4.1.2/bors.toml000066400000000000000000000010161347371274700151040ustar00rootroot00000000000000# Gate on Travis CI status = ["continuous-integration/travis-ci/push"] # Set bors's timeout to 6 hours # # bors's timeout should always be twice a long as the test suite takes. # This is to allow Travis to fast-fail a test; if one of the builders # immediately reports a failure, then bors will move on to the next batch, # leaving the slower builders to work through the already-doomed run and # the next one. # # At the time it was written, a run had taken 3 hours. # bors's default timeout is 4 hours. timeout_sec = 14400 redis-rb-4.1.2/examples/000077500000000000000000000000001347371274700150625ustar00rootroot00000000000000redis-rb-4.1.2/examples/basic.rb000066400000000000000000000001741347371274700164720ustar00rootroot00000000000000require 'redis' r = Redis.new r.del('foo') puts p'set foo to "bar"' r['foo'] = 'bar' puts p 'value of foo' p r['foo'] redis-rb-4.1.2/examples/consistency.rb000066400000000000000000000100241347371274700177450ustar00rootroot00000000000000# This file implements a simple consistency test for Redis-rb (or any other # Redis environment if you pass a different client object) where a client # writes to the database using INCR in order to increment keys, but actively # remember the value the key should have. Before every write a read is performed # to check if the value in the database matches the value expected. # # In this way this program can check for lost writes, or acknowledged writes # that were executed. # # Copyright (C) 2013-2014 Salvatore Sanfilippo # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require 'redis' class ConsistencyTester def initialize(redis) @r = redis @working_set = 10000 @keyspace = 100000 @writes = 0 @reads = 0 @failed_writes = 0 @failed_reads = 0 @lost_writes = 0 @not_ack_writes = 0 @delay = 0 @cached = {} # We take our view of data stored in the DB. @prefix = [Process.pid.to_s,Time.now.usec,@r.object_id,""].join("|") @errtime = {} end def genkey # Write more often to a small subset of keys ks = rand() > 0.5 ? @keyspace : @working_set @prefix+"key_"+rand(ks).to_s end def check_consistency(key,value) expected = @cached[key] return if !expected # We lack info about previous state. if expected > value @lost_writes += expected-value elsif expected < value @not_ack_writes += value-expected end end def puterr(msg) if !@errtime[msg] || Time.now.to_i != @errtime[msg] puts msg end @errtime[msg] = Time.now.to_i end def test last_report = Time.now.to_i while true # Read key = genkey begin val = @r.get(key) check_consistency(key,val.to_i) @reads += 1 rescue => e puterr "Reading: #{e.class}: #{e.message} (#{e.backtrace.first})" @failed_reads += 1 end # Write begin @cached[key] = @r.incr(key).to_i @writes += 1 rescue => e puterr "Writing: #{e.class}: #{e.message} (#{e.backtrace.first})" @failed_writes += 1 end # Report sleep @delay if Time.now.to_i != last_report report = "#{@reads} R (#{@failed_reads} err) | " + "#{@writes} W (#{@failed_writes} err) | " report += "#{@lost_writes} lost | " if @lost_writes > 0 report += "#{@not_ack_writes} noack | " if @not_ack_writes > 0 last_report = Time.now.to_i puts report end end end end Sentinels = [{:host => "127.0.0.1", :port => 26379}, {:host => "127.0.0.1", :port => 26380}] r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master) tester = ConsistencyTester.new(r) tester.test redis-rb-4.1.2/examples/dist_redis.rb000066400000000000000000000014631347371274700175440ustar00rootroot00000000000000require "redis" require "redis/distributed" r = Redis::Distributed.new %w[redis://localhost:6379 redis://localhost:6380 redis://localhost:6381 redis://localhost:6382] r.flushdb r['urmom'] = 'urmom' r['urdad'] = 'urdad' r['urmom1'] = 'urmom1' r['urdad1'] = 'urdad1' r['urmom2'] = 'urmom2' r['urdad2'] = 'urdad2' r['urmom3'] = 'urmom3' r['urdad3'] = 'urdad3' p r['urmom'] p r['urdad'] p r['urmom1'] p r['urdad1'] p r['urmom2'] p r['urdad2'] p r['urmom3'] p r['urdad3'] r.rpush 'listor', 'foo1' r.rpush 'listor', 'foo2' r.rpush 'listor', 'foo3' r.rpush 'listor', 'foo4' r.rpush 'listor', 'foo5' p r.rpop('listor') p r.rpop('listor') p r.rpop('listor') p r.rpop('listor') p r.rpop('listor') puts "key distribution:" r.ring.nodes.each do |node| p [node.client(:getname), node.keys("*")] end r.flushdb p r.keys('*') redis-rb-4.1.2/examples/incr-decr.rb000066400000000000000000000003061347371274700172540ustar00rootroot00000000000000require 'redis' r = Redis.new puts p 'incr' r.del 'counter' p r.incr('counter') p r.incr('counter') p r.incr('counter') puts p 'decr' p r.decr('counter') p r.decr('counter') p r.decr('counter') redis-rb-4.1.2/examples/list.rb000066400000000000000000000006731347371274700163700ustar00rootroot00000000000000require 'rubygems' require 'redis' r = Redis.new r.del 'logs' puts p "pushing log messages into a LIST" r.rpush 'logs', 'some log message' r.rpush 'logs', 'another log message' r.rpush 'logs', 'yet another log message' r.rpush 'logs', 'also another log message' puts p 'contents of logs LIST' p r.lrange('logs', 0, -1) puts p 'Trim logs LIST to last 2 elements(easy circular buffer)' r.ltrim('logs', -2, -1) p r.lrange('logs', 0, -1) redis-rb-4.1.2/examples/pubsub.rb000066400000000000000000000014661347371274700167160ustar00rootroot00000000000000require "redis" puts <<-EOS To play with this example use redis-cli from another terminal, like this: $ redis-cli publish one hello Finally force the example to exit sending the 'exit' message with: $ redis-cli publish two exit EOS redis = Redis.new trap(:INT) { puts; exit } begin redis.subscribe(:one, :two) do |on| on.subscribe do |channel, subscriptions| puts "Subscribed to ##{channel} (#{subscriptions} subscriptions)" end on.message do |channel, message| puts "##{channel}: #{message}" redis.unsubscribe if message == "exit" end on.unsubscribe do |channel, subscriptions| puts "Unsubscribed from ##{channel} (#{subscriptions} subscriptions)" end end rescue Redis::BaseConnectionError => error puts "#{error}, retrying in 1s" sleep 1 retry end redis-rb-4.1.2/examples/sentinel.rb000066400000000000000000000017771347371274700172440ustar00rootroot00000000000000require 'redis' # This example creates a master-slave setup with a sentinel, then connects to # it and sends write commands in a loop. # # After 30 seconds, the master dies. You will be able to see how a new master # is elected and things continue to work as if nothing happened. # # To run this example: # # $ ruby -I./lib examples/sentinel.rb # at_exit do begin Process.kill(:INT, $redises) rescue Errno::ESRCH end Process.waitall end $redises = spawn("examples/sentinel/start") Sentinels = [{:host => "127.0.0.1", :port => 26379}, {:host => "127.0.0.1", :port => 26380}] r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master) # Set keys into a loop. # # The example traps errors so that you can actually try to failover while # running the script to see redis-rb reconfiguring. (0..1000000).each{|i| begin r.set(i,i) $stdout.write("SET (#{i} times)\n") if i % 100 == 0 rescue => e $stdout.write("E") end sleep(0.01) } redis-rb-4.1.2/examples/sentinel/000077500000000000000000000000001347371274700167035ustar00rootroot00000000000000redis-rb-4.1.2/examples/sentinel/sentinel.conf000066400000000000000000000005051347371274700213730ustar00rootroot00000000000000sentinel monitor master1 127.0.0.1 6380 2 sentinel down-after-milliseconds master1 5000 sentinel failover-timeout master1 15000 sentinel parallel-syncs master1 1 sentinel monitor master2 127.0.0.1 6381 2 sentinel down-after-milliseconds master2 5000 sentinel failover-timeout master2 15000 sentinel parallel-syncs master2 1 redis-rb-4.1.2/examples/sentinel/start000077500000000000000000000025641347371274700177750ustar00rootroot00000000000000#! /usr/bin/env ruby # This is a helper script used together with examples/sentinel.rb # It runs two Redis masters, two slaves for each of them, and two sentinels. # After 30 seconds, the first master dies. # # You don't need to run this script yourself. Rather, use examples/sentinel.rb. require "fileutils" $pids = [] at_exit do $pids.each do |pid| begin Process.kill(:INT, pid) rescue Errno::ESRCH end end Process.waitall end base = File.expand_path(File.dirname(__FILE__)) # Masters $pids << spawn("redis-server --port 6380 --loglevel warning") $pids << spawn("redis-server --port 6381 --loglevel warning") # Slaves of Master 1 $pids << spawn("redis-server --port 63800 --slaveof 127.0.0.1 6380 --loglevel warning") $pids << spawn("redis-server --port 63801 --slaveof 127.0.0.1 6380 --loglevel warning") # Slaves of Master 2 $pids << spawn("redis-server --port 63810 --slaveof 127.0.0.1 6381 --loglevel warning") $pids << spawn("redis-server --port 63811 --slaveof 127.0.0.1 6381 --loglevel warning") FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel1.conf") FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel2.conf") # Sentinels $pids << spawn("redis-server tmp/sentinel1.conf --sentinel --port 26379") $pids << spawn("redis-server tmp/sentinel2.conf --sentinel --port 26380") sleep 30 Process.kill(:KILL, $pids[0]) Process.waitall redis-rb-4.1.2/examples/sets.rb000066400000000000000000000007671347371274700163770ustar00rootroot00000000000000require 'rubygems' require 'redis' r = Redis.new r.del 'foo-tags' r.del 'bar-tags' puts p "create a set of tags on foo-tags" r.sadd 'foo-tags', 'one' r.sadd 'foo-tags', 'two' r.sadd 'foo-tags', 'three' puts p "create a set of tags on bar-tags" r.sadd 'bar-tags', 'three' r.sadd 'bar-tags', 'four' r.sadd 'bar-tags', 'five' puts p 'foo-tags' p r.smembers('foo-tags') puts p 'bar-tags' p r.smembers('bar-tags') puts p 'intersection of foo-tags and bar-tags' p r.sinter('foo-tags', 'bar-tags') redis-rb-4.1.2/examples/unicorn/000077500000000000000000000000001347371274700165375ustar00rootroot00000000000000redis-rb-4.1.2/examples/unicorn/config.ru000066400000000000000000000001321347371274700203500ustar00rootroot00000000000000run lambda { |env| [200, {"Content-Type" => "text/plain"}, [Redis.current.randomkey]] } redis-rb-4.1.2/examples/unicorn/unicorn.rb000066400000000000000000000010171347371274700205400ustar00rootroot00000000000000require "redis" worker_processes 3 # If you set the connection to Redis *before* forking, # you will cause forks to share a file descriptor. # # This causes a concurrency problem by which one fork # can read or write to the socket while others are # performing other operations. # # Most likely you'll be getting ProtocolError exceptions # mentioning a wrong initial byte in the reply. # # Thus we need to connect to Redis after forking the # worker processes. after_fork do |server, worker| Redis.current.disconnect! end redis-rb-4.1.2/lib/000077500000000000000000000000001347371274700140125ustar00rootroot00000000000000redis-rb-4.1.2/lib/redis.rb000066400000000000000000003140301347371274700154460ustar00rootroot00000000000000# frozen_string_literal: true require "monitor" require_relative "redis/errors" class Redis def self.current @current ||= Redis.new end def self.current=(redis) @current = redis end include MonitorMixin # Create a new client instance # # @param [Hash] options # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection: `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket connection: `unix://[path to Redis socket]`. This overrides all other options. # @option options [String] :host ("127.0.0.1") server hostname # @option options [Fixnum] :port (6379) server port # @option options [String] :path path to server socket (overrides host and port) # @option options [Float] :timeout (5.0) timeout in seconds # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds # @option options [String] :password Password to authenticate against server # @option options [Fixnum] :db (0) Database to select after initial connect # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony` # @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME` # @option options [Hash, Fixnum] :tcp_keepalive Keepalive values, if Fixnum `intvl` and `probe` are calculated based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Fixnum # @option options [Fixnum] :reconnect_attempts Number of attempts trying to connect # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not # @option options [Array] :sentinels List of sentinels to contact # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave` # @option options [Array String, Integer}>] :cluster List of cluster nodes to contact # @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not # @option options [Class] :connector Class of custom connector # # @return [Redis] a new client instance def initialize(options = {}) @options = options.dup @cluster_mode = options.key?(:cluster) client = @cluster_mode ? Cluster : Client @original_client = @client = client.new(options) @queue = Hash.new { |h, k| h[k] = [] } super() # Monitor#initialize end def synchronize mon_synchronize { yield(@client) } end # Run code with the client reconnecting def with_reconnect(val=true, &blk) synchronize do |client| client.with_reconnect(val, &blk) end end # Run code without the client reconnecting def without_reconnect(&blk) with_reconnect(false, &blk) end # Test whether or not the client is connected def connected? @original_client.connected? end # Disconnect the client as quickly and silently as possible. def close @original_client.disconnect end alias disconnect! close # Sends a command to Redis and returns its reply. # # Replies are converted to Ruby objects according to the RESP protocol, so # you can expect a Ruby array, integer or nil when Redis sends one. Higher # level transformations, such as converting an array of pairs into a Ruby # hash, are up to consumers. # # Redis error replies are raised as Ruby exceptions. def call(*command) synchronize do |client| client.call(command) end end # Queues a command for pipelining. # # Commands in the queue are executed with the Redis#commit method. # # See http://redis.io/topics/pipelining for more details. # def queue(*command) @queue[Thread.current.object_id] << command end # Sends all commands in the queue. # # See http://redis.io/topics/pipelining for more details. # def commit synchronize do |client| begin pipeline = Pipeline.new(client) @queue[Thread.current.object_id].each do |command| pipeline.call(command) end client.call_pipelined(pipeline) ensure @queue.delete(Thread.current.object_id) end end end def _client @client end # Authenticate to the server. # # @param [String] password must match the password specified in the # `requirepass` directive in the configuration file # @return [String] `OK` def auth(password) synchronize do |client| client.call([:auth, password]) end end # Change the selected database for the current connection. # # @param [Fixnum] db zero-based index of the DB to use (0 to 15) # @return [String] `OK` def select(db) synchronize do |client| client.db = db client.call([:select, db]) end end # Ping the server. # # @param [optional, String] message # @return [String] `PONG` def ping(message = nil) synchronize do |client| client.call([:ping, message].compact) end end # Echo the given string. # # @param [String] value # @return [String] def echo(value) synchronize do |client| client.call([:echo, value]) end end # Close the connection. # # @return [String] `OK` def quit synchronize do |client| begin client.call([:quit]) rescue ConnectionError ensure client.disconnect end end end # Asynchronously rewrite the append-only file. # # @return [String] `OK` def bgrewriteaof synchronize do |client| client.call([:bgrewriteaof]) end end # Asynchronously save the dataset to disk. # # @return [String] `OK` def bgsave synchronize do |client| client.call([:bgsave]) end end # Get or set server configuration parameters. # # @param [Symbol] action e.g. `:get`, `:set`, `:resetstat` # @return [String, Hash] string reply, or hash when retrieving more than one # property with `CONFIG GET` def config(action, *args) synchronize do |client| client.call([:config, action] + args) do |reply| if reply.kind_of?(Array) && action == :get Hashify.call(reply) else reply end end end end # Manage client connections. # # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname` # @return [String, Hash] depends on subcommand def client(subcommand = nil, *args) synchronize do |client| client.call([:client, subcommand] + args) do |reply| if subcommand.to_s == "list" reply.lines.map do |line| entries = line.chomp.split(/[ =]/) Hash[entries.each_slice(2).to_a] end else reply end end end end # Return the number of keys in the selected database. # # @return [Fixnum] def dbsize synchronize do |client| client.call([:dbsize]) end end def debug(*args) synchronize do |client| client.call([:debug] + args) end end # Remove all keys from all databases. # # @param [Hash] options # - `:async => Boolean`: async flush (default: false) # @return [String] `OK` def flushall(options = nil) synchronize do |client| if options && options[:async] client.call([:flushall, :async]) else client.call([:flushall]) end end end # Remove all keys from the current database. # # @param [Hash] options # - `:async => Boolean`: async flush (default: false) # @return [String] `OK` def flushdb(options = nil) synchronize do |client| if options && options[:async] client.call([:flushdb, :async]) else client.call([:flushdb]) end end end # Get information and statistics about the server. # # @param [String, Symbol] cmd e.g. "commandstats" # @return [Hash] def info(cmd = nil) synchronize do |client| client.call([:info, cmd].compact) do |reply| if reply.kind_of?(String) reply = HashifyInfo.call(reply) if cmd && cmd.to_s == "commandstats" # Extract nested hashes for INFO COMMANDSTATS reply = Hash[reply.map do |k, v| v = v.split(",").map { |e| e.split("=") } [k[/^cmdstat_(.*)$/, 1], Hash[v]] end] end end reply end end end # Get the UNIX time stamp of the last successful save to disk. # # @return [Fixnum] def lastsave synchronize do |client| client.call([:lastsave]) end end # Listen for all requests received by the server in real time. # # There is no way to interrupt this command. # # @yield a block to be called for every line of output # @yieldparam [String] line timestamp and command that was executed def monitor(&block) synchronize do |client| client.call_loop([:monitor], &block) end end # Synchronously save the dataset to disk. # # @return [String] def save synchronize do |client| client.call([:save]) end end # Synchronously save the dataset to disk and then shut down the server. def shutdown synchronize do |client| client.with_reconnect(false) do begin client.call([:shutdown]) rescue ConnectionError # This means Redis has probably exited. nil end end end end # Make the server a slave of another instance, or promote it as master. def slaveof(host, port) synchronize do |client| client.call([:slaveof, host, port]) end end # Interact with the slowlog (get, len, reset) # # @param [String] subcommand e.g. `get`, `len`, `reset` # @param [Fixnum] length maximum number of entries to return # @return [Array, Fixnum, String] depends on subcommand def slowlog(subcommand, length=nil) synchronize do |client| args = [:slowlog, subcommand] args << length if length client.call args end end # Internal command used for replication. def sync synchronize do |client| client.call([:sync]) end end # Return the server time. # # @example # r.time # => [ 1333093196, 606806 ] # # @return [Array] tuple of seconds since UNIX epoch and # microseconds in the current second def time synchronize do |client| client.call([:time]) do |reply| reply.map(&:to_i) if reply end end end # Remove the expiration from a key. # # @param [String] key # @return [Boolean] whether the timeout was removed or not def persist(key) synchronize do |client| client.call([:persist, key], &Boolify) end end # Set a key's time to live in seconds. # # @param [String] key # @param [Fixnum] seconds time to live # @return [Boolean] whether the timeout was set or not def expire(key, seconds) synchronize do |client| client.call([:expire, key, seconds], &Boolify) end end # Set the expiration for a key as a UNIX timestamp. # # @param [String] key # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp # @return [Boolean] whether the timeout was set or not def expireat(key, unix_time) synchronize do |client| client.call([:expireat, key, unix_time], &Boolify) end end # Get the time to live (in seconds) for a key. # # @param [String] key # @return [Fixnum] remaining time to live in seconds. # # In Redis 2.6 or older the command returns -1 if the key does not exist or if # the key exist but has no associated expire. # # Starting with Redis 2.8 the return value in case of error changed: # # - The command returns -2 if the key does not exist. # - The command returns -1 if the key exists but has no associated expire. def ttl(key) synchronize do |client| client.call([:ttl, key]) end end # Set a key's time to live in milliseconds. # # @param [String] key # @param [Fixnum] milliseconds time to live # @return [Boolean] whether the timeout was set or not def pexpire(key, milliseconds) synchronize do |client| client.call([:pexpire, key, milliseconds], &Boolify) end end # Set the expiration for a key as number of milliseconds from UNIX Epoch. # # @param [String] key # @param [Fixnum] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch. # @return [Boolean] whether the timeout was set or not def pexpireat(key, ms_unix_time) synchronize do |client| client.call([:pexpireat, key, ms_unix_time], &Boolify) end end # Get the time to live (in milliseconds) for a key. # # @param [String] key # @return [Fixnum] remaining time to live in milliseconds # In Redis 2.6 or older the command returns -1 if the key does not exist or if # the key exist but has no associated expire. # # Starting with Redis 2.8 the return value in case of error changed: # # - The command returns -2 if the key does not exist. # - The command returns -1 if the key exists but has no associated expire. def pttl(key) synchronize do |client| client.call([:pttl, key]) end end # Return a serialized version of the value stored at a key. # # @param [String] key # @return [String] serialized_value def dump(key) synchronize do |client| client.call([:dump, key]) end end # Create a key using the serialized value, previously obtained using DUMP. # # @param [String] key # @param [String] ttl # @param [String] serialized_value # @param [Hash] options # - `:replace => Boolean`: if false, raises an error if key already exists # @raise [Redis::CommandError] # @return [String] `"OK"` def restore(key, ttl, serialized_value, options = {}) args = [:restore, key, ttl, serialized_value] args << 'REPLACE' if options[:replace] synchronize do |client| client.call(args) end end # Transfer a key from the connected instance to another instance. # # @param [String, Array] key # @param [Hash] options # - `:host => String`: host of instance to migrate to # - `:port => Integer`: port of instance to migrate to # - `:db => Integer`: database to migrate to (default: same as source) # - `:timeout => Integer`: timeout (default: same as connection timeout) # - `:copy => Boolean`: Do not remove the key from the local instance. # - `:replace => Boolean`: Replace existing key on the remote instance. # @return [String] `"OK"` def migrate(key, options) args = [:migrate] args << (options[:host] || raise(':host not specified')) args << (options[:port] || raise(':port not specified')) args << (key.is_a?(String) ? key : '') args << (options[:db] || @client.db).to_i args << (options[:timeout] || @client.timeout).to_i args << 'COPY' if options[:copy] args << 'REPLACE' if options[:replace] args += ['KEYS', *key] if key.is_a?(Array) synchronize { |client| client.call(args) } end # Delete one or more keys. # # @param [String, Array] keys # @return [Fixnum] number of keys that were deleted def del(*keys) synchronize do |client| client.call([:del] + keys) end end # Unlink one or more keys. # # @param [String, Array] keys # @return [Fixnum] number of keys that were unlinked def unlink(*keys) synchronize do |client| client.call([:unlink] + keys) end end # Determine if a key exists. # # @param [String] key # @return [Boolean] def exists(key) synchronize do |client| client.call([:exists, key], &Boolify) end end # Find all keys matching the given pattern. # # @param [String] pattern # @return [Array] def keys(pattern = "*") synchronize do |client| client.call([:keys, pattern]) do |reply| if reply.kind_of?(String) reply.split(" ") else reply end end end end # Move a key to another database. # # @example Move a key to another database # redis.set "foo", "bar" # # => "OK" # redis.move "foo", 2 # # => true # redis.exists "foo" # # => false # redis.select 2 # # => "OK" # redis.exists "foo" # # => true # redis.get "foo" # # => "bar" # # @param [String] key # @param [Fixnum] db # @return [Boolean] whether the key was moved or not def move(key, db) synchronize do |client| client.call([:move, key, db], &Boolify) end end def object(*args) synchronize do |client| client.call([:object] + args) end end # Return a random key from the keyspace. # # @return [String] def randomkey synchronize do |client| client.call([:randomkey]) end end # Rename a key. If the new key already exists it is overwritten. # # @param [String] old_name # @param [String] new_name # @return [String] `OK` def rename(old_name, new_name) synchronize do |client| client.call([:rename, old_name, new_name]) end end # Rename a key, only if the new key does not exist. # # @param [String] old_name # @param [String] new_name # @return [Boolean] whether the key was renamed or not def renamenx(old_name, new_name) synchronize do |client| client.call([:renamenx, old_name, new_name], &Boolify) end end # Sort the elements in a list, set or sorted set. # # @example Retrieve the first 2 elements from an alphabetically sorted "list" # redis.sort("list", :order => "alpha", :limit => [0, 2]) # # => ["a", "b"] # @example Store an alphabetically descending list in "target" # redis.sort("list", :order => "desc alpha", :store => "target") # # => 26 # # @param [String] key # @param [Hash] options # - `:by => String`: use external key to sort elements by # - `:limit => [offset, count]`: skip `offset` elements, return a maximum # of `count` elements # - `:get => [String, Array]`: single key or array of keys to # retrieve per element in the result # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA` # - `:store => String`: key to store the result at # # @return [Array, Array>, Fixnum] # - when `:get` is not specified, or holds a single element, an array of elements # - when `:get` is specified, and holds more than one element, an array of # elements where every element is an array with the result for every # element specified in `:get` # - when `:store` is specified, the number of elements in the stored result def sort(key, options = {}) args = [] by = options[:by] args.concat(["BY", by]) if by limit = options[:limit] args.concat(["LIMIT"] + limit) if limit get = Array(options[:get]) args.concat(["GET"].product(get).flatten) unless get.empty? order = options[:order] args.concat(order.split(" ")) if order store = options[:store] args.concat(["STORE", store]) if store synchronize do |client| client.call([:sort, key] + args) do |reply| if get.size > 1 && !store if reply reply.each_slice(get.size).to_a end else reply end end end end # Determine the type stored at key. # # @param [String] key # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none` def type(key) synchronize do |client| client.call([:type, key]) end end # Decrement the integer value of a key by one. # # @example # redis.decr("value") # # => 4 # # @param [String] key # @return [Fixnum] value after decrementing it def decr(key) synchronize do |client| client.call([:decr, key]) end end # Decrement the integer value of a key by the given number. # # @example # redis.decrby("value", 5) # # => 0 # # @param [String] key # @param [Fixnum] decrement # @return [Fixnum] value after decrementing it def decrby(key, decrement) synchronize do |client| client.call([:decrby, key, decrement]) end end # Increment the integer value of a key by one. # # @example # redis.incr("value") # # => 6 # # @param [String] key # @return [Fixnum] value after incrementing it def incr(key) synchronize do |client| client.call([:incr, key]) end end # Increment the integer value of a key by the given integer number. # # @example # redis.incrby("value", 5) # # => 10 # # @param [String] key # @param [Fixnum] increment # @return [Fixnum] value after incrementing it def incrby(key, increment) synchronize do |client| client.call([:incrby, key, increment]) end end # Increment the numeric value of a key by the given float number. # # @example # redis.incrbyfloat("value", 1.23) # # => 1.23 # # @param [String] key # @param [Float] increment # @return [Float] value after incrementing it def incrbyfloat(key, increment) synchronize do |client| client.call([:incrbyfloat, key, increment], &Floatify) end end # Set the string value of a key. # # @param [String] key # @param [String] value # @param [Hash] options # - `:ex => Fixnum`: Set the specified expire time, in seconds. # - `:px => Fixnum`: Set the specified expire time, in milliseconds. # - `:nx => true`: Only set the key if it does not already exist. # - `:xx => true`: Only set the key if it already exist. # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true` def set(key, value, options = {}) args = [] ex = options[:ex] args.concat(["EX", ex]) if ex px = options[:px] args.concat(["PX", px]) if px nx = options[:nx] args.concat(["NX"]) if nx xx = options[:xx] args.concat(["XX"]) if xx synchronize do |client| if nx || xx client.call([:set, key, value.to_s] + args, &BoolifySet) else client.call([:set, key, value.to_s] + args) end end end # Set the time to live in seconds of a key. # # @param [String] key # @param [Fixnum] ttl # @param [String] value # @return [String] `"OK"` def setex(key, ttl, value) synchronize do |client| client.call([:setex, key, ttl, value.to_s]) end end # Set the time to live in milliseconds of a key. # # @param [String] key # @param [Fixnum] ttl # @param [String] value # @return [String] `"OK"` def psetex(key, ttl, value) synchronize do |client| client.call([:psetex, key, ttl, value.to_s]) end end # Set the value of a key, only if the key does not exist. # # @param [String] key # @param [String] value # @return [Boolean] whether the key was set or not def setnx(key, value) synchronize do |client| client.call([:setnx, key, value.to_s], &Boolify) end end # Set one or more values. # # @example # redis.mset("key1", "v1", "key2", "v2") # # => "OK" # # @param [Array] args array of keys and values # @return [String] `"OK"` # # @see #mapped_mset def mset(*args) synchronize do |client| client.call([:mset] + args) end end # Set one or more values. # # @example # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" }) # # => "OK" # # @param [Hash] hash keys mapping to values # @return [String] `"OK"` # # @see #mset def mapped_mset(hash) mset(hash.to_a.flatten) end # Set one or more values, only if none of the keys exist. # # @example # redis.msetnx("key1", "v1", "key2", "v2") # # => true # # @param [Array] args array of keys and values # @return [Boolean] whether or not all values were set # # @see #mapped_msetnx def msetnx(*args) synchronize do |client| client.call([:msetnx] + args, &Boolify) end end # Set one or more values, only if none of the keys exist. # # @example # redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" }) # # => true # # @param [Hash] hash keys mapping to values # @return [Boolean] whether or not all values were set # # @see #msetnx def mapped_msetnx(hash) msetnx(hash.to_a.flatten) end # Get the value of a key. # # @param [String] key # @return [String] def get(key) synchronize do |client| client.call([:get, key]) end end # Get the values of all the given keys. # # @example # redis.mget("key1", "key1") # # => ["v1", "v2"] # # @param [Array] keys # @return [Array] an array of values for the specified keys # # @see #mapped_mget def mget(*keys, &blk) synchronize do |client| client.call([:mget] + keys, &blk) end end # Get the values of all the given keys. # # @example # redis.mapped_mget("key1", "key2") # # => { "key1" => "v1", "key2" => "v2" } # # @param [Array] keys array of keys # @return [Hash] a hash mapping the specified keys to their values # # @see #mget def mapped_mget(*keys) mget(*keys) do |reply| if reply.kind_of?(Array) Hash[keys.zip(reply)] else reply end end end # Overwrite part of a string at key starting at the specified offset. # # @param [String] key # @param [Fixnum] offset byte offset # @param [String] value # @return [Fixnum] length of the string after it was modified def setrange(key, offset, value) synchronize do |client| client.call([:setrange, key, offset, value.to_s]) end end # Get a substring of the string stored at a key. # # @param [String] key # @param [Fixnum] start zero-based start offset # @param [Fixnum] stop zero-based end offset. Use -1 for representing # the end of the string # @return [Fixnum] `0` or `1` def getrange(key, start, stop) synchronize do |client| client.call([:getrange, key, start, stop]) end end # Sets or clears the bit at offset in the string value stored at key. # # @param [String] key # @param [Fixnum] offset bit offset # @param [Fixnum] value bit value `0` or `1` # @return [Fixnum] the original bit value stored at `offset` def setbit(key, offset, value) synchronize do |client| client.call([:setbit, key, offset, value]) end end # Returns the bit value at offset in the string value stored at key. # # @param [String] key # @param [Fixnum] offset bit offset # @return [Fixnum] `0` or `1` def getbit(key, offset) synchronize do |client| client.call([:getbit, key, offset]) end end # Append a value to a key. # # @param [String] key # @param [String] value value to append # @return [Fixnum] length of the string after appending def append(key, value) synchronize do |client| client.call([:append, key, value]) end end # Count the number of set bits in a range of the string value stored at key. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Fixnum] the number of bits set to 1 def bitcount(key, start = 0, stop = -1) synchronize do |client| client.call([:bitcount, key, start, stop]) end end # Perform a bitwise operation between strings and store the resulting string in a key. # # @param [String] operation e.g. `and`, `or`, `xor`, `not` # @param [String] destkey destination key # @param [String, Array] keys one or more source keys to perform `operation` # @return [Fixnum] the length of the string stored in `destkey` def bitop(operation, destkey, *keys) synchronize do |client| client.call([:bitop, operation, destkey] + keys) end end # Return the position of the first bit set to 1 or 0 in a string. # # @param [String] key # @param [Fixnum] bit whether to look for the first 1 or 0 bit # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Fixnum] the position of the first 1/0 bit. # -1 if looking for 1 and it is not found or start and stop are given. def bitpos(key, bit, start=nil, stop=nil) if stop and not start raise(ArgumentError, 'stop parameter specified without start parameter') end synchronize do |client| command = [:bitpos, key, bit] command << start if start command << stop if stop client.call(command) end end # Set the string value of a key and return its old value. # # @param [String] key # @param [String] value value to replace the current value with # @return [String] the old value stored in the key, or `nil` if the key # did not exist def getset(key, value) synchronize do |client| client.call([:getset, key, value.to_s]) end end # Get the length of the value stored in a key. # # @param [String] key # @return [Fixnum] the length of the value stored in the key, or 0 # if the key does not exist def strlen(key) synchronize do |client| client.call([:strlen, key]) end end # Get the length of a list. # # @param [String] key # @return [Fixnum] def llen(key) synchronize do |client| client.call([:llen, key]) end end # Prepend one or more values to a list, creating the list if it doesn't exist # # @param [String] key # @param [String, Array] value string value, or array of string values to push # @return [Fixnum] the length of the list after the push operation def lpush(key, value) synchronize do |client| client.call([:lpush, key, value]) end end # Prepend a value to a list, only if the list exists. # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def lpushx(key, value) synchronize do |client| client.call([:lpushx, key, value]) end end # Append one or more values to a list, creating the list if it doesn't exist # # @param [String] key # @param [String, Array] value string value, or array of string values to push # @return [Fixnum] the length of the list after the push operation def rpush(key, value) synchronize do |client| client.call([:rpush, key, value]) end end # Append a value to a list, only if the list exists. # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def rpushx(key, value) synchronize do |client| client.call([:rpushx, key, value]) end end # Remove and get the first element in a list. # # @param [String] key # @return [String] def lpop(key) synchronize do |client| client.call([:lpop, key]) end end # Remove and get the last element in a list. # # @param [String] key # @return [String] def rpop(key) synchronize do |client| client.call([:rpop, key]) end end # Remove the last element in a list, append it to another list and return it. # # @param [String] source source key # @param [String] destination destination key # @return [nil, String] the element, or nil when the source key does not exist def rpoplpush(source, destination) synchronize do |client| client.call([:rpoplpush, source, destination]) end end def _bpop(cmd, args, &blk) options = {} if args.last.is_a?(Hash) options = args.pop elsif args.last.respond_to?(:to_int) # Issue deprecation notice in obnoxious mode... options[:timeout] = args.pop.to_int end if args.size > 1 # Issue deprecation notice in obnoxious mode... end keys = args.flatten timeout = options[:timeout] || 0 synchronize do |client| command = [cmd, keys, timeout] timeout += client.timeout if timeout > 0 client.call_with_timeout(command, timeout, &blk) end end # Remove and get the first element in a list, or block until one is available. # # @example With timeout # list, element = redis.blpop("list", :timeout => 5) # # => nil on timeout # # => ["list", "element"] on success # @example Without timeout # list, element = redis.blpop("list") # # => ["list", "element"] # @example Blocking pop on multiple lists # list, element = redis.blpop(["list", "another_list"]) # # => ["list", "element"] # # @param [String, Array] keys one or more keys to perform the # blocking pop on # @param [Hash] options # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout # # @return [nil, [String, String]] # - `nil` when the operation timed out # - tuple of the list that was popped from and element was popped otherwise def blpop(*args) _bpop(:blpop, args) end # Remove and get the last element in a list, or block until one is available. # # @param [String, Array] keys one or more keys to perform the # blocking pop on # @param [Hash] options # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout # # @return [nil, [String, String]] # - `nil` when the operation timed out # - tuple of the list that was popped from and element was popped otherwise # # @see #blpop def brpop(*args) _bpop(:brpop, args) end # Pop a value from a list, push it to another list and return it; or block # until one is available. # # @param [String] source source key # @param [String] destination destination key # @param [Hash] options # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout # # @return [nil, String] # - `nil` when the operation timed out # - the element was popped and pushed otherwise def brpoplpush(source, destination, options = {}) case options when Integer # Issue deprecation notice in obnoxious mode... options = { :timeout => options } end timeout = options[:timeout] || 0 synchronize do |client| command = [:brpoplpush, source, destination, timeout] timeout += client.timeout if timeout > 0 client.call_with_timeout(command, timeout) end end # Get an element from a list by its index. # # @param [String] key # @param [Fixnum] index # @return [String] def lindex(key, index) synchronize do |client| client.call([:lindex, key, index]) end end # Insert an element before or after another element in a list. # # @param [String] key # @param [String, Symbol] where `BEFORE` or `AFTER` # @param [String] pivot reference element # @param [String] value # @return [Fixnum] length of the list after the insert operation, or `-1` # when the element `pivot` was not found def linsert(key, where, pivot, value) synchronize do |client| client.call([:linsert, key, where, pivot, value]) end end # Get a range of elements from a list. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Array] def lrange(key, start, stop) synchronize do |client| client.call([:lrange, key, start, stop]) end end # Remove elements from a list. # # @param [String] key # @param [Fixnum] count number of elements to remove. Use a positive # value to remove the first `count` occurrences of `value`. A negative # value to remove the last `count` occurrences of `value`. Or zero, to # remove all occurrences of `value` from the list. # @param [String] value # @return [Fixnum] the number of removed elements def lrem(key, count, value) synchronize do |client| client.call([:lrem, key, count, value]) end end # Set the value of an element in a list by its index. # # @param [String] key # @param [Fixnum] index # @param [String] value # @return [String] `OK` def lset(key, index, value) synchronize do |client| client.call([:lset, key, index, value]) end end # Trim a list to the specified range. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [String] `OK` def ltrim(key, start, stop) synchronize do |client| client.call([:ltrim, key, start, stop]) end end # Get the number of members in a set. # # @param [String] key # @return [Fixnum] def scard(key) synchronize do |client| client.call([:scard, key]) end end # Add one or more members to a set. # # @param [String] key # @param [String, Array] member one member, or array of members # @return [Boolean, Fixnum] `Boolean` when a single member is specified, # holding whether or not adding the member succeeded, or `Fixnum` when an # array of members is specified, holding the number of members that were # successfully added def sadd(key, member) synchronize do |client| client.call([:sadd, key, member]) do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean Boolify.call(reply) end end end end # Remove one or more members from a set. # # @param [String] key # @param [String, Array] member one member, or array of members # @return [Boolean, Fixnum] `Boolean` when a single member is specified, # holding whether or not removing the member succeeded, or `Fixnum` when an # array of members is specified, holding the number of members that were # successfully removed def srem(key, member) synchronize do |client| client.call([:srem, key, member]) do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean Boolify.call(reply) end end end end # Remove and return one or more random member from a set. # # @param [String] key # @return [String] # @param [Fixnum] count def spop(key, count = nil) synchronize do |client| if count.nil? client.call([:spop, key]) else client.call([:spop, key, count]) end end end # Get one or more random members from a set. # # @param [String] key # @param [Fixnum] count # @return [String] def srandmember(key, count = nil) synchronize do |client| if count.nil? client.call([:srandmember, key]) else client.call([:srandmember, key, count]) end end end # Move a member from one set to another. # # @param [String] source source key # @param [String] destination destination key # @param [String] member member to move from `source` to `destination` # @return [Boolean] def smove(source, destination, member) synchronize do |client| client.call([:smove, source, destination, member], &Boolify) end end # Determine if a given value is a member of a set. # # @param [String] key # @param [String] member # @return [Boolean] def sismember(key, member) synchronize do |client| client.call([:sismember, key, member], &Boolify) end end # Get all the members in a set. # # @param [String] key # @return [Array] def smembers(key) synchronize do |client| client.call([:smembers, key]) end end # Subtract multiple sets. # # @param [String, Array] keys keys pointing to sets to subtract # @return [Array] members in the difference def sdiff(*keys) synchronize do |client| client.call([:sdiff] + keys) end end # Subtract multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array] keys keys pointing to sets to subtract # @return [Fixnum] number of elements in the resulting set def sdiffstore(destination, *keys) synchronize do |client| client.call([:sdiffstore, destination] + keys) end end # Intersect multiple sets. # # @param [String, Array] keys keys pointing to sets to intersect # @return [Array] members in the intersection def sinter(*keys) synchronize do |client| client.call([:sinter] + keys) end end # Intersect multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array] keys keys pointing to sets to intersect # @return [Fixnum] number of elements in the resulting set def sinterstore(destination, *keys) synchronize do |client| client.call([:sinterstore, destination] + keys) end end # Add multiple sets. # # @param [String, Array] keys keys pointing to sets to unify # @return [Array] members in the union def sunion(*keys) synchronize do |client| client.call([:sunion] + keys) end end # Add multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array] keys keys pointing to sets to unify # @return [Fixnum] number of elements in the resulting set def sunionstore(destination, *keys) synchronize do |client| client.call([:sunionstore, destination] + keys) end end # Get the number of members in a sorted set. # # @example # redis.zcard("zset") # # => 4 # # @param [String] key # @return [Fixnum] def zcard(key) synchronize do |client| client.call([:zcard, key]) end end # Add one or more members to a sorted set, or update the score for members # that already exist. # # @example Add a single `[score, member]` pair to a sorted set # redis.zadd("zset", 32.0, "member") # @example Add an array of `[score, member]` pairs to a sorted set # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]]) # # @param [String] key # @param [[Float, String], Array<[Float, String]>] args # - a single `[score, member]` pair # - an array of `[score, member]` pairs # @param [Hash] options # - `:xx => true`: Only update elements that already exist (never # add elements) # - `:nx => true`: Don't update already existing elements (always # add new elements) # - `:ch => true`: Modify the return value from the number of new # elements added, to the total number of elements changed (CH is an # abbreviation of changed); changed elements are new elements added # and elements already existing for which the score was updated # - `:incr => true`: When this option is specified ZADD acts like # ZINCRBY; only one score-element pair can be specified in this mode # # @return [Boolean, Fixnum, Float] # - `Boolean` when a single pair is specified, holding whether or not it was # **added** to the sorted set. # - `Fixnum` when an array of pairs is specified, holding the number of # pairs that were **added** to the sorted set. # - `Float` when option :incr is specified, holding the score of the member # after incrementing it. def zadd(key, *args) #, options zadd_options = [] if args.last.is_a?(Hash) options = args.pop nx = options[:nx] zadd_options << "NX" if nx xx = options[:xx] zadd_options << "XX" if xx ch = options[:ch] zadd_options << "CH" if ch incr = options[:incr] zadd_options << "INCR" if incr end synchronize do |client| if args.size == 1 && args[0].is_a?(Array) # Variadic: return float if INCR, integer if !INCR client.call([:zadd, key] + zadd_options + args[0], &(incr ? Floatify : nil)) elsif args.size == 2 # Single pair: return float if INCR, boolean if !INCR client.call([:zadd, key] + zadd_options + args, &(incr ? Floatify : Boolify)) else raise ArgumentError, "wrong number of arguments" end end end # Increment the score of a member in a sorted set. # # @example # redis.zincrby("zset", 32.0, "a") # # => 64.0 # # @param [String] key # @param [Float] increment # @param [String] member # @return [Float] score of the member after incrementing it def zincrby(key, increment, member) synchronize do |client| client.call([:zincrby, key, increment, member], &Floatify) end end # Remove one or more members from a sorted set. # # @example Remove a single member from a sorted set # redis.zrem("zset", "a") # @example Remove an array of members from a sorted set # redis.zrem("zset", ["a", "b"]) # # @param [String] key # @param [String, Array] member # - a single member # - an array of members # # @return [Boolean, Fixnum] # - `Boolean` when a single member is specified, holding whether or not it # was removed from the sorted set # - `Fixnum` when an array of pairs is specified, holding the number of # members that were removed to the sorted set def zrem(key, member) synchronize do |client| client.call([:zrem, key, member]) do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean Boolify.call(reply) end end end end # Removes and returns up to count members with the highest scores in the sorted set stored at key. # # @example Popping a member # redis.zpopmax('zset') # #=> ['b', 2.0] # @example With count option # redis.zpopmax('zset', 2) # #=> [['b', 2.0], ['a', 1.0]] # # @params key [String] a key of the sorted set # @params count [Integer] a number of members # # @return [Array] element and score pair if count is not specified # @return [Array>] list of popped elements and scores def zpopmax(key, count = nil) synchronize do |client| members = client.call([:zpopmax, key, count].compact, &FloatifyPairs) count.to_i > 1 ? members : members.first end end # Removes and returns up to count members with the lowest scores in the sorted set stored at key. # # @example Popping a member # redis.zpopmin('zset') # #=> ['a', 1.0] # @example With count option # redis.zpopmin('zset', 2) # #=> [['a', 1.0], ['b', 2.0]] # # @params key [String] a key of the sorted set # @params count [Integer] a number of members # # @return [Array] element and score pair if count is not specified # @return [Array>] list of popped elements and scores def zpopmin(key, count = nil) synchronize do |client| members = client.call([:zpopmin, key, count].compact, &FloatifyPairs) count.to_i > 1 ? members : members.first end end # Removes and returns up to count members with the highest scores in the sorted set stored at keys, # or block until one is available. # # @example Popping a member from a sorted set # redis.bzpopmax('zset', 1) # #=> ['zset', 'b', 2.0] # @example Popping a member from multiple sorted sets # redis.bzpopmax('zset1', 'zset2', 1) # #=> ['zset1', 'b', 2.0] # # @params keys [Array] one or multiple keys of the sorted sets # @params timeout [Integer] the maximum number of seconds to block # # @return [Array] a touple of key, member and score # @return [nil] when no element could be popped and the timeout expired def bzpopmax(*args) _bpop(:bzpopmax, args) do |reply| reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply end end # Removes and returns up to count members with the lowest scores in the sorted set stored at keys, # or block until one is available. # # @example Popping a member from a sorted set # redis.bzpopmin('zset', 1) # #=> ['zset', 'a', 1.0] # @example Popping a member from multiple sorted sets # redis.bzpopmin('zset1', 'zset2', 1) # #=> ['zset1', 'a', 1.0] # # @params keys [Array] one or multiple keys of the sorted sets # @params timeout [Integer] the maximum number of seconds to block # # @return [Array] a touple of key, member and score # @return [nil] when no element could be popped and the timeout expired def bzpopmin(*args) _bpop(:bzpopmin, args) do |reply| reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply end end # Get the score associated with the given member in a sorted set. # # @example Get the score for member "a" # redis.zscore("zset", "a") # # => 32.0 # # @param [String] key # @param [String] member # @return [Float] score of the member def zscore(key, member) synchronize do |client| client.call([:zscore, key, member], &Floatify) end end # Return a range of members in a sorted set, by index. # # @example Retrieve all members from a sorted set # redis.zrange("zset", 0, -1) # # => ["a", "b"] # @example Retrieve all members and their scores from a sorted set # redis.zrange("zset", 0, -1, :with_scores => true) # # => [["a", 32.0], ["b", 64.0]] # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @param [Hash] options # - `:with_scores => true`: include scores in output # # @return [Array, Array<[String, Float]>] # - when `:with_scores` is not specified, an array of members # - when `:with_scores` is specified, an array with `[member, score]` pairs def zrange(key, start, stop, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << "WITHSCORES" block = FloatifyPairs end synchronize do |client| client.call([:zrange, key, start, stop] + args, &block) end end # Return a range of members in a sorted set, by index, with scores ordered # from high to low. # # @example Retrieve all members from a sorted set # redis.zrevrange("zset", 0, -1) # # => ["b", "a"] # @example Retrieve all members and their scores from a sorted set # redis.zrevrange("zset", 0, -1, :with_scores => true) # # => [["b", 64.0], ["a", 32.0]] # # @see #zrange def zrevrange(key, start, stop, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << "WITHSCORES" block = FloatifyPairs end synchronize do |client| client.call([:zrevrange, key, start, stop] + args, &block) end end # Determine the index of a member in a sorted set. # # @param [String] key # @param [String] member # @return [Fixnum] def zrank(key, member) synchronize do |client| client.call([:zrank, key, member]) end end # Determine the index of a member in a sorted set, with scores ordered from # high to low. # # @param [String] key # @param [String] member # @return [Fixnum] def zrevrank(key, member) synchronize do |client| client.call([:zrevrank, key, member]) end end # Remove all members in a sorted set within the given indexes. # # @example Remove first 5 members # redis.zremrangebyrank("zset", 0, 4) # # => 5 # @example Remove last 5 members # redis.zremrangebyrank("zset", -5, -1) # # => 5 # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Fixnum] number of members that were removed def zremrangebyrank(key, start, stop) synchronize do |client| client.call([:zremrangebyrank, key, start, stop]) end end # Count the members, with the same score in a sorted set, within the given lexicographical range. # # @example Count members matching a # redis.zlexcount("zset", "[a", "[a\xff") # # => 1 # @example Count members matching a-z # redis.zlexcount("zset", "[a", "[z\xff") # # => 26 # # @param [String] key # @param [String] min # - inclusive minimum is specified by prefixing `(` # - exclusive minimum is specified by prefixing `[` # @param [String] max # - inclusive maximum is specified by prefixing `(` # - exclusive maximum is specified by prefixing `[` # # @return [Fixnum] number of members within the specified lexicographical range def zlexcount(key, min, max) synchronize do |client| client.call([:zlexcount, key, min, max]) end end # Return a range of members with the same score in a sorted set, by lexicographical ordering # # @example Retrieve members matching a # redis.zrangebylex("zset", "[a", "[a\xff") # # => ["aaren", "aarika", "abagael", "abby"] # @example Retrieve the first 2 members matching a # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2]) # # => ["aaren", "aarika"] # # @param [String] key # @param [String] min # - inclusive minimum is specified by prefixing `(` # - exclusive minimum is specified by prefixing `[` # @param [String] max # - inclusive maximum is specified by prefixing `(` # - exclusive maximum is specified by prefixing `[` # @param [Hash] options # - `:limit => [offset, count]`: skip `offset` members, return a maximum of # `count` members # # @return [Array, Array<[String, Float]>] def zrangebylex(key, min, max, options = {}) args = [] limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrangebylex, key, min, max] + args) end end # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering. # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex. # # @example Retrieve members matching a # redis.zrevrangebylex("zset", "[a", "[a\xff") # # => ["abbygail", "abby", "abagael", "aaren"] # @example Retrieve the last 2 members matching a # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2]) # # => ["abbygail", "abby"] # # @see #zrangebylex def zrevrangebylex(key, max, min, options = {}) args = [] limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrevrangebylex, key, max, min] + args) end end # Return a range of members in a sorted set, by score. # # @example Retrieve members with score `>= 5` and `< 100` # redis.zrangebyscore("zset", "5", "(100") # # => ["a", "b"] # @example Retrieve the first 2 members with score `>= 0` # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2]) # # => ["a", "b"] # @example Retrieve members and their scores with scores `> 5` # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true) # # => [["a", 32.0], ["b", 64.0]] # # @param [String] key # @param [String] min # - inclusive minimum score is specified verbatim # - exclusive minimum score is specified by prefixing `(` # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @param [Hash] options # - `:with_scores => true`: include scores in output # - `:limit => [offset, count]`: skip `offset` members, return a maximum of # `count` members # # @return [Array, Array<[String, Float]>] # - when `:with_scores` is not specified, an array of members # - when `:with_scores` is specified, an array with `[member, score]` pairs def zrangebyscore(key, min, max, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << "WITHSCORES" block = FloatifyPairs end limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrangebyscore, key, min, max] + args, &block) end end # Return a range of members in a sorted set, by score, with scores ordered # from high to low. # # @example Retrieve members with score `< 100` and `>= 5` # redis.zrevrangebyscore("zset", "(100", "5") # # => ["b", "a"] # @example Retrieve the first 2 members with score `<= 0` # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2]) # # => ["b", "a"] # @example Retrieve members and their scores with scores `> 5` # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true) # # => [["b", 64.0], ["a", 32.0]] # # @see #zrangebyscore def zrevrangebyscore(key, max, min, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << ["WITHSCORES"] block = FloatifyPairs end limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrevrangebyscore, key, max, min] + args, &block) end end # Remove all members in a sorted set within the given scores. # # @example Remove members with score `>= 5` and `< 100` # redis.zremrangebyscore("zset", "5", "(100") # # => 2 # @example Remove members with scores `> 5` # redis.zremrangebyscore("zset", "(5", "+inf") # # => 2 # # @param [String] key # @param [String] min # - inclusive minimum score is specified verbatim # - exclusive minimum score is specified by prefixing `(` # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @return [Fixnum] number of members that were removed def zremrangebyscore(key, min, max) synchronize do |client| client.call([:zremrangebyscore, key, min, max]) end end # Count the members in a sorted set with scores within the given values. # # @example Count members with score `>= 5` and `< 100` # redis.zcount("zset", "5", "(100") # # => 2 # @example Count members with scores `> 5` # redis.zcount("zset", "(5", "+inf") # # => 2 # # @param [String] key # @param [String] min # - inclusive minimum score is specified verbatim # - exclusive minimum score is specified by prefixing `(` # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @return [Fixnum] number of members in within the specified range def zcount(key, min, max) synchronize do |client| client.call([:zcount, key, min, max]) end end # Intersect multiple sorted sets and store the resulting sorted set in a new # key. # # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum") # # => 4 # # @param [String] destination destination key # @param [Array] keys source keys # @param [Hash] options # - `:weights => [Float, Float, ...]`: weights to associate with source # sorted sets # - `:aggregate => String`: aggregate function to use (sum, min, max, ...) # @return [Fixnum] number of elements in the resulting sorted set def zinterstore(destination, keys, options = {}) args = [] weights = options[:weights] args.concat(["WEIGHTS"] + weights) if weights aggregate = options[:aggregate] args.concat(["AGGREGATE", aggregate]) if aggregate synchronize do |client| client.call([:zinterstore, destination, keys.size] + keys + args) end end # Add multiple sorted sets and store the resulting sorted set in a new key. # # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum") # # => 8 # # @param [String] destination destination key # @param [Array] keys source keys # @param [Hash] options # - `:weights => [Float, Float, ...]`: weights to associate with source # sorted sets # - `:aggregate => String`: aggregate function to use (sum, min, max, ...) # @return [Fixnum] number of elements in the resulting sorted set def zunionstore(destination, keys, options = {}) args = [] weights = options[:weights] args.concat(["WEIGHTS"] + weights) if weights aggregate = options[:aggregate] args.concat(["AGGREGATE", aggregate]) if aggregate synchronize do |client| client.call([:zunionstore, destination, keys.size] + keys + args) end end # Get the number of fields in a hash. # # @param [String] key # @return [Fixnum] number of fields in the hash def hlen(key) synchronize do |client| client.call([:hlen, key]) end end # Set the string value of a hash field. # # @param [String] key # @param [String] field # @param [String] value # @return [Boolean] whether or not the field was **added** to the hash def hset(key, field, value) synchronize do |client| client.call([:hset, key, field, value], &Boolify) end end # Set the value of a hash field, only if the field does not exist. # # @param [String] key # @param [String] field # @param [String] value # @return [Boolean] whether or not the field was **added** to the hash def hsetnx(key, field, value) synchronize do |client| client.call([:hsetnx, key, field, value], &Boolify) end end # Set one or more hash values. # # @example # redis.hmset("hash", "f1", "v1", "f2", "v2") # # => "OK" # # @param [String] key # @param [Array] attrs array of fields and values # @return [String] `"OK"` # # @see #mapped_hmset def hmset(key, *attrs) synchronize do |client| client.call([:hmset, key] + attrs) end end # Set one or more hash values. # # @example # redis.mapped_hmset("hash", { "f1" => "v1", "f2" => "v2" }) # # => "OK" # # @param [String] key # @param [Hash] hash a non-empty hash with fields mapping to values # @return [String] `"OK"` # # @see #hmset def mapped_hmset(key, hash) hmset(key, hash.to_a.flatten) end # Get the value of a hash field. # # @param [String] key # @param [String] field # @return [String] def hget(key, field) synchronize do |client| client.call([:hget, key, field]) end end # Get the values of all the given hash fields. # # @example # redis.hmget("hash", "f1", "f2") # # => ["v1", "v2"] # # @param [String] key # @param [Array] fields array of fields # @return [Array] an array of values for the specified fields # # @see #mapped_hmget def hmget(key, *fields, &blk) synchronize do |client| client.call([:hmget, key] + fields, &blk) end end # Get the values of all the given hash fields. # # @example # redis.mapped_hmget("hash", "f1", "f2") # # => { "f1" => "v1", "f2" => "v2" } # # @param [String] key # @param [Array] fields array of fields # @return [Hash] a hash mapping the specified fields to their values # # @see #hmget def mapped_hmget(key, *fields) hmget(key, *fields) do |reply| if reply.kind_of?(Array) Hash[fields.zip(reply)] else reply end end end # Delete one or more hash fields. # # @param [String] key # @param [String, Array] field # @return [Fixnum] the number of fields that were removed from the hash def hdel(key, *fields) synchronize do |client| client.call([:hdel, key, *fields]) end end # Determine if a hash field exists. # # @param [String] key # @param [String] field # @return [Boolean] whether or not the field exists in the hash def hexists(key, field) synchronize do |client| client.call([:hexists, key, field], &Boolify) end end # Increment the integer value of a hash field by the given integer number. # # @param [String] key # @param [String] field # @param [Fixnum] increment # @return [Fixnum] value of the field after incrementing it def hincrby(key, field, increment) synchronize do |client| client.call([:hincrby, key, field, increment]) end end # Increment the numeric value of a hash field by the given float number. # # @param [String] key # @param [String] field # @param [Float] increment # @return [Float] value of the field after incrementing it def hincrbyfloat(key, field, increment) synchronize do |client| client.call([:hincrbyfloat, key, field, increment], &Floatify) end end # Get all the fields in a hash. # # @param [String] key # @return [Array] def hkeys(key) synchronize do |client| client.call([:hkeys, key]) end end # Get all the values in a hash. # # @param [String] key # @return [Array] def hvals(key) synchronize do |client| client.call([:hvals, key]) end end # Get all the fields and values in a hash. # # @param [String] key # @return [Hash] def hgetall(key) synchronize do |client| client.call([:hgetall, key], &Hashify) end end # Post a message to a channel. def publish(channel, message) synchronize do |client| client.call([:publish, channel, message]) end end def subscribed? synchronize do |client| client.kind_of? SubscribedClient end end # Listen for messages published to the given channels. def subscribe(*channels, &block) synchronize do |client| _subscription(:subscribe, 0, channels, block) end end # Listen for messages published to the given channels. Throw a timeout error if there is no messages for a timeout period. def subscribe_with_timeout(timeout, *channels, &block) synchronize do |client| _subscription(:subscribe_with_timeout, timeout, channels, block) end end # Stop listening for messages posted to the given channels. def unsubscribe(*channels) synchronize do |client| raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? client.unsubscribe(*channels) end end # Listen for messages published to channels matching the given patterns. def psubscribe(*channels, &block) synchronize do |client| _subscription(:psubscribe, 0, channels, block) end end # Listen for messages published to channels matching the given patterns. Throw a timeout error if there is no messages for a timeout period. def psubscribe_with_timeout(timeout, *channels, &block) synchronize do |client| _subscription(:psubscribe_with_timeout, timeout, channels, block) end end # Stop listening for messages posted to channels matching the given patterns. def punsubscribe(*channels) synchronize do |client| raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? client.punsubscribe(*channels) end end # Inspect the state of the Pub/Sub subsystem. # Possible subcommands: channels, numsub, numpat. def pubsub(subcommand, *args) synchronize do |client| client.call([:pubsub, subcommand] + args) end end # Watch the given keys to determine execution of the MULTI/EXEC block. # # Using a block is optional, but is necessary for thread-safety. # # An `#unwatch` is automatically issued if an exception is raised within the # block that is a subclass of StandardError and is not a ConnectionError. # # @example With a block # redis.watch("key") do # if redis.get("key") == "some value" # redis.multi do |multi| # multi.set("key", "other value") # multi.incr("counter") # end # else # redis.unwatch # end # end # # => ["OK", 6] # # @example Without a block # redis.watch("key") # # => "OK" # # @param [String, Array] keys one or more keys to watch # @return [Object] if using a block, returns the return value of the block # @return [String] if not using a block, returns `OK` # # @see #unwatch # @see #multi def watch(*keys) synchronize do |client| res = client.call([:watch] + keys) if block_given? begin yield(self) rescue ConnectionError raise rescue StandardError unwatch raise end else res end end end # Forget about all watched keys. # # @return [String] `OK` # # @see #watch # @see #multi def unwatch synchronize do |client| client.call([:unwatch]) end end def pipelined synchronize do |client| begin pipeline = Pipeline.new(@client) original, @client = @client, pipeline yield(self) original.call_pipeline(@client) ensure @client = original end end end # Mark the start of a transaction block. # # Passing a block is optional. # # @example With a block # redis.multi do |multi| # multi.set("key", "value") # multi.incr("counter") # end # => ["OK", 6] # # @example Without a block # redis.multi # # => "OK" # redis.set("key", "value") # # => "QUEUED" # redis.incr("counter") # # => "QUEUED" # redis.exec # # => ["OK", 6] # # @yield [multi] the commands that are called inside this block are cached # and written to the server upon returning from it # @yieldparam [Redis] multi `self` # # @return [String, Array<...>] # - when a block is not given, `OK` # - when a block is given, an array with replies # # @see #watch # @see #unwatch def multi synchronize do |client| if !block_given? client.call([:multi]) else begin pipeline = Pipeline::Multi.new(@client) original, @client = @client, pipeline yield(self) original.call_pipeline(pipeline) ensure @client = original end end end end # Execute all commands issued after MULTI. # # Only call this method when `#multi` was called **without** a block. # # @return [nil, Array<...>] # - when commands were not executed, `nil` # - when commands were executed, an array with their replies # # @see #multi # @see #discard def exec synchronize do |client| client.call([:exec]) end end # Discard all commands issued after MULTI. # # Only call this method when `#multi` was called **without** a block. # # @return [String] `"OK"` # # @see #multi # @see #exec def discard synchronize do |client| client.call([:discard]) end end # Control remote script registry. # # @example Load a script # sha = redis.script(:load, "return 1") # # => # @example Check if a script exists # redis.script(:exists, sha) # # => true # @example Check if multiple scripts exist # redis.script(:exists, [sha, other_sha]) # # => [true, false] # @example Flush the script registry # redis.script(:flush) # # => "OK" # @example Kill a running script # redis.script(:kill) # # => "OK" # # @param [String] subcommand e.g. `exists`, `flush`, `load`, `kill` # @param [Array] args depends on subcommand # @return [String, Boolean, Array, ...] depends on subcommand # # @see #eval # @see #evalsha def script(subcommand, *args) subcommand = subcommand.to_s.downcase if subcommand == "exists" synchronize do |client| arg = args.first client.call([:script, :exists, arg]) do |reply| reply = reply.map { |r| Boolify.call(r) } if arg.is_a?(Array) reply else reply.first end end end else synchronize do |client| client.call([:script, subcommand] + args) end end end def _eval(cmd, args) script = args.shift options = args.pop if args.last.is_a?(Hash) options ||= {} keys = args.shift || options[:keys] || [] argv = args.shift || options[:argv] || [] synchronize do |client| client.call([cmd, script, keys.length] + keys + argv) end end # Evaluate Lua script. # # @example EVAL without KEYS nor ARGV # redis.eval("return 1") # # => 1 # @example EVAL with KEYS and ARGV as array arguments # redis.eval("return { KEYS, ARGV }", ["k1", "k2"], ["a1", "a2"]) # # => [["k1", "k2"], ["a1", "a2"]] # @example EVAL with KEYS and ARGV in a hash argument # redis.eval("return { KEYS, ARGV }", :keys => ["k1", "k2"], :argv => ["a1", "a2"]) # # => [["k1", "k2"], ["a1", "a2"]] # # @param [Array] keys optional array with keys to pass to the script # @param [Array] argv optional array with arguments to pass to the script # @param [Hash] options # - `:keys => Array`: optional array with keys to pass to the script # - `:argv => Array`: optional array with arguments to pass to the script # @return depends on the script # # @see #script # @see #evalsha def eval(*args) _eval(:eval, args) end # Evaluate Lua script by its SHA. # # @example EVALSHA without KEYS nor ARGV # redis.evalsha(sha) # # => # @example EVALSHA with KEYS and ARGV as array arguments # redis.evalsha(sha, ["k1", "k2"], ["a1", "a2"]) # # => # @example EVALSHA with KEYS and ARGV in a hash argument # redis.evalsha(sha, :keys => ["k1", "k2"], :argv => ["a1", "a2"]) # # => # # @param [Array] keys optional array with keys to pass to the script # @param [Array] argv optional array with arguments to pass to the script # @param [Hash] options # - `:keys => Array`: optional array with keys to pass to the script # - `:argv => Array`: optional array with arguments to pass to the script # @return depends on the script # # @see #script # @see #eval def evalsha(*args) _eval(:evalsha, args) end def _scan(command, cursor, args, options = {}, &block) # SSCAN/ZSCAN/HSCAN already prepend the key to +args+. args << cursor if match = options[:match] args.concat(["MATCH", match]) end if count = options[:count] args.concat(["COUNT", count]) end synchronize do |client| client.call([command] + args, &block) end end # Scan the keyspace # # @example Retrieve the first batch of keys # redis.scan(0) # # => ["4", ["key:21", "key:47", "key:42"]] # @example Retrieve a batch of keys matching a pattern # redis.scan(4, :match => "key:1?") # # => ["92", ["key:13", "key:18"]] # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array] the next cursor and all found keys def scan(cursor, options={}) _scan(:scan, cursor, [], options) end # Scan the keyspace # # @example Retrieve all of the keys (with possible duplicates) # redis.scan_each.to_a # # => ["key:21", "key:47", "key:42"] # @example Execute block for each key matching a pattern # redis.scan_each(:match => "key:1?") {|key| puts key} # # => key:13 # # => key:18 # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all found keys def scan_each(options={}, &block) return to_enum(:scan_each, options) unless block_given? cursor = 0 loop do cursor, keys = scan(cursor, options) keys.each(&block) break if cursor == "0" end end # Scan a hash # # @example Retrieve the first batch of key/value pairs in a hash # redis.hscan("hash", 0) # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array<[String, String]>] the next cursor and all found keys def hscan(key, cursor, options={}) _scan(:hscan, cursor, [key], options) do |reply| [reply[0], reply[1].each_slice(2).to_a] end end # Scan a hash # # @example Retrieve all of the key/value pairs in a hash # redis.hscan_each("hash").to_a # # => [["key70", "70"], ["key80", "80"]] # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all found keys def hscan_each(key, options={}, &block) return to_enum(:hscan_each, key, options) unless block_given? cursor = 0 loop do cursor, values = hscan(key, cursor, options) values.each(&block) break if cursor == "0" end end # Scan a sorted set # # @example Retrieve the first batch of key/value pairs in a hash # redis.zscan("zset", 0) # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array<[String, Float]>] the next cursor and all found # members and scores def zscan(key, cursor, options={}) _scan(:zscan, cursor, [key], options) do |reply| [reply[0], FloatifyPairs.call(reply[1])] end end # Scan a sorted set # # @example Retrieve all of the members/scores in a sorted set # redis.zscan_each("zset").to_a # # => [["key70", "70"], ["key80", "80"]] # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all found scores and members def zscan_each(key, options={}, &block) return to_enum(:zscan_each, key, options) unless block_given? cursor = 0 loop do cursor, values = zscan(key, cursor, options) values.each(&block) break if cursor == "0" end end # Scan a set # # @example Retrieve the first batch of keys in a set # redis.sscan("set", 0) # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array] the next cursor and all found members def sscan(key, cursor, options={}) _scan(:sscan, cursor, [key], options) end # Scan a set # # @example Retrieve all of the keys in a set # redis.sscan_each("set").to_a # # => ["key1", "key2", "key3"] # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all keys in the set def sscan_each(key, options={}, &block) return to_enum(:sscan_each, key, options) unless block_given? cursor = 0 loop do cursor, keys = sscan(key, cursor, options) keys.each(&block) break if cursor == "0" end end # Add one or more members to a HyperLogLog structure. # # @param [String] key # @param [String, Array] member one member, or array of members # @return [Boolean] true if at least 1 HyperLogLog internal register was altered. false otherwise. def pfadd(key, member) synchronize do |client| client.call([:pfadd, key, member], &Boolify) end end # Get the approximate cardinality of members added to HyperLogLog structure. # # If called with multiple keys, returns the approximate cardinality of the # union of the HyperLogLogs contained in the keys. # # @param [String, Array] keys # @return [Fixnum] def pfcount(*keys) synchronize do |client| client.call([:pfcount] + keys) end end # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of # the observed Sets of the source HyperLogLog structures. # # @param [String] dest_key destination key # @param [String, Array] source_key source key, or array of keys # @return [Boolean] def pfmerge(dest_key, *source_key) synchronize do |client| client.call([:pfmerge, dest_key, *source_key], &BoolifySet) end end # Adds the specified geospatial items (latitude, longitude, name) to the specified key # # @param [String] key # @param [Array] member arguemnts for member or members: longitude, latitude, name # @return [Integer] number of elements added to the sorted set def geoadd(key, *member) synchronize do |client| client.call([:geoadd, key, *member]) end end # Returns geohash string representing position for specified members of the specified key. # # @param [String] key # @param [String, Array] member one member or array of members # @return [Array] returns array containg geohash string if member is present, nil otherwise def geohash(key, member) synchronize do |client| client.call([:geohash, key, member]) end end # Query a sorted set representing a geospatial index to fetch members matching a # given maximum distance from a point # # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi) # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center # @param [Integer] count limit the results to the first N matching items # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information # @return [Array] may be changed with `options` def georadius(*args, **geoptions) geoarguments = _geoarguments(*args, **geoptions) synchronize do |client| client.call([:georadius, *geoarguments]) end end # Query a sorted set representing a geospatial index to fetch members matching a # given maximum distance from an already existing member # # @param [Array] args key, member, radius, unit(m|km|ft|mi) # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center # @param [Integer] count limit the results to the first N matching items # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information # @return [Array] may be changed with `options` def georadiusbymember(*args, **geoptions) geoarguments = _geoarguments(*args, **geoptions) synchronize do |client| client.call([:georadiusbymember, *geoarguments]) end end # Returns longitude and latitude of members of a geospatial index # # @param [String] key # @param [String, Array] member one member or array of members # @return [Array, nil>] returns array of elements, where each element is either array of longitude and latitude or nil def geopos(key, member) synchronize do |client| client.call([:geopos, key, member]) end end # Returns the distance between two members of a geospatial index # # @param [String ]key # @param [Array] members # @param ['m', 'km', 'mi', 'ft'] unit # @return [String, nil] returns distance in spefied unit if both members present, nil otherwise. def geodist(key, member1, member2, unit = 'm') synchronize do |client| client.call([:geodist, key, member1, member2, unit]) end end # Returns the stream information each subcommand. # # @example stream # redis.xinfo(:stream, 'mystream') # @example groups # redis.xinfo(:groups, 'mystream') # @example consumers # redis.xinfo(:consumers, 'mystream', 'mygroup') # # @param subcommand [String] e.g. `stream` `groups` `consumers` # @param key [String] the stream key # @param group [String] the consumer group name, required if subcommand is `consumers` # # @return [Hash] information of the stream if subcommand is `stream` # @return [Array] information of the consumer groups if subcommand is `groups` # @return [Array] information of the consumers if subcommand is `consumers` def xinfo(subcommand, key, group = nil) args = [:xinfo, subcommand, key, group].compact synchronize do |client| client.call(args) do |reply| case subcommand.to_s.downcase when 'stream' then Hashify.call(reply) when 'groups', 'consumers' then reply.map { |arr| Hashify.call(arr) } else reply end end end end # Add new entry to the stream. # # @example Without options # redis.xadd('mystream', f1: 'v1', f2: 'v2') # @example With options # redis.xadd('mystream', { f1: 'v1', f2: 'v2' }, id: '0-0', maxlen: 1000, approximate: true) # # @param key [String] the stream key # @param entry [Hash] one or multiple field-value pairs # @param opts [Hash] several options for `XADD` command # # @option opts [String] :id the entry id, default value is `*`, it means auto generation # @option opts [Integer] :maxlen max length of entries # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not # # @return [String] the entry id def xadd(key, entry, opts = {}) args = [:xadd, key] args.concat(['MAXLEN', (opts[:approximate] ? '~' : nil), opts[:maxlen]].compact) if opts[:maxlen] args << (opts[:id] || '*') args.concat(entry.to_a.flatten) synchronize { |client| client.call(args) } end # Trims older entries of the stream if needed. # # @example Without options # redis.xtrim('mystream', 1000) # @example With options # redis.xtrim('mystream', 1000, approximate: true) # # @param key [String] the stream key # @param mexlen [Integer] max length of entries # @param approximate [Boolean] whether to add `~` modifier of maxlen or not # # @return [Integer] the number of entries actually deleted def xtrim(key, maxlen, approximate: false) args = [:xtrim, key, 'MAXLEN', (approximate ? '~' : nil), maxlen].compact synchronize { |client| client.call(args) } end # Delete entries by entry ids. # # @example With splatted entry ids # redis.xdel('mystream', '0-1', '0-2') # @example With arrayed entry ids # redis.xdel('mystream', ['0-1', '0-2']) # # @param key [String] the stream key # @param ids [Array] one or multiple entry ids # # @return [Integer] the number of entries actually deleted def xdel(key, *ids) args = [:xdel, key].concat(ids.flatten) synchronize { |client| client.call(args) } end # Fetches entries of the stream in ascending order. # # @example Without options # redis.xrange('mystream') # @example With a specific start # redis.xrange('mystream', '0-1') # @example With a specific start and end # redis.xrange('mystream', '0-1', '0-3') # @example With count options # redis.xrange('mystream', count: 10) # # @param key [String] the stream key # @param start [String] first entry id of range, default value is `-` # @param end [String] last entry id of range, default value is `+` # @param count [Integer] the number of entries as limit # # @return [Array>] the ids and entries pairs def xrange(key, start = '-', _end = '+', count: nil) args = [:xrange, key, start, _end] args.concat(['COUNT', count]) if count synchronize { |client| client.call(args, &HashifyStreamEntries) } end # Fetches entries of the stream in descending order. # # @example Without options # redis.xrevrange('mystream') # @example With a specific end # redis.xrevrange('mystream', '0-3') # @example With a specific end and start # redis.xrevrange('mystream', '0-3', '0-1') # @example With count options # redis.xrevrange('mystream', count: 10) # # @param key [String] the stream key # @param end [String] first entry id of range, default value is `+` # @param start [String] last entry id of range, default value is `-` # @params count [Integer] the number of entries as limit # # @return [Array>] the ids and entries pairs def xrevrange(key, _end = '+', start = '-', count: nil) args = [:xrevrange, key, _end, start] args.concat(['COUNT', count]) if count synchronize { |client| client.call(args, &HashifyStreamEntries) } end # Returns the number of entries inside a stream. # # @example With key # redis.xlen('mystream') # # @param key [String] the stream key # # @return [Integer] the number of entries def xlen(key) synchronize { |client| client.call([:xlen, key]) } end # Fetches entries from one or multiple streams. Optionally blocking. # # @example With a key # redis.xread('mystream', '0-0') # @example With multiple keys # redis.xread(%w[mystream1 mystream2], %w[0-0 0-0]) # @example With count option # redis.xread('mystream', '0-0', count: 2) # @example With block option # redis.xread('mystream', '$', block: 1000) # # @param keys [Array] one or multiple stream keys # @param ids [Array] one or multiple entry ids # @param count [Integer] the number of entries as limit per stream # @param block [Integer] the number of milliseconds as blocking timeout # # @return [Hash{String => Hash{String => Hash}}] the entries def xread(keys, ids, count: nil, block: nil) args = [:xread] args << 'COUNT' << count if count args << 'BLOCK' << block.to_i if block _xread(args, keys, ids, block) end # Manages the consumer group of the stream. # # @example With `create` subcommand # redis.xgroup(:create, 'mystream', 'mygroup', '$') # @example With `setid` subcommand # redis.xgroup(:setid, 'mystream', 'mygroup', '$') # @example With `destroy` subcommand # redis.xgroup(:destroy, 'mystream', 'mygroup') # @example With `delconsumer` subcommand # redis.xgroup(:delconsumer, 'mystream', 'mygroup', 'consumer1') # # @param subcommand [String] `create` `setid` `destroy` `delconsumer` # @param key [String] the stream key # @param group [String] the consumer group name # @param id_or_consumer [String] # * the entry id or `$`, required if subcommand is `create` or `setid` # * the consumer name, required if subcommand is `delconsumer` # @param mkstream [Boolean] whether to create an empty stream automatically or not # # @return [String] `OK` if subcommand is `create` or `setid` # @return [Integer] effected count if subcommand is `destroy` or `delconsumer` def xgroup(subcommand, key, group, id_or_consumer = nil, mkstream: false) args = [:xgroup, subcommand, key, group, id_or_consumer, (mkstream ? 'MKSTREAM' : nil)].compact synchronize { |client| client.call(args) } end # Fetches a subset of the entries from one or multiple streams related with the consumer group. # Optionally blocking. # # @example With a key # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>') # @example With multiple keys # redis.xreadgroup('mygroup', 'consumer1', %w[mystream1 mystream2], %w[> >]) # @example With count option # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', count: 2) # @example With block option # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', block: 1000) # @example With noack option # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', noack: true) # # @param group [String] the consumer group name # @param consumer [String] the consumer name # @param keys [Array] one or multiple stream keys # @param ids [Array] one or multiple entry ids # @param opts [Hash] several options for `XREADGROUP` command # # @option opts [Integer] :count the number of entries as limit # @option opts [Integer] :block the number of milliseconds as blocking timeout # @option opts [Boolean] :noack whether message loss is acceptable or not # # @return [Hash{String => Hash{String => Hash}}] the entries def xreadgroup(group, consumer, keys, ids, opts = {}) args = [:xreadgroup, 'GROUP', group, consumer] args << 'COUNT' << opts[:count] if opts[:count] args << 'BLOCK' << opts[:block].to_i if opts[:block] args << 'NOACK' if opts[:noack] _xread(args, keys, ids, opts[:block]) end # Removes one or multiple entries from the pending entries list of a stream consumer group. # # @example With a entry id # redis.xack('mystream', 'mygroup', '1526569495631-0') # @example With splatted entry ids # redis.xack('mystream', 'mygroup', '0-1', '0-2') # @example With arrayed entry ids # redis.xack('mystream', 'mygroup', %w[0-1 0-2]) # # @param key [String] the stream key # @param group [String] the consumer group name # @param ids [Array] one or multiple entry ids # # @return [Integer] the number of entries successfully acknowledged def xack(key, group, *ids) args = [:xack, key, group].concat(ids.flatten) synchronize { |client| client.call(args) } end # Changes the ownership of a pending entry # # @example With splatted entry ids # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-1', '0-2') # @example With arrayed entry ids # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2]) # @example With idle option # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], idle: 1000) # @example With time option # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], time: 1542866959000) # @example With retrycount option # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], retrycount: 10) # @example With force option # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], force: true) # @example With justid option # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], justid: true) # # @param key [String] the stream key # @param group [String] the consumer group name # @param consumer [String] the consumer name # @param min_idle_time [Integer] the number of milliseconds # @param ids [Array] one or multiple entry ids # @param opts [Hash] several options for `XCLAIM` command # # @option opts [Integer] :idle the number of milliseconds as last time it was delivered of the entry # @option opts [Integer] :time the number of milliseconds as a specific Unix Epoch time # @option opts [Integer] :retrycount the number of retry counter # @option opts [Boolean] :force whether to create the pending entry to the pending entries list or not # @option opts [Boolean] :justid whether to fetch just an array of entry ids or not # # @return [Hash{String => Hash}] the entries successfully claimed # @return [Array] the entry ids successfully claimed if justid option is `true` def xclaim(key, group, consumer, min_idle_time, *ids, **opts) args = [:xclaim, key, group, consumer, min_idle_time].concat(ids.flatten) args.concat(['IDLE', opts[:idle].to_i]) if opts[:idle] args.concat(['TIME', opts[:time].to_i]) if opts[:time] args.concat(['RETRYCOUNT', opts[:retrycount]]) if opts[:retrycount] args << 'FORCE' if opts[:force] args << 'JUSTID' if opts[:justid] blk = opts[:justid] ? Noop : HashifyStreamEntries synchronize { |client| client.call(args, &blk) } end # Fetches not acknowledging pending entries # # @example With key and group # redis.xpending('mystream', 'mygroup') # @example With range options # redis.xpending('mystream', 'mygroup', '-', '+', 10) # @example With range and consumer options # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1') # # @param key [String] the stream key # @param group [String] the consumer group name # @param start [String] start first entry id of range # @param end [String] end last entry id of range # @param count [Integer] count the number of entries as limit # @param consumer [String] the consumer name # # @return [Hash] the summary of pending entries # @return [Array] the pending entries details if options were specified def xpending(key, group, *args) command_args = [:xpending, key, group] case args.size when 0, 3, 4 command_args.concat(args) else raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 2, 5 or 6)" end summary_needed = args.empty? blk = summary_needed ? HashifyStreamPendings : HashifyStreamPendingDetails synchronize { |client| client.call(command_args, &blk) } end # Interact with the sentinel command (masters, master, slaves, failover) # # @param [String] subcommand e.g. `masters`, `master`, `slaves` # @param [Array] args depends on subcommand # @return [Array, Hash, String] depends on subcommand def sentinel(subcommand, *args) subcommand = subcommand.to_s.downcase synchronize do |client| client.call([:sentinel, subcommand] + args) do |reply| case subcommand when "get-master-addr-by-name" reply else if reply.kind_of?(Array) if reply[0].kind_of?(Array) reply.map(&Hashify) else Hashify.call(reply) end else reply end end end end end # Sends `CLUSTER *` command to random node and returns its reply. # # @see https://redis.io/commands#cluster Reference of cluster command # # @param subcommand [String, Symbol] the subcommand of cluster command # e.g. `:slots`, `:nodes`, `:slaves`, `:info` # # @return [Object] depends on the subcommand def cluster(subcommand, *args) subcommand = subcommand.to_s.downcase block = case subcommand when 'slots' then HashifyClusterSlots when 'nodes' then HashifyClusterNodes when 'slaves' then HashifyClusterSlaves when 'info' then HashifyInfo else Noop end # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected block = Noop unless @cluster_mode synchronize do |client| client.call([:cluster, subcommand] + args, &block) end end # Sends `ASKING` command to random node and returns its reply. # # @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection # # @return [String] `'OK'` def asking synchronize { |client| client.call(%i[asking]) } end def id @original_client.id end def inspect "#" end def dup self.class.new(@options) end def connection return @original_client.connection_info if @cluster_mode { host: @original_client.host, port: @original_client.port, db: @original_client.db, id: @original_client.id, location: @original_client.location } end def method_missing(command, *args) synchronize do |client| client.call([command] + args) end end private # Commands returning 1 for true and 0 for false may be executed in a pipeline # where the method call will return nil. Propagate the nil instead of falsely # returning false. Boolify = lambda { |value| case value when 1 true when 0 false else value end } BoolifySet = lambda { |value| case value when "OK" true when nil false else value end } Hashify = lambda { |value| if value.respond_to?(:each_slice) value.each_slice(2).to_h else value end } Floatify = lambda { |value| case value when "inf" Float::INFINITY when "-inf" -Float::INFINITY when String Float(value) else value end } FloatifyPairs = lambda { |value| return value unless value.respond_to?(:each_slice) value.each_slice(2).map do |member, score| [member, Floatify.call(score)] end } HashifyInfo = lambda { |reply| lines = reply.split("\r\n").grep_v(/^(#|$)/) lines.map! { |line| line.split(':', 2) } lines.compact! lines.to_h } HashifyStreams = lambda { |reply| case reply when nil {} else reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h end } HashifyStreamEntries = lambda { |reply| reply.map do |entry_id, values| [entry_id, values.each_slice(2).to_h] end } HashifyStreamPendings = lambda { |reply| { 'size' => reply[0], 'min_entry_id' => reply[1], 'max_entry_id' => reply[2], 'consumers' => reply[3].nil? ? {} : reply[3].to_h } } HashifyStreamPendingDetails = lambda { |reply| reply.map do |arr| { 'entry_id' => arr[0], 'consumer' => arr[1], 'elapsed' => arr[2], 'count' => arr[3] } end } HashifyClusterNodeInfo = lambda { |str| arr = str.split(' ') { 'node_id' => arr[0], 'ip_port' => arr[1], 'flags' => arr[2].split(','), 'master_node_id' => arr[3], 'ping_sent' => arr[4], 'pong_recv' => arr[5], 'config_epoch' => arr[6], 'link_state' => arr[7], 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-')) } } HashifyClusterSlots = lambda { |reply| reply.map do |arr| first_slot, last_slot = arr[0..1] master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] } replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } } { 'start_slot' => first_slot, 'end_slot' => last_slot, 'master' => master, 'replicas' => replicas } end } HashifyClusterNodes = lambda { |reply| reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) } } HashifyClusterSlaves = lambda { |reply| reply.map { |str| HashifyClusterNodeInfo.call(str) } } Noop = ->(reply) { reply } def _geoarguments(*args, options: nil, sort: nil, count: nil) args.push sort if sort args.push 'count', count if count args.push options if options args end def _subscription(method, timeout, channels, block) return @client.call([method] + channels) if subscribed? begin original, @client = @client, SubscribedClient.new(@client) if timeout > 0 @client.send(method, timeout, *channels, &block) else @client.send(method, *channels, &block) end ensure @client = original end end def _xread(args, keys, ids, blocking_timeout_msec) keys = keys.is_a?(Array) ? keys : [keys] ids = ids.is_a?(Array) ? ids : [ids] args << 'STREAMS' args.concat(keys) args.concat(ids) synchronize do |client| if blocking_timeout_msec.nil? client.call(args, &HashifyStreams) elsif blocking_timeout_msec.to_f.zero? client.call_without_timeout(args, &HashifyStreams) else timeout = client.timeout.to_f + blocking_timeout_msec.to_f / 1000.0 client.call_with_timeout(args, timeout, &HashifyStreams) end end end end require_relative "redis/version" require_relative "redis/connection" require_relative "redis/client" require_relative "redis/cluster" require_relative "redis/pipeline" require_relative "redis/subscribe" redis-rb-4.1.2/lib/redis/000077500000000000000000000000001347371274700151205ustar00rootroot00000000000000redis-rb-4.1.2/lib/redis/client.rb000066400000000000000000000400111347371274700167170ustar00rootroot00000000000000# frozen_string_literal: true require_relative "errors" require "socket" require "cgi" class Redis class Client DEFAULTS = { :url => lambda { ENV["REDIS_URL"] }, :scheme => "redis", :host => "127.0.0.1", :port => 6379, :path => nil, :timeout => 5.0, :password => nil, :db => 0, :driver => nil, :id => nil, :tcp_keepalive => 0, :reconnect_attempts => 1, :reconnect_delay => 0, :reconnect_delay_max => 0.5, :inherit_socket => false } attr_reader :options def scheme @options[:scheme] end def host @options[:host] end def port @options[:port] end def path @options[:path] end def read_timeout @options[:read_timeout] end def connect_timeout @options[:connect_timeout] end def timeout @options[:read_timeout] end def password @options[:password] end def db @options[:db] end def db=(db) @options[:db] = db.to_i end def driver @options[:driver] end def inherit_socket? @options[:inherit_socket] end attr_accessor :logger attr_reader :connection attr_reader :command_map def initialize(options = {}) @options = _parse_options(options) @reconnect = true @logger = @options[:logger] @connection = nil @command_map = {} @pending_reads = 0 @connector = if options.include?(:sentinels) Connector::Sentinel.new(@options) elsif options.include?(:connector) && options[:connector].respond_to?(:new) options.delete(:connector).new(@options) else Connector.new(@options) end end def connect @pid = Process.pid # Don't try to reconnect when the connection is fresh with_reconnect(false) do establish_connection call [:auth, password] if password call [:select, db] if db != 0 call [:client, :setname, @options[:id]] if @options[:id] @connector.check(self) end self end def id @options[:id] || "redis://#{location}/#{db}" end def location path || "#{host}:#{port}" end def call(command) reply = process([command]) { read } raise reply if reply.is_a?(CommandError) if block_given? yield reply else reply end end def call_loop(command, timeout = 0) error = nil result = with_socket_timeout(timeout) do process([command]) do loop do reply = read if reply.is_a?(CommandError) error = reply break else yield reply end end end end # Raise error when previous block broke out of the loop. raise error if error # Result is set to the value that the provided block used to break. result end def call_pipeline(pipeline) return [] if pipeline.futures.empty? with_reconnect pipeline.with_reconnect? do begin pipeline.finish(call_pipelined(pipeline)).tap do self.db = pipeline.db if pipeline.db end rescue ConnectionError => e return nil if pipeline.shutdown? # Assume the pipeline was sent in one piece, but execution of # SHUTDOWN caused none of the replies for commands that were executed # prior to it from coming back around. raise e end end end def call_pipelined(pipeline) return [] if pipeline.futures.empty? # The method #ensure_connected (called from #process) reconnects once on # I/O errors. To make an effort in making sure that commands are not # executed more than once, only allow reconnection before the first reply # has been read. When an error occurs after the first reply has been # read, retrying would re-execute the entire pipeline, thus re-issuing # already successfully executed commands. To circumvent this, don't retry # after the first reply has been read successfully. commands = pipeline.commands result = Array.new(commands.size) reconnect = @reconnect begin exception = nil process(commands) do pipeline.timeouts.each_with_index do |timeout, i| reply = if timeout with_socket_timeout(timeout) { read } else read end result[i] = reply @reconnect = false exception = reply if exception.nil? && reply.is_a?(CommandError) end end raise exception if exception ensure @reconnect = reconnect end result end def call_with_timeout(command, timeout, &blk) with_socket_timeout(timeout) do call(command, &blk) end rescue ConnectionError retry end def call_without_timeout(command, &blk) call_with_timeout(command, 0, &blk) end def process(commands) logging(commands) do ensure_connected do commands.each do |command| if command_map[command.first] command = command.dup command[0] = command_map[command.first] end write(command) end yield if block_given? end end end def connected? !! (connection && connection.connected?) end def disconnect connection.disconnect if connected? end def reconnect disconnect connect end def io yield rescue TimeoutError => e1 # Add a message to the exception without destroying the original stack e2 = TimeoutError.new("Connection timed out") e2.set_backtrace(e1.backtrace) raise e2 rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last] end def read io do value = connection.read @pending_reads -= 1 value end end def write(command) io do @pending_reads += 1 connection.write(command) end end def with_socket_timeout(timeout) connect unless connected? original = @options[:read_timeout] begin connection.timeout = timeout @options[:read_timeout] = timeout # for reconnection yield ensure connection.timeout = self.timeout if connected? @options[:read_timeout] = original end end def without_socket_timeout(&blk) with_socket_timeout(0, &blk) end def with_reconnect(val=true) begin original, @reconnect = @reconnect, val yield ensure @reconnect = original end end def without_reconnect(&blk) with_reconnect(false, &blk) end protected def logging(commands) return yield unless @logger && @logger.debug? begin commands.each do |name, *args| logged_args = args.map do |a| case when a.respond_to?(:inspect) then a.inspect when a.respond_to?(:to_s) then a.to_s else # handle poorly-behaved descendants of BasicObject klass = a.instance_exec { (class << self; self end).superclass } "\#<#{klass}:#{a.__id__}>" end end @logger.debug("[Redis] command=#{name.to_s.upcase} args=#{logged_args.join(' ')}") end t1 = Time.now yield ensure @logger.debug("[Redis] call_time=%0.2f ms" % ((Time.now - t1) * 1000)) if t1 end end def establish_connection server = @connector.resolve.dup @options[:host] = server[:host] @options[:port] = Integer(server[:port]) if server.include?(:port) @connection = @options[:driver].connect(@options) @pending_reads = 0 rescue TimeoutError, SocketError, Errno::EADDRNOTAVAIL, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ENOENT, Errno::ETIMEDOUT, Errno::EINVAL raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})" end def ensure_connected disconnect if @pending_reads > 0 attempts = 0 begin attempts += 1 if connected? unless inherit_socket? || Process.pid == @pid raise InheritedError, "Tried to use a connection from a child process without reconnecting. " + "You need to reconnect to Redis after forking " + "or set :inherit_socket to true." end else connect end yield rescue BaseConnectionError disconnect if attempts <= @options[:reconnect_attempts] && @reconnect sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)), @options[:reconnect_delay_max]].min Kernel.sleep(sleep_t) retry else raise end rescue Exception disconnect raise end end def _parse_options(options) return options if options[:_parsed] defaults = DEFAULTS.dup options = options.dup defaults.keys.each do |key| # Fill in defaults if needed if defaults[key].respond_to?(:call) defaults[key] = defaults[key].call end # Symbolize only keys that are needed options[key] = options[key.to_s] if options.has_key?(key.to_s) end url = options[:url] url = defaults[:url] if url == nil # Override defaults from URL if given if url require "uri" uri = URI(url) if uri.scheme == "unix" defaults[:path] = uri.path elsif uri.scheme == "redis" || uri.scheme == "rediss" defaults[:scheme] = uri.scheme defaults[:host] = uri.host if uri.host defaults[:port] = uri.port if uri.port defaults[:password] = CGI.unescape(uri.password) if uri.password defaults[:db] = uri.path[1..-1].to_i if uri.path defaults[:role] = :master else raise ArgumentError, "invalid uri scheme '#{uri.scheme}'" end defaults[:ssl] = true if uri.scheme == "rediss" end # Use default when option is not specified or nil defaults.keys.each do |key| options[key] = defaults[key] if options[key].nil? end if options[:path] # Unix socket options[:scheme] = "unix" options.delete(:host) options.delete(:port) else # TCP socket options[:host] = options[:host].to_s options[:port] = options[:port].to_i end if options.has_key?(:timeout) options[:connect_timeout] ||= options[:timeout] options[:read_timeout] ||= options[:timeout] options[:write_timeout] ||= options[:timeout] end options[:connect_timeout] = Float(options[:connect_timeout]) options[:read_timeout] = Float(options[:read_timeout]) options[:write_timeout] = Float(options[:write_timeout]) options[:reconnect_attempts] = options[:reconnect_attempts].to_i options[:reconnect_delay] = options[:reconnect_delay].to_f options[:reconnect_delay_max] = options[:reconnect_delay_max].to_f options[:db] = options[:db].to_i options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last case options[:tcp_keepalive] when Hash [:time, :intvl, :probes].each do |key| unless options[:tcp_keepalive][key].is_a?(Integer) raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer" end end when Integer if options[:tcp_keepalive] >= 60 options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2} elsif options[:tcp_keepalive] >= 30 options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2} elsif options[:tcp_keepalive] >= 5 options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1} end end options[:_parsed] = true options end def _parse_driver(driver) driver = driver.to_s if driver.is_a?(Symbol) if driver.kind_of?(String) begin require_relative "connection/#{driver}" rescue LoadError, NameError => e begin require "connection/#{driver}" rescue LoadError, NameError => e raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}" end end driver = Connection.const_get(driver.capitalize) end driver end class Connector def initialize(options) @options = options.dup end def resolve @options end def check(client) end class Sentinel < Connector def initialize(options) super(options) @options[:db] = DEFAULTS.fetch(:db) @sentinels = @options.delete(:sentinels).dup @role = @options.fetch(:role, "master").to_s @master = @options[:host] end def check(client) # Check the instance is really of the role we are looking for. # We can't assume the command is supported since it was introduced # recently and this client should work with old stuff. begin role = client.call([:role])[0] rescue Redis::CommandError # Assume the test is passed if we can't get a reply from ROLE... role = @role end if role != @role client.disconnect raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}." end end def resolve result = case @role when "master" resolve_master when "slave" resolve_slave else raise ArgumentError, "Unknown instance role #{@role}" end result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.") end def sentinel_detect @sentinels.each do |sentinel| client = Client.new(@options.merge({ :host => sentinel[:host], :port => sentinel[:port], password: sentinel[:password], :reconnect_attempts => 0, })) begin if result = yield(client) # This sentinel responded. Make sure we ask it first next time. @sentinels.delete(sentinel) @sentinels.unshift(sentinel) return result end rescue BaseConnectionError ensure client.disconnect end end raise CannotConnectError, "No sentinels available." end def resolve_master sentinel_detect do |client| if reply = client.call(["sentinel", "get-master-addr-by-name", @master]) {:host => reply[0], :port => reply[1]} end end end def resolve_slave sentinel_detect do |client| if reply = client.call(["sentinel", "slaves", @master]) slaves = reply.map { |s| s.each_slice(2).to_h } slaves.each { |s| s['flags'] = s.fetch('flags').split(',') } slaves.reject! { |s| s.fetch('flags').include?('s_down') } if slaves.empty? raise CannotConnectError, 'No slaves available.' else slave = slaves.sample { host: slave.fetch('ip'), port: slave.fetch('port'), } end end end end end end end end redis-rb-4.1.2/lib/redis/cluster.rb000066400000000000000000000213461347371274700171340ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'errors' require_relative 'client' require_relative 'cluster/command' require_relative 'cluster/command_loader' require_relative 'cluster/key_slot_converter' require_relative 'cluster/node' require_relative 'cluster/node_key' require_relative 'cluster/node_loader' require_relative 'cluster/option' require_relative 'cluster/slot' require_relative 'cluster/slot_loader' class Redis # Redis Cluster client # # @see https://github.com/antirez/redis-rb-cluster POC implementation # @see https://redis.io/topics/cluster-spec Redis Cluster specification # @see https://redis.io/topics/cluster-tutorial Redis Cluster tutorial # # Copyright (C) 2013 Salvatore Sanfilippo class Cluster def initialize(options = {}) @option = Option.new(options) @node, @slot = fetch_cluster_info!(@option) @command = fetch_command_details(@node) end def id @node.map(&:id).sort.join(' ') end # db feature is disabled in cluster mode def db 0 end # db feature is disabled in cluster mode def db=(_db); end def timeout @node.first.timeout end def connected? @node.any?(&:connected?) end def disconnect @node.each(&:disconnect) true end def connection_info @node.sort_by(&:id).map do |client| { host: client.host, port: client.port, db: client.db, id: client.id, location: client.location } end end def with_reconnect(val = true, &block) try_send(@node.sample, :with_reconnect, val, &block) end def call(command, &block) send_command(command, &block) end def call_loop(command, timeout = 0, &block) node = assign_node(command) try_send(node, :call_loop, command, timeout, &block) end def call_pipeline(pipeline) node_keys, command_keys = extract_keys_in_pipeline(pipeline) raise CrossSlotPipeliningError, command_keys if node_keys.size > 1 node = find_node(node_keys.first) try_send(node, :call_pipeline, pipeline) end def call_with_timeout(command, timeout, &block) node = assign_node(command) try_send(node, :call_with_timeout, command, timeout, &block) end def call_without_timeout(command, &block) call_with_timeout(command, 0, &block) end def process(commands, &block) if commands.size == 1 && %w[unsubscribe punsubscribe].include?(commands.first.first.to_s.downcase) && commands.first.size == 1 # Node is indeterminate. We do just a best-effort try here. @node.process_all(commands, &block) else node = assign_node(commands.first) try_send(node, :process, commands, &block) end end private def fetch_cluster_info!(option) node = Node.new(option.per_node_key) available_slots = SlotLoader.load(node) node_flags = NodeLoader.load_flags(node) available_node_urls = NodeKey.to_node_urls(available_slots.keys, secure: option.secure?) option.update_node(available_node_urls) [Node.new(option.per_node_key, node_flags, option.use_replica?), Slot.new(available_slots, node_flags, option.use_replica?)] ensure node.map(&:disconnect) end def fetch_command_details(nodes) details = CommandLoader.load(nodes) Command.new(details) end def send_command(command, &block) cmd = command.first.to_s.downcase case cmd when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save' @node.call_all(command, &block).first when 'flushall', 'flushdb' @node.call_master(command, &block).first when 'wait' then @node.call_master(command, &block).reduce(:+) when 'keys' then @node.call_slave(command, &block).flatten.sort when 'dbsize' then @node.call_slave(command, &block).reduce(:+) when 'lastsave' then @node.call_all(command, &block).sort when 'role' then @node.call_all(command, &block) when 'config' then send_config_command(command, &block) when 'client' then send_client_command(command, &block) when 'cluster' then send_cluster_command(command, &block) when 'readonly', 'readwrite', 'shutdown' raise OrchestrationCommandNotSupported, cmd when 'memory' then send_memory_command(command, &block) when 'script' then send_script_command(command, &block) when 'pubsub' then send_pubsub_command(command, &block) when 'discard', 'exec', 'multi', 'unwatch' raise AmbiguousNodeError, cmd else node = assign_node(command) try_send(node, :call, command, &block) end end def send_config_command(command, &block) case command[1].to_s.downcase when 'resetstat', 'rewrite', 'set' @node.call_all(command, &block).first else assign_node(command).call(command, &block) end end def send_memory_command(command, &block) case command[1].to_s.downcase when 'stats' then @node.call_all(command, &block) when 'purge' then @node.call_all(command, &block).first else assign_node(command).call(command, &block) end end def send_client_command(command, &block) case command[1].to_s.downcase when 'list' then @node.call_all(command, &block).flatten when 'pause', 'reply', 'setname' @node.call_all(command, &block).first else assign_node(command).call(command, &block) end end def send_cluster_command(command, &block) subcommand = command[1].to_s.downcase case subcommand when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate', 'reset', 'set-config-epoch', 'setslot' raise OrchestrationCommandNotSupported, 'cluster', subcommand when 'saveconfig' then @node.call_all(command, &block).first else assign_node(command).call(command, &block) end end def send_script_command(command, &block) case command[1].to_s.downcase when 'debug', 'kill' @node.call_all(command, &block).first when 'flush', 'load' @node.call_master(command, &block).first else assign_node(command).call(command, &block) end end def send_pubsub_command(command, &block) case command[1].to_s.downcase when 'channels' then @node.call_all(command, &block).flatten.uniq.sort when 'numsub' @node.call_all(command, &block).reject(&:empty?).map { |e| Hash[*e] } .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } } when 'numpat' then @node.call_all(command, &block).reduce(:+) else assign_node(command).call(command, &block) end end # @see https://redis.io/topics/cluster-spec#redirection-and-resharding # Redirection and resharding def try_send(node, method_name, *args, retry_count: 3, &block) node.public_send(method_name, *args, &block) rescue CommandError => err if err.message.start_with?('MOVED') assign_redirection_node(err.message).public_send(method_name, *args, &block) elsif err.message.start_with?('ASK') raise if retry_count <= 0 node = assign_asking_node(err.message) node.call(%i[asking]) retry_count -= 1 retry else raise end end def assign_redirection_node(err_msg) _, slot, node_key = err_msg.split(' ') slot = slot.to_i @slot.put(slot, node_key) find_node(node_key) end def assign_asking_node(err_msg) _, _, node_key = err_msg.split(' ') find_node(node_key) end def assign_node(command) node_key = find_node_key(command) find_node(node_key) end def find_node_key(command) key = @command.extract_first_key(command) return if key.empty? slot = KeySlotConverter.convert(key) return unless @slot.exists?(slot) if @command.should_send_to_master?(command) @slot.find_node_key_of_master(slot) else @slot.find_node_key_of_slave(slot) end end def find_node(node_key) return @node.sample if node_key.nil? @node.find_by(node_key) rescue Node::ReloadNeeded update_cluster_info!(node_key) @node.find_by(node_key) end def update_cluster_info!(node_key = nil) unless node_key.nil? host, port = NodeKey.split(node_key) @option.add_node(host, port) end @node.map(&:disconnect) @node, @slot = fetch_cluster_info!(@option) end def extract_keys_in_pipeline(pipeline) node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?) [node_keys, command_keys] end end end redis-rb-4.1.2/lib/redis/cluster/000077500000000000000000000000001347371274700166015ustar00rootroot00000000000000redis-rb-4.1.2/lib/redis/cluster/command.rb000066400000000000000000000042471347371274700205530ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../errors' class Redis class Cluster # Keep details about Redis commands for Redis Cluster Client. # @see https://redis.io/commands/command class Command def initialize(details) @details = pick_details(details) end def extract_first_key(command) i = determine_first_key_position(command) return '' if i == 0 key = command[i].to_s hash_tag = extract_hash_tag(key) hash_tag.empty? ? key : hash_tag end def should_send_to_master?(command) dig_details(command, :write) end def should_send_to_slave?(command) dig_details(command, :readonly) end private def pick_details(details) details.map do |command, detail| [command, { first_key_position: detail[:first], write: detail[:flags].include?('write'), readonly: detail[:flags].include?('readonly') }] end.to_h end def dig_details(command, key) name = command.first.to_s return unless @details.key?(name) @details.fetch(name).fetch(key) end def determine_first_key_position(command) case command.first.to_s.downcase when 'eval', 'evalsha', 'migrate', 'zinterstore', 'zunionstore' then 3 when 'object' then 2 when 'memory' command[1].to_s.casecmp('usage').zero? ? 2 : 0 when 'scan', 'sscan', 'hscan', 'zscan' determine_optional_key_position(command, 'match') when 'xread', 'xreadgroup' determine_optional_key_position(command, 'streams') else dig_details(command, :first_key_position).to_i end end def determine_optional_key_position(command, option_name) idx = command.map(&:to_s).map(&:downcase).index(option_name) idx.nil? ? 0 : idx + 1 end # @see https://redis.io/topics/cluster-spec#keys-hash-tags Keys hash tags def extract_hash_tag(key) s = key.index('{') e = key.index('}', s.to_i + 1) return '' if s.nil? || e.nil? key[s + 1..e - 1] end end end end redis-rb-4.1.2/lib/redis/cluster/command_loader.rb000066400000000000000000000015061347371274700220740ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../errors' class Redis class Cluster # Load details about Redis commands for Redis Cluster Client # @see https://redis.io/commands/command module CommandLoader module_function def load(nodes) details = {} nodes.each do |node| details = fetch_command_details(node) details.empty? ? next : break end details end def fetch_command_details(node) node.call(%i[command]).map do |reply| [reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }] end.to_h rescue CannotConnectError, ConnectionError, CommandError {} # can retry on another node end private_class_method :fetch_command_details end end end redis-rb-4.1.2/lib/redis/cluster/key_slot_converter.rb000066400000000000000000000063761347371274700230620ustar00rootroot00000000000000# frozen_string_literal: true class Redis class Cluster # Key to slot converter for Redis Cluster Client # # We can test it by `CLUSTER KEYSLOT` command. # # @see https://github.com/antirez/redis-rb-cluster # Reference implementation in Ruby # @see https://redis.io/topics/cluster-spec#appendix # Reference implementation in ANSI C # @see https://redis.io/commands/cluster-keyslot # CLUSTER KEYSLOT command reference # # Copyright (C) 2013 Salvatore Sanfilippo module KeySlotConverter XMODEM_CRC16_LOOKUP = [ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 ].freeze HASH_SLOTS = 16_384 module_function # Convert key into slot. # # @param key [String] the key of the redis command # # @return [Integer] slot number def convert(key) crc = 0 key.each_byte do |b| crc = ((crc << 8) & 0xffff) ^ XMODEM_CRC16_LOOKUP[((crc >> 8) ^ b) & 0xff] end crc % HASH_SLOTS end end end end redis-rb-4.1.2/lib/redis/cluster/node.rb000066400000000000000000000044711347371274700200610ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../errors' class Redis class Cluster # Keep client list of node for Redis Cluster Client class Node include Enumerable ReloadNeeded = Class.new(StandardError) ROLE_SLAVE = 'slave' def initialize(options, node_flags = {}, with_replica = false) @with_replica = with_replica @node_flags = node_flags @clients = build_clients(options) end def each(&block) @clients.values.each(&block) end def sample @clients.values.sample end def find_by(node_key) @clients.fetch(node_key) rescue KeyError raise ReloadNeeded end def call_all(command, &block) try_map { |_, client| client.call(command, &block) }.values end def call_master(command, &block) try_map do |node_key, client| next if slave?(node_key) client.call(command, &block) end.values end def call_slave(command, &block) return call_master(command, &block) if replica_disabled? try_map do |node_key, client| next if master?(node_key) client.call(command, &block) end.values end def process_all(commands, &block) try_map { |_, client| client.process(commands, &block) }.values end private def replica_disabled? !@with_replica end def master?(node_key) !slave?(node_key) end def slave?(node_key) @node_flags[node_key] == ROLE_SLAVE end def build_clients(options) clients = options.map do |node_key, option| next if replica_disabled? && slave?(node_key) client = Client.new(option) client.call(%i[readonly]) if slave?(node_key) [node_key, client] end clients.compact.to_h end def try_map errors = {} results = {} @clients.each do |node_key, client| begin reply = yield(node_key, client) results[node_key] = reply unless reply.nil? rescue CommandError => err errors[node_key] = err next end end return results if errors.empty? raise CommandErrorCollection, errors end end end end redis-rb-4.1.2/lib/redis/cluster/node_key.rb000066400000000000000000000015251347371274700207260ustar00rootroot00000000000000# frozen_string_literal: true class Redis class Cluster # Node key's format is `:`. # It is different from node id. # Node id is internal identifying code in Redis Cluster. module NodeKey DEFAULT_SCHEME = 'redis' SECURE_SCHEME = 'rediss' DELIMITER = ':' module_function def to_node_urls(node_keys, secure:) scheme = secure ? SECURE_SCHEME : DEFAULT_SCHEME node_keys .map { |k| k.split(DELIMITER) } .map { |k| URI::Generic.build(scheme: scheme, host: k[0], port: k[1].to_i).to_s } end def split(node_key) node_key.split(DELIMITER) end def build_from_uri(uri) "#{uri.host}#{DELIMITER}#{uri.port}" end def build_from_host_port(host, port) "#{host}#{DELIMITER}#{port}" end end end end redis-rb-4.1.2/lib/redis/cluster/node_loader.rb000066400000000000000000000016341347371274700214050ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../errors' class Redis class Cluster # Load and hashify node info for Redis Cluster Client module NodeLoader module_function def load_flags(nodes) info = {} nodes.each do |node| info = fetch_node_info(node) info.empty? ? next : break end return info unless info.empty? raise CannotConnectError, 'Redis client could not connect to any cluster nodes' end def fetch_node_info(node) node.call(%i[cluster nodes]) .split("\n") .map { |str| str.split(' ') } .map { |arr| [arr[1].split('@').first, (arr[2].split(',') & %w[master slave]).first] } .to_h rescue CannotConnectError, ConnectionError, CommandError {} # can retry on another node end private_class_method :fetch_node_info end end end redis-rb-4.1.2/lib/redis/cluster/option.rb000066400000000000000000000042621347371274700204420ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../errors' require_relative 'node_key' require 'uri' class Redis class Cluster # Keep options for Redis Cluster Client class Option DEFAULT_SCHEME = 'redis' SECURE_SCHEME = 'rediss' VALID_SCHEMES = [DEFAULT_SCHEME, SECURE_SCHEME].freeze def initialize(options) options = options.dup node_addrs = options.delete(:cluster) @node_uris = build_node_uris(node_addrs) @replica = options.delete(:replica) == true @options = options end def per_node_key @node_uris.map { |uri| [NodeKey.build_from_uri(uri), @options.merge(url: uri.to_s)] } .to_h end def secure? @node_uris.any? { |uri| uri.scheme == SECURE_SCHEME } || @options[:ssl_params] || false end def use_replica? @replica end def update_node(addrs) @node_uris = build_node_uris(addrs) end def add_node(host, port) @node_uris << parse_node_hash(host: host, port: port) end private def build_node_uris(addrs) raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array) addrs.map { |addr| parse_node_addr(addr) } end def parse_node_addr(addr) case addr when String parse_node_url(addr) when Hash parse_node_hash(addr) else raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash' end end def parse_node_url(addr) uri = URI(addr) raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme) uri rescue URI::InvalidURIError => err raise InvalidClientOptionError, err.message end def parse_node_hash(addr) addr = addr.map { |k, v| [k.to_sym, v] }.to_h raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys' if addr.values_at(:host, :port).any?(&:nil?) URI::Generic.build(scheme: DEFAULT_SCHEME, host: addr[:host], port: addr[:port].to_i) end end end end redis-rb-4.1.2/lib/redis/cluster/slot.rb000066400000000000000000000030711347371274700201100ustar00rootroot00000000000000# frozen_string_literal: true require 'set' class Redis class Cluster # Keep slot and node key map for Redis Cluster Client class Slot ROLE_SLAVE = 'slave' def initialize(available_slots, node_flags = {}, with_replica = false) @with_replica = with_replica @node_flags = node_flags @map = build_slot_node_key_map(available_slots) end def exists?(slot) @map.key?(slot) end def find_node_key_of_master(slot) return nil unless exists?(slot) @map[slot][:master] end def find_node_key_of_slave(slot) return nil unless exists?(slot) return find_node_key_of_master(slot) if replica_disabled? @map[slot][:slaves].to_a.sample end def put(slot, node_key) assign_node_key(@map, slot, node_key) nil end private def replica_disabled? !@with_replica end def master?(node_key) !slave?(node_key) end def slave?(node_key) @node_flags[node_key] == ROLE_SLAVE end def build_slot_node_key_map(available_slots) available_slots.each_with_object({}) do |(node_key, slots), acc| slots.each { |slot| assign_node_key(acc, slot, node_key) } end end def assign_node_key(mappings, slot, node_key) mappings[slot] ||= { master: nil, slaves: ::Set.new } if master?(node_key) mappings[slot][:master] = node_key else mappings[slot][:slaves].add(node_key) end end end end end redis-rb-4.1.2/lib/redis/cluster/slot_loader.rb000066400000000000000000000025041347371274700214360ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../errors' require_relative 'node_key' class Redis class Cluster # Load and hashify slot info for Redis Cluster Client module SlotLoader module_function def load(nodes) info = {} nodes.each do |node| info = Hash[*fetch_slot_info(node)] info.empty? ? next : break end return info unless info.empty? raise CannotConnectError, 'Redis client could not connect to any cluster nodes' end def fetch_slot_info(node) node.call(%i[cluster slots]) .map { |arr| parse_slot_info(arr, default_ip: node.host) } .flatten rescue CannotConnectError, ConnectionError, CommandError {} # can retry on another node end def parse_slot_info(arr, default_ip:) first_slot, last_slot = arr[0..1] slot_range = (first_slot..last_slot).freeze arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] } .flatten end def stringify_node_key(arr, default_ip) ip, port = arr ip = default_ip if ip.empty? # When cluster is down NodeKey.build_from_host_port(ip, port) end private_class_method :fetch_slot_info, :parse_slot_info, :stringify_node_key end end end redis-rb-4.1.2/lib/redis/connection.rb000066400000000000000000000010201347371274700175750ustar00rootroot00000000000000require_relative "connection/registry" # If a connection driver was required before this file, the array # Redis::Connection.drivers will contain one or more classes. The last driver # in this array will be used as default driver. If this array is empty, we load # the plain Ruby driver as our default. Another driver can be required at a # later point in time, causing it to be the last element of the #drivers array # and therefore be chosen by default. require_relative "connection/ruby" if Redis::Connection.drivers.empty? redis-rb-4.1.2/lib/redis/connection/000077500000000000000000000000001347371274700172575ustar00rootroot00000000000000redis-rb-4.1.2/lib/redis/connection/command_helper.rb000066400000000000000000000013511347371274700225610ustar00rootroot00000000000000class Redis module Connection module CommandHelper COMMAND_DELIMITER = "\r\n" def build_command(args) command = [nil] args.each do |i| if i.is_a? Array i.each do |j| j = j.to_s command << "$#{j.bytesize}" command << j end else i = i.to_s command << "$#{i.bytesize}" command << i end end command[0] = "*#{(command.length - 1) / 2}" # Trailing delimiter command << "" command.join(COMMAND_DELIMITER) end protected def encode(string) string.force_encoding(Encoding.default_external) end end end end redis-rb-4.1.2/lib/redis/connection/hiredis.rb000066400000000000000000000032511347371274700212340ustar00rootroot00000000000000require_relative "registry" require_relative "../errors" require "hiredis/connection" require "timeout" class Redis module Connection class Hiredis def self.connect(config) connection = ::Hiredis::Connection.new connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i if config[:scheme] == "unix" connection.connect_unix(config[:path], connect_timeout) elsif config[:scheme] == "rediss" || config[:ssl] raise NotImplementedError, "SSL not supported by hiredis driver" else connection.connect(config[:host], config[:port], connect_timeout) end instance = new(connection) instance.timeout = config[:read_timeout] instance rescue Errno::ETIMEDOUT raise TimeoutError end def initialize(connection) @connection = connection end def connected? @connection && @connection.connected? end def timeout=(timeout) # Hiredis works with microsecond timeouts @connection.timeout = Integer(timeout * 1_000_000) end def disconnect @connection.disconnect @connection = nil end def write(command) @connection.write(command.flatten(1)) rescue Errno::EAGAIN raise TimeoutError end def read reply = @connection.read reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError) reply rescue Errno::EAGAIN raise TimeoutError rescue RuntimeError => err raise ProtocolError.new(err.message) end end end end Redis::Connection.drivers << Redis::Connection::Hiredis redis-rb-4.1.2/lib/redis/connection/registry.rb000066400000000000000000000005441347371274700214570ustar00rootroot00000000000000class Redis module Connection # Store a list of loaded connection drivers in the Connection module. # Redis::Client uses the last required driver by default, and will be aware # of the loaded connection drivers if the user chooses to override the # default connection driver. def self.drivers @drivers ||= [] end end end redis-rb-4.1.2/lib/redis/connection/ruby.rb000066400000000000000000000265271347371274700206010ustar00rootroot00000000000000require_relative "registry" require_relative "command_helper" require_relative "../errors" require "socket" require "timeout" begin require "openssl" rescue LoadError # Not all systems have OpenSSL support end class Redis module Connection module SocketMixin CRLF = "\r\n".freeze def initialize(*args) super(*args) @timeout = @write_timeout = nil @buffer = "".dup end def timeout=(timeout) if timeout && timeout > 0 @timeout = timeout else @timeout = nil end end def write_timeout=(timeout) if timeout && timeout > 0 @write_timeout = timeout else @write_timeout = nil end end def read(nbytes) result = @buffer.slice!(0, nbytes) while result.bytesize < nbytes result << _read_from_socket(nbytes - result.bytesize) end result end def gets crlf = nil while (crlf = @buffer.index(CRLF)) == nil @buffer << _read_from_socket(1024) end @buffer.slice!(0, crlf + CRLF.bytesize) end def _read_from_socket(nbytes) begin read_nonblock(nbytes) rescue IO::WaitReadable if IO.select([self], nil, nil, @timeout) retry else raise Redis::TimeoutError end rescue IO::WaitWritable if IO.select(nil, [self], nil, @timeout) retry else raise Redis::TimeoutError end end rescue EOFError raise Errno::ECONNRESET end def _write_to_socket(data) begin write_nonblock(data) rescue IO::WaitWritable if IO.select(nil, [self], nil, @write_timeout) retry else raise Redis::TimeoutError end rescue IO::WaitReadable if IO.select([self], nil, nil, @write_timeout) retry else raise Redis::TimeoutError end end rescue EOFError raise Errno::ECONNRESET end def write(data) return super(data) unless @write_timeout length = data.bytesize total_count = 0 loop do count = _write_to_socket(data) total_count += count return total_count if total_count >= length data = data.byteslice(count..-1) end end end if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" require "timeout" class TCPSocket < ::TCPSocket include SocketMixin def self.connect(host, port, timeout) Timeout.timeout(timeout) do sock = new(host, port) sock end rescue Timeout::Error raise TimeoutError end end if defined?(::UNIXSocket) class UNIXSocket < ::UNIXSocket include SocketMixin def self.connect(path, timeout) Timeout.timeout(timeout) do sock = new(path) sock end rescue Timeout::Error raise TimeoutError end # JRuby raises Errno::EAGAIN on #read_nonblock even when IO.select # says it is readable (1.6.6, in both 1.8 and 1.9 mode). # Use the blocking #readpartial method instead. def _read_from_socket(nbytes) readpartial(nbytes) rescue EOFError raise Errno::ECONNRESET end end end else class TCPSocket < ::Socket include SocketMixin def self.connect_addrinfo(ai, port, timeout) sock = new(::Socket.const_get(ai[0]), Socket::SOCK_STREAM, 0) sockaddr = ::Socket.pack_sockaddr_in(port, ai[3]) begin sock.connect_nonblock(sockaddr) rescue Errno::EINPROGRESS if IO.select(nil, [sock], nil, timeout) == nil raise TimeoutError end begin sock.connect_nonblock(sockaddr) rescue Errno::EISCONN end end sock end def self.connect(host, port, timeout) # Don't pass AI_ADDRCONFIG as flag to getaddrinfo(3) # # From the man page for getaddrinfo(3): # # If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4 # addresses are returned in the list pointed to by res only if the # local system has at least one IPv4 address configured, and IPv6 # addresses are returned only if the local system has at least one # IPv6 address configured. The loopback address is not considered # for this case as valid as a configured address. # # We do want the IPv6 loopback address to be returned if applicable, # even if it is the only configured IPv6 address on the machine. # Also see: https://github.com/redis/redis-rb/pull/394. addrinfo = ::Socket.getaddrinfo(host, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM) # From the man page for getaddrinfo(3): # # Normally, the application should try using the addresses in the # order in which they are returned. The sorting function used # within getaddrinfo() is defined in RFC 3484 [...]. # addrinfo.each_with_index do |ai, i| begin return connect_addrinfo(ai, port, timeout) rescue SystemCallError # Raise if this was our last attempt. raise if addrinfo.length == i+1 end end end end class UNIXSocket < ::Socket include SocketMixin def self.connect(path, timeout) sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0) sockaddr = ::Socket.pack_sockaddr_un(path) begin sock.connect_nonblock(sockaddr) rescue Errno::EINPROGRESS if IO.select(nil, [sock], nil, timeout) == nil raise TimeoutError end begin sock.connect_nonblock(sockaddr) rescue Errno::EISCONN end end sock end end end if defined?(OpenSSL) class SSLSocket < ::OpenSSL::SSL::SSLSocket include SocketMixin def self.connect(host, port, timeout, ssl_params) # Note: this is using Redis::Connection::TCPSocket tcp_sock = TCPSocket.connect(host, port, timeout) ctx = OpenSSL::SSL::SSLContext.new ctx.set_params(ssl_params) if ssl_params && !ssl_params.empty? ssl_sock = new(tcp_sock, ctx) ssl_sock.hostname = host begin # Initiate the socket connection in the background. If it doesn't fail # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS) # indicating the connection is in progress. # Unlike waiting for a tcp socket to connect, you can't time out ssl socket # connections during the connect phase properly, because IO.select only partially works. # Instead, you have to retry. ssl_sock.connect_nonblock rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable if IO.select([ssl_sock], nil, nil, timeout) retry else raise TimeoutError end rescue IO::WaitWritable if IO.select(nil, [ssl_sock], nil, timeout) retry else raise TimeoutError end end unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE ssl_sock.post_connection_check(host) end ssl_sock end end end class Ruby include Redis::Connection::CommandHelper MINUS = "-".freeze PLUS = "+".freeze COLON = ":".freeze DOLLAR = "$".freeze ASTERISK = "*".freeze def self.connect(config) if config[:scheme] == "unix" raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl] sock = UNIXSocket.connect(config[:path], config[:connect_timeout]) elsif config[:scheme] == "rediss" || config[:ssl] sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params]) else sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout]) end instance = new(sock) instance.timeout = config[:read_timeout] instance.write_timeout = config[:write_timeout] instance.set_tcp_keepalive config[:tcp_keepalive] instance end if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c} def set_tcp_keepalive(keepalive) return unless keepalive.is_a?(Hash) @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time]) @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl]) @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes]) end def get_tcp_keepalive { :time => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int, :intvl => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int, :probes => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int, } end else def set_tcp_keepalive(keepalive) end def get_tcp_keepalive { } end end def initialize(sock) @sock = sock end def connected? !! @sock end def disconnect @sock.close rescue ensure @sock = nil end def timeout=(timeout) if @sock.respond_to?(:timeout=) @sock.timeout = timeout end end def write_timeout=(timeout) @sock.write_timeout = timeout end def write(command) @sock.write(build_command(command)) end def read line = @sock.gets reply_type = line.slice!(0, 1) format_reply(reply_type, line) rescue Errno::EAGAIN raise TimeoutError end def format_reply(reply_type, line) case reply_type when MINUS then format_error_reply(line) when PLUS then format_status_reply(line) when COLON then format_integer_reply(line) when DOLLAR then format_bulk_reply(line) when ASTERISK then format_multi_bulk_reply(line) else raise ProtocolError.new(reply_type) end end def format_error_reply(line) CommandError.new(line.strip) end def format_status_reply(line) line.strip end def format_integer_reply(line) line.to_i end def format_bulk_reply(line) bulklen = line.to_i return if bulklen == -1 reply = encode(@sock.read(bulklen)) @sock.read(2) # Discard CRLF. reply end def format_multi_bulk_reply(line) n = line.to_i return if n == -1 Array.new(n) { read } end end end end Redis::Connection.drivers << Redis::Connection::Ruby redis-rb-4.1.2/lib/redis/connection/synchrony.rb000066400000000000000000000062711347371274700216460ustar00rootroot00000000000000require_relative "command_helper" require_relative "registry" require_relative "../errors" require "em-synchrony" require "hiredis/reader" class Redis module Connection class RedisClient < EventMachine::Connection include EventMachine::Deferrable attr_accessor :timeout def post_init @req = nil @connected = false @reader = ::Hiredis::Reader.new end def connection_completed @connected = true succeed end def connected? @connected end def receive_data(data) @reader.feed(data) loop do begin reply = @reader.gets rescue RuntimeError => err @req.fail [:error, ProtocolError.new(err.message)] break end break if reply == false reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError) @req.succeed [:reply, reply] end end def read @req = EventMachine::DefaultDeferrable.new if @timeout > 0 @req.timeout(@timeout, :timeout) end EventMachine::Synchrony.sync @req end def send(data) callback { send_data data } end def unbind @connected = false if @req @req.fail [:error, Errno::ECONNRESET] @req = nil else fail end end end class Synchrony include Redis::Connection::CommandHelper def self.connect(config) if config[:scheme] == "unix" begin conn = EventMachine.connect_unix_domain(config[:path], RedisClient) rescue RuntimeError => e if e.message == "no connection" raise Errno::ECONNREFUSED else raise e end end elsif config[:scheme] == "rediss" || config[:ssl] raise NotImplementedError, "SSL not supported by synchrony driver" else conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c| c.pending_connect_timeout = [config[:connect_timeout], 0.1].max end end fiber = Fiber.current conn.callback { fiber.resume } conn.errback { fiber.resume :refused } raise Errno::ECONNREFUSED if Fiber.yield == :refused instance = new(conn) instance.timeout = config[:read_timeout] instance end def initialize(connection) @connection = connection end def connected? @connection && @connection.connected? end def timeout=(timeout) @connection.timeout = timeout end def disconnect @connection.close_connection @connection = nil end def write(command) @connection.send(build_command(command)) end def read type, payload = @connection.read if type == :reply payload elsif type == :error raise payload elsif type == :timeout raise TimeoutError else raise "Unknown type #{type.inspect}" end end end end end Redis::Connection.drivers << Redis::Connection::Synchrony redis-rb-4.1.2/lib/redis/distributed.rb000066400000000000000000000554321347371274700200000ustar00rootroot00000000000000require_relative "hash_ring" class Redis class Distributed class CannotDistribute < RuntimeError def initialize(command) @command = command end def message "#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic." end end attr_reader :ring def initialize(node_configs, options = {}) @tag = options[:tag] || /^\{(.+?)\}/ @ring = options[:ring] || HashRing.new @node_configs = node_configs.dup @default_options = options.dup node_configs.each { |node_config| add_node(node_config) } @subscribed_node = nil end def node_for(key) @ring.get_node(key_tag(key.to_s) || key.to_s) end def nodes @ring.nodes end def add_node(options) options = { :url => options } if options.is_a?(String) options = @default_options.merge(options) @ring.add_node Redis.new( options ) end # Change the selected database for the current connection. def select(db) on_each_node :select, db end # Ping the server. def ping on_each_node :ping end # Echo the given string. def echo(value) on_each_node :echo, value end # Close the connection. def quit on_each_node :quit end # Asynchronously save the dataset to disk. def bgsave on_each_node :bgsave end # Return the number of keys in the selected database. def dbsize on_each_node :dbsize end # Remove all keys from all databases. def flushall on_each_node :flushall end # Remove all keys from the current database. def flushdb on_each_node :flushdb end # Get information and statistics about the server. def info(cmd = nil) on_each_node :info, cmd end # Get the UNIX time stamp of the last successful save to disk. def lastsave on_each_node :lastsave end # Listen for all requests received by the server in real time. def monitor raise NotImplementedError end # Synchronously save the dataset to disk. def save on_each_node :save end # Get server time: an UNIX timestamp and the elapsed microseconds in the current second. def time on_each_node :time end # Remove the expiration from a key. def persist(key) node_for(key).persist(key) end # Set a key's time to live in seconds. def expire(key, seconds) node_for(key).expire(key, seconds) end # Set the expiration for a key as a UNIX timestamp. def expireat(key, unix_time) node_for(key).expireat(key, unix_time) end # Get the time to live (in seconds) for a key. def ttl(key) node_for(key).ttl(key) end # Set a key's time to live in milliseconds. def pexpire(key, milliseconds) node_for(key).pexpire(key, milliseconds) end # Set the expiration for a key as number of milliseconds from UNIX Epoch. def pexpireat(key, ms_unix_time) node_for(key).pexpireat(key, ms_unix_time) end # Get the time to live (in milliseconds) for a key. def pttl(key) node_for(key).pttl(key) end # Return a serialized version of the value stored at a key. def dump(key) node_for(key).dump(key) end # Create a key using the serialized value, previously obtained using DUMP. def restore(key, ttl, serialized_value, options = {}) node_for(key).restore(key, ttl, serialized_value, options) end # Transfer a key from the connected instance to another instance. def migrate(key, options) raise CannotDistribute, :migrate end # Delete a key. def del(*args) keys_per_node = args.group_by { |key| node_for(key) } keys_per_node.inject(0) do |sum, (node, keys)| sum + node.del(*keys) end end # Unlink keys. def unlink(*args) keys_per_node = args.group_by { |key| node_for(key) } keys_per_node.inject(0) do |sum, (node, keys)| sum + node.unlink(*keys) end end # Determine if a key exists. def exists(key) node_for(key).exists(key) end # Find all keys matching the given pattern. def keys(glob = "*") on_each_node(:keys, glob).flatten end # Move a key to another database. def move(key, db) node_for(key).move(key, db) end # Return a random key from the keyspace. def randomkey raise CannotDistribute, :randomkey end # Rename a key. def rename(old_name, new_name) ensure_same_node(:rename, [old_name, new_name]) do |node| node.rename(old_name, new_name) end end # Rename a key, only if the new key does not exist. def renamenx(old_name, new_name) ensure_same_node(:renamenx, [old_name, new_name]) do |node| node.renamenx(old_name, new_name) end end # Sort the elements in a list, set or sorted set. def sort(key, options = {}) keys = [key, options[:by], options[:store], *Array(options[:get])].compact ensure_same_node(:sort, keys) do |node| node.sort(key, options) end end # Determine the type stored at key. def type(key) node_for(key).type(key) end # Decrement the integer value of a key by one. def decr(key) node_for(key).decr(key) end # Decrement the integer value of a key by the given number. def decrby(key, decrement) node_for(key).decrby(key, decrement) end # Increment the integer value of a key by one. def incr(key) node_for(key).incr(key) end # Increment the integer value of a key by the given integer number. def incrby(key, increment) node_for(key).incrby(key, increment) end # Increment the numeric value of a key by the given float number. def incrbyfloat(key, increment) node_for(key).incrbyfloat(key, increment) end # Set the string value of a key. def set(key, value, options = {}) node_for(key).set(key, value, options) end # Set the time to live in seconds of a key. def setex(key, ttl, value) node_for(key).setex(key, ttl, value) end # Set the time to live in milliseconds of a key. def psetex(key, ttl, value) node_for(key).psetex(key, ttl, value) end # Set the value of a key, only if the key does not exist. def setnx(key, value) node_for(key).setnx(key, value) end # Set multiple keys to multiple values. def mset(*args) raise CannotDistribute, :mset end def mapped_mset(hash) raise CannotDistribute, :mapped_mset end # Set multiple keys to multiple values, only if none of the keys exist. def msetnx(*args) raise CannotDistribute, :msetnx end def mapped_msetnx(hash) raise CannotDistribute, :mapped_msetnx end # Get the value of a key. def get(key) node_for(key).get(key) end # Get the values of all the given keys as an Array. def mget(*keys) mapped_mget(*keys).values_at(*keys) end # Get the values of all the given keys as a Hash. def mapped_mget(*keys) keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)| results.merge! node.mapped_mget(*subkeys) end end # Overwrite part of a string at key starting at the specified offset. def setrange(key, offset, value) node_for(key).setrange(key, offset, value) end # Get a substring of the string stored at a key. def getrange(key, start, stop) node_for(key).getrange(key, start, stop) end # Sets or clears the bit at offset in the string value stored at key. def setbit(key, offset, value) node_for(key).setbit(key, offset, value) end # Returns the bit value at offset in the string value stored at key. def getbit(key, offset) node_for(key).getbit(key, offset) end # Append a value to a key. def append(key, value) node_for(key).append(key, value) end # Count the number of set bits in a range of the string value stored at key. def bitcount(key, start = 0, stop = -1) node_for(key).bitcount(key, start, stop) end # Perform a bitwise operation between strings and store the resulting string in a key. def bitop(operation, destkey, *keys) ensure_same_node(:bitop, [destkey] + keys) do |node| node.bitop(operation, destkey, *keys) end end # Return the position of the first bit set to 1 or 0 in a string. def bitpos(key, bit, start=nil, stop=nil) node_for(key).bitpos(key, bit, start, stop) end # Set the string value of a key and return its old value. def getset(key, value) node_for(key).getset(key, value) end # Get the length of the value stored in a key. def strlen(key) node_for(key).strlen(key) end def [](key) get(key) end def []=(key,value) set(key, value) end # Get the length of a list. def llen(key) node_for(key).llen(key) end # Prepend one or more values to a list. def lpush(key, value) node_for(key).lpush(key, value) end # Prepend a value to a list, only if the list exists. def lpushx(key, value) node_for(key).lpushx(key, value) end # Append one or more values to a list. def rpush(key, value) node_for(key).rpush(key, value) end # Append a value to a list, only if the list exists. def rpushx(key, value) node_for(key).rpushx(key, value) end # Remove and get the first element in a list. def lpop(key) node_for(key).lpop(key) end # Remove and get the last element in a list. def rpop(key) node_for(key).rpop(key) end # Remove the last element in a list, append it to another list and return # it. def rpoplpush(source, destination) ensure_same_node(:rpoplpush, [source, destination]) do |node| node.rpoplpush(source, destination) end end def _bpop(cmd, args) options = {} if args.last.is_a?(Hash) options = args.pop elsif args.last.respond_to?(:to_int) # Issue deprecation notice in obnoxious mode... options[:timeout] = args.pop.to_int end if args.size > 1 # Issue deprecation notice in obnoxious mode... end keys = args.flatten ensure_same_node(cmd, keys) do |node| node.__send__(cmd, keys, options) end end # Remove and get the first element in a list, or block until one is # available. def blpop(*args) _bpop(:blpop, args) end # Remove and get the last element in a list, or block until one is # available. def brpop(*args) _bpop(:brpop, args) end # Pop a value from a list, push it to another list and return it; or block # until one is available. def brpoplpush(source, destination, options = {}) case options when Integer # Issue deprecation notice in obnoxious mode... options = { :timeout => options } end ensure_same_node(:brpoplpush, [source, destination]) do |node| node.brpoplpush(source, destination, options) end end # Get an element from a list by its index. def lindex(key, index) node_for(key).lindex(key, index) end # Insert an element before or after another element in a list. def linsert(key, where, pivot, value) node_for(key).linsert(key, where, pivot, value) end # Get a range of elements from a list. def lrange(key, start, stop) node_for(key).lrange(key, start, stop) end # Remove elements from a list. def lrem(key, count, value) node_for(key).lrem(key, count, value) end # Set the value of an element in a list by its index. def lset(key, index, value) node_for(key).lset(key, index, value) end # Trim a list to the specified range. def ltrim(key, start, stop) node_for(key).ltrim(key, start, stop) end # Get the number of members in a set. def scard(key) node_for(key).scard(key) end # Add one or more members to a set. def sadd(key, member) node_for(key).sadd(key, member) end # Remove one or more members from a set. def srem(key, member) node_for(key).srem(key, member) end # Remove and return a random member from a set. def spop(key, count = nil) node_for(key).spop(key, count) end # Get a random member from a set. def srandmember(key, count = nil) node_for(key).srandmember(key, count) end # Move a member from one set to another. def smove(source, destination, member) ensure_same_node(:smove, [source, destination]) do |node| node.smove(source, destination, member) end end # Determine if a given value is a member of a set. def sismember(key, member) node_for(key).sismember(key, member) end # Get all the members in a set. def smembers(key) node_for(key).smembers(key) end # Scan a set def sscan(key, cursor, options={}) node_for(key).sscan(key, cursor, options) end # Scan a set and return an enumerator def sscan_each(key, options={}, &block) node_for(key).sscan_each(key, options, &block) end # Subtract multiple sets. def sdiff(*keys) ensure_same_node(:sdiff, keys) do |node| node.sdiff(*keys) end end # Subtract multiple sets and store the resulting set in a key. def sdiffstore(destination, *keys) ensure_same_node(:sdiffstore, [destination] + keys) do |node| node.sdiffstore(destination, *keys) end end # Intersect multiple sets. def sinter(*keys) ensure_same_node(:sinter, keys) do |node| node.sinter(*keys) end end # Intersect multiple sets and store the resulting set in a key. def sinterstore(destination, *keys) ensure_same_node(:sinterstore, [destination] + keys) do |node| node.sinterstore(destination, *keys) end end # Add multiple sets. def sunion(*keys) ensure_same_node(:sunion, keys) do |node| node.sunion(*keys) end end # Add multiple sets and store the resulting set in a key. def sunionstore(destination, *keys) ensure_same_node(:sunionstore, [destination] + keys) do |node| node.sunionstore(destination, *keys) end end # Get the number of members in a sorted set. def zcard(key) node_for(key).zcard(key) end # Add one or more members to a sorted set, or update the score for members # that already exist. def zadd(key, *args) node_for(key).zadd(key, *args) end # Increment the score of a member in a sorted set. def zincrby(key, increment, member) node_for(key).zincrby(key, increment, member) end # Remove one or more members from a sorted set. def zrem(key, member) node_for(key).zrem(key, member) end # Get the score associated with the given member in a sorted set. def zscore(key, member) node_for(key).zscore(key, member) end # Return a range of members in a sorted set, by index. def zrange(key, start, stop, options = {}) node_for(key).zrange(key, start, stop, options) end # Return a range of members in a sorted set, by index, with scores ordered # from high to low. def zrevrange(key, start, stop, options = {}) node_for(key).zrevrange(key, start, stop, options) end # Determine the index of a member in a sorted set. def zrank(key, member) node_for(key).zrank(key, member) end # Determine the index of a member in a sorted set, with scores ordered from # high to low. def zrevrank(key, member) node_for(key).zrevrank(key, member) end # Remove all members in a sorted set within the given indexes. def zremrangebyrank(key, start, stop) node_for(key).zremrangebyrank(key, start, stop) end # Return a range of members in a sorted set, by score. def zrangebyscore(key, min, max, options = {}) node_for(key).zrangebyscore(key, min, max, options) end # Return a range of members in a sorted set, by score, with scores ordered # from high to low. def zrevrangebyscore(key, max, min, options = {}) node_for(key).zrevrangebyscore(key, max, min, options) end # Remove all members in a sorted set within the given scores. def zremrangebyscore(key, min, max) node_for(key).zremrangebyscore(key, min, max) end # Get the number of members in a particular score range. def zcount(key, min, max) node_for(key).zcount(key, min, max) end # Intersect multiple sorted sets and store the resulting sorted set in a new # key. def zinterstore(destination, keys, options = {}) ensure_same_node(:zinterstore, [destination] + keys) do |node| node.zinterstore(destination, keys, options) end end # Add multiple sorted sets and store the resulting sorted set in a new key. def zunionstore(destination, keys, options = {}) ensure_same_node(:zunionstore, [destination] + keys) do |node| node.zunionstore(destination, keys, options) end end # Get the number of fields in a hash. def hlen(key) node_for(key).hlen(key) end # Set the string value of a hash field. def hset(key, field, value) node_for(key).hset(key, field, value) end # Set the value of a hash field, only if the field does not exist. def hsetnx(key, field, value) node_for(key).hsetnx(key, field, value) end # Set multiple hash fields to multiple values. def hmset(key, *attrs) node_for(key).hmset(key, *attrs) end def mapped_hmset(key, hash) node_for(key).hmset(key, *hash.to_a.flatten) end # Get the value of a hash field. def hget(key, field) node_for(key).hget(key, field) end # Get the values of all the given hash fields. def hmget(key, *fields) node_for(key).hmget(key, *fields) end def mapped_hmget(key, *fields) Hash[*fields.zip(hmget(key, *fields)).flatten] end # Delete one or more hash fields. def hdel(key, *fields) node_for(key).hdel(key, *fields) end # Determine if a hash field exists. def hexists(key, field) node_for(key).hexists(key, field) end # Increment the integer value of a hash field by the given integer number. def hincrby(key, field, increment) node_for(key).hincrby(key, field, increment) end # Increment the numeric value of a hash field by the given float number. def hincrbyfloat(key, field, increment) node_for(key).hincrbyfloat(key, field, increment) end # Get all the fields in a hash. def hkeys(key) node_for(key).hkeys(key) end # Get all the values in a hash. def hvals(key) node_for(key).hvals(key) end # Get all the fields and values in a hash. def hgetall(key) node_for(key).hgetall(key) end # Post a message to a channel. def publish(channel, message) node_for(channel).publish(channel, message) end def subscribed? !! @subscribed_node end # Listen for messages published to the given channels. def subscribe(channel, *channels, &block) if channels.empty? @subscribed_node = node_for(channel) @subscribed_node.subscribe(channel, &block) else ensure_same_node(:subscribe, [channel] + channels) do |node| @subscribed_node = node node.subscribe(channel, *channels, &block) end end end # Stop listening for messages posted to the given channels. def unsubscribe(*channels) raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? @subscribed_node.unsubscribe(*channels) end # Listen for messages published to channels matching the given patterns. def psubscribe(*channels, &block) raise NotImplementedError end # Stop listening for messages posted to channels matching the given # patterns. def punsubscribe(*channels) raise NotImplementedError end # Watch the given keys to determine execution of the MULTI/EXEC block. def watch(*keys) raise CannotDistribute, :watch end # Forget about all watched keys. def unwatch raise CannotDistribute, :unwatch end def pipelined raise CannotDistribute, :pipelined end # Mark the start of a transaction block. def multi raise CannotDistribute, :multi end # Execute all commands issued after MULTI. def exec raise CannotDistribute, :exec end # Discard all commands issued after MULTI. def discard raise CannotDistribute, :discard end # Control remote script registry. def script(subcommand, *args) on_each_node(:script, subcommand, *args) end # Add one or more members to a HyperLogLog structure. def pfadd(key, member) node_for(key).pfadd(key, member) end # Get the approximate cardinality of members added to HyperLogLog structure. def pfcount(*keys) ensure_same_node(:pfcount, keys.flatten(1)) do |node| node.pfcount(keys) end end # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of # the observed Sets of the source HyperLogLog structures. def pfmerge(dest_key, *source_key) ensure_same_node(:pfmerge, [dest_key, *source_key]) do |node| node.pfmerge(dest_key, *source_key) end end def _eval(cmd, args) script = args.shift options = args.pop if args.last.is_a?(Hash) options ||= {} keys = args.shift || options[:keys] || [] argv = args.shift || options[:argv] || [] ensure_same_node(cmd, keys) do |node| node.send(cmd, script, keys, argv) end end # Evaluate Lua script. def eval(*args) _eval(:eval, args) end # Evaluate Lua script by its SHA. def evalsha(*args) _eval(:evalsha, args) end def inspect "#" end def dup self.class.new(@node_configs, @default_options) end protected def on_each_node(command, *args) nodes.map do |node| node.send(command, *args) end end def node_index_for(key) nodes.index(node_for(key)) end def key_tag(key) key.to_s[@tag, 1] if @tag end def ensure_same_node(command, keys) all = true tags = keys.map do |key| tag = key_tag(key) all = false unless tag tag end if (all && tags.uniq.size != 1) || (!all && keys.uniq.size != 1) # Not 1 unique tag or not 1 unique key raise CannotDistribute, command end yield(node_for(keys.first)) end end end redis-rb-4.1.2/lib/redis/errors.rb000066400000000000000000000052721347371274700167670ustar00rootroot00000000000000class Redis # Base error for all redis-rb errors. class BaseError < RuntimeError end # Raised by the connection when a protocol error occurs. class ProtocolError < BaseError def initialize(reply_type) super(<<-EOS.gsub(/(?:^|\n)\s*/, " ")) Got '#{reply_type}' as initial reply byte. If you're in a forking environment, such as Unicorn, you need to connect to Redis after forking. EOS end end # Raised by the client when command execution returns an error reply. class CommandError < BaseError end # Base error for connection related errors. class BaseConnectionError < BaseError end # Raised when connection to a Redis server cannot be made. class CannotConnectError < BaseConnectionError end # Raised when connection to a Redis server is lost. class ConnectionError < BaseConnectionError end # Raised when performing I/O times out. class TimeoutError < BaseConnectionError end # Raised when the connection was inherited by a child process. class InheritedError < BaseConnectionError end # Raised when client options are invalid. class InvalidClientOptionError < BaseError end class Cluster # Raised when client connected to redis as cluster mode # and some cluster subcommands were called. class OrchestrationCommandNotSupported < BaseError def initialize(command, subcommand = '') str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase msg = "#{str} command should be used with care "\ 'only by applications orchestrating Redis Cluster, like redis-trib, '\ 'and the command if used out of the right context can leave the cluster '\ 'in a wrong state or cause data loss.' super(msg) end end # Raised when error occurs on any node of cluster. class CommandErrorCollection < BaseError attr_reader :errors # @param errors [Hash{String => Redis::CommandError}] # @param error_message [String] def initialize(errors, error_message = 'Command errors were replied on any node') @errors = errors super(error_message) end end # Raised when cluster client can't select node. class AmbiguousNodeError < BaseError def initialize(command) super("Cluster client doesn't know which node the #{command} command should be sent to.") end end # Raised when commands in pipelining include cross slot keys. class CrossSlotPipeliningError < BaseError def initialize(keys) super("Cluster client couldn't send pipelining to single node. "\ "The commands include cross slot keys. #{keys}") end end end end redis-rb-4.1.2/lib/redis/hash_ring.rb000066400000000000000000000041341347371274700174110ustar00rootroot00000000000000require 'zlib' class Redis class HashRing POINTS_PER_SERVER = 160 # this is the default in libmemcached attr_reader :ring, :sorted_keys, :replicas, :nodes # nodes is a list of objects that have a proper to_s representation. # replicas indicates how many virtual points should be used pr. node, # replicas are required to improve the distribution. def initialize(nodes=[], replicas=POINTS_PER_SERVER) @replicas = replicas @ring = {} @nodes = [] @sorted_keys = [] nodes.each do |node| add_node(node) end end # Adds a `node` to the hash ring (including a number of replicas). def add_node(node) @nodes << node @replicas.times do |i| key = Zlib.crc32("#{node.id}:#{i}") @ring[key] = node @sorted_keys << key end @sorted_keys.sort! end def remove_node(node) @nodes.reject!{|n| n.id == node.id} @replicas.times do |i| key = Zlib.crc32("#{node.id}:#{i}") @ring.delete(key) @sorted_keys.reject! {|k| k == key} end end # get the node in the hash ring for this key def get_node(key) get_node_pos(key)[0] end def get_node_pos(key) return [nil,nil] if @ring.size == 0 crc = Zlib.crc32(key) idx = HashRing.binary_search(@sorted_keys, crc) return [@ring[@sorted_keys[idx]], idx] end def iter_nodes(key) return [nil,nil] if @ring.size == 0 _, pos = get_node_pos(key) @ring.size.times do |n| yield @ring[@sorted_keys[(pos+n) % @ring.size]] end end # Find the closest index in HashRing with value <= the given value def self.binary_search(ary, value, &block) upper = ary.size - 1 lower = 0 idx = 0 while(lower <= upper) do idx = (lower + upper) / 2 comp = ary[idx] <=> value if comp == 0 return idx elsif comp > 0 upper = idx - 1 else lower = idx + 1 end end if upper < 0 upper = ary.size - 1 end return upper end end end redis-rb-4.1.2/lib/redis/pipeline.rb000066400000000000000000000066731347371274700172660ustar00rootroot00000000000000class Redis class Pipeline attr_accessor :db attr_reader :client attr :futures def initialize(client) @client = client.is_a?(Pipeline) ? client.client : client @with_reconnect = true @shutdown = false @futures = [] end def timeout client.timeout end def with_reconnect? @with_reconnect end def without_reconnect? !@with_reconnect end def shutdown? @shutdown end def empty? @futures.empty? end def call(command, timeout: nil, &block) # A pipeline that contains a shutdown should not raise ECONNRESET when # the connection is gone. @shutdown = true if command.first == :shutdown future = Future.new(command, block, timeout) @futures << future future end def call_with_timeout(command, timeout, &block) call(command, timeout: timeout, &block) end def call_pipeline(pipeline) @shutdown = true if pipeline.shutdown? @futures.concat(pipeline.futures) @db = pipeline.db nil end def commands @futures.map(&:_command) end def timeouts @futures.map(&:timeout) end def with_reconnect(val=true) @with_reconnect = false unless val yield end def without_reconnect(&blk) with_reconnect(false, &blk) end def finish(replies, &blk) if blk futures.each_with_index.map do |future, i| future._set(blk.call(replies[i])) end else futures.each_with_index.map do |future, i| future._set(replies[i]) end end end class Multi < self def finish(replies) exec = replies.last return if exec.nil? # The transaction failed because of WATCH. # EXEC command failed. raise exec if exec.is_a?(CommandError) if exec.size < futures.size # Some command wasn't recognized by Redis. raise replies.detect { |r| r.is_a?(CommandError) } end super(exec) do |reply| # Because an EXEC returns nested replies, hiredis won't be able to # convert an error reply to a CommandError instance itself. This is # specific to MULTI/EXEC, so we solve this here. reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply end end def timeouts if empty? [] else [nil, *super, nil] end end def commands if empty? [] else [[:multi]] + super + [[:exec]] end end end end class FutureNotReady < RuntimeError def initialize super("Value will be available once the pipeline executes.") end end class Future < BasicObject FutureNotReady = ::Redis::FutureNotReady.new attr_reader :timeout def initialize(command, transformation, timeout) @command = command @transformation = transformation @timeout = timeout @object = FutureNotReady end def inspect "" end def _set(object) @object = @transformation ? @transformation.call(object) : object value end def _command @command end def value ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError) @object end def is_a?(other) self.class.ancestors.include?(other) end def class Future end end end redis-rb-4.1.2/lib/redis/subscribe.rb000066400000000000000000000037561347371274700174410ustar00rootroot00000000000000class Redis class SubscribedClient def initialize(client) @client = client end def call(command) @client.process([command]) end def subscribe(*channels, &block) subscription("subscribe", "unsubscribe", channels, block) end def subscribe_with_timeout(timeout, *channels, &block) subscription("subscribe", "unsubscribe", channels, block, timeout) end def psubscribe(*channels, &block) subscription("psubscribe", "punsubscribe", channels, block) end def psubscribe_with_timeout(timeout, *channels, &block) subscription("psubscribe", "punsubscribe", channels, block, timeout) end def unsubscribe(*channels) call([:unsubscribe, *channels]) end def punsubscribe(*channels) call([:punsubscribe, *channels]) end protected def subscription(start, stop, channels, block, timeout = 0) sub = Subscription.new(&block) unsubscribed = false begin @client.call_loop([start, *channels], timeout) do |line| type, *rest = line sub.callbacks[type].call(*rest) unsubscribed = type == stop && rest.last == 0 break if unsubscribed end ensure # No need to unsubscribe here. The real client closes the connection # whenever an exception is raised (see #ensure_connected). end end end class Subscription attr :callbacks def initialize @callbacks = Hash.new do |hash, key| hash[key] = lambda { |*_| } end yield(self) end def subscribe(&block) @callbacks["subscribe"] = block end def unsubscribe(&block) @callbacks["unsubscribe"] = block end def message(&block) @callbacks["message"] = block end def psubscribe(&block) @callbacks["psubscribe"] = block end def punsubscribe(&block) @callbacks["punsubscribe"] = block end def pmessage(&block) @callbacks["pmessage"] = block end end end redis-rb-4.1.2/lib/redis/version.rb000066400000000000000000000000441347371274700171300ustar00rootroot00000000000000class Redis VERSION = '4.1.2' end redis-rb-4.1.2/makefile000066400000000000000000000070321347371274700147460ustar00rootroot00000000000000REDIS_BRANCH ?= 5.0 TMP := tmp BUILD_DIR := ${TMP}/cache/redis-${REDIS_BRANCH} TARBALL := ${TMP}/redis-${REDIS_BRANCH}.tar.gz BINARY := ${BUILD_DIR}/src/redis-server REDIS_CLIENT := ${BUILD_DIR}/src/redis-cli REDIS_TRIB := ${BUILD_DIR}/src/redis-trib.rb PID_PATH := ${BUILD_DIR}/redis.pid SOCKET_PATH := ${BUILD_DIR}/redis.sock PORT := 6381 SLAVE_PORT := 6382 SLAVE_PID_PATH := ${BUILD_DIR}/redis_slave.pid SLAVE_SOCKET_PATH := ${BUILD_DIR}/redis_slave.sock SENTINEL_PORTS := 6400 6401 6402 SENTINEL_PID_PATHS := $(addprefix ${TMP}/redis,$(addsuffix .pid,${SENTINEL_PORTS})) CLUSTER_PORTS := 7000 7001 7002 7003 7004 7005 CLUSTER_PID_PATHS := $(addprefix ${TMP}/redis,$(addsuffix .pid,${CLUSTER_PORTS})) CLUSTER_CONF_PATHS := $(addprefix ${TMP}/nodes,$(addsuffix .conf,${CLUSTER_PORTS})) CLUSTER_ADDRS := $(addprefix 127.0.0.1:,${CLUSTER_PORTS}) define kill-redis (ls $1 > /dev/null 2>&1 && kill $$(cat $1) && rm -f $1) || true endef all: @make --no-print-directory start_all @make --no-print-directory test @make --no-print-directory stop_all start_all: @make --no-print-directory start @make --no-print-directory start_slave @make --no-print-directory start_sentinel @make --no-print-directory start_cluster @make --no-print-directory create_cluster stop_all: @make --no-print-directory stop_sentinel @make --no-print-directory stop_slave @make --no-print-directory stop @make --no-print-directory stop_cluster ${TMP}: @mkdir -p $@ ${BINARY}: ${TMP} @bin/build ${REDIS_BRANCH} $< test: @env SOCKET_PATH=${SOCKET_PATH} bundle exec rake test stop: @$(call kill-redis,${PID_PATH}) start: ${BINARY} @${BINARY}\ --daemonize yes\ --pidfile ${PID_PATH}\ --port ${PORT}\ --unixsocket ${SOCKET_PATH} stop_slave: @$(call kill-redis,${SLAVE_PID_PATH}) start_slave: ${BINARY} @${BINARY}\ --daemonize yes\ --pidfile ${SLAVE_PID_PATH}\ --port ${SLAVE_PORT}\ --unixsocket ${SLAVE_SOCKET_PATH}\ --slaveof 127.0.0.1 ${PORT} stop_sentinel: @$(call kill-redis,${SENTINEL_PID_PATHS}) @rm -f ${TMP}/sentinel*.conf || true start_sentinel: ${BINARY} @for port in ${SENTINEL_PORTS}; do\ conf=${TMP}/sentinel$$port.conf;\ touch $$conf;\ echo '' > $$conf;\ echo 'sentinel monitor master1 127.0.0.1 ${PORT} 2' >> $$conf;\ echo 'sentinel down-after-milliseconds master1 5000' >> $$conf;\ echo 'sentinel failover-timeout master1 30000' >> $$conf;\ echo 'sentinel parallel-syncs master1 1' >> $$conf;\ ${BINARY} $$conf\ --daemonize yes\ --pidfile ${TMP}/redis$$port.pid\ --port $$port\ --sentinel;\ done stop_cluster: @$(call kill-redis,${CLUSTER_PID_PATHS}) @rm -f appendonly.aof || true @rm -f ${CLUSTER_CONF_PATHS} || true start_cluster: ${BINARY} @for port in ${CLUSTER_PORTS}; do\ ${BINARY}\ --daemonize yes\ --appendonly yes\ --cluster-enabled yes\ --cluster-config-file ${TMP}/nodes$$port.conf\ --cluster-node-timeout 5000\ --pidfile ${TMP}/redis$$port.pid\ --port $$port\ --unixsocket ${TMP}/redis$$port.sock;\ done create_cluster: @bin/cluster_creator ${CLUSTER_ADDRS} clean: @(test -d ${BUILD_DIR} && cd ${BUILD_DIR}/src && make clean distclean) || true .PHONY: all test stop start stop_slave start_slave stop_sentinel start_sentinel\ stop_cluster start_cluster create_cluster stop_all start_all clean redis-rb-4.1.2/redis.gemspec000066400000000000000000000017221347371274700157210ustar00rootroot00000000000000require "./lib/redis/version" Gem::Specification.new do |s| s.name = "redis" s.version = Redis::VERSION s.homepage = "https://github.com/redis/redis-rb" s.summary = "A Ruby client library for Redis" s.description = <<-EOS A Ruby client that tries to match Redis' API one-to-one, while still providing an idiomatic interface. EOS s.license = "MIT" s.authors = [ "Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark", "Brian McKinney", "Salvatore Sanfilippo", "Luca Guidi", "Michel Martens", "Damian Janowski", "Pieter Noordhuis" ] s.email = ["redis-db@googlegroups.com"] s.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "lib/**/*"] s.executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) } s.required_ruby_version = '>= 2.3.0' s.add_development_dependency("mocha") s.add_development_dependency("hiredis") s.add_development_dependency("em-synchrony") end redis-rb-4.1.2/test/000077500000000000000000000000001347371274700142235ustar00rootroot00000000000000redis-rb-4.1.2/test/bitpos_test.rb000066400000000000000000000026531347371274700171150ustar00rootroot00000000000000require_relative "helper" class TestBitpos < Minitest::Test include Helper::Client def test_bitpos_empty_zero target_version "2.9.11" do r.del "foo" assert_equal(0, r.bitpos("foo", 0)) end end def test_bitpos_empty_one target_version "2.9.11" do r.del "foo" assert_equal(-1, r.bitpos("foo", 1)) end end def test_bitpos_zero target_version "2.9.11" do r.set "foo", "\xff\xf0\x00" assert_equal(12, r.bitpos("foo", 0)) end end def test_bitpos_one target_version "2.9.11" do r.set "foo", "\x00\x0f\x00" assert_equal(12, r.bitpos("foo", 1)) end end def test_bitpos_zero_end_is_given target_version "2.9.11" do r.set "foo", "\xff\xff\xff" assert_equal(24, r.bitpos("foo", 0)) assert_equal(24, r.bitpos("foo", 0, 0)) assert_equal(-1, r.bitpos("foo", 0, 0, -1)) end end def test_bitpos_one_intervals target_version "2.9.11" do r.set "foo", "\x00\xff\x00" assert_equal(8, r.bitpos("foo", 1, 0, -1)) assert_equal(8, r.bitpos("foo", 1, 1, -1)) assert_equal(-1, r.bitpos("foo", 1, 2, -1)) assert_equal(-1, r.bitpos("foo", 1, 2, 200)) assert_equal(8, r.bitpos("foo", 1, 1, 1)) end end def test_bitpos_raise_exception_if_stop_not_start target_version "2.9.11" do assert_raises(ArgumentError) do r.bitpos("foo", 0, nil, 2) end end end end redis-rb-4.1.2/test/blocking_commands_test.rb000066400000000000000000000024621347371274700212640ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/blocking_commands' class TestBlockingCommands < Minitest::Test include Helper::Client include Lint::BlockingCommands def assert_takes_longer_than_client_timeout timeout = LOW_TIMEOUT delay = timeout * 5 mock(delay: delay) do |r| t1 = Time.now yield(r) t2 = Time.now assert timeout == r._client.timeout assert delay <= (t2 - t1) end end def test_blpop_disable_client_timeout assert_takes_longer_than_client_timeout do |r| assert_equal %w[foo 0], r.blpop('foo') end end def test_brpop_disable_client_timeout assert_takes_longer_than_client_timeout do |r| assert_equal %w[foo 0], r.brpop('foo') end end def test_brpoplpush_disable_client_timeout assert_takes_longer_than_client_timeout do |r| assert_equal '0', r.brpoplpush('foo', 'bar') end end def test_brpoplpush_in_transaction results = r.multi do r.brpoplpush('foo', 'bar') r.brpoplpush('foo', 'bar', timeout: 2) end assert_equal [nil, nil], results end def test_brpoplpush_in_pipeline mock do |r| results = r.pipelined do r.brpoplpush('foo', 'bar') r.brpoplpush('foo', 'bar', timeout: 2) end assert_equal ['0', '2'], results end end end redis-rb-4.1.2/test/client_test.rb000066400000000000000000000027371347371274700170760ustar00rootroot00000000000000require_relative "helper" class TestClient < Minitest::Test include Helper::Client def test_call result = r.call("PING") assert_equal result, "PONG" end def test_call_with_arguments result = r.call("SET", "foo", "bar") assert_equal result, "OK" end def test_call_integers result = r.call("INCR", "foo") assert_equal result, 1 end def test_call_raise assert_raises(Redis::CommandError) do r.call("INCR") end end def test_queue_commit r.queue("SET", "foo", "bar") r.queue("GET", "foo") result = r.commit assert_equal result, ["OK", "bar"] end def test_commit_raise r.queue("SET", "foo", "bar") r.queue("INCR") assert_raises(Redis::CommandError) do r.commit end end def test_queue_after_error r.queue("SET", "foo", "bar") r.queue("INCR") assert_raises(Redis::CommandError) do r.commit end r.queue("SET", "foo", "bar") r.queue("INCR", "baz") result = r.commit assert_equal result, ["OK", 1] end def test_client_with_custom_connector custom_connector = Class.new(Redis::Client::Connector) do def resolve @options[:host] = '127.0.0.5' @options[:port] = '999' @options end end error = assert_raises do new_redis = _new_client(connector: custom_connector) new_redis.ping end assert_equal 'Error connecting to Redis on 127.0.0.5:999 (Errno::ECONNREFUSED)', error.message end end redis-rb-4.1.2/test/cluster_abnormal_state_test.rb000066400000000000000000000017701347371274700223500ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_abnormal_state_test.rb class TestClusterAbnormalState < Minitest::Test include Helper::Cluster def test_the_state_of_cluster_down redis_cluster_down do assert_raises(Redis::CommandError, 'CLUSTERDOWN Hash slot not served') do redis.set('key1', 1) end assert_equal 'fail', redis.cluster(:info).fetch('cluster_state') end end def test_the_state_of_cluster_failover redis_cluster_failover do 100.times do |i| assert_equal 'OK', r.set("key#{i}", i) end 100.times do |i| assert_equal i.to_s, r.get("key#{i}") end assert_equal 'ok', redis.cluster(:info).fetch('cluster_state') end end def test_raising_error_when_nodes_are_not_cluster_mode assert_raises(Redis::CannotConnectError, 'Redis client could not connect to any cluster nodes') do build_another_client(cluster: %W[redis://127.0.0.1:#{PORT}]) end end end redis-rb-4.1.2/test/cluster_blocking_commands_test.rb000066400000000000000000000006411347371274700230220ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/blocking_commands' # ruby -w -Itest test/cluster_blocking_commands_test.rb class TestClusterBlockingCommands < Minitest::Test include Helper::Cluster include Lint::BlockingCommands def mock(options = {}, &blk) commands = build_mock_commands(options) redis_cluster_mock(commands, { timeout: LOW_TIMEOUT }, &blk) end end redis-rb-4.1.2/test/cluster_client_internals_test.rb000066400000000000000000000037531347371274700227150ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_client_internals_test.rb class TestClusterClientInternals < Minitest::Test include Helper::Cluster def test_handle_multiple_servers 100.times { |i| redis.set(i.to_s, "hogehoge#{i}") } 100.times { |i| assert_equal "hogehoge#{i}", redis.get(i.to_s) } end def test_info_of_cluster_mode_is_enabled assert_equal '1', redis.info['cluster_enabled'] end def test_unknown_commands_does_not_work_by_default assert_raises(Redis::CommandError) do redis.not_yet_implemented_command('boo', 'foo') end end def test_with_reconnect assert_equal('Hello World', redis.with_reconnect { 'Hello World' }) end def test_without_reconnect assert_equal('Hello World', redis.without_reconnect { 'Hello World' }) end def test_connected? assert_equal true, redis.connected? end def test_close assert_equal true, redis.close end def test_disconnect! assert_equal true, redis.disconnect! end def test_asking assert_equal 'OK', redis.asking end def test_id expected = 'redis://127.0.0.1:7000/0 '\ 'redis://127.0.0.1:7001/0 '\ 'redis://127.0.0.1:7002/0' assert_equal expected, redis.id end def test_inspect expected = "#' assert_equal expected, redis.inspect end def test_dup assert_instance_of Redis, redis.dup end def test_connection expected = [ { host: '127.0.0.1', port: 7000, db: 0, id: 'redis://127.0.0.1:7000/0', location: '127.0.0.1:7000' }, { host: '127.0.0.1', port: 7001, db: 0, id: 'redis://127.0.0.1:7001/0', location: '127.0.0.1:7001' }, { host: '127.0.0.1', port: 7002, db: 0, id: 'redis://127.0.0.1:7002/0', location: '127.0.0.1:7002' } ] assert_equal expected, redis.connection end end redis-rb-4.1.2/test/cluster_client_key_hash_tags_test.rb000066400000000000000000000116311347371274700235210ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_client_key_hash_tags_test.rb class TestClusterClientKeyHashTags < Minitest::Test include Helper::Cluster def build_described_class option = Redis::Cluster::Option.new(cluster: ['redis://127.0.0.1:7000']) node = Redis::Cluster::Node.new(option.per_node_key) details = Redis::Cluster::CommandLoader.load(node) Redis::Cluster::Command.new(details) end def test_key_extraction described_class = build_described_class assert_equal 'dogs:1', described_class.extract_first_key(%w[get dogs:1]) assert_equal 'user1000', described_class.extract_first_key(%w[get {user1000}.following]) assert_equal 'user1000', described_class.extract_first_key(%w[get {user1000}.followers]) assert_equal 'foo{}{bar}', described_class.extract_first_key(%w[get foo{}{bar}]) assert_equal '{bar', described_class.extract_first_key(%w[get foo{{bar}}zap]) assert_equal 'bar', described_class.extract_first_key(%w[get foo{bar}{zap}]) assert_equal '', described_class.extract_first_key([:get, '']) assert_equal '', described_class.extract_first_key([:get, nil]) assert_equal '', described_class.extract_first_key([:get]) assert_equal '', described_class.extract_first_key([:set, '', 1]) assert_equal '', described_class.extract_first_key([:set, nil, 1]) assert_equal '', described_class.extract_first_key([:set]) # Keyless commands assert_equal '', described_class.extract_first_key([:auth, 'password']) assert_equal '', described_class.extract_first_key(%i[client kill]) assert_equal '', described_class.extract_first_key(%i[cluster addslots]) assert_equal '', described_class.extract_first_key(%i[command]) assert_equal '', described_class.extract_first_key(%i[command count]) assert_equal '', described_class.extract_first_key(%i[config get]) assert_equal '', described_class.extract_first_key(%i[debug segfault]) assert_equal '', described_class.extract_first_key([:echo, 'Hello World']) assert_equal '', described_class.extract_first_key([:flushall, 'ASYNC']) assert_equal '', described_class.extract_first_key([:flushdb, 'ASYNC']) assert_equal '', described_class.extract_first_key([:info, 'cluster']) assert_equal '', described_class.extract_first_key(%i[memory doctor]) assert_equal '', described_class.extract_first_key([:ping, 'Hi']) assert_equal '', described_class.extract_first_key([:psubscribe, 'channel']) assert_equal '', described_class.extract_first_key([:pubsub, 'channels', '*']) assert_equal '', described_class.extract_first_key([:publish, 'channel', 'Hi']) assert_equal '', described_class.extract_first_key([:punsubscribe, 'channel']) assert_equal '', described_class.extract_first_key([:subscribe, 'channel']) assert_equal '', described_class.extract_first_key([:unsubscribe, 'channel']) assert_equal '', described_class.extract_first_key(%w[script exists sha1 sha1]) assert_equal '', described_class.extract_first_key([:select, 1]) assert_equal '', described_class.extract_first_key([:shutdown, 'SAVE']) assert_equal '', described_class.extract_first_key([:slaveof, '127.0.0.1', 6379]) assert_equal '', described_class.extract_first_key([:slowlog, 'get', 2]) assert_equal '', described_class.extract_first_key([:swapdb, 0, 1]) assert_equal '', described_class.extract_first_key([:wait, 1, 0]) # 2nd argument is not a key assert_equal 'key1', described_class.extract_first_key([:eval, 'script', 2, 'key1', 'key2', 'first', 'second']) assert_equal '', described_class.extract_first_key([:eval, 'return 0', 0]) assert_equal 'key1', described_class.extract_first_key([:evalsha, 'sha1', 2, 'key1', 'key2', 'first', 'second']) assert_equal '', described_class.extract_first_key([:evalsha, 'return 0', 0]) assert_equal 'key1', described_class.extract_first_key([:migrate, '127.0.0.1', 6379, 'key1', 0, 5000]) assert_equal 'key1', described_class.extract_first_key([:memory, :usage, 'key1']) assert_equal 'key1', described_class.extract_first_key([:object, 'refcount', 'key1']) assert_equal 'mystream', described_class.extract_first_key([:xread, 'COUNT', 2, 'STREAMS', 'mystream', 0]) assert_equal 'mystream', described_class.extract_first_key([:xreadgroup, 'GROUP', 'mygroup', 'Bob', 'COUNT', 2, 'STREAMS', 'mystream', '>']) end def test_whether_the_command_effect_is_readonly_or_not described_class = build_described_class assert_equal true, described_class.should_send_to_master?([:set]) assert_equal false, described_class.should_send_to_slave?([:set]) assert_equal false, described_class.should_send_to_master?([:get]) assert_equal true, described_class.should_send_to_slave?([:get]) target_version('3.2.0') do assert_equal false, described_class.should_send_to_master?([:info]) assert_equal false, described_class.should_send_to_slave?([:info]) end end end redis-rb-4.1.2/test/cluster_client_options_test.rb000066400000000000000000000105001347371274700223750ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_client_options_test.rb class TestClusterClientOptions < Minitest::Test include Helper::Cluster def test_option_class option = Redis::Cluster::Option.new(cluster: %w[rediss://127.0.0.1:7000], replica: true) assert_equal({ '127.0.0.1:7000' => { url: 'rediss://127.0.0.1:7000' } }, option.per_node_key) assert_equal true, option.secure? assert_equal true, option.use_replica? option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000], replica: false) assert_equal({ '127.0.0.1:7000' => { url: 'redis://127.0.0.1:7000' } }, option.per_node_key) assert_equal false, option.secure? assert_equal false, option.use_replica? option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000]) assert_equal({ '127.0.0.1:7000' => { url: 'redis://127.0.0.1:7000' } }, option.per_node_key) assert_equal false, option.secure? assert_equal false, option.use_replica? end def test_client_accepts_valid_node_configs nodes = ['redis://127.0.0.1:7000', 'redis://127.0.0.1:7001', { host: '127.0.0.1', port: '7002' }, { 'host' => '127.0.0.1', port: 7003 }, 'redis://127.0.0.1:7004', 'redis://127.0.0.1:7005'] build_another_client(cluster: nodes) end def test_client_accepts_valid_options build_another_client(timeout: TIMEOUT) end def test_client_ignores_invalid_options build_another_client(invalid_option: true) end def test_client_works_even_if_so_many_unavailable_nodes_specified nodes = (6001..7005).map { |port| "redis://127.0.0.1:#{port}" } redis = build_another_client(cluster: nodes) assert_equal 'PONG', redis.ping end def test_client_does_not_accept_db_specified_url assert_raises(Redis::CannotConnectError, 'Could not connect to any nodes') do build_another_client(cluster: ['redis://127.0.0.1:7000/1/namespace']) end assert_raises(Redis::CannotConnectError, 'Could not connect to any nodes') do build_another_client(cluster: [{ host: '127.0.0.1', port: '7000' }], db: 1) end end def test_client_does_not_accept_unconnectable_node_url_only nodes = ['redis://127.0.0.1:7006'] assert_raises(Redis::CannotConnectError, 'Could not connect to any nodes') do build_another_client(cluster: nodes) end end def test_client_accepts_unconnectable_node_url_included nodes = ['redis://127.0.0.1:7000', 'redis://127.0.0.1:7006'] build_another_client(cluster: nodes) end def test_client_does_not_accept_http_scheme_url nodes = ['http://127.0.0.1:80'] assert_raises(Redis::InvalidClientOptionError, "invalid uri scheme 'http'") do build_another_client(cluster: nodes) end end def test_client_does_not_accept_blank_included_config nodes = [''] assert_raises(Redis::InvalidClientOptionError, "invalid uri scheme ''") do build_another_client(cluster: nodes) end end def test_client_does_not_accept_bool_included_config nodes = [true] assert_raises(Redis::InvalidClientOptionError, "invalid uri scheme ''") do build_another_client(cluster: nodes) end end def test_client_does_not_accept_nil_included_config nodes = [nil] assert_raises(Redis::InvalidClientOptionError, "invalid uri scheme ''") do build_another_client(cluster: nodes) end end def test_client_does_not_accept_array_included_config nodes = [[]] assert_raises(Redis::InvalidClientOptionError, "invalid uri scheme ''") do build_another_client(cluster: nodes) end end def test_client_does_not_accept_empty_hash_included_config nodes = [{}] assert_raises(Redis::InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys') do build_another_client(cluster: nodes) end end def test_client_does_not_accept_object_included_config nodes = [Object.new] assert_raises(Redis::InvalidClientOptionError, 'Redis Cluster node config must includes String or Hash') do build_another_client(cluster: nodes) end end def test_client_does_not_accept_not_array_config nodes = :not_array assert_raises(Redis::InvalidClientOptionError, 'Redis Cluster node config must be Array') do build_another_client(cluster: nodes) end end end redis-rb-4.1.2/test/cluster_client_pipelining_test.rb000066400000000000000000000033031347371274700230430ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_client_pipelining_test.rb class TestClusterClientPipelining < Minitest::Test include Helper::Cluster def test_pipelining_with_a_hash_tag p1 = p2 = p3 = p4 = p5 = p6 = nil redis.pipelined do |r| r.set('{Presidents.of.USA}:1', 'George Washington') r.set('{Presidents.of.USA}:2', 'John Adams') r.set('{Presidents.of.USA}:3', 'Thomas Jefferson') r.set('{Presidents.of.USA}:4', 'James Madison') r.set('{Presidents.of.USA}:5', 'James Monroe') r.set('{Presidents.of.USA}:6', 'John Quincy Adams') p1 = r.get('{Presidents.of.USA}:1') p2 = r.get('{Presidents.of.USA}:2') p3 = r.get('{Presidents.of.USA}:3') p4 = r.get('{Presidents.of.USA}:4') p5 = r.get('{Presidents.of.USA}:5') p6 = r.get('{Presidents.of.USA}:6') end [p1, p2, p3, p4, p5, p6].each do |actual| assert_equal true, actual.is_a?(Redis::Future) end assert_equal('George Washington', p1.value) assert_equal('John Adams', p2.value) assert_equal('Thomas Jefferson', p3.value) assert_equal('James Madison', p4.value) assert_equal('James Monroe', p5.value) assert_equal('John Quincy Adams', p6.value) end def test_pipelining_without_hash_tags assert_raises(Redis::Cluster::CrossSlotPipeliningError) do redis.pipelined do redis.set(:a, 1) redis.set(:b, 2) redis.set(:c, 3) redis.set(:d, 4) redis.set(:e, 5) redis.set(:f, 6) redis.get(:a) redis.get(:b) redis.get(:c) redis.get(:d) redis.get(:e) redis.get(:f) end end end end redis-rb-4.1.2/test/cluster_client_replicas_test.rb000066400000000000000000000016121347371274700225100ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_client_replicas_test.rb class TestClusterClientReplicas < Minitest::Test include Helper::Cluster def test_client_can_command_with_replica r = build_another_client(replica: true) 100.times do |i| assert_equal 'OK', r.set("key#{i}", i) end r.wait(1, TIMEOUT.to_i * 1000) 100.times do |i| assert_equal i.to_s, r.get("key#{i}") end end def test_client_can_flush_with_replica r = build_another_client(replica: true) assert_equal 'OK', r.flushall assert_equal 'OK', r.flushdb end def test_some_reference_commands_are_sent_to_slaves_if_needed r = build_another_client(replica: true) 5.times { |i| r.set("key#{i}", i) } r.wait(1, TIMEOUT.to_i * 1000) assert_equal %w[key0 key1 key2 key3 key4], r.keys assert_equal 5, r.dbsize end end redis-rb-4.1.2/test/cluster_client_slots_test.rb000066400000000000000000000067111347371274700220570ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_client_slots_test.rb class TestClusterClientSlots < Minitest::Test include Helper::Cluster def test_slot_class slot = Redis::Cluster::Slot.new('127.0.0.1:7000' => 1..10) assert_equal false, slot.exists?(0) assert_equal true, slot.exists?(1) assert_equal true, slot.exists?(10) assert_equal false, slot.exists?(11) assert_nil slot.find_node_key_of_master(0) assert_nil slot.find_node_key_of_slave(0) assert_equal '127.0.0.1:7000', slot.find_node_key_of_master(1) assert_equal '127.0.0.1:7000', slot.find_node_key_of_slave(1) assert_equal '127.0.0.1:7000', slot.find_node_key_of_master(10) assert_equal '127.0.0.1:7000', slot.find_node_key_of_slave(10) assert_nil slot.find_node_key_of_master(11) assert_nil slot.find_node_key_of_slave(11) assert_nil slot.put(1, '127.0.0.1:7001') end def test_slot_class_with_node_flags_and_replicas slot = Redis::Cluster::Slot.new({ '127.0.0.1:7000' => 1..10, '127.0.0.1:7001' => 1..10 }, { '127.0.0.1:7000' => 'master', '127.0.0.1:7001' => 'slave' }, true) assert_equal false, slot.exists?(0) assert_equal true, slot.exists?(1) assert_equal true, slot.exists?(10) assert_equal false, slot.exists?(11) assert_nil slot.find_node_key_of_master(0) assert_nil slot.find_node_key_of_slave(0) assert_equal '127.0.0.1:7000', slot.find_node_key_of_master(1) assert_equal '127.0.0.1:7001', slot.find_node_key_of_slave(1) assert_equal '127.0.0.1:7000', slot.find_node_key_of_master(10) assert_equal '127.0.0.1:7001', slot.find_node_key_of_slave(10) assert_nil slot.find_node_key_of_master(11) assert_nil slot.find_node_key_of_slave(11) assert_nil slot.put(1, '127.0.0.1:7002') end def test_slot_class_with_node_flags_and_without_replicas slot = Redis::Cluster::Slot.new({ '127.0.0.1:7000' => 1..10, '127.0.0.1:7001' => 1..10 }, { '127.0.0.1:7000' => 'master', '127.0.0.1:7001' => 'slave' }, false) assert_equal false, slot.exists?(0) assert_equal true, slot.exists?(1) assert_equal true, slot.exists?(10) assert_equal false, slot.exists?(11) assert_nil slot.find_node_key_of_master(0) assert_nil slot.find_node_key_of_slave(0) assert_equal '127.0.0.1:7000', slot.find_node_key_of_master(1) assert_equal '127.0.0.1:7000', slot.find_node_key_of_slave(1) assert_equal '127.0.0.1:7000', slot.find_node_key_of_master(10) assert_equal '127.0.0.1:7000', slot.find_node_key_of_slave(10) assert_nil slot.find_node_key_of_master(11) assert_nil slot.find_node_key_of_slave(11) assert_nil slot.put(1, '127.0.0.1:7002') end def test_slot_class_with_empty_slots slot = Redis::Cluster::Slot.new({}) assert_equal false, slot.exists?(0) assert_equal false, slot.exists?(1) assert_nil slot.find_node_key_of_master(0) assert_nil slot.find_node_key_of_slave(0) assert_nil slot.find_node_key_of_master(1) assert_nil slot.find_node_key_of_slave(1) assert_nil slot.put(1, '127.0.0.1:7001') end def test_redirection_when_slot_is_resharding 100.times { |i| redis.set("{key}#{i}", i) } redis_cluster_resharding(12539, src: '127.0.0.1:7002', dest: '127.0.0.1:7000') do 100.times { |i| assert_equal i.to_s, redis.get("{key}#{i}") } end end end redis-rb-4.1.2/test/cluster_client_transactions_test.rb000066400000000000000000000032341347371274700234200ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_client_transactions_test.rb class TestClusterClientTransactions < Minitest::Test include Helper::Cluster def test_transaction_with_hash_tag rc1 = redis rc2 = build_another_client rc1.multi do |cli| 100.times { |i| cli.set("{key}#{i}", i) } end 100.times { |i| assert_equal i.to_s, rc1.get("{key}#{i}") } 100.times { |i| assert_equal i.to_s, rc2.get("{key}#{i}") } end def test_transaction_without_hash_tag rc1 = redis rc2 = build_another_client assert_raises(Redis::Cluster::CrossSlotPipeliningError) do rc1.multi do |cli| 100.times { |i| cli.set("key#{i}", i) } end end 100.times { |i| assert_nil rc1.get("key#{i}") } 100.times { |i| assert_nil rc2.get("key#{i}") } end def test_transaction_with_replicas rc1 = build_another_client(replica: true) rc2 = build_another_client(replica: true) rc1.multi do |cli| 100.times { |i| cli.set("{key}#{i}", i) } end rc1.wait(1, TIMEOUT.to_i * 1000) 100.times { |i| assert_equal i.to_s, rc1.get("{key}#{i}") } 100.times { |i| assert_equal i.to_s, rc2.get("{key}#{i}") } end def test_transaction_with_watch rc1 = redis rc2 = build_another_client rc1.set('{key}1', 100) rc1.watch('{key}1') rc2.set('{key}1', 200) val = rc1.get('{key}1').to_i val += 1 rc1.multi do |cli| cli.set('{key}1', val) cli.set('{key}2', 300) end assert_equal '200', rc1.get('{key}1') assert_equal '200', rc2.get('{key}1') assert_nil rc1.get('{key}2') assert_nil rc2.get('{key}2') end end redis-rb-4.1.2/test/cluster_commands_on_cluster_test.rb000066400000000000000000000133611347371274700234120ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_cluster_test.rb # @see https://redis.io/commands#cluster class TestClusterCommandsOnCluster < Minitest::Test include Helper::Cluster def test_cluster_addslots assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER ADDSLOTS command should be...') do redis.cluster(:addslots, 0, 1, 2) end end def test_cluster_count_failure_reports assert_raises(Redis::CommandError, 'ERR Unknown node unknown-node-id') do redis.cluster('count-failure-reports', 'unknown-node-id') end node_id = redis.cluster(:nodes).first.fetch('node_id') assert_equal true,(redis.cluster('count-failure-reports', node_id) >= 0) end def test_cluster_countkeysinslot assert_equal true,(redis.cluster(:countkeysinslot, 0) >= 0) assert_equal true,(redis.cluster(:countkeysinslot, 16383) >= 0) assert_raises(Redis::CommandError, 'ERR Invalid slot') do redis.cluster(:countkeysinslot, -1) end assert_raises(Redis::CommandError, 'ERR Invalid slot') do redis.cluster(:countkeysinslot, 16384) end end def test_cluster_delslots assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER DELSLOTS command should be...') do redis.cluster(:delslots, 0, 1, 2) end end def test_cluster_failover assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER FAILOVER command should be...') do redis.cluster(:failover, 'FORCE') end end def test_cluster_forget assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER FORGET command should be...') do redis.cluster(:forget, 'unknown-node-id') end end def test_cluster_getkeysinslot assert_instance_of Array, redis.cluster(:getkeysinslot, 0, 3) end def test_cluster_info info = redis.cluster(:info) assert_equal '3', info.fetch('cluster_size') end def test_cluster_keyslot assert_equal Redis::Cluster::KeySlotConverter.convert('hogehoge'), redis.cluster(:keyslot, 'hogehoge') assert_equal Redis::Cluster::KeySlotConverter.convert('12345'), redis.cluster(:keyslot, '12345') assert_equal Redis::Cluster::KeySlotConverter.convert('foo'), redis.cluster(:keyslot, 'boo{foo}woo') assert_equal Redis::Cluster::KeySlotConverter.convert('antirez.is.cool'), redis.cluster(:keyslot, 'antirez.is.cool') assert_equal Redis::Cluster::KeySlotConverter.convert(''), redis.cluster(:keyslot, '') end def test_cluster_meet assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER MEET command should be...') do redis.cluster(:meet, '127.0.0.1', 11211) end end def test_cluster_nodes cluster_nodes = redis.cluster(:nodes) sample_node = cluster_nodes.first assert_equal 6, cluster_nodes.length assert_equal true, sample_node.key?('node_id') assert_equal true, sample_node.key?('ip_port') assert_equal true, sample_node.key?('flags') assert_equal true, sample_node.key?('master_node_id') assert_equal true, sample_node.key?('ping_sent') assert_equal true, sample_node.key?('pong_recv') assert_equal true, sample_node.key?('config_epoch') assert_equal true, sample_node.key?('link_state') assert_equal true, sample_node.key?('slots') end def test_cluster_replicate assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER REPLICATE command should be...') do redis.cluster(:replicate) end end def test_cluster_reset assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER RESET command should be...') do redis.cluster(:reset) end end def test_cluster_saveconfig assert_equal 'OK', redis.cluster(:saveconfig) end def test_cluster_set_config_epoch assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER SET-CONFIG-EPOCH command should be...') do redis.cluster('set-config-epoch') end end def test_cluster_setslot assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'CLUSTER SETSLOT command should be...') do redis.cluster(:setslot) end end def test_cluster_slaves cluster_nodes = redis.cluster(:nodes) sample_master_node_id = cluster_nodes.find { |n| n.fetch('master_node_id') == '-' }.fetch('node_id') sample_slave_node_id = cluster_nodes.find { |n| n.fetch('master_node_id') != '-' }.fetch('node_id') assert_equal 'slave', redis.cluster(:slaves, sample_master_node_id).first.fetch('flags').first assert_raises(Redis::CommandError, 'ERR The specified node is not a master') do redis.cluster(:slaves, sample_slave_node_id) end end def test_cluster_slots slots = redis.cluster(:slots) sample_slot = slots.first assert_equal 3, slots.length assert_equal true, sample_slot.key?('start_slot') assert_equal true, sample_slot.key?('end_slot') assert_equal true, sample_slot.key?('master') assert_equal true, sample_slot.fetch('master').key?('ip') assert_equal true, sample_slot.fetch('master').key?('port') assert_equal true, sample_slot.fetch('master').key?('node_id') assert_equal true, sample_slot.key?('replicas') assert_equal true, sample_slot.fetch('replicas').is_a?(Array) assert_equal true, sample_slot.fetch('replicas').first.key?('ip') assert_equal true, sample_slot.fetch('replicas').first.key?('port') assert_equal true, sample_slot.fetch('replicas').first.key?('node_id') end def test_readonly assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'READONLY command should be...') do redis.readonly end end def test_readwrite assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'READWRITE command should be...') do redis.readwrite end end end redis-rb-4.1.2/test/cluster_commands_on_connection_test.rb000066400000000000000000000016311347371274700240650ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_connection_test.rb # @see https://redis.io/commands#connection class TestClusterCommandsOnConnection < Minitest::Test include Helper::Cluster def test_auth redis_cluster_mock(auth: ->(*_) { '+OK' }) do |redis| assert_equal 'OK', redis.auth('my-password-123') end end def test_echo assert_equal 'hogehoge', redis.echo('hogehoge') end def test_ping assert_equal 'hogehoge', redis.ping('hogehoge') end def test_quit redis2 = build_another_client assert_equal 'OK', redis2.quit end def test_select assert_raises(Redis::CommandError, 'ERR SELECT is not allowed in cluster mode') do redis.select(1) end end def test_swapdb assert_raises(Redis::CommandError, 'ERR SWAPDB is not allowed in cluster mode') do redis.swapdb(1, 2) end end end redis-rb-4.1.2/test/cluster_commands_on_geo_test.rb000066400000000000000000000045371347371274700225100ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_geo_test.rb # @see https://redis.io/commands#geo class TestClusterCommandsOnGeo < Minitest::Test include Helper::Cluster MIN_REDIS_VERSION = '3.2.0' def add_sicily redis.geoadd('Sicily', 13.361389, 38.115556, 'Palermo', 15.087269, 37.502669, 'Catania') end def test_geoadd target_version(MIN_REDIS_VERSION) do assert_equal 2, add_sicily end end def test_geohash target_version(MIN_REDIS_VERSION) do add_sicily assert_equal %w[sqc8b49rny0 sqdtr74hyu0], redis.geohash('Sicily', %w[Palermo Catania]) end end def test_geopos target_version(MIN_REDIS_VERSION) do add_sicily expected = [%w[13.36138933897018433 38.11555639549629859], %w[15.08726745843887329 37.50266842333162032], nil] assert_equal expected, redis.geopos('Sicily', %w[Palermo Catania NonExisting]) end end def test_geodist target_version(MIN_REDIS_VERSION) do add_sicily assert_equal '166274.1516', redis.geodist('Sicily', 'Palermo', 'Catania') assert_equal '166.2742', redis.geodist('Sicily', 'Palermo', 'Catania', 'km') assert_equal '103.3182', redis.geodist('Sicily', 'Palermo', 'Catania', 'mi') end end def test_georadius target_version(MIN_REDIS_VERSION) do add_sicily expected = [%w[Palermo 190.4424], %w[Catania 56.4413]] assert_equal expected, redis.georadius('Sicily', 15, 37, 200, 'km', 'WITHDIST') expected = [['Palermo', %w[13.36138933897018433 38.11555639549629859]], ['Catania', %w[15.08726745843887329 37.50266842333162032]]] assert_equal expected, redis.georadius('Sicily', 15, 37, 200, 'km', 'WITHCOORD') expected = [['Palermo', '190.4424', %w[13.36138933897018433 38.11555639549629859]], ['Catania', '56.4413', %w[15.08726745843887329 37.50266842333162032]]] assert_equal expected, redis.georadius('Sicily', 15, 37, 200, 'km', 'WITHDIST', 'WITHCOORD') end end def test_georadiusbymember target_version(MIN_REDIS_VERSION) do redis.geoadd('Sicily', 13.583333, 37.316667, 'Agrigento') add_sicily assert_equal %w[Agrigento Palermo], redis.georadiusbymember('Sicily', 'Agrigento', 100, 'km') end end end redis-rb-4.1.2/test/cluster_commands_on_hashes_test.rb000066400000000000000000000004401347371274700231760ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/hashes' # ruby -w -Itest test/cluster_commands_on_hashes_test.rb # @see https://redis.io/commands#hash class TestClusterCommandsOnHashes < Minitest::Test include Helper::Cluster include Lint::Hashes end redis-rb-4.1.2/test/cluster_commands_on_hyper_log_log_test.rb000066400000000000000000000006261347371274700245620ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/hyper_log_log' # ruby -w -Itest test/cluster_commands_on_hyper_log_log_test.rb # @see https://redis.io/commands#hyperloglog class TestClusterCommandsOnHyperLogLog < Minitest::Test include Helper::Cluster include Lint::HyperLogLog def test_pfmerge assert_raises Redis::CommandError do super end end end redis-rb-4.1.2/test/cluster_commands_on_keys_test.rb000066400000000000000000000071051347371274700227030ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_keys_test.rb # @see https://redis.io/commands#generic class TestClusterCommandsOnKeys < Minitest::Test include Helper::Cluster def set_some_keys redis.set('key1', 'Hello') redis.set('key2', 'World') redis.set('{key}1', 'Hello') redis.set('{key}2', 'World') end def test_del set_some_keys assert_raises(Redis::CommandError, "CROSSSLOT Keys in request don't hash to the same slot") do redis.del('key1', 'key2') end assert_equal 2, redis.del('{key}1', '{key}2') end def test_migrate redis.set('mykey', 1) assert_raises(Redis::CommandError, 'ERR Target instance replied with error: MOVED 14687 127.0.0.1:7002') do # We cannot move between cluster nodes. redis.migrate('mykey', host: '127.0.0.1', port: 7000) end redis_cluster_mock(migrate: ->(*_) { '-IOERR error or timeout writing to target instance' }) do |redis| assert_raises(Redis::CommandError, 'IOERR error or timeout writing to target instance') do redis.migrate('mykey', host: '127.0.0.1', port: 11211) end end redis_cluster_mock(migrate: ->(*_) { '+OK' }) do |redis| assert_equal 'OK', redis.migrate('mykey', host: '127.0.0.1', port: 6379) end end def test_object redis.lpush('mylist', 'Hello World') assert_equal 1, redis.object('refcount', 'mylist') expected_encoding = version < '3.2.0' ? 'ziplist' : 'quicklist' assert_equal expected_encoding, redis.object('encoding', 'mylist') expected_instance_type = RUBY_VERSION < '2.4.0' ? Fixnum : Integer assert_instance_of expected_instance_type, redis.object('idletime', 'mylist') redis.set('foo', 1000) assert_equal 'int', redis.object('encoding', 'foo') redis.set('bar', '1000bar') assert_equal 'embstr', redis.object('encoding', 'bar') end def test_randomkey set_some_keys assert_equal true, redis.randomkey.is_a?(String) end def test_rename set_some_keys assert_raises(Redis::CommandError, "CROSSSLOT Keys in request don't hash to the same slot") do redis.rename('key1', 'key3') end assert_equal 'OK', redis.rename('{key}1', '{key}3') end def test_renamenx set_some_keys assert_raises(Redis::CommandError, "CROSSSLOT Keys in request don't hash to the same slot") do redis.renamenx('key1', 'key2') end assert_equal false, redis.renamenx('{key}1', '{key}2') end def test_sort redis.lpush('mylist', 3) redis.lpush('mylist', 1) redis.lpush('mylist', 5) redis.lpush('mylist', 2) redis.lpush('mylist', 4) assert_equal %w[1 2 3 4 5], redis.sort('mylist') end def test_touch target_version('3.2.1') do set_some_keys assert_equal 1, redis.touch('key1') assert_equal 1, redis.touch('key2') assert_equal 1, redis.touch('key1', 'key2') assert_equal 2, redis.touch('{key}1', '{key}2') end end def test_unlink target_version('4.0.0') do set_some_keys assert_raises(Redis::CommandError, "CROSSSLOT Keys in request don't hash to the same slot") do redis.unlink('key1', 'key2', 'key3') end assert_equal 2, redis.unlink('{key}1', '{key}2', '{key}3') end end def test_wait set_some_keys assert_equal 3, redis.wait(1, TIMEOUT.to_i * 1000) end def test_scan set_some_keys cursor = 0 all_keys = [] loop do cursor, keys = redis.scan(cursor, match: '{key}*') all_keys += keys break if cursor == '0' end assert_equal 2, all_keys.uniq.size end end redis-rb-4.1.2/test/cluster_commands_on_lists_test.rb000066400000000000000000000005511347371274700230640ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/lists' # ruby -w -Itest test/cluster_commands_on_lists_test.rb # @see https://redis.io/commands#list class TestClusterCommandsOnLists < Minitest::Test include Helper::Cluster include Lint::Lists def test_rpoplpush assert_raises(Redis::CommandError) { super } end end redis-rb-4.1.2/test/cluster_commands_on_pub_sub_test.rb000066400000000000000000000071521347371274700233710ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_pub_sub_test.rb # @see https://redis.io/commands#pubsub class TestClusterCommandsOnPubSub < Minitest::Test include Helper::Cluster def test_publish_subscribe_unsubscribe_pubsub sub_cnt = 0 messages = {} wire = Wire.new do redis.subscribe('channel1', 'channel2') do |on| on.subscribe { |_c, t| sub_cnt = t } on.unsubscribe { |_c, t| sub_cnt = t } on.message do |c, msg| messages[c] = msg # FIXME: blocking occurs when `unsubscribe` method was called with channel arguments redis.unsubscribe if messages.size == 2 end end end Wire.pass until sub_cnt == 2 publisher = build_another_client assert_equal %w[channel1 channel2], publisher.pubsub(:channels) assert_equal %w[channel1 channel2], publisher.pubsub(:channels, 'cha*') assert_equal [], publisher.pubsub(:channels, 'chachacha*') assert_equal({}, publisher.pubsub(:numsub)) assert_equal({ 'channel1' => 1, 'channel2' => 1, 'channel3' => 0 }, publisher.pubsub(:numsub, 'channel1', 'channel2', 'channel3')) assert_equal 0, publisher.pubsub(:numpat) publisher.publish('channel1', 'one') publisher.publish('channel2', 'two') wire.join assert_equal({ 'channel1' => 'one', 'channel2' => 'two' }, messages.sort.to_h) assert_equal [], publisher.pubsub(:channels) assert_equal [], publisher.pubsub(:channels, 'cha*') assert_equal [], publisher.pubsub(:channels, 'chachacha*') assert_equal({}, publisher.pubsub(:numsub)) assert_equal({ 'channel1' => 0, 'channel2' => 0, 'channel3' => 0 }, publisher.pubsub(:numsub, 'channel1', 'channel2', 'channel3')) assert_equal 0, publisher.pubsub(:numpat) end def test_publish_psubscribe_punsubscribe_pubsub sub_cnt = 0 messages = {} wire = Wire.new do redis.psubscribe('cha*', 'her*') do |on| on.psubscribe { |_c, t| sub_cnt = t } on.punsubscribe { |_c, t| sub_cnt = t } on.pmessage do |_ptn, chn, msg| messages[chn] = msg # FIXME: blocking occurs when `unsubscribe` method was called with channel arguments redis.punsubscribe if messages.size == 3 end end end Wire.pass until sub_cnt == 2 publisher = build_another_client assert_equal [], publisher.pubsub(:channels) assert_equal [], publisher.pubsub(:channels, 'cha*') assert_equal [], publisher.pubsub(:channels, 'her*') assert_equal [], publisher.pubsub(:channels, 'guc*') assert_equal({}, publisher.pubsub(:numsub)) assert_equal({ 'channel1' => 0, 'channel2' => 0, 'hermes3' => 0, 'gucci4' => 0 }, publisher.pubsub(:numsub, 'channel1', 'channel2', 'hermes3', 'gucci4')) assert_equal 2, publisher.pubsub(:numpat) publisher.publish('chanel1', 'one') publisher.publish('chanel2', 'two') publisher.publish('hermes3', 'three') publisher.publish('gucci4', 'four') wire.join assert_equal({ 'chanel1' => 'one', 'chanel2' => 'two', 'hermes3' => 'three' }, messages.sort.to_h) assert_equal [], publisher.pubsub(:channels) assert_equal [], publisher.pubsub(:channels, 'cha*') assert_equal [], publisher.pubsub(:channels, 'her*') assert_equal [], publisher.pubsub(:channels, 'guc*') assert_equal({}, publisher.pubsub(:numsub)) assert_equal({ 'channel1' => 0, 'channel2' => 0, 'hermes3' => 0, 'gucci4' => 0 }, publisher.pubsub(:numsub, 'channel1', 'channel2', 'hermes3', 'gucci4')) assert_equal 0, publisher.pubsub(:numpat) end end redis-rb-4.1.2/test/cluster_commands_on_scripting_test.rb000066400000000000000000000031171347371274700237310ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_scripting_test.rb # @see https://redis.io/commands#scripting class TestClusterCommandsOnScripting < Minitest::Test include Helper::Cluster def test_eval script = 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}' argv = %w[first second] keys = %w[key1 key2] assert_raises(Redis::CommandError, "CROSSSLOT Keys in request don't hash to the same slot") do redis.eval(script, keys: keys, argv: argv) end keys = %w[{key}1 {key}2] expected = %w[{key}1 {key}2 first second] assert_equal expected, redis.eval(script, keys: keys, argv: argv) end def test_evalsha sha = redis.script(:load, 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}') expected = %w[{key}1 {key}2 first second] assert_equal expected, redis.evalsha(sha, keys: %w[{key}1 {key}2], argv: %w[first second]) end def test_script_debug target_version('3.2.0') do assert_equal 'OK', redis.script(:debug, 'yes') assert_equal 'OK', redis.script(:debug, 'no') end end def test_script_exists sha = redis.script(:load, 'return 1') assert_equal true, redis.script(:exists, sha) assert_equal false, redis.script(:exists, 'unknownsha') end def test_script_flush assert_equal 'OK', redis.script(:flush) end def test_script_kill redis_cluster_mock(kill: -> { '+OK' }) do |redis| assert_equal 'OK', redis.script(:kill) end end def test_script_load assert_equal 'e0e1f9fabfc9d4800c877a703b823ac0578ff8db', redis.script(:load, 'return 1') end end redis-rb-4.1.2/test/cluster_commands_on_server_test.rb000066400000000000000000000130741347371274700232400ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_server_test.rb # @see https://redis.io/commands#server class TestClusterCommandsOnServer < Minitest::Test include Helper::Cluster def test_bgrewriteaof assert_equal 'Background append only file rewriting started', redis.bgrewriteaof end def test_bgsave redis_cluster_mock(bgsave: ->(*_) { '+OK' }) do |redis| assert_equal 'OK', redis.bgsave end err_msg = 'ERR An AOF log rewriting in progress: '\ "can't BGSAVE right now. "\ 'Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever possible.' redis_cluster_mock(bgsave: ->(*_) { "-Error #{err_msg}" }) do |redis| assert_raises(Redis::Cluster::CommandErrorCollection, 'Command error replied on any node') do redis.bgsave end end end def test_client_kill redis_cluster_mock(client: ->(*_) { '-Error ERR No such client' }) do |redis| assert_raises(Redis::CommandError, 'ERR No such client') do redis.client(:kill, '127.0.0.1:6379') end end redis_cluster_mock(client: ->(*_) { '+OK' }) do |redis| assert_equal 'OK', redis.client(:kill, '127.0.0.1:6379') end end def test_client_list a_client_info = redis.client(:list).first actual = a_client_info.keys.sort expected = %w[addr age cmd db events fd flags id idle multi name obl oll omem psub qbuf qbuf-free sub] assert_equal expected, actual end def test_client_getname redis.client(:setname, 'my-client-01') assert_equal 'my-client-01', redis.client(:getname) end def test_client_pause assert_equal 'OK', redis.client(:pause, 0) end def test_client_reply target_version('3.2.0') do assert_equal 'OK', redis.client(:reply, 'ON') end end def test_client_setname assert_equal 'OK', redis.client(:setname, 'my-client-01') end def test_command assert_instance_of Array, redis.command end def test_command_count assert_equal true,(redis.command(:count) > 0) end def test_command_getkeys assert_equal %w[a c e], redis.command(:getkeys, :mset, 'a', 'b', 'c', 'd', 'e', 'f') end def test_command_info expected = [ ['get', 2, %w[readonly fast], 1, 1, 1], ['set', -3, %w[write denyoom], 1, 1, 1], ['eval', -3, %w[noscript movablekeys], 0, 0, 0] ] assert_equal expected, redis.command(:info, :get, :set, :eval) end def test_config_get expected_keys = if version < '3.2.0' %w[hash-max-ziplist-entries list-max-ziplist-entries set-max-intset-entries zset-max-ziplist-entries] else %w[hash-max-ziplist-entries set-max-intset-entries zset-max-ziplist-entries] end assert_equal expected_keys, redis.config(:get, '*max-*-entries*').keys.sort end def test_config_rewrite redis_cluster_mock(config: ->(*_) { '-Error ERR Rewriting config file: Permission denied' }) do |redis| assert_raises(Redis::Cluster::CommandErrorCollection, 'Command error replied on any node') do redis.config(:rewrite) end end redis_cluster_mock(config: ->(*_) { '+OK' }) do |redis| assert_equal 'OK', redis.config(:rewrite) end end def test_config_set assert_equal 'OK', redis.config(:set, 'hash-max-ziplist-entries', 512) end def test_config_resetstat assert_equal 'OK', redis.config(:resetstat) end def test_config_db_size 10.times { |i| redis.set("key#{i}", 1) } assert_equal 10, redis.dbsize end def test_debug_object # DEBUG OBJECT is a debugging command that should not be used by clients. end def test_debug_segfault # DEBUG SEGFAULT performs an invalid memory access that crashes Redis. # It is used to simulate bugs during the development. end def test_flushall assert_equal 'OK', redis.flushall end def test_flushdb assert_equal 'OK', redis.flushdb end def test_info assert_equal({ 'cluster_enabled' => '1' }, redis.info(:cluster)) end def test_lastsave assert_instance_of Array, redis.lastsave end def test_memory_doctor target_version('4.0.0') do assert_instance_of String, redis.memory(:doctor) end end def test_memory_help target_version('4.0.0') do assert_instance_of Array, redis.memory(:help) end end def test_memory_malloc_stats target_version('4.0.0') do assert_instance_of String, redis.memory('malloc-stats') end end def test_memory_purge target_version('4.0.0') do assert_equal 'OK', redis.memory(:purge) end end def test_memory_stats target_version('4.0.0') do assert_instance_of Array, redis.memory(:stats) end end def test_memory_usage target_version('4.0.0') do redis.set('key1', 'Hello World') assert_operator redis.memory(:usage, 'key1'), :>, 0 end end def test_monitor # Add MONITOR command test end def test_role assert_equal %w[master master master], redis.role.map(&:first) end def test_save assert_equal 'OK', redis.save end def test_shutdown assert_raises(Redis::Cluster::OrchestrationCommandNotSupported, 'SHUTDOWN command should be...') do redis.shutdown end end def test_slaveof assert_raises(Redis::CommandError, 'ERR SLAVEOF not allowed in cluster mode.') do redis.slaveof(:no, :one) end end def test_slowlog assert_instance_of Array, redis.slowlog(:get, 1) end def test_sync # Internal command used for replication end def test_time assert_instance_of Array, redis.time end end redis-rb-4.1.2/test/cluster_commands_on_sets_test.rb000066400000000000000000000014511347371274700227040ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/sets' # ruby -w -Itest test/cluster_commands_on_sets_test.rb # @see https://redis.io/commands#set class TestClusterCommandsOnSets < Minitest::Test include Helper::Cluster include Lint::Sets def test_sdiff assert_raises(Redis::CommandError) { super } end def test_sdiffstore assert_raises(Redis::CommandError) { super } end def test_sinter assert_raises(Redis::CommandError) { super } end def test_sinterstore assert_raises(Redis::CommandError) { super } end def test_smove assert_raises(Redis::CommandError) { super } end def test_sunion assert_raises(Redis::CommandError) { super } end def test_sunionstore assert_raises(Redis::CommandError) { super } end end redis-rb-4.1.2/test/cluster_commands_on_sorted_sets_test.rb000066400000000000000000000015121347371274700242620ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/sorted_sets' # ruby -w -Itest test/cluster_commands_on_sorted_sets_test.rb # @see https://redis.io/commands#sorted_set class TestClusterCommandsOnSortedSets < Minitest::Test include Helper::Cluster include Lint::SortedSets def test_zinterstore assert_raises(Redis::CommandError) { super } end def test_zinterstore_with_aggregate assert_raises(Redis::CommandError) { super } end def test_zinterstore_with_weights assert_raises(Redis::CommandError) { super } end def test_zunionstore assert_raises(Redis::CommandError) { super } end def test_zunionstore_with_aggregate assert_raises(Redis::CommandError) { super } end def test_zunionstore_with_weights assert_raises(Redis::CommandError) { super } end end redis-rb-4.1.2/test/cluster_commands_on_streams_test.rb000066400000000000000000000034531347371274700234100ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/streams' # ruby -w -Itest test/cluster_commands_on_streams_test.rb # @see https://redis.io/commands#stream class TestClusterCommandsOnStreams < Minitest::Test include Helper::Cluster include Lint::Streams def test_xread_with_multiple_keys err_msg = "CROSSSLOT Keys in request don't hash to the same slot" assert_raises(Redis::CommandError, err_msg) { super } end def test_xread_with_multiple_keys_and_hash_tags redis.xadd('{s}1', { f: 'v01' }, id: '0-1') redis.xadd('{s}1', { f: 'v02' }, id: '0-2') redis.xadd('{s}2', { f: 'v11' }, id: '1-1') redis.xadd('{s}2', { f: 'v12' }, id: '1-2') actual = redis.xread(%w[{s}1 {s}2], %w[0-1 1-1]) assert_equal %w(0-2), actual['{s}1'].map(&:first) assert_equal %w(v02), actual['{s}1'].map { |i| i.last['f'] } assert_equal %w(1-2), actual['{s}2'].map(&:first) assert_equal %w(v12), actual['{s}2'].map { |i| i.last['f'] } end def test_xreadgroup_with_multiple_keys err_msg = "CROSSSLOT Keys in request don't hash to the same slot" assert_raises(Redis::CommandError, err_msg) { super } end def test_xreadgroup_with_multiple_keys_and_hash_tags redis.xadd('{s}1', { f: 'v01' }, id: '0-1') redis.xgroup(:create, '{s}1', 'g1', '$') redis.xadd('{s}2', { f: 'v11' }, id: '1-1') redis.xgroup(:create, '{s}2', 'g1', '$') redis.xadd('{s}1', { f: 'v02' }, id: '0-2') redis.xadd('{s}2', { f: 'v12' }, id: '1-2') actual = redis.xreadgroup('g1', 'c1', %w[{s}1 {s}2], %w[> >]) assert_equal %w(0-2), actual['{s}1'].map(&:first) assert_equal %w(v02), actual['{s}1'].map { |i| i.last['f'] } assert_equal %w(1-2), actual['{s}2'].map(&:first) assert_equal %w(v12), actual['{s}2'].map { |i| i.last['f'] } end end redis-rb-4.1.2/test/cluster_commands_on_strings_test.rb000066400000000000000000000005551347371274700234230ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/strings' # ruby -w -Itest test/cluster_commands_on_strings_test.rb # @see https://redis.io/commands#string class TestClusterCommandsOnStrings < Minitest::Test include Helper::Cluster include Lint::Strings def mock(*args, &block) redis_cluster_mock(*args, &block) end end redis-rb-4.1.2/test/cluster_commands_on_transactions_test.rb000066400000000000000000000016151347371274700244400ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # ruby -w -Itest test/cluster_commands_on_transactions_test.rb # @see https://redis.io/commands#transactions class TestClusterCommandsOnTransactions < Minitest::Test include Helper::Cluster def test_discard assert_raises(Redis::Cluster::AmbiguousNodeError) do redis.discard end end def test_exec assert_raises(Redis::Cluster::AmbiguousNodeError) do redis.exec end end def test_multi assert_raises(Redis::Cluster::AmbiguousNodeError) do redis.multi end end def test_unwatch assert_raises(Redis::Cluster::AmbiguousNodeError) do redis.unwatch end end def test_watch assert_raises(Redis::CommandError, "CROSSSLOT Keys in request don't hash to the same slot") do redis.watch('key1', 'key2') end assert_equal 'OK', redis.watch('{key}1', '{key}2') end end redis-rb-4.1.2/test/cluster_commands_on_value_types_test.rb000066400000000000000000000005241347371274700242660ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/value_types' # ruby -w -Itest test/cluster_commands_on_value_types_test.rb class TestClusterCommandsOnValueTypes < Minitest::Test include Helper::Cluster include Lint::ValueTypes def test_move assert_raises(Redis::CommandError) { super } end end redis-rb-4.1.2/test/command_map_test.rb000066400000000000000000000010211347371274700200540ustar00rootroot00000000000000require_relative "helper" class TestCommandMap < Minitest::Test include Helper::Client def test_override_existing_commands r.set("counter", 1) assert_equal 2, r.incr("counter") r._client.command_map[:incr] = :decr assert_equal 1, r.incr("counter") end def test_override_non_existing_commands r.set("key", "value") assert_raises Redis::CommandError do r.idontexist("key") end r._client.command_map[:idontexist] = :get assert_equal "value", r.idontexist("key") end end redis-rb-4.1.2/test/commands_on_geo_test.rb000066400000000000000000000077231347371274700207470ustar00rootroot00000000000000require_relative "helper" class TestCommandsGeo < Minitest::Test include Helper::Client def setup super target_version "3.2.0" do added_items_count = r.geoadd("Sicily", 13.361389, 38.115556, "Palermo", 15.087269, 37.502669, "Catania") assert_equal 2, added_items_count end end def test_geoadd_with_array_params target_version "3.2.0" do added_items_count = r.geoadd("SicilyArray", [13.361389, 38.115556, "Palermo", 15.087269, 37.502669, "Catania"]) assert_equal 2, added_items_count end end def test_georadius_with_same_params target_version "3.2.0" do r.geoadd("Chad", 15, 15, "Kanem") nearest_cities = r.georadius("Chad", 15, 15, 15, 'km', sort: 'asc') assert_equal %w(Kanem), nearest_cities end end def test_georadius_with_sort target_version "3.2.0" do nearest_cities = r.georadius("Sicily", 15, 37, 200, 'km', sort: 'asc') assert_equal %w(Catania Palermo), nearest_cities farthest_cities = r.georadius("Sicily", 15, 37, 200, 'km', sort: 'desc') assert_equal %w(Palermo Catania), farthest_cities end end def test_georadius_with_count target_version "3.2.0" do city = r.georadius("Sicily", 15, 37, 200, 'km', count: 1) assert_equal %w(Catania), city end end def test_georadius_with_options_count_sort target_version "3.2.0" do city = r.georadius("Sicily", 15, 37, 200, 'km', sort: :desc, options: :WITHDIST, count: 1) assert_equal [["Palermo", "190.4424"]], city end end def test_georadiusbymember_with_sort target_version "3.2.0" do nearest_cities = r.georadiusbymember("Sicily", "Catania", 200, 'km', sort: 'asc') assert_equal %w(Catania Palermo), nearest_cities farthest_cities = r.georadiusbymember("Sicily", "Catania", 200, 'km', sort: 'desc') assert_equal %w(Palermo Catania), farthest_cities end end def test_georadiusbymember_with_count target_version "3.2.0" do city = r.georadiusbymember("Sicily", "Catania", 200, 'km', count: 1) assert_equal %w(Catania), city end end def test_georadiusbymember_with_options_count_sort target_version "3.2.0" do city = r.georadiusbymember("Sicily", "Catania", 200, 'km', sort: :desc, options: :WITHDIST, count: 1) assert_equal [["Palermo", "166.2742"]], city end end def test_geopos target_version "3.2.0" do location = r.geopos("Sicily", "Catania") assert_equal [["15.08726745843887329", "37.50266842333162032"]], location locations = r.geopos("Sicily", ["Palermo", "Catania"]) assert_equal [["13.36138933897018433", "38.11555639549629859"], ["15.08726745843887329", "37.50266842333162032"]], locations end end def test_geopos_nonexistant_location target_version "3.2.0" do location = r.geopos("Sicily", "Rome") assert_equal [nil], location locations = r.geopos("Sicily", ["Rome", "Catania"]) assert_equal [nil, ["15.08726745843887329", "37.50266842333162032"]], locations end end def test_geodist target_version "3.2.0" do distination_in_meters = r.geodist("Sicily", "Palermo", "Catania") assert_equal "166274.1516", distination_in_meters distination_in_feet = r.geodist("Sicily", "Palermo", "Catania", 'ft') assert_equal "545518.8700", distination_in_feet end end def test_geodist_with_nonexistant_location target_version "3.2.0" do distination = r.geodist("Sicily", "Palermo", "Rome") assert_nil distination end end def test_geohash target_version "3.2.0" do geohash = r.geohash("Sicily", "Palermo") assert_equal ["sqc8b49rny0"], geohash geohashes = r.geohash("Sicily", ["Palermo", "Catania"]) assert_equal %w(sqc8b49rny0 sqdtr74hyu0), geohashes end end def test_geohash_with_nonexistant_location target_version "3.2.0" do geohashes = r.geohash("Sicily", ["Palermo", "Rome"]) assert_equal ["sqc8b49rny0", nil], geohashes end end end redis-rb-4.1.2/test/commands_on_hashes_test.rb000066400000000000000000000002321347371274700214340ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/hashes' class TestCommandsOnHashes < Minitest::Test include Helper::Client include Lint::Hashes end redis-rb-4.1.2/test/commands_on_hyper_log_log_test.rb000066400000000000000000000002531347371274700230150ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/hyper_log_log' class TestCommandsOnHyperLogLog < Minitest::Test include Helper::Client include Lint::HyperLogLog end redis-rb-4.1.2/test/commands_on_lists_test.rb000066400000000000000000000002271347371274700213230ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/lists' class TestCommandsOnLists < Minitest::Test include Helper::Client include Lint::Lists end redis-rb-4.1.2/test/commands_on_sets_test.rb000066400000000000000000000002241347371274700211400ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/sets' class TestCommandsOnSets < Minitest::Test include Helper::Client include Lint::Sets end redis-rb-4.1.2/test/commands_on_sorted_sets_test.rb000066400000000000000000000002471347371274700225250ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/sorted_sets' class TestCommandsOnSortedSets < Minitest::Test include Helper::Client include Lint::SortedSets end redis-rb-4.1.2/test/commands_on_streams_test.rb000066400000000000000000000004261347371274700216440ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require_relative 'lint/streams' # ruby -w -Itest test/commands_on_streams_test.rb # @see https://redis.io/commands#stream class TestCommandsOnStreams < Minitest::Test include Helper::Client include Lint::Streams end redis-rb-4.1.2/test/commands_on_strings_test.rb000066400000000000000000000002351347371274700216550ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/strings' class TestCommandsOnStrings < Minitest::Test include Helper::Client include Lint::Strings end redis-rb-4.1.2/test/commands_on_value_types_test.rb000066400000000000000000000124411347371274700225260ustar00rootroot00000000000000require_relative "helper" require_relative "lint/value_types" class TestCommandsOnValueTypes < Minitest::Test include Helper::Client include Lint::ValueTypes def test_del r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del("foo") assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del("bar", "baz") assert_equal [], r.keys("*").sort end def test_del_with_array_argument r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del(["foo"]) assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del(["bar", "baz"]) assert_equal [], r.keys("*").sort end def test_unlink target_version "4.0.0" do r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.unlink("foo") assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.unlink("bar", "baz") assert_equal [], r.keys("*").sort end end def test_unlink_with_array_argument target_version "4.0.0" do r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.unlink(["foo"]) assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.unlink(["bar", "baz"]) assert_equal [], r.keys("*").sort end end def test_randomkey assert r.randomkey.to_s.empty? r.set("foo", "s1") assert_equal "foo", r.randomkey r.set("bar", "s2") 4.times do assert ["foo", "bar"].include?(r.randomkey) end end def test_rename r.set("foo", "s1") r.rename "foo", "bar" assert_equal "s1", r.get("bar") assert_nil r.get("foo") end def test_renamenx r.set("foo", "s1") r.set("bar", "s2") assert_equal false, r.renamenx("foo", "bar") assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") end def test_dbsize assert_equal 0, r.dbsize r.set("foo", "s1") assert_equal 1, r.dbsize end def test_flushdb # Test defaults r.set("foo", "s1") r.set("bar", "s2") assert_equal 2, r.dbsize r.flushdb assert_equal 0, r.dbsize # Test sync r.set("foo", "s1") r.set("bar", "s2") assert_equal 2, r.dbsize r.flushdb(:async => false) assert_equal 0, r.dbsize # Test async target_version "3.9.101" do r.set("foo", "s1") r.set("bar", "s2") assert_equal 2, r.dbsize r.flushdb(:async => true) assert_equal 0, r.dbsize redis_mock(:flushdb => lambda { |args| "+FLUSHDB #{args.upcase}" }) do |redis| assert_equal "FLUSHDB ASYNC", redis.flushdb(:async => true) end end end def test_flushall # Test defaults redis_mock(:flushall => lambda { "+FLUSHALL" }) do |redis| assert_equal "FLUSHALL", redis.flushall end # Test sync redis_mock(:flushall => lambda { "+FLUSHALL" }) do |redis| assert_equal "FLUSHALL", redis.flushall(:async => false) end # Test async target_version "3.9.101" do redis_mock(:flushall => lambda { |args| "+FLUSHALL #{args.upcase}" }) do |redis| assert_equal "FLUSHALL ASYNC", redis.flushall(:async => true) end end end def test_migrate redis_mock(:migrate => lambda { |*args| args }) do |redis| options = { :host => "127.0.0.1", :port => 1234 } ex = assert_raises(RuntimeError) do redis.migrate("foo", options.reject { |key, _| key == :host }) end assert ex.message =~ /host not specified/ ex = assert_raises(RuntimeError) do redis.migrate("foo", options.reject { |key, _| key == :port }) end assert ex.message =~ /port not specified/ default_db = redis._client.db.to_i default_timeout = redis._client.timeout.to_i # Test defaults actual = redis.migrate("foo", options) expected = ["127.0.0.1", "1234", "foo", default_db.to_s, default_timeout.to_s] assert_equal expected, actual # Test db override actual = redis.migrate("foo", options.merge(:db => default_db + 1)) expected = ["127.0.0.1", "1234", "foo", (default_db + 1).to_s, default_timeout.to_s] assert_equal expected, actual # Test timeout override actual = redis.migrate("foo", options.merge(:timeout => default_timeout + 1)) expected = ["127.0.0.1", "1234", "foo", default_db.to_s, (default_timeout + 1).to_s] assert_equal expected, actual # Test copy override actual = redis.migrate('foo', options.merge(copy: true)) expected = ['127.0.0.1', '1234', 'foo', default_db.to_s, default_timeout.to_s, 'COPY'] assert_equal expected, actual # Test replace override actual = redis.migrate('foo', options.merge(replace: true)) expected = ['127.0.0.1', '1234', 'foo', default_db.to_s, default_timeout.to_s, 'REPLACE'] assert_equal expected, actual # Test multiple keys actual = redis.migrate(%w[foo bar baz], options) expected = ['127.0.0.1', '1234', '', default_db.to_s, default_timeout.to_s, 'KEYS', 'foo', 'bar', 'baz'] assert_equal expected, actual end end end redis-rb-4.1.2/test/connection_handling_test.rb000066400000000000000000000134131347371274700216140ustar00rootroot00000000000000require_relative "helper" class TestConnectionHandling < Minitest::Test include Helper::Client def test_auth commands = { :auth => lambda { |password| $auth = password; "+OK" }, :get => lambda { |key| $auth == "secret" ? "$3\r\nbar" : "$-1" }, } redis_mock(commands, :password => "secret") do |redis| assert_equal "bar", redis.get("foo") end end def test_id commands = { :client => lambda { |cmd, name| $name = [cmd, name]; "+OK" }, :ping => lambda { "+PONG" }, } redis_mock(commands, :id => "client-name") do |redis| assert_equal "PONG", redis.ping end assert_equal ["setname","client-name"], $name end def test_ping assert_equal "PONG", r.ping end def test_select r.set "foo", "bar" r.select 14 assert_nil r.get("foo") r._client.disconnect assert_nil r.get("foo") end def test_quit r.quit assert !r._client.connected? end def test_close quit = 0 commands = { :quit => lambda do quit += 1 "+OK" end } redis_mock(commands) do |redis| assert_equal 0, quit redis.quit assert_equal 1, quit redis.ping redis.close assert_equal 1, quit assert !redis.connected? end end def test_disconnect quit = 0 commands = { :quit => lambda do quit += 1 "+OK" end } redis_mock(commands) do |redis| assert_equal 0, quit redis.quit assert_equal 1, quit redis.ping redis.disconnect! assert_equal 1, quit assert !redis.connected? end end def test_shutdown commands = { :shutdown => lambda { :exit } } redis_mock(commands) do |redis| # SHUTDOWN does not reply: test that it does not raise here. assert_nil redis.shutdown end end def test_shutdown_with_error connections = 0 commands = { :select => lambda { |*_| connections += 1; "+OK\r\n" }, :connections => lambda { ":#{connections}\r\n" }, :shutdown => lambda { "-ERR could not shutdown\r\n" } } redis_mock(commands) do |redis| connections = redis.connections # SHUTDOWN replies with an error: test that it gets raised assert_raises Redis::CommandError do redis.shutdown end # The connection should remain in tact assert_equal connections, redis.connections end end def test_shutdown_from_pipeline commands = { :shutdown => lambda { :exit } } redis_mock(commands) do |redis| result = redis.pipelined do redis.shutdown end assert_nil result assert !redis._client.connected? end end def test_shutdown_with_error_from_pipeline connections = 0 commands = { :select => lambda { |*_| connections += 1; "+OK\r\n" }, :connections => lambda { ":#{connections}\r\n" }, :shutdown => lambda { "-ERR could not shutdown\r\n" } } redis_mock(commands) do |redis| connections = redis.connections # SHUTDOWN replies with an error: test that it gets raised assert_raises Redis::CommandError do redis.pipelined do redis.shutdown end end # The connection should remain in tact assert_equal connections, redis.connections end end def test_shutdown_from_multi_exec commands = { :multi => lambda { "+OK\r\n" }, :shutdown => lambda { "+QUEUED\r\n" }, :exec => lambda { :exit } } redis_mock(commands) do |redis| result = redis.multi do redis.shutdown end assert_nil result assert !redis._client.connected? end end def test_shutdown_with_error_from_multi_exec connections = 0 commands = { :select => lambda { |*_| connections += 1; "+OK\r\n" }, :connections => lambda { ":#{connections}\r\n" }, :multi => lambda { "+OK\r\n" }, :shutdown => lambda { "+QUEUED\r\n" }, :exec => lambda { "*1\r\n-ERR could not shutdown\r\n" } } redis_mock(commands) do |redis| connections = redis.connections # SHUTDOWN replies with an error: test that it gets returned # We should test for Redis::CommandError here, but hiredis doesn't yet do # custom error classes. err = nil begin redis.multi { redis.shutdown } rescue => err end assert err.kind_of?(StandardError) # The connection should remain intact assert_equal connections, redis.connections end end def test_slaveof redis_mock(:slaveof => lambda { |host, port| "+SLAVEOF #{host} #{port}" }) do |redis| assert_equal "SLAVEOF somehost 6381", redis.slaveof("somehost", 6381) end end def test_bgrewriteaof redis_mock(:bgrewriteaof => lambda { "+BGREWRITEAOF" }) do |redis| assert_equal "BGREWRITEAOF", redis.bgrewriteaof end end def test_config_get assert r.config(:get, "*")["timeout"] != nil config = r.config(:get, "timeout") assert_equal ["timeout"], config.keys assert config.values.compact.size > 0 end def test_config_set begin assert_equal "OK", r.config(:set, "timeout", 200) assert_equal "200", r.config(:get, "*")["timeout"] assert_equal "OK", r.config(:set, "timeout", 100) assert_equal "100", r.config(:get, "*")["timeout"] ensure r.config :set, "timeout", 300 end end driver(:ruby, :hiredis) do def test_consistency_on_multithreaded_env t = nil commands = { :set => lambda { |key, value| t.kill; "+OK\r\n" }, :incr => lambda { |key| ":1\r\n" }, } redis_mock(commands) do |redis| t = Thread.new do redis.set("foo", "bar") end t.join assert_equal 1, redis.incr("baz") end end end end redis-rb-4.1.2/test/connection_test.rb000066400000000000000000000036531347371274700177550ustar00rootroot00000000000000require_relative "helper" class TestConnection < Minitest::Test include Helper::Client def test_provides_a_meaningful_inspect assert_equal "#", r.inspect end def test_connection_information assert_equal "127.0.0.1", r.connection.fetch(:host) assert_equal 6381, r.connection.fetch(:port) assert_equal 15, r.connection.fetch(:db) assert_equal "127.0.0.1:6381", r.connection.fetch(:location) assert_equal "redis://127.0.0.1:6381/15", r.connection.fetch(:id) end def test_default_id_with_host_and_port redis = Redis.new(OPTIONS.merge(:host => "host", :port => "1234", :db => 0)) assert_equal "redis://host:1234/0", redis.connection.fetch(:id) end def test_default_id_with_host_and_port_and_explicit_scheme redis = Redis.new(OPTIONS.merge(:host => "host", :port => "1234", :db => 0, :scheme => "foo")) assert_equal "redis://host:1234/0", redis.connection.fetch(:id) end def test_default_id_with_path redis = Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock", :db => 0)) assert_equal "redis:///tmp/redis.sock/0", redis.connection.fetch(:id) end def test_default_id_with_path_and_explicit_scheme redis = Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock", :db => 0, :scheme => "foo")) assert_equal "redis:///tmp/redis.sock/0", redis.connection.fetch(:id) end def test_override_id redis = Redis.new(OPTIONS.merge(:id => "test")) assert_equal "test", redis.connection.fetch(:id) end def test_id_inside_multi redis = Redis.new(OPTIONS) id = nil connection_id = nil redis.multi do id = redis.id connection_id = redis.connection.fetch(:id) end assert_equal "redis://127.0.0.1:6381/15", id assert_equal "redis://127.0.0.1:6381/15", connection_id end end redis-rb-4.1.2/test/db/000077500000000000000000000000001347371274700146105ustar00rootroot00000000000000redis-rb-4.1.2/test/db/.gitkeep000066400000000000000000000000001347371274700162270ustar00rootroot00000000000000redis-rb-4.1.2/test/distributed_blocking_commands_test.rb000066400000000000000000000021611347371274700236620ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/blocking_commands' class TestDistributedBlockingCommands < Minitest::Test include Helper::Distributed include Lint::BlockingCommands def test_blpop_raises assert_raises(Redis::Distributed::CannotDistribute) do r.blpop(%w[foo bar]) end end def test_blpop_raises_with_old_prototype assert_raises(Redis::Distributed::CannotDistribute) do r.blpop('foo', 'bar', 0) end end def test_brpop_raises assert_raises(Redis::Distributed::CannotDistribute) do r.brpop(%w[foo bar]) end end def test_brpop_raises_with_old_prototype assert_raises(Redis::Distributed::CannotDistribute) do r.brpop('foo', 'bar', 0) end end def test_brpoplpush_raises assert_raises(Redis::Distributed::CannotDistribute) do r.brpoplpush('foo', 'bar') end end def test_brpoplpush_raises_with_old_prototype assert_raises(Redis::Distributed::CannotDistribute) do r.brpoplpush('foo', 'bar', 0) end end def test_bzpopmin # Not implemented yet end def test_bzpopmax # Not implemented yet end end redis-rb-4.1.2/test/distributed_commands_on_hashes_test.rb000066400000000000000000000006311347371274700240410ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/hashes' class TestDistributedCommandsOnHashes < Minitest::Test include Helper::Distributed include Lint::Hashes def test_hscan # Not implemented yet end def test_hstrlen # Not implemented yet end def test_mapped_hmget_in_a_pipeline_returns_hash assert_raises(Redis::Distributed::CannotDistribute) do super end end end redis-rb-4.1.2/test/distributed_commands_on_hyper_log_log_test.rb000066400000000000000000000011251347371274700254160ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/hyper_log_log' class TestDistributedCommandsOnHyperLogLog < Minitest::Test include Helper::Distributed include Lint::HyperLogLog def test_pfmerge target_version '2.8.9' do assert_raises Redis::Distributed::CannotDistribute do super end end end def test_pfcount_multiple_keys_diff_nodes target_version '2.8.9' do assert_raises Redis::Distributed::CannotDistribute do r.pfadd 'foo', 's1' r.pfadd 'bar', 's2' assert r.pfcount('res', 'foo', 'bar') end end end end redis-rb-4.1.2/test/distributed_commands_on_lists_test.rb000066400000000000000000000006611347371274700237270ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/lists' class TestDistributedCommandsOnLists < Minitest::Test include Helper::Distributed include Lint::Lists def test_rpoplpush assert_raises Redis::Distributed::CannotDistribute do r.rpoplpush('foo', 'bar') end end def test_brpoplpush assert_raises Redis::Distributed::CannotDistribute do r.brpoplpush('foo', 'bar', timeout: 1) end end end redis-rb-4.1.2/test/distributed_commands_on_sets_test.rb000066400000000000000000000040361347371274700235470ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/sets' class TestDistributedCommandsOnSets < Minitest::Test include Helper::Distributed include Lint::Sets def test_smove assert_raises Redis::Distributed::CannotDistribute do r.sadd 'foo', 's1' r.sadd 'bar', 's2' r.smove('foo', 'bar', 's1') end end def test_sinter assert_raises Redis::Distributed::CannotDistribute do r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sinter('foo', 'bar') end end def test_sinterstore assert_raises Redis::Distributed::CannotDistribute do r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sinterstore('baz', 'foo', 'bar') end end def test_sunion assert_raises Redis::Distributed::CannotDistribute do r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' r.sunion('foo', 'bar') end end def test_sunionstore assert_raises Redis::Distributed::CannotDistribute do r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' r.sunionstore('baz', 'foo', 'bar') end end def test_sdiff assert_raises Redis::Distributed::CannotDistribute do r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' r.sdiff('foo', 'bar') end end def test_sdiffstore assert_raises Redis::Distributed::CannotDistribute do r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' r.sdiffstore('baz', 'foo', 'bar') end end def test_sscan r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' cursor, vals = r.sscan 'foo', 0 assert_equal '0', cursor assert_equal %w[s1 s2], vals.sort end def test_sscan_each r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' vals = r.sscan_each('foo').to_a assert_equal %w[s1 s2], vals.sort end end redis-rb-4.1.2/test/distributed_commands_on_sorted_sets_test.rb000066400000000000000000000022551347371274700251300ustar00rootroot00000000000000require_relative 'helper' require_relative 'lint/sorted_sets' class TestDistributedCommandsOnSortedSets < Minitest::Test include Helper::Distributed include Lint::SortedSets def test_zinterstore assert_raises(Redis::Distributed::CannotDistribute) { super } end def test_zinterstore_with_aggregate assert_raises(Redis::Distributed::CannotDistribute) { super } end def test_zinterstore_with_weights assert_raises(Redis::Distributed::CannotDistribute) { super } end def test_zlexcount # Not implemented yet end def test_zpopmax # Not implemented yet end def test_zpopmin # Not implemented yet end def test_zrangebylex # Not implemented yet end def test_zremrangebylex # Not implemented yet end def test_zrevrangebylex # Not implemented yet end def test_zscan # Not implemented yet end def test_zunionstore assert_raises(Redis::Distributed::CannotDistribute) { super } end def test_zunionstore_with_aggregate assert_raises(Redis::Distributed::CannotDistribute) { super } end def test_zunionstore_with_weights assert_raises(Redis::Distributed::CannotDistribute) { super } end end redis-rb-4.1.2/test/distributed_commands_on_strings_test.rb000066400000000000000000000033201347371274700242550ustar00rootroot00000000000000require_relative "helper" require_relative "lint/strings" class TestDistributedCommandsOnStrings < Minitest::Test include Helper::Distributed include Lint::Strings def test_mget r.set("foo", "s1") r.set("bar", "s2") assert_equal ["s1", "s2"] , r.mget("foo", "bar") assert_equal ["s1", "s2", nil], r.mget("foo", "bar", "baz") end def test_mget_mapped r.set("foo", "s1") r.set("bar", "s2") response = r.mapped_mget("foo", "bar") assert_equal "s1", response["foo"] assert_equal "s2", response["bar"] response = r.mapped_mget("foo", "bar", "baz") assert_equal "s1", response["foo"] assert_equal "s2", response["bar"] assert_nil response["baz"] end def test_mset assert_raises Redis::Distributed::CannotDistribute do r.mset(:foo, "s1", :bar, "s2") end end def test_mset_mapped assert_raises Redis::Distributed::CannotDistribute do r.mapped_mset(:foo => "s1", :bar => "s2") end end def test_msetnx assert_raises Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.msetnx(:foo, "s2", :bar, "s3") end end def test_msetnx_mapped assert_raises Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.mapped_msetnx(:foo => "s2", :bar => "s3") end end def test_bitop target_version "2.5.10" do assert_raises Redis::Distributed::CannotDistribute do r.set("foo", "a") r.set("bar", "b") r.bitop(:and, "foo&bar", "foo", "bar") end end end def test_mapped_mget_in_a_pipeline_returns_hash assert_raises Redis::Distributed::CannotDistribute do super end end def test_bitfield # Not implemented yet end end redis-rb-4.1.2/test/distributed_commands_on_value_types_test.rb000066400000000000000000000047541347371274700251400ustar00rootroot00000000000000require_relative "helper" require_relative "lint/value_types" class TestDistributedCommandsOnValueTypes < Minitest::Test include Helper::Distributed include Lint::ValueTypes def test_del r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del("foo") assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del("bar", "baz") assert_equal [], r.keys("*").sort end def test_del_with_array_argument r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del(["foo"]) assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del(["bar", "baz"]) assert_equal [], r.keys("*").sort end def test_unlink target_version "4.0.0" do r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.unlink("foo") assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.unlink("bar", "baz") assert_equal [], r.keys("*").sort end end def test_unlink_with_array_argument target_version "4.0.0" do r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.unlink(["foo"]) assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.unlink(["bar", "baz"]) assert_equal [], r.keys("*").sort end end def test_randomkey assert_raises Redis::Distributed::CannotDistribute do r.randomkey end end def test_rename assert_raises Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.rename "foo", "bar" end assert_equal "s1", r.get("foo") assert_nil r.get("bar") end def test_renamenx assert_raises Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.rename "foo", "bar" end assert_equal "s1", r.get("foo") assert_nil r.get("bar") end def test_dbsize assert_equal [0], r.dbsize r.set("foo", "s1") assert_equal [1], r.dbsize end def test_flushdb r.set("foo", "s1") r.set("bar", "s2") assert_equal [2], r.dbsize r.flushdb assert_equal [0], r.dbsize end def test_migrate r.set("foo", "s1") assert_raises Redis::Distributed::CannotDistribute do r.migrate("foo", {}) end end end redis-rb-4.1.2/test/distributed_commands_requiring_clustering_test.rb000066400000000000000000000103571347371274700263440ustar00rootroot00000000000000require_relative "helper" class TestDistributedCommandsRequiringClustering < Minitest::Test include Helper::Distributed def test_rename r.set("{qux}foo", "s1") r.rename "{qux}foo", "{qux}bar" assert_equal "s1", r.get("{qux}bar") assert_nil r.get("{qux}foo") end def test_renamenx r.set("{qux}foo", "s1") r.set("{qux}bar", "s2") assert_equal false, r.renamenx("{qux}foo", "{qux}bar") assert_equal "s1", r.get("{qux}foo") assert_equal "s2", r.get("{qux}bar") end def test_brpoplpush r.rpush "{qux}foo", "s1" r.rpush "{qux}foo", "s2" assert_equal "s2", r.brpoplpush("{qux}foo", "{qux}bar", :timeout => 1) assert_equal ["s2"], r.lrange("{qux}bar", 0, -1) end def test_rpoplpush r.rpush "{qux}foo", "s1" r.rpush "{qux}foo", "s2" assert_equal "s2", r.rpoplpush("{qux}foo", "{qux}bar") assert_equal ["s2"], r.lrange("{qux}bar", 0, -1) assert_equal "s1", r.rpoplpush("{qux}foo", "{qux}bar") assert_equal ["s1", "s2"], r.lrange("{qux}bar", 0, -1) end def test_smove r.sadd "{qux}foo", "s1" r.sadd "{qux}bar", "s2" assert r.smove("{qux}foo", "{qux}bar", "s1") assert r.sismember("{qux}bar", "s1") end def test_sinter r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" assert_equal ["s2"], r.sinter("{qux}foo", "{qux}bar") end def test_sinterstore r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sinterstore("{qux}baz", "{qux}foo", "{qux}bar") assert_equal ["s2"], r.smembers("{qux}baz") end def test_sunion r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" assert_equal ["s1", "s2", "s3"], r.sunion("{qux}foo", "{qux}bar").sort end def test_sunionstore r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" r.sunionstore("{qux}baz", "{qux}foo", "{qux}bar") assert_equal ["s1", "s2", "s3"], r.smembers("{qux}baz").sort end def test_sdiff r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" assert_equal ["s1"], r.sdiff("{qux}foo", "{qux}bar") assert_equal ["s3"], r.sdiff("{qux}bar", "{qux}foo") end def test_sdiffstore r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" r.sdiffstore("{qux}baz", "{qux}foo", "{qux}bar") assert_equal ["s1"], r.smembers("{qux}baz") end def test_sort r.set("{qux}foo:1", "s1") r.set("{qux}foo:2", "s2") r.rpush("{qux}bar", "1") r.rpush("{qux}bar", "2") assert_equal ["s1"], r.sort("{qux}bar", :get => "{qux}foo:*", :limit => [0, 1]) assert_equal ["s2"], r.sort("{qux}bar", :get => "{qux}foo:*", :limit => [0, 1], :order => "desc alpha") end def test_sort_with_an_array_of_gets r.set("{qux}foo:1:a", "s1a") r.set("{qux}foo:1:b", "s1b") r.set("{qux}foo:2:a", "s2a") r.set("{qux}foo:2:b", "s2b") r.rpush("{qux}bar", "1") r.rpush("{qux}bar", "2") assert_equal [["s1a", "s1b"]], r.sort("{qux}bar", :get => ["{qux}foo:*:a", "{qux}foo:*:b"], :limit => [0, 1]) assert_equal [["s2a", "s2b"]], r.sort("{qux}bar", :get => ["{qux}foo:*:a", "{qux}foo:*:b"], :limit => [0, 1], :order => "desc alpha") assert_equal [["s1a", "s1b"], ["s2a", "s2b"]], r.sort("{qux}bar", :get => ["{qux}foo:*:a", "{qux}foo:*:b"]) end def test_sort_with_store r.set("{qux}foo:1", "s1") r.set("{qux}foo:2", "s2") r.rpush("{qux}bar", "1") r.rpush("{qux}bar", "2") r.sort("{qux}bar", :get => "{qux}foo:*", :store => "{qux}baz") assert_equal ["s1", "s2"], r.lrange("{qux}baz", 0, -1) end def test_bitop target_version "2.5.10" do r.set("{qux}foo", "a") r.set("{qux}bar", "b") r.bitop(:and, "{qux}foo&bar", "{qux}foo", "{qux}bar") assert_equal "\x60", r.get("{qux}foo&bar") r.bitop(:or, "{qux}foo|bar", "{qux}foo", "{qux}bar") assert_equal "\x63", r.get("{qux}foo|bar") r.bitop(:xor, "{qux}foo^bar", "{qux}foo", "{qux}bar") assert_equal "\x03", r.get("{qux}foo^bar") r.bitop(:not, "{qux}~foo", "{qux}foo") assert_equal "\x9E", r.get("{qux}~foo") end end end redis-rb-4.1.2/test/distributed_connection_handling_test.rb000066400000000000000000000005041347371274700242130ustar00rootroot00000000000000require_relative "helper" class TestDistributedConnectionHandling < Minitest::Test include Helper::Distributed def test_ping assert_equal ["PONG"], r.ping end def test_select r.set "foo", "bar" r.select 14 assert_nil r.get("foo") r.select 15 assert_equal "bar", r.get("foo") end end redis-rb-4.1.2/test/distributed_internals_test.rb000066400000000000000000000045211347371274700222120ustar00rootroot00000000000000require_relative "helper" class TestDistributedInternals < Minitest::Test include Helper::Distributed def test_provides_a_meaningful_inspect nodes = ["redis://127.0.0.1:#{PORT}/15", *NODES] redis = Redis::Distributed.new nodes assert_equal "#", redis.inspect end def test_default_as_urls nodes = ["redis://127.0.0.1:#{PORT}/15", *NODES] redis = Redis::Distributed.new nodes assert_equal ["redis://127.0.0.1:#{PORT}/15", *NODES], redis.nodes.map { |node| node._client.id } end def test_default_as_config_hashes nodes = [OPTIONS.merge(:host => '127.0.0.1'), OPTIONS.merge(:host => 'somehost', :port => PORT.next)] redis = Redis::Distributed.new nodes assert_equal ["redis://127.0.0.1:#{PORT}/15","redis://somehost:#{PORT.next}/15"], redis.nodes.map { |node| node._client.id } end def test_as_mix_and_match nodes = ["redis://127.0.0.1:7389/15", OPTIONS.merge(:host => 'somehost'), OPTIONS.merge(:host => 'somehost', :port => PORT.next)] redis = Redis::Distributed.new nodes assert_equal ["redis://127.0.0.1:7389/15", "redis://somehost:#{PORT}/15", "redis://somehost:#{PORT.next}/15"], redis.nodes.map { |node| node._client.id } end def test_override_id nodes = [OPTIONS.merge(:host => '127.0.0.1', :id => "test"), OPTIONS.merge( :host => 'somehost', :port => PORT.next, :id => "test1")] redis = Redis::Distributed.new nodes assert_equal redis.nodes.first._client.id, "test" assert_equal redis.nodes.last._client.id, "test1" assert_equal "#", redis.inspect end def test_can_be_duped_to_create_a_new_connection redis = Redis::Distributed.new(NODES) clients = redis.info[0]["connected_clients"].to_i r2 = redis.dup r2.ping assert_equal clients + 1, redis.info[0]["connected_clients"].to_i end def test_keeps_options_after_dup r1 = Redis::Distributed.new(NODES, :tag => /^(\w+):/) assert_raises(Redis::Distributed::CannotDistribute) do r1.sinter("foo", "bar") end assert_equal [], r1.sinter("baz:foo", "baz:bar") r2 = r1.dup assert_raises(Redis::Distributed::CannotDistribute) do r2.sinter("foo", "bar") end assert_equal [], r2.sinter("baz:foo", "baz:bar") end end redis-rb-4.1.2/test/distributed_key_tags_test.rb000066400000000000000000000024411347371274700220200ustar00rootroot00000000000000require_relative "helper" class TestDistributedKeyTags < Minitest::Test include Helper include Helper::Distributed def test_hashes_consistently r1 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES] r2 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES] r3 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES] assert_equal r1.node_for("foo").id, r2.node_for("foo").id assert_equal r1.node_for("foo").id, r3.node_for("foo").id end def test_allows_clustering_of_keys r = Redis::Distributed.new(NODES) r.add_node("redis://127.0.0.1:#{PORT}/14") r.flushdb 100.times do |i| r.set "{foo}users:#{i}", i end assert_equal [0, 100], r.nodes.map { |node| node.keys.size } end def test_distributes_keys_if_no_clustering_is_used r.add_node("redis://127.0.0.1:#{PORT}/14") r.flushdb r.set "users:1", 1 r.set "users:4", 4 assert_equal [1, 1], r.nodes.map { |node| node.keys.size } end def test_allows_passing_a_custom_tag_extractor r = Redis::Distributed.new(NODES, :tag => /^(.+?):/) r.add_node("redis://127.0.0.1:#{PORT}/14") r.flushdb 100.times do |i| r.set "foo:users:#{i}", i end assert_equal [0, 100], r.nodes.map { |node| node.keys.size } end end redis-rb-4.1.2/test/distributed_persistence_control_commands_test.rb000066400000000000000000000010271347371274700261560ustar00rootroot00000000000000require_relative "helper" class TestDistributedPersistenceControlCommands < Minitest::Test include Helper::Distributed def test_save redis_mock(:save => lambda { "+SAVE" }) do |redis| assert_equal ["SAVE"], redis.save end end def test_bgsave redis_mock(:bgsave => lambda { "+BGSAVE" }) do |redis| assert_equal ["BGSAVE"], redis.bgsave end end def test_lastsave redis_mock(:lastsave => lambda { "+LASTSAVE" }) do |redis| assert_equal ["LASTSAVE"], redis.lastsave end end end redis-rb-4.1.2/test/distributed_publish_subscribe_test.rb000066400000000000000000000036101347371274700237200ustar00rootroot00000000000000require_relative "helper" class TestDistributedPublishSubscribe < Minitest::Test include Helper::Distributed def test_subscribe_and_unsubscribe assert_raises Redis::Distributed::CannotDistribute do r.subscribe("foo", "bar") { } end assert_raises Redis::Distributed::CannotDistribute do r.subscribe("{qux}foo", "bar") { } end end def test_subscribe_and_unsubscribe_with_tags @subscribed = false @unsubscribed = false wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @subscribed = true @t1 = total end on.message do |channel, message| if message == "s1" r.unsubscribe @message = message end end on.unsubscribe do |channel, total| @unsubscribed = true @t2 = total end end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis::Distributed.new(NODES).publish("foo", "s1") wire.join assert @subscribed assert_equal 1, @t1 assert @unsubscribed assert_equal 0, @t2 assert_equal "s1", @message end def test_subscribe_within_subscribe @channels = [] wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @channels << channel r.subscribe("bar") if channel == "foo" r.unsubscribe if channel == "bar" end end end wire.join assert_equal ["foo", "bar"], @channels end def test_other_commands_within_a_subscribe assert_raises Redis::CommandError do r.subscribe("foo") do |on| on.subscribe do |channel, total| r.set("bar", "s2") end end end end def test_subscribe_without_a_block assert_raises LocalJumpError do r.subscribe("foo") end end end redis-rb-4.1.2/test/distributed_remote_server_control_commands_test.rb000066400000000000000000000026261347371274700265210ustar00rootroot00000000000000require_relative "helper" class TestDistributedRemoteServerControlCommands < Minitest::Test include Helper::Distributed def test_info keys = [ "redis_version", "uptime_in_seconds", "uptime_in_days", "connected_clients", "used_memory", "total_connections_received", "total_commands_processed", ] infos = r.info infos.each do |info| keys.each do |k| msg = "expected #info to include #{k}" assert info.keys.include?(k), msg end end end def test_info_commandstats target_version "2.5.7" do r.nodes.each { |n| n.config(:resetstat) } r.ping # Executed on every node r.info(:commandstats).each do |info| assert_equal "1", info["ping"]["calls"] end end end def test_monitor begin r.monitor rescue Exception => ex ensure assert ex.kind_of?(NotImplementedError) end end def test_echo assert_equal ["foo bar baz\n"], r.echo("foo bar baz\n") end def test_time target_version "2.5.4" do # Test that the difference between the time that Ruby reports and the time # that Redis reports is minimal (prevents the test from being racy). r.time.each do |rv| redis_usec = rv[0] * 1_000_000 + rv[1] ruby_usec = Integer(Time.now.to_f * 1_000_000) assert 500_000 > (ruby_usec - redis_usec).abs end end end end redis-rb-4.1.2/test/distributed_scripting_test.rb000066400000000000000000000056011347371274700222150ustar00rootroot00000000000000require_relative "helper" class TestDistributedScripting < Minitest::Test include Helper::Distributed def to_sha(script) r.script(:load, script).first end def test_script_exists target_version "2.5.9" do # 2.6-rc1 a = to_sha("return 1") b = a.succ assert_equal [true], r.script(:exists, a) assert_equal [false], r.script(:exists, b) assert_equal [[true]], r.script(:exists, [a]) assert_equal [[false]], r.script(:exists, [b]) assert_equal [[true, false]], r.script(:exists, [a, b]) end end def test_script_flush target_version "2.5.9" do # 2.6-rc1 sha = to_sha("return 1") assert r.script(:exists, sha).first assert_equal ["OK"], r.script(:flush) assert !r.script(:exists, sha).first end end def test_script_kill target_version "2.5.9" do # 2.6-rc1 redis_mock(:script => lambda { |arg| "+#{arg.upcase}" }) do |redis| assert_equal ["KILL"], redis.script(:kill) end end end def test_eval target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return #KEYS") end assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return KEYS", ["k1", "k2"]) end assert_equal ["k1"], r.eval("return KEYS", ["k1"]) assert_equal ["a1", "a2"], r.eval("return ARGV", ["k1"], ["a1", "a2"]) end end def test_eval_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return #KEYS", {}) end assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return KEYS", { :keys => ["k1", "k2"] }) end assert_equal ["k1"], r.eval("return KEYS", { :keys => ["k1"] }) assert_equal ["a1", "a2"], r.eval("return ARGV", { :keys => ["k1"], :argv => ["a1", "a2"] }) end end def test_evalsha target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return #KEYS")) end assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return KEYS"), ["k1", "k2"]) end assert_equal ["k1"], r.evalsha(to_sha("return KEYS"), ["k1"]) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), ["k1"], ["a1", "a2"]) end end def test_evalsha_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return #KEYS"), {}) end assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return KEYS"), { :keys => ["k1", "k2"] }) end assert_equal ["k1"], r.evalsha(to_sha("return KEYS"), { :keys => ["k1"] }) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), { :keys => ["k1"], :argv => ["a1", "a2"] }) end end end redis-rb-4.1.2/test/distributed_sorting_test.rb000066400000000000000000000005511347371274700216770ustar00rootroot00000000000000require_relative "helper" class TestDistributedSorting < Minitest::Test include Helper::Distributed def test_sort assert_raises(Redis::Distributed::CannotDistribute) do r.set("foo:1", "s1") r.set("foo:2", "s2") r.rpush("bar", "1") r.rpush("bar", "2") r.sort("bar", :get => "foo:*", :limit => [0, 1]) end end end redis-rb-4.1.2/test/distributed_test.rb000066400000000000000000000027171347371274700201400ustar00rootroot00000000000000require_relative "helper" class TestDistributed < Minitest::Test include Helper::Distributed def test_handle_multiple_servers @r = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES] 100.times do |idx| @r.set(idx.to_s, "foo#{idx}") end 100.times do |idx| assert_equal "foo#{idx}", @r.get(idx.to_s) end assert_equal "0", @r.keys("*").sort.first assert_equal "string", @r.type("1") end def test_add_nodes logger = Logger.new("/dev/null") @r = Redis::Distributed.new NODES, :logger => logger, :timeout => 10 assert_equal "127.0.0.1", @r.nodes[0]._client.host assert_equal PORT, @r.nodes[0]._client.port assert_equal 15, @r.nodes[0]._client.db assert_equal 10, @r.nodes[0]._client.timeout assert_equal logger, @r.nodes[0]._client.logger @r.add_node("redis://127.0.0.1:6380/14") assert_equal "127.0.0.1", @r.nodes[1]._client.host assert_equal 6380, @r.nodes[1]._client.port assert_equal 14, @r.nodes[1]._client.db assert_equal 10, @r.nodes[1]._client.timeout assert_equal logger, @r.nodes[1]._client.logger end def test_pipelining_commands_cannot_be_distributed assert_raises Redis::Distributed::CannotDistribute do r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" end end end def test_unknown_commands_does_not_work_by_default assert_raises NoMethodError do r.not_yet_implemented_command end end end redis-rb-4.1.2/test/distributed_transactions_test.rb000066400000000000000000000010601347371274700227160ustar00rootroot00000000000000require_relative "helper" class TestDistributedTransactions < Minitest::Test include Helper::Distributed def test_multi_discard @foo = nil assert_raises Redis::Distributed::CannotDistribute do r.multi { @foo = 1 } end assert_nil @foo assert_raises Redis::Distributed::CannotDistribute do r.discard end end def test_watch_unwatch assert_raises Redis::Distributed::CannotDistribute do r.watch("foo") end assert_raises Redis::Distributed::CannotDistribute do r.unwatch end end end redis-rb-4.1.2/test/encoding_test.rb000066400000000000000000000004351347371274700173770ustar00rootroot00000000000000require_relative "helper" class TestEncoding < Minitest::Test include Helper::Client def test_returns_properly_encoded_strings with_external_encoding("UTF-8") do r.set "foo", "שלום" assert_equal "Shalom שלום", "Shalom " + r.get("foo") end end end redis-rb-4.1.2/test/error_replies_test.rb000066400000000000000000000026531347371274700204710ustar00rootroot00000000000000require_relative "helper" class TestErrorReplies < Minitest::Test include Helper::Client # Every test shouldn't disconnect from the server. Also, when error replies are # in play, the protocol should never get into an invalid state where there are # pending replies in the connection. Calling INFO after every test ensures that # the protocol is still in a valid state. def with_reconnection_check before = r.info["total_connections_received"] yield(r) after = r.info["total_connections_received"] ensure assert_equal before, after end def test_error_reply_for_single_command with_reconnection_check do begin r.unknown_command rescue => ex ensure assert ex.message =~ /unknown command/i end end end def test_raise_first_error_reply_in_pipeline with_reconnection_check do begin r.pipelined do r.set("foo", "s1") r.incr("foo") # not an integer r.lpush("foo", "value") # wrong kind of value end rescue => ex ensure assert ex.message =~ /not an integer/i end end end def test_recover_from_raise_in__call_loop with_reconnection_check do begin r._client.call_loop([:invalid_monitor]) do assert false # Should never be executed end rescue => ex ensure assert ex.message =~ /unknown command/i end end end end redis-rb-4.1.2/test/fork_safety_test.rb000066400000000000000000000030151347371274700201220ustar00rootroot00000000000000require_relative "helper" class TestForkSafety < Minitest::Test include Helper::Client driver(:ruby, :hiredis) do def test_fork_safety redis = Redis.new(OPTIONS) redis.set "foo", 1 child_pid = fork do begin # InheritedError triggers a reconnect, # so we need to disable reconnects to force # the exception bubble up redis.without_reconnect do redis.set "foo", 2 end exit! 0 rescue Redis::InheritedError exit! 127 end end _, status = Process.wait2(child_pid) assert_equal 127, status.exitstatus assert_equal "1", redis.get("foo") rescue NotImplementedError => error raise unless error.message =~ /fork is not available/ end def test_fork_safety_with_enabled_inherited_socket redis = Redis.new(OPTIONS.merge(:inherit_socket => true)) redis.set "foo", 1 child_pid = fork do begin # InheritedError triggers a reconnect, # so we need to disable reconnects to force # the exception bubble up redis.without_reconnect do redis.set "foo", 2 end exit! 0 rescue Redis::InheritedError exit! 127 end end _, status = Process.wait2(child_pid) assert_equal 0, status.exitstatus assert_equal "2", redis.get("foo") rescue NotImplementedError => error raise unless error.message =~ /fork is not available/ end end end redis-rb-4.1.2/test/helper.rb000066400000000000000000000207271347371274700160370ustar00rootroot00000000000000# frozen_string_literal: true require "minitest/autorun" require "mocha/minitest" require "logger" require "stringio" $VERBOSE = true ENV["DRIVER"] ||= "ruby" require_relative "../lib/redis" require_relative "../lib/redis/distributed" require_relative "../lib/redis/connection/#{ENV["DRIVER"]}" require_relative "support/redis_mock" require_relative "support/connection/#{ENV["DRIVER"]}" require_relative 'support/cluster/orchestrator' PORT = 6381 DB = 15 TIMEOUT = Float(ENV['TIMEOUT'] || 1.0) LOW_TIMEOUT = Float(ENV['LOW_TIMEOUT'] || 0.01) # for blocking-command tests OPTIONS = { port: PORT, db: DB, timeout: TIMEOUT }.freeze def driver(*drivers, &blk) if drivers.map(&:to_s).include?(ENV["DRIVER"]) class_eval(&blk) end end module Helper def run if respond_to?(:around) around { super } else super end end def silent verbose, $VERBOSE = $VERBOSE, false begin yield ensure $VERBOSE = verbose end end def with_external_encoding(encoding) original_encoding = Encoding.default_external begin silent { Encoding.default_external = Encoding.find(encoding) } yield ensure silent { Encoding.default_external = original_encoding } end end class Version include Comparable attr :parts def initialize(v) case v when Version @parts = v.parts else @parts = v.to_s.split(".") end end def <=>(other) other = Version.new(other) length = [self.parts.length, other.parts.length].max length.times do |i| a, b = self.parts[i], other.parts[i] return -1 if a.nil? return +1 if b.nil? return a.to_i <=> b.to_i if a != b end 0 end end module Generic include Helper attr_reader :log attr_reader :redis alias :r :redis def setup @log = StringIO.new @redis = init _new_client # Run GC to make sure orphaned connections are closed. GC.start super end def teardown redis.quit if redis super end def init(redis) redis.select 14 redis.flushdb redis.select 15 redis.flushdb redis rescue Redis::CannotConnectError puts <<-MSG Cannot connect to Redis. Make sure Redis is running on localhost, port #{PORT}. This testing suite connects to the database 15. Try this once: $ make clean Then run the build again: $ make MSG exit 1 end def redis_mock(commands, options = {}, &blk) RedisMock.start(commands, options) do |port| yield _new_client(options.merge(:port => port)) end end def redis_mock_with_handler(handler, options = {}, &blk) RedisMock.start_with_handler(handler, options) do |port| yield _new_client(options.merge(:port => port)) end end def assert_in_range(range, value) assert range.include?(value), "expected #{value} to be in #{range.inspect}" end def target_version(target) if version < target skip("Requires Redis > #{target}") if respond_to?(:skip) else yield end end def omit_version(min_ver) skip("Requires Redis > #{min_ver}") if version < min_ver end def version Version.new(redis.info['redis_version']) end end module Client include Generic private def _format_options(options) OPTIONS.merge(:logger => ::Logger.new(@log)).merge(options) end def _new_client(options = {}) Redis.new(_format_options(options).merge(:driver => ENV["DRIVER"])) end end module Sentinel include Generic MASTER_PORT = PORT.to_s SLAVE_PORT = '6382' SENTINEL_PORT = '6400' SENTINEL_PORTS = %w[6400 6401 6402].freeze MASTER_NAME = 'master1' LOCALHOST = '127.0.0.1' def build_sentinel_client(options = {}) opts = { host: LOCALHOST, port: SENTINEL_PORT, timeout: TIMEOUT, logger: ::Logger.new(@log) } Redis.new(opts.merge(options)) end def build_slave_role_client(options = {}) _new_client(options.merge(role: :slave)) end private def _format_options(options = {}) { url: "redis://#{MASTER_NAME}", sentinels: [{ host: LOCALHOST, port: SENTINEL_PORT }], role: :master, timeout: TIMEOUT, logger: ::Logger.new(@log) }.merge(options) end def _new_client(options = {}) Redis.new(_format_options(options).merge(driver: ENV['DRIVER'])) end end module Distributed include Generic NODES = ["redis://127.0.0.1:#{PORT}/#{DB}"].freeze def version Version.new(redis.info.first["redis_version"]) end private def _format_options(options) { :timeout => OPTIONS[:timeout], :logger => ::Logger.new(@log), }.merge(options) end def _new_client(options = {}) Redis::Distributed.new(NODES, _format_options(options).merge(:driver => ENV["conn"])) end end module Cluster include Generic DEFAULT_HOST = '127.0.0.1' DEFAULT_PORTS = (7000..7005).freeze ClusterSlotsRawReply = lambda { |host, port| # @see https://redis.io/topics/protocol <<-REPLY.delete(' ') *1\r *4\r :0\r :16383\r *3\r $#{host.size}\r #{host}\r :#{port}\r $40\r 649fa246273043021a05f547a79478597d3f1dc5\r *3\r $#{host.size}\r #{host}\r :#{port}\r $40\r 649fa246273043021a05f547a79478597d3f1dc5\r REPLY } ClusterNodesRawReply = lambda { |host, port| line = "649fa246273043021a05f547a79478597d3f1dc5 #{host}:#{port}@17000 "\ 'myself,master - 0 1530797742000 1 connected 0-16383' "$#{line.size}\r\n#{line}\r\n" } def init(redis) redis.flushall redis rescue Redis::CannotConnectError puts <<-MSG Cannot connect to Redis Cluster. Make sure Redis is running on localhost, port #{DEFAULT_PORTS}. Try this once: $ make stop_cluster Then run the build again: $ make MSG exit! 1 end def build_another_client(options = {}) _new_client(options) end def redis_cluster_mock(commands, options = {}) host = DEFAULT_HOST port = nil cluster_subcommands = if commands.key?(:cluster) commands.delete(:cluster) .map { |k, v| [k.to_s.downcase, v] } .to_h else {} end commands[:cluster] = lambda { |subcommand, *args| if cluster_subcommands.key?(subcommand) cluster_subcommands[subcommand].call(*args) else case subcommand when 'slots' then ClusterSlotsRawReply.call(host, port) when 'nodes' then ClusterNodesRawReply.call(host, port) else '+OK' end end } commands[:command] = ->(*_) { "*0\r\n" } RedisMock.start(commands, options) do |po| port = po scheme = options[:ssl] ? 'rediss' : 'redis' nodes = %W[#{scheme}://#{host}:#{port}] yield _new_client(options.merge(cluster: nodes)) end end def redis_cluster_down trib = ClusterOrchestrator.new(_default_nodes, timeout: TIMEOUT) trib.down yield ensure trib.rebuild trib.close end def redis_cluster_failover trib = ClusterOrchestrator.new(_default_nodes, timeout: TIMEOUT) trib.failover yield ensure trib.rebuild trib.close end # @param slot [Integer] # @param src [String] : # @param dest [String] : def redis_cluster_resharding(slot, src:, dest:) trib = ClusterOrchestrator.new(_default_nodes, timeout: TIMEOUT) trib.start_resharding(slot, src, dest) yield trib.finish_resharding(slot, dest) ensure trib.rebuild trib.close end private def _default_nodes(host: DEFAULT_HOST, ports: DEFAULT_PORTS) ports.map { |port| "redis://#{host}:#{port}" } end def _format_options(options) { timeout: OPTIONS[:timeout], logger: ::Logger.new(@log), cluster: _default_nodes }.merge(options) end def _new_client(options = {}) Redis.new(_format_options(options).merge(driver: ENV['DRIVER'])) end end end redis-rb-4.1.2/test/helper_test.rb000066400000000000000000000005321347371274700170660ustar00rootroot00000000000000require_relative "helper" class TestHelper < Minitest::Test include Helper def test_version_comparison v = Version.new("2.0.1") assert v > "1" assert v > "2" assert v < "3" assert v < "10" assert v < "2.1" assert v < "2.0.2" assert v < "2.0.1.1" assert v < "2.0.10" assert v == "2.0.1" end end redis-rb-4.1.2/test/internals_test.rb000066400000000000000000000226111347371274700176100ustar00rootroot00000000000000require_relative "helper" class TestInternals < Minitest::Test include Helper::Client def test_logger r.ping assert log.string["[Redis] command=PING"] assert log.string =~ /\[Redis\] call_time=\d+\.\d+ ms/ end def test_logger_with_pipelining r.pipelined do r.set "foo", "bar" r.get "foo" end assert log.string[" command=SET args=\"foo\" \"bar\""] assert log.string[" command=GET args=\"foo\""] end def test_recovers_from_failed_commands # See https://github.com/redis/redis-rb/issues#issue/28 assert_raises(Redis::CommandError) do r.command_that_doesnt_exist end r.info end def test_raises_on_protocol_errors redis_mock(:ping => lambda { |*_| "foo" }) do |redis| assert_raises(Redis::ProtocolError) do redis.ping end end end def test_redis_current assert_equal "127.0.0.1", Redis.current._client.host assert_equal 6379, Redis.current._client.port assert_equal 0, Redis.current._client.db Redis.current = Redis.new(OPTIONS.merge(:port => 6380, :db => 1)) t = Thread.new do assert_equal "127.0.0.1", Redis.current._client.host assert_equal 6380, Redis.current._client.port assert_equal 1, Redis.current._client.db end t.join assert_equal "127.0.0.1", Redis.current._client.host assert_equal 6380, Redis.current._client.port assert_equal 1, Redis.current._client.db end def test_redis_connected? fresh_client = _new_client assert !fresh_client.connected? fresh_client.ping assert fresh_client.connected? fresh_client.quit assert !fresh_client.connected? end def test_timeout Redis.new(OPTIONS.merge(:timeout => 0)) end driver(:ruby) do def test_tcp_keepalive keepalive = {:time => 20, :intvl => 10, :probes => 5} redis = Redis.new(OPTIONS.merge(:tcp_keepalive => keepalive)) redis.ping connection = redis._client.connection actual_keepalive = connection.get_tcp_keepalive [:time, :intvl, :probes].each do |key| if actual_keepalive.has_key?(key) assert_equal actual_keepalive[key], keepalive[key] end end end end def test_time target_version "2.5.4" do # Test that the difference between the time that Ruby reports and the time # that Redis reports is minimal (prevents the test from being racy). rv = r.time redis_usec = rv[0] * 1_000_000 + rv[1] ruby_usec = Integer(Time.now.to_f * 1_000_000) assert 500_000 > (ruby_usec - redis_usec).abs end end def test_connection_timeout opts = OPTIONS.merge(:host => "10.255.255.254", :connect_timeout => 0.1, :timeout => 5.0) start_time = Time.now assert_raises Redis::CannotConnectError do Redis.new(opts).ping end assert (Time.now - start_time) <= opts[:timeout] end def test_missing_socket opts = { :path => '/missing.sock' } assert_raises Redis::CannotConnectError do Redis.new(opts).ping end end def close_on_ping(seq, options = {}) $request = 0 command = lambda do idx = $request $request += 1 rv = "+%d" % idx rv = nil if seq.include?(idx) rv end redis_mock({:ping => command}, {:timeout => 0.1}.merge(options)) do |redis| yield(redis) end end def test_retry_by_default close_on_ping([0]) do |redis| assert_equal "1", redis.ping end end def test_retry_when_wrapped_in_with_reconnect_true close_on_ping([0]) do |redis| redis.with_reconnect(true) do assert_equal "1", redis.ping end end end def test_dont_retry_when_wrapped_in_with_reconnect_false close_on_ping([0]) do |redis| assert_raises Redis::ConnectionError do redis.with_reconnect(false) do redis.ping end end end end def test_dont_retry_when_wrapped_in_without_reconnect close_on_ping([0]) do |redis| assert_raises Redis::ConnectionError do redis.without_reconnect do redis.ping end end end end def test_retry_only_once_when_read_raises_econnreset close_on_ping([0, 1]) do |redis| assert_raises Redis::ConnectionError do redis.ping end assert !redis._client.connected? end end def test_retry_with_custom_reconnect_attempts close_on_ping([0, 1], :reconnect_attempts => 2) do |redis| assert_equal "2", redis.ping end end def test_retry_with_custom_reconnect_attempts_can_still_fail close_on_ping([0, 1, 2], :reconnect_attempts => 2) do |redis| assert_raises Redis::ConnectionError do redis.ping end assert !redis._client.connected? end end def test_retry_with_custom_reconnect_attempts_and_exponential_backoff close_on_ping([0, 1, 2], :reconnect_attempts => 3, :reconnect_delay_max => 0.5, :reconnect_delay => 0.01) do |redis| Kernel.expects(:sleep).with(0.01).returns(true) Kernel.expects(:sleep).with(0.02).returns(true) Kernel.expects(:sleep).with(0.04).returns(true) assert_equal "3", redis.ping end end def test_don_t_retry_when_second_read_in_pipeline_raises_econnreset close_on_ping([1]) do |redis| assert_raises Redis::ConnectionError do redis.pipelined do redis.ping redis.ping # Second #read times out end end assert !redis._client.connected? end end def close_on_connection(seq) $n = 0 read_command = lambda do |session| Array.new(session.gets[1..-3].to_i) do bytes = session.gets[1..-3].to_i arg = session.read(bytes) session.read(2) # Discard \r\n arg end end handler = lambda do |session| n = $n $n += 1 select = read_command.call(session) if select[0].downcase == "select" session.write("+OK\r\n") else raise "Expected SELECT" end if !seq.include?(n) while read_command.call(session) session.write("+#{n}\r\n") end end end redis_mock_with_handler(handler) do |redis| yield(redis) end end def test_retry_on_write_error_by_default close_on_connection([0]) do |redis| assert_equal "1", redis._client.call(["x" * 128 * 1024]) end end def test_retry_on_write_error_when_wrapped_in_with_reconnect_true close_on_connection([0]) do |redis| redis.with_reconnect(true) do assert_equal "1", redis._client.call(["x" * 128 * 1024]) end end end def test_dont_retry_on_write_error_when_wrapped_in_with_reconnect_false close_on_connection([0]) do |redis| assert_raises Redis::ConnectionError do redis.with_reconnect(false) do redis._client.call(["x" * 128 * 1024]) end end end end def test_dont_retry_on_write_error_when_wrapped_in_without_reconnect close_on_connection([0]) do |redis| assert_raises Redis::ConnectionError do redis.without_reconnect do redis._client.call(["x" * 128 * 1024]) end end end end def test_connecting_to_unix_domain_socket Redis.new(OPTIONS.merge(:path => ENV.fetch("SOCKET_PATH"))).ping end driver(:ruby, :hiredis) do def test_bubble_timeout_without_retrying serv = TCPServer.new(6380) redis = Redis.new(:port => 6380, :timeout => 0.1) assert_raises(Redis::TimeoutError) do redis.ping end ensure serv.close if serv end end def test_client_options redis = Redis.new(OPTIONS.merge(:host => "host", :port => 1234, :db => 1, :scheme => "foo")) assert_equal "host", redis._client.options[:host] assert_equal 1234, redis._client.options[:port] assert_equal 1, redis._client.options[:db] assert_equal "foo", redis._client.options[:scheme] end def test_resolves_localhost Redis.new(OPTIONS.merge(:host => 'localhost')).ping end class << self def af_family_supported(af) hosts = { Socket::AF_INET => "127.0.0.1", Socket::AF_INET6 => "::1", } begin s = Socket.new(af, Socket::SOCK_STREAM, 0) begin tries = 5 begin sa = Socket.pack_sockaddr_in(1024 + Random.rand(63076), hosts[af]) s.bind(sa) rescue Errno::EADDRINUSE => e # On JRuby (9.1.15.0), if IPv6 is globally disabled on the system, # we get an EADDRINUSE with belows message. if e.message =~ /Protocol family unavailable/ return end tries -= 1 retry if tries > 0 raise end yield rescue Errno::EADDRNOTAVAIL ensure s.close end rescue Errno::ESOCKTNOSUPPORT end end end def af_test(host) commands = { :ping => lambda { |*_| "+pong" }, } redis_mock(commands, :host => host) do |redis| redis.ping end end driver(:ruby) do af_family_supported(Socket::AF_INET) do def test_connect_ipv4 af_test("127.0.0.1") end end end driver(:ruby) do af_family_supported(Socket::AF_INET6) do def test_connect_ipv6 af_test("::1") end end end def test_can_be_duped_to_create_a_new_connection clients = r.info["connected_clients"].to_i r2 = r.dup r2.ping assert_equal clients + 1, r.info["connected_clients"].to_i end end redis-rb-4.1.2/test/lint/000077500000000000000000000000001347371274700151715ustar00rootroot00000000000000redis-rb-4.1.2/test/lint/blocking_commands.rb000066400000000000000000000132161347371274700211720ustar00rootroot00000000000000module Lint module BlockingCommands def setup super r.rpush('{zap}foo', 's1') r.rpush('{zap}foo', 's2') r.rpush('{zap}bar', 's1') r.rpush('{zap}bar', 's2') r.zadd('{szap}foo', %w[0 a 1 b 2 c]) r.zadd('{szap}bar', %w[0 c 1 d 2 e]) end def to_protocol(obj) case obj when String "$#{obj.length}\r\n#{obj}\r\n" when Array "*#{obj.length}\r\n" + obj.map { |e| to_protocol(e) }.join else raise end end def mock(options = {}, &blk) commands = build_mock_commands(options) redis_mock(commands, { timeout: LOW_TIMEOUT }, &blk) end def build_mock_commands(options = {}) { blpop: lambda do |*args| sleep options[:delay] if options.key?(:delay) to_protocol([args.first, args.last]) end, brpop: lambda do |*args| sleep options[:delay] if options.key?(:delay) to_protocol([args.first, args.last]) end, brpoplpush: lambda do |*args| sleep options[:delay] if options.key?(:delay) to_protocol(args.last) end, bzpopmax: lambda do |*args| sleep options[:delay] if options.key?(:delay) to_protocol([args.first, args.last]) end, bzpopmin: lambda do |*args| sleep options[:delay] if options.key?(:delay) to_protocol([args.first, args.last]) end } end def test_blpop assert_equal ['{zap}foo', 's1'], r.blpop('{zap}foo') assert_equal ['{zap}foo', 's2'], r.blpop(['{zap}foo']) assert_equal ['{zap}bar', 's1'], r.blpop(['{zap}bar', '{zap}foo']) assert_equal ['{zap}bar', 's2'], r.blpop(['{zap}foo', '{zap}bar']) end def test_blpop_timeout mock do |r| assert_equal ['{zap}foo', '0'], r.blpop('{zap}foo') assert_equal ['{zap}foo', LOW_TIMEOUT.to_s], r.blpop('{zap}foo', timeout: LOW_TIMEOUT) end end class FakeDuration def initialize(int) @int = int end def to_int @int end end def test_blpop_integer_like_timeout mock do |r| assert_equal ["{zap}foo", "1"], r.blpop("{zap}foo", FakeDuration.new(1)) end end def test_blpop_with_old_prototype assert_equal ['{zap}foo', 's1'], r.blpop('{zap}foo', 0) assert_equal ['{zap}foo', 's2'], r.blpop('{zap}foo', 0) assert_equal ['{zap}bar', 's1'], r.blpop('{zap}bar', '{zap}foo', 0) assert_equal ['{zap}bar', 's2'], r.blpop('{zap}foo', '{zap}bar', 0) end def test_blpop_timeout_with_old_prototype mock do |r| assert_equal ['{zap}foo', '0'], r.blpop('{zap}foo', 0) assert_equal ['{zap}foo', '1'], r.blpop('{zap}foo', 1) end end def test_brpop assert_equal ['{zap}foo', 's2'], r.brpop('{zap}foo') assert_equal ['{zap}foo', 's1'], r.brpop(['{zap}foo']) assert_equal ['{zap}bar', 's2'], r.brpop(['{zap}bar', '{zap}foo']) assert_equal ['{zap}bar', 's1'], r.brpop(['{zap}foo', '{zap}bar']) end def test_brpop_timeout mock do |r| assert_equal ['{zap}foo', '0'], r.brpop('{zap}foo') assert_equal ['{zap}foo', LOW_TIMEOUT.to_s], r.brpop('{zap}foo', timeout: LOW_TIMEOUT) end end def test_brpop_with_old_prototype assert_equal ['{zap}foo', 's2'], r.brpop('{zap}foo', 0) assert_equal ['{zap}foo', 's1'], r.brpop('{zap}foo', 0) assert_equal ['{zap}bar', 's2'], r.brpop('{zap}bar', '{zap}foo', 0) assert_equal ['{zap}bar', 's1'], r.brpop('{zap}foo', '{zap}bar', 0) end def test_brpop_timeout_with_old_prototype mock do |r| assert_equal ['{zap}foo', '0'], r.brpop('{zap}foo', 0) assert_equal ['{zap}foo', '1'], r.brpop('{zap}foo', 1) end end def test_brpoplpush assert_equal 's2', r.brpoplpush('{zap}foo', '{zap}qux') assert_equal ['s2'], r.lrange('{zap}qux', 0, -1) end def test_brpoplpush_timeout mock do |r| assert_equal '0', r.brpoplpush('{zap}foo', '{zap}bar') assert_equal LOW_TIMEOUT.to_s, r.brpoplpush('{zap}foo', '{zap}bar', timeout: LOW_TIMEOUT) end end def test_brpoplpush_with_old_prototype assert_equal 's2', r.brpoplpush('{zap}foo', '{zap}qux', 0) assert_equal ['s2'], r.lrange('{zap}qux', 0, -1) end def test_brpoplpush_timeout_with_old_prototype mock do |r| assert_equal '0', r.brpoplpush('{zap}foo', '{zap}bar', 0) assert_equal '1', r.brpoplpush('{zap}foo', '{zap}bar', 1) end end def test_bzpopmin target_version('5.0.0') do assert_equal ['{szap}foo', 'a', 0.0], r.bzpopmin('{szap}foo', '{szap}bar', 1) assert_nil r.bzpopmin('{szap}aaa', '{szap}bbb', 2) end end def test_bzpopmax target_version('5.0.0') do assert_equal ['{szap}foo', 'c', 2.0], r.bzpopmax('{szap}foo', '{szap}bar', 1) assert_nil r.bzpopmax('{szap}aaa', '{szap}bbb', 1) end end driver(:ruby, :hiredis) do def test_blpop_socket_timeout mock(delay: LOW_TIMEOUT * 5) do |r| assert_raises(Redis::TimeoutError) do r.blpop('{zap}foo', timeout: LOW_TIMEOUT) end end end def test_brpop_socket_timeout mock(delay: LOW_TIMEOUT * 5) do |r| assert_raises(Redis::TimeoutError) do r.brpop('{zap}foo', timeout: LOW_TIMEOUT) end end end def test_brpoplpush_socket_timeout mock(delay: LOW_TIMEOUT * 5) do |r| assert_raises(Redis::TimeoutError) do r.brpoplpush('{zap}foo', '{zap}bar', timeout: LOW_TIMEOUT) end end end end end end redis-rb-4.1.2/test/lint/hashes.rb000066400000000000000000000107251347371274700167760ustar00rootroot00000000000000module Lint module Hashes def test_hset_and_hget r.hset("foo", "f1", "s1") assert_equal "s1", r.hget("foo", "f1") end def test_hsetnx r.hset("foo", "f1", "s1") r.hsetnx("foo", "f1", "s2") assert_equal "s1", r.hget("foo", "f1") r.del("foo") r.hsetnx("foo", "f1", "s2") assert_equal "s2", r.hget("foo", "f1") end def test_hdel r.hset("foo", "f1", "s1") assert_equal "s1", r.hget("foo", "f1") assert_equal 1, r.hdel("foo", "f1") assert_nil r.hget("foo", "f1") end def test_splat_hdel target_version "2.3.9" do r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert_equal "s1", r.hget("foo", "f1") assert_equal "s2", r.hget("foo", "f2") assert_equal 2, r.hdel("foo", "f1", "f2") assert_nil r.hget("foo", "f1") assert_nil r.hget("foo", "f2") end end def test_variadic_hdel target_version "2.3.9" do r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert_equal "s1", r.hget("foo", "f1") assert_equal "s2", r.hget("foo", "f2") assert_equal 2, r.hdel("foo", ["f1", "f2"]) assert_nil r.hget("foo", "f1") assert_nil r.hget("foo", "f2") end end def test_hexists assert_equal false, r.hexists("foo", "f1") r.hset("foo", "f1", "s1") assert r.hexists("foo", "f1") end def test_hlen assert_equal 0, r.hlen("foo") r.hset("foo", "f1", "s1") assert_equal 1, r.hlen("foo") r.hset("foo", "f2", "s2") assert_equal 2, r.hlen("foo") end def test_hkeys assert_equal [], r.hkeys("foo") r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert_equal ["f1", "f2"], r.hkeys("foo") end def test_hvals assert_equal [], r.hvals("foo") r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert_equal ["s1", "s2"], r.hvals("foo") end def test_hgetall assert({} == r.hgetall("foo")) r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert({"f1" => "s1", "f2" => "s2"} == r.hgetall("foo")) end def test_hmset r.hmset("hash", "foo1", "bar1", "foo2", "bar2") assert_equal "bar1", r.hget("hash", "foo1") assert_equal "bar2", r.hget("hash", "foo2") end def test_hmset_with_invalid_arguments assert_raises(Redis::CommandError) do r.hmset("hash", "foo1", "bar1", "foo2", "bar2", "foo3") end end def test_mapped_hmset r.mapped_hmset("foo", :f1 => "s1", :f2 => "s2") assert_equal "s1", r.hget("foo", "f1") assert_equal "s2", r.hget("foo", "f2") end def test_hmget r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") r.hset("foo", "f3", "s3") assert_equal ["s2", "s3"], r.hmget("foo", "f2", "f3") end def test_hmget_mapped r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") r.hset("foo", "f3", "s3") assert({"f1" => "s1"} == r.mapped_hmget("foo", "f1")) assert({"f1" => "s1", "f2" => "s2"} == r.mapped_hmget("foo", "f1", "f2")) end def test_mapped_hmget_in_a_pipeline_returns_hash r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") result = r.pipelined do r.mapped_hmget("foo", "f1", "f2") end assert_equal result[0], { "f1" => "s1", "f2" => "s2" } end def test_hincrby r.hincrby("foo", "f1", 1) assert_equal "1", r.hget("foo", "f1") r.hincrby("foo", "f1", 2) assert_equal "3", r.hget("foo", "f1") r.hincrby("foo", "f1", -1) assert_equal "2", r.hget("foo", "f1") end def test_hincrbyfloat target_version "2.5.4" do r.hincrbyfloat("foo", "f1", 1.23) assert_equal "1.23", r.hget("foo", "f1") r.hincrbyfloat("foo", "f1", 0.77) assert_equal "2", r.hget("foo", "f1") r.hincrbyfloat("foo", "f1", -0.1) assert_equal "1.9", r.hget("foo", "f1") end end def test_hstrlen target_version('3.2.0') do redis.hmset('foo', 'f1', 'HelloWorld', 'f2', 99, 'f3', -256) assert_equal 10, r.hstrlen('foo', 'f1') assert_equal 2, r.hstrlen('foo', 'f2') assert_equal 4, r.hstrlen('foo', 'f3') end end def test_hscan redis.hmset('foo', 'f1', 'Jack', 'f2', 33) expected = ['0', [%w[f1 Jack], %w[f2 33]]] assert_equal expected, redis.hscan('foo', 0) end end end redis-rb-4.1.2/test/lint/hyper_log_log.rb000066400000000000000000000035261347371274700203550ustar00rootroot00000000000000module Lint module HyperLogLog def test_pfadd target_version "2.8.9" do assert_equal true, r.pfadd("foo", "s1") assert_equal true, r.pfadd("foo", "s2") assert_equal false, r.pfadd("foo", "s1") assert_equal 2, r.pfcount("foo") end end def test_variadic_pfadd target_version "2.8.9" do assert_equal true, r.pfadd("foo", ["s1", "s2"]) assert_equal true, r.pfadd("foo", ["s1", "s2", "s3"]) assert_equal 3, r.pfcount("foo") end end def test_pfcount target_version "2.8.9" do assert_equal 0, r.pfcount("foo") assert_equal true, r.pfadd("foo", "s1") assert_equal 1, r.pfcount("foo") end end def test_variadic_pfcount target_version "2.8.9" do assert_equal 0, r.pfcount(["{1}foo", "{1}bar"]) assert_equal true, r.pfadd("{1}foo", "s1") assert_equal true, r.pfadd("{1}bar", "s1") assert_equal true, r.pfadd("{1}bar", "s2") assert_equal 2, r.pfcount("{1}foo", "{1}bar") end end def test_variadic_pfcount_expanded target_version "2.8.9" do assert_equal 0, r.pfcount("{1}foo", "{1}bar") assert_equal true, r.pfadd("{1}foo", "s1") assert_equal true, r.pfadd("{1}bar", "s1") assert_equal true, r.pfadd("{1}bar", "s2") assert_equal 2, r.pfcount("{1}foo", "{1}bar") end end def test_pfmerge target_version '2.8.9' do r.pfadd 'foo', 's1' r.pfadd 'bar', 's2' assert_equal true, r.pfmerge('res', 'foo', 'bar') assert_equal 2, r.pfcount('res') end end def test_variadic_pfmerge_expanded redis.pfadd('{1}foo', %w[foo bar zap a]) redis.pfadd('{1}bar', %w[a b c foo]) assert_equal true, redis.pfmerge('{1}baz', '{1}foo', '{1}bar') end end end redis-rb-4.1.2/test/lint/lists.rb000066400000000000000000000070161347371274700166600ustar00rootroot00000000000000module Lint module Lists def test_lpush r.lpush "foo", "s1" r.lpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") end def test_variadic_lpush target_version "2.3.9" do # 2.4-rc6 assert_equal 3, r.lpush("foo", ["s1", "s2", "s3"]) assert_equal 3, r.llen("foo") assert_equal "s3", r.lpop("foo") end end def test_lpushx r.lpushx "foo", "s1" r.lpush "foo", "s2" r.lpushx "foo", "s3" assert_equal 2, r.llen("foo") assert_equal ["s3", "s2"], r.lrange("foo", 0, -1) end def test_rpush r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.rpop("foo") end def test_variadic_rpush target_version "2.3.9" do # 2.4-rc6 assert_equal 3, r.rpush("foo", ["s1", "s2", "s3"]) assert_equal 3, r.llen("foo") assert_equal "s3", r.rpop("foo") end end def test_rpushx r.rpushx "foo", "s1" r.rpush "foo", "s2" r.rpushx "foo", "s3" assert_equal 2, r.llen("foo") assert_equal ["s2", "s3"], r.lrange("foo", 0, -1) end def test_llen r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") end def test_lrange r.rpush "foo", "s1" r.rpush "foo", "s2" r.rpush "foo", "s3" assert_equal ["s2", "s3"], r.lrange("foo", 1, -1) assert_equal ["s1", "s2"], r.lrange("foo", 0, 1) assert_equal [], r.lrange("bar", 0, -1) end def test_ltrim r.rpush "foo", "s1" r.rpush "foo", "s2" r.rpush "foo", "s3" r.ltrim "foo", 0, 1 assert_equal 2, r.llen("foo") assert_equal ["s1", "s2"], r.lrange("foo", 0, -1) end def test_lindex r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal "s1", r.lindex("foo", 0) assert_equal "s2", r.lindex("foo", 1) end def test_lset r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal "s2", r.lindex("foo", 1) assert r.lset("foo", 1, "s3") assert_equal "s3", r.lindex("foo", 1) assert_raises Redis::CommandError do r.lset("foo", 4, "s3") end end def test_lrem r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 1, r.lrem("foo", 1, "s1") assert_equal ["s2"], r.lrange("foo", 0, -1) end def test_lpop r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s1", r.lpop("foo") assert_equal 1, r.llen("foo") end def test_rpop r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.rpop("foo") assert_equal 1, r.llen("foo") end def test_linsert r.rpush "foo", "s1" r.rpush "foo", "s3" r.linsert "foo", :before, "s3", "s2" assert_equal ["s1", "s2", "s3"], r.lrange("foo", 0, -1) assert_raises(Redis::CommandError) do r.linsert "foo", :anywhere, "s3", "s2" end end def test_rpoplpush r.rpush 'foo', 's1' r.rpush 'foo', 's2' assert_equal 's2', r.rpoplpush('foo', 'bar') assert_equal ['s2'], r.lrange('bar', 0, -1) assert_equal 's1', r.rpoplpush('foo', 'bar') assert_equal %w[s1 s2], r.lrange('bar', 0, -1) end def test_variadic_rpoplpush_expand redis.rpush('{1}foo', %w[a b c]) redis.rpush('{1}bar', %w[d e f]) assert_equal 'c', redis.rpoplpush('{1}foo', '{1}bar') end end end redis-rb-4.1.2/test/lint/sets.rb000066400000000000000000000151161347371274700165000ustar00rootroot00000000000000module Lint module Sets def test_sadd assert_equal true, r.sadd("foo", "s1") assert_equal true, r.sadd("foo", "s2") assert_equal false, r.sadd("foo", "s1") assert_equal ["s1", "s2"], r.smembers("foo").sort end def test_variadic_sadd target_version "2.3.9" do # 2.4-rc6 assert_equal 2, r.sadd("foo", ["s1", "s2"]) assert_equal 1, r.sadd("foo", ["s1", "s2", "s3"]) assert_equal ["s1", "s2", "s3"], r.smembers("foo").sort end end def test_srem r.sadd("foo", "s1") r.sadd("foo", "s2") assert_equal true, r.srem("foo", "s1") assert_equal false, r.srem("foo", "s3") assert_equal ["s2"], r.smembers("foo") end def test_variadic_srem target_version "2.3.9" do # 2.4-rc6 r.sadd("foo", "s1") r.sadd("foo", "s2") r.sadd("foo", "s3") assert_equal 1, r.srem("foo", ["s1", "aaa"]) assert_equal 0, r.srem("foo", ["bbb", "ccc" "ddd"]) assert_equal 1, r.srem("foo", ["eee", "s3"]) assert_equal ["s2"], r.smembers("foo") end end def test_spop r.sadd "foo", "s1" r.sadd "foo", "s2" assert ["s1", "s2"].include?(r.spop("foo")) assert ["s1", "s2"].include?(r.spop("foo")) assert_nil r.spop("foo") end def test_spop_with_positive_count target_version "3.2.0" do r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "foo", "s3" r.sadd "foo", "s4" pops = r.spop("foo", 3) assert !(["s1", "s2", "s3", "s4"] & pops).empty? assert_equal 3, pops.size assert_equal 1, r.scard("foo") end end def test_scard assert_equal 0, r.scard("foo") r.sadd "foo", "s1" assert_equal 1, r.scard("foo") r.sadd "foo", "s2" assert_equal 2, r.scard("foo") end def test_sismember assert_equal false, r.sismember("foo", "s1") r.sadd "foo", "s1" assert_equal true, r.sismember("foo", "s1") assert_equal false, r.sismember("foo", "s2") end def test_smembers assert_equal [], r.smembers("foo") r.sadd "foo", "s1" r.sadd "foo", "s2" assert_equal ["s1", "s2"], r.smembers("foo").sort end def test_srandmember r.sadd "foo", "s1" r.sadd "foo", "s2" 4.times do assert ["s1", "s2"].include?(r.srandmember("foo")) end assert_equal 2, r.scard("foo") end def test_srandmember_with_positive_count r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "foo", "s3" r.sadd "foo", "s4" 4.times do assert !(["s1", "s2", "s3", "s4"] & r.srandmember("foo", 3)).empty? assert_equal 3, r.srandmember("foo", 3).size end assert_equal 4, r.scard("foo") end def test_srandmember_with_negative_count r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "foo", "s3" r.sadd "foo", "s4" 4.times do assert !(["s1", "s2", "s3", "s4"] & r.srandmember("foo", -6)).empty? assert_equal 6, r.srandmember("foo", -6).size end assert_equal 4, r.scard("foo") end def test_smove r.sadd 'foo', 's1' r.sadd 'bar', 's2' assert r.smove('foo', 'bar', 's1') assert r.sismember('bar', 's1') end def test_sinter r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' assert_equal ['s2'], r.sinter('foo', 'bar') end def test_variadic_smove_expand r.sadd('{1}foo', 's1') r.sadd('{1}foo', 's2') r.sadd('{1}foo', 's3') r.sadd('{1}bar', 's3') r.sadd('{1}bar', 's4') r.sadd('{1}bar', 's5') assert_equal true, r.smove('{1}foo', '{1}bar', 's2') end def test_variadic_sinter_expand r.sadd('{1}foo', 's1') r.sadd('{1}foo', 's2') r.sadd('{1}foo', 's3') r.sadd('{1}bar', 's3') r.sadd('{1}bar', 's4') r.sadd('{1}bar', 's5') assert_equal %w[s3], r.sinter('{1}foo', '{1}bar') end def test_sinterstore r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sinterstore('baz', 'foo', 'bar') assert_equal ['s2'], r.smembers('baz') end def test_variadic_sinterstore_expand r.sadd('{1}foo', 's1') r.sadd('{1}foo', 's2') r.sadd('{1}foo', 's3') r.sadd('{1}bar', 's3') r.sadd('{1}bar', 's4') r.sadd('{1}bar', 's5') assert_equal 1, r.sinterstore('{1}baz', '{1}foo', '{1}bar') end def test_sunion r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' assert_equal %w[s1 s2 s3], r.sunion('foo', 'bar').sort end def test_variadic_sunion_expand r.sadd('{1}foo', 's1') r.sadd('{1}foo', 's2') r.sadd('{1}foo', 's3') r.sadd('{1}bar', 's3') r.sadd('{1}bar', 's4') r.sadd('{1}bar', 's5') assert_equal %w[s1 s2 s3 s4 s5], r.sunion('{1}foo', '{1}bar').sort end def test_sunionstore r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' r.sunionstore('baz', 'foo', 'bar') assert_equal %w[s1 s2 s3], r.smembers('baz').sort end def test_variadic_sunionstore_expand r.sadd('{1}foo', 's1') r.sadd('{1}foo', 's2') r.sadd('{1}foo', 's3') r.sadd('{1}bar', 's3') r.sadd('{1}bar', 's4') r.sadd('{1}bar', 's5') assert_equal 5, r.sunionstore('{1}baz', '{1}foo', '{1}bar') end def test_sdiff r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' assert_equal ['s1'], r.sdiff('foo', 'bar') assert_equal ['s3'], r.sdiff('bar', 'foo') end def test_variadic_sdiff_expand r.sadd('{1}foo', 's1') r.sadd('{1}foo', 's2') r.sadd('{1}foo', 's3') r.sadd('{1}bar', 's3') r.sadd('{1}bar', 's4') r.sadd('{1}bar', 's5') assert_equal %w[s1 s2], r.sdiff('{1}foo', '{1}bar').sort end def test_sdiffstore r.sadd 'foo', 's1' r.sadd 'foo', 's2' r.sadd 'bar', 's2' r.sadd 'bar', 's3' r.sdiffstore('baz', 'foo', 'bar') assert_equal ['s1'], r.smembers('baz') end def test_variadic_sdiffstore_expand r.sadd('{1}foo', 's1') r.sadd('{1}foo', 's2') r.sadd('{1}foo', 's3') r.sadd('{1}bar', 's3') r.sadd('{1}bar', 's4') r.sadd('{1}bar', 's5') assert_equal 2, r.sdiffstore('{1}baz', '{1}foo', '{1}bar') end def test_sscan r.sadd('foo', %w[1 2 3 foo foobar feelsgood]) assert_equal %w[0 feelsgood foo foobar], r.sscan('foo', 0, match: 'f*').flatten.sort end end end redis-rb-4.1.2/test/lint/sorted_sets.rb000066400000000000000000000420021347371274700200520ustar00rootroot00000000000000module Lint module SortedSets Infinity = 1.0/0.0 def test_zadd assert_equal 0, r.zcard("foo") assert_equal true, r.zadd("foo", 1, "s1") assert_equal false, r.zadd("foo", 1, "s1") assert_equal 1, r.zcard("foo") r.del "foo" target_version "3.0.2" do # XX option assert_equal 0, r.zcard("foo") assert_equal false, r.zadd("foo", 1, "s1", :xx => true) r.zadd("foo", 1, "s1") assert_equal false, r.zadd("foo", 2, "s1", :xx => true) assert_equal 2, r.zscore("foo", "s1") r.del "foo" # NX option assert_equal 0, r.zcard("foo") assert_equal true, r.zadd("foo", 1, "s1", :nx => true) assert_equal false, r.zadd("foo", 2, "s1", :nx => true) assert_equal 1, r.zscore("foo", "s1") assert_equal 1, r.zcard("foo") r.del "foo" # CH option assert_equal 0, r.zcard("foo") assert_equal true, r.zadd("foo", 1, "s1", :ch => true) assert_equal false, r.zadd("foo", 1, "s1", :ch => true) assert_equal true, r.zadd("foo", 2, "s1", :ch => true) assert_equal 1, r.zcard("foo") r.del "foo" # INCR option assert_equal 1.0, r.zadd("foo", 1, "s1", :incr => true) assert_equal 11.0, r.zadd("foo", 10, "s1", :incr => true) assert_equal(-Infinity, r.zadd("bar", "-inf", "s1", :incr => true)) assert_equal(+Infinity, r.zadd("bar", "+inf", "s2", :incr => true)) r.del 'foo' r.del 'bar' # Incompatible options combination assert_raises(Redis::CommandError) { r.zadd("foo", 1, "s1", :xx => true, :nx => true) } end end def test_variadic_zadd target_version "2.3.9" do # 2.4-rc6 # Non-nested array with pairs assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"]) assert_equal 1, r.zadd("foo", [4, "s1", 5, "s2", 6, "s3"]) assert_equal 3, r.zcard("foo") r.del "foo" # Nested array with pairs assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [[1, "s1"], [2, "s2"]]) assert_equal 1, r.zadd("foo", [[4, "s1"], [5, "s2"], [6, "s3"]]) assert_equal 3, r.zcard("foo") r.del "foo" # Wrong number of arguments assert_raises(Redis::CommandError) { r.zadd("foo", ["bar"]) } assert_raises(Redis::CommandError) { r.zadd("foo", ["bar", "qux", "zap"]) } end target_version "3.0.2" do # XX option assert_equal 0, r.zcard("foo") assert_equal 0, r.zadd("foo", [1, "s1", 2, "s2"], :xx => true) r.zadd("foo", [1, "s1", 2, "s2"]) assert_equal 0, r.zadd("foo", [2, "s1", 3, "s2", 4, "s3"], :xx => true) assert_equal 2, r.zscore("foo", "s1") assert_equal 3, r.zscore("foo", "s2") assert_nil r.zscore("foo", "s3") assert_equal 2, r.zcard("foo") r.del "foo" # NX option assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"], :nx => true) assert_equal 1, r.zadd("foo", [2, "s1", 3, "s2", 4, "s3"], :nx => true) assert_equal 1, r.zscore("foo", "s1") assert_equal 2, r.zscore("foo", "s2") assert_equal 4, r.zscore("foo", "s3") assert_equal 3, r.zcard("foo") r.del "foo" # CH option assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"], :ch => true) assert_equal 2, r.zadd("foo", [1, "s1", 3, "s2", 4, "s3"], :ch => true) assert_equal 3, r.zcard("foo") r.del "foo" # INCR option assert_equal 1.0, r.zadd("foo", [1, "s1"], :incr => true) assert_equal 11.0, r.zadd("foo", [10, "s1"], :incr => true) assert_equal(-Infinity, r.zadd("bar", ["-inf", "s1"], :incr => true)) assert_equal(+Infinity, r.zadd("bar", ["+inf", "s2"], :incr => true)) assert_raises(Redis::CommandError) { r.zadd("foo", [1, "s1", 2, "s2"], :incr => true) } r.del 'foo' r.del 'bar' # Incompatible options combination assert_raises(Redis::CommandError) { r.zadd("foo", [1, "s1"], :xx => true, :nx => true) } end end def test_zrem r.zadd("foo", 1, "s1") r.zadd("foo", 2, "s2") assert_equal 2, r.zcard("foo") assert_equal true, r.zrem("foo", "s1") assert_equal false, r.zrem("foo", "s1") assert_equal 1, r.zcard("foo") end def test_variadic_zrem target_version "2.3.9" do # 2.4-rc6 r.zadd("foo", 1, "s1") r.zadd("foo", 2, "s2") r.zadd("foo", 3, "s3") assert_equal 3, r.zcard("foo") assert_equal 1, r.zrem("foo", ["s1", "aaa"]) assert_equal 0, r.zrem("foo", ["bbb", "ccc" "ddd"]) assert_equal 1, r.zrem("foo", ["eee", "s3"]) assert_equal 1, r.zcard("foo") end end def test_zincrby rv = r.zincrby "foo", 1, "s1" assert_equal 1.0, rv rv = r.zincrby "foo", 10, "s1" assert_equal 11.0, rv rv = r.zincrby "bar", "-inf", "s1" assert_equal(-Infinity, rv) rv = r.zincrby "bar", "+inf", "s2" assert_equal(+Infinity, rv) end def test_zrank r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal 2, r.zrank("foo", "s3") end def test_zrevrank r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal 0, r.zrevrank("foo", "s3") end def test_zrange r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s1", "s2"], r.zrange("foo", 0, 1) assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :with_scores => true) assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :with_scores => true) assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :withscores => true) end def test_zrevrange r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s3", "s2"], r.zrevrange("foo", 0, 1) assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :with_scores => true) assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :with_scores => true) assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :withscores => true) end def test_zrangebyscore r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s2", "s3"], r.zrangebyscore("foo", 2, 3) end def test_zrevrangebyscore r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s3", "s2"], r.zrevrangebyscore("foo", 3, 2) end def test_zrangebyscore_with_limit r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal ["s2"], r.zrangebyscore("foo", 2, 4, :limit => [0, 1]) assert_equal ["s3"], r.zrangebyscore("foo", 2, 4, :limit => [1, 1]) assert_equal ["s3", "s4"], r.zrangebyscore("foo", 2, 4, :limit => [1, 2]) end def test_zrevrangebyscore_with_limit r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal ["s4"], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1]) assert_equal ["s3"], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1]) assert_equal ["s3", "s2"], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 2]) end def test_zrangebyscore_with_withscores r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal [["s2", 2.0]], r.zrangebyscore("foo", 2, 4, :limit => [0, 1], :with_scores => true) assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :with_scores => true) assert_equal [["s2", 2.0]], r.zrangebyscore("foo", 2, 4, :limit => [0, 1], :withscores => true) assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :with_scores => true) assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :with_scores => true) assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :withscores => true) assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :withscores => true) end def test_zrevrangebyscore_with_withscores r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal [["s4", 4.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1], :with_scores => true) assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :with_scores => true) assert_equal [["s4", 4.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1], :withscores => true) assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :with_scores => true) assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :with_scores => true) assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :withscores => true) assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :withscores => true) end def test_zcard assert_equal 0, r.zcard("foo") r.zadd "foo", 1, "s1" assert_equal 1, r.zcard("foo") end def test_zscore r.zadd "foo", 1, "s1" assert_equal 1.0, r.zscore("foo", "s1") assert_nil r.zscore("foo", "s2") assert_nil r.zscore("bar", "s1") r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal(-Infinity, r.zscore("bar", "s1")) assert_equal(+Infinity, r.zscore("bar", "s2")) end def test_zremrangebyrank r.zadd "foo", 10, "s1" r.zadd "foo", 20, "s2" r.zadd "foo", 30, "s3" r.zadd "foo", 40, "s4" assert_equal 3, r.zremrangebyrank("foo", 1, 3) assert_equal ["s1"], r.zrange("foo", 0, -1) end def test_zremrangebyscore r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal 3, r.zremrangebyscore("foo", 2, 4) assert_equal ["s1"], r.zrange("foo", 0, -1) end def test_zpopmax target_version('5.0.0') do r.zadd('foo', %w[0 a 1 b 2 c 3 d]) assert_equal ['d', 3.0], r.zpopmax('foo') assert_equal [['c', 2.0], ['b', 1.0]], r.zpopmax('foo', 2) assert_equal [['a', 0.0]], r.zrange('foo', 0, -1, with_scores: true) end end def test_zpopmin target_version('5.0.0') do r.zadd('foo', %w[0 a 1 b 2 c 3 d]) assert_equal ['a', 0.0], r.zpopmin('foo') assert_equal [['b', 1.0], ['c', 2.0]], r.zpopmin('foo', 2) assert_equal [['d', 3.0]], r.zrange('foo', 0, -1, with_scores: true) end end def test_zremrangebylex r.zadd('foo', %w[0 a 0 b 0 c 0 d 0 e 0 f 0 g]) assert_equal 5, r.zremrangebylex('foo', '(b', '[g') end def test_zlexcount target_version '2.8.9' do r.zadd 'foo', 0, 'aaren' r.zadd 'foo', 0, 'abagael' r.zadd 'foo', 0, 'abby' r.zadd 'foo', 0, 'abbygail' assert_equal 4, r.zlexcount('foo', '[a', "[a\xff") assert_equal 4, r.zlexcount('foo', '[aa', "[ab\xff") assert_equal 3, r.zlexcount('foo', '(aaren', "[ab\xff") assert_equal 2, r.zlexcount('foo', '[aba', '(abbygail') assert_equal 1, r.zlexcount('foo', '(aaren', '(abby') end end def test_zrangebylex target_version '2.8.9' do r.zadd 'foo', 0, 'aaren' r.zadd 'foo', 0, 'abagael' r.zadd 'foo', 0, 'abby' r.zadd 'foo', 0, 'abbygail' assert_equal %w[aaren abagael abby abbygail], r.zrangebylex('foo', '[a', "[a\xff") assert_equal %w[aaren abagael], r.zrangebylex('foo', '[a', "[a\xff", limit: [0, 2]) assert_equal %w[abby abbygail], r.zrangebylex('foo', '(abb', "(abb\xff") assert_equal %w[abbygail], r.zrangebylex('foo', '(abby', "(abby\xff") end end def test_zrevrangebylex target_version '2.9.9' do r.zadd 'foo', 0, 'aaren' r.zadd 'foo', 0, 'abagael' r.zadd 'foo', 0, 'abby' r.zadd 'foo', 0, 'abbygail' assert_equal %w[abbygail abby abagael aaren], r.zrevrangebylex('foo', "[a\xff", '[a') assert_equal %w[abbygail abby], r.zrevrangebylex('foo', "[a\xff", '[a', limit: [0, 2]) assert_equal %w[abbygail abby], r.zrevrangebylex('foo', "(abb\xff", '(abb') assert_equal %w[abbygail], r.zrevrangebylex('foo', "(abby\xff", '(abby') end end def test_zcount r.zadd 'foo', 1, 's1' r.zadd 'foo', 2, 's2' r.zadd 'foo', 3, 's3' assert_equal 2, r.zcount('foo', 2, 3) end def test_zunionstore r.zadd 'foo', 1, 's1' r.zadd 'bar', 2, 's2' r.zadd 'foo', 3, 's3' r.zadd 'bar', 4, 's4' assert_equal 4, r.zunionstore('foobar', %w[foo bar]) assert_equal %w[s1 s2 s3 s4], r.zrange('foobar', 0, -1) end def test_zunionstore_with_weights r.zadd 'foo', 1, 's1' r.zadd 'foo', 3, 's3' r.zadd 'bar', 20, 's2' r.zadd 'bar', 40, 's4' assert_equal 4, r.zunionstore('foobar', %w[foo bar]) assert_equal %w[s1 s3 s2 s4], r.zrange('foobar', 0, -1) assert_equal 4, r.zunionstore('foobar', %w[foo bar], weights: [10, 1]) assert_equal %w[s1 s2 s3 s4], r.zrange('foobar', 0, -1) end def test_zunionstore_with_aggregate r.zadd 'foo', 1, 's1' r.zadd 'foo', 2, 's2' r.zadd 'bar', 4, 's2' r.zadd 'bar', 3, 's3' assert_equal 3, r.zunionstore('foobar', %w[foo bar]) assert_equal %w[s1 s3 s2], r.zrange('foobar', 0, -1) assert_equal 3, r.zunionstore('foobar', %w[foo bar], aggregate: :min) assert_equal %w[s1 s2 s3], r.zrange('foobar', 0, -1) assert_equal 3, r.zunionstore('foobar', %w[foo bar], aggregate: :max) assert_equal %w[s1 s3 s2], r.zrange('foobar', 0, -1) end def test_zunionstore_expand r.zadd('{1}foo', %w[0 a 1 b 2 c]) r.zadd('{1}bar', %w[0 c 1 d 2 e]) assert_equal 5, r.zunionstore('{1}baz', %w[{1}foo {1}bar]) end def test_zinterstore r.zadd 'foo', 1, 's1' r.zadd 'bar', 2, 's1' r.zadd 'foo', 3, 's3' r.zadd 'bar', 4, 's4' assert_equal 1, r.zinterstore('foobar', %w[foo bar]) assert_equal ['s1'], r.zrange('foobar', 0, -1) end def test_zinterstore_with_weights r.zadd 'foo', 1, 's1' r.zadd 'foo', 2, 's2' r.zadd 'foo', 3, 's3' r.zadd 'bar', 20, 's2' r.zadd 'bar', 30, 's3' r.zadd 'bar', 40, 's4' assert_equal 2, r.zinterstore('foobar', %w[foo bar]) assert_equal %w[s2 s3], r.zrange('foobar', 0, -1) assert_equal 2, r.zinterstore('foobar', %w[foo bar], weights: [10, 1]) assert_equal %w[s2 s3], r.zrange('foobar', 0, -1) assert_equal 40.0, r.zscore('foobar', 's2') assert_equal 60.0, r.zscore('foobar', 's3') end def test_zinterstore_with_aggregate r.zadd 'foo', 1, 's1' r.zadd 'foo', 2, 's2' r.zadd 'foo', 3, 's3' r.zadd 'bar', 20, 's2' r.zadd 'bar', 30, 's3' r.zadd 'bar', 40, 's4' assert_equal 2, r.zinterstore('foobar', %w[foo bar]) assert_equal %w[s2 s3], r.zrange('foobar', 0, -1) assert_equal 22.0, r.zscore('foobar', 's2') assert_equal 33.0, r.zscore('foobar', 's3') assert_equal 2, r.zinterstore('foobar', %w[foo bar], aggregate: :min) assert_equal %w[s2 s3], r.zrange('foobar', 0, -1) assert_equal 2.0, r.zscore('foobar', 's2') assert_equal 3.0, r.zscore('foobar', 's3') assert_equal 2, r.zinterstore('foobar', %w[foo bar], aggregate: :max) assert_equal %w[s2 s3], r.zrange('foobar', 0, -1) assert_equal 20.0, r.zscore('foobar', 's2') assert_equal 30.0, r.zscore('foobar', 's3') end def test_zinterstore_expand r.zadd '{1}foo', %w[0 s1 1 s2 2 s3] r.zadd '{1}bar', %w[0 s3 1 s4 2 s5] assert_equal 1, r.zinterstore('{1}baz', %w[{1}foo {1}bar], weights: [2.0, 3.0]) end def test_zscan r.zadd('foo', %w[0 a 1 b 2 c]) expected = ['0', [['a', 0.0], ['b', 1.0], ['c', 2.0]]] assert_equal expected, r.zscan('foo', 0) end end end redis-rb-4.1.2/test/lint/streams.rb000066400000000000000000000555061347371274700172070ustar00rootroot00000000000000# frozen_string_literal: true module Lint module Streams MIN_REDIS_VERSION = '4.9.0' ENTRY_ID_FORMAT = /\d+-\d+/ def setup super omit_version(MIN_REDIS_VERSION) end def test_xinfo_with_stream_subcommand redis.xadd('s1', f: 'v1') redis.xadd('s1', f: 'v2') redis.xadd('s1', f: 'v3') redis.xadd('s1', f: 'v4') redis.xgroup(:create, 's1', 'g1', '$') actual = redis.xinfo(:stream, 's1') assert_match ENTRY_ID_FORMAT, actual['last-generated-id'] assert_equal 4, actual['length'] assert_equal 1, actual['groups'] assert_equal true, actual.key?('radix-tree-keys') assert_equal true, actual.key?('radix-tree-nodes') assert_kind_of Array, actual['first-entry'] assert_kind_of Array, actual['last-entry'] end def test_xinfo_with_groups_subcommand redis.xadd('s1', f: 'v') redis.xgroup(:create, 's1', 'g1', '$') actual = redis.xinfo(:groups, 's1').first assert_equal 0, actual['consumers'] assert_equal 0, actual['pending'] assert_equal 'g1', actual['name'] assert_match ENTRY_ID_FORMAT, actual['last-delivered-id'] end def test_xinfo_with_consumers_subcommand redis.xadd('s1', f: 'v') redis.xgroup(:create, 's1', 'g1', '$') assert_equal [], redis.xinfo(:consumers, 's1', 'g1') end def test_xinfo_with_invalid_arguments assert_raises(Redis::CommandError) { redis.xinfo('', '', '') } assert_raises(Redis::CommandError) { redis.xinfo(nil, nil, nil) } assert_raises(Redis::CommandError) { redis.xinfo(:stream, nil) } assert_raises(Redis::CommandError) { redis.xinfo(:groups, nil) } assert_raises(Redis::CommandError) { redis.xinfo(:consumers, nil) } assert_raises(Redis::CommandError) { redis.xinfo(:consumers, 's1', nil) } end def test_xadd_with_entry_as_splatted_params assert_match ENTRY_ID_FORMAT, redis.xadd('s1', f1: 'v1', f2: 'v2') end def test_xadd_with_entry_as_a_hash_literal entry = { f1: 'v1', f2: 'v2' } assert_match ENTRY_ID_FORMAT, redis.xadd('s1', entry) end def test_xadd_with_entry_id_option entry_id = "#{Time.now.strftime('%s%L')}-14" assert_equal entry_id, redis.xadd('s1', { f1: 'v1', f2: 'v2' }, id: entry_id) end def test_xadd_with_invalid_entry_id_option entry_id = 'invalid-format-entry-id' assert_raises(Redis::CommandError, 'ERR Invalid stream ID specified as stream command argument') do redis.xadd('s1', { f1: 'v1', f2: 'v2' }, id: entry_id) end end def test_xadd_with_old_entry_id_option redis.xadd('s1', { f1: 'v1', f2: 'v2' }, id: '0-1') err_msg = 'ERR The ID specified in XADD is equal or smaller than the target stream top item' assert_raises(Redis::CommandError, err_msg) do redis.xadd('s1', { f1: 'v1', f2: 'v2' }, id: '0-0') end end def test_xadd_with_maxlen_and_approximate_option actual = redis.xadd('s1', { f1: 'v1', f2: 'v2' }, maxlen: 2, approximate: true) assert_match ENTRY_ID_FORMAT, actual end def test_xadd_with_invalid_arguments assert_raises(Redis::CommandError) { redis.xadd(nil, {}) } assert_raises(Redis::CommandError) { redis.xadd('', {}) } assert_raises(Redis::CommandError) { redis.xadd('s1', {}) } end def test_xtrim redis.xadd('s1', f: 'v1') redis.xadd('s1', f: 'v2') redis.xadd('s1', f: 'v3') redis.xadd('s1', f: 'v4') assert_equal 2, redis.xtrim('s1', 2) end def test_xtrim_with_approximate_option redis.xadd('s1', f: 'v1') redis.xadd('s1', f: 'v2') redis.xadd('s1', f: 'v3') redis.xadd('s1', f: 'v4') assert_equal 0, redis.xtrim('s1', 2, approximate: true) end def test_xtrim_with_not_existed_stream assert_equal 0, redis.xtrim('not-existed-stream', 2) end def test_xtrim_with_invalid_arguments assert_equal 0, redis.xtrim('', '') assert_equal 0, redis.xtrim(nil, nil) assert_equal 0, redis.xtrim('s1', 0) assert_equal 0, redis.xtrim('s1', -1, approximate: true) end def test_xdel_with_splatted_entry_ids redis.xadd('s1', { f: '1' }, id: '0-1') redis.xadd('s1', { f: '2' }, id: '0-2') assert_equal 2, redis.xdel('s1', '0-1', '0-2', '0-3') end def test_xdel_with_arrayed_entry_ids redis.xadd('s1', { f: '1' }, id: '0-1') assert_equal 1, redis.xdel('s1', ['0-1', '0-2']) end def test_xdel_with_invalid_entry_ids assert_equal 0, redis.xdel('s1', 'invalid_format') end def test_xdel_with_invalid_arguments assert_equal 0, redis.xdel(nil, nil) assert_equal 0, redis.xdel(nil, [nil]) assert_equal 0, redis.xdel('', '') assert_equal 0, redis.xdel('', ['']) assert_raises(Redis::CommandError) { redis.xdel('s1', []) } end def test_xrange redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') actual = redis.xrange('s1') assert_equal %w(v1 v2 v3), actual.map { |i| i.last['f'] } end def test_xrange_with_start_option redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrange('s1', '0-2') assert_equal %w(0-2 0-3), actual.map(&:first) end def test_xrange_with_end_option redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrange('s1', '-', '0-2') assert_equal %w(0-1 0-2), actual.map(&:first) end def test_xrange_with_start_and_end_options redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrange('s1', '0-2', '0-2') assert_equal %w(0-2), actual.map(&:first) end def test_xrange_with_incomplete_entry_id_options redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '1-1') redis.xadd('s1', { f: 'v' }, id: '2-1') actual = redis.xrange('s1', '0', '1') assert_equal 2, actual.size assert_equal %w(0-1 1-1), actual.map(&:first) end def test_xrange_with_count_option redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrange('s1', count: 2) assert_equal %w(0-1 0-2), actual.map(&:first) end def test_xrange_with_not_existed_stream_key assert_equal([], redis.xrange('not-existed')) end def test_xrange_with_invalid_entry_id_options assert_raises(Redis::CommandError) { redis.xrange('s1', 'invalid', 'invalid') } end def test_xrange_with_invalid_arguments assert_equal([], redis.xrange(nil)) assert_equal([], redis.xrange('')) end def test_xrevrange redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') actual = redis.xrevrange('s1') assert_equal %w(0-3 0-2 0-1), actual.map(&:first) assert_equal %w(v3 v2 v1), actual.map { |i| i.last['f'] } end def test_xrevrange_with_start_option redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrevrange('s1', '+', '0-2') assert_equal %w(0-3 0-2), actual.map(&:first) end def test_xrevrange_with_end_option redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrevrange('s1', '0-2') assert_equal %w(0-2 0-1), actual.map(&:first) end def test_xrevrange_with_start_and_end_options redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrevrange('s1', '0-2', '0-2') assert_equal %w(0-2), actual.map(&:first) end def test_xrevrange_with_incomplete_entry_id_options redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '1-1') redis.xadd('s1', { f: 'v' }, id: '2-1') actual = redis.xrevrange('s1', '1', '0') assert_equal 2, actual.size assert_equal '1-1', actual.first.first end def test_xrevrange_with_count_option redis.xadd('s1', { f: 'v' }, id: '0-1') redis.xadd('s1', { f: 'v' }, id: '0-2') redis.xadd('s1', { f: 'v' }, id: '0-3') actual = redis.xrevrange('s1', count: 2) assert_equal 2, actual.size assert_equal '0-3', actual.first.first end def test_xrevrange_with_not_existed_stream_key assert_equal([], redis.xrevrange('not-existed')) end def test_xrevrange_with_invalid_entry_id_options assert_raises(Redis::CommandError) { redis.xrevrange('s1', 'invalid', 'invalid') } end def test_xrevrange_with_invalid_arguments assert_equal([], redis.xrevrange(nil)) assert_equal([], redis.xrevrange('')) end def test_xlen redis.xadd('s1', f: 'v1') redis.xadd('s1', f: 'v2') assert_equal 2, redis.xlen('s1') end def test_xlen_with_not_existed_key assert_equal 0, redis.xlen('not-existed') end def test_xlen_with_invalid_key assert_equal 0, redis.xlen(nil) assert_equal 0, redis.xlen('') end def test_xread_with_a_key redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xadd('s1', { f: 'v2' }, id: '0-2') actual = redis.xread('s1', 0) assert_equal %w(v1 v2), actual.fetch('s1').map { |i| i.last['f'] } end def test_xread_with_multiple_keys redis.xadd('s1', { f: 'v01' }, id: '0-1') redis.xadd('s1', { f: 'v02' }, id: '0-2') redis.xadd('s2', { f: 'v11' }, id: '1-1') redis.xadd('s2', { f: 'v12' }, id: '1-2') actual = redis.xread(%w[s1 s2], %w[0-1 1-1]) assert_equal 1, actual['s1'].size assert_equal 1, actual['s2'].size assert_equal 'v02', actual['s1'][0].last['f'] assert_equal 'v12', actual['s2'][0].last['f'] end def test_xread_with_count_option redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xadd('s1', { f: 'v2' }, id: '0-2') actual = redis.xread('s1', 0, count: 1) assert_equal 1, actual['s1'].size end def test_xread_with_block_option actual = redis.xread('s1', '$', block: LOW_TIMEOUT * 1000) assert_equal({}, actual) end def test_xread_does_not_raise_timeout_error_when_the_block_option_is_zero_msec prepared = false actual = nil wire = Wire.new do prepared = true actual = redis.xread('s1', 0, block: 0) end Wire.pass until prepared redis.dup.xadd('s1', { f: 'v1' }, id: '0-1') wire.join assert_equal ['v1'], actual.fetch('s1').map { |i| i.last['f'] } end def test_xread_with_invalid_arguments assert_raises(Redis::CommandError) { redis.xread(nil, nil) } assert_raises(Redis::CommandError) { redis.xread('', '') } assert_raises(Redis::CommandError) { redis.xread([], []) } assert_raises(Redis::CommandError) { redis.xread([''], ['']) } assert_raises(Redis::CommandError) { redis.xread('s1', '0-0', count: 'a') } assert_raises(Redis::CommandError) { redis.xread('s1', %w[0-0 0-0]) } end def test_xgroup_with_create_subcommand redis.xadd('s1', f: 'v') assert_equal 'OK', redis.xgroup(:create, 's1', 'g1', '$') end def test_xgroup_with_create_subcommand_and_mkstream_option err_msg = 'ERR The XGROUP subcommand requires the key to exist. '\ 'Note that for CREATE you may want to use the MKSTREAM option to create an empty stream automatically.' assert_raises(Redis::CommandError, err_msg) { redis.xgroup(:create, 's2', 'g1', '$') } assert_equal 'OK', redis.xgroup(:create, 's2', 'g1', '$', mkstream: true) end def test_xgroup_with_create_subcommand_and_existed_stream_key redis.xadd('s1', f: 'v') redis.xgroup(:create, 's1', 'g1', '$') assert_raises(Redis::CommandError, 'BUSYGROUP Consumer Group name already exists') do redis.xgroup(:create, 's1', 'g1', '$') end end def test_xgroup_with_setid_subcommand redis.xadd('s1', f: 'v') redis.xgroup(:create, 's1', 'g1', '$') assert_equal 'OK', redis.xgroup(:setid, 's1', 'g1', '0') end def test_xgroup_with_destroy_subcommand redis.xadd('s1', f: 'v') redis.xgroup(:create, 's1', 'g1', '$') assert_equal 1, redis.xgroup(:destroy, 's1', 'g1') end def test_xgroup_with_delconsumer_subcommand redis.xadd('s1', f: 'v') redis.xgroup(:create, 's1', 'g1', '$') assert_equal 0, redis.xgroup(:delconsumer, 's1', 'g1', 'c1') end def test_xgroup_with_invalid_arguments assert_raises(Redis::CommandError) { redis.xgroup(nil, nil, nil) } assert_raises(Redis::CommandError) { redis.xgroup('', '', '') } end def test_xreadgroup_with_a_key redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') actual = redis.xreadgroup('g1', 'c1', 's1', '>') assert_equal 2, actual['s1'].size assert_equal 'v2', actual['s1'][0].last['f'] assert_equal 'v3', actual['s1'][1].last['f'] end def test_xreadgroup_with_multiple_keys redis.xadd('s1', { f: 'v01' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s2', { f: 'v11' }, id: '1-1') redis.xgroup(:create, 's2', 'g1', '$') redis.xadd('s1', { f: 'v02' }, id: '0-2') redis.xadd('s2', { f: 'v12' }, id: '1-2') actual = redis.xreadgroup('g1', 'c1', %w[s1 s2], %w[> >]) assert_equal 1, actual['s1'].size assert_equal 1, actual['s2'].size assert_equal 'v02', actual['s1'][0].last['f'] assert_equal 'v12', actual['s2'][0].last['f'] end def test_xreadgroup_with_count_option redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') actual = redis.xreadgroup('g1', 'c1', 's1', '>', count: 1) assert_equal 1, actual['s1'].size end def test_xreadgroup_with_noack_option redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') actual = redis.xreadgroup('g1', 'c1', 's1', '>', noack: true) assert_equal 2, actual['s1'].size end def test_xreadgroup_with_block_option redis.xadd('s1', f: 'v') redis.xgroup(:create, 's1', 'g1', '$') actual = redis.xreadgroup('g1', 'c1', 's1', '>', block: LOW_TIMEOUT * 1000) assert_equal({}, actual) end def test_xreadgroup_with_invalid_arguments assert_raises(Redis::CommandError) { redis.xreadgroup(nil, nil, nil, nil) } assert_raises(Redis::CommandError) { redis.xreadgroup('', '', '', '') } assert_raises(Redis::CommandError) { redis.xreadgroup('', '', [], []) } assert_raises(Redis::CommandError) { redis.xreadgroup('', '', [''], ['']) } redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') assert_raises(Redis::CommandError) { redis.xreadgroup('g1', 'c1', 's1', '>', count: 'a') } assert_raises(Redis::CommandError) { redis.xreadgroup('g1', 'c1', 's1', %w[> >]) } end def test_xack_with_a_entry_id redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xreadgroup('g1', 'c1', 's1', '>') redis.xadd('s1', { f: 'v3' }, id: '0-3') assert_equal 1, redis.xack('s1', 'g1', '0-2') end def test_xack_with_splatted_entry_ids redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') redis.xadd('s1', { f: 'v4' }, id: '0-4') redis.xadd('s1', { f: 'v5' }, id: '0-5') assert_equal 2, redis.xack('s1', 'g1', '0-2', '0-3') end def test_xack_with_arrayed_entry_ids redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') redis.xadd('s1', { f: 'v4' }, id: '0-4') redis.xadd('s1', { f: 'v5' }, id: '0-5') assert_equal 2, redis.xack('s1', 'g1', %w[0-2 0-3]) end def test_xack_with_invalid_arguments assert_equal 0, redis.xack(nil, nil, nil) assert_equal 0, redis.xack('', '', '') assert_raises(Redis::CommandError) { redis.xack('', '', []) } assert_equal 0, redis.xack('', '', ['']) end def test_xclaim_with_splatted_entry_ids redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') sleep 0.01 actual = redis.xclaim('s1', 'g1', 'c2', 10, '0-2', '0-3') assert_equal %w(0-2 0-3), actual.map(&:first) assert_equal %w(v2 v3), actual.map { |i| i.last['f'] } end def test_xclaim_with_arrayed_entry_ids redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') sleep 0.01 actual = redis.xclaim('s1', 'g1', 'c2', 10, %w[0-2 0-3]) assert_equal %w(0-2 0-3), actual.map(&:first) assert_equal %w(v2 v3), actual.map { |i| i.last['f'] } end def test_xclaim_with_idle_option redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') sleep 0.01 actual = redis.xclaim('s1', 'g1', 'c2', 10, '0-2', '0-3', idle: 0) assert_equal %w(0-2 0-3), actual.map(&:first) assert_equal %w(v2 v3), actual.map { |i| i.last['f'] } end def test_xclaim_with_time_option time = Time.now.strftime('%s%L') redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') sleep 0.01 actual = redis.xclaim('s1', 'g1', 'c2', 10, '0-2', '0-3', time: time) assert_equal %w(0-2 0-3), actual.map(&:first) assert_equal %w(v2 v3), actual.map { |i| i.last['f'] } end def test_xclaim_with_retrycount_option redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') sleep 0.01 actual = redis.xclaim('s1', 'g1', 'c2', 10, '0-2', '0-3', retrycount: 10) assert_equal %w(0-2 0-3), actual.map(&:first) assert_equal %w(v2 v3), actual.map { |i| i.last['f'] } end def test_xclaim_with_force_option redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') sleep 0.01 actual = redis.xclaim('s1', 'g1', 'c2', 10, '0-2', '0-3', force: true) assert_equal %w(0-2 0-3), actual.map(&:first) assert_equal %w(v2 v3), actual.map { |i| i.last['f'] } end def test_xclaim_with_justid_option redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') sleep 0.01 actual = redis.xclaim('s1', 'g1', 'c2', 10, '0-2', '0-3', justid: true) assert_equal 2, actual.size assert_equal '0-2', actual[0] assert_equal '0-3', actual[1] end def test_xclaim_with_invalid_arguments assert_raises(Redis::CommandError) { redis.xclaim(nil, nil, nil, nil, nil) } assert_raises(Redis::CommandError) { redis.xclaim('', '', '', '', '') } end def test_xpending redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') actual = redis.xpending('s1', 'g1') assert_equal 2, actual['size'] assert_equal '0-2', actual['min_entry_id'] assert_equal '0-3', actual['max_entry_id'] assert_equal '2', actual['consumers']['c1'] end def test_xpending_with_range_options redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') redis.xadd('s1', { f: 'v4' }, id: '0-4') redis.xreadgroup('g1', 'c2', 's1', '>') actual = redis.xpending('s1', 'g1', '-', '+', 10) assert_equal 3, actual.size assert_equal '0-2', actual[0]['entry_id'] assert_equal 'c1', actual[0]['consumer'] assert_equal true, actual[0]['elapsed'] >= 0 assert_equal 1, actual[0]['count'] assert_equal '0-3', actual[1]['entry_id'] assert_equal 'c1', actual[1]['consumer'] assert_equal true, actual[1]['elapsed'] >= 0 assert_equal 1, actual[1]['count'] assert_equal '0-4', actual[2]['entry_id'] assert_equal 'c2', actual[2]['consumer'] assert_equal true, actual[2]['elapsed'] >= 0 assert_equal 1, actual[2]['count'] end def test_xpending_with_range_and_consumer_options redis.xadd('s1', { f: 'v1' }, id: '0-1') redis.xgroup(:create, 's1', 'g1', '$') redis.xadd('s1', { f: 'v2' }, id: '0-2') redis.xadd('s1', { f: 'v3' }, id: '0-3') redis.xreadgroup('g1', 'c1', 's1', '>') redis.xadd('s1', { f: 'v4' }, id: '0-4') redis.xreadgroup('g1', 'c2', 's1', '>') actual = redis.xpending('s1', 'g1', '-', '+', 10, 'c1') assert_equal 2, actual.size assert_equal '0-2', actual[0]['entry_id'] assert_equal 'c1', actual[0]['consumer'] assert_equal true, actual[0]['elapsed'] >= 0 assert_equal 1, actual[0]['count'] assert_equal '0-3', actual[1]['entry_id'] assert_equal 'c1', actual[1]['consumer'] assert_equal true, actual[1]['elapsed'] >= 0 assert_equal 1, actual[1]['count'] end end end redis-rb-4.1.2/test/lint/strings.rb000066400000000000000000000203251347371274700172110ustar00rootroot00000000000000module Lint module Strings def mock(*args, &block) redis_mock(*args, &block) end def test_set_and_get r.set("foo", "s1") assert_equal "s1", r.get("foo") end def test_set_and_get_with_newline_characters r.set("foo", "1\n") assert_equal "1\n", r.get("foo") end def test_set_and_get_with_non_string_value value = ["a", "b"] r.set("foo", value) assert_equal value.to_s, r.get("foo") end def test_set_and_get_with_ascii_characters with_external_encoding("ASCII-8BIT") do (0..255).each do |i| str = "#{i.chr}---#{i.chr}" r.set("foo", str) assert_equal str, r.get("foo") end end end def test_set_with_ex target_version "2.6.12" do r.set("foo", "bar", :ex => 2) assert_in_range 0..2, r.ttl("foo") end end def test_set_with_px target_version "2.6.12" do r.set("foo", "bar", :px => 2000) assert_in_range 0..2, r.ttl("foo") end end def test_set_with_nx target_version "2.6.12" do r.set("foo", "qux", :nx => true) assert !r.set("foo", "bar", :nx => true) assert_equal "qux", r.get("foo") r.del("foo") assert r.set("foo", "bar", :nx => true) assert_equal "bar", r.get("foo") end end def test_set_with_xx target_version "2.6.12" do r.set("foo", "qux") assert r.set("foo", "bar", :xx => true) assert_equal "bar", r.get("foo") r.del("foo") assert !r.set("foo", "bar", :xx => true) end end def test_setex assert r.setex("foo", 1, "bar") assert_equal "bar", r.get("foo") assert [0, 1].include? r.ttl("foo") end def test_setex_with_non_string_value value = ["b", "a", "r"] assert r.setex("foo", 1, value) assert_equal value.to_s, r.get("foo") assert [0, 1].include? r.ttl("foo") end def test_psetex target_version "2.5.4" do assert r.psetex("foo", 1000, "bar") assert_equal "bar", r.get("foo") assert [0, 1].include? r.ttl("foo") end end def test_psetex_with_non_string_value target_version "2.5.4" do value = ["b", "a", "r"] assert r.psetex("foo", 1000, value) assert_equal value.to_s, r.get("foo") assert [0, 1].include? r.ttl("foo") end end def test_getset r.set("foo", "bar") assert_equal "bar", r.getset("foo", "baz") assert_equal "baz", r.get("foo") end def test_getset_with_non_string_value r.set("foo", "zap") value = ["b", "a", "r"] assert_equal "zap", r.getset("foo", value) assert_equal value.to_s, r.get("foo") end def test_setnx r.set("foo", "qux") assert !r.setnx("foo", "bar") assert_equal "qux", r.get("foo") r.del("foo") assert r.setnx("foo", "bar") assert_equal "bar", r.get("foo") end def test_setnx_with_non_string_value value = ["b", "a", "r"] r.set("foo", "qux") assert !r.setnx("foo", value) assert_equal "qux", r.get("foo") r.del("foo") assert r.setnx("foo", value) assert_equal value.to_s, r.get("foo") end def test_incr assert_equal 1, r.incr("foo") assert_equal 2, r.incr("foo") assert_equal 3, r.incr("foo") end def test_incrby assert_equal 1, r.incrby("foo", 1) assert_equal 3, r.incrby("foo", 2) assert_equal 6, r.incrby("foo", 3) end def test_incrbyfloat target_version "2.5.4" do assert_equal 1.23, r.incrbyfloat("foo", 1.23) assert_equal 2 , r.incrbyfloat("foo", 0.77) assert_equal 1.9 , r.incrbyfloat("foo", -0.1) end end def test_decr r.set("foo", 3) assert_equal 2, r.decr("foo") assert_equal 1, r.decr("foo") assert_equal 0, r.decr("foo") end def test_decrby r.set("foo", 6) assert_equal 3, r.decrby("foo", 3) assert_equal 1, r.decrby("foo", 2) assert_equal 0, r.decrby("foo", 1) end def test_append r.set "foo", "s" r.append "foo", "1" assert_equal "s1", r.get("foo") end def test_getbit r.set("foo", "a") assert_equal 1, r.getbit("foo", 1) assert_equal 1, r.getbit("foo", 2) assert_equal 0, r.getbit("foo", 3) assert_equal 0, r.getbit("foo", 4) assert_equal 0, r.getbit("foo", 5) assert_equal 0, r.getbit("foo", 6) assert_equal 1, r.getbit("foo", 7) end def test_setbit r.set("foo", "a") r.setbit("foo", 6, 1) assert_equal "c", r.get("foo") end def test_bitcount target_version "2.5.10" do r.set("foo", "abcde") assert_equal 10, r.bitcount("foo", 1, 3) assert_equal 17, r.bitcount("foo", 0, -1) end end def test_getrange r.set("foo", "abcde") assert_equal "bcd", r.getrange("foo", 1, 3) assert_equal "abcde", r.getrange("foo", 0, -1) end def test_setrange r.set("foo", "abcde") r.setrange("foo", 1, "bar") assert_equal "abare", r.get("foo") end def test_setrange_with_non_string_value r.set("foo", "abcde") value = ["b", "a", "r"] r.setrange("foo", 2, value) assert_equal "ab#{value.to_s}", r.get("foo") end def test_strlen r.set "foo", "lorem" assert_equal 5, r.strlen("foo") end def test_bitfield target_version('3.2.0') do mock(bitfield: ->(*_) { "*2\r\n:1\r\n:0\r\n" }) do |redis| assert_equal [1, 0], redis.bitfield('foo', 'INCRBY', 'i5', 100, 1, 'GET', 'u4', 0) end end end def test_mget r.set('{1}foo', 's1') r.set('{1}bar', 's2') assert_equal %w[s1 s2], r.mget('{1}foo', '{1}bar') assert_equal ['s1', 's2', nil], r.mget('{1}foo', '{1}bar', '{1}baz') end def test_mget_mapped r.set('{1}foo', 's1') r.set('{1}bar', 's2') response = r.mapped_mget('{1}foo', '{1}bar') assert_equal 's1', response['{1}foo'] assert_equal 's2', response['{1}bar'] response = r.mapped_mget('{1}foo', '{1}bar', '{1}baz') assert_equal 's1', response['{1}foo'] assert_equal 's2', response['{1}bar'] assert_nil response['{1}baz'] end def test_mapped_mget_in_a_pipeline_returns_hash r.set('{1}foo', 's1') r.set('{1}bar', 's2') result = r.pipelined do r.mapped_mget('{1}foo', '{1}bar') end assert_equal({ '{1}foo' => 's1', '{1}bar' => 's2' }, result[0]) end def test_mset r.mset('{1}foo', 's1', '{1}bar', 's2') assert_equal 's1', r.get('{1}foo') assert_equal 's2', r.get('{1}bar') end def test_mset_mapped r.mapped_mset('{1}foo' => 's1', '{1}bar' => 's2') assert_equal 's1', r.get('{1}foo') assert_equal 's2', r.get('{1}bar') end def test_msetnx r.set('{1}foo', 's1') assert_equal false, r.msetnx('{1}foo', 's2', '{1}bar', 's3') assert_equal 's1', r.get('{1}foo') assert_nil r.get('{1}bar') r.del('{1}foo') assert_equal true, r.msetnx('{1}foo', 's2', '{1}bar', 's3') assert_equal 's2', r.get('{1}foo') assert_equal 's3', r.get('{1}bar') end def test_msetnx_mapped r.set('{1}foo', 's1') assert_equal false, r.mapped_msetnx('{1}foo' => 's2', '{1}bar' => 's3') assert_equal 's1', r.get('{1}foo') assert_nil r.get('{1}bar') r.del('{1}foo') assert_equal true, r.mapped_msetnx('{1}foo' => 's2', '{1}bar' => 's3') assert_equal 's2', r.get('{1}foo') assert_equal 's3', r.get('{1}bar') end def test_bitop with_external_encoding('UTF-8') do target_version '2.5.10' do r.set('foo{1}', 'a') r.set('bar{1}', 'b') r.bitop(:and, 'foo&bar{1}', 'foo{1}', 'bar{1}') assert_equal "\x60", r.get('foo&bar{1}') r.bitop(:or, 'foo|bar{1}', 'foo{1}', 'bar{1}') assert_equal "\x63", r.get('foo|bar{1}') r.bitop(:xor, 'foo^bar{1}', 'foo{1}', 'bar{1}') assert_equal "\x03", r.get('foo^bar{1}') r.bitop(:not, '~foo{1}', 'foo{1}') assert_equal "\x9E", r.get('~foo{1}') end end end end end redis-rb-4.1.2/test/lint/value_types.rb000066400000000000000000000055451347371274700200670ustar00rootroot00000000000000module Lint module ValueTypes def test_exists assert_equal false, r.exists("foo") r.set("foo", "s1") assert_equal true, r.exists("foo") end def test_type assert_equal "none", r.type("foo") r.set("foo", "s1") assert_equal "string", r.type("foo") end def test_keys r.set("f", "s1") r.set("fo", "s2") r.set("foo", "s3") assert_equal ["f","fo", "foo"], r.keys("f*").sort end def test_expire r.set("foo", "s1") assert r.expire("foo", 2) assert_in_range 0..2, r.ttl("foo") end def test_pexpire target_version "2.5.4" do r.set("foo", "s1") assert r.pexpire("foo", 2000) assert_in_range 0..2, r.ttl("foo") end end def test_expireat r.set("foo", "s1") assert r.expireat("foo", (Time.now + 2).to_i) assert_in_range 0..2, r.ttl("foo") end def test_pexpireat target_version "2.5.4" do r.set("foo", "s1") assert r.pexpireat("foo", (Time.now + 2).to_i * 1_000) assert_in_range 0..2, r.ttl("foo") end end def test_persist r.set("foo", "s1") r.expire("foo", 1) r.persist("foo") assert(-1 == r.ttl("foo")) end def test_ttl r.set("foo", "s1") r.expire("foo", 2) assert_in_range 0..2, r.ttl("foo") end def test_pttl target_version "2.5.4" do r.set("foo", "s1") r.expire("foo", 2) assert_in_range 1..2000, r.pttl("foo") end end def test_dump_and_restore target_version "2.5.7" do r.set("foo", "a") v = r.dump("foo") r.del("foo") assert r.restore("foo", 1000, v) assert_equal "a", r.get("foo") assert [0, 1].include? r.ttl("foo") r.rpush("bar", ["b", "c", "d"]) w = r.dump("bar") r.del("bar") assert r.restore("bar", 1000, w) assert_equal ["b", "c", "d"], r.lrange("bar", 0, -1) assert [0, 1].include? r.ttl("bar") r.set("bar", "somethingelse") assert_raises(Redis::CommandError) { r.restore("bar", 1000, w) } # ensure by default replace is false assert_raises(Redis::CommandError) { r.restore("bar", 1000, w, :replace => false) } assert_equal "somethingelse", r.get("bar") assert r.restore("bar", 1000, w, :replace => true) assert_equal ["b", "c", "d"], r.lrange("bar", 0, -1) assert [0, 1].include? r.ttl("bar") end end def test_move r.select 14 r.flushdb r.set "bar", "s3" r.select 15 r.set "foo", "s1" r.set "bar", "s2" assert r.move("foo", 14) assert_nil r.get("foo") assert !r.move("bar", 14) assert_equal "s2", r.get("bar") r.select 14 assert_equal "s1", r.get("foo") assert_equal "s3", r.get("bar") end end end redis-rb-4.1.2/test/persistence_control_commands_test.rb000066400000000000000000000010011347371274700235440ustar00rootroot00000000000000require_relative "helper" class TestPersistenceControlCommands < Minitest::Test include Helper::Client def test_save redis_mock(:save => lambda { "+SAVE" }) do |redis| assert_equal "SAVE", redis.save end end def test_bgsave redis_mock(:bgsave => lambda { "+BGSAVE" }) do |redis| assert_equal "BGSAVE", redis.bgsave end end def test_lastsave redis_mock(:lastsave => lambda { "+LASTSAVE" }) do |redis| assert_equal "LASTSAVE", redis.lastsave end end end redis-rb-4.1.2/test/pipelining_commands_test.rb000066400000000000000000000120421347371274700216250ustar00rootroot00000000000000require_relative "helper" class TestPipeliningCommands < Minitest::Test include Helper::Client def test_bulk_commands r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") end def test_multi_bulk_commands r.pipelined do r.mset("foo", "s1", "bar", "s2") r.mset("baz", "s3", "qux", "s4") end assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") assert_equal "s3", r.get("baz") assert_equal "s4", r.get("qux") end def test_bulk_and_multi_bulk_commands_mixed r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" r.mset("baz", "s3", "qux", "s4") end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") assert_equal "s3", r.get("baz") assert_equal "s4", r.get("qux") end def test_multi_bulk_and_bulk_commands_mixed r.pipelined do r.mset("baz", "s3", "qux", "s4") r.lpush "foo", "s1" r.lpush "foo", "s2" end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") assert_equal "s3", r.get("baz") assert_equal "s4", r.get("qux") end def test_pipelined_with_an_empty_block r.pipelined do end assert_equal 0, r.dbsize end def test_returning_the_result_of_a_pipeline result = r.pipelined do r.set "foo", "bar" r.get "foo" r.get "bar" end assert_equal ["OK", "bar", nil], result end def test_assignment_of_results_inside_the_block r.pipelined do @first = r.sadd("foo", 1) @second = r.sadd("foo", 1) end assert_equal true, @first.value assert_equal false, @second.value end # Although we could support accessing the values in these futures, # it doesn't make a lot of sense. def test_assignment_of_results_inside_the_block_with_errors assert_raises(Redis::CommandError) do r.pipelined do r.doesnt_exist @first = r.sadd("foo", 1) @second = r.sadd("foo", 1) end end assert_raises(Redis::FutureNotReady) { @first.value } assert_raises(Redis::FutureNotReady) { @second.value } end def test_assignment_of_results_inside_a_nested_block r.pipelined do @first = r.sadd("foo", 1) r.pipelined do @second = r.sadd("foo", 1) end end assert_equal true, @first.value assert_equal false, @second.value end def test_futures_raise_when_confused_with_something_else r.pipelined do @result = r.sadd("foo", 1) end assert_raises(NoMethodError) { @result.to_s } end def test_futures_raise_when_trying_to_access_their_values_too_early r.pipelined do assert_raises(Redis::FutureNotReady) do r.sadd("foo", 1).value end end end def test_futures_raise_when_command_errors_and_needs_transformation assert_raises(Redis::CommandError) do r.pipelined do @result = r.zrange("a", "b", 5, :with_scores => true) end end end def test_futures_can_be_identified r.pipelined do @result = r.sadd("foo", 1) end assert_equal true, @result.is_a?(Redis::Future) assert_equal true, @result.is_a?(::BasicObject) assert_equal Redis::Future, @result.class end def test_returning_the_result_of_an_empty_pipeline result = r.pipelined do end assert_equal [], result end def test_nesting_pipeline_blocks r.pipelined do r.set("foo", "s1") r.pipelined do r.set("bar", "s2") end end assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") end def test_info_in_a_pipeline_returns_hash result = r.pipelined do r.info end assert result.first.kind_of?(Hash) end def test_config_get_in_a_pipeline_returns_hash result = r.pipelined do r.config(:get, "*") end assert result.first.kind_of?(Hash) end def test_hgetall_in_a_pipeline_returns_hash r.hmset("hash", "field", "value") result = r.pipelined do r.hgetall("hash") end assert_equal result.first, { "field" => "value" } end def test_keys_in_a_pipeline r.set("key", "value") result = r.pipelined do r.keys("*") end assert_equal ["key"], result.first end def test_pipeline_yields_a_connection r.pipelined do |p| p.set("foo", "bar") end assert_equal "bar", r.get("foo") end def test_pipeline_select r.select 1 r.set("db", "1") r.pipelined do |p| p.select 2 p.set("db", "2") end r.select 1 assert_equal "1", r.get("db") r.select 2 assert_equal "2", r.get("db") end def test_pipeline_select_client_db r.select 1 r.pipelined do |p2| p2.select 2 end assert_equal 2, r._client.db end def test_nested_pipeline_select_client_db r.select 1 r.pipelined do |p2| p2.select 2 p2.pipelined do |p3| p3.select 3 end end assert_equal 3, r._client.db end end redis-rb-4.1.2/test/publish_subscribe_test.rb000066400000000000000000000141331347371274700213200ustar00rootroot00000000000000require_relative "helper" class TestPublishSubscribe < Minitest::Test include Helper::Client class TestError < StandardError end def test_subscribe_and_unsubscribe @subscribed = false @unsubscribed = false wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @subscribed = true @t1 = total end on.message do |channel, message| if message == "s1" r.unsubscribe @message = message end end on.unsubscribe do |channel, total| @unsubscribed = true @t2 = total end end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert @subscribed assert_equal 1, @t1 assert @unsubscribed assert_equal 0, @t2 assert_equal "s1", @message end def test_psubscribe_and_punsubscribe @subscribed = false @unsubscribed = false wire = Wire.new do r.psubscribe("f*") do |on| on.psubscribe do |pattern, total| @subscribed = true @t1 = total end on.pmessage do |pattern, channel, message| if message == "s1" r.punsubscribe @message = message end end on.punsubscribe do |pattern, total| @unsubscribed = true @t2 = total end end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert @subscribed assert_equal 1, @t1 assert @unsubscribed assert_equal 0, @t2 assert_equal "s1", @message end def test_pubsub_with_numpat_subcommand target_version("2.8.0") do @subscribed = false wire = Wire.new do r.psubscribe("f*") do |on| on.psubscribe { |channel, total| @subscribed = true } on.pmessage { |pattern, channel, message| r.punsubscribe } end end Wire.pass while !@subscribed redis = Redis.new(OPTIONS) numpat_result = redis.pubsub(:numpat) redis.publish("foo", "s1") wire.join assert_equal redis.pubsub(:numpat), 0 assert_equal numpat_result, 1 end end def test_pubsub_with_channels_and_numsub_subcommnads target_version("2.8.0") do @subscribed = false wire = Wire.new do r.subscribe("foo") do |on| on.subscribe { |channel, total| @subscribed = true } on.message { |channel, message| r.unsubscribe } end end Wire.pass while !@subscribed redis = Redis.new(OPTIONS) channels_result = redis.pubsub(:channels) channels_result.delete('__sentinel__:hello') numsub_result = redis.pubsub(:numsub, 'foo', 'boo') redis.publish("foo", "s1") wire.join assert_equal channels_result, ['foo'] assert_equal numsub_result, ['foo', 1, 'boo', 0] end end def test_subscribe_connection_usable_after_raise @subscribed = false wire = Wire.new do begin r.subscribe("foo") do |on| on.subscribe do |channel, total| @subscribed = true end on.message do |channel, message| raise TestError end end rescue TestError end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert_equal "PONG", r.ping end def test_psubscribe_connection_usable_after_raise @subscribed = false wire = Wire.new do begin r.psubscribe("f*") do |on| on.psubscribe do |pattern, total| @subscribed = true end on.pmessage do |pattern, channel, message| raise TestError end end rescue TestError end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert_equal "PONG", r.ping end def test_subscribe_within_subscribe @channels = [] wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @channels << channel r.subscribe("bar") if channel == "foo" r.unsubscribe if channel == "bar" end end end wire.join assert_equal ["foo", "bar"], @channels end def test_other_commands_within_a_subscribe assert_raises Redis::CommandError do r.subscribe("foo") do |on| on.subscribe do |channel, total| r.set("bar", "s2") end end end end def test_subscribe_without_a_block assert_raises LocalJumpError do r.subscribe("foo") end end def test_unsubscribe_without_a_subscribe assert_raises RuntimeError do r.unsubscribe end assert_raises RuntimeError do r.punsubscribe end end def test_subscribe_past_a_timeout # For some reason, a thread here doesn't reproduce the issue. sleep = %(sleep 0.05) publish = %{ruby -rsocket -e 't=TCPSocket.new("127.0.0.1",#{OPTIONS[:port]});t.write("publish foo bar\\r\\n");t.read(4);t.close'} cmd = [sleep, publish].join('; ') IO.popen(cmd, 'r+') do |_pipe| received = false r.subscribe 'foo' do |on| on.message do |_channel, _message| received = true r.unsubscribe end end assert received end end def test_subscribe_with_timeout received = false assert_raises Redis::TimeoutError do r.subscribe_with_timeout(LOW_TIMEOUT, "foo") do |on| on.message do |channel, message| received = true end end end assert !received end def test_psubscribe_with_timeout received = false assert_raises Redis::TimeoutError do r.psubscribe_with_timeout(LOW_TIMEOUT, "f*") do |on| on.message do |channel, message| received = true end end end assert !received end end redis-rb-4.1.2/test/remote_server_control_commands_test.rb000066400000000000000000000065131347371274700241160ustar00rootroot00000000000000require_relative "helper" class TestRemoteServerControlCommands < Minitest::Test include Helper::Client def test_info keys = [ "redis_version", "uptime_in_seconds", "uptime_in_days", "connected_clients", "used_memory", "total_connections_received", "total_commands_processed", ] info = r.info keys.each do |k| msg = "expected #info to include #{k}" assert info.keys.include?(k), msg end end def test_info_commandstats target_version "2.5.7" do r.config(:resetstat) r.ping result = r.info(:commandstats) assert_equal "1", result["ping"]["calls"] end end def test_monitor_redis_lt_2_5_0 return unless version < "2.5.0" log = [] wire = Wire.new do Redis.new(OPTIONS).monitor do |line| log << line break if log.size == 3 end end Wire.pass while log.empty? # Faster than sleep r.set "foo", "s1" wire.join assert log[-1][%q{(db 15) "set" "foo" "s1"}] end def test_monitor_redis_gte_2_5_0 return unless version >= "2.5.0" log = [] wire = Wire.new do Redis.new(OPTIONS).monitor do |line| log << line break if line =~ /set/ end end Wire.pass while log.empty? # Faster than sleep r.set "foo", "s1" wire.join assert log[-1] =~ /\b15\b.* "set" "foo" "s1"/ end def test_monitor_returns_value_for_break result = r.monitor do |line| break line end assert_equal "OK", result end def test_echo assert_equal "foo bar baz\n", r.echo("foo bar baz\n") end def test_debug r.set "foo", "s1" assert r.debug(:object, "foo").kind_of?(String) end def test_object r.lpush "list", "value" assert_equal 1, r.object(:refcount, "list") encoding = r.object(:encoding, "list") assert "ziplist" == encoding || "quicklist" == encoding, "Wrong encoding for list" assert r.object(:idletime, "list").kind_of?(Integer) end def test_sync redis_mock(:sync => lambda { "+OK" }) do |redis| assert_equal "OK", redis.sync end end def test_slowlog r.slowlog(:reset) result = r.slowlog(:len) assert_equal 0, result end def test_client assert_equal r.instance_variable_get(:@client), r._client end def test_client_list return if version < "2.4.0" keys = [ "addr", "fd", "name", "age", "idle", "flags", "db", "sub", "psub", "multi", "qbuf", "qbuf-free", "obl", "oll", "omem", "events", "cmd" ] clients = r.client(:list) clients.each do |client| keys.each do |k| msg = "expected #client(:list) to include #{k}" assert client.keys.include?(k), msg end end end def test_client_kill return if version < "2.6.9" r.client(:setname, 'redis-rb') clients = r.client(:list) i = clients.index {|client| client['name'] == 'redis-rb'} assert_equal "OK", r.client(:kill, clients[i]["addr"]) clients = r.client(:list) i = clients.index {|client| client['name'] == 'redis-rb'} assert_nil i end def test_client_getname_and_setname return if version < "2.6.9" assert_nil r.client(:getname) r.client(:setname, 'redis-rb') name = r.client(:getname) assert_equal 'redis-rb', name end end redis-rb-4.1.2/test/scanning_test.rb000066400000000000000000000230101347371274700174030ustar00rootroot00000000000000require_relative "helper" class TestScanning < Minitest::Test include Helper::Client def test_scan_basic target_version "2.7.105" do r.debug :populate, 1000 cursor = 0 all_keys = [] loop { cursor, keys = r.scan cursor all_keys += keys break if cursor == "0" } assert_equal 1000, all_keys.uniq.size end end def test_scan_count target_version "2.7.105" do r.debug :populate, 1000 cursor = 0 all_keys = [] loop { cursor, keys = r.scan cursor, :count => 5 all_keys += keys break if cursor == "0" } assert_equal 1000, all_keys.uniq.size end end def test_scan_match target_version "2.7.105" do r.debug :populate, 1000 cursor = 0 all_keys = [] loop { cursor, keys = r.scan cursor, :match => "key:1??" all_keys += keys break if cursor == "0" } assert_equal 100, all_keys.uniq.size end end def test_scan_each_enumerator target_version "2.7.105" do r.debug :populate, 1000 scan_enumerator = r.scan_each assert_equal true, scan_enumerator.is_a?(::Enumerator) keys_from_scan = scan_enumerator.to_a.uniq all_keys = r.keys "*" assert all_keys.sort == keys_from_scan.sort end end def test_scan_each_enumerator_match target_version "2.7.105" do r.debug :populate, 1000 keys_from_scan = r.scan_each(:match => "key:1??").to_a.uniq all_keys = r.keys "key:1??" assert all_keys.sort == keys_from_scan.sort end end def test_scan_each_block target_version "2.7.105" do r.debug :populate, 100 keys_from_scan = [] r.scan_each {|key| keys_from_scan << key } all_keys = r.keys "*" assert all_keys.sort == keys_from_scan.uniq.sort end end def test_scan_each_block_match target_version "2.7.105" do r.debug :populate, 100 keys_from_scan = [] r.scan_each(:match => "key:1?") {|key| keys_from_scan << key } all_keys = r.keys "key:1?" assert all_keys.sort == keys_from_scan.uniq.sort end end def test_sscan_with_encoding target_version "2.7.105" do [:intset, :hashtable].each do |enc| r.del "set" prefix = "" prefix = "ele:" if enc == :hashtable elements = [] 100.times { |j| elements << "#{prefix}#{j}" } r.sadd "set", elements assert_equal enc.to_s, r.object("encoding", "set") cursor = 0 all_keys = [] loop { cursor, keys = r.sscan "set", cursor all_keys += keys break if cursor == "0" } assert_equal 100, all_keys.uniq.size end end end def test_sscan_each_enumerator target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements scan_enumerator = r.sscan_each("set") assert_equal true, scan_enumerator.is_a?(::Enumerator) keys_from_scan = scan_enumerator.to_a.uniq all_keys = r.smembers("set") assert all_keys.sort == keys_from_scan.sort end end def test_sscan_each_enumerator_match target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements keys_from_scan = r.sscan_each("set", :match => "ele:1?").to_a.uniq all_keys = r.smembers("set").grep(/^ele:1.$/) assert all_keys.sort == keys_from_scan.sort end end def test_sscan_each_enumerator_block target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements keys_from_scan = [] r.sscan_each("set") do |key| keys_from_scan << key end all_keys = r.smembers("set") assert all_keys.sort == keys_from_scan.uniq.sort end end def test_sscan_each_enumerator_block_match target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements keys_from_scan = [] r.sscan_each("set", :match => "ele:1?") do |key| keys_from_scan << key end all_keys = r.smembers("set").grep(/^ele:1.$/) assert all_keys.sort == keys_from_scan.uniq.sort end end def test_hscan_with_encoding target_version "2.7.105" do [:ziplist, :hashtable].each do |enc| r.del "set" count = 1000 count = 30 if enc == :ziplist elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements assert_equal enc.to_s, r.object("encoding", "hash") cursor = 0 all_key_values = [] loop { cursor, key_values = r.hscan "hash", cursor all_key_values.concat key_values break if cursor == "0" } keys2 = [] all_key_values.each do |k, v| assert_equal "key:#{v}", k keys2 << k end assert_equal count, keys2.uniq.size end end end def test_hscan_each_enumerator target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements scan_enumerator = r.hscan_each("hash") assert_equal true, scan_enumerator.is_a?(::Enumerator) keys_from_scan = scan_enumerator.to_a.uniq all_keys = r.hgetall("hash").to_a assert all_keys.sort == keys_from_scan.sort end end def test_hscan_each_enumerator_match target_version "2.7.105" do count = 100 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements keys_from_scan = r.hscan_each("hash", :match => "key:1?").to_a.uniq all_keys = r.hgetall("hash").to_a.select{|k,v| k =~ /^key:1.$/} assert all_keys.sort == keys_from_scan.sort end end def test_hscan_each_block target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements keys_from_scan = [] r.hscan_each("hash") do |field, value| keys_from_scan << [field, value] end all_keys = r.hgetall("hash").to_a assert all_keys.sort == keys_from_scan.uniq.sort end end def test_hscan_each_block_match target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements keys_from_scan = [] r.hscan_each("hash", :match => "key:1?") do |field, value| keys_from_scan << [field, value] end all_keys = r.hgetall("hash").to_a.select{|k,v| k =~ /^key:1.$/} assert all_keys.sort == keys_from_scan.uniq.sort end end def test_zscan_with_encoding target_version "2.7.105" do [:ziplist, :skiplist].each do |enc| r.del "zset" count = 1000 count = 30 if enc == :ziplist elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements assert_equal enc.to_s, r.object("encoding", "zset") cursor = 0 all_key_scores = [] loop { cursor, key_scores = r.zscan "zset", cursor all_key_scores.concat key_scores break if cursor == "0" } keys2 = [] all_key_scores.each do |k, v| assert_equal true, v.is_a?(Float) assert_equal "key:#{Integer(v)}", k keys2 << k end assert_equal count, keys2.uniq.size end end end def test_zscan_each_enumerator target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scan_enumerator = r.zscan_each "zset" assert_equal true, scan_enumerator.is_a?(::Enumerator) scores_from_scan = scan_enumerator.to_a.uniq member_scores = r.zrange("zset", 0, -1, :with_scores => true) assert member_scores.sort == scores_from_scan.sort end end def test_zscan_each_enumerator_match target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scores_from_scan = r.zscan_each("zset", :match => "key:1??").to_a.uniq member_scores = r.zrange("zset", 0, -1, :with_scores => true) filtered_members = member_scores.select{|k,s| k =~ /^key:1..$/} assert filtered_members.sort == scores_from_scan.sort end end def test_zscan_each_block target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scores_from_scan = [] r.zscan_each("zset") do |member, score| scores_from_scan << [member, score] end member_scores = r.zrange("zset", 0, -1, :with_scores => true) assert member_scores.sort == scores_from_scan.sort end end def test_zscan_each_block_match target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scores_from_scan = [] r.zscan_each("zset", :match => "key:1??") do |member, score| scores_from_scan << [member, score] end member_scores = r.zrange("zset", 0, -1, :with_scores => true) filtered_members = member_scores.select{|k,s| k =~ /^key:1..$/} assert filtered_members.sort == scores_from_scan.sort end end end redis-rb-4.1.2/test/scripting_test.rb000066400000000000000000000045011347371274700176110ustar00rootroot00000000000000require_relative "helper" class TestScripting < Minitest::Test include Helper::Client def to_sha(script) r.script(:load, script) end def test_script_exists target_version "2.5.9" do # 2.6-rc1 a = to_sha("return 1") b = a.succ assert_equal true, r.script(:exists, a) assert_equal false, r.script(:exists, b) assert_equal [true], r.script(:exists, [a]) assert_equal [false], r.script(:exists, [b]) assert_equal [true, false], r.script(:exists, [a, b]) end end def test_script_flush target_version "2.5.9" do # 2.6-rc1 sha = to_sha("return 1") assert r.script(:exists, sha) assert_equal "OK", r.script(:flush) assert !r.script(:exists, sha) end end def test_script_kill target_version "2.5.9" do # 2.6-rc1 redis_mock(:script => lambda { |arg| "+#{arg.upcase}" }) do |redis| assert_equal "KILL", redis.script(:kill) end end end def test_eval target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.eval("return #KEYS") assert_equal 0, r.eval("return #ARGV") assert_equal ["k1", "k2"], r.eval("return KEYS", ["k1", "k2"]) assert_equal ["a1", "a2"], r.eval("return ARGV", [], ["a1", "a2"]) end end def test_eval_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.eval("return #KEYS", {}) assert_equal 0, r.eval("return #ARGV", {}) assert_equal ["k1", "k2"], r.eval("return KEYS", { :keys => ["k1", "k2"] }) assert_equal ["a1", "a2"], r.eval("return ARGV", { :argv => ["a1", "a2"] }) end end def test_evalsha target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.evalsha(to_sha("return #KEYS")) assert_equal 0, r.evalsha(to_sha("return #ARGV")) assert_equal ["k1", "k2"], r.evalsha(to_sha("return KEYS"), ["k1", "k2"]) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), [], ["a1", "a2"]) end end def test_evalsha_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.evalsha(to_sha("return #KEYS"), {}) assert_equal 0, r.evalsha(to_sha("return #ARGV"), {}) assert_equal ["k1", "k2"], r.evalsha(to_sha("return KEYS"), { :keys => ["k1", "k2"] }) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), { :argv => ["a1", "a2"] }) end end end redis-rb-4.1.2/test/sentinel_command_test.rb000066400000000000000000000032731347371274700211330ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' # @see https://redis.io/topics/sentinel#sentinel-commands Sentinel commands class SentinelCommandsTest < Minitest::Test include Helper::Sentinel def test_sentinel_command_master redis = build_sentinel_client result = redis.sentinel('master', MASTER_NAME) assert_equal result['name'], MASTER_NAME assert_equal result['ip'], LOCALHOST end def test_sentinel_command_masters redis = build_sentinel_client result = redis.sentinel('masters') assert_equal result[0]['name'], MASTER_NAME assert_equal result[0]['ip'], LOCALHOST assert_equal result[0]['port'], MASTER_PORT end def test_sentinel_command_slaves redis = build_sentinel_client result = redis.sentinel('slaves', MASTER_NAME) assert_equal result[0]['name'], "#{LOCALHOST}:#{SLAVE_PORT}" assert_equal result[0]['ip'], LOCALHOST assert_equal result[0]['port'], SLAVE_PORT end def test_sentinel_command_sentinels redis = build_sentinel_client result = redis.sentinel('sentinels', MASTER_NAME) assert_equal result[0]['ip'], LOCALHOST actual_ports = result.map { |r| r['port'] }.sort expected_ports = SENTINEL_PORTS[1..-1] assert_equal actual_ports, expected_ports end def test_sentinel_command_get_master_by_name redis = build_sentinel_client result = redis.sentinel('get-master-addr-by-name', MASTER_NAME) assert_equal result, [LOCALHOST, MASTER_PORT] end def test_sentinel_command_ckquorum redis = build_sentinel_client result = redis.sentinel('ckquorum', MASTER_NAME) assert_equal result, 'OK 3 usable Sentinels. Quorum and failover authorization can be reached' end end redis-rb-4.1.2/test/sentinel_test.rb000066400000000000000000000220371347371274700174340ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' class SentinelTest < Minitest::Test include Helper::Sentinel def test_sentinel_master_role_connection actual = redis.role assert_equal 'master', actual[0] assert_equal SLAVE_PORT, actual[2][0][1] end def test_sentinel_slave_role_connection redis = build_slave_role_client actual = redis.role assert_equal 'slave', actual[0] assert_equal MASTER_PORT.to_i, actual[2] end def test_the_client_can_connect_to_available_slaves commands = { sentinel: lambda do |*_| [ ['ip', '127.0.0.1', 'port', '6382', 'flags', 'slave'], ['ip', '127.0.0.1', 'port', '6383', 'flags', 's_down,slave,disconnected'] ] end } RedisMock.start(commands) do |port| redis = build_slave_role_client(sentinels: [{ host: 'localhost', port: port }]) assert_equal 'PONG', redis.ping end end def test_the_client_raises_error_when_there_is_no_available_slaves commands = { sentinel: lambda do |*_| [ ['ip', '127.0.0.1', 'port', '6382', 'flags', 's_down,slave,disconnected'], ['ip', '127.0.0.1', 'port', '6383', 'flags', 's_down,slave,disconnected'] ] end } RedisMock.start(commands) do |port| redis = build_slave_role_client(sentinels: [{ host: 'localhost', port: port }]) assert_raises(Redis::CannotConnectError) { redis.ping } end end def test_sentinel_failover sentinels = [{:host => "127.0.0.1", :port => 26381}, {:host => "127.0.0.1", :port => 26382}] commands = { :s1 => [], :s2 => [], } s1 = { :sentinel => lambda do |command, *args| commands[:s1] << [command, *args] "$-1" # Nil end } s2 = { :sentinel => lambda do |command, *args| commands[:s2] << [command, *args] ["127.0.0.1", "6381"] end } RedisMock.start(s1) do |s1_port| RedisMock.start(s2) do |s2_port| sentinels[0][:port] = s1_port sentinels[1][:port] = s2_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master) assert redis.ping end end assert_equal commands[:s1], [%w[get-master-addr-by-name master1]] assert_equal commands[:s2], [%w[get-master-addr-by-name master1]] end def test_sentinel_failover_prioritize_healthy_sentinel sentinels = [{:host => "127.0.0.1", :port => 26381}, {:host => "127.0.0.1", :port => 26382}] commands = { :s1 => [], :s2 => [], } s1 = { :sentinel => lambda do |command, *args| commands[:s1] << [command, *args] "$-1" # Nil end } s2 = { :sentinel => lambda do |command, *args| commands[:s2] << [command, *args] ["127.0.0.1", "6381"] end } RedisMock.start(s1) do |s1_port| RedisMock.start(s2) do |s2_port| sentinels[0][:port] = s1_port sentinels[1][:port] = s2_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master) assert redis.ping redis.quit assert redis.ping end end assert_equal commands[:s1], [%w[get-master-addr-by-name master1]] assert_equal commands[:s2], [%w[get-master-addr-by-name master1], %w[get-master-addr-by-name master1]] end def test_sentinel_with_non_sentinel_options commands = { s1: [], m1: [] } sentinel = lambda do |port| { auth: lambda do |pass| commands[:s1] << ['auth', pass] '+OK' end, select: lambda do |db| commands[:s1] << ['select', db] "-ERR unknown command 'select'" end, sentinel: lambda do |command, *args| commands[:s1] << [command, *args] ['127.0.0.1', port.to_s] end } end master = { auth: lambda do |pass| commands[:m1] << ['auth', pass] '+OK' end, role: lambda do commands[:m1] << ['role'] ['master'] end } RedisMock.start(master) do |master_port| RedisMock.start(sentinel.call(master_port)) do |sen_port| s = [{ host: '127.0.0.1', port: sen_port }] redis = Redis.new(url: 'redis://:foo@master1/15', sentinels: s, role: :master) assert redis.ping end end assert_equal [%w[get-master-addr-by-name master1]], commands[:s1] assert_equal [%w[auth foo], %w[role]], commands[:m1] end def test_authentication_for_sentinel commands = { s1: [], m1: [] } sentinel = lambda do |port| { auth: lambda do |pass| commands[:s1] << ['auth', pass] '+OK' end, select: lambda do |db| commands[:s1] << ['select', db] '-ERR unknown command `select`' end, sentinel: lambda do |command, *args| commands[:s1] << [command, *args] ['127.0.0.1', port.to_s] end } end master = { auth: lambda do |pass| commands[:m1] << ['auth', pass] '-ERR Client sent AUTH, but no password is set' end, role: lambda do commands[:m1] << ['role'] ['master'] end } RedisMock.start(master) do |master_port| RedisMock.start(sentinel.call(master_port)) do |sen_port| s = [{ host: '127.0.0.1', port: sen_port, password: 'foo' }] r = Redis.new(host: 'master1', sentinels: s, role: :master) assert r.ping end end assert_equal [%w[auth foo], %w[get-master-addr-by-name master1]], commands[:s1] assert_equal [%w[role]], commands[:m1] end def test_authentication_for_sentinel_and_redis commands = { s1: [], m1: [] } sentinel = lambda do |port| { auth: lambda do |pass| commands[:s1] << ['auth', pass] '+OK' end, select: lambda do |db| commands[:s1] << ['select', db] '-ERR unknown command `select`' end, sentinel: lambda do |command, *args| commands[:s1] << [command, *args] ['127.0.0.1', port.to_s] end } end master = { auth: lambda do |pass| commands[:m1] << ['auth', pass] '+OK' end, role: lambda do commands[:m1] << ['role'] ['master'] end } RedisMock.start(master) do |master_port| RedisMock.start(sentinel.call(master_port)) do |sen_port| s = [{ host: '127.0.0.1', port: sen_port, password: 'foo' }] r = Redis.new(host: 'master1', sentinels: s, role: :master, password: 'bar') assert r.ping end end assert_equal [%w[auth foo], %w[get-master-addr-by-name master1]], commands[:s1] assert_equal [%w[auth bar], %w[role]], commands[:m1] end def test_sentinel_role_mismatch sentinels = [{:host => "127.0.0.1", :port => 26381}] sentinel = lambda do |port| { :sentinel => lambda do |command, *args| ["127.0.0.1", port.to_s] end } end master = { :role => lambda do ["slave"] end } ex = assert_raises(Redis::ConnectionError) do RedisMock.start(master) do |master_port| RedisMock.start(sentinel.call(master_port)) do |sen_port| sentinels[0][:port] = sen_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master) assert redis.ping end end end assert_match(/Instance role mismatch/, ex.message) end def test_sentinel_retries sentinels = [{:host => "127.0.0.1", :port => 26381}, {:host => "127.0.0.1", :port => 26382}] connections = [] handler = lambda do |id, port| { :sentinel => lambda do |command, *args| connections << id if connections.count(id) < 2 :close else ["127.0.0.1", port.to_s] end end } end master = { :role => lambda do ["master"] end } RedisMock.start(master) do |master_port| RedisMock.start(handler.call(:s1, master_port)) do |s1_port| RedisMock.start(handler.call(:s2, master_port)) do |s2_port| sentinels[0][:port] = s1_port sentinels[1][:port] = s2_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 1) assert redis.ping end end end assert_equal [:s1, :s2, :s1], connections connections.clear ex = assert_raises(Redis::CannotConnectError) do RedisMock.start(master) do |master_port| RedisMock.start(handler.call(:s1, master_port)) do |s1_port| RedisMock.start(handler.call(:s2, master_port)) do |s2_port| sentinels[0][:port] = s1_port + 1 sentinels[1][:port] = s2_port + 2 redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 0) assert redis.ping end end end end assert_match(/No sentinels available/, ex.message) end end redis-rb-4.1.2/test/sorting_test.rb000066400000000000000000000027521347371274700173020ustar00rootroot00000000000000require_relative "helper" class TestSorting < Minitest::Test include Helper::Client def test_sort r.set("foo:1", "s1") r.set("foo:2", "s2") r.rpush("bar", "1") r.rpush("bar", "2") assert_equal ["s1"], r.sort("bar", :get => "foo:*", :limit => [0, 1]) assert_equal ["s2"], r.sort("bar", :get => "foo:*", :limit => [0, 1], :order => "desc alpha") end def test_sort_with_an_array_of_gets r.set("foo:1:a", "s1a") r.set("foo:1:b", "s1b") r.set("foo:2:a", "s2a") r.set("foo:2:b", "s2b") r.rpush("bar", "1") r.rpush("bar", "2") assert_equal [["s1a", "s1b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1]) assert_equal [["s2a", "s2b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1], :order => "desc alpha") assert_equal [["s1a", "s1b"], ["s2a", "s2b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"]) end def test_sort_with_store r.set("foo:1", "s1") r.set("foo:2", "s2") r.rpush("bar", "1") r.rpush("bar", "2") r.sort("bar", :get => "foo:*", :store => "baz") assert_equal ["s1", "s2"], r.lrange("baz", 0, -1) end def test_sort_with_an_array_of_gets_and_with_store r.set("foo:1:a", "s1a") r.set("foo:1:b", "s1b") r.set("foo:2:a", "s2a") r.set("foo:2:b", "s2b") r.rpush("bar", "1") r.rpush("bar", "2") r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :store => 'baz') assert_equal ["s1a", "s1b", "s2a", "s2b"], r.lrange("baz", 0, -1) end end redis-rb-4.1.2/test/ssl_test.rb000066400000000000000000000040161347371274700164110ustar00rootroot00000000000000require_relative "helper" class SslTest < Minitest::Test include Helper::Client driver(:ruby) do def test_connection_to_non_ssl_server assert_raises(Redis::CannotConnectError) do redis = Redis.new(OPTIONS.merge(ssl: true)) redis.ping end end def test_verified_ssl_connection RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("trusted")) do |port| redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file }) assert_equal redis.ping, "PONG" end end def test_unverified_ssl_connection assert_raises(OpenSSL::SSL::SSLError) do RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("untrusted")) do |port| redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file }) redis.ping end end end def test_ssl_blocking RedisMock.start({}, ssl_server_opts("trusted")) do |port| redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file }) assert_equal redis.set("boom", "a" * 10_000_000), "OK" end end end driver(:hiredis, :synchrony) do def test_ssl_not_implemented_exception assert_raises(NotImplementedError) do RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("trusted")) do |port| redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file }) redis.ping end end end end private def ssl_server_opts(prefix) ssl_cert = File.join(cert_path, "#{prefix}-cert.crt") ssl_key = File.join(cert_path, "#{prefix}-cert.key") { :ssl => true, :ssl_params => { :cert => OpenSSL::X509::Certificate.new(File.read(ssl_cert)), :key => OpenSSL::PKey::RSA.new(File.read(ssl_key)) } } end def ssl_ca_file File.join(cert_path, "trusted-ca.crt") end def cert_path File.expand_path("../support/ssl/", __FILE__) end end redis-rb-4.1.2/test/support/000077500000000000000000000000001347371274700157375ustar00rootroot00000000000000redis-rb-4.1.2/test/support/cluster/000077500000000000000000000000001347371274700174205ustar00rootroot00000000000000redis-rb-4.1.2/test/support/cluster/orchestrator.rb000066400000000000000000000167171347371274700225000ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../../../lib/redis' class ClusterOrchestrator SLOT_SIZE = 16384 def initialize(node_addrs, timeout: 30.0) raise 'Redis Cluster requires at least 3 master nodes.' if node_addrs.size < 3 @clients = node_addrs.map do |addr| Redis.new(url: addr, timeout: timeout, reconnect_attempts: 10, reconnect_delay: 1.5, reconnect_delay_max: 10.0) end @timeout = timeout end def rebuild flush_all_data(@clients) reset_cluster(@clients) assign_slots(@clients) save_config_epoch(@clients) meet_each_other(@clients) wait_meeting(@clients) replicate(@clients) save_config(@clients) wait_cluster_building(@clients) wait_replication(@clients) wait_cluster_recovering(@clients) end def down flush_all_data(@clients) reset_cluster(@clients) end def failover master, slave = take_replication_pairs(@clients) wait_replication_delay(@clients, @timeout) slave.cluster(:failover, :takeover) wait_failover(to_node_key(master), to_node_key(slave), @clients) wait_replication_delay(@clients, @timeout) wait_cluster_recovering(@clients) end def start_resharding(slot, src_node_key, dest_node_key, slice_size: 10) node_map = hashify_node_map(@clients.first) src_node_id = node_map.fetch(src_node_key) src_client = find_client(@clients, src_node_key) dest_node_id = node_map.fetch(dest_node_key) dest_client = find_client(@clients, dest_node_key) dest_host, dest_port = dest_node_key.split(':') dest_client.cluster(:setslot, slot, 'IMPORTING', src_node_id) src_client.cluster(:setslot, slot, 'MIGRATING', dest_node_id) keys_count = src_client.cluster(:countkeysinslot, slot) loop do break if keys_count <= 0 keys = src_client.cluster(:getkeysinslot, slot, slice_size) break if keys.empty? keys.each do |k| begin src_client.migrate(k, host: dest_host, port: dest_port) rescue Redis::CommandError => err raise unless err.message.start_with?('IOERR') src_client.migrate(k, host: dest_host, port: dest_port, replace: true) # retry once ensure keys_count -= 1 end end end end def finish_resharding(slot, dest_node_key) node_map = hashify_node_map(@clients.first) @clients.first.cluster(:setslot, slot, 'NODE', node_map.fetch(dest_node_key)) end def close @clients.each(&:quit) end private def flush_all_data(clients) clients.each do |c| begin c.flushall rescue Redis::CommandError # READONLY You can't write against a read only slave. nil end end end def reset_cluster(clients) clients.each { |c| c.cluster(:reset) } end def assign_slots(clients) masters = take_masters(clients) slot_slice = SLOT_SIZE / masters.size mod = SLOT_SIZE % masters.size slot_sizes = Array.new(masters.size, slot_slice) mod.downto(1) { |i| slot_sizes[i] += 1 } slot_idx = 0 masters.zip(slot_sizes).each do |c, s| slot_range = slot_idx..slot_idx + s - 1 c.cluster(:addslots, *slot_range.to_a) slot_idx += s end end def save_config_epoch(clients) clients.each_with_index do |c, i| begin c.cluster('set-config-epoch', i + 1) rescue Redis::CommandError # ERR Node config epoch is already non-zero nil end end end def meet_each_other(clients) first_cliient = clients.first target_info = first_cliient.connection target_host = target_info.fetch(:host) target_port = target_info.fetch(:port) clients.each do |client| next if first_cliient.id == client.id client.cluster(:meet, target_host, target_port) end end def wait_meeting(clients, max_attempts: 600) size = clients.size.to_s wait_for_state(clients, max_attempts) do |client| info = hashify_cluster_info(client) info['cluster_known_nodes'] == size end end def replicate(clients) node_map = hashify_node_map(clients.first) masters = take_masters(clients) take_slaves(clients).each_with_index do |slave, i| master_info = masters[i].connection master_host = master_info.fetch(:host) master_port = master_info.fetch(:port) loop do begin master_node_id = node_map.fetch("#{master_host}:#{master_port}") slave.cluster(:replicate, master_node_id) rescue Redis::CommandError # ERR Unknown node [key] sleep 0.1 node_map = hashify_node_map(clients.first) next end break end end end def save_config(clients) clients.each { |c| c.cluster(:saveconfig) } end def wait_cluster_building(clients, max_attempts: 600) wait_for_state(clients, max_attempts) do |client| info = hashify_cluster_info(client) info['cluster_state'] == 'ok' end end def wait_replication(clients, max_attempts: 600) wait_for_state(clients, max_attempts) do |client| flags = hashify_cluster_node_flags(client) flags.values.select { |f| f == 'slave' }.size == 3 end end def wait_failover(master_key, slave_key, clients, max_attempts: 600) wait_for_state(clients, max_attempts) do |client| flags = hashify_cluster_node_flags(client) flags[master_key] == 'slave' && flags[slave_key] == 'master' end end def wait_replication_delay(clients, timeout_sec) timeout_msec = timeout_sec.to_i * 1000 wait_for_state(clients, clients.size + 1) do |client| client.wait(1, timeout_msec) if client.role.first == 'master' true end end def wait_cluster_recovering(clients, max_attempts: 600) key = 0 wait_for_state(clients, max_attempts) do |client| begin client.get(key) if client.role.first == 'master' true rescue Redis::CommandError => err if err.message.start_with?('CLUSTERDOWN') false elsif err.message.start_with?('MOVED') key += 1 false else true end end end end def wait_for_state(clients, max_attempts) attempt_count = 1 clients.each do |client| attempt_count.step(max_attempts) do |i| break if i >= max_attempts attempt_count += 1 break if yield(client) sleep 0.1 end end end def hashify_cluster_info(client) client.cluster(:info).split("\r\n").map { |str| str.split(':') }.to_h end def hashify_cluster_node_flags(client) client.cluster(:nodes) .split("\n") .map { |str| str.split(' ') } .map { |arr| [arr[1].split('@').first, (arr[2].split(',') & %w[master slave]).first] } .to_h end def hashify_node_map(client) client.cluster(:nodes) .split("\n") .map { |str| str.split(' ') } .map { |arr| [arr[1].split('@').first, arr[0]] } .to_h end def take_masters(clients) size = clients.size / 2 return clients if size < 3 clients.take(size) end def take_slaves(clients) size = clients.size / 2 return [] if size < 3 clients[size..size * 2] end def take_replication_pairs(clients) [take_masters(clients).last, take_slaves(clients).last] end def find_client(clients, node_key) clients.find { |cli| node_key == to_node_key(cli) } end def to_node_key(client) con = client.connection "#{con.fetch(:host)}:#{con.fetch(:port)}" end end redis-rb-4.1.2/test/support/connection/000077500000000000000000000000001347371274700200765ustar00rootroot00000000000000redis-rb-4.1.2/test/support/connection/hiredis.rb000066400000000000000000000000421347371274700220460ustar00rootroot00000000000000require_relative "../wire/thread" redis-rb-4.1.2/test/support/connection/ruby.rb000066400000000000000000000000421347371274700214000ustar00rootroot00000000000000require_relative "../wire/thread" redis-rb-4.1.2/test/support/connection/synchrony.rb000066400000000000000000000003031347371274700224530ustar00rootroot00000000000000require_relative "../wire/synchrony" module Helper def around rv = nil EM.synchrony do begin rv = yield ensure EM.stop end end rv end end redis-rb-4.1.2/test/support/redis_mock.rb000066400000000000000000000062041347371274700204050ustar00rootroot00000000000000require "socket" module RedisMock class Server def initialize(options = {}, &block) tcp_server = TCPServer.new(options[:host] || "127.0.0.1", 0) tcp_server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) if options[:ssl] ctx = OpenSSL::SSL::SSLContext.new ssl_params = options.fetch(:ssl_params, {}) ctx.set_params(ssl_params) unless ssl_params.empty? @server = OpenSSL::SSL::SSLServer.new(tcp_server, ctx) else @server = tcp_server end end def port @server.addr[1] end def start(&block) @thread = Thread.new { run(&block) } end def shutdown @thread.kill end def run begin loop do session = @server.accept begin return if yield(session) == :exit ensure session.close end end rescue => ex $stderr.puts "Error running mock server: #{ex.class}: #{ex.message}" $stderr.puts ex.backtrace retry ensure @server.close end end end # Starts a mock Redis server in a thread. # # The server will use the lambda handler passed as argument to handle # connections. For example: # # handler = lambda { |session| session.close } # RedisMock.start_with_handler(handler) do # # Every connection will be closed immediately # end # def self.start_with_handler(blk, options = {}) server = Server.new(options) port = server.port begin server.start(&blk) yield(port) ensure server.shutdown end end # Starts a mock Redis server in a thread. # # The server will reply with a `+OK` to all commands, but you can # customize it by providing a hash. For example: # # RedisMock.start(:ping => lambda { "+PONG" }) do |port| # assert_equal "PONG", Redis.new(:port => port).ping # end # def self.start(commands, options = {}, &blk) handler = lambda do |session| while line = session.gets argv = Array.new(line[1..-3].to_i) do bytes = session.gets[1..-3].to_i arg = session.read(bytes) session.read(2) # Discard \r\n arg end command = argv.shift blk = commands[command.to_sym] blk ||= lambda { |*_| "+OK" } response = blk.call(*argv) # Convert a nil response to :close response ||= :close if response == :exit break :exit elsif response == :close break :close elsif response.is_a?(Array) session.write("*%d\r\n" % response.size) response.each do |resp| if resp.is_a?(Array) session.write("*%d\r\n" % resp.size) resp.each do |r| session.write("$%d\r\n%s\r\n" % [r.length, r]) end else session.write("$%d\r\n%s\r\n" % [resp.length, resp]) end end else session.write(response) session.write("\r\n") unless response.end_with?("\r\n") end end end start_with_handler(handler, options, &blk) end end redis-rb-4.1.2/test/support/ssl/000077500000000000000000000000001347371274700165405ustar00rootroot00000000000000redis-rb-4.1.2/test/support/ssl/gen_certs.sh000077500000000000000000000017711347371274700210560ustar00rootroot00000000000000#!/bin/sh get_subject() { if [ "$1" = "trusted" ] then echo "/C=IT/ST=Sicily/L=Catania/O=Redis/OU=Security/CN=127.0.0.1" else echo "/C=XX/ST=Untrusted/L=Evilville/O=Evil Hacker/OU=Attack Department/CN=127.0.0.1" fi } # Generate two CAs: one to be considered trusted, and one that's untrusted for type in trusted untrusted; do rm -rf ./demoCA mkdir -p ./demoCA mkdir -p ./demoCA/certs mkdir -p ./demoCA/crl mkdir -p ./demoCA/newcerts mkdir -p ./demoCA/private touch ./demoCA/index.txt openssl genrsa -out ${type}-ca.key 2048 openssl req -new -x509 -days 12500 -key ${type}-ca.key -out ${type}-ca.crt -subj "$(get_subject $type)" openssl x509 -in ${type}-ca.crt -noout -next_serial -out ./demoCA/serial openssl req -newkey rsa:2048 -keyout ${type}-cert.key -nodes -out ${type}-cert.req -subj "$(get_subject $type)" openssl ca -days 12500 -cert ${type}-ca.crt -keyfile ${type}-ca.key -out ${type}-cert.crt -infiles ${type}-cert.req rm ${type}-cert.req done rm -rf ./demoCA redis-rb-4.1.2/test/support/ssl/trusted-ca.crt000066400000000000000000000027251347371274700213330ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEIDCCAwigAwIBAgIJAM7kyjC89Qj/MA0GCSqGSIb3DQEBCwUAMGcxCzAJBgNV BAYTAklUMQ8wDQYDVQQIEwZTaWNpbHkxEDAOBgNVBAcTB0NhdGFuaWExDjAMBgNV BAoTBVJlZGlzMREwDwYDVQQLEwhTZWN1cml0eTESMBAGA1UEAxMJMTI3LjAuMC4x MCAXDTE2MDQwMjAzMzQ0MVoYDzIwNTAwNjIzMDMzNDQxWjBnMQswCQYDVQQGEwJJ VDEPMA0GA1UECBMGU2ljaWx5MRAwDgYDVQQHEwdDYXRhbmlhMQ4wDAYDVQQKEwVS ZWRpczERMA8GA1UECxMIU2VjdXJpdHkxEjAQBgNVBAMTCTEyNy4wLjAuMTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMeibFqEG38mtN9DSXy6NZdd7AjH 4/D+VdDzlbJlI5IBACCV9p6P2j5PFlFvkHFE6vr6biMaLXNAmUHYfDzeT95LODHH t+8HlR51cNYrnt9B3eiVwEnJ7+axuDHg6nUgLXeKeog+vEqreZwLnFibxt2qpFze xzyKJ37Pm+iAey5glCc/v7ECYQ4sWVVV+ciC+sAwmZDfZXCBQtRRokJ6ikqQDwWV DugGcV46feTpu79OmkLLM8PI3E7ow2F/3iv67gmdlO5m9wX1ahWzJKUapBTxgf4X QG0s60WbC9iJIvgXRGW7wWSsqSVJkfLYllDTPgfpLyl1+FR3A4awrsPiMVUCAwEA AaOBzDCByTAdBgNVHQ4EFgQU+YG9kJR3Vy31d7QVyxRAYyKTK18wgZkGA1UdIwSB kTCBjoAU+YG9kJR3Vy31d7QVyxRAYyKTK1+ha6RpMGcxCzAJBgNVBAYTAklUMQ8w DQYDVQQIEwZTaWNpbHkxEDAOBgNVBAcTB0NhdGFuaWExDjAMBgNVBAoTBVJlZGlz MREwDwYDVQQLEwhTZWN1cml0eTESMBAGA1UEAxMJMTI3LjAuMC4xggkAzuTKMLz1 CP8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAeFKB7DUixmxbdvNw n/mNoHK+OOZXmfxZDCo0v2gcQ4WXUiCqL6MagrImCvkEz5RL6Fk2ZflEV2iGQ5Ds CmF2n47ISpqG29bfI5R1rcbfqK/5tazUIhQu12ThNmkEh7hCuW/0LqJrnmxpuRLy le9e3svCC96lwjFczzU/utWurKt7S7Di3C4P+AXAJJuszDMLMCBLaB/3j24cNpOx zzeZo02x4rpsD2+MMfRDWMWezVEyk63KnI0kt3JGnepsKCFc48ZOk09LwFk3Rfaq zuKSgEJJw1mfsdBfysM0HQw20yyjSdoTEfQq3bXctTNi+pEOgW6x7TMsnngYYLXV 9XTrpg== -----END CERTIFICATE----- redis-rb-4.1.2/test/support/ssl/trusted-ca.key000066400000000000000000000032171347371274700213300ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAx6JsWoQbfya030NJfLo1l13sCMfj8P5V0POVsmUjkgEAIJX2 no/aPk8WUW+QcUTq+vpuIxotc0CZQdh8PN5P3ks4Mce37weVHnVw1iue30Hd6JXA Scnv5rG4MeDqdSAtd4p6iD68Sqt5nAucWJvG3aqkXN7HPIonfs+b6IB7LmCUJz+/ sQJhDixZVVX5yIL6wDCZkN9lcIFC1FGiQnqKSpAPBZUO6AZxXjp95Om7v06aQssz w8jcTujDYX/eK/ruCZ2U7mb3BfVqFbMkpRqkFPGB/hdAbSzrRZsL2Iki+BdEZbvB ZKypJUmR8tiWUNM+B+kvKXX4VHcDhrCuw+IxVQIDAQABAoIBAQCzbGHiQJXOA+XQ O9OSjHGaJ8n6Yl2VvaE3eZXzjj8X/Fo271GGVVgbZE10x8aUZxKim+3dEqwCx+52 ZbHTqyMxcX2CEDRaWwBFLdxKQU467iIZ5m26ZAp/1v7rpXBT8KWsqQNT7L6ihdd4 zl6orOlhVPsAlSGQYcL5kHJZ1w/fL0phEbwdISd3PYhGHXMNmqfXorzJYHDQA4R+ yR7WpP1dmnUeEKrHc9FFcBZ75BGlWjdCPZMFKc7IndZumarhBpWH9yZMUxrUIo4V SCweRUFdD5H1lMZ0YiIAE25wKNEQ2iGd3Jfr8Vj1KFSHC9I2FJA3aFRRUgTwxx/W h0mJy1ZJAoGBAPYsSSlwQdxZjnyZiVkNSD4MoLdof//nRxeKGejq6AiXDvcsLyJy 0MKk4YBFw2249TWm/KBbMAFiBE7d8uPtP5pPfjNVPX6VltH3AhSZ7Ugbpo6C3NFA GpzFVtNaWgCVDloDVdmsY7ssDFuAIih0paklPAqnLY+Ua9m1BiEPrB+bAoGBAM+a i+0NMR4AyKpuo1exdd+7BIHw5HNPwGmR1ggdGWduH0zsOhEawQKKFv1X9xKAcXxW PyeD56/Tmn7fkWvuE8dOu9E6em0vgmxhYyn4nyLAFYF5uKXYo78MpIEThdpl1ldT iHwG/25vunaBUHhwbHPUD+F989tmRuCjoFkuA5nPAoGAaqPIlcDhZvkMtoE0dHVC hE6oGIuWV17y9wmGK9YG6iG2A/EKAhxGvur6HL0b6Z4j6zgJW9Xkt9SkFR4kqAQQ d2JUQxx75SgcC5y7M/1yQrhnsHiT+7mPTbZW5HvRXUs0yl2DhSYeleiA+epJ4ciW Mu3EUsEVBYvAJLE8lHnbkF0CgYEAhyxpz3+3a4G3JsHDOWYjCfoLhVAEb9CNyC9c 3QuVbvMVDlEBvgFdivm+3lZYWYOoYP0HQgNw59svzUxks5Hg7vUk9abN8CnvEgKX PszTUR0g450NzW6xr8PbmO/NR9bnKRUK2Tb1OkMldePdMY6CDykU7g3EqiZ+H+Zq kaaUUaECgYEAmk5W+S94q5jLemnfAChC5lva/0/aHdhtaoH4Lo+j0haMsdiy8/ZE sh+3gQ8pqwaCAwnKxAcppt/FNZ7tHRsH3oyY6biypn3WppQj+BA41nuzbspOKJhR ZDXKFCItbzUjyi23Dx4P4DgMivkpV+e88RMIuBnv4yjl5iOLq+vf4Rg= -----END RSA PRIVATE KEY----- redis-rb-4.1.2/test/support/ssl/trusted-cert.crt000066400000000000000000000106151347371274700217020ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 14908262977180600576 (0xcee4ca30bcf50900) Signature Algorithm: sha1WithRSAEncryption Issuer: C=IT, ST=Sicily, L=Catania, O=Redis, OU=Security, CN=127.0.0.1 Validity Not Before: Apr 2 03:34:42 2016 GMT Not After : Jun 23 03:34:42 2050 GMT Subject: C=IT, ST=Sicily, O=Redis, OU=Security, CN=127.0.0.1 Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:ab:bf:ac:ef:dc:99:35:fa:07:3f:d5:33:86:f1: 7d:9e:57:8b:d5:c1:10:04:0c:35:95:7c:61:ff:05: a6:f9:ef:71:5c:c5:83:68:a2:ad:5d:0f:a5:2b:b4: 76:9f:36:8f:df:75:fb:d6:48:00:c0:f0:68:56:f6: 49:84:4d:4e:e1:ca:dd:24:9f:2f:5e:7c:35:26:57: d6:d5:95:d1:3f:40:32:22:43:2c:8c:b7:8c:89:56: 7c:d0:94:e5:f7:cf:4a:51:3f:60:b2:fe:1f:3b:38: d6:47:5d:2e:4f:38:75:d9:9b:c8:0f:d1:fd:91:5a: 07:c3:94:95:1f:7b:f1:ae:dc:a1:83:e2:6b:78:05: 34:b3:8b:87:86:31:9f:cc:8b:15:cd:18:2e:06:36: ca:f8:29:f8:6e:93:60:78:ec:8a:e8:a6:94:ad:24: a8:e3:d4:ac:42:da:52:0f:34:e8:d0:10:e5:53:db: f8:3a:56:48:10:33:df:80:70:1c:72:5e:1f:c3:11: bb:3b:b9:6b:0a:e0:82:eb:67:d4:8f:5c:30:d3:cf: 17:6d:86:01:0e:ae:43:c1:d8:c0:5e:99:ef:fa:60: 0a:f2:62:68:62:8b:05:f3:8b:b1:34:d8:70:78:35: 74:76:c2:46:13:a3:1f:5d:7b:3b:49:20:1e:98:54: 63:77 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: 81:DE:C0:39:F9:8A:57:50:DB:B1:6A:B3:D0:5F:E9:2C:87:5A:1E:3D X509v3 Authority Key Identifier: keyid:F9:81:BD:90:94:77:57:2D:F5:77:B4:15:CB:14:40:63:22:93:2B:5F Signature Algorithm: sha1WithRSAEncryption a3:0a:d7:22:5a:bc:cc:f6:ed:2f:f2:9f:dd:e0:46:02:73:14: dd:a7:f5:39:b9:16:19:16:36:b6:22:5c:66:14:c0:d3:ac:55: fc:52:2d:c3:b2:70:5f:cf:3d:23:71:78:e9:31:88:65:2c:2e: 4a:09:6e:4b:97:bb:4d:38:87:d8:25:ed:bb:ed:62:19:08:50: f2:40:cc:39:ee:f9:a8:3a:5d:2b:e7:34:eb:8a:74:c7:c9:bc: 88:9b:9b:ca:5b:11:20:ca:53:b2:0b:20:49:fc:b9:f7:ec:03: c9:5d:c1:24:75:27:f8:7c:70:dc:6a:2c:98:48:93:5f:7f:7e: 94:a1:cf:79:b3:24:e3:de:9e:f0:0f:d8:d6:3e:c9:52:30:31: 87:90:c2:d2:23:be:d8:7a:e9:e6:bb:4b:00:75:30:49:4b:98: d5:f6:7d:b5:83:b5:57:85:20:98:00:51:55:c3:a2:81:ec:6c: 11:91:33:60:14:7b:d2:01:ee:5b:bf:5b:68:f5:e0:4e:45:0a: 68:cd:33:4f:29:72:fa:fe:6a:19:b6:84:70:90:a4:d5:7a:04: 2e:da:5b:98:4f:e4:aa:a6:c4:68:aa:5c:8c:a5:5e:df:20:94: 22:f7:37:45:71:a4:bc:72:34:ee:42:cf:9d:0f:fb:4a:39:d1: 8e:41:f3:3f -----BEGIN CERTIFICATE----- MIIDvDCCAqSgAwIBAgIJAM7kyjC89QkAMA0GCSqGSIb3DQEBBQUAMGcxCzAJBgNV BAYTAklUMQ8wDQYDVQQIEwZTaWNpbHkxEDAOBgNVBAcTB0NhdGFuaWExDjAMBgNV BAoTBVJlZGlzMREwDwYDVQQLEwhTZWN1cml0eTESMBAGA1UEAxMJMTI3LjAuMC4x MCAXDTE2MDQwMjAzMzQ0MloYDzIwNTAwNjIzMDMzNDQyWjBVMQswCQYDVQQGEwJJ VDEPMA0GA1UECBMGU2ljaWx5MQ4wDAYDVQQKEwVSZWRpczERMA8GA1UECxMIU2Vj dXJpdHkxEjAQBgNVBAMTCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAKu/rO/cmTX6Bz/VM4bxfZ5Xi9XBEAQMNZV8Yf8FpvnvcVzFg2ii rV0PpSu0dp82j991+9ZIAMDwaFb2SYRNTuHK3SSfL158NSZX1tWV0T9AMiJDLIy3 jIlWfNCU5ffPSlE/YLL+Hzs41kddLk84ddmbyA/R/ZFaB8OUlR978a7coYPia3gF NLOLh4Yxn8yLFc0YLgY2yvgp+G6TYHjsiuimlK0kqOPUrELaUg806NAQ5VPb+DpW SBAz34BwHHJeH8MRuzu5awrggutn1I9cMNPPF22GAQ6uQ8HYwF6Z7/pgCvJiaGKL BfOLsTTYcHg1dHbCRhOjH117O0kgHphUY3cCAwEAAaN7MHkwCQYDVR0TBAIwADAs BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD VR0OBBYEFIHewDn5ildQ27Fqs9Bf6SyHWh49MB8GA1UdIwQYMBaAFPmBvZCUd1ct 9Xe0FcsUQGMikytfMA0GCSqGSIb3DQEBBQUAA4IBAQCjCtciWrzM9u0v8p/d4EYC cxTdp/U5uRYZFja2IlxmFMDTrFX8Ui3DsnBfzz0jcXjpMYhlLC5KCW5Ll7tNOIfY Je277WIZCFDyQMw57vmoOl0r5zTrinTHybyIm5vKWxEgylOyCyBJ/Ln37APJXcEk dSf4fHDcaiyYSJNff36Uoc95syTj3p7wD9jWPslSMDGHkMLSI77Yeunmu0sAdTBJ S5jV9n21g7VXhSCYAFFVw6KB7GwRkTNgFHvSAe5bv1to9eBORQpozTNPKXL6/moZ toRwkKTVegQu2luYT+SqpsRoqlyMpV7fIJQi9zdFcaS8cjTuQs+dD/tKOdGOQfM/ -----END CERTIFICATE----- redis-rb-4.1.2/test/support/ssl/trusted-cert.key000066400000000000000000000032501347371274700216770ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrv6zv3Jk1+gc/ 1TOG8X2eV4vVwRAEDDWVfGH/Bab573FcxYNooq1dD6UrtHafNo/fdfvWSADA8GhW 9kmETU7hyt0kny9efDUmV9bVldE/QDIiQyyMt4yJVnzQlOX3z0pRP2Cy/h87ONZH XS5POHXZm8gP0f2RWgfDlJUfe/Gu3KGD4mt4BTSzi4eGMZ/MixXNGC4GNsr4Kfhu k2B47IroppStJKjj1KxC2lIPNOjQEOVT2/g6VkgQM9+AcBxyXh/DEbs7uWsK4ILr Z9SPXDDTzxdthgEOrkPB2MBeme/6YAryYmhiiwXzi7E02HB4NXR2wkYTox9deztJ IB6YVGN3AgMBAAECggEASmOxIgtoiQqMzUcpFE/Q2x6MQL9okng/VUoUoALwudzO OyKJsm6TrHU0U2PM5VUap+1QcRWqzebTKqduXFGn0wCtHEmemMwvsTXmpYhIo57I mDKEP0bZJjtBwI5dtSIhzGMpHR4YpOwPU8W2YzXPRbvFwaRwsd5O8pWOqZ5jphrQ DtkLNz4hIFsMihPeYFpuAjsZ2cMIGPtlY2qbfjyno7hd7LxNzL/2vMlDw5MHHtw4 snxLN92KomC6rSUUydNDyemyMpg8iRwm7gmYzVoZf6aTbI2RdFcv2KZfpUWYdB+I yU8ZV1Sch7VQ+xLVy74SuY8AZ2Rq4S3M+EmEa5ghoQKBgQDfgOIyStulfYn6UC1A OYwcGoSOaVNfPE/m9BZN58xK0+XnEqQECMsyg/GYS65Zri4+KJYPxqv6f9ljLTGE 0PxiA7wq+QWnv4EM+3aGShxwyVlmgJZyyBfJtAMr1iDm4JsidTT5GMdfxRICPGZY WVggcz/cVu39OxRrumREuTWAzwKBgQDEuGheZv68cYt5EkzOWxeFQyt1bzXn1CJg IXnIFZIekJhVGpBG+zMAYri9+hSheiDrwfIcSfTq9LxF6JNUvaU4qMrkwvW21jKs n7ofcA+VYq2mggoIuuvKVqXemLHorC0U/zCMnM6rycaa9sB5tsF+Js93uvf1TEJt veV0yCeM2QKBgF1M0iAoe7SDyXuCyMEMxN5ee4NvmGwjIz/IGR+Aahm6hziE4Y8F lL2LsujefvPU8FzmWG5Rgy1Y/YiXLxrAmvrXkE9oEOJL4TVoK7w3Z9P1Waqedy+H M9bxnHlKNAXtMRWbU/fATko+XBwu1pJ/CXjSY5A5gbO6W/X0ozLFFf6lAoGABRZ7 5I0nY3pQUCZQBDpI5nJxSk1BCKjs5q2W97zPFalJt1HDj4JptEXZX1h7dh2xgkd2 2pJzGiyQPgKg5N0uy8NZ1AbS0hLCJsLOzodYb9Wohhjw537mIEqTaalrWIgzdkqP V+OqWLkUQOfG3J8EbB3W2dLlHNwHD82MhLO0iikCgYEAvdK5LmpUdZXMVtiOMZO5 t3M0vwi6vPhW7DV1QET51x/U+SyH4rvZGeqRl+gcKrZ8SreOlyICvsPgVmvLCrHE gJLPWJIzI8Mg6u91/KpiVmRahnJjOn3oHNuLSqFjn9lIhmA5dN7zQDXzPdYrWPNR u1QX+JLhlP33ejgdkdLsNiM= -----END PRIVATE KEY----- redis-rb-4.1.2/test/support/ssl/untrusted-ca.crt000066400000000000000000000030461347371274700216730ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEXDCCA0SgAwIBAgIJAIgFm03l5AJkMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV BAYTAlhYMRIwEAYDVQQIEwlVbnRydXN0ZWQxEjAQBgNVBAcTCUV2aWx2aWxsZTEU MBIGA1UEChMLRXZpbCBIYWNrZXIxGjAYBgNVBAsTEUF0dGFjayBEZXBhcnRtZW50 MRIwEAYDVQQDEwkxMjcuMC4wLjEwIBcNMTYwNDAyMDMzNDUxWhgPMjA1MDA2MjMw MzM0NTFaMHsxCzAJBgNVBAYTAlhYMRIwEAYDVQQIEwlVbnRydXN0ZWQxEjAQBgNV BAcTCUV2aWx2aWxsZTEUMBIGA1UEChMLRXZpbCBIYWNrZXIxGjAYBgNVBAsTEUF0 dGFjayBEZXBhcnRtZW50MRIwEAYDVQQDEwkxMjcuMC4wLjEwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCtXxcEGUrGqAqlBK94B8IwSkB8OnLYC/c/6Tde WG46pzmZWZvffi0akcKKubRNcfoavOuqLuNnpQDkIlnJ37K/LZk8Q5+aMoUGBiQ2 jSN1707sFqH3eTFvXOUzlDEcsBa7Y7RuaI8SXg1UGsnnCcj6H3BW2xKcXPN6/s30 vhNw2CPqtXm4NOD3Zb5FkB9epAEejRg0OPn5DJ3mESVp/H2EqkptMZ+6cOk2/CMc e8AAfcxBGwKuOMXNODszTNxN+OuGCHOxx8+vR/eV35tonISwbkmO9WI6DC+pWT2s PvDhuQtqsrVofCP/pireb5Ce/7bP/FsZcNSMMfV5dponcYrrAgMBAAGjgeAwgd0w HQYDVR0OBBYEFLeDNvKpJKmuyPsamax2AZTijdkwMIGtBgNVHSMEgaUwgaKAFLeD NvKpJKmuyPsamax2AZTijdkwoX+kfTB7MQswCQYDVQQGEwJYWDESMBAGA1UECBMJ VW50cnVzdGVkMRIwEAYDVQQHEwlFdmlsdmlsbGUxFDASBgNVBAoTC0V2aWwgSGFj a2VyMRowGAYDVQQLExFBdHRhY2sgRGVwYXJ0bWVudDESMBAGA1UEAxMJMTI3LjAu MC4xggkAiAWbTeXkAmQwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA FWYTrxi/h7PYIpp09QsbDiGdC7gmp04HTx82NvBaUFaLk8ygz4DUz5u7QyTDdAga yWviHghuyZ6vv5Ubaj7XLOzLM6rYsQjkVq5ltwP+9V/U/b5jOHvZdYqdatVXUXxR SO+e3QYiMpM4Vs/NNXhpUp6apD7VcoB2LgK3vGDJ526PBJjgw24311t8O7kDTwkt AwX56/KTolMI+k9rT8Ee6aucT6gBNf0judhNkPVo+6CYgjmEVRrN/xaFCUNSpv5E O6uIcxSSX6a5iOZ/EH+GyHb6kDmztn/Hes+UN9+gMuAK7+LgsD2mYbxn9Pnaerrs 2nER8XurylLxi0GLvNWNdQ== -----END CERTIFICATE----- redis-rb-4.1.2/test/support/ssl/untrusted-ca.key000066400000000000000000000032171347371274700216730ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEArV8XBBlKxqgKpQSveAfCMEpAfDpy2Av3P+k3XlhuOqc5mVmb 334tGpHCirm0TXH6Grzrqi7jZ6UA5CJZyd+yvy2ZPEOfmjKFBgYkNo0jde9O7Bah 93kxb1zlM5QxHLAWu2O0bmiPEl4NVBrJ5wnI+h9wVtsSnFzzev7N9L4TcNgj6rV5 uDTg92W+RZAfXqQBHo0YNDj5+Qyd5hElafx9hKpKbTGfunDpNvwjHHvAAH3MQRsC rjjFzTg7M0zcTfjrhghzscfPr0f3ld+baJyEsG5JjvViOgwvqVk9rD7w4bkLarK1 aHwj/6Yq3m+Qnv+2z/xbGXDUjDH1eXaaJ3GK6wIDAQABAoIBAQCeC0QxAVlwNTnW 6qmGsxPr75RPavzMREQ1p8VIpTZ/E3hneg+lMiGtydhdnCJoQxGrFDOFJU86aWmh jkrpw5nvu4KoNEEnUQyAzFJwxELiPLBmec9WiM1u5nEujtYif8eJNcACsiBSrxhZ Zj5N9laW5NgE5ZpWnkl7AxL/G9MfFvifr9KtyDcs+wnYD6ffz/bRwS54veMccj/q SkVQRL7FM4NJczG0TTp+LT/1R3s8YVv9GHnJ6K7Gol3E0PbFS1HztDuMVonhWiac 9Rjt7w0rNgeH6ZbCMXrUd+8I8amazA78p1ky0Mh8d6UUVFU1jjtyxlgDh06IPsnE +exeAClxAoGBAOMZ7LEFr3VcFwym7RvgckeQhd6Rmz8Bh7kGfB9pDsHFprGJ0rm4 XgNETJXOky5wUCPZmMBN1iAU/ehyyXqPykXiKjAQLxQNHR9/Z6P58PsHs2Uw8VZa XdZwlBME5+/yl5DiirO5rCt804DdCQgSu7denudwWbbtzAsodSKj5zEJAoGBAMNu 21hZnsvhtZlvQVHZ4sQttrv9e5VpWWHkDPRN3sdLZPfK/+3L0BmUrGotgNWpTZwR 8YvKRT2Xn1unzpKlkHtIVuHU7khOj+tYHLIx3rezVanw9QzbIANMel6STlUr3jwX fjnibgkJixxHTOBs8/zm219Q1sNTos9GUOAZQb1TAoGALwGFsVpo59TI3JCMkXGS led/HgNra84oRo7mECZRrJ/5kdPiLxjPNMPlSji41CrhG5qFeIBj6r4NlBh2RY0P pAldDBe9dtwEBCn9zL4GOB9u7WoE+ge4VpN0wr8INu0ynAWYCf1LerDaolid7vLZ sem+4E6r8yYjTsfv/tyIFOkCgYEAlCZobxxZLbNn5+2X9cWXiyIgYXgyBDy9fmDT lSum0yuLWfDwfELB+XJkFYVzIgVbCRHtKwxl2uAi9OdLyI1r7pkTC9VP4U50+XJt JoR5koaHTPGVwm4mYXnLVf/RE+3SZXllvdmxknZCl2hRldviRfh3mlT8yUuQo1Jp oshitnMCgYAXTQLA7B5YLmhCG8HRM/h/xM55ObdDX1SIWUbk3p1uxak1W0abfvpi FPBy2riOdSDA6Uv7V8y1j4tENGVMyyMsEpFdLDX4Lkbh9niOoPeHCWdO0boPk0Fw aPXtT7gdTPWJulKOxtLuGqBjZZ77TO49uqWlEMaerulWyjhRm8zzvA== -----END RSA PRIVATE KEY----- redis-rb-4.1.2/test/support/ssl/untrusted-cert.crt000066400000000000000000000107471347371274700222530ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 9801410922913464933 (0x88059b4de5e40265) Signature Algorithm: sha1WithRSAEncryption Issuer: C=XX, ST=Untrusted, L=Evilville, O=Evil Hacker, OU=Attack Department, CN=127.0.0.1 Validity Not Before: Apr 2 03:34:51 2016 GMT Not After : Jun 23 03:34:51 2050 GMT Subject: C=XX, ST=Untrusted, O=Evil Hacker, OU=Attack Department, CN=127.0.0.1 Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:9a:73:e7:45:fc:d3:b5:4a:bd:bd:ad:30:e5:24: 74:38:01:89:8f:a9:90:bf:3c:4a:bf:d1:f1:5e:db: c8:aa:26:59:e6:ec:b3:a0:0f:4d:74:59:dd:c9:27: 2f:e1:48:7d:30:d9:59:06:2f:29:f0:d1:25:33:79: 5f:58:9d:d7:54:c8:a7:aa:1a:84:00:a2:85:63:32: cc:ef:73:7d:b0:26:c6:95:f1:86:16:68:38:63:57: 09:0d:6f:6a:70:e8:75:3b:72:b4:b1:4d:01:0e:01: 0e:bf:bf:6a:8c:88:fe:0d:cb:88:43:1b:da:ed:0c: 88:25:33:f7:b9:b1:fc:32:b8:94:c9:20:7c:ac:49: e4:c1:58:93:69:0e:41:e3:df:96:e3:47:11:14:8c: e4:4b:b6:56:df:6f:5e:d2:48:dc:a1:8a:98:cc:4b: 02:89:95:ea:f6:de:a5:3a:9c:06:7c:f0:7c:09:6f: 27:11:f2:b1:1b:47:6b:a3:ea:d6:ee:a1:65:91:84: cf:2e:81:d3:55:4a:e8:01:4e:72:41:ac:92:e0:7d: 7c:fe:85:f0:2e:f1:ee:4a:80:f9:4e:5a:b4:95:6c: bb:fe:ff:46:58:4a:7b:fc:a0:63:59:5d:01:5b:63: 06:5c:94:83:30:27:81:f0:1a:13:89:5a:5a:a2:e2: 0f:eb Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: 1B:71:91:99:43:12:0F:D3:59:FC:00:EF:99:F3:42:CF:41:FD:40:1D X509v3 Authority Key Identifier: keyid:B7:83:36:F2:A9:24:A9:AE:C8:FB:1A:99:AC:76:01:94:E2:8D:D9:30 Signature Algorithm: sha1WithRSAEncryption a4:cd:88:c3:19:b7:cd:7e:7a:e7:85:1f:fb:3e:31:0b:ff:9d: 6f:b1:a2:72:56:4a:b1:ec:6c:f3:99:bd:65:08:0a:e9:47:1d: 79:55:5b:29:b1:d4:85:69:85:65:3f:30:37:a1:0e:76:d2:1f: b0:76:2a:23:75:c9:05:a4:89:cf:c1:68:42:16:46:d6:c9:a8: e5:06:5b:52:45:d4:41:5d:f3:c7:00:d1:ca:cc:3e:4c:63:e6: 7a:fe:ce:20:a4:df:e3:7c:e3:75:6e:f7:18:84:1c:9b:56:ce: 55:fb:04:b9:de:11:6e:7d:5d:47:de:a9:ed:3e:79:48:a5:4f: 32:d5:96:8d:ea:e2:a6:8a:c2:e9:f5:b0:8d:da:ef:71:96:60: b0:7e:c3:3d:e9:37:91:27:bf:ae:5c:e8:c0:9a:6f:c8:38:62: 90:d0:49:c1:7f:28:13:da:29:bb:5b:d1:72:6f:23:7c:a0:87: 44:96:47:53:0e:0d:1d:74:d9:26:6b:b3:01:24:9c:5e:c8:f4: 11:fe:35:14:6c:ec:e7:42:5f:32:56:f0:9d:8d:11:02:21:07: cc:ce:7b:f0:e9:bc:83:c8:93:b0:8c:a7:e9:b1:c2:12:6b:30: 2b:75:dc:61:b8:d4:87:6b:07:2d:75:b0:7a:18:6e:19:7f:04: 78:c6:c7:b7 -----BEGIN CERTIFICATE----- MIID4jCCAsqgAwIBAgIJAIgFm03l5AJlMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV BAYTAlhYMRIwEAYDVQQIEwlVbnRydXN0ZWQxEjAQBgNVBAcTCUV2aWx2aWxsZTEU MBIGA1UEChMLRXZpbCBIYWNrZXIxGjAYBgNVBAsTEUF0dGFjayBEZXBhcnRtZW50 MRIwEAYDVQQDEwkxMjcuMC4wLjEwIBcNMTYwNDAyMDMzNDUxWhgPMjA1MDA2MjMw MzM0NTFaMGcxCzAJBgNVBAYTAlhYMRIwEAYDVQQIEwlVbnRydXN0ZWQxFDASBgNV BAoTC0V2aWwgSGFja2VyMRowGAYDVQQLExFBdHRhY2sgRGVwYXJ0bWVudDESMBAG A1UEAxMJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA mnPnRfzTtUq9va0w5SR0OAGJj6mQvzxKv9HxXtvIqiZZ5uyzoA9NdFndyScv4Uh9 MNlZBi8p8NElM3lfWJ3XVMinqhqEAKKFYzLM73N9sCbGlfGGFmg4Y1cJDW9qcOh1 O3K0sU0BDgEOv79qjIj+DcuIQxva7QyIJTP3ubH8MriUySB8rEnkwViTaQ5B49+W 40cRFIzkS7ZW329e0kjcoYqYzEsCiZXq9t6lOpwGfPB8CW8nEfKxG0dro+rW7qFl kYTPLoHTVUroAU5yQayS4H18/oXwLvHuSoD5Tlq0lWy7/v9GWEp7/KBjWV0BW2MG XJSDMCeB8BoTiVpaouIP6wIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIB DQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUG3GR mUMSD9NZ/ADvmfNCz0H9QB0wHwYDVR0jBBgwFoAUt4M28qkkqa7I+xqZrHYBlOKN 2TAwDQYJKoZIhvcNAQEFBQADggEBAKTNiMMZt81+eueFH/s+MQv/nW+xonJWSrHs bPOZvWUICulHHXlVWymx1IVphWU/MDehDnbSH7B2KiN1yQWkic/BaEIWRtbJqOUG W1JF1EFd88cA0crMPkxj5nr+ziCk3+N843Vu9xiEHJtWzlX7BLneEW59XUfeqe0+ eUilTzLVlo3q4qaKwun1sI3a73GWYLB+wz3pN5Env65c6MCab8g4YpDQScF/KBPa Kbtb0XJvI3ygh0SWR1MODR102SZrswEknF7I9BH+NRRs7OdCXzJW8J2NEQIhB8zO e/DpvIPIk7CMp+mxwhJrMCt13GG41IdrBy11sHoYbhl/BHjGx7c= -----END CERTIFICATE----- redis-rb-4.1.2/test/support/ssl/untrusted-cert.key000066400000000000000000000032501347371274700222420ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCac+dF/NO1Sr29 rTDlJHQ4AYmPqZC/PEq/0fFe28iqJlnm7LOgD010Wd3JJy/hSH0w2VkGLynw0SUz eV9YnddUyKeqGoQAooVjMszvc32wJsaV8YYWaDhjVwkNb2pw6HU7crSxTQEOAQ6/ v2qMiP4Ny4hDG9rtDIglM/e5sfwyuJTJIHysSeTBWJNpDkHj35bjRxEUjORLtlbf b17SSNyhipjMSwKJler23qU6nAZ88HwJbycR8rEbR2uj6tbuoWWRhM8ugdNVSugB TnJBrJLgfXz+hfAu8e5KgPlOWrSVbLv+/0ZYSnv8oGNZXQFbYwZclIMwJ4HwGhOJ Wlqi4g/rAgMBAAECggEAPX3fmfGqqAb1u8p0KQZ2bsXN6rBrvHdYmz4OhuGh5nwW VuXuLc9p2uTcc/VyDpM5pHUkCF5GqGXcFb5Aw5sz28F3XzXnUAlkabYT+VFVvQfz EEd0Rv9/U62XIQ42pnUmF2D3p48s2FJ7eMPQu9reqsdZnL4+TxoqKgWinv/JlLdh zBxjgVgaDMsvVc4cuuT6bcI3DUe2F9ALBKfaCxZoOUSsmgieuXog00Bzv0NmZoUD WsAX0syzUlwjVmCr8J4I0IByYAbn1S/ozU141Z+H+VUyuEpYw0zDqDNrlmdYclc8 neoq8Xj9Cx1zHdF5H3aT9SLUGxdHPJpED9wQNx2toQKBgQDJcgJEG39u3h3mW/At f8jl8evar5nUOOn5AIxVGFAWx4ZvoboxHSRlS6UkF0AImlH4L7xQESb9BMzOrObN PBNQrccH+fz1o1fHDhob7EvyMMwzmDCPpQnN/6KXRzapu2MDFvlMkEMITTN7J0En c9BOxo06Q4DKXGVCiWmbIwXihQKBgQDER/KfaWRZWOA2mQ26giJVjUX4+s1GeQM0 V4AIo1KS6fDzh68RsAQpMTx/N8aHEcxf+2qGIOTCvFY3Nxqe5aw/Xiz47MPlYulM OecovSO96nidhyv2Zux+HpvI85tcWTyORi+RWho51gTOLth6BJ4uvSsaooWmO0Va GoIxKcaLrwKBgH/guuWHWy8DG5H3fRE1FFA8cc+iN5HMC2NBYNRIGddME+BblznE WS1ghtXRWJnddPmLPAzLxqdJ28W7ZsyUPWKy3i0HGfjJF1jKb/KX32JAbfC2xOT7 DK1TgWBtGZtH1EPK2rkqvxLPB0Y/lhG4aF0Jl++LmH9dhf5mAr8zzXGNAoGBAKEi l7H66aDX76mi2LxmnR0yz2DpNKBINDNCKh/tRJrLZz3mA/k3URMoEow2E8tK90dM tVTLqEGeMAFAQaB02IVlIPJyHRgxrWkgl/6/15nP5ZkdISA1uqyHIElGhCK6N5Zt VBu1ppYYdvV1S85QADRKpBpHlgSz3+lqnbsSmqaNAoGAacA3XSIzTHj6+06VzEHN aO2LJbBxl6Eduj6eTEgcZBlQOX6cVvaleTAT2g2xM0aGMV4StmdijUdktjBQVLpH 8PBTqlfVuLXEXQd+qWMpUJwEkh/pdmf9EPoLSfp3zQLaNI/kCg3jQtR4n6/68hfi 5Q6L0mN+SoB+jRNPDSV7JWA= -----END PRIVATE KEY----- redis-rb-4.1.2/test/support/wire/000077500000000000000000000000001347371274700167055ustar00rootroot00000000000000redis-rb-4.1.2/test/support/wire/synchrony.rb000066400000000000000000000007201347371274700212650ustar00rootroot00000000000000class Wire < Fiber # We cannot run this fiber explicitly because EM schedules it. Resuming the # current fiber on the next tick to let the reactor do work. def self.pass f = Fiber.current EM.next_tick { f.resume } Fiber.yield end def self.sleep(sec) EM::Synchrony.sleep(sec) end def initialize(&blk) super # Schedule run in next tick EM.next_tick { resume } end def join self.class.pass while alive? end end redis-rb-4.1.2/test/support/wire/thread.rb000066400000000000000000000001121347371274700204730ustar00rootroot00000000000000class Wire < Thread def self.sleep(sec) Kernel.sleep(sec) end end redis-rb-4.1.2/test/synchrony_driver.rb000066400000000000000000000033031347371274700201560ustar00rootroot00000000000000require "em-synchrony" require "em-synchrony/connection_pool" require_relative "../lib/redis" require_relative "../lib/redis/connection/synchrony" require_relative "helper" PORT = 6381 OPTIONS = {:port => PORT, :db => 15} # # if running under Eventmachine + Synchrony (Ruby 1.9+), then # we can simulate the blocking API while performing the network # IO via the EM reactor. # EM.synchrony do r = Redis.new OPTIONS r.flushdb r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.rpop("foo") r.set("foo", "bar") assert_equal "bar", r.getset("foo", "baz") assert_equal "baz", r.get("foo") r.set("foo", "a") assert_equal 1, r.getbit("foo", 1) assert_equal 1, r.getbit("foo", 2) assert_equal 0, r.getbit("foo", 3) assert_equal 0, r.getbit("foo", 4) assert_equal 0, r.getbit("foo", 5) assert_equal 0, r.getbit("foo", 6) assert_equal 1, r.getbit("foo", 7) r.flushdb # command pipelining r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") assert_equal "OK", r._client.call(:quit) assert_equal "PONG", r.ping rpool = EM::Synchrony::ConnectionPool.new(size: 5) { Redis.new OPTIONS } result = rpool.watch 'foo' do |rd| assert_kind_of Redis, rd rd.set "foo", "s1" rd.multi do |multi| multi.set "foo", "s2" end end assert_nil result assert_equal "s1", rpool.get("foo") result = rpool.watch "foo" do |rd| assert_kind_of Redis, rd rd.multi do |multi| multi.set "foo", "s3" end end assert_equal ["OK"], result assert_equal "s3", rpool.get("foo") EM.stop end redis-rb-4.1.2/test/test.conf.erb000066400000000000000000000002631347371274700166210ustar00rootroot00000000000000dir <%= REDIS_DIR %> pidfile <%= REDIS_PID %> port 6381 unixsocket <%= REDIS_SOCKET %> timeout 300 loglevel debug logfile <%= REDIS_LOG %> databases 16 daemonize yes redis-rb-4.1.2/test/thread_safety_test.rb000066400000000000000000000020571347371274700204350ustar00rootroot00000000000000require_relative "helper" class TestThreadSafety < Minitest::Test include Helper::Client driver(:ruby, :hiredis) do def test_thread_safety redis = Redis.new(OPTIONS) redis.set "foo", 1 redis.set "bar", 2 sample = 100 t1 = Thread.new do $foos = Array.new(sample) { redis.get "foo" } end t2 = Thread.new do $bars = Array.new(sample) { redis.get "bar" } end t1.join t2.join assert_equal ["1"], $foos.uniq assert_equal ["2"], $bars.uniq end def test_thread_safety_queue_commit redis = Redis.new(OPTIONS) redis.set "foo", 1 redis.set "bar", 2 sample = 100 t1 = Thread.new do sample.times do r.queue("get", "foo") end $foos = r.commit end t2 = Thread.new do sample.times do r.queue("get", "bar") end $bars = r.commit end t1.join t2.join assert_equal ["1"], $foos.uniq assert_equal ["2"], $bars.uniq end end end redis-rb-4.1.2/test/transactions_test.rb000066400000000000000000000153151347371274700203240ustar00rootroot00000000000000require_relative "helper" class TestTransactions < Minitest::Test include Helper::Client def test_multi_discard r.multi assert_equal "QUEUED", r.set("foo", "1") assert_equal "QUEUED", r.get("foo") r.discard assert_nil r.get("foo") end def test_multi_exec_with_a_block r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_multi_exec_with_a_block_doesn_t_return_replies_for_multi_and_exec r1, r2, nothing_else = r.multi do |multi| multi.set "foo", "s1" multi.get "foo" end assert_equal "OK", r1 assert_equal "s1", r2 assert_nil nothing_else end def test_assignment_inside_multi_exec_block r.multi do |m| @first = m.sadd("foo", 1) @second = m.sadd("foo", 1) end assert_equal true, @first.value assert_equal false, @second.value end # Although we could support accessing the values in these futures, # it doesn't make a lot of sense. def test_assignment_inside_multi_exec_block_with_delayed_command_errors assert_raises(Redis::CommandError) do r.multi do |m| @first = m.set("foo", "s1") @second = m.incr("foo") # not an integer @third = m.lpush("foo", "value") # wrong kind of value end end assert_equal "OK", @first.value assert_raises(Redis::CommandError) { @second.value } assert_raises(Redis::FutureNotReady) { @third.value } end def test_assignment_inside_multi_exec_block_with_immediate_command_errors assert_raises(Redis::CommandError) do r.multi do |m| m.doesnt_exist @first = m.sadd("foo", 1) @second = m.sadd("foo", 1) end end assert_raises(Redis::FutureNotReady) { @first.value } assert_raises(Redis::FutureNotReady) { @second.value } end def test_raise_immediate_errors_in_multi_exec assert_raises(RuntimeError) do r.multi do |multi| multi.set "bar", "s2" raise "Some error" multi.set "baz", "s3" end end assert_nil r.get("bar") assert_nil r.get("baz") end def test_transformed_replies_as_return_values_for_multi_exec_block info, _ = r.multi do |m| r.info end assert info.kind_of?(Hash) end def test_transformed_replies_inside_multi_exec_block r.multi do |m| @info = r.info end assert @info.value.kind_of?(Hash) end def test_raise_command_errors_when_reply_is_not_transformed assert_raises(Redis::CommandError) do r.multi do |m| m.set("foo", "s1") m.incr("foo") # not an integer m.lpush("foo", "value") # wrong kind of value end end assert_equal "s1", r.get("foo") end def test_empty_multi_exec result = nil redis_mock(:exec => lambda { |*_| "-ERROR" }) do |redis| result = redis.multi {} end assert_equal [], result end def test_raise_command_errors_when_reply_is_transformed_from_int_to_boolean assert_raises(Redis::CommandError) do r.multi do |m| m.set("foo", 1) m.sadd("foo", 2) end end end def test_raise_command_errors_when_reply_is_transformed_from_ok_to_boolean assert_raises(Redis::CommandError) do r.multi do |m| m.set("foo", 1, ex: 0, nx: true) end end end def test_raise_command_errors_when_reply_is_transformed_to_float assert_raises(Redis::CommandError) do r.multi do |m| m.set("foo", 1) m.zscore("foo", "b") end end end def test_raise_command_errors_when_reply_is_transformed_to_floats assert_raises(Redis::CommandError) do r.multi do |m| m.zrange("a", "b", 5, :with_scores => true) end end end def test_raise_command_errors_when_reply_is_transformed_to_hash assert_raises(Redis::CommandError) do r.multi do |m| m.set("foo", 1) m.hgetall("foo") end end end def test_raise_command_errors_when_accessing_futures_after_multi_exec begin r.multi do |m| m.set("foo", "s1") @counter = m.incr("foo") # not an integer end rescue Exception # Not gonna deal with it end # We should test for Redis::Error here, but hiredis doesn't yet do # custom error classes. err = nil begin @counter.value rescue => err end assert err.kind_of?(RuntimeError) end def test_multi_with_a_block_yielding_the_client r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_raise_command_error_when_exec_fails redis_mock(:exec => lambda { |*_| "-ERROR" }) do |redis| assert_raises(Redis::CommandError) do redis.multi do |m| m.set "foo", "s1" end end end end def test_watch res = r.watch "foo" assert_equal "OK", res end def test_watch_with_an_unmodified_key r.watch "foo" r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_watch_with_an_unmodified_key_passed_as_array r.watch ["foo", "bar"] r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_watch_with_a_modified_key r.watch "foo" r.set "foo", "s1" res = r.multi do |multi| multi.set "foo", "s2" end assert_nil res assert_equal "s1", r.get("foo") end def test_watch_with_a_modified_key_passed_as_array r.watch ["foo", "bar"] r.set "foo", "s1" res = r.multi do |multi| multi.set "foo", "s2" end assert_nil res assert_equal "s1", r.get("foo") end def test_watch_with_a_block_and_an_unmodified_key result = r.watch "foo" do |rd| assert_same r, rd rd.multi do |multi| multi.set "foo", "s1" end end assert_equal ["OK"], result assert_equal "s1", r.get("foo") end def test_watch_with_a_block_and_a_modified_key result = r.watch "foo" do |rd| assert_same r, rd rd.set "foo", "s1" rd.multi do |multi| multi.set "foo", "s2" end end assert_nil result assert_equal "s1", r.get("foo") end def test_watch_with_a_block_that_raises_an_exception r.set("foo", "s1") begin r.watch "foo" do raise "test" end rescue RuntimeError end r.set("foo", "s2") # If the watch was still set from within the block above, this multi/exec # would fail. This proves that raising an exception above unwatches. r.multi do |multi| multi.set "foo", "s3" end assert_equal "s3", r.get("foo") end def test_unwatch_with_a_modified_key r.watch "foo" r.set "foo", "s1" r.unwatch r.multi do |multi| multi.set "foo", "s2" end assert_equal "s2", r.get("foo") end end redis-rb-4.1.2/test/unknown_commands_test.rb000066400000000000000000000003361347371274700211710ustar00rootroot00000000000000require_relative "helper" class TestUnknownCommands < Minitest::Test include Helper::Client def test_should_try_to_work assert_raises Redis::CommandError do r.not_yet_implemented_command end end end redis-rb-4.1.2/test/url_param_test.rb000066400000000000000000000102411347371274700175670ustar00rootroot00000000000000require_relative "helper" class TestUrlParam < Minitest::Test include Helper::Client def test_url_defaults_to_______________ redis = Redis.new assert_equal "127.0.0.1", redis._client.host assert_equal 6379, redis._client.port assert_equal 0, redis._client.db assert_nil redis._client.password end def test_allows_to_pass_in_a_url redis = Redis.new :url => "redis://:secr3t@foo.com:999/2" assert_equal "foo.com", redis._client.host assert_equal 999, redis._client.port assert_equal 2, redis._client.db assert_equal "secr3t", redis._client.password end def test_allows_to_pass_in_a_url_with_string_key redis = Redis.new "url" => "redis://:secr3t@foo.com:999/2" assert_equal "foo.com", redis._client.host assert_equal 999, redis._client.port assert_equal 2, redis._client.db assert_equal "secr3t", redis._client.password end def test_unescape_password_from_url redis = Redis.new :url => "redis://:secr3t%3A@foo.com:999/2" assert_equal "secr3t:", redis._client.password end def test_unescape_password_from_url_with_string_key redis = Redis.new "url" => "redis://:secr3t%3A@foo.com:999/2" assert_equal "secr3t:", redis._client.password end def test_does_not_unescape_password_when_explicitly_passed redis = Redis.new :url => "redis://:secr3t%3A@foo.com:999/2", :password => "secr3t%3A" assert_equal "secr3t%3A", redis._client.password end def test_does_not_unescape_password_when_explicitly_passed_with_string_key redis = Redis.new :url => "redis://:secr3t%3A@foo.com:999/2", "password" => "secr3t%3A" assert_equal "secr3t%3A", redis._client.password end def test_override_url_if_path_option_is_passed redis = Redis.new :url => "redis://:secr3t@foo.com/foo:999/2", :path => "/tmp/redis.sock" assert_equal "/tmp/redis.sock", redis._client.path assert_nil redis._client.host assert_nil redis._client.port end def test_override_url_if_path_option_is_passed_with_string_key redis = Redis.new :url => "redis://:secr3t@foo.com/foo:999/2", "path" => "/tmp/redis.sock" assert_equal "/tmp/redis.sock", redis._client.path assert_nil redis._client.host assert_nil redis._client.port end def test_overrides_url_if_another_connection_option_is_passed redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", :port => 1000 assert_equal "foo.com", redis._client.host assert_equal 1000, redis._client.port assert_equal 2, redis._client.db assert_equal "secr3t", redis._client.password end def test_overrides_url_if_another_connection_option_is_passed_with_string_key redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", "port" => 1000 assert_equal "foo.com", redis._client.host assert_equal 1000, redis._client.port assert_equal 2, redis._client.db assert_equal "secr3t", redis._client.password end def test_does_not_overrides_url_if_a_nil_option_is_passed redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", :port => nil assert_equal "foo.com", redis._client.host assert_equal 999, redis._client.port assert_equal 2, redis._client.db assert_equal "secr3t", redis._client.password end def test_does_not_overrides_url_if_a_nil_option_is_passed_with_string_key redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", "port" => nil assert_equal "foo.com", redis._client.host assert_equal 999, redis._client.port assert_equal 2, redis._client.db assert_equal "secr3t", redis._client.password end def test_does_not_modify_the_passed_options options = { :url => "redis://:secr3t@foo.com:999/2" } Redis.new(options) assert({ :url => "redis://:secr3t@foo.com:999/2" } == options) end def test_uses_redis_url_over_default_if_available ENV["REDIS_URL"] = "redis://:secr3t@foo.com:999/2" redis = Redis.new assert_equal "foo.com", redis._client.host assert_equal 999, redis._client.port assert_equal 2, redis._client.db assert_equal "secr3t", redis._client.password ENV.delete("REDIS_URL") end def test_defaults_to_localhost redis = Redis.new(:url => "redis:///") assert_equal "127.0.0.1", redis._client.host end end