debian-mirror-gitlab/spec/lib/gitlab/utils/strong_memoize_spec.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

303 lines
8.4 KiB
Ruby
Raw Normal View History

2019-12-04 20:38:33 +05:30
# frozen_string_literal: true
2022-08-27 11:52:29 +05:30
require 'fast_spec_helper'
require 'rspec-benchmark'
RSpec.configure do |config|
config.include RSpec::Benchmark::Matchers
end
2018-03-17 18:26:18 +05:30
2020-07-28 23:09:34 +05:30
RSpec.describe Gitlab::Utils::StrongMemoize do
2018-03-17 18:26:18 +05:30
let(:klass) do
2022-08-27 11:52:29 +05:30
strong_memoize_class = described_class
Struct.new(:value) do
include strong_memoize_class
def self.method_added_list
@method_added_list ||= []
end
def self.method_added(name)
method_added_list << name
end
2018-03-17 18:26:18 +05:30
def method_name
2023-03-04 22:38:38 +05:30
strong_memoize(:method_name) do # rubocop: disable Gitlab/StrongMemoizeAttr
2018-03-17 18:26:18 +05:30
trace << value
value
end
end
2022-08-27 11:52:29 +05:30
def method_name_attr
trace << value
value
end
strong_memoize_attr :method_name_attr
def different_method_name_attr
trace << value
value
end
2023-01-13 00:05:48 +05:30
strong_memoize_attr :different_method_name_attr, :different_member_name_attr
2022-08-27 11:52:29 +05:30
def enabled?
true
end
2023-01-13 00:05:48 +05:30
strong_memoize_attr :enabled?
def method_name_with_args(*args)
strong_memoize_with(:method_name_with_args, args) do
trace << [value, args]
value
end
end
2022-08-27 11:52:29 +05:30
2018-03-17 18:26:18 +05:30
def trace
@trace ||= []
end
2022-08-27 11:52:29 +05:30
protected
2023-03-04 22:38:38 +05:30
def private_method; end
2022-08-27 11:52:29 +05:30
private :private_method
strong_memoize_attr :private_method
public
2023-03-04 22:38:38 +05:30
def protected_method; end
2022-08-27 11:52:29 +05:30
protected :protected_method
strong_memoize_attr :protected_method
private
2023-03-04 22:38:38 +05:30
def public_method; end
2022-08-27 11:52:29 +05:30
public :public_method
strong_memoize_attr :public_method
end
2018-03-17 18:26:18 +05:30
end
subject(:object) { klass.new(value) }
shared_examples 'caching the value' do
it 'only calls the block once' do
2022-08-27 11:52:29 +05:30
value0 = object.send(method_name)
value1 = object.send(method_name)
2018-03-17 18:26:18 +05:30
expect(value0).to eq(value)
expect(value1).to eq(value)
expect(object.trace).to contain_exactly(value)
end
it 'returns and defines the instance variable for the exact value' do
2022-08-27 11:52:29 +05:30
returned_value = object.send(method_name)
memoized_value = object.instance_variable_get(:"@#{member_name}")
2018-03-17 18:26:18 +05:30
expect(returned_value).to eql(value)
expect(memoized_value).to eql(value)
end
end
describe '#strong_memoize' do
[nil, false, true, 'value', 0, [0]].each do |value|
context "with value #{value}" do
let(:value) { value }
2022-08-27 11:52:29 +05:30
let(:method_name) { :method_name }
let(:member_name) { :method_name }
2018-03-17 18:26:18 +05:30
it_behaves_like 'caching the value'
2022-05-07 20:08:51 +05:30
2022-08-27 11:52:29 +05:30
it 'raises exception for invalid type as key' do
2022-05-07 20:08:51 +05:30
expect { object.strong_memoize(10) { 20 } }.to raise_error /Invalid type of '10'/
end
2022-08-27 11:52:29 +05:30
it 'raises exception for invalid characters in key' do
expect { object.strong_memoize(:enabled?) { 20 } }
.to raise_error /is not allowed as an instance variable name/
end
2022-05-07 20:08:51 +05:30
end
end
context "memory allocation", type: :benchmark do
let(:value) { 'aaa' }
before do
object.method_name # warmup
end
[:method_name, "method_name"].each do |argument|
context "for #{argument.class}" do
it 'does allocate exactly one string when fetching value' do
expect do
object.strong_memoize(argument) { 10 }
end.to perform_allocation(1)
end
it 'does allocate exactly one string when storing value' do
object.clear_memoization(:method_name) # clear to force set
expect do
object.strong_memoize(argument) { 10 }
end.to perform_allocation(1)
end
end
2018-03-17 18:26:18 +05:30
end
end
end
2023-01-13 00:05:48 +05:30
describe '#strong_memoize_with' do
[nil, false, true, 'value', 0, [0]].each do |value|
context "with value #{value}" do
let(:value) { value }
it 'only calls the block once' do
value0 = object.method_name_with_args(1)
value1 = object.method_name_with_args(1)
value2 = object.method_name_with_args([2, 3])
value3 = object.method_name_with_args([2, 3])
expect(value0).to eq(value)
expect(value1).to eq(value)
expect(value2).to eq(value)
expect(value3).to eq(value)
expect(object.trace).to contain_exactly([value, [1]], [value, [[2, 3]]])
end
it 'returns and defines the instance variable for the exact value' do
returned_value = object.method_name_with_args(1, 2, 3)
memoized_value = object.instance_variable_get(:@method_name_with_args)
expect(returned_value).to eql(value)
expect(memoized_value).to eql({ [[1, 2, 3]] => value })
end
end
end
end
2019-12-04 20:38:33 +05:30
describe '#strong_memoized?' do
let(:value) { :anything }
subject { object.strong_memoized?(:method_name) }
it 'returns false if the value is uncached' do
is_expected.to be(false)
end
it 'returns true if the value is cached' do
object.method_name
is_expected.to be(true)
end
end
2018-03-17 18:26:18 +05:30
describe '#clear_memoization' do
let(:value) { 'mepmep' }
it 'removes the instance variable' do
object.method_name
object.clear_memoization(:method_name)
expect(object.instance_variable_defined?(:@method_name)).to be(false)
end
end
2022-08-27 11:52:29 +05:30
describe '.strong_memoize_attr' do
[nil, false, true, 'value', 0, [0]].each do |value|
let(:value) { value }
context "memoized after method definition with value #{value}" do
let(:method_name) { :method_name_attr }
let(:member_name) { :method_name_attr }
it_behaves_like 'caching the value'
it 'calls the existing .method_added' do
expect(klass.method_added_list).to include(:method_name_attr)
end
2023-03-04 22:38:38 +05:30
it 'retains method arity' do
expect(klass.instance_method(member_name).arity).to eq(0)
end
2022-08-27 11:52:29 +05:30
end
context "memoized before method definition with different member name and value #{value}" do
let(:method_name) { :different_method_name_attr }
let(:member_name) { :different_member_name_attr }
it_behaves_like 'caching the value'
it 'calls the existing .method_added' do
expect(klass.method_added_list).to include(:different_method_name_attr)
end
end
context 'with valid method name' do
let(:method_name) { :enabled? }
context 'with invalid member name' do
let(:member_name) { :enabled? }
it 'is invalid' do
expect { object.send(method_name) { value } }.to raise_error /is not allowed as an instance variable name/
end
end
end
end
describe 'method visibility' do
it 'sets private visibility' do
expect(klass.private_instance_methods).to include(:private_method)
expect(klass.protected_instance_methods).not_to include(:private_method)
expect(klass.public_instance_methods).not_to include(:private_method)
end
it 'sets protected visibility' do
expect(klass.private_instance_methods).not_to include(:protected_method)
expect(klass.protected_instance_methods).to include(:protected_method)
expect(klass.public_instance_methods).not_to include(:protected_method)
end
it 'sets public visibility' do
expect(klass.private_instance_methods).not_to include(:public_method)
expect(klass.protected_instance_methods).not_to include(:public_method)
expect(klass.public_instance_methods).to include(:public_method)
end
end
2023-01-13 00:05:48 +05:30
context "when method doesn't exist" do
let(:klass) do
strong_memoize_class = described_class
Struct.new(:value) do
include strong_memoize_class
end
end
subject { klass.strong_memoize_attr(:nonexistent_method) }
it 'fails when strong-memoizing a nonexistent method' do
expect { subject }.to raise_error(NameError, %r{undefined method `nonexistent_method' for class})
end
end
2023-03-04 22:38:38 +05:30
context 'when memoized method has parameters' do
it 'raises an error' do
expected_message = /Using `strong_memoize_attr` on methods with parameters is not supported/
expect do
strong_memoize_class = described_class
Class.new do
include strong_memoize_class
def method_with_parameters(params); end
strong_memoize_attr :method_with_parameters
end
end.to raise_error(RuntimeError, expected_message)
end
end
2022-08-27 11:52:29 +05:30
end
2018-03-17 18:26:18 +05:30
end