1 /*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "EGL_test"
18 // #define LOG_NDEBUG 0
19
20 #include <gtest/gtest.h>
21
22 #include <utils/Log.h>
23
24 #include <android-base/test_utils.h>
25
26 #include "egl_cache.h"
27 #include "MultifileBlobCache.h"
28 #include "egl_display.h"
29
30 #include <fstream>
31 #include <memory>
32
33 using namespace std::literals;
34
35 namespace android {
36
37 class EGLCacheTest : public ::testing::TestWithParam<egl_cache_t::EGLCacheMode> {
38 protected:
SetUp()39 virtual void SetUp() {
40 // Terminate to clean up any previous cache in this process
41 mCache->terminate();
42
43 mTempFile.reset(new TemporaryFile());
44 mCache->setCacheFilename(&mTempFile->path[0]);
45 mCache->setCacheLimit(1024);
46 mCache->setCacheMode(mCacheMode);
47 }
48
TearDown()49 virtual void TearDown() {
50 mCache->terminate();
51 mCache->setCacheFilename("");
52 mTempFile.reset(nullptr);
53 }
54
55 std::string getCachefileName();
56
57 egl_cache_t* mCache = egl_cache_t::get();
58 std::unique_ptr<TemporaryFile> mTempFile;
59 egl_cache_t::EGLCacheMode mCacheMode = GetParam();
60 };
61
TEST_P(EGLCacheTest,UninitializedCacheAlwaysMisses)62 TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) {
63 uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
64 mCache->setBlob("abcd", 4, "efgh", 4);
65 ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
66 ASSERT_EQ(0xee, buf[0]);
67 ASSERT_EQ(0xee, buf[1]);
68 ASSERT_EQ(0xee, buf[2]);
69 ASSERT_EQ(0xee, buf[3]);
70 }
71
TEST_P(EGLCacheTest,InitializedCacheAlwaysHits)72 TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) {
73 uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
74 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
75 mCache->setBlob("abcd", 4, "efgh", 4);
76 ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
77 ASSERT_EQ('e', buf[0]);
78 ASSERT_EQ('f', buf[1]);
79 ASSERT_EQ('g', buf[2]);
80 ASSERT_EQ('h', buf[3]);
81 }
82
TEST_P(EGLCacheTest,TerminatedCacheAlwaysMisses)83 TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) {
84 uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
85 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
86 mCache->setBlob("abcd", 4, "efgh", 4);
87 mCache->terminate();
88 ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
89 ASSERT_EQ(0xee, buf[0]);
90 ASSERT_EQ(0xee, buf[1]);
91 ASSERT_EQ(0xee, buf[2]);
92 ASSERT_EQ(0xee, buf[3]);
93 }
94
TEST_P(EGLCacheTest,ReinitializedCacheContainsValues)95 TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) {
96 uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
97 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
98 mCache->setBlob("abcd", 4, "efgh", 4);
99 mCache->terminate();
100 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
101 ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
102 ASSERT_EQ('e', buf[0]);
103 ASSERT_EQ('f', buf[1]);
104 ASSERT_EQ('g', buf[2]);
105 ASSERT_EQ('h', buf[3]);
106 }
107
getCachefileName()108 std::string EGLCacheTest::getCachefileName() {
109 // Return the monolithic filename unless we find the multifile dir
110 std::string cachePath = &mTempFile->path[0];
111 std::string multifileDirName = cachePath + ".multifile";
112 std::string cachefileName = "";
113
114 struct stat info;
115 if (stat(multifileDirName.c_str(), &info) == 0) {
116 // Ensure we only have one file to manage
117 int entryFileCount = 0;
118
119 // We have a multifile dir. Return the only entry file in it.
120 DIR* dir;
121 struct dirent* entry;
122 if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
123 while ((entry = readdir(dir)) != nullptr) {
124 if (entry->d_name == "."s || entry->d_name == ".."s ||
125 strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
126 continue;
127 }
128 cachefileName = multifileDirName + "/" + entry->d_name;
129 entryFileCount++;
130 }
131 } else {
132 printf("Unable to open %s, error: %s\n",
133 multifileDirName.c_str(), std::strerror(errno));
134 }
135
136 if (entryFileCount != 1) {
137 // If there was more than one real file in the directory, this
138 // violates test assumptions
139 cachefileName = "";
140 }
141 } else {
142 printf("Unable to stat %s, error: %s\n",
143 multifileDirName.c_str(), std::strerror(errno));
144 }
145
146 return cachefileName;
147 }
148
TEST_P(EGLCacheTest,ModifiedCacheBeginMisses)149 TEST_P(EGLCacheTest, ModifiedCacheBeginMisses) {
150 // Skip if not in multifile mode
151 if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
152 GTEST_SKIP() << "Skipping test designed for multifile";
153 }
154
155 uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
156 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
157
158 mCache->setBlob("abcd", 4, "efgh", 4);
159 ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
160 ASSERT_EQ('e', buf[0]);
161 ASSERT_EQ('f', buf[1]);
162 ASSERT_EQ('g', buf[2]);
163 ASSERT_EQ('h', buf[3]);
164
165 // Ensure the cache file is written to disk
166 mCache->terminate();
167
168 // Depending on the cache mode, the file will be in different locations
169 std::string cachefileName = getCachefileName();
170 ASSERT_TRUE(cachefileName.length() > 0);
171
172 // Stomp on the beginning of the cache file, breaking the key match
173 const char* stomp = "BADF00D";
174 std::fstream fs(cachefileName);
175 fs.seekp(0, std::ios_base::beg);
176 fs.write(stomp, strlen(stomp));
177 fs.flush();
178 fs.close();
179
180 // Ensure no cache hit
181 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
182 uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee };
183 // getBlob may return junk for required size, but should not return a cache hit
184 mCache->getBlob("abcd", 4, buf2, 4);
185 ASSERT_EQ(0xee, buf2[0]);
186 ASSERT_EQ(0xee, buf2[1]);
187 ASSERT_EQ(0xee, buf2[2]);
188 ASSERT_EQ(0xee, buf2[3]);
189 }
190
TEST_P(EGLCacheTest,ModifiedCacheEndMisses)191 TEST_P(EGLCacheTest, ModifiedCacheEndMisses) {
192 // Skip if not in multifile mode
193 if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
194 GTEST_SKIP() << "Skipping test designed for multifile";
195 }
196
197 uint8_t buf[16] = { 0xee, 0xee, 0xee, 0xee,
198 0xee, 0xee, 0xee, 0xee,
199 0xee, 0xee, 0xee, 0xee,
200 0xee, 0xee, 0xee, 0xee };
201
202 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
203
204 mCache->setBlob("abcdefghij", 10, "klmnopqrstuvwxyz", 16);
205 ASSERT_EQ(16, mCache->getBlob("abcdefghij", 10, buf, 16));
206 ASSERT_EQ('w', buf[12]);
207 ASSERT_EQ('x', buf[13]);
208 ASSERT_EQ('y', buf[14]);
209 ASSERT_EQ('z', buf[15]);
210
211 // Ensure the cache file is written to disk
212 mCache->terminate();
213
214 // Depending on the cache mode, the file will be in different locations
215 std::string cachefileName = getCachefileName();
216 ASSERT_TRUE(cachefileName.length() > 0);
217
218 // Stomp on the END of the cache file, modifying its contents
219 const char* stomp = "BADF00D";
220 std::fstream fs(cachefileName);
221 fs.seekp(-strlen(stomp), std::ios_base::end);
222 fs.write(stomp, strlen(stomp));
223 fs.flush();
224 fs.close();
225
226 // Ensure no cache hit
227 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
228 uint8_t buf2[16] = { 0xee, 0xee, 0xee, 0xee,
229 0xee, 0xee, 0xee, 0xee,
230 0xee, 0xee, 0xee, 0xee,
231 0xee, 0xee, 0xee, 0xee };
232
233 // getBlob may return junk for required size, but should not return a cache hit
234 mCache->getBlob("abcdefghij", 10, buf2, 16);
235 ASSERT_EQ(0xee, buf2[0]);
236 ASSERT_EQ(0xee, buf2[1]);
237 ASSERT_EQ(0xee, buf2[2]);
238 ASSERT_EQ(0xee, buf2[3]);
239 }
240
TEST_P(EGLCacheTest,TerminatedCacheBelowCacheLimit)241 TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
242 uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
243 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
244
245 mCache->setBlob("abcd", 4, "efgh", 4);
246 ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
247 ASSERT_EQ('e', buf[0]);
248 ASSERT_EQ('f', buf[1]);
249 ASSERT_EQ('g', buf[2]);
250 ASSERT_EQ('h', buf[3]);
251
252 mCache->setBlob("ijkl", 4, "mnop", 4);
253 ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4));
254 ASSERT_EQ('m', buf[0]);
255 ASSERT_EQ('n', buf[1]);
256 ASSERT_EQ('o', buf[2]);
257 ASSERT_EQ('p', buf[3]);
258
259 mCache->setBlob("qrst", 4, "uvwx", 4);
260 ASSERT_EQ(4, mCache->getBlob("qrst", 4, buf, 4));
261 ASSERT_EQ('u', buf[0]);
262 ASSERT_EQ('v', buf[1]);
263 ASSERT_EQ('w', buf[2]);
264 ASSERT_EQ('x', buf[3]);
265
266 // Cache should contain both the key and the value
267 // So 8 bytes per entry, at least 24 bytes
268 ASSERT_GE(mCache->getCacheSize(), 24);
269
270 // Set the new limit and initialize cache
271 mCache->terminate();
272 mCache->setCacheLimit(4);
273 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
274
275 // Ensure the new limit is respected
276 ASSERT_LE(mCache->getCacheSize(), 4);
277 }
278
TEST_P(EGLCacheTest,TrimCacheOnOverflow)279 TEST_P(EGLCacheTest, TrimCacheOnOverflow) {
280 // Skip if not in multifile mode
281 if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
282 GTEST_SKIP() << "Skipping test designed for multifile";
283 }
284
285 uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
286 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
287
288 // Set one value in the cache
289 mCache->setBlob("abcd", 4, "efgh", 4);
290 ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
291 ASSERT_EQ('e', buf[0]);
292 ASSERT_EQ('f', buf[1]);
293 ASSERT_EQ('g', buf[2]);
294 ASSERT_EQ('h', buf[3]);
295
296 // Get the size of cache with a single entry
297 size_t cacheEntrySize = mCache->getCacheSize();
298
299 // Now reinitialize the cache, using max size equal to a single entry
300 mCache->terminate();
301 mCache->setCacheLimit(cacheEntrySize);
302 mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
303
304 // Ensure our cache still has original value
305 ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
306 ASSERT_EQ('e', buf[0]);
307 ASSERT_EQ('f', buf[1]);
308 ASSERT_EQ('g', buf[2]);
309 ASSERT_EQ('h', buf[3]);
310
311 // Set another value, which should overflow the cache and trim
312 mCache->setBlob("ijkl", 4, "mnop", 4);
313 ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4));
314 ASSERT_EQ('m', buf[0]);
315 ASSERT_EQ('n', buf[1]);
316 ASSERT_EQ('o', buf[2]);
317 ASSERT_EQ('p', buf[3]);
318
319 // The cache should still be under the limit
320 ASSERT_TRUE(mCache->getCacheSize() == cacheEntrySize);
321
322 // And no cache hit on trimmed entry
323 uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee };
324 mCache->getBlob("abcd", 4, buf2, 4);
325 ASSERT_EQ(0xee, buf2[0]);
326 ASSERT_EQ(0xee, buf2[1]);
327 ASSERT_EQ(0xee, buf2[2]);
328 ASSERT_EQ(0xee, buf2[3]);
329 }
330
331 INSTANTIATE_TEST_CASE_P(MonolithicCacheTests,
332 EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic));
333 INSTANTIATE_TEST_CASE_P(MultifileCacheTests,
334 EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile));
335 }
336