/*
 * Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE file.
 */

#include "Clock.hpp"

#include <atomic>
#include <condition_variable>
#include <mutex>
#include <shared_mutex>
#include <tuple>
#include <type_traits>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include "ClockTestSupport.hpp"
#include "concurrent/ScopedThread.hpp"
#include "TestSupport.hpp"

using namespace kotlin;

TEST(ClockInternalTest, WaitUntilViaFor_Int_ImmediateOK) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    constexpr int timeoutValue = 42;
    constexpr int okValue = 13;
    testing::StrictMock<testing::MockFunction<int(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(okValue));
    }
    auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
    EXPECT_THAT(result, okValue);
}

TEST(ClockInternalTest, WaitUntilViaFor_Int_EventualOK) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    constexpr int timeoutValue = 42;
    constexpr int okValue = 13;
    testing::StrictMock<testing::MockFunction<int(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(okValue));
    }
    auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
    EXPECT_THAT(result, okValue);
}

TEST(ClockInternalTest, WaitUntilViaFor_Int_LastChanceOK) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    constexpr int timeoutValue = 42;
    constexpr int okValue = 13;
    testing::StrictMock<testing::MockFunction<int(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step));
        EXPECT_CALL(waitForF, Call(rest)).WillOnce(testing::Return(okValue));
    }
    auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
    EXPECT_THAT(result, okValue);
}

TEST(ClockInternalTest, WaitUntilViaFor_Int_Timeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    constexpr int timeoutValue = 42;
    testing::StrictMock<testing::MockFunction<int(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step));
        EXPECT_CALL(waitForF, Call(rest)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + rest));
    }
    auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
    EXPECT_THAT(result, timeoutValue);
}

TEST(ClockInternalTest, WaitUntilViaFor_Int_ImmediateTimeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    constexpr int timeoutValue = 42;
    testing::StrictMock<testing::MockFunction<int(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + step));
    }
    auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
    EXPECT_THAT(result, timeoutValue);
}

TEST(ClockInternalTest, WaitUntilViaFor_Int_ClockJumpTimeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    constexpr int timeoutValue = 42;
    testing::StrictMock<testing::MockFunction<int(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        // Instead of incrementing by `step`, the clock jumped straight to `until`.
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(until));
    }
    auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
    EXPECT_THAT(result, timeoutValue);
}

TEST(ClockInternalTest, WaitUntilViaFor_Int_NonconformantWaitTimeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    // waitFor non-conformingly waits less than specified.
    constexpr auto actualStep = std::chrono::seconds(7);
    constexpr auto rest = std::chrono::seconds(3);
    constexpr auto until = TimePoint() + step + step + rest;
    constexpr int timeoutValue = 42;
    testing::StrictMock<testing::MockFunction<int(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep));
        EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep));
        // Waited 2 * 7 out of 2 * 10 + 3 seconds. 9 seconds left. Will wait only 6 seconds.
        EXPECT_CALL(waitForF, Call(std::chrono::seconds(9))).WillOnce(testing::Return(timeoutValue));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(6)));
        EXPECT_CALL(waitForF, Call(std::chrono::seconds(3))).WillOnce(testing::Return(timeoutValue));
        // Finally waited enough.
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(9)));
    }
    auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
    EXPECT_THAT(result, timeoutValue);
}

TEST(ClockInternalTest, WaitUntilViaFor_Void_Timeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    testing::StrictMock<testing::MockFunction<void(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step));
        EXPECT_CALL(waitForF, Call(step));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step));
        EXPECT_CALL(waitForF, Call(rest));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + rest));
    }
    internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
}

TEST(ClockInternalTest, WaitUntilViaFor_Void_ImmediateTimeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    testing::StrictMock<testing::MockFunction<void(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + step));
    }
    internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
}

TEST(ClockInternalTest, WaitUntilViaFor_Void_ClockJumpTimeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    constexpr auto rest = std::chrono::seconds(1);
    constexpr auto until = TimePoint() + step + step + rest;
    testing::StrictMock<testing::MockFunction<void(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step));
        // Instead of incrementing by `step`, the clock jumped straight to `until`.
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(until));
    }
    internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
}

TEST(ClockInternalTest, WaitUntilViaFor_Void_NonconformantWaitTimeout) {
    using TimePoint = std::chrono::time_point<test_support::manual_clock>;
    testing::StrictMock<testing::MockFunction<TimePoint()>> nowF;
    constexpr auto step = std::chrono::seconds(10);
    // waitFor non-conformingly waits less than specified.
    constexpr auto actualStep = std::chrono::seconds(7);
    constexpr auto rest = std::chrono::seconds(3);
    constexpr auto until = TimePoint() + step + step + rest;
    testing::StrictMock<testing::MockFunction<void(std::chrono::seconds)>> waitForF;

    {
        testing::InSequence s;
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint()));
        EXPECT_CALL(waitForF, Call(step));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep));
        EXPECT_CALL(waitForF, Call(step));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep));
        // Waited 2 * 7 out of 2 * 10 + 3 seconds. 9 seconds left. Will wait only 6 seconds.
        EXPECT_CALL(waitForF, Call(std::chrono::seconds(9)));
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(6)));
        EXPECT_CALL(waitForF, Call(std::chrono::seconds(3)));
        // Finally waited enough.
        EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(9)));
    }
    internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) {
        return waitForF.Call(std::chrono::duration_cast<std::chrono::seconds>(interval));
    });
}

namespace {

class ClockTestNames {
public:
    template <typename T>
    static std::string GetName(int) {
        if constexpr (std::is_same_v<T, kotlin::steady_clock>) {
            return "steady_clock";
        } else if constexpr (std::is_same_v<T, kotlin::test_support::manual_clock>) {
            return "manual_clock";
        } else {
            return "unknown";
        }
    }
};

template <typename Clock>
struct WaitForPendingImpl {
    void operator()() {}
};

template <>
struct WaitForPendingImpl<test_support::manual_clock> {
    void operator()() {
        while (!test_support::manual_clock::pending()) {
        }
    }
};

template <typename Clock>
void waitForPending() {
    WaitForPendingImpl<Clock>()();
}

} // namespace

template <typename T>
class ClockTest : public testing::Test {
public:
    ClockTest() noexcept { test_support::manual_clock::reset(); }
};

using ClockTestTypes = testing::Types<kotlin::steady_clock, kotlin::test_support::manual_clock>;
TYPED_TEST_SUITE(ClockTest, ClockTestTypes, ClockTestNames);

TYPED_TEST(ClockTest, SleepFor) {
    constexpr auto interval = milliseconds(1);
    auto before = TypeParam::now();
    TypeParam::sleep_for(interval);
    auto after = TypeParam::now();
    EXPECT_THAT(after - before, testing::Ge(interval));
}

TYPED_TEST(ClockTest, SleepUntil) {
    auto until = TypeParam::now() + milliseconds(1);
    TypeParam::sleep_until(until);
    auto after = TypeParam::now();
    EXPECT_THAT(after, testing::Ge(until));
}

TYPED_TEST(ClockTest, CVWaitFor_OK) {
    constexpr auto interval = hours(10);
    std::condition_variable cv;
    std::mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(cv, guard, interval, [&] { return ok; });
    auto after = TypeParam::now();
    EXPECT_TRUE(result);
    EXPECT_THAT(after - before, testing::Lt(interval));
}

TYPED_TEST(ClockTest, CVWaitFor_Timeout) {
    constexpr auto interval = microseconds(10);
    std::condition_variable cv;
    std::mutex m;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    std::unique_lock guard(m);
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(cv, guard, interval, [] { return false; });
    auto after = TypeParam::now();
    EXPECT_FALSE(result);
    EXPECT_THAT(after - before, testing::Ge(interval));
}

TYPED_TEST(ClockTest, CVWaitFor_InfiniteTimeout) {
    std::condition_variable cv;
    std::mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_for` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    go = true;
    auto result = TypeParam::wait_for(cv, guard, microseconds::max(), [&] { return ok; });
    EXPECT_TRUE(result);
}

TYPED_TEST(ClockTest, CVWaitUntil_OK) {
    std::condition_variable cv;
    std::mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    auto until = TypeParam::now() + hours(10);
    go = true;
    auto result = TypeParam::wait_until(cv, guard, until, [&] { return ok; });
    auto after = TypeParam::now();
    EXPECT_TRUE(result);
    EXPECT_THAT(after, testing::Lt(until));
}

TYPED_TEST(ClockTest, CVWaitUntil_Timeout) {
    constexpr auto interval = microseconds(10);
    std::condition_variable cv;
    std::mutex m;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    std::unique_lock guard(m);
    auto until = TypeParam::now() + interval;
    go = true;
    auto result = TypeParam::wait_until(cv, guard, until, [] { return false; });
    auto after = TypeParam::now();
    EXPECT_FALSE(result);
    EXPECT_THAT(after, testing::Ge(until));
}

TYPED_TEST(ClockTest, CVWaitUntil_InfiniteTimeout) {
    std::condition_variable cv;
    std::mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_until` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    go = true;
    auto result = TypeParam::wait_until(cv, guard, TypeParam::time_point::max(), [&] { return ok; });
    EXPECT_TRUE(result);
}

TYPED_TEST(ClockTest, CVAnyWaitFor_OK) {
    constexpr auto interval = hours(10);
    std::condition_variable_any cv;
    std::shared_mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(cv, guard, interval, [&] { return ok; });
    auto after = TypeParam::now();
    EXPECT_TRUE(result);
    EXPECT_THAT(after - before, testing::Lt(interval));
}

TYPED_TEST(ClockTest, CVAnyWaitFor_Timeout) {
    constexpr auto interval = microseconds(10);
    std::condition_variable_any cv;
    std::shared_mutex m;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    std::unique_lock guard(m);
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(cv, guard, interval, [] { return false; });
    auto after = TypeParam::now();
    EXPECT_FALSE(result);
    EXPECT_THAT(after - before, testing::Ge(interval));
}

TYPED_TEST(ClockTest, CVAnyWaitFor_InfiniteTimeout) {
    std::condition_variable_any cv;
    std::shared_mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_for` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    go = true;
    auto result = TypeParam::wait_for(cv, guard, microseconds::max(), [&] { return ok; });
    EXPECT_TRUE(result);
}

TYPED_TEST(ClockTest, CVAnyWaitUntil_OK) {
    std::condition_variable_any cv;
    std::shared_mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    auto until = TypeParam::now() + hours(10);
    go = true;
    auto result = TypeParam::wait_until(cv, guard, until, [&] { return ok; });
    auto after = TypeParam::now();
    EXPECT_TRUE(result);
    EXPECT_THAT(after, testing::Lt(until));
}

TYPED_TEST(ClockTest, CVAnyWaitUntil_Timeout) {
    constexpr auto interval = microseconds(10);
    std::condition_variable_any cv;
    std::shared_mutex m;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    std::unique_lock guard(m);
    auto until = TypeParam::now() + interval;
    go = true;
    auto result = TypeParam::wait_until(cv, guard, until, [] { return false; });
    auto after = TypeParam::now();
    EXPECT_FALSE(result);
    EXPECT_THAT(after, testing::Ge(until));
}

TYPED_TEST(ClockTest, CVAnyWaitUntil_InfiniteTimeout) {
    std::condition_variable_any cv;
    std::shared_mutex m;
    bool ok = false;
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_until` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        {
            std::unique_lock guard(m);
            ok = true;
        }
        cv.notify_all();
    });
    std::unique_lock guard(m);
    go = true;
    auto result = TypeParam::wait_until(cv, guard, TypeParam::time_point::max(), [&] { return ok; });
    EXPECT_TRUE(result);
}

TYPED_TEST(ClockTest, FutureWaitFor_OK) {
    constexpr auto interval = hours(10);
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        promise.set_value(42);
    });
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(future, interval);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::ready);
    EXPECT_THAT(after - before, testing::Lt(interval));
}

TYPED_TEST(ClockTest, FutureWaitFor_Deferred) {
    constexpr auto interval = hours(10);
    std::future<int> future = std::async(std::launch::deferred, [] { return 42; });
    auto before = TypeParam::now();
    auto result = TypeParam::wait_for(future, interval);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::deferred);
    EXPECT_THAT(after - before, testing::Lt(interval));
}

TYPED_TEST(ClockTest, FutureWaitFor_Timeout) {
    constexpr auto interval = microseconds(10);
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(future, interval);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::timeout);
    EXPECT_THAT(after - before, testing::Ge(interval));
}

TYPED_TEST(ClockTest, FutureWaitFor_InfiniteTimeout) {
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_for` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        promise.set_value(42);
    });
    go = true;
    auto result = TypeParam::wait_for(future, microseconds::max());
    EXPECT_THAT(result, std::future_status::ready);
}

TYPED_TEST(ClockTest, FutureWaitUntil_OK) {
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        promise.set_value(42);
    });
    auto until = TypeParam::now() + hours(10);
    go = true;
    auto result = TypeParam::wait_until(future, until);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::ready);
    EXPECT_THAT(after, testing::Lt(until));
}

TYPED_TEST(ClockTest, FutureWaitUntil_Deferred) {
    std::future<int> future = std::async(std::launch::deferred, [] { return 42; });
    auto until = TypeParam::now() + hours(10);
    auto result = TypeParam::wait_until(future, until);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::deferred);
    EXPECT_THAT(after, testing::Lt(until));
}

TYPED_TEST(ClockTest, FutureWaitUntil_Timeout) {
    constexpr auto interval = microseconds(10);
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    auto until = TypeParam::now() + interval;
    go = true;
    auto result = TypeParam::wait_until(future, until);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::timeout);
    EXPECT_THAT(after, testing::Ge(until));
}

TYPED_TEST(ClockTest, FutureWaitUntil_InfiniteTimeout) {
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_until` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        promise.set_value(42);
    });
    go = true;
    auto result = TypeParam::wait_until(future, TypeParam::time_point::max());
    EXPECT_THAT(result, std::future_status::ready);
}

TYPED_TEST(ClockTest, SharedFutureWaitFor_OK) {
    constexpr auto interval = hours(10);
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        promise.set_value(42);
    });
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(future, interval);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::ready);
    EXPECT_THAT(after - before, testing::Lt(interval));
}

TYPED_TEST(ClockTest, SharedFutureWaitFor_Deferred) {
    constexpr auto interval = hours(10);
    std::shared_future<int> future = std::async(std::launch::deferred, [] { return 42; });
    auto before = TypeParam::now();
    auto result = TypeParam::wait_for(future, interval);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::deferred);
    EXPECT_THAT(after - before, testing::Lt(interval));
}

TYPED_TEST(ClockTest, SharedFutureWaitFor_Timeout) {
    constexpr auto interval = microseconds(10);
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    auto before = TypeParam::now();
    go = true;
    auto result = TypeParam::wait_for(future, interval);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::timeout);
    EXPECT_THAT(after - before, testing::Ge(interval));
}

TYPED_TEST(ClockTest, SharedFutureWaitFor_InfiniteTimeout) {
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_for` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        promise.set_value(42);
    });
    go = true;
    auto result = TypeParam::wait_for(future, microseconds::max());
    EXPECT_THAT(result, std::future_status::ready);
}

TYPED_TEST(ClockTest, SharedFutureWaitUntil_OK) {
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        promise.set_value(42);
    });
    auto until = TypeParam::now() + hours(10);
    go = true;
    auto result = TypeParam::wait_until(future, until);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::ready);
    EXPECT_THAT(after, testing::Lt(until));
}

TYPED_TEST(ClockTest, SharedFutureWaitUntil_Deferred) {
    std::shared_future<int> future = std::async(std::launch::deferred, [] { return 42; });
    auto until = TypeParam::now() + hours(10);
    auto result = TypeParam::wait_until(future, until);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::deferred);
    EXPECT_THAT(after, testing::Lt(until));
}

TYPED_TEST(ClockTest, SharedFutureWaitUntil_Timeout) {
    constexpr auto interval = microseconds(10);
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        TypeParam::sleep_for(interval);
    });
    auto until = TypeParam::now() + interval;
    go = true;
    auto result = TypeParam::wait_until(future, until);
    auto after = TypeParam::now();
    EXPECT_THAT(result, std::future_status::timeout);
    EXPECT_THAT(after, testing::Ge(until));
}

TYPED_TEST(ClockTest, SharedFutureWaitUntil_InfiniteTimeout) {
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    std::atomic<bool> go = false;
    ScopedThread thread([&] {
        while (!go.load()) {
        }
        waitForPending<TypeParam>();
        // Wait to see if `TypeParam::wait_until` wakes up from timeout.
        TypeParam::sleep_for(milliseconds(1));
        promise.set_value(42);
    });
    go = true;
    auto result = TypeParam::wait_until(future, TypeParam::time_point::max());
    EXPECT_THAT(result, std::future_status::ready);
}

namespace {

class ClockTypesTestNames {
public:
    template <typename T>
    static std::string GetName(int) {
        std::string result;
        result += clockName<T>();
        result += "_";
        result += durationName<T>();
        return result;
    }

private:
    template <typename T>
    static const char* clockName() {
        using Clock = typename std::tuple_element<0, T>::type;
        if constexpr (std::is_same_v<Clock, kotlin::steady_clock>) {
            return "steady_clock";
        } else if constexpr (std::is_same_v<Clock, kotlin::test_support::manual_clock>) {
            return "manual_clock";
        } else {
            return "unknown";
        }
    }

    template <typename T>
    static const char* durationName() {
        using Duration = typename std::tuple_element<1, T>::type;
        if constexpr (std::is_same_v<Duration, std::chrono::nanoseconds>) {
            return "ns";
        } else if constexpr (std::is_same_v<Duration, std::chrono::microseconds>) {
            return "us";
        } else if constexpr (std::is_same_v<Duration, std::chrono::milliseconds>) {
            return "ms";
        } else if constexpr (std::is_same_v<Duration, std::chrono::seconds>) {
            return "s";
        } else if constexpr (std::is_same_v<Duration, std::chrono::minutes>) {
            return "m";
        } else if constexpr (std::is_same_v<Duration, std::chrono::hours>) {
            return "h";
        } else if constexpr (std::is_same_v<Duration, kotlin::nanoseconds>) {
            return "sat_ns";
        } else if constexpr (std::is_same_v<Duration, kotlin::microseconds>) {
            return "sat_us";
        } else if constexpr (std::is_same_v<Duration, kotlin::milliseconds>) {
            return "sat_ms";
        } else if constexpr (std::is_same_v<Duration, kotlin::seconds>) {
            return "sat_s";
        } else if constexpr (std::is_same_v<Duration, kotlin::minutes>) {
            return "sat_m";
        } else if constexpr (std::is_same_v<Duration, kotlin::hours>) {
            return "sat_h";
        } else {
            return "unknown";
        }
    }
};

} // namespace

template <typename T>
class ClockTypesTest : public testing::Test {
public:
    using Clock = typename std::tuple_element<0, T>::type;
    using Duration = typename std::tuple_element<1, T>::type;
};

using ClockTypesTestTypes = testing::Types<
        std::tuple<kotlin::steady_clock, std::chrono::nanoseconds>,
        std::tuple<kotlin::steady_clock, std::chrono::microseconds>,
        std::tuple<kotlin::steady_clock, std::chrono::milliseconds>,
        std::tuple<kotlin::steady_clock, std::chrono::seconds>,
        std::tuple<kotlin::steady_clock, std::chrono::minutes>,
        std::tuple<kotlin::steady_clock, std::chrono::hours>,
        std::tuple<kotlin::steady_clock, kotlin::nanoseconds>,
        std::tuple<kotlin::steady_clock, kotlin::microseconds>,
        std::tuple<kotlin::steady_clock, kotlin::milliseconds>,
        std::tuple<kotlin::steady_clock, kotlin::seconds>,
        std::tuple<kotlin::steady_clock, kotlin::minutes>,
        std::tuple<kotlin::steady_clock, kotlin::hours>,
        std::tuple<kotlin::test_support::manual_clock, std::chrono::nanoseconds>,
        std::tuple<kotlin::test_support::manual_clock, std::chrono::microseconds>,
        std::tuple<kotlin::test_support::manual_clock, std::chrono::milliseconds>,
        std::tuple<kotlin::test_support::manual_clock, std::chrono::seconds>,
        std::tuple<kotlin::test_support::manual_clock, std::chrono::minutes>,
        std::tuple<kotlin::test_support::manual_clock, std::chrono::hours>,
        std::tuple<kotlin::test_support::manual_clock, kotlin::nanoseconds>,
        std::tuple<kotlin::test_support::manual_clock, kotlin::microseconds>,
        std::tuple<kotlin::test_support::manual_clock, kotlin::milliseconds>,
        std::tuple<kotlin::test_support::manual_clock, kotlin::seconds>,
        std::tuple<kotlin::test_support::manual_clock, kotlin::minutes>,
        std::tuple<kotlin::test_support::manual_clock, kotlin::hours>>;
TYPED_TEST_SUITE(ClockTypesTest, ClockTypesTestTypes, ClockTypesTestNames);

TYPED_TEST(ClockTypesTest, SleepFor) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<void, decltype(Clock::sleep_for(std::declval<Duration>()))>);
}

TYPED_TEST(ClockTypesTest, SleepUntil) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<void, decltype(Clock::sleep_until(std::declval<std::chrono::time_point<Clock, Duration>>()))>);
}

TYPED_TEST(ClockTypesTest, CVWaitFor) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  bool,
                  decltype(Clock::wait_for(
                          std::declval<std::condition_variable&>(), std::declval<std::unique_lock<std::mutex>&>(), std::declval<Duration>(),
                          std::declval<std::function<bool()>>()))>);
}

TYPED_TEST(ClockTypesTest, CVWaitUntil) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  bool,
                  decltype(Clock::wait_until(
                          std::declval<std::condition_variable&>(), std::declval<std::unique_lock<std::mutex>&>(),
                          std::declval<std::chrono::time_point<Clock, Duration>>(), std::declval<std::function<bool()>>()))>);
}

TYPED_TEST(ClockTypesTest, CVAnyWaitFor) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  bool,
                  decltype(Clock::wait_for(
                          std::declval<std::condition_variable_any&>(), std::declval<std::unique_lock<std::shared_mutex>&>(),
                          std::declval<Duration>(), std::declval<std::function<bool()>>()))>);
}

TYPED_TEST(ClockTypesTest, CVAnyWaitUntil) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  bool,
                  decltype(Clock::wait_until(
                          std::declval<std::condition_variable_any&>(), std::declval<std::unique_lock<std::shared_mutex>&>(),
                          std::declval<std::chrono::time_point<Clock, Duration>>(), std::declval<std::function<bool()>>()))>);
}

TYPED_TEST(ClockTypesTest, FutureWaitFor) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  std::future_status, decltype(Clock::wait_for(std::declval<const std::future<int>&>(), std::declval<Duration>()))>);
}

TYPED_TEST(ClockTypesTest, FutureWaitUntil) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  std::future_status,
                  decltype(Clock::wait_until(
                          std::declval<const std::future<int>&>(), std::declval<std::chrono::time_point<Clock, Duration>>()))>);
}

TYPED_TEST(ClockTypesTest, SharedFutureWaitFor) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  std::future_status, decltype(Clock::wait_for(std::declval<const std::shared_future<int>&>(), std::declval<Duration>()))>);
}

TYPED_TEST(ClockTypesTest, SharedFutureWaitUntil) {
    using Clock = typename ClockTypesTest<TypeParam>::Clock;
    using Duration = typename ClockTypesTest<TypeParam>::Duration;
    static_assert(std::is_same_v<
                  std::future_status,
                  decltype(Clock::wait_until(
                          std::declval<const std::shared_future<int>&>(), std::declval<std::chrono::time_point<Clock, Duration>>()))>);
}

TEST(ManualClockTest, SleepUntil) {
    test_support::manual_clock::reset();

    auto before = test_support::manual_clock::now();
    test_support::manual_clock::sleep_until(before + seconds(2));
    EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2));
    // Sleep until current time.
    test_support::manual_clock::sleep_until(before + seconds(2));
    EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2));
    // Sleep until moment in the past.
    test_support::manual_clock::sleep_until(before);
    EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2));
}

TEST(ManualClockTest, Pending) {
    test_support::manual_clock::reset();

    // Nothing pending at start.
    EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt);
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    ScopedThread thread([&] { test_support::manual_clock::wait_for(future, seconds(1)); });
    test_support::manual_clock::waitForPending(test_support::manual_clock::now() + seconds(1));

    // Unblocks the thread.
    test_support::manual_clock::sleep_for(seconds(1));
    thread.join();

    // Nothing pending anymore.
    EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt);
}

TEST(ManualClockTest, ConcurrentSleepUntil) {
    test_support::manual_clock::reset();

    constexpr auto threadCount = kDefaultThreadCount;
    std::vector<ScopedThread> threads;
    std::atomic<bool> run = false;
    std::atomic<int> ready = 0;
    for (int i = 0; i < threadCount; ++i) {
        threads.emplace_back([&, i] {
            auto now = test_support::manual_clock::now();
            ++ready;
            while (!run.load()) {
            }
            test_support::manual_clock::sleep_until(now + seconds(i));
        });
    }
    auto before = test_support::manual_clock::now();
    while (ready.load() < threadCount) {
    }
    run = true;
    threads.clear();
    auto after = test_support::manual_clock::now();
    EXPECT_THAT(after - before, seconds(threadCount - 1));
}

TEST(ManualClockTest, ConcurrentWaits) {
    test_support::manual_clock::reset();

    constexpr auto threadCount = kDefaultThreadCount;
    std::vector<ScopedThread> threads;
    std::mutex mutex;
    std::condition_variable cv;
    std::condition_variable_any cvAny;
    std::promise<int> promise1;
    std::promise<int> promise2;
    std::future<int> future = promise1.get_future();
    std::shared_future<int> shared_future = promise2.get_future().share();
    std::atomic<bool> run = false;
    std::atomic<int> ready = 0;
    for (int i = 0; i < threadCount; ++i) {
        threads.emplace_back([&, i] {
            auto now = test_support::manual_clock::now();
            ++ready;
            while (!run.load()) {
            }
            switch (i % 4) {
                case 0: {
                    std::unique_lock guard(mutex);
                    test_support::manual_clock::wait_until(cv, guard, now + seconds(i / 3 + 1), [] { return false; });
                }
                case 1: {
                    std::unique_lock guard(mutex);
                    test_support::manual_clock::wait_until(cvAny, guard, now + seconds(i / 3 + 1), [] { return false; });
                }
                case 2: {
                    test_support::manual_clock::wait_until(future, now + seconds(i / 3 + 1));
                }
                case 3: {
                    test_support::manual_clock::wait_until(shared_future, now + seconds(i / 3 + 1));
                }
            }
        });
    }
    auto before = test_support::manual_clock::now();
    while (ready.load() < threadCount) {
    }
    run = true;

    test_support::manual_clock::sleep_until(before + seconds(1));
    // Now the first 3 threads will be unblocked.
    threads[0].join();
    threads[1].join();
    threads[2].join();
    // Make sure at least one other thread is waiting.
    while (!test_support::manual_clock::pending()) {
    }
    auto pendingAfterSecond = *test_support::manual_clock::pending();
    EXPECT_THAT(pendingAfterSecond, testing::Ge(before + seconds(2)));

    // Unblock all the threads.
    test_support::manual_clock::sleep_until(before + seconds((threadCount - 1) / 3 + 1));
    threads.clear();
    // All threads are gone, nothing can possibly be pending.
    EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt);
}
