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

Side by Side Diff: util/mach/notify_server_test.cc

Issue 804633002: Add NotifyServer and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Created 6 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 unified diff | Download patch
« no previous file with comments | « util/mach/notify_server.cc ('k') | util/util.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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/notify_server.h"
16
17 #include "base/compiler_specific.h"
18 #include "base/mac/scoped_mach_port.h"
19 #include "gmock/gmock.h"
20 #include "gtest/gtest.h"
21 #include "util/mach/mach_extensions.h"
22 #include "util/mach/mach_message.h"
23 #include "util/mach/mach_message_server.h"
24 #include "util/test/mac/mach_errors.h"
25
26 namespace crashpad {
27 namespace test {
28 namespace {
29
30 using testing::AllOf;
31 using testing::Eq;
32 using testing::Invoke;
33 using testing::Ne;
34 using testing::Pointee;
35 using testing::ResultOf;
36 using testing::Return;
37 using testing::SetArgPointee;
38 using testing::StrictMock;
39 using testing::WithArg;
40
41 //! \brief Allocates and returns a new receive right.
42 //!
43 //! \return The new receive right. On failure, `MACH_PORT_NULL` with a gtest
44 //! failure added.
45 mach_port_t NewReceiveRight() {
46 mach_port_t receive_right;
47 kern_return_t kr = mach_port_allocate(
48 mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &receive_right);
49 if (kr != KERN_SUCCESS) {
50 EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate");
51 return MACH_PORT_NULL;
52 }
53 return receive_right;
54 }
55
56 //! \brief Adds a send right to an existing receive right.
57 //!
58 //! \param[in] receive_right The receive right to add a send right to.
59 //!
60 //! \return The send right, which will have the same name as the receive right.
61 //! On failure, `MACH_PORT_NULL` with a gtest failure added.
62 mach_port_t SendRightFromReceiveRight(mach_port_t receive_right) {
63 kern_return_t kr = mach_port_insert_right(
64 mach_task_self(), receive_right, receive_right, MACH_MSG_TYPE_MAKE_SEND);
65 if (kr != KERN_SUCCESS) {
66 EXPECT_EQ(KERN_SUCCESS, kr)
67 << MachErrorMessage(kr, "mach_port_insert_right");
68 return MACH_PORT_NULL;
69 }
70
71 return receive_right;
72 }
73
74 //! \brief Extracts a send-once right from a receive right.
75 //!
76 //! \param[in] receive_right The receive right to make a send-once right from.
77 //!
78 //! \return The send-once right. On failure, `MACH_PORT_NULL` with a gtest
79 //! failure added.
80 mach_port_t SendOnceRightFromReceiveRight(mach_port_t receive_right) {
81 mach_port_t send_once_right;
82 mach_msg_type_name_t acquired_type;
83 kern_return_t kr = mach_port_extract_right(mach_task_self(),
84 receive_right,
85 MACH_MSG_TYPE_MAKE_SEND_ONCE,
86 &send_once_right,
87 &acquired_type);
88 if (kr != KERN_SUCCESS) {
89 EXPECT_EQ(KERN_SUCCESS, kr)
90 << MachErrorMessage(kr, "mach_port_extract_right");
91 return MACH_PORT_NULL;
92 }
93
94 EXPECT_EQ(implicit_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND_ONCE),
95 acquired_type);
96
97 return send_once_right;
98 }
99
100 //! \brief Deallocates a Mach port by calling `mach_port_deallocate()`.
101 //!
102 //! This function exists to adapt `mach_port_deallocate()` to a function that
103 //! accepts a single argument and has no return value. It can be used with the
104 //! testing::Invoke() gmock action.
105 //!
106 //! On failure, a gtest failure will be added.
107 void MachPortDeallocate(mach_port_t port) {
108 kern_return_t kr = mach_port_deallocate(mach_task_self(), port);
109 EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_deallocate");
110 }
111
112 //! \brief Determines whether a specific right is held for a Mach port.
113 //!
114 //! \param[in] port The port to check for a right.
115 //! \param[in] right The right to check for.
116 //!
117 //! \return `true` if \a port has \a right, `false` otherwise. On faliure,
118 //! `false` with a gtest failure added.
119 bool IsRight(mach_port_t port, mach_port_type_t right) {
120 mach_port_type_t type;
121 kern_return_t kr = mach_port_type(mach_task_self(), port, &type);
122 if (kr != KERN_SUCCESS) {
123 EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_type");
124 return false;
125 }
126
127 return type & right;
128 }
129
130 //! \brief Determines whether a receive right is held for a Mach port.
131 //!
132 //! This is a special single-argument form of IsRight() for ease of use in a
133 //! gmock matcher.
134 //!
135 //! \param[in] port The port to check for a receive right.
136 //!
137 //! \return `true` if a receive right is held, `false` otherwise. On faliure,
138 //! `false` with a gtest failure added.
139 bool IsReceiveRight(mach_port_t port) {
140 return IsRight(port, MACH_PORT_TYPE_RECEIVE);
141 }
142
143 //! \brief Returns the user reference count for port rights.
144 //!
145 //! \param[in] port The port whose user reference count should be returned.
146 //! \param[in] right The port right to return the user reference count for.
147 //!
148 //! \return The user reference count for the specified port and right. On
149 //! failure, `-1` with a gtest failure added.
150 mach_port_urefs_t RightRefCount(mach_port_t port, mach_port_right_t right) {
151 mach_port_urefs_t refs;
152 kern_return_t kr = mach_port_get_refs(mach_task_self(), port, right, &refs);
153 if (kr != KERN_SUCCESS) {
154 EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_get_refs");
155 return -1;
156 }
157
158 return refs;
159 }
160
161 //! \brief Returns the user reference count for a port’s dead-name rights.
162 //!
163 //! This is a special single-argument form of RightRefCount() for ease of use in
164 //! a gmock matcher.
165 //!
166 //! \param[in] port The port whose dead-name user reference count should be
167 //! returned.
168 //!
169 //! \return The user reference count for the port’s dead-name rights. On
170 //! failure, `-1` with a gtest failure added.
171 mach_port_urefs_t DeadNameRightRefCount(mach_port_t port) {
172 return RightRefCount(port, MACH_PORT_RIGHT_DEAD_NAME);
173 }
174
175 class NotifyServerTestBase : public testing::Test,
176 public NotifyServer::Interface {
177 public:
178 // NotifyServer::Interface:
179
180 MOCK_METHOD3(DoMachNotifyPortDeleted,
181 kern_return_t(notify_port_t notify,
182 mach_port_name_t name,
183 const mach_msg_trailer_t* trailer));
184
185 MOCK_METHOD4(DoMachNotifyPortDestroyed,
186 kern_return_t(notify_port_t notify,
187 mach_port_t rights,
188 const mach_msg_trailer_t* trailer,
189 bool* destroy_request));
190
191 MOCK_METHOD3(DoMachNotifyNoSenders,
192 kern_return_t(notify_port_t notify,
193 mach_port_mscount_t mscount,
194 const mach_msg_trailer_t* trailer));
195
196 MOCK_METHOD2(DoMachNotifySendOnce,
197 kern_return_t(notify_port_t notify,
198 const mach_msg_trailer_t* trailer));
199
200 MOCK_METHOD3(DoMachNotifyDeadName,
201 kern_return_t(notify_port_t notify,
202 mach_port_name_t name,
203 const mach_msg_trailer_t* trailer));
204
205 protected:
206 NotifyServerTestBase() : testing::Test(), NotifyServer::Interface() {}
207
208 ~NotifyServerTestBase() override {}
209
210 //! \brief Requests a Mach port notification.
211 //!
212 //! \a name, \a variant, and \a sync are passed as-is to
213 //! `mach_port_request_notification()`. The notification will be sent to a
214 //! send-once right made from ServerPort(). Any previous send right for the
215 //! notification will be deallocated.
216 //!
217 //! \return `true` on success, `false` on failure with a gtest failure added.
218 bool RequestMachPortNotification(mach_port_t name,
219 mach_msg_id_t variant,
220 mach_port_mscount_t sync) {
221 mach_port_t previous;
222 kern_return_t kr =
223 mach_port_request_notification(mach_task_self(),
224 name,
225 variant,
226 sync,
227 ServerPort(),
228 MACH_MSG_TYPE_MAKE_SEND_ONCE,
229 &previous);
230 if (kr != KERN_SUCCESS) {
231 EXPECT_EQ(KERN_SUCCESS, kr)
232 << MachErrorMessage(kr, "mach_port_request_notification");
233 return false;
234 }
235
236 base::mac::ScopedMachSendRight previous_owner(previous);
237 EXPECT_EQ(kMachPortNull, previous);
238
239 return true;
240 }
241
242 //! \brief Runs a NotifyServer Mach message server.
243 //!
244 //! The server will listen on ServerPort() in persistent nonblocking mode, and
245 //! dispatch received messages to the appropriate NotifyServer::Interface
246 //! method. gmock expectations check that the proper method, if any, is called
247 //! exactly once, and that no undesired methods are called.
248 //!
249 //! MachMessageServer::Run() is expected to return `MACH_RCV_TIMED_OUT`,
250 //! because it runs in persistent nonblocking mode. If it returns anything
251 //! else, a gtest assertion is added.
252 void RunServer() {
253 NotifyServer notify_server(this);
254 mach_msg_return_t mr =
255 MachMessageServer::Run(&notify_server,
256 ServerPort(),
257 MACH_MSG_OPTION_NONE,
258 MachMessageServer::kPersistent,
259 MachMessageServer::kReceiveLargeError,
260 kMachMessageTimeoutNonblocking);
261 ASSERT_EQ(MACH_RCV_TIMED_OUT, mr)
262 << MachErrorMessage(mr, "MachMessageServer::Run");
263 }
264
265 //! \brief Returns the receive right to be used for the server.
266 //!
267 //! This receive right is created lazily on a per-test basis. It is destroyed
268 //! by TearDown() at the conclusion of each test.
269 //!
270 //! \return The server port receive right, creating it if one has not yet been
271 //! established for the current test. On failure, returns `MACH_PORT_NULL`
272 //! with a gtest failure added.
273 mach_port_t ServerPort() {
274 if (!server_port_) {
275 server_port_.reset(NewReceiveRight());
276 }
277
278 return server_port_;
279 }
280
281 // testing::Test:
282 void TearDown() override {
283 server_port_.reset();
284 }
285
286 private:
287 base::mac::ScopedMachReceiveRight server_port_;
288
289 DISALLOW_COPY_AND_ASSIGN(NotifyServerTestBase);
290 };
291
292 using NotifyServerTest = StrictMock<NotifyServerTestBase>;
293
294 TEST_F(NotifyServerTest, Basic) {
295 NotifyServer server(this);
296
297 std::set<mach_msg_id_t> expect_request_ids;
298 expect_request_ids.insert(MACH_NOTIFY_PORT_DELETED);
299 expect_request_ids.insert(MACH_NOTIFY_PORT_DESTROYED);
300 expect_request_ids.insert(MACH_NOTIFY_NO_SENDERS);
301 expect_request_ids.insert(MACH_NOTIFY_SEND_ONCE);
302 expect_request_ids.insert(MACH_NOTIFY_DEAD_NAME);
303 EXPECT_EQ(expect_request_ids, server.MachMessageServerRequestIDs());
304
305 // The port-destroyed notification is the largest request message in the
306 // subsystem. <mach/notify.h> defines the same structure, but with a basic
307 // trailer, so use offsetof to get the size of the basic structure without any
308 // trailer.
309 EXPECT_EQ(offsetof(mach_port_destroyed_notification_t, trailer),
310 server.MachMessageServerRequestSize());
311
312 mig_reply_error_t reply;
313 EXPECT_EQ(sizeof(reply), server.MachMessageServerReplySize());
314 }
315
316 // When no notifications are requested, nothing should happen.
317 TEST_F(NotifyServerTest, NoNotification) {
318 RunServer();
319 }
320
321 // When a send-once right with a dead-name notification request is deallocated,
322 // a port-deleted notification should be generated.
323 TEST_F(NotifyServerTest, MachNotifyPortDeleted) {
324 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
325 ASSERT_NE(kMachPortNull, receive_right);
326
327 base::mac::ScopedMachSendRight send_once_right(
328 SendOnceRightFromReceiveRight(receive_right));
329 ASSERT_NE(kMachPortNull, send_once_right);
330
331 ASSERT_TRUE(RequestMachPortNotification(
332 send_once_right, MACH_NOTIFY_DEAD_NAME, 0));
333
334 EXPECT_CALL(*this, DoMachNotifyPortDeleted(ServerPort(),
335 send_once_right.get(),
336 Ne(nullptr)))
337 .WillOnce(Return(MIG_NO_REPLY))
338 .RetiresOnSaturation();
339
340 send_once_right.reset();
341
342 RunServer();
343 }
344
345 // When a receive right with a port-destroyed notification request is destroyed,
346 // a port-destroyed notification should be generated.
347 TEST_F(NotifyServerTest, MachNotifyPortDestroyed) {
348 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
349 ASSERT_NE(kMachPortNull, receive_right);
350
351 ASSERT_TRUE(RequestMachPortNotification(
352 receive_right, MACH_NOTIFY_PORT_DESTROYED, 0));
353
354 EXPECT_CALL(*this, DoMachNotifyPortDestroyed(ServerPort(),
355 ResultOf(IsReceiveRight, true),
356 Ne(nullptr),
357 Pointee(Eq(false))))
358 .WillOnce(DoAll(SetArgPointee<3>(true), Return(MIG_NO_REPLY)))
359 .RetiresOnSaturation();
360
361 receive_right.reset();
362
363 RunServer();
364 }
365
366 // When a receive right with a port-destroyed notification request is not
367 // destroyed, no port-destroyed notification should be generated.
368 TEST_F(NotifyServerTest, MachNotifyPortDestroyed_NoNotification) {
369 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
370 ASSERT_NE(kMachPortNull, receive_right);
371
372 ASSERT_TRUE(RequestMachPortNotification(
373 receive_right, MACH_NOTIFY_PORT_DESTROYED, 0));
374
375 RunServer();
376 }
377
378 // When a no-senders notification request is registered for a receive right with
379 // no senders, a no-senders notification should be generated.
380 TEST_F(NotifyServerTest, MachNotifyNoSenders_NoSendRight) {
381 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
382 ASSERT_NE(kMachPortNull, receive_right);
383
384 ASSERT_TRUE(RequestMachPortNotification(
385 receive_right, MACH_NOTIFY_NO_SENDERS, 0));
386
387 EXPECT_CALL(*this, DoMachNotifyNoSenders(ServerPort(), 0, Ne(nullptr)))
388 .WillOnce(Return(MIG_NO_REPLY))
389 .RetiresOnSaturation();
390
391 RunServer();
392 }
393
394 // When the last send right corresponding to a receive right with a no-senders
395 // notification request is deallocated, a no-senders notification should be
396 // generated.
397 TEST_F(NotifyServerTest, MachNotifyNoSenders_SendRightDeallocated) {
398 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
399 ASSERT_NE(kMachPortNull, receive_right);
400
401 base::mac::ScopedMachSendRight send_right(
402 SendRightFromReceiveRight(receive_right));
403 ASSERT_NE(kMachPortNull, send_right);
404
405 ASSERT_TRUE(RequestMachPortNotification(
406 receive_right, MACH_NOTIFY_NO_SENDERS, 1));
407
408 EXPECT_CALL(*this, DoMachNotifyNoSenders(ServerPort(), 1, Ne(nullptr)))
409 .WillOnce(Return(MIG_NO_REPLY))
410 .RetiresOnSaturation();
411
412 send_right.reset();
413
414 RunServer();
415 }
416
417 // When the a receive right with a no-senders notification request never loses
418 // all senders, no no-senders notification should be generated.
419 TEST_F(NotifyServerTest, MachNotifyNoSenders_NoNotification) {
420 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
421 ASSERT_NE(kMachPortNull, receive_right);
422
423 base::mac::ScopedMachSendRight send_right_0(
424 SendRightFromReceiveRight(receive_right));
425 ASSERT_NE(kMachPortNull, send_right_0);
426
427 base::mac::ScopedMachSendRight send_right_1(
428 SendRightFromReceiveRight(receive_right));
429 ASSERT_NE(kMachPortNull, send_right_1);
430
431 ASSERT_TRUE(RequestMachPortNotification(
432 receive_right, MACH_NOTIFY_NO_SENDERS, 1));
433
434 send_right_1.reset();
435
436 RunServer();
437
438 EXPECT_EQ(1u, RightRefCount(receive_right, MACH_PORT_RIGHT_RECEIVE));
439 EXPECT_EQ(1u, RightRefCount(receive_right, MACH_PORT_RIGHT_SEND));
440 }
441
442 // When a send-once right is deallocated without being used, a send-once
443 // notification notification should be sent via the send-once right.
444 TEST_F(NotifyServerTest, MachNotifySendOnce_ExplicitDeallocation) {
445 base::mac::ScopedMachSendRight send_once_right(
446 SendOnceRightFromReceiveRight(ServerPort()));
447 ASSERT_NE(kMachPortNull, send_once_right);
448
449 EXPECT_CALL(*this, DoMachNotifySendOnce(ServerPort(), Ne(nullptr)))
450 .WillOnce(Return(MIG_NO_REPLY))
451 .RetiresOnSaturation();
452
453 send_once_right.reset();
454
455 RunServer();
456 }
457
458 // When a send-once right is sent to a receiver that never dequeues the message,
459 // the send-once right is destroyed, and a send-once notification should appear
460 // on the reply port.
461 TEST_F(NotifyServerTest, MachNotifySendOnce_ImplicitDeallocation) {
462 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
463 ASSERT_NE(kMachPortNull, receive_right);
464
465 mach_msg_empty_send_t message = {};
466 message.header.msgh_bits =
467 MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
468 message.header.msgh_size = sizeof(message);
469 message.header.msgh_remote_port = receive_right;
470 message.header.msgh_local_port = ServerPort();
471 mach_msg_return_t mr = mach_msg(&message.header,
472 MACH_SEND_MSG | MACH_SEND_TIMEOUT,
473 message.header.msgh_size,
474 0,
475 MACH_PORT_NULL,
476 0,
477 MACH_PORT_NULL);
478 ASSERT_EQ(MACH_MSG_SUCCESS, mr) << MachErrorMessage(mr, "mach_msg");
479
480 EXPECT_CALL(*this, DoMachNotifySendOnce(ServerPort(), Ne(nullptr)))
481 .WillOnce(Return(MIG_NO_REPLY))
482 .RetiresOnSaturation();
483
484 receive_right.reset();
485
486 RunServer();
487 }
488
489 // When the receive right corresponding to a send-once right with a dead-name
490 // notification request is destroyed, a dead-name notification should be
491 // generated.
492 TEST_F(NotifyServerTest, MachNotifyDeadName) {
493 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
494 ASSERT_NE(kMachPortNull, receive_right);
495
496 base::mac::ScopedMachSendRight send_once_right(
497 SendOnceRightFromReceiveRight(receive_right));
498 ASSERT_NE(kMachPortNull, send_once_right);
499
500 ASSERT_TRUE(RequestMachPortNotification(
501 send_once_right, MACH_NOTIFY_DEAD_NAME, 0));
502
503 // send_once_right becomes a dead name with the send-once right’s original
504 // user reference count of 1, but the dead-name notification increments the
505 // dead-name reference count, so it becomes 2. Take care to deallocate that
506 // reference. The original reference is managed by send_once_right_owner.
507 EXPECT_CALL(*this,
508 DoMachNotifyDeadName(ServerPort(),
509 AllOf(send_once_right.get(),
510 ResultOf(DeadNameRightRefCount, 2)),
511 Ne(nullptr)))
512 .WillOnce(DoAll(WithArg<1>(Invoke(MachPortDeallocate)),
513 Return(MIG_NO_REPLY)))
514 .RetiresOnSaturation();
515
516 receive_right.reset();
517
518 RunServer();
519
520 EXPECT_TRUE(IsRight(send_once_right, MACH_PORT_TYPE_DEAD_NAME));
521
522 EXPECT_EQ(0u, RightRefCount(send_once_right, MACH_PORT_RIGHT_SEND_ONCE));
523 EXPECT_EQ(1u, DeadNameRightRefCount(send_once_right));
524 }
525
526 // When the receive right corresponding to a send-once right with a dead-name
527 // notification request is not destroyed, no dead-name notification should be
528 // generated.
529 TEST_F(NotifyServerTest, MachNotifyDeadName_NoNotification) {
530 base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
531 ASSERT_NE(kMachPortNull, receive_right);
532
533 base::mac::ScopedMachSendRight send_once_right(
534 SendOnceRightFromReceiveRight(receive_right));
535 ASSERT_NE(kMachPortNull, send_once_right);
536
537 ASSERT_TRUE(RequestMachPortNotification(
538 send_once_right, MACH_NOTIFY_DEAD_NAME, 0));
539
540 RunServer();
541
542 EXPECT_FALSE(IsRight(send_once_right, MACH_PORT_TYPE_DEAD_NAME));
543
544 EXPECT_EQ(1u, RightRefCount(send_once_right, MACH_PORT_RIGHT_SEND_ONCE));
545 EXPECT_EQ(0u, DeadNameRightRefCount(send_once_right));
546 }
547
548 } // namespace
549 } // namespace test
550 } // namespace crashpad
OLDNEW
« no previous file with comments | « util/mach/notify_server.cc ('k') | util/util.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698