Skip to content

Commit c1351fe

Browse files
authored
Add some test cases (#18)
1 parent 8903554 commit c1351fe

File tree

8 files changed

+182
-39
lines changed

8 files changed

+182
-39
lines changed

lib/redis_client/cluster.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ def initialize(config, pool: nil, **kwargs)
1212
@config = config.dup
1313
@pool = pool
1414
@client_kwargs = kwargs
15-
@node = fetch_cluster_info!(@config, @pool, **@client_kwargs)
15+
@node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
1616
@command = ::RedisClient::Cluster::Command.load(@node)
1717
end
1818

1919
def inspect
20-
@node.inspect
20+
"#<#{self.class.name} #{@node.node_keys.join(', ')}>"
2121
end
2222

2323
def call(*command, **kwargs, &block)
@@ -113,11 +113,12 @@ def process(commands, &block)
113113

114114
private
115115

116-
def fetch_cluster_info!(config, pool, **kwargs)
116+
def fetch_cluster_info!(config, pool: nil, **kwargs)
117117
node_info = ::RedisClient::Cluster::Node.load_info(config.per_node_key, **kwargs)
118118
node_addrs = node_info.map { |info| ::RedisClient::Cluster::NodeKey.hashify(info[:node_key]) }
119119
config.update_node(node_addrs)
120-
::RedisClient::Cluster::Node.new(config.per_node_key, node_info, pool, with_replica: config.use_replica?, **kwargs)
120+
::RedisClient::Cluster::Node.new(config.per_node_key,
121+
node_info: node_info, pool: pool, with_replica: config.use_replica?, **kwargs)
121122
end
122123

123124
def send_command(method, *command, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
@@ -300,7 +301,7 @@ def update_cluster_info!(node_key = nil)
300301
end
301302

302303
@node.each(&:close)
303-
@node = fetch_cluster_info!(@config, @pool, **@client_kwargs)
304+
@node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
304305
end
305306
end
306307
end

lib/redis_client/cluster/node.rb

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class Node
1010
include Enumerable
1111

1212
SLOT_SIZE = 16_384
13+
MIN_SLOT = 0
14+
MAX_SLOT = SLOT_SIZE - 1
1315
ReloadNeeded = Class.new(::RedisClient::Error)
1416

1517
class Config < ::RedisClient::Config
@@ -70,15 +72,15 @@ def parse_node_info(info) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticC
7072
end
7173
end
7274

73-
def initialize(options, node_info = [], pool = nil, with_replica: false, **kwargs)
75+
def initialize(options, node_info: [], pool: nil, with_replica: false, **kwargs)
7476
@with_replica = with_replica
7577
@slots = build_slot_node_mappings(node_info)
7678
@replications = build_replication_mappings(node_info)
77-
@clients = build_clients(options, pool, **kwargs)
79+
@clients = build_clients(options, pool: pool, **kwargs)
7880
end
7981

8082
def inspect
81-
@clients.keys.sort.to_s
83+
"#<#{self.class.name} #{node_keys.join(', ')}>"
8284
end
8385

8486
def each(&block)
@@ -89,6 +91,10 @@ def sample
8991
@clients.values.sample
9092
end
9193

94+
def node_keys
95+
@clients.keys.sort
96+
end
97+
9298
def find_by(node_key)
9399
@clients.fetch(node_key)
94100
rescue KeyError
@@ -123,26 +129,29 @@ def process_all(commands, &block)
123129
end
124130

125131
def scale_reading_clients
126-
reading_clients = []
127-
128-
@clients.each do |node_key, client|
129-
next unless replica_disabled? ? primary?(node_key) : replica?(node_key)
130-
131-
reading_clients << client
132-
end
133-
134-
reading_clients
132+
@clients.select do |node_key, _|
133+
replica_disabled? ? primary?(node_key) : replica?(node_key)
134+
end.values
135135
end
136136

137137
def slot_exists?(slot)
138+
slot = Integer(slot)
139+
return false if slot < MIN_SLOT || slot > MAX_SLOT
140+
138141
!@slots[slot].nil?
139142
end
140143

141144
def find_node_key_of_primary(slot)
145+
slot = Integer(slot)
146+
return if slot < MIN_SLOT || slot > MAX_SLOT
147+
142148
@slots[slot]
143149
end
144150

145151
def find_node_key_of_replica(slot)
152+
slot = Integer(slot)
153+
return if slot < MIN_SLOT || slot > MAX_SLOT
154+
146155
return @slots[slot] if replica_disabled? || @replications[@slots[slot]].size.zero?
147156

148157
@replications[@slots[slot]].sample
@@ -186,12 +195,12 @@ def build_replication_mappings(node_info)
186195
end
187196
end
188197

189-
def build_clients(options, pool, **kwargs)
198+
def build_clients(options, pool: nil, **kwargs)
190199
options.filter_map do |node_key, option|
191200
next if replica_disabled? && replica?(node_key)
192201

193-
config = ::RedisClient::Cluster::Node::Config.new(scale_read: replica?(node_key), **option)
194-
client = pool.nil? ? config.new_client(**kwargs) : config.new_pool(**pool, **kwargs)
202+
config = ::RedisClient::Cluster::Node::Config.new(scale_read: replica?(node_key), **option.merge(kwargs))
203+
client = pool.nil? ? config.new_client : config.new_pool(**pool)
195204

196205
[node_key, client]
197206
end.to_h

lib/redis_client/cluster_config.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def initialize(nodes: DEFAULT_NODES, replica: false, fixed_hostname: '', **clien
2626
end
2727

2828
def inspect
29-
"#<#{self.class.name}: #{per_node_key.values}>"
29+
"#<#{self.class.name} #{per_node_key.values}>"
3030
end
3131

3232
def new_pool(size: 5, timeout: 5, **kwargs)

test/redis_client/cluster/test_command.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
class RedisClient
1010
class Cluster
1111
class TestCommand < Minitest::Test
12-
include ::RedisClient::TestingHelper
12+
def setup
13+
@raw_clients = TEST_NODE_URIS.map { |addr| ::RedisClient.config(url: addr, timeout: TEST_TIMEOUT_SEC).new_client }
14+
end
15+
16+
def teardown
17+
@raw_clients&.each(&:close)
18+
end
1319

1420
def test_load
1521
[

test/redis_client/cluster/test_key_slot_converter.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
class RedisClient
77
class Cluster
88
class TestKeySlotConverter < Minitest::Test
9-
include TestingHelper
9+
def setup
10+
@raw_clients = TEST_NODE_URIS.map { |addr| ::RedisClient.config(url: addr, timeout: TEST_TIMEOUT_SEC).new_client }
11+
end
12+
13+
def teardown
14+
@raw_clients&.each(&:close)
15+
end
1016

1117
def test_convert
1218
(1..255).map { |i| "key#{i}" }.each_with_index do |key, idx|

test/redis_client/cluster/test_node.rb

Lines changed: 131 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'uri'
44
require 'testing_helper'
55
require 'redis_client/cluster/node'
6+
require 'redis_client/cluster/node_key'
67

78
class RedisClient
89
class Cluster
@@ -22,11 +23,34 @@ def test_connection_prelude
2223
end
2324

2425
class TestNode < Minitest::Test
26+
def setup
27+
config = ::RedisClient::ClusterConfig.new(nodes: TEST_NODE_URIS)
28+
@node_info = ::RedisClient::Cluster::Node.load_info(config.per_node_key, timeout: TEST_TIMEOUT_SEC)
29+
node_addrs = @node_info.map { |info| ::RedisClient::Cluster::NodeKey.hashify(info[:node_key]) }
30+
config.update_node(node_addrs)
31+
@test_node = ::RedisClient::Cluster::Node.new(config.per_node_key, node_info: @node_info, timeout: TEST_TIMEOUT_SEC)
32+
@test_node_with_scale_read = ::RedisClient::Cluster::Node.new(config.per_node_key, node_info: @node_info, with_replica: true, timeout: TEST_TIMEOUT_SEC)
33+
end
34+
35+
def teardown
36+
@test_node&.each(&:close)
37+
@test_node_with_scale_read&.each(&:close)
38+
end
39+
2540
def test_load_info
2641
[
27-
{ params: { options: TEST_NODE_OPTIONS, kwargs: {} }, want: { size: TEST_NODE_OPTIONS.size } },
28-
{ params: { options: { '127.0.0.1:11211' => { host: '127.0.0.1', port: 11_211 } }, kwargs: {} }, want: { error: ::RedisClient::Cluster::InitialSetupError } },
29-
{ params: { options: {}, kwargs: {} }, want: { error: ::RedisClient::Cluster::InitialSetupError } }
42+
{
43+
params: { options: TEST_NODE_OPTIONS, kwargs: { timeout: TEST_TIMEOUT_SEC } },
44+
want: { size: TEST_NODE_OPTIONS.size }
45+
},
46+
{
47+
params: { options: { '127.0.0.1:11211' => { host: '127.0.0.1', port: 11_211 } }, kwargs: { timeout: TEST_TIMEOUT_SEC } },
48+
want: { error: ::RedisClient::Cluster::InitialSetupError }
49+
},
50+
{
51+
params: { options: {}, kwargs: { timeout: TEST_TIMEOUT_SEC } },
52+
want: { error: ::RedisClient::Cluster::InitialSetupError }
53+
}
3054
].each_with_index do |c, idx|
3155
msg = "Case: #{idx}"
3256
got = -> { ::RedisClient::Cluster::Node.load_info(c[:params][:options], **c[:params][:kwargs]) }
@@ -125,46 +149,140 @@ def test_parse_node_info
125149
end
126150

127151
def test_inspect
128-
skip('TODO')
152+
assert_match(/^#<RedisClient::Cluster::Node [0-9., :]*>$/, @test_node.inspect)
129153
end
130154

131155
def test_enumerable
132-
skip('TODO')
156+
refute(@test_node.any?(&:nil?))
157+
end
158+
159+
def test_node_keys
160+
want = @node_info.map { |info| info[:node_key] }
161+
@test_node.node_keys.each do |got|
162+
assert_includes(want, got, "Case: #{got}")
163+
end
133164
end
134165

135166
def test_find_by
136-
skip('TODO')
167+
@node_info.each do |info|
168+
msg = "Case: primary only: #{info[:node_key]}"
169+
got = -> { @test_node.find_by(info[:node_key]) }
170+
if info[:role] == 'master'
171+
assert_instance_of(::RedisClient, got.call, msg)
172+
else
173+
assert_raises(::RedisClient::Cluster::Node::ReloadNeeded, msg, &got)
174+
end
175+
176+
msg = "Case: scale read: #{info[:node_key]}"
177+
got = @test_node_with_scale_read.find_by(info[:node_key])
178+
assert_instance_of(::RedisClient, got, msg)
179+
end
137180
end
138181

139182
def test_call_all
140-
skip('TODO')
183+
want = (1..(@node_info.count { |info| info[:role] == 'master' })).map { |_| 'PONG' }
184+
got = @test_node.call_all(:call, 'PING')
185+
assert_equal(want, got, 'Case: primary only')
186+
187+
want = (1..(@node_info.count)).map { |_| 'PONG' }
188+
got = @test_node_with_scale_read.call_all(:call, 'PING')
189+
assert_equal(want, got, 'Case: scale read')
141190
end
142191

143192
def test_call_primary
144-
skip('TODO')
193+
want = (1..(@node_info.count { |info| info[:role] == 'master' })).map { |_| 'PONG' }
194+
got = @test_node.call_primary(:call, 'PING')
195+
assert_equal(want, got)
145196
end
146197

147198
def test_call_replica
148-
skip('TODO')
199+
want = (1..(@node_info.count { |info| info[:role] == 'master' })).map { |_| 'PONG' }
200+
got = @test_node.call_replica(:call, 'PING')
201+
assert_equal(want, got, 'Case: primary only')
202+
203+
want = (1..(@node_info.count { |info| info[:role] == 'slave' })).map { |_| 'PONG' }
204+
got = @test_node_with_scale_read.call_replica(:call, 'PING')
205+
assert_equal(want, got, 'Case: scale read')
149206
end
150207

151208
def test_scale_reading_clients
152-
skip('TODO')
209+
want = @node_info.select { |info| info[:role] == 'master' }.map { |info| info[:node_key] }.sort
210+
got = @test_node.scale_reading_clients.map { |client| "#{client.config.host}:#{client.config.port}" }.sort
211+
assert_equal(want, got, 'Case: primary only')
212+
213+
want = @node_info.select { |info| info[:role] == 'slave' }.map { |info| info[:node_key] }.sort
214+
got = @test_node_with_scale_read.scale_reading_clients.map { |client| "#{client.config.host}:#{client.config.port}" }.sort
215+
assert_equal(want, got, 'Case: scale read')
153216
end
154217

155218
def test_slot_exists?
156-
skip('TODO')
219+
refute(@test_node.slot_exists?(-1))
220+
assert(@test_node.slot_exists?(0))
221+
assert(@test_node.slot_exists?(16_383))
222+
refute(@test_node.slot_exists?(16_384))
223+
assert_raises(TypeError) { @test_node.slot_exists?(:foo) }
157224
end
158225

159226
def test_find_node_key_of_primary
160-
skip('TODO')
227+
sample_node = @node_info.find { |info| info[:role] == 'master' }
228+
sample_slot = sample_node[:slots].first.first
229+
got = @test_node.find_node_key_of_primary(sample_slot)
230+
assert_equal(sample_node[:node_key], got)
161231
end
162232

163233
def test_find_node_key_of_replica
164-
skip('TODO')
234+
sample_node = @node_info.find { |info| info[:role] == 'master' }
235+
sample_slot = sample_node[:slots].first.first
236+
got = @test_node.find_node_key_of_replica(sample_slot)
237+
assert_equal(sample_node[:node_key], got, 'Case: primary only')
238+
239+
sample_replica = @node_info.find { |info| info[:role] == 'slave' }
240+
sample_primary = @node_info.find { |info| info[:id] == sample_replica[:primary_id] }
241+
sample_slot = sample_primary[:slots].first.first
242+
got = @test_node_with_scale_read.find_node_key_of_replica(sample_slot)
243+
assert_equal(sample_replica[:node_key], got, 'Case: scale read')
165244
end
166245

167246
def test_update_slot
247+
sample_slot = 0
248+
base_node_key = @test_node.find_node_key_of_primary(sample_slot)
249+
another_node_key = @node_info.find { |info| info[:node_key] != base_node_key && info[:role] == 'master' }
250+
@test_node.update_slot(sample_slot, another_node_key)
251+
assert_equal(another_node_key, @test_node.find_node_key_of_primary(sample_slot))
252+
end
253+
254+
def test_replica_disabled?
255+
assert(@test_node.send(:replica_disabled?))
256+
refute(@test_node_with_scale_read.send(:replica_disabled?))
257+
end
258+
259+
def test_primary?
260+
sample_primary = @node_info.find { |info| info[:role] == 'master' }
261+
sample_replica = @node_info.find { |info| info[:role] == 'slave' }
262+
assert(@test_node.send(:primary?, sample_primary[:node_key]))
263+
refute(@test_node.send(:primary?, sample_replica[:node_key]))
264+
end
265+
266+
def test_replica?
267+
sample_primary = @node_info.find { |info| info[:role] == 'master' }
268+
sample_replica = @node_info.find { |info| info[:role] == 'slave' }
269+
refute(@test_node.send(:replica?, sample_primary[:node_key]))
270+
assert(@test_node.send(:replica?, sample_replica[:node_key]))
271+
end
272+
273+
def test_build_slot_node_mappings
274+
skip('TODO')
275+
end
276+
277+
def test_build_replication_mappings
278+
skip('TODO')
279+
end
280+
281+
def test_build_clients
282+
skip('TODO')
283+
end
284+
285+
def test_try_map
168286
skip('TODO')
169287
end
170288
end

test/redis_client/test_cluster_config.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
class RedisClient
77
class TestClusterConfig < Minitest::Test
88
def test_inspect
9-
want = '#<RedisClient::ClusterConfig: [{:host=>"127.0.0.1", :port=>6379}]>'
9+
want = '#<RedisClient::ClusterConfig [{:host=>"127.0.0.1", :port=>6379}]>'
1010
got = ::RedisClient::ClusterConfig.new.inspect
1111
assert_equal(want, got)
1212
end

0 commit comments

Comments
 (0)