/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "EGL_test" // #define LOG_NDEBUG 0 #include #include #include #include "egl_cache.h" #include "MultifileBlobCache.h" #include "egl_display.h" #include #include using namespace std::literals; namespace android { class EGLCacheTest : public ::testing::TestWithParam { protected: virtual void SetUp() { // Terminate to clean up any previous cache in this process mCache->terminate(); mTempFile.reset(new TemporaryFile()); mCache->setCacheFilename(&mTempFile->path[0]); mCache->setCacheLimit(1024); mCache->setCacheMode(mCacheMode); } virtual void TearDown() { mCache->terminate(); mCache->setCacheFilename(""); mTempFile.reset(nullptr); } std::string getCachefileName(); egl_cache_t* mCache = egl_cache_t::get(); std::unique_ptr mTempFile; egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ(0xee, buf[0]); ASSERT_EQ(0xee, buf[1]); ASSERT_EQ(0xee, buf[2]); ASSERT_EQ(0xee, buf[3]); } TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ('e', buf[0]); ASSERT_EQ('f', buf[1]); ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); } TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); mCache->terminate(); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ(0xee, buf[0]); ASSERT_EQ(0xee, buf[1]); ASSERT_EQ(0xee, buf[2]); ASSERT_EQ(0xee, buf[3]); } TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); mCache->terminate(); mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ('e', buf[0]); ASSERT_EQ('f', buf[1]); ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); } std::string EGLCacheTest::getCachefileName() { // Return the monolithic filename unless we find the multifile dir std::string cachePath = &mTempFile->path[0]; std::string multifileDirName = cachePath + ".multifile"; std::string cachefileName = ""; struct stat info; if (stat(multifileDirName.c_str(), &info) == 0) { // Ensure we only have one file to manage int entryFileCount = 0; // We have a multifile dir. Return the only entry file in it. DIR* dir; struct dirent* entry; if ((dir = opendir(multifileDirName.c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (entry->d_name == "."s || entry->d_name == ".."s || strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) { continue; } cachefileName = multifileDirName + "/" + entry->d_name; entryFileCount++; } } else { printf("Unable to open %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno)); } if (entryFileCount != 1) { // If there was more than one real file in the directory, this // violates test assumptions cachefileName = ""; } } else { printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno)); } return cachefileName; } TEST_P(EGLCacheTest, ModifiedCacheBeginMisses) { // Skip if not in multifile mode if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { GTEST_SKIP() << "Skipping test designed for multifile"; } uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ('e', buf[0]); ASSERT_EQ('f', buf[1]); ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); // Ensure the cache file is written to disk mCache->terminate(); // Depending on the cache mode, the file will be in different locations std::string cachefileName = getCachefileName(); ASSERT_TRUE(cachefileName.length() > 0); // Stomp on the beginning of the cache file, breaking the key match const char* stomp = "BADF00D"; std::fstream fs(cachefileName); fs.seekp(0, std::ios_base::beg); fs.write(stomp, strlen(stomp)); fs.flush(); fs.close(); // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; // getBlob may return junk for required size, but should not return a cache hit mCache->getBlob("abcd", 4, buf2, 4); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } TEST_P(EGLCacheTest, ModifiedCacheEndMisses) { // Skip if not in multifile mode if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { GTEST_SKIP() << "Skipping test designed for multifile"; } uint8_t buf[16] = { 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcdefghij", 10, "klmnopqrstuvwxyz", 16); ASSERT_EQ(16, mCache->getBlob("abcdefghij", 10, buf, 16)); ASSERT_EQ('w', buf[12]); ASSERT_EQ('x', buf[13]); ASSERT_EQ('y', buf[14]); ASSERT_EQ('z', buf[15]); // Ensure the cache file is written to disk mCache->terminate(); // Depending on the cache mode, the file will be in different locations std::string cachefileName = getCachefileName(); ASSERT_TRUE(cachefileName.length() > 0); // Stomp on the END of the cache file, modifying its contents const char* stomp = "BADF00D"; std::fstream fs(cachefileName); fs.seekp(-strlen(stomp), std::ios_base::end); fs.write(stomp, strlen(stomp)); fs.flush(); fs.close(); // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); uint8_t buf2[16] = { 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee }; // getBlob may return junk for required size, but should not return a cache hit mCache->getBlob("abcdefghij", 10, buf2, 16); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ('e', buf[0]); ASSERT_EQ('f', buf[1]); ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); mCache->setBlob("ijkl", 4, "mnop", 4); ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4)); ASSERT_EQ('m', buf[0]); ASSERT_EQ('n', buf[1]); ASSERT_EQ('o', buf[2]); ASSERT_EQ('p', buf[3]); mCache->setBlob("qrst", 4, "uvwx", 4); ASSERT_EQ(4, mCache->getBlob("qrst", 4, buf, 4)); ASSERT_EQ('u', buf[0]); ASSERT_EQ('v', buf[1]); ASSERT_EQ('w', buf[2]); ASSERT_EQ('x', buf[3]); // Cache should contain both the key and the value // So 8 bytes per entry, at least 24 bytes ASSERT_GE(mCache->getCacheSize(), 24); // Set the new limit and initialize cache mCache->terminate(); mCache->setCacheLimit(4); mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); // Ensure the new limit is respected ASSERT_LE(mCache->getCacheSize(), 4); } TEST_P(EGLCacheTest, TrimCacheOnOverflow) { // Skip if not in multifile mode if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { GTEST_SKIP() << "Skipping test designed for multifile"; } uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); // Set one value in the cache mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ('e', buf[0]); ASSERT_EQ('f', buf[1]); ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); // Get the size of cache with a single entry size_t cacheEntrySize = mCache->getCacheSize(); // Now reinitialize the cache, using max size equal to a single entry mCache->terminate(); mCache->setCacheLimit(cacheEntrySize); mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); // Ensure our cache still has original value ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); ASSERT_EQ('e', buf[0]); ASSERT_EQ('f', buf[1]); ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); // Set another value, which should overflow the cache and trim mCache->setBlob("ijkl", 4, "mnop", 4); ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4)); ASSERT_EQ('m', buf[0]); ASSERT_EQ('n', buf[1]); ASSERT_EQ('o', buf[2]); ASSERT_EQ('p', buf[3]); // The cache should still be under the limit ASSERT_TRUE(mCache->getCacheSize() == cacheEntrySize); // And no cache hit on trimmed entry uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->getBlob("abcd", 4, buf2, 4); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); INSTANTIATE_TEST_CASE_P(MultifileCacheTests, EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); }