| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ledger/cloud_provider_firestore/bin/firestore/listen_call.h" |
| |
| #include <lib/fit/function.h> |
| |
| #include "gtest/gtest.h" |
| |
| namespace cloud_provider_firestore { |
| |
| class TestListenStream : public ListenStream { |
| public: |
| TestListenStream() {} |
| ~TestListenStream() override {} |
| |
| // ListenStream: |
| void StartCall(void* tag) override { |
| connect_tag = static_cast<fit::function<void(bool)>*>(tag); |
| } |
| void ReadInitialMetadata(void* /*tag*/) override {} |
| void Read(google::firestore::v1beta1::ListenResponse* /*response*/, |
| void* tag) override { |
| read_tag = static_cast<fit::function<void(bool)>*>(tag); |
| } |
| void Write(const google::firestore::v1beta1::ListenRequest& /*request*/, |
| void* tag) override { |
| write_tag = static_cast<fit::function<void(bool)>*>(tag); |
| } |
| void Write(const google::firestore::v1beta1::ListenRequest& /*request*/, |
| grpc::WriteOptions /*options*/, void* /*tag*/) override {} |
| void WritesDone(void* /*tag*/) override {} |
| void Finish(grpc::Status* /*status*/, void* tag) override { |
| finish_tag = static_cast<fit::function<void(bool)>*>(tag); |
| } |
| |
| fit::function<void(bool)>* connect_tag = nullptr; |
| fit::function<void(bool)>* read_tag = nullptr; |
| fit::function<void(bool)>* write_tag = nullptr; |
| fit::function<void(bool)>* finish_tag = nullptr; |
| |
| private: |
| FXL_DISALLOW_COPY_AND_ASSIGN(TestListenStream); |
| }; |
| |
| class ListenCallTest : public ::testing::Test, public ListenCallClient { |
| public: |
| ListenCallTest() { |
| auto stream = std::make_unique<TestListenStream>(); |
| auto context = std::make_unique<grpc::ClientContext>(); |
| stream_ = stream.get(); |
| call_ = std::make_unique<ListenCall>(this, std::move(context), |
| std::move(stream)); |
| call_->set_on_empty([this] { on_empty_calls_++; }); |
| } |
| ~ListenCallTest() override {} |
| |
| // ListenCallClient: |
| void OnConnected() override { on_connected_calls_++; } |
| |
| void OnResponse( |
| google::firestore::v1beta1::ListenResponse /*response*/) override { |
| on_response_calls_++; |
| } |
| |
| void OnFinished(grpc::Status status) override { |
| on_finished_calls_++; |
| status_ = status; |
| } |
| |
| protected: |
| TestListenStream* stream_; |
| std::unique_ptr<ListenCall> call_; |
| |
| int on_connected_calls_ = 0; |
| int on_response_calls_ = 0; |
| int on_finished_calls_ = 0; |
| grpc::Status status_; |
| |
| int on_empty_calls_ = 0; |
| |
| private: |
| FXL_DISALLOW_COPY_AND_ASSIGN(ListenCallTest); |
| }; |
| |
| TEST_F(ListenCallTest, DeleteHandlerBeforeConnect) { |
| auto handler = call_->MakeHandler(); |
| handler.reset(); |
| |
| // Simulate the connection response arriving. |
| (*stream_->connect_tag)(true); |
| |
| // Verify that on_empty was called. |
| EXPECT_EQ(1, on_empty_calls_); |
| |
| // Verify that no client calls were made (because the handler was deleted at |
| // the very beginning and no client calls should be made after it goes away). |
| EXPECT_EQ(0, on_connected_calls_); |
| EXPECT_EQ(0, on_response_calls_); |
| EXPECT_EQ(0, on_finished_calls_); |
| } |
| |
| TEST_F(ListenCallTest, ConnectionError) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(false); |
| EXPECT_EQ(0, on_connected_calls_); |
| EXPECT_EQ(0, on_response_calls_); |
| EXPECT_EQ(1, on_finished_calls_); |
| EXPECT_FALSE(status_.ok()); |
| } |
| |
| TEST_F(ListenCallTest, DeleteHandlerAfterConnect) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(true); |
| EXPECT_EQ(1, on_connected_calls_); |
| |
| // Delete the handler and simulate the pending read call failing due to being |
| // interrupted. |
| handler.reset(); |
| (*stream_->read_tag)(false); |
| |
| // Verify that on_empty was called. |
| EXPECT_EQ(1, on_empty_calls_); |
| |
| // Verify that no further client calls were made after the handler is deleted. |
| EXPECT_EQ(1, on_connected_calls_); |
| EXPECT_EQ(0, on_response_calls_); |
| EXPECT_EQ(0, on_finished_calls_); |
| } |
| |
| TEST_F(ListenCallTest, WriteAndDeleteHandler) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(true); |
| EXPECT_EQ(1, on_connected_calls_); |
| |
| handler->Write(google::firestore::v1beta1::ListenRequest()); |
| (*stream_->write_tag)(true); |
| |
| handler.reset(); |
| (*stream_->read_tag)(false); |
| EXPECT_EQ(1, on_empty_calls_); |
| EXPECT_EQ(1, on_connected_calls_); |
| EXPECT_EQ(0, on_response_calls_); |
| EXPECT_EQ(0, on_finished_calls_); |
| } |
| |
| TEST_F(ListenCallTest, ReadAndDeleteHandler) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(true); |
| EXPECT_EQ(1, on_connected_calls_); |
| |
| (*stream_->read_tag)(true); |
| (*stream_->read_tag)(true); |
| (*stream_->read_tag)(true); |
| |
| handler.reset(); |
| (*stream_->read_tag)(false); |
| EXPECT_EQ(1, on_empty_calls_); |
| EXPECT_EQ(1, on_connected_calls_); |
| EXPECT_EQ(3, on_response_calls_); |
| EXPECT_EQ(0, on_finished_calls_); |
| } |
| |
| TEST_F(ListenCallTest, ReadError) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(true); |
| EXPECT_EQ(1, on_connected_calls_); |
| |
| (*stream_->read_tag)(true); |
| EXPECT_EQ(1, on_response_calls_); |
| EXPECT_FALSE(stream_->finish_tag); |
| |
| // Simulate read error, verify that we attempt to finish the stream to |
| // retrieve the error status. |
| (*stream_->read_tag)(false); |
| EXPECT_TRUE(stream_->finish_tag); |
| |
| (*stream_->finish_tag)(true); |
| EXPECT_EQ(1, on_finished_calls_); |
| } |
| |
| TEST_F(ListenCallTest, ReadAndWriteError) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(true); |
| EXPECT_EQ(1, on_connected_calls_); |
| |
| (*stream_->read_tag)(true); |
| EXPECT_EQ(1, on_response_calls_); |
| EXPECT_FALSE(stream_->finish_tag); |
| |
| handler->Write(google::firestore::v1beta1::ListenRequest()); |
| (*stream_->read_tag)(false); |
| (*stream_->write_tag)(false); |
| EXPECT_TRUE(stream_->finish_tag); |
| |
| (*stream_->finish_tag)(true); |
| EXPECT_EQ(1, on_finished_calls_); |
| } |
| |
| TEST_F(ListenCallTest, WriteAndReadError) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(true); |
| EXPECT_EQ(1, on_connected_calls_); |
| |
| (*stream_->read_tag)(true); |
| EXPECT_EQ(1, on_response_calls_); |
| EXPECT_FALSE(stream_->finish_tag); |
| |
| handler->Write(google::firestore::v1beta1::ListenRequest()); |
| (*stream_->write_tag)(false); |
| (*stream_->read_tag)(false); |
| EXPECT_TRUE(stream_->finish_tag); |
| |
| (*stream_->finish_tag)(true); |
| EXPECT_EQ(1, on_finished_calls_); |
| } |
| |
| // Verifies that we don't crash when the handler outlives the call object, see |
| // LE-584. |
| TEST_F(ListenCallTest, DeleteCallObjectBeforeHandler) { |
| auto handler = call_->MakeHandler(); |
| (*stream_->connect_tag)(true); |
| EXPECT_EQ(1, on_connected_calls_); |
| |
| (*stream_->read_tag)(true); |
| EXPECT_EQ(1, on_response_calls_); |
| EXPECT_FALSE(stream_->finish_tag); |
| |
| // Simulate read error which will close the stream. |
| (*stream_->read_tag)(false); |
| EXPECT_TRUE(stream_->finish_tag); |
| (*stream_->finish_tag)(true); |
| EXPECT_EQ(1, on_finished_calls_); |
| |
| // Verify that on_empty was called. |
| EXPECT_EQ(1, on_empty_calls_); |
| |
| // Delete the call object. |
| call_.reset(); |
| |
| // Delete the handler. |
| handler.reset(); |
| } |
| |
| } // namespace cloud_provider_firestore |