redis 2.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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