// 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 {}
  void BeginNativeWorkBeforeDoWork() 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
