// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /* ******************************************************************************* * Copyright (C) 2015, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * * File UNIFIEDCACHETEST.CPP * ******************************************************************************** */ #include "cstring.h" #include "intltest.h" #include "unifiedcache.h" #include "unicode/datefmt.h" class UCTItem : public SharedObject { public: char *value; UCTItem(const char *x) : value(NULL) { value = uprv_strdup(x); } virtual ~UCTItem() { uprv_free(value); } }; class UCTItem2 : public SharedObject { }; U_NAMESPACE_BEGIN template<> U_EXPORT const UCTItem *LocaleCacheKey::createObject( const void *context, UErrorCode &status) const { const UnifiedCache *cacheContext = (const UnifiedCache *) context; if (uprv_strcmp(fLoc.getName(), "zh") == 0) { status = U_MISSING_RESOURCE_ERROR; return NULL; } if (uprv_strcmp(fLoc.getLanguage(), fLoc.getName()) != 0) { const UCTItem *item = NULL; if (cacheContext == NULL) { UnifiedCache::getByLocale(fLoc.getLanguage(), item, status); } else { cacheContext->get(LocaleCacheKey(fLoc.getLanguage()), item, status); } if (U_FAILURE(status)) { return NULL; } return item; } UCTItem *result = new UCTItem(fLoc.getName()); result->addRef(); return result; } template<> U_EXPORT const UCTItem2 *LocaleCacheKey::createObject( const void * /*unused*/, UErrorCode & /*status*/) const { return NULL; } U_NAMESPACE_END class UnifiedCacheTest : public IntlTest { public: UnifiedCacheTest() { } void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=0) override; private: void TestEvictionPolicy(); void TestBounded(); void TestBasic(); void TestError(); void TestHashEquals(); void TestEvictionUnderStress(); }; void UnifiedCacheTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/) { TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(TestEvictionPolicy); TESTCASE_AUTO(TestBounded); TESTCASE_AUTO(TestBasic); TESTCASE_AUTO(TestError); TESTCASE_AUTO(TestHashEquals); TESTCASE_AUTO(TestEvictionUnderStress); TESTCASE_AUTO_END; } void UnifiedCacheTest::TestEvictionUnderStress() { #if !UCONFIG_NO_FORMATTING int32_t localeCount; const Locale *locales = DateFormat::getAvailableLocales(localeCount); UErrorCode status = U_ZERO_ERROR; const UnifiedCache *cache = UnifiedCache::getInstance(status); int64_t evictedCountBefore = cache->autoEvictedCount(); for (int32_t i = 0; i < localeCount; ++i) { LocalPointer ptr(DateFormat::createInstanceForSkeleton("yMd", locales[i], status)); } int64_t evictedCountAfter = cache->autoEvictedCount(); if (evictedCountBefore == evictedCountAfter) { dataerrln("%s:%d Items should have been evicted from cache", __FILE__, __LINE__); } #endif /* #if !UCONFIG_NO_FORMATTING */ } void UnifiedCacheTest::TestEvictionPolicy() { UErrorCode status = U_ZERO_ERROR; // We have to call this first or else calling the UnifiedCache // ctor will fail. This is by design to deter clients from using the // cache API incorrectly by creating their own cache instances. UnifiedCache::getInstance(status); // We create our own local UnifiedCache instance to ensure we have // complete control over it. Real clients should never ever create // their own cache! UnifiedCache cache(status); assertSuccess("", status); // Don't allow unused entries to exceed more than 100% of in use entries. cache.setEvictionPolicy(0, 100, status); static const char *locales[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"}; const UCTItem *usedReferences[] = {NULL, NULL, NULL, NULL, NULL}; const UCTItem *unusedReference = NULL; // Add 5 in-use entries for (int32_t i = 0; i < UPRV_LENGTHOF(usedReferences); i++) { cache.get( LocaleCacheKey(locales[i]), &cache, usedReferences[i], status); } // Add 10 not in use entries. for (int32_t i = 0; i < 10; ++i) { cache.get( LocaleCacheKey( locales[i + UPRV_LENGTHOF(usedReferences)]), &cache, unusedReference, status); } unusedReference->removeRef(); // unused count not to exceed in use count assertEquals("T1", UPRV_LENGTHOF(usedReferences), cache.unusedCount()); assertEquals("T2", 2*UPRV_LENGTHOF(usedReferences), cache.keyCount()); // Free up those used entries. for (int32_t i = 0; i < UPRV_LENGTHOF(usedReferences); i++) { usedReferences[i]->removeRef(); } // This should free up all cache items assertEquals("T3", 0, cache.keyCount()); assertSuccess("T4", status); } void UnifiedCacheTest::TestBounded() { UErrorCode status = U_ZERO_ERROR; // We have to call this first or else calling the UnifiedCache // ctor will fail. This is by design to deter clients from using the // cache API incorrectly by creating their own cache instances. UnifiedCache::getInstance(status); // We create our own local UnifiedCache instance to ensure we have // complete control over it. Real clients should never ever create // their own cache! UnifiedCache cache(status); assertSuccess("T0", status); // Maximum unused count is 3. cache.setEvictionPolicy(3, 0, status); // Our cache will hold up to 3 unused key-value pairs // We test the following invariants: // 1. unusedCount <= 3 // 2. cache->get(X) always returns the same reference as long as caller // already holds references to that same object. // We first add 5 key-value pairs with two distinct values, "en" and "fr" // keeping all those references. const UCTItem *en = NULL; const UCTItem *enGb = NULL; const UCTItem *enUs = NULL; const UCTItem *fr = NULL; const UCTItem *frFr = NULL; cache.get(LocaleCacheKey("en_US"), &cache, enUs, status); cache.get(LocaleCacheKey("en"), &cache, en, status); assertEquals("T1", 1, cache.unusedCount()); cache.get(LocaleCacheKey("en_GB"), &cache, enGb, status); cache.get(LocaleCacheKey("fr_FR"), &cache, frFr, status); cache.get(LocaleCacheKey("fr"), &cache, fr, status); // Client holds two unique references, "en" and "fr" the other three // entries are eligible for eviction. assertEquals("T2", 3, cache.unusedCount()); assertEquals("T3", 5, cache.keyCount()); // Exercise cache more but don't hold the references except for // the last one. At the end of this, we will hold references to one // additional distinct value, so we will have references to 3 distinct // values. const UCTItem *throwAway = NULL; cache.get(LocaleCacheKey("zn_AA"), &cache, throwAway, status); cache.get(LocaleCacheKey("sr_AA"), &cache, throwAway, status); cache.get(LocaleCacheKey("de_AU"), &cache, throwAway, status); const UCTItem *deAu(throwAway); deAu->addRef(); // Client holds three unique references, "en", "fr", "de" although we // could have a total of 8 entries in the cache maxUnusedCount == 3 // so we have only 6 entries. assertEquals("T4", 3, cache.unusedCount()); assertEquals("T5", 6, cache.keyCount()); // For all the references we have, cache must continue to return // those same references (#2) cache.get(LocaleCacheKey("en"), &cache, throwAway, status); if (throwAway != en) { errln("T6: Expected en to resolve to the same object."); } cache.get(LocaleCacheKey("en_US"), &cache, throwAway, status); if (throwAway != enUs) { errln("T7: Expected enUs to resolve to the same object."); } cache.get(LocaleCacheKey("en_GB"), &cache, throwAway, status); if (throwAway != enGb) { errln("T8: Expected enGb to resolve to the same object."); } cache.get(LocaleCacheKey("fr_FR"), &cache, throwAway, status); if (throwAway != frFr) { errln("T9: Expected frFr to resolve to the same object."); } cache.get(LocaleCacheKey("fr_FR"), &cache, throwAway, status); cache.get(LocaleCacheKey("fr"), &cache, throwAway, status); if (throwAway != fr) { errln("T10: Expected fr to resolve to the same object."); } cache.get(LocaleCacheKey("de_AU"), &cache, throwAway, status); if (throwAway != deAu) { errln("T11: Expected deAu to resolve to the same object."); } assertEquals("T12", 3, cache.unusedCount()); assertEquals("T13", 6, cache.keyCount()); // Now we hold a references to two more distinct values. Cache size // should grow to 8. const UCTItem *es = NULL; const UCTItem *ru = NULL; cache.get(LocaleCacheKey("es"), &cache, es, status); cache.get(LocaleCacheKey("ru"), &cache, ru, status); assertEquals("T14", 3, cache.unusedCount()); assertEquals("T15", 8, cache.keyCount()); // Now release all the references we hold except for // es, ru, and en SharedObject::clearPtr(enGb); SharedObject::clearPtr(enUs); SharedObject::clearPtr(fr); SharedObject::clearPtr(frFr); SharedObject::clearPtr(deAu); SharedObject::clearPtr(es); SharedObject::clearPtr(ru); SharedObject::clearPtr(en); SharedObject::clearPtr(throwAway); // Size of cache should magically drop to 3. assertEquals("T16", 3, cache.unusedCount()); assertEquals("T17", 3, cache.keyCount()); // Be sure nothing happens setting the eviction policy in the middle of // a run. cache.setEvictionPolicy(3, 0, status); assertSuccess("T18", status); } void UnifiedCacheTest::TestBasic() { UErrorCode status = U_ZERO_ERROR; const UnifiedCache *cache = UnifiedCache::getInstance(status); assertSuccess("", status); cache->flush(); int32_t baseCount = cache->keyCount(); const UCTItem *en = NULL; const UCTItem *enGb = NULL; const UCTItem *enGb2 = NULL; const UCTItem *enUs = NULL; const UCTItem *fr = NULL; const UCTItem *frFr = NULL; cache->get(LocaleCacheKey("en"), en, status); cache->get(LocaleCacheKey("en_US"), enUs, status); cache->get(LocaleCacheKey("en_GB"), enGb, status); cache->get(LocaleCacheKey("fr_FR"), frFr, status); cache->get(LocaleCacheKey("fr"), fr, status); cache->get(LocaleCacheKey("en_GB"), enGb2, status); SharedObject::clearPtr(enGb2); if (enGb != enUs) { errln("Expected en_GB and en_US to resolve to same object."); } if (fr != frFr) { errln("Expected fr and fr_FR to resolve to same object."); } if (enGb == fr) { errln("Expected en_GB and fr to return different objects."); } assertSuccess("T1", status); // en_US, en_GB, en share one object; fr_FR and fr don't share. // 5 keys in all. assertEquals("T2", baseCount + 5, cache->keyCount()); SharedObject::clearPtr(enGb); cache->flush(); // Only 2 unique values in the cache. flushing trims cache down // to this minimum size. assertEquals("T3", baseCount + 2, cache->keyCount()); SharedObject::clearPtr(enUs); SharedObject::clearPtr(en); cache->flush(); // With en_GB and en_US and en cleared there are no more hard references to // the "en" object, so it gets flushed and the keys that refer to it // get removed from the cache. Now we have just one unique value, fr, in // the cache assertEquals("T4", baseCount + 1, cache->keyCount()); SharedObject::clearPtr(fr); cache->flush(); assertEquals("T5", baseCount + 1, cache->keyCount()); SharedObject::clearPtr(frFr); cache->flush(); assertEquals("T6", baseCount + 0, cache->keyCount()); assertSuccess("T7", status); } void UnifiedCacheTest::TestError() { UErrorCode status = U_ZERO_ERROR; const UnifiedCache *cache = UnifiedCache::getInstance(status); assertSuccess("", status); cache->flush(); int32_t baseCount = cache->keyCount(); const UCTItem *zh = NULL; const UCTItem *zhTw = NULL; const UCTItem *zhHk = NULL; status = U_ZERO_ERROR; cache->get(LocaleCacheKey("zh"), zh, status); if (status != U_MISSING_RESOURCE_ERROR) { errln("Expected U_MISSING_RESOURCE_ERROR"); } status = U_ZERO_ERROR; cache->get(LocaleCacheKey("zh_TW"), zhTw, status); if (status != U_MISSING_RESOURCE_ERROR) { errln("Expected U_MISSING_RESOURCE_ERROR"); } status = U_ZERO_ERROR; cache->get(LocaleCacheKey("zh_HK"), zhHk, status); if (status != U_MISSING_RESOURCE_ERROR) { errln("Expected U_MISSING_RESOURCE_ERROR"); } // 3 keys in cache zh, zhTW, zhHk all pointing to error placeholders assertEquals("", baseCount + 3, cache->keyCount()); cache->flush(); // error placeholders have no hard references so they always get flushed. assertEquals("", baseCount + 0, cache->keyCount()); } void UnifiedCacheTest::TestHashEquals() { LocaleCacheKey key1("en_US"); LocaleCacheKey key2("en_US"); LocaleCacheKey diffKey1("en_UT"); LocaleCacheKey diffKey2("en_US"); assertTrue("", key1.hashCode() == key2.hashCode()); assertTrue("", key1.hashCode() != diffKey1.hashCode()); assertTrue("", key1.hashCode() != diffKey2.hashCode()); assertTrue("", diffKey1.hashCode() != diffKey2.hashCode()); assertTrue("", key1 == key2); assertTrue("", key1 != diffKey1); assertTrue("", key1 != diffKey2); assertTrue("", diffKey1 != diffKey2); } extern IntlTest *createUnifiedCacheTest() { return new UnifiedCacheTest(); }