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