From 3a4b971efd5b5c340145209e91a8f5ab88d91250 Mon Sep 17 00:00:00 2001 From: Rustam Ibragimov Date: Mon, 3 Dec 2018 01:00:44 +0300 Subject: [PATCH] [marshaling] specs, specs, specs... (mutli_operations and dalli bugs) --- .../adapters/active_support_naive_store.rb | 2 +- lib/any_cache/adapters/dalli.rb | 5 +- spec/features/raw_non_raw_read_write_spec.rb | 98 +++++++++++++++---- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/lib/any_cache/adapters/active_support_naive_store.rb b/lib/any_cache/adapters/active_support_naive_store.rb index 2bf4800..6338392 100644 --- a/lib/any_cache/adapters/active_support_naive_store.rb +++ b/lib/any_cache/adapters/active_support_naive_store.rb @@ -150,7 +150,7 @@ def fetch_multi(*keys, **options, &fallback) # use own #fetch_multi implementation cuz original #fetch_multi # does not support :force option keys.each_with_object({}) do |key, dataset| - dataset[key] = driver.fetch(key, force: force, expires_in: expires_in, &fallback) + dataset[key] = driver.fetch(key, force: force_rewrite, expires_in: expires_in, &fallback) end end end diff --git a/lib/any_cache/adapters/dalli.rb b/lib/any_cache/adapters/dalli.rb index a1b1f11..fc31a3e 100644 --- a/lib/any_cache/adapters/dalli.rb +++ b/lib/any_cache/adapters/dalli.rb @@ -79,10 +79,13 @@ def read_multi(*keys, **options) raw = options.fetch(:raw, false) entries = get_multi(*keys).tap do |res| + # TODO: returned res.keys items should have consistent types with initial keys items + # (at this moment you cant return requested symbolic keys - + # your keys will be stringifed by dalli) res.merge!(Hash[(keys - res.keys).zip(READ_MULTI_EMPTY_KEYS_SET)]) end - raw ? entires : detransform_pairset(entries) + raw ? entries : detransform_pairset(entries) end # @param key [String] diff --git a/spec/features/raw_non_raw_read_write_spec.rb b/spec/features/raw_non_raw_read_write_spec.rb index ee3116e..68b5af3 100644 --- a/spec/features/raw_non_raw_read_write_spec.rb +++ b/spec/features/raw_non_raw_read_write_spec.rb @@ -11,14 +11,12 @@ cached_ruby_object = cache_store.read(:ruby_object) expect(cached_ruby_object).to be_a(SimpleRubyObject) - expect(cached_ruby_object.a).to eq('a') - expect(cached_ruby_object.b).to eq(123) - expect(cached_ruby_object.c).to eq(true) + expect(cached_ruby_object).to have_attributes(a: 'a', b: 123, c: true) end - context 'write non-raw (:raw option is false)' do + context 'write non-raw (write real ruby object) (:raw => false)' do context 'single read/write' do - specify 'writes real ruby object' do + specify '#read/#write works correctly' do # works with initial structures ruby_object = { a: 1, b: 2, c: 3 } cache_store.write(:non_raw, ruby_object, raw: false) @@ -33,19 +31,30 @@ cached_another_ruby_object = cache_store.read(:non_raw, raw: false) expect(cached_another_ruby_object).to be_a(SimpleRubyObject) - expect(cached_another_ruby_object.a).to eq(1) - expect(cached_another_ruby_object.b).to eq(2) - expect(cached_another_ruby_object.c).to eq(3) + expect(cached_another_ruby_object).to have_attributes(a: 1, b: 2, c: 3) + end + + specify '#fetch works correctly' do + returned_ruby_object = cache_store.fetch(:non_raw, raw: false) do |key| + SimpleRubyObject.new(key, 1, false) + end + + cached_ruby_object = cache_store.fetch(:non_raw, raw: false) + + [cached_ruby_object, returned_ruby_object].each do |cached_object| + expect(cached_object).to be_a(SimpleRubyObject) + expect(cached_object).to have_attributes(a: :non_raw, b: 1, c: false) + end end end context 'multi read/write' do - specify 'writes real ruby object' do + specify '#read_multi/#write_multi works correctly' do # worsk with any adequate type ruby_entries = { - hash_ruby_obj: { a: 1, b: 2, c: 3 }, - bool_ruby_obj: true, - real_ruby_obj: SimpleRubyObject.new(1, 2, 3) + 'hash_ruby_obj' => { a: 1, b: 2, c: 3 }, + 'bool_ruby_obj' => true, + 'real_ruby_obj' => SimpleRubyObject.new(1, 2, 3) } cache_store.write_multi(ruby_entries, raw: false) @@ -53,6 +62,24 @@ expect(cached_ruby_entries).to match(ruby_entries) end + + specify '#fetch_multi works correctly' do + returned_ruby_entries = cache_store.fetch_multi(:a, :b, :c, raw: false) do |key| + SimpleRubyObject.new(key, 1, true) + end + + cached_ruby_entries = cache_store.fetch_multi(:a, :b, :c, raw: false) + + [cached_ruby_entries, returned_ruby_entries].each do |cached_entries| + expect(cached_entries[:a]).to be_a(SimpleRubyObject) + expect(cached_entries[:b]).to be_a(SimpleRubyObject) + expect(cached_entries[:c]).to be_a(SimpleRubyObject) + + expect(cached_entries[:a]).to have_attributes(a: :a, b: 1, c: true) + expect(cached_entries[:b]).to have_attributes(a: :b, b: 1, c: true) + expect(cached_entries[:c]).to have_attributes(a: :c, b: 1, c: true) + end + end end end @@ -60,11 +87,15 @@ # ActiveSupport::Cache::FileStore and ActiveSupport::Cache::ReadStore does not support # raw/non-raw optionality cuz under the hood these classes invokes #write_entry method # that uses Marshal.dump before the write-to-disk and write-to-memory operations respectively. - context 'write raw (:raw option is true)', exclude: %i[as_file_store as_memory_store] do + context( + 'write raw (:raw => true) (uses internal cache-related string-like object write operation)', + exclude: %i[as_file_store as_memory_store] + ) do + before { stub_const('SimpleRubyObject', Struct.new(:a, :b, :c)) } context 'single read/write' do - specify 'uses internal cache-related string-like object write operation' do + specify '#read/#write works correctly' do ruby_object = { a: 1, b: 2, c: 3} cache_store.write(:raw, ruby_object, raw: true) cached_ruby_object = cache_store.read(:raw, raw: true) @@ -77,15 +108,30 @@ expect(cached_another_ruby_object).to be_a(String) end + + specify '#fetch works correctly' do + returned_ruby_object = cache_store.fetch(:raw, raw: true) do |key| + SimpleRubyObject.new(key, 1, false) + end + + cached_ruby_object = cache_store.fetch(:raw, raw: true) + + # TODO: think about consistent fetching + # - expect(returned_ruby_object).to be_a(String) + # - nonexistent value (first #fetch invokation) + # will return the &fallback's proc result (real ruby object) + + expect(cached_ruby_object).to be_a(String) + end end context 'multi read/write' do - specify 'uses internal cache-related string-like object write operation' do + specify '#read_multi/#write_multi works correctly' do # works with any adequate type ruby_entries = { - hash_ruby_obj: { a: 1, b: 2, c: 3 }, - bool_ruby_obj: true, - real_ruby_obj: SimpleRubyObject.new(1, 2, 3) + 'hash_ruby_obj' => { a: 1, b: 2, c: 3 }, + 'bool_ruby_obj' => true, + 'real_ruby_obj' => SimpleRubyObject.new(1, 2, 3) } cache_store.write_multi(ruby_entries, raw: true) @@ -94,6 +140,22 @@ expect(cached_ruby_entries.keys).to contain_exactly(*ruby_entries.keys) expect(cached_ruby_entries.values).to all(be_a(String)) end + + specify '#fetch_multi works correctly' do + returned_ruby_entries = cache_store.fetch_multi(:a, :b, raw: true) do |key| + SimpleRubyObject.new(key, true, false) + end + + cached_ruby_entries = cache_store.fetch_multi(:a, :b, raw: true) + + # TODO: think about consistent fetching + # - expect(returned_ruby_entries.values).to all(be_a(String)) and so on + # - nonexistent value (first #fetch invokation) + # will return the &fallback's proc result (real ruby object) + + expect(cached_ruby_entries.keys).to contain_exactly(:a, :b) + expect(cached_ruby_entries.values).to all(be_a(String)) + end end end end