/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "test/single_threaded_task_queue.h"

#include <atomic>
#include <memory>
#include <vector>

#include "api/task_queue/task_queue_test.h"
#include "rtc_base/event.h"
#include "rtc_base/task_queue_for_test.h"
#include "test/gtest.h"

namespace webrtc {
namespace test {

namespace {

using TaskId = DEPRECATED_SingleThreadedTaskQueueForTesting::TaskId;

// Test should not rely on the object under test not being faulty. If the task
// queue ever blocks forever, we want the tests to fail, rather than hang.
constexpr int kMaxWaitTimeMs = 10000;

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     SanityConstructionDestruction) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest, ExecutesPostedTasks) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  std::atomic<bool> executed(false);
  rtc::Event done;

  task_queue.PostTask([&executed, &done]() {
    executed.store(true);
    done.Set();
  });
  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));

  EXPECT_TRUE(executed.load());
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     PostMultipleTasksFromSameExternalThread) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  constexpr size_t kCount = 3;
  std::atomic<bool> executed[kCount];
  for (std::atomic<bool>& exec : executed) {
    exec.store(false);
  }

  std::vector<std::unique_ptr<rtc::Event>> done_events;
  for (size_t i = 0; i < kCount; i++) {
    done_events.emplace_back(std::make_unique<rtc::Event>());
  }

  // To avoid the tasks which comprise the actual test from running before they
  // have all be posted, which could result in only one task ever being in the
  // queue at any given time, post one waiting task that would block the
  // task-queue, and unblock only after all tasks have been posted.
  rtc::Event rendezvous;
  task_queue.PostTask(
      [&rendezvous]() { ASSERT_TRUE(rendezvous.Wait(kMaxWaitTimeMs)); });

  // Post the tasks which comprise the test.
  for (size_t i = 0; i < kCount; i++) {
    task_queue.PostTask([&executed, &done_events, i]() {  // |i| by value.
      executed[i].store(true);
      done_events[i]->Set();
    });
  }

  rendezvous.Set();  // Release the task-queue.

  // Wait until the task queue has executed all the tasks.
  for (size_t i = 0; i < kCount; i++) {
    ASSERT_TRUE(done_events[i]->Wait(kMaxWaitTimeMs));
  }

  for (size_t i = 0; i < kCount; i++) {
    EXPECT_TRUE(executed[i].load());
  }
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     PostToTaskQueueFromOwnThread) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  std::atomic<bool> executed(false);
  rtc::Event done;

  auto internally_posted_task = [&executed, &done]() {
    executed.store(true);
    done.Set();
  };

  auto externally_posted_task = [&task_queue, &internally_posted_task]() {
    task_queue.PostTask(internally_posted_task);
  };

  task_queue.PostTask(externally_posted_task);

  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));
  EXPECT_TRUE(executed.load());
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     TasksExecutedInSequence) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  // The first task would perform:
  // accumulator = 10 * accumulator + i
  // Where |i| is 1, 2 and 3 for the 1st, 2nd and 3rd tasks, respectively.
  // The result would be 123 if and only iff the tasks were executed in order.
  size_t accumulator = 0;
  size_t expected_value = 0;  // Updates to the correct value.

  // Prevent the chain from being set in motion before we've had time to
  // schedule it all, lest the queue only contain one task at a time.
  rtc::Event rendezvous;
  task_queue.PostTask(
      [&rendezvous]() { ASSERT_TRUE(rendezvous.Wait(kMaxWaitTimeMs)); });

  for (size_t i = 0; i < 3; i++) {
    task_queue.PostTask([&accumulator, i]() {  // |i| passed by value.
      accumulator = 10 * accumulator + i;
    });
    expected_value = 10 * expected_value + i;
  }

  // The test will wait for the task-queue to finish.
  rtc::Event done;
  task_queue.PostTask([&done]() { done.Set(); });

  rendezvous.Set();  // Set the chain in motion.

  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));

  EXPECT_EQ(accumulator, expected_value);
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     ExecutesPostedDelayedTask) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  std::atomic<bool> executed(false);
  rtc::Event done;

  constexpr int64_t delay_ms = 20;
  static_assert(delay_ms < kMaxWaitTimeMs / 2, "Delay too long for tests.");

  task_queue.PostDelayedTask(
      [&executed, &done]() {
        executed.store(true);
        done.Set();
      },
      delay_ms);
  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));

  EXPECT_TRUE(executed.load());
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     DoesNotExecuteDelayedTaskTooSoon) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  std::atomic<bool> executed(false);

  constexpr int64_t delay_ms = 2000;
  static_assert(delay_ms < kMaxWaitTimeMs / 2, "Delay too long for tests.");

  task_queue.PostDelayedTask([&executed]() { executed.store(true); }, delay_ms);

  // Wait less than is enough, make sure the task was not yet executed.
  rtc::Event not_done;
  ASSERT_FALSE(not_done.Wait(delay_ms / 2));
  EXPECT_FALSE(executed.load());
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     TaskWithLesserDelayPostedAfterFirstDelayedTaskExectuedBeforeFirst) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  std::atomic<bool> earlier_executed(false);
  constexpr int64_t earlier_delay_ms = 500;

  std::atomic<bool> later_executed(false);
  constexpr int64_t later_delay_ms = 1000;

  static_assert(earlier_delay_ms + later_delay_ms < kMaxWaitTimeMs / 2,
                "Delay too long for tests.");

  rtc::Event done;

  auto earlier_task = [&earlier_executed, &later_executed]() {
    EXPECT_FALSE(later_executed.load());
    earlier_executed.store(true);
  };

  auto later_task = [&earlier_executed, &later_executed, &done]() {
    EXPECT_TRUE(earlier_executed.load());
    later_executed.store(true);
    done.Set();
  };

  task_queue.PostDelayedTask(later_task, later_delay_ms);
  task_queue.PostDelayedTask(earlier_task, earlier_delay_ms);

  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));
  ASSERT_TRUE(earlier_executed);
  ASSERT_TRUE(later_executed);
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     TaskWithGreaterDelayPostedAfterFirstDelayedTaskExectuedAfterFirst) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  std::atomic<bool> earlier_executed(false);
  constexpr int64_t earlier_delay_ms = 500;

  std::atomic<bool> later_executed(false);
  constexpr int64_t later_delay_ms = 1000;

  static_assert(earlier_delay_ms + later_delay_ms < kMaxWaitTimeMs / 2,
                "Delay too long for tests.");

  rtc::Event done;

  auto earlier_task = [&earlier_executed, &later_executed]() {
    EXPECT_FALSE(later_executed.load());
    earlier_executed.store(true);
  };

  auto later_task = [&earlier_executed, &later_executed, &done]() {
    EXPECT_TRUE(earlier_executed.load());
    later_executed.store(true);
    done.Set();
  };

  task_queue.PostDelayedTask(earlier_task, earlier_delay_ms);
  task_queue.PostDelayedTask(later_task, later_delay_ms);

  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));
  ASSERT_TRUE(earlier_executed);
  ASSERT_TRUE(later_executed);
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     ExternalThreadCancelsTask) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  rtc::Event done;

  // Prevent the to-be-cancelled task from being executed before we've had
  // time to cancel it.
  rtc::Event rendezvous;
  task_queue.PostTask(
      [&rendezvous]() { ASSERT_TRUE(rendezvous.Wait(kMaxWaitTimeMs)); });

  TaskId cancelled_task_id = task_queue.PostTask([]() { EXPECT_TRUE(false); });
  task_queue.PostTask([&done]() { done.Set(); });

  task_queue.CancelTask(cancelled_task_id);

  // Set the tasks in motion; the cancelled task does not run (otherwise the
  // test would fail). The last task ends the test, showing that the queue
  // progressed beyond the cancelled task.
  rendezvous.Set();
  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));
}

// In this test, we'll set off a chain where the first task cancels the second
// task, then a third task runs (showing that we really cancelled the task,
// rather than just halted the task-queue).
TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     InternalThreadCancelsTask) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  rtc::Event done;

  // Prevent the chain from being set-off before we've set everything up.
  rtc::Event rendezvous;
  task_queue.PostTask(
      [&rendezvous]() { ASSERT_TRUE(rendezvous.Wait(kMaxWaitTimeMs)); });

  // This is the canceller-task. It takes cancelled_task_id by reference,
  // because the ID will only become known after the cancelled task is
  // scheduled.
  TaskId cancelled_task_id;
  auto canceller_task = [&task_queue, &cancelled_task_id]() {
    task_queue.CancelTask(cancelled_task_id);
  };
  task_queue.PostTask(canceller_task);

  // This task will be cancelled by the task before it.
  auto cancelled_task = []() { EXPECT_TRUE(false); };
  cancelled_task_id = task_queue.PostTask(cancelled_task);

  // When this task runs, it will allow the test to be finished.
  auto completion_marker_task = [&done]() { done.Set(); };
  task_queue.PostTask(completion_marker_task);

  rendezvous.Set();  // Set the chain in motion.

  ASSERT_TRUE(done.Wait(kMaxWaitTimeMs));
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest, SendTask) {
  DEPRECATED_SingleThreadedTaskQueueForTesting task_queue("task_queue");

  std::atomic<bool> executed(false);

  SendTask(RTC_FROM_HERE, &task_queue, [&executed]() {
    // Intentionally delay, so that if SendTask didn't block, the sender thread
    // would have time to read |executed|.
    rtc::Event delay;
    ASSERT_FALSE(delay.Wait(1000));
    executed.store(true);
  });

  EXPECT_TRUE(executed);
}

TEST(DEPRECATED_SingleThreadedTaskQueueForTestingTest,
     DestructTaskQueueWhileTasksPending) {
  auto task_queue =
      std::make_unique<DEPRECATED_SingleThreadedTaskQueueForTesting>(
          "task_queue");

  std::atomic<size_t> counter(0);

  constexpr size_t tasks = 10;
  for (size_t i = 0; i < tasks; i++) {
    task_queue->PostTask([&counter]() {
      std::atomic_fetch_add(&counter, static_cast<size_t>(1));
      rtc::Event delay;
      ASSERT_FALSE(delay.Wait(500));
    });
  }

  task_queue.reset();

  EXPECT_LT(counter, tasks);
}

class SingleThreadedTaskQueueForTestingFactory : public TaskQueueFactory {
 public:
  std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue(
      absl::string_view /* name */,
      Priority /*priority*/) const override {
    return std::unique_ptr<TaskQueueBase, TaskQueueDeleter>(
        new DEPRECATED_SingleThreadedTaskQueueForTesting("noname"));
  }
};

INSTANTIATE_TEST_SUITE_P(
    DeprecatedSingleThreadedTaskQueueForTesting,
    TaskQueueTest,
    ::testing::Values(
        std::make_unique<SingleThreadedTaskQueueForTestingFactory>));

}  // namespace
}  // namespace test
}  // namespace webrtc