| // Copyright (c) 2010 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 "build/build_config.h" |
| |
| #if defined(OS_WIN) |
| #include <windows.h> |
| #elif defined(OS_POSIX) |
| #include <sys/types.h> |
| #include <unistd.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <string> |
| #include <utility> |
| |
| #include "ipc/ipc_tests.h" |
| |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/debug/debug_on_start_win.h" |
| #include "base/perftimer.h" |
| #include "base/test/perf_test_suite.h" |
| #include "base/test/test_suite.h" |
| #include "base/threading/thread.h" |
| #include "ipc/ipc_descriptors.h" |
| #include "ipc/ipc_channel.h" |
| #include "ipc/ipc_channel_proxy.h" |
| #include "ipc/ipc_message_utils.h" |
| #include "ipc/ipc_switches.h" |
| #include "testing/multiprocess_func_list.h" |
| |
| // Define to enable IPC performance testing instead of the regular unit tests |
| // #define PERFORMANCE_TEST |
| |
| const char kTestClientChannel[] = "T1"; |
| const char kReflectorChannel[] = "T2"; |
| const char kFuzzerChannel[] = "F3"; |
| const char kSyncSocketChannel[] = "S4"; |
| |
| const size_t kLongMessageStringNumBytes = 50000; |
| |
| #ifndef PERFORMANCE_TEST |
| |
| void IPCChannelTest::SetUp() { |
| MultiProcessTest::SetUp(); |
| |
| // Construct a fresh IO Message loop for the duration of each test. |
| message_loop_ = new MessageLoopForIO(); |
| } |
| |
| void IPCChannelTest::TearDown() { |
| delete message_loop_; |
| message_loop_ = NULL; |
| |
| MultiProcessTest::TearDown(); |
| } |
| |
| #if defined(OS_WIN) |
| base::ProcessHandle IPCChannelTest::SpawnChild(ChildType child_type, |
| IPC::Channel *channel) { |
| // kDebugChildren support. |
| bool debug_on_start = |
| CommandLine::ForCurrentProcess()->HasSwitch(switches::kDebugChildren); |
| |
| switch (child_type) { |
| case TEST_CLIENT: |
| return MultiProcessTest::SpawnChild("RunTestClient", debug_on_start); |
| case TEST_REFLECTOR: |
| return MultiProcessTest::SpawnChild("RunReflector", debug_on_start); |
| case FUZZER_SERVER: |
| return MultiProcessTest::SpawnChild("RunFuzzServer", debug_on_start); |
| case SYNC_SOCKET_SERVER: |
| return MultiProcessTest::SpawnChild("RunSyncSocketServer", debug_on_start); |
| default: |
| return NULL; |
| } |
| } |
| #elif defined(OS_POSIX) |
| base::ProcessHandle IPCChannelTest::SpawnChild(ChildType child_type, |
| IPC::Channel *channel) { |
| // kDebugChildren support. |
| bool debug_on_start = |
| CommandLine::ForCurrentProcess()->HasSwitch(switches::kDebugChildren); |
| |
| base::file_handle_mapping_vector fds_to_map; |
| const int ipcfd = channel->GetClientFileDescriptor(); |
| if (ipcfd > -1) { |
| fds_to_map.push_back(std::pair<int, int>(ipcfd, kPrimaryIPCChannel + 3)); |
| } |
| |
| base::ProcessHandle ret = base::kNullProcessHandle; |
| switch (child_type) { |
| case TEST_CLIENT: |
| ret = MultiProcessTest::SpawnChild("RunTestClient", |
| fds_to_map, |
| debug_on_start); |
| break; |
| case TEST_DESCRIPTOR_CLIENT: |
| ret = MultiProcessTest::SpawnChild("RunTestDescriptorClient", |
| fds_to_map, |
| debug_on_start); |
| break; |
| case TEST_DESCRIPTOR_CLIENT_SANDBOXED: |
| ret = MultiProcessTest::SpawnChild("RunTestDescriptorClientSandboxed", |
| fds_to_map, |
| debug_on_start); |
| break; |
| case TEST_REFLECTOR: |
| ret = MultiProcessTest::SpawnChild("RunReflector", |
| fds_to_map, |
| debug_on_start); |
| break; |
| case FUZZER_SERVER: |
| ret = MultiProcessTest::SpawnChild("RunFuzzServer", |
| fds_to_map, |
| debug_on_start); |
| break; |
| case SYNC_SOCKET_SERVER: |
| ret = MultiProcessTest::SpawnChild("RunSyncSocketServer", |
| fds_to_map, |
| debug_on_start); |
| break; |
| default: |
| return base::kNullProcessHandle; |
| break; |
| } |
| return ret; |
| } |
| #endif // defined(OS_POSIX) |
| |
| TEST_F(IPCChannelTest, BasicMessageTest) { |
| int v1 = 10; |
| std::string v2("foobar"); |
| std::wstring v3(L"hello world"); |
| |
| IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); |
| EXPECT_TRUE(m.WriteInt(v1)); |
| EXPECT_TRUE(m.WriteString(v2)); |
| EXPECT_TRUE(m.WriteWString(v3)); |
| |
| void* iter = NULL; |
| |
| int vi; |
| std::string vs; |
| std::wstring vw; |
| |
| EXPECT_TRUE(m.ReadInt(&iter, &vi)); |
| EXPECT_EQ(v1, vi); |
| |
| EXPECT_TRUE(m.ReadString(&iter, &vs)); |
| EXPECT_EQ(v2, vs); |
| |
| EXPECT_TRUE(m.ReadWString(&iter, &vw)); |
| EXPECT_EQ(v3, vw); |
| |
| // should fail |
| EXPECT_FALSE(m.ReadInt(&iter, &vi)); |
| EXPECT_FALSE(m.ReadString(&iter, &vs)); |
| EXPECT_FALSE(m.ReadWString(&iter, &vw)); |
| } |
| |
| static void Send(IPC::Message::Sender* sender, const char* text) { |
| static int message_index = 0; |
| |
| IPC::Message* message = new IPC::Message(0, |
| 2, |
| IPC::Message::PRIORITY_NORMAL); |
| message->WriteInt(message_index++); |
| message->WriteString(std::string(text)); |
| |
| // Make sure we can handle large messages. |
| char junk[kLongMessageStringNumBytes]; |
| memset(junk, 'a', sizeof(junk)-1); |
| junk[sizeof(junk)-1] = 0; |
| message->WriteString(std::string(junk)); |
| |
| // DEBUG: printf("[%u] sending message [%s]\n", GetCurrentProcessId(), text); |
| sender->Send(message); |
| } |
| |
| class MyChannelListener : public IPC::Channel::Listener { |
| public: |
| virtual bool OnMessageReceived(const IPC::Message& message) { |
| IPC::MessageIterator iter(message); |
| |
| iter.NextInt(); |
| const std::string data = iter.NextString(); |
| const std::string big_string = iter.NextString(); |
| EXPECT_EQ(kLongMessageStringNumBytes - 1, big_string.length()); |
| |
| |
| if (--messages_left_ == 0) { |
| MessageLoop::current()->Quit(); |
| } else { |
| Send(sender_, "Foo"); |
| } |
| return true; |
| } |
| |
| virtual void OnChannelError() { |
| // There is a race when closing the channel so the last message may be lost. |
| EXPECT_LE(messages_left_, 1); |
| MessageLoop::current()->Quit(); |
| } |
| |
| void Init(IPC::Message::Sender* s) { |
| sender_ = s; |
| messages_left_ = 50; |
| } |
| |
| private: |
| IPC::Message::Sender* sender_; |
| int messages_left_; |
| }; |
| |
| TEST_F(IPCChannelTest, ChannelTest) { |
| MyChannelListener channel_listener; |
| // Setup IPC channel. |
| IPC::Channel chan(kTestClientChannel, IPC::Channel::MODE_SERVER, |
| &channel_listener); |
| ASSERT_TRUE(chan.Connect()); |
| |
| channel_listener.Init(&chan); |
| |
| base::ProcessHandle process_handle = SpawnChild(TEST_CLIENT, &chan); |
| ASSERT_TRUE(process_handle); |
| |
| Send(&chan, "hello from parent"); |
| |
| // Run message loop. |
| MessageLoop::current()->Run(); |
| |
| // Close Channel so client gets its OnChannelError() callback fired. |
| chan.Close(); |
| |
| // Cleanup child process. |
| EXPECT_TRUE(base::WaitForSingleProcess(process_handle, 5000)); |
| base::CloseProcessHandle(process_handle); |
| } |
| |
| TEST_F(IPCChannelTest, ChannelProxyTest) { |
| MyChannelListener channel_listener; |
| |
| // The thread needs to out-live the ChannelProxy. |
| base::Thread thread("ChannelProxyTestServer"); |
| base::Thread::Options options; |
| options.message_loop_type = MessageLoop::TYPE_IO; |
| thread.StartWithOptions(options); |
| { |
| // setup IPC channel proxy |
| IPC::ChannelProxy chan(kTestClientChannel, IPC::Channel::MODE_SERVER, |
| &channel_listener, thread.message_loop_proxy()); |
| |
| channel_listener.Init(&chan); |
| |
| #if defined(OS_WIN) |
| base::ProcessHandle process_handle = SpawnChild(TEST_CLIENT, NULL); |
| #elif defined(OS_POSIX) |
| bool debug_on_start = CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDebugChildren); |
| base::file_handle_mapping_vector fds_to_map; |
| const int ipcfd = chan.GetClientFileDescriptor(); |
| if (ipcfd > -1) { |
| fds_to_map.push_back(std::pair<int, int>(ipcfd, kPrimaryIPCChannel + 3)); |
| } |
| |
| base::ProcessHandle process_handle = MultiProcessTest::SpawnChild( |
| "RunTestClient", |
| fds_to_map, |
| debug_on_start); |
| #endif // defined(OS_POSIX) |
| |
| ASSERT_TRUE(process_handle); |
| |
| Send(&chan, "hello from parent"); |
| |
| // run message loop |
| MessageLoop::current()->Run(); |
| |
| // cleanup child process |
| EXPECT_TRUE(base::WaitForSingleProcess(process_handle, 5000)); |
| base::CloseProcessHandle(process_handle); |
| } |
| thread.Stop(); |
| } |
| |
| class ChannelListenerWithOnConnectedSend : public IPC::Channel::Listener { |
| public: |
| virtual void OnChannelConnected(int32 peer_pid) { |
| SendNextMessage(); |
| } |
| |
| virtual bool OnMessageReceived(const IPC::Message& message) { |
| IPC::MessageIterator iter(message); |
| |
| iter.NextInt(); |
| const std::string data = iter.NextString(); |
| const std::string big_string = iter.NextString(); |
| EXPECT_EQ(kLongMessageStringNumBytes - 1, big_string.length()); |
| SendNextMessage(); |
| return true; |
| } |
| |
| virtual void OnChannelError() { |
| // There is a race when closing the channel so the last message may be lost. |
| EXPECT_LE(messages_left_, 1); |
| MessageLoop::current()->Quit(); |
| } |
| |
| void Init(IPC::Message::Sender* s) { |
| sender_ = s; |
| messages_left_ = 50; |
| } |
| |
| private: |
| void SendNextMessage() { |
| if (--messages_left_ == 0) { |
| MessageLoop::current()->Quit(); |
| } else { |
| Send(sender_, "Foo"); |
| } |
| } |
| |
| IPC::Message::Sender* sender_; |
| int messages_left_; |
| }; |
| |
| TEST_F(IPCChannelTest, SendMessageInChannelConnected) { |
| // This tests the case of a listener sending back an event in it's |
| // OnChannelConnected handler. |
| |
| ChannelListenerWithOnConnectedSend channel_listener; |
| // Setup IPC channel. |
| IPC::Channel channel(kTestClientChannel, IPC::Channel::MODE_SERVER, |
| &channel_listener); |
| channel_listener.Init(&channel); |
| ASSERT_TRUE(channel.Connect()); |
| |
| base::ProcessHandle process_handle = SpawnChild(TEST_CLIENT, &channel); |
| ASSERT_TRUE(process_handle); |
| |
| Send(&channel, "hello from parent"); |
| |
| // Run message loop. |
| MessageLoop::current()->Run(); |
| |
| // Close Channel so client gets its OnChannelError() callback fired. |
| channel.Close(); |
| |
| // Cleanup child process. |
| EXPECT_TRUE(base::WaitForSingleProcess(process_handle, 5000)); |
| base::CloseProcessHandle(process_handle); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(RunTestClient) { |
| MessageLoopForIO main_message_loop; |
| MyChannelListener channel_listener; |
| |
| // setup IPC channel |
| IPC::Channel chan(kTestClientChannel, IPC::Channel::MODE_CLIENT, |
| &channel_listener); |
| CHECK(chan.Connect()); |
| channel_listener.Init(&chan); |
| Send(&chan, "hello from child"); |
| // run message loop |
| MessageLoop::current()->Run(); |
| // return true; |
| return 0; |
| } |
| |
| #endif // !PERFORMANCE_TEST |
| |
| #ifdef PERFORMANCE_TEST |
| |
| //----------------------------------------------------------------------------- |
| // Manually performance test |
| // |
| // This test times the roundtrip IPC message cycle. It is enabled with a |
| // special preprocessor define to enable it instead of the standard IPC |
| // unit tests. This works around some funny termination conditions in the |
| // regular unit tests. |
| // |
| // This test is not automated. To test, you will want to vary the message |
| // count and message size in TEST to get the numbers you want. |
| // |
| // FIXME(brettw): Automate this test and have it run by default. |
| |
| // This channel listener just replies to all messages with the exact same |
| // message. It assumes each message has one string parameter. When the string |
| // "quit" is sent, it will exit. |
| class ChannelReflectorListener : public IPC::Channel::Listener { |
| public: |
| explicit ChannelReflectorListener(IPC::Channel *channel) : |
| channel_(channel), |
| count_messages_(0), |
| latency_messages_(0) { |
| std::cout << "Reflector up" << std::endl; |
| } |
| |
| ~ChannelReflectorListener() { |
| std::cout << "Client Messages: " << count_messages_ << std::endl; |
| std::cout << "Client Latency: " << latency_messages_ << std::endl; |
| } |
| |
| virtual bool OnMessageReceived(const IPC::Message& message) { |
| count_messages_++; |
| IPC::MessageIterator iter(message); |
| int time = iter.NextInt(); |
| int msgid = iter.NextInt(); |
| std::string payload = iter.NextString(); |
| latency_messages_ += GetTickCount() - time; |
| |
| // cout << "reflector msg received: " << msgid << endl; |
| if (payload == "quit") |
| MessageLoop::current()->Quit(); |
| |
| IPC::Message* msg = new IPC::Message(0, |
| 2, |
| IPC::Message::PRIORITY_NORMAL); |
| msg->WriteInt(GetTickCount()); |
| msg->WriteInt(msgid); |
| msg->WriteString(payload); |
| channel_->Send(msg); |
| return true; |
| } |
| private: |
| IPC::Channel *channel_; |
| int count_messages_; |
| int latency_messages_; |
| }; |
| |
| class ChannelPerfListener : public IPC::Channel::Listener { |
| public: |
| ChannelPerfListener(IPC::Channel* channel, int msg_count, int msg_size) : |
| count_down_(msg_count), |
| channel_(channel), |
| count_messages_(0), |
| latency_messages_(0) { |
| payload_.resize(msg_size); |
| for (int i = 0; i < static_cast<int>(payload_.size()); i++) |
| payload_[i] = 'a'; |
| std::cout << "perflistener up" << std::endl; |
| } |
| |
| ~ChannelPerfListener() { |
| std::cout << "Server Messages: " << count_messages_ << std::endl; |
| std::cout << "Server Latency: " << latency_messages_ << std::endl; |
| } |
| |
| virtual bool OnMessageReceived(const IPC::Message& message) { |
| count_messages_++; |
| // decode the string so this gets counted in the total time |
| IPC::MessageIterator iter(message); |
| int time = iter.NextInt(); |
| int msgid = iter.NextInt(); |
| std::string cur = iter.NextString(); |
| latency_messages_ += GetTickCount() - time; |
| |
| // cout << "perflistener got message" << endl; |
| |
| count_down_--; |
| if (count_down_ == 0) { |
| IPC::Message* msg = new IPC::Message(0, |
| 2, |
| IPC::Message::PRIORITY_NORMAL); |
| msg->WriteInt(GetTickCount()); |
| msg->WriteInt(count_down_); |
| msg->WriteString("quit"); |
| channel_->Send(msg); |
| SetTimer(NULL, 1, 250, (TIMERPROC) PostQuitMessage); |
| return true; |
| } |
| |
| IPC::Message* msg = new IPC::Message(0, |
| 2, |
| IPC::Message::PRIORITY_NORMAL); |
| msg->WriteInt(GetTickCount()); |
| msg->WriteInt(count_down_); |
| msg->WriteString(payload_); |
| channel_->Send(msg); |
| return true; |
| } |
| |
| private: |
| int count_down_; |
| std::string payload_; |
| IPC::Channel *channel_; |
| int count_messages_; |
| int latency_messages_; |
| }; |
| |
| TEST_F(IPCChannelTest, Performance) { |
| // setup IPC channel |
| IPC::Channel chan(kReflectorChannel, IPC::Channel::MODE_SERVER, NULL); |
| ChannelPerfListener perf_listener(&chan, 10000, 100000); |
| chan.set_listener(&perf_listener); |
| ASSERT_TRUE(chan.Connect()); |
| |
| HANDLE process = SpawnChild(TEST_REFLECTOR, &chan); |
| ASSERT_TRUE(process); |
| |
| PlatformThread::Sleep(1000); |
| |
| PerfTimeLogger logger("IPC_Perf"); |
| |
| // this initial message will kick-start the ping-pong of messages |
| IPC::Message* message = new IPC::Message(0, |
| 2, |
| IPC::Message::PRIORITY_NORMAL); |
| message->WriteInt(GetTickCount()); |
| message->WriteInt(-1); |
| message->WriteString("Hello"); |
| chan.Send(message); |
| |
| // run message loop |
| MessageLoop::current()->Run(); |
| |
| // cleanup child process |
| WaitForSingleObject(process, 5000); |
| CloseHandle(process); |
| } |
| |
| // This message loop bounces all messages back to the sender |
| MULTIPROCESS_TEST_MAIN(RunReflector) { |
| MessageLoopForIO main_message_loop; |
| IPC::Channel chan(kReflectorChannel, IPC::Channel::MODE_CLIENT, NULL); |
| ChannelReflectorListener channel_reflector_listener(&chan); |
| chan.set_listener(&channel_reflector_listener); |
| ASSERT_TRUE(chan.Connect()); |
| |
| MessageLoop::current()->Run(); |
| return true; |
| } |
| |
| #endif // PERFORMANCE_TEST |
| |
| int main(int argc, char** argv) { |
| #ifdef PERFORMANCE_TEST |
| int retval = base::PerfTestSuite(argc, argv).Run(); |
| #else |
| int retval = base::TestSuite(argc, argv).Run(); |
| #endif |
| return retval; |
| } |