From 2d71bd10643da807fe2407a48b02629207d7b70d Mon Sep 17 00:00:00 2001 From: "dmaclach@chromium.org" Date: Wed, 15 Dec 2010 16:04:38 +0000 Subject: Add support for sockets that can listen and accept a connection. These sockets allow one connection at a time, however clients can connect and disconnect repeatedly. These are going to be used by Cloud Print, Remoting and Automation. BUG=NONE TEST=BUILD Review URL: http://codereview.chromium.org/5749001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69264 0039d316-1c4b-4281-b951-d872f2087c98 --- ipc/ipc_channel_posix_unittest.cc | 334 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 ipc/ipc_channel_posix_unittest.cc (limited to 'ipc/ipc_channel_posix_unittest.cc') diff --git a/ipc/ipc_channel_posix_unittest.cc b/ipc/ipc_channel_posix_unittest.cc new file mode 100644 index 0000000..4b668c4 --- /dev/null +++ b/ipc/ipc_channel_posix_unittest.cc @@ -0,0 +1,334 @@ +// Copyright (c) 2006-2009 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. + +// These tests are POSIX only. + +#include "ipc/ipc_channel_posix.h" + +#include +#include +#include +#include + +#include "base/basictypes.h" +#include "base/eintr_wrapper.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "testing/multiprocess_func_list.h" + +namespace { + +enum { + QUIT_MESSAGE = 47 +}; + +class IPCChannelPosixTestListener : public IPC::Channel::Listener { + public: + enum STATUS { + DISCONNECTED, + MESSAGE_RECEIVED, + CHANNEL_ERROR, + CONNECTED, + DENIED, + LISTEN_ERROR + }; + + IPCChannelPosixTestListener(bool quit_only_on_message) + : status_(DISCONNECTED), quit_only_on_message_(quit_only_on_message) {} + + virtual ~IPCChannelPosixTestListener() {} + + virtual void OnMessageReceived(const IPC::Message& message) { + EXPECT_EQ(message.type(), QUIT_MESSAGE); + status_ = MESSAGE_RECEIVED; + QuitRunLoop(); + } + + virtual void OnChannelConnected(int32 peer_pid) { + status_ = CONNECTED; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelError() { + status_ = CHANNEL_ERROR; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelDenied() { + status_ = DENIED; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelListenError() { + status_ = LISTEN_ERROR; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + STATUS status() { return status_; } + + void QuitRunLoop() { + MessageLoopForIO::current()->QuitNow(); + } + + private: + // The current status of the listener. + STATUS status_; + // If |quit_only_on_message_| then the listener will only break out of + // the run loop when the QUIT_MESSAGE is received. + bool quit_only_on_message_; +}; + +} // namespace + +class IPCChannelPosixTest : public base::MultiProcessTest { + public: + static const char kConnectionSocketTestName[]; + static void SetUpSocket(IPC::ChannelHandle *handle, + IPC::Channel::Mode mode); + static void SpinRunLoop(int milliseconds); + + protected: + virtual void SetUp(); + virtual void TearDown(); + +private: + scoped_ptr message_loop_; +}; + +const char IPCChannelPosixTest::kConnectionSocketTestName[] = + "/var/tmp/chrome_IPCChannelPosixTest__AdvancedConnected"; + +void IPCChannelPosixTest::SetUp() { + MultiProcessTest::SetUp(); + // Construct a fresh IO Message loop for the duration of each test. + message_loop_.reset(new MessageLoopForIO()); +} + +void IPCChannelPosixTest::TearDown() { + message_loop_.reset(NULL); + MultiProcessTest::TearDown(); +} + +// Create up a socket and bind and listen to it, or connect it +// depending on the |mode|. +void IPCChannelPosixTest::SetUpSocket(IPC::ChannelHandle *handle, + IPC::Channel::Mode mode) { + const std::string& name = handle->name; + int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); + ASSERT_GE(socket_fd, 0) << name; + ASSERT_GE(fcntl(socket_fd, F_SETFL, O_NONBLOCK), 0); + struct sockaddr_un server_address = { 0 }; + server_address.sun_family = AF_UNIX; + memcpy(server_address.sun_path, name.c_str(), name.length() + 1); + if (mode == IPC::Channel::MODE_NAMED_SERVER) { + // Only one server at a time. Cleanup garbage if it exists. + unlink(name.c_str()); + ASSERT_GE(bind(socket_fd, + reinterpret_cast(&server_address), + SUN_LEN(&server_address)), 0) << name; + ASSERT_GE(listen(socket_fd, SOMAXCONN), 0) << name; + } else if (mode == IPC::Channel::MODE_NAMED_CLIENT) { + ASSERT_GE(connect(socket_fd, + reinterpret_cast(&server_address), + SUN_LEN(&server_address)), 0) << name; + } else { + FAIL() << "Unknown mode " << mode; + } + handle->socket.fd = socket_fd; +} + +void IPCChannelPosixTest::SpinRunLoop(int milliseconds) { + MessageLoopForIO *loop = MessageLoopForIO::current(); + // Post a quit task so that this loop eventually ends and we don't hang + // in the case of a bad test. Usually, the run loop will quit sooner than + // that because all tests use a IPCChannelPosixTestListener which quits the + // current run loop on any channel activity. + loop->PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), milliseconds); + loop->Run(); +} + +TEST_F(IPCChannelPosixTest, BasicListen) { + // Test creating a socket that is listening. + IPC::ChannelHandle handle("/var/tmp/IPCChannelPosixTest::BasicListen"); + SetUpSocket(&handle, IPC::Channel::MODE_NAMED_SERVER); + unlink(handle.name.c_str()); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_SERVER, NULL); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + channel.ResetToAcceptingConnectionState(); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, BasicConnected) { + // Test creating a socket that is connected. + int pipe_fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds)); + std::string socket_name("/var/tmp/IPCChannelPosixTest::BasicConnected"); + base::FileDescriptor fd(pipe_fds[0], false); + IPC::ChannelHandle handle(socket_name, fd); + IPC::Channel channel(handle, IPC::Channel::MODE_SERVER, NULL); + ASSERT_TRUE(channel.Connect()); + ASSERT_FALSE(channel.AcceptsConnections()); + channel.Close(); + ASSERT_TRUE(HANDLE_EINTR(close(pipe_fds[1])) == 0); + + // Make sure that we can use the socket that is created for us by + // a standard channel. + IPC::Channel channel2(socket_name, IPC::Channel::MODE_SERVER, NULL); + ASSERT_TRUE(channel2.Connect()); + ASSERT_FALSE(channel2.AcceptsConnections()); +} + +TEST_F(IPCChannelPosixTest, AdvancedConnected) { + // Test creating a connection to an external process. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(kConnectionSocketTestName); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + QUIT_MESSAGE, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(0, exit_code); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, ResetState) { + // Test creating a connection to an external process. Close the connection, + // but continue to listen and make sure another external process can connect + // to us. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(kConnectionSocketTestName); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + channel.ResetToAcceptingConnectionState(); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle2 = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle2); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + QUIT_MESSAGE, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + EXPECT_TRUE(base::KillProcess(handle, 0, false)); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle2, &exit_code)); + EXPECT_EQ(0, exit_code); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, MultiConnection) { + // Test setting up a connection to an external process, and then have + // another external process attempt to connect to us. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(kConnectionSocketTestName); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + base::ProcessHandle handle2 = SpawnChild("IPCChannelPosixFailConnectionProc", + false); + ASSERT_TRUE(handle2); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle2, &exit_code)); + EXPECT_EQ(exit_code, 0); + ASSERT_EQ(IPCChannelPosixTestListener::DENIED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + QUIT_MESSAGE, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(exit_code, 0); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +// A long running process that connects to us +MULTIPROCESS_TEST_MAIN(IPCChannelPosixTestConnectionProc) { + MessageLoopForIO message_loop; + IPCChannelPosixTestListener listener(true); + IPC::ChannelHandle handle(IPCChannelPosixTest::kConnectionSocketTestName); + IPCChannelPosixTest::SetUpSocket(&handle, IPC::Channel::MODE_NAMED_CLIENT); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_CLIENT, &listener); + EXPECT_TRUE(channel.Connect()); + IPCChannelPosixTest::SpinRunLoop(TestTimeouts::action_timeout_ms()); + EXPECT_EQ(IPCChannelPosixTestListener::MESSAGE_RECEIVED, listener.status()); + return 0; +} + +// Simple external process that shouldn't be able to connect to us. +MULTIPROCESS_TEST_MAIN(IPCChannelPosixFailConnectionProc) { + MessageLoopForIO message_loop; + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle handle(IPCChannelPosixTest::kConnectionSocketTestName); + IPCChannelPosixTest::SetUpSocket(&handle, IPC::Channel::MODE_NAMED_CLIENT); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_CLIENT, &listener); + + // In this case connect may succeed or fail depending on if the packet + // actually gets sent at sendmsg. Since we never delay on send, we may not + // see the error. However even if connect succeeds, eventually we will get an + // error back since the channel will be closed when we attempt to read from + // it. + bool connected = channel.Connect(); + if (connected) { + IPCChannelPosixTest::SpinRunLoop(TestTimeouts::action_timeout_ms()); + EXPECT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + } else { + EXPECT_EQ(IPCChannelPosixTestListener::DISCONNECTED, listener.status()); + } + return 0; +} + -- cgit v1.1