]>
Commit | Line | Data |
---|---|---|
f3c0d7a5 A |
1 | // © 2016 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html | |
b331163b A |
3 | /* |
4 | ******************************************************************************* | |
2ca993e8 | 5 | * Copyright (C) 2015, International Business Machines Corporation and * |
b331163b A |
6 | * others. All Rights Reserved. * |
7 | ******************************************************************************* | |
8 | * | |
9 | * File UNIFIEDCACHETEST.CPP | |
10 | * | |
11 | ******************************************************************************** | |
12 | */ | |
13 | #include "cstring.h" | |
14 | #include "intltest.h" | |
15 | #include "unifiedcache.h" | |
2ca993e8 | 16 | #include "unicode/datefmt.h" |
b331163b A |
17 | |
18 | class UCTItem : public SharedObject { | |
19 | public: | |
20 | char *value; | |
21 | UCTItem(const char *x) : value(NULL) { | |
22 | value = uprv_strdup(x); | |
23 | } | |
24 | virtual ~UCTItem() { | |
25 | uprv_free(value); | |
26 | } | |
27 | }; | |
28 | ||
29 | class UCTItem2 : public SharedObject { | |
30 | }; | |
31 | ||
32 | U_NAMESPACE_BEGIN | |
33 | ||
34 | template<> U_EXPORT | |
35 | const UCTItem *LocaleCacheKey<UCTItem>::createObject( | |
2ca993e8 A |
36 | const void *context, UErrorCode &status) const { |
37 | const UnifiedCache *cacheContext = (const UnifiedCache *) context; | |
b331163b A |
38 | if (uprv_strcmp(fLoc.getName(), "zh") == 0) { |
39 | status = U_MISSING_RESOURCE_ERROR; | |
40 | return NULL; | |
41 | } | |
42 | if (uprv_strcmp(fLoc.getLanguage(), fLoc.getName()) != 0) { | |
43 | const UCTItem *item = NULL; | |
2ca993e8 A |
44 | if (cacheContext == NULL) { |
45 | UnifiedCache::getByLocale(fLoc.getLanguage(), item, status); | |
46 | } else { | |
47 | cacheContext->get(LocaleCacheKey<UCTItem>(fLoc.getLanguage()), item, status); | |
48 | } | |
b331163b A |
49 | if (U_FAILURE(status)) { |
50 | return NULL; | |
51 | } | |
52 | return item; | |
53 | } | |
54 | UCTItem *result = new UCTItem(fLoc.getName()); | |
55 | result->addRef(); | |
56 | return result; | |
57 | } | |
58 | ||
59 | template<> U_EXPORT | |
60 | const UCTItem2 *LocaleCacheKey<UCTItem2>::createObject( | |
61 | const void * /*unused*/, UErrorCode & /*status*/) const { | |
62 | return NULL; | |
63 | } | |
64 | ||
65 | U_NAMESPACE_END | |
66 | ||
67 | ||
68 | class UnifiedCacheTest : public IntlTest { | |
69 | public: | |
70 | UnifiedCacheTest() { | |
71 | } | |
72 | void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=0); | |
73 | private: | |
2ca993e8 A |
74 | void TestEvictionPolicy(); |
75 | void TestBounded(); | |
b331163b A |
76 | void TestBasic(); |
77 | void TestError(); | |
78 | void TestHashEquals(); | |
2ca993e8 | 79 | void TestEvictionUnderStress(); |
b331163b A |
80 | }; |
81 | ||
82 | void UnifiedCacheTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/) { | |
83 | TESTCASE_AUTO_BEGIN; | |
2ca993e8 A |
84 | TESTCASE_AUTO(TestEvictionPolicy); |
85 | TESTCASE_AUTO(TestBounded); | |
b331163b A |
86 | TESTCASE_AUTO(TestBasic); |
87 | TESTCASE_AUTO(TestError); | |
88 | TESTCASE_AUTO(TestHashEquals); | |
2ca993e8 | 89 | TESTCASE_AUTO(TestEvictionUnderStress); |
b331163b A |
90 | TESTCASE_AUTO_END; |
91 | } | |
92 | ||
2ca993e8 A |
93 | void UnifiedCacheTest::TestEvictionUnderStress() { |
94 | #if !UCONFIG_NO_FORMATTING | |
95 | int32_t localeCount; | |
96 | const Locale *locales = DateFormat::getAvailableLocales(localeCount); | |
97 | UErrorCode status = U_ZERO_ERROR; | |
98 | const UnifiedCache *cache = UnifiedCache::getInstance(status); | |
99 | int64_t evictedCountBefore = cache->autoEvictedCount(); | |
100 | for (int32_t i = 0; i < localeCount; ++i) { | |
101 | LocalPointer<DateFormat> ptr(DateFormat::createInstanceForSkeleton("yMd", locales[i], status)); | |
102 | } | |
103 | int64_t evictedCountAfter = cache->autoEvictedCount(); | |
104 | if (evictedCountBefore == evictedCountAfter) { | |
105 | dataerrln("%s:%d Items should have been evicted from cache", | |
106 | __FILE__, __LINE__); | |
107 | } | |
108 | #endif /* #if !UCONFIG_NO_FORMATTING */ | |
109 | } | |
110 | ||
111 | void UnifiedCacheTest::TestEvictionPolicy() { | |
112 | UErrorCode status = U_ZERO_ERROR; | |
113 | ||
114 | // We have to call this first or else calling the UnifiedCache | |
115 | // ctor will fail. This is by design to deter clients from using the | |
116 | // cache API incorrectly by creating their own cache instances. | |
117 | UnifiedCache::getInstance(status); | |
118 | ||
119 | // We create our own local UnifiedCache instance to ensure we have | |
120 | // complete control over it. Real clients should never ever create | |
121 | // their own cache! | |
122 | UnifiedCache cache(status); | |
123 | assertSuccess("", status); | |
124 | ||
125 | // Don't allow unused entries to exeed more than 100% of in use entries. | |
126 | cache.setEvictionPolicy(0, 100, status); | |
127 | ||
128 | static const char *locales[] = { | |
129 | "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", | |
130 | "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"}; | |
131 | ||
132 | const UCTItem *usedReferences[] = {NULL, NULL, NULL, NULL, NULL}; | |
133 | const UCTItem *unusedReference = NULL; | |
134 | ||
135 | // Add 5 in-use entries | |
136 | for (int32_t i = 0; i < UPRV_LENGTHOF(usedReferences); i++) { | |
137 | cache.get( | |
138 | LocaleCacheKey<UCTItem>(locales[i]), | |
139 | &cache, | |
140 | usedReferences[i], | |
141 | status); | |
142 | } | |
143 | ||
144 | // Add 10 not in use entries. | |
145 | for (int32_t i = 0; i < 10; ++i) { | |
146 | cache.get( | |
147 | LocaleCacheKey<UCTItem>( | |
148 | locales[i + UPRV_LENGTHOF(usedReferences)]), | |
149 | &cache, | |
150 | unusedReference, | |
151 | status); | |
152 | } | |
153 | unusedReference->removeRef(); | |
154 | ||
155 | // unused count not to exeed in use count | |
0f5d89e8 A |
156 | assertEquals("T1", UPRV_LENGTHOF(usedReferences), cache.unusedCount()); |
157 | assertEquals("T2", 2*UPRV_LENGTHOF(usedReferences), cache.keyCount()); | |
2ca993e8 A |
158 | |
159 | // Free up those used entries. | |
160 | for (int32_t i = 0; i < UPRV_LENGTHOF(usedReferences); i++) { | |
161 | usedReferences[i]->removeRef(); | |
162 | } | |
163 | ||
164 | // This should free up all cache items | |
0f5d89e8 | 165 | assertEquals("T3", 0, cache.keyCount()); |
2ca993e8 | 166 | |
0f5d89e8 | 167 | assertSuccess("T4", status); |
2ca993e8 A |
168 | } |
169 | ||
170 | ||
171 | ||
172 | void UnifiedCacheTest::TestBounded() { | |
173 | UErrorCode status = U_ZERO_ERROR; | |
174 | ||
175 | // We have to call this first or else calling the UnifiedCache | |
176 | // ctor will fail. This is by design to deter clients from using the | |
177 | // cache API incorrectly by creating their own cache instances. | |
178 | UnifiedCache::getInstance(status); | |
179 | ||
180 | // We create our own local UnifiedCache instance to ensure we have | |
181 | // complete control over it. Real clients should never ever create | |
182 | // their own cache! | |
183 | UnifiedCache cache(status); | |
0f5d89e8 | 184 | assertSuccess("T0", status); |
2ca993e8 A |
185 | |
186 | // Maximum unused count is 3. | |
187 | cache.setEvictionPolicy(3, 0, status); | |
188 | ||
189 | // Our cache will hold up to 3 unused key-value pairs | |
190 | // We test the following invariants: | |
191 | // 1. unusedCount <= 3 | |
192 | // 2. cache->get(X) always returns the same reference as long as caller | |
193 | // already holds references to that same object. | |
194 | ||
195 | // We first add 5 key-value pairs with two distinct values, "en" and "fr" | |
196 | // keeping all those references. | |
197 | ||
198 | const UCTItem *en = NULL; | |
199 | const UCTItem *enGb = NULL; | |
200 | const UCTItem *enUs = NULL; | |
201 | const UCTItem *fr = NULL; | |
202 | const UCTItem *frFr = NULL; | |
203 | cache.get(LocaleCacheKey<UCTItem>("en_US"), &cache, enUs, status); | |
204 | cache.get(LocaleCacheKey<UCTItem>("en"), &cache, en, status); | |
0f5d89e8 | 205 | assertEquals("T1", 1, cache.unusedCount()); |
2ca993e8 A |
206 | cache.get(LocaleCacheKey<UCTItem>("en_GB"), &cache, enGb, status); |
207 | cache.get(LocaleCacheKey<UCTItem>("fr_FR"), &cache, frFr, status); | |
208 | cache.get(LocaleCacheKey<UCTItem>("fr"), &cache, fr, status); | |
209 | ||
210 | // Client holds two unique references, "en" and "fr" the other three | |
211 | // entries are eligible for eviction. | |
0f5d89e8 A |
212 | assertEquals("T2", 3, cache.unusedCount()); |
213 | assertEquals("T3", 5, cache.keyCount()); | |
2ca993e8 A |
214 | |
215 | // Exercise cache more but don't hold the references except for | |
216 | // the last one. At the end of this, we will hold references to one | |
217 | // additional distinct value, so we will have references to 3 distinct | |
218 | // values. | |
219 | const UCTItem *throwAway = NULL; | |
220 | cache.get(LocaleCacheKey<UCTItem>("zn_AA"), &cache, throwAway, status); | |
221 | cache.get(LocaleCacheKey<UCTItem>("sr_AA"), &cache, throwAway, status); | |
222 | cache.get(LocaleCacheKey<UCTItem>("de_AU"), &cache, throwAway, status); | |
223 | ||
224 | const UCTItem *deAu(throwAway); | |
225 | deAu->addRef(); | |
226 | ||
227 | // Client holds three unique references, "en", "fr", "de" although we | |
228 | // could have a total of 8 entries in the cache maxUnusedCount == 3 | |
229 | // so we have only 6 entries. | |
0f5d89e8 A |
230 | assertEquals("T4", 3, cache.unusedCount()); |
231 | assertEquals("T5", 6, cache.keyCount()); | |
2ca993e8 A |
232 | |
233 | // For all the references we have, cache must continue to return | |
234 | // those same references (#2) | |
235 | ||
236 | cache.get(LocaleCacheKey<UCTItem>("en"), &cache, throwAway, status); | |
237 | if (throwAway != en) { | |
0f5d89e8 | 238 | errln("T6: Expected en to resolve to the same object."); |
2ca993e8 A |
239 | } |
240 | cache.get(LocaleCacheKey<UCTItem>("en_US"), &cache, throwAway, status); | |
241 | if (throwAway != enUs) { | |
0f5d89e8 | 242 | errln("T7: Expected enUs to resolve to the same object."); |
2ca993e8 A |
243 | } |
244 | cache.get(LocaleCacheKey<UCTItem>("en_GB"), &cache, throwAway, status); | |
245 | if (throwAway != enGb) { | |
0f5d89e8 | 246 | errln("T8: Expected enGb to resolve to the same object."); |
2ca993e8 A |
247 | } |
248 | cache.get(LocaleCacheKey<UCTItem>("fr_FR"), &cache, throwAway, status); | |
249 | if (throwAway != frFr) { | |
0f5d89e8 | 250 | errln("T9: Expected frFr to resolve to the same object."); |
2ca993e8 A |
251 | } |
252 | cache.get(LocaleCacheKey<UCTItem>("fr_FR"), &cache, throwAway, status); | |
253 | cache.get(LocaleCacheKey<UCTItem>("fr"), &cache, throwAway, status); | |
254 | if (throwAway != fr) { | |
0f5d89e8 | 255 | errln("T10: Expected fr to resolve to the same object."); |
2ca993e8 A |
256 | } |
257 | cache.get(LocaleCacheKey<UCTItem>("de_AU"), &cache, throwAway, status); | |
258 | if (throwAway != deAu) { | |
0f5d89e8 | 259 | errln("T11: Expected deAu to resolve to the same object."); |
2ca993e8 A |
260 | } |
261 | ||
0f5d89e8 A |
262 | assertEquals("T12", 3, cache.unusedCount()); |
263 | assertEquals("T13", 6, cache.keyCount()); | |
2ca993e8 A |
264 | |
265 | // Now we hold a references to two more distinct values. Cache size | |
266 | // should grow to 8. | |
267 | const UCTItem *es = NULL; | |
268 | const UCTItem *ru = NULL; | |
269 | cache.get(LocaleCacheKey<UCTItem>("es"), &cache, es, status); | |
270 | cache.get(LocaleCacheKey<UCTItem>("ru"), &cache, ru, status); | |
0f5d89e8 A |
271 | assertEquals("T14", 3, cache.unusedCount()); |
272 | assertEquals("T15", 8, cache.keyCount()); | |
2ca993e8 A |
273 | |
274 | // Now release all the references we hold except for | |
275 | // es, ru, and en | |
276 | SharedObject::clearPtr(enGb); | |
277 | SharedObject::clearPtr(enUs); | |
278 | SharedObject::clearPtr(fr); | |
279 | SharedObject::clearPtr(frFr); | |
280 | SharedObject::clearPtr(deAu); | |
281 | SharedObject::clearPtr(es); | |
282 | SharedObject::clearPtr(ru); | |
283 | SharedObject::clearPtr(en); | |
284 | SharedObject::clearPtr(throwAway); | |
285 | ||
286 | // Size of cache should magically drop to 3. | |
0f5d89e8 A |
287 | assertEquals("T16", 3, cache.unusedCount()); |
288 | assertEquals("T17", 3, cache.keyCount()); | |
2ca993e8 A |
289 | |
290 | // Be sure nothing happens setting the eviction policy in the middle of | |
291 | // a run. | |
292 | cache.setEvictionPolicy(3, 0, status); | |
0f5d89e8 | 293 | assertSuccess("T18", status); |
2ca993e8 A |
294 | |
295 | } | |
296 | ||
b331163b A |
297 | void UnifiedCacheTest::TestBasic() { |
298 | UErrorCode status = U_ZERO_ERROR; | |
299 | const UnifiedCache *cache = UnifiedCache::getInstance(status); | |
300 | assertSuccess("", status); | |
301 | cache->flush(); | |
302 | int32_t baseCount = cache->keyCount(); | |
303 | const UCTItem *en = NULL; | |
304 | const UCTItem *enGb = NULL; | |
305 | const UCTItem *enGb2 = NULL; | |
306 | const UCTItem *enUs = NULL; | |
307 | const UCTItem *fr = NULL; | |
308 | const UCTItem *frFr = NULL; | |
309 | cache->get(LocaleCacheKey<UCTItem>("en"), en, status); | |
310 | cache->get(LocaleCacheKey<UCTItem>("en_US"), enUs, status); | |
311 | cache->get(LocaleCacheKey<UCTItem>("en_GB"), enGb, status); | |
312 | cache->get(LocaleCacheKey<UCTItem>("fr_FR"), frFr, status); | |
313 | cache->get(LocaleCacheKey<UCTItem>("fr"), fr, status); | |
0f5d89e8 | 314 | cache->get(LocaleCacheKey<UCTItem>("en_GB"), enGb2, status); |
b331163b A |
315 | SharedObject::clearPtr(enGb2); |
316 | if (enGb != enUs) { | |
317 | errln("Expected en_GB and en_US to resolve to same object."); | |
318 | } | |
319 | if (fr != frFr) { | |
320 | errln("Expected fr and fr_FR to resolve to same object."); | |
321 | } | |
322 | if (enGb == fr) { | |
323 | errln("Expected en_GB and fr to return different objects."); | |
324 | } | |
0f5d89e8 | 325 | assertSuccess("T1", status); |
b331163b A |
326 | // en_US, en_GB, en share one object; fr_FR and fr don't share. |
327 | // 5 keys in all. | |
0f5d89e8 | 328 | assertEquals("T2", baseCount + 5, cache->keyCount()); |
b331163b A |
329 | SharedObject::clearPtr(enGb); |
330 | cache->flush(); | |
2ca993e8 A |
331 | |
332 | // Only 2 unique values in the cache. flushing trims cache down | |
333 | // to this minimum size. | |
0f5d89e8 | 334 | assertEquals("T3", baseCount + 2, cache->keyCount()); |
b331163b A |
335 | SharedObject::clearPtr(enUs); |
336 | SharedObject::clearPtr(en); | |
337 | cache->flush(); | |
338 | // With en_GB and en_US and en cleared there are no more hard references to | |
339 | // the "en" object, so it gets flushed and the keys that refer to it | |
2ca993e8 A |
340 | // get removed from the cache. Now we have just one unique value, fr, in |
341 | // the cache | |
0f5d89e8 | 342 | assertEquals("T4", baseCount + 1, cache->keyCount()); |
b331163b A |
343 | SharedObject::clearPtr(fr); |
344 | cache->flush(); | |
0f5d89e8 | 345 | assertEquals("T5", baseCount + 1, cache->keyCount()); |
b331163b A |
346 | SharedObject::clearPtr(frFr); |
347 | cache->flush(); | |
0f5d89e8 A |
348 | assertEquals("T6", baseCount + 0, cache->keyCount()); |
349 | assertSuccess("T7", status); | |
b331163b A |
350 | } |
351 | ||
352 | void UnifiedCacheTest::TestError() { | |
353 | UErrorCode status = U_ZERO_ERROR; | |
354 | const UnifiedCache *cache = UnifiedCache::getInstance(status); | |
355 | assertSuccess("", status); | |
356 | cache->flush(); | |
357 | int32_t baseCount = cache->keyCount(); | |
358 | const UCTItem *zh = NULL; | |
359 | const UCTItem *zhTw = NULL; | |
360 | const UCTItem *zhHk = NULL; | |
361 | ||
362 | status = U_ZERO_ERROR; | |
363 | cache->get(LocaleCacheKey<UCTItem>("zh"), zh, status); | |
364 | if (status != U_MISSING_RESOURCE_ERROR) { | |
365 | errln("Expected U_MISSING_RESOURCE_ERROR"); | |
366 | } | |
367 | status = U_ZERO_ERROR; | |
368 | cache->get(LocaleCacheKey<UCTItem>("zh_TW"), zhTw, status); | |
369 | if (status != U_MISSING_RESOURCE_ERROR) { | |
370 | errln("Expected U_MISSING_RESOURCE_ERROR"); | |
371 | } | |
372 | status = U_ZERO_ERROR; | |
373 | cache->get(LocaleCacheKey<UCTItem>("zh_HK"), zhHk, status); | |
374 | if (status != U_MISSING_RESOURCE_ERROR) { | |
375 | errln("Expected U_MISSING_RESOURCE_ERROR"); | |
376 | } | |
377 | // 3 keys in cache zh, zhTW, zhHk all pointing to error placeholders | |
378 | assertEquals("", baseCount + 3, cache->keyCount()); | |
379 | cache->flush(); | |
380 | // error placeholders have no hard references so they always get flushed. | |
381 | assertEquals("", baseCount + 0, cache->keyCount()); | |
382 | } | |
383 | ||
384 | void UnifiedCacheTest::TestHashEquals() { | |
385 | LocaleCacheKey<UCTItem> key1("en_US"); | |
386 | LocaleCacheKey<UCTItem> key2("en_US"); | |
387 | LocaleCacheKey<UCTItem> diffKey1("en_UT"); | |
388 | LocaleCacheKey<UCTItem2> diffKey2("en_US"); | |
389 | assertTrue("", key1.hashCode() == key2.hashCode()); | |
390 | assertTrue("", key1.hashCode() != diffKey1.hashCode()); | |
391 | assertTrue("", key1.hashCode() != diffKey2.hashCode()); | |
392 | assertTrue("", diffKey1.hashCode() != diffKey2.hashCode()); | |
393 | assertTrue("", key1 == key2); | |
394 | assertTrue("", key1 != diffKey1); | |
395 | assertTrue("", key1 != diffKey2); | |
396 | assertTrue("", diffKey1 != diffKey2); | |
397 | } | |
398 | ||
399 | extern IntlTest *createUnifiedCacheTest() { | |
400 | return new UnifiedCacheTest(); | |
401 | } |