require 'spec_helper' describe TimedLRU do subject { described_class.new max_size: 4 } def full_chain return [] unless head res = [head] while curr = res.last.right expect(curr.left).to eq(res.last) res << curr end expect(head.left).to be_nil expect(tail.right).to be_nil expect(res.last).to eq(tail) res end def chain full_chain.map(&:key) end def head subject.instance_variable_get(:@head) end def tail subject.instance_variable_get(:@tail) end describe "defaults" do subject { described_class.new } describe '#max_size' do subject { super().max_size } it { is_expected.to be(100) } end describe '#ttl' do subject { super().ttl } it { is_expected.to be_nil } end it { is_expected.to be_a(MonitorMixin) } it { is_expected.not_to be_a(described_class::ThreadUnsafe) } it { is_expected.to respond_to(:empty?) } it { is_expected.to respond_to(:keys) } it { is_expected.to respond_to(:size) } it { is_expected.to respond_to(:each_key) } end describe "init" do subject { described_class.new max_size: 25, ttl: 120, thread_safe: false } describe '#max_size' do subject { super().max_size } it { is_expected.to be(25) } end describe '#ttl' do subject { super().ttl } it { is_expected.to be(120) } end it { is_expected.to be_a(described_class::ThreadUnsafe) } it 'should assert correct option values' do expect { described_class.new(max_size: "X") }.to raise_error(ArgumentError) expect { described_class.new(max_size: -1) }.to raise_error(ArgumentError) expect { described_class.new(max_size: 0) }.to raise_error(ArgumentError) expect { described_class.new(ttl: "X") }.to raise_error(ArgumentError) expect { described_class.new(ttl: true) }.to raise_error(TypeError) expect { described_class.new(ttl: 0) }.to raise_error(ArgumentError) end end describe "storing" do it "should set head + tail on first item" do expect { expect(subject.store("a", 1)).to eq(1) }.to change { chain }.from([]).to(["a"]) end it "should shift chain when new items are added" do subject["a"] = 1 expect { subject["b"] = 2 }.to change { chain }.from(%w|a|).to(%w|b a|) expect { subject["c"] = 3 }.to change { chain }.to(%w|c b a|) expect { subject["d"] = 4 }.to change { chain }.to(%w|d c b a|) end it "should expire LRU items when chain exceeds max size" do ("a".."d").each {|x| subject[x] = 1 } expect { subject["e"] = 5 }.to change { chain }.to(%w|e d c b|) expect { subject["f"] = 6 }.to change { chain }.to(%w|f e d c|) end it "should update items" do ("a".."d").each {|x| subject[x] = 1 } expect { subject["d"] = 2 }.not_to change { chain } expect { subject["c"] = 2 }.to change { chain }.to(%w|c d b a|) expect { subject["b"] = 2 }.to change { chain }.to(%w|b c d a|) expect { subject["a"] = 2 }.to change { chain }.to(%w|a b c d|) end end describe "retrieving" do it 'should fetch values' do expect(subject.fetch("a")).to be_nil expect(subject["a"]).to be_nil subject["a"] = 1 expect(subject["a"]).to eq(1) end it 'should renew membership on access' do ("a".."d").each {|x| subject[x] = 1 } expect { subject["d"] }.not_to change { chain } expect { subject["c"] }.to change { chain }.to(%w|c d b a|) expect { subject["b"] }.to change { chain }.to(%w|b c d a|) expect { subject["a"] }.to change { chain }.to(%w|a b c d|) expect { subject["x"] }.not_to change { chain } end end describe "deleting" do it 'should delete an return values' do expect(subject.delete("a")).to be_nil subject["a"] = 1 expect(subject.delete("a")).to eq(1) end it 'should re-arrange membership chain' do ("a".."d").each {|x| subject[x] = 1 } expect { subject.delete("x") }.not_to change { chain } expect { subject.delete("c") }.to change { chain }.to(%w|d b a|) expect { subject.delete("a") }.to change { chain }.to(%w|d b|) expect { subject.delete("d") }.to change { chain }.to(%w|b|) expect { subject.delete("b") }.to change { subject.size }.from(1).to(0) end end describe "TTL expiration" do subject { described_class.new max_size: 4, ttl: 60 } def in_past(ago) allow(Time).to receive_messages now: (Time.now - ago) yield ensure allow(Time).to receive(:now).and_call_original end it 'should expire on access' do in_past(70) do subject["a"] = 1 expect(chain).to eq(%w|a|) end in_past(50) do subject["b"] = 2 expect(chain).to eq(%w|b a|) end subject["c"] = 3 expect(chain).to eq(%w|c b|) end it 'should renew expiration on access' do in_past(70) do subject["a"] = 1 subject["b"] = 2 expect(chain).to eq(%w|b a|) end in_past(50) do expect(subject["a"]).to eq(1) expect(chain).to eq(%w|a b|) end subject["c"] = 3 expect(chain).to eq(%w|c a|) end end end