tools: refactor nfd-status

refs #3658

Change-Id: Ia347074bea802eba5f539208e276e849a60db8a4
diff --git a/tests/tools/nfd-status/channel-module.t.cpp b/tests/tools/nfd-status/channel-module.t.cpp
new file mode 100644
index 0000000..f524907
--- /dev/null
+++ b/tests/tools/nfd-status/channel-module.t.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/channel-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestChannelModule, ModuleFixture<ChannelModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <channels>
+    <channel>
+      <localUri>tcp4://192.0.2.1:6363</localUri>
+    </channel>
+    <channel>
+      <localUri>ws://[::]:9696/NFD</localUri>
+    </channel>
+  </channels>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+Channels:
+  tcp4://192.0.2.1:6363
+  ws://[::]:9696/NFD
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  ChannelStatus payload1;
+  payload1.setLocalUri("tcp4://192.0.2.1:6363");
+  ChannelStatus payload2;
+  payload2.setLocalUri("ws://[::]:9696/NFD");
+  this->sendDataset("/localhost/nfd/faces/channels", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestChannelModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/face-module.t.cpp b/tests/tools/nfd-status/face-module.t.cpp
new file mode 100644
index 0000000..292ccd8
--- /dev/null
+++ b/tests/tools/nfd-status/face-module.t.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/face-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestFaceModule, ModuleFixture<FaceModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <faces>
+    <face>
+      <faceId>134</faceId>
+      <remoteUri>udp4://233.252.0.4:6363</remoteUri>
+      <localUri>udp4://192.0.2.1:6363</localUri>
+      <faceScope>non-local</faceScope>
+      <facePersistency>permanent</facePersistency>
+      <linkType>multi-access</linkType>
+      <packetCounters>
+        <incomingPackets>
+          <nInterests>22562</nInterests>
+          <nDatas>22031</nDatas>
+          <nNacks>63</nNacks>
+        </incomingPackets>
+        <outgoingPackets>
+          <nInterests>30121</nInterests>
+          <nDatas>20940</nDatas>
+          <nNacks>1218</nNacks>
+        </outgoingPackets>
+      </packetCounters>
+      <byteCounters>
+        <incomingBytes>2522915</incomingBytes>
+        <outgoingBytes>1353592</outgoingBytes>
+      </byteCounters>
+    </face>
+    <face>
+      <faceId>745</faceId>
+      <remoteUri>fd://75</remoteUri>
+      <localUri>unix:///var/run/nfd.sock</localUri>
+      <faceScope>local</faceScope>
+      <facePersistency>on-demand</facePersistency>
+      <linkType>point-to-point</linkType>
+      <packetCounters>
+        <incomingPackets>
+          <nInterests>18998</nInterests>
+          <nDatas>26701</nDatas>
+          <nNacks>147</nNacks>
+        </incomingPackets>
+        <outgoingPackets>
+          <nInterests>34779</nInterests>
+          <nDatas>17028</nDatas>
+          <nNacks>1176</nNacks>
+        </outgoingPackets>
+      </packetCounters>
+      <byteCounters>
+        <incomingBytes>4672308</incomingBytes>
+        <outgoingBytes>8957187</outgoingBytes>
+      </byteCounters>
+    </face>
+  </faces>
+)XML");
+
+const std::string STATUS_TEXT =
+  "Faces:\n"
+  "  faceid=134 remote=udp4://233.252.0.4:6363 local=udp4://192.0.2.1:6363"
+    " counters={in={22562i 22031d 63n 2522915B} out={30121i 20940d 1218n 1353592B}}"
+    " non-local permanent multi-access\n"
+  "  faceid=745 remote=fd://75 local=unix:///var/run/nfd.sock"
+    " counters={in={18998i 26701d 147n 4672308B} out={34779i 17028d 1176n 8957187B}}"
+    " local on-demand point-to-point\n";
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  FaceStatus payload1;
+  payload1.setFaceId(134)
+          .setRemoteUri("udp4://233.252.0.4:6363")
+          .setLocalUri("udp4://192.0.2.1:6363")
+          .setFaceScope(ndn::nfd::FACE_SCOPE_NON_LOCAL)
+          .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT)
+          .setLinkType(ndn::nfd::LINK_TYPE_MULTI_ACCESS)
+          .setNInInterests(22562)
+          .setNInDatas(22031)
+          .setNInNacks(63)
+          .setNOutInterests(30121)
+          .setNOutDatas(20940)
+          .setNOutNacks(1218)
+          .setNInBytes(2522915)
+          .setNOutBytes(1353592);
+  FaceStatus payload2;
+  payload2.setFaceId(745)
+          .setRemoteUri("fd://75")
+          .setLocalUri("unix:///var/run/nfd.sock")
+          .setFaceScope(ndn::nfd::FACE_SCOPE_LOCAL)
+          .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND)
+          .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
+          .setNInInterests(18998)
+          .setNInDatas(26701)
+          .setNInNacks(147)
+          .setNOutInterests(34779)
+          .setNOutDatas(17028)
+          .setNOutNacks(1176)
+          .setNInBytes(4672308)
+          .setNOutBytes(8957187);
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/fib-module.t.cpp b/tests/tools/nfd-status/fib-module.t.cpp
new file mode 100644
index 0000000..f509461
--- /dev/null
+++ b/tests/tools/nfd-status/fib-module.t.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/fib-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestFibModule, ModuleFixture<FibModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <fib>
+    <fibEntry>
+      <prefix>/</prefix>
+      <nextHops>
+        <nextHop>
+          <faceId>262</faceId>
+          <cost>9</cost>
+        </nextHop>
+        <nextHop>
+          <faceId>272</faceId>
+          <cost>50</cost>
+        </nextHop>
+        <nextHop>
+          <faceId>274</faceId>
+          <cost>78</cost>
+        </nextHop>
+      </nextHops>
+    </fibEntry>
+    <fibEntry>
+      <prefix>/localhost/nfd</prefix>
+      <nextHops>
+        <nextHop>
+          <faceId>1</faceId>
+          <cost>0</cost>
+        </nextHop>
+        <nextHop>
+          <faceId>274</faceId>
+          <cost>0</cost>
+        </nextHop>
+      </nextHops>
+    </fibEntry>
+  </fib>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+FIB:
+  / nexthops={faceid=262 (cost=9), faceid=272 (cost=50), faceid=274 (cost=78)}
+  /localhost/nfd nexthops={faceid=1 (cost=0), faceid=274 (cost=0)}
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  FibEntry payload1;
+  payload1.setPrefix("/")
+          .addNextHopRecord(NextHopRecord().setFaceId(262).setCost(9))
+          .addNextHopRecord(NextHopRecord().setFaceId(272).setCost(50))
+          .addNextHopRecord(NextHopRecord().setFaceId(274).setCost(78));
+  FibEntry payload2;
+  payload2.setPrefix("/localhost/nfd")
+          .addNextHopRecord(NextHopRecord().setFaceId(1).setCost(0))
+          .addNextHopRecord(NextHopRecord().setFaceId(274).setCost(0));
+  this->sendDataset("/localhost/nfd/fib/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFibModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/format-helpers.t.cpp b/tests/tools/nfd-status/format-helpers.t.cpp
new file mode 100644
index 0000000..01d7f47
--- /dev/null
+++ b/tests/tools/nfd-status/format-helpers.t.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/format-helpers.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_AUTO_TEST_SUITE(TestFormatHelpers)
+
+BOOST_AUTO_TEST_SUITE(Xml)
+
+BOOST_AUTO_TEST_CASE(TextEscaping)
+{
+  output_test_stream os;
+  os << xml::Text{"\"less than\" & 'greater than' surround XML <element> tag name"};
+
+  BOOST_CHECK(os.is_equal("&quot;less than&quot; &amp; &apos;greater than&apos;"
+                          " surround XML &lt;element&gt; tag name"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Xml
+
+BOOST_AUTO_TEST_SUITE(Text)
+
+BOOST_AUTO_TEST_CASE(Sep)
+{
+  output_test_stream os;
+  text::Separator sep(",");
+  for (int i = 1; i <= 3; ++i) {
+    os << sep << i;
+  }
+
+  BOOST_CHECK(os.is_equal("1,2,3"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Text
+
+BOOST_AUTO_TEST_SUITE_END() // TestFormatHelpers
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/forwarder-general-module.t.cpp b/tests/tools/nfd-status/forwarder-general-module.t.cpp
new file mode 100644
index 0000000..e9c881b
--- /dev/null
+++ b/tests/tools/nfd-status/forwarder-general-module.t.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/forwarder-general-module.hpp"
+#include <ndn-cxx/security/signing-helpers.hpp>
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestForwarderGeneralModule, ModuleFixture<ForwarderGeneralModule>)
+
+class MakeNfdIdCollector
+{
+public:
+  unique_ptr<NfdIdCollector>
+  operator()(Face&, KeyChain&) const
+  {
+    return make_unique<NfdIdCollector>(make_unique<ValidatorNull>());
+  };
+};
+
+class ForwarderGeneralStatusFixture : public ModuleFixture<ForwarderGeneralModule,
+                                                           MakeNfdIdCollector>
+{
+protected:
+  ForwarderGeneralStatusFixture()
+  {
+    module.setNfdIdCollector(*validator);
+
+    this->systemClock->setNow(time::seconds(1468784936));
+    BOOST_REQUIRE(this->addIdentity("/nfd-status/test-nfdid"));
+  }
+
+private:
+  virtual void
+  signDatasetReply(Data& data) override
+  {
+    m_keyChain.sign(data, ndn::security::signingByIdentity("/nfd-status/test-nfdid"));
+  }
+};
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <generalStatus>
+    <nfdId>/nfd-status/test-nfdid/KEY/ksk-1468784936000/ID-CERT</nfdId>
+    <version>0.4.1-1-g704430c</version>
+    <startTime>2016-06-24T15:13:46.856000</startTime>
+    <currentTime>2016-07-17T17:55:54.109000</currentTime>
+    <uptime>PT1996927S</uptime>
+    <nNameTreeEntries>668</nNameTreeEntries>
+    <nFibEntries>70</nFibEntries>
+    <nPitEntries>7</nPitEntries>
+    <nMeasurementsEntries>1</nMeasurementsEntries>
+    <nCsEntries>65536</nCsEntries>
+    <packetCounters>
+      <incomingPackets>
+        <nInterests>20699052</nInterests>
+        <nDatas>5598070</nDatas>
+        <nNacks>7230</nNacks>
+      </incomingPackets>
+      <outgoingPackets>
+        <nInterests>36501092</nInterests>
+        <nDatas>5671942</nDatas>
+        <nNacks>26762</nNacks>
+      </outgoingPackets>
+    </packetCounters>
+  </generalStatus>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+General NFD status:
+                 nfdId=/nfd-status/test-nfdid/KEY/ksk-1468784936000/ID-CERT
+               version=0.4.1-1-g704430c
+             startTime=20160624T151346.856000
+           currentTime=20160717T175554.109000
+                uptime=1996927 seconds
+      nNameTreeEntries=668
+           nFibEntries=70
+           nPitEntries=7
+  nMeasurementsEntries=1
+            nCsEntries=65536
+          nInInterests=20699052
+         nOutInterests=36501092
+              nInDatas=5598070
+             nOutDatas=5671942
+              nInNacks=7230
+             nOutNacks=26762
+)TEXT").substr(1);
+
+BOOST_FIXTURE_TEST_CASE(Status, ForwarderGeneralStatusFixture)
+{
+  this->fetchStatus();
+  ForwarderStatus payload;
+  payload.setNfdVersion("0.4.1-1-g704430c")
+         .setStartTimestamp(time::fromUnixTimestamp(time::milliseconds(1466781226856)))
+         .setCurrentTimestamp(time::fromUnixTimestamp(time::milliseconds(1468778154109)))
+         .setNNameTreeEntries(668)
+         .setNFibEntries(70)
+         .setNPitEntries(7)
+         .setNMeasurementsEntries(1)
+         .setNCsEntries(65536)
+         .setNInInterests(20699052)
+         .setNInDatas(5598070)
+         .setNInNacks(7230)
+         .setNOutInterests(36501092)
+         .setNOutDatas(5671942)
+         .setNOutNacks(26762);
+  this->sendDataset("/localhost/nfd/status/general", payload);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_CASE(StatusNoNfdId)
+{
+  this->fetchStatus();
+  ForwarderStatus payload;
+  payload.setNfdVersion("0.4.1-1-g704430c");
+  this->sendDataset("/localhost/nfd/status/general", payload);
+  BOOST_CHECK_NO_THROW(this->prepareStatusOutput());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestForwarderGeneralModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/module-fixture.hpp b/tests/tools/nfd-status/module-fixture.hpp
new file mode 100644
index 0000000..38d0d64
--- /dev/null
+++ b/tests/tools/nfd-status/module-fixture.hpp
@@ -0,0 +1,236 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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_TOOLS_NFD_STATUS_MODULE_FIXTURE_HPP
+#define NFD_TESTS_TOOLS_NFD_STATUS_MODULE_FIXTURE_HPP
+
+#include "nfd-status/module.hpp"
+#include <ndn-cxx/security/validator-null.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+#include "tests/test-common.hpp"
+#include "tests/identity-management-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+using namespace nfd::tests;
+using ndn::Face;
+using ndn::KeyChain;
+using ndn::Validator;
+using ndn::ValidatorNull;
+using ndn::util::DummyClientFace;
+using boost::test_tools::output_test_stream;
+
+class MakeValidatorNull
+{
+public:
+  unique_ptr<ValidatorNull>
+  operator()(Face&, KeyChain&) const
+  {
+    return make_unique<ValidatorNull>();
+  };
+};
+
+/** \brief fixture to test a \p Module
+ *  \tparam MODULE a subclass of \p Module
+ *  \tparam MakeValidator a callable to make a Validator for use in \p controller;
+ *                        MakeValidator()(Face&, KeyChain&) should return a unique_ptr
+ *                        to Validator or its subclass
+ */
+template<typename MODULE, typename MakeValidator = MakeValidatorNull>
+class ModuleFixture : public UnitTestTimeFixture
+                    , public IdentityManagementFixture
+{
+protected:
+  typedef typename std::result_of<MakeValidator(Face&, KeyChain&)>::type ValidatorUniquePtr;
+
+  ModuleFixture()
+    : face(g_io, m_keyChain)
+    , validator(MakeValidator()(face, m_keyChain))
+    , controller(face, m_keyChain, *validator)
+    , nFetchStatusSuccess(0)
+  {
+  }
+
+protected: // status fetching
+  /** \brief start fetching status
+   *
+   *  A test case should call \p fetchStatus, \p sendDataset, and \p prepareStatusOutput
+   *  in this order, and then check \p statusXml and \p statusText contain the correct outputs.
+   *  No advanceClocks is needed in between, as they are handled by the fixture.
+   */
+  void
+  fetchStatus()
+  {
+    nFetchStatusSuccess = 0;
+    module.fetchStatus(controller, [this] { ++nFetchStatusSuccess; },
+                       [this] (uint32_t code, const std::string& reason) {
+                         BOOST_FAIL("fetchStatus failure " << code << " " << reason);
+                       },
+                       CommandOptions());
+    this->advanceClocks(time::milliseconds(1));
+  }
+
+  /** \brief send one WireEncodable in reply to StatusDataset request
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload payload block
+   *  \note payload must fit in one Data
+   *  \pre fetchStatus has been invoked, sendDataset has not been invoked
+   */
+  template<typename T>
+  void
+  sendDataset(const Name& prefix, const T& payload)
+  {
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T>));
+
+    this->sendDatasetReply(prefix, payload.wireEncode());
+  }
+
+  /** \brief send two WireEncodables in reply to StatusDataset request
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload1 first vector item
+   *  \param payload2 second vector item
+   *  \note all payloads must fit in one Data
+   *  \pre fetchStatus has been invoked, sendDataset has not been invoked
+   */
+  template<typename T1, typename T2>
+  void
+  sendDataset(const Name& prefix, const T1& payload1, const T2& payload2)
+  {
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T1>));
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T2>));
+
+    ndn::encoding::EncodingBuffer buffer;
+    payload2.wireEncode(buffer);
+    payload1.wireEncode(buffer);
+
+    this->sendDatasetReply(prefix, buffer.buf(), buffer.size());
+  }
+
+  /** \brief prepare status output as XML and text
+   *  \pre sendDataset has been invoked
+   */
+  void
+  prepareStatusOutput()
+  {
+    this->advanceClocks(time::milliseconds(1));
+    BOOST_REQUIRE_EQUAL(nFetchStatusSuccess, 1);
+
+    statusXml.str("");
+    module.formatStatusXml(statusXml);
+    statusText.str("");
+    module.formatStatusText(statusText);
+  }
+
+private:
+  /** \brief send a payload in reply to StatusDataset request
+   *  \param prefix dataset prefix without version and segment
+   *  \param contentArgs passed to Data::setContent
+   */
+  template<typename ...ContentArgs>
+  void
+  sendDatasetReply(const Name& prefix, ContentArgs&&...contentArgs)
+  {
+    Name name = prefix;
+    name.appendVersion().appendSegment(0);
+
+    // These warnings assist in debugging a `nFetchStatusSuccess != 1` check failure.
+    // They usually indicate a misspelled prefix or incorrect timing in the test case.
+    if (face.sentInterests.size() < 1) {
+      BOOST_WARN_MESSAGE(false, "no Interest expressed");
+    }
+    else {
+      BOOST_WARN_MESSAGE(face.sentInterests.back().getName().isPrefixOf(name),
+                         "last Interest " << face.sentInterests.back().getName() <<
+                         " cannot be satisfied by this Data " << name);
+    }
+
+    auto data = make_shared<Data>(name);
+    data->setFinalBlockId(name[-1]);
+    data->setContent(std::forward<ContentArgs>(contentArgs)...);
+    this->signDatasetReply(*data);
+    face.receive(*data);
+  }
+
+  virtual void
+  signDatasetReply(Data& data)
+  {
+    signData(data);
+  }
+
+protected:
+  DummyClientFace face;
+  ValidatorUniquePtr validator;
+  Controller controller;
+
+  MODULE module;
+
+  int nFetchStatusSuccess;
+  output_test_stream statusXml;
+  output_test_stream statusText;
+};
+
+/** \brief strips leading spaces on every line in expected XML
+ *
+ *  This allows expected XML to be written as:
+ *  \code
+ *  const std::string STATUS_XML = stripXmlSpaces(R"XML(
+ *    <rootElement>
+ *      <element>value</element>
+ *    </rootElement>
+ *  )XML");
+ *  \endcode
+ *  And \p STATUS_XML would be assigned:
+ *  \code
+ *  "<rootElement><element>value</element></rootElement>"
+ *  \endcode
+ */
+inline std::string
+stripXmlSpaces(const std::string& xml)
+{
+  std::string s;
+  bool isSkipping = true;
+  std::copy_if(xml.begin(), xml.end(), std::back_inserter(s),
+               [&isSkipping] (char ch) {
+                 if (ch == '\n') {
+                   isSkipping = true;
+                 }
+                 else if (ch != ' ') {
+                   isSkipping = false;
+                 }
+                 return !isSkipping;
+               });
+  return s;
+}
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TESTS_TOOLS_NFD_STATUS_MODULE_FIXTURE_HPP
diff --git a/tests/tools/nfd-status/rib-module.t.cpp b/tests/tools/nfd-status/rib-module.t.cpp
new file mode 100644
index 0000000..bfd663c
--- /dev/null
+++ b/tests/tools/nfd-status/rib-module.t.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/rib-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestRibModule, ModuleFixture<RibModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <rib>
+    <ribEntry>
+      <prefix>/</prefix>
+      <routes>
+        <route>
+          <faceId>262</faceId>
+          <origin>255</origin>
+          <cost>9</cost>
+          <flags>
+            <ribCapture/>
+          </flags>
+        </route>
+        <route>
+          <faceId>272</faceId>
+          <origin>255</origin>
+          <cost>50</cost>
+          <flags/>
+        </route>
+        <route>
+          <faceId>274</faceId>
+          <origin>255</origin>
+          <cost>78</cost>
+          <flags>
+            <childInherit/>
+            <ribCapture/>
+          </flags>
+        </route>
+        <route>
+          <faceId>276</faceId>
+          <origin>255</origin>
+          <cost>79</cost>
+          <flags>
+            <childInherit/>
+          </flags>
+          <expirationPeriod>PT47S</expirationPeriod>
+        </route>
+      </routes>
+    </ribEntry>
+    <ribEntry>
+      <prefix>/localhost/nfd</prefix>
+      <routes>
+        <route>
+          <faceId>258</faceId>
+          <origin>0</origin>
+          <cost>0</cost>
+          <flags>
+            <childInherit/>
+          </flags>
+        </route>
+      </routes>
+    </ribEntry>
+  </rib>
+)XML");
+
+const std::string STATUS_TEXT =
+  "RIB:\n"
+  "  / route={faceid=262 (origin=255 cost=9 RibCapture), faceid=272 (origin=255 cost=50), "
+    "faceid=274 (origin=255 cost=78 ChildInherit RibCapture), "
+    "faceid=276 (origin=255 cost=79 expires=47s ChildInherit)}\n"
+  "  /localhost/nfd route={faceid=258 (origin=0 cost=0 ChildInherit)}\n";
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  RibEntry payload1;
+  payload1.setName("/")
+          .addRoute(Route().setFaceId(262)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(9)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE))
+          .addRoute(Route().setFaceId(272)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(50)
+                           .setFlags(ndn::nfd::ROUTE_FLAGS_NONE))
+          .addRoute(Route().setFaceId(274)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(78)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT | ndn::nfd::ROUTE_FLAG_CAPTURE))
+          .addRoute(Route().setFaceId(276)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(79)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
+                           .setExpirationPeriod(time::milliseconds(47292)));
+  RibEntry payload2;
+  payload2.setName("/localhost/nfd")
+          .addRoute(Route().setFaceId(258)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_APP)
+                           .setCost(0)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
+  this->sendDataset("/localhost/nfd/rib/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/status-report.t.cpp b/tests/tools/nfd-status/status-report.t.cpp
new file mode 100644
index 0000000..309c83a
--- /dev/null
+++ b/tests/tools/nfd-status/status-report.t.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/status-report.hpp"
+#include "core/scheduler.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <?xml version="1.0"?>
+  <nfdStatus xmlns="ndn:/localhost/nfd/status/1">
+    <module1/>
+    <module2/>
+  </nfdStatus>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+module1
+module2
+)TEXT").substr(1);
+
+class DummyModule : public Module
+{
+public:
+  explicit
+  DummyModule(const std::string& moduleName)
+    : m_moduleName(moduleName)
+    , m_res(0)
+    , m_delay(time::milliseconds(1))
+  {
+  }
+
+  /** \brief cause fetchStatus to succeed or fail
+   *  \param res zero to succeed, non-zero to fail with specific code
+   *  \param delay duration from fetchStatus invocation to succeed or fail; must be positive
+   */
+  void
+  setResult(uint32_t res, time::nanoseconds delay)
+  {
+    BOOST_ASSERT(delay > time::nanoseconds::zero());
+    m_res = res;
+    m_delay = delay;
+  }
+
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override
+  {
+    ++nFetchStatusCalls;
+    scheduler::schedule(m_delay, [=] {
+      if (m_res == 0) {
+        onSuccess();
+      }
+      else {
+        onFailure(m_res, m_moduleName + " fails with code " + to_string(m_res));
+      }
+    });
+  }
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override
+  {
+    os << '<' << m_moduleName << "/>";
+  }
+
+  virtual void
+  formatStatusText(std::ostream& os) const override
+  {
+    os << m_moduleName << '\n';
+  }
+
+public:
+  int nFetchStatusCalls = 0;
+
+private:
+  std::string m_moduleName;
+  uint32_t m_res;
+  time::nanoseconds m_delay;
+};
+
+class StatusReportTester : public StatusReport
+{
+private:
+  virtual void
+  processEvents(Face&) override
+  {
+    processEventsFunc();
+  }
+
+public:
+  std::function<void()> processEventsFunc;
+};
+
+class StatusReportModulesFixture : public IdentityManagementTimeFixture
+{
+protected:
+  StatusReportModulesFixture()
+    : face(g_io, m_keyChain)
+    , controller(face, m_keyChain, validator)
+    , res(0)
+  {
+  }
+
+  DummyModule&
+  addModule(const std::string& moduleName)
+  {
+    report.sections.push_back(make_unique<DummyModule>(moduleName));
+    return static_cast<DummyModule&>(*report.sections.back());
+  }
+
+  void
+  collect(time::nanoseconds tick, size_t nTicks)
+  {
+    report.processEventsFunc = [=] {
+      this->advanceClocks(tick, nTicks);
+    };
+    res = report.collect(face, m_keyChain, validator, CommandOptions());
+
+    if (res == 0) {
+      statusXml.str("");
+      report.formatXml(statusXml);
+      statusText.str("");
+      report.formatText(statusText);
+    }
+  }
+
+protected:
+  DummyClientFace face;
+  ValidatorNull validator;
+  Controller controller;
+  StatusReportTester report;
+
+  uint32_t res;
+  output_test_stream statusXml;
+  output_test_stream statusText;
+};
+
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestStatusReport, StatusReportModulesFixture)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  DummyModule& m1 = addModule("module1");
+  m1.setResult(0, time::milliseconds(10));
+  DummyModule& m2 = addModule("module2");
+  m2.setResult(0, time::milliseconds(20));
+
+  this->collect(time::milliseconds(5), 6);
+
+  BOOST_CHECK_EQUAL(m1.nFetchStatusCalls, 1);
+  BOOST_CHECK_EQUAL(m2.nFetchStatusCalls, 1);
+
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_CASE(Reorder)
+{
+  DummyModule& m1 = addModule("module1");
+  m1.setResult(0, time::milliseconds(20));
+  DummyModule& m2 = addModule("module2");
+  m2.setResult(0, time::milliseconds(10)); // module2 completes earlier than module1
+
+  this->collect(time::milliseconds(5), 6);
+
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML)); // output is still in order
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_CASE(Error)
+{
+  DummyModule& m1 = addModule("module1");
+  m1.setResult(0, time::milliseconds(20));
+  DummyModule& m2 = addModule("module2");
+  m2.setResult(500, time::milliseconds(10));
+
+  this->collect(time::milliseconds(5), 6);
+
+  BOOST_CHECK_EQUAL(res, 1000500);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/strategy-choice-module.t.cpp b/tests/tools/nfd-status/strategy-choice-module.t.cpp
new file mode 100644
index 0000000..a41ba1f
--- /dev/null
+++ b/tests/tools/nfd-status/strategy-choice-module.t.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  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 "nfd-status/strategy-choice-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestStrategyChoiceModule, ModuleFixture<StrategyChoiceModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <strategyChoices>
+    <strategyChoice>
+      <namespace>/</namespace>
+      <strategy>
+        <name>/localhost/nfd/strategy/best-route/%FD%04</name>
+      </strategy>
+    </strategyChoice>
+    <strategyChoice>
+      <namespace>/localhost</namespace>
+      <strategy>
+        <name>/localhost/nfd/strategy/multicast/%FD%01</name>
+      </strategy>
+    </strategyChoice>
+  </strategyChoices>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+Strategy choices:
+  / strategy=/localhost/nfd/strategy/best-route/%FD%04
+  /localhost strategy=/localhost/nfd/strategy/multicast/%FD%01
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  StrategyChoice payload1;
+  payload1.setName("/")
+          .setStrategy("/localhost/nfd/strategy/best-route/%FD%04");
+  StrategyChoice payload2;
+  payload2.setName("/localhost")
+          .setStrategy("/localhost/nfd/strategy/multicast/%FD%01");
+  this->sendDataset("/localhost/nfd/strategy-choice/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStrategyChoiceModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd