redis 3.3.5 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis/Gemfile +8 -1
  4. data/.travis.yml +34 -62
  5. data/CHANGELOG.md +45 -2
  6. data/Gemfile +5 -1
  7. data/README.md +32 -76
  8. data/benchmarking/logging.rb +1 -1
  9. data/bin/build +71 -0
  10. data/bors.toml +14 -0
  11. data/lib/redis/client.rb +38 -20
  12. data/lib/redis/cluster/command.rb +81 -0
  13. data/lib/redis/cluster/command_loader.rb +32 -0
  14. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  15. data/lib/redis/cluster/node.rb +104 -0
  16. data/lib/redis/cluster/node_key.rb +35 -0
  17. data/lib/redis/cluster/node_loader.rb +35 -0
  18. data/lib/redis/cluster/option.rb +76 -0
  19. data/lib/redis/cluster/slot.rb +69 -0
  20. data/lib/redis/cluster/slot_loader.rb +47 -0
  21. data/lib/redis/cluster.rb +285 -0
  22. data/lib/redis/connection/command_helper.rb +2 -8
  23. data/lib/redis/connection/hiredis.rb +2 -2
  24. data/lib/redis/connection/ruby.rb +13 -30
  25. data/lib/redis/connection/synchrony.rb +12 -4
  26. data/lib/redis/connection.rb +2 -2
  27. data/lib/redis/distributed.rb +29 -8
  28. data/lib/redis/errors.rb +46 -0
  29. data/lib/redis/hash_ring.rb +20 -64
  30. data/lib/redis/pipeline.rb +9 -7
  31. data/lib/redis/version.rb +1 -1
  32. data/lib/redis.rb +287 -52
  33. data/makefile +74 -0
  34. data/redis.gemspec +9 -10
  35. data/test/bitpos_test.rb +13 -19
  36. data/test/blocking_commands_test.rb +3 -5
  37. data/test/client_test.rb +18 -1
  38. data/test/cluster_abnormal_state_test.rb +38 -0
  39. data/test/cluster_blocking_commands_test.rb +15 -0
  40. data/test/cluster_client_internals_test.rb +77 -0
  41. data/test/cluster_client_key_hash_tags_test.rb +88 -0
  42. data/test/cluster_client_options_test.rb +147 -0
  43. data/test/cluster_client_pipelining_test.rb +59 -0
  44. data/test/cluster_client_replicas_test.rb +36 -0
  45. data/test/cluster_client_slots_test.rb +94 -0
  46. data/test/cluster_client_transactions_test.rb +71 -0
  47. data/test/cluster_commands_on_cluster_test.rb +165 -0
  48. data/test/cluster_commands_on_connection_test.rb +40 -0
  49. data/test/cluster_commands_on_geo_test.rb +74 -0
  50. data/test/cluster_commands_on_hashes_test.rb +11 -0
  51. data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
  52. data/test/cluster_commands_on_keys_test.rb +134 -0
  53. data/test/cluster_commands_on_lists_test.rb +15 -0
  54. data/test/cluster_commands_on_pub_sub_test.rb +101 -0
  55. data/test/cluster_commands_on_scripting_test.rb +56 -0
  56. data/test/cluster_commands_on_server_test.rb +221 -0
  57. data/test/cluster_commands_on_sets_test.rb +39 -0
  58. data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
  59. data/test/cluster_commands_on_streams_test.rb +196 -0
  60. data/test/cluster_commands_on_strings_test.rb +15 -0
  61. data/test/cluster_commands_on_transactions_test.rb +41 -0
  62. data/test/cluster_commands_on_value_types_test.rb +14 -0
  63. data/test/command_map_test.rb +3 -5
  64. data/test/commands_on_geo_test.rb +116 -0
  65. data/test/commands_on_hashes_test.rb +2 -16
  66. data/test/commands_on_hyper_log_log_test.rb +3 -17
  67. data/test/commands_on_lists_test.rb +2 -15
  68. data/test/commands_on_sets_test.rb +2 -72
  69. data/test/commands_on_sorted_sets_test.rb +2 -132
  70. data/test/commands_on_strings_test.rb +2 -96
  71. data/test/commands_on_value_types_test.rb +80 -6
  72. data/test/connection_handling_test.rb +5 -7
  73. data/test/distributed_blocking_commands_test.rb +10 -4
  74. data/test/distributed_commands_on_hashes_test.rb +16 -5
  75. data/test/distributed_commands_on_hyper_log_log_test.rb +8 -15
  76. data/test/distributed_commands_on_lists_test.rb +4 -7
  77. data/test/distributed_commands_on_sets_test.rb +58 -36
  78. data/test/distributed_commands_on_sorted_sets_test.rb +51 -10
  79. data/test/distributed_commands_on_strings_test.rb +30 -10
  80. data/test/distributed_commands_on_value_types_test.rb +38 -4
  81. data/test/distributed_commands_requiring_clustering_test.rb +1 -3
  82. data/test/distributed_connection_handling_test.rb +1 -3
  83. data/test/distributed_internals_test.rb +8 -19
  84. data/test/distributed_key_tags_test.rb +4 -6
  85. data/test/distributed_persistence_control_commands_test.rb +1 -3
  86. data/test/distributed_publish_subscribe_test.rb +1 -3
  87. data/test/distributed_remote_server_control_commands_test.rb +1 -3
  88. data/test/distributed_scripting_test.rb +1 -3
  89. data/test/distributed_sorting_test.rb +1 -3
  90. data/test/distributed_test.rb +12 -14
  91. data/test/distributed_transactions_test.rb +1 -3
  92. data/test/encoding_test.rb +4 -8
  93. data/test/error_replies_test.rb +2 -4
  94. data/test/fork_safety_test.rb +1 -6
  95. data/test/helper.rb +179 -66
  96. data/test/helper_test.rb +1 -3
  97. data/test/internals_test.rb +47 -56
  98. data/test/lint/blocking_commands.rb +40 -16
  99. data/test/lint/hashes.rb +41 -0
  100. data/test/lint/hyper_log_log.rb +15 -1
  101. data/test/lint/lists.rb +16 -0
  102. data/test/lint/sets.rb +142 -0
  103. data/test/lint/sorted_sets.rb +183 -2
  104. data/test/lint/strings.rb +108 -20
  105. data/test/lint/value_types.rb +8 -0
  106. data/test/persistence_control_commands_test.rb +1 -3
  107. data/test/pipelining_commands_test.rb +12 -8
  108. data/test/publish_subscribe_test.rb +1 -3
  109. data/test/remote_server_control_commands_test.rb +60 -3
  110. data/test/scanning_test.rb +1 -7
  111. data/test/scripting_test.rb +1 -3
  112. data/test/sentinel_command_test.rb +1 -3
  113. data/test/sentinel_test.rb +1 -3
  114. data/test/sorting_test.rb +1 -3
  115. data/test/ssl_test.rb +45 -49
  116. data/test/support/cluster/orchestrator.rb +199 -0
  117. data/test/support/connection/hiredis.rb +1 -1
  118. data/test/support/connection/ruby.rb +1 -1
  119. data/test/support/connection/synchrony.rb +1 -1
  120. data/test/support/redis_mock.rb +1 -1
  121. data/test/synchrony_driver.rb +6 -9
  122. data/test/thread_safety_test.rb +1 -3
  123. data/test/transactions_test.rb +11 -3
  124. data/test/unknown_commands_test.rb +1 -3
  125. data/test/url_param_test.rb +44 -46
  126. metadata +109 -16
  127. data/Rakefile +0 -87
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class TestPublishSubscribe < Test::Unit::TestCase
6
4
 
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class TestRemoteServerControlCommands < Test::Unit::TestCase
6
4
 
@@ -115,4 +113,63 @@ class TestRemoteServerControlCommands < Test::Unit::TestCase
115
113
  result = r.slowlog(:len)
116
114
  assert_equal 0, result
117
115
  end
116
+
117
+ def test_client
118
+ assert_equal r.instance_variable_get(:@client), r._client
119
+ end
120
+
121
+ def test_client_list
122
+ return if version < "2.4.0"
123
+
124
+ keys = [
125
+ "addr",
126
+ "fd",
127
+ "name",
128
+ "age",
129
+ "idle",
130
+ "flags",
131
+ "db",
132
+ "sub",
133
+ "psub",
134
+ "multi",
135
+ "qbuf",
136
+ "qbuf-free",
137
+ "obl",
138
+ "oll",
139
+ "omem",
140
+ "events",
141
+ "cmd"
142
+ ]
143
+
144
+ clients = r.client(:list)
145
+ clients.each do |client|
146
+ keys.each do |k|
147
+ msg = "expected #client(:list) to include #{k}"
148
+ assert client.keys.include?(k), msg
149
+ end
150
+ end
151
+ end
152
+
153
+ def test_client_kill
154
+ return if version < "2.6.9"
155
+
156
+ r.client(:setname, 'redis-rb')
157
+ clients = r.client(:list)
158
+ i = clients.index {|client| client['name'] == 'redis-rb'}
159
+ assert_equal "OK", r.client(:kill, clients[i]["addr"])
160
+
161
+ clients = r.client(:list)
162
+ i = clients.index {|client| client['name'] == 'redis-rb'}
163
+ assert_equal nil, i
164
+ end
165
+
166
+ def test_client_getname_and_setname
167
+ return if version < "2.6.9"
168
+
169
+ assert_equal nil, r.client(:getname)
170
+
171
+ r.client(:setname, 'redis-rb')
172
+ name = r.client(:getname)
173
+ assert_equal 'redis-rb', name
174
+ end
118
175
  end
@@ -1,10 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
4
-
5
- unless defined?(Enumerator)
6
- Enumerator = Enumerable::Enumerator
7
- end
1
+ require_relative "helper"
8
2
 
9
3
  class TestScanning < Test::Unit::TestCase
10
4
 
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class TestScripting < Test::Unit::TestCase
6
4
 
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class SentinelCommandsTest < Test::Unit::TestCase
6
4
 
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class SentinelTest < Test::Unit::TestCase
6
4
 
data/test/sorting_test.rb CHANGED
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class TestSorting < Test::Unit::TestCase
6
4
 
data/test/ssl_test.rb CHANGED
@@ -1,73 +1,69 @@
1
- # encoding: UTF-8
1
+ require_relative "helper"
2
2
 
3
- if RUBY_VERSION >= "1.9.3"
4
- require File.expand_path("helper", File.dirname(__FILE__))
3
+ class SslTest < Test::Unit::TestCase
5
4
 
6
- class SslTest < Test::Unit::TestCase
5
+ include Helper::Client
7
6
 
8
- include Helper::Client
7
+ driver(:ruby) do
9
8
 
10
- driver(:ruby) do
11
-
12
- def test_verified_ssl_connection
13
- RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("trusted")) do |port|
14
- redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file })
15
- assert_equal redis.ping, "PONG"
16
- end
17
- end
18
-
19
- def test_unverified_ssl_connection
20
- assert_raise(OpenSSL::SSL::SSLError) do
21
- RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("untrusted")) do |port|
22
- redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file })
23
- redis.ping
24
- end
25
- end
9
+ def test_verified_ssl_connection
10
+ RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("trusted")) do |port|
11
+ redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file })
12
+ assert_equal redis.ping, "PONG"
26
13
  end
14
+ end
27
15
 
28
- def test_ssl_blocking
29
- RedisMock.start({}, ssl_server_opts("trusted")) do |port|
16
+ def test_unverified_ssl_connection
17
+ assert_raise(OpenSSL::SSL::SSLError) do
18
+ RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("untrusted")) do |port|
30
19
  redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file })
31
- assert_equal redis.set("boom", "a" * 10_000_000), "OK"
20
+ redis.ping
32
21
  end
33
22
  end
23
+ end
34
24
 
25
+ def test_ssl_blocking
26
+ RedisMock.start({}, ssl_server_opts("trusted")) do |port|
27
+ redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file })
28
+ assert_equal redis.set("boom", "a" * 10_000_000), "OK"
29
+ end
35
30
  end
36
31
 
37
- driver(:hiredis, :synchrony) do
32
+ end
33
+
34
+ driver(:hiredis, :synchrony) do
38
35
 
39
- def test_ssl_not_implemented_exception
40
- assert_raise(NotImplementedError) do
41
- RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("trusted")) do |port|
42
- redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file })
43
- redis.ping
44
- end
36
+ def test_ssl_not_implemented_exception
37
+ assert_raise(NotImplementedError) do
38
+ RedisMock.start({ :ping => proc { "+PONG" } }, ssl_server_opts("trusted")) do |port|
39
+ redis = Redis.new(:port => port, :ssl => true, :ssl_params => { :ca_file => ssl_ca_file })
40
+ redis.ping
45
41
  end
46
42
  end
47
-
48
43
  end
49
44
 
50
- private
45
+ end
46
+
47
+ private
51
48
 
52
- def ssl_server_opts(prefix)
53
- ssl_cert = File.join(cert_path, "#{prefix}-cert.crt")
54
- ssl_key = File.join(cert_path, "#{prefix}-cert.key")
49
+ def ssl_server_opts(prefix)
50
+ ssl_cert = File.join(cert_path, "#{prefix}-cert.crt")
51
+ ssl_key = File.join(cert_path, "#{prefix}-cert.key")
55
52
 
56
- {
57
- :ssl => true,
58
- :ssl_params => {
59
- :cert => OpenSSL::X509::Certificate.new(File.read(ssl_cert)),
60
- :key => OpenSSL::PKey::RSA.new(File.read(ssl_key))
61
- }
53
+ {
54
+ :ssl => true,
55
+ :ssl_params => {
56
+ :cert => OpenSSL::X509::Certificate.new(File.read(ssl_cert)),
57
+ :key => OpenSSL::PKey::RSA.new(File.read(ssl_key))
62
58
  }
63
- end
59
+ }
60
+ end
64
61
 
65
- def ssl_ca_file
66
- File.join(cert_path, "trusted-ca.crt")
67
- end
62
+ def ssl_ca_file
63
+ File.join(cert_path, "trusted-ca.crt")
64
+ end
68
65
 
69
- def cert_path
70
- File.expand_path("../support/ssl/", __FILE__)
71
- end
66
+ def cert_path
67
+ File.expand_path("../support/ssl/", __FILE__)
72
68
  end
73
69
  end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/redis'
4
+
5
+ class ClusterOrchestrator
6
+ SLOT_SIZE = 16384
7
+
8
+ def initialize(node_addrs)
9
+ raise 'Redis Cluster requires at least 3 master nodes.' if node_addrs.size < 3
10
+ @clients = node_addrs.map { |addr| Redis.new(url: addr) }
11
+ end
12
+
13
+ def rebuild
14
+ flush_all_data(@clients)
15
+ reset_cluster(@clients)
16
+ assign_slots(@clients)
17
+ save_config_epoch(@clients)
18
+ meet_each_other(@clients)
19
+ wait_meeting(@clients)
20
+ replicate(@clients)
21
+ save_config(@clients)
22
+ wait_cluster_building(@clients)
23
+ sleep 3
24
+ end
25
+
26
+ def down
27
+ flush_all_data(@clients)
28
+ reset_cluster(@clients)
29
+ end
30
+
31
+ def failover
32
+ take_slaves(@clients).last.cluster(:failover, :takeover)
33
+ sleep 3
34
+ end
35
+
36
+ def start_resharding(slot, src_node_key, dest_node_key)
37
+ node_map = hashify_node_map(@clients.first)
38
+ src_node_id = node_map.fetch(src_node_key)
39
+ src_client = find_client(@clients, src_node_key)
40
+ dest_node_id = node_map.fetch(dest_node_key)
41
+ dest_client = find_client(@clients, dest_node_key)
42
+ dest_host, dest_port = dest_node_key.split(':')
43
+
44
+ dest_client.cluster(:setslot, slot, 'IMPORTING', src_node_id)
45
+ src_client.cluster(:setslot, slot, 'MIGRATING', dest_node_id)
46
+
47
+ loop do
48
+ keys = src_client.cluster(:getkeysinslot, slot, 100)
49
+ break if keys.empty?
50
+ keys.each { |k| src_client.migrate(k, host: dest_host, port: dest_port) }
51
+ sleep 0.1
52
+ end
53
+ end
54
+
55
+ def finish_resharding(slot, dest_node_key)
56
+ node_map = hashify_node_map(@clients.first)
57
+ @clients.first.cluster(:setslot, slot, 'NODE', node_map.fetch(dest_node_key))
58
+ end
59
+
60
+ def close
61
+ @clients.each(&:quit)
62
+ end
63
+
64
+ private
65
+
66
+ def flush_all_data(clients)
67
+ clients.each do |c|
68
+ begin
69
+ c.flushall
70
+ rescue Redis::CommandError
71
+ # READONLY You can't write against a read only slave.
72
+ nil
73
+ end
74
+ end
75
+ end
76
+
77
+ def reset_cluster(clients)
78
+ clients.each { |c| c.cluster(:reset) }
79
+ end
80
+
81
+ def assign_slots(clients)
82
+ masters = take_masters(clients)
83
+ slot_slice = SLOT_SIZE / masters.size
84
+ mod = SLOT_SIZE % masters.size
85
+ slot_sizes = Array.new(masters.size, slot_slice)
86
+ mod.downto(1) { |i| slot_sizes[i] += 1 }
87
+
88
+ slot_idx = 0
89
+ masters.zip(slot_sizes).each do |c, s|
90
+ slot_range = slot_idx..slot_idx + s - 1
91
+ c.cluster(:addslots, *slot_range.to_a)
92
+ slot_idx += s
93
+ end
94
+ end
95
+
96
+ def save_config_epoch(clients)
97
+ clients.each_with_index do |c, i|
98
+ begin
99
+ c.cluster('set-config-epoch', i + 1)
100
+ rescue Redis::CommandError
101
+ # ERR Node config epoch is already non-zero
102
+ nil
103
+ end
104
+ end
105
+ end
106
+
107
+ def meet_each_other(clients)
108
+ first_cliient = clients.first
109
+ target_info = first_cliient.connection
110
+ target_host = target_info.fetch(:host)
111
+ target_port = target_info.fetch(:port)
112
+
113
+ clients.each do |client|
114
+ next if first_cliient.id == client.id
115
+ client.cluster(:meet, target_host, target_port)
116
+ end
117
+ end
118
+
119
+ def wait_meeting(clients)
120
+ first_cliient = clients.first
121
+ size = clients.size
122
+
123
+ loop do
124
+ info = hashify_cluster_info(first_cliient)
125
+ break if info['cluster_known_nodes'].to_i == size
126
+ sleep 0.1
127
+ end
128
+ end
129
+
130
+ def replicate(clients)
131
+ node_map = hashify_node_map(clients.first)
132
+ masters = take_masters(clients)
133
+
134
+ take_slaves(clients).each_with_index do |slave, i|
135
+ master_info = masters[i].connection
136
+ master_host = master_info.fetch(:host)
137
+ master_port = master_info.fetch(:port)
138
+
139
+ loop do
140
+ begin
141
+ master_node_id = node_map.fetch("#{master_host}:#{master_port}")
142
+ slave.cluster(:replicate, master_node_id)
143
+ rescue Redis::CommandError
144
+ # ERR Unknown node [key]
145
+ sleep 0.1
146
+ node_map = hashify_node_map(clients.first)
147
+ next
148
+ end
149
+
150
+ break
151
+ end
152
+ end
153
+ end
154
+
155
+ def save_config(clients)
156
+ clients.each { |c| c.cluster(:saveconfig) }
157
+ end
158
+
159
+ def wait_cluster_building(clients)
160
+ first_cliient = clients.first
161
+
162
+ loop do
163
+ info = hashify_cluster_info(first_cliient)
164
+ break if info['cluster_state'] == 'ok'
165
+ sleep 0.1
166
+ end
167
+ end
168
+
169
+ def hashify_cluster_info(client)
170
+ client.cluster(:info).split("\r\n").map { |str| str.split(':') }.to_h
171
+ end
172
+
173
+ def hashify_node_map(client)
174
+ client.cluster(:nodes)
175
+ .split("\n")
176
+ .map { |str| str.split(' ') }
177
+ .map { |arr| [arr[1].split('@').first, arr[0]] }
178
+ .to_h
179
+ end
180
+
181
+ def take_masters(clients)
182
+ size = clients.size / 2
183
+ return clients if size < 3
184
+ clients.take(size)
185
+ end
186
+
187
+ def take_slaves(clients)
188
+ size = clients.size / 2
189
+ return [] if size < 3
190
+ clients[size..size * 2]
191
+ end
192
+
193
+ def find_client(clients, node_key)
194
+ clients.find do |cli|
195
+ con = cli.connection
196
+ node_key == "#{con.fetch(:host)}:#{con.fetch(:port)}"
197
+ end
198
+ end
199
+ end
@@ -1 +1 @@
1
- require "support/wire/thread"
1
+ require_relative "../wire/thread"
@@ -1 +1 @@
1
- require "support/wire/thread"
1
+ require_relative "../wire/thread"
@@ -1,4 +1,4 @@
1
- require "support/wire/synchrony"
1
+ require_relative "../wire/synchrony"
2
2
 
3
3
  module Helper
4
4
  def around
@@ -42,7 +42,7 @@ module RedisMock
42
42
  end
43
43
  end
44
44
  rescue => ex
45
- $stderr.puts "Error running mock server: #{ex.message}"
45
+ $stderr.puts "Error running mock server: #{ex.class}: #{ex.message}"
46
46
  $stderr.puts ex.backtrace
47
47
  retry
48
48
  ensure
@@ -1,13 +1,10 @@
1
- # encoding: UTF-8
1
+ require "em-synchrony"
2
+ require "em-synchrony/connection_pool"
2
3
 
3
- require 'em-synchrony'
4
- require 'em-synchrony/connection_pool'
4
+ require_relative "../lib/redis"
5
+ require_relative "../lib/redis/connection/synchrony"
5
6
 
6
- require 'redis'
7
- require 'redis/connection/synchrony'
8
-
9
-
10
- require File.expand_path("./helper", File.dirname(__FILE__))
7
+ require_relative "helper"
11
8
 
12
9
  PORT = 6381
13
10
  OPTIONS = {:port => PORT, :db => 15}
@@ -55,7 +52,7 @@ EM.synchrony do
55
52
  assert_equal "s2", r.lpop("foo")
56
53
  assert_equal "s1", r.lpop("foo")
57
54
 
58
- assert_equal "OK", r.client.call(:quit)
55
+ assert_equal "OK", r._client.call(:quit)
59
56
  assert_equal "PONG", r.ping
60
57
 
61
58
 
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class TestThreadSafety < Test::Unit::TestCase
6
4
 
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class TestTransactions < Test::Unit::TestCase
6
4
 
@@ -116,6 +114,16 @@ class TestTransactions < Test::Unit::TestCase
116
114
  assert_equal "s1", r.get("foo")
117
115
  end
118
116
 
117
+ def test_empty_multi_exec
118
+ result = nil
119
+
120
+ redis_mock(:exec => lambda { |*_| "-ERROR" }) do |redis|
121
+ result = redis.multi {}
122
+ end
123
+
124
+ assert_equal [], result
125
+ end
126
+
119
127
  def test_raise_command_errors_when_accessing_futures_after_multi_exec
120
128
  begin
121
129
  r.multi do |m|
@@ -1,6 +1,4 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("helper", File.dirname(__FILE__))
1
+ require_relative "helper"
4
2
 
5
3
  class TestUnknownCommands < Test::Unit::TestCase
6
4