/* * * 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 "test/cpp/util/grpc_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include "src/proto/grpc/testing/echo.grpc.pb.h" #include "src/proto/grpc/testing/echo.pb.h" #include "test/core/util/port.h" #include "test/core/util/test_config.h" #include "test/cpp/util/cli_credentials.h" #include "test/cpp/util/string_ref_helper.h" 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" #define ECHO_RESPONSE_MESSAGE \ "message: \"echo\"\n" \ "param {\n" \ " host: \"localhost\"\n" \ " peer: \"peer\"\n" \ "}\n\n" namespace grpc { namespace testing { DECLARE_bool(binary_input); DECLARE_bool(binary_output); DECLARE_bool(l); namespace { class TestCliCredentials final : public grpc::testing::CliCredentials { public: std::shared_ptr GetCredentials() const override { return InsecureChannelCredentials(); } const grpc::string GetCredentialUsage() const override { return ""; } }; bool PrintStream(std::stringstream* ss, const grpc::string& output) { (*ss) << output; return true; } template size_t ArraySize(T& a) { return ((sizeof(a) / sizeof(*(a))) / static_cast(!(sizeof(a) % sizeof(*(a))))); } class TestServiceImpl : public ::grpc::testing::EchoTestService::Service { public: Status Echo(ServerContext* context, const EchoRequest* request, EchoResponse* response) override { if (!context->client_metadata().empty()) { for (std::multimap::const_iterator iter = context->client_metadata().begin(); iter != context->client_metadata().end(); ++iter) { context->AddInitialMetadata(ToString(iter->first), ToString(iter->second)); } } context->AddTrailingMetadata("trailing_key", "trailing_value"); response->set_message(request->message()); return Status::OK; } }; } // namespace class GrpcToolTest : public ::testing::Test { protected: GrpcToolTest() {} // SetUpServer cannot be used with EXPECT_EXIT. grpc_pick_unused_port_or_die() // uses atexit() to free chosen ports, and it will spawn a new thread in // resolve_address_posix.c:192 at exit time. const grpc::string SetUpServer() { std::ostringstream server_address; int port = grpc_pick_unused_port_or_die(); server_address << "localhost:" << port; // Setup server ServerBuilder builder; builder.AddListeningPort(server_address.str(), InsecureServerCredentials()); builder.RegisterService(&service_); server_ = builder.BuildAndStart(); return server_address.str(); } void ShutdownServer() { server_->Shutdown(); } void ExitWhenError(int argc, const char** argv, const CliCredentials& cred, GrpcToolOutputCallback callback) { int result = GrpcToolMainLib(argc, argv, cred, callback); if (result) { exit(result); } } std::unique_ptr server_; TestServiceImpl service_; reflection::ProtoServerReflectionPlugin plugin_; }; TEST_F(GrpcToolTest, NoCommand) { // Test input "grpc_cli" std::stringstream output_stream; const char* argv[] = {"grpc_cli"}; // Exit with 1, print usage instruction in stderr EXPECT_EXIT( GrpcToolMainLib( ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1)), ::testing::ExitedWithCode(1), "No command specified\n" USAGE_REGEX); // No output EXPECT_TRUE(0 == output_stream.tellp()); } TEST_F(GrpcToolTest, InvalidCommand) { // Test input "grpc_cli" std::stringstream output_stream; const char* argv[] = {"grpc_cli", "abc"}; // Exit with 1, print usage instruction in stderr EXPECT_EXIT( GrpcToolMainLib( ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1)), ::testing::ExitedWithCode(1), "Invalid command 'abc'\n" USAGE_REGEX); // No output EXPECT_TRUE(0 == output_stream.tellp()); } TEST_F(GrpcToolTest, HelpCommand) { // Test input "grpc_cli help" std::stringstream output_stream; const char* argv[] = {"grpc_cli", "help"}; // Exit with 1, print usage instruction in stderr EXPECT_EXIT(GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1)), ::testing::ExitedWithCode(1), USAGE_REGEX); // No output EXPECT_TRUE(0 == output_stream.tellp()); } TEST_F(GrpcToolTest, ListCommand) { // Test input "grpc_cli list localhost:" 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: 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, TypeCommand) { // Test input "grpc_cli type localhost: grpc.testing.EchoRequest" std::stringstream output_stream; const grpc::string server_address = SetUpServer(); const char* argv[] = {"grpc_cli", "type", server_address.c_str(), "grpc.testing.EchoRequest"}; EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1))); const grpc::protobuf::Descriptor* desc = grpc::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName( "grpc.testing.EchoRequest"); // Expected output: the DebugString of grpc.testing.EchoRequest EXPECT_TRUE(0 == strcmp(output_stream.str().c_str(), desc->DebugString().c_str())); ShutdownServer(); } TEST_F(GrpcToolTest, ListOneMethod) { // Test input "grpc_cli list localhost: 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, TypeNotFound) { // Test input "grpc_cli type localhost: grpc.testing.DummyRequest" std::stringstream output_stream; const grpc::string server_address = SetUpServer(); const char* argv[] = {"grpc_cli", "type", server_address.c_str(), "grpc.testing.DummyRequest"}; EXPECT_DEATH(ExitWhenError(ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1)), ".*Type grpc.testing.DummyRequest not found.*"); ShutdownServer(); } TEST_F(GrpcToolTest, CallCommand) { // Test input "grpc_cli call localhost: Echo "message: 'Hello'" std::stringstream output_stream; const grpc::string server_address = SetUpServer(); const char* argv[] = {"grpc_cli", "call", server_address.c_str(), "Echo", "message: 'Hello'"}; EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1))); // Expected output: "message: \"Hello\"" EXPECT_TRUE(NULL != strstr(output_stream.str().c_str(), "message: \"Hello\"")); ShutdownServer(); } TEST_F(GrpcToolTest, ParseCommand) { // Test input "grpc_cli parse localhost: grpc.testing.EchoResponse // ECHO_RESPONSE_MESSAGE" std::stringstream output_stream; std::stringstream binary_output_stream; const grpc::string server_address = SetUpServer(); const char* argv[] = {"grpc_cli", "parse", server_address.c_str(), "grpc.testing.EchoResponse", ECHO_RESPONSE_MESSAGE}; FLAGS_binary_input = false; FLAGS_binary_output = false; EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1))); // Expected output: ECHO_RESPONSE_MESSAGE EXPECT_TRUE(0 == strcmp(output_stream.str().c_str(), ECHO_RESPONSE_MESSAGE)); // Parse text message to binary message and then parse it back to text message output_stream.str(grpc::string()); output_stream.clear(); FLAGS_binary_output = true; EXPECT_TRUE(0 == GrpcToolMainLib(ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1))); grpc::string binary_data = output_stream.str(); output_stream.str(grpc::string()); output_stream.clear(); argv[4] = binary_data.c_str(); FLAGS_binary_input = true; FLAGS_binary_output = false; EXPECT_TRUE(0 == GrpcToolMainLib(5, argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1))); // Expected output: ECHO_RESPONSE_MESSAGE EXPECT_TRUE(0 == strcmp(output_stream.str().c_str(), ECHO_RESPONSE_MESSAGE)); ShutdownServer(); } TEST_F(GrpcToolTest, TooFewArguments) { // Test input "grpc_cli call Echo" std::stringstream output_stream; const char* argv[] = {"grpc_cli", "call", "Echo"}; // Exit with 1 EXPECT_EXIT( GrpcToolMainLib( ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1)), ::testing::ExitedWithCode(1), ".*Wrong number of arguments for call.*"); // No output EXPECT_TRUE(0 == output_stream.tellp()); } TEST_F(GrpcToolTest, TooManyArguments) { // Test input "grpc_cli call localhost: Echo Echo "message: 'Hello'" std::stringstream output_stream; const char* argv[] = {"grpc_cli", "call", "localhost:10000", "Echo", "Echo", "message: 'Hello'"}; // Exit with 1 EXPECT_EXIT( GrpcToolMainLib( ArraySize(argv), argv, TestCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1)), ::testing::ExitedWithCode(1), ".*Wrong number of arguments for call.*"); // No output EXPECT_TRUE(0 == output_stream.tellp()); } } // namespace testing } // namespace grpc int main(int argc, char** argv) { grpc_test_init(argc, argv); ::testing::InitGoogleTest(&argc, argv); ::testing::FLAGS_gtest_death_test_style = "threadsafe"; return RUN_ALL_TESTS(); }