sin_lru_redux 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/lint.yml +18 -0
- data/.github/workflows/test.yml +21 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +202 -0
- data/Gemfile +19 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +23 -0
- data/README.md +288 -0
- data/Rakefile +11 -0
- data/bench/bench.rb +46 -0
- data/bench/bench_ttl.rb +35 -0
- data/lib/lru_redux/cache.rb +129 -0
- data/lib/lru_redux/thread_safe_cache.rb +5 -0
- data/lib/lru_redux/ttl/cache.rb +222 -0
- data/lib/lru_redux/ttl/thread_safe_cache.rb +5 -0
- data/lib/lru_redux/ttl.rb +5 -0
- data/lib/lru_redux/util/safe_sync.rb +117 -0
- data/lib/lru_redux/util.rb +3 -0
- data/lib/lru_redux/version.rb +5 -0
- data/lib/lru_redux.rb +7 -0
- data/sin_lru_redux.gemspec +32 -0
- data/test/cache_test.rb +146 -0
- data/test/thread_safe_cache_test.rb +19 -0
- data/test/ttl/cache_test.rb +93 -0
- data/test/ttl/thread_safe_cache_test.rb +20 -0
- metadata +71 -0
data/bench/bench_ttl.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'fast_cache'
|
6
|
+
|
7
|
+
Bundler.require
|
8
|
+
|
9
|
+
# FastCache
|
10
|
+
fast_cache = FastCache::Cache.new(1_000, 5 * 60)
|
11
|
+
|
12
|
+
# LruRedux
|
13
|
+
redux_ttl = LruRedux::TTL::Cache.new(1_000, 5 * 60)
|
14
|
+
redux_ttl_thread_safe = LruRedux::TTL::ThreadSafeCache.new(1_000, 5 * 60)
|
15
|
+
redux_ttl_disabled = LruRedux::TTL::Cache.new(1_000, :none)
|
16
|
+
|
17
|
+
puts '** TTL Benchmarks **'
|
18
|
+
|
19
|
+
Benchmark.bmbm do |bm|
|
20
|
+
bm.report 'FastCache' do
|
21
|
+
1_000_000.times { fast_cache.fetch(rand(2_000)) { :value } } # rubocop:disable Style/RedundantFetchBlock
|
22
|
+
end
|
23
|
+
|
24
|
+
bm.report 'LruRedux::TTL::Cache' do
|
25
|
+
1_000_000.times { redux_ttl.getset(rand(2_000)) { :value } }
|
26
|
+
end
|
27
|
+
|
28
|
+
bm.report 'LruRedux::TTL::ThreadSafeCache' do
|
29
|
+
1_000_000.times { redux_ttl_thread_safe.getset(rand(2_000)) { :value } }
|
30
|
+
end
|
31
|
+
|
32
|
+
bm.report 'LruRedux::TTL::Cache (TTL disabled)' do
|
33
|
+
1_000_000.times { redux_ttl_disabled.getset(rand(2_000)) { :value } }
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LruRedux::Cache
|
4
|
+
def initialize(*args)
|
5
|
+
max_size, ignore_nil, _ = args
|
6
|
+
|
7
|
+
ignore_nil ||= false
|
8
|
+
|
9
|
+
raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
|
10
|
+
raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
|
11
|
+
|
12
|
+
@max_size = max_size
|
13
|
+
@ignore_nil = ignore_nil
|
14
|
+
@data = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def max_size=(max_size)
|
18
|
+
max_size ||= @max_size
|
19
|
+
|
20
|
+
raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
|
21
|
+
|
22
|
+
@max_size = max_size
|
23
|
+
|
24
|
+
@data.shift while @data.size > @max_size
|
25
|
+
end
|
26
|
+
|
27
|
+
def ttl=(_)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def ignore_nil=(ignore_nil)
|
32
|
+
ignore_nil ||= @ignore_nil
|
33
|
+
raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
|
34
|
+
|
35
|
+
@ignore_nil = ignore_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def getset(key)
|
39
|
+
found = true
|
40
|
+
value = @data.delete(key) { found = false }
|
41
|
+
if found
|
42
|
+
@data[key] = value
|
43
|
+
else
|
44
|
+
result = @data[key] = yield
|
45
|
+
@data.shift if @data.length > @max_size
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch(key)
|
51
|
+
found = true
|
52
|
+
value = @data.delete(key) { found = false }
|
53
|
+
if found
|
54
|
+
@data[key] = value
|
55
|
+
else
|
56
|
+
yield if block_given? # rubocop:disable Style/IfInsideElse
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](key)
|
61
|
+
found = true
|
62
|
+
value = @data.delete(key) { found = false }
|
63
|
+
@data[key] = value if found
|
64
|
+
end
|
65
|
+
|
66
|
+
def []=(key, val)
|
67
|
+
@data.delete(key)
|
68
|
+
@data[key] = val
|
69
|
+
@data.shift if @data.length > @max_size
|
70
|
+
val # rubocop:disable Lint/Void
|
71
|
+
end
|
72
|
+
|
73
|
+
def each(&block)
|
74
|
+
array = @data.to_a
|
75
|
+
array.reverse!.each(&block)
|
76
|
+
end
|
77
|
+
|
78
|
+
# used further up the chain, non thread safe each
|
79
|
+
alias_method :each_unsafe, :each
|
80
|
+
|
81
|
+
def to_a
|
82
|
+
array = @data.to_a
|
83
|
+
array.reverse!
|
84
|
+
end
|
85
|
+
|
86
|
+
def values
|
87
|
+
vals = @data.values
|
88
|
+
vals.reverse!
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete(key)
|
92
|
+
@data.delete(key)
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method :evict, :delete
|
96
|
+
|
97
|
+
def key?(key)
|
98
|
+
@data.key?(key)
|
99
|
+
end
|
100
|
+
|
101
|
+
alias_method :has_key?, :key?
|
102
|
+
|
103
|
+
def clear
|
104
|
+
@data.clear
|
105
|
+
end
|
106
|
+
|
107
|
+
def count
|
108
|
+
@data.size
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
def valid_max_size?(max_size)
|
114
|
+
return true if max_size.is_a?(Integer) && max_size >= 1
|
115
|
+
|
116
|
+
false
|
117
|
+
end
|
118
|
+
|
119
|
+
def valid_ignore_nil?(ignore_nil)
|
120
|
+
return true if [true, false].include?(ignore_nil)
|
121
|
+
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
125
|
+
# for cache validation only, ensures all is sound
|
126
|
+
def valid?
|
127
|
+
true
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LruRedux
|
4
|
+
module TTL
|
5
|
+
class Cache
|
6
|
+
attr_reader :max_size, :ttl, :ignore_nil
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
max_size, ttl, ignore_nil = args
|
10
|
+
|
11
|
+
ttl ||= :none
|
12
|
+
ignore_nil ||= false
|
13
|
+
|
14
|
+
raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
|
15
|
+
raise ArgumentError.new(:ttl) unless valid_ttl?(ttl)
|
16
|
+
raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
|
17
|
+
|
18
|
+
@max_size = max_size
|
19
|
+
@ttl = ttl
|
20
|
+
@ignore_nil = ignore_nil
|
21
|
+
@data_lru = {}
|
22
|
+
@data_ttl = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def max_size=(max_size)
|
26
|
+
max_size ||= @max_size
|
27
|
+
|
28
|
+
raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
|
29
|
+
|
30
|
+
@max_size = max_size
|
31
|
+
|
32
|
+
resize
|
33
|
+
end
|
34
|
+
|
35
|
+
def ttl=(ttl)
|
36
|
+
ttl ||= @ttl
|
37
|
+
raise ArgumentError.new(:ttl) unless valid_ttl?(ttl)
|
38
|
+
|
39
|
+
@ttl = ttl
|
40
|
+
|
41
|
+
ttl_evict
|
42
|
+
end
|
43
|
+
|
44
|
+
def ignore_nil=(ignore_nil)
|
45
|
+
ignore_nil ||= @ignore_nil
|
46
|
+
raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
|
47
|
+
|
48
|
+
@ignore_nil = ignore_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def getset(key)
|
52
|
+
ttl_evict
|
53
|
+
|
54
|
+
found = true
|
55
|
+
value = @data_lru.delete(key) { found = false }
|
56
|
+
if found
|
57
|
+
@data_lru[key] = value
|
58
|
+
else
|
59
|
+
result = yield
|
60
|
+
|
61
|
+
if !result.nil? || !@ignore_nil
|
62
|
+
@data_lru[key] = result
|
63
|
+
@data_ttl[key] = Time.now.to_f
|
64
|
+
|
65
|
+
if @data_lru.size > @max_size
|
66
|
+
key, _ = @data_lru.first
|
67
|
+
|
68
|
+
@data_ttl.delete(key)
|
69
|
+
@data_lru.delete(key)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
result
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def fetch(key)
|
78
|
+
ttl_evict
|
79
|
+
|
80
|
+
found = true
|
81
|
+
value = @data_lru.delete(key) { found = false }
|
82
|
+
if found
|
83
|
+
@data_lru[key] = value
|
84
|
+
else
|
85
|
+
yield if block_given? # rubocop:disable Style/IfInsideElse
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def [](key)
|
90
|
+
ttl_evict
|
91
|
+
|
92
|
+
found = true
|
93
|
+
value = @data_lru.delete(key) { found = false }
|
94
|
+
@data_lru[key] = value if found
|
95
|
+
end
|
96
|
+
|
97
|
+
def []=(key, val)
|
98
|
+
ttl_evict
|
99
|
+
|
100
|
+
@data_lru.delete(key)
|
101
|
+
@data_ttl.delete(key)
|
102
|
+
|
103
|
+
@data_lru[key] = val
|
104
|
+
@data_ttl[key] = Time.now.to_f
|
105
|
+
|
106
|
+
if @data_lru.size > @max_size
|
107
|
+
key, _ = @data_lru.first
|
108
|
+
|
109
|
+
@data_ttl.delete(key)
|
110
|
+
@data_lru.delete(key)
|
111
|
+
end
|
112
|
+
|
113
|
+
val # rubocop:disable Lint/Void
|
114
|
+
end
|
115
|
+
|
116
|
+
def each(&block)
|
117
|
+
ttl_evict
|
118
|
+
|
119
|
+
array = @data_lru.to_a
|
120
|
+
array.reverse!.each(&block)
|
121
|
+
end
|
122
|
+
|
123
|
+
# used further up the chain, non thread safe each
|
124
|
+
alias_method :each_unsafe, :each
|
125
|
+
|
126
|
+
def to_a
|
127
|
+
ttl_evict
|
128
|
+
|
129
|
+
array = @data_lru.to_a
|
130
|
+
array.reverse!
|
131
|
+
end
|
132
|
+
|
133
|
+
def values
|
134
|
+
ttl_evict
|
135
|
+
|
136
|
+
vals = @data_lru.values
|
137
|
+
vals.reverse!
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete(key)
|
141
|
+
ttl_evict
|
142
|
+
|
143
|
+
@data_ttl.delete(key)
|
144
|
+
@data_lru.delete(key)
|
145
|
+
end
|
146
|
+
|
147
|
+
alias_method :evict, :delete
|
148
|
+
|
149
|
+
def key?(key)
|
150
|
+
ttl_evict
|
151
|
+
|
152
|
+
@data_lru.key?(key)
|
153
|
+
end
|
154
|
+
|
155
|
+
alias_method :has_key?, :key?
|
156
|
+
|
157
|
+
def clear
|
158
|
+
@data_ttl.clear
|
159
|
+
@data_lru.clear
|
160
|
+
end
|
161
|
+
|
162
|
+
def expire
|
163
|
+
ttl_evict
|
164
|
+
end
|
165
|
+
|
166
|
+
def count
|
167
|
+
@data_lru.size
|
168
|
+
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
def valid_max_size?(max_size)
|
173
|
+
return true if max_size.is_a?(Integer) && max_size >= 1
|
174
|
+
|
175
|
+
false
|
176
|
+
end
|
177
|
+
|
178
|
+
def valid_ttl?(ttl)
|
179
|
+
return true if ttl == :none
|
180
|
+
return true if ttl.is_a?(Numeric) && ttl >= 0
|
181
|
+
|
182
|
+
false
|
183
|
+
end
|
184
|
+
|
185
|
+
def valid_ignore_nil?(ignore_nil)
|
186
|
+
return true if [true, false].include?(ignore_nil)
|
187
|
+
|
188
|
+
false
|
189
|
+
end
|
190
|
+
|
191
|
+
# for cache validation only, ensures all is sound
|
192
|
+
def valid?
|
193
|
+
@data_lru.size == @data_ttl.size
|
194
|
+
end
|
195
|
+
|
196
|
+
def ttl_evict
|
197
|
+
return if @ttl == :none
|
198
|
+
|
199
|
+
ttl_horizon = Time.now.to_f - @ttl
|
200
|
+
key, time = @data_ttl.first
|
201
|
+
|
202
|
+
until time.nil? || time > ttl_horizon
|
203
|
+
@data_ttl.delete(key)
|
204
|
+
@data_lru.delete(key)
|
205
|
+
|
206
|
+
key, time = @data_ttl.first
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def resize
|
211
|
+
ttl_evict
|
212
|
+
|
213
|
+
while @data_lru.size > @max_size
|
214
|
+
key, _ = @data_lru.first
|
215
|
+
|
216
|
+
@data_ttl.delete(key)
|
217
|
+
@data_lru.delete(key)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'monitor'
|
4
|
+
|
5
|
+
module LruRedux
|
6
|
+
module Util
|
7
|
+
module SafeSync
|
8
|
+
include MonitorMixin
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def max_size=(max_size)
|
15
|
+
synchronize do
|
16
|
+
super(max_size)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def ttl=(ttl)
|
21
|
+
synchronize do
|
22
|
+
super(ttl)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ignore_nil=(ignore_nil)
|
27
|
+
synchronize do
|
28
|
+
super(ignore_nil)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def getset(key)
|
33
|
+
synchronize do
|
34
|
+
super(key)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch(key)
|
39
|
+
synchronize do
|
40
|
+
super(key)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](key)
|
45
|
+
synchronize do
|
46
|
+
super(key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def []=(key, value)
|
51
|
+
synchronize do
|
52
|
+
super(key, value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def each
|
57
|
+
synchronize do
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_a
|
63
|
+
synchronize do
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def values
|
69
|
+
synchronize do
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete(key)
|
75
|
+
synchronize do
|
76
|
+
super(key)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def evict(key)
|
81
|
+
synchronize do
|
82
|
+
super(key)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def key?(key)
|
87
|
+
synchronize do
|
88
|
+
super(key)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def has_key?(key) # rubocop:disable Naming/PredicateName
|
93
|
+
synchronize do
|
94
|
+
super(key)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def clear
|
99
|
+
synchronize do
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def count
|
105
|
+
synchronize do
|
106
|
+
super
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def valid?
|
111
|
+
synchronize do
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/lru_redux.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'lru_redux/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'sin_lru_redux'
|
9
|
+
spec.version = LruRedux::VERSION
|
10
|
+
spec.description = 'An efficient implementation of an lru cache'
|
11
|
+
spec.summary = 'An efficient implementation of an lru cache'
|
12
|
+
spec.authors = ['Masahiro']
|
13
|
+
spec.email = ['watanabe@cadenza-tech.com']
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
github_root_uri = 'https://github.com/cadenza-tech/sin_lru_redux'
|
17
|
+
spec.homepage = "#{github_root_uri}/tree/#{spec.version}/#{spec.name}"
|
18
|
+
spec.metadata = {
|
19
|
+
'homepage_uri' => spec.homepage,
|
20
|
+
'source_code_uri' => spec.homepage,
|
21
|
+
'changelog_uri' => "#{github_root_uri}/blob/#{spec.version}#changelog",
|
22
|
+
'bug_tracker_uri' => "#{github_root_uri}/issues",
|
23
|
+
'documentation_uri' => "https://rubydoc.info/gems/#{spec.name}/#{spec.version}",
|
24
|
+
'rubygems_mfa_required' => 'true'
|
25
|
+
}
|
26
|
+
|
27
|
+
spec.required_ruby_version = '>= 2.3.0'
|
28
|
+
|
29
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
30
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
end
|
data/test/cache_test.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lru_redux'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/pride'
|
6
|
+
|
7
|
+
class CacheTest < Minitest::Test
|
8
|
+
def setup
|
9
|
+
@c = LruRedux::Cache.new(3)
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
assert @c.send(:valid?) # rubocop:disable Minitest/AssertionInLifecycleHook
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_drops_old
|
17
|
+
@c[:a] = 1
|
18
|
+
@c[:b] = 2
|
19
|
+
@c[:c] = 3
|
20
|
+
@c[:d] = 4
|
21
|
+
|
22
|
+
assert_equal [[:d, 4], [:c, 3], [:b, 2]], @c.to_a
|
23
|
+
assert_nil @c[:a]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_fetch
|
27
|
+
@c[:a] = nil
|
28
|
+
@c[:b] = 2
|
29
|
+
|
30
|
+
assert_nil @c.fetch(:a) { 1 } # rubocop:disable Style/RedundantFetchBlock
|
31
|
+
assert_equal 3, @c.fetch(:c) { 3 } # rubocop:disable Style/RedundantFetchBlock
|
32
|
+
assert_equal [[:a, nil], [:b, 2]], @c.to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_getset # rubocop:disable Minitest/MultipleAssertions
|
36
|
+
assert_equal 1, @c.getset(:a) { 1 }
|
37
|
+
@c.getset(:b) { 2 }
|
38
|
+
|
39
|
+
assert_equal 1, @c.getset(:a) { 11 }
|
40
|
+
@c.getset(:c) { 3 }
|
41
|
+
|
42
|
+
assert_equal 4, @c.getset(:d) { 4 }
|
43
|
+
assert_equal [[:d, 4], [:c, 3], [:a, 1]], @c.to_a
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_pushes_lru_to_back
|
47
|
+
@c[:a] = 1
|
48
|
+
@c[:b] = 2
|
49
|
+
@c[:c] = 3
|
50
|
+
|
51
|
+
@c[:a]
|
52
|
+
@c[:d] = 4
|
53
|
+
|
54
|
+
assert_equal [[:d, 4], [:a, 1], [:c, 3]], @c.to_a
|
55
|
+
assert_nil @c[:b]
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_delete # rubocop:disable Minitest/MultipleAssertions
|
59
|
+
@c[:a] = 1
|
60
|
+
@c[:b] = 2
|
61
|
+
@c[:c] = 3
|
62
|
+
@c.delete(:a)
|
63
|
+
|
64
|
+
assert_equal [[:c, 3], [:b, 2]], @c.to_a
|
65
|
+
assert_nil @c[:a]
|
66
|
+
|
67
|
+
# Regression test for a bug in the legacy delete method
|
68
|
+
@c.delete(:b)
|
69
|
+
@c[:d] = 4
|
70
|
+
@c[:e] = 5
|
71
|
+
@c[:f] = 6
|
72
|
+
|
73
|
+
assert_equal [[:f, 6], [:e, 5], [:d, 4]], @c.to_a
|
74
|
+
assert_nil @c[:b]
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_key?
|
78
|
+
@c[:a] = 1
|
79
|
+
@c[:b] = 2
|
80
|
+
|
81
|
+
assert @c.key?(:a)
|
82
|
+
refute @c.key?(:c)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_update
|
86
|
+
@c[:a] = 1
|
87
|
+
@c[:b] = 2
|
88
|
+
@c[:c] = 3
|
89
|
+
@c[:a] = 99
|
90
|
+
|
91
|
+
assert_equal [[:a, 99], [:c, 3], [:b, 2]], @c.to_a
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_clear
|
95
|
+
@c[:a] = 1
|
96
|
+
@c[:b] = 2
|
97
|
+
@c[:c] = 3
|
98
|
+
|
99
|
+
@c.clear
|
100
|
+
|
101
|
+
assert_empty @c.to_a
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_grow
|
105
|
+
@c[:a] = 1
|
106
|
+
@c[:b] = 2
|
107
|
+
@c[:c] = 3
|
108
|
+
@c.max_size = 4
|
109
|
+
@c[:d] = 4
|
110
|
+
|
111
|
+
assert_equal [[:d, 4], [:c, 3], [:b, 2], [:a, 1]], @c.to_a
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_shrink
|
115
|
+
@c[:a] = 1
|
116
|
+
@c[:b] = 2
|
117
|
+
@c[:c] = 3
|
118
|
+
@c.max_size = 1
|
119
|
+
|
120
|
+
assert_equal [[:c, 3]], @c.to_a
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_each
|
124
|
+
@c.max_size = 2
|
125
|
+
@c[:a] = 1
|
126
|
+
@c[:b] = 2
|
127
|
+
@c[:c] = 3
|
128
|
+
|
129
|
+
pairs = []
|
130
|
+
@c.each do |pair| # rubocop:disable Style/MapIntoArray
|
131
|
+
pairs << pair
|
132
|
+
end
|
133
|
+
|
134
|
+
assert_equal [[:c, 3], [:b, 2]], pairs
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_values
|
138
|
+
@c[:a] = 1
|
139
|
+
@c[:b] = 2
|
140
|
+
@c[:c] = 3
|
141
|
+
@c[:d] = 4
|
142
|
+
|
143
|
+
assert_equal [4, 3, 2], @c.values
|
144
|
+
assert_nil @c[:a]
|
145
|
+
end
|
146
|
+
end
|