Index: third_party/mojo/src/mojo/edk/system/ipc_support_unittest.cc |
diff --git a/third_party/mojo/src/mojo/edk/system/ipc_support_unittest.cc b/third_party/mojo/src/mojo/edk/system/ipc_support_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b613e457fec49698898054d9e93a0b128df6ce33 |
--- /dev/null |
+++ b/third_party/mojo/src/mojo/edk/system/ipc_support_unittest.cc |
@@ -0,0 +1,342 @@ |
+// 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 "mojo/edk/system/ipc_support.h" |
+ |
+#include "base/bind.h" |
+#include "base/command_line.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/macros.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/test/test_io_thread.h" |
+#include "base/test/test_timeouts.h" |
+#include "mojo/edk/embedder/master_process_delegate.h" |
+#include "mojo/edk/embedder/platform_channel_pair.h" |
+#include "mojo/edk/embedder/simple_platform_support.h" |
+#include "mojo/edk/embedder/slave_process_delegate.h" |
+#include "mojo/edk/system/channel_manager.h" |
+#include "mojo/edk/system/connection_identifier.h" |
+#include "mojo/edk/system/message_pipe_dispatcher.h" |
+#include "mojo/edk/system/process_identifier.h" |
+#include "mojo/edk/system/test_utils.h" |
+#include "mojo/edk/system/waiter.h" |
+#include "mojo/edk/test/multiprocess_test_helper.h" |
+#include "mojo/edk/test/test_utils.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace mojo { |
+namespace system { |
+namespace { |
+ |
+const char kConnectionIdFlag[] = "test-connection-id"; |
+ |
+class TestMasterProcessDelegate : public embedder::MasterProcessDelegate { |
+ public: |
+ TestMasterProcessDelegate() |
+ : on_slave_disconnect_event_(true, false) {} // Manual reset. |
+ ~TestMasterProcessDelegate() override {} |
+ |
+ bool TryWaitForOnSlaveDisconnect() { |
+ return on_slave_disconnect_event_.TimedWait(TestTimeouts::action_timeout()); |
+ } |
+ |
+ private: |
+ // |embedder::MasterProcessDelegate| methods: |
+ void OnShutdownComplete() override { NOTREACHED(); } |
+ |
+ void OnSlaveDisconnect(embedder::SlaveInfo /*slave_info*/) override { |
+ on_slave_disconnect_event_.Signal(); |
+ } |
+ |
+ base::WaitableEvent on_slave_disconnect_event_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestMasterProcessDelegate); |
+}; |
+ |
+class TestSlaveProcessDelegate : public embedder::SlaveProcessDelegate { |
+ public: |
+ TestSlaveProcessDelegate() {} |
+ ~TestSlaveProcessDelegate() override {} |
+ |
+ private: |
+ // |embedder::SlaveProcessDelegate| methods: |
+ void OnShutdownComplete() override { NOTREACHED(); } |
+ |
+ void OnMasterDisconnect() override { NOTREACHED(); } |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestSlaveProcessDelegate); |
+}; |
+ |
+TEST(IPCSupportTest, MasterSlave) { |
+ embedder::SimplePlatformSupport platform_support; |
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); |
+ TestMasterProcessDelegate master_process_delegate; |
+ // Note: Run master process delegate methods on the I/O thread. |
+ IPCSupport master_ipc_support( |
+ &platform_support, embedder::ProcessType::MASTER, |
+ test_io_thread.task_runner(), &master_process_delegate, |
+ test_io_thread.task_runner(), embedder::ScopedPlatformHandle()); |
+ |
+ ConnectionIdentifier connection_id = |
+ master_ipc_support.GenerateConnectionIdentifier(); |
+ |
+ embedder::PlatformChannelPair channel_pair; |
+ // Note: |ChannelId|s and |ProcessIdentifier|s are interchangeable. |
+ ProcessIdentifier slave_id = kInvalidProcessIdentifier; |
+ base::WaitableEvent event1(true, false); |
+ scoped_refptr<MessagePipeDispatcher> master_mp = |
+ master_ipc_support.ConnectToSlave( |
+ connection_id, nullptr, channel_pair.PassServerHandle(), |
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event1)), |
+ nullptr, &slave_id); |
+ ASSERT_TRUE(master_mp); |
+ EXPECT_NE(slave_id, kInvalidProcessIdentifier); |
+ EXPECT_NE(slave_id, kMasterProcessIdentifier); |
+ // Note: We don't have to wait on |event1| now, but we'll have to do so before |
+ // tearing down the channel. |
+ |
+ TestSlaveProcessDelegate slave_process_delegate; |
+ // Note: Run process delegate methods on the I/O thread. |
+ IPCSupport slave_ipc_support( |
+ &platform_support, embedder::ProcessType::SLAVE, |
+ test_io_thread.task_runner(), &slave_process_delegate, |
+ test_io_thread.task_runner(), channel_pair.PassClientHandle()); |
+ |
+ ProcessIdentifier master_id = kInvalidProcessIdentifier; |
+ base::WaitableEvent event2(true, false); |
+ scoped_refptr<MessagePipeDispatcher> slave_mp = |
+ slave_ipc_support.ConnectToMaster( |
+ connection_id, |
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event2)), |
+ nullptr, &master_id); |
+ ASSERT_TRUE(slave_mp); |
+ EXPECT_EQ(kMasterProcessIdentifier, master_id); |
+ |
+ // Set up waiting on the slave end first (to avoid racing). |
+ Waiter waiter; |
+ waiter.Init(); |
+ ASSERT_EQ( |
+ MOJO_RESULT_OK, |
+ slave_mp->AddAwakable(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 0, nullptr)); |
+ |
+ // Write a message with just 'x' through the master's end. |
+ EXPECT_EQ(MOJO_RESULT_OK, |
+ master_mp->WriteMessage(UserPointer<const void>("x"), 1, nullptr, |
+ MOJO_WRITE_MESSAGE_FLAG_NONE)); |
+ |
+ // Wait for it to arrive. |
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(test::ActionDeadline(), nullptr)); |
+ slave_mp->RemoveAwakable(&waiter, nullptr); |
+ |
+ // Read the message from the slave's end. |
+ char buffer[10] = {}; |
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); |
+ EXPECT_EQ(MOJO_RESULT_OK, |
+ slave_mp->ReadMessage(UserPointer<void>(buffer), |
+ MakeUserPointer(&buffer_size), 0, nullptr, |
+ MOJO_READ_MESSAGE_FLAG_NONE)); |
+ EXPECT_EQ(1u, buffer_size); |
+ EXPECT_EQ('x', buffer[0]); |
+ |
+ // Don't need the message pipe anymore. |
+ master_mp->Close(); |
+ slave_mp->Close(); |
+ |
+ // A message was sent through the message pipe, |Channel|s must have been |
+ // established on both sides. The events have thus almost certainly been |
+ // signalled, but we'll wait just to be sure. |
+ EXPECT_TRUE(event1.TimedWait(TestTimeouts::action_timeout())); |
+ EXPECT_TRUE(event2.TimedWait(TestTimeouts::action_timeout())); |
+ |
+ test_io_thread.PostTaskAndWait( |
+ FROM_HERE, |
+ base::Bind(&ChannelManager::ShutdownChannelOnIOThread, |
+ base::Unretained(slave_ipc_support.channel_manager()), |
+ master_id)); |
+ test_io_thread.PostTaskAndWait( |
+ FROM_HERE, base::Bind(&IPCSupport::ShutdownOnIOThread, |
+ base::Unretained(&slave_ipc_support))); |
+ |
+ EXPECT_TRUE(master_process_delegate.TryWaitForOnSlaveDisconnect()); |
+ |
+ test_io_thread.PostTaskAndWait( |
+ FROM_HERE, |
+ base::Bind(&ChannelManager::ShutdownChannelOnIOThread, |
+ base::Unretained(master_ipc_support.channel_manager()), |
+ slave_id)); |
+ test_io_thread.PostTaskAndWait( |
+ FROM_HERE, base::Bind(&IPCSupport::ShutdownOnIOThread, |
+ base::Unretained(&master_ipc_support))); |
+} |
+ |
+} // namespace |
+ |
+// Note: This test isn't in an anonymous namespace, since it needs to be |
+// friended by |IPCSupport|. |
+TEST(IPCSupportTest, MasterSlaveInternal) { |
+ embedder::SimplePlatformSupport platform_support; |
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); |
+ TestMasterProcessDelegate master_process_delegate; |
+ // Note: Run master process delegate methods on the I/O thread. |
+ IPCSupport master_ipc_support( |
+ &platform_support, embedder::ProcessType::MASTER, |
+ test_io_thread.task_runner(), &master_process_delegate, |
+ test_io_thread.task_runner(), embedder::ScopedPlatformHandle()); |
+ |
+ ConnectionIdentifier connection_id = |
+ master_ipc_support.GenerateConnectionIdentifier(); |
+ |
+ embedder::PlatformChannelPair channel_pair; |
+ ProcessIdentifier slave_id = kInvalidProcessIdentifier; |
+ embedder::ScopedPlatformHandle master_second_platform_handle = |
+ master_ipc_support.ConnectToSlaveInternal( |
+ connection_id, nullptr, channel_pair.PassServerHandle(), &slave_id); |
+ ASSERT_TRUE(master_second_platform_handle.is_valid()); |
+ EXPECT_NE(slave_id, kInvalidProcessIdentifier); |
+ EXPECT_NE(slave_id, kMasterProcessIdentifier); |
+ |
+ TestSlaveProcessDelegate slave_process_delegate; |
+ // Note: Run process delegate methods on the I/O thread. |
+ IPCSupport slave_ipc_support( |
+ &platform_support, embedder::ProcessType::SLAVE, |
+ test_io_thread.task_runner(), &slave_process_delegate, |
+ test_io_thread.task_runner(), channel_pair.PassClientHandle()); |
+ |
+ embedder::ScopedPlatformHandle slave_second_platform_handle = |
+ slave_ipc_support.ConnectToMasterInternal(connection_id); |
+ ASSERT_TRUE(slave_second_platform_handle.is_valid()); |
+ |
+ // Write an 'x' through the master's end. |
+ size_t n = 0; |
+ EXPECT_TRUE(mojo::test::BlockingWrite(master_second_platform_handle.get(), |
+ "x", 1, &n)); |
+ EXPECT_EQ(1u, n); |
+ |
+ // Read it from the slave's end. |
+ char c = '\0'; |
+ n = 0; |
+ EXPECT_TRUE( |
+ mojo::test::BlockingRead(slave_second_platform_handle.get(), &c, 1, &n)); |
+ EXPECT_EQ(1u, n); |
+ EXPECT_EQ('x', c); |
+ |
+ test_io_thread.PostTaskAndWait( |
+ FROM_HERE, base::Bind(&IPCSupport::ShutdownOnIOThread, |
+ base::Unretained(&slave_ipc_support))); |
+ |
+ EXPECT_TRUE(master_process_delegate.TryWaitForOnSlaveDisconnect()); |
+ |
+ test_io_thread.PostTaskAndWait( |
+ FROM_HERE, base::Bind(&IPCSupport::ShutdownOnIOThread, |
+ base::Unretained(&master_ipc_support))); |
+} |
+ |
+// This is a true multiprocess version of IPCSupportTest.MasterSlaveInternal. |
+// Note: This test isn't in an anonymous namespace, since it needs to be |
+// friended by |IPCSupport|. |
+#if defined(OS_ANDROID) |
+// Android multi-process tests are not executing the new process. This is flaky. |
+// TODO(vtl): I'm guessing this is true of this test too? |
+#define MAYBE_MultiprocessMasterSlaveInternal \ |
+ DISABLED_MultiprocessMasterSlaveInternal |
+#else |
+#define MAYBE_MultiprocessMasterSlaveInternal MultiprocessMasterSlaveInternal |
+#endif // defined(OS_ANDROID) |
+TEST(IPCSupportTest, MAYBE_MultiprocessMasterSlaveInternal) { |
+ embedder::SimplePlatformSupport platform_support; |
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); |
+ TestMasterProcessDelegate master_process_delegate; |
+ // Note: Run process delegate methods on the I/O thread. |
+ IPCSupport ipc_support(&platform_support, embedder::ProcessType::MASTER, |
+ test_io_thread.task_runner(), &master_process_delegate, |
+ test_io_thread.task_runner(), |
+ embedder::ScopedPlatformHandle()); |
+ |
+ ConnectionIdentifier connection_id = |
+ ipc_support.GenerateConnectionIdentifier(); |
+ mojo::test::MultiprocessTestHelper multiprocess_test_helper; |
+ ProcessIdentifier slave_id = kInvalidProcessIdentifier; |
+ embedder::ScopedPlatformHandle second_platform_handle = |
+ ipc_support.ConnectToSlaveInternal( |
+ connection_id, nullptr, |
+ multiprocess_test_helper.server_platform_handle.Pass(), &slave_id); |
+ ASSERT_TRUE(second_platform_handle.is_valid()); |
+ EXPECT_NE(slave_id, kInvalidProcessIdentifier); |
+ EXPECT_NE(slave_id, kMasterProcessIdentifier); |
+ |
+ multiprocess_test_helper.StartChildWithExtraSwitch( |
+ "MultiprocessMasterSlaveInternal", kConnectionIdFlag, |
+ connection_id.ToString()); |
+ |
+ // We write a '?'. The slave should write a '!' in response. |
+ size_t n = 0; |
+ EXPECT_TRUE( |
+ mojo::test::BlockingWrite(second_platform_handle.get(), "?", 1, &n)); |
+ EXPECT_EQ(1u, n); |
+ |
+ char c = '\0'; |
+ n = 0; |
+ EXPECT_TRUE( |
+ mojo::test::BlockingRead(second_platform_handle.get(), &c, 1, &n)); |
+ EXPECT_EQ(1u, n); |
+ EXPECT_EQ('!', c); |
+ |
+ EXPECT_TRUE(master_process_delegate.TryWaitForOnSlaveDisconnect()); |
+ EXPECT_TRUE(multiprocess_test_helper.WaitForChildTestShutdown()); |
+ |
+ test_io_thread.PostTaskAndWait(FROM_HERE, |
+ base::Bind(&IPCSupport::ShutdownOnIOThread, |
+ base::Unretained(&ipc_support))); |
+} |
+ |
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(MultiprocessMasterSlaveInternal) { |
+ embedder::ScopedPlatformHandle client_platform_handle = |
+ mojo::test::MultiprocessTestHelper::client_platform_handle.Pass(); |
+ ASSERT_TRUE(client_platform_handle.is_valid()); |
+ |
+ embedder::SimplePlatformSupport platform_support; |
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); |
+ TestSlaveProcessDelegate slave_process_delegate; |
+ // Note: Run process delegate methods on the I/O thread. |
+ IPCSupport ipc_support(&platform_support, embedder::ProcessType::SLAVE, |
+ test_io_thread.task_runner(), &slave_process_delegate, |
+ test_io_thread.task_runner(), |
+ client_platform_handle.Pass()); |
+ |
+ const base::CommandLine& command_line = |
+ *base::CommandLine::ForCurrentProcess(); |
+ ASSERT_TRUE(command_line.HasSwitch(kConnectionIdFlag)); |
+ bool ok = false; |
+ ConnectionIdentifier connection_id = ConnectionIdentifier::FromString( |
+ command_line.GetSwitchValueASCII(kConnectionIdFlag), &ok); |
+ ASSERT_TRUE(ok); |
+ |
+ embedder::ScopedPlatformHandle second_platform_handle = |
+ ipc_support.ConnectToMasterInternal(connection_id); |
+ ASSERT_TRUE(second_platform_handle.is_valid()); |
+ |
+ // The master should write a '?'. We'll write a '!' in response. |
+ char c = '\0'; |
+ size_t n = 0; |
+ EXPECT_TRUE( |
+ mojo::test::BlockingRead(second_platform_handle.get(), &c, 1, &n)); |
+ EXPECT_EQ(1u, n); |
+ EXPECT_EQ('?', c); |
+ |
+ n = 0; |
+ EXPECT_TRUE( |
+ mojo::test::BlockingWrite(second_platform_handle.get(), "!", 1, &n)); |
+ EXPECT_EQ(1u, n); |
+ |
+ test_io_thread.PostTaskAndWait(FROM_HERE, |
+ base::Bind(&IPCSupport::ShutdownOnIOThread, |
+ base::Unretained(&ipc_support))); |
+} |
+ |
+// TODO(vtl): Also test the case of the master "dying" before the slave. (The |
+// slave should get OnMasterDisconnect(), which we currently don't test.) |
+ |
+} // namespace system |
+} // namespace mojo |