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