diff options
Diffstat (limited to 'activesupport/test/cache')
20 files changed, 1613 insertions, 0 deletions
diff --git a/activesupport/test/cache/behaviors.rb b/activesupport/test/cache/behaviors.rb new file mode 100644 index 0000000000..cb08a10bba --- /dev/null +++ b/activesupport/test/cache/behaviors.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require_relative "behaviors/autoloading_cache_behavior" +require_relative "behaviors/cache_delete_matched_behavior" +require_relative "behaviors/cache_increment_decrement_behavior" +require_relative "behaviors/cache_store_behavior" +require_relative "behaviors/cache_store_version_behavior" +require_relative "behaviors/encoded_key_cache_behavior" +require_relative "behaviors/local_cache_behavior" diff --git a/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb b/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb new file mode 100644 index 0000000000..b340eb6c48 --- /dev/null +++ b/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "dependencies_test_helpers" + +module AutoloadingCacheBehavior + include DependenciesTestHelpers + + def test_simple_autoloading + with_autoloading_fixtures do + @cache.write("foo", EM.new) + end + + remove_constants(:EM) + ActiveSupport::Dependencies.clear + + with_autoloading_fixtures do + assert_kind_of EM, @cache.read("foo") + end + + remove_constants(:EM) + ActiveSupport::Dependencies.clear + end + + def test_two_classes_autoloading + with_autoloading_fixtures do + @cache.write("foo", [EM.new, ClassFolder.new]) + end + + remove_constants(:EM, :ClassFolder) + ActiveSupport::Dependencies.clear + + with_autoloading_fixtures do + loaded = @cache.read("foo") + assert_kind_of Array, loaded + assert_equal 2, loaded.size + assert_kind_of EM, loaded[0] + assert_kind_of ClassFolder, loaded[1] + end + + remove_constants(:EM, :ClassFolder) + ActiveSupport::Dependencies.clear + end +end diff --git a/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb b/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb new file mode 100644 index 0000000000..6f59ce48d2 --- /dev/null +++ b/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module CacheDeleteMatchedBehavior + def test_delete_matched + @cache.write("foo", "bar") + @cache.write("fu", "baz") + @cache.write("foo/bar", "baz") + @cache.write("fu/baz", "bar") + @cache.delete_matched(/oo/) + assert !@cache.exist?("foo") + assert @cache.exist?("fu") + assert !@cache.exist?("foo/bar") + assert @cache.exist?("fu/baz") + end +end diff --git a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb new file mode 100644 index 0000000000..16b7abc679 --- /dev/null +++ b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module CacheIncrementDecrementBehavior + def test_increment + @cache.write("foo", 1, raw: true) + assert_equal 1, @cache.read("foo").to_i + assert_equal 2, @cache.increment("foo") + assert_equal 2, @cache.read("foo").to_i + assert_equal 3, @cache.increment("foo") + assert_equal 3, @cache.read("foo").to_i + + missing = @cache.increment("bar") + assert(missing.nil? || missing == 1) + end + + def test_decrement + @cache.write("foo", 3, raw: true) + assert_equal 3, @cache.read("foo").to_i + assert_equal 2, @cache.decrement("foo") + assert_equal 2, @cache.read("foo").to_i + assert_equal 1, @cache.decrement("foo") + assert_equal 1, @cache.read("foo").to_i + + missing = @cache.decrement("bar") + assert(missing.nil? || missing == -1) + end +end diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb new file mode 100644 index 0000000000..73a9b2a71c --- /dev/null +++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb @@ -0,0 +1,341 @@ +# frozen_string_literal: true + +# Tests the base functionality that should be identical across all cache stores. +module CacheStoreBehavior + def test_should_read_and_write_strings + assert @cache.write("foo", "bar") + assert_equal "bar", @cache.read("foo") + end + + def test_should_overwrite + @cache.write("foo", "bar") + @cache.write("foo", "baz") + assert_equal "baz", @cache.read("foo") + end + + def test_fetch_without_cache_miss + @cache.write("foo", "bar") + assert_not_called(@cache, :write) do + assert_equal "bar", @cache.fetch("foo") { "baz" } + end + end + + def test_fetch_with_cache_miss + assert_called_with(@cache, :write, ["foo", "baz", @cache.options]) do + assert_equal "baz", @cache.fetch("foo") { "baz" } + end + end + + def test_fetch_with_cache_miss_passes_key_to_block + cache_miss = false + assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length } + assert cache_miss + + cache_miss = false + assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length } + assert !cache_miss + end + + def test_fetch_with_forced_cache_miss + @cache.write("foo", "bar") + assert_not_called(@cache, :read) do + assert_called_with(@cache, :write, ["foo", "bar", @cache.options.merge(force: true)]) do + @cache.fetch("foo", force: true) { "bar" } + end + end + end + + def test_fetch_with_cached_nil + @cache.write("foo", nil) + assert_not_called(@cache, :write) do + assert_nil @cache.fetch("foo") { "baz" } + end + end + + def test_fetch_with_forced_cache_miss_with_block + @cache.write("foo", "bar") + assert_equal "foo_bar", @cache.fetch("foo", force: true) { "foo_bar" } + end + + def test_fetch_with_forced_cache_miss_without_block + @cache.write("foo", "bar") + assert_raises(ArgumentError) do + @cache.fetch("foo", force: true) + end + + assert_equal "bar", @cache.read("foo") + end + + def test_should_read_and_write_hash + assert @cache.write("foo", a: "b") + assert_equal({ a: "b" }, @cache.read("foo")) + end + + def test_should_read_and_write_integer + assert @cache.write("foo", 1) + assert_equal 1, @cache.read("foo") + end + + def test_should_read_and_write_nil + assert @cache.write("foo", nil) + assert_nil @cache.read("foo") + end + + def test_should_read_and_write_false + assert @cache.write("foo", false) + assert_equal false, @cache.read("foo") + end + + def test_read_multi + @cache.write("foo", "bar") + @cache.write("fu", "baz") + @cache.write("fud", "biz") + assert_equal({ "foo" => "bar", "fu" => "baz" }, @cache.read_multi("foo", "fu")) + end + + def test_read_multi_with_expires + time = Time.now + @cache.write("foo", "bar", expires_in: 10) + @cache.write("fu", "baz") + @cache.write("fud", "biz") + Time.stub(:now, time + 11) do + assert_equal({ "fu" => "baz" }, @cache.read_multi("foo", "fu")) + end + end + + def test_fetch_multi + @cache.write("foo", "bar") + @cache.write("fud", "biz") + + values = @cache.fetch_multi("foo", "fu", "fud") { |value| value * 2 } + + assert_equal({ "foo" => "bar", "fu" => "fufu", "fud" => "biz" }, values) + assert_equal("fufu", @cache.read("fu")) + end + + def test_multi_with_objects + cache_struct = Struct.new(:cache_key, :title) + foo = cache_struct.new("foo", "FOO!") + bar = cache_struct.new("bar") + + @cache.write("bar", "BAM!") + + values = @cache.fetch_multi(foo, bar) { |object| object.title } + + assert_equal({ foo => "FOO!", bar => "BAM!" }, values) + end + + def test_fetch_multi_without_block + assert_raises(ArgumentError) do + @cache.fetch_multi("foo") + end + end + + def test_read_and_write_compressed_small_data + @cache.write("foo", "bar", compress: true) + assert_equal "bar", @cache.read("foo") + end + + def test_read_and_write_compressed_large_data + @cache.write("foo", "bar", compress: true, compress_threshold: 2) + assert_equal "bar", @cache.read("foo") + end + + def test_read_and_write_compressed_nil + @cache.write("foo", nil, compress: true) + assert_nil @cache.read("foo") + end + + def test_read_and_write_uncompressed_small_data + @cache.write("foo", "bar", compress: false) + assert_equal "bar", @cache.read("foo") + end + + def test_read_and_write_uncompressed_nil + @cache.write("foo", nil, compress: false) + assert_nil @cache.read("foo") + end + + def test_cache_key + obj = Object.new + def obj.cache_key + :foo + end + @cache.write(obj, "bar") + assert_equal "bar", @cache.read("foo") + end + + def test_param_as_cache_key + obj = Object.new + def obj.to_param + "foo" + end + @cache.write(obj, "bar") + assert_equal "bar", @cache.read("foo") + end + + def test_array_as_cache_key + @cache.write([:fu, "foo"], "bar") + assert_equal "bar", @cache.read("fu/foo") + end + + def test_hash_as_cache_key + @cache.write({ foo: 1, fu: 2 }, "bar") + assert_equal "bar", @cache.read("foo=1/fu=2") + end + + def test_keys_are_case_sensitive + @cache.write("foo", "bar") + assert_nil @cache.read("FOO") + end + + def test_exist + @cache.write("foo", "bar") + assert_equal true, @cache.exist?("foo") + assert_equal false, @cache.exist?("bar") + end + + def test_nil_exist + @cache.write("foo", nil) + assert @cache.exist?("foo") + end + + def test_delete + @cache.write("foo", "bar") + assert @cache.exist?("foo") + assert @cache.delete("foo") + assert !@cache.exist?("foo") + end + + def test_original_store_objects_should_not_be_immutable + bar = "bar".dup + @cache.write("foo", bar) + assert_nothing_raised { bar.gsub!(/.*/, "baz") } + end + + def test_expires_in + time = Time.local(2008, 4, 24) + + Time.stub(:now, time) do + @cache.write("foo", "bar") + assert_equal "bar", @cache.read("foo") + end + + Time.stub(:now, time + 30) do + assert_equal "bar", @cache.read("foo") + end + + Time.stub(:now, time + 61) do + assert_nil @cache.read("foo") + end + end + + def test_race_condition_protection_skipped_if_not_defined + @cache.write("foo", "bar") + time = @cache.send(:read_entry, @cache.send(:normalize_key, "foo", {}), {}).expires_at + + Time.stub(:now, Time.at(time)) do + result = @cache.fetch("foo") do + assert_nil @cache.read("foo") + "baz" + end + assert_equal "baz", result + end + end + + def test_race_condition_protection_is_limited + time = Time.now + @cache.write("foo", "bar", expires_in: 60) + Time.stub(:now, time + 71) do + result = @cache.fetch("foo", race_condition_ttl: 10) do + assert_nil @cache.read("foo") + "baz" + end + assert_equal "baz", result + end + end + + def test_race_condition_protection_is_safe + time = Time.now + @cache.write("foo", "bar", expires_in: 60) + Time.stub(:now, time + 61) do + begin + @cache.fetch("foo", race_condition_ttl: 10) do + assert_equal "bar", @cache.read("foo") + raise ArgumentError.new + end + rescue ArgumentError + end + assert_equal "bar", @cache.read("foo") + end + Time.stub(:now, time + 91) do + assert_nil @cache.read("foo") + end + end + + def test_race_condition_protection + time = Time.now + @cache.write("foo", "bar", expires_in: 60) + Time.stub(:now, time + 61) do + result = @cache.fetch("foo", race_condition_ttl: 10) do + assert_equal "bar", @cache.read("foo") + "baz" + end + assert_equal "baz", result + end + end + + def test_crazy_key_characters + crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-" + assert @cache.write(crazy_key, "1", raw: true) + assert_equal "1", @cache.read(crazy_key) + assert_equal "1", @cache.fetch(crazy_key) + assert @cache.delete(crazy_key) + assert_equal "2", @cache.fetch(crazy_key, raw: true) { "2" } + assert_equal 3, @cache.increment(crazy_key) + assert_equal 2, @cache.decrement(crazy_key) + end + + def test_really_long_keys + key = "".dup + 900.times { key << "x" } + assert @cache.write(key, "bar") + assert_equal "bar", @cache.read(key) + assert_equal "bar", @cache.fetch(key) + assert_nil @cache.read("#{key}x") + assert_equal({ key => "bar" }, @cache.read_multi(key)) + assert @cache.delete(key) + end + + def test_cache_hit_instrumentation + key = "test_key" + @events = [] + ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args| + @events << ActiveSupport::Notifications::Event.new(*args) + end + assert @cache.write(key, "1", raw: true) + assert @cache.fetch(key) {} + assert_equal 1, @events.length + assert_equal "cache_read.active_support", @events[0].name + assert_equal :fetch, @events[0].payload[:super_operation] + assert @events[0].payload[:hit] + ensure + ActiveSupport::Notifications.unsubscribe "cache_read.active_support" + end + + def test_cache_miss_instrumentation + @events = [] + ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args| + @events << ActiveSupport::Notifications::Event.new(*args) + end + assert_not @cache.fetch("bad_key") {} + assert_equal 3, @events.length + assert_equal "cache_read.active_support", @events[0].name + assert_equal "cache_generate.active_support", @events[1].name + assert_equal "cache_write.active_support", @events[2].name + assert_equal :fetch, @events[0].payload[:super_operation] + assert_not @events[0].payload[:hit] + ensure + ActiveSupport::Notifications.unsubscribe "cache_read.active_support" + end +end diff --git a/activesupport/test/cache/behaviors/cache_store_version_behavior.rb b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb new file mode 100644 index 0000000000..c2e4d046af --- /dev/null +++ b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module CacheStoreVersionBehavior + ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version) + + def test_fetch_with_right_version_should_hit + @cache.fetch("foo", version: 1) { "bar" } + assert_equal "bar", @cache.read("foo", version: 1) + end + + def test_fetch_with_wrong_version_should_miss + @cache.fetch("foo", version: 1) { "bar" } + assert_nil @cache.read("foo", version: 2) + end + + def test_read_with_right_version_should_hit + @cache.write("foo", "bar", version: 1) + assert_equal "bar", @cache.read("foo", version: 1) + end + + def test_read_with_wrong_version_should_miss + @cache.write("foo", "bar", version: 1) + assert_nil @cache.read("foo", version: 2) + end + + def test_exist_with_right_version_should_be_true + @cache.write("foo", "bar", version: 1) + assert @cache.exist?("foo", version: 1) + end + + def test_exist_with_wrong_version_should_be_false + @cache.write("foo", "bar", version: 1) + assert !@cache.exist?("foo", version: 2) + end + + def test_reading_and_writing_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.write(m1v1, "bar") + assert_equal "bar", @cache.read(m1v1) + assert_nil @cache.read(m1v2) + end + + def test_reading_and_writing_with_model_supporting_cache_version_using_nested_key + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.write([ "something", m1v1 ], "bar") + assert_equal "bar", @cache.read([ "something", m1v1 ]) + assert_nil @cache.read([ "something", m1v2 ]) + end + + def test_fetching_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.fetch(m1v1) { "bar" } + assert_equal "bar", @cache.fetch(m1v1) { "bu" } + assert_equal "bu", @cache.fetch(m1v2) { "bu" } + end + + def test_exist_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.write(m1v1, "bar") + assert @cache.exist?(m1v1) + assert_not @cache.fetch(m1v2) + end + + def test_fetch_multi_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m2v1 = ModelWithKeyAndVersion.new("model/2", 1) + m2v2 = ModelWithKeyAndVersion.new("model/2", 2) + + first_fetch_values = @cache.fetch_multi(m1v1, m2v1) { |m| m.cache_key } + second_fetch_values = @cache.fetch_multi(m1v1, m2v2) { |m| m.cache_key + " 2nd" } + + assert_equal({ m1v1 => "model/1", m2v1 => "model/2" }, first_fetch_values) + assert_equal({ m1v1 => "model/1", m2v2 => "model/2 2nd" }, second_fetch_values) + end + + def test_version_is_normalized + @cache.write("foo", "bar", version: 1) + assert_equal "bar", @cache.read("foo", version: "1") + end +end diff --git a/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb b/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb new file mode 100644 index 0000000000..da16142496 --- /dev/null +++ b/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters +# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special +# characters like the umlaut in UTF-8. +module EncodedKeyCacheBehavior + Encoding.list.each do |encoding| + define_method "test_#{encoding.name.underscore}_encoded_values" do + key = "foo".dup.force_encoding(encoding) + assert @cache.write(key, "1", raw: true) + assert_equal "1", @cache.read(key) + assert_equal "1", @cache.fetch(key) + assert @cache.delete(key) + assert_equal "2", @cache.fetch(key, raw: true) { "2" } + assert_equal 3, @cache.increment(key) + assert_equal 2, @cache.decrement(key) + end + end + + def test_common_utf8_values + key = "\xC3\xBCmlaut".dup.force_encoding(Encoding::UTF_8) + assert @cache.write(key, "1", raw: true) + assert_equal "1", @cache.read(key) + assert_equal "1", @cache.fetch(key) + assert @cache.delete(key) + assert_equal "2", @cache.fetch(key, raw: true) { "2" } + assert_equal 3, @cache.increment(key) + assert_equal 2, @cache.decrement(key) + end + + def test_retains_encoding + key = "\xC3\xBCmlaut".dup.force_encoding(Encoding::UTF_8) + assert @cache.write(key, "1", raw: true) + assert_equal Encoding::UTF_8, key.encoding + end +end diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb new file mode 100644 index 0000000000..f7302df4c8 --- /dev/null +++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +module LocalCacheBehavior + def test_local_writes_are_persistent_on_the_remote_cache + retval = @cache.with_local_cache do + @cache.write("foo", "bar") + end + assert retval + assert_equal "bar", @cache.read("foo") + end + + def test_clear_also_clears_local_cache + @cache.with_local_cache do + @cache.write("foo", "bar") + @cache.clear + assert_nil @cache.read("foo") + end + + assert_nil @cache.read("foo") + end + + def test_cleanup_clears_local_cache_but_not_remote_cache + begin + @cache.cleanup + rescue NotImplementedError + skip + end + + @cache.with_local_cache do + @cache.write("foo", "bar") + assert_equal "bar", @cache.read("foo") + + @cache.send(:bypass_local_cache) { @cache.write("foo", "baz") } + assert_equal "bar", @cache.read("foo") + + @cache.cleanup + assert_equal "baz", @cache.read("foo") + end + end + + def test_local_cache_of_write + @cache.with_local_cache do + @cache.write("foo", "bar") + @peek.delete("foo") + assert_equal "bar", @cache.read("foo") + end + end + + def test_local_cache_of_read + @cache.write("foo", "bar") + @cache.with_local_cache do + assert_equal "bar", @cache.read("foo") + end + end + + def test_local_cache_of_read_nil + @cache.with_local_cache do + assert_nil @cache.read("foo") + @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" } + assert_nil @cache.read("foo") + end + end + + def test_local_cache_fetch + @cache.with_local_cache do + @cache.send(:local_cache).write "foo", "bar" + assert_equal "bar", @cache.send(:local_cache).fetch("foo") + end + end + + def test_local_cache_of_write_nil + @cache.with_local_cache do + assert @cache.write("foo", nil) + assert_nil @cache.read("foo") + @peek.write("foo", "bar") + assert_nil @cache.read("foo") + end + end + + def test_local_cache_of_write_with_unless_exist + @cache.with_local_cache do + @cache.write("foo", "bar") + @cache.write("foo", "baz", unless_exist: true) + assert_equal @peek.read("foo"), @cache.read("foo") + end + end + + def test_local_cache_of_delete + @cache.with_local_cache do + @cache.write("foo", "bar") + @cache.delete("foo") + assert_nil @cache.read("foo") + end + end + + def test_local_cache_of_exist + @cache.with_local_cache do + @cache.write("foo", "bar") + @peek.delete("foo") + assert @cache.exist?("foo") + end + end + + def test_local_cache_of_increment + @cache.with_local_cache do + @cache.write("foo", 1, raw: true) + @peek.write("foo", 2, raw: true) + @cache.increment("foo") + assert_equal 3, @cache.read("foo") + end + end + + def test_local_cache_of_decrement + @cache.with_local_cache do + @cache.write("foo", 1, raw: true) + @peek.write("foo", 3, raw: true) + @cache.decrement("foo") + assert_equal 2, @cache.read("foo") + end + end + + def test_middleware + app = lambda { |env| + result = @cache.write("foo", "bar") + assert_equal "bar", @cache.read("foo") # make sure 'foo' was written + assert result + [200, {}, []] + } + app = @cache.middleware.new(app) + app.call({}) + end +end diff --git a/activesupport/test/cache/cache_entry_test.rb b/activesupport/test/cache/cache_entry_test.rb new file mode 100644 index 0000000000..80ff7ad564 --- /dev/null +++ b/activesupport/test/cache/cache_entry_test.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" + +class CacheEntryTest < ActiveSupport::TestCase + def test_expired + entry = ActiveSupport::Cache::Entry.new("value") + assert !entry.expired?, "entry not expired" + entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60) + assert !entry.expired?, "entry not expired" + Time.stub(:now, Time.now + 61) do + assert entry.expired?, "entry is expired" + end + end + + def test_compressed_values + value = "value" * 100 + entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1) + assert_equal value, entry.value + assert(value.bytesize > entry.size, "value is compressed") + end + + def test_compressed_by_default + value = "value" * 100 + entry = ActiveSupport::Cache::Entry.new(value, compress_threshold: 1) + assert_equal value, entry.value + assert(value.bytesize > entry.size, "value is compressed") + end + + def test_uncompressed_values + value = "value" * 100 + entry = ActiveSupport::Cache::Entry.new(value, compress: false) + assert_equal value, entry.value + assert_equal value.bytesize, entry.size + end +end diff --git a/activesupport/test/cache/cache_key_test.rb b/activesupport/test/cache/cache_key_test.rb new file mode 100644 index 0000000000..84e656f504 --- /dev/null +++ b/activesupport/test/cache/cache_key_test.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" + +class CacheKeyTest < ActiveSupport::TestCase + def test_entry_legacy_optional_ivars + legacy = Class.new(ActiveSupport::Cache::Entry) do + def initialize(value, options = {}) + @value = value + @expires_in = nil + @created_at = nil + super + end + end + + entry = legacy.new "foo" + assert_equal "foo", entry.value + end + + def test_expand_cache_key + assert_equal "1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true]) + assert_equal "name/1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true], :name) + end + + def test_expand_cache_key_with_rails_cache_id + with_env("RAILS_CACHE_ID" => "c99") do + assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo) + assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key([:foo]) + assert_equal "c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar]) + assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key(:foo, :nm) + assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key([:foo], :nm) + assert_equal "nm/c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm) + end + end + + def test_expand_cache_key_with_rails_app_version + with_env("RAILS_APP_VERSION" => "rails3") do + assert_equal "rails3/foo", ActiveSupport::Cache.expand_cache_key(:foo) + end + end + + def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version + with_env("RAILS_CACHE_ID" => "c99", "RAILS_APP_VERSION" => "rails3") do + assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo) + end + end + + def test_expand_cache_key_respond_to_cache_key + key = "foo".dup + def key.cache_key + :foo_key + end + assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key(key) + end + + def test_expand_cache_key_array_with_something_that_responds_to_cache_key + key = "foo".dup + def key.cache_key + :foo_key + end + assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key([key]) + end + + def test_expand_cache_key_of_nil + assert_equal "", ActiveSupport::Cache.expand_cache_key(nil) + end + + def test_expand_cache_key_of_false + assert_equal "false", ActiveSupport::Cache.expand_cache_key(false) + end + + def test_expand_cache_key_of_true + assert_equal "true", ActiveSupport::Cache.expand_cache_key(true) + end + + def test_expand_cache_key_of_array_like_object + assert_equal "foo/bar/baz", ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum) + end + + private + + def with_env(kv) + old_values = {} + kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value } + yield + ensure + old_values.each { |key, value| ENV[key] = value } + end +end diff --git a/activesupport/test/cache/cache_store_logger_test.rb b/activesupport/test/cache/cache_store_logger_test.rb new file mode 100644 index 0000000000..1af6893cc9 --- /dev/null +++ b/activesupport/test/cache/cache_store_logger_test.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" + +class CacheStoreLoggerTest < ActiveSupport::TestCase + def setup + @cache = ActiveSupport::Cache.lookup_store(:memory_store) + + @buffer = StringIO.new + @cache.logger = ActiveSupport::Logger.new(@buffer) + end + + def test_logging + @cache.fetch("foo") { "bar" } + assert @buffer.string.present? + end + + def test_log_with_string_namespace + @cache.fetch("foo", namespace: "string_namespace") { "bar" } + assert_match %r{string_namespace:foo}, @buffer.string + end + + def test_log_with_proc_namespace + proc = Proc.new do + "proc_namespace" + end + @cache.fetch("foo", namespace: proc) { "bar" } + assert_match %r{proc_namespace:foo}, @buffer.string + end + + def test_mute_logging + @cache.mute { @cache.fetch("foo") { "bar" } } + assert @buffer.string.blank? + end +end diff --git a/activesupport/test/cache/cache_store_namespace_test.rb b/activesupport/test/cache/cache_store_namespace_test.rb new file mode 100644 index 0000000000..b52a61c500 --- /dev/null +++ b/activesupport/test/cache/cache_store_namespace_test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" + +class CacheStoreNamespaceTest < ActiveSupport::TestCase + def test_static_namespace + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester") + cache.write("foo", "bar") + assert_equal "bar", cache.read("foo") + assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value + end + + def test_proc_namespace + test_val = "tester" + proc = lambda { test_val } + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: proc) + cache.write("foo", "bar") + assert_equal "bar", cache.read("foo") + assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value + end + + def test_delete_matched_key_start + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester") + cache.write("foo", "bar") + cache.write("fu", "baz") + cache.delete_matched(/^fo/) + assert !cache.exist?("foo") + assert cache.exist?("fu") + end + + def test_delete_matched_key + cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "foo") + cache.write("foo", "bar") + cache.write("fu", "baz") + cache.delete_matched(/OO/i) + assert !cache.exist?("foo") + assert cache.exist?("fu") + end +end diff --git a/activesupport/test/cache/cache_store_setting_test.rb b/activesupport/test/cache/cache_store_setting_test.rb new file mode 100644 index 0000000000..368cb39f97 --- /dev/null +++ b/activesupport/test/cache/cache_store_setting_test.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require "dalli" + +class CacheStoreSettingTest < ActiveSupport::TestCase + def test_memory_store_gets_created_if_no_arguments_passed_to_lookup_store_method + store = ActiveSupport::Cache.lookup_store + assert_kind_of(ActiveSupport::Cache::MemoryStore, store) + end + + def test_memory_store + store = ActiveSupport::Cache.lookup_store :memory_store + assert_kind_of(ActiveSupport::Cache::MemoryStore, store) + end + + def test_file_fragment_cache_store + store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory" + assert_kind_of(ActiveSupport::Cache::FileStore, store) + assert_equal "/path/to/cache/directory", store.cache_path + end + + def test_mem_cache_fragment_cache_store + assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost" + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + end + end + + def test_mem_cache_fragment_cache_store_with_given_mem_cache + mem_cache = Dalli::Client.new + assert_not_called(Dalli::Client, :new) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + end + end + + def test_mem_cache_fragment_cache_store_with_not_dalli_client + assert_not_called(Dalli::Client, :new) do + memcache = Object.new + assert_raises(ArgumentError) do + ActiveSupport::Cache.lookup_store :mem_cache_store, memcache + end + end + end + + def test_mem_cache_fragment_cache_store_with_multiple_servers + assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1" + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + end + end + + def test_mem_cache_fragment_cache_store_with_options + assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { timeout: 10 }]) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1", namespace: "foo", timeout: 10 + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + assert_equal "foo", store.options[:namespace] + end + end + + def test_object_assigned_fragment_cache_store + store = ActiveSupport::Cache.lookup_store ActiveSupport::Cache::FileStore.new("/path/to/cache/directory") + assert_kind_of(ActiveSupport::Cache::FileStore, store) + assert_equal "/path/to/cache/directory", store.cache_path + end +end diff --git a/activesupport/test/cache/cache_store_write_multi_test.rb b/activesupport/test/cache/cache_store_write_multi_test.rb new file mode 100644 index 0000000000..5b6fd678c5 --- /dev/null +++ b/activesupport/test/cache/cache_store_write_multi_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" + +class CacheStoreWriteMultiEntriesStoreProviderInterfaceTest < ActiveSupport::TestCase + setup do + @cache = ActiveSupport::Cache.lookup_store(:null_store) + end + + test "fetch_multi uses write_multi_entries store provider interface" do + assert_called_with(@cache, :write_multi_entries) do + @cache.fetch_multi "a", "b", "c" do |key| + key * 2 + end + end + end +end + +class CacheStoreWriteMultiInstrumentationTest < ActiveSupport::TestCase + setup do + @cache = ActiveSupport::Cache.lookup_store(:null_store) + end + + test "instrumentation" do + writes = { "a" => "aa", "b" => "bb" } + + events = with_instrumentation "write_multi" do + @cache.write_multi(writes) + end + + assert_equal %w[ cache_write_multi.active_support ], events.map(&:name) + assert_nil events[0].payload[:super_operation] + assert_equal({ "a" => "aa", "b" => "bb" }, events[0].payload[:key]) + end + + test "instrumentation with fetch_multi as super operation" do + skip "fetch_multi isn't instrumented yet" + + events = with_instrumentation "write_multi" do + @cache.fetch_multi("a", "b") { |key| key * 2 } + end + + assert_equal %w[ cache_write_multi.active_support ], events.map(&:name) + assert_nil events[0].payload[:super_operation] + assert !events[0].payload[:hit] + end + + private + def with_instrumentation(method) + event_name = "cache_#{method}.active_support" + + [].tap do |events| + ActiveSupport::Notifications.subscribe event_name do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + yield + end + ensure + ActiveSupport::Notifications.unsubscribe event_name + end +end diff --git a/activesupport/test/cache/local_cache_middleware_test.rb b/activesupport/test/cache/local_cache_middleware_test.rb new file mode 100644 index 0000000000..e59fae0b4c --- /dev/null +++ b/activesupport/test/cache/local_cache_middleware_test.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" + +module ActiveSupport + module Cache + module Strategy + module LocalCache + class MiddlewareTest < ActiveSupport::TestCase + def test_local_cache_cleared_on_close + key = "super awesome key" + assert_nil LocalCacheRegistry.cache_for key + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" + [200, {}, []] + }) + _, _, body = middleware.call({}) + assert LocalCacheRegistry.cache_for(key), "should still have a cache" + body.each {} + assert LocalCacheRegistry.cache_for(key), "should still have a cache" + body.close + assert_nil LocalCacheRegistry.cache_for(key) + end + + def test_local_cache_cleared_and_response_should_be_present_on_invalid_parameters_error + key = "super awesome key" + assert_nil LocalCacheRegistry.cache_for key + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" + raise Rack::Utils::InvalidParameterError + }) + response = middleware.call({}) + assert response, "response should exist" + assert_nil LocalCacheRegistry.cache_for(key) + end + + def test_local_cache_cleared_on_exception + key = "super awesome key" + assert_nil LocalCacheRegistry.cache_for key + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" + raise + }) + assert_raises(RuntimeError) { middleware.call({}) } + assert_nil LocalCacheRegistry.cache_for(key) + end + + def test_local_cache_cleared_on_throw + key = "super awesome key" + assert_nil LocalCacheRegistry.cache_for key + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" + throw :warden + }) + assert_throws(:warden) { middleware.call({}) } + assert_nil LocalCacheRegistry.cache_for(key) + end + end + end + end + end +end diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb new file mode 100644 index 0000000000..66231b0a82 --- /dev/null +++ b/activesupport/test/cache/stores/file_store_test.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require_relative "../behaviors" +require "pathname" + +class FileStoreTest < ActiveSupport::TestCase + def setup + Dir.mkdir(cache_dir) unless File.exist?(cache_dir) + @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60) + @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60) + @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), expires_in: 60) + + @buffer = StringIO.new + @cache.logger = ActiveSupport::Logger.new(@buffer) + end + + def teardown + FileUtils.rm_r(cache_dir) + rescue Errno::ENOENT + end + + def cache_dir + File.join(Dir.pwd, "tmp_cache") + end + + include CacheStoreBehavior + include CacheStoreVersionBehavior + include LocalCacheBehavior + include CacheDeleteMatchedBehavior + include CacheIncrementDecrementBehavior + include AutoloadingCacheBehavior + + def test_clear + gitkeep = File.join(cache_dir, ".gitkeep") + keep = File.join(cache_dir, ".keep") + FileUtils.touch([gitkeep, keep]) + @cache.clear + assert File.exist?(gitkeep) + assert File.exist?(keep) + end + + def test_clear_without_cache_dir + FileUtils.rm_r(cache_dir) + @cache.clear + end + + def test_long_uri_encoded_keys + @cache.write("%" * 870, 1) + assert_equal 1, @cache.read("%" * 870) + end + + def test_key_transformation + key = @cache.send(:normalize_key, "views/index?id=1", {}) + assert_equal "views/index?id=1", @cache.send(:file_path_key, key) + end + + def test_key_transformation_with_pathname + FileUtils.touch(File.join(cache_dir, "foo")) + key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {}) + assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) + end + + # Test that generated cache keys are short enough to have Tempfile stuff added to them and + # remain valid + def test_filename_max_size + key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}" + path = @cache.send(:normalize_key, key, {}) + Dir::Tmpname.create(path) do |tmpname, n, opts| + assert File.basename(tmpname + ".lock").length <= 255, "Temp filename too long: #{File.basename(tmpname + '.lock').length}" + end + end + + # Because file systems have a maximum filename size, filenames > max size should be split in to directories + # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B + def test_key_transformation_max_filename_size + key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B" + path = @cache.send(:normalize_key, key, {}) + assert path.split("/").all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE } + assert_equal "B", File.basename(path) + end + + # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist + # Ensure delete_matched gracefully handles this case + def test_delete_matched_when_cache_directory_does_not_exist + assert_nothing_raised do + ActiveSupport::Cache::FileStore.new("/test/cache/directory").delete_matched(/does_not_exist/) + end + end + + def test_delete_does_not_delete_empty_parent_dir + sub_cache_dir = File.join(cache_dir, "subdir/") + sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir) + assert_nothing_raised do + assert sub_cache_store.write("foo", "bar") + assert sub_cache_store.delete("foo") + end + assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!" + assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!" + assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty? + end + + def test_log_exception_when_cache_read_fails + File.stub(:exist?, -> { raise StandardError.new("failed") }) do + @cache.send(:read_entry, "winston", {}) + assert @buffer.string.present? + end + end + + def test_cleanup_removes_all_expired_entries + time = Time.now + @cache.write("foo", "bar", expires_in: 10) + @cache.write("baz", "qux") + @cache.write("quux", "corge", expires_in: 20) + Time.stub(:now, time + 15) do + @cache.cleanup + assert_not @cache.exist?("foo") + assert @cache.exist?("baz") + assert @cache.exist?("quux") + assert_equal 2, Dir.glob(File.join(cache_dir, "**")).size + end + end + + def test_write_with_unless_exist + assert_equal true, @cache.write(1, "aaaaaaaaaa") + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) + @cache.write(1, nil) + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) + end +end diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb new file mode 100644 index 0000000000..1b73fb65eb --- /dev/null +++ b/activesupport/test/cache/stores/mem_cache_store_test.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require_relative "../behaviors" +require "dalli" + +class MemCacheStoreTest < ActiveSupport::TestCase + begin + ss = Dalli::Client.new("localhost:11211").stats + raise Dalli::DalliError unless ss["localhost:11211"] + + MEMCACHE_UP = true + rescue Dalli::DalliError + $stderr.puts "Skipping memcached tests. Start memcached and try again." + MEMCACHE_UP = false + end + + def setup + skip "memcache server is not up" unless MEMCACHE_UP + + @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, expires_in: 60) + @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store) + @data = @cache.instance_variable_get(:@data) + @cache.clear + @cache.silence! + @cache.logger = ActiveSupport::Logger.new(File::NULL) + end + + include CacheStoreBehavior + include CacheStoreVersionBehavior + include LocalCacheBehavior + include CacheIncrementDecrementBehavior + include EncodedKeyCacheBehavior + include AutoloadingCacheBehavior + + def test_raw_values + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache.clear + cache.write("foo", 2) + assert_equal "2", cache.read("foo") + end + + def test_raw_values_with_marshal + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache.clear + cache.write("foo", Marshal.dump([])) + assert_equal [], cache.read("foo") + end + + def test_local_cache_raw_values + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache.clear + cache.with_local_cache do + cache.write("foo", 2) + assert_equal "2", cache.read("foo") + end + end + + def test_local_cache_raw_values_with_marshal + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache.clear + cache.with_local_cache do + cache.write("foo", Marshal.dump([])) + assert_equal [], cache.read("foo") + end + end + + def test_read_should_return_a_different_object_id_each_time_it_is_called + @cache.write("foo", "bar") + value = @cache.read("foo") + assert_not_equal value.object_id, @cache.read("foo").object_id + value << "bingo" + assert_not_equal value, @cache.read("foo") + end +end diff --git a/activesupport/test/cache/stores/memory_store_test.rb b/activesupport/test/cache/stores/memory_store_test.rb new file mode 100644 index 0000000000..3981f05331 --- /dev/null +++ b/activesupport/test/cache/stores/memory_store_test.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require_relative "../behaviors" + +class MemoryStoreTest < ActiveSupport::TestCase + def setup + @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa")) + @cache = ActiveSupport::Cache.lookup_store(:memory_store, expires_in: 60, size: @record_size * 10 + 1) + end + + include CacheStoreBehavior + include CacheStoreVersionBehavior + include CacheDeleteMatchedBehavior + include CacheIncrementDecrementBehavior + + def test_prune_size + @cache.write(1, "aaaaaaaaaa") && sleep(0.001) + @cache.write(2, "bbbbbbbbbb") && sleep(0.001) + @cache.write(3, "cccccccccc") && sleep(0.001) + @cache.write(4, "dddddddddd") && sleep(0.001) + @cache.write(5, "eeeeeeeeee") && sleep(0.001) + @cache.read(2) && sleep(0.001) + @cache.read(4) + @cache.prune(@record_size * 3) + assert @cache.exist?(5) + assert @cache.exist?(4) + assert !@cache.exist?(3), "no entry" + assert @cache.exist?(2) + assert !@cache.exist?(1), "no entry" + end + + def test_prune_size_on_write + @cache.write(1, "aaaaaaaaaa") && sleep(0.001) + @cache.write(2, "bbbbbbbbbb") && sleep(0.001) + @cache.write(3, "cccccccccc") && sleep(0.001) + @cache.write(4, "dddddddddd") && sleep(0.001) + @cache.write(5, "eeeeeeeeee") && sleep(0.001) + @cache.write(6, "ffffffffff") && sleep(0.001) + @cache.write(7, "gggggggggg") && sleep(0.001) + @cache.write(8, "hhhhhhhhhh") && sleep(0.001) + @cache.write(9, "iiiiiiiiii") && sleep(0.001) + @cache.write(10, "kkkkkkkkkk") && sleep(0.001) + @cache.read(2) && sleep(0.001) + @cache.read(4) && sleep(0.001) + @cache.write(11, "llllllllll") + assert @cache.exist?(11) + assert @cache.exist?(10) + assert @cache.exist?(9) + assert @cache.exist?(8) + assert @cache.exist?(7) + assert !@cache.exist?(6), "no entry" + assert !@cache.exist?(5), "no entry" + assert @cache.exist?(4) + assert !@cache.exist?(3), "no entry" + assert @cache.exist?(2) + assert !@cache.exist?(1), "no entry" + end + + def test_prune_size_on_write_based_on_key_length + @cache.write(1, "aaaaaaaaaa") && sleep(0.001) + @cache.write(2, "bbbbbbbbbb") && sleep(0.001) + @cache.write(3, "cccccccccc") && sleep(0.001) + @cache.write(4, "dddddddddd") && sleep(0.001) + @cache.write(5, "eeeeeeeeee") && sleep(0.001) + @cache.write(6, "ffffffffff") && sleep(0.001) + @cache.write(7, "gggggggggg") && sleep(0.001) + @cache.write(8, "hhhhhhhhhh") && sleep(0.001) + @cache.write(9, "iiiiiiiiii") && sleep(0.001) + long_key = "*" * 2 * @record_size + @cache.write(long_key, "llllllllll") + assert @cache.exist?(long_key) + assert @cache.exist?(9) + assert @cache.exist?(8) + assert @cache.exist?(7) + assert @cache.exist?(6) + assert !@cache.exist?(5), "no entry" + assert !@cache.exist?(4), "no entry" + assert !@cache.exist?(3), "no entry" + assert !@cache.exist?(2), "no entry" + assert !@cache.exist?(1), "no entry" + end + + def test_pruning_is_capped_at_a_max_time + def @cache.delete_entry(*args) + sleep(0.01) + super + end + @cache.write(1, "aaaaaaaaaa") && sleep(0.001) + @cache.write(2, "bbbbbbbbbb") && sleep(0.001) + @cache.write(3, "cccccccccc") && sleep(0.001) + @cache.write(4, "dddddddddd") && sleep(0.001) + @cache.write(5, "eeeeeeeeee") && sleep(0.001) + @cache.prune(30, 0.001) + assert @cache.exist?(5) + assert @cache.exist?(4) + assert @cache.exist?(3) + assert @cache.exist?(2) + assert !@cache.exist?(1) + end + + def test_write_with_unless_exist + assert_equal true, @cache.write(1, "aaaaaaaaaa") + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) + @cache.write(1, nil) + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) + end +end diff --git a/activesupport/test/cache/stores/null_store_test.rb b/activesupport/test/cache/stores/null_store_test.rb new file mode 100644 index 0000000000..a891cbffc8 --- /dev/null +++ b/activesupport/test/cache/stores/null_store_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require_relative "../behaviors" + +class NullStoreTest < ActiveSupport::TestCase + def setup + @cache = ActiveSupport::Cache.lookup_store(:null_store) + end + + def test_clear + @cache.clear + end + + def test_cleanup + @cache.cleanup + end + + def test_write + assert_equal true, @cache.write("name", "value") + end + + def test_read + @cache.write("name", "value") + assert_nil @cache.read("name") + end + + def test_delete + @cache.write("name", "value") + assert_equal false, @cache.delete("name") + end + + def test_increment + @cache.write("name", 1, raw: true) + assert_nil @cache.increment("name") + end + + def test_decrement + @cache.write("name", 1, raw: true) + assert_nil @cache.increment("name") + end + + def test_delete_matched + @cache.write("name", "value") + @cache.delete_matched(/name/) + end + + def test_local_store_strategy + @cache.with_local_cache do + @cache.write("name", "value") + assert_equal "value", @cache.read("name") + @cache.delete("name") + assert_nil @cache.read("name") + @cache.write("name", "value") + end + assert_nil @cache.read("name") + end +end diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb new file mode 100644 index 0000000000..988de9207f --- /dev/null +++ b/activesupport/test/cache/stores/redis_cache_store_test.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require "active_support/cache/redis_cache_store" +require_relative "../behaviors" + +module ActiveSupport::Cache::RedisCacheStoreTests + class LookupTest < ActiveSupport::TestCase + test "may be looked up as :redis_cache_store" do + assert_kind_of ActiveSupport::Cache::RedisCacheStore, + ActiveSupport::Cache.lookup_store(:redis_cache_store) + end + end + + class InitializationTest < ActiveSupport::TestCase + test "omitted URL uses Redis client with default settings" do + assert_called_with Redis, :new, [ + url: nil, + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build + end + end + + test "no URLs uses Redis client with default settings" do + assert_called_with Redis, :new, [ + url: nil, + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: [] + end + end + + test "singular URL uses Redis client" do + assert_called_with Redis, :new, [ + url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: "redis://localhost:6379/0" + end + end + + test "one URL uses Redis client" do + assert_called_with Redis, :new, [ + url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: %w[ redis://localhost:6379/0 ] + end + end + + test "multiple URLs uses Redis::Distributed client" do + assert_called_with Redis, :new, [ + [ url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0 ], + [ url: "redis://localhost:6379/1", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0 ], + ], returns: Redis.new do + @cache = build url: %w[ redis://localhost:6379/0 redis://localhost:6379/1 ] + assert_kind_of ::Redis::Distributed, @cache.redis + end + end + + test "block argument uses yielded client" do + block = -> { :custom_redis_client } + assert_called block, :call do + build redis: block + end + end + + private + def build(**kwargs) + ActiveSupport::Cache::RedisCacheStore.new(**kwargs).tap do |cache| + cache.redis + end + end + end + + class StoreTest < ActiveSupport::TestCase + setup do + @namespace = "namespace" + + @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace, expires_in: 60) + #@cache.logger = Logger.new($stdout) # For test debugging + + # For LocalCacheBehavior tests + @peek = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace) + end + + teardown do + @cache.clear + @cache.redis.disconnect! + end + end + + class RedisCacheStoreCommonBehaviorTest < StoreTest + include CacheStoreBehavior + include CacheStoreVersionBehavior + include LocalCacheBehavior + include CacheIncrementDecrementBehavior + include AutoloadingCacheBehavior + end + + # Separate test class so we can omit the namespace which causes expected, + # appropriate complaints about incompatible string encodings. + class KeyEncodingSafetyTest < StoreTest + include EncodedKeyCacheBehavior + + setup do + @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1) + @cache.logger = nil + end + end + + class StoreAPITest < StoreTest + end + + class FailureSafetyTest < StoreTest + test "fetch read failure returns nil" do + end + + test "fetch read failure does not attempt to write" do + end + + test "write failure returns nil" do + end + end + + class DeleteMatchedTest < StoreTest + test "deletes keys matching glob" do + @cache.write("foo", "bar") + @cache.write("fu", "baz") + @cache.delete_matched("foo*") + assert !@cache.exist?("foo") + assert @cache.exist?("fu") + end + + test "fails with regexp matchers" do + assert_raise ArgumentError do + @cache.delete_matched(/OO/i) + end + end + end +end |