libmemcached_store 0.2.3 → 0.6.0

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.
@@ -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