blob: 940d66fef7c0d1c8d1f34db3e3ee4b1784f6ba42 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COBALT_SRC_LIB_UTIL_TESTING_CLOCK_FAKES_H_
#define COBALT_SRC_LIB_UTIL_TESTING_CLOCK_FAKES_H_
#include <chrono>
#include <deque>
#include <functional>
#include <optional>
#include <vector>
#include "src/public/lib/clock_interfaces.h"
#include "src/public/lib/compat/clock.h"
#include "src/public/lib/statusor/statusor.h"
namespace cobalt::util::testing {
// A clock that returns an incrementing sequence of tics each time it is called. Optionally a
// callback may be set that will be invoked each time the clock ticks. Intended for use in tests
// only.
template <typename ClockInterface, typename Clock>
class IncrementingClock : public ClockInterface {
public:
IncrementingClock() = default;
// Constructs an IncrementingClock which increments by |increment| seconds each time it is called.
explicit IncrementingClock(Clock::duration increment) : increment_(increment) {}
Clock::time_point now() override {
time_ += increment_;
if (callback_) {
callback_(time_);
}
return time_;
}
// Return the current value of time_ without advancing time.
Clock::time_point peek_now() { return time_; }
// Returns the value that now() would return without advancing time. This is
// different than peek_now() which returns instead the last value that now()
// would have already returned that previous time it was invoked.
Clock::time_point peek_later() { return time_ + increment_; }
// Set the value by which the clock is incremented each time it is called.
void set_increment(Clock::duration increment) { increment_ = increment; }
// Increment the clock's current time once.
void increment_by(Clock::duration increment) {
time_ += increment;
if (callback_) {
callback_(time_);
}
}
void set_time(Clock::time_point t) { time_ = t; }
void set_callback(std::function<void(typename Clock::time_point)> c) { callback_ = std::move(c); }
private:
Clock::time_point time_ = typename Clock::time_point(typename Clock::duration(0));
Clock::duration increment_ = typename Clock::duration(1);
std::function<void(typename Clock::time_point)> callback_;
};
using IncrementingBootClock = IncrementingClock<BootClockInterface, compat::boot_clock>;
using IncrementingSteadyClock = IncrementingClock<SteadyClockInterface, std::chrono::steady_clock>;
using IncrementingSystemClock = IncrementingClock<SystemClockInterface, std::chrono::system_clock>;
// A wrapper for a SystemClockInterface that allows a test to control whether the clock is accurate.
class FakeValidatedClock : public util::ValidatedClockInterface {
public:
explicit FakeValidatedClock(SystemClockInterface* incrementing_clock)
: incrementing_clock_(incrementing_clock) {}
std::optional<std::chrono::system_clock::time_point> now() override {
bool accurate = accurate_sequence_.front();
if (accurate_sequence_.size() > 1) {
accurate_sequence_.pop_front();
}
if (accurate) {
return incrementing_clock_->now();
}
return std::nullopt;
}
void SetAccurate(bool accurate) { accurate_sequence_ = std::deque<bool>({accurate}); }
// Set the sequence of accurate/inaccurate responses this clock should return.
// If more calls are made than are in this sequence, the last item in the sequence continues to be
// returned.
void SetAccurate(const std::vector<bool>& accurate_sequence) {
accurate_sequence_ = std::deque<bool>(accurate_sequence.begin(), accurate_sequence.end());
}
private:
SystemClockInterface* incrementing_clock_;
std::deque<bool> accurate_sequence_{false};
};
// An implementation of CivilTimeConverterInterface for use in tests.
//
// The converter applies a specific UTC offset and DST flag. If a threshold time point and a second
// pair (UTC offset, DST flag) are provided, then the converter applies the first set of time zone
// parameters before the threshold time, and the second pair afterwards. This can be used to test
// the behavior of civil time consumers during time zone transitions, including DST transitions.
class FakeCivilTimeConverter : public util::CivilTimeConverterInterface {
public:
FakeCivilTimeConverter(int start_utc_offset, bool start_isdst, int end_utc_offset = 0,
bool end_isdst = false,
std::chrono::system_clock::time_point threshold =
std::chrono::system_clock::time_point::max())
: start_utc_offset_(start_utc_offset),
start_isdst_(start_isdst),
end_utc_offset_(end_utc_offset),
end_isdst_(end_isdst),
threshold_(threshold) {}
[[nodiscard]] lib::statusor::StatusOr<std::tm> civil_time(
std::chrono::system_clock::time_point time, const std::string& /*time_zone*/) const override {
int utc_offset = (time < threshold_ ? start_utc_offset_ : end_utc_offset_);
bool isdst = (time < threshold_ ? start_isdst_ : end_isdst_);
std::time_t time_t =
std::chrono::system_clock::to_time_t(time + std::chrono::hours(utc_offset));
std::tm time_struct;
gmtime_r(&time_t, &time_struct);
time_struct.tm_isdst = isdst;
return time_struct;
}
private:
int start_utc_offset_;
bool start_isdst_;
int end_utc_offset_;
bool end_isdst_;
std::chrono::system_clock::time_point threshold_;
};
} // namespace cobalt::util::testing
#endif // COBALT_SRC_LIB_UTIL_TESTING_CLOCK_FAKES_H_