OLD | NEW |
1 // Copyright 2014 Google Inc. All Rights Reserved. | 1 // Copyright 2014 Google Inc. All Rights Reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 library quiver.collection.lru_map_test; | 15 library quiver.collection.lru_map_test; |
16 | 16 |
17 import 'package:quiver/collection.dart'; | 17 import 'package:quiver/collection.dart'; |
18 import 'package:test/test.dart'; | 18 import 'package:test/test.dart'; |
19 | 19 |
20 void main() { | 20 void main() { |
21 group('LruMap', () { | 21 group('LruMap', () { |
22 /// A map that will be initialize by individual tests. | 22 /// A map that will be initialize by individual tests. |
23 LruMap<String, String> lruMap; | 23 LruMap<String, String> lruMap; |
24 | 24 |
25 test('the length property reflects how many keys are in the map', () { | 25 test('the length property reflects how many keys are in the map', () { |
26 lruMap = new LruMap(); | 26 lruMap = new LruMap(); |
27 expect(lruMap, hasLength(0)); | 27 expect(lruMap, hasLength(0)); |
28 | 28 |
29 lruMap.addAll({ | 29 lruMap.addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
30 'A': 'Alpha', | |
31 'B': 'Beta', | |
32 'C': 'Charlie' | |
33 }); | |
34 expect(lruMap, hasLength(3)); | 30 expect(lruMap, hasLength(3)); |
35 }); | 31 }); |
36 | 32 |
37 test('accessing keys causes them to be promoted', () { | 33 test('accessing keys causes them to be promoted', () { |
38 lruMap = new LruMap()..addAll({ | 34 lruMap = new LruMap() |
39 'A': 'Alpha', | 35 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
40 'B': 'Beta', | |
41 'C': 'Charlie' | |
42 }); | |
43 | 36 |
44 expect(lruMap.keys.toList(), ['C', 'B', 'A']); | 37 expect(lruMap.keys.toList(), ['C', 'B', 'A']); |
45 | 38 |
46 lruMap['B']; | 39 lruMap['B']; |
47 | 40 |
48 // In a LRU cache, the first key is the one that will be removed if the | 41 // In a LRU cache, the first key is the one that will be removed if the |
49 // capacity is reached, so adding keys to the end is considered to be a | 42 // capacity is reached, so adding keys to the end is considered to be a |
50 // 'promotion'. | 43 // 'promotion'. |
51 expect(lruMap.keys.toList(), ['B', 'C', 'A']); | 44 expect(lruMap.keys.toList(), ['B', 'C', 'A']); |
52 }); | 45 }); |
53 | 46 |
54 test('new keys are added at the beginning', () { | 47 test('new keys are added at the beginning', () { |
55 lruMap = new LruMap()..addAll({ | 48 lruMap = new LruMap() |
56 'A': 'Alpha', | 49 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
57 'B': 'Beta', | |
58 'C': 'Charlie' | |
59 }); | |
60 | 50 |
61 lruMap['D'] = 'Delta'; | 51 lruMap['D'] = 'Delta'; |
62 expect(lruMap.keys.toList(), ['D', 'C', 'B', 'A']); | 52 expect(lruMap.keys.toList(), ['D', 'C', 'B', 'A']); |
63 }); | 53 }); |
64 | 54 |
65 test('setting values on existing keys works, and promotes the key', () { | 55 test('setting values on existing keys works, and promotes the key', () { |
66 lruMap = new LruMap()..addAll({ | 56 lruMap = new LruMap() |
67 'A': 'Alpha', | 57 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
68 'B': 'Beta', | |
69 'C': 'Charlie' | |
70 }); | |
71 | 58 |
72 lruMap['B'] = 'Bravo'; | 59 lruMap['B'] = 'Bravo'; |
73 expect(lruMap.keys.toList(), ['B', 'C', 'A']); | 60 expect(lruMap.keys.toList(), ['B', 'C', 'A']); |
74 expect(lruMap['B'], 'Bravo'); | 61 expect(lruMap['B'], 'Bravo'); |
75 }); | 62 }); |
76 | 63 |
77 test('the least recently used key is evicted when capacity hit', () { | 64 test('the least recently used key is evicted when capacity hit', () { |
78 lruMap = new LruMap(maximumSize: 3)..addAll({ | 65 lruMap = new LruMap(maximumSize: 3) |
79 'A': 'Alpha', | 66 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
80 'B': 'Beta', | |
81 'C': 'Charlie' | |
82 }); | |
83 | 67 |
84 lruMap['D'] = 'Delta'; | 68 lruMap['D'] = 'Delta'; |
85 expect(lruMap.keys.toList(), ['D', 'C', 'B']); | 69 expect(lruMap.keys.toList(), ['D', 'C', 'B']); |
86 }); | 70 }); |
87 | 71 |
88 test('setting maximum size evicts keys until the size is met', () { | 72 test('setting maximum size evicts keys until the size is met', () { |
89 lruMap = new LruMap(maximumSize: 5)..addAll({ | 73 lruMap = new LruMap(maximumSize: 5) |
| 74 ..addAll({ |
90 'A': 'Alpha', | 75 'A': 'Alpha', |
91 'B': 'Beta', | 76 'B': 'Beta', |
92 'C': 'Charlie', | 77 'C': 'Charlie', |
93 'D': 'Delta', | 78 'D': 'Delta', |
94 'E': 'Epsilon' | 79 'E': 'Epsilon' |
95 }); | 80 }); |
96 | 81 |
97 lruMap.maximumSize = 3; | 82 lruMap.maximumSize = 3; |
98 expect(lruMap.keys.toList(), ['E', 'D', 'C']); | 83 expect(lruMap.keys.toList(), ['E', 'D', 'C']); |
99 }); | 84 }); |
100 | 85 |
101 test('accessing the `keys` collection does not affect position', () { | 86 test('accessing the `keys` collection does not affect position', () { |
102 lruMap = new LruMap()..addAll({ | 87 lruMap = new LruMap() |
103 'A': 'Alpha', | 88 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
104 'B': 'Beta', | |
105 'C': 'Charlie' | |
106 }); | |
107 | 89 |
108 expect(lruMap.keys.toList(), ['C', 'B', 'A']); | 90 expect(lruMap.keys.toList(), ['C', 'B', 'A']); |
109 | 91 |
110 lruMap.keys.forEach((_){}); | 92 lruMap.keys.forEach((_) {}); |
111 lruMap.keys.forEach((_){}); | 93 lruMap.keys.forEach((_) {}); |
112 | 94 |
113 expect(lruMap.keys.toList(), ['C', 'B', 'A']); | 95 expect(lruMap.keys.toList(), ['C', 'B', 'A']); |
114 }); | 96 }); |
115 | 97 |
116 test('accessing the `values` collection does not affect position', () { | 98 test('accessing the `values` collection does not affect position', () { |
117 lruMap = new LruMap()..addAll({ | 99 lruMap = new LruMap() |
118 'A': 'Alpha', | 100 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
119 'B': 'Beta', | |
120 'C': 'Charlie' | |
121 }); | |
122 | 101 |
123 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); | 102 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); |
124 | 103 |
125 lruMap.values.forEach((_){}); | 104 lruMap.values.forEach((_) {}); |
126 lruMap.values.forEach((_){}); | 105 lruMap.values.forEach((_) {}); |
127 | 106 |
128 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); | 107 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); |
129 }); | 108 }); |
130 | 109 |
131 test('clearing removes all keys and values', () { | 110 test('clearing removes all keys and values', () { |
132 lruMap = new LruMap()..addAll({ | 111 lruMap = new LruMap() |
133 'A': 'Alpha', | 112 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
134 'B': 'Beta', | |
135 'C': 'Charlie' | |
136 }); | |
137 | 113 |
138 expect(lruMap.isNotEmpty, isTrue); | 114 expect(lruMap.isNotEmpty, isTrue); |
139 expect(lruMap.keys.isNotEmpty, isTrue); | 115 expect(lruMap.keys.isNotEmpty, isTrue); |
140 expect(lruMap.values.isNotEmpty, isTrue); | 116 expect(lruMap.values.isNotEmpty, isTrue); |
141 | 117 |
142 lruMap.clear(); | 118 lruMap.clear(); |
143 | 119 |
144 expect(lruMap.isEmpty, isTrue); | 120 expect(lruMap.isEmpty, isTrue); |
145 expect(lruMap.keys.isEmpty, isTrue); | 121 expect(lruMap.keys.isEmpty, isTrue); |
146 expect(lruMap.values.isEmpty, isTrue); | 122 expect(lruMap.values.isEmpty, isTrue); |
147 }); | 123 }); |
148 | 124 |
149 test('`containsKey` returns true if the key is in the map', () { | 125 test('`containsKey` returns true if the key is in the map', () { |
150 lruMap = new LruMap()..addAll({ | 126 lruMap = new LruMap() |
151 'A': 'Alpha', | 127 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
152 'B': 'Beta', | |
153 'C': 'Charlie' | |
154 }); | |
155 | 128 |
156 expect(lruMap.containsKey('A'), isTrue); | 129 expect(lruMap.containsKey('A'), isTrue); |
157 expect(lruMap.containsKey('D'), isFalse); | 130 expect(lruMap.containsKey('D'), isFalse); |
158 }); | 131 }); |
159 | 132 |
160 test('`containsValue` returns true if the value is in the map', () { | 133 test('`containsValue` returns true if the value is in the map', () { |
161 lruMap = new LruMap()..addAll({ | 134 lruMap = new LruMap() |
162 'A': 'Alpha', | 135 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
163 'B': 'Beta', | |
164 'C': 'Charlie' | |
165 }); | |
166 | 136 |
167 expect(lruMap.containsValue('Alpha'), isTrue); | 137 expect(lruMap.containsValue('Alpha'), isTrue); |
168 expect(lruMap.containsValue('Delta'), isFalse); | 138 expect(lruMap.containsValue('Delta'), isFalse); |
169 }); | 139 }); |
170 | 140 |
171 test('`forEach` returns all key-value pairs without modifying order', () { | 141 test('`forEach` returns all key-value pairs without modifying order', () { |
172 final keys = []; | 142 final keys = []; |
173 final values = []; | 143 final values = []; |
174 | 144 |
175 lruMap = new LruMap()..addAll({ | 145 lruMap = new LruMap() |
176 'A': 'Alpha', | 146 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
177 'B': 'Beta', | |
178 'C': 'Charlie' | |
179 }); | |
180 | 147 |
181 expect(lruMap.keys.toList(), ['C', 'B', 'A']); | 148 expect(lruMap.keys.toList(), ['C', 'B', 'A']); |
182 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); | 149 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); |
183 | 150 |
184 lruMap.forEach((key, value) { | 151 lruMap.forEach((key, value) { |
185 keys.add(key); | 152 keys.add(key); |
186 values.add(value); | 153 values.add(value); |
187 }); | 154 }); |
188 | 155 |
189 expect(keys, ['C', 'B', 'A']); | 156 expect(keys, ['C', 'B', 'A']); |
190 expect(values, ['Charlie', 'Beta', 'Alpha']); | 157 expect(values, ['Charlie', 'Beta', 'Alpha']); |
191 expect(lruMap.keys.toList(), ['C', 'B', 'A']); | 158 expect(lruMap.keys.toList(), ['C', 'B', 'A']); |
192 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); | 159 expect(lruMap.values.toList(), ['Charlie', 'Beta', 'Alpha']); |
193 }); | 160 }); |
194 | 161 |
195 group('`remove`', () { | 162 group('`remove`', () { |
196 setUp(() { | 163 setUp(() { |
197 lruMap = new LruMap()..addAll({ | 164 lruMap = new LruMap() |
198 'A': 'Alpha', | 165 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
199 'B': 'Beta', | |
200 'C': 'Charlie' | |
201 }); | |
202 }); | 166 }); |
203 | 167 |
204 test('returns the value associated with a key, if it exists', () { | 168 test('returns the value associated with a key, if it exists', () { |
205 expect(lruMap.remove('A'), 'Alpha'); | 169 expect(lruMap.remove('A'), 'Alpha'); |
206 }); | 170 }); |
207 | 171 |
208 test('returns null if the provided key does not exist', () { | 172 test('returns null if the provided key does not exist', () { |
209 expect(lruMap.remove('D'), isNull); | 173 expect(lruMap.remove('D'), isNull); |
210 }); | 174 }); |
211 | 175 |
212 test('can remove the head', () { | 176 test('can remove the head', () { |
213 lruMap.remove('C'); | 177 lruMap.remove('C'); |
214 expect(lruMap.keys.toList(), ['B', 'A']); | 178 expect(lruMap.keys.toList(), ['B', 'A']); |
215 }); | 179 }); |
216 | 180 |
217 test('can remove the tail', () { | 181 test('can remove the tail', () { |
218 lruMap.remove('A'); | 182 lruMap.remove('A'); |
219 expect(lruMap.keys.toList(), ['C', 'B']); | 183 expect(lruMap.keys.toList(), ['C', 'B']); |
220 }); | 184 }); |
221 | 185 |
222 test('can remove a middle entry', () { | 186 test('can remove a middle entry', () { |
223 lruMap.remove('B'); | 187 lruMap.remove('B'); |
224 expect(lruMap.keys.toList(), ['C', 'A']); | 188 expect(lruMap.keys.toList(), ['C', 'A']); |
225 }); | 189 }); |
226 }); | 190 }); |
227 | 191 |
228 group('`putIfAbsent`', () { | 192 group('`putIfAbsent`', () { |
229 setUp(() { | 193 setUp(() { |
230 lruMap = new LruMap()..addAll({ | 194 lruMap = new LruMap() |
231 'A': 'Alpha', | 195 ..addAll({'A': 'Alpha', 'B': 'Beta', 'C': 'Charlie'}); |
232 'B': 'Beta', | |
233 'C': 'Charlie' | |
234 }); | |
235 }); | 196 }); |
236 | 197 |
237 test('adds an item if it does not exist, and moves it to the MRU', () { | 198 test('adds an item if it does not exist, and moves it to the MRU', () { |
238 expect(lruMap.putIfAbsent('D', () => 'Delta'), 'Delta'); | 199 expect(lruMap.putIfAbsent('D', () => 'Delta'), 'Delta'); |
239 expect(lruMap.keys.toList(), ['D', 'C', 'B', 'A']); | 200 expect(lruMap.keys.toList(), ['D', 'C', 'B', 'A']); |
240 }); | 201 }); |
241 | 202 |
242 test('does not add an item if it exists, but does promote it to MRU', () { | 203 test('does not add an item if it exists, but does promote it to MRU', () { |
243 expect(lruMap.putIfAbsent('B', () => throw 'Oops!'), 'Beta'); | 204 expect(lruMap.putIfAbsent('B', () => throw 'Oops!'), 'Beta'); |
244 expect(lruMap.keys.toList(), ['B', 'C', 'A']); | 205 expect(lruMap.keys.toList(), ['B', 'C', 'A']); |
245 }); | 206 }); |
246 | 207 |
247 test('removes the LRU item if `maximumSize` exceeded', () { | 208 test('removes the LRU item if `maximumSize` exceeded', () { |
248 lruMap.maximumSize = 3; | 209 lruMap.maximumSize = 3; |
249 expect(lruMap.putIfAbsent('D', () => 'Delta'), 'Delta'); | 210 expect(lruMap.putIfAbsent('D', () => 'Delta'), 'Delta'); |
250 expect(lruMap.keys.toList(), ['D', 'C', 'B']); | 211 expect(lruMap.keys.toList(), ['D', 'C', 'B']); |
251 }); | 212 }); |
252 }); | 213 }); |
253 }); | 214 }); |
254 } | 215 } |
OLD | NEW |