// Copyright 2020 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/task/thread_pool.h"

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_waitable_event.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

TEST(ThreadPool, PostTaskAndReplyWithResultThreeArgs) {
  base::test::TaskEnvironment env;

  base::RunLoop run_loop;
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce([]() { return 3; }),
      base::OnceCallback<void(int)>(
          base::BindLambdaForTesting([&run_loop](int x) {
            EXPECT_EQ(x, 3);
            run_loop.Quit();
          })));
  run_loop.Run();
}

TEST(ThreadPool, PostTaskAndReplyWithResultFourArgs) {
  base::test::TaskEnvironment env;

  base::RunLoop run_loop;
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, /*traits=*/{}, base::BindOnce([]() { return 3; }),
      base::OnceCallback<void(int)>(
          base::BindLambdaForTesting([&run_loop](int x) {
            EXPECT_EQ(x, 3);
            run_loop.Quit();
          })));
  run_loop.Run();
}

TEST(ThreadPool, BindPostTaskFizzlesOnShutdown) {
  // Tests that a callback bound to a BLOCK_SHUTDOWN sequence doesn't trigger a
  // DCHECK when it's deleted without running.
  base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
      "BindPostTaskFizzlesOnShutdownTest");

  {
    // Creating this callback and deleting it after the thread pool is shutdown
    // used to trigger a DCHECK in task_tracker because the
    // BindPostTaskTrampoline destructor has to delete its state on the sequence
    // it's bound to. There is a DCHECK that ensures BLOCK_SHUTDOWN tasks aren't
    // posted after shutdown, but BindPostTaskTrampoline uses
    // base::ThreadPoolInstance::ScopedFizzleBlockShutdownTasks to avoid
    // triggering it.
    auto bound_callback =
        base::BindPostTask(base::ThreadPool::CreateSequencedTaskRunner(
                               {TaskShutdownBehavior::BLOCK_SHUTDOWN}),
                           base::BindOnce([]() { ADD_FAILURE(); }));
    base::ThreadPoolInstance::Get()->Shutdown();
  }

  ThreadPoolInstance::Get()->JoinForTesting();
  ThreadPoolInstance::Set(nullptr);
}

TEST(ThreadPool, PostTaskAndReplyFizzlesOnShutdown) {
  // Tests that a PostTaskAndReply from a BLOCK_SHUTDOWN sequence doesn't
  // trigger a DCHECK when it's not run at shutdown.
  base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
      "PostTaskAndReplyFizzlesOnShutdown");

  {
    base::SingleThreadTaskExecutor executor;
    auto blocking_task_runner = base::ThreadPool::CreateSequencedTaskRunner(
        {TaskShutdownBehavior::BLOCK_SHUTDOWN});

    base::RunLoop run_loop;

    // The setup that this test is exercising is as follows:
    // - A task is posted using PostTaskAndReply from a BLOCK_SHUTDOWN sequence
    // to the main thread
    // - The task is not run on the main thread
    // - Shutdown happens, the ThreadPool is shutdown
    // - The main thread destroys its un-run tasks
    // - ~PostTaskAndReplyRelay calls `DeleteSoon` to delete its reply's state
    // on the sequence it's bound to
    // - TaskTracker ensures that no BLOCK_SHUTDOWN tasks can be posted after
    // shutdown. ~PostTaskAndReplyRelay avoids triggering this DCHECK by using a
    // base::ThreadPoolInstance::ScopedFizzleBlockShutdownTasks

    // Post a task to the BLOCK_SHUTDOWN thread pool sequence to setup the test.
    base::TestWaitableEvent event;
    blocking_task_runner->PostTask(
        FROM_HERE, base::BindLambdaForTesting([&]() {
          // Enqueue a task whose only purpose is to exit the run loop, ensuring
          // the following task is never run.
          executor.task_runner()->PostTask(FROM_HERE,
                                           base::BindLambdaForTesting([&]() {
                                             event.Wait();
                                             run_loop.Quit();
                                           }));

          // Post the task for which the reply will trigger the `DeleteSoon`
          // from `~PostTaskAndReplyRelay`.
          executor.task_runner()->PostTaskAndReply(
              FROM_HERE, base::BindOnce([]() { ADD_FAILURE(); }),
              base::BindOnce([]() { ADD_FAILURE(); }));

          event.Signal();
        }));

    // Run until the first task posted to the SingleThreadTaskExecutor quits the
    // run loop, resulting in the `PostTaskAndReply` being queued but not run.
    run_loop.Run();

    base::ThreadPoolInstance::Get()->Shutdown();
  }

  ThreadPoolInstance::Get()->JoinForTesting();
  ThreadPoolInstance::Set(nullptr);
}

}  // namespace base
