/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/executors/ThreadedExecutor.h>

#include <stdexcept>

#include <folly/Conv.h>
#include <folly/futures/Future.h>
#include <folly/gen/Base.h>
#include <folly/portability/GTest.h>

namespace {

class ThreadedExecutorTest : public testing::Test {};
} // namespace

TEST_F(ThreadedExecutorTest, example) {
  folly::ThreadedExecutor x;
  auto ret = folly::via(&x)
                 .thenValue([&](auto&&) { return 42; })
                 .thenValue([&](int n) { return folly::to<std::string>(n); })
                 .get();

  EXPECT_EQ("42", ret);
}

TEST_F(ThreadedExecutorTest, exception) {
  folly::ThreadedExecutor x;
  x.add([] { throw std::runtime_error("This should not crash the program"); });
}

TEST_F(ThreadedExecutorTest, dtor_waits) {
  constexpr auto kDelay = std::chrono::milliseconds(100);
  auto x = std::make_unique<folly::ThreadedExecutor>();
  auto fut = folly::via(&*x, [&] { /* sleep override */
                                   std::this_thread::sleep_for(kDelay);
  });
  x = nullptr;

  EXPECT_TRUE(fut.isReady());
}

TEST_F(ThreadedExecutorTest, many) {
  constexpr auto kNumTasks = 1024;
  folly::ThreadedExecutor x;
  auto rets =
      folly::collect(
          folly::gen::range<size_t>(0, kNumTasks) |
          folly::gen::map([&](size_t i) {
            return folly::via(&x)
                .thenValue([=](auto&&) { return i; })
                .thenValue([](size_t k) { return folly::to<std::string>(k); });
          }) |
          folly::gen::as<std::vector>())
          .get();

  EXPECT_EQ("42", rets[42]);
}

TEST_F(ThreadedExecutorTest, many_sleeping_constant_time) {
  constexpr auto kNumTasks = 256;
  constexpr auto kDelay = std::chrono::milliseconds(100);
  folly::ThreadedExecutor x;
  auto rets =
      folly::collect(
          folly::gen::range<size_t>(0, kNumTasks) |
          folly::gen::map([&](size_t i) {
            return folly::via(&x)
                .thenValue([=](auto&&) {
                  /* sleep override */ std::this_thread::sleep_for(kDelay);
                })
                .thenValue([=](auto&&) { return i; })
                .thenValue([](size_t k) { return folly::to<std::string>(k); });
          }) |
          folly::gen::as<std::vector>())
          .get();

  EXPECT_EQ("42", rets[42]);
}

TEST_F(ThreadedExecutorTest, many_sleeping_decreasing_time) {
  constexpr auto kNumTasks = 256;
  constexpr auto kDelay = std::chrono::milliseconds(100);
  folly::ThreadedExecutor x;
  auto rets =
      folly::collect(
          folly::gen::range<size_t>(0, kNumTasks) |
          folly::gen::map([&](size_t i) {
            return folly::via(&x)
                .thenValue([=](auto&&) {
                  auto delay = kDelay * (kNumTasks - i) / kNumTasks;
                  /* sleep override */ std::this_thread::sleep_for(delay);
                })
                .thenValue([=](auto&&) { return i; })
                .thenValue([](size_t k) { return folly::to<std::string>(k); });
          }) |
          folly::gen::as<std::vector>())
          .get();

  EXPECT_EQ("42", rets[42]);
}
