spymemcached_store 1.0.1-java

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0cf1de67233c72496592c0350be6f3ca2800c5b4
4
+ data.tar.gz: 2e3376af818bf52570cc8ccc4e8450d9c76c2b6e
5
+ SHA512:
6
+ metadata.gz: 9a4b8c7c6b64c1ffdeacfa2d732768515e9358c4c6fdb15b3e6eb6da529121a35036caa7e7558b1f6561d70f7dc8a9504324665cb063a12eace84eb6401121cb
7
+ data.tar.gz: 1eee6860ba61259be722275852131475bf5398dd774885c9475e6b65dab9ba76f9c3bb00470e6b5216cef581fd2df0e20c58aff909bc7c59a310138e3ed9e0ec
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1 @@
1
+ jruby-1.7.16
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spymemcached_store.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Xiao Li swing1979@gmail.com
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # SpymemcachedStore
2
+
3
+ This is Rails 4 compatible cache store for [spymemcached.jruby](https://github.com/ThoughtWorksStudios/spymemcached.jruby)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'spymemcached_store'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install spymemcached_store
20
+
21
+ ## Usage
22
+
23
+ config.cache_store = :spymemcached_store, { :expires_in => 60, :namespace => 'app-namespace', :timeout => 0.1 }
24
+
25
+ Supports all Rails cache store options, see [spymemcached.jruby](https://github.com/ThoughtWorksStudios/spymemcached.jruby) for additional options.
26
+
27
+ It is not recommended to use :compress and :compress_threshold options, as spymemcached.jruby does it by default.
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it ( https://github.com/ThoughtWorksStudios/spymemcached_store/fork )
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create a new Pull Request
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/*_test.rb']
7
+ t.warning = true
8
+ t.verbose = false
9
+ end
10
+
11
+ task :default => [:test]
@@ -0,0 +1,166 @@
1
+ require 'active_support/cache'
2
+ require 'spymemcached'
3
+
4
+ module ActiveSupport
5
+ module Cache
6
+ class SpymemcachedStore < ActiveSupport::Cache::Store
7
+
8
+ # namespace
9
+ def initialize(*addresses)
10
+ addresses = addresses.flatten
11
+ options = addresses.extract_options!
12
+ super(options)
13
+
14
+ unless [String, Spymemcached, NilClass].include?(addresses.first.class)
15
+ raise ArgumentError, "First argument must be an empty array, an array of hosts or a Spymemcached instance."
16
+ end
17
+ @client = if addresses.first.is_a?(Spymemcached)
18
+ addresses.first
19
+ else
20
+ mem_cache_options = options.dup
21
+ UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
22
+ Spymemcached.new(addresses, mem_cache_options)
23
+ end
24
+
25
+ extend Strategy::LocalCache
26
+ extend LocalCacheWithRaw
27
+ end
28
+
29
+ # Read multiple values at once from the cache. Options can be passed
30
+ # in the last argument.
31
+ #
32
+ # Some cache implementation may optimize this method.
33
+ #
34
+ # Returns a hash mapping the names provided to the values found.
35
+ def read_multi(*names)
36
+ options = names.extract_options!
37
+ options = merged_options(options)
38
+ keys_to_names = Hash[names.map{|name| [namespaced_key(name, options), name]}]
39
+ raw_values = @client.get_multi(keys_to_names.keys)
40
+ values = {}
41
+ raw_values.each do |key, value|
42
+ entry = deserialize_entry(value)
43
+ values[keys_to_names[key]] = entry.value unless entry.expired?
44
+ end
45
+ values
46
+ end
47
+
48
+ # Increment an integer value in the cache.
49
+ #
50
+ # Options are passed to the underlying cache implementation.
51
+ #
52
+ # All implementations may not support this method.
53
+ def increment(name, amount = 1, options = nil)
54
+ options = merged_options(options)
55
+
56
+ instrument(:increment, name, :amount => amount) do
57
+ @client.incr(name, amount)
58
+ end
59
+ rescue Spymemcached::Error => e
60
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
61
+ nil
62
+ end
63
+
64
+ # Decrement an integer value in the cache.
65
+ #
66
+ # Options are passed to the underlying cache implementation.
67
+ #
68
+ # All implementations may not support this method.
69
+ def decrement(name, amount = 1, options = nil)
70
+ options = merged_options(options)
71
+
72
+ instrument(:decrement, name, :amount => amount) do
73
+ @client.decr(name, amount)
74
+ end
75
+ rescue Spymemcached::Error => e
76
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
77
+ nil
78
+ end
79
+
80
+ # Clear the entire cache. Be careful with this method since it could
81
+ # affect other processes if shared cache is being used.
82
+ #
83
+ # The options hash is passed to the underlying cache implementation.
84
+ #
85
+ # All implementations may not support this method.
86
+ def clear(options = nil)
87
+ @client.flush_all
88
+ rescue Spymemcached::Error => e
89
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
90
+ nil
91
+ end
92
+
93
+ # Get the statistics from the memcached servers.
94
+ def stats
95
+ @client.stats
96
+ end
97
+
98
+ protected
99
+ # Read an entry from the cache implementation. Subclasses must implement
100
+ # this method.
101
+ def read_entry(key, options) # :nodoc:
102
+ deserialize_entry(@client.get(key))
103
+ rescue Spymemcached::Error => e
104
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
105
+ nil
106
+ end
107
+
108
+ # Write an entry to the cache implementation. Subclasses must implement
109
+ # this method.
110
+ def write_entry(key, entry, options) # :nodoc:
111
+ method = options && options[:unless_exist] ? :add : :set
112
+ value = options[:raw] ? entry.value.to_s : entry
113
+ expires_in = options[:expires_in].to_i
114
+ if expires_in > 0 && !options[:raw]
115
+ # Set the memcache expire a few minutes in the future to support race condition ttls on read
116
+ expires_in += 5.minutes
117
+ end
118
+ @client.send(method, key, value, expires_in)
119
+ rescue Spymemcached::Error => e
120
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
121
+ false
122
+ end
123
+
124
+ # Delete an entry from the cache implementation. Subclasses must
125
+ # implement this method.
126
+ def delete_entry(key, options) # :nodoc:
127
+ @client.delete(key)
128
+ rescue Spymemcached::Error => e
129
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
130
+ false
131
+ end
132
+
133
+ protected
134
+ def deserialize_entry(raw_value)
135
+ if raw_value
136
+ entry = Marshal.load(raw_value) rescue raw_value
137
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
138
+ else
139
+ nil
140
+ end
141
+ end
142
+
143
+ # Provide support for raw values in the local cache strategy.
144
+ module LocalCacheWithRaw # :nodoc:
145
+ protected
146
+ def read_entry(key, options)
147
+ entry = super
148
+ if options[:raw] && local_cache && entry
149
+ entry = deserialize_entry(entry.value)
150
+ end
151
+ entry
152
+ end
153
+
154
+ def write_entry(key, entry, options) # :nodoc:
155
+ retval = super
156
+ if options[:raw] && local_cache && retval
157
+ raw_entry = Entry.new(entry.value.to_s)
158
+ raw_entry.expires_at = entry.expires_at
159
+ local_cache.write_entry(key, raw_entry, options)
160
+ end
161
+ retval
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,5 @@
1
+ require "spymemcached_store/version"
2
+
3
+ module SpymemcachedStore
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,3 @@
1
+ module SpymemcachedStore
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'spymemcached_store/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "spymemcached_store"
8
+ spec.version = SpymemcachedStore::VERSION
9
+ spec.platform = 'java'
10
+ spec.authors = ["Xiao Li"]
11
+ spec.email = ["swing1979@gmail.com"]
12
+ spec.summary = %q{Rails 3 & 4 cache store for spymemcached.jruby.}
13
+ spec.description = %q{Rails 3 & 4 cache store for spymemcached.jruby.}
14
+ spec.homepage = "https://github.com/ThoughtWorksStudios/spymemcached_store"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "spymemcached.jruby"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rack"
27
+ spec.add_development_dependency "mocha"
28
+ spec.add_development_dependency "activesupport"
29
+ spec.add_development_dependency 'minitest', '~> 5.1'
30
+
31
+ end
@@ -0,0 +1,50 @@
1
+ require 'dependencies_test_helpers'
2
+
3
+ module AutoloadingCacheBehavior
4
+ class E
5
+ end
6
+ class ClassFolder
7
+ class NestedClass
8
+ end
9
+
10
+ class SiblingClass
11
+ end
12
+ end
13
+
14
+ include DependenciesTestHelpers
15
+ def test_simple_autoloading
16
+ with_autoloading_fixtures do
17
+ @cache.write('foo', E.new)
18
+ end
19
+
20
+ remove_constants(:E)
21
+ ActiveSupport::Dependencies.clear
22
+
23
+ with_autoloading_fixtures do
24
+ assert_kind_of E, @cache.read('foo')
25
+ end
26
+
27
+ remove_constants(:E)
28
+ ActiveSupport::Dependencies.clear
29
+ end
30
+
31
+ def test_two_classes_autoloading
32
+ with_autoloading_fixtures do
33
+ @cache.write('foo', [E.new, ClassFolder.new])
34
+ end
35
+
36
+ remove_constants(:E, :ClassFolder)
37
+ ActiveSupport::Dependencies.clear
38
+
39
+ with_autoloading_fixtures do
40
+ loaded = @cache.read('foo')
41
+ assert_kind_of Array, loaded
42
+ assert_equal 2, loaded.size
43
+ assert_kind_of E, loaded[0]
44
+ assert_kind_of ClassFolder, loaded[1]
45
+ end
46
+
47
+ remove_constants(:E, :ClassFolder)
48
+ ActiveSupport::Dependencies.clear
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+ module CacheIncrementDecrementBehavior
2
+ def test_increment
3
+ @cache.write('foo', 1, :raw => true)
4
+ assert_equal 1, @cache.read('foo').to_i
5
+ assert_equal 2, @cache.increment('foo')
6
+ assert_equal 2, @cache.read('foo').to_i
7
+ assert_equal 3, @cache.increment('foo')
8
+ assert_equal 3, @cache.read('foo').to_i
9
+ # spymemcached will set zero as default value
10
+ assert_equal 0, @cache.increment('bar')
11
+ end
12
+
13
+ def test_decrement
14
+ @cache.write('foo', 3, :raw => true)
15
+ assert_equal 3, @cache.read('foo').to_i
16
+ assert_equal 2, @cache.decrement('foo')
17
+ assert_equal 2, @cache.read('foo').to_i
18
+ assert_equal 1, @cache.decrement('foo')
19
+ assert_equal 1, @cache.read('foo').to_i
20
+
21
+ # spymemcached will set zero as default value
22
+ assert_equal 0, @cache.decrement('bar')
23
+ end
24
+ end
@@ -0,0 +1,248 @@
1
+ module CacheStoreBehavior
2
+ def test_should_read_and_write_strings
3
+ assert @cache.write('foo', 'bar')
4
+ assert_equal 'bar', @cache.read('foo')
5
+ end
6
+
7
+ def test_should_overwrite
8
+ @cache.write('foo', 'bar')
9
+ @cache.write('foo', 'baz')
10
+ assert_equal 'baz', @cache.read('foo')
11
+ end
12
+
13
+ def test_fetch_without_cache_miss
14
+ @cache.write('foo', 'bar')
15
+ @cache.expects(:write).never
16
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
17
+ end
18
+
19
+ def test_fetch_with_cache_miss
20
+ @cache.expects(:write).with('foo', 'baz', @cache.options)
21
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
22
+ end
23
+
24
+ def test_fetch_with_cache_miss_passes_key_to_block
25
+ cache_miss = false
26
+ assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length }
27
+ assert cache_miss
28
+
29
+ cache_miss = false
30
+ assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length }
31
+ assert !cache_miss
32
+ end
33
+
34
+ def test_fetch_with_forced_cache_miss
35
+ @cache.write('foo', 'bar')
36
+ @cache.expects(:read).never
37
+ @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true))
38
+ @cache.fetch('foo', :force => true) { 'bar' }
39
+ end
40
+
41
+ def test_fetch_with_cached_nil
42
+ @cache.write('foo', nil)
43
+ @cache.expects(:write).never
44
+ assert_nil @cache.fetch('foo') { 'baz' }
45
+ end
46
+
47
+ def test_should_read_and_write_hash
48
+ assert @cache.write('foo', {:a => "b"})
49
+ assert_equal({:a => "b"}, @cache.read('foo'))
50
+ end
51
+
52
+ def test_should_read_and_write_integer
53
+ assert @cache.write('foo', 1)
54
+ assert_equal 1, @cache.read('foo')
55
+ end
56
+
57
+ def test_should_read_and_write_nil
58
+ assert @cache.write('foo', nil)
59
+ assert_equal nil, @cache.read('foo')
60
+ end
61
+
62
+ def test_should_read_and_write_false
63
+ assert @cache.write('foo', false)
64
+ assert_equal false, @cache.read('foo')
65
+ end
66
+
67
+ def test_read_multi
68
+ @cache.write('foo', 'bar')
69
+ @cache.write('fu', 'baz')
70
+ @cache.write('fud', 'biz')
71
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
72
+ end
73
+
74
+ def test_read_multi_with_expires
75
+ time = Time.now
76
+ @cache.write('foo', 'bar', :expires_in => 10)
77
+ @cache.write('fu', 'baz')
78
+ @cache.write('fud', 'biz')
79
+ Time.stubs(:now).returns(time + 11)
80
+ assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
81
+ end
82
+
83
+ def test_fetch_multi
84
+ @cache.write('foo', 'bar')
85
+ @cache.write('fud', 'biz')
86
+
87
+ values = @cache.fetch_multi('foo', 'fu', 'fud') {|value| value * 2 }
88
+
89
+ assert_equal(["bar", "fufu", "biz"], values)
90
+ assert_equal("fufu", @cache.read('fu'))
91
+ end
92
+
93
+ def test_multi_with_objects
94
+ foo = stub(:title => "FOO!", :cache_key => "foo")
95
+ bar = stub(:cache_key => "bar")
96
+
97
+ @cache.write('bar', "BAM!")
98
+
99
+ values = @cache.fetch_multi(foo, bar) {|object| object.title }
100
+ assert_equal(["FOO!", "BAM!"], values)
101
+ end
102
+
103
+ def test_read_and_write_compressed_small_data
104
+ @cache.write('foo', 'bar', :compress => true)
105
+ assert_equal 'bar', @cache.read('foo')
106
+ end
107
+
108
+ def test_read_and_write_compressed_large_data
109
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
110
+ assert_equal 'bar', @cache.read('foo')
111
+ end
112
+
113
+ def test_read_and_write_compressed_nil
114
+ @cache.write('foo', nil, :compress => true)
115
+ assert_nil @cache.read('foo')
116
+ end
117
+
118
+ def test_cache_key
119
+ obj = Object.new
120
+ def obj.cache_key
121
+ :foo
122
+ end
123
+ @cache.write(obj, "bar")
124
+ assert_equal "bar", @cache.read("foo")
125
+ end
126
+
127
+ def test_param_as_cache_key
128
+ obj = Object.new
129
+ def obj.to_param
130
+ "foo"
131
+ end
132
+ @cache.write(obj, "bar")
133
+ assert_equal "bar", @cache.read("foo")
134
+ end
135
+
136
+ def test_array_as_cache_key
137
+ @cache.write([:fu, "foo"], "bar")
138
+ assert_equal "bar", @cache.read("fu/foo")
139
+ end
140
+
141
+ def test_hash_as_cache_key
142
+ @cache.write({:foo => 1, :fu => 2}, "bar")
143
+ assert_equal "bar", @cache.read("foo=1/fu=2")
144
+ end
145
+
146
+ def test_keys_are_case_sensitive
147
+ @cache.write("foo", "bar")
148
+ assert_nil @cache.read("FOO")
149
+ end
150
+
151
+ def test_exist
152
+ @cache.write('foo', 'bar')
153
+ assert_equal true, @cache.exist?('foo')
154
+ assert_equal false, @cache.exist?('bar')
155
+ end
156
+
157
+ def test_nil_exist
158
+ @cache.write('foo', nil)
159
+ assert @cache.exist?('foo')
160
+ end
161
+
162
+ def test_delete
163
+ @cache.write('foo', 'bar')
164
+ assert @cache.exist?('foo')
165
+ assert @cache.delete('foo')
166
+ assert !@cache.exist?('foo')
167
+ end
168
+
169
+ def test_original_store_objects_should_not_be_immutable
170
+ bar = 'bar'
171
+ @cache.write('foo', bar)
172
+ assert_nothing_raised { bar.gsub!(/.*/, 'baz') }
173
+ end
174
+
175
+ def test_expires_in
176
+ time = Time.local(2008, 4, 24)
177
+ Time.stubs(:now).returns(time)
178
+
179
+ @cache.write('foo', 'bar')
180
+ assert_equal 'bar', @cache.read('foo')
181
+
182
+ Time.stubs(:now).returns(time + 30)
183
+ assert_equal 'bar', @cache.read('foo')
184
+
185
+ Time.stubs(:now).returns(time + 61)
186
+ assert_nil @cache.read('foo')
187
+ end
188
+
189
+ def test_race_condition_protection
190
+ time = Time.now
191
+ @cache.write('foo', 'bar', :expires_in => 60)
192
+ Time.stubs(:now).returns(time + 61)
193
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
194
+ assert_equal 'bar', @cache.read('foo')
195
+ "baz"
196
+ end
197
+ assert_equal "baz", result
198
+ end
199
+
200
+ def test_race_condition_protection_is_limited
201
+ time = Time.now
202
+ @cache.write('foo', 'bar', :expires_in => 60)
203
+ Time.stubs(:now).returns(time + 71)
204
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
205
+ assert_equal nil, @cache.read('foo')
206
+ "baz"
207
+ end
208
+ assert_equal "baz", result
209
+ end
210
+
211
+ def test_race_condition_protection_is_safe
212
+ time = Time.now
213
+ @cache.write('foo', 'bar', :expires_in => 60)
214
+ Time.stubs(:now).returns(time + 61)
215
+ begin
216
+ @cache.fetch('foo', :race_condition_ttl => 10) do
217
+ assert_equal 'bar', @cache.read('foo')
218
+ raise ArgumentError.new
219
+ end
220
+ rescue ArgumentError
221
+ end
222
+ assert_equal "bar", @cache.read('foo')
223
+ Time.stubs(:now).returns(time + 91)
224
+ assert_nil @cache.read('foo')
225
+ end
226
+
227
+ def test_crazy_key_characters
228
+ crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
229
+ assert @cache.write(crazy_key, "1", :raw => true)
230
+ assert_equal "1", @cache.read(crazy_key)
231
+ assert_equal "1", @cache.fetch(crazy_key)
232
+ assert @cache.delete(crazy_key)
233
+ assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
234
+ assert_equal 3, @cache.increment(crazy_key)
235
+ assert_equal 2, @cache.decrement(crazy_key)
236
+ end
237
+
238
+ def test_really_long_keys
239
+ key = ""
240
+ 900.times{key << "x"}
241
+ assert @cache.write(key, "bar")
242
+ assert_equal "bar", @cache.read(key)
243
+ assert_equal "bar", @cache.fetch(key)
244
+ assert_nil @cache.read("#{key}x")
245
+ assert_equal({key => "bar"}, @cache.read_multi(key))
246
+ assert @cache.delete(key)
247
+ end
248
+ end
@@ -0,0 +1,28 @@
1
+ module DependenciesTestHelpers
2
+ def with_loading(*from)
3
+ old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
4
+ this_dir = File.dirname(__FILE__)
5
+ parent_dir = File.dirname(this_dir)
6
+ path_copy = $LOAD_PATH.dup
7
+ $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
8
+ prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths
9
+ ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" }
10
+ yield
11
+ ensure
12
+ $LOAD_PATH.replace(path_copy)
13
+ ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
14
+ ActiveSupport::Dependencies.mechanism = old_mechanism
15
+ ActiveSupport::Dependencies.explicitly_unloadable_constants = []
16
+ ActiveSupport::Dependencies.clear
17
+ end
18
+
19
+ def with_autoloading_fixtures(&block)
20
+ with_loading 'autoloading_fixtures', &block
21
+ end
22
+
23
+ def remove_constants(*constants)
24
+ constants.each do |constant|
25
+ Object.send(:remove_const, constant) if Object.const_defined?(constant)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ # https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
2
+ # The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
3
+ # characters like the umlaut in UTF-8.
4
+ module EncodedKeyCacheBehavior
5
+ Encoding.list.each do |encoding|
6
+ define_method "test_#{encoding.name.underscore}_encoded_values" do
7
+ key = "foo".force_encoding(encoding)
8
+ assert @cache.write(key, "1", :raw => true)
9
+ assert_equal "1", @cache.read(key)
10
+ assert_equal "1", @cache.fetch(key)
11
+ assert @cache.delete(key)
12
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
13
+ assert_equal 3, @cache.increment(key)
14
+ assert_equal 2, @cache.decrement(key)
15
+ end
16
+ end
17
+
18
+ def test_common_utf8_values
19
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
20
+ assert @cache.write(key, "1", :raw => true)
21
+ assert_equal "1", @cache.read(key)
22
+ assert_equal "1", @cache.fetch(key)
23
+ assert @cache.delete(key)
24
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
25
+ assert_equal 3, @cache.increment(key)
26
+ assert_equal 2, @cache.decrement(key)
27
+ end
28
+
29
+ def test_retains_encoding
30
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
31
+ assert @cache.write(key, "1", :raw => true)
32
+ assert_equal Encoding::UTF_8, key.encoding
33
+ end
34
+ end
@@ -0,0 +1,88 @@
1
+ module LocalCacheBehavior
2
+ def test_local_writes_are_persistent_on_the_remote_cache
3
+ retval = @cache.with_local_cache do
4
+ @cache.write('foo', 'bar')
5
+ end
6
+ assert retval
7
+ assert_equal 'bar', @cache.read('foo')
8
+ end
9
+
10
+ def test_clear_also_clears_local_cache
11
+ @cache.with_local_cache do
12
+ @cache.write('foo', 'bar')
13
+ @cache.clear
14
+ assert_nil @cache.read('foo')
15
+ end
16
+
17
+ assert_nil @cache.read('foo')
18
+ end
19
+
20
+ def test_local_cache_of_write
21
+ @cache.with_local_cache do
22
+ @cache.write('foo', 'bar')
23
+ @peek.delete('foo')
24
+ assert_equal 'bar', @cache.read('foo')
25
+ end
26
+ end
27
+
28
+ def test_local_cache_of_read
29
+ @cache.write('foo', 'bar')
30
+ @cache.with_local_cache do
31
+ assert_equal 'bar', @cache.read('foo')
32
+ end
33
+ end
34
+
35
+ def test_local_cache_of_write_nil
36
+ @cache.with_local_cache do
37
+ assert @cache.write('foo', nil)
38
+ assert_nil @cache.read('foo')
39
+ @peek.write('foo', 'bar')
40
+ assert_nil @cache.read('foo')
41
+ end
42
+ end
43
+
44
+ def test_local_cache_of_delete
45
+ @cache.with_local_cache do
46
+ @cache.write('foo', 'bar')
47
+ @cache.delete('foo')
48
+ assert_nil @cache.read('foo')
49
+ end
50
+ end
51
+
52
+ def test_local_cache_of_exist
53
+ @cache.with_local_cache do
54
+ @cache.write('foo', 'bar')
55
+ @peek.delete('foo')
56
+ assert @cache.exist?('foo')
57
+ end
58
+ end
59
+
60
+ def test_local_cache_of_increment
61
+ @cache.with_local_cache do
62
+ @cache.write('foo', 1, :raw => true)
63
+ @peek.write('foo', 2, :raw => true)
64
+ @cache.increment('foo')
65
+ assert_equal 3, @cache.read('foo')
66
+ end
67
+ end
68
+
69
+ def test_local_cache_of_decrement
70
+ @cache.with_local_cache do
71
+ @cache.write('foo', 1, :raw => true)
72
+ @peek.write('foo', 3, :raw => true)
73
+ @cache.decrement('foo')
74
+ assert_equal 2, @cache.read('foo')
75
+ end
76
+ end
77
+
78
+ def test_middleware
79
+ app = lambda { |env|
80
+ result = @cache.write('foo', 'bar')
81
+ assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
82
+ assert result
83
+ [200, {}, []]
84
+ }
85
+ app = @cache.middleware.new(app)
86
+ app.call({})
87
+ end
88
+ end
@@ -0,0 +1,90 @@
1
+ gem 'minitest' # make sure we get the gem, not stdlib
2
+ require 'minitest'
3
+ require "minitest/autorun"
4
+
5
+ require 'active_support'
6
+ require 'active_support/inflector'
7
+ require 'mocha/setup' # FIXME: stop using mocha
8
+
9
+ require 'cache_store_behavior'
10
+ require 'local_cache_behavior'
11
+ require 'cache_increment_decrement_behavior'
12
+ require 'encoded_key_cache_behavior'
13
+ require 'autoloading_cache_behavior'
14
+
15
+ class SpymemcachedStoreTest < ::Minitest::Test
16
+
17
+ def setup
18
+ @cache = @@cache ||= ActiveSupport::Cache.lookup_store(:spymemcached_store, :expires_in => 60)
19
+ @peek = @@peek ||= ActiveSupport::Cache.lookup_store(:spymemcached_store)
20
+ @data = @cache.instance_variable_get(:@client)
21
+ @cache.clear
22
+ @cache.silence!
23
+ @cache.logger = ActiveSupport::Logger.new("/dev/null")
24
+ end
25
+
26
+ include CacheStoreBehavior
27
+ include LocalCacheBehavior
28
+ include CacheIncrementDecrementBehavior
29
+ include EncodedKeyCacheBehavior
30
+ include AutoloadingCacheBehavior
31
+
32
+ def test_raw_values
33
+ cache = @peek
34
+ cache.write("foo", 2)
35
+ #spymemcached returns right type
36
+ assert_equal 2, cache.read("foo")
37
+ end
38
+
39
+ def test_raw_values_with_marshal
40
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
41
+ cache.clear
42
+ cache.write("foo", Marshal.dump([]))
43
+ assert_equal [], cache.read("foo")
44
+ end
45
+
46
+ def test_local_cache_raw_values
47
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
48
+ cache.clear
49
+ cache.with_local_cache do
50
+ cache.write("foo", '2')
51
+ assert_equal "2", cache.read("foo")
52
+ end
53
+ end
54
+
55
+ def test_local_cache_raw_values_with_marshal
56
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
57
+ cache.clear
58
+ cache.with_local_cache do
59
+ cache.write("foo", Marshal.dump([]))
60
+ assert_equal [], cache.read("foo")
61
+ end
62
+ end
63
+
64
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
65
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
66
+ cache.write('foo', 'bar')
67
+ value = cache.read('foo')
68
+ assert_not_equal value.object_id, cache.read('foo').object_id
69
+ value << 'bingo'
70
+ assert_not_equal value, cache.read('foo')
71
+ end
72
+
73
+ def test_namespace
74
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :namespace => "abc")
75
+ cache.write('foo', 'bar')
76
+ assert_equal 'bar', cache.read('foo')
77
+ assert_nil @cache.read('foo')
78
+ assert_equal 'bar', @cache.read('abc:foo')
79
+ end
80
+
81
+ def assert_not_equal(expected, result)
82
+ assert expected != result, "expect #{expected.inspect} not equal #{result.inspect}"
83
+ end
84
+
85
+ def assert_nothing_raised(&block)
86
+ block.call
87
+ rescue => e
88
+ fail("expected nothing raised, but caught #{e.class}: #{e.message}")
89
+ end
90
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spymemcached_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: java
6
+ authors:
7
+ - Xiao Li
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: spymemcached.jruby
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - '>='
23
+ - !ruby/object:Gem::Version
24
+ version: '0'
25
+ prerelease: false
26
+ type: :runtime
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: '1.7'
39
+ prerelease: false
40
+ type: :development
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: '10.0'
53
+ prerelease: false
54
+ type: :development
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ prerelease: false
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ name: mocha
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ prerelease: false
82
+ type: :development
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ prerelease: false
96
+ type: :development
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '5.1'
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ~>
107
+ - !ruby/object:Gem::Version
108
+ version: '5.1'
109
+ prerelease: false
110
+ type: :development
111
+ description: Rails 3 & 4 cache store for spymemcached.jruby.
112
+ email:
113
+ - swing1979@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .ruby-version
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - lib/active_support/cache/spymemcached_store.rb
125
+ - lib/spymemcached_store.rb
126
+ - lib/spymemcached_store/version.rb
127
+ - spymemcached_store.gemspec
128
+ - test/autoloading_cache_behavior.rb
129
+ - test/cache_increment_decrement_behavior.rb
130
+ - test/cache_store_behavior.rb
131
+ - test/dependencies_test_helpers.rb
132
+ - test/encoded_key_cache_behavior.rb
133
+ - test/local_cache_behavior.rb
134
+ - test/spymemcached_store_test.rb
135
+ homepage: https://github.com/ThoughtWorksStudios/spymemcached_store
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.1.9
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Rails 3 & 4 cache store for spymemcached.jruby.
159
+ test_files:
160
+ - test/autoloading_cache_behavior.rb
161
+ - test/cache_increment_decrement_behavior.rb
162
+ - test/cache_store_behavior.rb
163
+ - test/dependencies_test_helpers.rb
164
+ - test/encoded_key_cache_behavior.rb
165
+ - test/local_cache_behavior.rb
166
+ - test/spymemcached_store_test.rb