Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(573)

Unified Diff: util/mach/mach_message_server_test.cc

Issue 544393002: Add MachMessageServer and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Address review feedback Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « util/mach/mach_message_server.cc ('k') | util/util.gyp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..dcbbae388d7d0c30df44ee1905feed75e27b2cc1
--- /dev/null
+++ b/util/mach/mach_message_server_test.cc
@@ -0,0 +1,517 @@
+// 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 "base/mac/scoped_mach_port.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 MachMessageServer::Interface,
+ public MachMultiprocess {
+ public:
+ struct Options {
+ // The type of reply port that the client should put in its request message.
+ enum ReplyPortType {
+ // The normal reply port is the client’s local port, to which it holds
+ // a receive right. This allows the server to respond directly to the
+ // client. The client will expect a reply.
+ kReplyPortNormal,
+
+ // Use MACH_PORT_NULL as the reply port, which the server should detect
+ // avoid attempting to send a message to, and return success. The client
+ // will not expect a reply.
+ kReplyPortNull,
+
+ // Make the server see the reply port as a dead name by setting the reply
+ // port to a receive right and then destroying that right before the
+ // server processes the request. The server should return
+ // MACH_SEND_INVALID_DEST, and the client will not expect a reply.
+ kReplyPortDead,
+ };
+
+ Options()
+ : expect_server_interface_method_called(true),
+ parent_wait_for_child_pipe(false),
+ server_persistent(MachMessageServer::kOneShot),
+ server_nonblocking(MachMessageServer::kBlocking),
+ server_timeout_ms(MACH_MSG_TIMEOUT_NONE),
+ server_mig_retcode(KERN_SUCCESS),
+ expect_server_result(KERN_SUCCESS),
+ client_send_request_count(1),
+ client_reply_port_type(kReplyPortNormal),
+ child_send_all_requests_before_receiving_any_replies(false) {
+ }
+
+ // true if MachMessageServerFunction() is expected to be called.
+ bool expect_server_interface_method_called;
+
+ // true if the parent should wait for the child to write a byte to the pipe
+ // as a signal that the child is ready for the parent to begin its side of
+ // the test. This is used for nonblocking tests, which require that there
+ // be something in the server’s queue before attempting a nonblocking
+ // receive if the receive is to be successful.
+ bool parent_wait_for_child_pipe;
+
+ // Whether the server should run in one-shot or persistent mode.
+ MachMessageServer::Persistent server_persistent;
+
+ // Whether the server should run in blocking or nonblocking mode.
+ MachMessageServer::Nonblocking server_nonblocking;
+
+ // The server’s timeout.
+ mach_msg_timeout_t server_timeout_ms;
+
+ // The return code that the server returns to the client via the
+ // mig_reply_error_t::RetCode field. A client would normally see this as
+ // a Mach RPC return value.
+ kern_return_t server_mig_retcode;
+
+ // The expected return value from MachMessageServer::Run().
+ kern_return_t expect_server_result;
+
+ // The number of requests that the client should send to the server.
+ size_t client_send_request_count;
+
+ // The type of reply port that the client should provide in its request’s
+ // mach_msg_header_t::msgh_local_port, which will appear to the server as
+ // mach_msg_header_t::msgh_remote_port.
+ ReplyPortType client_reply_port_type;
+
+ // true if the client should send all requests before attempting to receive
+ // any replies from the server. This is used for the persistent nonblocking
+ // test, which requires the client to fill the server’s queue before the
+ // server can attempt processing it.
+ bool child_send_all_requests_before_receiving_any_replies;
+ };
+
+ explicit TestMachMessageServer(const Options& options)
+ : MachMessageServer::Interface(),
+ MachMultiprocess(),
+ options_(options) {
+ }
+
+ // Runs the test.
+ void Test() {
+ EXPECT_EQ(requests_, replies_);
+ uint32_t start = requests_;
+
+ Run();
+
+ EXPECT_EQ(requests_, replies_);
+ EXPECT_EQ(options_.client_send_request_count, 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;
+
+ EXPECT_TRUE(options_.expect_server_interface_method_called);
+ if (!options_.expect_server_interface_method_called) {
+ return false;
+ }
+
+ struct ReceiveRequestMessage : public RequestMessage {
+ mach_msg_trailer_t trailer;
+ };
+
+ const ReceiveRequestMessage* request =
+ reinterpret_cast<ReceiveRequestMessage*>(in);
+ EXPECT_EQ(static_cast<mach_msg_bits_t>(
+ MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND)),
+ request->header.msgh_bits);
+ EXPECT_EQ(sizeof(RequestMessage), request->header.msgh_size);
+ if (options_.client_reply_port_type == Options::kReplyPortNormal) {
+ EXPECT_EQ(RemotePort(), request->header.msgh_remote_port);
+ }
+ EXPECT_EQ(LocalPort(), request->header.msgh_local_port);
+ EXPECT_EQ(kRequestMessageId, request->header.msgh_id);
+ EXPECT_EQ(0, memcmp(&request->ndr, &NDR_record, sizeof(NDR_record)));
+ EXPECT_EQ(requests_, request->number);
+ EXPECT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
+ request->trailer.msgh_trailer_type);
+ EXPECT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE,
+ request->trailer.msgh_trailer_size);
+
+ ++requests_;
+
+ ReplyMessage* reply = reinterpret_cast<ReplyMessage*>(out);
+ reply->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
+ reply->Head.msgh_size = sizeof(*reply);
+ reply->Head.msgh_remote_port = request->header.msgh_remote_port;
+ reply->Head.msgh_local_port = MACH_PORT_NULL;
+ reply->Head.msgh_id = kReplyMessageId;
+ reply->NDR = NDR_record;
+ reply->RetCode = options_.server_mig_retcode;
+ reply->number = replies_++;
+
+ return true;
+ }
+
+ virtual mach_msg_size_t MachMessageServerRequestSize() override {
+ return sizeof(RequestMessage);
+ }
+
+ virtual mach_msg_size_t MachMessageServerReplySize() override {
+ return sizeof(ReplyMessage);
+ }
+
+ private:
+ struct RequestMessage {
+ mach_msg_header_t header;
+ NDR_record_t ndr;
+ uint32_t number;
+ };
+
+ struct ReplyMessage : public mig_reply_error_t {
+ uint32_t number;
+ };
+
+ // MachMultiprocess:
+
+ virtual void MachMultiprocessParent() override {
+ if (options_.parent_wait_for_child_pipe) {
+ // 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);
+ }
+
+ kern_return_t kr;
+ ASSERT_EQ(options_.expect_server_result,
+ (kr = MachMessageServer::Run(this,
+ LocalPort(),
+ MACH_MSG_OPTION_NONE,
+ options_.server_persistent,
+ options_.server_nonblocking,
+ options_.server_timeout_ms)))
+ << MachErrorMessage(kr, "MachMessageServer");
+ }
+
+ virtual void MachMultiprocessChild() override {
+ for (size_t index = 0;
+ index < options_.client_send_request_count;
+ ++index) {
+ if (options_.child_send_all_requests_before_receiving_any_replies) {
+ // For this test, all of the messages need to go into the queue before
+ // the parent is allowed to start processing them. Don’t attempt to
+ // process replies before all of the requests are sent, because the
+ // server won’t have sent any replies until all of the requests are in
+ // its queue.
+ ChildSendRequest();
+ } else {
+ ChildSendRequestAndWaitForReply();
+ }
+ if (testing::Test::HasFatalFailure()) {
+ return;
+ }
+ }
+
+ if (options_.parent_wait_for_child_pipe &&
+ options_.child_send_all_requests_before_receiving_any_replies) {
+ // 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 < options_.client_send_request_count;
+ ++index) {
+ ChildWaitForReply();
+ if (testing::Test::HasFatalFailure()) {
+ return;
+ }
+ }
+ }
+ }
+
+ // In the child process, sends a request message to the server.
+ void ChildSendRequest() {
+ // local_receive_port_owner will the receive right that is created in this
+ // scope and intended to be destroyed when leaving this scope, after it has
+ // been carried in a Mach message.
+ base::mac::ScopedMachReceiveRight local_receive_port_owner;
+
+ RequestMessage 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();
+ kern_return_t kr;
+ switch (options_.client_reply_port_type) {
+ case Options::kReplyPortNormal:
+ request.header.msgh_local_port = LocalPort();
+ break;
+ case Options::kReplyPortNull:
+ request.header.msgh_local_port = MACH_PORT_NULL;
+ break;
+ case Options::kReplyPortDead: {
+ // Use a newly-allocated receive right that will be destroyed when this
+ // method returns. A send right will be made from this receive right and
+ // carried in the request message to the server. By the time the server
+ // looks at the right, it will have become a dead name.
+ kr = mach_port_allocate(mach_task_self(),
+ MACH_PORT_RIGHT_RECEIVE,
+ &request.header.msgh_local_port);
+ ASSERT_EQ(KERN_SUCCESS, kr)
+ << MachErrorMessage(kr, "mach_port_allocate");
+ local_receive_port_owner.reset(request.header.msgh_local_port);
+ break;
+ }
+ }
+ request.header.msgh_id = kRequestMessageId;
+ request.number = requests_++;
+ request.ndr = NDR_record;
+
+ 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() {
+ if (options_.client_reply_port_type != Options::kReplyPortNormal) {
+ // The client shouldn’t expect a reply when it didn’t send a good reply
+ // port with its request.
+ return;
+ }
+
+ struct ReceiveReplyMessage : public ReplyMessage {
+ mach_msg_trailer_t trailer;
+ };
+
+ ReceiveReplyMessage reply = {};
+ kern_return_t kr = mach_msg(&reply.Head,
+ 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_msg_bits_t>(
+ MACH_MSGH_BITS(0, MACH_MSG_TYPE_MOVE_SEND)), reply.Head.msgh_bits);
+ ASSERT_EQ(sizeof(ReplyMessage), reply.Head.msgh_size);
+ ASSERT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL),
+ reply.Head.msgh_remote_port);
+ ASSERT_EQ(LocalPort(), reply.Head.msgh_local_port);
+ ASSERT_EQ(kReplyMessageId, reply.Head.msgh_id);
+ ASSERT_EQ(0, memcmp(&reply.NDR, &NDR_record, sizeof(NDR_record)));
+ ASSERT_EQ(options_.server_mig_retcode, reply.RetCode);
+ ASSERT_EQ(replies_, reply.number);
+ ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
+ reply.trailer.msgh_trailer_type);
+ ASSERT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE, reply.trailer.msgh_trailer_size);
+
+ ++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 (options_.parent_wait_for_child_pipe &&
+ !options_.child_send_all_requests_before_receiving_any_replies) {
+ // 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();
+ }
+
+ const Options& options_;
+
+ static uint32_t requests_;
+ static uint32_t replies_;
+
+ static const mach_msg_id_t kRequestMessageId = 16237;
+ static const mach_msg_id_t kReplyMessageId = kRequestMessageId + 100;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMachMessageServer);
+};
+
+uint32_t TestMachMessageServer::requests_;
+uint32_t TestMachMessageServer::replies_;
+const mach_msg_id_t TestMachMessageServer::kRequestMessageId;
+const mach_msg_id_t TestMachMessageServer::kReplyMessageId;
+
+TEST(MachMessageServer, Basic) {
+ // The client sends one message to the server, which will wait indefinitely in
+ // blocking mode for it.
+ TestMachMessageServer::Options options;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, NonblockingNoMessage) {
+ // The server waits in nonblocking mode and the client sends nothing, so the
+ // server should return immediately without processing any message.
+ TestMachMessageServer::Options options;
+ options.expect_server_interface_method_called = false;
+ options.server_nonblocking = MachMessageServer::kNonblocking;
+ options.expect_server_result = MACH_RCV_TIMED_OUT;
+ options.client_send_request_count = 0;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, TimeoutNoMessage) {
+ // 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.
+ TestMachMessageServer::Options options;
+ options.expect_server_interface_method_called = false;
+ options.server_timeout_ms = 10;
+ options.expect_server_result = MACH_RCV_TIMED_OUT;
+ options.client_send_request_count = 0;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, Nonblocking) {
+ // 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.
+ TestMachMessageServer::Options options;
+ options.parent_wait_for_child_pipe = true;
+ options.server_nonblocking = MachMessageServer::kNonblocking;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, Timeout) {
+ // The client sends one message to the server, which will wait in blocking
+ // mode for it up to a specific timeout.
+ TestMachMessageServer::Options options;
+ options.server_timeout_ms = 10;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, PersistentTenMessages) {
+ // 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.
+ TestMachMessageServer::Options options;
+ options.server_persistent = MachMessageServer::kPersistent;
+ options.server_timeout_ms = 10;
+ options.expect_server_result = MACH_RCV_TIMED_OUT;
+ options.client_send_request_count = 10;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, PersistentNonblockingFourMessages) {
+ // 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.
+ TestMachMessageServer::Options options;
+ options.parent_wait_for_child_pipe = true;
+ options.server_persistent = MachMessageServer::kPersistent;
+ options.server_nonblocking = MachMessageServer::kNonblocking;
+ options.expect_server_result = MACH_RCV_TIMED_OUT;
+ options.client_send_request_count = 4;
+ options.child_send_all_requests_before_receiving_any_replies = true;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ReturnCodeInvalidArgument) {
+ // This tests that the mig_reply_error_t::RetCode field is properly returned
+ // to the client.
+ TestMachMessageServer::Options options;
+ TestMachMessageServer test_mach_message_server(options);
+ options.server_mig_retcode = KERN_INVALID_ARGUMENT;
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ReplyPortNull) {
+ // The client sets its reply port to MACH_PORT_NULL. The server should see
+ // this and avoid sending a message to the null port. No reply message is
+ // sent and the server returns success.
+ TestMachMessageServer::Options options;
+ TestMachMessageServer test_mach_message_server(options);
+ options.client_reply_port_type =
+ TestMachMessageServer::Options::kReplyPortNull;
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ReplyPortDead) {
+ // The client allocates a new port and uses it as the reply port in its
+ // request message, and then deallocates its receive right to that port. It
+ // then signals the server to process the request message. The server’s view
+ // of the port is that it is a dead name. The server function will return
+ // MACH_SEND_INVALID_DEST because it’s not possible to send a message to a
+ // dead name.
+ TestMachMessageServer::Options options;
+ TestMachMessageServer test_mach_message_server(options);
+ options.parent_wait_for_child_pipe = true;
+ options.expect_server_result = MACH_SEND_INVALID_DEST;
+ options.client_reply_port_type =
+ TestMachMessageServer::Options::kReplyPortDead;
+ test_mach_message_server.Test();
+}
+
+} // namespace
« no previous file with comments | « util/mach/mach_message_server.cc ('k') | util/util.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698