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