Adding a unit test for HHWheelTimer exercising the default timeout functionality.
[folly.git] / folly / io / async / test / HHWheelTimerTest.cpp
index 05a84c8f4fadb505822aa73c77c7e8ee04bbed03..695b6e7d10097dc651f74149723bc0ce69a69351 100644 (file)
@@ -35,22 +35,44 @@ class TestTimeout : public HHWheelTimer::Callback {
   TestTimeout(HHWheelTimer* t, milliseconds timeout) {
     t->scheduleTimeout(this, timeout);
   }
-  virtual void timeoutExpired() noexcept {
-    timestamps.push_back(TimePoint());
+
+  void timeoutExpired() noexcept override {
+    timestamps.emplace_back();
+    if (fn) {
+      fn();
+    }
+  }
+
+  void callbackCanceled() noexcept override {
+    canceledTimestamps.emplace_back();
     if (fn) {
       fn();
     }
   }
 
   std::deque<TimePoint> timestamps;
+  std::deque<TimePoint> canceledTimestamps;
   std::function<void()> fn;
 };
 
+
+class TestTimeoutDelayed : public TestTimeout {
+ protected:
+    std::chrono::milliseconds getCurTime() override {
+      return std::chrono::duration_cast<std::chrono::milliseconds>(
+        std::chrono::steady_clock::now().time_since_epoch()) -
+        milliseconds(5);
+    }
+};
+
+struct HHWheelTimerTest : public ::testing::Test {
+  EventBase eventBase;
+};
+
 /*
  * Test firing some simple timeouts that are fired once and never rescheduled
  */
-TEST(HHWheelTimerTest, FireOnce) {
-  EventBase eventBase;
+TEST_F(HHWheelTimerTest, FireOnce) {
   StackWheelTimer t(&eventBase, milliseconds(1));
 
   const HHWheelTimer::Callback* nullCallback = nullptr;
@@ -80,18 +102,42 @@ TEST(HHWheelTimerTest, FireOnce) {
 
   ASSERT_EQ(t.count(), 0);
 
-  T_CHECK_TIMEOUT(start, t1.timestamps[0], 5);
-  T_CHECK_TIMEOUT(start, t2.timestamps[0], 5);
-  T_CHECK_TIMEOUT(start, t3.timestamps[0], 10);
-  T_CHECK_TIMEOUT(start, end, 10);
+  T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
+  T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
+  T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
+  T_CHECK_TIMEOUT(start, end, milliseconds(10));
+}
+
+/*
+ * Test scheduling a timeout from another timeout callback.
+ */
+TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
+  StackWheelTimer t(&eventBase, milliseconds(10));
+  const HHWheelTimer::Callback* nullCallback = nullptr;
+
+  TestTimeout t1;
+  // Delayed to simulate the steady_clock counter lagging
+  TestTimeoutDelayed t2;
+
+  t.scheduleTimeout(&t1, milliseconds(500));
+  t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
+  // If t is in an inconsistent state, detachEventBase should fail.
+  t2.fn = [&] { t.detachEventBase(); };
+
+  ASSERT_EQ(t.count(), 1);
+
+  eventBase.loop();
+
+  ASSERT_EQ(t.count(), 0);
+  ASSERT_EQ(t1.timestamps.size(), 1);
+  ASSERT_EQ(t2.timestamps.size(), 1);
 }
 
 /*
  * Test cancelling a timeout when it is scheduled to be fired right away.
  */
 
-TEST(HHWheelTimerTest, CancelTimeout) {
-  EventBase eventBase;
+TEST_F(HHWheelTimerTest, CancelTimeout) {
   StackWheelTimer t(&eventBase, milliseconds(1));
 
   // Create several timeouts that will all fire in 5ms.
@@ -146,16 +192,16 @@ TEST(HHWheelTimerTest, CancelTimeout) {
   TimePoint end;
 
   ASSERT_EQ(t5_1.timestamps.size(), 1);
-  T_CHECK_TIMEOUT(start, t5_1.timestamps[0], 5);
+  T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
 
   ASSERT_EQ(t5_3.timestamps.size(), 2);
-  T_CHECK_TIMEOUT(start, t5_3.timestamps[0], 5);
-  T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], 5);
+  T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
+  T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
 
   ASSERT_EQ(t10_1.timestamps.size(), 1);
-  T_CHECK_TIMEOUT(start, t10_1.timestamps[0], 10);
+  T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
   ASSERT_EQ(t10_3.timestamps.size(), 1);
-  T_CHECK_TIMEOUT(start, t10_3.timestamps[0], 10);
+  T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
 
   // Cancelled timeouts
   ASSERT_EQ(t5_2.timestamps.size(), 0);
@@ -165,15 +211,14 @@ TEST(HHWheelTimerTest, CancelTimeout) {
   ASSERT_EQ(t20_1.timestamps.size(), 0);
   ASSERT_EQ(t20_2.timestamps.size(), 0);
 
-  T_CHECK_TIMEOUT(start, end, 10);
+  T_CHECK_TIMEOUT(start, end, milliseconds(10));
 }
 
 /*
  * Test destroying a HHWheelTimer with timeouts outstanding
  */
 
-TEST(HHWheelTimerTest, DestroyTimeoutSet) {
-  EventBase eventBase;
+TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
 
   HHWheelTimer::UniquePtr t(
     new HHWheelTimer(&eventBase, milliseconds(1)));
@@ -200,22 +245,21 @@ TEST(HHWheelTimerTest, DestroyTimeoutSet) {
   TimePoint end;
 
   ASSERT_EQ(t5_1.timestamps.size(), 1);
-  T_CHECK_TIMEOUT(start, t5_1.timestamps[0], 5);
+  T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
   ASSERT_EQ(t5_2.timestamps.size(), 1);
-  T_CHECK_TIMEOUT(start, t5_2.timestamps[0], 5);
+  T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
 
   ASSERT_EQ(t5_3.timestamps.size(), 0);
   ASSERT_EQ(t10_1.timestamps.size(), 0);
   ASSERT_EQ(t10_2.timestamps.size(), 0);
 
-  T_CHECK_TIMEOUT(start, end, 5);
+  T_CHECK_TIMEOUT(start, end, milliseconds(5));
 }
 
 /*
  * Test the tick interval parameter
  */
-TEST(HHWheelTimerTest, AtMostEveryN) {
-  EventBase eventBase;
+TEST_F(HHWheelTimerTest, AtMostEveryN) {
 
   // Create a timeout set with a 10ms interval, to fire no more than once
   // every 3ms.
@@ -278,8 +322,7 @@ TEST(HHWheelTimerTest, AtMostEveryN) {
     // T_CHECK_TIMEOUT() normally has a tolerance of 5ms.  Allow an additional
     // atMostEveryN.
     milliseconds tolerance = milliseconds(5) + interval;
-    T_CHECK_TIMEOUT(scheduledTime, firedTime, atMostEveryN.count(),
-                    tolerance.count());
+    T_CHECK_TIMEOUT(scheduledTime, firedTime, atMostEveryN, tolerance);
 
     // Assert that the difference between the previous timeout and now was
     // either very small (fired in the same event loop), or larger than
@@ -290,10 +333,11 @@ TEST(HHWheelTimerTest, AtMostEveryN) {
     }
     TimePoint prev(timeouts[idx - 1].timestamps[1]);
 
-    milliseconds delta((firedTime.getTimeStart() - prev.getTimeEnd()) -
-                       (firedTime.getTimeWaiting() - prev.getTimeWaiting()));
+    auto delta = (firedTime.getTimeStart() - prev.getTimeEnd()) -
+      (firedTime.getTimeWaiting() - prev.getTimeWaiting());
     if (delta > milliseconds(1)) {
-      T_CHECK_TIMEOUT(prev, firedTime, atMostEveryN.count()); }
+      T_CHECK_TIMEOUT(prev, firedTime, atMostEveryN);
+    }
   }
 }
 
@@ -301,8 +345,7 @@ TEST(HHWheelTimerTest, AtMostEveryN) {
  * Test an event loop that is blocking
  */
 
-TEST(HHWheelTimerTest, SlowLoop) {
-  EventBase eventBase;
+TEST_F(HHWheelTimerTest, SlowLoop) {
   StackWheelTimer t(&eventBase, milliseconds(1));
 
   TestTimeout t1;
@@ -323,8 +366,8 @@ TEST(HHWheelTimerTest, SlowLoop) {
   ASSERT_EQ(t.count(), 0);
 
   // Check that the timeout was delayed by sleep
-  T_CHECK_TIMEOUT(start, t1.timestamps[0], 15, 1);
-  T_CHECK_TIMEOUT(start, end, 15, 1);
+  T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(15), milliseconds(1));
+  T_CHECK_TIMEOUT(start, end, milliseconds(15), milliseconds(1));
 
   // Try it again, this time with catchup timing every loop
   t.setCatchupEveryN(1);
@@ -342,6 +385,66 @@ TEST(HHWheelTimerTest, SlowLoop) {
   ASSERT_EQ(t.count(), 0);
 
   // Check that the timeout was NOT delayed by sleep
-  T_CHECK_TIMEOUT(start2, t2.timestamps[0], 10, 1);
-  T_CHECK_TIMEOUT(start2, end2, 10, 1);
+  T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(10), milliseconds(1));
+  T_CHECK_TIMEOUT(start2, end2, milliseconds(10), milliseconds(1));
+}
+
+/*
+ * Test scheduling a mix of timers with default timeout and variable timeout.
+ */
+TEST_F(HHWheelTimerTest, DefaultTimeout) {
+  milliseconds defaultTimeout(milliseconds(5));
+  StackWheelTimer t(&eventBase,
+                    milliseconds(1),
+                    AsyncTimeout::InternalEnum::NORMAL,
+                    defaultTimeout);
+
+  TestTimeout t1;
+  TestTimeout t2;
+
+  ASSERT_EQ(t.count(), 0);
+  ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
+
+  t.scheduleTimeout(&t1);
+  t.scheduleTimeout(&t2, milliseconds(10));
+
+  ASSERT_EQ(t.count(), 2);
+
+  TimePoint start;
+  eventBase.loop();
+  TimePoint end;
+
+  ASSERT_EQ(t1.timestamps.size(), 1);
+  ASSERT_EQ(t2.timestamps.size(), 1);
+
+  ASSERT_EQ(t.count(), 0);
+
+  T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
+  T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
+  T_CHECK_TIMEOUT(start, end, milliseconds(10));
+}
+
+TEST_F(HHWheelTimerTest, lambda) {
+  StackWheelTimer t(&eventBase, milliseconds(1));
+  size_t count = 0;
+  t.scheduleTimeoutFn([&]{ count++; }, milliseconds(1));
+  eventBase.loop();
+  EXPECT_EQ(1, count);
+}
+
+// shouldn't crash because we swallow and log the error (you'll have to look
+// at the console to confirm logging)
+TEST_F(HHWheelTimerTest, lambdaThrows) {
+  StackWheelTimer t(&eventBase, milliseconds(1));
+  t.scheduleTimeoutFn([&]{ throw std::runtime_error("expected"); },
+                      milliseconds(1));
+  eventBase.loop();
+}
+
+TEST_F(HHWheelTimerTest, cancelAll) {
+  StackWheelTimer t(&eventBase);
+  TestTimeout tt;
+  t.scheduleTimeout(&tt, std::chrono::minutes(1));
+  EXPECT_EQ(1, t.cancelAll());
+  EXPECT_EQ(1, tt.canceledTimestamps.size());
 }