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

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

Powered by Google App Engine
This is Rietveld 408576698