libmemcached_store 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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