gorsuch-redis 3.0.0.rc1

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 (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,42 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "INFO" do |r|
12
+ %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|
13
+ r.info.each do |info|
14
+ assert info.keys.include?(x)
15
+ end
16
+ end
17
+ end
18
+
19
+ test "INFO COMMANDSTATS" do |r|
20
+ # Only available on Redis >= 2.9.0
21
+ next if version(r) < 209000
22
+
23
+ r.nodes.each { |n| n.config(:resetstat) }
24
+ r.ping # Executed on every node
25
+
26
+ r.info(:commandstats).each do |info|
27
+ assert "1" == info["ping"]["calls"]
28
+ end
29
+ end
30
+
31
+ test "MONITOR" do |r|
32
+ begin
33
+ r.monitor
34
+ rescue Exception => ex
35
+ ensure
36
+ assert ex.kind_of?(NotImplementedError)
37
+ end
38
+ end
39
+
40
+ test "ECHO" do |r|
41
+ assert ["foo bar baz\n"] == r.echo("foo bar baz\n")
42
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "SORT" do |r|
12
+ assert_raise Redis::Distributed::CannotDistribute do
13
+ r.set("foo:1", "s1")
14
+ r.set("foo:2", "s2")
15
+
16
+ r.rpush("bar", "1")
17
+ r.rpush("bar", "2")
18
+
19
+ r.sort("bar", :get => "foo:*", :limit => [0, 1])
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "handle multiple servers" do
12
+ @r = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
13
+
14
+ 100.times do |idx|
15
+ @r.set(idx.to_s, "foo#{idx}")
16
+ end
17
+
18
+ 100.times do |idx|
19
+ assert "foo#{idx}" == @r.get(idx.to_s)
20
+ end
21
+
22
+ assert "0" == @r.keys("*").sort.first
23
+ assert "string" == @r.type("1")
24
+ end
25
+
26
+ test "add nodes" do
27
+ logger = Logger.new("/dev/null")
28
+
29
+ @r = Redis::Distributed.new NODES, :logger => logger, :timeout => 10
30
+
31
+ assert "127.0.0.1" == @r.nodes[0].client.host
32
+ assert 6379 == @r.nodes[0].client.port
33
+ assert 15 == @r.nodes[0].client.db
34
+ assert 10 == @r.nodes[0].client.timeout
35
+ assert logger == @r.nodes[0].client.logger
36
+
37
+ @r.add_node("redis://localhost:6380/14")
38
+
39
+ assert "localhost" == @r.nodes[1].client.host
40
+ assert 6380 == @r.nodes[1].client.port
41
+ assert 14 == @r.nodes[1].client.db
42
+ assert 10 == @r.nodes[1].client.timeout
43
+ assert logger == @r.nodes[1].client.logger
44
+ end
45
+
46
+ test "Pipelining commands cannot be distributed" do |r|
47
+ assert_raise Redis::Distributed::CannotDistribute do
48
+ r.pipelined do
49
+ r.lpush "foo", "s1"
50
+ r.lpush "foo", "s2"
51
+ end
52
+ end
53
+ end
54
+
55
+ test "Unknown commands does not work by default" do |r|
56
+ assert_raise NoMethodError do
57
+ r.not_yet_implemented_command
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "MULTI/DISCARD" do |r|
12
+ @foo = nil
13
+
14
+ assert_raise Redis::Distributed::CannotDistribute do
15
+ r.multi { @foo = 1 }
16
+ end
17
+
18
+ assert nil == @foo
19
+
20
+ assert_raise Redis::Distributed::CannotDistribute do
21
+ r.discard
22
+ end
23
+ end
24
+
25
+ test "WATCH/UNWATCH" do |r|
26
+ assert_raise Redis::Distributed::CannotDistribute do
27
+ r.watch("foo")
28
+ end
29
+
30
+ assert_raise Redis::Distributed::CannotDistribute do
31
+ r.unwatch
32
+ end
33
+ end
@@ -0,0 +1,15 @@
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 "returns properly encoded strings" do |r|
10
+ with_external_encoding("UTF-8") do
11
+ r.set "foo", "שלום"
12
+
13
+ assert "Shalom שלום" == "Shalom " + r.get("foo")
14
+ end
15
+ end if defined?(Encoding)
@@ -0,0 +1,53 @@
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
+ # Every test shouldn't disconnect from the server. Also, when error replies are
10
+ # in play, the protocol should never get into an invalid state where there are
11
+ # pending replies in the connection. Calling INFO after every test ensures that
12
+ # the protocol is still in a valid state.
13
+ def test_with_reconnection_check(title)
14
+ test(title) do |r|
15
+ before = r.info["total_connections_received"]
16
+ yield(r)
17
+ after = r.info["total_connections_received"]
18
+ assert before == after
19
+ end
20
+ end
21
+
22
+ test_with_reconnection_check "Error reply for single command" do |r|
23
+ begin
24
+ r.unknown_command
25
+ rescue => ex
26
+ ensure
27
+ assert ex.message =~ /unknown command/i
28
+ end
29
+ end
30
+
31
+ test_with_reconnection_check "Raise first error reply in pipeline" do |r|
32
+ begin
33
+ r.pipelined do
34
+ r.set("foo", "s1")
35
+ r.incr("foo") # not an integer
36
+ r.lpush("foo", "value") # wrong kind of value
37
+ end
38
+ rescue => ex
39
+ ensure
40
+ assert ex.message =~ /not an integer/i
41
+ end
42
+ end
43
+
44
+ test_with_reconnection_check "Recover from raise in #call_loop" do |r|
45
+ begin
46
+ r.client.call_loop([:invalid_monitor]) do
47
+ assert false # Should never be executed
48
+ end
49
+ rescue => ex
50
+ ensure
51
+ assert ex.message =~ /unknown command/i
52
+ end
53
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,155 @@
1
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ #require "cutest"
4
+ require "logger"
5
+ require "stringio"
6
+
7
+ begin
8
+ require "ruby-debug"
9
+ rescue LoadError
10
+ end
11
+
12
+ PORT = 6379
13
+ OPTIONS = {:port => PORT, :db => 15, :timeout => 3}
14
+ NODES = ["redis://127.0.0.1:6379/15"]
15
+
16
+ def init(redis)
17
+ begin
18
+ redis.flushdb
19
+ redis.select 14
20
+ redis.flushdb
21
+ redis.select 15
22
+ redis
23
+ rescue Redis::CannotConnectError
24
+ puts <<-EOS
25
+
26
+ Cannot connect to Redis.
27
+
28
+ Make sure Redis is running on localhost, port 6379.
29
+ This testing suite connects to the database 15.
30
+
31
+ To install redis:
32
+ visit <http://redis.io/download/>.
33
+
34
+ To start the server:
35
+ rake start
36
+
37
+ To stop the server:
38
+ rake stop
39
+
40
+ EOS
41
+ exit 1
42
+ end
43
+ end
44
+
45
+ $VERBOSE = true
46
+
47
+ require "redis/connection/%s" % (ENV["REDIS_CONNECTION_DRIVER"] || "ruby")
48
+ require "redis"
49
+
50
+ def driver
51
+ Redis::Connection.drivers.last.to_s.split("::").last.downcase.to_sym
52
+ end
53
+
54
+ if driver == :synchrony
55
+ # Make cutest fiber + eventmachine aware if the synchrony driver is used.
56
+ undef test if defined? test
57
+ def test(name = nil, &block)
58
+ cutest[:test] = name
59
+
60
+ blk = Proc.new do
61
+ prepare.each { |blk| blk.call }
62
+ block.call(setup && setup.call)
63
+ end
64
+
65
+ t = Thread.current[:cutest]
66
+ if defined? EventMachine
67
+ EM.synchrony do
68
+ Thread.current[:cutest] = t
69
+ blk.call
70
+ EM.stop
71
+ end
72
+ else
73
+ blk.call
74
+ end
75
+ end
76
+
77
+ class Wire < Fiber
78
+ # We cannot run this fiber explicitly because EM schedules it. Resuming the
79
+ # current fiber on the next tick to let the reactor do work.
80
+ def self.pass
81
+ f = Fiber.current
82
+ EM.next_tick { f.resume }
83
+ Fiber.yield
84
+ end
85
+
86
+ def self.sleep(sec)
87
+ EM::Synchrony.sleep(sec)
88
+ end
89
+
90
+ def initialize(&blk)
91
+ super
92
+
93
+ # Schedule run in next tick
94
+ EM.next_tick { resume }
95
+ end
96
+
97
+ def join
98
+ self.class.pass while alive?
99
+ end
100
+ end
101
+ else
102
+ class Wire < Thread
103
+ def self.sleep(sec)
104
+ Kernel.sleep(sec)
105
+ end
106
+ end
107
+ end
108
+
109
+ def capture_stderr
110
+ stderr = $stderr
111
+ $stderr = StringIO.new
112
+
113
+ yield
114
+
115
+ $stderr = stderr
116
+ end
117
+
118
+ def silent
119
+ verbose, $VERBOSE = $VERBOSE, false
120
+
121
+ begin
122
+ yield
123
+ ensure
124
+ $VERBOSE = verbose
125
+ end
126
+ end
127
+
128
+ def version(r)
129
+ info = r.info
130
+ info = info.first unless info.is_a?(Hash)
131
+ version_str_to_i info["redis_version"]
132
+ end
133
+
134
+ def version_str_to_i(version_str)
135
+ version_str.split(".").map{ |v| v.ljust(2, '0') }.join.to_i
136
+ end
137
+
138
+ def with_external_encoding(encoding)
139
+ original_encoding = Encoding.default_external
140
+
141
+ begin
142
+ silent { Encoding.default_external = Encoding.find(encoding) }
143
+ yield
144
+ ensure
145
+ silent { Encoding.default_external = original_encoding }
146
+ end
147
+ end
148
+
149
+ def assert_nothing_raised(*exceptions)
150
+ begin
151
+ yield
152
+ rescue *exceptions
153
+ flunk(caller[1])
154
+ end
155
+ end
@@ -0,0 +1,8 @@
1
+ require File.expand_path("./helper", File.dirname(__FILE__))
2
+
3
+ test "version_str_to_i" do
4
+ assert_equal 200000, version_str_to_i('2.0.0')
5
+ assert_equal 202020, version_str_to_i('2.2.2')
6
+ assert_equal 202022, version_str_to_i('2.2.22')
7
+ assert_equal 222222, version_str_to_i('22.22.22')
8
+ end
@@ -0,0 +1,152 @@
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
+ log = StringIO.new
10
+
11
+ [Redis.new(OPTIONS.merge(:logger => ::Logger.new(log))), log]
12
+ end
13
+
14
+ $TEST_PIPELINING = true
15
+
16
+ load File.expand_path("./lint/internals.rb", File.dirname(__FILE__))
17
+
18
+ test "provides a meaningful inspect" do |r, _|
19
+ assert "#<Redis client v#{Redis::VERSION} connected to redis://127.0.0.1:6379/15 (Redis v#{r.info["redis_version"]})>" == r.inspect
20
+ end
21
+
22
+ test "Redis.current" do
23
+ Redis.current.set("foo", "bar")
24
+
25
+ assert "bar" == Redis.current.get("foo")
26
+
27
+ Redis.current = Redis.new(OPTIONS.merge(:db => 14))
28
+
29
+ assert Redis.current.get("foo").nil?
30
+ end
31
+
32
+ test "Timeout" do
33
+ assert_nothing_raised do
34
+ Redis.new(OPTIONS.merge(:timeout => 0))
35
+ end
36
+ end
37
+
38
+ test "Connection timeout" do
39
+ next if driver == :synchrony
40
+
41
+ assert_raise Redis::CannotConnectError do
42
+ Redis.new(OPTIONS.merge(:host => "10.255.255.254", :timeout => 0.1)).ping
43
+ end
44
+ end
45
+
46
+ test "Retry when first read raises ECONNRESET" do
47
+ $request = 0
48
+
49
+ command = lambda do
50
+ case ($request += 1)
51
+ when 1; nil # Close on first command
52
+ else "+%d" % $request
53
+ end
54
+ end
55
+
56
+ redis_mock(:ping => command) do
57
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
58
+ assert "2" == redis.ping
59
+ end
60
+ end
61
+
62
+ test "Don't retry when wrapped inside #without_reconnect" do
63
+ $request = 0
64
+
65
+ command = lambda do
66
+ case ($request += 1)
67
+ when 1; nil # Close on first command
68
+ else "+%d" % $request
69
+ end
70
+ end
71
+
72
+ redis_mock(:ping => command) do
73
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
74
+ assert_raise Redis::ConnectionError do
75
+ redis.without_reconnect do
76
+ redis.ping
77
+ end
78
+ end
79
+
80
+ assert !redis.client.connected?
81
+ end
82
+ end
83
+
84
+ test "Retry only once when read raises ECONNRESET" do
85
+ $request = 0
86
+
87
+ command = lambda do
88
+ case ($request += 1)
89
+ when 1; nil # Close on first command
90
+ when 2; nil # Close on second command
91
+ else "+%d" % $request
92
+ end
93
+ end
94
+
95
+ redis_mock(:ping => command) do
96
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
97
+ assert_raise Redis::ConnectionError do
98
+ redis.ping
99
+ end
100
+
101
+ assert !redis.client.connected?
102
+ end
103
+ end
104
+
105
+ test "Don't retry when second read in pipeline raises ECONNRESET" do
106
+ $request = 0
107
+
108
+ command = lambda do
109
+ case ($request += 1)
110
+ when 2; nil # Close on second command
111
+ else "+%d" % $request
112
+ end
113
+ end
114
+
115
+ redis_mock(:ping => command) do
116
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
117
+ assert_raise Redis::ConnectionError do
118
+ redis.pipelined do
119
+ redis.ping
120
+ redis.ping # Second #read times out
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ test "Connecting to UNIX domain socket" do
127
+ assert_nothing_raised do
128
+ Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock")).ping
129
+ end
130
+ end
131
+
132
+ # if driver == :ruby || driver == :hiredis
133
+ # # Using a mock server in a thread doesn't work here (possibly because blocking
134
+ # # socket ops, raw socket timeouts and Ruby's thread scheduling don't mix).
135
+ # test "Bubble EAGAIN without retrying" do
136
+ # cmd = %{(sleep 0.3; echo "+PONG\r\n") | nc -l 6380}
137
+ # IO.popen(cmd) do |_|
138
+ # sleep 0.1 # Give nc a little time to start listening
139
+ # redis = Redis.connect(:port => 6380, :timeout => 0.1)
140
+ #
141
+ # begin
142
+ # assert_raise(Errno::EAGAIN) { redis.ping }
143
+ # ensure
144
+ # # Explicitly close connection so nc can quit
145
+ # redis.client.disconnect
146
+ #
147
+ # # Make the reactor loop do a tick to really close
148
+ # EM::Synchrony.sleep(0) if driver == :synchrony
149
+ # end
150
+ # end
151
+ # end
152
+ # end