util: DummyClientFace can override processEvents

refs #3769

Change-Id: Idf8b674ed238f296c7c4293b19a6f769cbd7e332
diff --git a/src/face.cpp b/src/face.cpp
index bbb9f08..d20f57c 100644
--- a/src/face.cpp
+++ b/src/face.cpp
@@ -439,7 +439,7 @@
 }
 
 void
-Face::processEvents(const time::milliseconds& timeout, bool keepThread)
+Face::doProcessEvents(const time::milliseconds& timeout, bool keepThread)
 {
   if (m_ioService.stopped()) {
     m_ioService.reset(); // ensure that run()/poll() will do some work
diff --git a/src/face.hpp b/src/face.hpp
index 27af415..6c6c957 100644
--- a/src/face.hpp
+++ b/src/face.hpp
@@ -676,7 +676,10 @@
    */
   void
   processEvents(const time::milliseconds& timeout = time::milliseconds::zero(),
-                bool keepThread = false);
+                bool keepThread = false)
+  {
+    this->doProcessEvents(timeout, keepThread);
+  }
 
   /**
    * @brief Shutdown face operations
@@ -705,6 +708,10 @@
   shared_ptr<Transport>
   getTransport();
 
+protected:
+  virtual void
+  doProcessEvents(const time::milliseconds& timeout, bool keepThread);
+
 private:
   /**
    * @throw ConfigFile::Error on parse error and unsupported protocols
diff --git a/src/util/dummy-client-face.cpp b/src/util/dummy-client-face.cpp
index a1c6408..43efff9 100644
--- a/src/util/dummy-client-face.cpp
+++ b/src/util/dummy-client-face.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "dummy-client-face.hpp"
+#include "../lp/packet.hpp"
 #include "../lp/tags.hpp"
 #include "../mgmt/nfd/controller.hpp"
 #include "../mgmt/nfd/control-response.hpp"
@@ -30,8 +31,6 @@
 namespace ndn {
 namespace util {
 
-const DummyClientFace::Options DummyClientFace::DEFAULT_OPTIONS{true, false};
-
 class DummyClientFace::Transport : public ndn::Transport
 {
 public:
@@ -163,6 +162,8 @@
 
   if (options.enableRegistrationReply)
     this->enableRegistrationReply();
+
+  m_processEventsOverride = options.processEventsOverride;
 }
 
 void
@@ -250,5 +251,16 @@
   static_pointer_cast<Transport>(getTransport())->receive(lpPacket.wireEncode());
 }
 
+void
+DummyClientFace::doProcessEvents(const time::milliseconds& timeout, bool keepThread)
+{
+  if (m_processEventsOverride != nullptr) {
+    m_processEventsOverride(timeout);
+  }
+  else {
+    this->Face::doProcessEvents(timeout, keepThread);
+  }
+}
+
 } // namespace util
 } // namespace ndn
diff --git a/src/util/dummy-client-face.hpp b/src/util/dummy-client-face.hpp
index ae32e14..7d661a7 100644
--- a/src/util/dummy-client-face.hpp
+++ b/src/util/dummy-client-face.hpp
@@ -24,7 +24,7 @@
 
 #include "../face.hpp"
 #include "signal.hpp"
-#include "../lp/packet.hpp"
+#include "../security/key-chain.hpp"
 
 namespace ndn {
 namespace util {
@@ -36,8 +36,28 @@
 public:
   /** \brief options for DummyClientFace
    */
-  struct Options
+  class Options
   {
+  public:
+    Options(bool enablePacketLogging, bool enableRegistrationReply,
+            const std::function<void(time::milliseconds)>& processEventsOverride)
+      : enablePacketLogging(enablePacketLogging)
+      , enableRegistrationReply(enableRegistrationReply)
+      , processEventsOverride(processEventsOverride)
+    {
+    }
+
+    Options(bool enablePacketLogging, bool enableRegistrationReply)
+      : Options(enablePacketLogging, enableRegistrationReply, nullptr)
+    {
+    }
+
+    Options()
+      : Options(true, false)
+    {
+    }
+
+  public:
     /** \brief if true, packets sent out of DummyClientFace will be appended to a container
      */
     bool enablePacketLogging;
@@ -46,29 +66,31 @@
      *         replied with a successful response
      */
     bool enableRegistrationReply;
+
+    /** \brief if not empty, face.processEvents() will be overridden by this function
+     */
+    std::function<void(time::milliseconds)> processEventsOverride;
   };
 
-  /**
-   * @brief Create a dummy face with internal IO service
+  /** \brief Create a dummy face with internal IO service
    */
-  DummyClientFace(const Options& options = DummyClientFace::DEFAULT_OPTIONS);
+  explicit
+  DummyClientFace(const Options& options = Options());
 
-  /**
-   * @brief Create a dummy face with internal IO service and the specified KeyChain
+  /** \brief Create a dummy face with internal IO service and the specified KeyChain
    */
-  DummyClientFace(KeyChain& keyChain, const Options& options = DummyClientFace::DEFAULT_OPTIONS);
+  explicit
+  DummyClientFace(KeyChain& keyChain, const Options& options = Options());
 
-  /**
-   * @brief Create a dummy face with the provided IO service
+  /** \brief Create a dummy face with the provided IO service
    */
-  DummyClientFace(boost::asio::io_service& ioService,
-                  const Options& options = DummyClientFace::DEFAULT_OPTIONS);
+  explicit
+  DummyClientFace(boost::asio::io_service& ioService, const Options& options = Options());
 
-  /**
-   * @brief Create a dummy face with the provided IO service and the specified KeyChain
+  /** \brief Create a dummy face with the provided IO service and the specified KeyChain
    */
   DummyClientFace(boost::asio::io_service& ioService, KeyChain& keyChain,
-                  const Options& options = DummyClientFace::DEFAULT_OPTIONS);
+                  const Options& options = Options());
 
   /** \brief cause the Face to receive a packet
    *  \tparam Packet either Interest or Data
@@ -77,27 +99,22 @@
   void
   receive(const Packet& packet);
 
-private: // constructors
+private:
   class Transport;
 
   void
   construct(const Options& options);
 
-private:
   void
   enablePacketLogging();
 
   void
   enableRegistrationReply();
 
-public:
-  /** \brief default options
-   *
-   *  enablePacketLogging=true
-   *  enableRegistrationReply=false
-   */
-  static const Options DEFAULT_OPTIONS;
+  virtual void
+  doProcessEvents(const time::milliseconds& timeout, bool keepThread) override;
 
+public:
   /** \brief Interests sent out of this DummyClientFace
    *
    *  Sent Interests are appended to this container if options.enablePacketLogger is true.
@@ -114,9 +131,9 @@
    */
   std::vector<Data> sentData;
 
-  /** \brief NACKs sent out of this DummyClientFace
+  /** \brief Nacks sent out of this DummyClientFace
    *
-   *  Sent NACKs are appended to this container if options.enablePacketLogger is true.
+   *  Sent Nacks are appended to this container if options.enablePacketLogger is true.
    *  User of this class is responsible for cleaning up the container, if necessary.
    *  After .put, .processEvents must be called before the NACK would show up here.
    */
@@ -134,7 +151,7 @@
    */
   Signal<DummyClientFace, Data> onSendData;
 
-  /** \brief emits whenever a NACK is sent
+  /** \brief emits whenever a Nack is sent
    *
    *  After .put, .processEvents must be called before this signal would be emitted.
    */
@@ -143,6 +160,7 @@
 private:
   std::unique_ptr<KeyChain> m_internalKeyChain;
   KeyChain& m_keyChain;
+  std::function<void(time::milliseconds)> m_processEventsOverride;
 };
 
 template<>
diff --git a/tests/unit-tests/util/dummy-client-face.t.cpp b/tests/unit-tests/util/dummy-client-face.t.cpp
new file mode 100644
index 0000000..2e7368d
--- /dev/null
+++ b/tests/unit-tests/util/dummy-client-face.t.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestDummyClientFace)
+
+BOOST_AUTO_TEST_CASE(ProcessEventsOverride)
+{
+  bool isOverrideInvoked = false;
+  auto override = [&] (time::milliseconds timeout) {
+    isOverrideInvoked = true;
+    BOOST_CHECK_EQUAL(timeout, time::milliseconds(200));
+  };
+
+  DummyClientFace face({false, false, override});
+  face.processEvents(time::milliseconds(200));
+  BOOST_CHECK(isOverrideInvoked);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDummyClientFace
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn