redis 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. data/.gitignore +8 -0
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +190 -0
  4. data/Rakefile +194 -79
  5. data/benchmarking/logging.rb +62 -0
  6. data/benchmarking/pipeline.rb +51 -0
  7. data/benchmarking/speed.rb +21 -0
  8. data/benchmarking/suite.rb +24 -0
  9. data/benchmarking/thread_safety.rb +38 -0
  10. data/benchmarking/worker.rb +71 -0
  11. data/examples/basic.rb +15 -0
  12. data/examples/dist_redis.rb +43 -0
  13. data/examples/incr-decr.rb +17 -0
  14. data/examples/list.rb +26 -0
  15. data/examples/pubsub.rb +31 -0
  16. data/examples/sets.rb +36 -0
  17. data/examples/unicorn/config.ru +3 -0
  18. data/examples/unicorn/unicorn.rb +20 -0
  19. data/lib/redis.rb +612 -156
  20. data/lib/redis/client.rb +98 -57
  21. data/lib/redis/connection.rb +9 -134
  22. data/lib/redis/connection/command_helper.rb +45 -0
  23. data/lib/redis/connection/hiredis.rb +49 -0
  24. data/lib/redis/connection/registry.rb +12 -0
  25. data/lib/redis/connection/ruby.rb +131 -0
  26. data/lib/redis/connection/synchrony.rb +125 -0
  27. data/lib/redis/distributed.rb +161 -5
  28. data/lib/redis/pipeline.rb +6 -0
  29. data/lib/redis/version.rb +3 -0
  30. data/redis.gemspec +24 -0
  31. data/test/commands_on_hashes_test.rb +32 -0
  32. data/test/commands_on_lists_test.rb +60 -0
  33. data/test/commands_on_sets_test.rb +78 -0
  34. data/test/commands_on_sorted_sets_test.rb +109 -0
  35. data/test/commands_on_strings_test.rb +80 -0
  36. data/test/commands_on_value_types_test.rb +88 -0
  37. data/test/connection_handling_test.rb +87 -0
  38. data/test/db/.gitignore +1 -0
  39. data/test/distributed_blocking_commands_test.rb +53 -0
  40. data/test/distributed_commands_on_hashes_test.rb +12 -0
  41. data/test/distributed_commands_on_lists_test.rb +24 -0
  42. data/test/distributed_commands_on_sets_test.rb +85 -0
  43. data/test/distributed_commands_on_strings_test.rb +50 -0
  44. data/test/distributed_commands_on_value_types_test.rb +73 -0
  45. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  46. data/test/distributed_connection_handling_test.rb +25 -0
  47. data/test/distributed_internals_test.rb +18 -0
  48. data/test/distributed_key_tags_test.rb +53 -0
  49. data/test/distributed_persistence_control_commands_test.rb +24 -0
  50. data/test/distributed_publish_subscribe_test.rb +101 -0
  51. data/test/distributed_remote_server_control_commands_test.rb +31 -0
  52. data/test/distributed_sorting_test.rb +21 -0
  53. data/test/distributed_test.rb +60 -0
  54. data/test/distributed_transactions_test.rb +34 -0
  55. data/test/encoding_test.rb +16 -0
  56. data/test/error_replies_test.rb +53 -0
  57. data/test/helper.rb +145 -0
  58. data/test/internals_test.rb +157 -0
  59. data/test/lint/hashes.rb +114 -0
  60. data/test/lint/internals.rb +41 -0
  61. data/test/lint/lists.rb +93 -0
  62. data/test/lint/sets.rb +66 -0
  63. data/test/lint/sorted_sets.rb +167 -0
  64. data/test/lint/strings.rb +137 -0
  65. data/test/lint/value_types.rb +84 -0
  66. data/test/persistence_control_commands_test.rb +22 -0
  67. data/test/pipelining_commands_test.rb +123 -0
  68. data/test/publish_subscribe_test.rb +158 -0
  69. data/test/redis_mock.rb +80 -0
  70. data/test/remote_server_control_commands_test.rb +63 -0
  71. data/test/sorting_test.rb +44 -0
  72. data/test/synchrony_driver.rb +57 -0
  73. data/test/test.conf +8 -0
  74. data/test/thread_safety_test.rb +30 -0
  75. data/test/transactions_test.rb +100 -0
  76. data/test/unknown_commands_test.rb +14 -0
  77. data/test/url_param_test.rb +60 -0
  78. metadata +128 -19
  79. data/README.markdown +0 -129
@@ -0,0 +1,84 @@
1
+ test "EXISTS" do |r|
2
+ assert false == r.exists("foo")
3
+
4
+ r.set("foo", "s1")
5
+
6
+ assert true == r.exists("foo")
7
+ end
8
+
9
+ test "TYPE" do |r|
10
+ assert "none" == r.type("foo")
11
+
12
+ r.set("foo", "s1")
13
+
14
+ assert "string" == r.type("foo")
15
+ end
16
+
17
+ test "KEYS" do |r|
18
+ r.set("f", "s1")
19
+ r.set("fo", "s2")
20
+ r.set("foo", "s3")
21
+
22
+ assert ["f","fo", "foo"] == r.keys("f*").sort
23
+ end
24
+
25
+ test "EXPIRE" do |r|
26
+ r.set("foo", "s1")
27
+ r.expire("foo", 1)
28
+
29
+ assert "s1" == r.get("foo")
30
+
31
+ sleep 2
32
+
33
+ assert nil == r.get("foo")
34
+ end
35
+
36
+ test "EXPIREAT" do |r|
37
+ r.set("foo", "s1")
38
+ r.expireat("foo", Time.now.to_i + 1)
39
+
40
+ assert "s1" == r.get("foo")
41
+
42
+ sleep 2
43
+
44
+ assert nil == r.get("foo")
45
+ end
46
+
47
+ test "PERSIST" do |r|
48
+ r.set("foo", "s1")
49
+ r.expire("foo", 1)
50
+ r.persist("foo")
51
+
52
+ assert(-1 == r.ttl("foo"))
53
+ end
54
+
55
+ test "TTL" do |r|
56
+ r.set("foo", "s1")
57
+ r.expire("foo", 1)
58
+
59
+ assert 1 == r.ttl("foo")
60
+ end
61
+
62
+ test "MOVE" do |r|
63
+ r.select 14
64
+ r.flushdb
65
+
66
+ r.set "bar", "s3"
67
+
68
+ r.select 15
69
+
70
+ r.set "foo", "s1"
71
+ r.set "bar", "s2"
72
+
73
+ assert r.move("foo", 14)
74
+ assert nil == r.get("foo")
75
+
76
+ assert !r.move("bar", 14)
77
+ assert "s2" == r.get("bar")
78
+
79
+ r.select 14
80
+
81
+ assert "s1" == r.get("foo")
82
+ assert "s3" == r.get("bar")
83
+ end
84
+
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+
5
+ setup do
6
+ init Redis.new(OPTIONS)
7
+ end
8
+
9
+ test "SAVE and BGSAVE" do |r|
10
+ assert_nothing_raised do
11
+ r.save
12
+ end
13
+
14
+ assert_nothing_raised do
15
+ r.bgsave
16
+ end
17
+ end
18
+
19
+ test "LASTSAVE" do |r|
20
+ assert Time.at(r.lastsave) <= Time.now
21
+ end
22
+
@@ -0,0 +1,123 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+
5
+ setup do
6
+ init Redis.new(OPTIONS)
7
+ end
8
+
9
+ test "BULK commands" do |r|
10
+ r.pipelined do
11
+ r.lpush "foo", "s1"
12
+ r.lpush "foo", "s2"
13
+ end
14
+
15
+ assert 2 == r.llen("foo")
16
+ assert "s2" == r.lpop("foo")
17
+ assert "s1" == r.lpop("foo")
18
+ end
19
+
20
+ test "MULTI_BULK commands" do |r|
21
+ r.pipelined do
22
+ r.mset("foo", "s1", "bar", "s2")
23
+ r.mset("baz", "s3", "qux", "s4")
24
+ end
25
+
26
+ assert "s1" == r.get("foo")
27
+ assert "s2" == r.get("bar")
28
+ assert "s3" == r.get("baz")
29
+ assert "s4" == r.get("qux")
30
+ end
31
+
32
+ test "BULK and MULTI_BULK commands mixed" do |r|
33
+ r.pipelined do
34
+ r.lpush "foo", "s1"
35
+ r.lpush "foo", "s2"
36
+ r.mset("baz", "s3", "qux", "s4")
37
+ end
38
+
39
+ assert 2 == r.llen("foo")
40
+ assert "s2" == r.lpop("foo")
41
+ assert "s1" == r.lpop("foo")
42
+ assert "s3" == r.get("baz")
43
+ assert "s4" == r.get("qux")
44
+ end
45
+
46
+ test "MULTI_BULK and BULK commands mixed" do |r|
47
+ r.pipelined do
48
+ r.mset("baz", "s3", "qux", "s4")
49
+ r.lpush "foo", "s1"
50
+ r.lpush "foo", "s2"
51
+ end
52
+
53
+ assert 2 == r.llen("foo")
54
+ assert "s2" == r.lpop("foo")
55
+ assert "s1" == r.lpop("foo")
56
+ assert "s3" == r.get("baz")
57
+ assert "s4" == r.get("qux")
58
+ end
59
+
60
+ test "Pipelined with an empty block" do |r|
61
+ assert_nothing_raised do
62
+ r.pipelined do
63
+ end
64
+ end
65
+
66
+ assert 0 == r.dbsize
67
+ end
68
+
69
+ test "Returning the result of a pipeline" do |r|
70
+ result = r.pipelined do
71
+ r.set "foo", "bar"
72
+ r.get "foo"
73
+ r.get "bar"
74
+ end
75
+
76
+ assert ["OK", "bar", nil] == result
77
+ end
78
+
79
+ test "Nesting pipeline blocks" do |r|
80
+ r.pipelined do
81
+ r.set("foo", "s1")
82
+ r.pipelined do
83
+ r.set("bar", "s2")
84
+ end
85
+ end
86
+
87
+ assert "s1" == r.get("foo")
88
+ assert "s2" == r.get("bar")
89
+ end
90
+
91
+ test "INFO in a pipeline" do |r|
92
+ result = r.pipelined do
93
+ r.info
94
+ end
95
+
96
+ assert result.first.kind_of?(String)
97
+ end
98
+
99
+ test "CONFIG GET in a pipeline" do |r|
100
+ result = r.pipelined do
101
+ r.config(:get, "*")
102
+ end
103
+
104
+ assert result.first.kind_of?(Array)
105
+ end
106
+
107
+ test "HGETALL in a pipeline should not return hash" do |r|
108
+ r.hmset("hash", "field", "value")
109
+ result = r.pipelined do
110
+ r.hgetall("hash")
111
+ end
112
+
113
+ assert ["field", "value"] == result.first
114
+ end
115
+
116
+ test "KEYS in a pipeline" do |r|
117
+ r.set("key", "value")
118
+ result = r.pipelined do
119
+ r.keys("*")
120
+ end
121
+
122
+ assert ["key"] == result.first
123
+ end
@@ -0,0 +1,158 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+
5
+ setup do
6
+ init Redis.new(OPTIONS)
7
+ end
8
+
9
+ test "SUBSCRIBE and UNSUBSCRIBE" do |r|
10
+ listening = false
11
+
12
+ wire = Wire.new do
13
+ r.subscribe("foo") do |on|
14
+ on.subscribe do |channel, total|
15
+ @subscribed = true
16
+ @t1 = total
17
+ end
18
+
19
+ on.message do |channel, message|
20
+ if message == "s1"
21
+ r.unsubscribe
22
+ @message = message
23
+ end
24
+ end
25
+
26
+ on.unsubscribe do |channel, total|
27
+ @unsubscribed = true
28
+ @t2 = total
29
+ end
30
+
31
+ listening = true
32
+ end
33
+ end
34
+
35
+ Wire.pass while !listening
36
+
37
+ Redis.new(OPTIONS).publish("foo", "s1")
38
+
39
+ wire.join
40
+
41
+ assert @subscribed
42
+ assert 1 == @t1
43
+ assert @unsubscribed
44
+ assert 0 == @t2
45
+ assert "s1" == @message
46
+ end
47
+
48
+ test "PSUBSCRIBE and PUNSUBSCRIBE" do |r|
49
+ listening = false
50
+
51
+ wire = Wire.new do
52
+ r.psubscribe("f*") do |on|
53
+ on.psubscribe do |pattern, total|
54
+ @subscribed = true
55
+ @t1 = total
56
+ end
57
+
58
+ on.pmessage do |pattern, channel, message|
59
+ if message == "s1"
60
+ r.punsubscribe
61
+ @message = message
62
+ end
63
+ end
64
+
65
+ on.punsubscribe do |pattern, total|
66
+ @unsubscribed = true
67
+ @t2 = total
68
+ end
69
+
70
+ listening = true
71
+ end
72
+ end
73
+
74
+ Wire.pass while !listening
75
+
76
+ Redis.new(OPTIONS).publish("foo", "s1")
77
+
78
+ wire.join
79
+
80
+ assert @subscribed
81
+ assert 1 == @t1
82
+ assert @unsubscribed
83
+ assert 0 == @t2
84
+ assert "s1" == @message
85
+ end
86
+
87
+ test "SUBSCRIBE within SUBSCRIBE" do |r|
88
+ listening = false
89
+
90
+ @channels = []
91
+
92
+ wire = Wire.new do
93
+ r.subscribe("foo") do |on|
94
+ on.subscribe do |channel, total|
95
+ @channels << channel
96
+
97
+ r.subscribe("bar") if channel == "foo"
98
+ r.unsubscribe if channel == "bar"
99
+ end
100
+
101
+ listening = true
102
+ end
103
+ end
104
+
105
+ Wire.pass while !listening
106
+
107
+ Redis.new(OPTIONS).publish("foo", "s1")
108
+
109
+ wire.join
110
+
111
+ assert ["foo", "bar"] == @channels
112
+ end
113
+
114
+ test "other commands within a SUBSCRIBE" do |r|
115
+ assert_raise RuntimeError do
116
+ r.subscribe("foo") do |on|
117
+ on.subscribe do |channel, total|
118
+ r.set("bar", "s2")
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ test "SUBSCRIBE without a block" do |r|
125
+ assert_raise LocalJumpError do
126
+ r.subscribe("foo")
127
+ end
128
+ end
129
+
130
+ test "UNSUBSCRIBE without a SUBSCRIBE" do |r|
131
+ assert_raise RuntimeError do
132
+ r.unsubscribe
133
+ end
134
+
135
+ assert_raise RuntimeError do
136
+ r.punsubscribe
137
+ end
138
+ end
139
+
140
+ test "SUBSCRIBE past a timeout" do |r|
141
+ # For some reason, a thread here doesn't reproduce the issue.
142
+ sleep = %{sleep #{OPTIONS[:timeout] + 1}}
143
+ publish = %{echo "publish foo bar\r\n" | nc localhost #{OPTIONS[:port]}}
144
+ cmd = [sleep, publish].join("; ")
145
+
146
+ IO.popen(cmd, "r+") do |pipe|
147
+ received = false
148
+
149
+ r.subscribe "foo" do |on|
150
+ on.message do |channel, message|
151
+ received = true
152
+ r.unsubscribe
153
+ end
154
+ end
155
+
156
+ assert received
157
+ end
158
+ end
@@ -0,0 +1,80 @@
1
+ require "socket"
2
+
3
+ module RedisMock
4
+ class Server
5
+ VERBOSE = false
6
+
7
+ def initialize(port = 6380, &block)
8
+ @server = TCPServer.new("127.0.0.1", port)
9
+ @server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
10
+ @thread = Thread.new { run(&block) }
11
+ end
12
+
13
+ # This raises an exception in the thread calling @server.accept which
14
+ # in turn will cause the thread to terminate.
15
+ def shutdown
16
+ @server.close if @server
17
+ rescue => ex
18
+ $stderr.puts "Error closing mock server: #{ex.message}" if VERBOSE
19
+ $stderr.puts ex.backtrace if VERBOSE
20
+ end
21
+
22
+ def run
23
+ loop do
24
+ session = @server.accept
25
+
26
+ begin
27
+ while line = session.gets
28
+ parts = Array.new(line[1..-3].to_i) do
29
+ bytes = session.gets[1..-3].to_i
30
+ argument = session.read(bytes)
31
+ session.read(2) # Discard \r\n
32
+ argument
33
+ end
34
+
35
+ response = yield(*parts)
36
+
37
+ if response.nil?
38
+ session.shutdown(Socket::SHUT_RDWR)
39
+ break
40
+ else
41
+ session.write(response)
42
+ session.write("\r\n")
43
+ end
44
+ end
45
+ rescue Errno::ECONNRESET
46
+ # Ignore client closing the connection
47
+ end
48
+ end
49
+ rescue => ex
50
+ $stderr.puts "Error running mock server: #{ex.message}" if VERBOSE
51
+ $stderr.puts ex.backtrace if VERBOSE
52
+ end
53
+ end
54
+
55
+ module Helper
56
+ # Starts a mock Redis server in a thread on port 6380.
57
+ #
58
+ # The server will reply with a `+OK` to all commands, but you can
59
+ # customize it by providing a hash. For example:
60
+ #
61
+ # redis_mock(:ping => lambda { "+PONG" }) do
62
+ # assert_equal "PONG", Redis.new(:port => 6380).ping
63
+ # end
64
+ #
65
+ def redis_mock(replies = {})
66
+ begin
67
+ server = Server.new do |command, *args|
68
+ (replies[command.to_sym] || lambda { |*_| "+OK" }).call(*args)
69
+ end
70
+
71
+ sleep 0.1 # Give time for the socket to start listening.
72
+
73
+ yield
74
+
75
+ ensure
76
+ server.shutdown
77
+ end
78
+ end
79
+ end
80
+ end