diff --git a/Gemfile b/Gemfile index 02e5b3d..d75e719 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ source :rubygems +gemspec + group :development, :test do gem 'rake' end diff --git a/Gemfile.lock b/Gemfile.lock index 9b23505..5230a7c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,18 @@ +PATH + remote: . + specs: + diskcached (1.1.0) + GEM remote: http://rubygems.org/ specs: diff-lcs (1.1.3) + json (1.8.1) memcached (1.4.3) multi_json (1.3.6) rake (0.9.2.2) + rdoc (4.1.1) + json (~> 1.4) redcarpet (2.1.1) rspec (2.11.0) rspec-core (~> 2.11.0) @@ -18,15 +26,19 @@ GEM multi_json (~> 1.0) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) + timecop (0.7.1) yard (0.8.2.1) PLATFORMS ruby DEPENDENCIES + diskcached! memcached rake + rdoc redcarpet rspec simplecov + timecop yard diff --git a/diskcached.gemspec b/diskcached.gemspec index 18d0801..83810f9 100644 --- a/diskcached.gemspec +++ b/diskcached.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rspec" s.add_development_dependency "simplecov" s.add_development_dependency "rdoc" + s.add_development_dependency "timecop" s.files = Dir.glob("lib/**/*") + %w(README.md HISTORY.md Benchmark.md Gemfile Rakefile) s.require_path = 'lib' diff --git a/lib/diskcached.rb b/lib/diskcached.rb index 2d1281f..2249c60 100644 --- a/lib/diskcached.rb +++ b/lib/diskcached.rb @@ -114,22 +114,18 @@ def set key, value # which exist and aren't expired, it raises # Diskcache::NotFound if none are available def get key - begin - if key.is_a? Array - hash = {} - key.each do |k| - hash[k] = Marshal::load(read_cache_file(k)) unless expired?(k) - end - flush_expired if gc_auto - return hash unless hash.empty? - else - flush_expired if gc_auto - return Marshal::load(read_cache_file(key)) unless expired?(key) + if key.is_a? Array + hash = {} + key.each do |k| + hash[k] = Marshal::load(read_cache_file(k)) unless expired?(k) end - raise Diskcached::NotFound - rescue - raise Diskcached::NotFound + flush_expired if gc_auto + return hash unless hash.empty? + else + flush_expired if gc_auto + return Marshal::load(read_cache_file(key)) unless expired?(key) end + raise Diskcached::NotFound end # returns path to cache file with 'key' diff --git a/spec/diskcached_spec.rb b/spec/diskcached_spec.rb index 2c1fd2e..48eeec4 100644 --- a/spec/diskcached_spec.rb +++ b/spec/diskcached_spec.rb @@ -1,148 +1,187 @@ require 'spec_helper.rb' +require 'tmpdir' +require 'timecop' +Timecop.safe_mode = true describe Diskcached do + before(:each) do + # tests should be independent, so let's use a new cache for each one + @cache_dir = File.join(Dir.mktmpdir, "cache") + end + describe '#new' do it "should init" do expect { @cache = Diskcached.new }.to_not raise_error end + it "should init with 'store'" do - expect { @cache = Diskcached.new('/tmp/rspec/cache') }.to_not raise_error - @cache.store.should eq '/tmp/rspec/cache' + expect { @cache = Diskcached.new(@cache_dir) }.to_not raise_error + @cache.store.should eq @cache_dir end + it "should init with 'store' and 'timeout'" do - expect { @cache = Diskcached.new('/tmp/rspec/cache', 10) }.to_not raise_error + expect { @cache = Diskcached.new(@cache_dir, 10) }.to_not raise_error @cache.timeout.should eq 10 end + it "should init with 'store', 'timeout' and 'gc_auto'" do - expect { @cache = Diskcached.new('/tmp/rspec/cache', 10, false) }.to_not raise_error + expect { @cache = Diskcached.new(@cache_dir, 10, false) }.to_not raise_error @cache.gc_auto.should be_false end + it "should set 'gc_time' to nil if 'timeout' is nil" do - expect { @cache = Diskcached.new('/tmp/rspec/cache', nil) }.to_not raise_error + expect { @cache = Diskcached.new(@cache_dir, nil) }.to_not raise_error @cache.gc_time.should be_nil end + it "should set 'gc_last' to nil if 'timeout' is nil" do - expect { @cache = Diskcached.new('/tmp/rspec/cache', nil) }.to_not raise_error + expect { @cache = Diskcached.new(@cache_dir, nil) }.to_not raise_error @cache.gc_last.should be_nil end + it "should set 'gc_auto' to false if 'timeout' is nil" do - expect { @cache = Diskcached.new('/tmp/rspec/cache', nil) }.to_not raise_error + expect { @cache = Diskcached.new(@cache_dir, nil) }.to_not raise_error @cache.gc_auto.should be_false end + it "should create cache dir if it doesn't exist" do - File.directory?('/tmp/rspec/cache').should be_true + File.directory?(@cache_dir).should_not be_true + Diskcached.new(@cache_dir) + File.directory?(@cache_dir).should be_true end end describe "#set", "(alias #add, #replace)" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache") + before(:each) do + @cache = Diskcached.new(@cache_dir) end + it "should create a new cache" do @cache.set('test1', "test string").should be_true end + it "should create a file on disk" do - File.exists?("/tmp/rspec/cache/test1.cache").should be_true + @cache.set('test1', "test string") + File.exists?(File.join(@cache_dir, "test1.cache")).should be_true end end describe "#get", "single" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.5) + before(:each) do + @cache = Diskcached.new(@cache_dir, 10) # 0.5 doesn't work here -- we don't have subsecond resolution... + @cache.set('test1', "test string") end + it "should read cache before expiration" do - @cache.get('test1').should eq "test string" - @cache.get('test1').should be_a_kind_of String + Timecop.freeze(Time.now + 5) do + test1 = @cache.get('test1') + test1.should eq "test string" + test1.should be_a_kind_of String + end end + it "should expire correctly" do - sleep 0.51 - expect { @cache.get('test1') }.to raise_error /Diskcached::NotFound/ + Timecop.freeze(Time.now + 15) do + expect { @cache.get('test1') }.to raise_error /Diskcached::NotFound/ + end end end describe "#get", "multiple" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.5) + before(:each) do + @cache = Diskcached.new(@cache_dir, 10) # 0.5 doesn't work here -- we don't have subsecond resolution... @cache.set('test1', "test string") - @cache.set('test2', "test string") + @cache.set('test2', "test string") end - it "should read multiple caches into a Hash" do + + it "should read multiple caches into a Hash" do @cache.get(['test1', 'test2']).should be_a_kind_of Hash @cache.get(['test1', 'test2']).keys.count.should eq 2 @cache.get(['test1', 'test2'])['test1'].should eq "test string" end + it "should expire correctly" do - sleep 0.51 - expect { @cache.get(['test1', 'test2']) }.to raise_error /Diskcached::NotFound/ + Timecop.freeze(Time.now + 15) do + expect { @cache.get(['test1', 'test2']) }.to raise_error /Diskcached::NotFound/ + end end end describe "#cache" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.5) - end - it "should create a new cache" do + before(:each) do + @cache = Diskcached.new(@cache_dir, 10) + @cache.cache('test1') do "test string" end.should eq "test string" - File.exists?("/tmp/rspec/cache/test1.cache").should be_true end - it "should create a file on disk" do - File.exists?("/tmp/rspec/cache/test1.cache").should be_true + + it "should create a new cache" do + File.exist?(File.join(@cache_dir, "test1.cache")).should be_true end + it "should read cache before expiration" do @cache.cache('test1').should eq "test string" @cache.cache('test1').should be_a_kind_of String end + it "should expire correctly" do - sleep 0.51 - @cache.cache('test1') do - "new test string" - end.should eq "new test string" + Timecop.freeze(Time.now + 15) do + expect { @cache.get('test1') }.to raise_error /Diskcached::NotFound/ + end end + it "should return nil if no block is passed and cache is expired" do - sleep 0.51 - @cache.cache('test1').should be_nil + Timecop.freeze(Time.now + 15) do + @cache.cache('test1').should be_nil + end end end describe "#expired?" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.1) + before(:each) do + @cache = Diskcached.new(@cache_dir, 10) @cache.cache('test2') { "cache test2" } end + it "should be false" do @cache.expired?('test2').should be_false end + it "should be true" do - sleep 0.11 - @cache.expired?('test2').should be_true + Timecop.freeze(Time.now + 15) do + @cache.expired?('test2').should be_true + end end end describe "#delete" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache") + before(:each) do + @cache = Diskcached.new(@cache_dir) @cache.cache('test3') { "cache test3" } end + it "should expire cache" do @cache.expired?('test3').should be_false expect { @cache.delete('test3') }.to_not raise_error @cache.expired?('test3').should be_true end + it "should remove cache file" do - File.exists?("/tmp/rspec/cache/test3.cache").should be_false + expect { @cache.delete('test3') }.to_not raise_error + File.exists?(File.join(@cache_dir, "test3.cache")).should be_false end end describe "#flush" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache") + before(:each) do + @cache = Diskcached.new(@cache_dir) @cache.cache('test4') { "cache test4" } @cache.cache('test5') { "cache test5" } @cache.cache('test6') { "cache test6" } end + it "should expire all caches" do @cache.expired?('test4').should be_false @cache.expired?('test5').should be_false @@ -152,75 +191,87 @@ @cache.expired?('test5').should be_true @cache.expired?('test6').should be_true end + it "should remove all cache files" do - Dir['/tmp/rspec/cache/*.cache'].should be_empty + expect { @cache.flush }.to_not raise_error + Dir[File.join(@cache_dir, '*.cache')].should be_empty end end describe "#flush_expired" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.5) + before(:each) do + @cache = Diskcached.new(@cache_dir, 10) @cache.cache('flush1') { "cache flush" } end + it "should not flush caches that aren't expired" do @cache.expired?('flush1').should be_false expect { @cache.flush_expired }.to_not raise_error @cache.expired?('flush1').should be_false end + it "should not flush caches if caches recently flushed" do - sleep 0.5 - @cache.expired?('flush1').should be_true - @cache.instance_variable_set(:@gc_last, Time.now) - expect { @cache.flush_expired }.to_not raise_error - File.exists?('/tmp/rspec/cache/flush1.cache').should be_true - end + Timecop.freeze(Time.now + 15) do + @cache.expired?('flush1').should be_true + @cache.instance_variable_set(:@gc_last, Time.now) + expect { @cache.flush_expired }.to_not raise_error + File.exists?(File.join(@cache_dir, 'flush1.cache')).should be_true + end + end + it "should flush caches are are expired" do - sleep 0.5 - expect { @cache.flush_expired }.to_not raise_error - @cache.expired?('flush1').should be_true - File.exists?('/tmp/rspec/cache/flush1.cache').should be_false + Timecop.freeze(Time.now + 15) do + expect { @cache.flush_expired }.to_not raise_error + @cache.expired?('flush1').should be_true + File.exists?(File.join(@cache_dir, 'flush1.cache')).should be_false + end end end describe "#flush_expired!" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.5) + before(:each) do + @cache = Diskcached.new(@cache_dir, 10) @cache.cache('flush1') { "cache flush" } end + it "should not flush caches that aren't expired" do @cache.expired?('flush1').should be_false expect { @cache.flush_expired! }.to_not raise_error @cache.expired?('flush1').should be_false end + it "should flush caches even when recently flushed" do - sleep 0.5 - @cache.expired?('flush1').should be_true - @cache.instance_variable_set(:@gc_last, Time.now) - expect { @cache.flush_expired! }.to_not raise_error - File.exists?('/tmp/rspec/cache/flush1.cache').should be_false + Timecop.freeze(Time.now + 15) do + @cache.expired?('flush1').should be_true + @cache.instance_variable_set(:@gc_last, Time.now) + expect { @cache.flush_expired! }.to_not raise_error + File.exists?(File.join(@cache_dir, "flush1.cache")).should be_false + end end end describe "#cache_file" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache") + before(:each) do + @cache = Diskcached.new(@cache_dir) end + it "should build cache path" do - @cache.cache_file("test7").should eq "/tmp/rspec/cache/test7.cache" + @cache.cache_file("test7").should eq File.join(@cache_dir, "test7.cache") end end describe "automatic garbage collection ON" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.5) + before(:each) do + @cache = Diskcached.new(@cache_dir, 1) @cache.cache('test8') { "cache test8" } @cache.cache('test9') { "cache test9" } @cache.cache('test10') { "cache test10" } end + it "should clean up expired caches" do - sleep 0.51 + sleep 1 expect { @cache.cache('test10') { "cache test10" } }.to_not raise_error - sleep 0.1 + sleep 1 File.exists?(@cache.cache_file('test8')).should be_false File.exists?(@cache.cache_file('test9')).should be_false File.exists?(@cache.cache_file('test10')).should be_true @@ -228,16 +279,17 @@ end describe "automatic garbage collection OFF" do - before(:all) do - @cache = Diskcached.new("/tmp/rspec/cache", 0.5, false) + before(:each) do + @cache = Diskcached.new(@cache_dir, 1, false) @cache.cache('test8') { "cache test8" } @cache.cache('test9') { "cache test9" } @cache.cache('test10') { "cache test10" } end + it "should not clean up expired caches" do - sleep 0.51 + sleep 1 expect { @cache.cache('test10') { "cache test10" } }.to_not raise_error - sleep 0.1 + sleep 1 File.exists?(@cache.cache_file('test8')).should be_true File.exists?(@cache.cache_file('test9')).should be_true File.exists?(@cache.cache_file('test10')).should be_true @@ -247,8 +299,9 @@ end describe Diskcached, "advanced test cases" do - before(:all) do - @cache = Diskcached.new('/tmp/rspec/cache') + before(:each) do + # tests should be independent, so let's use a new cache for each one + @cache = Diskcached.new(File.join(Dir.mktmpdir, "cache")) @testo = TestObject.new end @@ -301,6 +354,7 @@ @cache.get('object1').should be_a_kind_of TestSubObject @cache.get('object1').sub_foo.should eq "foo" end + it "should cache modified objects" do @cache.delete('object') @cache.cache('object') do @@ -311,6 +365,7 @@ @cache.cache('object').should be_a_kind_of TestSubObject @cache.cache('object').sub_bar.should eq 'bar' end + it "should cache complex objects" do # might be redundant, but tests more complexity @cache.delete('object') @@ -320,5 +375,4 @@ @cache.cache('object').hash.should be_a_kind_of Hash @cache.cache('object').obj.should be_a_kind_of TestSubObject end - end