gorsuch-redis 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/.gitignore +10 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +113 -0
  4. data/LICENSE +20 -0
  5. data/README.md +214 -0
  6. data/Rakefile +260 -0
  7. data/TODO.md +4 -0
  8. data/benchmarking/logging.rb +62 -0
  9. data/benchmarking/pipeline.rb +51 -0
  10. data/benchmarking/speed.rb +21 -0
  11. data/benchmarking/suite.rb +24 -0
  12. data/benchmarking/thread_safety.rb +38 -0
  13. data/benchmarking/worker.rb +71 -0
  14. data/examples/basic.rb +15 -0
  15. data/examples/dist_redis.rb +43 -0
  16. data/examples/incr-decr.rb +17 -0
  17. data/examples/list.rb +26 -0
  18. data/examples/pubsub.rb +31 -0
  19. data/examples/sets.rb +36 -0
  20. data/examples/unicorn/config.ru +3 -0
  21. data/examples/unicorn/unicorn.rb +20 -0
  22. data/lib/redis/client.rb +303 -0
  23. data/lib/redis/connection/command_helper.rb +44 -0
  24. data/lib/redis/connection/hiredis.rb +52 -0
  25. data/lib/redis/connection/registry.rb +12 -0
  26. data/lib/redis/connection/ruby.rb +136 -0
  27. data/lib/redis/connection/synchrony.rb +131 -0
  28. data/lib/redis/connection.rb +9 -0
  29. data/lib/redis/distributed.rb +696 -0
  30. data/lib/redis/errors.rb +38 -0
  31. data/lib/redis/hash_ring.rb +131 -0
  32. data/lib/redis/pipeline.rb +106 -0
  33. data/lib/redis/subscribe.rb +79 -0
  34. data/lib/redis/version.rb +3 -0
  35. data/lib/redis.rb +1724 -0
  36. data/redis.gemspec +43 -0
  37. data/test/command_map_test.rb +29 -0
  38. data/test/commands_on_hashes_test.rb +20 -0
  39. data/test/commands_on_lists_test.rb +60 -0
  40. data/test/commands_on_sets_test.rb +76 -0
  41. data/test/commands_on_sorted_sets_test.rb +108 -0
  42. data/test/commands_on_strings_test.rb +80 -0
  43. data/test/commands_on_value_types_test.rb +87 -0
  44. data/test/connection_handling_test.rb +204 -0
  45. data/test/db/.gitignore +1 -0
  46. data/test/distributed_blocking_commands_test.rb +53 -0
  47. data/test/distributed_commands_on_hashes_test.rb +11 -0
  48. data/test/distributed_commands_on_lists_test.rb +23 -0
  49. data/test/distributed_commands_on_sets_test.rb +84 -0
  50. data/test/distributed_commands_on_sorted_sets_test.rb +19 -0
  51. data/test/distributed_commands_on_strings_test.rb +49 -0
  52. data/test/distributed_commands_on_value_types_test.rb +72 -0
  53. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  54. data/test/distributed_connection_handling_test.rb +24 -0
  55. data/test/distributed_internals_test.rb +27 -0
  56. data/test/distributed_key_tags_test.rb +52 -0
  57. data/test/distributed_persistence_control_commands_test.rb +23 -0
  58. data/test/distributed_publish_subscribe_test.rb +100 -0
  59. data/test/distributed_remote_server_control_commands_test.rb +42 -0
  60. data/test/distributed_sorting_test.rb +21 -0
  61. data/test/distributed_test.rb +59 -0
  62. data/test/distributed_transactions_test.rb +33 -0
  63. data/test/encoding_test.rb +15 -0
  64. data/test/error_replies_test.rb +53 -0
  65. data/test/helper.rb +155 -0
  66. data/test/helper_test.rb +8 -0
  67. data/test/internals_test.rb +152 -0
  68. data/test/lint/hashes.rb +140 -0
  69. data/test/lint/internals.rb +36 -0
  70. data/test/lint/lists.rb +107 -0
  71. data/test/lint/sets.rb +90 -0
  72. data/test/lint/sorted_sets.rb +196 -0
  73. data/test/lint/strings.rb +133 -0
  74. data/test/lint/value_types.rb +81 -0
  75. data/test/persistence_control_commands_test.rb +21 -0
  76. data/test/pipelining_commands_test.rb +186 -0
  77. data/test/publish_subscribe_test.rb +158 -0
  78. data/test/redis_mock.rb +89 -0
  79. data/test/remote_server_control_commands_test.rb +88 -0
  80. data/test/sorting_test.rb +43 -0
  81. data/test/synchrony_driver.rb +57 -0
  82. data/test/test.conf +9 -0
  83. data/test/thread_safety_test.rb +30 -0
  84. data/test/transactions_test.rb +173 -0
  85. data/test/unknown_commands_test.rb +13 -0
  86. data/test/url_param_test.rb +59 -0
  87. metadata +236 -0
@@ -0,0 +1,89 @@
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
+ # Bail out of @server.accept before closing the socket. This is required
14
+ # to avoid EADDRINUSE after a couple of iterations.
15
+ def shutdown
16
+ @thread.terminate if @thread
17
+ @server.close if @server
18
+ rescue => ex
19
+ $stderr.puts "Error closing mock server: #{ex.message}" if VERBOSE
20
+ $stderr.puts ex.backtrace if VERBOSE
21
+ end
22
+
23
+ def run
24
+ loop do
25
+ session = @server.accept
26
+
27
+ begin
28
+ while line = session.gets
29
+ parts = Array.new(line[1..-3].to_i) do
30
+ bytes = session.gets[1..-3].to_i
31
+ argument = session.read(bytes)
32
+ session.read(2) # Discard \r\n
33
+ argument
34
+ end
35
+
36
+ response = yield(*parts)
37
+
38
+ # Convert a nil response to :close
39
+ response ||= :close
40
+
41
+ if response == :exit
42
+ session.shutdown(Socket::SHUT_RDWR)
43
+ return # exit server body
44
+ elsif response == :close
45
+ session.shutdown(Socket::SHUT_RDWR)
46
+ break # exit connection body
47
+ else
48
+ session.write(response)
49
+ session.write("\r\n") unless response.end_with?("\r\n")
50
+ end
51
+ end
52
+ rescue Errno::ECONNRESET
53
+ # Ignore client closing the connection
54
+ end
55
+ end
56
+ rescue => ex
57
+ $stderr.puts "Error running mock server: #{ex.message}" if VERBOSE
58
+ $stderr.puts ex.backtrace if VERBOSE
59
+ ensure
60
+ @server.close
61
+ end
62
+ end
63
+
64
+ module Helper
65
+ # Starts a mock Redis server in a thread on port 6380.
66
+ #
67
+ # The server will reply with a `+OK` to all commands, but you can
68
+ # customize it by providing a hash. For example:
69
+ #
70
+ # redis_mock(:ping => lambda { "+PONG" }) do
71
+ # assert_equal "PONG", Redis.new(:port => 6380).ping
72
+ # end
73
+ #
74
+ def redis_mock(replies = {})
75
+ begin
76
+ server = Server.new do |command, *args|
77
+ (replies[command.to_sym] || lambda { |*_| "+OK" }).call(*args)
78
+ end
79
+
80
+ sleep 0.1 # Give time for the socket to start listening.
81
+
82
+ yield
83
+
84
+ ensure
85
+ server.shutdown
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require File.expand_path("./redis_mock", File.dirname(__FILE__))
5
+
6
+ include RedisMock::Helper
7
+
8
+ setup do
9
+ init Redis.new(OPTIONS)
10
+ end
11
+
12
+ test "INFO" do |r|
13
+ %w(last_save_time redis_version total_connections_received connected_clients total_commands_processed connected_slaves uptime_in_seconds used_memory uptime_in_days changes_since_last_save).each do |x|
14
+ assert r.info.keys.include?(x)
15
+ end
16
+ end
17
+
18
+ test "INFO COMMANDSTATS" do |r|
19
+ # Only available on Redis >= 2.9.0
20
+ next if version(r) < 209000
21
+
22
+ r.config(:resetstat)
23
+ r.ping
24
+
25
+ result = r.info(:commandstats)
26
+ assert "1" == result["ping"]["calls"]
27
+ end
28
+
29
+ test "MONITOR" do |r|
30
+ log = []
31
+
32
+ wire = Wire.new do
33
+ Redis.new(OPTIONS).monitor do |line|
34
+ log << line
35
+ break if log.size == 3
36
+ end
37
+ end
38
+
39
+ Wire.pass while log.empty? # Faster than sleep
40
+
41
+ r.set "foo", "s1"
42
+
43
+ wire.join
44
+
45
+ assert log[-1][%q{(db 15) "set" "foo" "s1"}]
46
+ end
47
+
48
+ test "MONITOR returns value for break" do |r|
49
+ result = r.monitor do |line|
50
+ break line
51
+ end
52
+
53
+ assert result == "OK"
54
+ end
55
+
56
+ test "ECHO" do |r|
57
+ assert "foo bar baz\n" == r.echo("foo bar baz\n")
58
+ end
59
+
60
+ test "DEBUG" do |r|
61
+ r.set "foo", "s1"
62
+
63
+ assert r.debug(:object, "foo").kind_of?(String)
64
+ end
65
+
66
+ test "OBJECT" do |r|
67
+ r.lpush "list", "value"
68
+
69
+ assert r.object(:refcount, "list") == 1
70
+ assert r.object(:encoding, "list") == "ziplist"
71
+ assert r.object(:idletime, "list").kind_of?(Fixnum)
72
+ end
73
+
74
+ test "SYNC" do |r|
75
+ replies = {:sync => lambda { "+OK" }}
76
+
77
+ redis_mock(replies) do
78
+ redis = Redis.new(OPTIONS.merge(:port => 6380))
79
+
80
+ assert "OK" == redis.sync
81
+ end
82
+ end
83
+
84
+ test "SLOWLOG" do |r|
85
+ r.slowlog(:reset)
86
+ result = r.slowlog(:len)
87
+ assert result == 0
88
+ end
@@ -0,0 +1,43 @@
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 "SORT" do |r|
10
+ r.set("foo:1", "s1")
11
+ r.set("foo:2", "s2")
12
+
13
+ r.rpush("bar", "1")
14
+ r.rpush("bar", "2")
15
+
16
+ assert ["s1"] == r.sort("bar", :get => "foo:*", :limit => [0, 1])
17
+ assert ["s2"] == r.sort("bar", :get => "foo:*", :limit => [0, 1], :order => "desc alpha")
18
+ end
19
+
20
+ test "SORT with an array of GETs" do |r|
21
+ r.set("foo:1:a", "s1a")
22
+ r.set("foo:1:b", "s1b")
23
+
24
+ r.set("foo:2:a", "s2a")
25
+ r.set("foo:2:b", "s2b")
26
+
27
+ r.rpush("bar", "1")
28
+ r.rpush("bar", "2")
29
+
30
+ assert ["s1a", "s1b"] == r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1])
31
+ assert ["s2a", "s2b"] == r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1], :order => "desc alpha")
32
+ end
33
+
34
+ test "SORT with STORE" do |r|
35
+ r.set("foo:1", "s1")
36
+ r.set("foo:2", "s2")
37
+
38
+ r.rpush("bar", "1")
39
+ r.rpush("bar", "2")
40
+
41
+ r.sort("bar", :get => "foo:*", :store => "baz")
42
+ assert ["s1", "s2"] == r.lrange("baz", 0, -1)
43
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'em-synchrony'
4
+
5
+ require 'redis'
6
+ require 'redis/connection/synchrony'
7
+
8
+ require File.expand_path("./helper", File.dirname(__FILE__))
9
+
10
+ #
11
+ # if running under Eventmachine + Synchrony (Ruby 1.9+), then
12
+ # we can simulate the blocking API while performing the network
13
+ # IO via the EM reactor.
14
+ #
15
+
16
+ EM.synchrony do
17
+ r = Redis.new
18
+ r.flushdb
19
+
20
+ r.rpush "foo", "s1"
21
+ r.rpush "foo", "s2"
22
+
23
+ assert 2 == r.llen("foo")
24
+ assert "s2" == r.rpop("foo")
25
+
26
+ r.set("foo", "bar")
27
+
28
+ assert "bar" == r.getset("foo", "baz")
29
+ assert "baz" == r.get("foo")
30
+
31
+ r.set("foo", "a")
32
+
33
+ assert_equal 1, r.getbit("foo", 1)
34
+ assert_equal 1, r.getbit("foo", 2)
35
+ assert_equal 0, r.getbit("foo", 3)
36
+ assert_equal 0, r.getbit("foo", 4)
37
+ assert_equal 0, r.getbit("foo", 5)
38
+ assert_equal 0, r.getbit("foo", 6)
39
+ assert_equal 1, r.getbit("foo", 7)
40
+
41
+ r.flushdb
42
+
43
+ # command pipelining
44
+ r.pipelined do
45
+ r.lpush "foo", "s1"
46
+ r.lpush "foo", "s2"
47
+ end
48
+
49
+ assert 2 == r.llen("foo")
50
+ assert "s2" == r.lpop("foo")
51
+ assert "s1" == r.lpop("foo")
52
+
53
+ assert "OK" == r.client.call(:quit)
54
+ assert "PONG" == r.ping
55
+
56
+ EM.stop
57
+ end
data/test/test.conf ADDED
@@ -0,0 +1,9 @@
1
+ dir ./test/db
2
+ pidfile ./redis.pid
3
+ port 6379
4
+ unixsocket /tmp/redis.sock
5
+ timeout 300
6
+ loglevel debug
7
+ logfile stdout
8
+ databases 16
9
+ daemonize yes
@@ -0,0 +1,30 @@
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 "thread safety" do
10
+ redis = Redis.connect(OPTIONS.merge(:thread_safe => true))
11
+
12
+ redis.set "foo", 1
13
+ redis.set "bar", 2
14
+
15
+ sample = 100
16
+
17
+ t1 = Thread.new do
18
+ $foos = Array.new(sample) { redis.get "foo" }
19
+ end
20
+
21
+ t2 = Thread.new do
22
+ $bars = Array.new(sample) { redis.get "bar" }
23
+ end
24
+
25
+ t1.join
26
+ t2.join
27
+
28
+ assert_equal ["1"], $foos.uniq
29
+ assert_equal ["2"], $bars.uniq
30
+ end
@@ -0,0 +1,173 @@
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 "MULTI/DISCARD" do |r|
10
+ r.multi
11
+
12
+ assert "QUEUED" == r.set("foo", "1")
13
+ assert "QUEUED" == r.get("foo")
14
+
15
+ r.discard
16
+
17
+ assert nil == r.get("foo")
18
+ end
19
+
20
+ test "MULTI/EXEC with a block" do |r|
21
+ r.multi do |multi|
22
+ multi.set "foo", "s1"
23
+ end
24
+
25
+ assert "s1" == r.get("foo")
26
+ end
27
+
28
+ test "MULTI/EXEC with a block doesn't return replies for MULTI and EXEC" do |r|
29
+ r1, r2, nothing_else = r.multi do |multi|
30
+ multi.set "foo", "s1"
31
+ multi.get "foo"
32
+ end
33
+
34
+ assert_equal "OK", r1
35
+ assert_equal "s1", r2
36
+ assert_equal nil, nothing_else
37
+ end
38
+
39
+ test "Assignment inside MULTI/EXEC block" do |r|
40
+ r.multi do |m|
41
+ @first = m.sadd("foo", 1)
42
+ @second = m.sadd("foo", 1)
43
+ end
44
+
45
+ assert_equal true, @first.value
46
+ assert_equal false, @second.value
47
+ end
48
+
49
+ # Although we could support accessing the values in these futures,
50
+ # it doesn't make a lot of sense.
51
+ test "Assignment inside MULTI/EXEC block with delayed command errors" do |r|
52
+ assert_raise do
53
+ r.multi do |m|
54
+ @first = m.set("foo", "s1")
55
+ @second = m.incr("foo") # not an integer
56
+ @third = m.lpush("foo", "value") # wrong kind of value
57
+ end
58
+ end
59
+
60
+ assert_equal "OK", @first.value
61
+ assert_raise { @second.value }
62
+ assert_raise { @third.value }
63
+ end
64
+
65
+ test "Assignment inside MULTI/EXEC block with immediate command errors" do |r|
66
+ assert_raise do
67
+ r.multi do |m|
68
+ m.doesnt_exist
69
+ @first = m.sadd("foo", 1)
70
+ m.doesnt_exist
71
+ @second = m.sadd("foo", 1)
72
+ m.doesnt_exist
73
+ end
74
+ end
75
+
76
+ assert_raise(Redis::FutureNotReady) { @first.value }
77
+ assert_raise(Redis::FutureNotReady) { @second.value }
78
+ end
79
+
80
+ test "Raise immediate errors in MULTI/EXEC" do |r|
81
+ assert_raise(RuntimeError) do
82
+ r.multi do |multi|
83
+ multi.set "bar", "s2"
84
+ raise "Some error"
85
+ multi.set "baz", "s3"
86
+ end
87
+ end
88
+
89
+ assert nil == r.get("bar")
90
+ assert nil == r.get("baz")
91
+ end
92
+
93
+ test "Transformed replies as return values for MULTI/EXEC block" do |r|
94
+ info, _ = r.multi do |m|
95
+ r.info
96
+ end
97
+
98
+ assert info.kind_of?(Hash)
99
+ end
100
+
101
+ test "Transformed replies inside MULTI/EXEC block" do |r|
102
+ r.multi do |m|
103
+ @info = r.info
104
+ end
105
+
106
+ assert @info.value.kind_of?(Hash)
107
+ end
108
+
109
+ test "Raise command errors in MULTI/EXEC" do |r|
110
+ assert_raise do
111
+ r.multi do |m|
112
+ m.set("foo", "s1")
113
+ m.incr("foo") # not an integer
114
+ m.lpush("foo", "value") # wrong kind of value
115
+ end
116
+ end
117
+
118
+ assert "s1" == r.get("foo")
119
+ end
120
+
121
+ test "Raise command errors when accessing futures after MULTI/EXEC" do |r|
122
+ begin
123
+ r.multi do |m|
124
+ m.set("foo", "s1")
125
+ @counter = m.incr("foo") # not an integer
126
+ end
127
+ rescue Exception
128
+ # Not gonna deal with it
129
+ end
130
+
131
+ # We should test for Redis::Error here, but hiredis doesn't yet do
132
+ # custom error classes.
133
+ assert_raise(RuntimeError) { @counter.value }
134
+ end
135
+
136
+ test "MULTI with a block yielding the client" do |r|
137
+ r.multi do |multi|
138
+ multi.set "foo", "s1"
139
+ end
140
+
141
+ assert "s1" == r.get("foo")
142
+ end
143
+
144
+ test "WATCH with an unmodified key" do |r|
145
+ r.watch "foo"
146
+ r.multi do |multi|
147
+ multi.set "foo", "s1"
148
+ end
149
+
150
+ assert "s1" == r.get("foo")
151
+ end
152
+
153
+ test "WATCH with a modified key" do |r|
154
+ r.watch "foo"
155
+ r.set "foo", "s1"
156
+ res = r.multi do |multi|
157
+ multi.set "foo", "s2"
158
+ end
159
+
160
+ assert nil == res
161
+ assert "s1" == r.get("foo")
162
+ end
163
+
164
+ test "UNWATCH with a modified key" do |r|
165
+ r.watch "foo"
166
+ r.set "foo", "s1"
167
+ r.unwatch
168
+ r.multi do |multi|
169
+ multi.set "foo", "s2"
170
+ end
171
+
172
+ assert "s2" == r.get("foo")
173
+ end