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