OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 #include "util/mach/mach_message_server.h" |
| 16 |
| 17 #include <mach/mach.h> |
| 18 #include <string.h> |
| 19 |
| 20 #include "base/basictypes.h" |
| 21 #include "base/mac/scoped_mach_port.h" |
| 22 #include "gtest/gtest.h" |
| 23 #include "util/file/fd_io.h" |
| 24 #include "util/test/errors.h" |
| 25 #include "util/test/mac/mach_errors.h" |
| 26 #include "util/test/mac/mach_multiprocess.h" |
| 27 |
| 28 namespace { |
| 29 |
| 30 using namespace crashpad; |
| 31 using namespace crashpad::test; |
| 32 |
| 33 class TestMachMessageServer : public MachMessageServer::Interface, |
| 34 public MachMultiprocess { |
| 35 public: |
| 36 struct Options { |
| 37 // The type of reply port that the client should put in its request message. |
| 38 enum ReplyPortType { |
| 39 // The normal reply port is the client’s local port, to which it holds |
| 40 // a receive right. This allows the server to respond directly to the |
| 41 // client. The client will expect a reply. |
| 42 kReplyPortNormal, |
| 43 |
| 44 // Use MACH_PORT_NULL as the reply port, which the server should detect |
| 45 // avoid attempting to send a message to, and return success. The client |
| 46 // will not expect a reply. |
| 47 kReplyPortNull, |
| 48 |
| 49 // Make the server see the reply port as a dead name by setting the reply |
| 50 // port to a receive right and then destroying that right before the |
| 51 // server processes the request. The server should return |
| 52 // MACH_SEND_INVALID_DEST, and the client will not expect a reply. |
| 53 kReplyPortDead, |
| 54 }; |
| 55 |
| 56 Options() |
| 57 : expect_server_interface_method_called(true), |
| 58 parent_wait_for_child_pipe(false), |
| 59 server_persistent(MachMessageServer::kOneShot), |
| 60 server_nonblocking(MachMessageServer::kBlocking), |
| 61 server_timeout_ms(MACH_MSG_TIMEOUT_NONE), |
| 62 server_mig_retcode(KERN_SUCCESS), |
| 63 expect_server_result(KERN_SUCCESS), |
| 64 client_send_request_count(1), |
| 65 client_reply_port_type(kReplyPortNormal), |
| 66 child_send_all_requests_before_receiving_any_replies(false) { |
| 67 } |
| 68 |
| 69 // true if MachMessageServerFunction() is expected to be called. |
| 70 bool expect_server_interface_method_called; |
| 71 |
| 72 // true if the parent should wait for the child to write a byte to the pipe |
| 73 // as a signal that the child is ready for the parent to begin its side of |
| 74 // the test. This is used for nonblocking tests, which require that there |
| 75 // be something in the server’s queue before attempting a nonblocking |
| 76 // receive if the receive is to be successful. |
| 77 bool parent_wait_for_child_pipe; |
| 78 |
| 79 // Whether the server should run in one-shot or persistent mode. |
| 80 MachMessageServer::Persistent server_persistent; |
| 81 |
| 82 // Whether the server should run in blocking or nonblocking mode. |
| 83 MachMessageServer::Nonblocking server_nonblocking; |
| 84 |
| 85 // The server’s timeout. |
| 86 mach_msg_timeout_t server_timeout_ms; |
| 87 |
| 88 // The return code that the server returns to the client via the |
| 89 // mig_reply_error_t::RetCode field. A client would normally see this as |
| 90 // a Mach RPC return value. |
| 91 kern_return_t server_mig_retcode; |
| 92 |
| 93 // The expected return value from MachMessageServer::Run(). |
| 94 kern_return_t expect_server_result; |
| 95 |
| 96 // The number of requests that the client should send to the server. |
| 97 size_t client_send_request_count; |
| 98 |
| 99 // The type of reply port that the client should provide in its request’s |
| 100 // mach_msg_header_t::msgh_local_port, which will appear to the server as |
| 101 // mach_msg_header_t::msgh_remote_port. |
| 102 ReplyPortType client_reply_port_type; |
| 103 |
| 104 // true if the client should send all requests before attempting to receive |
| 105 // any replies from the server. This is used for the persistent nonblocking |
| 106 // test, which requires the client to fill the server’s queue before the |
| 107 // server can attempt processing it. |
| 108 bool child_send_all_requests_before_receiving_any_replies; |
| 109 }; |
| 110 |
| 111 explicit TestMachMessageServer(const Options& options) |
| 112 : MachMessageServer::Interface(), |
| 113 MachMultiprocess(), |
| 114 options_(options) { |
| 115 } |
| 116 |
| 117 // Runs the test. |
| 118 void Test() { |
| 119 EXPECT_EQ(requests_, replies_); |
| 120 uint32_t start = requests_; |
| 121 |
| 122 Run(); |
| 123 |
| 124 EXPECT_EQ(requests_, replies_); |
| 125 EXPECT_EQ(options_.client_send_request_count, requests_ - start); |
| 126 } |
| 127 |
| 128 // MachMessageServerInterface: |
| 129 |
| 130 virtual bool MachMessageServerFunction( |
| 131 mach_msg_header_t* in, |
| 132 mach_msg_header_t* out, |
| 133 bool* destroy_complex_request) override { |
| 134 *destroy_complex_request = true; |
| 135 |
| 136 EXPECT_TRUE(options_.expect_server_interface_method_called); |
| 137 if (!options_.expect_server_interface_method_called) { |
| 138 return false; |
| 139 } |
| 140 |
| 141 struct ReceiveRequestMessage : public RequestMessage { |
| 142 mach_msg_trailer_t trailer; |
| 143 }; |
| 144 |
| 145 const ReceiveRequestMessage* request = |
| 146 reinterpret_cast<ReceiveRequestMessage*>(in); |
| 147 EXPECT_EQ(static_cast<mach_msg_bits_t>( |
| 148 MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND)), |
| 149 request->header.msgh_bits); |
| 150 EXPECT_EQ(sizeof(RequestMessage), request->header.msgh_size); |
| 151 if (options_.client_reply_port_type == Options::kReplyPortNormal) { |
| 152 EXPECT_EQ(RemotePort(), request->header.msgh_remote_port); |
| 153 } |
| 154 EXPECT_EQ(LocalPort(), request->header.msgh_local_port); |
| 155 EXPECT_EQ(kRequestMessageId, request->header.msgh_id); |
| 156 EXPECT_EQ(0, memcmp(&request->ndr, &NDR_record, sizeof(NDR_record))); |
| 157 EXPECT_EQ(requests_, request->number); |
| 158 EXPECT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0), |
| 159 request->trailer.msgh_trailer_type); |
| 160 EXPECT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE, |
| 161 request->trailer.msgh_trailer_size); |
| 162 |
| 163 ++requests_; |
| 164 |
| 165 ReplyMessage* reply = reinterpret_cast<ReplyMessage*>(out); |
| 166 reply->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); |
| 167 reply->Head.msgh_size = sizeof(*reply); |
| 168 reply->Head.msgh_remote_port = request->header.msgh_remote_port; |
| 169 reply->Head.msgh_local_port = MACH_PORT_NULL; |
| 170 reply->Head.msgh_id = kReplyMessageId; |
| 171 reply->NDR = NDR_record; |
| 172 reply->RetCode = options_.server_mig_retcode; |
| 173 reply->number = replies_++; |
| 174 |
| 175 return true; |
| 176 } |
| 177 |
| 178 virtual mach_msg_size_t MachMessageServerRequestSize() override { |
| 179 return sizeof(RequestMessage); |
| 180 } |
| 181 |
| 182 virtual mach_msg_size_t MachMessageServerReplySize() override { |
| 183 return sizeof(ReplyMessage); |
| 184 } |
| 185 |
| 186 private: |
| 187 struct RequestMessage { |
| 188 mach_msg_header_t header; |
| 189 NDR_record_t ndr; |
| 190 uint32_t number; |
| 191 }; |
| 192 |
| 193 struct ReplyMessage : public mig_reply_error_t { |
| 194 uint32_t number; |
| 195 }; |
| 196 |
| 197 // MachMultiprocess: |
| 198 |
| 199 virtual void MachMultiprocessParent() override { |
| 200 if (options_.parent_wait_for_child_pipe) { |
| 201 // Wait until the child is done sending what it’s going to send. |
| 202 char c; |
| 203 ssize_t rv = ReadFD(ReadPipeFD(), &c, 1); |
| 204 EXPECT_EQ(1, rv) << ErrnoMessage("read"); |
| 205 EXPECT_EQ('\0', c); |
| 206 } |
| 207 |
| 208 kern_return_t kr; |
| 209 ASSERT_EQ(options_.expect_server_result, |
| 210 (kr = MachMessageServer::Run(this, |
| 211 LocalPort(), |
| 212 MACH_MSG_OPTION_NONE, |
| 213 options_.server_persistent, |
| 214 options_.server_nonblocking, |
| 215 options_.server_timeout_ms))) |
| 216 << MachErrorMessage(kr, "MachMessageServer"); |
| 217 } |
| 218 |
| 219 virtual void MachMultiprocessChild() override { |
| 220 for (size_t index = 0; |
| 221 index < options_.client_send_request_count; |
| 222 ++index) { |
| 223 if (options_.child_send_all_requests_before_receiving_any_replies) { |
| 224 // For this test, all of the messages need to go into the queue before |
| 225 // the parent is allowed to start processing them. Don’t attempt to |
| 226 // process replies before all of the requests are sent, because the |
| 227 // server won’t have sent any replies until all of the requests are in |
| 228 // its queue. |
| 229 ChildSendRequest(); |
| 230 } else { |
| 231 ChildSendRequestAndWaitForReply(); |
| 232 } |
| 233 if (testing::Test::HasFatalFailure()) { |
| 234 return; |
| 235 } |
| 236 } |
| 237 |
| 238 if (options_.parent_wait_for_child_pipe && |
| 239 options_.child_send_all_requests_before_receiving_any_replies) { |
| 240 // Now that all of the requests have been sent, let the parent know that |
| 241 // it’s safe to begin processing them, and then wait for the replies. |
| 242 ChildNotifyParentViaPipe(); |
| 243 if (testing::Test::HasFatalFailure()) { |
| 244 return; |
| 245 } |
| 246 |
| 247 for (size_t index = 0; |
| 248 index < options_.client_send_request_count; |
| 249 ++index) { |
| 250 ChildWaitForReply(); |
| 251 if (testing::Test::HasFatalFailure()) { |
| 252 return; |
| 253 } |
| 254 } |
| 255 } |
| 256 } |
| 257 |
| 258 // In the child process, sends a request message to the server. |
| 259 void ChildSendRequest() { |
| 260 // local_receive_port_owner will the receive right that is created in this |
| 261 // scope and intended to be destroyed when leaving this scope, after it has |
| 262 // been carried in a Mach message. |
| 263 base::mac::ScopedMachReceiveRight local_receive_port_owner; |
| 264 |
| 265 RequestMessage request = {}; |
| 266 request.header.msgh_bits = |
| 267 MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); |
| 268 request.header.msgh_size = sizeof(request); |
| 269 request.header.msgh_remote_port = RemotePort(); |
| 270 kern_return_t kr; |
| 271 switch (options_.client_reply_port_type) { |
| 272 case Options::kReplyPortNormal: |
| 273 request.header.msgh_local_port = LocalPort(); |
| 274 break; |
| 275 case Options::kReplyPortNull: |
| 276 request.header.msgh_local_port = MACH_PORT_NULL; |
| 277 break; |
| 278 case Options::kReplyPortDead: { |
| 279 // Use a newly-allocated receive right that will be destroyed when this |
| 280 // method returns. A send right will be made from this receive right and |
| 281 // carried in the request message to the server. By the time the server |
| 282 // looks at the right, it will have become a dead name. |
| 283 kr = mach_port_allocate(mach_task_self(), |
| 284 MACH_PORT_RIGHT_RECEIVE, |
| 285 &request.header.msgh_local_port); |
| 286 ASSERT_EQ(KERN_SUCCESS, kr) |
| 287 << MachErrorMessage(kr, "mach_port_allocate"); |
| 288 local_receive_port_owner.reset(request.header.msgh_local_port); |
| 289 break; |
| 290 } |
| 291 } |
| 292 request.header.msgh_id = kRequestMessageId; |
| 293 request.number = requests_++; |
| 294 request.ndr = NDR_record; |
| 295 |
| 296 kr = mach_msg(&request.header, |
| 297 MACH_SEND_MSG | MACH_SEND_TIMEOUT, |
| 298 request.header.msgh_size, |
| 299 0, |
| 300 MACH_PORT_NULL, |
| 301 MACH_MSG_TIMEOUT_NONE, |
| 302 MACH_PORT_NULL); |
| 303 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); |
| 304 } |
| 305 |
| 306 // In the child process, waits for a reply message from the server. |
| 307 void ChildWaitForReply() { |
| 308 if (options_.client_reply_port_type != Options::kReplyPortNormal) { |
| 309 // The client shouldn’t expect a reply when it didn’t send a good reply |
| 310 // port with its request. |
| 311 return; |
| 312 } |
| 313 |
| 314 struct ReceiveReplyMessage : public ReplyMessage { |
| 315 mach_msg_trailer_t trailer; |
| 316 }; |
| 317 |
| 318 ReceiveReplyMessage reply = {}; |
| 319 kern_return_t kr = mach_msg(&reply.Head, |
| 320 MACH_RCV_MSG, |
| 321 0, |
| 322 sizeof(reply), |
| 323 LocalPort(), |
| 324 MACH_MSG_TIMEOUT_NONE, |
| 325 MACH_PORT_NULL); |
| 326 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); |
| 327 |
| 328 ASSERT_EQ(static_cast<mach_msg_bits_t>( |
| 329 MACH_MSGH_BITS(0, MACH_MSG_TYPE_MOVE_SEND)), reply.Head.msgh_bits); |
| 330 ASSERT_EQ(sizeof(ReplyMessage), reply.Head.msgh_size); |
| 331 ASSERT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), |
| 332 reply.Head.msgh_remote_port); |
| 333 ASSERT_EQ(LocalPort(), reply.Head.msgh_local_port); |
| 334 ASSERT_EQ(kReplyMessageId, reply.Head.msgh_id); |
| 335 ASSERT_EQ(0, memcmp(&reply.NDR, &NDR_record, sizeof(NDR_record))); |
| 336 ASSERT_EQ(options_.server_mig_retcode, reply.RetCode); |
| 337 ASSERT_EQ(replies_, reply.number); |
| 338 ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0), |
| 339 reply.trailer.msgh_trailer_type); |
| 340 ASSERT_EQ(MACH_MSG_TRAILER_MINIMUM_SIZE, reply.trailer.msgh_trailer_size); |
| 341 |
| 342 ++replies_; |
| 343 } |
| 344 |
| 345 // For test types where the child needs to notify the server in the parent |
| 346 // that the child is ready, this method will send a byte via the POSIX pipe. |
| 347 // The parent will be waiting in a read() on this pipe, and will proceed to |
| 348 // running MachMessageServer() once it’s received. |
| 349 void ChildNotifyParentViaPipe() { |
| 350 char c = '\0'; |
| 351 ssize_t rv = WriteFD(WritePipeFD(), &c, 1); |
| 352 ASSERT_EQ(1, rv) << ErrnoMessage("write"); |
| 353 } |
| 354 |
| 355 // In the child process, sends a request message to the server and then |
| 356 // receives a reply message. |
| 357 void ChildSendRequestAndWaitForReply() { |
| 358 ChildSendRequest(); |
| 359 if (testing::Test::HasFatalFailure()) { |
| 360 return; |
| 361 } |
| 362 |
| 363 if (options_.parent_wait_for_child_pipe && |
| 364 !options_.child_send_all_requests_before_receiving_any_replies) { |
| 365 // The parent is waiting to read a byte to indicate that the message has |
| 366 // been placed in the queue. |
| 367 ChildNotifyParentViaPipe(); |
| 368 if (testing::Test::HasFatalFailure()) { |
| 369 return; |
| 370 } |
| 371 } |
| 372 |
| 373 ChildWaitForReply(); |
| 374 } |
| 375 |
| 376 const Options& options_; |
| 377 |
| 378 static uint32_t requests_; |
| 379 static uint32_t replies_; |
| 380 |
| 381 static const mach_msg_id_t kRequestMessageId = 16237; |
| 382 static const mach_msg_id_t kReplyMessageId = kRequestMessageId + 100; |
| 383 |
| 384 DISALLOW_COPY_AND_ASSIGN(TestMachMessageServer); |
| 385 }; |
| 386 |
| 387 uint32_t TestMachMessageServer::requests_; |
| 388 uint32_t TestMachMessageServer::replies_; |
| 389 const mach_msg_id_t TestMachMessageServer::kRequestMessageId; |
| 390 const mach_msg_id_t TestMachMessageServer::kReplyMessageId; |
| 391 |
| 392 TEST(MachMessageServer, Basic) { |
| 393 // The client sends one message to the server, which will wait indefinitely in |
| 394 // blocking mode for it. |
| 395 TestMachMessageServer::Options options; |
| 396 TestMachMessageServer test_mach_message_server(options); |
| 397 test_mach_message_server.Test(); |
| 398 } |
| 399 |
| 400 TEST(MachMessageServer, NonblockingNoMessage) { |
| 401 // The server waits in nonblocking mode and the client sends nothing, so the |
| 402 // server should return immediately without processing any message. |
| 403 TestMachMessageServer::Options options; |
| 404 options.expect_server_interface_method_called = false; |
| 405 options.server_nonblocking = MachMessageServer::kNonblocking; |
| 406 options.expect_server_result = MACH_RCV_TIMED_OUT; |
| 407 options.client_send_request_count = 0; |
| 408 TestMachMessageServer test_mach_message_server(options); |
| 409 test_mach_message_server.Test(); |
| 410 } |
| 411 |
| 412 TEST(MachMessageServer, TimeoutNoMessage) { |
| 413 // The server waits in blocking mode for one message, but with a timeout. The |
| 414 // client sends no message, so the server returns after the timeout. |
| 415 TestMachMessageServer::Options options; |
| 416 options.expect_server_interface_method_called = false; |
| 417 options.server_timeout_ms = 10; |
| 418 options.expect_server_result = MACH_RCV_TIMED_OUT; |
| 419 options.client_send_request_count = 0; |
| 420 TestMachMessageServer test_mach_message_server(options); |
| 421 test_mach_message_server.Test(); |
| 422 } |
| 423 |
| 424 TEST(MachMessageServer, Nonblocking) { |
| 425 // The client sends one message to the server and then signals the server that |
| 426 // it’s safe to start waiting for it in nonblocking mode. The message is in |
| 427 // the server’s queue, so it’s able to receive it when it begins listening in |
| 428 // nonblocking mode. |
| 429 TestMachMessageServer::Options options; |
| 430 options.parent_wait_for_child_pipe = true; |
| 431 options.server_nonblocking = MachMessageServer::kNonblocking; |
| 432 TestMachMessageServer test_mach_message_server(options); |
| 433 test_mach_message_server.Test(); |
| 434 } |
| 435 |
| 436 TEST(MachMessageServer, Timeout) { |
| 437 // The client sends one message to the server, which will wait in blocking |
| 438 // mode for it up to a specific timeout. |
| 439 TestMachMessageServer::Options options; |
| 440 options.server_timeout_ms = 10; |
| 441 TestMachMessageServer test_mach_message_server(options); |
| 442 test_mach_message_server.Test(); |
| 443 } |
| 444 |
| 445 TEST(MachMessageServer, PersistentTenMessages) { |
| 446 // The server waits for as many messages as it can receive in blocking mode |
| 447 // with a timeout. The client sends several messages, and the server processes |
| 448 // them all. |
| 449 TestMachMessageServer::Options options; |
| 450 options.server_persistent = MachMessageServer::kPersistent; |
| 451 options.server_timeout_ms = 10; |
| 452 options.expect_server_result = MACH_RCV_TIMED_OUT; |
| 453 options.client_send_request_count = 10; |
| 454 TestMachMessageServer test_mach_message_server(options); |
| 455 test_mach_message_server.Test(); |
| 456 } |
| 457 |
| 458 TEST(MachMessageServer, PersistentNonblockingFourMessages) { |
| 459 // The client sends several messages to the server and then signals the server |
| 460 // that it’s safe to start waiting for them in nonblocking mode. The server |
| 461 // then listens for them in nonblocking persistent mode, and receives all of |
| 462 // them because they’ve been queued up. The client doesn’t wait for the |
| 463 // replies until after it’s put all of its requests into the server’s queue. |
| 464 // |
| 465 // This test is sensitive to the length of the IPC queue limit. Mach ports |
| 466 // normally have a queue length limit of MACH_PORT_QLIMIT_DEFAULT (which is |
| 467 // MACH_PORT_QLIMIT_BASIC, or 5). The number of messages sent for this test |
| 468 // must be below this, because the server does not begin dequeueing request |
| 469 // messages until the client has finished sending them. |
| 470 TestMachMessageServer::Options options; |
| 471 options.parent_wait_for_child_pipe = true; |
| 472 options.server_persistent = MachMessageServer::kPersistent; |
| 473 options.server_nonblocking = MachMessageServer::kNonblocking; |
| 474 options.expect_server_result = MACH_RCV_TIMED_OUT; |
| 475 options.client_send_request_count = 4; |
| 476 options.child_send_all_requests_before_receiving_any_replies = true; |
| 477 TestMachMessageServer test_mach_message_server(options); |
| 478 test_mach_message_server.Test(); |
| 479 } |
| 480 |
| 481 TEST(MachMessageServer, ReturnCodeInvalidArgument) { |
| 482 // This tests that the mig_reply_error_t::RetCode field is properly returned |
| 483 // to the client. |
| 484 TestMachMessageServer::Options options; |
| 485 TestMachMessageServer test_mach_message_server(options); |
| 486 options.server_mig_retcode = KERN_INVALID_ARGUMENT; |
| 487 test_mach_message_server.Test(); |
| 488 } |
| 489 |
| 490 TEST(MachMessageServer, ReplyPortNull) { |
| 491 // The client sets its reply port to MACH_PORT_NULL. The server should see |
| 492 // this and avoid sending a message to the null port. No reply message is |
| 493 // sent and the server returns success. |
| 494 TestMachMessageServer::Options options; |
| 495 TestMachMessageServer test_mach_message_server(options); |
| 496 options.client_reply_port_type = |
| 497 TestMachMessageServer::Options::kReplyPortNull; |
| 498 test_mach_message_server.Test(); |
| 499 } |
| 500 |
| 501 TEST(MachMessageServer, ReplyPortDead) { |
| 502 // The client allocates a new port and uses it as the reply port in its |
| 503 // request message, and then deallocates its receive right to that port. It |
| 504 // then signals the server to process the request message. The server’s view |
| 505 // of the port is that it is a dead name. The server function will return |
| 506 // MACH_SEND_INVALID_DEST because it’s not possible to send a message to a |
| 507 // dead name. |
| 508 TestMachMessageServer::Options options; |
| 509 TestMachMessageServer test_mach_message_server(options); |
| 510 options.parent_wait_for_child_pipe = true; |
| 511 options.expect_server_result = MACH_SEND_INVALID_DEST; |
| 512 options.client_reply_port_type = |
| 513 TestMachMessageServer::Options::kReplyPortDead; |
| 514 test_mach_message_server.Test(); |
| 515 } |
| 516 |
| 517 } // namespace |
OLD | NEW |