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

Unified Diff: third_party/crashpad/crashpad/util/mach/mach_message_server_test.cc

Issue 1505213004: Copy Crashpad into the Chrome tree instead of importing it via DEPS (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address review comments, update README.chromium Created 5 years 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
Index: third_party/crashpad/crashpad/util/mach/mach_message_server_test.cc
diff --git a/third_party/crashpad/crashpad/util/mach/mach_message_server_test.cc b/third_party/crashpad/crashpad/util/mach/mach_message_server_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a59554b39fef537902f6a8c99c4843800cd4423a
--- /dev/null
+++ b/third_party/crashpad/crashpad/util/mach/mach_message_server_test.cc
@@ -0,0 +1,854 @@
+// 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 <set>
+
+#include "base/basictypes.h"
+#include "base/mac/scoped_mach_port.h"
+#include "gtest/gtest.h"
+#include "test/mac/mach_errors.h"
+#include "test/mac/mach_multiprocess.h"
+#include "util/file/file_io.h"
+#include "util/mach/mach_extensions.h"
+#include "util/mach/mach_message.h"
+#include "util/misc/implicit_cast.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+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_options(MACH_MSG_OPTION_NONE),
+ server_persistent(MachMessageServer::kOneShot),
+ server_receive_large(MachMessageServer::kReceiveLargeError),
+ server_timeout_ms(kMachMessageTimeoutWaitIndefinitely),
+ server_mig_retcode(KERN_SUCCESS),
+ server_destroy_complex(true),
+ expect_server_destroyed_complex(true),
+ expect_server_result(KERN_SUCCESS),
+ expect_server_transaction_count(1),
+ child_wait_for_parent_pipe_early(false),
+ client_send_request_count(1),
+ client_send_complex(false),
+ client_send_large(false),
+ client_reply_port_type(kReplyPortNormal),
+ client_expect_reply(true),
+ child_send_all_requests_before_receiving_any_replies(false),
+ child_wait_for_parent_pipe_late(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;
+
+ // Options to pass to MachMessageServer::Run() as the |options| parameter.
+ mach_msg_options_t server_options;
+
+ // Whether the server should run in one-shot or persistent mode.
+ MachMessageServer::Persistent server_persistent;
+
+ // The strategy for handling large messages.
+ MachMessageServer::ReceiveLarge server_receive_large;
+
+ // The server’s timeout in milliseconds, or kMachMessageTimeoutNonblocking
+ // or kMachMessageTimeoutWaitIndefinitely.
+ 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 value that the server function should set its destroy_complex_request
+ // parameter to. This is true if resources sent in complex request messages
+ // should be destroyed, and false if they should not be destroyed, assuming
+ // that the server function indicates success.
+ bool server_destroy_complex;
+
+ // Whether to expect the server to destroy a complex message. Even if
+ // server_destroy_complex is false, a complex message will be destroyed if
+ // the MIG return code was unsuccessful.
+ bool expect_server_destroyed_complex;
+
+ // The expected return value from MachMessageServer::Run().
+ kern_return_t expect_server_result;
+
+ // The number of transactions that the server is expected to handle.
+ size_t expect_server_transaction_count;
+
+ // true if the child should wait for the parent to signal that it’s ready
+ // for the child to begin sending requests via the pipe. This is done if the
+ // parent needs to perform operations on its receive port before the child
+ // should be permitted to send anything to it. Currently, this is used to
+ // allow the parent to ensure that the receive port’s queue length is high
+ // enough before the child begins attempting to fill it.
+ bool child_wait_for_parent_pipe_early;
+
+ // The number of requests that the client should send to the server.
+ size_t client_send_request_count;
+
+ // true if the client should send a complex message, one that carries a port
+ // descriptor in its body. Normally false.
+ bool client_send_complex;
+
+ // true if the client should send a larger message than the server has
+ // allocated space to receive. The server’s response is directed by
+ // server_receive_large.
+ bool client_send_large;
+
+ // 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 wait for a reply from the server. For
+ // non-normal reply ports or requests which the server responds to with no
+ // reply (MIG_NO_REPLY), the server will either not send a reply or not
+ // succeed in sending a reply, and the child process should not wait for
+ // one.
+ bool client_expect_reply;
+
+ // 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;
+
+ // true if the child should wait to receive a byte from the parent before
+ // exiting. This can be used to keep a receive right in the child alive
+ // until the parent has a chance to verify that it’s holding a send right.
+ // Otherwise, the right might appear in the parent as a dead name if the
+ // child exited before the parent had a chance to examine it. This would be
+ // a race.
+ bool child_wait_for_parent_pipe_late;
+ };
+
+ explicit TestMachMessageServer(const Options& options)
+ : MachMessageServer::Interface(),
+ MachMultiprocess(),
+ options_(options),
+ child_complex_message_port_(),
+ parent_complex_message_port_(MACH_PORT_NULL) {
+ }
+
+ // Runs the test.
+ void Test() {
+ EXPECT_EQ(requests_, replies_);
+ uint32_t start = requests_;
+
+ Run();
+
+ EXPECT_EQ(requests_, replies_);
+ EXPECT_EQ(options_.expect_server_transaction_count, requests_ - start);
+ }
+
+ // MachMessageServerInterface:
+
+ virtual bool MachMessageServerFunction(
+ const mach_msg_header_t* in,
+ mach_msg_header_t* out,
+ bool* destroy_complex_request) override {
+ *destroy_complex_request = options_.server_destroy_complex;
+
+ 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;
+ };
+
+ struct ReceiveLargeRequestMessage : public LargeRequestMessage {
+ mach_msg_trailer_t trailer;
+ };
+
+ const ReceiveRequestMessage* request =
+ reinterpret_cast<const ReceiveRequestMessage*>(in);
+ const mach_msg_bits_t expect_msgh_bits =
+ MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND) |
+ (options_.client_send_complex ? MACH_MSGH_BITS_COMPLEX : 0);
+ EXPECT_EQ(expect_msgh_bits, request->header.msgh_bits);
+ EXPECT_EQ(options_.client_send_large ? sizeof(LargeRequestMessage)
+ : 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);
+ if (options_.client_send_complex) {
+ EXPECT_EQ(1u, request->body.msgh_descriptor_count);
+ EXPECT_NE(kMachPortNull, request->port_descriptor.name);
+ parent_complex_message_port_ = request->port_descriptor.name;
+ EXPECT_EQ(implicit_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_MOVE_SEND),
+ request->port_descriptor.disposition);
+ EXPECT_EQ(
+ implicit_cast<mach_msg_descriptor_type_t>(MACH_MSG_PORT_DESCRIPTOR),
+ request->port_descriptor.type);
+ } else {
+ EXPECT_EQ(0u, request->body.msgh_descriptor_count);
+ EXPECT_EQ(kMachPortNull, request->port_descriptor.name);
+ EXPECT_EQ(0u, request->port_descriptor.disposition);
+ EXPECT_EQ(0u, request->port_descriptor.type);
+ }
+ EXPECT_EQ(0, memcmp(&request->ndr, &NDR_record, sizeof(NDR_record)));
+ EXPECT_EQ(requests_, request->number);
+
+ // Look for the trailer in the right spot, depending on whether the request
+ // message was a RequestMessage or a LargeRequestMessage.
+ const mach_msg_trailer_t* trailer;
+ if (options_.client_send_large) {
+ const ReceiveLargeRequestMessage* large_request =
+ reinterpret_cast<const ReceiveLargeRequestMessage*>(request);
+ for (size_t index = 0; index < sizeof(large_request->data); ++index) {
+ EXPECT_EQ('!', large_request->data[index]);
+ }
+ trailer = &large_request->trailer;
+ } else {
+ trailer = &request->trailer;
+ }
+
+ EXPECT_EQ(implicit_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
+ trailer->msgh_trailer_type);
+ EXPECT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE, 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;
+ }
+
+ std::set<mach_msg_id_t> MachMessageServerRequestIDs() override {
+ const mach_msg_id_t request_ids[] = {kRequestMessageID};
+ return std::set<mach_msg_id_t>(&request_ids[0],
+ &request_ids[arraysize(request_ids)]);
+ }
+
+ mach_msg_size_t MachMessageServerRequestSize() override {
+ return sizeof(RequestMessage);
+ }
+
+ mach_msg_size_t MachMessageServerReplySize() override {
+ return sizeof(ReplyMessage);
+ }
+
+ private:
+ struct RequestMessage : public mach_msg_base_t {
+ // If body.msgh_descriptor_count is 0, port_descriptor will still be
+ // present, but it will be zeroed out. It wouldn’t normally be present in a
+ // message froma MIG-generated interface, but it’s harmless and simpler to
+ // leave it here and just treat it as more data.
+ mach_msg_port_descriptor_t port_descriptor;
+
+ NDR_record_t ndr;
+ uint32_t number;
+ };
+
+ // LargeRequestMessage is larger enough than a regular RequestMessage to
+ // ensure that whatever buffer was allocated to receive a RequestMessage is
+ // not large enough to receive a LargeRequestMessage.
+ struct LargeRequestMessage : public RequestMessage {
+ uint8_t data[4 * PAGE_SIZE];
+ };
+
+ struct ReplyMessage : public mig_reply_error_t {
+ uint32_t number;
+ };
+
+ // MachMultiprocess:
+
+ void MachMultiprocessParent() override {
+ mach_port_t local_port = LocalPort();
+
+ kern_return_t kr;
+ if (options_.child_send_all_requests_before_receiving_any_replies) {
+ // On Mac OS X 10.10, the queue limit of a new Mach port seems to be 2
+ // by default, which is below the value of MACH_PORT_QLIMIT_DEFAULT. Set
+ // the port’s queue limit explicitly here.
+ mach_port_limits limits = {};
+ limits.mpl_qlimit = MACH_PORT_QLIMIT_DEFAULT;
+ kr = mach_port_set_attributes(mach_task_self(),
+ local_port,
+ MACH_PORT_LIMITS_INFO,
+ reinterpret_cast<mach_port_info_t>(&limits),
+ MACH_PORT_LIMITS_INFO_COUNT);
+ ASSERT_EQ(KERN_SUCCESS, kr)
+ << MachErrorMessage(kr, "mach_port_set_attributes");
+ }
+
+ if (options_.child_wait_for_parent_pipe_early) {
+ // Tell the child to begin sending messages.
+ char c = '\0';
+ CheckedWriteFile(WritePipeHandle(), &c, 1);
+ }
+
+ if (options_.parent_wait_for_child_pipe) {
+ // Wait until the child is done sending what it’s going to send.
+ char c;
+ CheckedReadFile(ReadPipeHandle(), &c, 1);
+ EXPECT_EQ('\0', c);
+ }
+
+ ASSERT_EQ(options_.expect_server_result,
+ (kr = MachMessageServer::Run(this,
+ local_port,
+ options_.server_options,
+ options_.server_persistent,
+ options_.server_receive_large,
+ options_.server_timeout_ms)))
+ << MachErrorMessage(kr, "MachMessageServer");
+
+ if (options_.client_send_complex) {
+ EXPECT_NE(kMachPortNull, parent_complex_message_port_);
+ mach_port_type_t type;
+
+ if (!options_.expect_server_destroyed_complex) {
+ // MachMessageServer should not have destroyed the resources sent in the
+ // complex request message.
+ kr = mach_port_type(
+ mach_task_self(), parent_complex_message_port_, &type);
+ EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_type");
+ EXPECT_EQ(MACH_PORT_TYPE_SEND, type);
+
+ // Destroy the resources here.
+ kr = mach_port_deallocate(mach_task_self(),
+ parent_complex_message_port_);
+ EXPECT_EQ(KERN_SUCCESS, kr)
+ << MachErrorMessage(kr, "mach_port_deallocate");
+ }
+
+ // The kernel won’t have reused the same name for another Mach port in
+ // this task so soon. It’s possible that something else in this task could
+ // have reused the name, but it’s unlikely for that to have happened in
+ // this test environment.
+ kr =
+ mach_port_type(mach_task_self(), parent_complex_message_port_, &type);
+ EXPECT_EQ(KERN_INVALID_NAME, kr)
+ << MachErrorMessage(kr, "mach_port_type");
+ }
+
+ if (options_.child_wait_for_parent_pipe_late) {
+ // Let the child know it’s safe to exit.
+ char c = '\0';
+ CheckedWriteFile(WritePipeHandle(), &c, 1);
+ }
+ }
+
+ void MachMultiprocessChild() override {
+ if (options_.child_wait_for_parent_pipe_early) {
+ // Wait until the parent is done setting things up on its end.
+ char c;
+ CheckedReadFile(ReadPipeHandle(), &c, 1);
+ EXPECT_EQ('\0', c);
+ }
+
+ 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.
+ ASSERT_NO_FATAL_FAILURE(ChildSendRequest());
+ } else {
+ ASSERT_NO_FATAL_FAILURE(ChildSendRequestAndWaitForReply());
+ }
+ }
+
+ 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.
+ ASSERT_NO_FATAL_FAILURE(ChildNotifyParentViaPipe());
+
+ for (size_t index = 0;
+ index < options_.client_send_request_count;
+ ++index) {
+ ASSERT_NO_FATAL_FAILURE(ChildWaitForReply());
+ }
+ }
+
+ if (options_.child_wait_for_parent_pipe_late) {
+ char c;
+ CheckedReadFile(ReadPipeHandle(), &c, 1);
+ ASSERT_EQ('\0', c);
+ }
+ }
+
+ // 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;
+
+ // A LargeRequestMessage is always allocated, but the message that will be
+ // sent will be a normal RequestMessage due to the msgh_size field
+ // indicating the size of the smaller base structure unless
+ // options_.client_send_large is true.
+ LargeRequestMessage request = {};
+
+ request.header.msgh_bits =
+ MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND) |
+ (options_.client_send_complex ? MACH_MSGH_BITS_COMPLEX : 0);
+ request.header.msgh_size = options_.client_send_large ?
+ sizeof(LargeRequestMessage) : sizeof(RequestMessage);
+ 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.
+ local_receive_port_owner.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
+ ASSERT_TRUE(local_receive_port_owner.is_valid());
+ request.header.msgh_local_port = local_receive_port_owner.get();
+ break;
+ }
+ }
+ request.header.msgh_id = kRequestMessageID;
+ if (options_.client_send_complex) {
+ // Allocate a new receive right in this process and make a send right that
+ // will appear in the parent process. This is used to test that the server
+ // properly handles ownership of resources received in complex messages.
+ request.body.msgh_descriptor_count = 1;
+ child_complex_message_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
+ ASSERT_TRUE(child_complex_message_port_.is_valid());
+ request.port_descriptor.name = child_complex_message_port_.get();
+ request.port_descriptor.disposition = MACH_MSG_TYPE_MAKE_SEND;
+ request.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
+ } else {
+ request.body.msgh_descriptor_count = 0;
+ request.port_descriptor.name = MACH_PORT_NULL;
+ request.port_descriptor.disposition = 0;
+ request.port_descriptor.type = 0;
+ }
+ request.ndr = NDR_record;
+ request.number = requests_++;
+
+ if (options_.client_send_large) {
+ memset(request.data, '!', sizeof(request.data));
+ }
+
+ 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_expect_reply) {
+ // The client shouldn’t expect a reply when it didn’t send a good reply
+ // port with its request, or when testing the server behaving in a way
+ // that doesn’t send replies.
+ 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(implicit_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(kMachPortNull, 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(implicit_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';
+ CheckedWriteFile(WritePipeHandle(), &c, 1);
+ }
+
+ // In the child process, sends a request message to the server and then
+ // receives a reply message.
+ void ChildSendRequestAndWaitForReply() {
+ ASSERT_NO_FATAL_FAILURE(ChildSendRequest());
+
+ 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.
+ ASSERT_NO_FATAL_FAILURE(ChildNotifyParentViaPipe());
+ }
+
+ ASSERT_NO_FATAL_FAILURE(ChildWaitForReply());
+ }
+
+ const Options& options_;
+
+ // A receive right allocated in the child process. A send right will be
+ // created from this right and sent to the parent parent process in the
+ // request message.
+ base::mac::ScopedMachReceiveRight child_complex_message_port_;
+
+ // The send right received in the parent process. This right is stored in a
+ // member variable to test that resources carried in complex messages are
+ // properly destroyed in the server when expected.
+ mach_port_t parent_complex_message_port_;
+
+ 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_timeout_ms = kMachMessageTimeoutNonblocking;
+ options.expect_server_result = MACH_RCV_TIMED_OUT;
+ options.expect_server_transaction_count = 0;
+ 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.expect_server_transaction_count = 0;
+ 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_timeout_ms = kMachMessageTimeoutNonblocking;
+ 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.expect_server_transaction_count = 10;
+ 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.
+ //
+ // The queue limit on new ports has been seen to be below
+ // MACH_PORT_QLIMIT_DEFAULT, so it will explicitly be set by
+ // mach_port_set_attributes() for this test. This needs to happen before the
+ // child is allowed to begin sending messages, so
+ // child_wait_for_parent_pipe_early is used to make the child wait until the
+ // parent is ready.
+ const size_t kTransactionCount = 4;
+ static_assert(kTransactionCount <= MACH_PORT_QLIMIT_DEFAULT,
+ "must not exceed queue limit");
+
+ TestMachMessageServer::Options options;
+ options.parent_wait_for_child_pipe = true;
+ options.server_persistent = MachMessageServer::kPersistent;
+ options.server_timeout_ms = kMachMessageTimeoutNonblocking;
+ options.expect_server_result = MACH_RCV_TIMED_OUT;
+ options.expect_server_transaction_count = kTransactionCount;
+ options.child_wait_for_parent_pipe_early = true;
+ options.client_send_request_count = kTransactionCount;
+ 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;
+ options.server_mig_retcode = KERN_INVALID_ARGUMENT;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ReturnCodeNoReply) {
+ // This tests that when mig_reply_error_t::RetCode is set to MIG_NO_REPLY, no
+ // response is sent to the client.
+ TestMachMessageServer::Options options;
+ options.server_mig_retcode = MIG_NO_REPLY;
+ options.client_expect_reply = false;
+ options.child_wait_for_parent_pipe_late = true;
+ TestMachMessageServer test_mach_message_server(options);
+ 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;
+ options.client_reply_port_type =
+ TestMachMessageServer::Options::kReplyPortNull;
+ options.client_expect_reply = false;
+ TestMachMessageServer test_mach_message_server(options);
+ 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;
+ options.parent_wait_for_child_pipe = true;
+ options.expect_server_result = MACH_SEND_INVALID_DEST;
+ options.client_reply_port_type =
+ TestMachMessageServer::Options::kReplyPortDead;
+ options.client_expect_reply = false;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, Complex) {
+ // The client allocates a new receive right and sends a complex request
+ // message to the server with a send right made out of this receive right. The
+ // server receives this message and is instructed to destroy the send right
+ // when it is done handling the request-reply transaction. The former send
+ // right is verified to be invalid after the server runs. This test ensures
+ // that resources transferred to a server process temporarily aren’t leaked.
+ TestMachMessageServer::Options options;
+ options.client_send_complex = true;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ComplexNotDestroyed) {
+ // As in MachMessageServer.Complex, but the server is instructed not to
+ // destroy the send right. After the server runs, the send right is verified
+ // to continue to exist in the server task. The client process is then
+ // signalled by pipe that it’s safe to exit so that the send right in the
+ // server task doesn’t prematurely become a dead name. This test ensures that
+ // rights that are expected to be retained in the server task are properly
+ // retained.
+ TestMachMessageServer::Options options;
+ options.server_destroy_complex = false;
+ options.expect_server_destroyed_complex = false;
+ options.client_send_complex = true;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ComplexDestroyedInvalidArgument) {
+ // As in MachMessageServer.ComplexNotDestroyed, but the server does not return
+ // a successful code via MIG. The server is expected to destroy resources in
+ // this case, because server_destroy_complex = false is only honored when a
+ // MIG request is handled successfully or with no reply.
+ TestMachMessageServer::Options options;
+ options.server_mig_retcode = KERN_INVALID_TASK;
+ options.server_destroy_complex = false;
+ options.client_send_complex = true;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ComplexNotDestroyedNoReply) {
+ // As in MachMessageServer.ComplexNotDestroyed, but the server does not send
+ // a reply message and is expected to retain the send right in the server
+ // task.
+ TestMachMessageServer::Options options;
+ options.server_mig_retcode = MIG_NO_REPLY;
+ options.server_destroy_complex = false;
+ options.expect_server_destroyed_complex = false;
+ options.client_send_complex = true;
+ options.client_expect_reply = false;
+ options.child_wait_for_parent_pipe_late = true;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ReceiveLargeError) {
+ // The client sends a request to the server that is larger than the server is
+ // expecting. server_receive_large is kReceiveLargeError, so the request is
+ // destroyed and the server returns a MACH_RCV_TOO_LARGE error. The client
+ // does not receive a reply.
+ TestMachMessageServer::Options options;
+ options.expect_server_result = MACH_RCV_TOO_LARGE;
+ options.expect_server_transaction_count = 0;
+ options.client_send_large = true;
+ options.client_expect_reply = false;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ReceiveLargeRetry) {
+ // The client sends a request to the server that is larger than the server is
+ // initially expecting. server_receive_large is kReceiveLargeResize, so a new
+ // buffer is allocated to receive the message. The server receives the large
+ // request message, processes it, and returns a reply to the client.
+ TestMachMessageServer::Options options;
+ options.server_receive_large = MachMessageServer::kReceiveLargeResize;
+ options.client_send_large = true;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+TEST(MachMessageServer, ReceiveLargeIgnore) {
+ // The client sends a request to the server that is larger than the server is
+ // expecting. server_receive_large is kReceiveLargeIgnore, so the request is
+ // destroyed but the server does not consider this an error. The server is
+ // running in blocking mode with a timeout, and continues to wait for a
+ // message until it times out. The client does not receive a reply.
+ TestMachMessageServer::Options options;
+ options.server_receive_large = MachMessageServer::kReceiveLargeIgnore;
+ options.server_timeout_ms = 10;
+ options.expect_server_result = MACH_RCV_TIMED_OUT;
+ options.expect_server_transaction_count = 0;
+ options.client_send_large = true;
+ options.client_expect_reply = false;
+ TestMachMessageServer test_mach_message_server(options);
+ test_mach_message_server.Test();
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad

Powered by Google App Engine
This is Rietveld 408576698