ryansch-mock_redis 0.2.0.1

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 (138) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +1 -0
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +9 -0
  5. data/LICENSE +19 -0
  6. data/README.md +94 -0
  7. data/Rakefile +10 -0
  8. data/lib/mock_redis.rb +32 -0
  9. data/lib/mock_redis/assertions.rb +13 -0
  10. data/lib/mock_redis/database.rb +432 -0
  11. data/lib/mock_redis/exceptions.rb +3 -0
  12. data/lib/mock_redis/expire_wrapper.rb +25 -0
  13. data/lib/mock_redis/hash_methods.rb +102 -0
  14. data/lib/mock_redis/list_methods.rb +187 -0
  15. data/lib/mock_redis/multi_db_wrapper.rb +86 -0
  16. data/lib/mock_redis/set_methods.rb +125 -0
  17. data/lib/mock_redis/string_methods.rb +195 -0
  18. data/lib/mock_redis/transaction_wrapper.rb +80 -0
  19. data/lib/mock_redis/undef_redis_methods.rb +11 -0
  20. data/lib/mock_redis/utility_methods.rb +22 -0
  21. data/lib/mock_redis/version.rb +3 -0
  22. data/lib/mock_redis/zset.rb +110 -0
  23. data/lib/mock_redis/zset_methods.rb +209 -0
  24. data/mock_redis.gemspec +24 -0
  25. data/spec/cloning_spec.rb +96 -0
  26. data/spec/commands/append_spec.rb +24 -0
  27. data/spec/commands/auth_spec.rb +7 -0
  28. data/spec/commands/bgrewriteaof_spec.rb +7 -0
  29. data/spec/commands/bgsave_spec.rb +7 -0
  30. data/spec/commands/blpop_spec.rb +55 -0
  31. data/spec/commands/brpop_spec.rb +54 -0
  32. data/spec/commands/brpoplpush_spec.rb +53 -0
  33. data/spec/commands/dbsize_spec.rb +18 -0
  34. data/spec/commands/decr_spec.rb +34 -0
  35. data/spec/commands/decrby_spec.rb +34 -0
  36. data/spec/commands/del_spec.rb +20 -0
  37. data/spec/commands/echo_spec.rb +11 -0
  38. data/spec/commands/exists_spec.rb +14 -0
  39. data/spec/commands/expire_spec.rb +83 -0
  40. data/spec/commands/expireat_spec.rb +48 -0
  41. data/spec/commands/flushall_spec.rb +38 -0
  42. data/spec/commands/flushdb_spec.rb +38 -0
  43. data/spec/commands/get_spec.rb +23 -0
  44. data/spec/commands/getbit_spec.rb +34 -0
  45. data/spec/commands/getrange_spec.rb +22 -0
  46. data/spec/commands/getset_spec.rb +23 -0
  47. data/spec/commands/hdel_spec.rb +35 -0
  48. data/spec/commands/hexists_spec.rb +22 -0
  49. data/spec/commands/hget_spec.rb +23 -0
  50. data/spec/commands/hgetall_spec.rb +22 -0
  51. data/spec/commands/hincrby_spec.rb +52 -0
  52. data/spec/commands/hkeys_spec.rb +19 -0
  53. data/spec/commands/hlen_spec.rb +19 -0
  54. data/spec/commands/hmget_spec.rb +30 -0
  55. data/spec/commands/hmset_spec.rb +43 -0
  56. data/spec/commands/hset_spec.rb +23 -0
  57. data/spec/commands/hsetnx_spec.rb +39 -0
  58. data/spec/commands/hvals_spec.rb +19 -0
  59. data/spec/commands/incr_spec.rb +34 -0
  60. data/spec/commands/incrby_spec.rb +44 -0
  61. data/spec/commands/info_spec.rb +13 -0
  62. data/spec/commands/keys_spec.rb +87 -0
  63. data/spec/commands/lastsave_spec.rb +8 -0
  64. data/spec/commands/lindex_spec.rb +39 -0
  65. data/spec/commands/linsert_spec.rb +68 -0
  66. data/spec/commands/llen_spec.rb +16 -0
  67. data/spec/commands/lpop_spec.rb +34 -0
  68. data/spec/commands/lpush_spec.rb +30 -0
  69. data/spec/commands/lpushx_spec.rb +33 -0
  70. data/spec/commands/lrange_spec.rb +35 -0
  71. data/spec/commands/lrem_spec.rb +79 -0
  72. data/spec/commands/lset_spec.rb +38 -0
  73. data/spec/commands/ltrim_spec.rb +35 -0
  74. data/spec/commands/mget_spec.rb +34 -0
  75. data/spec/commands/move_spec.rb +147 -0
  76. data/spec/commands/mset_spec.rb +29 -0
  77. data/spec/commands/msetnx_spec.rb +40 -0
  78. data/spec/commands/persist_spec.rb +49 -0
  79. data/spec/commands/ping_spec.rb +7 -0
  80. data/spec/commands/quit_spec.rb +7 -0
  81. data/spec/commands/randomkey_spec.rb +20 -0
  82. data/spec/commands/rename_spec.rb +31 -0
  83. data/spec/commands/renamenx_spec.rb +36 -0
  84. data/spec/commands/rpop_spec.rb +34 -0
  85. data/spec/commands/rpoplpush_spec.rb +45 -0
  86. data/spec/commands/rpush_spec.rb +30 -0
  87. data/spec/commands/rpushx_spec.rb +33 -0
  88. data/spec/commands/sadd_spec.rb +22 -0
  89. data/spec/commands/save_spec.rb +7 -0
  90. data/spec/commands/scard_spec.rb +18 -0
  91. data/spec/commands/sdiff_spec.rb +47 -0
  92. data/spec/commands/sdiffstore_spec.rb +58 -0
  93. data/spec/commands/select_spec.rb +53 -0
  94. data/spec/commands/set_spec.rb +7 -0
  95. data/spec/commands/setbit_spec.rb +46 -0
  96. data/spec/commands/setex_spec.rb +22 -0
  97. data/spec/commands/setnx_spec.rb +25 -0
  98. data/spec/commands/setrange_spec.rb +30 -0
  99. data/spec/commands/sinter_spec.rb +41 -0
  100. data/spec/commands/sinterstore_spec.rb +53 -0
  101. data/spec/commands/sismember_spec.rb +29 -0
  102. data/spec/commands/smembers_spec.rb +18 -0
  103. data/spec/commands/smove_spec.rb +41 -0
  104. data/spec/commands/spop_spec.rb +25 -0
  105. data/spec/commands/srandmember_spec.rb +25 -0
  106. data/spec/commands/srem_spec.rb +35 -0
  107. data/spec/commands/strlen_spec.rb +19 -0
  108. data/spec/commands/sunion_spec.rb +40 -0
  109. data/spec/commands/sunionstore_spec.rb +53 -0
  110. data/spec/commands/ttl_spec.rb +36 -0
  111. data/spec/commands/type_spec.rb +36 -0
  112. data/spec/commands/unwatch_spec.rb +7 -0
  113. data/spec/commands/watch_spec.rb +7 -0
  114. data/spec/commands/zadd_spec.rb +29 -0
  115. data/spec/commands/zcard_spec.rb +19 -0
  116. data/spec/commands/zcount_spec.rb +23 -0
  117. data/spec/commands/zincrby_spec.rb +24 -0
  118. data/spec/commands/zinterstore_spec.rb +96 -0
  119. data/spec/commands/zrange_spec.rb +31 -0
  120. data/spec/commands/zrangebyscore_spec.rb +68 -0
  121. data/spec/commands/zrank_spec.rb +23 -0
  122. data/spec/commands/zrem_spec.rb +25 -0
  123. data/spec/commands/zremrangebyrank_spec.rb +22 -0
  124. data/spec/commands/zremrangebyscore_spec.rb +28 -0
  125. data/spec/commands/zrevrange_spec.rb +31 -0
  126. data/spec/commands/zrevrangebyscore_spec.rb +47 -0
  127. data/spec/commands/zrevrank_spec.rb +23 -0
  128. data/spec/commands/zscore_spec.rb +16 -0
  129. data/spec/commands/zunionstore_spec.rb +104 -0
  130. data/spec/spec_helper.rb +44 -0
  131. data/spec/support/redis_multiplexer.rb +91 -0
  132. data/spec/support/shared_examples/only_operates_on_hashes.rb +13 -0
  133. data/spec/support/shared_examples/only_operates_on_lists.rb +13 -0
  134. data/spec/support/shared_examples/only_operates_on_sets.rb +13 -0
  135. data/spec/support/shared_examples/only_operates_on_strings.rb +13 -0
  136. data/spec/support/shared_examples/only_operates_on_zsets.rb +57 -0
  137. data/spec/transactions_spec.rb +96 -0
  138. metadata +361 -0
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe "#zrevrank(key, member)" do
4
+ before do
5
+ @key = 'mock-redis-test:zrevrank'
6
+
7
+ @redises.zadd(@key, 1, 'one')
8
+ @redises.zadd(@key, 2, 'two')
9
+ @redises.zadd(@key, 3, 'three')
10
+ end
11
+
12
+ it "returns nil if member wasn't present in the set" do
13
+ @redises.zrevrank(@key, 'foo').should be_nil
14
+ end
15
+
16
+ it "returns the index of the member in the set (ordered by -score)" do
17
+ @redises.zrevrank(@key, 'one').should == 2
18
+ @redises.zrevrank(@key, 'two').should == 1
19
+ @redises.zrevrank(@key, 'three').should == 0
20
+ end
21
+
22
+ it_should_behave_like "a zset-only command"
23
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe "#zscore(key, member)" do
4
+ before { @key = 'mock-redis-test:zscore' }
5
+
6
+ it "returns the score as a string" do
7
+ @redises.zadd(@key, 0.25, 'foo').should be_true
8
+ @redises.zscore(@key, 'foo').should == "0.25"
9
+ end
10
+
11
+ it "returns nil if member is not present in the set" do
12
+ @redises.zscore(@key, 'foo').should be_nil
13
+ end
14
+
15
+ it_should_behave_like "a zset-only command"
16
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe "#zunionstore(destination, keys, [:weights => [w,w,], [:aggregate => :sum|:min|:max])" do
4
+ before do
5
+ @set1 = 'mock-redis-test:zunionstore1'
6
+ @set2 = 'mock-redis-test:zunionstore2'
7
+ @set3 = 'mock-redis-test:zunionstore3'
8
+ @dest = 'mock-redis-test:zunionstoredest'
9
+
10
+ @redises.zadd(@set1, 1, 'one')
11
+
12
+ @redises.zadd(@set2, 1, 'one')
13
+ @redises.zadd(@set2, 2, 'two')
14
+
15
+ @redises.zadd(@set3, 1, 'one')
16
+ @redises.zadd(@set3, 2, 'two')
17
+ @redises.zadd(@set3, 3, 'three')
18
+ end
19
+
20
+ it "returns the number of elements in the new set" do
21
+ @redises.zunionstore(@dest, [@set1, @set2, @set3]).should == 3
22
+ end
23
+
24
+ it "sums the members' scores by default" do
25
+ @redises.zunionstore(@dest, [@set1, @set2, @set3])
26
+ @redises.zrange(@dest, 0, -1, :with_scores => true).should ==
27
+ %w[one 3 three 3 two 4]
28
+ end
29
+
30
+ it "removes existing elements in destination" do
31
+ @redises.zadd(@dest, 10, 'ten')
32
+
33
+ @redises.zunionstore(@dest, [@set1])
34
+ @redises.zrange(@dest, 0, -1, :with_scores => true).should ==
35
+ %w[one 1]
36
+ end
37
+
38
+ it "raises an error if keys is empty" do
39
+ lambda do
40
+ @redises.zunionstore(@dest, [])
41
+ end.should raise_error(RuntimeError)
42
+ end
43
+
44
+ context "the :weights argument" do
45
+ it "multiplies the scores by the weights while aggregating" do
46
+ @redises.zunionstore(@dest, [@set1, @set2, @set3], :weights => [2, 3, 5])
47
+ @redises.zrange(@dest, 0, -1, :with_scores => true).should ==
48
+ %w[one 10 three 15 two 16]
49
+ end
50
+
51
+ it "raises an error if the number of weights != the number of keys" do
52
+ lambda do
53
+ @redises.zunionstore(@dest, [@set1, @set2, @set3], :weights => [1,2])
54
+ end.should raise_error(RuntimeError)
55
+ end
56
+ end
57
+
58
+ context "the :aggregate argument" do
59
+ before do
60
+ @smalls = 'mock-redis-test:zunionstore:smalls'
61
+ @bigs = 'mock-redis-test:zunionstore:bigs'
62
+
63
+ @redises.zadd(@smalls, 1, 'bert')
64
+ @redises.zadd(@smalls, 2, 'ernie')
65
+ @redises.zadd(@bigs, 100, 'bert')
66
+ @redises.zadd(@bigs, 200, 'ernie')
67
+ end
68
+
69
+ it "aggregates scores with min when :aggregate => :min is specified" do
70
+ @redises.zunionstore(@dest, [@bigs, @smalls], :aggregate => :min)
71
+ @redises.zrange(@dest, 0, -1, :with_scores => true).should ==
72
+ %w[bert 1 ernie 2]
73
+ end
74
+
75
+ it "aggregates scores with max when :aggregate => :max is specified" do
76
+ @redises.zunionstore(@dest, [@bigs, @smalls], :aggregate => :max)
77
+ @redises.zrange(@dest, 0, -1, :with_scores => true).should ==
78
+ %w[bert 100 ernie 200]
79
+ end
80
+
81
+ it "ignores scores for missing members" do
82
+ @redises.zadd(@smalls, 3, 'grover')
83
+ @redises.zunionstore(@dest, [@bigs, @smalls], :aggregate => :min)
84
+ @redises.zscore(@dest, 'grover').should == '3'
85
+
86
+ @redises.zunionstore(@dest, [@bigs, @smalls], :aggregate => :max)
87
+ @redises.zscore(@dest, 'grover').should == '3'
88
+ end
89
+
90
+ it "allows 'min', 'MIN', etc. as aliases for :min" do
91
+ @redises.zunionstore(@dest, [@bigs, @smalls], :aggregate => 'min')
92
+ @redises.zscore(@dest, 'bert').should == '1'
93
+
94
+ @redises.zunionstore(@dest, [@bigs, @smalls], :aggregate => 'MIN')
95
+ @redises.zscore(@dest, 'bert').should == '1'
96
+ end
97
+
98
+ it "raises an error for unknown aggregation function" do
99
+ lambda do
100
+ @redises.zunionstore(@dest, [@bigs, @smalls], :aggregate => :mix)
101
+ end.should raise_error(RuntimeError)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,44 @@
1
+ require 'rspec'
2
+ require 'redis'
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "..", "..", "lib")))
4
+ require 'mock_redis'
5
+
6
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..')))
7
+ Dir["spec/support/**/*.rb"].each {|x| require x}
8
+
9
+ module TypeCheckingHelper
10
+ def method_from_description
11
+ # extracting this from the RSpec description string may or may not
12
+ # be a good idea. On the one hand, it enforces the convention of
13
+ # putting the method name in the right place; on the other hand,
14
+ # it's pretty magic-looking.
15
+ self.example.full_description.match(/#(\w+)/).captures.first
16
+ end
17
+
18
+ def args_for_method(method)
19
+ method_arity = @redises.real.method(method).arity
20
+ if method_arity < 0 # -1 comes from def foo(*args)
21
+ [1, 2] # probably good enough
22
+ else
23
+ 1.upto(method_arity - 1).to_a
24
+ end
25
+ end
26
+ end
27
+
28
+ RSpec.configure do |config|
29
+ config.include(TypeCheckingHelper)
30
+
31
+ config.before(:all) do
32
+ @redises = RedisMultiplexer.new
33
+ end
34
+
35
+ config.before(:each) do
36
+ # databases mentioned in our tests
37
+ [1, 0].each do |db|
38
+ @redises.send_without_checking(:select, db)
39
+ @redises.send_without_checking(:keys, "mock-redis-test:*").each do |key|
40
+ @redises.send_without_checking(:del, key)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,91 @@
1
+ class BlankSlate
2
+ instance_methods.each {|m| undef_method(m) unless m =~ /^__/ || ['inspect', 'object_id'].include?(m.to_s)}
3
+ end
4
+
5
+ class RedisMultiplexer < BlankSlate
6
+ MismatchedResponse = Class.new(StandardError)
7
+
8
+ def initialize(*a)
9
+ @mock_redis = MockRedis.new(*a)
10
+ @real_redis = Redis.new(*a)
11
+ end
12
+
13
+ def method_missing(method, *args, &blk)
14
+ mock_retval, mock_error = catch_errors { @mock_redis.send(method, *args, &blk) }
15
+ real_retval, real_error = catch_errors { @real_redis.send(method, *args, &blk) }
16
+
17
+ mock_retval = handle_special_cases(method, mock_retval)
18
+ real_retval = handle_special_cases(method, real_retval)
19
+
20
+ if (!equalish?(mock_retval, real_retval) && !mock_error && !real_error)
21
+ # no exceptions, just different behavior
22
+ raise MismatchedResponse,
23
+ "Mock failure: responses not equal.\n" +
24
+ "Redis.#{method}(#{args.inspect}) returned #{real_retval.inspect}\n" +
25
+ "MockRedis.#{method}(#{args.inspect}) returned #{mock_retval.inspect}\n"
26
+ elsif (!mock_error && real_error)
27
+ raise MismatchedResponse,
28
+ "Mock failure: didn't raise an error when it should have.\n" +
29
+ "Redis.#{method}(#{args.inspect}) raised #{real_error.inspect}\n" +
30
+ "MockRedis.#{method}(#{args.inspect}) raised nothing " +
31
+ "and returned #{mock_retval.inspect}"
32
+ elsif (!real_error && mock_error)
33
+ raise MismatchedResponse,
34
+ "Mock failure: raised an error when it shouldn't have.\n" +
35
+ "Redis.#{method}(#{args.inspect}) returned #{real_retval.inspect}\n" +
36
+ "MockRedis.#{method}(#{args.inspect}) raised #{mock_error.inspect}"
37
+ elsif (mock_error && real_error && !equalish?(mock_error, real_error))
38
+ raise MismatchedResponse,
39
+ "Mock failure: raised the wrong error.\n" +
40
+ "Redis.#{method}(#{args.inspect}) raised #{real_error.inspect}\n" +
41
+ "MockRedis.#{method}(#{args.inspect}) raised #{mock_error.inspect}"
42
+ end
43
+
44
+ raise mock_error if mock_error
45
+ mock_retval
46
+ end
47
+
48
+ def equalish?(a, b)
49
+ if a == b
50
+ true
51
+ elsif a.is_a?(Array) && b.is_a?(Array)
52
+ a.zip(b).all? {|(x,y)| equalish?(x,y)}
53
+ elsif a.is_a?(Exception) && b.is_a?(Exception)
54
+ a.class == b.class && a.message == b.message
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ def mock() @mock_redis end
61
+ def real() @real_redis end
62
+
63
+ # Some commands require special handling due to nondeterminism in
64
+ # the returned values.
65
+ def handle_special_cases(method, value)
66
+ case method.to_s
67
+ when 'keys', 'hkeys', 'sdiff', 'sinter', 'smembers', 'sunion'
68
+ # The order is irrelevant, but [a,b] != [b,a] in Ruby, so we
69
+ # sort the returned values so we can ignore the order.
70
+ value.sort if value
71
+ else
72
+ value
73
+ end
74
+ end
75
+
76
+ # Used in cleanup before() blocks.
77
+ def send_without_checking(method, *args)
78
+ @mock_redis.send(method, *args)
79
+ @real_redis.send(method, *args)
80
+ end
81
+
82
+ def catch_errors
83
+ begin
84
+ retval = yield
85
+ [retval, nil]
86
+ rescue StandardError => e
87
+ [nil, e]
88
+ end
89
+ end
90
+ end
91
+
@@ -0,0 +1,13 @@
1
+ shared_examples_for "a hash-only command" do
2
+ it "raises an error for non-hash values" do
3
+ key = 'mock-redis-test:hash-only'
4
+
5
+ method = method_from_description
6
+ args = args_for_method(method).unshift(key)
7
+
8
+ @redises.set(key, 1)
9
+ lambda do
10
+ @redises.send(method, *args)
11
+ end.should raise_error(RuntimeError)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ shared_examples_for "a list-only command" do
2
+ it "raises an error for non-list values" do
3
+ key = 'mock-redis-test:list-only'
4
+
5
+ method = method_from_description
6
+ args = args_for_method(method).unshift(key)
7
+
8
+ @redises.set(key, 1)
9
+ lambda do
10
+ @redises.send(method, *args)
11
+ end.should raise_error(RuntimeError)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ shared_examples_for "a set-only command" do
2
+ it "raises an error for non-set values" do
3
+ key = 'mock-redis-test:set-only'
4
+
5
+ method = method_from_description
6
+ args = args_for_method(method).unshift(key)
7
+
8
+ @redises.set(key, 1)
9
+ lambda do
10
+ @redises.send(method, *args)
11
+ end.should raise_error(RuntimeError)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ shared_examples_for "a string-only command" do
2
+ it "raises an error for non-string values" do
3
+ key = "mock-redis-test:string-only-command"
4
+
5
+ method = method_from_description
6
+ args = args_for_method(method).unshift(key)
7
+
8
+ @redises.lpush(key, 1)
9
+ lambda do
10
+ @redises.send(method, *args)
11
+ end.should raise_error(RuntimeError)
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ shared_examples_for "a zset-only command" do
2
+ it "raises an error for non-zset values" do
3
+ key = 'mock-redis-test:zset-only'
4
+
5
+ method = method_from_description
6
+ args = args_for_method(method).unshift(key)
7
+
8
+ @redises.set(key, 1)
9
+ lambda do
10
+ @redises.send(method, *args)
11
+ end.should raise_error(RuntimeError)
12
+ end
13
+ end
14
+
15
+ shared_examples_for "arg 1 is a score" do
16
+ before { @_arg_index = 1 }
17
+ it_should_behave_like "arg N is a score"
18
+ end
19
+
20
+ shared_examples_for "arg 2 is a score" do
21
+ before { @_arg_index = 2 }
22
+ it_should_behave_like "arg N is a score"
23
+ end
24
+
25
+ shared_examples_for "arg N is a score" do
26
+ before do
27
+ key = 'mock-redis-test:zset-only'
28
+
29
+ @method = method_from_description
30
+ @args = args_for_method(@method).unshift(key)
31
+ end
32
+
33
+ it "is okay with positive ints" do
34
+ @args[@_arg_index] = 1
35
+ lambda { @redises.send(@method, *@args) }.should_not raise_error
36
+ end
37
+
38
+ it "is okay with negative ints" do
39
+ @args[@_arg_index] = -1
40
+ lambda { @redises.send(@method, *@args) }.should_not raise_error
41
+ end
42
+
43
+ it "is okay with positive floats" do
44
+ @args[@_arg_index] = 1.5
45
+ lambda { @redises.send(@method, *@args) }.should_not raise_error
46
+ end
47
+
48
+ it "is okay with negative floats" do
49
+ @args[@_arg_index] = 1.5
50
+ lambda { @redises.send(@method, *@args) }.should_not raise_error
51
+ end
52
+
53
+ it "rejects non-numbers" do
54
+ @args[@_arg_index] = 'foo'
55
+ lambda { @redises.send(@method, *@args) }.should raise_error(RuntimeError)
56
+ end
57
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'transactions (multi/exec/discard)' do
4
+ before(:each) do
5
+ @redises.discard rescue nil
6
+ end
7
+
8
+ context "#multi" do
9
+ it "responds with 'OK'" do
10
+ @redises.multi.should == 'OK'
11
+ end
12
+
13
+ it "forbids nesting" do
14
+ @redises.multi
15
+ lambda do
16
+ @redises.multi
17
+ end.should raise_error(RuntimeError)
18
+ end
19
+ end
20
+
21
+ context "#blocks" do
22
+ it "implicitly runs exec when finished" do
23
+ @redises.set("counter", 5)
24
+ @redises.multi do |r|
25
+ r.set("test", 1)
26
+ r.incr("counter")
27
+ end
28
+ @redises.get("counter").should == "6"
29
+ @redises.get("test").should == "1"
30
+ end
31
+
32
+ it "forbids nesting via blocks" do
33
+ # Have to use only the mock here. redis-rb has a bug in it where
34
+ # nested #multi calls raise NoMethodError because it gets a nil
35
+ # where it's not expecting one.
36
+ @redises.mock.multi do |r|
37
+ lambda do
38
+ r.multi {}
39
+ end.should raise_error(RuntimeError)
40
+ end
41
+ end
42
+ end
43
+
44
+ context "#discard" do
45
+ it "responds with 'OK' after #multi" do
46
+ @redises.multi
47
+ @redises.discard.should == 'OK'
48
+ end
49
+
50
+ it "can't be run outside of #multi/#exec" do
51
+ lambda do
52
+ @redises.discard
53
+ end.should raise_error(RuntimeError)
54
+ end
55
+ end
56
+
57
+ context "#exec" do
58
+ it "raises an error outside of #multi" do
59
+ lambda do
60
+ @redises.exec.should raise_error
61
+ end
62
+ end
63
+ end
64
+
65
+ context "saving up commands for later" do
66
+ before(:each) do
67
+ @redises.multi
68
+ @string = 'mock-redis-test:string'
69
+ @list = 'mock-redis-test:list'
70
+ end
71
+
72
+ it "makes commands respond with 'QUEUED'" do
73
+ @redises.set(@string, 'string').should == 'QUEUED'
74
+ @redises.lpush(@list, 'list').should == 'QUEUED'
75
+ end
76
+
77
+ it "gives you the commands' responses when you call #exec" do
78
+ @redises.set(@string, 'string')
79
+ @redises.lpush(@list, 'list')
80
+ @redises.lpush(@list, 'list')
81
+
82
+ @redises.exec.should == ['OK', 1, 2]
83
+ end
84
+
85
+ it "does not raise exceptions, but rather puts them in #exec's response" do
86
+ @redises.set(@string, 'string')
87
+ @redises.lpush(@string, 'oops!')
88
+ @redises.lpush(@list, 'list')
89
+
90
+ responses = @redises.exec
91
+ responses[0].should == 'OK'
92
+ responses[1].should be_a(RuntimeError)
93
+ responses[2].should == 1
94
+ end
95
+ end
96
+ end