require 'test/unit' $TESTING = true $TESTING_CM = true RAILS_ENV = 'production' module Rails; end module Rails::VERSION MAJOR = 1 MINOR = 1 TINY = 2 end module ActiveRecord; end class ActiveRecord::Base @count = 1000 attr_accessor :id, :attributes def self.abstract_class=(arg) @abstract_class = arg end def self.abstract_class? return !!@abstract_class end ## # Need doco, blech def self.find(*args) args.flatten! case args.first when :first then return find(:all, *args).first when :all then return find_by_sql("SELECT * FROM #{table_name} WHERE (#{table_name}.#{primary_key} = '#{args.last}') LIMIT 1") else case args.length when 1 then return find(:first, *args) if Fixnum === args.first raise "Dunno what to do" when 2 then return find(args.first) if Hash === args.last return new when 3 then return [new, new, new] else raise "Dunno what to do" end end end def self.find_by_sql(query) return [new] end def self.next_id @count += 1 return @count end def self.table_name name.downcase end def self.primary_key 'id' end def initialize @id = ActiveRecord::Base.next_id @attributes = { :data => 'data' } end def ==(other) self.class == other.class and id == other.id and attributes_before_type_cast == other.attributes_before_type_cast end def attributes_before_type_cast { :data => @attributes[:data] } end def destroy end def reload @attributes[:data].succ! @attributes[:extra] = nil end def update end end require 'cached_model' class Concrete < CachedModel; end class STILameness < Concrete; end module Cache @cache = {} @ttl = {} class << self; attr_accessor :cache, :ttl; end def self.delete(key) @cache.delete key @ttl.delete key return nil end def self.get(key) @cache[key] end def self.put(key, value, ttl) value = Marshal.load Marshal.dump(value) @cache[key] = value @ttl[key] = ttl end end class << CachedModel attr_writer :cache_local end class TestCachedModel < Test::Unit::TestCase DEFAULT_USE_LOCAL_CACHE = CachedModel.use_local_cache? DEFAULT_USE_MEMCACHE = CachedModel.use_memcache? def setup @model = Concrete.new Cache.cache = {} Cache.ttl = {} CachedModel.cache_local = {} CachedModel.use_local_cache = DEFAULT_USE_LOCAL_CACHE CachedModel.use_memcache = DEFAULT_USE_MEMCACHE end def teardown CachedModel.use_local_cache = DEFAULT_USE_LOCAL_CACHE CachedModel.use_memcache = DEFAULT_USE_MEMCACHE end def test_class_abstract_class_eh assert_equal true, CachedModel.abstract_class?, 'CachedModel is abstract' assert_equal false, Concrete.abstract_class?, 'Concrete is not abstract' end def test_class_cache_delete CachedModel.use_local_cache = true CachedModel.use_memcache = true util_set CachedModel.cache_delete @model.class, @model.id assert_equal true, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? end def test_class_cache_delete_without_local_cache CachedModel.use_local_cache = false CachedModel.use_memcache = true util_set CachedModel.cache_delete @model.class, @model.id assert_equal false, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? end def test_class_cache_delete_without_memcache CachedModel.use_local_cache = true CachedModel.use_memcache = false util_set CachedModel.cache_delete @model.class, @model.id assert_equal true, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? end def test_class_cache_reset CachedModel.use_local_cache = true CachedModel.use_memcache = true util_set CachedModel.cache_reset assert_equal true, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? end def test_class_cache_reset_without_local_cache CachedModel.use_local_cache = false CachedModel.use_memcache = true util_set CachedModel.cache_reset assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? end def test_class_cache_reset_without_memcache CachedModel.use_local_cache = true CachedModel.use_memcache = false util_set CachedModel.cache_reset assert_equal true, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? end def test_class_find_complex CachedModel.use_local_cache = true CachedModel.use_memcache = true record = Concrete.find(1, :order => 'lock_version') assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_complex_without_local_cache CachedModel.use_local_cache = false CachedModel.use_memcache = true record = Concrete.find(1, :order => 'lock_version') assert_equal @model.id + 1, record.id assert_equal nil, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_complex_without_memcache CachedModel.use_local_cache = true CachedModel.use_memcache = false record = Concrete.find(1, :order => 'lock_version') assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal nil, Cache.cache[record.cache_key_memcache] end def test_class_find_in_local_cache CachedModel.use_local_cache = true util_set record = Concrete.find(1, :order => 'lock_version') assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] end def test_class_find_in_memcache CachedModel.use_local_cache = true CachedModel.use_memcache = true util_set CachedModel.cache_reset record = Concrete.find @model.id assert_equal @model, record assert_equal record, CachedModel.cache_local[record.cache_key_local] end def test_class_find_multiple CachedModel.use_local_cache = true ids = [@model.id + 1, @model.id + 2, @model.id + 3] records = Concrete.find(*ids) assert_equal ids, records.map { |r| r.id } assert_equal ids.map { |i| "#{@model.class}:#{i}" }.sort, CachedModel.cache_local.keys.sort assert_equal ids.map { |i| "active_record:#{@model.class}:#{i}" }.sort, Cache.cache.keys.sort end def test_class_find_not_cached CachedModel.use_local_cache = true record = Concrete.find @model.id + 1 assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_by_sql CachedModel.use_local_cache = true q = "SELECT * FROM concrete WHERE (concrete.id = #{@model.id + 1}) LIMIT 1" record = Concrete.find_by_sql(q).first assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_by_sql_extra_space CachedModel.use_local_cache = true q = "SELECT * FROM concrete WHERE (concrete.id = #{@model.id + 1}) LIMIT 1" record = Concrete.find_by_sql(q).first assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_ttl assert_equal 900, CachedModel.ttl CachedModel.ttl = 800 assert_equal 800, CachedModel.ttl end def test_class_use_local_cache_eh CachedModel.use_local_cache = true assert_equal true, CachedModel.use_local_cache? CachedModel.use_local_cache = false assert_equal false, CachedModel.use_local_cache? end def test_class_use_memcache_eh CachedModel.use_memcache = true assert_equal true, CachedModel.use_memcache? CachedModel.use_memcache = false assert_equal false, CachedModel.use_memcache? end def test_cache_delete CachedModel.use_local_cache = true util_set @model.cache_delete assert_equal true, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? end def test_cache_key_local assert_equal "#{@model.class}:#{@model.id}", @model.cache_key_local end def test_cache_key_memcache assert_equal "active_record:#{@model.class}:#{@model.id}", @model.cache_key_memcache end def test_cache_local assert_same CachedModel.cache_local, @model.cache_local end def test_cache_store CachedModel.use_local_cache = true CachedModel.use_memcache = true @model.cache_store assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? assert_equal @model, CachedModel.cache_local[@model.cache_key_local] assert_equal @model, Cache.cache[@model.cache_key_memcache] assert_equal CachedModel.ttl, Cache.ttl[@model.cache_key_memcache] end def test_cache_store_without_local_cache CachedModel.use_local_cache = false CachedModel.use_memcache = true @model.cache_store assert_equal true, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? assert_equal nil, CachedModel.cache_local[@model.cache_key_local] assert_equal @model, Cache.cache[@model.cache_key_memcache] assert_equal CachedModel.ttl, Cache.ttl[@model.cache_key_memcache] end def test_cache_store_without_memcache CachedModel.use_local_cache = true CachedModel.use_memcache = false @model.cache_store assert_equal false, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? assert_equal @model, CachedModel.cache_local[@model.cache_key_local] assert_equal nil, Cache.cache[@model.cache_key_memcache] end def test_cache_store_with_attributes CachedModel.use_local_cache = true @model.attributes[:extra] = 'extra' @model.cache_store assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? local_model = CachedModel.cache_local[@model.cache_key_local] mem_model = Cache.cache[@model.cache_key_memcache] assert_equal @model, local_model assert_equal @model, mem_model assert_equal CachedModel.ttl, Cache.ttl[@model.cache_key_memcache] expected = {:data => 'data'} assert_equal expected, local_model.attributes assert_equal expected, mem_model.attributes end def test_destroy CachedModel.use_local_cache = true util_set @model.destroy assert_equal true, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? end def test_reload util_set @model.reload assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? assert_equal 'datb', CachedModel.cache_local[@model.cache_key_local].attributes[:data] assert_equal 'datb', Cache.cache[@model.cache_key_memcache].attributes[:data] end def test_update util_set @model.attributes[:data] = 'atad' @model.update assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? assert_equal 'atad', CachedModel.cache_local[@model.cache_key_local].attributes[:data] assert_equal 'atad', Cache.cache[@model.cache_key_memcache].attributes[:data] end def util_set(klass = @model.class, id = @model.id, data = @model) key = "#{klass}:#{id}" CachedModel.cache_local[key] = data Cache.cache["active_record:#{key}"] = data end end