redis 4.0.2 → 4.0.3

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +6 -0
  5. data/lib/redis.rb +97 -11
  6. data/lib/redis/client.rb +19 -11
  7. data/lib/redis/cluster.rb +285 -0
  8. data/lib/redis/cluster/command.rb +81 -0
  9. data/lib/redis/cluster/command_loader.rb +32 -0
  10. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  11. data/lib/redis/cluster/node.rb +104 -0
  12. data/lib/redis/cluster/node_key.rb +35 -0
  13. data/lib/redis/cluster/node_loader.rb +35 -0
  14. data/lib/redis/cluster/option.rb +76 -0
  15. data/lib/redis/cluster/slot.rb +69 -0
  16. data/lib/redis/cluster/slot_loader.rb +47 -0
  17. data/lib/redis/errors.rb +46 -0
  18. data/lib/redis/version.rb +1 -1
  19. data/makefile +54 -16
  20. data/redis.gemspec +2 -1
  21. data/test/client_test.rb +17 -0
  22. data/test/cluster_abnormal_state_test.rb +38 -0
  23. data/test/cluster_blocking_commands_test.rb +15 -0
  24. data/test/cluster_client_internals_test.rb +77 -0
  25. data/test/cluster_client_key_hash_tags_test.rb +88 -0
  26. data/test/cluster_client_options_test.rb +147 -0
  27. data/test/cluster_client_pipelining_test.rb +59 -0
  28. data/test/cluster_client_replicas_test.rb +36 -0
  29. data/test/cluster_client_slots_test.rb +94 -0
  30. data/test/cluster_client_transactions_test.rb +71 -0
  31. data/test/cluster_commands_on_cluster_test.rb +165 -0
  32. data/test/cluster_commands_on_connection_test.rb +40 -0
  33. data/test/cluster_commands_on_geo_test.rb +74 -0
  34. data/test/cluster_commands_on_hashes_test.rb +11 -0
  35. data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
  36. data/test/cluster_commands_on_keys_test.rb +134 -0
  37. data/test/cluster_commands_on_lists_test.rb +15 -0
  38. data/test/cluster_commands_on_pub_sub_test.rb +101 -0
  39. data/test/cluster_commands_on_scripting_test.rb +56 -0
  40. data/test/cluster_commands_on_server_test.rb +221 -0
  41. data/test/cluster_commands_on_sets_test.rb +39 -0
  42. data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
  43. data/test/cluster_commands_on_streams_test.rb +196 -0
  44. data/test/cluster_commands_on_strings_test.rb +15 -0
  45. data/test/cluster_commands_on_transactions_test.rb +41 -0
  46. data/test/cluster_commands_on_value_types_test.rb +14 -0
  47. data/test/commands_on_hashes_test.rb +2 -14
  48. data/test/commands_on_hyper_log_log_test.rb +2 -14
  49. data/test/commands_on_lists_test.rb +2 -13
  50. data/test/commands_on_sets_test.rb +2 -70
  51. data/test/commands_on_sorted_sets_test.rb +2 -145
  52. data/test/commands_on_strings_test.rb +2 -94
  53. data/test/distributed_blocking_commands_test.rb +8 -0
  54. data/test/distributed_commands_on_hashes_test.rb +16 -3
  55. data/test/distributed_commands_on_hyper_log_log_test.rb +8 -13
  56. data/test/distributed_commands_on_lists_test.rb +4 -5
  57. data/test/distributed_commands_on_sets_test.rb +45 -46
  58. data/test/distributed_commands_on_sorted_sets_test.rb +51 -8
  59. data/test/distributed_commands_on_strings_test.rb +10 -0
  60. data/test/helper.rb +176 -32
  61. data/test/internals_test.rb +13 -0
  62. data/test/lint/blocking_commands.rb +40 -16
  63. data/test/lint/hashes.rb +26 -0
  64. data/test/lint/hyper_log_log.rb +15 -1
  65. data/test/lint/lists.rb +16 -0
  66. data/test/lint/sets.rb +142 -0
  67. data/test/lint/sorted_sets.rb +183 -2
  68. data/test/lint/strings.rb +102 -0
  69. data/test/support/cluster/orchestrator.rb +199 -0
  70. metadata +79 -4
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors'
4
+ require_relative 'node_key'
5
+
6
+ class Redis
7
+ class Cluster
8
+ # Load and hashify slot info for Redis Cluster Client
9
+ module SlotLoader
10
+ module_function
11
+
12
+ def load(nodes)
13
+ info = {}
14
+
15
+ nodes.each do |node|
16
+ info = Hash[*fetch_slot_info(node)]
17
+ info.empty? ? next : break
18
+ end
19
+
20
+ return info unless info.empty?
21
+
22
+ raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
23
+ end
24
+
25
+ def fetch_slot_info(node)
26
+ node.call(%i[cluster slots])
27
+ .map { |arr| parse_slot_info(arr, default_ip: node.host) }
28
+ .flatten
29
+ rescue CannotConnectError, ConnectionError, CommandError
30
+ {} # can retry on another node
31
+ end
32
+
33
+ def parse_slot_info(arr, default_ip:)
34
+ first_slot, last_slot = arr[0..1]
35
+ slot_range = (first_slot..last_slot).freeze
36
+ arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
37
+ .flatten
38
+ end
39
+
40
+ def stringify_node_key(arr, default_ip)
41
+ ip, port = arr
42
+ ip = default_ip if ip.empty? # When cluster is down
43
+ NodeKey.build_from_host_port(ip, port)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -37,4 +37,50 @@ class Redis
37
37
  # Raised when the connection was inherited by a child process.
38
38
  class InheritedError < BaseConnectionError
39
39
  end
40
+
41
+ # Raised when client options are invalid.
42
+ class InvalidClientOptionError < BaseError
43
+ end
44
+
45
+ class Cluster
46
+ # Raised when client connected to redis as cluster mode
47
+ # and some cluster subcommands were called.
48
+ class OrchestrationCommandNotSupported < BaseError
49
+ def initialize(command, subcommand = '')
50
+ str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
51
+ msg = "#{str} command should be used with care "\
52
+ 'only by applications orchestrating Redis Cluster, like redis-trib, '\
53
+ 'and the command if used out of the right context can leave the cluster '\
54
+ 'in a wrong state or cause data loss.'
55
+ super(msg)
56
+ end
57
+ end
58
+
59
+ # Raised when error occurs on any node of cluster.
60
+ class CommandErrorCollection < BaseError
61
+ attr_reader :errors
62
+
63
+ # @param errors [Hash{String => Redis::CommandError}]
64
+ # @param error_message [String]
65
+ def initialize(errors, error_message = 'Command errors were replied on any node')
66
+ @errors = errors
67
+ super(error_message)
68
+ end
69
+ end
70
+
71
+ # Raised when cluster client can't select node.
72
+ class AmbiguousNodeError < BaseError
73
+ def initialize(command)
74
+ super("Cluster client doesn't know which node the #{command} command should be sent to.")
75
+ end
76
+ end
77
+
78
+ # Raised when commands in pipelining include cross slot keys.
79
+ class CrossSlotPipeliningError < BaseError
80
+ def initialize(keys)
81
+ super("Cluster client couldn't send pipelining to single node. "\
82
+ "The commands include cross slot keys. #{keys}")
83
+ end
84
+ end
85
+ end
40
86
  end
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "4.0.2"
2
+ VERSION = "4.0.3"
3
3
  end
data/makefile CHANGED
@@ -1,27 +1,43 @@
1
- TEST_FILES := $(shell find ./test -name *_test.rb -type f)
2
- REDIS_BRANCH ?= unstable
3
- TMP := tmp
4
- BUILD_DIR := ${TMP}/cache/redis-${REDIS_BRANCH}
5
- TARBALL := ${TMP}/redis-${REDIS_BRANCH}.tar.gz
6
- BINARY := ${BUILD_DIR}/src/redis-server
7
- PID_PATH := ${BUILD_DIR}/redis.pid
8
- SOCKET_PATH := ${BUILD_DIR}/redis.sock
9
- PORT := 6381
1
+ TEST_FILES := $(shell find ./test -name *_test.rb -type f)
2
+ REDIS_BRANCH ?= unstable
3
+ TMP := tmp
4
+ BUILD_DIR := ${TMP}/cache/redis-${REDIS_BRANCH}
5
+ TARBALL := ${TMP}/redis-${REDIS_BRANCH}.tar.gz
6
+ BINARY := ${BUILD_DIR}/src/redis-server
7
+ REDIS_CLIENT := ${BUILD_DIR}/src/redis-cli
8
+ REDIS_TRIB := ${BUILD_DIR}/src/redis-trib.rb
9
+ PID_PATH := ${BUILD_DIR}/redis.pid
10
+ SOCKET_PATH := ${BUILD_DIR}/redis.sock
11
+ PORT := 6381
12
+ CLUSTER_PORTS := 7000 7001 7002 7003 7004 7005
13
+ CLUSTER_PID_PATHS := $(addprefix ${TMP}/redis,$(addsuffix .pid,${CLUSTER_PORTS}))
14
+ CLUSTER_CONF_PATHS := $(addprefix ${TMP}/nodes,$(addsuffix .conf,${CLUSTER_PORTS}))
15
+ CLUSTER_ADDRS := $(addprefix 127.0.0.1:,${CLUSTER_PORTS})
10
16
 
11
- test: ${TEST_FILES}
17
+ define kill-redis
18
+ (ls $1 2> /dev/null && kill $$(cat $1) && rm -f $1) || true
19
+ endef
20
+
21
+ all:
12
22
  make start
13
- env SOCKET_PATH=${SOCKET_PATH} \
14
- bundle exec ruby -v -e 'ARGV.each { |test_file| require test_file }' ${TEST_FILES}
23
+ make start_cluster
24
+ make create_cluster
25
+ make test
15
26
  make stop
27
+ make stop_cluster
16
28
 
17
29
  ${TMP}:
18
- mkdir $@
30
+ mkdir -p $@
19
31
 
20
32
  ${BINARY}: ${TMP}
21
- bin/build ${REDIS_BRANCH} ${TMP}
33
+ bin/build ${REDIS_BRANCH} $<
34
+
35
+ test: ${TEST_FILES}
36
+ env SOCKET_PATH=${SOCKET_PATH} \
37
+ bundle exec ruby -v -e 'ARGV.each { |test_file| require test_file }' ${TEST_FILES}
22
38
 
23
39
  stop:
24
- (test -f ${PID_PATH} && (kill $$(cat ${PID_PATH}) || true) && rm -f ${PID_PATH}) || true
40
+ $(call kill-redis,${PID_PATH})
25
41
 
26
42
  start: ${BINARY}
27
43
  ${BINARY} \
@@ -30,7 +46,29 @@ start: ${BINARY}
30
46
  --port ${PORT} \
31
47
  --unixsocket ${SOCKET_PATH}
32
48
 
49
+ stop_cluster:
50
+ $(call kill-redis,${CLUSTER_PID_PATHS})
51
+ rm -f appendonly.aof || true
52
+ rm -f ${CLUSTER_CONF_PATHS} || true
53
+
54
+ start_cluster: ${BINARY}
55
+ for port in ${CLUSTER_PORTS}; do \
56
+ ${BINARY} \
57
+ --daemonize yes \
58
+ --appendonly yes \
59
+ --cluster-enabled yes \
60
+ --cluster-config-file ${TMP}/nodes$$port.conf \
61
+ --cluster-node-timeout 5000 \
62
+ --pidfile ${TMP}/redis$$port.pid \
63
+ --port $$port \
64
+ --unixsocket ${TMP}/redis$$port.sock; \
65
+ done
66
+
67
+ create_cluster:
68
+ yes yes | ((bundle exec ruby ${REDIS_TRIB} create --replicas 1 ${CLUSTER_ADDRS}) || \
69
+ (${REDIS_CLIENT} --cluster create ${CLUSTER_ADDRS} --cluster-replicas 1))
70
+
33
71
  clean:
34
72
  (test -d ${BUILD_DIR} && cd ${BUILD_DIR}/src && make clean distclean) || true
35
73
 
36
- .PHONY: test start stop
74
+ .PHONY: all test stop start stop_cluster start_cluster create_cluster clean
@@ -32,11 +32,12 @@ Gem::Specification.new do |s|
32
32
 
33
33
  s.files = `git ls-files`.split("\n")
34
34
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
35
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
35
+ s.executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) }
36
36
 
37
37
  s.required_ruby_version = '>= 2.2.2'
38
38
 
39
39
  s.add_development_dependency("test-unit", ">= 3.1.5")
40
+ s.add_development_dependency("mocha")
40
41
  s.add_development_dependency("hiredis")
41
42
  s.add_development_dependency("em-synchrony")
42
43
  end
@@ -56,4 +56,21 @@ class TestClient < Test::Unit::TestCase
56
56
 
57
57
  assert_equal result, ["OK", 1]
58
58
  end
59
+
60
+ def test_client_with_custom_connector
61
+ custom_connector = Class.new(Redis::Client::Connector) do
62
+ def resolve
63
+ @options[:host] = '127.0.0.5'
64
+ @options[:port] = '999'
65
+ @options
66
+ end
67
+ end
68
+
69
+ assert_raise_message(
70
+ 'Error connecting to Redis on 127.0.0.5:999 (Errno::ECONNREFUSED)'
71
+ ) do
72
+ new_redis = _new_client(connector: custom_connector)
73
+ new_redis.ping
74
+ end
75
+ end
59
76
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ # ruby -w -Itest test/cluster_abnormal_state_test.rb
6
+ class TestClusterAbnormalState < Test::Unit::TestCase
7
+ include Helper::Cluster
8
+
9
+ def test_the_state_of_cluster_down
10
+ redis_cluster_down do
11
+ assert_raise(Redis::CommandError, 'CLUSTERDOWN Hash slot not served') do
12
+ redis.set('key1', 1)
13
+ end
14
+
15
+ assert_equal 'fail', redis.cluster(:info).fetch('cluster_state')
16
+ end
17
+ end
18
+
19
+ def test_the_state_of_cluster_failover
20
+ redis_cluster_failover do
21
+ 100.times do |i|
22
+ assert_equal 'OK', r.set("key#{i}", i)
23
+ end
24
+
25
+ 100.times do |i|
26
+ assert_equal i.to_s, r.get("key#{i}")
27
+ end
28
+
29
+ assert_equal 'ok', redis.cluster(:info).fetch('cluster_state')
30
+ end
31
+ end
32
+
33
+ def test_raising_error_when_nodes_are_not_cluster_mode
34
+ assert_raise(Redis::CannotConnectError, 'Redis client could not connect to any cluster nodes') do
35
+ build_another_client(cluster: %W[redis://127.0.0.1:#{PORT}])
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require_relative 'lint/blocking_commands'
5
+
6
+ # ruby -w -Itest test/cluster_blocking_commands_test.rb
7
+ class TestClusterBlockingCommands < Test::Unit::TestCase
8
+ include Helper::Cluster
9
+ include Lint::BlockingCommands
10
+
11
+ def mock(options = {}, &blk)
12
+ commands = build_mock_commands(options)
13
+ redis_cluster_mock(commands, &blk)
14
+ end
15
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ # ruby -w -Itest test/cluster_client_internals_test.rb
6
+ class TestClusterClientInternals < Test::Unit::TestCase
7
+ include Helper::Cluster
8
+
9
+ def test_handle_multiple_servers
10
+ 100.times { |i| redis.set(i.to_s, "hogehoge#{i}") }
11
+ 100.times { |i| assert_equal "hogehoge#{i}", redis.get(i.to_s) }
12
+ end
13
+
14
+ def test_info_of_cluster_mode_is_enabled
15
+ assert_equal '1', redis.info['cluster_enabled']
16
+ end
17
+
18
+ def test_unknown_commands_does_not_work_by_default
19
+ assert_raise(Redis::CommandError) do
20
+ redis.not_yet_implemented_command('boo', 'foo')
21
+ end
22
+ end
23
+
24
+ def test_with_reconnect
25
+ assert_equal('Hello World', redis.with_reconnect { 'Hello World' })
26
+ end
27
+
28
+ def test_without_reconnect
29
+ assert_equal('Hello World', redis.without_reconnect { 'Hello World' })
30
+ end
31
+
32
+ def test_connected?
33
+ assert_equal true, redis.connected?
34
+ end
35
+
36
+ def test_close
37
+ assert_equal true, redis.close
38
+ end
39
+
40
+ def test_disconnect!
41
+ assert_equal true, redis.disconnect!
42
+ end
43
+
44
+ def test_asking
45
+ assert_equal 'OK', redis.asking
46
+ end
47
+
48
+ def test_id
49
+ expected = 'redis://127.0.0.1:7000/0 '\
50
+ 'redis://127.0.0.1:7001/0 '\
51
+ 'redis://127.0.0.1:7002/0'
52
+ assert_equal expected, redis.id
53
+ end
54
+
55
+ def test_inspect
56
+ expected = "#<Redis client v#{Redis::VERSION} for "\
57
+ 'redis://127.0.0.1:7000/0 '\
58
+ 'redis://127.0.0.1:7001/0 '\
59
+ 'redis://127.0.0.1:7002/0>'
60
+
61
+ assert_equal expected, redis.inspect
62
+ end
63
+
64
+ def test_dup
65
+ assert_instance_of Redis, redis.dup
66
+ end
67
+
68
+ def test_connection
69
+ expected = [
70
+ { host: '127.0.0.1', port: 7000, db: 0, id: 'redis://127.0.0.1:7000/0', location: '127.0.0.1:7000' },
71
+ { host: '127.0.0.1', port: 7001, db: 0, id: 'redis://127.0.0.1:7001/0', location: '127.0.0.1:7001' },
72
+ { host: '127.0.0.1', port: 7002, db: 0, id: 'redis://127.0.0.1:7002/0', location: '127.0.0.1:7002' }
73
+ ]
74
+
75
+ assert_equal expected, redis.connection
76
+ end
77
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ # ruby -w -Itest test/cluster_client_key_hash_tags_test.rb
6
+ class TestClusterClientKeyHashTags < Test::Unit::TestCase
7
+ include Helper::Cluster
8
+
9
+ def build_described_class
10
+ option = Redis::Cluster::Option.new(cluster: ['redis://127.0.0.1:7000'])
11
+ node = Redis::Cluster::Node.new(option.per_node_key)
12
+ details = Redis::Cluster::CommandLoader.load(node)
13
+ Redis::Cluster::Command.new(details)
14
+ end
15
+
16
+ def test_key_extraction
17
+ described_class = build_described_class
18
+
19
+ assert_equal 'dogs:1', described_class.extract_first_key(%w[get dogs:1])
20
+ assert_equal 'user1000', described_class.extract_first_key(%w[get {user1000}.following])
21
+ assert_equal 'user1000', described_class.extract_first_key(%w[get {user1000}.followers])
22
+ assert_equal 'foo{}{bar}', described_class.extract_first_key(%w[get foo{}{bar}])
23
+ assert_equal '{bar', described_class.extract_first_key(%w[get foo{{bar}}zap])
24
+ assert_equal 'bar', described_class.extract_first_key(%w[get foo{bar}{zap}])
25
+
26
+ assert_equal '', described_class.extract_first_key([:get, ''])
27
+ assert_equal '', described_class.extract_first_key([:get, nil])
28
+ assert_equal '', described_class.extract_first_key([:get])
29
+
30
+ assert_equal '', described_class.extract_first_key([:set, '', 1])
31
+ assert_equal '', described_class.extract_first_key([:set, nil, 1])
32
+ assert_equal '', described_class.extract_first_key([:set])
33
+
34
+ # Keyless commands
35
+ assert_equal '', described_class.extract_first_key([:auth, 'password'])
36
+ assert_equal '', described_class.extract_first_key(%i[client kill])
37
+ assert_equal '', described_class.extract_first_key(%i[cluster addslots])
38
+ assert_equal '', described_class.extract_first_key(%i[command])
39
+ assert_equal '', described_class.extract_first_key(%i[command count])
40
+ assert_equal '', described_class.extract_first_key(%i[config get])
41
+ assert_equal '', described_class.extract_first_key(%i[debug segfault])
42
+ assert_equal '', described_class.extract_first_key([:echo, 'Hello World'])
43
+ assert_equal '', described_class.extract_first_key([:flushall, 'ASYNC'])
44
+ assert_equal '', described_class.extract_first_key([:flushdb, 'ASYNC'])
45
+ assert_equal '', described_class.extract_first_key([:info, 'cluster'])
46
+ assert_equal '', described_class.extract_first_key(%i[memory doctor])
47
+ assert_equal '', described_class.extract_first_key([:ping, 'Hi'])
48
+ assert_equal '', described_class.extract_first_key([:psubscribe, 'channel'])
49
+ assert_equal '', described_class.extract_first_key([:pubsub, 'channels', '*'])
50
+ assert_equal '', described_class.extract_first_key([:publish, 'channel', 'Hi'])
51
+ assert_equal '', described_class.extract_first_key([:punsubscribe, 'channel'])
52
+ assert_equal '', described_class.extract_first_key([:subscribe, 'channel'])
53
+ assert_equal '', described_class.extract_first_key([:unsubscribe, 'channel'])
54
+ assert_equal '', described_class.extract_first_key(%w[script exists sha1 sha1])
55
+ assert_equal '', described_class.extract_first_key([:select, 1])
56
+ assert_equal '', described_class.extract_first_key([:shutdown, 'SAVE'])
57
+ assert_equal '', described_class.extract_first_key([:slaveof, '127.0.0.1', 6379])
58
+ assert_equal '', described_class.extract_first_key([:slowlog, 'get', 2])
59
+ assert_equal '', described_class.extract_first_key([:swapdb, 0, 1])
60
+ assert_equal '', described_class.extract_first_key([:wait, 1, 0])
61
+
62
+ # 2nd argument is not a key
63
+ assert_equal 'key1', described_class.extract_first_key([:eval, 'script', 2, 'key1', 'key2', 'first', 'second'])
64
+ assert_equal '', described_class.extract_first_key([:eval, 'return 0', 0])
65
+ assert_equal 'key1', described_class.extract_first_key([:evalsha, 'sha1', 2, 'key1', 'key2', 'first', 'second'])
66
+ assert_equal '', described_class.extract_first_key([:evalsha, 'return 0', 0])
67
+ assert_equal 'key1', described_class.extract_first_key([:migrate, '127.0.0.1', 6379, 'key1', 0, 5000])
68
+ assert_equal 'key1', described_class.extract_first_key([:memory, :usage, 'key1'])
69
+ assert_equal 'key1', described_class.extract_first_key([:object, 'refcount', 'key1'])
70
+ assert_equal 'mystream', described_class.extract_first_key([:xread, 'COUNT', 2, 'STREAMS', 'mystream', 0])
71
+ assert_equal 'mystream', described_class.extract_first_key([:xreadgroup, 'GROUP', 'mygroup', 'Bob', 'COUNT', 2, 'STREAMS', 'mystream', '>'])
72
+ end
73
+
74
+ def test_whether_the_command_effect_is_readonly_or_not
75
+ described_class = build_described_class
76
+
77
+ assert_equal true, described_class.should_send_to_master?([:set])
78
+ assert_equal false, described_class.should_send_to_slave?([:set])
79
+
80
+ assert_equal false, described_class.should_send_to_master?([:get])
81
+ assert_equal true, described_class.should_send_to_slave?([:get])
82
+
83
+ target_version('3.2.0') do
84
+ assert_equal false, described_class.should_send_to_master?([:info])
85
+ assert_equal false, described_class.should_send_to_slave?([:info])
86
+ end
87
+ end
88
+ end