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 |