Chromium Code Reviews| Index: util/mach/mach_message_server_test.cc |
| diff --git a/util/mach/mach_message_server_test.cc b/util/mach/mach_message_server_test.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0b811ca2a9085ec4b692cb8b093d2aa4a689ae21 |
| --- /dev/null |
| +++ b/util/mach/mach_message_server_test.cc |
| @@ -0,0 +1,386 @@ |
| +// Copyright 2014 The Crashpad Authors. All rights reserved. |
| +// |
| +// Licensed under the Apache License, Version 2.0 (the "License"); |
| +// you may not use this file except in compliance with the License. |
| +// You may obtain a copy of the License at |
| +// |
| +// http://www.apache.org/licenses/LICENSE-2.0 |
| +// |
| +// Unless required by applicable law or agreed to in writing, software |
| +// distributed under the License is distributed on an "AS IS" BASIS, |
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| +// See the License for the specific language governing permissions and |
| +// limitations under the License. |
| + |
| +#include "util/mach/mach_message_server.h" |
| + |
| +#include <mach/mach.h> |
| +#include <string.h> |
| + |
| +#include "base/basictypes.h" |
| +#include "gtest/gtest.h" |
| +#include "util/file/fd_io.h" |
| +#include "util/test/errors.h" |
| +#include "util/test/mac/mach_errors.h" |
| +#include "util/test/mac/mach_multiprocess.h" |
| + |
| +namespace { |
| + |
| +using namespace crashpad; |
| +using namespace crashpad::test; |
| + |
| +class TestMachMessageServer : public MachMessageServerInterface, |
| + public MachMultiprocess { |
| + public: |
| + // Variations on the MachMessageServer test. |
| + enum TestType { |
|
Robert Sesek
2014/09/08 16:28:47
No tests for the error reply scenario? What about
|
| + // The client sends one message to the server, which will wait indefinitely |
| + // in blocking mode for it. |
| + kBasic = 0, |
| + |
| + // The server waits in nonblocking mode and the client sends nothing, so |
| + // the server should return immediately without processing any message. |
| + kNonblockingNoMessage, |
| + |
| + // The server waits in blocking mode for one message, but with a timeout. |
| + // The client sends no message, so the server returns after the timeout. |
| + kTimeoutNoMessage, |
| + |
| + // The client sends one message to the server and then signals the server |
| + // that it’s safe to start waiting for it in nonblocking mode. The message |
| + // is in the server’s queue, so it’s able to receive it when it begins |
| + // listening in nonblocking mode. |
| + kNonblocking, |
| + |
| + // The client sends one message to the server, which will wait in blocking |
| + // mode for it up to a specific timeout. |
| + kTimeout, |
| + |
| + // The server waits for as many messages as it can receive in blocking mode |
| + // with a timeout. The client sends several messages, and the server |
| + // processes them all. |
| + kPersistentTenMessages, |
| + |
| + // The client sends several messages to the server and then signals the |
| + // server that it’s safe to start waiting for them in nonblocking mode. The |
| + // server then listens for them in nonblocking persistent mode, and receives |
| + // all of them because they’ve been queued up. The client doesn’t wait for |
| + // the replies until after it’s put all of its requests into the server’s |
| + // queue. |
| + // |
| + // This test is sensitive to the length of the IPC queue limit. Mach ports |
| + // normally have a queue length limit of MACH_PORT_QLIMIT_DEFAULT (which |
| + // is MACH_PORT_QLIMIT_BASIC, or 5). The number of messages sent for this |
| + // test must be below this, because the server does not begin dequeueing |
| + // request messages until the client has finished sending them. |
| + kPersistentNonblockingFourMessages, |
| + }; |
| + |
| + explicit TestMachMessageServer(TestType test_type) |
| + : MachMessageServerInterface(), |
| + MachMultiprocess(), |
| + test_type_(test_type) { |
| + } |
| + |
| + // Runs the test, verifying that the number or requests and replies begins |
| + // and remains equal. Returns the number of round-trip messages processed. |
| + uint32_t Test() { |
| + EXPECT_EQ(requests_, replies_); |
| + uint32_t start = requests_; |
| + |
| + Run(); |
| + |
| + EXPECT_EQ(requests_, replies_); |
| + return requests_ - start; |
| + } |
| + |
| + // MachMessageServerInterface: |
| + |
| + virtual bool MachMessageServerFunction( |
| + mach_msg_header_t* in, |
| + mach_msg_header_t* out, |
| + bool* destroy_complex_request) override { |
| + *destroy_complex_request = true; |
| + |
| + switch (test_type_) { |
| + case kNonblockingNoMessage: |
| + case kTimeoutNoMessage: |
| + // These test types should not result in any messages being processed. |
| + ADD_FAILURE(); |
| + return false; |
| + |
| + default: |
| + break; |
| + } |
| + |
| + const ReceiveRequestMessage* request = |
| + reinterpret_cast<ReceiveRequestMessage*>(in); |
| + EXPECT_EQ(RemotePort(), request->header.msgh_remote_port); |
| + EXPECT_EQ(sizeof(SendRequestMessage), request->header.msgh_size); |
| + EXPECT_EQ(requests_, request->number); |
| + ++requests_; |
| + |
| + SendReplyMessage* reply = |
| + reinterpret_cast<SendReplyMessage*>(out); |
| + reply->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); |
| + reply->header.msgh_remote_port = request->header.msgh_remote_port; |
| + reply->header.msgh_size = sizeof(SendReplyMessage); |
| + reply->header.msgh_local_port = MACH_PORT_NULL; |
| + reply->number = replies_++; |
| + |
| + return true; |
| + } |
| + |
| + virtual mach_msg_size_t MachMessageServerRequestSize() override { |
| + return sizeof(SendRequestMessage); |
| + } |
| + |
| + virtual mach_msg_size_t MachMessageServerReplySize() override { |
| + return sizeof(SendReplyMessage); |
| + } |
| + |
| + private: |
| + struct SendRequestMessage { |
| + mach_msg_header_t header; |
| + uint32_t number; |
| + }; |
| + |
| + struct ReceiveRequestMessage : public SendRequestMessage { |
| + mach_msg_trailer_t trailer; |
| + }; |
| + |
| + struct SendReplyMessage { |
| + mach_msg_header_t header; |
| + uint32_t number; |
| + }; |
| + |
| + struct ReceiveReplyMessage : public SendReplyMessage { |
| + mach_msg_trailer_t trailer; |
| + }; |
| + |
| + // MachMultiprocess: |
| + |
| + virtual void MachMultiprocessParent() override { |
| + switch (test_type_) { |
| + case kNonblocking: |
| + case kPersistentNonblockingFourMessages: { |
| + // Wait until the child is done sending what it’s going to send. |
| + char c; |
| + ssize_t rv = ReadFD(ReadPipeFD(), &c, 1); |
| + EXPECT_EQ(1, rv) << ErrnoMessage("read"); |
| + EXPECT_EQ('\0', c); |
| + break; |
| + } |
| + |
| + default: |
| + break; |
| + } |
| + |
| + bool persistent = false; |
| + bool nonblocking = false; |
| + mach_msg_timeout_t timeout = MACH_MSG_TIMEOUT_NONE; |
| + kern_return_t expect = KERN_SUCCESS; |
| + |
| + switch (test_type_) { |
| + case kNonblockingNoMessage: |
| + nonblocking = true; |
| + expect = MACH_RCV_TIMED_OUT; |
| + break; |
| + case kTimeoutNoMessage: |
| + timeout = 10; // milliseconds |
| + expect = MACH_RCV_TIMED_OUT; |
| + break; |
| + case kNonblocking: |
| + nonblocking = true; |
| + break; |
| + case kTimeout: |
| + timeout = 10; // milliseconds |
| + break; |
| + case kPersistentTenMessages: |
| + persistent = true; |
| + timeout = 10; // milliseconds |
| + expect = MACH_RCV_TIMED_OUT; |
| + break; |
| + case kPersistentNonblockingFourMessages: |
| + persistent = true; |
| + nonblocking = true; |
| + expect = MACH_RCV_TIMED_OUT; |
| + break; |
| + default: |
| + break; |
| + } |
| + |
| + kern_return_t kr; |
| + ASSERT_EQ(expect, (kr = MachMessageServer(this, |
| + LocalPort(), |
| + MACH_MSG_OPTION_NONE, |
| + persistent, |
| + nonblocking, |
| + timeout))) |
| + << MachErrorMessage(kr, "MachMessageServer"); |
| + } |
| + |
| + virtual void MachMultiprocessChild() override { |
|
Robert Sesek
2014/09/08 16:28:47
The test is pretty comprehensive, which is good, b
|
| + size_t count = 1; |
| + switch (test_type_) { |
| + case kNonblockingNoMessage: |
| + case kTimeoutNoMessage: |
| + count = 0; |
| + break; |
| + case kPersistentTenMessages: |
| + count = 10; |
| + break; |
| + case kPersistentNonblockingFourMessages: |
| + count = 4; |
| + break; |
| + default: |
| + break; |
| + } |
| + |
| + for (size_t index = 0; index < count; ++index) { |
| + if (test_type_ == kPersistentNonblockingFourMessages) { |
| + // For this test, all of the messages need to go into the queue before |
| + // the parent is allowed to start processing them, so the replies won’t |
| + // be processed until all of the requests are sent. |
| + ChildSendRequest(); |
| + } else { |
| + ChildSendRequestAndWaitForReply(); |
| + } |
| + if (testing::Test::HasFatalFailure()) { |
| + return; |
| + } |
| + } |
| + |
| + if (test_type_ == kPersistentNonblockingFourMessages) { |
| + // Now that all of the requests have been sent, let the parent know that |
| + // it’s safe to begin processing them, and then wait for the replies. |
| + ChildNotifyParentViaPipe(); |
| + if (testing::Test::HasFatalFailure()) { |
| + return; |
| + } |
| + |
| + for (size_t index = 0; index < count; ++index) { |
| + ChildWaitForReply(); |
| + if (testing::Test::HasFatalFailure()) { |
| + return; |
| + } |
| + } |
| + } |
| + } |
| + |
| + // In the child process, sends a request message to the server. |
| + void ChildSendRequest() { |
| + SendRequestMessage request = {}; |
| + request.header.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); |
| + request.header.msgh_size = sizeof(request); |
| + request.header.msgh_remote_port = RemotePort(); |
| + request.header.msgh_local_port = LocalPort(); |
| + request.number = requests_++; |
| + |
| + kern_return_t kr = mach_msg(&request.header, |
| + MACH_SEND_MSG | MACH_SEND_TIMEOUT, |
| + request.header.msgh_size, |
| + 0, |
| + MACH_PORT_NULL, |
| + MACH_MSG_TIMEOUT_NONE, |
| + MACH_PORT_NULL); |
| + ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); |
| + } |
| + |
| + // In the child process, waits for a reply message from the server. |
| + void ChildWaitForReply() { |
| + ReceiveReplyMessage reply = {}; |
| + kern_return_t kr = mach_msg(&reply.header, |
| + MACH_RCV_MSG, |
| + 0, |
| + sizeof(reply), |
| + LocalPort(), |
| + MACH_MSG_TIMEOUT_NONE, |
| + MACH_PORT_NULL); |
| + ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); |
| + |
| + ASSERT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), |
| + reply.header.msgh_remote_port); |
| + ASSERT_EQ(sizeof(SendReplyMessage), reply.header.msgh_size); |
| + ASSERT_EQ(replies_, reply.number); |
| + ++replies_; |
| + } |
| + |
| + // For test types where the child needs to notify the server in the parent |
| + // that the child is ready, this method will send a byte via the POSIX pipe. |
| + // The parent will be waiting in a read() on this pipe, and will proceed to |
| + // running MachMessageServer() once it’s received. |
| + void ChildNotifyParentViaPipe() { |
| + char c = '\0'; |
| + ssize_t rv = WriteFD(WritePipeFD(), &c, 1); |
| + ASSERT_EQ(1, rv) << ErrnoMessage("write"); |
| + } |
| + |
| + // In the child process, sends a request message to the server and then |
| + // receives a reply message. |
| + void ChildSendRequestAndWaitForReply() { |
| + ChildSendRequest(); |
| + if (testing::Test::HasFatalFailure()) { |
| + return; |
| + } |
| + |
| + if (test_type_ == kNonblocking) { |
| + // The parent is waiting to read a byte to indicate that the message has |
| + // been placed in the queue. |
| + ChildNotifyParentViaPipe(); |
| + if (testing::Test::HasFatalFailure()) { |
| + return; |
| + } |
| + } |
| + |
| + ChildWaitForReply(); |
| + } |
| + |
| + TestType test_type_; |
| + |
| + static uint32_t requests_; |
| + static uint32_t replies_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TestMachMessageServer); |
| +}; |
| + |
| +uint32_t TestMachMessageServer::requests_; |
| +uint32_t TestMachMessageServer::replies_; |
| + |
| +TEST(MachMessageServer, Basic) { |
| + TestMachMessageServer test_mach_message_server(TestMachMessageServer::kBasic); |
| + EXPECT_EQ(1u, test_mach_message_server.Test()); |
| +} |
| + |
| +TEST(MachMessageServer, NonblockingNoMessage) { |
| + TestMachMessageServer test_mach_message_server( |
| + TestMachMessageServer::kNonblockingNoMessage); |
| + EXPECT_EQ(0u, test_mach_message_server.Test()); |
| +} |
| + |
| +TEST(MachMessageServer, TimeoutNoMessage) { |
| + TestMachMessageServer test_mach_message_server( |
| + TestMachMessageServer::kTimeoutNoMessage); |
| + EXPECT_EQ(0u, test_mach_message_server.Test()); |
| +} |
| + |
| +TEST(MachMessageServer, Nonblocking) { |
| + TestMachMessageServer test_mach_message_server( |
| + TestMachMessageServer::kNonblocking); |
| + EXPECT_EQ(1u, test_mach_message_server.Test()); |
| +} |
| + |
| +TEST(MachMessageServer, PersistentTenMessages) { |
| + TestMachMessageServer test_mach_message_server( |
| + TestMachMessageServer::kPersistentTenMessages); |
| + EXPECT_EQ(10u, test_mach_message_server.Test()); |
| +} |
| + |
| +TEST(MachMessageServer, PersistentNonblockingFourMessages) { |
| + TestMachMessageServer test_mach_message_server( |
| + TestMachMessageServer::kPersistentNonblockingFourMessages); |
| + EXPECT_EQ(4u, test_mach_message_server.Test()); |
| +} |
| + |
| +} // namespace |