xref: /aosp_15_r20/external/cronet/base/message_loop/message_pump_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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