libmemcached_store 0.2.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,2 @@
1
1
  require 'active_support/cache/libmemcached_store'
2
- require 'active_support/cache/compressed_libmemcached_store'
2
+ require 'action_dispatch/session/libmemcached_store'
@@ -0,0 +1,42 @@
1
+ #
2
+ # Allow get method to returns value + entry's flags
3
+ # This is useful to set compression flag.
4
+ #
5
+ module GetWithFlags
6
+ def get(keys, marshal=true, with_flags=false)
7
+ if keys.is_a? Array
8
+ # Multi get
9
+ ret = Memcached::Lib.memcached_mget(@struct, keys);
10
+ check_return_code(ret, keys)
11
+
12
+ hash, flags_hash = {}, {}
13
+ value, key, flags, ret = Memcached::Lib.memcached_fetch_rvalue(@struct)
14
+ while ret != 21 do # Lib::MEMCACHED_END
15
+ if ret == 0 # Lib::MEMCACHED_SUCCESS
16
+ hash[key] = value
17
+ flags_hash[key] = flags if with_flags
18
+ elsif ret != 16 # Lib::MEMCACHED_NOTFOUND
19
+ check_return_code(ret, key)
20
+ end
21
+ value, key, flags, ret = Memcached::Lib.memcached_fetch_rvalue(@struct)
22
+ end
23
+ if marshal
24
+ hash.each do |key, value|
25
+ hash[key] = Marshal.load(value)
26
+ end
27
+ end
28
+ with_flags ? [hash, flags_hash] : hash
29
+ else
30
+ # Single get
31
+ value, flags, ret = Memcached::Lib.memcached_get_rvalue(@struct, keys)
32
+ check_return_code(ret, keys)
33
+ value = Marshal.load(value) if marshal
34
+ with_flags ? [value, flags] : value
35
+ end
36
+ rescue => e
37
+ tries ||= 0
38
+ raise unless tries < options[:exception_retry_limit] && should_retry(e)
39
+ tries += 1
40
+ retry
41
+ end
42
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module LibmemcachedStore
2
+ VERSION = "0.6.0"
3
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "libmemcached_store"
7
+ s.version = LibmemcachedStore::VERSION.dup
8
+ s.platform = Gem::Platform::RUBY
9
+ s.summary = "ActiveSupport 3+ cache store for the C-based libmemcached client"
10
+ s.email = "cocchi.c@gmail.com"
11
+ s.homepage = "http://github.com/ccocchi/libmemcached_store"
12
+ s.description = %q{An ActiveSupport cache store that uses the C-based libmemcached client through
13
+ Evan Weaver's Ruby/SWIG wrapper, memcached. libmemcached is fast, lightweight,
14
+ and supports consistent hashing, non-blocking IO, and graceful server failover.}
15
+ s.authors = ["Christopher Cocchi-Perrier", "Ben Hutton", "Jeffrey Hardy"]
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("memcached", ">= 0")
23
+
24
+ s.add_development_dependency('rack')
25
+ s.add_development_dependency('rake')
26
+ s.add_development_dependency('mocha')
27
+ s.add_development_dependency('activesupport', '>= 3')
28
+ s.add_development_dependency('actionpack', '>= 3')
29
+ end
30
+
@@ -0,0 +1,43 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/module/delegation'
3
+ require 'active_support/core_ext/module/attribute_accessors'
4
+ require 'action_controller'
5
+ require 'action_dispatch/routing'
6
+ require 'action_dispatch/middleware/head'
7
+ require 'action_dispatch/testing/assertions'
8
+ require 'action_dispatch/testing/test_process'
9
+ require 'action_dispatch/testing/integration'
10
+
11
+ SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
12
+
13
+ class RoutedRackApp
14
+ attr_reader :routes
15
+
16
+ def initialize(routes, &blk)
17
+ @routes = routes
18
+ @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes)
19
+ end
20
+
21
+ def call(env)
22
+ @stack.call(env)
23
+ end
24
+ end
25
+
26
+ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
27
+ def self.build_app(routes = nil)
28
+ RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
29
+ if defined?(ActionDispatch::PublicExceptions)
30
+ middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("/dev/null")
31
+ middleware.use "ActionDispatch::DebugExceptions"
32
+ else
33
+ middleware.use "ActionDispatch::ShowExceptions"
34
+ end
35
+ middleware.use "ActionDispatch::Callbacks"
36
+ middleware.use "ActionDispatch::ParamsParser"
37
+ middleware.use "ActionDispatch::Cookies"
38
+ middleware.use "ActionDispatch::Flash"
39
+ middleware.use "ActionDispatch::Head"
40
+ yield(middleware) if block_given?
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,200 @@
1
+ require 'test_helper'
2
+ require File.expand_path('../abstract_unit', __FILE__)
3
+ require 'action_dispatch/session/libmemcached_store'
4
+
5
+ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
6
+ class TestController < ActionController::Base
7
+ def no_session_access
8
+ head :ok
9
+ end
10
+
11
+ def set_session_value
12
+ session[:foo] = "bar"
13
+ head :ok
14
+ end
15
+
16
+ def set_serialized_session_value
17
+ session[:foo] = SessionAutoloadTest::Foo.new
18
+ head :ok
19
+ end
20
+
21
+ def get_session_value
22
+ render :text => "foo: #{session[:foo].inspect}"
23
+ end
24
+
25
+ def get_session_id
26
+ render :text => "#{request.session_options[:id]}"
27
+ end
28
+
29
+ def call_reset_session
30
+ session[:bar]
31
+ reset_session
32
+ session[:bar] = "baz"
33
+ head :ok
34
+ end
35
+
36
+ def self._routes
37
+ SharedTestRoutes
38
+ end
39
+ end
40
+
41
+ def test_setting_and_getting_session_value
42
+ with_test_route_set do
43
+ get '/set_session_value'
44
+ assert_response :success
45
+ assert cookies['_session_id']
46
+
47
+ get '/get_session_value'
48
+ assert_response :success
49
+ assert_equal 'foo: "bar"', response.body
50
+ end
51
+ end
52
+
53
+ def test_getting_nil_session_value
54
+ with_test_route_set do
55
+ get '/get_session_value'
56
+ assert_response :success
57
+ assert_equal 'foo: nil', response.body
58
+ end
59
+ end
60
+
61
+ def test_getting_session_value_after_session_reset
62
+ with_test_route_set do
63
+ get '/set_session_value'
64
+ assert_response :success
65
+ assert cookies['_session_id']
66
+ session_cookie = cookies.send(:hash_for)['_session_id']
67
+
68
+ get '/call_reset_session'
69
+ assert_response :success
70
+ assert_not_equal [], headers['Set-Cookie']
71
+
72
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
73
+
74
+ get '/get_session_value'
75
+ assert_response :success
76
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
77
+ end
78
+ end
79
+
80
+ def test_getting_from_nonexistent_session
81
+ with_test_route_set do
82
+ get '/get_session_value'
83
+ assert_response :success
84
+ assert_equal 'foo: nil', response.body
85
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
86
+ end
87
+ end
88
+
89
+ def test_setting_session_value_after_session_reset
90
+ with_test_route_set do
91
+ get '/set_session_value'
92
+ assert_response :success
93
+ assert cookies['_session_id']
94
+ session_id = cookies['_session_id']
95
+
96
+ get '/call_reset_session'
97
+ assert_response :success
98
+ assert_not_equal [], headers['Set-Cookie']
99
+
100
+ get '/get_session_value'
101
+ assert_response :success
102
+ assert_equal 'foo: nil', response.body
103
+
104
+ get '/get_session_id'
105
+ assert_response :success
106
+ assert_not_equal session_id, response.body
107
+ end
108
+ end
109
+
110
+ def test_getting_session_id
111
+ with_test_route_set do
112
+ get '/set_session_value'
113
+ assert_response :success
114
+ assert cookies['_session_id']
115
+ session_id = cookies['_session_id']
116
+
117
+ get '/get_session_id'
118
+ assert_response :success
119
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
120
+ end
121
+ end
122
+
123
+ def test_deserializes_unloaded_class
124
+ with_test_route_set do
125
+ with_autoload_path "session_autoload_test" do
126
+ get '/set_serialized_session_value'
127
+ assert_response :success
128
+ assert cookies['_session_id']
129
+ end
130
+ with_autoload_path "session_autoload_test" do
131
+ get '/get_session_id'
132
+ assert_response :success
133
+ end
134
+ with_autoload_path "session_autoload_test" do
135
+ get '/get_session_value'
136
+ assert_response :success
137
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
138
+ end
139
+ end
140
+ end
141
+
142
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
143
+ with_test_route_set do
144
+ get '/set_session_value'
145
+ assert_response :success
146
+ assert cookies['_session_id']
147
+
148
+ get '/get_session_value'
149
+ assert_response :success
150
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
151
+ end
152
+ end
153
+
154
+ def test_prevents_session_fixation
155
+ with_test_route_set do
156
+ get '/get_session_value'
157
+ assert_response :success
158
+ assert_equal 'foo: nil', response.body
159
+ session_id = cookies['_session_id']
160
+
161
+ reset!
162
+
163
+ get '/set_session_value', :_session_id => session_id
164
+ assert_response :success
165
+ assert_not_equal session_id, cookies['_session_id']
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def with_test_route_set
172
+ with_routing do |set|
173
+ set.draw do
174
+ match ':action', :to => TestController
175
+ end
176
+
177
+ @app = self.class.build_app(set) do |middleware|
178
+ middleware.use ActionDispatch::Session::LibmemcachedStore, :key => '_session_id'
179
+ middleware.delete "ActionDispatch::ShowExceptions"
180
+ end
181
+
182
+ yield
183
+ end
184
+ end
185
+
186
+ def with_autoload_path(path)
187
+ path = File.join(File.dirname(__FILE__), "../fixtures", path)
188
+ if ActiveSupport::Dependencies.autoload_paths.include?(path)
189
+ yield
190
+ else
191
+ begin
192
+ ActiveSupport::Dependencies.autoload_paths << path
193
+ yield
194
+ ensure
195
+ ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
196
+ ActiveSupport::Dependencies.clear
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,341 @@
1
+ # encoding: utf-8
2
+
3
+ require 'test_helper'
4
+ require 'memcached'
5
+ require 'active_support'
6
+ require 'active_support/core_ext/module/aliasing'
7
+ require 'active_support/core_ext/object/duplicable'
8
+ require 'active_support/cache/libmemcached_store'
9
+
10
+ # Make it easier to get at the underlying cache options during testing.
11
+ class ActiveSupport::Cache::LibmemcachedStore
12
+ def client_options
13
+ @cache.options
14
+ end
15
+ end
16
+
17
+ class MockUser
18
+ def cache_key
19
+ 'foo'
20
+ end
21
+ end
22
+
23
+ module CacheStoreBehavior
24
+ def test_fetch_without_cache_miss
25
+ @cache.write('foo', 'bar')
26
+ @cache.expects(:write_entry).never
27
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
28
+ end
29
+
30
+ def test_fetch_with_cache_miss
31
+ @cache.expects(:write_entry).with('foo', 'baz', nil)
32
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
33
+ end
34
+
35
+ def test_fetch_with_forced_cache_miss
36
+ @cache.write('foo', 'bar')
37
+ @cache.expects(:read_entry).never
38
+ @cache.expects(:write_entry).with('foo', 'baz', force: true)
39
+ assert_equal 'baz', @cache.fetch('foo', force: true) { 'baz' }
40
+ end
41
+
42
+ def test_fetch_with_cached_false
43
+ @cache.write('foo', false)
44
+ refute @cache.fetch('foo') { raise }
45
+ end
46
+
47
+ def test_fetch_with_raw_object
48
+ o = Object.new
49
+ o.instance_variable_set :@foo, 'bar'
50
+ assert_equal o, @cache.fetch('foo', raw: true) { o }
51
+ end
52
+
53
+ def test_fetch_with_cache_key
54
+ u = MockUser.new
55
+ @cache.write(u.cache_key, 'bar')
56
+ assert_equal 'bar', @cache.fetch(u) { raise }
57
+ end
58
+
59
+ def test_should_read_and_write_strings
60
+ assert @cache.write('foo', 'bar')
61
+ assert_equal 'bar', @cache.read('foo')
62
+ end
63
+
64
+ def test_should_read_and_write_hash
65
+ assert @cache.write('foo', { a: 'b' })
66
+ assert_equal({ a: 'b' }, @cache.read('foo'))
67
+ end
68
+
69
+ def test_should_read_and_write_integer
70
+ assert @cache.write('foo', 1)
71
+ assert_equal 1, @cache.read('foo')
72
+ end
73
+
74
+ def test_should_read_and_write_nil
75
+ assert @cache.write('foo', nil)
76
+ assert_equal nil, @cache.read('foo')
77
+ end
78
+
79
+ def test_should_read_and_write_false
80
+ assert @cache.write('foo', false)
81
+ assert_equal false, @cache.read('foo')
82
+ end
83
+
84
+ def test_read_and_write_compressed_data
85
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
86
+ assert_equal 'bar', @cache.read('foo')
87
+ end
88
+
89
+ def test_write_should_overwrite
90
+ @cache.write('foo', 'bar')
91
+ @cache.write('foo', 'baz')
92
+ assert_equal 'baz', @cache.read('foo')
93
+ end
94
+
95
+ def test_write_compressed_data
96
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 1, :raw => true)
97
+ assert_equal Zlib::Deflate.deflate('bar'), @cache.instance_variable_get(:@cache).get('foo', false)
98
+ end
99
+
100
+ def test_read_miss
101
+ assert_nil @cache.read('foo')
102
+ end
103
+
104
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
105
+ @cache.write('foo', 'bar')
106
+ refute_equal @cache.read('foo').object_id, @cache.read('foo').object_id
107
+ end
108
+
109
+ def test_read_multi
110
+ @cache.write('foo', 'bar')
111
+ @cache.write('fu', 'baz')
112
+ @cache.write('fud', 'biz')
113
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
114
+ end
115
+
116
+ def test_read_multi_with_array
117
+ @cache.write('foo', 'bar')
118
+ @cache.write('fu', 'baz')
119
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi(['foo', 'fu']))
120
+ end
121
+
122
+ def test_read_multi_with_raw
123
+ @cache.write('foo', 'bar', :raw => true)
124
+ @cache.write('fu', 'baz', :raw => true)
125
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
126
+ end
127
+
128
+ def test_read_multi_with_compress
129
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
130
+ @cache.write('fu', 'baz', :compress => true, :compress_threshold => 1)
131
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
132
+ end
133
+
134
+ def test_cache_key
135
+ o = MockUser.new
136
+ @cache.write(o, 'bar')
137
+ assert_equal 'bar', @cache.read('foo')
138
+ end
139
+
140
+ def test_param_as_cache_key
141
+ obj = Object.new
142
+ def obj.to_param
143
+ 'foo'
144
+ end
145
+ @cache.write(obj, 'bar')
146
+ assert_equal 'bar', @cache.read('foo')
147
+ end
148
+
149
+ def test_array_as_cache_key
150
+ @cache.write([:fu, 'foo'], 'bar')
151
+ assert_equal 'bar', @cache.read('fu/foo')
152
+ end
153
+
154
+ def test_hash_as_cache_key
155
+ @cache.write({:foo => 1, :fu => 2}, 'bar')
156
+ assert_equal 'bar', @cache.read('foo=1/fu=2')
157
+ end
158
+
159
+ def test_keys_are_case_sensitive
160
+ @cache.write('foo', 'bar')
161
+ assert_nil @cache.read('FOO')
162
+ end
163
+
164
+ def test_keys_with_spaces
165
+ assert_equal 'baz', @cache.fetch('foo bar') { 'baz' }
166
+ end
167
+
168
+ def test_exist
169
+ @cache.write('foo', 'bar')
170
+ assert @cache.exist?('foo')
171
+ refute @cache.exist?('bar')
172
+ end
173
+
174
+ def test_delete
175
+ @cache.write('foo', 'bar')
176
+ assert @cache.exist?('foo')
177
+ assert @cache.delete('foo')
178
+ refute @cache.exist?('foo')
179
+ end
180
+
181
+ def test_delete_with_unexistent_key
182
+ @cache.expects(:log_error).never
183
+ refute @cache.exist?('foo')
184
+ refute @cache.delete('foo')
185
+ end
186
+
187
+ def test_store_objects_should_be_immutable
188
+ @cache.write('foo', 'bar')
189
+ @cache.read('foo').gsub!(/.*/, 'baz')
190
+ assert_equal 'bar', @cache.read('foo')
191
+ end
192
+
193
+ def test_original_store_objects_should_not_be_immutable
194
+ bar = 'bar'
195
+ @cache.write('foo', bar)
196
+ assert_equal 'baz', bar.gsub!(/r/, 'z')
197
+ end
198
+
199
+ def test_crazy_key_characters
200
+ crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
201
+ assert @cache.write(crazy_key, "1", :raw => true)
202
+ assert_equal "1", @cache.read(crazy_key)
203
+ assert_equal "1", @cache.fetch(crazy_key)
204
+ assert @cache.delete(crazy_key)
205
+ refute @cache.exist?(crazy_key)
206
+ assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
207
+ assert_equal 3, @cache.increment(crazy_key)
208
+ assert_equal 2, @cache.decrement(crazy_key)
209
+ end
210
+
211
+ def test_really_long_keys
212
+ key = "a" * 251
213
+ assert @cache.write(key, "bar")
214
+ assert_equal "bar", @cache.read(key)
215
+ assert_equal "bar", @cache.fetch(key)
216
+ assert_nil @cache.read("#{key}x")
217
+ assert_equal({key => "bar"}, @cache.read_multi(key))
218
+ assert @cache.delete(key)
219
+ refute @cache.exist?(key)
220
+ assert @cache.write(key, '2', :raw => true)
221
+ assert_equal 3, @cache.increment(key)
222
+ assert_equal 2, @cache.decrement(key)
223
+ end
224
+
225
+ def test_really_long_keys_with_namespace
226
+ @cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, :expires_in => 60, :namespace => 'namespace')
227
+ @cache.silence!
228
+ test_really_long_keys
229
+ end
230
+
231
+ def test_clear
232
+ @cache.write("foo", "bar")
233
+ @cache.clear
234
+ assert_nil @cache.read("foo")
235
+ end
236
+
237
+ def test_clear_with_options
238
+ @cache.write("foo", "bar")
239
+ @cache.clear(:some_option => true)
240
+ assert_nil @cache.read("foo")
241
+ end
242
+ end
243
+
244
+ module CacheIncrementDecrementBehavior
245
+ def test_increment
246
+ @cache.write('foo', '1', :raw => true)
247
+ assert_equal 1, @cache.read('foo').to_i
248
+ assert_equal 2, @cache.increment('foo')
249
+ assert_equal 2, @cache.read('foo').to_i
250
+ assert_equal 3, @cache.increment('foo')
251
+ assert_equal 3, @cache.read('foo').to_i
252
+ end
253
+
254
+ def test_decrement
255
+ @cache.write('foo', '3', :raw => true)
256
+ assert_equal 3, @cache.read('foo').to_i
257
+ assert_equal 2, @cache.decrement('foo')
258
+ assert_equal 2, @cache.read('foo').to_i
259
+ assert_equal 1, @cache.decrement('foo')
260
+ assert_equal 1, @cache.read('foo').to_i
261
+ end
262
+
263
+ def test_increment_decrement_non_existing_keys
264
+ @cache.expects(:log_error).never
265
+ assert_nil @cache.increment('foo')
266
+ assert_nil @cache.decrement('bar')
267
+ end
268
+ end
269
+
270
+ module CacheCompressBehavior
271
+ def test_read_and_write_compressed_small_data
272
+ @cache.write('foo', 'bar', :compress => true)
273
+ raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
274
+ assert_equal 'bar', @cache.read('foo')
275
+ value = Marshal.load(raw_value) rescue raw_value
276
+ assert_equal 'bar', value
277
+ end
278
+
279
+ def test_read_and_write_compressed_large_data
280
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
281
+ raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
282
+ assert_equal 'bar', @cache.read('foo')
283
+ assert_equal 'bar', Marshal.load(Zlib::Inflate.inflate(raw_value))
284
+ end
285
+ end
286
+
287
+ class LibmemcachedStoreTest < MiniTest::Unit::TestCase
288
+ include CacheStoreBehavior
289
+ include CacheIncrementDecrementBehavior
290
+
291
+ def setup
292
+ @cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, expires_in: 60)
293
+ @cache.clear
294
+ @cache.silence!
295
+ end
296
+
297
+ def test_should_identify_cache_store
298
+ assert_kind_of ActiveSupport::Cache::LibmemcachedStore, @cache
299
+ end
300
+
301
+ def test_should_set_server_addresses_to_nil_if_none_are_given
302
+ assert_equal [], @cache.addresses
303
+ end
304
+
305
+ def test_should_set_custom_server_addresses
306
+ store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', '192.168.1.1'
307
+ assert_equal %w(localhost 192.168.1.1), store.addresses
308
+ end
309
+
310
+ def test_should_enable_consistent_ketema_hashing_by_default
311
+ assert_equal :consistent_ketama, @cache.client_options[:distribution]
312
+ end
313
+
314
+ def test_should_not_enable_non_blocking_io_by_default
315
+ assert_equal false, @cache.client_options[:no_block]
316
+ end
317
+
318
+ def test_should_not_enable_server_failover_by_default
319
+ assert_nil @cache.client_options[:failover]
320
+ end
321
+
322
+ def test_should_allow_configuration_of_custom_options
323
+ options = { client: { tcp_nodelay: true, distribution: :modula } }
324
+
325
+ store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', options
326
+
327
+ assert_equal :modula, store.client_options[:distribution]
328
+ assert_equal true, store.client_options[:tcp_nodelay]
329
+ end
330
+
331
+ def test_should_allow_mute_and_silence
332
+ cache = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost'
333
+ cache.mute do
334
+ assert cache.write('foo', 'bar')
335
+ assert_equal 'bar', cache.read('foo')
336
+ end
337
+ refute cache.silence?
338
+ cache.silence!
339
+ assert cache.silence?
340
+ end
341
+ end