redis 3.2.1 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of redis might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1245d9a408be7cfa9f796b5b02e6f2dcaff10f2a
4
- data.tar.gz: 2dce33db07a7a16dcade71b081996f241d2075ab
3
+ metadata.gz: 183e90b7d3961182b8b5b20b1295b6dd9d30df12
4
+ data.tar.gz: 60023071a61af0e34d85b0a999db0bc5877070ad
5
5
  SHA512:
6
- metadata.gz: ef3734d43bbd4fb5f50fbef3d9b3348d382c9389433a8af91e9d94410b818b3eaa8d306b827e91aafaa26fa28aa461f9c028293280c6df48bf5d28412554140a
7
- data.tar.gz: cc7122626f5587a8a750746acfb59d7462fb426cd68234ee141805e77b5bbfb39736543f71fc26575ef5bd1bd6dce3b7eb2b8857408461cf4f9ac2e4f2d05c78
6
+ metadata.gz: a1d93f8ee26f3dfecb10d223d178e4f78aa399d3bc8a78a4db479f0e6d89bd77c97d9e113b0138d102fbecf3d0a4452ce5f0da9412e069726c3b1fd7bb7bf7a4
7
+ data.tar.gz: 1a2077544497d0f2a1cd9035677dc1d514a9395b070b10f380e8fcc35cbbbbbae898e1b4ad3e7ff44e2fb96930330cb66b021ccaa726451c2c15276ff26b0011
@@ -3,9 +3,12 @@ language: ruby
3
3
  rvm:
4
4
  - 1.8.7
5
5
  - 1.9.3
6
+ - 2.0
6
7
  - 2.1
8
+ - 2.2
7
9
  - jruby-18mode
8
10
  - jruby-19mode
11
+ - rbx-2
9
12
 
10
13
  gemfile: ".travis/Gemfile"
11
14
 
@@ -45,6 +48,8 @@ matrix:
45
48
  - rvm: jruby-19mode
46
49
  gemfile: .travis/Gemfile
47
50
  env: conn=synchrony REDIS_BRANCH=2.8
51
+ allow_failures:
52
+ - rvm: rbx-2
48
53
 
49
54
  notifications:
50
55
  irc:
@@ -12,6 +12,17 @@
12
12
  security updates in June of 2013; continuing to support it would prevent
13
13
  the use of newer features of Ruby.
14
14
 
15
+ # 3.2.2
16
+
17
+ * Added support for `ZADD` options `NX`, `XX`, `CH`, `INCR`. See #547.
18
+
19
+ * Added support for sentinel commands. See #556.
20
+
21
+ * New `:id` option allows you to identify the client against Redis. See #510.
22
+
23
+ * `Redis::Distributed` will raise when adding two nodes with the same ID.
24
+ See #354.
25
+
15
26
  # 3.2.1
16
27
 
17
28
  * Added support for `PUBSUB` command.
data/README.md CHANGED
@@ -23,6 +23,20 @@ most important changes, as well as a full list of changes.
23
23
 
24
24
  ## Getting started
25
25
 
26
+ To install **redis-rb**, run the following command:
27
+
28
+ ```
29
+ gem install redis
30
+ ```
31
+
32
+ Or if you are using **bundler**, add
33
+
34
+ ```
35
+ gem 'redis', '~>3.2'
36
+ ```
37
+
38
+ to your `Gemfile`, and run `bundle install`
39
+
26
40
  As of version 2.0 this client only targets Redis version 2.0 and higher.
27
41
  You can use an older version of this client if you need to interface
28
42
  with a Redis instance older than 2.0, but this is no longer supported.
@@ -43,12 +57,14 @@ server or a different port, try:
43
57
  redis = Redis.new(:host => "10.0.1.1", :port => 6380, :db => 15)
44
58
  ```
45
59
 
46
- You can also specify connection options as an URL:
60
+ You can also specify connection options as a [`redis://` URL][redis-url]:
47
61
 
48
62
  ```ruby
49
63
  redis = Redis.new(:url => "redis://:p4ssw0rd@10.0.1.1:6380/15")
50
64
  ```
51
65
 
66
+ [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
67
+
52
68
  By default, the client will try to read the `REDIS_URL` environment variable
53
69
  and use that as URL to connect to. The above statement is therefore equivalent
54
70
  to setting this environment variable and calling `Redis.new` without arguments.
@@ -298,7 +314,7 @@ all contributors)
298
314
  * Matthew Clark
299
315
  * Brian McKinney
300
316
  * Luca Guidi
301
- * Salvatore Sanfillipo
317
+ * Salvatore Sanfilippo
302
318
  * Chris Wanstrath
303
319
  * Damian Janowski
304
320
  * Michel Martens
@@ -16,5 +16,5 @@ worker_processes 3
16
16
  # worker processes.
17
17
 
18
18
  after_fork do |server, worker|
19
- Redis.current.quit
19
+ Redis.current.disconnect!
20
20
  end
@@ -26,6 +26,26 @@ class Redis
26
26
 
27
27
  include MonitorMixin
28
28
 
29
+ # Create a new client instance
30
+ #
31
+ # @param [Hash] options
32
+ # @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.
33
+ # @option options [String] :host ("127.0.0.1") server hostname
34
+ # @option options [Fixnum] :port (6379) server port
35
+ # @option options [String] :path path to server socket (overrides host and port)
36
+ # @option options [Float] :timeout (5.0) timeout in seconds
37
+ # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
38
+ # @option options [String] :password Password to authenticate against server
39
+ # @option options [Fixnum] :db (0) Database to select after initial connect
40
+ # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony`
41
+ # @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`
42
+ # @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
43
+ # @option options [Fixnum] :reconnect_attempts Number of attempts trying to connect
44
+ # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
45
+ # @option options [Array] :sentinels List of sentinels to contact
46
+ # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
47
+ #
48
+ # @return [Redis] a new client instance
29
49
  def initialize(options = {})
30
50
  @options = options.dup
31
51
  @original_client = @client = Client.new(options)
@@ -329,8 +349,15 @@ class Redis
329
349
  # Get the time to live (in seconds) for a key.
330
350
  #
331
351
  # @param [String] key
332
- # @return [Fixnum] remaining time to live in seconds, or -1 if the
333
- # key does not exist or does not have a timeout
352
+ # @return [Fixnum] remaining time to live in seconds.
353
+ #
354
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
355
+ # the key exist but has no associated expire.
356
+ #
357
+ # Starting with Redis 2.8 the return value in case of error changed:
358
+ #
359
+ # - The command returns -2 if the key does not exist.
360
+ # - The command returns -1 if the key exists but has no associated expire.
334
361
  def ttl(key)
335
362
  synchronize do |client|
336
363
  client.call([:ttl, key])
@@ -362,8 +389,14 @@ class Redis
362
389
  # Get the time to live (in milliseconds) for a key.
363
390
  #
364
391
  # @param [String] key
365
- # @return [Fixnum] remaining time to live in milliseconds, or -1 if the
366
- # key does not exist or does not have a timeout
392
+ # @return [Fixnum] remaining time to live in milliseconds
393
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
394
+ # the key exist but has no associated expire.
395
+ #
396
+ # Starting with Redis 2.8 the return value in case of error changed:
397
+ #
398
+ # - The command returns -2 if the key does not exist.
399
+ # - The command returns -1 if the key exists but has no associated expire.
367
400
  def pttl(key)
368
401
  synchronize do |client|
369
402
  client.call([:pttl, key])
@@ -385,7 +418,7 @@ class Redis
385
418
  # @param [String] key
386
419
  # @param [String] ttl
387
420
  # @param [String] serialized_value
388
- # @return `"OK"`
421
+ # @return [String] `"OK"`
389
422
  def restore(key, ttl, serialized_value)
390
423
  synchronize do |client|
391
424
  client.call([:restore, key, ttl, serialized_value])
@@ -690,7 +723,7 @@ class Redis
690
723
  # @param [String] key
691
724
  # @param [Fixnum] ttl
692
725
  # @param [String] value
693
- # @return `"OK"`
726
+ # @return [String] `"OK"`
694
727
  def setex(key, ttl, value)
695
728
  synchronize do |client|
696
729
  client.call([:setex, key, ttl, value.to_s])
@@ -702,7 +735,7 @@ class Redis
702
735
  # @param [String] key
703
736
  # @param [Fixnum] ttl
704
737
  # @param [String] value
705
- # @return `"OK"`
738
+ # @return [String] `"OK"`
706
739
  def psetex(key, ttl, value)
707
740
  synchronize do |client|
708
741
  client.call([:psetex, key, ttl, value.to_s])
@@ -727,7 +760,7 @@ class Redis
727
760
  # # => "OK"
728
761
  #
729
762
  # @param [Array<String>] args array of keys and values
730
- # @return `"OK"`
763
+ # @return [String] `"OK"`
731
764
  #
732
765
  # @see #mapped_mset
733
766
  def mset(*args)
@@ -743,7 +776,7 @@ class Redis
743
776
  # # => "OK"
744
777
  #
745
778
  # @param [Hash] hash keys mapping to values
746
- # @return `"OK"`
779
+ # @return [String] `"OK"`
747
780
  #
748
781
  # @see #mset
749
782
  def mapped_mset(hash)
@@ -968,7 +1001,7 @@ class Redis
968
1001
  # Prepend one or more values to a list, creating the list if it doesn't exist
969
1002
  #
970
1003
  # @param [String] key
971
- # @param [String, Array] string value, or array of string values to push
1004
+ # @param [String, Array] value string value, or array of string values to push
972
1005
  # @return [Fixnum] the length of the list after the push operation
973
1006
  def lpush(key, value)
974
1007
  synchronize do |client|
@@ -1410,20 +1443,50 @@ class Redis
1410
1443
  # @param [[Float, String], Array<[Float, String]>] args
1411
1444
  # - a single `[score, member]` pair
1412
1445
  # - an array of `[score, member]` pairs
1413
- #
1414
- # @return [Boolean, Fixnum]
1446
+ # @param [Hash] options
1447
+ # - `:xx => true`: Only update elements that already exist (never
1448
+ # add elements)
1449
+ # - `:nx => true`: Don't update already existing elements (always
1450
+ # add new elements)
1451
+ # - `:ch => true`: Modify the return value from the number of new
1452
+ # elements added, to the total number of elements changed (CH is an
1453
+ # abbreviation of changed); changed elements are new elements added
1454
+ # and elements already existing for which the score was updated
1455
+ # - `:incr => true`: When this option is specified ZADD acts like
1456
+ # ZINCRBY; only one score-element pair can be specified in this mode
1457
+ #
1458
+ # @return [Boolean, Fixnum, Float]
1415
1459
  # - `Boolean` when a single pair is specified, holding whether or not it was
1416
- # **added** to the sorted set
1460
+ # **added** to the sorted set.
1417
1461
  # - `Fixnum` when an array of pairs is specified, holding the number of
1418
- # pairs that were **added** to the sorted set
1419
- def zadd(key, *args)
1462
+ # pairs that were **added** to the sorted set.
1463
+ # - `Float` when option :incr is specified, holding the score of the member
1464
+ # after incrementing it.
1465
+ def zadd(key, *args) #, options
1466
+ zadd_options = []
1467
+ if args.last.is_a?(Hash)
1468
+ options = args.pop
1469
+
1470
+ nx = options[:nx]
1471
+ zadd_options << "NX" if nx
1472
+
1473
+ xx = options[:xx]
1474
+ zadd_options << "XX" if xx
1475
+
1476
+ ch = options[:ch]
1477
+ zadd_options << "CH" if ch
1478
+
1479
+ incr = options[:incr]
1480
+ zadd_options << "INCR" if incr
1481
+ end
1482
+
1420
1483
  synchronize do |client|
1421
1484
  if args.size == 1 && args[0].is_a?(Array)
1422
- # Variadic: return integer
1423
- client.call([:zadd, key] + args[0])
1485
+ # Variadic: return float if INCR, integer if !INCR
1486
+ client.call([:zadd, key] + zadd_options + args[0], &(incr ? _floatify : _identity))
1424
1487
  elsif args.size == 2
1425
- # Single pair: return boolean
1426
- client.call([:zadd, key, args[0], args[1]], &_boolify)
1488
+ # Single pair: return float if INCR, boolean if !INCR
1489
+ client.call([:zadd, key] + zadd_options + args, &(incr ? _floatify : _boolify))
1427
1490
  else
1428
1491
  raise ArgumentError, "wrong number of arguments"
1429
1492
  end
@@ -1867,7 +1930,7 @@ class Redis
1867
1930
  #
1868
1931
  # @param [String] key
1869
1932
  # @param [Array<String>] attrs array of fields and values
1870
- # @return `"OK"`
1933
+ # @return [String] `"OK"`
1871
1934
  #
1872
1935
  # @see #mapped_hmset
1873
1936
  def hmset(key, *attrs)
@@ -1883,8 +1946,8 @@ class Redis
1883
1946
  # # => "OK"
1884
1947
  #
1885
1948
  # @param [String] key
1886
- # @param [Hash] a non-empty hash with fields mapping to values
1887
- # @return `"OK"`
1949
+ # @param [Hash] hash a non-empty hash with fields mapping to values
1950
+ # @return [String] `"OK"`
1888
1951
  #
1889
1952
  # @see #hmset
1890
1953
  def mapped_hmset(key, hash)
@@ -2207,7 +2270,7 @@ class Redis
2207
2270
  #
2208
2271
  # Only call this method when `#multi` was called **without** a block.
2209
2272
  #
2210
- # @return `"OK"`
2273
+ # @return [String] `"OK"`
2211
2274
  #
2212
2275
  # @see #multi
2213
2276
  # @see #exec
@@ -2355,7 +2418,7 @@ class Redis
2355
2418
  # redis.scan(4, :match => "key:1?")
2356
2419
  # # => ["92", ["key:13", "key:18"]]
2357
2420
  #
2358
- # @param [String, Integer] cursor: the cursor of the iteration
2421
+ # @param [String, Integer] cursor the cursor of the iteration
2359
2422
  # @param [Hash] options
2360
2423
  # - `:match => String`: only return keys matching the pattern
2361
2424
  # - `:count => Integer`: return count keys at most per iteration
@@ -2395,7 +2458,7 @@ class Redis
2395
2458
  # @example Retrieve the first batch of key/value pairs in a hash
2396
2459
  # redis.hscan("hash", 0)
2397
2460
  #
2398
- # @param [String, Integer] cursor: the cursor of the iteration
2461
+ # @param [String, Integer] cursor the cursor of the iteration
2399
2462
  # @param [Hash] options
2400
2463
  # - `:match => String`: only return keys matching the pattern
2401
2464
  # - `:count => Integer`: return count keys at most per iteration
@@ -2433,7 +2496,7 @@ class Redis
2433
2496
  # @example Retrieve the first batch of key/value pairs in a hash
2434
2497
  # redis.zscan("zset", 0)
2435
2498
  #
2436
- # @param [String, Integer] cursor: the cursor of the iteration
2499
+ # @param [String, Integer] cursor the cursor of the iteration
2437
2500
  # @param [Hash] options
2438
2501
  # - `:match => String`: only return keys matching the pattern
2439
2502
  # - `:count => Integer`: return count keys at most per iteration
@@ -2472,7 +2535,7 @@ class Redis
2472
2535
  # @example Retrieve the first batch of keys in a set
2473
2536
  # redis.sscan("set", 0)
2474
2537
  #
2475
- # @param [String, Integer] cursor: the cursor of the iteration
2538
+ # @param [String, Integer] cursor the cursor of the iteration
2476
2539
  # @param [Hash] options
2477
2540
  # - `:match => String`: only return keys matching the pattern
2478
2541
  # - `:count => Integer`: return count keys at most per iteration
@@ -2539,6 +2602,33 @@ class Redis
2539
2602
  end
2540
2603
  end
2541
2604
 
2605
+ # Interact with the sentinel command (masters, master, slaves, failover)
2606
+ #
2607
+ # @param [String] subcommand e.g. `masters`, `master`, `slaves`
2608
+ # @param [Array<String>] args depends on subcommand
2609
+ # @return [Array<String>, Hash<String, String>, String] depends on subcommand
2610
+ def sentinel(subcommand, *args)
2611
+ subcommand = subcommand.to_s.downcase
2612
+ synchronize do |client|
2613
+ client.call([:sentinel, subcommand] + args) do |reply|
2614
+ case subcommand
2615
+ when "get-master-addr-by-name"
2616
+ reply
2617
+ else
2618
+ if reply.kind_of?(Array)
2619
+ if reply[0].kind_of?(Array)
2620
+ reply.map(&_hashify)
2621
+ else
2622
+ _hashify.call(reply)
2623
+ end
2624
+ else
2625
+ reply
2626
+ end
2627
+ end
2628
+ end
2629
+ end
2630
+ end
2631
+
2542
2632
  def id
2543
2633
  @original_client.id
2544
2634
  end
@@ -2614,6 +2704,12 @@ private
2614
2704
  array.each_slice(2).to_a
2615
2705
  end
2616
2706
 
2707
+ def _identity
2708
+ lambda { |value|
2709
+ value
2710
+ }
2711
+ end
2712
+
2617
2713
  def _subscription(method, channels, block)
2618
2714
  return @client.call([method] + channels) if subscribed?
2619
2715
 
@@ -94,6 +94,7 @@ class Redis
94
94
  establish_connection
95
95
  call [:auth, password] if password
96
96
  call [:select, db] if db != 0
97
+ call [:client, :setname, @options[:id]] if @options[:id]
97
98
  @connector.check(self)
98
99
  end
99
100
 
@@ -392,11 +393,8 @@ class Redis
392
393
  if uri.scheme == "unix"
393
394
  defaults[:path] = uri.path
394
395
  elsif uri.scheme == "redis"
395
- # Require the URL to have at least a host
396
- raise ArgumentError, "invalid url: #{uri}" unless uri.host
397
-
398
396
  defaults[:scheme] = uri.scheme
399
- defaults[:host] = uri.host
397
+ defaults[:host] = uri.host if uri.host
400
398
  defaults[:port] = uri.port if uri.port
401
399
  defaults[:password] = CGI.unescape(uri.password) if uri.password
402
400
  defaults[:db] = uri.path[1..-1].to_i if uri.path
@@ -25,6 +25,7 @@ class Redis
25
25
  @nodes << node
26
26
  @replicas.times do |i|
27
27
  key = Zlib.crc32("#{node.id}:#{i}")
28
+ raise "Node ID collision" if @ring.has_key?(key)
28
29
  @ring[key] = node
29
30
  @sorted_keys << key
30
31
  end
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.2.1"
2
+ VERSION = "3.2.2"
3
3
  end
@@ -40,4 +40,5 @@ Gem::Specification.new do |s|
40
40
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
41
41
 
42
42
  s.add_development_dependency("rake")
43
+ s.add_development_dependency("test-unit")
43
44
  end
@@ -17,6 +17,19 @@ class TestConnectionHandling < Test::Unit::TestCase
17
17
  end
18
18
  end
19
19
 
20
+ def test_id
21
+ commands = {
22
+ :client => lambda { |cmd, name| $name = [cmd, name]; "+OK" },
23
+ :ping => lambda { "+PONG" },
24
+ }
25
+
26
+ redis_mock(commands, :id => "client-name") do |redis|
27
+ assert_equal "PONG", redis.ping
28
+ end
29
+
30
+ assert_equal ["setname","client-name"], $name
31
+ end
32
+
20
33
  def test_ping
21
34
  assert_equal "PONG", r.ping
22
35
  end
@@ -7,16 +7,16 @@ class TestDistributedInternals < Test::Unit::TestCase
7
7
  include Helper::Distributed
8
8
 
9
9
  def test_provides_a_meaningful_inspect
10
- nodes = ["redis://127.0.0.1:#{PORT}/15", *NODES]
10
+ nodes = ["redis://localhost:#{PORT}/15", *NODES]
11
11
  redis = Redis::Distributed.new nodes
12
12
 
13
13
  assert_equal "#<Redis client v#{Redis::VERSION} for #{redis.nodes.map(&:id).join(', ')}>", redis.inspect
14
14
  end
15
15
 
16
16
  def test_default_as_urls
17
- nodes = ["redis://127.0.0.1:#{PORT}/15", *NODES]
17
+ nodes = ["redis://localhost:#{PORT}/15", *NODES]
18
18
  redis = Redis::Distributed.new nodes
19
- assert_equal ["redis://127.0.0.1:#{PORT}/15", *NODES], redis.nodes.map { |node| node.client.id}
19
+ assert_equal ["redis://localhost:#{PORT}/15", *NODES], redis.nodes.map { |node| node.client.id}
20
20
  end
21
21
 
22
22
  def test_default_as_config_hashes
@@ -67,4 +67,13 @@ class TestDistributedInternals < Test::Unit::TestCase
67
67
 
68
68
  assert_equal [], r2.sinter("baz:foo", "baz:bar")
69
69
  end
70
+
71
+ def test_colliding_node_ids
72
+ nodes = ["redis://localhost:#{PORT}/15", "redis://localhost:#{PORT}/15", *NODES]
73
+
74
+ assert_raise(RuntimeError) do
75
+ redis = Redis::Distributed.new nodes
76
+ end
77
+ end
78
+
70
79
  end
@@ -8,9 +8,9 @@ class TestDistributedKeyTags < Test::Unit::TestCase
8
8
  include Helper::Distributed
9
9
 
10
10
  def test_hashes_consistently
11
- r1 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES]
12
- r2 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES]
13
- r3 = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES]
11
+ r1 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES]
12
+ r2 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES]
13
+ r3 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES]
14
14
 
15
15
  assert_equal r1.node_for("foo").id, r2.node_for("foo").id
16
16
  assert_equal r1.node_for("foo").id, r3.node_for("foo").id
@@ -7,7 +7,7 @@ class TestDistributed < Test::Unit::TestCase
7
7
  include Helper::Distributed
8
8
 
9
9
  def test_handle_multiple_servers
10
- @r = Redis::Distributed.new ["redis://127.0.0.1:#{PORT}/15", *NODES]
10
+ @r = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES]
11
11
 
12
12
  100.times do |idx|
13
13
  @r.set(idx.to_s, "foo#{idx}")
@@ -9,6 +9,43 @@ module Lint
9
9
  assert_equal true, r.zadd("foo", 1, "s1")
10
10
  assert_equal false, r.zadd("foo", 1, "s1")
11
11
  assert_equal 1, r.zcard("foo")
12
+ r.del "foo"
13
+
14
+ target_version "3.0.2" do
15
+ # XX option
16
+ assert_equal 0, r.zcard("foo")
17
+ assert_equal false, r.zadd("foo", 1, "s1", :xx => true)
18
+ r.zadd("foo", 1, "s1")
19
+ assert_equal false, r.zadd("foo", 2, "s1", :xx => true)
20
+ assert_equal 2, r.zscore("foo", "s1")
21
+ r.del "foo"
22
+
23
+ # NX option
24
+ assert_equal 0, r.zcard("foo")
25
+ assert_equal true, r.zadd("foo", 1, "s1", :nx => true)
26
+ assert_equal false, r.zadd("foo", 2, "s1", :nx => true)
27
+ assert_equal 1, r.zscore("foo", "s1")
28
+ assert_equal 1, r.zcard("foo")
29
+ r.del "foo"
30
+
31
+ # CH option
32
+ assert_equal 0, r.zcard("foo")
33
+ assert_equal true, r.zadd("foo", 1, "s1", :ch => true)
34
+ assert_equal false, r.zadd("foo", 1, "s1", :ch => true)
35
+ assert_equal true, r.zadd("foo", 2, "s1", :ch => true)
36
+ assert_equal 1, r.zcard("foo")
37
+ r.del "foo"
38
+
39
+ # INCR option
40
+ assert_equal 1.0, r.zadd("foo", 1, "s1", :incr => true)
41
+ assert_equal 11.0, r.zadd("foo", 10, "s1", :incr => true)
42
+ assert_equal -Infinity, r.zadd("bar", "-inf", "s1", :incr => true)
43
+ assert_equal +Infinity, r.zadd("bar", "+inf", "s2", :incr => true)
44
+ r.del "foo", "bar"
45
+
46
+ # Incompatible options combination
47
+ assert_raise(Redis::CommandError) { r.zadd("foo", 1, "s1", :xx => true, :nx => true) }
48
+ end
12
49
  end
13
50
 
14
51
  def test_variadic_zadd
@@ -31,6 +68,47 @@ module Lint
31
68
  assert_raise(Redis::CommandError) { r.zadd("foo", ["bar"]) }
32
69
  assert_raise(Redis::CommandError) { r.zadd("foo", ["bar", "qux", "zap"]) }
33
70
  end
71
+
72
+ target_version "3.0.2" do
73
+ # XX option
74
+ assert_equal 0, r.zcard("foo")
75
+ assert_equal 0, r.zadd("foo", [1, "s1", 2, "s2"], :xx => true)
76
+ r.zadd("foo", [1, "s1", 2, "s2"])
77
+ assert_equal 0, r.zadd("foo", [2, "s1", 3, "s2", 4, "s3"], :xx => true)
78
+ assert_equal 2, r.zscore("foo", "s1")
79
+ assert_equal 3, r.zscore("foo", "s2")
80
+ assert_equal nil, r.zscore("foo", "s3")
81
+ assert_equal 2, r.zcard("foo")
82
+ r.del "foo"
83
+
84
+ # NX option
85
+ assert_equal 0, r.zcard("foo")
86
+ assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"], :nx => true)
87
+ assert_equal 1, r.zadd("foo", [2, "s1", 3, "s2", 4, "s3"], :nx => true)
88
+ assert_equal 1, r.zscore("foo", "s1")
89
+ assert_equal 2, r.zscore("foo", "s2")
90
+ assert_equal 4, r.zscore("foo", "s3")
91
+ assert_equal 3, r.zcard("foo")
92
+ r.del "foo"
93
+
94
+ # CH option
95
+ assert_equal 0, r.zcard("foo")
96
+ assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"], :ch => true)
97
+ assert_equal 2, r.zadd("foo", [1, "s1", 3, "s2", 4, "s3"], :ch => true)
98
+ assert_equal 3, r.zcard("foo")
99
+ r.del "foo"
100
+
101
+ # INCR option
102
+ assert_equal 1.0, r.zadd("foo", [1, "s1"], :incr => true)
103
+ assert_equal 11.0, r.zadd("foo", [10, "s1"], :incr => true)
104
+ assert_equal -Infinity, r.zadd("bar", ["-inf", "s1"], :incr => true)
105
+ assert_equal +Infinity, r.zadd("bar", ["+inf", "s2"], :incr => true)
106
+ assert_raise(Redis::CommandError) { r.zadd("foo", [1, "s1", 2, "s2"], :incr => true) }
107
+ r.del "foo", "bar"
108
+
109
+ # Incompatible options combination
110
+ assert_raise(Redis::CommandError) { r.zadd("foo", [1, "s1"], :xx => true, :nx => true) }
111
+ end
34
112
  end
35
113
 
36
114
  def test_zrem
@@ -0,0 +1,80 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("helper", File.dirname(__FILE__))
4
+
5
+ class SentinalCommandsTest < Test::Unit::TestCase
6
+
7
+ include Helper::Client
8
+
9
+ def test_sentinel_command_master
10
+
11
+ handler = lambda do |id|
12
+ {
13
+ :sentinel => lambda do |command, *args|
14
+ ["name", "master1", "ip", "127.0.0.1"]
15
+ end
16
+ }
17
+ end
18
+
19
+ RedisMock.start(handler.call(:s1)) do |port|
20
+ redis = Redis.new(:host => "127.0.0.1", :port => port)
21
+
22
+ result = redis.sentinel('master', 'master1')
23
+ assert_equal result, { "name" => "master1", "ip" => "127.0.0.1" }
24
+ end
25
+ end
26
+
27
+ def test_sentinel_command_masters
28
+
29
+ handler = lambda do |id|
30
+ {
31
+ :sentinel => lambda do |command, *args|
32
+ [%w[name master1 ip 127.0.0.1 port 6381], %w[name master1 ip 127.0.0.1 port 6382]]
33
+ end
34
+ }
35
+ end
36
+
37
+ RedisMock.start(handler.call(:s1)) do |port|
38
+ redis = Redis.new(:host => "127.0.0.1", :port => port)
39
+
40
+ result = redis.sentinel('masters')
41
+ assert_equal result[0], { "name" => "master1", "ip" => "127.0.0.1", "port" => "6381" }
42
+ assert_equal result[1], { "name" => "master1", "ip" => "127.0.0.1", "port" => "6382" }
43
+ end
44
+ end
45
+
46
+ def test_sentinel_command_get_master_by_name
47
+
48
+ handler = lambda do |id|
49
+ {
50
+ :sentinel => lambda do |command, *args|
51
+ ["127.0.0.1", "6381"]
52
+ end
53
+ }
54
+ end
55
+
56
+ RedisMock.start(handler.call(:s1)) do |port|
57
+ redis = Redis.new(:host => "127.0.0.1", :port => port)
58
+
59
+ result = redis.sentinel('get-master-addr-by-name', 'master1')
60
+ assert_equal result, ["127.0.0.1", "6381"]
61
+ end
62
+ end
63
+
64
+ def test_sentinel_command_ckquorum
65
+ handler = lambda do |id|
66
+ {
67
+ :sentinel => lambda do |command, *args|
68
+ "+OK 2 usable Sentinels. Quorum and failover authorization can be reached"
69
+ end
70
+ }
71
+ end
72
+
73
+ RedisMock.start(handler.call(:s1)) do |port|
74
+ redis = Redis.new(:host => "127.0.0.1", :port => port)
75
+
76
+ result = redis.sentinel('ckquorum', 'master1')
77
+ assert_equal result, "OK 2 usable Sentinels. Quorum and failover authorization can be reached"
78
+ end
79
+ end
80
+ end
@@ -24,8 +24,10 @@ class SentinalTest < Test::Unit::TestCase
24
24
  }
25
25
  end
26
26
 
27
- RedisMock.start(handler.call(:s1), {}, 26381) do
28
- RedisMock.start(handler.call(:s2), {}, 26382) do
27
+ RedisMock.start(handler.call(:s1)) do |s1_port|
28
+ RedisMock.start(handler.call(:s2)) do |s2_port|
29
+ sentinels[0][:port] = s1_port
30
+ sentinels[1][:port] = s2_port
29
31
  redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
30
32
 
31
33
  assert redis.ping
@@ -59,8 +61,10 @@ class SentinalTest < Test::Unit::TestCase
59
61
  end
60
62
  }
61
63
 
62
- RedisMock.start(s1, {}, 26381) do
63
- RedisMock.start(s2, {}, 26382) do
64
+ RedisMock.start(s1) do |s1_port|
65
+ RedisMock.start(s2) do |s2_port|
66
+ sentinels[0][:port] = s1_port
67
+ sentinels[1][:port] = s2_port
64
68
  redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
65
69
 
66
70
  assert redis.ping
@@ -94,8 +98,10 @@ class SentinalTest < Test::Unit::TestCase
94
98
  end
95
99
  }
96
100
 
97
- RedisMock.start(s1, {}, 26381) do
98
- RedisMock.start(s2, {}, 26382) do
101
+ RedisMock.start(s1) do |s1_port|
102
+ RedisMock.start(s2) do |s2_port|
103
+ sentinels[0][:port] = s1_port
104
+ sentinels[1][:port] = s2_port
99
105
  redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
100
106
 
101
107
  assert redis.ping
@@ -118,20 +124,22 @@ class SentinalTest < Test::Unit::TestCase
118
124
  :m1 => []
119
125
  }
120
126
 
121
- sentinel = {
122
- :auth => lambda do |pass|
123
- commands[:s1] << ["auth", pass]
124
- "-ERR unknown command 'auth'"
125
- end,
126
- :select => lambda do |db|
127
- commands[:s1] << ["select", db]
128
- "-ERR unknown command 'select'"
129
- end,
130
- :sentinel => lambda do |command, *args|
131
- commands[:s1] << [command, *args]
132
- ["127.0.0.1", "6382"]
133
- end
134
- }
127
+ sentinel = lambda do |port|
128
+ {
129
+ :auth => lambda do |pass|
130
+ commands[:s1] << ["auth", pass]
131
+ "-ERR unknown command 'auth'"
132
+ end,
133
+ :select => lambda do |db|
134
+ commands[:s1] << ["select", db]
135
+ "-ERR unknown command 'select'"
136
+ end,
137
+ :sentinel => lambda do |command, *args|
138
+ commands[:s1] << [command, *args]
139
+ ["127.0.0.1", port.to_s]
140
+ end
141
+ }
142
+ end
135
143
 
136
144
  master = {
137
145
  :auth => lambda do |pass|
@@ -144,8 +152,9 @@ class SentinalTest < Test::Unit::TestCase
144
152
  end
145
153
  }
146
154
 
147
- RedisMock.start(master, {}, 6382) do
148
- RedisMock.start(sentinel, {}, 26381) do
155
+ RedisMock.start(master) do |master_port|
156
+ RedisMock.start(sentinel.call(master_port)) do |sen_port|
157
+ sentinels[0][:port] = sen_port
149
158
  redis = Redis.new(:url => "redis://:foo@master1/15", :sentinels => sentinels, :role => :master)
150
159
 
151
160
  assert redis.ping
@@ -159,11 +168,13 @@ class SentinalTest < Test::Unit::TestCase
159
168
  def test_sentinel_role_mismatch
160
169
  sentinels = [{:host => "127.0.0.1", :port => 26381}]
161
170
 
162
- sentinel = {
163
- :sentinel => lambda do |command, *args|
164
- ["127.0.0.1", "6382"]
165
- end
166
- }
171
+ sentinel = lambda do |port|
172
+ {
173
+ :sentinel => lambda do |command, *args|
174
+ ["127.0.0.1", port.to_s]
175
+ end
176
+ }
177
+ end
167
178
 
168
179
  master = {
169
180
  :role => lambda do
@@ -172,8 +183,9 @@ class SentinalTest < Test::Unit::TestCase
172
183
  }
173
184
 
174
185
  ex = assert_raise(Redis::ConnectionError) do
175
- RedisMock.start(master, {}, 6382) do
176
- RedisMock.start(sentinel, {}, 26381) do
186
+ RedisMock.start(master) do |master_port|
187
+ RedisMock.start(sentinel.call(master_port)) do |sen_port|
188
+ sentinels[0][:port] = sen_port
177
189
  redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master)
178
190
 
179
191
  assert redis.ping
@@ -190,7 +202,7 @@ class SentinalTest < Test::Unit::TestCase
190
202
 
191
203
  connections = []
192
204
 
193
- handler = lambda do |id|
205
+ handler = lambda do |id, port|
194
206
  {
195
207
  :sentinel => lambda do |command, *args|
196
208
  connections << id
@@ -198,7 +210,7 @@ class SentinalTest < Test::Unit::TestCase
198
210
  if connections.count(id) < 2
199
211
  :close
200
212
  else
201
- ["127.0.0.1", "6382"]
213
+ ["127.0.0.1", port.to_s]
202
214
  end
203
215
  end
204
216
  }
@@ -210,9 +222,11 @@ class SentinalTest < Test::Unit::TestCase
210
222
  end
211
223
  }
212
224
 
213
- RedisMock.start(master, {}, 6382) do
214
- RedisMock.start(handler.call(:s1), {}, 26381) do
215
- RedisMock.start(handler.call(:s2), {}, 26382) do
225
+ RedisMock.start(master) do |master_port|
226
+ RedisMock.start(handler.call(:s1, master_port)) do |s1_port|
227
+ RedisMock.start(handler.call(:s2, master_port)) do |s2_port|
228
+ sentinels[0][:port] = s1_port
229
+ sentinels[1][:port] = s2_port
216
230
  redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 1)
217
231
 
218
232
  assert redis.ping
@@ -225,9 +239,9 @@ class SentinalTest < Test::Unit::TestCase
225
239
  connections.clear
226
240
 
227
241
  ex = assert_raise(Redis::CannotConnectError) do
228
- RedisMock.start(master, {}, 6382) do
229
- RedisMock.start(handler.call(:s1), {}, 26381) do
230
- RedisMock.start(handler.call(:s2), {}, 26382) do
242
+ RedisMock.start(master) do |master_port|
243
+ RedisMock.start(handler.call(:s1, master_port)) do |s1_port|
244
+ RedisMock.start(handler.call(:s2, master_port)) do |s2_port|
231
245
  redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 0)
232
246
 
233
247
  assert redis.ping
@@ -2,25 +2,21 @@ require "socket"
2
2
 
3
3
  module RedisMock
4
4
  class Server
5
- VERBOSE = false
5
+ def initialize(options = {}, &block)
6
+ @server = TCPServer.new(options[:host] || "127.0.0.1", 0)
7
+ @server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
8
+ end
6
9
 
7
- def initialize(port, options = {}, &block)
8
- @server = TCPServer.new(options[:host] || "127.0.0.1", port)
9
- @server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
10
+ def port
11
+ @server.addr[1]
10
12
  end
11
13
 
12
14
  def start(&block)
13
15
  @thread = Thread.new { run(&block) }
14
16
  end
15
17
 
16
- # Bail out of @server.accept before closing the socket. This is required
17
- # to avoid EADDRINUSE after a couple of iterations.
18
18
  def shutdown
19
- @thread.terminate if @thread
20
- @server.close if @server
21
- rescue => ex
22
- $stderr.puts "Error closing mock server: #{ex.message}" if VERBOSE
23
- $stderr.puts ex.backtrace if VERBOSE
19
+ @thread.kill
24
20
  end
25
21
 
26
22
  def run
@@ -35,20 +31,15 @@ module RedisMock
35
31
  end
36
32
  end
37
33
  rescue => ex
38
- $stderr.puts "Error running mock server: #{ex.message}" if VERBOSE
39
- $stderr.puts ex.backtrace if VERBOSE
34
+ $stderr.puts "Error running mock server: #{ex.message}"
35
+ $stderr.puts ex.backtrace
40
36
  retry
41
37
  ensure
42
- begin
43
- @server.close
44
- rescue IOError
45
- end
38
+ @server.close
46
39
  end
47
40
  end
48
41
  end
49
42
 
50
- MOCK_PORT = 6382
51
-
52
43
  # Starts a mock Redis server in a thread.
53
44
  #
54
45
  # The server will use the lambda handler passed as argument to handle
@@ -59,14 +50,13 @@ module RedisMock
59
50
  # # Every connection will be closed immediately
60
51
  # end
61
52
  #
62
- def self.start_with_handler(blk, options = {}, port = MOCK_PORT)
63
- server = Server.new(port, options)
53
+ def self.start_with_handler(blk, options = {})
54
+ server = Server.new(options)
55
+ port = server.port
64
56
 
65
57
  begin
66
58
  server.start(&blk)
67
-
68
59
  yield(port)
69
-
70
60
  ensure
71
61
  server.shutdown
72
62
  end
@@ -77,11 +67,11 @@ module RedisMock
77
67
  # The server will reply with a `+OK` to all commands, but you can
78
68
  # customize it by providing a hash. For example:
79
69
  #
80
- # RedisMock.start(:ping => lambda { "+PONG" }) do
81
- # assert_equal "PONG", Redis.new(:port => MOCK_PORT).ping
70
+ # RedisMock.start(:ping => lambda { "+PONG" }) do |port|
71
+ # assert_equal "PONG", Redis.new(:port => port).ping
82
72
  # end
83
73
  #
84
- def self.start(commands, options = {}, port = MOCK_PORT, &blk)
74
+ def self.start(commands, options = {}, &blk)
85
75
  handler = lambda do |session|
86
76
  while line = session.gets
87
77
  argv = Array.new(line[1..-3].to_i) do
@@ -106,8 +96,16 @@ module RedisMock
106
96
  break :close
107
97
  elsif response.is_a?(Array)
108
98
  session.write("*%d\r\n" % response.size)
109
- response.each do |e|
110
- session.write("$%d\r\n%s\r\n" % [e.length, e])
99
+
100
+ response.each do |resp|
101
+ if resp.is_a?(Array)
102
+ session.write("*%d\r\n" % resp.size)
103
+ resp.each do |r|
104
+ session.write("$%d\r\n%s\r\n" % [r.length, r])
105
+ end
106
+ else
107
+ session.write("$%d\r\n%s\r\n" % [resp.length, resp])
108
+ end
111
109
  end
112
110
  else
113
111
  session.write(response)
@@ -116,6 +114,6 @@ module RedisMock
116
114
  end
117
115
  end
118
116
 
119
- start_with_handler(handler, options, port, &blk)
117
+ start_with_handler(handler, options, &blk)
120
118
  end
121
119
  end
@@ -129,4 +129,10 @@ class TestUrlParam < Test::Unit::TestCase
129
129
 
130
130
  ENV.delete("REDIS_URL")
131
131
  end
132
+
133
+ def test_defaults_to_localhost
134
+ redis = Redis.new(:url => "redis:///")
135
+
136
+ assert_equal "127.0.0.1", redis.client.host
137
+ end
132
138
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -16,7 +16,7 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2015-02-11 00:00:00.000000000 Z
19
+ date: 2015-11-16 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: rake
@@ -32,6 +32,20 @@ dependencies:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
34
  version: '0'
35
+ - !ruby/object:Gem::Dependency
36
+ name: test-unit
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
35
49
  description: |2
36
50
  A Ruby client that tries to match Redis' API one-to-one, while still
37
51
  providing an idiomatic interface. It features thread-safety,
@@ -134,6 +148,7 @@ files:
134
148
  - test/remote_server_control_commands_test.rb
135
149
  - test/scanning_test.rb
136
150
  - test/scripting_test.rb
151
+ - test/sentinel_command_test.rb
137
152
  - test/sentinel_test.rb
138
153
  - test/sorting_test.rb
139
154
  - test/support/connection/hiredis.rb
@@ -168,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
183
  version: '0'
169
184
  requirements: []
170
185
  rubyforge_project:
171
- rubygems_version: 2.2.2
186
+ rubygems_version: 2.4.5.1
172
187
  signing_key:
173
188
  specification_version: 4
174
189
  summary: A Ruby client library for Redis
@@ -224,6 +239,7 @@ test_files:
224
239
  - test/remote_server_control_commands_test.rb
225
240
  - test/scanning_test.rb
226
241
  - test/scripting_test.rb
242
+ - test/sentinel_command_test.rb
227
243
  - test/sentinel_test.rb
228
244
  - test/sorting_test.rb
229
245
  - test/support/connection/hiredis.rb