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.
- data/.gitignore +10 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +113 -0
- data/LICENSE +20 -0
- data/README.md +214 -0
- data/Rakefile +260 -0
- data/TODO.md +4 -0
- data/benchmarking/logging.rb +62 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/thread_safety.rb +38 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +31 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis/client.rb +303 -0
- data/lib/redis/connection/command_helper.rb +44 -0
- data/lib/redis/connection/hiredis.rb +52 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +136 -0
- data/lib/redis/connection/synchrony.rb +131 -0
- data/lib/redis/connection.rb +9 -0
- data/lib/redis/distributed.rb +696 -0
- data/lib/redis/errors.rb +38 -0
- data/lib/redis/hash_ring.rb +131 -0
- data/lib/redis/pipeline.rb +106 -0
- data/lib/redis/subscribe.rb +79 -0
- data/lib/redis/version.rb +3 -0
- data/lib/redis.rb +1724 -0
- data/redis.gemspec +43 -0
- data/test/command_map_test.rb +29 -0
- data/test/commands_on_hashes_test.rb +20 -0
- data/test/commands_on_lists_test.rb +60 -0
- data/test/commands_on_sets_test.rb +76 -0
- data/test/commands_on_sorted_sets_test.rb +108 -0
- data/test/commands_on_strings_test.rb +80 -0
- data/test/commands_on_value_types_test.rb +87 -0
- data/test/connection_handling_test.rb +204 -0
- data/test/db/.gitignore +1 -0
- data/test/distributed_blocking_commands_test.rb +53 -0
- data/test/distributed_commands_on_hashes_test.rb +11 -0
- data/test/distributed_commands_on_lists_test.rb +23 -0
- data/test/distributed_commands_on_sets_test.rb +84 -0
- data/test/distributed_commands_on_sorted_sets_test.rb +19 -0
- data/test/distributed_commands_on_strings_test.rb +49 -0
- data/test/distributed_commands_on_value_types_test.rb +72 -0
- data/test/distributed_commands_requiring_clustering_test.rb +148 -0
- data/test/distributed_connection_handling_test.rb +24 -0
- data/test/distributed_internals_test.rb +27 -0
- data/test/distributed_key_tags_test.rb +52 -0
- data/test/distributed_persistence_control_commands_test.rb +23 -0
- data/test/distributed_publish_subscribe_test.rb +100 -0
- data/test/distributed_remote_server_control_commands_test.rb +42 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +59 -0
- data/test/distributed_transactions_test.rb +33 -0
- data/test/encoding_test.rb +15 -0
- data/test/error_replies_test.rb +53 -0
- data/test/helper.rb +155 -0
- data/test/helper_test.rb +8 -0
- data/test/internals_test.rb +152 -0
- data/test/lint/hashes.rb +140 -0
- data/test/lint/internals.rb +36 -0
- data/test/lint/lists.rb +107 -0
- data/test/lint/sets.rb +90 -0
- data/test/lint/sorted_sets.rb +196 -0
- data/test/lint/strings.rb +133 -0
- data/test/lint/value_types.rb +81 -0
- data/test/persistence_control_commands_test.rb +21 -0
- data/test/pipelining_commands_test.rb +186 -0
- data/test/publish_subscribe_test.rb +158 -0
- data/test/redis_mock.rb +89 -0
- data/test/remote_server_control_commands_test.rb +88 -0
- data/test/sorting_test.rb +43 -0
- data/test/synchrony_driver.rb +57 -0
- data/test/test.conf +9 -0
- data/test/thread_safety_test.rb +30 -0
- data/test/transactions_test.rb +173 -0
- data/test/unknown_commands_test.rb +13 -0
- data/test/url_param_test.rb +59 -0
- metadata +236 -0
data/test/redis_mock.rb
ADDED
@@ -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,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
|