[K/N] Add mockable clock. ^KT-48537
This commit is contained in:
committed by
Space
parent
e6b7a142b2
commit
eae9d05f50
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "Saturating.hpp"
|
||||
|
||||
namespace kotlin {
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename NowF, typename StepDuration, typename Clock, typename Rep, typename Period, typename Ret, typename WaitForF>
|
||||
Ret waitUntilViaFor(
|
||||
NowF&& nowF,
|
||||
StepDuration step,
|
||||
std::chrono::time_point<Clock, std::chrono::duration<saturating<Rep>, Period>> until,
|
||||
Ret timeoutValue,
|
||||
WaitForF&& waitForF) {
|
||||
while (true) {
|
||||
auto current = std::invoke(std::forward<NowF>(nowF));
|
||||
if (current >= until) {
|
||||
return timeoutValue;
|
||||
}
|
||||
auto left = until - current;
|
||||
// Shield standard library from saturating types.
|
||||
auto interval = left > step ? std::chrono::duration<Rep, Period>(step) : std::chrono::duration<Rep, Period>(left);
|
||||
if (auto value = std::invoke(std::forward<WaitForF>(waitForF), interval); value != timeoutValue) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename NowF, typename StepDuration, typename Clock, typename Rep, typename Period, typename WaitForF>
|
||||
void waitUntilViaFor(
|
||||
NowF&& nowF,
|
||||
StepDuration step,
|
||||
std::chrono::time_point<Clock, std::chrono::duration<saturating<Rep>, Period>> until,
|
||||
WaitForF&& waitForF) {
|
||||
while (true) {
|
||||
auto current = std::invoke(std::forward<NowF>(nowF));
|
||||
if (current >= until) {
|
||||
return;
|
||||
}
|
||||
auto left = until - current;
|
||||
// Shield standard library from saturating types.
|
||||
auto interval = left > step ? std::chrono::duration<Rep, Period>(step) : std::chrono::duration<Rep, Period>(left);
|
||||
std::invoke(std::forward<WaitForF>(waitForF), interval);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Lock>
|
||||
struct IsStdCV : public std::false_type {};
|
||||
|
||||
template <>
|
||||
struct IsStdCV<std::condition_variable, std::unique_lock<std::mutex>> : public std::true_type {};
|
||||
|
||||
template <typename Lock>
|
||||
struct IsStdCV<std::condition_variable_any, Lock> : public std::true_type {};
|
||||
|
||||
template <typename T, typename Lock>
|
||||
inline constexpr bool isStdCV = IsStdCV<T, Lock>::value;
|
||||
|
||||
template <typename T>
|
||||
struct IsStdFuture : public std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct IsStdFuture<std::future<T>> : public std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct IsStdFuture<std::shared_future<T>> : public std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
inline constexpr bool isStdFuture = IsStdFuture<T>::value;
|
||||
|
||||
template <typename Clock>
|
||||
class ClockWaitImpl {
|
||||
public:
|
||||
template <typename Rep, typename Period>
|
||||
static void sleep_for(std::chrono::duration<Rep, Period> interval) {
|
||||
// Not using this_thread::sleep_for, because it may mishandle "infinite" intervals. Use saturating arithmetics to address this.
|
||||
return ClockWaitImpl<Clock>::sleep_until(Clock::now() + interval);
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
static void sleep_until(std::chrono::time_point<Clock, std::chrono::duration<Rep, Period>> until) {
|
||||
if constexpr (is_saturating_v<Rep>) {
|
||||
return Clock::sleepImpl(until);
|
||||
} else {
|
||||
return ClockWaitImpl<Clock>::sleep_until(std::chrono::time_point<Clock, std::chrono::duration<saturating<Rep>, Period>>(until));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CV, typename Lock, typename Rep, typename Period, typename F, typename = std::enable_if_t<isStdCV<CV, Lock>>>
|
||||
static bool wait_for(CV& cv, Lock& lock, std::chrono::duration<Rep, Period> interval, F&& f) {
|
||||
// Not using cv.wait_for, because it may mishandle "infinite" intervals. Use saturating arithmetics to address this.
|
||||
return ClockWaitImpl<Clock>::wait_until(cv, lock, Clock::now() + interval, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename CV, typename Lock, typename Rep, typename Period, typename F, typename = std::enable_if_t<isStdCV<CV, Lock>>>
|
||||
static bool wait_until(CV& cv, Lock& lock, std::chrono::time_point<Clock, std::chrono::duration<Rep, Period>> until, F&& f) {
|
||||
if constexpr (is_saturating_v<Rep>) {
|
||||
[[maybe_unused]] auto pendingWait = Clock::addPendingWait(until);
|
||||
// Implement in terms of repeated cv.wait_for of non-"infinite" intervals.
|
||||
return internal::waitUntilViaFor(&Clock::now, Clock::wait_step, until, false, [&](auto interval) {
|
||||
return cv.wait_for(lock, interval, std::forward<F>(f));
|
||||
});
|
||||
} else {
|
||||
return ClockWaitImpl<Clock>::wait_until(
|
||||
cv, lock, std::chrono::time_point<Clock, std::chrono::duration<saturating<Rep>, Period>>(until), std::forward<F>(f));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Future, typename Rep, typename Period, typename = std::enable_if_t<isStdFuture<Future>>>
|
||||
static std::future_status wait_for(const Future& future, std::chrono::duration<Rep, Period> interval) {
|
||||
// Not using future.wait_for, because it may mishandle "infinite" intervals. Use saturating arithmetics to address this.
|
||||
return ClockWaitImpl<Clock>::wait_until(future, Clock::now() + interval);
|
||||
}
|
||||
|
||||
template <typename Future, typename Rep, typename Period, typename = std::enable_if_t<isStdFuture<Future>>>
|
||||
static std::future_status wait_until(const Future& future, std::chrono::time_point<Clock, std::chrono::duration<Rep, Period>> until) {
|
||||
if constexpr (is_saturating_v<Rep>) {
|
||||
[[maybe_unused]] auto pendingWait = Clock::addPendingWait(until);
|
||||
// Implement in terms of repeated future.wait_for of non-"infinite" intervals.
|
||||
return internal::waitUntilViaFor(&Clock::now, Clock::wait_step, until, std::future_status::timeout, [&](auto interval) {
|
||||
return future.wait_for(interval);
|
||||
});
|
||||
} else {
|
||||
return ClockWaitImpl<Clock>::wait_until(
|
||||
future, std::chrono::time_point<Clock, std::chrono::duration<saturating<Rep>, Period>>(until));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
using nanoseconds = std::chrono::duration<saturating<std::chrono::nanoseconds::rep>, std::chrono::nanoseconds::period>;
|
||||
using microseconds = std::chrono::duration<saturating<std::chrono::microseconds::rep>, std::chrono::microseconds::period>;
|
||||
using milliseconds = std::chrono::duration<saturating<std::chrono::milliseconds::rep>, std::chrono::milliseconds::period>;
|
||||
using seconds = std::chrono::duration<saturating<std::chrono::seconds::rep>, std::chrono::seconds::period>;
|
||||
using minutes = std::chrono::duration<saturating<std::chrono::minutes::rep>, std::chrono::minutes::period>;
|
||||
using hours = std::chrono::duration<saturating<std::chrono::hours::rep>, std::chrono::hours::period>;
|
||||
|
||||
class steady_clock : public internal::ClockWaitImpl<steady_clock> {
|
||||
public:
|
||||
using rep = saturating<std::chrono::steady_clock::rep>;
|
||||
using period = std::chrono::steady_clock::period;
|
||||
using duration = std::chrono::duration<rep, period>;
|
||||
using time_point = std::chrono::time_point<steady_clock>;
|
||||
|
||||
static constexpr bool is_steady = true;
|
||||
|
||||
static time_point now() noexcept {
|
||||
auto time = std::chrono::steady_clock::now().time_since_epoch();
|
||||
return time_point(time);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class internal::ClockWaitImpl<steady_clock>;
|
||||
|
||||
// Use non-saturating type here, because step may be fed into the standard library.
|
||||
static inline constexpr auto wait_step = std::chrono::hours(24);
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
static void sleepImpl(std::chrono::time_point<steady_clock, std::chrono::duration<saturating<Rep>, Period>> until) {
|
||||
// Implement in terms of repeated this_thread::sleep_for of non-"infinite" intervals.
|
||||
return internal::waitUntilViaFor(&now, wait_step, until, [&](auto interval) { std::this_thread::sleep_for(interval); });
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
static int addPendingWait(std::chrono::time_point<steady_clock, std::chrono::duration<saturating<Rep>, Period>> until) {
|
||||
// No need to register here.
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace kotlin
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 "ClockTestSupport.hpp"
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
// static
|
||||
std::atomic<test_support::manual_clock::time_point> test_support::manual_clock::now_ = test_support::manual_clock::time_point::min();
|
||||
|
||||
// static
|
||||
std::mutex test_support::manual_clock::pendingWaitsMutex_;
|
||||
|
||||
// static
|
||||
KStdOrderedMultiset<test_support::manual_clock::time_point> test_support::manual_clock::pendingWaits_;
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Clock.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "Types.h"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace kotlin::test_support {
|
||||
|
||||
// Clock that is manually advanced (cannot go backwards). For testing purposes.
|
||||
// TODO: Should be reset at the start of each test.
|
||||
class manual_clock : public internal::ClockWaitImpl<manual_clock> {
|
||||
public:
|
||||
using duration = nanoseconds;
|
||||
using rep = duration::rep;
|
||||
using period = duration::period;
|
||||
using time_point = std::chrono::time_point<manual_clock>;
|
||||
|
||||
// Steady because it cannot go backwards.
|
||||
static constexpr bool is_steady = true;
|
||||
|
||||
static time_point now() noexcept { return now_.load(); }
|
||||
|
||||
static std::optional<time_point> pending() noexcept {
|
||||
std::unique_lock guard(pendingWaitsMutex_);
|
||||
auto it = pendingWaits_.begin();
|
||||
if (it == pendingWaits_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
|
||||
template <typename Duration, typename Rep, typename Period>
|
||||
static void waitForPending(
|
||||
std::chrono::time_point<manual_clock, Duration> expectedPending, std::chrono::duration<Rep, Period> timeout) {
|
||||
// Use real clock for timeout.
|
||||
auto until = std::chrono::steady_clock::now() + timeout;
|
||||
while (!pending() || *pending() != expectedPending) {
|
||||
if (std::chrono::steady_clock::now() > until) {
|
||||
EXPECT_THAT(pending(), std::make_optional(expectedPending));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
static void waitForPending(std::chrono::time_point<manual_clock, Duration> expectedPending) {
|
||||
waitForPending(expectedPending, std::chrono::minutes(5));
|
||||
}
|
||||
|
||||
// Should be done before the timer is used (in the beginning of the test, for example).
|
||||
static void reset(time_point start = time_point::min()) noexcept {
|
||||
std::unique_lock guard(pendingWaitsMutex_);
|
||||
RuntimeAssert(pendingWaits_.empty(), "To reset, there must not be any pending waits");
|
||||
now_ = start;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class internal::ClockWaitImpl<manual_clock>;
|
||||
|
||||
// Use non-saturating type here, because step may be fed into the standard library.
|
||||
static inline constexpr auto wait_step = std::chrono::microseconds(1);
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
static void sleepImpl(std::chrono::time_point<manual_clock, std::chrono::duration<saturating<Rep>, Period>> until) {
|
||||
time_point before = now();
|
||||
while (before < until) {
|
||||
now_.compare_exchange_weak(before, until);
|
||||
}
|
||||
}
|
||||
|
||||
class PendingWaitRegistration : private Pinned {
|
||||
public:
|
||||
~PendingWaitRegistration() noexcept {
|
||||
std::unique_lock guard(pendingWaitsMutex_);
|
||||
pendingWaits_.erase(it_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class manual_clock;
|
||||
|
||||
explicit PendingWaitRegistration(KStdOrderedMultiset<time_point>::iterator it) noexcept : it_(it) {}
|
||||
|
||||
KStdOrderedMultiset<time_point>::iterator it_;
|
||||
};
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
static PendingWaitRegistration addPendingWait(
|
||||
std::chrono::time_point<manual_clock, std::chrono::duration<saturating<Rep>, Period>> until) {
|
||||
std::unique_lock guard(pendingWaitsMutex_);
|
||||
auto it = pendingWaits_.insert(until);
|
||||
return PendingWaitRegistration(it);
|
||||
}
|
||||
|
||||
static std::atomic<time_point> now_;
|
||||
static std::mutex pendingWaitsMutex_;
|
||||
static KStdOrderedMultiset<time_point> pendingWaits_;
|
||||
};
|
||||
|
||||
} // namespace kotlin::test_support
|
||||
Reference in New Issue
Block a user