910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_PUBLIC_TEST_BROWSER_TASK_ENVIRONMENT_H_
#define CONTENT_PUBLIC_TEST_BROWSER_TASK_ENVIRONMENT_H_

#include <memory>

#include "base/compiler_specific.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"

namespace base {
#if BUILDFLAG(IS_WIN)
namespace win {
class ScopedCOMInitializer;
}  // namespace win
#endif
}  // namespace base

namespace content {

class TestBrowserThread;

// BrowserTaskEnvironment is a convenience class which allows usage of these
// APIs within its scope:
// - Same APIs as base::test::TaskEnvironment.
// - content::BrowserThread.
// - Public APIs of base::test::TaskEnvironment.
//
// Only tests that need the BrowserThread API should instantiate a
// BrowserTaskEnvironment. Use base::test::SingleThreadTaskEnvironment or
// base::test::TaskEnvironment otherwise.
//
// By default, BrowserThread::UI/IO are backed by a single shared message loop
// on the main thread. If a test truly needs BrowserThread::IO tasks to run on a
// separate thread, it can pass the REAL_IO_THREAD option to the constructor.
// ThreadPool tasks always run on dedicated threads.
//
// To synchronously run tasks from the shared message loop:
//
// ... until there are no undelayed tasks in the shared message loop:
//    base::RunLoop().RunUntilIdle();
//
// ... until there are no undelayed tasks in the shared message loop, in
// ThreadPool (excluding tasks not posted from the shared message loop's thread
// or ThreadPool): task_environment.RunUntilIdle();
//
// ... until a condition is met:
//    base::RunLoop run_loop;
//    // Runs until a task running in the shared message loop calls
//    // run_loop.Quit() or runs run_loop.QuitClosure() (&run_loop or
//    // run_loop.QuitClosure() must be kept somewhere accessible by that task).
//    run_loop.Run();
//
// To wait until there are no pending undelayed tasks in ThreadPool, without
// running tasks from the shared message loop (this is rarely needed):
//    base::ThreadPoolInstance::Get()->FlushForTesting();
//
// The destructor of BrowserTaskEnvironment runs remaining UI/IO tasks and
// remaining thread pool tasks.
//
// If a test needs to pump IO messages on the main thread, it should use the
// IO_MAINLOOP option. Most of the time, IO_MAINLOOP avoids needing to use a
// REAL_IO_THREAD.
//
// For some tests it is important to emulate real browser startup. During real
// browser startup, the main message loop and the ThreadPool are created before
// browser threads. Passing DONT_CREATE_BROWSER_THREADS to the constructor will
// delay creating BrowserThreads until the test explicitly calls
// CreateBrowserThreads().
//
// DONT_CREATE_BROWSER_THREADS should only be used in conjunction with
// REAL_IO_THREAD.
//
// Basic usage:
//
//   class MyTestFixture : public testing::Test {
//    public:
//     (...)
//
//    protected:
//     // Must be the first member (or at least before any member that cares
//     // about tasks) to be initialized first and destroyed last. protected
//     // instead of private visibility will allow controlling the task
//     // environment (e.g. clock --see base::test::TaskEnvironment for
//     // details).
//     content::BrowserTaskEnvironment task_environment_;
//
//     // Other members go here (or further below in private section.)
//   };
//
// To add a BrowserTaskEnvironment to a ChromeFooBase test fixture when its
// FooBase base class already provides a base::test::TaskEnvironment:
//   class FooBase {
//    public:
//     // Constructs a FooBase with |traits| being forwarded to its
//     // TaskEnvironment.
//     template <typename... TaskEnvironmentTraits>
//     explicit FooBase(TaskEnvironmentTraits&&... traits)
//         : task_environment_(
//               std::in_place,
//               std::forward<TaskEnvironmentTraits>(traits)...) {}
//
//     // Alternatively a subclass may pass this tag to ask this FooBase not to
//     // instantiate a TaskEnvironment. The subclass is then responsible
//     // to instantiate one before FooBase::SetUp().
//     struct SubclassManagesTaskEnvironment {};
//     FooBase(SubclassManagesTaskEnvironment tag);
//
//    protected:
//     // Use this protected member directly from the test body to drive tasks
//     // posted within a FooBase-based test.
//     std::optional<base::test::TaskEnvironment> task_environment_;
//   };
//
//   class ChromeFooBase : public FooBase {
//    public:
//     explicit ChromeFooBase(TaskEnvironmentTraits&&... traits)
//         : FooBase(FooBase::SubclassManagesTaskEnvironment()),
//           task_environment_(
//               std::forward<TaskEnvironmentTraits>(traits)...) {}
//
//    protected:
//     // Use this protected member directly to drive tasks posted within a
//     // ChromeFooBase-based test.
//     content::BrowserTaskEnvironment task_environment_;
//   };
// See views::ViewsTestBase / ChromeViewsTestBase for a real-world example.
class BrowserTaskEnvironment : public base::test::TaskEnvironment {
 public:
  enum Options { REAL_IO_THREAD };

  // The main thread will use a MessageLoopForIO (and support the
  // base::FileDescriptorWatcher API on POSIX).
  // TODO(alexclarke): Replace IO_MAINLOOP usage by MainThreadType::IO and
  // remove this.
  static constexpr MainThreadType IO_MAINLOOP = MainThreadType::IO;

  struct ValidTraits {
    ValidTraits(TaskEnvironment::ValidTraits);
    ValidTraits(Options);
  };

  // Constructor which accepts zero or more traits to configure the
  // TaskEnvironment and optionally request a real IO thread. Unlike
  // TaskEnvironment the default MainThreadType for
  // BrowserTaskEnvironment is MainThreadType::UI.
  template <typename... TaskEnvironmentTraits>
    requires base::trait_helpers::AreValidTraits<ValidTraits,
                                                 TaskEnvironmentTraits...>
  NOINLINE explicit BrowserTaskEnvironment(TaskEnvironmentTraits... traits)
      : BrowserTaskEnvironment(
            CreateTaskEnvironmentWithPriorities(
                CreateBrowserTaskPrioritySettings(),
                SubclassCreatesDefaultTaskRunner{},
                base::trait_helpers::GetEnum<MainThreadType,
                                             MainThreadType::UI>(traits...),
                base::trait_helpers::Exclude<MainThreadType, Options>::Filter(
                    traits)...),
            UseRealIOThread(
                base::trait_helpers::GetOptionalEnum<Options>(traits...))) {}

  // Flush the IO thread. Replacement for RunLoop::RunUntilIdle() for tests that
  // have a REAL_IO_THREAD. As with TaskEnvironment::RunUntilIdle() prefer using
  // RunLoop+QuitClosure() to await an async condition.
  void RunIOThreadUntilIdle();

  BrowserTaskEnvironment(const BrowserTaskEnvironment&) = delete;
  BrowserTaskEnvironment& operator=(const BrowserTaskEnvironment&) = delete;

  ~BrowserTaskEnvironment() override;

 private:
  static base::sequence_manager::SequenceManager::PrioritySettings
  CreateBrowserTaskPrioritySettings();

  // The template constructor has to be in the header but it delegates to this
  // constructor to initialize all other members out-of-line.
  BrowserTaskEnvironment(base::test::TaskEnvironment&& scoped_task_environment,
                         bool real_io_thread);

  void Init();

  static constexpr bool UseRealIOThread(std::optional<Options> options) {
    if (!options)
      return false;
    return *options == Options::REAL_IO_THREAD;
  }

  constexpr bool HasIOMainLoop() const {
    return main_thread_type() == MainThreadType::IO;
  }

  const bool real_io_thread_;
  std::unique_ptr<TestBrowserThread> ui_thread_;
  std::unique_ptr<TestBrowserThread> io_thread_;

#if BUILDFLAG(IS_WIN)
  std::unique_ptr<base::win::ScopedCOMInitializer> com_initializer_;
#endif
};

}  // namespace content

#endif  // CONTENT_PUBLIC_TEST_BROWSER_TASK_ENVIRONMENT_H_