// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_TEST_SCOPED_TASK_ENVIRONMENT_H_
#define BASE_TEST_SCOPED_TASK_ENVIRONMENT_H_

#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h"
#include "base/task_scheduler/lazy_task_runner.h"
#include "build/build_config.h"

namespace base {

namespace internal {
class ScopedSetSequenceLocalStorageMapForCurrentThread;
class SequenceLocalStorageMap;
}  // namespace internal

class FileDescriptorWatcher;
class MessageLoop;
class TaskScheduler;
class TestMockTimeTaskRunner;
class TickClock;

namespace test {

// ScopedTaskEnvironment allows usage of these APIs within its scope:
// - (Thread|Sequenced)TaskRunnerHandle, on the thread where it lives
// - base/task_scheduler/post_task.h, on any thread
//
// Tests that need either of these APIs should instantiate a
// ScopedTaskEnvironment.
//
// Tasks posted to the (Thread|Sequenced)TaskRunnerHandle run synchronously when
// RunLoop::Run(UntilIdle) or ScopedTaskEnvironment::RunUntilIdle is called on
// the thread where the ScopedTaskEnvironment lives.
//
// Tasks posted through base/task_scheduler/post_task.h run on dedicated
// threads. If ExecutionMode is QUEUED, they run when RunUntilIdle() or
// ~ScopedTaskEnvironment is called. If ExecutionMode is ASYNC, they run
// as they are posted.
//
// All methods of ScopedTaskEnvironment must be called from the same thread.
//
// 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) once such features are added (see design doc
//     // below for details), until then it at least doesn't hurt :).
//     base::test::ScopedTaskEnvironment scoped_task_environment_;
//
//     // Other members go here (or further below in private section.)
//   };
//
// Design and future improvements documented in
// https://docs.google.com/document/d/1QabRo8c7D9LsYY3cEcaPQbOCLo8Tu-6VLykYXyl3Pkk/edit
class ScopedTaskEnvironment {
 public:
  enum class MainThreadType {
    // The main thread doesn't pump system messages.
    DEFAULT,
    // The main thread doesn't pump system messages and uses a mock clock for
    // delayed tasks (controllable via FastForward*() methods).
    // TODO(gab): Make this the default |main_thread_type|.
    // TODO(gab): Also mock the TaskScheduler's clock simultaneously (this
    // currently only mocks the main thread's clock).
    MOCK_TIME,
    // The main thread pumps UI messages.
    UI,
    // The main thread pumps asynchronous IO messages and supports the
    // FileDescriptorWatcher API on POSIX.
    IO,
  };

  enum class ExecutionMode {
    // Tasks are queued and only executed when RunUntilIdle() is explicitly
    // called.
    QUEUED,
    // Tasks run as they are posted. RunUntilIdle() can still be used to block
    // until done.
    ASYNC,
  };

  ScopedTaskEnvironment(
      MainThreadType main_thread_type = MainThreadType::DEFAULT,
      ExecutionMode execution_control_mode = ExecutionMode::ASYNC);

  // Waits until no undelayed TaskScheduler tasks remain. Then, unregisters the
  // TaskScheduler and the (Thread|Sequenced)TaskRunnerHandle.
  ~ScopedTaskEnvironment();

  // Returns a TaskRunner that schedules tasks on the main thread.
  scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner();

  // Returns whether the main thread's TaskRunner has pending tasks.
  bool MainThreadHasPendingTask() const;

  // Runs tasks until both the (Thread|Sequenced)TaskRunnerHandle and the
  // TaskScheduler's non-delayed queues are empty.
  void RunUntilIdle();

  // Only valid for instances with a MOCK_TIME MainThreadType. Fast-forwards
  // virtual time by |delta|, causing all tasks on the main thread with a
  // remaining delay less than or equal to |delta| to be executed before this
  // returns. |delta| must be non-negative.
  // TODO(gab): Make this apply to TaskScheduler delayed tasks as well
  // (currently only main thread time is mocked).
  void FastForwardBy(TimeDelta delta);

  // Only valid for instances with a MOCK_TIME MainThreadType.
  // Short for FastForwardBy(TimeDelta::Max()).
  void FastForwardUntilNoTasksRemain();

  // Only valid for instances with a MOCK_TIME MainThreadType.  Returns a
  // TickClock whose time is updated by FastForward(By|UntilNoTasksRemain).
  const TickClock* GetMockTickClock();
  std::unique_ptr<TickClock> DeprecatedGetMockTickClock();

  // Only valid for instances with a MOCK_TIME MainThreadType.
  // Returns the number of pending tasks of the main thread's TaskRunner.
  size_t GetPendingMainThreadTaskCount() const;

  // Only valid for instances with a MOCK_TIME MainThreadType.
  // Returns the delay until the next delayed pending task of the main thread's
  // TaskRunner.
  TimeDelta NextMainThreadPendingTaskDelay() const;

 private:
  class TestTaskTracker;

  const ExecutionMode execution_control_mode_;

  // Exactly one of these will be non-null to provide the task environment on
  // the main thread. Users of this class should NOT rely on the presence of a
  // MessageLoop beyond (Thread|Sequenced)TaskRunnerHandle and RunLoop as
  // the backing implementation of each MainThreadType may change over time.
  const std::unique_ptr<MessageLoop> message_loop_;
  const scoped_refptr<TestMockTimeTaskRunner> mock_time_task_runner_;

  // Non-null in MOCK_TIME, where an explicit SequenceLocalStorageMap needs to
  // be provided. TODO(gab): This can be removed once mock time support is added
  // to MessageLoop directly.
  const std::unique_ptr<internal::SequenceLocalStorageMap> slsm_for_mock_time_;
  const std::unique_ptr<
      internal::ScopedSetSequenceLocalStorageMapForCurrentThread>
      slsm_registration_for_mock_time_;

#if defined(OS_POSIX)
  // Enables the FileDescriptorWatcher API iff running a MainThreadType::IO.
  const std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher_;
#endif

  const TaskScheduler* task_scheduler_ = nullptr;

  // Owned by |task_scheduler_|.
  TestTaskTracker* const task_tracker_;

  // Ensures destruction of lazy TaskRunners when this is destroyed.
  internal::ScopedLazyTaskRunnerListForTesting
      scoped_lazy_task_runner_list_for_testing_;

  DISALLOW_COPY_AND_ASSIGN(ScopedTaskEnvironment);
};

}  // namespace test
}  // namespace base

#endif  // BASE_TEST_SCOPED_ASYNC_TASK_SCHEDULER_H_
