tools: nfdc strategy list/show commands

refs #3865

Change-Id: I6cc115b3c3f3d0840814a329c68356bbe6ad2e3f
diff --git a/docs/manpages/nfdc-strategy.rst b/docs/manpages/nfdc-strategy.rst
index f89ee2b..7d32d0e 100644
--- a/docs/manpages/nfdc-strategy.rst
+++ b/docs/manpages/nfdc-strategy.rst
@@ -4,6 +4,7 @@
 SYNOPSIS
 --------
 | nfdc strategy [list]
+| nfdc strategy show [prefix] <PREFIX>
 | nfdc set-strategy <PREFIX> <STRATEGY>
 | nfdc unset-strategy <PREFIX>
 
@@ -17,7 +18,9 @@
 NFD contains multiple forwarding strategy implementations.
 The strategy choice table determines which strategy is used in forwarding an Interest.
 
-The **nfdc strategy list** command shows the strategy choices.
+The **nfdc strategy list** command shows a list of strategy choices.
+
+The **nfdc strategy show** command shows the effective strategy choice for a specific name.
 
 The **nfdc set-strategy** command sets the strategy for a name prefix.
 
@@ -39,6 +42,15 @@
     A name that identifies the forwarding strategy.
     Consult NFD Developer's Guide for a complete list of all implemented strategies.
 
+EXIT CODES
+----------
+
+0: Success
+
+1: An unspecified error occurred
+
+2: Malformed command line
+
 SEE ALSO
 --------
 nfd(1), nfdc(1)
diff --git a/tests/tools/nfdc/strategy-choice-module.t.cpp b/tests/tools/nfdc/strategy-choice-module.t.cpp
index 3ed8c34..ff6605a 100644
--- a/tests/tools/nfdc/strategy-choice-module.t.cpp
+++ b/tests/tools/nfdc/strategy-choice-module.t.cpp
@@ -25,6 +25,7 @@
 
 #include "nfdc/strategy-choice-module.hpp"
 
+#include "execute-command-fixture.hpp"
 #include "status-fixture.hpp"
 
 namespace nfd {
@@ -33,7 +34,97 @@
 namespace tests {
 
 BOOST_AUTO_TEST_SUITE(Nfdc)
-BOOST_FIXTURE_TEST_SUITE(TestStrategyChoiceModule, StatusFixture<StrategyChoiceModule>)
+BOOST_AUTO_TEST_SUITE(TestStrategyChoiceModule)
+
+class StrategyListFixture : public ExecuteCommandFixture
+{
+protected:
+  bool
+  respondStrategyChoiceDataset(const Interest& interest)
+  {
+    if (!Name("/localhost/nfd/strategy-choice/list").isPrefixOf(interest.getName())) {
+      return false;
+    }
+
+    StrategyChoice entry1;
+    entry1.setName("/");
+    entry1.setStrategy("/strategyP/%FD%01");
+
+    StrategyChoice entry2;
+    entry2.setName("/52VRvpL9/Yqfut4TNHv");
+    entry2.setStrategy("/strategyQ/%FD%02");
+
+    this->sendDataset(interest.getName(), entry1, entry2);
+    return true;
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(ListCommand, StrategyListFixture)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    BOOST_CHECK(this->respondStrategyChoiceDataset(interest));
+  };
+
+  this->execute("strategy list");
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("prefix=/ strategy=/strategyP/%FD%01\n"
+                           "prefix=/52VRvpL9/Yqfut4TNHv strategy=/strategyQ/%FD%02\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(ErrorDataset)
+{
+  this->processInterest = nullptr; // no response to dataset or command
+
+  this->execute("strategy list");
+  BOOST_CHECK_EQUAL(exitCode, 1);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Error 10060 when fetching strategy choice dataset: Timeout\n"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ListCommand
+
+BOOST_FIXTURE_TEST_SUITE(ShowCommand, StrategyListFixture)
+
+BOOST_AUTO_TEST_CASE(NormalDefaultStrategy)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    BOOST_CHECK(this->respondStrategyChoiceDataset(interest));
+  };
+
+  this->execute("strategy show /I1Ixgg0X");
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("  prefix=/\n"
+                           "strategy=/strategyP/%FD%01\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(NormalNonDefaultStrategy)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    BOOST_CHECK(this->respondStrategyChoiceDataset(interest));
+  };
+
+  this->execute("strategy show /52VRvpL9/Yqfut4TNHv/Y5gY7gom");
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("  prefix=/52VRvpL9/Yqfut4TNHv\n"
+                           "strategy=/strategyQ/%FD%02\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(ErrorDataset)
+{
+  this->processInterest = nullptr; // no response to dataset or command
+
+  this->execute("strategy show /xVoIhNsJ");
+  BOOST_CHECK_EQUAL(exitCode, 1);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Error 10060 when fetching strategy choice dataset: Timeout\n"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ShowCommand
 
 const std::string STATUS_XML = stripXmlSpaces(R"XML(
   <strategyChoices>
@@ -54,11 +145,11 @@
 
 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
+  prefix=/ strategy=/localhost/nfd/strategy/best-route/%FD%04
+  prefix=/localhost strategy=/localhost/nfd/strategy/multicast/%FD%01
 )TEXT").substr(1);
 
-BOOST_AUTO_TEST_CASE(Status)
+BOOST_FIXTURE_TEST_CASE(Status, StatusFixture<StrategyChoiceModule>)
 {
   this->fetchStatus();
   StrategyChoice payload1;
diff --git a/tools/nfdc/available-commands.cpp b/tools/nfdc/available-commands.cpp
index dced4b1..a1d22cb 100644
--- a/tools/nfdc/available-commands.cpp
+++ b/tools/nfdc/available-commands.cpp
@@ -30,6 +30,7 @@
 #include "legacy-status.hpp"
 #include "rib-module.hpp"
 #include "status.hpp"
+#include "strategy-choice-module.hpp"
 
 namespace nfd {
 namespace tools {
@@ -42,6 +43,7 @@
   registerStatusCommands(parser);
   FaceModule::registerCommands(parser);
   RibModule::registerCommands(parser);
+  StrategyChoiceModule::registerCommands(parser);
 
   registerLegacyStatusCommand(parser);
 
diff --git a/tools/nfdc/status.cpp b/tools/nfdc/status.cpp
index a09fd7e..f87cd8b 100644
--- a/tools/nfdc/status.cpp
+++ b/tools/nfdc/status.cpp
@@ -136,11 +136,6 @@
     .setTitle("print channel list");
   parser.addCommand(defChannelList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantChannels));
 
-  CommandDefinition defStrategyList("strategy", "list");
-  defStrategyList
-    .setTitle("print strategy choices");
-  parser.addCommand(defStrategyList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantStrategyChoice));
-
   CommandDefinition defFibList("fib", "list");
   defFibList
     .setTitle("print FIB entries");
diff --git a/tools/nfdc/strategy-choice-module.cpp b/tools/nfdc/strategy-choice-module.cpp
index 4a6b36f..e5fe584 100644
--- a/tools/nfdc/strategy-choice-module.cpp
+++ b/tools/nfdc/strategy-choice-module.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -31,6 +31,59 @@
 namespace nfdc {
 
 void
+StrategyChoiceModule::registerCommands(CommandParser& parser)
+{
+  CommandDefinition defStrategyList("strategy", "list");
+  defStrategyList
+    .setTitle("print strategy choices");
+  parser.addCommand(defStrategyList, &StrategyChoiceModule::list);
+
+  CommandDefinition defStrategyShow("strategy", "show");
+  defStrategyShow
+    .setTitle("show strategy choice of an entry")
+    .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES);
+  parser.addCommand(defStrategyShow, &StrategyChoiceModule::show);
+}
+
+void
+StrategyChoiceModule::list(ExecuteContext& ctx)
+{
+  ctx.controller.fetch<ndn::nfd::StrategyChoiceDataset>(
+    [&] (const std::vector<StrategyChoice>& dataset) {
+      for (const StrategyChoice& entry : dataset) {
+        formatItemText(ctx.out, entry);
+        ctx.out << '\n';
+      }
+    },
+    ctx.makeDatasetFailureHandler("strategy choice dataset"),
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
+StrategyChoiceModule::show(ExecuteContext& ctx)
+{
+  auto prefix = ctx.args.get<Name>("prefix");
+
+  ctx.controller.fetch<ndn::nfd::StrategyChoiceDataset>(
+    [&] (const std::vector<StrategyChoice>& dataset) {
+      StrategyChoice match; // longest prefix match
+      for (const StrategyChoice& entry : dataset) {
+        if (entry.getName().isPrefixOf(prefix) &&
+            entry.getName().size() >= match.getName().size()) {
+          match = entry;
+        }
+      }
+      formatItemText(ctx.out, match, true);
+    },
+    ctx.makeDatasetFailureHandler("strategy choice dataset"),
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
 StrategyChoiceModule::fetchStatus(Controller& controller,
                                   const function<void()>& onSuccess,
                                   const Controller::DatasetFailCallback& onFailure,
@@ -68,16 +121,19 @@
 {
   os << "Strategy choices:\n";
   for (const StrategyChoice& item : m_status) {
-    this->formatItemText(os, item);
+    os << "  ";
+    formatItemText(os, item);
+    os << '\n';
   }
 }
 
 void
-StrategyChoiceModule::formatItemText(std::ostream& os, const StrategyChoice& item) const
+StrategyChoiceModule::formatItemText(std::ostream& os, const StrategyChoice& item, bool wantMultiLine)
 {
-  os << "  " << item.getName()
-     << " strategy=" << item.getStrategy()
-     << "\n";
+  text::ItemAttributes ia(wantMultiLine, 8);
+  os << ia("prefix") << item.getName()
+     << ia("strategy") << item.getStrategy()
+     << ia.end();
 }
 
 } // namespace nfdc
diff --git a/tools/nfdc/strategy-choice-module.hpp b/tools/nfdc/strategy-choice-module.hpp
index 4d26896..aabc3db 100644
--- a/tools/nfdc/strategy-choice-module.hpp
+++ b/tools/nfdc/strategy-choice-module.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -23,10 +23,11 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef NFD_TOOLS_NFDC_STARTEGY_CHOICE_MODULE_HPP
-#define NFD_TOOLS_NFDC_STARTEGY_CHOICE_MODULE_HPP
+#ifndef NFD_TOOLS_NFDC_STRATEGY_CHOICE_MODULE_HPP
+#define NFD_TOOLS_NFDC_STRATEGY_CHOICE_MODULE_HPP
 
 #include "module.hpp"
+#include "command-parser.hpp"
 
 namespace nfd {
 namespace tools {
@@ -40,13 +41,28 @@
 class StrategyChoiceModule : public Module, noncopyable
 {
 public:
-  virtual void
+  /** \brief register 'strategy list', 'strategy show', 'strategy set', 'strategy unset' commands
+   */
+  static void
+  registerCommands(CommandParser& parser);
+
+  /** \brief the 'strategy list' command
+   */
+  static void
+  list(ExecuteContext& ctx);
+
+  /** \brief the 'strategy show' command
+   */
+  static void
+  show(ExecuteContext& ctx);
+
+  void
   fetchStatus(Controller& controller,
               const function<void()>& onSuccess,
               const Controller::DatasetFailCallback& onFailure,
               const CommandOptions& options) override;
 
-  virtual void
+  void
   formatStatusXml(std::ostream& os) const override;
 
   /** \brief format a single status item as XML
@@ -56,15 +72,16 @@
   void
   formatItemXml(std::ostream& os, const StrategyChoice& item) const;
 
-  virtual void
+  void
   formatStatusText(std::ostream& os) const override;
 
   /** \brief format a single status item as text
    *  \param os output stream
    *  \param item status item
+   *  \param wantMultiLine use multi-line style
    */
-  void
-  formatItemText(std::ostream& os, const StrategyChoice& item) const;
+  static void
+  formatItemText(std::ostream& os, const StrategyChoice& item, bool wantMultiLine = false);
 
 private:
   std::vector<StrategyChoice> m_status;
@@ -74,4 +91,4 @@
 } // namespace tools
 } // namespace nfd
 
-#endif // NFD_TOOLS_NFDC_STARTEGY_CHOICE_MODULE_HPP
+#endif // NFD_TOOLS_NFDC_STRATEGY_CHOICE_MODULE_HPP