Add list command
diff --git a/test/cpp/util/grpc_tool.cc b/test/cpp/util/grpc_tool.cc
index f06053c..bb2d8eb 100644
--- a/test/cpp/util/grpc_tool.cc
+++ b/test/cpp/util/grpc_tool.cc
@@ -51,8 +51,13 @@
 #include "test/cpp/util/cli_call.h"
 #include "test/cpp/util/proto_file_parser.h"
 #include "test/cpp/util/proto_reflection_descriptor_database.h"
+#include "test/cpp/util/service_describer.h"
 #include "test/cpp/util/test_config.h"
 
+namespace grpc {
+namespace testing {
+
+DEFINE_bool(l, false, "Use a long listing format");
 DEFINE_bool(remotedb, true, "Use server types to parse and format messages");
 DEFINE_string(metadata, "",
               "Metadata to send to server, in the form of key1:val1:key2:val2");
@@ -62,8 +67,6 @@
 DEFINE_bool(binary_output, false, "Output in binary format");
 DEFINE_string(infile, "", "Input file (default is stdin)");
 
-namespace grpc {
-namespace testing {
 namespace {
 
 class GrpcTool {
@@ -75,9 +78,9 @@
             GrpcToolOutputCallback callback);
   bool CallMethod(int argc, const char** argv, CliCredentials cred,
                   GrpcToolOutputCallback callback);
+  bool ListServices(int argc, const char** argv, CliCredentials cred,
+                    GrpcToolOutputCallback callback);
   // TODO(zyc): implement the following methods
-  // bool ListServices(int argc, const char** argv, GrpcToolOutputCallback
-  // callback);
   // bool PrintType(int argc, const char** argv, GrpcToolOutputCallback
   // callback);
   // bool PrintTypeId(int argc, const char** argv, GrpcToolOutputCallback
@@ -165,8 +168,8 @@
 
 const Command ops[] = {
     {"help", BindWith5Args(&GrpcTool::Help), 0, INT_MAX},
-    // {"ls", BindWith5Args(&GrpcTool::ListServices), 1, 3},
-    // {"list", BindWith5Args(&GrpcTool::ListServices), 1, 3},
+    {"ls", BindWith5Args(&GrpcTool::ListServices), 1, 3},
+    {"list", BindWith5Args(&GrpcTool::ListServices), 1, 3},
     {"call", BindWith5Args(&GrpcTool::CallMethod), 2, 3},
     // {"type", BindWith5Args(&GrpcTool::PrintType), 2, 2},
     // {"parse", BindWith5Args(&GrpcTool::ParseMessage), 2, 3},
@@ -178,7 +181,7 @@
   fprintf(
       stderr,
       "%s\n"
-      // "  grpc_cli ls ...         ; List services\n"
+      "  grpc_cli ls ...         ; List services\n"
       "  grpc_cli call ...       ; Call method\n"
       // "  grpc_cli type ...       ; Print type\n"
       // "  grpc_cli parse ...      ; Parse message\n"
@@ -257,6 +260,105 @@
   return true;
 }
 
+bool GrpcTool::ListServices(int argc, const char** argv,
+                            const CliCredentials cred,
+                            GrpcToolOutputCallback callback) {
+  CommandUsage(
+      "List services\n"
+      "  grpc_cli ls <address> [<service>[/<method>]]\n"
+      "    <address>                ; host:port\n"
+      "    <service>                ; Exported service name\n"
+      "    <method>                 ; Method name\n"
+      "    --l                      ; Use a long listing format\n"
+      "    --outfile                ; Output filename (defaults to stdout)\n" +
+      cred.GetCredentialUsage());
+
+  grpc::string server_address(argv[0]);
+  std::shared_ptr<grpc::Channel> channel =
+      grpc::CreateChannel(server_address, cred.GetCredentials());
+  grpc::ProtoReflectionDescriptorDatabase desc_db(channel);
+  google::protobuf::DescriptorPool desc_pool(&desc_db);
+
+  std::vector<grpc::string> service_list;
+  if (!desc_db.GetServices(&service_list)) {
+    return false;
+  }
+
+  // If no service is specified, dump the list of services.
+  grpc::string output;
+  if (argc < 2) {
+    // List all services, if --l is passed, then include full description,
+    // otherwise include a summarized list only.
+    if (FLAGS_l) {
+      output = DescribeServiceList(service_list, desc_pool);
+    } else {
+      for (auto const& service : service_list) {
+        output.append(service);
+        output.append("\n");
+      }
+    }
+  } else {
+    grpc::string service_name;
+    grpc::string method_name;
+    std::stringstream ss(argv[1]);
+
+    // Remove leading slashes.
+    while (ss.peek() == '/') {
+      ss.get();
+    }
+
+    // Parse service and method names. Support the following patterns:
+    //   Service
+    //   Service Method
+    //   Service.Method
+    //   Service/Method
+    if (argc == 3) {
+      std::getline(ss, service_name, '/');
+      method_name = argv[2];
+    } else {
+      if (std::getline(ss, service_name, '/')) {
+        std::getline(ss, method_name);
+      }
+    }
+
+    const google::protobuf::ServiceDescriptor* service =
+        desc_pool.FindServiceByName(service_name);
+    if (service != nullptr) {
+      if (method_name.empty()) {
+        output = FLAGS_l ? DescribeService(service) : SummarizeService(service);
+      } else {
+        method_name.insert(0, 1, '.');
+        method_name.insert(0, service_name);
+        const google::protobuf::MethodDescriptor* method =
+            desc_pool.FindMethodByName(method_name);
+        if (method != nullptr) {
+          output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);
+        } else {
+          fprintf(stderr, "Method %s not found in service %s.\n",
+                  method_name.c_str(), service_name.c_str());
+          return false;
+        }
+      }
+    } else {
+      if (!method_name.empty()) {
+        fprintf(stderr, "Service %s not found.\n", service_name.c_str());
+        return false;
+      } else {
+        const google::protobuf::MethodDescriptor* method =
+            desc_pool.FindMethodByName(service_name);
+        if (method != nullptr) {
+          output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);
+        } else {
+          fprintf(stderr, "Service or method %s not found.\n",
+                  service_name.c_str());
+          return false;
+        }
+      }
+    }
+  }
+  return callback(output);
+}
+
 bool GrpcTool::CallMethod(int argc, const char** argv,
                           const CliCredentials cred,
                           GrpcToolOutputCallback callback) {
diff --git a/test/cpp/util/grpc_tool_test.cc b/test/cpp/util/grpc_tool_test.cc
index b96afaf..12ca6c4 100644
--- a/test/cpp/util/grpc_tool_test.cc
+++ b/test/cpp/util/grpc_tool_test.cc
@@ -43,6 +43,7 @@
 #include <grpc++/server_builder.h>
 #include <grpc++/server_context.h>
 #include <grpc/grpc.h>
+#include <gflags/gflags.h>
 #include <gtest/gtest.h>
 
 #include "src/proto/grpc/testing/echo.grpc.pb.h"
@@ -55,8 +56,41 @@
 using grpc::testing::EchoRequest;
 using grpc::testing::EchoResponse;
 
+#define USAGE_REGEX "(  grpc_cli .+\n){2,10}"
+
+#define ECHO_TEST_SERVICE_SUMMARY \
+  "Echo\n"                        \
+  "RequestStream\n"               \
+  "ResponseStream\n"              \
+  "BidiStream\n"                  \
+  "Unimplemented\n"
+
+#define ECHO_TEST_SERVICE_DESCRIPTION                                         \
+  "filename: src/proto/grpc/testing/echo.proto\n"                             \
+  "package: grpc.testing;\n"                                                  \
+  "service EchoTestService {\n"                                               \
+  "  rpc Echo(grpc.testing.EchoRequest) returns (grpc.testing.EchoResponse) " \
+  "{}\n"                                                                      \
+  "  rpc RequestStream(stream grpc.testing.EchoRequest) returns "             \
+  "(grpc.testing.EchoResponse) {}\n"                                          \
+  "  rpc ResponseStream(grpc.testing.EchoRequest) returns (stream "           \
+  "grpc.testing.EchoResponse) {}\n"                                           \
+  "  rpc BidiStream(stream grpc.testing.EchoRequest) returns (stream "        \
+  "grpc.testing.EchoResponse) {}\n"                                           \
+  "  rpc Unimplemented(grpc.testing.EchoRequest) returns "                    \
+  "(grpc.testing.EchoResponse) {}\n"                                          \
+  "}\n"                                                                       \
+  "\n"
+
+#define ECHO_METHOD_DESCRIPTION                                               \
+  "  rpc Echo(grpc.testing.EchoRequest) returns (grpc.testing.EchoResponse) " \
+  "{}\n"
+
 namespace grpc {
 namespace testing {
+
+DECLARE_bool(l);
+
 namespace {
 
 class TestCliCredentials GRPC_FINAL : public grpc::testing::CliCredentials {
@@ -68,6 +102,17 @@
   const grpc::string GetCredentialUsage() const GRPC_OVERRIDE { return ""; }
 };
 
+bool PrintStream(std::stringstream* ss, const grpc::string& output) {
+  (*ss) << output;
+  return true;
+}
+
+template <typename T>
+size_t ArraySize(T& a) {
+  return ((sizeof(a) / sizeof(*(a))) /
+          static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))));
+}
+
 }  // namespame
 
 class TestServiceImpl : public ::grpc::testing::EchoTestService::Service {
@@ -114,19 +159,6 @@
   reflection::ProtoServerReflectionPlugin plugin_;
 };
 
-static bool PrintStream(std::stringstream* ss, const grpc::string& output) {
-  (*ss) << output << std::endl;
-  return true;
-}
-
-template <typename T>
-static size_t ArraySize(T& a) {
-  return ((sizeof(a) / sizeof(*(a))) /
-          static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))));
-}
-
-#define USAGE_REGEX "(  grpc_cli .+\n){2,10}"
-
 TEST_F(GrpcToolTest, NoCommand) {
   // Test input "grpc_cli"
   std::stringstream output_stream;
@@ -168,8 +200,85 @@
   EXPECT_TRUE(0 == output_stream.tellp());
 }
 
+TEST_F(GrpcToolTest, ListCommand) {
+  // Test input "grpc_cli list localhost:<port>"
+  std::stringstream output_stream;
+
+  const grpc::string server_address = SetUpServer();
+  const char* argv[] = {"grpc_cli", "ls", server_address.c_str()};
+
+  FLAGS_l = false;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  EXPECT_TRUE(0 == strcmp(output_stream.str().c_str(),
+                          "grpc.testing.EchoTestService\n"
+                          "grpc.reflection.v1alpha.ServerReflection\n"));
+
+  ShutdownServer();
+}
+
+TEST_F(GrpcToolTest, ListOneService) {
+  // Test input "grpc_cli list localhost:<port> grpc.testing.EchoTestService"
+  std::stringstream output_stream;
+
+  const grpc::string server_address = SetUpServer();
+  const char* argv[] = {"grpc_cli", "ls", server_address.c_str(),
+                        "grpc.testing.EchoTestService"};
+  // without -l flag
+  FLAGS_l = false;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: ECHO_TEST_SERVICE_SUMMARY
+  EXPECT_TRUE(0 ==
+              strcmp(output_stream.str().c_str(), ECHO_TEST_SERVICE_SUMMARY));
+
+  // with -l flag
+  output_stream.str(grpc::string());
+  output_stream.clear();
+  FLAGS_l = true;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: ECHO_TEST_SERVICE_DESCRIPTION
+  EXPECT_TRUE(
+      0 == strcmp(output_stream.str().c_str(), ECHO_TEST_SERVICE_DESCRIPTION));
+
+  ShutdownServer();
+}
+
+TEST_F(GrpcToolTest, ListOneMethod) {
+  // Test input "grpc_cli list localhost:<port> grpc.testing.EchoTestService"
+  std::stringstream output_stream;
+
+  const grpc::string server_address = SetUpServer();
+  const char* argv[] = {"grpc_cli", "ls", server_address.c_str(),
+                        "grpc.testing.EchoTestService.Echo"};
+  // without -l flag
+  FLAGS_l = false;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: "Echo"
+  EXPECT_TRUE(0 == strcmp(output_stream.str().c_str(), "Echo\n"));
+
+  // with -l flag
+  output_stream.str(grpc::string());
+  output_stream.clear();
+  FLAGS_l = true;
+  EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(),
+                                   std::bind(PrintStream, &output_stream,
+                                             std::placeholders::_1)));
+  // Expected output: ECHO_METHOD_DESCRIPTION
+  EXPECT_TRUE(0 ==
+              strcmp(output_stream.str().c_str(), ECHO_METHOD_DESCRIPTION));
+
+  ShutdownServer();
+}
+
 TEST_F(GrpcToolTest, CallCommand) {
-  // Test input "grpc_cli call Echo"
+  // Test input "grpc_cli call localhost:<port> Echo "message: 'Hello'"
   std::stringstream output_stream;
 
   const grpc::string server_address = SetUpServer();
@@ -186,7 +295,7 @@
 }
 
 TEST_F(GrpcToolTest, TooFewArguments) {
-  // Test input "grpc_cli call localhost:<port> Echo "message: 'Hello'"
+  // Test input "grpc_cli call Echo"
   std::stringstream output_stream;
   const char* argv[] = {"grpc_cli", "call", "Echo"};
 
diff --git a/test/cpp/util/service_describer.cc b/test/cpp/util/service_describer.cc
new file mode 100644
index 0000000..b475e36
--- /dev/null
+++ b/test/cpp/util/service_describer.cc
@@ -0,0 +1,109 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "service_describer.h"
+
+#include <google/protobuf/descriptor.pb.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace grpc {
+namespace testing {
+
+grpc::string DescribeServiceList(std::vector<grpc::string> service_list,
+                                 google::protobuf::DescriptorPool& desc_pool) {
+  std::stringstream result;
+  for (auto const& service : service_list) {
+    const google::protobuf::ServiceDescriptor* service_desc =
+        desc_pool.FindServiceByName(service);
+    if (service_desc != nullptr) {
+      result << DescribeService(service_desc);
+    }
+  }
+  return result.str();
+}
+
+grpc::string DescribeService(
+    const google::protobuf::ServiceDescriptor* service) {
+  grpc::string result;
+  if (service->options().deprecated()) {
+    result.append("DEPRECATED\n");
+  }
+  result.append("filename: " + service->file()->name() + "\n");
+
+  grpc::string package = service->full_name();
+  size_t pos = package.rfind("." + service->name());
+  if (pos != grpc::string::npos) {
+    package.erase(pos);
+    result.append("package: " + package + ";\n");
+  }
+  result.append("service " + service->name() + " {\n");
+  for (int i = 0; i < service->method_count(); ++i) {
+    result.append(DescribeMethod(service->method(i)));
+  }
+  result.append("}\n\n");
+  return result;
+}
+
+grpc::string DescribeMethod(const google::protobuf::MethodDescriptor* method) {
+  std::stringstream result;
+  result << "  rpc " << method->name()
+         << (method->client_streaming() ? "(stream " : "(")
+         << method->input_type()->full_name() << ") returns "
+         << (method->server_streaming() ? "(stream " : "(")
+         << method->output_type()->full_name() << ") {}\n";
+  if (method->options().deprecated()) {
+    result << " DEPRECATED";
+  }
+  return result.str();
+}
+
+grpc::string SummarizeService(
+    const google::protobuf::ServiceDescriptor* service) {
+  grpc::string result;
+  for (int i = 0; i < service->method_count(); ++i) {
+    result.append(SummarizeMethod(service->method(i)));
+  }
+  return result;
+}
+
+grpc::string SummarizeMethod(const google::protobuf::MethodDescriptor* method) {
+  grpc::string result = method->name();
+  result.append("\n");
+  return result;
+}
+
+}  // namespace testing
+}  // namespace grpc
diff --git a/test/cpp/util/service_describer.h b/test/cpp/util/service_describer.h
new file mode 100644
index 0000000..e4edd2d
--- /dev/null
+++ b/test/cpp/util/service_describer.h
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <google/protobuf/descriptor.h>
+#include <grpc++/support/config.h>
+
+namespace grpc {
+namespace testing {
+
+grpc::string DescribeServiceList(std::vector<grpc::string> service_list,
+                                 google::protobuf::DescriptorPool& desc_pool);
+
+grpc::string DescribeService(
+    const google::protobuf::ServiceDescriptor* service);
+
+grpc::string DescribeMethod(const google::protobuf::MethodDescriptor* method);
+
+grpc::string SummarizeService(
+    const google::protobuf::ServiceDescriptor* service);
+
+grpc::string SummarizeMethod(const google::protobuf::MethodDescriptor* method);
+
+}  // namespase testing
+}  // namespace grpc