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,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