| 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 | 
|  |