1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/message_loop/message_pump.h"
6
7 #include <type_traits>
8
9 #include "base/functional/bind.h"
10 #include "base/logging.h"
11 #include "base/memory/raw_ptr.h"
12 #include "base/message_loop/message_pump_for_io.h"
13 #include "base/message_loop/message_pump_for_ui.h"
14 #include "base/message_loop/message_pump_type.h"
15 #include "base/run_loop.h"
16 #include "base/task/single_thread_task_executor.h"
17 #include "base/test/bind.h"
18 #include "base/test/test_timeouts.h"
19 #include "base/threading/thread.h"
20 #include "build/build_config.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 #if BUILDFLAG(IS_WIN)
25 #include <windows.h>
26 #endif
27
28 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
29 #include "base/message_loop/message_pump_libevent.h"
30 #endif
31
32 using ::testing::_;
33 using ::testing::AnyNumber;
34 using ::testing::AtMost;
35 using ::testing::Invoke;
36 using ::testing::Return;
37
38 namespace base {
39
40 namespace {
41
42 // On most platforms, the MessagePump impl controls when native work (e.g.
43 // handling input messages) gets its turn. Tests below verify that by expecting
44 // OnBeginWorkItem() calls that cover native work. In some configurations
45 // however, the platform owns the message loop and is the one yielding to
46 // Chrome's MessagePump to DoWork(). Under those configurations, it is not
47 // possible to precisely account for OnBeginWorkItem() calls as they can occur
48 // nondeterministically. For example, on some versions of iOS, the native loop
49 // can surprisingly go through multiple cycles of
50 // kCFRunLoopAfterWaiting=>kCFRunLoopBeforeWaiting before invoking Chrome's
51 // RunWork() for the first time, triggering multiple ScopedDoWorkItem 's for
52 // potential native work before the first DoWork().
ChromeControlsNativeEventProcessing(MessagePumpType pump_type)53 constexpr bool ChromeControlsNativeEventProcessing(MessagePumpType pump_type) {
54 #if BUILDFLAG(IS_MAC)
55 return pump_type != MessagePumpType::UI;
56 #elif BUILDFLAG(IS_IOS)
57 return false;
58 #else
59 return true;
60 #endif
61 }
62
63 class MockMessagePumpDelegate : public MessagePump::Delegate {
64 public:
MockMessagePumpDelegate(MessagePumpType pump_type)65 explicit MockMessagePumpDelegate(MessagePumpType pump_type)
66 : check_work_items_(ChromeControlsNativeEventProcessing(pump_type)),
67 native_work_item_accounting_is_on_(
68 !ChromeControlsNativeEventProcessing(pump_type)) {}
69
~MockMessagePumpDelegate()70 ~MockMessagePumpDelegate() override { ValidateNoOpenWorkItems(); }
71
72 MockMessagePumpDelegate(const MockMessagePumpDelegate&) = delete;
73 MockMessagePumpDelegate& operator=(const MockMessagePumpDelegate&) = delete;
74
BeforeWait()75 void BeforeWait() override {}
BeginNativeWorkBeforeDoWork()76 void BeginNativeWorkBeforeDoWork() override {}
77 MOCK_METHOD0(DoWork, MessagePump::Delegate::NextWorkInfo());
78 MOCK_METHOD0(DoIdleWork, bool());
79
80 // Functions invoked directly by the message pump.
OnBeginWorkItem()81 void OnBeginWorkItem() override {
82 any_work_begun_ = true;
83
84 if (check_work_items_) {
85 MockOnBeginWorkItem();
86 }
87
88 ++work_item_count_;
89 }
90
OnEndWorkItem(int run_level_depth)91 void OnEndWorkItem(int run_level_depth) override {
92 if (check_work_items_) {
93 MockOnEndWorkItem(run_level_depth);
94 }
95
96 EXPECT_EQ(run_level_depth, work_item_count_);
97
98 --work_item_count_;
99
100 // It's not possible to close more scopes than there are open ones.
101 EXPECT_GE(work_item_count_, 0);
102 }
103
RunDepth()104 int RunDepth() override { return work_item_count_; }
105
ValidateNoOpenWorkItems()106 void ValidateNoOpenWorkItems() {
107 // Upon exiting there cannot be any open scopes.
108 EXPECT_EQ(work_item_count_, 0);
109
110 if (native_work_item_accounting_is_on_) {
111 // Tests should trigger work beginning at least once except on iOS where
112 // they need a call to MessagePumpUIApplication::Attach() to do so when on
113 // the UI thread.
114 #if !BUILDFLAG(IS_IOS)
115 EXPECT_TRUE(any_work_begun_);
116 #endif
117 }
118 }
119
120 // Mock functions for asserting.
121 MOCK_METHOD0(MockOnBeginWorkItem, void(void));
122 MOCK_METHOD1(MockOnEndWorkItem, void(int));
123
124 // If native events are covered in the current configuration it's not
125 // possible to precisely test all assertions related to work items. This is
126 // because a number of speculative WorkItems are created during execution of
127 // such loops and it's not possible to determine their number before the
128 // execution of the test. In such configurations the functioning of the
129 // message pump is still verified by looking at the counts of opened and
130 // closed WorkItems.
131 const bool check_work_items_;
132 const bool native_work_item_accounting_is_on_;
133
134 int work_item_count_ = 0;
135 bool any_work_begun_ = false;
136 };
137
138 class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> {
139 public:
MessagePumpTest()140 MessagePumpTest() : message_pump_(MessagePump::Create(GetParam())) {}
141
142 protected:
143 #if defined(USE_GLIB)
144 // Because of a GLIB implementation quirk, the pump doesn't do the same things
145 // between each DoWork. In this case, it won't set/clear a ScopedDoWorkItem
146 // because we run a chrome work item in the runloop outside of GLIB's control,
147 // so we oscillate between setting and not setting PreDoWorkExpectations.
148 std::map<MessagePump::Delegate*, int> do_work_counts;
149 #endif
AddPreDoWorkExpectations(testing::StrictMock<MockMessagePumpDelegate> & delegate)150 void AddPreDoWorkExpectations(
151 testing::StrictMock<MockMessagePumpDelegate>& delegate) {
152 #if BUILDFLAG(IS_WIN)
153 if (GetParam() == MessagePumpType::UI) {
154 // The Windows MessagePumpForUI may do native work from ::PeekMessage()
155 // and labels itself as such.
156 EXPECT_CALL(delegate, MockOnBeginWorkItem);
157 EXPECT_CALL(delegate, MockOnEndWorkItem);
158
159 // If the above event was MessagePumpForUI's own kMsgHaveWork internal
160 // event, it will process another event to replace it (ref.
161 // ProcessPumpReplacementMessage).
162 EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
163 EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
164 }
165 #endif // BUILDFLAG(IS_WIN)
166 #if defined(USE_GLIB)
167 do_work_counts.try_emplace(&delegate, 0);
168 if (GetParam() == MessagePumpType::UI) {
169 if (++do_work_counts[&delegate] % 2) {
170 // The GLib MessagePump will do native work before chrome work on
171 // startup.
172 EXPECT_CALL(delegate, MockOnBeginWorkItem);
173 EXPECT_CALL(delegate, MockOnEndWorkItem);
174 }
175 }
176 #endif // defined(USE_GLIB)
177 }
178
AddPostDoWorkExpectations(testing::StrictMock<MockMessagePumpDelegate> & delegate)179 void AddPostDoWorkExpectations(
180 testing::StrictMock<MockMessagePumpDelegate>& delegate) {
181 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
182 // MessagePumpLibEvent checks for native notifications once after processing
183 // a DoWork() but only instantiates a ScopedDoWorkItem that triggers
184 // MessagePumpLibevent::OnLibeventNotification() which this test does not
185 // so there are no post-work expectations at the moment.
186 #endif
187 #if defined(USE_GLIB)
188 if (GetParam() == MessagePumpType::UI) {
189 // The GLib MessagePump can create and destroy work items between DoWorks
190 // depending on internal state.
191 EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
192 EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
193 }
194 #endif // defined(USE_GLIB)
195 }
196
197 std::unique_ptr<MessagePump> message_pump_;
198 };
199
200 } // namespace
201
TEST_P(MessagePumpTest,QuitStopsWork)202 TEST_P(MessagePumpTest, QuitStopsWork) {
203 testing::InSequence sequence;
204 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
205
206 AddPreDoWorkExpectations(delegate);
207
208 // Not expecting any calls to DoIdleWork after quitting, nor any of the
209 // PostDoWorkExpectations, quitting should be instantaneous.
210 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
211 message_pump_->Quit();
212 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
213 }));
214
215 // MessagePumpGlib uses a work item between a HandleDispatch() call and
216 // passing control back to the chrome loop, which handles the Quit() despite
217 // us not necessarily doing any native work during that time.
218 #if defined(USE_GLIB)
219 if (GetParam() == MessagePumpType::UI) {
220 AddPostDoWorkExpectations(delegate);
221 }
222 #endif
223
224 EXPECT_CALL(delegate, DoIdleWork()).Times(0);
225
226 message_pump_->ScheduleWork();
227 message_pump_->Run(&delegate);
228 }
229
TEST_P(MessagePumpTest,QuitStopsWorkWithNestedRunLoop)230 TEST_P(MessagePumpTest, QuitStopsWorkWithNestedRunLoop) {
231 testing::InSequence sequence;
232 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
233 testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
234
235 AddPreDoWorkExpectations(delegate);
236
237 // We first schedule a call to DoWork, which runs a nested run loop. After
238 // the nested loop exits, we schedule another DoWork which quits the outer
239 // (original) run loop. The test verifies that there are no extra calls to
240 // DoWork after the outer loop quits.
241 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([&] {
242 message_pump_->Run(&nested_delegate);
243 // A null NextWorkInfo indicates immediate follow-up work.
244 return MessagePump::Delegate::NextWorkInfo();
245 }));
246
247 AddPreDoWorkExpectations(nested_delegate);
248 EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([&] {
249 // Quit the nested run loop.
250 message_pump_->Quit();
251 // The underlying pump should process the next task in the first run-level
252 // regardless of whether the nested run-level indicates there's no more work
253 // (e.g. can happen when the only remaining tasks are non-nestable).
254 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
255 }));
256
257 // The `nested_delegate` will quit first.
258 AddPostDoWorkExpectations(nested_delegate);
259
260 // Return a delayed task with |yield_to_native| set, and exit.
261 AddPostDoWorkExpectations(delegate);
262
263 AddPreDoWorkExpectations(delegate);
264
265 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
266 message_pump_->Quit();
267 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
268 }));
269
270 message_pump_->ScheduleWork();
271 message_pump_->Run(&delegate);
272 }
273
TEST_P(MessagePumpTest,YieldToNativeRequestedSmokeTest)274 TEST_P(MessagePumpTest, YieldToNativeRequestedSmokeTest) {
275 // The handling of the "yield_to_native" boolean in the NextWorkInfo is only
276 // implemented on the MessagePumpForUI on android. However since we inject a
277 // fake one for testing this is hard to test. This test ensures that setting
278 // this boolean doesn't cause any MessagePump to explode.
279 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
280
281 testing::InSequence sequence;
282
283 // Return an immediate task with |yield_to_native| set.
284 AddPreDoWorkExpectations(delegate);
285 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([] {
286 return MessagePump::Delegate::NextWorkInfo{TimeTicks(), TimeDelta(),
287 TimeTicks(),
288 /* yield_to_native = */ true};
289 }));
290 AddPostDoWorkExpectations(delegate);
291
292 AddPreDoWorkExpectations(delegate);
293 // Return a delayed task with |yield_to_native| set, and exit.
294 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
295 message_pump_->Quit();
296 auto now = TimeTicks::Now();
297 return MessagePump::Delegate::NextWorkInfo{now + Milliseconds(1),
298 TimeDelta(), now, true};
299 }));
300 EXPECT_CALL(delegate, DoIdleWork()).Times(AnyNumber());
301
302 message_pump_->ScheduleWork();
303 message_pump_->Run(&delegate);
304 }
305
TEST_P(MessagePumpTest,LeewaySmokeTest)306 TEST_P(MessagePumpTest, LeewaySmokeTest) {
307 // The handling of the "leeway" in the NextWorkInfo is only implemented on
308 // mac. However since we inject a fake one for testing this is hard to test.
309 // This test ensures that setting this boolean doesn't cause any MessagePump
310 // to explode.
311 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
312
313 testing::InSequence sequence;
314
315 AddPreDoWorkExpectations(delegate);
316 // Return a delayed task with |yield_to_native| set, and exit.
317 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
318 message_pump_->Quit();
319 auto now = TimeTicks::Now();
320 return MessagePump::Delegate::NextWorkInfo{now + Milliseconds(1),
321 Milliseconds(8), now};
322 }));
323 EXPECT_CALL(delegate, DoIdleWork()).Times(AnyNumber());
324
325 message_pump_->ScheduleWork();
326 message_pump_->Run(&delegate);
327 }
328
TEST_P(MessagePumpTest,RunWithoutScheduleWorkInvokesDoWork)329 TEST_P(MessagePumpTest, RunWithoutScheduleWorkInvokesDoWork) {
330 testing::InSequence sequence;
331 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
332
333 AddPreDoWorkExpectations(delegate);
334
335 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
336 message_pump_->Quit();
337 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
338 }));
339
340 AddPostDoWorkExpectations(delegate);
341
342 #if BUILDFLAG(IS_IOS)
343 EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
344 #endif
345
346 message_pump_->Run(&delegate);
347 }
348
TEST_P(MessagePumpTest,NestedRunWithoutScheduleWorkInvokesDoWork)349 TEST_P(MessagePumpTest, NestedRunWithoutScheduleWorkInvokesDoWork) {
350 testing::InSequence sequence;
351 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
352 testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
353
354 AddPreDoWorkExpectations(delegate);
355
356 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this, &nested_delegate] {
357 message_pump_->Run(&nested_delegate);
358 message_pump_->Quit();
359 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
360 }));
361
362 AddPreDoWorkExpectations(nested_delegate);
363
364 EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([this] {
365 message_pump_->Quit();
366 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
367 }));
368
369 // We quit `nested_delegate` before `delegate`
370 AddPostDoWorkExpectations(nested_delegate);
371
372 AddPostDoWorkExpectations(delegate);
373
374 #if BUILDFLAG(IS_IOS)
375 EXPECT_CALL(nested_delegate, DoIdleWork).Times(AnyNumber());
376 EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
377 #endif
378
379 message_pump_->Run(&delegate);
380 }
381
382 INSTANTIATE_TEST_SUITE_P(All,
383 MessagePumpTest,
384 ::testing::Values(MessagePumpType::DEFAULT,
385 MessagePumpType::UI,
386 MessagePumpType::IO));
387
388 } // namespace base
389