proxygen
LRUPersistentCacheTest.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 <chrono>
17 #include <thread>
18 #include <vector>
19 
20 #include <folly/Memory.h>
22 #include <folly/futures/Future.h>
28 
29 using namespace folly;
30 using namespace std;
31 using namespace testing;
32 using namespace wangle;
33 
35 
36 using MutexTypes = ::testing::Types<std::mutex, folly::SharedMutex>;
38 
39 template<typename T>
40 static shared_ptr<LRUPersistentCache<string, string, T>> createCache(
41  size_t capacity,
42  uint32_t syncMillis,
43  std::unique_ptr<TestPersistenceLayer> persistence = nullptr) {
44  using TestCache = LRUPersistentCache<string, string, T>;
45  return std::make_shared<TestCache>(
46  capacity,
47  std::chrono::milliseconds(syncMillis),
48  3,
49  std::move(persistence));
50 }
51 
52 template <typename T>
53 static shared_ptr<LRUPersistentCache<string, string, T>>
55  std::shared_ptr<folly::Executor> executor,
56  std::unique_ptr<TestPersistenceLayer> persistence,
57  std::chrono::milliseconds syncInterval,
58  int retryLimit) {
59  return std::make_shared<LRUPersistentCache<string, string, T>>(
60  std::move(executor),
61  10,
62  syncInterval,
63  retryLimit,
64  std::move(persistence));
65 }
66 
68  public:
69  ~MockPersistenceLayer() override {
70  LOG(ERROR) << "ok.";
71  }
72  bool persist(const dynamic& obj) noexcept override {
73  return persist_(obj);
74  }
76  return load_();
77  }
80  }
83  }
84  MOCK_METHOD0(clear, void());
85  MOCK_METHOD1(persist_, bool(const dynamic&));
87  MOCK_CONST_METHOD0(getLastPersistedVersion, CacheDataVersion());
88  GMOCK_METHOD1_(, noexcept, , setPersistedVersion, void(CacheDataVersion));
89 };
90 
91 template<typename MutexT>
92 class LRUPersistentCacheTest : public Test {
93  protected:
94  void SetUp() override {
95  persistence = make_unique<MockPersistenceLayer>();
96  ON_CALL(*persistence, getLastPersistedVersion())
97  .WillByDefault(Invoke(
98  persistence.get(),
100  ON_CALL(*persistence, setPersistedVersion(_))
101  .WillByDefault(Invoke(
102  persistence.get(),
104  manualExecutor = std::make_shared<folly::ManualExecutor>();
105  inlineExecutor = std::make_shared<folly::InlineExecutor>();
106  }
107 
108  unique_ptr<MockPersistenceLayer> persistence;
109  std::shared_ptr<folly::ManualExecutor> manualExecutor;
110  std::shared_ptr<folly::InlineExecutor> inlineExecutor;
111 };
112 
114  // make sure things sync even without a persistence layer
115  auto cache = createCache<TypeParam>(10, 1, nullptr);
116  cache->put("k0", "v0");
117  makeFuture().delayed(std::chrono::milliseconds(20)).thenValue(
118  [cache](auto&&) {
119  auto val = cache->get("k0");
120  EXPECT_TRUE(val);
121  EXPECT_EQ(*val, "v0");
122  EXPECT_FALSE(cache->hasPendingUpdates());
123  });
124 }
125 
126 MATCHER_P(DynSize, n, "") {
127  return size_t(n) == arg.size();
128 }
129 
130 TYPED_TEST(LRUPersistentCacheTest, SettingPersistence) {
131  auto cache = createCache<TypeParam>(10, 10, nullptr);
132  cache->put("k0", "v0");
134  InSequence seq;
135  EXPECT_CALL(*this->persistence, load_())
136  .Times(1)
137  .WillOnce(Return(data));
138  EXPECT_CALL(*this->persistence, persist_(DynSize(2)))
139  .Times(1)
140  .WillOnce(Return(true));
141  cache->setPersistence(std::move(this->persistence));
142 }
143 
145  auto cache = createCache<TypeParam>(10, 10000, nullptr);
146  cache->setSyncOnDestroy(true);
147  auto persistence = this->persistence.get();
148  cache->setPersistence(std::move(this->persistence));
149  cache->put("k0", "v0");
150  EXPECT_CALL(*persistence, persist_(_))
151  .Times(1)
152  .WillOnce(Return(true));
153  cache.reset();
154 }
155 
156 TYPED_TEST(LRUPersistentCacheTest, SetPersistenceMidPersist) {
157  // Setup a cache with no persistence layer
158  // Add some items
159  // Add a persistence layer, that during persist will call a function
160  // to set a new persistence layer on the cache
161  // Ensure that the new layer is called with the data
162  auto cache = createCache<TypeParam>(10, 10, nullptr);
163  cache->put("k0", "v0");
164  cache->put("k1", "v1");
165 
166  EXPECT_CALL(*this->persistence, load_())
167  .Times(1)
168  .WillOnce(Return(dynamic::array()));
169 
170  auto func = [cache](const folly::dynamic& /* kv */ ) {
171  // The cache persistence that we'll set during a call to persist
172  auto p2 = make_unique<MockPersistenceLayer>();
173  ON_CALL(*p2, getLastPersistedVersion())
174  .WillByDefault(
175  Invoke(
176  p2.get(),
178  EXPECT_CALL(*p2, load_())
179  .Times(1)
180  .WillOnce(Return(dynamic::array()));
181  EXPECT_CALL(*p2, persist_(DynSize(2)))
182  .Times(1)
183  .WillOnce(Return(true));
184 
185  cache->setPersistence(std::move(p2));
186  return true;
187  };
188  EXPECT_CALL(*this->persistence, persist_(DynSize(2)))
189  .Times(1)
190  .WillOnce(Invoke(func));
191 
192  cache->setPersistence(std::move(this->persistence));
193  makeFuture().delayed(std::chrono::milliseconds(100)).get();
194 }
195 
196 TYPED_TEST(LRUPersistentCacheTest, PersistNotCalled) {
198  EXPECT_CALL(*this->persistence, load_())
199  .Times(1)
200  .WillOnce(Return(data));
201  EXPECT_CALL(*this->persistence, persist_(_))
202  .Times(0)
203  .WillOnce(Return(false));
204  auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
205  EXPECT_EQ(cache->size(), 1);
206 }
207 
208 TYPED_TEST(LRUPersistentCacheTest, PersistentSetBeforeSyncer) {
209  EXPECT_CALL(*this->persistence, getLastPersistedVersion())
210  .Times(AtLeast(1))
211  .WillRepeatedly(
212  Invoke(
213  this->persistence.get(),
215  auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
216 }
217 
218 TYPED_TEST(LRUPersistentCacheTest, ClearKeepPersist) {
219  EXPECT_CALL(*this->persistence, clear()).Times(0);
220  auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
221  cache->clear();
222 }
223 
224 TYPED_TEST(LRUPersistentCacheTest, ClearDontKeepPersist) {
225  EXPECT_CALL(*this->persistence, clear()).Times(1);
226  auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
227  cache->clear(true);
228 }
229 
230 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheDeallocBeforeAdd) {
231  auto cache = createCacheWithExecutor<TypeParam>(
232  this->manualExecutor,
233  std::move(this->persistence),
234  std::chrono::milliseconds::zero(),
235  1);
236  cache.reset();
237  // Nothing should happen here
238  this->manualExecutor->drain();
239 }
240 
241 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheRunTask) {
243  EXPECT_CALL(*this->persistence, load_())
244  .Times(1)
245  .WillOnce(Return(data));
246  auto rawPersistence = this->persistence.get();
247  auto cache = createCacheWithExecutor<TypeParam>(
248  this->manualExecutor,
249  std::move(this->persistence),
250  std::chrono::milliseconds::zero(),
251  1);
252  cache->put("k0", "v0");
253  EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
254  .Times(1)
255  .WillOnce(Invoke(
256  rawPersistence,
258  EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
259  .Times(1)
260  .WillOnce(Return(true));
261  this->manualExecutor->run();
262  cache.reset();
263 }
264 
265 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheRunTaskInline) {
267  EXPECT_CALL(*this->persistence, load_())
268  .Times(1)
269  .WillOnce(Return(data));
270  auto rawPersistence = this->persistence.get();
271  auto cache = createCacheWithExecutor<TypeParam>(
272  this->inlineExecutor,
273  std::move(this->persistence),
274  std::chrono::milliseconds::zero(),
275  1);
276  EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
277  .Times(1)
278  .WillOnce(Invoke(
279  rawPersistence,
281  EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
282  .Times(1)
283  .WillOnce(Return(true));
284  cache->put("k0", "v0");
285 
286  EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
287  .Times(1)
288  .WillOnce(Invoke(
289  rawPersistence,
291  EXPECT_CALL(*rawPersistence, persist_(DynSize(3)))
292  .Times(1)
293  .WillOnce(Return(true));
294  cache->put("k2", "v2");
295  cache.reset();
296 }
297 
298 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheRetries) {
299  EXPECT_CALL(*this->persistence, load_())
300  .Times(1)
301  .WillOnce(Return(dynamic::array()));
302  auto rawPersistence = this->persistence.get();
303  auto cache = createCacheWithExecutor<TypeParam>(
304  this->manualExecutor,
305  std::move(this->persistence),
306  std::chrono::milliseconds::zero(),
307  2);
308  EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
309  .WillRepeatedly(Invoke(
310  rawPersistence,
312 
313  cache->put("k0", "v0");
314  EXPECT_CALL(*rawPersistence, persist_(DynSize(1)))
315  .Times(1)
316  .WillOnce(Return(false));
317  this->manualExecutor->run();
318 
319  cache->put("k1", "v1");
320  EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
321  .Times(1)
322  .WillOnce(Return(false));
323  // reached retry limit, so we will set a version anyway
324  EXPECT_CALL(*rawPersistence, setPersistedVersion(_))
325  .Times(1)
326  .WillOnce(Invoke(
328  this->manualExecutor->run();
329 
330  cache.reset();
331 }
332 
333 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheSchduledAndDealloc) {
335  EXPECT_CALL(*this->persistence, load_())
336  .Times(1)
337  .WillOnce(Return(data));
338  auto cache = createCacheWithExecutor<TypeParam>(
339  this->manualExecutor,
340  std::move(this->persistence),
341  std::chrono::milliseconds::zero(),
342  1);
343  cache->put("k0", "v0");
344  cache->put("k2", "v2");
345 
346  // Kill cache first then try to run scheduled tasks. Nothing will run and no
347  // one should crash.
348  cache.reset();
349  this->manualExecutor->drain();
350 }
351 
352 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheScheduleInterval) {
353  EXPECT_CALL(*this->persistence, load_())
354  .Times(1)
355  .WillOnce(Return(dynamic::array()));
356  auto rawPersistence = this->persistence.get();
357  auto cache = createCacheWithExecutor<TypeParam>(
358  this->manualExecutor,
359  std::move(this->persistence),
360  std::chrono::milliseconds(60 * 60 * 1000),
361  1);
362  EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
363  .WillRepeatedly(Invoke(
364  rawPersistence,
366 
367  cache->put("k0", "v0");
368  EXPECT_CALL(*rawPersistence, persist_(DynSize(1)))
369  .Times(1)
370  .WillOnce(Return(false));
371  this->manualExecutor->run();
372 
373  // None of the following will trigger a run
374  EXPECT_CALL(*rawPersistence, persist_(DynSize(2))).Times(0);
375  EXPECT_CALL(*rawPersistence, setPersistedVersion(_)).Times(0);
376  cache->put("k1", "v1");
377  this->manualExecutor->run();
378  cache.reset();
379  this->manualExecutor->drain();
380 }
static shared_ptr< LRUPersistentCache< string, string, T > > createCacheWithExecutor(std::shared_ptr< folly::Executor > executor, std::unique_ptr< TestPersistenceLayer > persistence, std::chrono::milliseconds syncInterval, int retryLimit)
static shared_ptr< LRUPersistentCache< string, string, T > > createCache(size_t capacity, uint32_t syncMillis, std::unique_ptr< TestPersistenceLayer > persistence=nullptr)
#define GMOCK_METHOD1_(tn, constness, ct, Method,...)
GTEST_API_ Cardinality AtLeast(int n)
CacheDataVersion getLastPersistedVersionConcrete() const
uint64_t CacheDataVersion
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
void setPersistedVersionConcrete(CacheDataVersion version)
STL namespace.
double val
Definition: String.cpp:273
Gen seq(Value first, Value last)
Definition: Base.h:484
Optional< dynamic > load() noexceptoverride
unique_ptr< MockPersistenceLayer > persistence
TYPED_TEST_CASE(SynchronizedTest, SynchronizedTestTypes)
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
requires E e noexcept(noexcept(s.error(std::move(e))))
PUSHMI_INLINE_VAR constexpr __adl::get_executor_fn executor
ProtocolVersion version
virtual void setPersistedVersion(CacheDataVersion version) noexcept
PolymorphicAction< internal::InvokeAction< FunctionImpl > > Invoke(FunctionImpl function_impl)
TYPED_TEST(SynchronizedTest, Basic)
constexpr auto data(C &c) -> decltype(c.data())
Definition: Access.h:71
std::shared_ptr< folly::ManualExecutor > manualExecutor
::testing::Types< std::mutex, folly::SharedMutex > MutexTypes
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
#define ON_CALL(obj, call)
#define MOCK_METHOD1(m,...)
static void array(EmptyArrayTag)
Definition: dynamic-inl.h:233
#define MOCK_CONST_METHOD0(m,...)
#define EXPECT_CALL(obj, call)
const internal::AnythingMatcher _
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
virtual CacheDataVersion getLastPersistedVersion() const
bool persist(const dynamic &obj) noexceptoverride
Future< typename std::decay< T >::type > makeFuture(T &&t)
Definition: Future-inl.h:1310
MATCHER_P(DynSize, n,"")
internal::ReturnAction< R > Return(R value)
std::shared_ptr< folly::InlineExecutor > inlineExecutor
#define MOCK_METHOD0(m,...)