proxygen
MultiFilePollerTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2004-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  */
17 
18 #include <folly/futures/Future.h>
19 #include <folly/FileUtil.h>
20 #include <folly/MapUtil.h>
21 #include <folly/String.h>
25 
26 using namespace wangle;
27 using namespace folly::test;
28 
29 // Fine to set below 1_s because we have a padding of kWriteWaitMs.
30 static const std::chrono::milliseconds kPollIntervalMs{200};
31 
32 // FilePoller's min granularity.
33 static const std::chrono::milliseconds kWriteWaitMs{1000};
34 
35 // Should be a few times larger than kPollInterval.
36 static const std::chrono::milliseconds kMaxSemaphoreWaitMs{1000};
37 
39  public:
40  void SetUp() override {
41  updater_.reset(new MultiFilePoller(kPollIntervalMs));
42  tmpdirPath_ = folly::fs::canonical(tmpdir_.path()).string();
43  }
44 
45  void delayedWrite(const std::string& path, const std::string& data) {
46  // The delay makes sure mtime (in granularity of sec) of the modified
47  // file is increased by at least 1. Otherwise wangle::FilePoller may not
48  // detect the change.
50  .delayed(kWriteWaitMs)
51  .thenValue(
52  [&](auto&&) { ASSERT_TRUE(folly::writeFile(data, path.c_str())); })
53  .wait();
54  }
55 
56  protected:
57  std::unique_ptr<MultiFilePoller> updater_;
59  TemporaryDirectory tmpdir_{"MultiFilePollerTest"};
60 };
61 
70  const std::string f(tmpdirPath_ + "/Basic1"), d1("a"), d2("b"), d3("c");
71  folly::SaturatingSemaphore<true /* MayBlock */> sem;
72  size_t count = 0;
73 
74  // Write initial data.
75  ASSERT_TRUE(folly::writeFile(d1, f.c_str()));
76 
77  // Register the callback.
78  auto cbId = updater_->registerFile(
79  f, [&](const MultiFilePoller::CallbackArg& newData) {
80  auto& content = folly::get_or_throw(newData, f);
81  EXPECT_EQ(d2, content);
82  EXPECT_EQ(1, ++count);
83  sem.post();
84  });
85 
86  delayedWrite(f, d2);
87 
88  // Check whether the callback is triggered by acquiring the semaphore.
91  sem.reset();
92  ASSERT_EQ(1, count);
93 
94  // Cancel the callback.
95  updater_->cancelCallback(cbId);
96 
97  // Write to the file again. The callback should not run.
98  delayedWrite(f, d3);
99 
102  // If the callback runs, the assertion inside callback will also fail.
103 }
104 
109 TEST_F(MultiFilePollerTest, CancellationTest) {
110  const std::string f(tmpdirPath_ + "/Cancel1"), d("111");
111 
112  auto cb = updater_->registerFile(
113  f, [&](const MultiFilePoller::CallbackArg& /* unused */) { FAIL(); });
114 
115  // Proper cancellation.
116  updater_->cancelCallback(cb);
117 
118  // Refuse double cancellation.
119  EXPECT_THROW(updater_->cancelCallback(cb), std::out_of_range);
120 
121  delayedWrite(f, d);
122  // Test will also fail if the callback runs.
123 }
124 
136  const std::string f1(tmpdirPath_ + "/Complex1"), d1("a"), d11("AA");
137  const std::string f2(tmpdirPath_ + "/Complex2"), d2("b"), d21("B"), d22("X");
138  const std::string f3(tmpdirPath_ + "/Complex3"), d3("c");
139 
140  folly::SaturatingSemaphore<true> sem1, sem2, sem3, sem4;
141  std::string data2, data4;
142  std::vector<std::string> data3;
143  size_t count1 = 0, count2 = 0, count3 = 0, count4 = 0;
144 
145  // cb1 is only triggered once. It expects the content to equal d1, which is
146  // written to the file to trigger the callback.
147  auto cb1 = updater_->registerFile(
148  f1, [&](const MultiFilePoller::CallbackArg& newData) {
149  auto& content = folly::get_or_throw(newData, f1);
150  EXPECT_EQ(d1, content);
151  EXPECT_EQ(1, ++count1); // Fail if run more than once.
152  sem1.post();
153  });
154 
155  // cb2 copies content of f2 from arg to data2, and increments count2.
156  auto cb2 = updater_->registerFile(
157  f2, [&](const MultiFilePoller::CallbackArg& newData) {
158  data2 = folly::get_or_throw(newData, f2);
159  count2++;
160  sem2.post();
161  });
162 
163  // cb3 concatenates the data of f3 and f3 and writes the value to data3.
164  /* cb3 */ updater_->registerFiles(
165  {f3, f1}, [&](const MultiFilePoller::CallbackArg& newData) {
166  if (count3 == 0) {
167  // When we write to f3 to trigger cb3, f1 does not exist yet.
168  // Check that f1 is not in the map.
169  ASSERT_EQ(newData.end(), newData.find(f1));
170  } else {
171  // Otherwise f1 must exist in the map.
172  ASSERT_NE(newData.end(), newData.find(f1));
173  }
174  const auto& f3Data = folly::get_or_throw(newData, f3);
175  const auto& f1Data = folly::get_default(newData, f1, "<NODATA>");
176  data3 = {f3Data, f1Data};
177  count3++;
178  sem3.post();
179  });
180 
181  // Create f2 to trigger cb2.
182  ASSERT_TRUE(folly::writeFile(d2, f2.c_str()));
185  sem2.reset();
186  EXPECT_EQ(1, count2); // +1.
187  EXPECT_EQ(0, count1); // No change.
188  EXPECT_EQ(0, count3); // No change.
189  EXPECT_EQ(d2, data2); // The data obtained by cb should equal what's written.
190 
191  // Create f3 to trigger cb3. Note that cb1 should not run.
192  ASSERT_TRUE(folly::writeFile(d3, f3.c_str()));
195  sem3.reset();
196  EXPECT_EQ(1, count3); // +1.
197  EXPECT_EQ(0, count1); // No change.
198  EXPECT_EQ(1, count2); // No change.
199  EXPECT_EQ(std::vector<std::string>({d3, "<NODATA>"}), data3);
200 
201  // Create f1 to trigger cb3 and cb1. Order doesn't matter.
202  ASSERT_TRUE(folly::writeFile(d1, f1.c_str()));
205  sem1.reset();
206  EXPECT_EQ(1, count1); // +1.
207  EXPECT_EQ(1, count2); // No change.
210  sem3.reset();
211  EXPECT_EQ(2, count3); // +1.
212  EXPECT_EQ(std::vector<std::string>({d3, d1}), data3);
213 
214  // cb4 is the same as cb2 except that it's another callback.
215  auto cb4 = updater_->registerFile(
216  f2, [&](const MultiFilePoller::CallbackArg& newData) {
217  data4 = folly::get_or_throw(newData, f2);
218  count4++;
219  sem4.post();
220  });
221 
222  // Write to f2 to trigger second callback.
223  delayedWrite(f2, d21);
224 
227  sem2.reset();
230  sem4.reset();
231  EXPECT_EQ(2, count2); // +1.
232  EXPECT_EQ(1, count4); // +1.
233  EXPECT_EQ(1, count1); // No change.
234  EXPECT_EQ(2, count3); // No change.
235  EXPECT_EQ(d21, data2); // Both data2 and data4 got what was written.
236  EXPECT_EQ(d21, data4);
237 
238  // f1 is in two different callbacks. Cancel cb1 should not affect cb3.
239  updater_->cancelCallback(cb1);
240  ASSERT_TRUE(folly::writeFile(d11, f1.c_str())); // Last write to f1 > 1s ago.
243  sem3.reset();
246  EXPECT_EQ(3, count3); // +1.
247  EXPECT_EQ(1, count1); // No change.
248  EXPECT_EQ(2, count2); // No change.
249  EXPECT_EQ(1, count4); // No change.
250  EXPECT_EQ(std::vector<std::string>({d3, d11}), data3);
251 
252  // cb2 and cb4 use and only use f2. Cancel cb2 should not affect cb4.
253  updater_->cancelCallback(cb2);
254  ASSERT_TRUE(folly::writeFile(d22, f2.c_str())); // Last write to f2 > 1s ago.
257  sem4.reset();
260  EXPECT_EQ(d21, data2); // cb2 should not run, so not updated.
261  EXPECT_EQ(d22, data4); // cb4 should run, so updated to d22.
262  EXPECT_EQ(2, count2); // No change.
263  EXPECT_EQ(2, count4); // +1
264 
265  // Now we cancel cb4. Record of f2 should be cleaned up.
266  updater_->cancelCallback(cb4);
267  ASSERT_TRUE(folly::writeFile(d1, f2.c_str()));
270  EXPECT_EQ(d21, data2); // cb2 should not run, so not updated to d1.
271  EXPECT_EQ(d22, data4); // cb4 should not run, so not updated to d1.
272 }
auto f
Map::mapped_type get_default(const Map &map, const Key &key)
Definition: MapUtil.h:31
#define FAIL()
Definition: gtest.h:1822
static const std::chrono::milliseconds kPollIntervalMs
#define EXPECT_THROW(statement, expected_exception)
Definition: gtest.h:1843
#define ASSERT_EQ(val1, val2)
Definition: gtest.h:1956
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
std::chrono::steady_clock::time_point now()
void delayedWrite(const std::string &path, const std::string &data)
std::unordered_map< std::string, std::string > CallbackArg
FOLLY_ALWAYS_INLINE void post() noexcept
FOLLY_ALWAYS_INLINE bool try_wait_until(const std::chrono::time_point< Clock, Duration > &deadline, const WaitOptions &opt=wait_options()) noexcept
static const std::chrono::milliseconds kMaxSemaphoreWaitMs
std::unique_ptr< MultiFilePoller > updater_
bool wait(Waiter *waiter, bool shouldSleep, Waiter *&next)
int * count
const char * string
Definition: Conv.cpp:212
const Map::mapped_type & get_or_throw(const Map &map, const Key &key, const std::string &exceptionStrPrefix=std::string())
Definition: MapUtil.h:71
bool writeFile(const Container &data, const char *filename, int flags=O_WRONLY|O_CREAT|O_TRUNC, mode_t mode=0666)
Definition: FileUtil.h:211
#define ASSERT_NE(val1, val2)
Definition: gtest.h:1960
#define ASSERT_FALSE(condition)
Definition: gtest.h:1868
static const std::chrono::milliseconds kWriteWaitMs
RfcParam d3(false, exampleHex1)
#define ASSERT_TRUE(condition)
Definition: gtest.h:1865
static constexpr uint64_t data[1]
Definition: Fingerprint.cpp:43
Future< typename std::decay< T >::type > makeFuture(T &&t)
Definition: Future-inl.h:1310
TEST_F(AcceptorTest, TestCanAcceptWithNoConnectionCounter)