| // | |
| // | |
| // Copyright 2015 gRPC authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| // | |
| #include <climits> | |
| #include <iostream> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "absl/types/optional.h" | |
| #include <grpc/grpc.h> | |
| #include <grpc/support/log.h> | |
| #include <grpc/support/time.h> | |
| #include <grpcpp/channel.h> | |
| #include <grpcpp/client_context.h> | |
| #include <grpcpp/create_channel.h> | |
| #include <grpcpp/server.h> | |
| #include <grpcpp/server_builder.h> | |
| #include <grpcpp/server_context.h> | |
| #include <grpcpp/test/default_reactor_test_peer.h> | |
| #include <grpcpp/test/mock_stream.h> | |
| #include "src/core/lib/gprpp/crash.h" | |
| #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" | |
| using std::vector; | |
| using ::testing::_; | |
| using ::testing::AtLeast; | |
| using ::testing::DoAll; | |
| using ::testing::Return; | |
| using ::testing::SaveArg; | |
| using ::testing::SetArgPointee; | |
| using ::testing::WithArg; | |
| namespace grpc { | |
| namespace testing { | |
| namespace { | |
| class FakeClient { | |
| public: | |
| explicit FakeClient(EchoTestService::StubInterface* stub) : stub_(stub) {} | |
| void DoEcho() { | |
| ClientContext context; | |
| EchoRequest request; | |
| EchoResponse response; | |
| request.set_message("hello world"); | |
| Status s = stub_->Echo(&context, request, &response); | |
| EXPECT_EQ(request.message(), response.message()); | |
| EXPECT_TRUE(s.ok()); | |
| } | |
| void DoRequestStream() { | |
| EchoRequest request; | |
| EchoResponse response; | |
| ClientContext context; | |
| std::string msg("hello"); | |
| std::string exp(msg); | |
| std::unique_ptr<ClientWriterInterface<EchoRequest>> cstream = | |
| stub_->RequestStream(&context, &response); | |
| request.set_message(msg); | |
| EXPECT_TRUE(cstream->Write(request)); | |
| msg = ", world"; | |
| request.set_message(msg); | |
| exp.append(msg); | |
| EXPECT_TRUE(cstream->Write(request)); | |
| cstream->WritesDone(); | |
| Status s = cstream->Finish(); | |
| EXPECT_EQ(exp, response.message()); | |
| EXPECT_TRUE(s.ok()); | |
| } | |
| void DoResponseStream() { | |
| EchoRequest request; | |
| EchoResponse response; | |
| request.set_message("hello world"); | |
| ClientContext context; | |
| std::unique_ptr<ClientReaderInterface<EchoResponse>> cstream = | |
| stub_->ResponseStream(&context, request); | |
| std::string exp; | |
| EXPECT_TRUE(cstream->Read(&response)); | |
| exp.append(response.message() + " "); | |
| EXPECT_TRUE(cstream->Read(&response)); | |
| exp.append(response.message()); | |
| EXPECT_FALSE(cstream->Read(&response)); | |
| EXPECT_EQ(request.message(), exp); | |
| Status s = cstream->Finish(); | |
| EXPECT_TRUE(s.ok()); | |
| } | |
| void DoBidiStream() { | |
| EchoRequest request; | |
| EchoResponse response; | |
| ClientContext context; | |
| std::string msg("hello"); | |
| std::unique_ptr<ClientReaderWriterInterface<EchoRequest, EchoResponse>> | |
| stream = stub_->BidiStream(&context); | |
| request.set_message(msg + "0"); | |
| EXPECT_TRUE(stream->Write(request)); | |
| EXPECT_TRUE(stream->Read(&response)); | |
| EXPECT_EQ(response.message(), request.message()); | |
| request.set_message(msg + "1"); | |
| EXPECT_TRUE(stream->Write(request)); | |
| EXPECT_TRUE(stream->Read(&response)); | |
| EXPECT_EQ(response.message(), request.message()); | |
| request.set_message(msg + "2"); | |
| EXPECT_TRUE(stream->Write(request)); | |
| EXPECT_TRUE(stream->Read(&response)); | |
| EXPECT_EQ(response.message(), request.message()); | |
| stream->WritesDone(); | |
| EXPECT_FALSE(stream->Read(&response)); | |
| Status s = stream->Finish(); | |
| EXPECT_TRUE(s.ok()); | |
| } | |
| void ResetStub(EchoTestService::StubInterface* stub) { stub_ = stub; } | |
| private: | |
| EchoTestService::StubInterface* stub_; | |
| }; | |
| class CallbackTestServiceImpl : public EchoTestService::CallbackService { | |
| public: | |
| ServerUnaryReactor* Echo(CallbackServerContext* context, | |
| const EchoRequest* request, | |
| EchoResponse* response) override { | |
| // Make the mock service explicitly treat empty input messages as invalid | |
| // arguments so that we can test various results of status. In general, a | |
| // mocked service should just use the original service methods, but we are | |
| // adding this variance in Status return value just to improve coverage in | |
| // this test. | |
| auto* reactor = context->DefaultReactor(); | |
| if (request->message().length() > 0) { | |
| response->set_message(request->message()); | |
| reactor->Finish(Status::OK); | |
| } else { | |
| reactor->Finish(Status(StatusCode::INVALID_ARGUMENT, "Invalid request")); | |
| } | |
| return reactor; | |
| } | |
| }; | |
| class MockCallbackTest : public ::testing::Test { | |
| protected: | |
| CallbackTestServiceImpl service_; | |
| ServerContext context_; | |
| }; | |
| TEST_F(MockCallbackTest, MockedCallSucceedsWithWait) { | |
| CallbackServerContext ctx; | |
| EchoRequest req; | |
| EchoResponse resp; | |
| struct { | |
| grpc::internal::Mutex mu; | |
| grpc::internal::CondVar cv; | |
| absl::optional<grpc::Status> ABSL_GUARDED_BY(mu) status; | |
| } status; | |
| DefaultReactorTestPeer peer(&ctx, [&](grpc::Status s) { | |
| grpc::internal::MutexLock l(&status.mu); | |
| status.status = std::move(s); | |
| status.cv.Signal(); | |
| }); | |
| req.set_message("mock 1"); | |
| auto* reactor = service_.Echo(&ctx, &req, &resp); | |
| grpc::internal::MutexLock l(&status.mu); | |
| while (!status.status.has_value()) { | |
| status.cv.Wait(&status.mu); | |
| } | |
| EXPECT_EQ(reactor, peer.reactor()); | |
| EXPECT_TRUE(peer.test_status_set()); | |
| EXPECT_TRUE(peer.test_status().ok()); | |
| EXPECT_TRUE(status.status.has_value()); | |
| EXPECT_TRUE(status.status.value().ok()); | |
| EXPECT_EQ(req.message(), resp.message()); | |
| } | |
| TEST_F(MockCallbackTest, MockedCallSucceeds) { | |
| CallbackServerContext ctx; | |
| EchoRequest req; | |
| EchoResponse resp; | |
| DefaultReactorTestPeer peer(&ctx); | |
| req.set_message("ha ha, consider yourself mocked."); | |
| auto* reactor = service_.Echo(&ctx, &req, &resp); | |
| EXPECT_EQ(reactor, peer.reactor()); | |
| EXPECT_TRUE(peer.test_status_set()); | |
| EXPECT_TRUE(peer.test_status().ok()); | |
| } | |
| TEST_F(MockCallbackTest, MockedCallFails) { | |
| CallbackServerContext ctx; | |
| EchoRequest req; | |
| EchoResponse resp; | |
| DefaultReactorTestPeer peer(&ctx); | |
| auto* reactor = service_.Echo(&ctx, &req, &resp); | |
| EXPECT_EQ(reactor, peer.reactor()); | |
| EXPECT_TRUE(peer.test_status_set()); | |
| EXPECT_EQ(peer.test_status().error_code(), StatusCode::INVALID_ARGUMENT); | |
| } | |
| class TestServiceImpl : public EchoTestService::Service { | |
| public: | |
| Status Echo(ServerContext* /*context*/, const EchoRequest* request, | |
| EchoResponse* response) override { | |
| response->set_message(request->message()); | |
| return Status::OK; | |
| } | |
| Status RequestStream(ServerContext* /*context*/, | |
| ServerReader<EchoRequest>* reader, | |
| EchoResponse* response) override { | |
| EchoRequest request; | |
| std::string resp; | |
| while (reader->Read(&request)) { | |
| gpr_log(GPR_INFO, "recv msg %s", request.message().c_str()); | |
| resp.append(request.message()); | |
| } | |
| response->set_message(resp); | |
| return Status::OK; | |
| } | |
| Status ResponseStream(ServerContext* /*context*/, const EchoRequest* request, | |
| ServerWriter<EchoResponse>* writer) override { | |
| EchoResponse response; | |
| vector<std::string> tokens = split(request->message()); | |
| for (const std::string& token : tokens) { | |
| response.set_message(token); | |
| writer->Write(response); | |
| } | |
| return Status::OK; | |
| } | |
| Status BidiStream( | |
| ServerContext* /*context*/, | |
| ServerReaderWriter<EchoResponse, EchoRequest>* stream) override { | |
| EchoRequest request; | |
| EchoResponse response; | |
| while (stream->Read(&request)) { | |
| gpr_log(GPR_INFO, "recv msg %s", request.message().c_str()); | |
| response.set_message(request.message()); | |
| stream->Write(response); | |
| } | |
| return Status::OK; | |
| } | |
| private: | |
| vector<std::string> split(const std::string& input) { | |
| std::string buff; | |
| vector<std::string> result; | |
| for (auto n : input) { | |
| if (n != ' ') { | |
| buff += n; | |
| continue; | |
| } | |
| if (buff.empty()) continue; | |
| result.push_back(buff); | |
| buff = ""; | |
| } | |
| if (!buff.empty()) result.push_back(buff); | |
| return result; | |
| } | |
| }; | |
| class MockTest : public ::testing::Test { | |
| protected: | |
| MockTest() {} | |
| void SetUp() override { | |
| 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(); | |
| } | |
| void TearDown() override { server_->Shutdown(); } | |
| void ResetStub() { | |
| std::shared_ptr<Channel> channel = grpc::CreateChannel( | |
| server_address_.str(), InsecureChannelCredentials()); | |
| stub_ = grpc::testing::EchoTestService::NewStub(channel); | |
| } | |
| std::unique_ptr<grpc::testing::EchoTestService::Stub> stub_; | |
| std::unique_ptr<Server> server_; | |
| std::ostringstream server_address_; | |
| TestServiceImpl service_; | |
| }; | |
| // Do one real rpc and one mocked one | |
| TEST_F(MockTest, SimpleRpc) { | |
| ResetStub(); | |
| FakeClient client(stub_.get()); | |
| client.DoEcho(); | |
| 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(); | |
| } | |
| TEST_F(MockTest, ClientStream) { | |
| ResetStub(); | |
| FakeClient client(stub_.get()); | |
| client.DoRequestStream(); | |
| MockEchoTestServiceStub stub; | |
| auto w = new MockClientWriter<EchoRequest>(); | |
| EchoResponse resp; | |
| resp.set_message("hello, world"); | |
| EXPECT_CALL(*w, Write(_, _)).Times(2).WillRepeatedly(Return(true)); | |
| EXPECT_CALL(*w, WritesDone()); | |
| EXPECT_CALL(*w, Finish()).WillOnce(Return(Status::OK)); | |
| EXPECT_CALL(stub, RequestStreamRaw(_, _)) | |
| .WillOnce(DoAll(SetArgPointee<1>(resp), Return(w))); | |
| client.ResetStub(&stub); | |
| client.DoRequestStream(); | |
| } | |
| TEST_F(MockTest, ServerStream) { | |
| ResetStub(); | |
| FakeClient client(stub_.get()); | |
| client.DoResponseStream(); | |
| MockEchoTestServiceStub stub; | |
| auto r = new MockClientReader<EchoResponse>(); | |
| EchoResponse resp1; | |
| resp1.set_message("hello"); | |
| EchoResponse resp2; | |
| resp2.set_message("world"); | |
| EXPECT_CALL(*r, Read(_)) | |
| .WillOnce(DoAll(SetArgPointee<0>(resp1), Return(true))) | |
| .WillOnce(DoAll(SetArgPointee<0>(resp2), Return(true))) | |
| .WillOnce(Return(false)); | |
| EXPECT_CALL(*r, Finish()).WillOnce(Return(Status::OK)); | |
| EXPECT_CALL(stub, ResponseStreamRaw(_, _)).WillOnce(Return(r)); | |
| client.ResetStub(&stub); | |
| client.DoResponseStream(); | |
| } | |
| ACTION_P(copy, msg) { arg0->set_message(msg->message()); } | |
| TEST_F(MockTest, BidiStream) { | |
| ResetStub(); | |
| FakeClient client(stub_.get()); | |
| client.DoBidiStream(); | |
| MockEchoTestServiceStub stub; | |
| auto rw = new MockClientReaderWriter<EchoRequest, EchoResponse>(); | |
| 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(); | |
| } | |
| } // namespace | |
| } // namespace testing | |
| } // namespace grpc | |
| int main(int argc, char** argv) { | |
| grpc::testing::TestEnvironment env(&argc, argv); | |
| ::testing::InitGoogleTest(&argc, argv); | |
| return RUN_ALL_TESTS(); | |
| } |