sin_lru_redux 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|