rails-cache-tags 1.0.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 ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rails-cache-tags.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 amikhailov
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.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Synopsys
2
+
3
+ Tagged caching support within your Rails application.
4
+
5
+ # Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rails-cache-tags'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rails-cache-tags
18
+
19
+ # Usage
20
+
21
+ Anywhere:
22
+
23
+ ```ruby
24
+ cache = Rails.cache
25
+
26
+ cache.write "foo", "bar", :tags => %w(baz foo)
27
+ cache.read "foo" # => "bar"
28
+
29
+ cache.delete_tag "baz"
30
+ cache.read "foo" => nil
31
+ ```
32
+
33
+ In your controller:
34
+ ```ruby
35
+ class PostController < ActionController::Base
36
+ def update
37
+ @post = Post.find_by_id(params[:id])
38
+
39
+ if @post.update_attributes(params)
40
+ expire_fragments_by_tags :post => @post.id
41
+ else
42
+ render :edit
43
+ end
44
+ end
45
+ end
46
+ ```
47
+
48
+ # Contributing
49
+
50
+ 1. Fork it
51
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
52
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
53
+ 4. Push to the branch (`git push origin my-new-feature`)
54
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ desc 'Run tests'
5
+ task :test do
6
+ test_dir = File.expand_path(File.join("..", "test"), __FILE__)
7
+
8
+ Dir[File.join(test_dir, '**', '*_test.rb')].each { |f| require f }
9
+ require 'minitest/autorun'
10
+ end
@@ -0,0 +1,27 @@
1
+ require "active_support/cache"
2
+
3
+ require "rails/cache/tag"
4
+ require "rails/cache/tags/store"
5
+
6
+ module ActiveSupport
7
+ module Cache
8
+ class Store
9
+ extend Rails::Cache::Tags::Store
10
+ end
11
+
12
+ class Entry
13
+ attr_accessor :tags
14
+ end
15
+ end
16
+ end
17
+
18
+ if defined?(ActionController::Base)
19
+ class ActionController::Base < ActionController::Metal
20
+ def expire_fragments_by_tags *args
21
+ return unless cache_configured?
22
+
23
+ cache_store.delete_tag *args
24
+ end
25
+ alias expire_fragments_by_tag expire_fragments_by_tags
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ module Rails
2
+ module Cache
3
+ class Tag #:nodoc:all:
4
+ KEY_PREFIX = '_tags'
5
+
6
+ class << self
7
+ # {:post => ['1', '2', '3']} => [Tag(post:1), Tag(post:2), Tag(post:3)]
8
+ # {:post => 1, :user => 2} => [Tag(post:1), Tag(user:2)]
9
+ # ['post:1', 'post:2', 'post:3'] => [Tag(post:1), Tag(post:2), Tag(post:3)]
10
+ def build_tags(names)
11
+ case names
12
+ when NilClass then nil
13
+ when Hash then names.map do |key, value|
14
+ Array.wrap(value).map { |v| new([key, v]) }
15
+ end.flatten
16
+ when Enumerable then names.map { |v| build_tags(v) }.flatten
17
+ when self then names
18
+ else [new(names)]
19
+ end
20
+ end
21
+ end
22
+
23
+ attr_reader :name
24
+
25
+ # Tag constructor, accepts String, Symbol and Array
26
+ def initialize(name)
27
+ @name = case name
28
+ when String, Symbol then name
29
+ when Array then name.join(':')
30
+ else raise ArgumentError
31
+ end
32
+ end
33
+
34
+ # real cache key
35
+ def to_key
36
+ [KEY_PREFIX, name].join('/')
37
+ end
38
+
39
+ # read tag's version from +store+
40
+ def fetch(store)
41
+ store.read(to_key)
42
+ end
43
+
44
+ # increment tag's version inside +store+
45
+ def increment(store)
46
+ current = fetch(store)
47
+
48
+ version = if current.is_a?(Fixnum)
49
+ current + 1
50
+ else
51
+ 1
52
+ end
53
+
54
+ store.write(to_key, version, :expires_in => nil)
55
+
56
+ version
57
+ end
58
+ end # class Tag
59
+ end # module Cache
60
+ end # module Rails
@@ -0,0 +1,78 @@
1
+ module Rails
2
+ module Cache
3
+ module Tags
4
+ module Store
5
+ # patched +new+ method
6
+ def new(*args, &block) #:nodoc:
7
+ unless acts_like?(:cached_tags)
8
+ extend ClassMethods
9
+ include InstanceMethods
10
+
11
+ alias_method_chain :read_entry, :tags
12
+ alias_method_chain :write_entry, :tags
13
+ end
14
+
15
+ super
16
+ end
17
+
18
+ module ClassMethods #:nodoc:all:
19
+ def acts_like_cached_tags?
20
+ end
21
+ end
22
+
23
+ module InstanceMethods
24
+ # Increment the version of tags, so all entries referring to the tags become invalid
25
+ def delete_tag *names
26
+ tags = Rails::Cache::Tag.build_tags(names)
27
+
28
+ tags.each { |tag| tag.increment(self) } unless tags.empty?
29
+ end
30
+ alias delete_by_tag delete_tag
31
+ alias delete_by_tags delete_tag
32
+ alias expire_tag delete_tag
33
+
34
+ protected
35
+ def read_entry_with_tags(key, options) #:nodoc
36
+ entry = read_entry_without_tags(key, options)
37
+
38
+ if entry && entry.tags.present?
39
+ current_versions = fetch_tags(entry.tags.keys)
40
+ saved_versions = entry.tags.values
41
+
42
+ if current_versions != saved_versions
43
+ delete_entry(key, options)
44
+
45
+ return nil
46
+ end
47
+ end
48
+
49
+ entry
50
+ end # def read_entry_with_tags
51
+
52
+ def write_entry_with_tags(key, entry, options) #:nodoc:
53
+ tags = Rails::Cache::Tag.build_tags Array.wrap(options[:tags]).flatten.compact
54
+
55
+ unless tags.empty?
56
+ current_versions = fetch_tags(tags) # => [1, 2, 3]
57
+ entry.tags = Hash[tags.zip(current_versions).map { |tag, v| [tag.name, v || tag.increment(self)] }]
58
+ end
59
+
60
+ write_entry_without_tags(key, entry, options)
61
+ end # def write_entry_without_tags
62
+
63
+ private
64
+ # fetch tags versions from store
65
+ # fetch ['user:1', 'post:2', 'country:2'] => [3, 4, nil]
66
+ def fetch_tags(names) #:nodoc:
67
+ tags = Rails::Cache::Tag.build_tags names
68
+ keys = tags.map(&:to_key)
69
+ stored = read_multi(*keys)
70
+
71
+ # we should save order
72
+ keys.collect { |k| stored[k] }
73
+ end
74
+ end
75
+ end # module Store
76
+ end # module Tags
77
+ end # module Cache
78
+ end # module Rails
@@ -0,0 +1,7 @@
1
+ module Rails
2
+ module Cache
3
+ module Tags
4
+ VERSION = "1.0.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rails/cache/tags/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Alexei Mikhailov"]
6
+ gem.email = %W(amikhailov83@gmail.com)
7
+ gem.description = %q{Tagged caching support for Rails}
8
+ gem.summary = %q{Tagged caching support for Rails}
9
+ gem.homepage = "https://github.com/take-five/rails-cache-tags"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "rails-cache-tags"
14
+ gem.require_paths = %W(lib)
15
+ gem.version = Rails::Cache::Tags::VERSION
16
+
17
+ gem.add_dependency "activesupport", ">= 3.0"
18
+ gem.add_dependency "actionpack", ">= 3.0"
19
+
20
+ gem.add_development_dependency 'minitest', '~> 3.2'
21
+ gem.add_development_dependency 'memcache-client'
22
+ gem.add_development_dependency 'rack'
23
+ gem.add_development_dependency 'mocha'
24
+ gem.add_development_dependency 'simplecov'
25
+ end
@@ -0,0 +1,80 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require "test_helper"
4
+ require "caching_test"
5
+
6
+ module CacheTagsBehavior
7
+ def test_read_and_write_with_tags
8
+ @cache.write("foo", "bar", :tags => "baz")
9
+ assert_equal 'bar', @cache.read('foo')
10
+ end
11
+
12
+ def test_read_after_tag_deleted
13
+ @cache.write("foo", "bar", :tags => "baz")
14
+ @cache.delete_tag("baz")
15
+
16
+ assert_nil @cache.read("foo")
17
+ end
18
+
19
+ def test_read_after_another_tag_deleted
20
+ @cache.write("foo", "bar", :tags => "baz")
21
+ @cache.delete_tag("fu")
22
+
23
+ assert_equal 'bar', @cache.read('foo')
24
+ end
25
+
26
+ def test_read_and_write_with_multiple_tags
27
+ @cache.write("foo", "bar", :tags => [:baz, :kung])
28
+ assert_equal 'bar', @cache.read('foo')
29
+ end
30
+
31
+ def test_read_after_one_of_tags_deleted
32
+ @cache.write("foo", "bar", :tags => [:baz, :kung])
33
+ @cache.delete_tag :kung
34
+
35
+ assert_nil @cache.read("foo")
36
+ end
37
+
38
+ def test_read_after_another_of_multiple_tags_deleted
39
+ @cache.write("foo", "bar", :tags => [:baz, :kung])
40
+ @cache.delete_tag("fu")
41
+
42
+ assert_equal 'bar', @cache.read('foo')
43
+ end
44
+
45
+ def test_read_with_small_default_expiration_time
46
+ cache = if is_a?(FileStoreTest)
47
+ @cache.class.new @cache.cache_path, :expires_in => 0.001
48
+ else
49
+ @cache.class.new :expires_in => 0.001
50
+ end
51
+
52
+ cache.write("foo", "bar", :tags => "baz", :expires_in => 2)
53
+ sleep 0.02
54
+
55
+ assert_equal 'bar', cache.read('foo')
56
+ end
57
+
58
+ def test_exists_with_tags
59
+ @cache.write("foo", "bar", :tags => "baz")
60
+ @cache.delete_tag("baz")
61
+
62
+ assert_equal @cache.exist?("foo"), false
63
+ end
64
+
65
+ def test_read_and_write_with_tags_hash
66
+ @cache.write("foo", "bar", :tags => {:baz => 1})
67
+ assert_equal 'bar', @cache.read('foo')
68
+ end
69
+
70
+ def test_read_and_write_with_tags_hash_after_expiration
71
+ @cache.write("foo", "bar", :tags => {:baz => 1})
72
+ @cache.delete_tag :baz => 1
73
+
74
+ assert_nil @cache.read('foo')
75
+ end
76
+ end
77
+
78
+ [FileStoreTest, MemoryStoreTest, MemCacheStoreTest].each do |klass|
79
+ klass.send :include, CacheTagsBehavior
80
+ end
@@ -0,0 +1,846 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'test_helper'
4
+ require 'logger'
5
+
6
+ class CacheKeyTest < ActiveSupport::TestCase
7
+ def test_expand_cache_key
8
+ assert_equal '1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true])
9
+ assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name)
10
+ end
11
+
12
+ def test_expand_cache_key_with_rails_cache_id
13
+ begin
14
+ ENV['RAILS_CACHE_ID'] = 'c99'
15
+ assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
16
+ assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo])
17
+ assert_equal 'c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar])
18
+ assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm)
19
+ assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm)
20
+ assert_equal 'nm/c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
21
+ ensure
22
+ ENV['RAILS_CACHE_ID'] = nil
23
+ end
24
+ end
25
+
26
+ def test_expand_cache_key_with_rails_app_version
27
+ begin
28
+ ENV['RAILS_APP_VERSION'] = 'rails3'
29
+ assert_equal 'rails3/foo', ActiveSupport::Cache.expand_cache_key(:foo)
30
+ ensure
31
+ ENV['RAILS_APP_VERSION'] = nil
32
+ end
33
+ end
34
+
35
+ def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
36
+ begin
37
+ ENV['RAILS_CACHE_ID'] = 'c99'
38
+ ENV['RAILS_APP_VERSION'] = 'rails3'
39
+ assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
40
+ ensure
41
+ ENV['RAILS_CACHE_ID'] = nil
42
+ ENV['RAILS_APP_VERSION'] = nil
43
+ end
44
+ end
45
+
46
+ def test_expand_cache_key_respond_to_cache_key
47
+ key = 'foo'
48
+ def key.cache_key
49
+ :foo_key
50
+ end
51
+ assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key(key)
52
+ end
53
+
54
+ def test_expand_cache_key_array_with_something_that_responds_to_cache_key
55
+ key = 'foo'
56
+ def key.cache_key
57
+ :foo_key
58
+ end
59
+ assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key([key])
60
+ end
61
+
62
+ def test_expand_cache_key_of_nil
63
+ assert_equal '', ActiveSupport::Cache.expand_cache_key(nil)
64
+ end
65
+
66
+ def test_expand_cache_key_of_false
67
+ assert_equal 'false', ActiveSupport::Cache.expand_cache_key(false)
68
+ end
69
+
70
+ def test_expand_cache_key_of_true
71
+ assert_equal 'true', ActiveSupport::Cache.expand_cache_key(true)
72
+ end
73
+ end
74
+
75
+ class CacheStoreSettingTest < ActiveSupport::TestCase
76
+ def test_file_fragment_cache_store
77
+ store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory"
78
+ assert_kind_of(ActiveSupport::Cache::FileStore, store)
79
+ assert_equal "/path/to/cache/directory", store.cache_path
80
+ end
81
+
82
+ def test_mem_cache_fragment_cache_store
83
+ MemCache.expects(:new).with(%w[localhost], {})
84
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
85
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
86
+ end
87
+
88
+ def test_mem_cache_fragment_cache_store_with_given_mem_cache
89
+ mem_cache = MemCache.new
90
+ MemCache.expects(:new).never
91
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
92
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
93
+ end
94
+
95
+ def test_mem_cache_fragment_cache_store_with_given_mem_cache_like_object
96
+ MemCache.expects(:new).never
97
+ memcache = Object.new
98
+ def memcache.get() true end
99
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
100
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
101
+ end
102
+
103
+ def test_mem_cache_fragment_cache_store_with_multiple_servers
104
+ MemCache.expects(:new).with(%w[localhost 192.168.1.1], {})
105
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
106
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
107
+ end
108
+
109
+ def test_mem_cache_fragment_cache_store_with_options
110
+ MemCache.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 })
111
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
112
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
113
+ assert_equal 'foo', store.options[:namespace]
114
+ end
115
+
116
+ def test_object_assigned_fragment_cache_store
117
+ store = ActiveSupport::Cache.lookup_store ActiveSupport::Cache::FileStore.new("/path/to/cache/directory")
118
+ assert_kind_of(ActiveSupport::Cache::FileStore, store)
119
+ assert_equal "/path/to/cache/directory", store.cache_path
120
+ end
121
+ end
122
+
123
+ class CacheStoreNamespaceTest < ActiveSupport::TestCase
124
+ def test_static_namespace
125
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "tester")
126
+ cache.write("foo", "bar")
127
+ assert_equal "bar", cache.read("foo")
128
+ assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
129
+ end
130
+
131
+ def test_proc_namespace
132
+ test_val = "tester"
133
+ proc = lambda{test_val}
134
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => proc)
135
+ cache.write("foo", "bar")
136
+ assert_equal "bar", cache.read("foo")
137
+ assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
138
+ end
139
+
140
+ def test_delete_matched_key_start
141
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "tester")
142
+ cache.write("foo", "bar")
143
+ cache.write("fu", "baz")
144
+ cache.delete_matched(/^fo/)
145
+ assert !cache.exist?("foo")
146
+ assert cache.exist?("fu")
147
+ end
148
+
149
+ def test_delete_matched_key
150
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, :namespace => "foo")
151
+ cache.write("foo", "bar")
152
+ cache.write("fu", "baz")
153
+ cache.delete_matched(/OO/i)
154
+ assert !cache.exist?("foo")
155
+ assert cache.exist?("fu")
156
+ end
157
+ end
158
+
159
+ # Tests the base functionality that should be identical across all cache stores.
160
+ module CacheStoreBehavior
161
+ def test_should_read_and_write_strings
162
+ assert @cache.write('foo', 'bar')
163
+ assert_equal 'bar', @cache.read('foo')
164
+ end
165
+
166
+ def test_should_overwrite
167
+ @cache.write('foo', 'bar')
168
+ @cache.write('foo', 'baz')
169
+ assert_equal 'baz', @cache.read('foo')
170
+ end
171
+
172
+ def test_fetch_without_cache_miss
173
+ @cache.write('foo', 'bar')
174
+ @cache.expects(:write).never
175
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
176
+ end
177
+
178
+ def test_fetch_with_cache_miss
179
+ @cache.expects(:write).with('foo', 'baz', @cache.options)
180
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
181
+ end
182
+
183
+ def test_fetch_with_forced_cache_miss
184
+ @cache.write('foo', 'bar')
185
+ @cache.expects(:read).never
186
+ @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true))
187
+ @cache.fetch('foo', :force => true) { 'bar' }
188
+ end
189
+
190
+ def test_fetch_with_cached_nil
191
+ @cache.write('foo', nil)
192
+ @cache.expects(:write).never
193
+ assert_nil @cache.fetch('foo') { 'baz' }
194
+ end
195
+
196
+ def test_should_read_and_write_hash
197
+ assert @cache.write('foo', {:a => "b"})
198
+ assert_equal({:a => "b"}, @cache.read('foo'))
199
+ end
200
+
201
+ def test_should_read_and_write_integer
202
+ assert @cache.write('foo', 1)
203
+ assert_equal 1, @cache.read('foo')
204
+ end
205
+
206
+ def test_should_read_and_write_nil
207
+ assert @cache.write('foo', nil)
208
+ assert_equal nil, @cache.read('foo')
209
+ end
210
+
211
+ def test_should_read_and_write_false
212
+ assert @cache.write('foo', false)
213
+ assert_equal false, @cache.read('foo')
214
+ end
215
+
216
+ def test_should_read_cached_numeric_from_previous_rails_versions
217
+ @old_cache = ActiveSupport::Cache::Entry.create( 1, Time.now )
218
+ assert_equal( 1, @old_cache.value )
219
+ end
220
+
221
+ def test_should_read_cached_hash_from_previous_rails_versions
222
+ @old_cache = ActiveSupport::Cache::Entry.create( {}, Time.now )
223
+ assert_equal( {}, @old_cache.value )
224
+ end
225
+
226
+ def test_should_read_cached_string_from_previous_rails_versions
227
+ @old_cache = ActiveSupport::Cache::Entry.create( 'string', Time.now )
228
+ assert_equal( 'string', @old_cache.value )
229
+ end
230
+
231
+ def test_read_multi
232
+ @cache.write('foo', 'bar')
233
+ @cache.write('fu', 'baz')
234
+ @cache.write('fud', 'biz')
235
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
236
+ end
237
+
238
+ def test_read_multi_with_expires
239
+ @cache.write('foo', 'bar', :expires_in => 0.001)
240
+ @cache.write('fu', 'baz')
241
+ @cache.write('fud', 'biz')
242
+ sleep(0.002)
243
+ assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
244
+ end
245
+
246
+ def test_read_and_write_compressed_small_data
247
+ @cache.write('foo', 'bar', :compress => true)
248
+ raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
249
+ assert_equal 'bar', @cache.read('foo')
250
+ assert_equal 'bar', Marshal.load(raw_value)
251
+ end
252
+
253
+ def test_read_and_write_compressed_large_data
254
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
255
+ raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
256
+ assert_equal 'bar', @cache.read('foo')
257
+ assert_equal 'bar', Marshal.load(Zlib::Inflate.inflate(raw_value))
258
+ end
259
+
260
+ def test_read_and_write_compressed_nil
261
+ @cache.write('foo', nil, :compress => true)
262
+ assert_nil @cache.read('foo')
263
+ end
264
+
265
+ def test_cache_key
266
+ obj = Object.new
267
+ def obj.cache_key
268
+ :foo
269
+ end
270
+ @cache.write(obj, "bar")
271
+ assert_equal "bar", @cache.read("foo")
272
+ end
273
+
274
+ def test_param_as_cache_key
275
+ obj = Object.new
276
+ def obj.to_param
277
+ "foo"
278
+ end
279
+ @cache.write(obj, "bar")
280
+ assert_equal "bar", @cache.read("foo")
281
+ end
282
+
283
+ def test_array_as_cache_key
284
+ @cache.write([:fu, "foo"], "bar")
285
+ assert_equal "bar", @cache.read("fu/foo")
286
+ end
287
+
288
+ def test_hash_as_cache_key
289
+ @cache.write({:foo => 1, :fu => 2}, "bar")
290
+ assert_equal "bar", @cache.read("foo=1/fu=2")
291
+ end
292
+
293
+ def test_keys_are_case_sensitive
294
+ @cache.write("foo", "bar")
295
+ assert_nil @cache.read("FOO")
296
+ end
297
+
298
+ def test_exist
299
+ @cache.write('foo', 'bar')
300
+ assert @cache.exist?('foo')
301
+ assert !@cache.exist?('bar')
302
+ end
303
+
304
+ def test_nil_exist
305
+ @cache.write('foo', nil)
306
+ assert @cache.exist?('foo')
307
+ end
308
+
309
+ def test_delete
310
+ @cache.write('foo', 'bar')
311
+ assert @cache.exist?('foo')
312
+ assert @cache.delete('foo')
313
+ assert !@cache.exist?('foo')
314
+ end
315
+
316
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
317
+ @cache.write('foo', 'bar')
318
+ assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id
319
+ value = @cache.read('foo')
320
+ value << 'bingo'
321
+ assert_not_equal value, @cache.read('foo')
322
+ end
323
+
324
+ def test_original_store_objects_should_not_be_immutable
325
+ bar = 'bar'
326
+ @cache.write('foo', bar)
327
+ assert_nothing_raised { bar.gsub!(/.*/, 'baz') }
328
+ end
329
+
330
+ def test_expires_in
331
+ time = Time.local(2008, 4, 24)
332
+ Time.stubs(:now).returns(time)
333
+
334
+ @cache.write('foo', 'bar')
335
+ assert_equal 'bar', @cache.read('foo')
336
+
337
+ Time.stubs(:now).returns(time + 30)
338
+ assert_equal 'bar', @cache.read('foo')
339
+
340
+ Time.stubs(:now).returns(time + 61)
341
+ assert_nil @cache.read('foo')
342
+ end
343
+
344
+ def test_race_condition_protection
345
+ time = Time.now
346
+ @cache.write('foo', 'bar', :expires_in => 60)
347
+ Time.stubs(:now).returns(time + 61)
348
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
349
+ assert_equal 'bar', @cache.read('foo')
350
+ "baz"
351
+ end
352
+ assert_equal "baz", result
353
+ end
354
+
355
+ def test_race_condition_protection_is_limited
356
+ time = Time.now
357
+ @cache.write('foo', 'bar', :expires_in => 60)
358
+ Time.stubs(:now).returns(time + 71)
359
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
360
+ assert_equal nil, @cache.read('foo')
361
+ "baz"
362
+ end
363
+ assert_equal "baz", result
364
+ end
365
+
366
+ def test_race_condition_protection_is_safe
367
+ time = Time.now
368
+ @cache.write('foo', 'bar', :expires_in => 60)
369
+ Time.stubs(:now).returns(time + 61)
370
+ begin
371
+ @cache.fetch('foo', :race_condition_ttl => 10) do
372
+ assert_equal 'bar', @cache.read('foo')
373
+ raise ArgumentError.new
374
+ end
375
+ rescue ArgumentError
376
+ end
377
+ assert_equal "bar", @cache.read('foo')
378
+ Time.stubs(:now).returns(time + 71)
379
+ assert_nil @cache.read('foo')
380
+ end
381
+
382
+ def test_crazy_key_characters
383
+ crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
384
+ assert @cache.write(crazy_key, "1", :raw => true)
385
+ assert_equal "1", @cache.read(crazy_key)
386
+ assert_equal "1", @cache.fetch(crazy_key)
387
+ assert @cache.delete(crazy_key)
388
+ assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
389
+ assert_equal 3, @cache.increment(crazy_key)
390
+ assert_equal 2, @cache.decrement(crazy_key)
391
+ end
392
+
393
+ def test_really_long_keys
394
+ key = ""
395
+ 900.times{key << "x"}
396
+ assert @cache.write(key, "bar")
397
+ assert_equal "bar", @cache.read(key)
398
+ assert_equal "bar", @cache.fetch(key)
399
+ assert_nil @cache.read("#{key}x")
400
+ assert_equal({key => "bar"}, @cache.read_multi(key))
401
+ assert @cache.delete(key)
402
+ end
403
+ end
404
+
405
+ # https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
406
+ # The error is caused by charcter encodings that can't be compared with ASCII-8BIT regular expressions and by special
407
+ # characters like the umlaut in UTF-8.
408
+ module EncodedKeyCacheBehavior
409
+ if defined?(Encoding)
410
+ Encoding.list.each do |encoding|
411
+ define_method "test_#{encoding.name.underscore}_encoded_values" do
412
+ key = "foo".force_encoding(encoding)
413
+ assert @cache.write(key, "1", :raw => true)
414
+ assert_equal "1", @cache.read(key)
415
+ assert_equal "1", @cache.fetch(key)
416
+ assert @cache.delete(key)
417
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
418
+ assert_equal 3, @cache.increment(key)
419
+ assert_equal 2, @cache.decrement(key)
420
+ end
421
+ end
422
+
423
+ def test_common_utf8_values
424
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
425
+ assert @cache.write(key, "1", :raw => true)
426
+ assert_equal "1", @cache.read(key)
427
+ assert_equal "1", @cache.fetch(key)
428
+ assert @cache.delete(key)
429
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
430
+ assert_equal 3, @cache.increment(key)
431
+ assert_equal 2, @cache.decrement(key)
432
+ end
433
+
434
+ def test_retains_encoding
435
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
436
+ assert @cache.write(key, "1", :raw => true)
437
+ assert_equal Encoding::UTF_8, key.encoding
438
+ end
439
+ end
440
+ end
441
+
442
+ module CacheDeleteMatchedBehavior
443
+ def test_delete_matched
444
+ @cache.write("foo", "bar")
445
+ @cache.write("fu", "baz")
446
+ @cache.write("foo/bar", "baz")
447
+ @cache.write("fu/baz", "bar")
448
+ @cache.delete_matched(/oo/)
449
+ assert !@cache.exist?("foo")
450
+ assert @cache.exist?("fu")
451
+ assert !@cache.exist?("foo/bar")
452
+ assert @cache.exist?("fu/baz")
453
+ end
454
+ end
455
+
456
+ module CacheIncrementDecrementBehavior
457
+ def test_increment
458
+ @cache.write('foo', 1, :raw => true)
459
+ assert_equal 1, @cache.read('foo').to_i
460
+ assert_equal 2, @cache.increment('foo')
461
+ assert_equal 2, @cache.read('foo').to_i
462
+ assert_equal 3, @cache.increment('foo')
463
+ assert_equal 3, @cache.read('foo').to_i
464
+ end
465
+
466
+ def test_decrement
467
+ @cache.write('foo', 3, :raw => true)
468
+ assert_equal 3, @cache.read('foo').to_i
469
+ assert_equal 2, @cache.decrement('foo')
470
+ assert_equal 2, @cache.read('foo').to_i
471
+ assert_equal 1, @cache.decrement('foo')
472
+ assert_equal 1, @cache.read('foo').to_i
473
+ end
474
+ end
475
+
476
+ module LocalCacheBehavior
477
+ def test_local_writes_are_persistent_on_the_remote_cache
478
+ retval = @cache.with_local_cache do
479
+ @cache.write('foo', 'bar')
480
+ end
481
+ assert retval
482
+ assert_equal 'bar', @cache.read('foo')
483
+ end
484
+
485
+ def test_clear_also_clears_local_cache
486
+ @cache.with_local_cache do
487
+ @cache.write('foo', 'bar')
488
+ @cache.clear
489
+ assert_nil @cache.read('foo')
490
+ end
491
+
492
+ assert_nil @cache.read('foo')
493
+ end
494
+
495
+ def test_local_cache_of_write
496
+ @cache.with_local_cache do
497
+ @cache.write('foo', 'bar')
498
+ @peek.delete('foo')
499
+ assert_equal 'bar', @cache.read('foo')
500
+ end
501
+ end
502
+
503
+ def test_local_cache_of_read
504
+ @cache.write('foo', 'bar')
505
+ @cache.with_local_cache do
506
+ assert_equal 'bar', @cache.read('foo')
507
+ end
508
+ end
509
+
510
+ def test_local_cache_of_write_nil
511
+ @cache.with_local_cache do
512
+ assert @cache.write('foo', nil)
513
+ assert_nil @cache.read('foo')
514
+ @peek.write('foo', 'bar')
515
+ assert_nil @cache.read('foo')
516
+ end
517
+ end
518
+
519
+ def test_local_cache_of_delete
520
+ @cache.with_local_cache do
521
+ @cache.write('foo', 'bar')
522
+ @cache.delete('foo')
523
+ assert_nil @cache.read('foo')
524
+ end
525
+ end
526
+
527
+ def test_local_cache_of_exist
528
+ @cache.with_local_cache do
529
+ @cache.write('foo', 'bar')
530
+ @peek.delete('foo')
531
+ assert @cache.exist?('foo')
532
+ end
533
+ end
534
+
535
+ def test_local_cache_of_increment
536
+ @cache.with_local_cache do
537
+ @cache.write('foo', 1, :raw => true)
538
+ @peek.write('foo', 2, :raw => true)
539
+ @cache.increment('foo')
540
+ assert_equal 3, @cache.read('foo')
541
+ end
542
+ end
543
+
544
+ def test_local_cache_of_decrement
545
+ @cache.with_local_cache do
546
+ @cache.write('foo', 1, :raw => true)
547
+ @peek.write('foo', 3, :raw => true)
548
+ @cache.decrement('foo')
549
+ assert_equal 2, @cache.read('foo')
550
+ end
551
+ end
552
+
553
+ def test_middleware
554
+ app = lambda { |env|
555
+ result = @cache.write('foo', 'bar')
556
+ assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
557
+ assert result
558
+ }
559
+ app = @cache.middleware.new(app)
560
+ app.call({})
561
+ end
562
+ end
563
+
564
+ class FileStoreTest < ActiveSupport::TestCase
565
+ def setup
566
+ Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
567
+ @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60)
568
+ @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60)
569
+ @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), :expires_in => 60)
570
+ end
571
+
572
+ def teardown
573
+ FileUtils.rm_r(cache_dir)
574
+ end
575
+
576
+ def cache_dir
577
+ File.join(Dir.pwd, 'tmp_cache')
578
+ end
579
+
580
+ include CacheStoreBehavior
581
+ include LocalCacheBehavior
582
+ include CacheDeleteMatchedBehavior
583
+ include CacheIncrementDecrementBehavior
584
+
585
+ def test_key_transformation
586
+ key = @cache.send(:key_file_path, "views/index?id=1")
587
+ assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
588
+ end
589
+
590
+ def test_key_transformation_with_pathname
591
+ FileUtils.touch(File.join(cache_dir, "foo"))
592
+ key = @cache_with_pathname.send(:key_file_path, "views/index?id=1")
593
+ assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
594
+ end
595
+
596
+ # Because file systems have a maximum filename size, filenames > max size should be split in to directories
597
+ # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
598
+ def test_key_transformation_max_filename_size
599
+ key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
600
+ path = @cache.send(:key_file_path, key)
601
+ assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}
602
+ assert_equal 'B', File.basename(path)
603
+ end
604
+
605
+ # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist
606
+ # Ensure delete_matched gracefully handles this case
607
+ def test_delete_matched_when_cache_directory_does_not_exist
608
+ assert_nothing_raised(Exception) do
609
+ ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/)
610
+ end
611
+ end
612
+ end
613
+
614
+ class MemoryStoreTest < ActiveSupport::TestCase
615
+ def setup
616
+ @record_size = Marshal.dump("aaaaaaaaaa").bytesize
617
+ @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10)
618
+ end
619
+
620
+ include CacheStoreBehavior
621
+ include CacheDeleteMatchedBehavior
622
+ include CacheIncrementDecrementBehavior
623
+
624
+ def test_prune_size
625
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
626
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
627
+ @cache.write(3, "cccccccccc") && sleep(0.001)
628
+ @cache.write(4, "dddddddddd") && sleep(0.001)
629
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
630
+ @cache.read(2) && sleep(0.001)
631
+ @cache.read(4)
632
+ @cache.prune(@record_size * 3)
633
+ assert @cache.exist?(5)
634
+ assert @cache.exist?(4)
635
+ assert !@cache.exist?(3)
636
+ assert @cache.exist?(2)
637
+ assert !@cache.exist?(1)
638
+ end
639
+
640
+ def test_prune_size_on_write
641
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
642
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
643
+ @cache.write(3, "cccccccccc") && sleep(0.001)
644
+ @cache.write(4, "dddddddddd") && sleep(0.001)
645
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
646
+ @cache.write(6, "ffffffffff") && sleep(0.001)
647
+ @cache.write(7, "gggggggggg") && sleep(0.001)
648
+ @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
649
+ @cache.write(9, "iiiiiiiiii") && sleep(0.001)
650
+ @cache.write(10, "kkkkkkkkkk") && sleep(0.001)
651
+ @cache.read(2) && sleep(0.001)
652
+ @cache.read(4) && sleep(0.001)
653
+ @cache.write(11, "llllllllll")
654
+ assert @cache.exist?(11)
655
+ assert @cache.exist?(10)
656
+ assert @cache.exist?(9)
657
+ assert @cache.exist?(8)
658
+ assert @cache.exist?(7)
659
+ assert !@cache.exist?(6)
660
+ assert !@cache.exist?(5)
661
+ assert @cache.exist?(4)
662
+ assert !@cache.exist?(3)
663
+ assert @cache.exist?(2)
664
+ assert !@cache.exist?(1)
665
+ end
666
+
667
+ def test_pruning_is_capped_at_a_max_time
668
+ def @cache.delete_entry (*args)
669
+ sleep(0.01)
670
+ super
671
+ end
672
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
673
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
674
+ @cache.write(3, "cccccccccc") && sleep(0.001)
675
+ @cache.write(4, "dddddddddd") && sleep(0.001)
676
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
677
+ @cache.prune(30, 0.001)
678
+ assert @cache.exist?(5)
679
+ assert @cache.exist?(4)
680
+ assert @cache.exist?(3)
681
+ assert @cache.exist?(2)
682
+ assert !@cache.exist?(1)
683
+ end
684
+ end
685
+
686
+ uses_memcached 'memcached backed store' do
687
+ class MemCacheStoreTest < ActiveSupport::TestCase
688
+ def setup
689
+ @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :expires_in => 60)
690
+ @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store)
691
+ @data = @cache.instance_variable_get(:@data)
692
+ @cache.clear
693
+ @cache.silence!
694
+ @cache.logger = Logger.new("/dev/null")
695
+ end
696
+
697
+ include CacheStoreBehavior
698
+ include LocalCacheBehavior
699
+ include CacheIncrementDecrementBehavior
700
+ include EncodedKeyCacheBehavior
701
+
702
+ def test_raw_values
703
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
704
+ cache.clear
705
+ cache.write("foo", 2)
706
+ assert_equal "2", cache.read("foo")
707
+ end
708
+
709
+ def test_raw_values_with_marshal
710
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
711
+ cache.clear
712
+ cache.write("foo", Marshal.dump([]))
713
+ assert_equal [], cache.read("foo")
714
+ end
715
+
716
+ def test_local_cache_raw_values
717
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
718
+ cache.clear
719
+ cache.with_local_cache do
720
+ cache.write("foo", 2)
721
+ assert_equal "2", cache.read("foo")
722
+ end
723
+ end
724
+
725
+ def test_local_cache_raw_values_with_marshal
726
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
727
+ cache.clear
728
+ cache.with_local_cache do
729
+ cache.write("foo", Marshal.dump([]))
730
+ assert_equal [], cache.read("foo")
731
+ end
732
+ end
733
+ end
734
+ end
735
+
736
+ class NullStoreTest < ActiveSupport::TestCase
737
+ def setup
738
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
739
+ end
740
+
741
+ def test_clear
742
+ @cache.clear
743
+ end
744
+
745
+ def test_cleanup
746
+ @cache.cleanup
747
+ end
748
+
749
+ def test_write
750
+ assert_equal true, @cache.write("name", "value")
751
+ end
752
+
753
+ def test_read
754
+ @cache.write("name", "value")
755
+ assert_nil @cache.read("name")
756
+ end
757
+
758
+ def test_delete
759
+ @cache.write("name", "value")
760
+ assert_equal false, @cache.delete("name")
761
+ end
762
+
763
+ def test_increment
764
+ @cache.write("name", 1, :raw => true)
765
+ assert_nil @cache.increment("name")
766
+ end
767
+
768
+ def test_decrement
769
+ @cache.write("name", 1, :raw => true)
770
+ assert_nil @cache.increment("name")
771
+ end
772
+
773
+ def test_delete_matched
774
+ @cache.write("name", "value")
775
+ @cache.delete_matched(/name/)
776
+ end
777
+
778
+ def test_local_store_strategy
779
+ @cache.with_local_cache do
780
+ @cache.write("name", "value")
781
+ assert_equal "value", @cache.read("name")
782
+ @cache.delete("name")
783
+ assert_nil @cache.read("name")
784
+ @cache.write("name", "value")
785
+ end
786
+ assert_nil @cache.read("name")
787
+ end
788
+
789
+ def test_setting_nil_cache_store
790
+ assert ActiveSupport::Cache.lookup_store.class.name, ActiveSupport::Cache::NullStore.name
791
+ end
792
+ end
793
+
794
+ class CacheStoreLoggerTest < ActiveSupport::TestCase
795
+ def setup
796
+ @cache = ActiveSupport::Cache.lookup_store(:memory_store)
797
+
798
+ @buffer = StringIO.new
799
+ @cache.logger = Logger.new(@buffer)
800
+ end
801
+
802
+ def test_logging
803
+ @cache.fetch('foo') { 'bar' }
804
+ assert_present @buffer.string
805
+ end
806
+
807
+ def test_mute_logging
808
+ @cache.mute { @cache.fetch('foo') { 'bar' } }
809
+ assert_blank @buffer.string
810
+ end
811
+ end
812
+
813
+ class CacheEntryTest < ActiveSupport::TestCase
814
+ def test_create_raw_entry
815
+ time = Time.now
816
+ entry = ActiveSupport::Cache::Entry.create("raw", time, :compress => false, :expires_in => 300)
817
+ assert_equal "raw", entry.raw_value
818
+ assert_equal time.to_f, entry.created_at
819
+ assert !entry.compressed?
820
+ assert_equal 300, entry.expires_in
821
+ end
822
+
823
+ def test_expired
824
+ entry = ActiveSupport::Cache::Entry.new("value")
825
+ assert !entry.expired?, 'entry not expired'
826
+ entry = ActiveSupport::Cache::Entry.new("value", :expires_in => 60)
827
+ assert !entry.expired?, 'entry not expired'
828
+ time = Time.now + 61
829
+ Time.stubs(:now).returns(time)
830
+ assert entry.expired?, 'entry is expired'
831
+ end
832
+
833
+ def test_compress_values
834
+ entry = ActiveSupport::Cache::Entry.new("value", :compress => true, :compress_threshold => 1)
835
+ assert_equal "value", entry.value
836
+ assert entry.compressed?
837
+ assert_equal "value", Marshal.load(Zlib::Inflate.inflate(entry.raw_value))
838
+ end
839
+
840
+ def test_non_compress_values
841
+ entry = ActiveSupport::Cache::Entry.new("value")
842
+ assert_equal "value", entry.value
843
+ assert_equal "value", Marshal.load(entry.raw_value)
844
+ assert !entry.compressed?
845
+ end
846
+ end