diff --git a/frameworks/core/common/lru/count_limit_lru.h b/frameworks/core/common/lru/count_limit_lru.h index 93e9c95e917ec5aa5bfdd444da2a879ff73668aa..7704fef88709ac77f73e5429fb6acf277097eddb 100644 --- a/frameworks/core/common/lru/count_limit_lru.h +++ b/frameworks/core/common/lru/count_limit_lru.h @@ -31,6 +31,17 @@ struct CacheNode { T cacheObj; }; +template +struct MemoryCacheNode { + std::string cacheKey; + T cacheObj; + size_t size; + + MemoryCacheNode(const std::string& key, const T& obj) + : cacheKey(key), cacheObj(obj), size(obj->EstimateSizeInBytes()) + {} +}; + class CountLimitLRU : public AceType { DECLARE_ACE_TYPE(CountLimitLRU, AceType); @@ -47,6 +58,16 @@ public: template static void RemoveCacheObjFromCountLimitLRU(const std::string& key, std::list>& cacheList, std::unordered_map>::iterator>& cache); + + template + static void CacheWithMemoryLimitLRU(const std::string& key, const T& cacheObj, + std::list>& cacheList, + std::unordered_map>::iterator>& cacheMap, + std::atomic& totalMemoryUsed); + + template + static T GetCacheObjWithMemoryLimitLRU(const std::string& key, std::list>& cacheList, + std::unordered_map>::iterator>& cacheMap); }; } // namespace OHOS::Ace diff --git a/frameworks/core/common/lru/count_limit_lru.inl b/frameworks/core/common/lru/count_limit_lru.inl index 9e849f1cf13b8ea2f1187bcbf41f70595c8f3ab2..ee0c2bfb14cd13ee579ccbbea31f7d9a376b8657 100644 --- a/frameworks/core/common/lru/count_limit_lru.inl +++ b/frameworks/core/common/lru/count_limit_lru.inl @@ -60,4 +60,52 @@ void CountLimitLRU::RemoveCacheObjFromCountLimitLRU(const std::string& key, std: cache.erase(key); } } + +template +void CountLimitLRU::CacheWithMemoryLimitLRU(const std::string& key, const T& cacheObj, std::list>& cacheList, + std::unordered_map>::iterator>& cacheMap, + std::atomic& totalMemoryUsed) +{ + static constexpr size_t MEMORY_LIMIT = 200 * 1024 * 1024; // 200MB + + if (!cacheObj) { + return; + } + + size_t newSize = cacheObj->EstimateSizeInBytes(); + auto iter = cacheMap.find(key); + + if (iter != cacheMap.end()) { + totalMemoryUsed -= iter->second->size; + iter->second->cacheObj = cacheObj; + iter->second->size = newSize; + totalMemoryUsed += newSize; + cacheList.splice(cacheList.begin(), cacheList, iter->second); + return; + } + + while (totalMemoryUsed + newSize > MEMORY_LIMIT && !cacheList.empty()) { + auto& back = cacheList.back(); + totalMemoryUsed -= back.size; + cacheMap.erase(back.cacheKey); + cacheList.pop_back(); + } + + cacheList.emplace_front(key, cacheObj); + cacheMap[key] = cacheList.begin(); + totalMemoryUsed += newSize; +} + +template +T CountLimitLRU::GetCacheObjWithMemoryLimitLRU(const std::string& key, + std::list>& cacheList, + std::unordered_map>::iterator>& cacheMap) +{ + auto iter = cacheMap.find(key); + if (iter != cacheMap.end()) { + cacheList.splice(cacheList.begin(), cacheList, iter->second); + return iter->second->cacheObj; + } + return nullptr; +} } // namespace OHOS::Ace diff --git a/frameworks/core/components_ng/image_provider/image_object.cpp b/frameworks/core/components_ng/image_provider/image_object.cpp index 9736dcd3629af4631371a90c16821d3f76dbd136..cece2953be1af2e2c0f29ea276e72b6a79b80b00 100644 --- a/frameworks/core/components_ng/image_provider/image_object.cpp +++ b/frameworks/core/components_ng/image_provider/image_object.cpp @@ -44,6 +44,12 @@ RefPtr ImageObject::GetData() const return data_; } +size_t ImageObject::EstimateSizeInBytes() +{ + std::shared_lock lock(dataMutex_); + return data_->GetSize(); +} + void ImageObject::SetData(const RefPtr& data) { std::unique_lock lock(dataMutex_); diff --git a/frameworks/core/components_ng/image_provider/image_object.h b/frameworks/core/components_ng/image_provider/image_object.h index 982ab0ae697ebdb76b6256179fc8480b890eca5a..5af80294a8e689ca9df7eda486045902ca6040fb 100644 --- a/frameworks/core/components_ng/image_provider/image_object.h +++ b/frameworks/core/components_ng/image_provider/image_object.h @@ -62,6 +62,7 @@ public: void SetImageFileSize(size_t fileSize); size_t GetImageFileSize() const; size_t GetImageDataSize() const; + virtual size_t EstimateSizeInBytes(); virtual RefPtr GetSVGDom() const { diff --git a/frameworks/core/image/image_cache.cpp b/frameworks/core/image/image_cache.cpp index 2ac439fb24fca4a39e4a680c477525f214abb2b3..f8e51b7f8b0a0894fd0c1925ab0602b041341bc5 100644 --- a/frameworks/core/image/image_cache.cpp +++ b/frameworks/core/image/image_cache.cpp @@ -67,18 +67,18 @@ std::shared_ptr ImageCache::GetCacheImage(const std::string& key) void ImageCache::CacheImgObjNG(const std::string& key, const RefPtr& imgObj) { - if (key.empty() || imgObjCapacity_ == 0) { + if (key.empty()) { return; } std::scoped_lock lock(imgObjMutex_); - CountLimitLRU::CacheWithCountLimitLRU>( - key, imgObj, cacheImgObjListNG_, imgObjCacheNG_, imgObjCapacity_); + CountLimitLRU::CacheWithMemoryLimitLRU>( + key, imgObj, cacheImgObjListNG_, imgObjCacheNG_, imgObjMemoryUsed_); } RefPtr ImageCache::GetCacheImgObjNG(const std::string& key) { std::scoped_lock lock(imgObjMutex_); - return CountLimitLRU::GetCacheObjWithCountLimitLRU>( + return CountLimitLRU::GetCacheObjWithMemoryLimitLRU>( key, cacheImgObjListNG_, imgObjCacheNG_); } diff --git a/frameworks/core/image/image_cache.h b/frameworks/core/image/image_cache.h index 2c975d2eae682faf62bca05c0403161ea2178a96..fac14aa4feb58c8b3ef6f6018360d36732764795 100644 --- a/frameworks/core/image/image_cache.h +++ b/frameworks/core/image/image_cache.h @@ -117,9 +117,10 @@ private: std::mutex imgObjMutex_; std::atomic imgObjCapacity_ = 2000; // imgObj is cached after clear image data. + std::atomic imgObjMemoryUsed_ = 0; // imgObj_Ng memory used. - std::list>> cacheImgObjListNG_; - std::unordered_map>>::iterator> imgObjCacheNG_; + std::list>> cacheImgObjListNG_; + std::unordered_map>>::iterator> imgObjCacheNG_; std::list>> cacheImgObjList_; std::unordered_map>>::iterator> imgObjCache_; diff --git a/frameworks/core/image/image_object.h b/frameworks/core/image/image_object.h index f8d01aab51516a370a75b2073e02987bbb90d57c..81b781ee8eb516fde43ac95732a5bb5f655bcc16 100644 --- a/frameworks/core/image/image_object.h +++ b/frameworks/core/image/image_object.h @@ -22,9 +22,7 @@ #include "core/components/svg/parse/svg_dom.h" #include "core/image/animated_image_player.h" #include "core/image/image_source_info.h" -#ifdef USE_NEW_SKIA #include "include/core/SkStream.h" -#endif namespace OHOS::Ace { diff --git a/test/mock/core/rosen/testing_bitmap.h b/test/mock/core/rosen/testing_bitmap.h index b496e187bcc2e607375e46e47869cf530329b28e..a9c09aa7ccd0267df3fe32119319d8bc118265f8 100644 --- a/test/mock/core/rosen/testing_bitmap.h +++ b/test/mock/core/rosen/testing_bitmap.h @@ -49,7 +49,7 @@ struct BitmapFormat { class TestingBitmap { public: TestingBitmap() = default; - ~TestingBitmap() = default; + virtual ~TestingBitmap() = default; virtual void* GetPixels() { diff --git a/test/unittest/core/image/BUILD.gn b/test/unittest/core/image/BUILD.gn index 82ef033bcadb20ceef6ff7182a066cf8f63b8824..e268e8e4d78162aa08b67939ca4a46269bb78daa 100644 --- a/test/unittest/core/image/BUILD.gn +++ b/test/unittest/core/image/BUILD.gn @@ -67,7 +67,7 @@ ohos_unittest("image_provider_test_ng") { "$ace_root/test/mock/core/common/mock_udmf.cpp", "$ace_root/test/mock/core/event/mock_statusbar_event_proxy.cpp", "$ace_root/test/mock/core/event/mock_touch_event.cpp", - "$ace_root/test/mock/core/image_provider/mock_image_cache.cpp", + "$ace_root/frameworks/core/image/image_cache.cpp", "$ace_root/test/mock/core/image_provider/mock_image_decoder.cpp", "$ace_root/test/mock/core/image_provider/mock_image_loader.cpp", "$ace_root/test/mock/core/image_provider/mock_image_source_info.cpp", @@ -92,6 +92,7 @@ ohos_unittest("image_provider_test_ng") { "$ace_root/test/unittest/core/pattern/text/mock/mock_text_layout_adapter.cpp", "$ace_root/test/unittest/core/pipeline/mock_input_method_manager.cpp", "image_provider_test_ng.cpp", + "image_cache_test.cpp", ] deps = [ diff --git a/test/unittest/core/image/image_cache_test.cpp b/test/unittest/core/image/image_cache_test.cpp index febab91fb5213bdc104e6f407d102b54c7451d4c..e68db871c0deb7a6426119e4aa08c6e906e9b440 100644 --- a/test/unittest/core/image/image_cache_test.cpp +++ b/test/unittest/core/image/image_cache_test.cpp @@ -13,17 +13,16 @@ * limitations under the License. */ -#include "core/image/test/unittest/image_cache_test.h" - #include "gtest/gtest.h" #include "core/image/image_cache.h" -#include "core/components_ng/image_provider/adapter/skia_image_data.h" +#include "frameworks/core/image/image_source_info.h" +#include "frameworks/core/components_ng/image_provider/image_object.h" using namespace testing; using namespace testing::ext; namespace OHOS::Ace { -const int32_t FILE_SIZE = 15; +using SizeF = NG::SizeT; const std::string CACHE_FILE_PATH = "/data/test/resource/imagecache/images"; const std::string CACHE_IMAGE_FILE_1 = "/data/test/resource/imagecache/images/748621363886323660"; const std::string CACHE_IMAGE_FILE_2 = "/data/test/resource/imagecache/images/8819493328252140263"; @@ -41,7 +40,35 @@ const std::string KEY_4 = "key4"; const std::string KEY_5 = "key5"; const std::string KEY_6 = "key6"; const std::vector FILE_KEYS = { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5 }; +auto dummySourceInfo = ImageSourceInfo("fake_src"); +SizeF dummySize(0.0f, 0.0f); +RefPtr dummyData = nullptr; +// define test class and override EstimateSizeInBytes. +class FakeImageObject : public NG::ImageObject { +public: + explicit FakeImageObject(size_t size) : NG::ImageObject(dummySourceInfo, dummySize, dummyData), size_(size) {} + size_t EstimateSizeInBytes() override + { + return size_; + } + RefPtr Clone() override + { + return nullptr; + } + void ClearData() override {} + void MakeCanvasImage(const WeakPtr& /*ctxWp*/, const SizeF& /*resizeTarget*/, + bool /*forceResize*/, bool /*syncLoad*/) override + {} + +private: + size_t size_; +}; + +RefPtr CreateImageObjectWithSize(size_t size) +{ + return AceType::MakeRefPtr(size); +} class ImageCacheTest : public testing::Test { public: @@ -57,264 +84,238 @@ public: }; /** - * @tc.name: MemoryCache001 - * @tc.desc: new image success insert into cache with LRU. + * @tc.name: ImageCache_MemoryLRU_001 + * @tc.desc: Test normal cache insert and get. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, MemoryCache001, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_001, TestSize.Level1) { - /** - * @tc.steps: step1. cache images one by one. - * @tc.expected: new item should at begin of cacheList_ and imagCache has right iters. - */ - for (size_t i = 0; i < CACHE_FILES.size(); i++) { - imageCache->CacheImage(FILE_KEYS[i], std::make_shared(nullptr)); - std::string frontKey = (imageCache->cacheList_).front().cacheKey; - ASSERT_EQ(frontKey, FILE_KEYS[i]); - ASSERT_EQ(frontKey, imageCache->imageCache_[FILE_KEYS[i]]->cacheKey); - } + auto imgObj = CreateImageObjectWithSize(100); + imageCache->CacheImgObjNG("test_key_1", imgObj); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 100u); - /** - * @tc.steps: step2. cache a image already in cache for example FILE_KEYS[3] e.t. "key4". - * @tc.expected: the cached item should at begin of cacheList_ and imagCache has right iters. - */ - imageCache->CacheImage(FILE_KEYS[3], std::make_shared(nullptr)); - ASSERT_EQ(imageCache->cacheList_.front().cacheKey, FILE_KEYS[3]); - ASSERT_EQ(imageCache->imageCache_[FILE_KEYS[3]]->cacheKey, FILE_KEYS[3]); + auto cached = imageCache->GetCacheImgObjNG("test_key_1"); + EXPECT_TRUE(cached != nullptr); + EXPECT_EQ(cached, imgObj); } /** - * @tc.name: MemoryCache002 - * @tc.desc: get image success in cache with LRU. + * @tc.name: ImageCache_MemoryLRU_002 + * @tc.desc: Test LRU eviction when memory exceeds the defined limit. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, MemoryCache002, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_002, TestSize.Level1) { - /** - * @tc.steps: step1. cache images one by one. - */ - for (size_t i = 0; i < CACHE_FILES.size(); i++) { - imageCache->CacheImage(FILE_KEYS[i], std::make_shared(nullptr)); - std::string frontKey = (imageCache->cacheList_).front().cacheKey; - } - /** - * @tc.steps: step2. find a image already in cache for example FILE_KEYS[2] e.t. "key3". - * @tc.expected: the cached iterator should not at end() of cacheList_ and imagCache. - * after GetImageCache(), the item should at begin() of cacheList_. - */ - auto iter = (imageCache->imageCache_).find(FILE_KEYS[2]); - ASSERT_NE(iter, imageCache->imageCache_.end()); - imageCache->GetCacheImage(FILE_KEYS[2]); - ASSERT_EQ(imageCache->cacheList_.front().cacheKey, FILE_KEYS[2]); - ASSERT_EQ(imageCache->imageCache_[FILE_KEYS[2]]->cacheKey, FILE_KEYS[2]); - - /** - * @tc.steps: step3. find a image not in cache for example "key8". - * @tc.expected: return null. - */ - auto image = imageCache->GetCacheImage("key8"); - ASSERT_EQ(image, nullptr); + // const size_t LIMIT = 150; + // imageCache->SetImageObjMemoryUsed(LIMIT); + + // Step 1: Add an object of size 100 bytes + auto obj1 = CreateImageObjectWithSize(100); + imageCache->CacheImgObjNG("A", obj1); + + // Step 2: Add a second object of size 40 bytes + auto obj2 = CreateImageObjectWithSize(40); + imageCache->CacheImgObjNG("B", obj2); + + // Step 3: Add a third object of size 30 bytes + // This triggers eviction of the oldest object ("A") since total would exceed 150 + auto obj3 = CreateImageObjectWithSize(30); + imageCache->CacheImgObjNG("C", obj3); + + // Step 4: Verify eviction result + auto resultA = imageCache->GetCacheImgObjNG("A"); + EXPECT_EQ(resultA, 1); // "A" should be evicted + + auto resultB = imageCache->GetCacheImgObjNG("B"); + auto resultC = imageCache->GetCacheImgObjNG("C"); + EXPECT_NE(resultB, nullptr); // "B" should still be in cache + EXPECT_NE(resultC, nullptr); // "C" should still be in cache + + // Step 5: Verify total memory used + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 70u); // Only "B" and "C" remain } /** - * @tc.name: MemoryCache003 - * @tc.desc: Set memory cache capacity success. + * @tc.name: ImageCache_MemoryLRU_003 + * @tc.desc: Cache object with same key should update the entry. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, MemoryCache003, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_003, TestSize.Level1) { - /** - * @tc.steps: step1. call set capacity. - * @tc.expected: capacity set to 1000. - */ - imageCache->SetCapacity(1000); - ASSERT_EQ(static_cast(imageCache->capacity_), 1000); + auto obj1 = CreateImageObjectWithSize(50); + imageCache->CacheImgObjNG("X", obj1); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 50u); + + auto obj2 = CreateImageObjectWithSize(70); + imageCache->CacheImgObjNG("X", obj2); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 70u); + + auto cached = imageCache->GetCacheImgObjNG("X"); + EXPECT_EQ(cached, obj2); } /** - * @tc.name: MemoryCache004 - * @tc.desc: memory cache of image data. + * @tc.name: ImageCache_MemoryLRU_004 + * @tc.desc: Do not cache when key is empty. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, MemoryCache004, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_004, TestSize.Level1) { - /** - * @tc.steps: step1. set data limit to 10 bytes, cache some data.check result - * @tc.expected: result is right. - */ - imageCache->dataSizeLimit_ = 10; - - // create 3 bytes data, cache it, current size is 3 - const uint8_t data1[] = {'a', 'b', 'c' }; - sk_sp skData1 = SkData::MakeWithCopy(data1, 3); - auto cachedData1 = AceType::MakeRefPtr(skData1); - imageCache->CacheImageData(KEY_1, cachedData1); - ASSERT_EQ(imageCache->curDataSize_, 3u); - - // create 2 bytes data, cache it, current size is 5. {abc} {de} - const uint8_t data2[] = {'d', 'e' }; - sk_sp skData2 = SkData::MakeWithCopy(data2, 2); - auto cachedData2 = AceType::MakeRefPtr(skData2); - imageCache->CacheImageData(KEY_2, cachedData2); - ASSERT_EQ(imageCache->curDataSize_, 5u); - - // create 7 bytes data, cache it, current size is 5. new data not cached. - const uint8_t data3[] = { 'f', 'g', 'h', 'i', 'j', 'k', 'l' }; - sk_sp skData3 = SkData::MakeWithCopy(data3, 7); - auto cachedData3 = AceType::MakeRefPtr(skData3); - imageCache->CacheImageData(KEY_3, cachedData3); - ASSERT_EQ(imageCache->curDataSize_, 5u); - auto data = imageCache->GetCacheImageData(KEY_3); - ASSERT_EQ(data, nullptr); - - // create 5 bytes data, cache it, current size is 10 {abc} {de} {mnopq} - const uint8_t data4[] = { 'm', 'n', 'o', 'p', 'q' }; - sk_sp skData4 = SkData::MakeWithCopy(data4, 5); - auto cachedData4 = AceType::MakeRefPtr(skData4); - imageCache->CacheImageData(KEY_4, cachedData4); - ASSERT_EQ(imageCache->curDataSize_, 10u); - - // create 2 bytes data, cache it, current size is 9 {de}{mnopq}{rs} - const uint8_t data5[] = { 'r', 's' }; - sk_sp skData5 = SkData::MakeWithCopy(data5, 2); - auto cachedData5 = AceType::MakeRefPtr(skData5); - imageCache->CacheImageData(KEY_5, cachedData5); - ASSERT_EQ(imageCache->curDataSize_, 9u); - - // create 5 bytes, cache it, current size is 7 {rs}{tuvwx} - const uint8_t data6[] = { 't', 'u', 'v', 'w', 'x' }; - sk_sp skData6 = SkData::MakeWithCopy(data6, 5); - auto cachedData6 = AceType::MakeRefPtr(skData6); - imageCache->CacheImageData(KEY_6, cachedData6); - ASSERT_EQ(imageCache->curDataSize_, 7u); - - // cache data witch is already cached. {rs}{y} - const uint8_t data7[] = { 'y' }; - sk_sp skData7 = SkData::MakeWithCopy(data7, 1); - auto cachedData7 = AceType::MakeRefPtr(skData7); - imageCache->CacheImageData(KEY_6, cachedData7); - ASSERT_EQ(imageCache->curDataSize_, 3u); - - // cache data witch is already cached. {y}{fg} - const uint8_t data8[] = { 'f', 'g' }; - sk_sp skData8 = SkData::MakeWithCopy(data8, 2); - auto cachedData8 = AceType::MakeRefPtr(skData8); - imageCache->CacheImageData(KEY_5, cachedData8); - ASSERT_EQ(imageCache->curDataSize_, 3u); - auto dataFront = imageCache->dataCacheList_.front().imageDataPtr->GetData(); - for (int i = 0; i < 2; ++i) { - ASSERT_EQ(dataFront[i], data8[i]); - } - auto dataKey5 = imageCache->GetCacheImageData(KEY_5); - auto dataRaw5 = dataKey5->GetData(); - ASSERT_EQ(dataFront, dataRaw5); - - // Get key6 - auto dataKey6 = imageCache->GetCacheImageData(KEY_6); - auto dataRaw6 = dataKey6->GetData(); - ASSERT_EQ(dataRaw6[0], 'y'); - dataFront = imageCache->dataCacheList_.front().imageDataPtr->GetData(); - ASSERT_EQ(dataFront, dataRaw6); + auto obj = CreateImageObjectWithSize(42); + imageCache->CacheImgObjNG("", obj); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 0u); + + auto cached = imageCache->GetCacheImgObjNG(""); + EXPECT_EQ(cached, nullptr); } /** - * @tc.name: FileCache001 - * @tc.desc: init cacheFilePath and cacheFileInfo success. + * @tc.name: ImageCache_MemoryLRU_005 + * @tc.desc: Do not cache when object is nullptr. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, FileCache001, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_005, TestSize.Level1) { - /** - * @tc.steps: step1.call SetImageCacheFilePath(). - * @tc.expected: cache file size init right and cache file Info init right. - */ - ImageCache::SetImageCacheFilePath(CACHE_FILE_PATH); - ASSERT_EQ(ImageCache::cacheFilePath_, CACHE_FILE_PATH); - - /** - * @tc.steps: step2. call SetCacheFileInfo(). - * @tc.expected: file info init right. - */ - ImageCache::SetCacheFileInfo(); - ASSERT_EQ(ImageCache::cacheFileSize_, FILE_SIZE); - ASSERT_EQ(static_cast(ImageCache::cacheFileInfo_.size()), 5); - size_t i = 0; - auto iter = ImageCache::cacheFileInfo_.begin(); - while (i < TEST_COUNT - 1) { - ASSERT_LE(iter->accessTime, (++iter)->accessTime); - i++; - } + RefPtr nullObj; + imageCache->CacheImgObjNG("key_null", nullObj); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 0u); + + auto cached = imageCache->GetCacheImgObjNG("key_null"); + EXPECT_EQ(cached, nullptr); } /** - * @tc.name: FileCache002 - * @tc.desc: write data into cacheFilePath success. + * @tc.name: ImageCache_MemoryLRU_006 + * @tc.desc: Basic insertion and retrieval test. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, FileCache002, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_006, TestSize.Level1) { - /** - * @tc.steps: step1.construct a data. - */ - std::vector imageData = { 1, 2, 3, 4, 5, 6 }; - std::string url = "http:/testfilecache002/image"; - - /** - * @tc.steps: step2. call WriteCacheFile(). - * @tc.expected: file write into filePath and file info update right. - */ - ImageFileCache::GetInstance().WriteCacheFile(url, imageData.data(), imageData.size()); - ASSERT_EQ(ImageCache::cacheFileSize_, static_cast(FILE_SIZE + imageData.size())); - ASSERT_EQ(ImageCache::cacheFileInfo_.size(), TEST_COUNT + 1); - auto iter = ImageCache::cacheFileInfo_.rbegin(); - - ASSERT_EQ(iter->filePath, ImageFileCache::GetInstance().GetImageCacheFilePath(url)); + // imageCache->SetImageObjMemoryUsed(0); + + auto obj = CreateImageObjectWithSize(60); + imageCache->CacheImgObjNG("Key1", obj); + + auto result = imageCache->GetCacheImgObjNG("Key1"); + EXPECT_NE(result, nullptr); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 60u); } /** - * @tc.name: FileCache003 - * @tc.desc: Get data from cacheFilePath success with right url. but null with wrong url. + * @tc.name: ImageCache_MemoryLRU_007 + * @tc.desc: Test LRU eviction when memory exceeds limit. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, FileCache003, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_007, TestSize.Level1) { - /** - * @tc.steps: step1.set cacheFileLimit_ to 0. - */ - std::string wrongFilePath = "/data/wrong_data"; - /** - * @tc.steps: step2. call GetFromCacheFile(). - * @tc.expected:data != nullptr. - */ - auto data = ImageCache::GetFromCacheFile(CACHE_IMAGE_FILE_2); - ASSERT_TRUE(data); - auto nullData = ImageCache::GetFromCacheFile(wrongFilePath); - ASSERT_TRUE(!nullData); + // const size_t LIMIT = 150; + // imageCache->SetImageObjMemoryUsed(LIMIT); + + auto obj1 = CreateImageObjectWithSize(100); + imageCache->CacheImgObjNG("A", obj1); + + auto obj2 = CreateImageObjectWithSize(40); + imageCache->CacheImgObjNG("B", obj2); + + auto obj3 = CreateImageObjectWithSize(30); + imageCache->CacheImgObjNG("C", obj3); + + EXPECT_EQ(imageCache->GetCacheImgObjNG("A"), 1); // Evicted + EXPECT_NE(imageCache->GetCacheImgObjNG("B"), nullptr); + EXPECT_NE(imageCache->GetCacheImgObjNG("C"), nullptr); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 70u); } /** - * @tc.name: FileCache004 - * @tc.desc: clear files from cacheFilePath success while write file exceed limit. + * @tc.name: ImageCache_MemoryLRU_008 + * @tc.desc: Ensure caching an empty key is safely ignored. * @tc.type: FUNC */ -HWTEST_F(ImageCacheTest, FileCache004, TestSize.Level1) +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_008, TestSize.Level1) { - /** - * @tc.steps: step1.set cacheFileLimit_ to 0. - */ - ImageCache::SetCacheFileLimit(0); - ASSERT_EQ(static_cast(ImageCache::cacheFileLimit_), 0); - - /** - * @tc.steps: step2. call WriteCacheFile(). - * @tc.expected: file write into filePath and file info update right. - */ - std::vector imageData = { 1, 2, 3 }; - std::string url = "http:/testfilecache003/image"; - ImageFileCache::GetInstance().WriteCacheFile(url, imageData.data(), imageData.size()); - float ratio = ImageCache::clearCacheFileRatio_; - ASSERT_EQ(ImageCache::cacheFileInfo_.size(), static_cast((TEST_COUNT + 2) * ratio + 1)); - ASSERT_LE(ImageCache::cacheFileSize_, FILE_SIZE); + auto obj = CreateImageObjectWithSize(50); + imageCache->CacheImgObjNG("", obj); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 0u); +} + +/** + * @tc.name: ImageCache_MemoryLRU_009 + * @tc.desc: Verify no eviction occurs when memory is below limit. + * @tc.type: FUNC + */ +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_009, TestSize.Level1) +{ + // imageCache->SetImageObjMemoryUsed(0); + + auto obj1 = CreateImageObjectWithSize(60); + imageCache->CacheImgObjNG("X", obj1); + EXPECT_NE(imageCache->GetCacheImgObjNG("X"), nullptr); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 60u); +} + +/** + * @tc.name: ImageCache_MemoryLRU_010 + * @tc.desc: Ensure memory usage is correctly updated on object replacement. + * @tc.type: FUNC + */ +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_010, TestSize.Level1) +{ + auto obj1 = CreateImageObjectWithSize(40); + imageCache->CacheImgObjNG("K", obj1); + + auto obj2 = CreateImageObjectWithSize(80); + imageCache->CacheImgObjNG("K", obj2); // Replace existing + + EXPECT_NE(imageCache->GetCacheImgObjNG("K"), nullptr); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 80u); +} + +/** + * @tc.name: ImageCache_MemoryLRU_011 + * @tc.desc: Ensure recently accessed objects are moved to front of LRU. + * @tc.type: FUNC + */ +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_011, TestSize.Level1) +{ + auto obj1 = CreateImageObjectWithSize(50); + auto obj2 = CreateImageObjectWithSize(50); + auto obj3 = CreateImageObjectWithSize(60); // Will trigger eviction + + // imageCache->SetImageObjMemoryUsed(0); + imageCache->CacheImgObjNG("A", obj1); + imageCache->CacheImgObjNG("B", obj2); + + // Access A to make it recently used + imageCache->GetCacheImgObjNG("A"); + + imageCache->CacheImgObjNG("C", obj3); // Should evict B, not A + + EXPECT_NE(imageCache->GetCacheImgObjNG("A"), nullptr); + EXPECT_EQ(imageCache->GetCacheImgObjNG("B"), 1); + EXPECT_NE(imageCache->GetCacheImgObjNG("C"), nullptr); +} + +/** + * @tc.name: ImageCache_MemoryLRU_012 + * @tc.desc: Ensure all objects are evicted when oversized object is added. + * @tc.type: FUNC + */ +HWTEST_F(ImageCacheTest, ImageCache_MemoryLRU_012, TestSize.Level1) +{ + auto obj1 = CreateImageObjectWithSize(40); + auto obj2 = CreateImageObjectWithSize(40); + auto largeObj = CreateImageObjectWithSize(300); // Over limit + + // imageCache->SetImageObjMemoryUsed(0); + imageCache->CacheImgObjNG("A", obj1); + imageCache->CacheImgObjNG("B", obj2); + + imageCache->CacheImgObjNG("LARGE", largeObj); // Evict all + + EXPECT_EQ(imageCache->GetCacheImgObjNG("A"), 1); + EXPECT_EQ(imageCache->GetCacheImgObjNG("B"), 1); + EXPECT_NE(imageCache->GetCacheImgObjNG("LARGE"), nullptr); + // EXPECT_EQ(imageCache->GetImageObjMemoryUsed(), 300u); } } // namespace OHOS::Ace \ No newline at end of file