902 lines
24 KiB
Ruby
902 lines
24 KiB
Ruby
require 'thread_safe'
|
|
require 'thread'
|
|
require File.join(File.dirname(__FILE__), "test_helper")
|
|
|
|
Thread.abort_on_exception = true
|
|
|
|
class TestCache < Minitest::Test
|
|
def setup
|
|
@cache = ThreadSafe::Cache.new
|
|
end
|
|
|
|
def test_concurrency
|
|
cache = @cache
|
|
(1..100).map do |i|
|
|
Thread.new do
|
|
1000.times do |j|
|
|
key = i*1000+j
|
|
cache[key] = i
|
|
cache[key]
|
|
cache.delete(key)
|
|
end
|
|
end
|
|
end.map(&:join)
|
|
end
|
|
|
|
def test_retrieval
|
|
assert_size_change 1 do
|
|
assert_equal nil, @cache[:a]
|
|
assert_equal nil, @cache.get(:a)
|
|
@cache[:a] = 1
|
|
assert_equal 1, @cache[:a]
|
|
assert_equal 1, @cache.get(:a)
|
|
end
|
|
end
|
|
|
|
def test_put_if_absent
|
|
with_or_without_default_proc do
|
|
assert_size_change 1 do
|
|
assert_equal nil, @cache.put_if_absent(:a, 1)
|
|
assert_equal 1, @cache.put_if_absent(:a, 1)
|
|
assert_equal 1, @cache.put_if_absent(:a, 2)
|
|
assert_equal 1, @cache[:a]
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_compute_if_absent
|
|
with_or_without_default_proc do
|
|
assert_size_change 3 do
|
|
assert_equal(1, (@cache.compute_if_absent(:a) {1}))
|
|
assert_equal(1, (@cache.compute_if_absent(:a) {2}))
|
|
assert_equal 1, @cache[:a]
|
|
@cache[:b] = nil
|
|
assert_equal(nil, (@cache.compute_if_absent(:b) {1}))
|
|
assert_equal(nil, (@cache.compute_if_absent(:c) {}))
|
|
assert_equal nil, @cache[:c]
|
|
assert_equal true, @cache.key?(:c)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_compute_if_absent_with_return
|
|
with_or_without_default_proc { assert_handles_return_lambda(:compute_if_absent, :a) }
|
|
end
|
|
|
|
def test_compute_if_absent_exception
|
|
with_or_without_default_proc { assert_handles_exception(:compute_if_absent, :a) }
|
|
end
|
|
|
|
def test_compute_if_absent_atomicity
|
|
late_compute_threads_count = 10
|
|
late_put_if_absent_threads_count = 10
|
|
getter_threads_count = 5
|
|
compute_started = ThreadSafe::Test::Latch.new(1)
|
|
compute_proceed = ThreadSafe::Test::Latch.new(late_compute_threads_count + late_put_if_absent_threads_count + getter_threads_count)
|
|
block_until_compute_started = lambda do |name|
|
|
if (v = @cache[:a]) != nil
|
|
assert_equal nil, v
|
|
end
|
|
compute_proceed.release
|
|
compute_started.await
|
|
end
|
|
|
|
assert_size_change 1 do
|
|
late_compute_threads = Array.new(late_compute_threads_count) do
|
|
Thread.new do
|
|
block_until_compute_started.call('compute_if_absent')
|
|
assert_equal(1, (@cache.compute_if_absent(:a) { flunk }))
|
|
end
|
|
end
|
|
|
|
late_put_if_absent_threads = Array.new(late_put_if_absent_threads_count) do
|
|
Thread.new do
|
|
block_until_compute_started.call('put_if_absent')
|
|
assert_equal(1, @cache.put_if_absent(:a, 2))
|
|
end
|
|
end
|
|
|
|
getter_threads = Array.new(getter_threads_count) do
|
|
Thread.new do
|
|
block_until_compute_started.call('getter')
|
|
Thread.pass while @cache[:a].nil?
|
|
assert_equal 1, @cache[:a]
|
|
end
|
|
end
|
|
|
|
Thread.new do
|
|
@cache.compute_if_absent(:a) do
|
|
compute_started.release
|
|
compute_proceed.await
|
|
sleep(0.2)
|
|
1
|
|
end
|
|
end.join
|
|
(late_compute_threads + late_put_if_absent_threads + getter_threads).each(&:join)
|
|
end
|
|
end
|
|
|
|
def test_compute_if_present
|
|
with_or_without_default_proc do
|
|
assert_no_size_change do
|
|
assert_equal(nil, @cache.compute_if_present(:a) {})
|
|
assert_equal(nil, @cache.compute_if_present(:a) {1})
|
|
assert_equal(nil, @cache.compute_if_present(:a) {flunk})
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
|
|
@cache[:a] = 1
|
|
assert_no_size_change do
|
|
assert_equal(1, @cache.compute_if_present(:a) {1})
|
|
assert_equal(1, @cache[:a])
|
|
assert_equal(2, @cache.compute_if_present(:a) {2})
|
|
assert_equal(2, @cache[:a])
|
|
assert_equal(false, @cache.compute_if_present(:a) {false})
|
|
assert_equal(false, @cache[:a])
|
|
|
|
@cache[:a] = 1
|
|
yielded = false
|
|
@cache.compute_if_present(:a) do |old_value|
|
|
yielded = true
|
|
assert_equal 1, old_value
|
|
2
|
|
end
|
|
assert yielded
|
|
end
|
|
|
|
assert_size_change -1 do
|
|
assert_equal(nil, @cache.compute_if_present(:a) {})
|
|
assert_equal(false, @cache.key?(:a))
|
|
assert_equal(nil, @cache.compute_if_present(:a) {1})
|
|
assert_equal(false, @cache.key?(:a))
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_compute_if_present_with_return
|
|
with_or_without_default_proc do
|
|
@cache[:a] = 1
|
|
assert_handles_return_lambda(:compute_if_present, :a)
|
|
end
|
|
end
|
|
|
|
def test_compute_if_present_exception
|
|
with_or_without_default_proc do
|
|
@cache[:a] = 1
|
|
assert_handles_exception(:compute_if_present, :a)
|
|
end
|
|
end
|
|
|
|
def test_compute
|
|
with_or_without_default_proc do
|
|
assert_no_size_change do
|
|
assert_compute(:a, nil, nil) {}
|
|
end
|
|
|
|
assert_size_change 1 do
|
|
assert_compute(:a, nil, 1) {1}
|
|
assert_compute(:a, 1, 2) {2}
|
|
assert_compute(:a, 2, false) {false}
|
|
assert_equal false, @cache[:a]
|
|
end
|
|
|
|
assert_size_change -1 do
|
|
assert_compute(:a, false, nil) {}
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_compute_with_return
|
|
with_or_without_default_proc do
|
|
assert_handles_return_lambda(:compute, :a)
|
|
@cache[:a] = 1
|
|
assert_handles_return_lambda(:compute, :a)
|
|
end
|
|
end
|
|
|
|
def test_compute_exception
|
|
with_or_without_default_proc do
|
|
assert_handles_exception(:compute, :a)
|
|
@cache[:a] = 1
|
|
assert_handles_exception(:compute, :a)
|
|
end
|
|
end
|
|
|
|
def test_merge_pair
|
|
with_or_without_default_proc do
|
|
assert_size_change 1 do
|
|
assert_equal(nil, @cache.merge_pair(:a, nil) {flunk})
|
|
assert_equal true, @cache.key?(:a)
|
|
assert_equal nil, @cache[:a]
|
|
end
|
|
|
|
assert_no_size_change do
|
|
assert_merge_pair(:a, nil, nil, false) {false}
|
|
assert_merge_pair(:a, nil, false, 1) {1}
|
|
assert_merge_pair(:a, nil, 1, 2) {2}
|
|
end
|
|
|
|
assert_size_change -1 do
|
|
assert_merge_pair(:a, nil, 2, nil) {}
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_merge_pair_with_return
|
|
with_or_without_default_proc do
|
|
@cache[:a] = 1
|
|
assert_handles_return_lambda(:merge_pair, :a, 2)
|
|
end
|
|
end
|
|
|
|
def test_merge_pair_exception
|
|
with_or_without_default_proc do
|
|
@cache[:a] = 1
|
|
assert_handles_exception(:merge_pair, :a, 2)
|
|
end
|
|
end
|
|
|
|
def test_updates_dont_block_reads
|
|
getters_count = 20
|
|
key_klass = ThreadSafe::Test::HashCollisionKey
|
|
keys = [key_klass.new(1, 100), key_klass.new(2, 100), key_klass.new(3, 100)] # hash colliding keys
|
|
inserted_keys = []
|
|
|
|
keys.each do |key, i|
|
|
compute_started = ThreadSafe::Test::Latch.new(1)
|
|
compute_finished = ThreadSafe::Test::Latch.new(1)
|
|
getters_started = ThreadSafe::Test::Latch.new(getters_count)
|
|
getters_finished = ThreadSafe::Test::Latch.new(getters_count)
|
|
|
|
computer_thread = Thread.new do
|
|
getters_started.await
|
|
@cache.compute_if_absent(key) do
|
|
compute_started.release
|
|
getters_finished.await
|
|
1
|
|
end
|
|
compute_finished.release
|
|
end
|
|
|
|
getter_threads = (1..getters_count).map do
|
|
Thread.new do
|
|
getters_started.release
|
|
inserted_keys.each do |inserted_key|
|
|
assert_equal true, @cache.key?(inserted_key)
|
|
assert_equal 1, @cache[inserted_key]
|
|
end
|
|
assert_equal false, @cache.key?(key)
|
|
compute_started.await
|
|
inserted_keys.each do |inserted_key|
|
|
assert_equal true, @cache.key?(inserted_key)
|
|
assert_equal 1, @cache[inserted_key]
|
|
end
|
|
assert_equal false, @cache.key?(key)
|
|
assert_equal nil, @cache[key]
|
|
getters_finished.release
|
|
compute_finished.await
|
|
assert_equal true, @cache.key?(key)
|
|
assert_equal 1, @cache[key]
|
|
end
|
|
end
|
|
|
|
(getter_threads << computer_thread).map {|t| assert(t.join(2))} # asserting no deadlocks
|
|
inserted_keys << key
|
|
end
|
|
end
|
|
|
|
def test_collision_resistance
|
|
assert_collision_resistance((0..1000).map {|i| ThreadSafe::Test::HashCollisionKey(i, 1)})
|
|
end
|
|
|
|
def test_collision_resistance_with_arrays
|
|
special_array_class = Class.new(Array) do
|
|
def key # assert_collision_resistance expects to be able to call .key to get the "real" key
|
|
first.key
|
|
end
|
|
end
|
|
# Test collision resistance with a keys that say they responds_to <=>, but then raise exceptions
|
|
# when actually called (ie: an Array filled with non-comparable keys).
|
|
# See https://github.com/headius/thread_safe/issues/19 for more info.
|
|
assert_collision_resistance((0..100).map do |i|
|
|
special_array_class.new([ThreadSafe::Test::HashCollisionKeyNonComparable.new(i, 1)])
|
|
end)
|
|
end
|
|
|
|
def test_replace_pair
|
|
with_or_without_default_proc do
|
|
assert_no_size_change do
|
|
assert_equal false, @cache.replace_pair(:a, 1, 2)
|
|
assert_equal false, @cache.replace_pair(:a, nil, nil)
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
|
|
@cache[:a] = 1
|
|
assert_no_size_change do
|
|
assert_equal true, @cache.replace_pair(:a, 1, 2)
|
|
assert_equal false, @cache.replace_pair(:a, 1, 2)
|
|
assert_equal 2, @cache[:a]
|
|
assert_equal true, @cache.replace_pair(:a, 2, 2)
|
|
assert_equal 2, @cache[:a]
|
|
assert_equal true, @cache.replace_pair(:a, 2, nil)
|
|
assert_equal false, @cache.replace_pair(:a, 2, nil)
|
|
assert_equal nil, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
assert_equal true, @cache.replace_pair(:a, nil, nil)
|
|
assert_equal true, @cache.key?(:a)
|
|
assert_equal true, @cache.replace_pair(:a, nil, 1)
|
|
assert_equal 1, @cache[:a]
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_replace_if_exists
|
|
with_or_without_default_proc do
|
|
assert_no_size_change do
|
|
assert_equal nil, @cache.replace_if_exists(:a, 1)
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
|
|
@cache[:a] = 1
|
|
assert_no_size_change do
|
|
assert_equal 1, @cache.replace_if_exists(:a, 2)
|
|
assert_equal 2, @cache[:a]
|
|
assert_equal 2, @cache.replace_if_exists(:a, nil)
|
|
assert_equal nil, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
assert_equal nil, @cache.replace_if_exists(:a, 1)
|
|
assert_equal 1, @cache[:a]
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_get_and_set
|
|
with_or_without_default_proc do
|
|
assert_size_change 1 do
|
|
assert_equal nil, @cache.get_and_set(:a, 1)
|
|
assert_equal true, @cache.key?(:a)
|
|
assert_equal 1, @cache[:a]
|
|
assert_equal 1, @cache.get_and_set(:a, 2)
|
|
assert_equal 2, @cache.get_and_set(:a, nil)
|
|
assert_equal nil, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
assert_equal nil, @cache.get_and_set(:a, 1)
|
|
assert_equal 1, @cache[:a]
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_key
|
|
with_or_without_default_proc do
|
|
assert_equal nil, @cache.key(1)
|
|
@cache[:a] = 1
|
|
assert_equal :a, @cache.key(1)
|
|
assert_equal nil, @cache.key(0)
|
|
assert_equal :a, @cache.index(1) if RUBY_VERSION =~ /1\.8/
|
|
end
|
|
end
|
|
|
|
def test_key?
|
|
with_or_without_default_proc do
|
|
assert_equal false, @cache.key?(:a)
|
|
@cache[:a] = 1
|
|
assert_equal true, @cache.key?(:a)
|
|
end
|
|
end
|
|
|
|
def test_value?
|
|
with_or_without_default_proc do
|
|
assert_equal false, @cache.value?(1)
|
|
@cache[:a] = 1
|
|
assert_equal true, @cache.value?(1)
|
|
end
|
|
end
|
|
|
|
def test_delete
|
|
with_or_without_default_proc do |default_proc_set|
|
|
assert_no_size_change do
|
|
assert_equal nil, @cache.delete(:a)
|
|
end
|
|
@cache[:a] = 1
|
|
assert_size_change -1 do
|
|
assert_equal 1, @cache.delete(:a)
|
|
end
|
|
assert_no_size_change do
|
|
assert_equal nil, @cache[:a] unless default_proc_set
|
|
|
|
assert_equal false, @cache.key?(:a)
|
|
assert_equal nil, @cache.delete(:a)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_delete_pair
|
|
with_or_without_default_proc do
|
|
assert_no_size_change do
|
|
assert_equal false, @cache.delete_pair(:a, 2)
|
|
assert_equal false, @cache.delete_pair(:a, nil)
|
|
end
|
|
@cache[:a] = 1
|
|
assert_no_size_change do
|
|
assert_equal false, @cache.delete_pair(:a, 2)
|
|
end
|
|
assert_size_change -1 do
|
|
assert_equal 1, @cache[:a]
|
|
assert_equal true, @cache.delete_pair(:a, 1)
|
|
assert_equal false, @cache.delete_pair(:a, 1)
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_default_proc
|
|
@cache = cache_with_default_proc(1)
|
|
assert_no_size_change do
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
assert_size_change 1 do
|
|
assert_equal 1, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
end
|
|
end
|
|
|
|
def test_falsy_default_proc
|
|
@cache = cache_with_default_proc(nil)
|
|
assert_no_size_change do
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
assert_size_change 1 do
|
|
assert_equal nil, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
end
|
|
end
|
|
|
|
def test_fetch
|
|
with_or_without_default_proc do |default_proc_set|
|
|
assert_no_size_change do
|
|
assert_equal 1, @cache.fetch(:a, 1)
|
|
assert_equal(1, (@cache.fetch(:a) {1}))
|
|
assert_equal false, @cache.key?(:a)
|
|
|
|
assert_equal nil, @cache[:a] unless default_proc_set
|
|
end
|
|
|
|
@cache[:a] = 1
|
|
assert_no_size_change do
|
|
assert_equal(1, (@cache.fetch(:a) {flunk}))
|
|
end
|
|
|
|
assert_raises(ThreadSafe::Cache::KEY_ERROR) do
|
|
@cache.fetch(:b)
|
|
end
|
|
|
|
assert_no_size_change do
|
|
assert_equal 1, (@cache.fetch(:b, :c) {1}) # assert block supersedes default value argument
|
|
assert_equal false, @cache.key?(:b)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_falsy_fetch
|
|
with_or_without_default_proc do
|
|
assert_equal false, @cache.key?(:a)
|
|
|
|
assert_no_size_change do
|
|
assert_equal(nil, @cache.fetch(:a, nil))
|
|
assert_equal(false, @cache.fetch(:a, false))
|
|
assert_equal(nil, (@cache.fetch(:a) {}))
|
|
assert_equal(false, (@cache.fetch(:a) {false}))
|
|
end
|
|
|
|
@cache[:a] = nil
|
|
assert_no_size_change do
|
|
assert_equal true, @cache.key?(:a)
|
|
assert_equal(nil, (@cache.fetch(:a) {flunk}))
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_fetch_with_return
|
|
with_or_without_default_proc do
|
|
r = lambda do
|
|
@cache.fetch(:a) { return 10 }
|
|
end.call
|
|
|
|
assert_no_size_change do
|
|
assert_equal 10, r
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_fetch_or_store
|
|
with_or_without_default_proc do |default_proc_set|
|
|
assert_size_change 1 do
|
|
assert_equal 1, @cache.fetch_or_store(:a, 1)
|
|
assert_equal 1, @cache[:a]
|
|
end
|
|
|
|
@cache.delete(:a)
|
|
|
|
assert_size_change 1 do
|
|
assert_equal 1, (@cache.fetch_or_store(:a) {1})
|
|
assert_equal 1, @cache[:a]
|
|
end
|
|
|
|
assert_no_size_change do
|
|
assert_equal(1, (@cache.fetch_or_store(:a) {flunk}))
|
|
end
|
|
|
|
assert_raises(ThreadSafe::Cache::KEY_ERROR) do
|
|
@cache.fetch_or_store(:b)
|
|
end
|
|
|
|
assert_size_change 1 do
|
|
assert_equal 1, (@cache.fetch_or_store(:b, :c) {1}) # assert block supersedes default value argument
|
|
assert_equal 1, @cache[:b]
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_falsy_fetch_or_store
|
|
with_or_without_default_proc do
|
|
assert_equal false, @cache.key?(:a)
|
|
|
|
assert_size_change 1 do
|
|
assert_equal(nil, @cache.fetch_or_store(:a, nil))
|
|
assert_equal nil, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
end
|
|
@cache.delete(:a)
|
|
|
|
assert_size_change 1 do
|
|
assert_equal(false, @cache.fetch_or_store(:a, false))
|
|
assert_equal false, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
end
|
|
@cache.delete(:a)
|
|
|
|
assert_size_change 1 do
|
|
assert_equal(nil, (@cache.fetch_or_store(:a) {}))
|
|
assert_equal nil, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
end
|
|
@cache.delete(:a)
|
|
|
|
assert_size_change 1 do
|
|
assert_equal(false, (@cache.fetch_or_store(:a) {false}))
|
|
assert_equal false, @cache[:a]
|
|
assert_equal true, @cache.key?(:a)
|
|
end
|
|
|
|
@cache[:a] = nil
|
|
assert_no_size_change do
|
|
assert_equal(nil, (@cache.fetch_or_store(:a) {flunk}))
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_fetch_or_store_with_return
|
|
with_or_without_default_proc do
|
|
r = lambda do
|
|
@cache.fetch_or_store(:a) { return 10 }
|
|
end.call
|
|
|
|
assert_no_size_change do
|
|
assert_equal 10, r
|
|
assert_equal false, @cache.key?(:a)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_clear
|
|
@cache[:a] = 1
|
|
assert_size_change -1 do
|
|
assert_equal @cache, @cache.clear
|
|
assert_equal false, @cache.key?(:a)
|
|
assert_equal nil, @cache[:a]
|
|
end
|
|
end
|
|
|
|
def test_each_pair
|
|
@cache.each_pair {|k, v| flunk}
|
|
assert_equal(@cache, (@cache.each_pair {}))
|
|
@cache[:a] = 1
|
|
|
|
h = {}
|
|
@cache.each_pair {|k, v| h[k] = v}
|
|
assert_equal({:a => 1}, h)
|
|
|
|
@cache[:b] = 2
|
|
h = {}
|
|
@cache.each_pair {|k, v| h[k] = v}
|
|
assert_equal({:a => 1, :b => 2}, h)
|
|
end
|
|
|
|
def test_each_pair_iterator
|
|
@cache[:a] = 1
|
|
@cache[:b] = 2
|
|
i = 0
|
|
r = @cache.each_pair do |k, v|
|
|
if i == 0
|
|
i += 1
|
|
next
|
|
flunk
|
|
elsif i == 1
|
|
break :breaked
|
|
end
|
|
end
|
|
|
|
assert_equal :breaked, r
|
|
end
|
|
|
|
def test_each_pair_allows_modification
|
|
@cache[:a] = 1
|
|
@cache[:b] = 1
|
|
@cache[:c] = 1
|
|
|
|
assert_size_change 1 do
|
|
@cache.each_pair do |k, v|
|
|
@cache[:z] = 1
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_keys
|
|
assert_equal [], @cache.keys
|
|
|
|
@cache[1] = 1
|
|
assert_equal [1], @cache.keys
|
|
|
|
@cache[2] = 2
|
|
assert_equal [1, 2], @cache.keys.sort
|
|
end
|
|
|
|
def test_values
|
|
assert_equal [], @cache.values
|
|
|
|
@cache[1] = 1
|
|
assert_equal [1], @cache.values
|
|
|
|
@cache[2] = 2
|
|
assert_equal [1, 2], @cache.values.sort
|
|
end
|
|
|
|
def test_each_key
|
|
assert_equal(@cache, (@cache.each_key {flunk}))
|
|
|
|
@cache[1] = 1
|
|
arr = []
|
|
@cache.each_key {|k| arr << k}
|
|
assert_equal [1], arr
|
|
|
|
@cache[2] = 2
|
|
arr = []
|
|
@cache.each_key {|k| arr << k}
|
|
assert_equal [1, 2], arr.sort
|
|
end
|
|
|
|
def test_each_value
|
|
assert_equal(@cache, (@cache.each_value {flunk}))
|
|
|
|
@cache[1] = 1
|
|
arr = []
|
|
@cache.each_value {|k| arr << k}
|
|
assert_equal [1], arr
|
|
|
|
@cache[2] = 2
|
|
arr = []
|
|
@cache.each_value {|k| arr << k}
|
|
assert_equal [1, 2], arr.sort
|
|
end
|
|
|
|
def test_empty
|
|
assert_equal true, @cache.empty?
|
|
@cache[:a] = 1
|
|
assert_equal false, @cache.empty?
|
|
end
|
|
|
|
def test_options_validation
|
|
assert_valid_options(nil)
|
|
assert_valid_options({})
|
|
assert_valid_options(:foo => :bar)
|
|
end
|
|
|
|
def test_initial_capacity_options_validation
|
|
assert_valid_option(:initial_capacity, nil)
|
|
assert_valid_option(:initial_capacity, 1)
|
|
assert_invalid_option(:initial_capacity, '')
|
|
assert_invalid_option(:initial_capacity, 1.0)
|
|
assert_invalid_option(:initial_capacity, -1)
|
|
end
|
|
|
|
def test_load_factor_options_validation
|
|
assert_valid_option(:load_factor, nil)
|
|
assert_valid_option(:load_factor, 0.01)
|
|
assert_valid_option(:load_factor, 0.75)
|
|
assert_valid_option(:load_factor, 1)
|
|
assert_invalid_option(:load_factor, '')
|
|
assert_invalid_option(:load_factor, 0)
|
|
assert_invalid_option(:load_factor, 1.1)
|
|
assert_invalid_option(:load_factor, 2)
|
|
assert_invalid_option(:load_factor, -1)
|
|
end
|
|
|
|
def test_size
|
|
assert_equal 0, @cache.size
|
|
@cache[:a] = 1
|
|
assert_equal 1, @cache.size
|
|
@cache[:b] = 1
|
|
assert_equal 2, @cache.size
|
|
@cache.delete(:a)
|
|
assert_equal 1, @cache.size
|
|
@cache.delete(:b)
|
|
assert_equal 0, @cache.size
|
|
end
|
|
|
|
def test_get_or_default
|
|
with_or_without_default_proc do
|
|
assert_equal 1, @cache.get_or_default(:a, 1)
|
|
assert_equal nil, @cache.get_or_default(:a, nil)
|
|
assert_equal false, @cache.get_or_default(:a, false)
|
|
assert_equal false, @cache.key?(:a)
|
|
|
|
@cache[:a] = 1
|
|
assert_equal 1, @cache.get_or_default(:a, 2)
|
|
end
|
|
end
|
|
|
|
def test_dup_clone
|
|
[:dup, :clone].each do |meth|
|
|
cache = cache_with_default_proc(:default_value)
|
|
cache[:a] = 1
|
|
dupped = cache.send(meth)
|
|
assert_equal 1, dupped[:a]
|
|
assert_equal 1, dupped.size
|
|
assert_size_change 1, cache do
|
|
assert_no_size_change dupped do
|
|
cache[:b] = 1
|
|
end
|
|
end
|
|
assert_equal false, dupped.key?(:b)
|
|
assert_no_size_change cache do
|
|
assert_size_change -1, dupped do
|
|
dupped.delete(:a)
|
|
end
|
|
end
|
|
assert_equal false, dupped.key?(:a)
|
|
assert_equal true, cache.key?(:a)
|
|
# test default proc
|
|
assert_size_change 1, cache do
|
|
assert_no_size_change dupped do
|
|
assert_equal :default_value, cache[:c]
|
|
assert_equal false, dupped.key?(:c)
|
|
end
|
|
end
|
|
assert_no_size_change cache do
|
|
assert_size_change 1, dupped do
|
|
assert_equal :default_value, dupped[:d]
|
|
assert_equal false, cache.key?(:d)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_is_unfreezable
|
|
assert_raises(NoMethodError) { @cache.freeze }
|
|
end
|
|
|
|
def test_marshal_dump_load
|
|
new_cache = Marshal.load(Marshal.dump(@cache))
|
|
assert_instance_of ThreadSafe::Cache, new_cache
|
|
assert_equal 0, new_cache.size
|
|
@cache[:a] = 1
|
|
new_cache = Marshal.load(Marshal.dump(@cache))
|
|
assert_equal 1, @cache[:a]
|
|
assert_equal 1, new_cache.size
|
|
end
|
|
|
|
def test_marshal_dump_doesnt_work_with_default_proc
|
|
assert_raises(TypeError) do
|
|
Marshal.dump(ThreadSafe::Cache.new {})
|
|
end
|
|
end
|
|
|
|
private
|
|
def with_or_without_default_proc
|
|
yield false
|
|
@cache = ThreadSafe::Cache.new {|h, k| h[k] = :default_value}
|
|
yield true
|
|
end
|
|
|
|
def cache_with_default_proc(default_value = 1)
|
|
ThreadSafe::Cache.new {|cache, k| cache[k] = default_value}
|
|
end
|
|
|
|
def assert_valid_option(option_name, value)
|
|
assert_valid_options(option_name => value)
|
|
end
|
|
|
|
def assert_valid_options(options)
|
|
c = ThreadSafe::Cache.new(options)
|
|
assert_instance_of ThreadSafe::Cache, c
|
|
end
|
|
|
|
def assert_invalid_option(option_name, value)
|
|
assert_invalid_options(option_name => value)
|
|
end
|
|
|
|
def assert_invalid_options(options)
|
|
assert_raises(ArgumentError) { ThreadSafe::Cache.new(options) }
|
|
end
|
|
|
|
def assert_size_change(change, cache = @cache)
|
|
start = cache.size
|
|
yield
|
|
assert_equal change, cache.size - start
|
|
end
|
|
|
|
def assert_no_size_change(cache = @cache, &block)
|
|
assert_size_change(0, cache, &block)
|
|
end
|
|
|
|
def assert_handles_return_lambda(method, key, *args)
|
|
before_had_key = @cache.key?(key)
|
|
before_had_value = before_had_key ? @cache[key] : nil
|
|
|
|
returning_lambda = lambda do
|
|
@cache.send(method, key, *args) { return :direct_return }
|
|
end
|
|
|
|
assert_no_size_change do
|
|
assert_equal(:direct_return, returning_lambda.call)
|
|
assert_equal before_had_key, @cache.key?(key)
|
|
assert_equal before_had_value, @cache[key] if before_had_value
|
|
end
|
|
end
|
|
|
|
class TestException < Exception; end
|
|
def assert_handles_exception(method, key, *args)
|
|
before_had_key = @cache.key?(key)
|
|
before_had_value = before_had_key ? @cache[key] : nil
|
|
|
|
assert_no_size_change do
|
|
assert_raises(TestException) do
|
|
@cache.send(method, key, *args) { raise TestException, '' }
|
|
end
|
|
assert_equal before_had_key, @cache.key?(key)
|
|
assert_equal before_had_value, @cache[key] if before_had_value
|
|
end
|
|
end
|
|
|
|
def assert_compute(key, expected_old_value, expected_result)
|
|
result = @cache.compute(:a) do |old_value|
|
|
assert_equal expected_old_value, old_value
|
|
yield
|
|
end
|
|
assert_equal expected_result, result
|
|
end
|
|
|
|
def assert_merge_pair(key, value, expected_old_value, expected_result)
|
|
result = @cache.merge_pair(key, value) do |old_value|
|
|
assert_equal expected_old_value, old_value
|
|
yield
|
|
end
|
|
assert_equal expected_result, result
|
|
end
|
|
|
|
def assert_collision_resistance(keys)
|
|
keys.each {|k| @cache[k] = k.key}
|
|
10.times do |i|
|
|
size = keys.size
|
|
while i < size
|
|
k = keys[i]
|
|
assert(k.key == @cache.delete(k) && !@cache.key?(k) && (@cache[k] = k.key; @cache[k] == k.key))
|
|
i += 10
|
|
end
|
|
end
|
|
assert(keys.all? {|k| @cache[k] == k.key})
|
|
end
|
|
end
|