libmemcached_store 0.6.2 → 0.7.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.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  .DS_Store
2
2
  *.swp
3
3
  Gemfile.lock
4
+ gemfiles/*.lock
@@ -1,7 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
+ - 2.0.0
4
5
  gemfile:
5
6
  - gemfiles/rails30.gemfile
6
7
  - gemfiles/rails31.gemfile
7
- - gemfiles/rails32.gemfile
8
+ - gemfiles/rails32.gemfile
9
+ - gemfiles/rails40.gemfile
10
+
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+ * Add support for memcached 1.7 (grosser)
5
+ * Test with Rails 4 (grosser)
6
+ * Add race_condition_ttl option to #fetch (grosser)
7
+
3
8
  ## 0.6.2
4
9
  * Add :hit to the #fetch payload
5
10
 
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activesupport', '~> 4.0.0'
4
+ gem 'actionpack', '~> 4.0.0'
5
+ gem 'mocha'
6
+
7
+ gemspec :path => '../'
@@ -9,7 +9,7 @@ module ActionDispatch
9
9
  options[:expire_after] ||= options[:expires]
10
10
  super
11
11
  client_options = { default_ttl: options.fetch(:expire_after, 0) }
12
- client_options[:namespace] = options[:namespace] || 'rack:session'
12
+ client_options[:prefix_key] = options[:prefix_key] || options[:namespace] || 'rack:session'
13
13
  @mutex = Mutex.new
14
14
  @pool = options[:cache] || Memcached.new(@default_options[:memcache_server], client_options)
15
15
  end
@@ -27,6 +27,23 @@ module ActiveSupport
27
27
  include GetWithFlags
28
28
  end
29
29
 
30
+ class FetchWithRaceConditionTTLEntry
31
+ attr_accessor :value, :extended
32
+
33
+ def initialize(value, expires_in)
34
+ @value, @extended = value, false
35
+ @expires_at = Time.now.to_i + expires_in
36
+ end
37
+
38
+ def expires_in
39
+ [@expires_at - Time.now.to_i, 1].max # never set to 0 -> never expires
40
+ end
41
+
42
+ def expired?
43
+ @expires_at <= Time.now.to_i
44
+ end
45
+ end
46
+
30
47
  attr_reader :addresses
31
48
 
32
49
  DEFAULT_CLIENT_OPTIONS = { distribution: :consistent_ketama, binary_protocol: true, default_ttl: 0 }
@@ -64,40 +81,69 @@ module ActiveSupport
64
81
  end
65
82
  client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in]
66
83
 
67
- @options = options.reverse_merge(compress_threshold: DEFAULT_COMPRESS_THRESHOLD)
84
+ @options = {compress_threshold: DEFAULT_COMPRESS_THRESHOLD}.merge(options)
68
85
  @addresses = addresses
69
- @cache = MemcachedWithFlags.new(@addresses, client_options.reverse_merge(DEFAULT_CLIENT_OPTIONS))
86
+ @cache = MemcachedWithFlags.new(@addresses, DEFAULT_CLIENT_OPTIONS.merge(client_options))
70
87
  end
71
88
 
72
- def fetch(key, options = nil)
89
+ def fetch(key, options = nil, &block)
73
90
  if block_given?
74
- key = expanded_key(key)
75
- unless options && options[:force]
76
- entry = instrument(:read, key, options) do |payload|
77
- read_entry(key, options).tap do |result|
78
- if payload
79
- payload[:super_operation] = :fetch
80
- payload[:hit] = !!result
91
+ if options && options[:race_condition_ttl]
92
+ fetch_with_race_condition_ttl(key, options, &block)
93
+ else
94
+ key = expanded_key(key)
95
+ unless options && options[:force]
96
+ entry = instrument(:read, key, options) do |payload|
97
+ read_entry(key, options).tap do |result|
98
+ if payload
99
+ payload[:super_operation] = :fetch
100
+ payload[:hit] = !!result
101
+ end
81
102
  end
82
103
  end
83
104
  end
84
- end
85
105
 
86
- if entry.nil?
87
- result = instrument(:generate, key, options) do |payload|
88
- yield
106
+ if entry.nil?
107
+ result = instrument(:generate, key, options) do |payload|
108
+ yield
109
+ end
110
+ write_entry(key, result, options)
111
+ result
112
+ else
113
+ instrument(:fetch_hit, key, options) { |payload| }
114
+ entry
89
115
  end
90
- write_entry(key, result, options)
91
- result
92
- else
93
- instrument(:fetch_hit, key, options) { |payload| }
94
- entry
95
116
  end
96
117
  else
97
118
  read(key, options)
98
119
  end
99
120
  end
100
121
 
122
+ def fetch_with_race_condition_ttl(key, options={}, &block)
123
+ options = options.dup
124
+
125
+ race_ttl = options.delete(:race_condition_ttl) || raise("Use :race_condition_ttl option or normal fetch")
126
+ expires_in = options.fetch(:expires_in)
127
+ options[:expires_in] = expires_in + race_ttl
128
+ options[:preserve_race_condition_entry] = true
129
+
130
+ value = fetch(key, options) { FetchWithRaceConditionTTLEntry.new(yield, expires_in) }
131
+
132
+ return value unless value.is_a?(FetchWithRaceConditionTTLEntry)
133
+
134
+ if value.expired? && !value.extended
135
+ # we take care of refreshing the cache, all others should keep reading
136
+ value.extended = true
137
+ write(key, value, options.merge(:expires_in => value.expires_in + race_ttl))
138
+
139
+ # calculate new value and store it
140
+ value = FetchWithRaceConditionTTLEntry.new(yield, expires_in)
141
+ write(key, value, options)
142
+ end
143
+
144
+ value.value
145
+ end
146
+
101
147
  def read(key, options = nil)
102
148
  key = expanded_key(key)
103
149
  instrument(:read, key, options) do |payload|
@@ -191,7 +237,8 @@ module ActiveSupport
191
237
  def read_entry(key, options = nil)
192
238
  options ||= {}
193
239
  raw_value, flags = @cache.get(escape_and_normalize(key), false, true)
194
- deserialize(raw_value, options[:raw], flags)
240
+ value = deserialize(raw_value, options[:raw], flags)
241
+ convert_race_condition_entry(value, options)
195
242
  rescue Memcached::NotFound
196
243
  nil
197
244
  rescue Memcached::Error => e
@@ -229,6 +276,14 @@ module ActiveSupport
229
276
 
230
277
  private
231
278
 
279
+ def convert_race_condition_entry(value, options)
280
+ if !options[:preserve_race_condition_entry] && value.is_a?(FetchWithRaceConditionTTLEntry)
281
+ value.value
282
+ else
283
+ value
284
+ end
285
+ end
286
+
232
287
  def deserialize(value, raw = false, flags = 0)
233
288
  value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
234
289
  raw ? value : Marshal.load(value)
@@ -1,3 +1,3 @@
1
1
  module LibmemcachedStore
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  s.add_development_dependency('rack')
25
25
  s.add_development_dependency('rake')
26
26
  s.add_development_dependency('mocha')
27
+ s.add_development_dependency('minitest', '~> 4.7')
27
28
  s.add_development_dependency('activesupport', '>= 3')
28
29
  s.add_development_dependency('actionpack', '>= 3')
29
30
  end
@@ -3,7 +3,11 @@ require 'active_support/core_ext/module/delegation'
3
3
  require 'active_support/core_ext/module/attribute_accessors'
4
4
  require 'action_controller'
5
5
  require 'action_dispatch/routing'
6
- require 'action_dispatch/middleware/head'
6
+ if ActionPack::VERSION::MAJOR >= 4
7
+ require 'rack/head'
8
+ else
9
+ require 'action_dispatch/middleware/head'
10
+ end
7
11
  require 'action_dispatch/testing/assertions'
8
12
  require 'action_dispatch/testing/test_process'
9
13
  require 'action_dispatch/testing/integration'
@@ -36,8 +40,12 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
36
40
  middleware.use "ActionDispatch::ParamsParser"
37
41
  middleware.use "ActionDispatch::Cookies"
38
42
  middleware.use "ActionDispatch::Flash"
39
- middleware.use "ActionDispatch::Head"
43
+ if ActionPack::VERSION::MAJOR >= 4
44
+ middleware.use "Rack::Head"
45
+ else
46
+ middleware.use "ActionDispatch::Head"
47
+ end
40
48
  yield(middleware) if block_given?
41
49
  end
42
50
  end
43
- end
51
+ end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require_relative '../test_helper'
2
2
  require File.expand_path('../abstract_unit', __FILE__)
3
3
  require 'action_dispatch/session/libmemcached_store'
4
4
 
@@ -38,7 +38,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
38
38
  end
39
39
  end
40
40
 
41
- def test_setting_and_getting_session_value
41
+ test "setting and getting session value" do
42
42
  with_test_route_set do
43
43
  get '/set_session_value'
44
44
  assert_response :success
@@ -50,7 +50,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
50
50
  end
51
51
  end
52
52
 
53
- def test_getting_nil_session_value
53
+ test "getting nil session value" do
54
54
  with_test_route_set do
55
55
  get '/get_session_value'
56
56
  assert_response :success
@@ -58,7 +58,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
58
58
  end
59
59
  end
60
60
 
61
- def test_getting_session_value_after_session_reset
61
+ test "getting session value after session reset" do
62
62
  with_test_route_set do
63
63
  get '/set_session_value'
64
64
  assert_response :success
@@ -77,7 +77,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
77
77
  end
78
78
  end
79
79
 
80
- def test_getting_from_nonexistent_session
80
+ test "getting from nonexistent session" do
81
81
  with_test_route_set do
82
82
  get '/get_session_value'
83
83
  assert_response :success
@@ -86,7 +86,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
86
86
  end
87
87
  end
88
88
 
89
- def test_setting_session_value_after_session_reset
89
+ test "setting session value after session reset" do
90
90
  with_test_route_set do
91
91
  get '/set_session_value'
92
92
  assert_response :success
@@ -107,7 +107,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
107
107
  end
108
108
  end
109
109
 
110
- def test_getting_session_id
110
+ test "getting session id" do
111
111
  with_test_route_set do
112
112
  get '/set_session_value'
113
113
  assert_response :success
@@ -120,7 +120,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
120
120
  end
121
121
  end
122
122
 
123
- def test_deserializes_unloaded_class
123
+ test "deserializes unloaded class" do
124
124
  with_test_route_set do
125
125
  with_autoload_path "session_autoload_test" do
126
126
  get '/set_serialized_session_value'
@@ -139,7 +139,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
139
139
  end
140
140
  end
141
141
 
142
- def test_doesnt_write_session_cookie_if_session_id_is_already_exists
142
+ test "doesnt write session cookie if session id is already exists" do
143
143
  with_test_route_set do
144
144
  get '/set_session_value'
145
145
  assert_response :success
@@ -151,7 +151,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
151
151
  end
152
152
  end
153
153
 
154
- def test_prevents_session_fixation
154
+ test "prevents session fixation" do
155
155
  with_test_route_set do
156
156
  get '/get_session_value'
157
157
  assert_response :success
@@ -171,7 +171,7 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
171
171
  def with_test_route_set
172
172
  with_routing do |set|
173
173
  set.draw do
174
- match ':action', :to => TestController
174
+ get ':action', :to => TestController
175
175
  end
176
176
 
177
177
  @app = self.class.build_app(set) do |middleware|
@@ -197,4 +197,4 @@ class LibMemcachedStoreTest < ActionDispatch::IntegrationTest
197
197
  end
198
198
  end
199
199
  end
200
- end
200
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'test_helper'
3
+ require_relative '../test_helper'
4
4
  require 'memcached'
5
5
  require 'active_support'
6
6
  require 'active_support/core_ext/module/aliasing'
@@ -8,318 +8,319 @@ require 'active_support/core_ext/object/duplicable'
8
8
  require 'active_support/cache/libmemcached_store'
9
9
 
10
10
  # Make it easier to get at the underlying cache options during testing.
11
- class ActiveSupport::Cache::LibmemcachedStore
11
+ ActiveSupport::Cache::LibmemcachedStore.class_eval do
12
12
  def client_options
13
13
  @cache.options
14
14
  end
15
15
  end
16
16
 
17
- class MockUser
18
- def cache_key
19
- 'foo'
17
+ describe ActiveSupport::Cache::LibmemcachedStore do
18
+ class MockUser
19
+ def cache_key
20
+ 'foo'
21
+ end
20
22
  end
21
- end
22
23
 
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' }
24
+ before do
25
+ @cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, expires_in: 60)
26
+ @cache.clear
27
+ @cache.silence!
28
28
  end
29
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
30
+ describe "cache store behavior" do
31
+ def really_long_keys_test
32
+ key = "a" * 251
33
+ assert @cache.write(key, "bar")
34
+ assert_equal "bar", @cache.read(key)
35
+ assert_equal "bar", @cache.fetch(key)
36
+ assert_nil @cache.read("#{key}x")
37
+ assert_equal({key => "bar"}, @cache.read_multi(key))
38
+ assert @cache.delete(key)
39
+ refute @cache.exist?(key)
40
+ assert @cache.write(key, '2', :raw => true)
41
+ assert_equal 3, @cache.increment(key)
42
+ assert_equal 2, @cache.decrement(key)
43
+ end
34
44
 
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
45
+ it "fetch_without_cache_miss" do
46
+ @cache.write('foo', 'bar')
47
+ @cache.expects(:write_entry).never
48
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
49
+ end
41
50
 
42
- def test_fetch_with_cached_false
43
- @cache.write('foo', false)
44
- refute @cache.fetch('foo') { raise }
45
- end
51
+ it "fetch_with_cache_miss" do
52
+ @cache.expects(:write_entry).with('foo', 'baz', nil)
53
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
54
+ end
46
55
 
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
56
+ it "fetch_with_forced_cache_miss" do
57
+ @cache.write('foo', 'bar')
58
+ @cache.expects(:read_entry).never
59
+ @cache.expects(:write_entry).with('foo', 'baz', force: true)
60
+ assert_equal 'baz', @cache.fetch('foo', force: true) { 'baz' }
61
+ end
52
62
 
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
63
+ it "fetch_with_cached_false" do
64
+ @cache.write('foo', false)
65
+ refute @cache.fetch('foo') { raise }
66
+ end
58
67
 
59
- def test_should_read_and_write_strings
60
- assert @cache.write('foo', 'bar')
61
- assert_equal 'bar', @cache.read('foo')
62
- end
68
+ it "fetch_with_raw_object" do
69
+ o = Object.new
70
+ o.instance_variable_set :@foo, 'bar'
71
+ assert_equal o, @cache.fetch('foo', raw: true) { o }
72
+ end
63
73
 
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
74
+ it "fetch_with_cache_key" do
75
+ u = MockUser.new
76
+ @cache.write(u.cache_key, 'bar')
77
+ assert_equal 'bar', @cache.fetch(u) { raise }
78
+ end
68
79
 
69
- def test_should_read_and_write_integer
70
- assert @cache.write('foo', 1)
71
- assert_equal 1, @cache.read('foo')
72
- end
80
+ it "should_read_and_write_strings" do
81
+ assert @cache.write('foo', 'bar')
82
+ assert_equal 'bar', @cache.read('foo')
83
+ end
73
84
 
74
- def test_should_read_and_write_nil
75
- assert @cache.write('foo', nil)
76
- assert_equal nil, @cache.read('foo')
77
- end
85
+ it "should_read_and_write_hash" do
86
+ assert @cache.write('foo', { a: 'b' })
87
+ assert_equal({ a: 'b' }, @cache.read('foo'))
88
+ end
78
89
 
79
- def test_should_read_and_write_false
80
- assert @cache.write('foo', false)
81
- assert_equal false, @cache.read('foo')
82
- end
90
+ it "should_read_and_write_integer" do
91
+ assert @cache.write('foo', 1)
92
+ assert_equal 1, @cache.read('foo')
93
+ end
83
94
 
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
95
+ it "should_read_and_write_nil" do
96
+ assert @cache.write('foo', nil)
97
+ assert_equal nil, @cache.read('foo')
98
+ end
88
99
 
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
100
+ it "should_read_and_write_false" do
101
+ assert @cache.write('foo', false)
102
+ assert_equal false, @cache.read('foo')
103
+ end
94
104
 
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
105
+ it "read_and_write_compressed_data" do
106
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
107
+ assert_equal 'bar', @cache.read('foo')
108
+ end
99
109
 
100
- def test_read_miss
101
- assert_nil @cache.read('foo')
102
- end
110
+ it "write_should_overwrite" do
111
+ @cache.write('foo', 'bar')
112
+ @cache.write('foo', 'baz')
113
+ assert_equal 'baz', @cache.read('foo')
114
+ end
103
115
 
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
116
+ it "write_compressed_data" do
117
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 1, :raw => true)
118
+ assert_equal Zlib::Deflate.deflate('bar'), @cache.instance_variable_get(:@cache).get('foo', false)
119
+ end
108
120
 
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
121
+ it "read_miss" do
122
+ assert_nil @cache.read('foo')
123
+ end
115
124
 
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
125
+ it "read_should_return_a_different_object_id_each_time_it_is_called" do
126
+ @cache.write('foo', 'bar')
127
+ refute_equal @cache.read('foo').object_id, @cache.read('foo').object_id
128
+ end
121
129
 
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
130
+ it "read_multi" do
131
+ @cache.write('foo', 'bar')
132
+ @cache.write('fu', 'baz')
133
+ @cache.write('fud', 'biz')
134
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
135
+ end
127
136
 
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
137
+ it "read_multi_with_array" do
138
+ @cache.write('foo', 'bar')
139
+ @cache.write('fu', 'baz')
140
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi(['foo', 'fu']))
141
+ end
133
142
 
134
- def test_cache_key
135
- o = MockUser.new
136
- @cache.write(o, 'bar')
137
- assert_equal 'bar', @cache.read('foo')
138
- end
143
+ it "read_multi_with_raw" do
144
+ @cache.write('foo', 'bar', :raw => true)
145
+ @cache.write('fu', 'baz', :raw => true)
146
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
147
+ end
139
148
 
140
- def test_param_as_cache_key
141
- obj = Object.new
142
- def obj.to_param
143
- 'foo'
149
+ it "read_multi_with_compress" do
150
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
151
+ @cache.write('fu', 'baz', :compress => true, :compress_threshold => 1)
152
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
144
153
  end
145
- @cache.write(obj, 'bar')
146
- assert_equal 'bar', @cache.read('foo')
147
- end
148
154
 
149
- def test_array_as_cache_key
150
- @cache.write([:fu, 'foo'], 'bar')
151
- assert_equal 'bar', @cache.read('fu/foo')
152
- end
155
+ it "cache_key" do
156
+ o = MockUser.new
157
+ @cache.write(o, 'bar')
158
+ assert_equal 'bar', @cache.read('foo')
159
+ end
153
160
 
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
161
+ it "param_as_cache_key" do
162
+ obj = Object.new
163
+ def obj.to_param
164
+ 'foo'
165
+ end
166
+ @cache.write(obj, 'bar')
167
+ assert_equal 'bar', @cache.read('foo')
168
+ end
158
169
 
159
- def test_keys_are_case_sensitive
160
- @cache.write('foo', 'bar')
161
- assert_nil @cache.read('FOO')
162
- end
170
+ it "array_as_cache_key" do
171
+ @cache.write([:fu, 'foo'], 'bar')
172
+ assert_equal 'bar', @cache.read('fu/foo')
173
+ end
163
174
 
164
- def test_keys_with_spaces
165
- assert_equal 'baz', @cache.fetch('foo bar') { 'baz' }
166
- end
175
+ it "hash_as_cache_key" do
176
+ @cache.write({:foo => 1, :fu => 2}, 'bar')
177
+ assert_equal 'bar', @cache.read('foo=1/fu=2')
178
+ end
167
179
 
168
- def test_exist
169
- @cache.write('foo', 'bar')
170
- assert @cache.exist?('foo')
171
- refute @cache.exist?('bar')
172
- end
180
+ it "keys_are_case_sensitive" do
181
+ @cache.write('foo', 'bar')
182
+ assert_nil @cache.read('FOO')
183
+ end
173
184
 
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
185
+ it "keys_with_spaces" do
186
+ assert_equal 'baz', @cache.fetch('foo bar') { 'baz' }
187
+ end
180
188
 
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
189
+ it "exist" do
190
+ @cache.write('foo', 'bar')
191
+ assert @cache.exist?('foo')
192
+ refute @cache.exist?('bar')
193
+ end
186
194
 
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
195
+ it "delete" do
196
+ @cache.write('foo', 'bar')
197
+ assert @cache.exist?('foo')
198
+ assert @cache.delete('foo')
199
+ refute @cache.exist?('foo')
200
+ end
192
201
 
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
202
+ it "delete_with_unexistent_key" do
203
+ @cache.expects(:log_error).never
204
+ refute @cache.exist?('foo')
205
+ refute @cache.delete('foo')
206
+ end
198
207
 
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
208
+ it "store_objects_should_be_immutable" do
209
+ @cache.write('foo', 'bar')
210
+ @cache.read('foo').gsub!(/.*/, 'baz')
211
+ assert_equal 'bar', @cache.read('foo')
212
+ end
210
213
 
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
214
+ it "original_store_objects_should_not_be_immutable" do
215
+ bar = 'bar'
216
+ @cache.write('foo', bar)
217
+ assert_equal 'baz', bar.gsub!(/r/, 'z')
218
+ end
224
219
 
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
220
+ it "crazy_key_characters" do
221
+ crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
222
+ assert @cache.write(crazy_key, "1", :raw => true)
223
+ assert_equal "1", @cache.read(crazy_key)
224
+ assert_equal "1", @cache.fetch(crazy_key)
225
+ assert @cache.delete(crazy_key)
226
+ refute @cache.exist?(crazy_key)
227
+ assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
228
+ assert_equal 3, @cache.increment(crazy_key)
229
+ assert_equal 2, @cache.decrement(crazy_key)
230
+ end
230
231
 
231
- def test_clear
232
- @cache.write("foo", "bar")
233
- @cache.clear
234
- assert_nil @cache.read("foo")
235
- end
232
+ it "really_long_keys" do
233
+ really_long_keys_test
234
+ end
236
235
 
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
236
+ it "really_long_keys_with_namespace" do
237
+ @cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, :expires_in => 60, :namespace => 'namespace')
238
+ @cache.silence!
239
+ really_long_keys_test
240
+ end
243
241
 
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
242
+ it "clear" do
243
+ @cache.write("foo", "bar")
244
+ @cache.clear
245
+ assert_nil @cache.read("foo")
246
+ end
253
247
 
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
248
+ it "clear_with_options" do
249
+ @cache.write("foo", "bar")
250
+ @cache.clear(:some_option => true)
251
+ assert_nil @cache.read("foo")
252
+ end
261
253
  end
262
254
 
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
255
+ describe "compression" do
256
+ it "read_and_write_compressed_small_data" do
257
+ @cache.write('foo', 'bar', :compress => true)
258
+ raw_value = @cache.send(:read_entry, 'foo', {})
259
+ assert_equal 'bar', @cache.read('foo')
260
+ value = Marshal.load(raw_value) rescue raw_value
261
+ assert_equal 'bar', value
262
+ end
269
263
 
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
264
+ it "read_and_write_compressed_large_data" do
265
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
266
+ raw_value = @cache.send(:read_entry, 'foo', :raw => true)
267
+ assert_equal 'bar', @cache.read('foo')
268
+ assert_equal 'bar', Marshal.load(raw_value)
269
+ end
277
270
  end
278
271
 
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
272
+ describe "increment / decrement" do
273
+ it "increment" do
274
+ @cache.write('foo', '1', :raw => true)
275
+ assert_equal 1, @cache.read('foo').to_i
276
+ assert_equal 2, @cache.increment('foo')
277
+ assert_equal 2, @cache.read('foo').to_i
278
+ assert_equal 3, @cache.increment('foo')
279
+ assert_equal 3, @cache.read('foo').to_i
280
+ end
286
281
 
287
- class LibmemcachedStoreTest < MiniTest::Unit::TestCase
288
- include CacheStoreBehavior
289
- include CacheIncrementDecrementBehavior
282
+ it "decrement" do
283
+ @cache.write('foo', '3', :raw => true)
284
+ assert_equal 3, @cache.read('foo').to_i
285
+ assert_equal 2, @cache.decrement('foo')
286
+ assert_equal 2, @cache.read('foo').to_i
287
+ assert_equal 1, @cache.decrement('foo')
288
+ assert_equal 1, @cache.read('foo').to_i
289
+ end
290
290
 
291
- def setup
292
- @cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, expires_in: 60)
293
- @cache.clear
294
- @cache.silence!
291
+ it "increment_decrement_non_existing_keys" do
292
+ @cache.expects(:log_error).never
293
+ assert_nil @cache.increment('foo')
294
+ assert_nil @cache.decrement('bar')
295
+ end
295
296
  end
296
297
 
297
- def test_should_identify_cache_store
298
+ it "should_identify_cache_store" do
298
299
  assert_kind_of ActiveSupport::Cache::LibmemcachedStore, @cache
299
300
  end
300
301
 
301
- def test_should_set_server_addresses_to_nil_if_none_are_given
302
+ it "should_set_server_addresses_to_nil_if_none_are_given" do
302
303
  assert_equal [], @cache.addresses
303
304
  end
304
305
 
305
- def test_should_set_custom_server_addresses
306
+ it "should_set_custom_server_addresses" do
306
307
  store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', '192.168.1.1'
307
308
  assert_equal %w(localhost 192.168.1.1), store.addresses
308
309
  end
309
310
 
310
- def test_should_enable_consistent_ketema_hashing_by_default
311
+ it "should_enable_consistent_ketema_hashing_by_default" do
311
312
  assert_equal :consistent_ketama, @cache.client_options[:distribution]
312
313
  end
313
314
 
314
- def test_should_not_enable_non_blocking_io_by_default
315
+ it "should_not_enable_non_blocking_io_by_default" do
315
316
  assert_equal false, @cache.client_options[:no_block]
316
317
  end
317
318
 
318
- def test_should_not_enable_server_failover_by_default
319
+ it "should_not_enable_server_failover_by_default" do
319
320
  assert_nil @cache.client_options[:failover]
320
321
  end
321
322
 
322
- def test_should_allow_configuration_of_custom_options
323
+ it "should_allow_configuration_of_custom_options" do
323
324
  options = { client: { tcp_nodelay: true, distribution: :modula } }
324
325
 
325
326
  store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', options
@@ -328,7 +329,7 @@ class LibmemcachedStoreTest < MiniTest::Unit::TestCase
328
329
  assert_equal true, store.client_options[:tcp_nodelay]
329
330
  end
330
331
 
331
- def test_should_allow_mute_and_silence
332
+ it "should_allow_mute_and_silence" do
332
333
  cache = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost'
333
334
  cache.mute do
334
335
  assert cache.write('foo', 'bar')
@@ -338,4 +339,50 @@ class LibmemcachedStoreTest < MiniTest::Unit::TestCase
338
339
  cache.silence!
339
340
  assert cache.silence?
340
341
  end
342
+
343
+ describe "#fetch with :race_condition_ttl" do
344
+ let(:options) { {:expires_in => 1, :race_condition_ttl => 5} }
345
+
346
+ def fetch(&block)
347
+ @cache.fetch("unknown", options, &block)
348
+ end
349
+
350
+ after do
351
+ Thread.list.each { |t| t.exit unless t == Thread.current }
352
+ end
353
+
354
+ it "works like a normal fetch" do
355
+ fetch { 1 }.must_equal 1
356
+ end
357
+
358
+ it "keeps a cached value even if the cache expires" do
359
+ fetch { 1 } # fill it
360
+
361
+ future = Time.now + 3 * 60
362
+ Time.stubs(:now).returns future
363
+
364
+ Thread.new do
365
+ sleep 0.1
366
+ fetch { raise }.must_equal 1 # 3rd fetch -> read expired value
367
+ end
368
+ fetch { sleep 0.2; 2 }.must_equal 2 # 2nd fetch -> takes time to generate but returns correct value
369
+ fetch { 3 }.must_equal 2 # 4th fetch still correct value
370
+ end
371
+
372
+ it "can be read by a normal read" do
373
+ fetch { 1 }
374
+ @cache.read("unknown").must_equal 1
375
+ end
376
+
377
+ it "can be read by a normal fetch" do
378
+ fetch { 1 }
379
+ @cache.fetch("unknown") { 2 }.must_equal 1
380
+ end
381
+
382
+ it "can write to things that get fetched" do
383
+ fetch { 1 }
384
+ @cache.write "unknown", 2
385
+ fetch { 1 }.must_equal 2
386
+ end
387
+ end
341
388
  end
@@ -1,5 +1,6 @@
1
1
  ENV["RAILS_ENV"] = "test"
2
- $:.unshift File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
3
 
4
4
  require 'minitest/autorun'
5
- require 'mocha/setup'
5
+ require 'minitest/spec'
6
+ require 'mocha/setup'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libmemcached_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-06-11 00:00:00.000000000 Z
14
+ date: 2013-10-31 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: memcached
@@ -77,6 +77,22 @@ dependencies:
77
77
  - - ! '>='
78
78
  - !ruby/object:Gem::Version
79
79
  version: '0'
80
+ - !ruby/object:Gem::Dependency
81
+ name: minitest
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '4.7'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ~>
94
+ - !ruby/object:Gem::Version
95
+ version: '4.7'
80
96
  - !ruby/object:Gem::Dependency
81
97
  name: activesupport
82
98
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +145,7 @@ files:
129
145
  - gemfiles/rails30.gemfile
130
146
  - gemfiles/rails31.gemfile
131
147
  - gemfiles/rails32.gemfile
148
+ - gemfiles/rails40.gemfile
132
149
  - lib/action_dispatch/session/libmemcached_store.rb
133
150
  - lib/active_support/cache/libmemcached_store.rb
134
151
  - lib/libmemcached_store.rb