blob: ce5818229acf267c7f0cd2b002198e73c3076f7b [file] [log] [blame]
/*
* 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 <gtest/gtest.h>
#include <utils/Log.h>
#include <android-base/test_utils.h>
#include "egl_cache.h"
#include "MultifileBlobCache.h"
#include "egl_display.h"
#include <fstream>
#include <memory>
using namespace std::literals;
namespace android {
class EGLCacheTest : public ::testing::TestWithParam<egl_cache_t::EGLCacheMode> {
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<TemporaryFile> 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));
}