From 31d92d42ff8493fc0a9eb419ff662e4e5244096b Mon Sep 17 00:00:00 2001 From: Mahak Mukhi Date: Sat, 8 Apr 2017 15:43:07 -0700 Subject: [PATCH] Initial commit: Auto-generate GMOCK code for client stub. --- Makefile | 7 +- build.yaml | 1 + include/grpc++/test/mock_stream.h | 128 +++++++++++++++++++++ src/compiler/cpp_generator.cc | 184 ++++++++++++++++++++++++++++++ src/compiler/cpp_generator.h | 12 ++ src/compiler/cpp_plugin.cc | 10 ++ test/cpp/end2end/mock_test.cc | 142 +++++++---------------- 7 files changed, 378 insertions(+), 106 deletions(-) create mode 100644 include/grpc++/test/mock_stream.h diff --git a/Makefile b/Makefile index 02840b3c26c..4d12b95238b 100644 --- a/Makefile +++ b/Makefile @@ -409,8 +409,9 @@ AROPTS = $(GRPC_CROSS_AROPTS) # e.g., rc --target=elf32-little USE_BUILT_PROTOC = false endif -GTEST_LIB = -Ithird_party/googletest/googletest/include -Ithird_party/googletest/googletest third_party/googletest/googletest/src/gtest-all.cc +GTEST_LIB = -Ithird_party/googletest/googletest/include -Ithird_party/googletest/googletest third_party/googletest/googletest/src/gtest-all.cc -Ithird_party/googletest/googlemock/include -Ithird_party/googletest/googlemock third_party/googletest/googlemock/src/gmock-all.cc GTEST_LIB += -lgflags + ifeq ($(V),1) E = @: Q = @@ -784,7 +785,7 @@ PROTOBUF_PKG_CONFIG = false PC_REQUIRES_GRPCXX = PC_LIBS_GRPCXX = -CPPFLAGS := -Ithird_party/googletest/googletest/include $(CPPFLAGS) +CPPFLAGS := -Ithird_party/googletest/googletest/include -Ithird_party/googletest/googlemock/include $(CPPFLAGS) PROTOC_PLUGINS_ALL = $(BINDIR)/$(CONFIG)/grpc_cpp_plugin $(BINDIR)/$(CONFIG)/grpc_csharp_plugin $(BINDIR)/$(CONFIG)/grpc_node_plugin $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin $(BINDIR)/$(CONFIG)/grpc_php_plugin $(BINDIR)/$(CONFIG)/grpc_python_plugin $(BINDIR)/$(CONFIG)/grpc_ruby_plugin PROTOC_PLUGINS_DIR = $(BINDIR)/$(CONFIG) @@ -15198,7 +15199,7 @@ else $(BINDIR)/$(CONFIG)/mock_test: $(PROTOBUF_DEP) $(MOCK_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(E) "[LD] Linking $@" $(Q) mkdir -p `dirname $@` - $(Q) $(LDXX) $(LDFLAGS) $(MOCK_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/mock_test + $(Q) $(LDXX) $(LDFLAGS) $(MOCK_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/mock_test endif diff --git a/build.yaml b/build.yaml index 414950cd6fc..d4b50b25992 100644 --- a/build.yaml +++ b/build.yaml @@ -949,6 +949,7 @@ filegroups: language: c++ public_headers: - include/grpc++/test/server_context_test_spouse.h + - include/grpc++/test/mock_stream.h deps: - grpc++ - name: thrift_util diff --git a/include/grpc++/test/mock_stream.h b/include/grpc++/test/mock_stream.h new file mode 100644 index 00000000000..f99a1b1128b --- /dev/null +++ b/include/grpc++/test/mock_stream.h @@ -0,0 +1,128 @@ +#ifndef NET_GRPC_PUBLIC_INCLUDE_TEST_MOCK_STREAM_H_ +#define NET_GRPC_PUBLIC_INCLUDE_TEST_MOCK_STREAM_H_ + +#include + +#include +#include +#include +#include +#include + +namespace grpc { +namespace testing { + +template +class MockClientReader : public ClientReaderInterface { + public: + MockClientReader() = default; + + // ClientStreamingInterface + MOCK_METHOD0_T(Finish, Status()); + + // ReaderInterface + MOCK_METHOD1_T(NextMessageSize, bool(uint32_t*)); + MOCK_METHOD1_T(Read, bool(R*)); + + // ClientReaderInterface + MOCK_METHOD0_T(WaitForInitialMetadata, void()); +}; + +template +class MockClientWriter : public ClientWriterInterface { + public: + MockClientWriter() = default; + + // ClientStreamingInterface + MOCK_METHOD0_T(Finish, Status()); + + // WriterInterface + MOCK_METHOD2_T(Write, bool(const W&, const WriteOptions&)); + + // ClientWriterInterface + MOCK_METHOD0_T(WritesDone, bool()); +}; + +template +class MockClientReaderWriter : public ClientReaderWriterInterface { + public: + MockClientReaderWriter() = default; + + // ClientStreamingInterface + MOCK_METHOD0_T(Finish, Status()); + + // ReaderInterface + MOCK_METHOD1_T(NextMessageSize, bool(uint32_t*)); + MOCK_METHOD1_T(Read, bool(R*)); + + // WriterInterface + MOCK_METHOD2_T(Write, bool(const W&, const WriteOptions)); + + // ClientReaderWriterInterface + MOCK_METHOD0_T(WaitForInitialMetadata, void()); + MOCK_METHOD0_T(WritesDone, bool()); +}; + +template +class MockClientAsyncResponseReader + : public ClientAsyncResponseReaderInterface { + public: + MockClientAsyncResponseReader() = default; + + MOCK_METHOD1_T(ReadInitialMetadata, void(void*)); + MOCK_METHOD3_T(Finish, void(R*, Status*, void*)); +}; + +template +class MockClientAsyncReader : public ClientAsyncReaderInterface { + public: + MockClientAsyncReader() = default; + + // ClientAsyncStreamingInterface + MOCK_METHOD1_T(ReadInitialMetadata, void(void*)); + MOCK_METHOD2_T(Finish, void(Status*, void*)); + + // AsyncReaderInterface + MOCK_METHOD2_T(Read, void(R*, void*)); +}; + +template +class MockClientAsyncWriter : public ClientAsyncWriterInterface { + public: + MockClientAsyncWriter() = default; + + // ClientAsyncStreamingInterface + MOCK_METHOD1_T(ReadInitialMetadata, void(void*)); + MOCK_METHOD2_T(Finish, void(Status*, void*)); + + // AsyncWriterInterface + MOCK_METHOD2_T(Write, void(const W&, void*)); + + // ClientAsyncWriterInterface + MOCK_METHOD1_T(WritesDone, void(void*)); +}; + +template +class MockClientAsyncReaderWriter + : public ClientAsyncReaderWriterInterface { + public: + MockClientAsyncReaderWriter() = default; + + // ClientAsyncStreamingInterface + MOCK_METHOD1_T(ReadInitialMetadata, void(void*)); + MOCK_METHOD2_T(Finish, void(Status*, void*)); + + // AsyncWriterInterface + MOCK_METHOD2_T(Write, void(const W&, void*)); + + // AsyncReaderInterface + MOCK_METHOD2_T(Read, void(R*, void*)); + + // ClientAsyncReaderWriterInterface + MOCK_METHOD1_T(WritesDone, void(void*)); +}; + +} // namespace testing +} // namespace grpc + +#endif // NET_GRPC_PUBLIC_INCLUDE_TEST_MOCK_STREAM_H_ diff --git a/src/compiler/cpp_generator.cc b/src/compiler/cpp_generator.cc index c01e830cd68..400627ecca7 100644 --- a/src/compiler/cpp_generator.cc +++ b/src/compiler/cpp_generator.cc @@ -1407,4 +1407,188 @@ grpc::string GetSourceEpilogue(grpc_generator::File *file, return temp; } +// TODO(mmukhi): Make sure we need parameters or not. +grpc::string GetMockPrologue(File *file, const Parameters & ) { + grpc::string output; + { + // Scope the output stream so it closes and finalizes output to the string. + auto printer = file->CreatePrinter(&output); + std::map vars; + + vars["filename"] = file->filename(); + vars["filename_base"] = file->filename_without_ext(); + vars["message_header_ext"] = file->message_header_ext(); + vars["service_header_ext"] = file->service_header_ext(); + + printer->Print(vars, "// Generated by the gRPC C++ plugin.\n"); + printer->Print(vars, + "// If you make any local change, they will be lost.\n"); + printer->Print(vars, "// source: $filename$\n\n"); + + printer->Print(vars, "#include \"$filename_base$$message_header_ext$\"\n"); + printer->Print(vars, "#include \"$filename_base$$service_header_ext$\"\n"); + printer->Print(vars, file->additional_headers().c_str()); + printer->Print(vars, "\n"); + } + return output; +} + +// TODO(mmukhi): Add client-stream and completion-queue headers. +grpc::string GetMockIncludes(File *file, const Parameters ¶ms) { + grpc::string output; + { + // Scope the output stream so it closes and finalizes output to the string. + auto printer = file->CreatePrinter(&output); + std::map vars; + + static const char *headers_strs[] = { + "grpc++/impl/codegen/async_stream.h", + "grpc++/impl/codegen/sync_stream.h", + "gmock/gmock.h", + }; + std::vector headers(headers_strs, array_end(headers_strs)); + PrintIncludes(printer.get(), headers, params); + + if (!file->package().empty()) { + std::vector parts = file->package_parts(); + + for(auto part = parts.begin(); part != parts.end(); part++) { + vars["part"] = *part; + printer->Print(vars, "namespace $part$ {\n"); + } + } + + printer->Print(vars, "\n"); + } + return output; +} + +void PrintMockClientMethods( + Printer *printer, const Method *method, + std::map *vars) { + (*vars)["Method"] = method->name(); + (*vars)["Request"] = method->input_type_name(); + (*vars)["Response"] = method->output_type_name(); + + if (method->NoStreaming()) { + printer->Print( + *vars, + "MOCK_METHOD3($Method$, ::grpc::Status(::grpc::ClientContext* context, " + "const $Request$& request, $Response$* response));\n"); + printer->Print( + *vars, + "MOCK_METHOD3(Async$Method$Raw, " + "::grpc::ClientAsyncResponseReaderInterface< $Response$>*" + "(::grpc::ClientContext* context, const $Request$& request, " + "::grpc::CompletionQueue* cq));\n"); + } else if (method->ClientOnlyStreaming()) { + printer->Print( + *vars, + "MOCK_METHOD2($Method$Raw, " + "::grpc::ClientWriterInterface< $Request$>*" + "(::grpc::ClientContext* context, $Response$* response));\n"); + printer->Print( + *vars, + "MOCK_METHOD4(Async$Method$Raw, " + "::grpc::ClientAsyncWriterInterface< $Request$>*" + "(::grpc::ClientContext* context, $Response$* response, " + "::grpc::CompletionQueue* cq, void* tag));\n"); + } else if (method->ServerOnlyStreaming()) { + printer->Print( + *vars, + "MOCK_METHOD2($Method$Raw, " + "::grpc::ClientReaderInterface< $Response$>*" + "(::grpc::ClientContext* context, const $Request$& request));\n"); + printer->Print( + *vars, + "MOCK_METHOD4(Async$Method$Raw, " + "::grpc::ClientAsyncReaderInterface< $Response$>*" + "(::grpc::ClientContext* context, const $Request$& request, " + "::grpc::CompletionQueue* cq, void* tag));\n"); + } else if (method->BidiStreaming()) { + printer->Print( + *vars, + "MOCK_METHOD1($Method$Raw, " + "::grpc::ClientReaderWriterInterface< $Request$, $Response$>*" + "(::grpc::ClientContext* context));\n"); + printer->Print( + *vars, + "MOCK_METHOD3(Async$Method$Raw, " + "::grpc::ClientAsyncReaderWriterInterface<$Request$, $Response$>*" + "(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, " + "void* tag));\n"); + } +} + +void PrintMockService(Printer *printer, + const Service *service, + std::map *vars) { + (*vars)["Service"] = service->name(); + + printer->Print(service->GetLeadingComments().c_str()); + printer->Print(*vars, + "class Mock$Service$Stub : public $Service$::StubInterface {\n" + " public:\n"); + printer->Indent(); + printer->Print(*vars, + "Mock$Service$Stub(){}\n" + "~Mock$Service$Stub(){}\n"); + for (int i = 0; i < service->method_count(); ++i) { + printer->Print(service->method(i)->GetLeadingComments().c_str()); + PrintMockClientMethods(printer, service->method(i).get(), vars); + printer->Print(service->method(i)->GetTrailingComments().c_str()); + } + printer->Outdent(); + printer->Print("};\n"); + +} + +grpc::string GetMockServices(File *file, + const Parameters ¶ms) { + grpc::string output; + { + // Scope the output stream so it closes and finalizes output to the string. + auto printer = file->CreatePrinter(&output); + std::map vars; + // Package string is empty or ends with a dot. It is used to fully qualify + // method names. + vars["Package"] = file->package(); + if (!file->package().empty()) { + vars["Package"].append("."); + } + + if(!params.services_namespace.empty()) { + vars["services_namespace"] = params.services_namespace; + printer->Print(vars, "\nnamespace $services_namespace$ {\n\n"); + } + + for (int i =0; i < file->service_count(); i++) { + PrintMockService(printer.get(), file->service(i).get(), &vars); + printer->Print("\n"); + } + + if (!params.services_namespace.empty()) { + printer->Print(vars, "} // namespace $services_namespace$\n\n"); + } + } + return output; +} + +grpc::string GetMockEpilogue(File *file, const Parameters &) { + grpc::string temp; + + if (!file->package().empty()) { + std::vector parts = file->package_parts(); + + for (auto part = parts.begin(); part != parts.end(); part++){ + temp.append("} // namespace "); + temp.append(*part); + temp.append("\n"); + } + temp.append("\n"); + } + + return temp; +} + } // namespace grpc_cpp_generator diff --git a/src/compiler/cpp_generator.h b/src/compiler/cpp_generator.h index 69fd8a93e93..55432203d3e 100644 --- a/src/compiler/cpp_generator.h +++ b/src/compiler/cpp_generator.h @@ -99,6 +99,18 @@ grpc::string GetSourceServices(grpc_generator::File *file, grpc::string GetSourceEpilogue(grpc_generator::File *file, const Parameters ¶ms); +// Return the prologue of the generated mock file. +grpc::string GetMockPrologue(File *file, const Parameters ¶ms); + +// Return the includes needed for generated mock file. +grpc::string GetMockIncludes(File *file, const Parameters ¶ms); + +// Return the services for generated mock file. +grpc::string GetMockServices(File* file, const Parameters ¶ms); + +// Return the epilogue of generated mock file. +grpc::string GetMockEpilogue(File* file, const Parameters ¶ms); + } // namespace grpc_cpp_generator #endif // GRPC_INTERNAL_COMPILER_CPP_GENERATOR_H diff --git a/src/compiler/cpp_plugin.cc b/src/compiler/cpp_plugin.cc index 4ee05ee0370..a0986d92cee 100644 --- a/src/compiler/cpp_plugin.cc +++ b/src/compiler/cpp_plugin.cc @@ -114,6 +114,16 @@ class CppGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { grpc::protobuf::io::CodedOutputStream source_coded_out(source_output.get()); source_coded_out.WriteRaw(source_code.data(), source_code.size()); + grpc::string mock_code = + grpc_cpp_generator::GetMockPrologue(&pbfile, generator_parameters) + + grpc_cpp_generator::GetMockIncludes(&pbfile, generator_parameters) + + grpc_cpp_generator::GetMockServices(&pbfile, generator_parameters) + + grpc_cpp_generator::GetMockEpilogue(&pbfile, generator_parameters); + std::unique_ptr mock_output( + context->Open(file_name + "_mock.grpc.pb.h")); + grpc::protobuf::io::CodedOutputStream mock_coded_out(mock_output.get()); + mock_coded_out.WriteRaw(mock_code.data(), mock_code.size()); + return true; } diff --git a/test/cpp/end2end/mock_test.cc b/test/cpp/end2end/mock_test.cc index fdb2732e503..16c04032ab1 100644 --- a/test/cpp/end2end/mock_test.cc +++ b/test/cpp/end2end/mock_test.cc @@ -1,5 +1,5 @@ /* - * +* * Copyright 2015, Google Inc. * All rights reserved. * @@ -45,121 +45,37 @@ #include #include #include +#include + +#include #include "src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.h" #include "src/proto/grpc/testing/echo.grpc.pb.h" +#include "src/proto/grpc/testing/echo_mock.grpc.pb.h" #include "test/core/util/port.h" #include "test/core/util/test_config.h" +#include + +using namespace std; using grpc::testing::EchoRequest; using grpc::testing::EchoResponse; using grpc::testing::EchoTestService; +using grpc::testing::MockClientReaderWriter; using std::chrono::system_clock; +using ::testing::AtLeast; +using ::testing::SetArgPointee; +using ::testing::SaveArg; +using ::testing::_; +using ::testing::Return; +using ::testing::Invoke; +using ::testing::WithArg; +using ::testing::DoAll; namespace grpc { namespace testing { namespace { -template -class MockClientReaderWriter final : public ClientReaderWriterInterface { - public: - void WaitForInitialMetadata() override {} - bool NextMessageSize(uint32_t* sz) override { - *sz = UINT_MAX; - return true; - } - bool Read(R* msg) override { return true; } - bool Write(const W& msg) override { return true; } - bool WritesDone() override { return true; } - Status Finish() override { return Status::OK; } -}; -template <> -class MockClientReaderWriter final - : public ClientReaderWriterInterface { - public: - MockClientReaderWriter() : writes_done_(false) {} - void WaitForInitialMetadata() override {} - bool NextMessageSize(uint32_t* sz) override { - *sz = UINT_MAX; - return true; - } - bool Read(EchoResponse* msg) override { - if (writes_done_) return false; - msg->set_message(last_message_); - return true; - } - - bool Write(const EchoRequest& msg, WriteOptions options) override { - gpr_log(GPR_INFO, "mock recv msg %s", msg.message().c_str()); - last_message_ = msg.message(); - return true; - } - bool WritesDone() override { - writes_done_ = true; - return true; - } - Status Finish() override { return Status::OK; } - - private: - bool writes_done_; - grpc::string last_message_; -}; - -// Mocked stub. -class MockStub : public EchoTestService::StubInterface { - public: - MockStub() {} - ~MockStub() {} - Status Echo(ClientContext* context, const EchoRequest& request, - EchoResponse* response) override { - response->set_message(request.message()); - return Status::OK; - } - Status Unimplemented(ClientContext* context, const EchoRequest& request, - EchoResponse* response) override { - return Status::OK; - } - - private: - ClientAsyncResponseReaderInterface* AsyncEchoRaw( - ClientContext* context, const EchoRequest& request, - CompletionQueue* cq) override { - return nullptr; - } - ClientWriterInterface* RequestStreamRaw( - ClientContext* context, EchoResponse* response) override { - return nullptr; - } - ClientAsyncWriterInterface* AsyncRequestStreamRaw( - ClientContext* context, EchoResponse* response, CompletionQueue* cq, - void* tag) override { - return nullptr; - } - ClientReaderInterface* ResponseStreamRaw( - ClientContext* context, const EchoRequest& request) override { - return nullptr; - } - ClientAsyncReaderInterface* AsyncResponseStreamRaw( - ClientContext* context, const EchoRequest& request, CompletionQueue* cq, - void* tag) override { - return nullptr; - } - ClientReaderWriterInterface* BidiStreamRaw( - ClientContext* context) override { - return new MockClientReaderWriter(); - } - ClientAsyncReaderWriterInterface* - AsyncBidiStreamRaw(ClientContext* context, CompletionQueue* cq, - void* tag) override { - return nullptr; - } - ClientAsyncResponseReaderInterface* AsyncUnimplementedRaw( - ClientContext* context, const EchoRequest& request, - CompletionQueue* cq) override { - return nullptr; - } -}; - class FakeClient { public: explicit FakeClient(EchoTestService::StubInterface* stub) : stub_(stub) {} @@ -267,16 +183,36 @@ TEST_F(MockTest, SimpleRpc) { ResetStub(); FakeClient client(stub_.get()); client.DoEcho(); - MockStub stub; + MockEchoTestServiceStub stub; + EchoResponse resp; + resp.set_message("hello world"); + EXPECT_CALL(stub, Echo(_, _, _)).Times(AtLeast(1)).WillOnce(DoAll(SetArgPointee<2>(resp), Return(Status::OK))); client.ResetStub(&stub); client.DoEcho(); } +ACTION_P(copy, msg) { + arg0->set_message(msg->message()); +} + TEST_F(MockTest, BidiStream) { ResetStub(); FakeClient client(stub_.get()); client.DoBidiStream(); - MockStub stub; + MockEchoTestServiceStub stub; + auto rw = new MockClientReaderWriter(); + EchoRequest msg; + + EXPECT_CALL(*rw, Write(_, _)).Times(3).WillRepeatedly(DoAll(SaveArg<0>(&msg), Return(true))); + EXPECT_CALL(*rw, Read(_)). + WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))). + WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))). + WillOnce(DoAll(WithArg<0>(copy(&msg)), Return(true))). + WillOnce(Return(false)); + EXPECT_CALL(*rw, WritesDone()); + EXPECT_CALL(*rw, Finish()).WillOnce(Return(Status::OK)); + + EXPECT_CALL(stub, BidiStreamRaw(_)).WillOnce(Return(rw)); client.ResetStub(&stub); client.DoBidiStream(); }