proxygen
FilePersistentCacheTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017-present Facebook, Inc.
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 #include <folly/futures/Barrier.h>
20 
21 using namespace std;
22 using namespace testing;
23 using namespace wangle;
24 
26 
27 template<typename MutexT>
28 class FilePersistentCacheTest : public Test {};
29 
30 using MutexTypes = ::testing::Types<std::mutex, folly::SharedMutex>;
32 
33 TYPED_TEST(FilePersistentCacheTest, stringTypesGetPutTest) {
34  using KeyType = string;
35  using ValType = string;
36  vector<KeyType> keys = {"key1", "key2"};
37  vector<ValType> values = {"value1", "value2"};
38  testSimplePutGet<KeyType, ValType, TypeParam>(keys, values);
39 }
40 
41 TYPED_TEST(FilePersistentCacheTest, basicTypeGetPutTest) {
42  using KeyType = int;
43  using ValType = double;
44  vector<KeyType> keys = {1, 2};
45  vector<ValType> values = {3.0, 4.0};
46  testSimplePutGet<KeyType, ValType, TypeParam>(keys, values);
47 }
48 
49 TYPED_TEST(FilePersistentCacheTest, stringCompositeGetPutTest) {
50  using KeyType = string;
51  using ValType = list<string>;
52  vector<KeyType> keys = {"key1", "key2"};
53  vector<ValType> values =
54  {ValType({"fma", "shijin"}), ValType({"foo", "bar"})};
55  testSimplePutGet<KeyType, ValType, TypeParam>(keys, values);
56 }
57 
58 TYPED_TEST(FilePersistentCacheTest, stringNestedValGetPutTest) {
59  using KeyType = string;
60  using ValType = map<string, list<string>>;
61  vector<KeyType> keys = {"cool", "not cool"};
62  vector<ValType> values = {
63  ValType({
64  {"NYC", {"fma", "shijin"}},
65  {"MPK", {"ranjeeth", "dsp"}}
66  }),
67  ValType({
68  {"MPK", {"subodh", "blake"}},
69  {"STL", {"pgriess"}}
70  }),
71  };
72  testSimplePutGet<KeyType, ValType, TypeParam>(keys, values);
73 }
74 
75 TYPED_TEST(FilePersistentCacheTest, stringNestedKeyValGetPutTest) {
76  using KeyType = pair<string, string>;
77  using ValType = map<string, list<string>>;
78  vector<KeyType> keys = {
79  make_pair("cool", "what the=?"),
80  make_pair("not_cool", "how on *& earth?")
81  };
82  vector<ValType> values = {
83  ValType({
84  {"NYC", {"fma", "shijin kong$"}},
85  {"MPK", {"ranjeeth", "dsp"}}
86  }),
87  ValType({
88  {"MPK", {"subodh", "blake"}},
89  {"STL", {"pgriess"}}
90  }),
91  };
92  testSimplePutGet<KeyType, ValType, TypeParam>(keys, values);
93 }
94 
95 template<typename K, typename V, typename MutexT>
96 void testEmptyFile() {
97  string filename = getPersistentCacheFilename();
98  size_t cacheCapacity = 10;
99  int fd = folly::openNoInt(
100  filename.c_str(),
101  O_WRONLY | O_CREAT | O_TRUNC,
102  S_IRUSR | S_IWUSR
103  );
104  EXPECT_TRUE(fd != -1);
105  using CacheType = FilePersistentCache<K, V, MutexT>;
106  CacheType cache(filename, cacheCapacity, chrono::seconds(1));
107  EXPECT_EQ(cache.size(), 0);
108  EXPECT_TRUE(folly::closeNoInt(fd) != -1);
109  EXPECT_TRUE(unlink(filename.c_str()) != -1);
110 }
111 
112 TYPED_TEST(FilePersistentCacheTest, stringTypesEmptyFile) {
113  using KeyType = string;
114  using ValType = string;
115  testEmptyFile<KeyType, ValType, TypeParam>();
116 }
117 
118 TYPED_TEST(FilePersistentCacheTest, stringNestedValEmptyFile) {
119  using KeyType = string;
120  using ValType = map<string, list<string>>;
121  testEmptyFile<KeyType, ValType, TypeParam>();
122 }
123 
124 //TODO_ranjeeth : integrity, should we sign the file somehow t3623725
125 template<typename K, typename V, typename MutexT>
126 void testInvalidFile(const std::string& content) {
127  string filename = getPersistentCacheFilename();
128  size_t cacheCapacity = 10;
129  int fd = folly::openNoInt(
130  filename.c_str(),
131  O_WRONLY | O_CREAT | O_TRUNC,
132  S_IRUSR | S_IWUSR
133  );
134  EXPECT_TRUE(fd != -1);
135  EXPECT_EQ(
136  folly::writeFull(fd, content.data(), content.size()),
137  content.size()
138  );
139  using CacheType = FilePersistentCache<K, V, MutexT>;
140  CacheType cache(filename, cacheCapacity, chrono::seconds(1));
141  EXPECT_EQ(cache.size(), 0);
142  EXPECT_TRUE(folly::closeNoInt(fd) != -1);
143  EXPECT_TRUE(unlink(filename.c_str()) != -1);
144 }
145 
146 TYPED_TEST(FilePersistentCacheTest, stringTypesInvalidFile) {
147  using KeyType = string;
148  using ValType = string;
149  testInvalidFile<KeyType, ValType, TypeParam>(string("{\"k1\":\"v1\",1}"));
150 }
151 
152 TYPED_TEST(FilePersistentCacheTest, stringNestedValInvalidFile) {
153  using KeyType = string;
154  using ValType = map<string, list<string>>;
155  testInvalidFile<KeyType, ValType, TypeParam>(string("{\"k1\":\"v1\"}"));
156 }
157 
158 //TODO_ranjeeth : integrity, should we sign the file somehow t3623725
159 template<typename K, typename V, typename MutexT>
161  const std::string& content,
162  const vector<K>& keys,
163  const vector<V>& values
164  ) {
165  string filename = getPersistentCacheFilename();
166  size_t cacheCapacity = 10;
167  int fd = folly::openNoInt(
168  filename.c_str(),
169  O_WRONLY | O_CREAT | O_TRUNC,
170  S_IRUSR | S_IWUSR
171  );
172  EXPECT_TRUE(fd != -1);
173  EXPECT_EQ(
174  folly::writeFull(fd, content.data(), content.size()),
175  content.size()
176  );
177  using CacheType = FilePersistentCache<K, V, MutexT>;
178  CacheType cache(filename, cacheCapacity, chrono::seconds(1));
179  EXPECT_EQ(cache.size(), keys.size());
180  for (size_t i = 0; i < keys.size(); ++i) {
181  EXPECT_EQ(cache.get(keys[i]).value(), values[i]);
182  }
183  EXPECT_TRUE(folly::closeNoInt(fd) != -1);
184  EXPECT_TRUE(unlink(filename.c_str()) != -1);
185 }
186 
187 TYPED_TEST(FilePersistentCacheTest, stringTypesValidFileTest) {
188  using KeyType = string;
189  using ValType = string;
190  vector<KeyType> keys = {"key1", "key2"};
191  vector<ValType> values = {"value1", "value2"};
192  std::string content = "[[\"key1\",\"value1\"], [\"key2\",\"value2\"]]";
193  testValidFile<KeyType, ValType, TypeParam>(content, keys, values);
194 }
195 
196 TYPED_TEST(FilePersistentCacheTest, basicEvictionTest) {
197  string filename = getPersistentCacheFilename();
198  {
199  size_t cacheCapacity = 10;
201  cacheCapacity, chrono::seconds(1));
202  for (int i = 0; i < 10; ++i) {
203  cache.put(i, i);
204  }
205  EXPECT_EQ(cache.size(), 10); // MRU to LRU : 9, 8, ...1, 0
206 
207  cache.put(10, 10); // evicts 0
208  EXPECT_EQ(cache.size(), 10);
209  EXPECT_FALSE(cache.get(0).hasValue());
210  EXPECT_EQ(cache.get(10).value(), 10); // MRU to LRU : 10, 9, ... 2, 1
211 
212  EXPECT_EQ(cache.get(1).value(), 1); // MRU to LRU : 1, 10, 9, ..., 3, 2
213  cache.put(11, 11); // evicts 2
214  EXPECT_EQ(cache.size(), 10);
215  EXPECT_FALSE(cache.get(2).hasValue());
216  EXPECT_EQ(cache.get(11).value(), 11);
217  }
218 
219  EXPECT_TRUE(unlink(filename.c_str()) != -1);
220 }
221 
222 // serialization has changed, so test if things will be alright afterwards
223 TYPED_TEST(FilePersistentCacheTest, backwardCompatiblityTest) {
224  using KeyType = string;
225  using ValType = string;
226 
227  // write an old style file
228  vector<KeyType> keys = {"key1", "key2"};
229  vector<ValType> values = {"value1", "value2"};
230  std::string content = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
232  string filename = getPersistentCacheFilename();
233  size_t cacheCapacity = 10;
234  int fd = folly::openNoInt(
235  filename.c_str(),
236  O_WRONLY | O_CREAT | O_TRUNC,
237  S_IRUSR | S_IWUSR
238  );
239  EXPECT_TRUE(fd != -1);
240  EXPECT_EQ(
241  folly::writeFull(fd, content.data(), content.size()),
242  content.size()
243  );
244  EXPECT_TRUE(folly::closeNoInt(fd) != -1);
245 
246  {
247  // it should fail to load
248  CacheType cache(filename, cacheCapacity, chrono::seconds(1));
249  EXPECT_EQ(cache.size(), 0);
250 
251  // .. but new entries should work
252  cache.put("key1", "value1");
253  cache.put("key2", "value2");
254  EXPECT_EQ(cache.size(), 2);
255  EXPECT_EQ(cache.get("key1").value(), "value1");
256  EXPECT_EQ(cache.get("key2").value(), "value2");
257  }
258  {
259  // new format persists
260  CacheType cache(filename, cacheCapacity, chrono::seconds(1));
261  EXPECT_EQ(cache.size(), 2);
262  EXPECT_EQ(cache.get("key1").value(), "value1");
263  EXPECT_EQ(cache.get("key2").value(), "value2");
264  }
265  EXPECT_TRUE(unlink(filename.c_str()) != -1);
266 }
267 
271 
272  auto cache1 = std::make_unique<CacheType>(
273  cacheFile, 10, std::chrono::seconds(3));
274  cache1.reset();
275  auto cache2 = std::make_unique<CacheType>(
276  cacheFile, 10, std::chrono::seconds(3));
277  cache2.reset();
278 }
279 
281  // make sure no errors crop up when hitting the same cache
282  // with multiple threads
283 
284  // spin up a few of threads that add and remove
285  // their own set of values on the same cache
287  auto cacheFile = getPersistentCacheFilename();
288  auto sharedCache = std::make_unique<CacheType>(
289  cacheFile, 10, std::chrono::seconds(10));
290 
291  int numThreads = 3;
292  Barrier b(numThreads);
293  auto threadFunc = [&b, numThreads](CacheType* cache, int tNum) {
294  auto key1 = folly::to<string>("key", tNum);
295  auto nextNum = (tNum + 1) % numThreads;
296  auto key2 = folly::to<string>("key", nextNum);
297  b.wait().get();
298  for (int i = 0; i < 100; ++i) {
299  cache->put(key1, i);
300  cache->put(key2, i);
301  cache->remove(key1);
302  cache->remove(key2);
303  }
304  // wait til everyone is done
305  b.wait().get();
306  cache->put(key1, tNum);
307  };
308 
309  std::vector<std::thread> threads;
310  for (int i = 0; i < numThreads; ++i) {
311  threads.push_back(std::thread(threadFunc, sharedCache.get(), i));
312  }
313  for (int i = 0; i < numThreads; ++i) {
314  threads[i].join();
315  }
316  EXPECT_EQ(sharedCache->size(), numThreads);
317  for (auto i = 0; i < numThreads; ++i) {
318  auto key = folly::to<string>("key", i);
319  auto val = sharedCache->get(key);
320  EXPECT_TRUE(val.hasValue());
321  EXPECT_EQ(*val, i);
322  }
323 }
folly::Future< bool > wait()
Definition: Barrier.cpp:72
char b
int closeNoInt(int fd)
Definition: FileUtil.cpp:56
void testValidFile(const std::string &content, const vector< K > &keys, const vector< V > &values)
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
STL namespace.
double val
Definition: String.cpp:273
KeyType
Definition: Signature.h:17
TYPED_TEST(FilePersistentCacheTest, stringTypesGetPutTest)
folly::Optional< V > get(const K &key) override
static void destroy()
std::vector< std::thread::id > threads
void testEmptyFile()
FOLLY_CPP14_CONSTEXPR bool hasValue() const noexcept
Definition: Optional.h:300
ssize_t writeFull(int fd, const void *buf, size_t count)
Definition: FileUtil.cpp:134
::testing::Types< std::mutex, folly::SharedMutex > MutexTypes
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
int openNoInt(const char *name, int flags, mode_t mode)
Definition: FileUtil.cpp:36
const char * string
Definition: Conv.cpp:212
FOLLY_CPP14_CONSTEXPR const Value & value() const &
Definition: Optional.h:268
void testInvalidFile(const std::string &content)
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
std::string getPersistentCacheFilename()
Definition: TestUtil.cpp:22
TYPED_TEST_CASE(FilePersistentCacheTest, MutexTypes)
std::vector< int > values(1'000)
void put(const K &key, const V &val) override