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