| // Copyright 2015 The Chromium 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 "chrome/service/service_ipc_server.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/thread.h" |
| #include "build/build_config.h" |
| #include "chrome/common/service_messages.h" |
| #include "ipc/ipc_channel.h" |
| #include "ipc/ipc_channel_mojo.h" |
| #include "mojo/public/cpp/system/message_pipe.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| void PumpCurrentLoop() { |
| base::MessageLoop::ScopedNestableTaskAllower nestable_task_allower( |
| base::MessageLoop::current()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| class FakeServiceIPCServerClient : public ServiceIPCServer::Client { |
| public: |
| FakeServiceIPCServerClient() {} |
| ~FakeServiceIPCServerClient() override {} |
| void OnShutdown() override; |
| void OnUpdateAvailable() override; |
| bool OnIPCClientDisconnect() override; |
| mojo::ScopedMessagePipeHandle CreateChannelMessagePipe() override; |
| |
| int shutdown_calls_ = 0; |
| int update_available_calls_ = 0; |
| int ipc_client_disconnect_calls_ = 0; |
| mojo::ScopedMessagePipeHandle channel_handle_; |
| }; |
| |
| void FakeServiceIPCServerClient::OnShutdown() { |
| shutdown_calls_++; |
| } |
| |
| void FakeServiceIPCServerClient::OnUpdateAvailable() { |
| update_available_calls_++; |
| } |
| |
| bool FakeServiceIPCServerClient::OnIPCClientDisconnect() { |
| ipc_client_disconnect_calls_++; |
| |
| // Always return true to indicate the server must continue listening for new |
| // connections. |
| return true; |
| } |
| |
| mojo::ScopedMessagePipeHandle |
| FakeServiceIPCServerClient::CreateChannelMessagePipe() { |
| mojo::MessagePipe channel; |
| channel_handle_ = std::move(channel.handle0); |
| return std::move(channel.handle1); |
| } |
| |
| class FakeChannelListener : public IPC::Listener { |
| public: |
| FakeChannelListener() {} |
| ~FakeChannelListener() override {} |
| bool OnMessageReceived(const IPC::Message& message) override { return true; } |
| }; |
| |
| class FakeMessageHandler : public ServiceIPCServer::MessageHandler { |
| public: |
| explicit FakeMessageHandler(bool should_handle); |
| bool HandleMessage(const IPC::Message& message) override; |
| bool should_handle_; |
| int handle_message_calls_; |
| }; |
| |
| FakeMessageHandler::FakeMessageHandler(bool should_handle) |
| : should_handle_(should_handle), handle_message_calls_(0) { |
| } |
| |
| bool FakeMessageHandler::HandleMessage(const IPC::Message& message) { |
| handle_message_calls_++; |
| return should_handle_; |
| } |
| |
| } // namespace |
| |
| class ServiceIPCServerTest : public ::testing::Test { |
| public: |
| ServiceIPCServerTest(); |
| ~ServiceIPCServerTest() override {} |
| void SetUp() override; |
| void TearDown() override; |
| void PumpLoops(); |
| |
| // Simulates the browser process connecting to the service process. |
| void ConnectClientChannel(); |
| |
| // Simulates the browser process shutting down. |
| void DestroyClientChannel(); |
| |
| // Sends |message| to the ServiceIPCServer. |
| void SendToServiceProcess(IPC::Message* message); |
| |
| IPC::SyncChannel* GetServerChannel() { |
| return server_->channel_.get(); |
| } |
| |
| protected: |
| FakeServiceIPCServerClient service_process_client_; |
| base::MessageLoopForUI main_message_loop_; |
| base::Thread io_thread_; |
| base::WaitableEvent shutdown_event_; |
| std::unique_ptr<ServiceIPCServer> server_; |
| FakeChannelListener client_process_channel_listener_; |
| std::unique_ptr<IPC::SyncChannel> client_process_channel_; |
| }; |
| |
| ServiceIPCServerTest::ServiceIPCServerTest() |
| : io_thread_("ServiceIPCServerTest IO"), |
| shutdown_event_(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED) {} |
| |
| void ServiceIPCServerTest::SetUp() { |
| base::Thread::Options options; |
| mojo::MessagePipe channel; |
| options.message_loop_type = base::MessageLoop::TYPE_IO; |
| ASSERT_TRUE(io_thread_.StartWithOptions(options)); |
| |
| server_.reset(new ServiceIPCServer(&service_process_client_, |
| io_thread_.task_runner(), |
| &shutdown_event_)); |
| server_->Init(); |
| } |
| |
| void ServiceIPCServerTest::TearDown() { |
| // Close the ipc channels to prevent memory leaks. |
| if (client_process_channel_) { |
| client_process_channel_->Close(); |
| PumpLoops(); |
| } |
| if (GetServerChannel()) { |
| GetServerChannel()->Close(); |
| PumpLoops(); |
| } |
| io_thread_.Stop(); |
| } |
| |
| void ServiceIPCServerTest::PumpLoops() { |
| base::RunLoop run_loop; |
| io_thread_.task_runner()->PostTaskAndReply(FROM_HERE, |
| base::Bind(&PumpCurrentLoop), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| PumpCurrentLoop(); |
| } |
| |
| void ServiceIPCServerTest::ConnectClientChannel() { |
| client_process_channel_ = IPC::SyncChannel::Create( |
| IPC::ChannelMojo::CreateClientFactory( |
| std::move(service_process_client_.channel_handle_), |
| io_thread_.task_runner()), |
| &client_process_channel_listener_, io_thread_.task_runner(), |
| true /* create_pipe_now */, &shutdown_event_); |
| PumpLoops(); |
| } |
| |
| void ServiceIPCServerTest::DestroyClientChannel() { |
| client_process_channel_.reset(); |
| PumpLoops(); |
| } |
| |
| void ServiceIPCServerTest::SendToServiceProcess(IPC::Message* message) { |
| client_process_channel_->Send(message); |
| PumpLoops(); |
| } |
| |
| TEST_F(ServiceIPCServerTest, ConnectDisconnectReconnect) { |
| // Initially there is no ipc client connected. |
| ASSERT_FALSE(server_->is_ipc_client_connected()); |
| |
| // When a channel is connected the server is notified via OnChannelConnected. |
| ConnectClientChannel(); |
| ASSERT_TRUE(server_->is_ipc_client_connected()); |
| |
| // When the channel is destroyed the server is notified via OnChannelError. |
| // In turn, the server notifies its service process client. |
| DestroyClientChannel(); |
| ASSERT_FALSE(server_->is_ipc_client_connected()); |
| ASSERT_EQ(1, service_process_client_.ipc_client_disconnect_calls_); |
| |
| ConnectClientChannel(); |
| ASSERT_TRUE(server_->is_ipc_client_connected()); |
| SendToServiceProcess(new ServiceMsg_UpdateAvailable()); |
| ASSERT_TRUE(server_->is_ipc_client_connected()); |
| |
| // Destroy the client process channel again to verify the |
| // ServiceIPCServer::Client is notified again. This means that unlike |
| // OnChannelConnected, OnChannelError is called more than once. |
| DestroyClientChannel(); |
| ASSERT_FALSE(server_->is_ipc_client_connected()); |
| ASSERT_EQ(2, service_process_client_.ipc_client_disconnect_calls_); |
| } |
| |
| TEST_F(ServiceIPCServerTest, Shutdown) { |
| ConnectClientChannel(); |
| ASSERT_TRUE(server_->is_ipc_client_connected()); |
| |
| // When a shutdown message is received, the ServiceIPCServer::Client is |
| // notified. |
| SendToServiceProcess(new ServiceMsg_Shutdown()); |
| ASSERT_EQ(1, service_process_client_.shutdown_calls_); |
| } |
| |
| TEST_F(ServiceIPCServerTest, UpdateAvailable) { |
| ConnectClientChannel(); |
| ASSERT_TRUE(server_->is_ipc_client_connected()); |
| |
| // When a product update message is received, the ServiceIPCServer::Client is |
| // notified. |
| SendToServiceProcess(new ServiceMsg_UpdateAvailable()); |
| ASSERT_EQ(1, service_process_client_.update_available_calls_); |
| } |
| |
| TEST_F(ServiceIPCServerTest, SingleMessageHandler) { |
| ConnectClientChannel(); |
| ASSERT_TRUE(server_->is_ipc_client_connected()); |
| |
| // Verify that a message handler is offered messages not handled by the server |
| // itself. |
| FakeMessageHandler* handler = |
| new FakeMessageHandler(true /* should_handle */); |
| server_->AddMessageHandler(base::WrapUnique(handler)); |
| SendToServiceProcess(new ServiceMsg_DisableCloudPrintProxy()); |
| ASSERT_EQ(1, handler->handle_message_calls_); |
| } |
| |
| TEST_F(ServiceIPCServerTest, MultipleMessageHandlers) { |
| ConnectClientChannel(); |
| ASSERT_TRUE(server_->is_ipc_client_connected()); |
| |
| // If there are multiple handlers they are offered the message in order of |
| // being added until it is handled. |
| FakeMessageHandler* handler1 = |
| new FakeMessageHandler(false /* should_handle */); |
| server_->AddMessageHandler(base::WrapUnique(handler1)); |
| FakeMessageHandler* handler2 = |
| new FakeMessageHandler(true /* should_handle */); |
| server_->AddMessageHandler(base::WrapUnique(handler2)); |
| FakeMessageHandler* handler3 = |
| new FakeMessageHandler(true /* should_handle */); |
| server_->AddMessageHandler(base::WrapUnique(handler3)); |
| SendToServiceProcess(new ServiceMsg_DisableCloudPrintProxy()); |
| ASSERT_EQ(1, handler1->handle_message_calls_); |
| ASSERT_EQ(1, handler2->handle_message_calls_); |
| ASSERT_EQ(0, handler3->handle_message_calls_); |
| } |