redis 4.0.2 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
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