tests: introduce ClockFixture

Identical to UnitTestTimeFixture, except that it does not derive
from BaseFixture, and thus does not depend on a global io_service.

Refs: #4528
Change-Id: Ia2e6c4835a1e461c73281e103109d5cc85be355e
diff --git a/tests/clock-fixture.cpp b/tests/clock-fixture.cpp
new file mode 100644
index 0000000..de55cf3
--- /dev/null
+++ b/tests/clock-fixture.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tests/clock-fixture.hpp"
+
+namespace nfd {
+namespace tests {
+
+ClockFixture::ClockFixture(boost::asio::io_service& io)
+  : m_steadyClock(make_shared<time::UnitTestSteadyClock>())
+  , m_systemClock(make_shared<time::UnitTestSystemClock>())
+  , m_io(io)
+{
+  time::setCustomClocks(m_steadyClock, m_systemClock);
+}
+
+ClockFixture::~ClockFixture()
+{
+  time::setCustomClocks(nullptr, nullptr);
+}
+
+void
+ClockFixture::advanceClocks(time::nanoseconds tick, time::nanoseconds total)
+{
+  BOOST_ASSERT(tick > time::nanoseconds::zero());
+  BOOST_ASSERT(total >= time::nanoseconds::zero());
+
+  while (total > time::nanoseconds::zero()) {
+    auto t = std::min(tick, total);
+    m_steadyClock->advance(t);
+    m_systemClock->advance(t);
+    total -= t;
+
+    pollAfterClockTick();
+  }
+}
+
+void
+ClockFixture::pollAfterClockTick()
+{
+  if (m_io.stopped())
+    m_io.reset();
+  m_io.poll();
+}
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/clock-fixture.hpp b/tests/clock-fixture.hpp
new file mode 100644
index 0000000..5c69334
--- /dev/null
+++ b/tests/clock-fixture.hpp
@@ -0,0 +1,91 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TESTS_CLOCK_FIXTURE_HPP
+#define NFD_TESTS_CLOCK_FIXTURE_HPP
+
+#include "core/common.hpp"
+
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+
+namespace nfd {
+namespace tests {
+
+/** \brief A test fixture that overrides steady clock and system clock.
+ */
+class ClockFixture
+{
+public:
+  virtual
+  ~ClockFixture();
+
+  /** \brief Advance steady and system clocks.
+   *
+   *  Clocks are advanced in increments of \p tick for \p nTicks ticks.
+   *  After each tick, global io_service is polled to process pending I/O events.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancing would stop in case of an exception.
+   */
+  void
+  advanceClocks(time::nanoseconds tick, size_t nTicks = 1)
+  {
+    advanceClocks(tick, tick * nTicks);
+  }
+
+  /** \brief Advance steady and system clocks.
+   *
+   *  Clocks are advanced in increments of \p tick for \p total time.
+   *  The last increment might be shorter than \p tick.
+   *  After each tick, global io_service is polled to process pending I/O events.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancing would stop in case of an exception.
+   */
+  void
+  advanceClocks(time::nanoseconds tick, time::nanoseconds total);
+
+protected:
+  explicit
+  ClockFixture(boost::asio::io_service& io);
+
+private:
+  /** \brief Called by advanceClocks() after each clock advancement (tick).
+   */
+  virtual void
+  pollAfterClockTick();
+
+protected:
+  shared_ptr<time::UnitTestSteadyClock> m_steadyClock;
+  shared_ptr<time::UnitTestSystemClock> m_systemClock;
+
+private:
+  boost::asio::io_service& m_io;
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_CLOCK_FIXTURE_HPP
diff --git a/tests/daemon/rib-io-fixture.cpp b/tests/daemon/rib-io-fixture.cpp
index 251d6e9..98563a9 100644
--- a/tests/daemon/rib-io-fixture.cpp
+++ b/tests/daemon/rib-io-fixture.cpp
@@ -23,7 +23,7 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "rib-io-fixture.hpp"
+#include "tests/daemon/rib-io-fixture.hpp"
 #include "daemon/global.hpp"
 
 #include <boost/exception/diagnostic_information.hpp>
@@ -122,26 +122,9 @@
 }
 
 void
-RibIoTimeFixture::advanceClocks(time::nanoseconds tick, time::nanoseconds total)
+RibIoTimeFixture::pollAfterClockTick()
 {
-  BOOST_ASSERT(tick > time::nanoseconds::zero());
-  BOOST_ASSERT(total >= time::nanoseconds::zero());
-
-  time::nanoseconds remaining = total;
-  while (remaining > time::nanoseconds::zero()) {
-    if (remaining >= tick) {
-      steadyClock->advance(tick);
-      systemClock->advance(tick);
-      remaining -= tick;
-    }
-    else {
-      steadyClock->advance(remaining);
-      systemClock->advance(remaining);
-      remaining = time::nanoseconds::zero();
-    }
-
-    poll();
-  }
+  poll();
 }
 
 } // namespace tests
diff --git a/tests/daemon/rib-io-fixture.hpp b/tests/daemon/rib-io-fixture.hpp
index 99d4387..67b803c 100644
--- a/tests/daemon/rib-io-fixture.hpp
+++ b/tests/daemon/rib-io-fixture.hpp
@@ -35,7 +35,7 @@
 namespace nfd {
 namespace tests {
 
-/** \brief a base test fixture that provides both main and RIB io_service
+/** \brief A base test fixture that provides both main and RIB io_service.
  */
 class RibIoFixture : public virtual BaseFixture
 {
@@ -45,13 +45,13 @@
   ~RibIoFixture();
 
 protected:
-  /** \brief Poll main and RIB thread io_service to process all pending I/O events
+  /** \brief Poll main and RIB thread io_service to process all pending I/O events.
    *
    * This call will execute all pending I/O events, including events that are posted
    * inside the processing event, i.e., main and RIB thread io_service will be polled
    * repeatedly until all pending events are processed.
    *
-   *  \note Must be called from the main thread
+   * \warning Must be called from the main thread
    */
   void
   poll();
@@ -77,26 +77,13 @@
   std::condition_variable m_ribPollEndCv;
 };
 
-/** \brief RibIoFixture that also overrides steady clock and system clock
+/** \brief RibIoFixture that also overrides steady clock and system clock.
  */
 class RibIoTimeFixture : public RibIoFixture, public UnitTestTimeFixture
 {
-protected:
-  using UnitTestTimeFixture::advanceClocks;
-
-  /** \brief advance steady and system clocks in the main and RIB threads
-   *
-   *  Clocks are advanced in increments of \p tick for \p total time.
-   *  The last increment might be shorter than \p tick.
-   *  After each tick, the main and RIB thread io_service is polled to process pending I/O events.
-   *
-   *  Exceptions thrown during I/O events are propagated to the caller.
-   *  Clock advancing would stop in case of an exception.
-   *
-   *  \note Must be called from the main thread
-   */
+private:
   void
-  advanceClocks(time::nanoseconds tick, time::nanoseconds total) override;
+  pollAfterClockTick() override;
 };
 
 } // namespace tests
diff --git a/tests/test-common.cpp b/tests/test-common.cpp
index 59060d6..236007e 100644
--- a/tests/test-common.cpp
+++ b/tests/test-common.cpp
@@ -23,7 +23,7 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "test-common.hpp"
+#include "tests/test-common.hpp"
 #include "daemon/global.hpp"
 
 #include <ndn-cxx/security/signature-sha256-with-rsa.hpp>
@@ -41,49 +41,6 @@
   resetGlobalIoService();
 }
 
-UnitTestTimeFixture::UnitTestTimeFixture()
-  : steadyClock(make_shared<time::UnitTestSteadyClock>())
-  , systemClock(make_shared<time::UnitTestSystemClock>())
-{
-  time::setCustomClocks(steadyClock, systemClock);
-}
-
-UnitTestTimeFixture::~UnitTestTimeFixture()
-{
-  time::setCustomClocks(nullptr, nullptr);
-}
-
-void
-UnitTestTimeFixture::advanceClocks(time::nanoseconds tick, size_t nTicks)
-{
-  advanceClocks(tick, tick * nTicks);
-}
-
-void
-UnitTestTimeFixture::advanceClocks(time::nanoseconds tick, time::nanoseconds total)
-{
-  BOOST_ASSERT(tick > time::nanoseconds::zero());
-  BOOST_ASSERT(total >= time::nanoseconds::zero());
-
-  time::nanoseconds remaining = total;
-  while (remaining > time::nanoseconds::zero()) {
-    if (remaining >= tick) {
-      steadyClock->advance(tick);
-      systemClock->advance(tick);
-      remaining -= tick;
-    }
-    else {
-      steadyClock->advance(remaining);
-      systemClock->advance(remaining);
-      remaining = time::nanoseconds::zero();
-    }
-
-    if (g_io.stopped())
-      g_io.reset();
-    g_io.poll();
-  }
-}
-
 shared_ptr<Interest>
 makeInterest(const Name& name, uint32_t nonce)
 {
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
index 178502a..775c032 100644
--- a/tests/test-common.hpp
+++ b/tests/test-common.hpp
@@ -28,10 +28,9 @@
 
 #include "boost-test.hpp"
 
-#include "core/common.hpp"
+#include "tests/clock-fixture.hpp"
 
 #include <ndn-cxx/prefix-announcement.hpp>
-#include <ndn-cxx/util/time-unit-test-clock.hpp>
 
 #ifdef HAVE_PRIVILEGE_DROP_AND_ELEVATE
 #include <unistd.h>
@@ -70,42 +69,13 @@
 
 /** \brief a base test fixture that overrides steady clock and system clock
  */
-class UnitTestTimeFixture : public virtual BaseFixture
+class UnitTestTimeFixture : public virtual BaseFixture, public ClockFixture
 {
 protected:
-  UnitTestTimeFixture();
-
-  virtual
-  ~UnitTestTimeFixture();
-
-  /** \brief advance steady and system clocks
-   *
-   *  Clocks are advanced in increments of \p tick for \p nTicks ticks.
-   *  After each tick, global io_service is polled to process pending I/O events.
-   *
-   *  Exceptions thrown during I/O events are propagated to the caller.
-   *  Clock advancing would stop in case of an exception.
-   */
-  virtual void
-  advanceClocks(time::nanoseconds tick, size_t nTicks = 1);
-
-  /** \brief advance steady and system clocks
-   *
-   *  Clocks are advanced in increments of \p tick for \p total time.
-   *  The last increment might be shorter than \p tick.
-   *  After each tick, global io_service is polled to process pending I/O events.
-   *
-   *  Exceptions thrown during I/O events are propagated to the caller.
-   *  Clock advancing would stop in case of an exception.
-   */
-  virtual void
-  advanceClocks(time::nanoseconds tick, time::nanoseconds total);
-
-protected:
-  shared_ptr<time::UnitTestSteadyClock> steadyClock;
-  shared_ptr<time::UnitTestSystemClock> systemClock;
-
-  friend class LimitedIo;
+  UnitTestTimeFixture()
+    : ClockFixture(g_io)
+  {
+  }
 };
 
 /** \brief create an Interest
diff --git a/tests/tools/mock-nfd-mgmt-fixture.hpp b/tests/tools/mock-nfd-mgmt-fixture.hpp
index 9e8bd53..1e1d592 100644
--- a/tests/tools/mock-nfd-mgmt-fixture.hpp
+++ b/tests/tools/mock-nfd-mgmt-fixture.hpp
@@ -26,8 +26,9 @@
 #ifndef NFD_TESTS_TOOLS_MOCK_NFD_MGMT_FIXTURE_HPP
 #define NFD_TESTS_TOOLS_MOCK_NFD_MGMT_FIXTURE_HPP
 
-#include "tests/test-common.hpp"
+#include "tests/clock-fixture.hpp"
 #include "tests/key-chain-fixture.hpp"
+#include "tests/test-common.hpp"
 
 #include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
 #include <ndn-cxx/mgmt/nfd/control-response.hpp>
@@ -40,27 +41,23 @@
 using namespace nfd::tests;
 using ndn::nfd::ControlParameters;
 
-/** \brief fixture to emulate NFD management
+/** \brief Fixture to emulate NFD management.
  */
-class MockNfdMgmtFixture : public UnitTestTimeFixture, public KeyChainFixture
+class MockNfdMgmtFixture : public ClockFixture, public KeyChainFixture
 {
 protected:
   MockNfdMgmtFixture()
-    : face(g_io, m_keyChain,
+    : ClockFixture(m_io)
+    , face(m_io, m_keyChain,
            {true, false, bind(&MockNfdMgmtFixture::processEventsOverride, this, _1)})
   {
-    face.onSendInterest.connect([=] (const Interest& interest) {
-      g_io.post([=] {
-        if (processInterest != nullptr) {
-          processInterest(interest);
-        }
-      });
+    face.onSendInterest.connect([this] (const Interest& interest) {
+      if (processInterest) {
+        m_io.post([=] { processInterest(interest); });
+      }
     });
   }
 
-  virtual
-  ~MockNfdMgmtFixture() = default;
-
 protected: // ControlCommand
   /** \brief check the Interest is a command with specified prefix
    *  \retval nullopt last Interest is not the expected command
@@ -206,6 +203,9 @@
     signData(data);
   }
 
+private:
+  boost::asio::io_service m_io;
+
 protected:
   ndn::util::DummyClientFace face;
   std::function<void(const Interest&)> processInterest;
diff --git a/tests/tools/nfdc/status-report.t.cpp b/tests/tools/nfdc/status-report.t.cpp
index 6b1cd20..7c8e32c 100644
--- a/tests/tools/nfdc/status-report.t.cpp
+++ b/tests/tools/nfdc/status-report.t.cpp
@@ -120,11 +120,12 @@
   std::function<void()> processEventsFunc;
 };
 
-class StatusReportModulesFixture : public UnitTestTimeFixture, public KeyChainFixture
+class StatusReportModulesFixture : public ClockFixture, public KeyChainFixture
 {
 protected:
   StatusReportModulesFixture()
-    : face(g_io, m_keyChain)
+    : ClockFixture(m_io)
+    , face(m_io, m_keyChain)
     , controller(face, m_keyChain, validator)
     , res(0)
   {
@@ -133,7 +134,7 @@
   DummyModule&
   addModule(const std::string& moduleName)
   {
-    report.sections.push_back(make_unique<DummyModule>(moduleName, g_io));
+    report.sections.push_back(make_unique<DummyModule>(moduleName, m_io));
     return static_cast<DummyModule&>(*report.sections.back());
   }
 
@@ -153,6 +154,9 @@
     }
   }
 
+private:
+  boost::asio::io_service m_io;
+
 protected:
   ndn::util::DummyClientFace face;
   ValidatorNull validator;
@@ -164,7 +168,6 @@
   output_test_stream statusText;
 };
 
-
 BOOST_AUTO_TEST_SUITE(Nfdc)
 BOOST_FIXTURE_TEST_SUITE(TestStatusReport, StatusReportModulesFixture)