1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
/// Time source abstraction
///
/// Simple abstraction representing time either real-time or manually-specified for testing
///
/// # Examples
///
/// ```rust
/// # struct Client {
/// # // stub
/// # }
/// #
/// # impl Client {
/// # fn with_timesource(ts: TimeSource) -> Self {
/// # Client { }
/// # }
/// # }
/// use aws_credential_types::time_source::TimeSource;
/// let time = TimeSource::default();
/// let client = Client::with_timesource(time);
/// ```
#[derive(Debug, Clone)]
pub struct TimeSource(Inner);
impl TimeSource {
/// Creates `TimeSource` from the manually specified `time_source`.
pub fn testing(time_source: &TestingTimeSource) -> Self {
TimeSource(Inner::Testing(time_source.clone()))
}
/// Returns the current system time based on the mode.
pub fn now(&self) -> SystemTime {
match &self.0 {
Inner::Default => SystemTime::now(),
Inner::Testing(testing) => testing.now(),
}
}
}
impl Default for TimeSource {
/// Creates `TimeSource` from the current system time.
fn default() -> Self {
TimeSource(Inner::Default)
}
}
/// Time Source that can be manually moved for tests
///
/// # Examples
///
/// ```rust
/// # struct Client {
/// # // stub
/// # }
/// #
/// # impl Client {
/// # fn with_timesource(ts: TimeSource) -> Self {
/// # Client { }
/// # }
/// # }
/// use aws_credential_types::time_source::{TestingTimeSource, TimeSource};
/// use std::time::{UNIX_EPOCH, Duration};
/// let mut time = TestingTimeSource::new(UNIX_EPOCH);
/// let client = Client::with_timesource(TimeSource::testing(&time));
/// time.advance(Duration::from_secs(100));
/// ```
#[derive(Clone, Debug)]
pub struct TestingTimeSource {
queries: Arc<Mutex<Vec<SystemTime>>>,
now: Arc<Mutex<SystemTime>>,
}
impl TestingTimeSource {
/// Creates `TestingTimeSource` with `start_time`.
pub fn new(start_time: SystemTime) -> Self {
Self {
queries: Default::default(),
now: Arc::new(Mutex::new(start_time)),
}
}
/// Sets time to the specified `time`.
pub fn set_time(&mut self, time: SystemTime) {
let mut now = self.now.lock().unwrap();
*now = time;
}
/// Advances time by `delta`.
pub fn advance(&mut self, delta: Duration) {
let mut now = self.now.lock().unwrap();
*now += delta;
}
/// Returns a `Vec` of queried times so far.
pub fn queries(&self) -> impl Deref<Target = Vec<SystemTime>> + '_ {
self.queries.lock().unwrap()
}
/// Returns the current time understood by `TestingTimeSource`.
pub fn now(&self) -> SystemTime {
let ts = *self.now.lock().unwrap();
self.queries.lock().unwrap().push(ts);
ts
}
}
// In the future, if needed we can add a time source trait, however, the testing time source
// should cover most test use cases.
#[derive(Debug, Clone)]
enum Inner {
Default,
Testing(TestingTimeSource),
}
#[cfg(test)]
mod test {
use super::{TestingTimeSource, TimeSource};
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn default_time_source_should_not_panic_on_calling_now() {
let time_source = TimeSource::default();
// no panics
let _ = time_source.now();
}
#[test]
fn testing_time_source_should_behave_as_expected() {
let mut testing = TestingTimeSource::new(UNIX_EPOCH);
let time_source = TimeSource::testing(&testing);
assert_eq!(time_source.now(), UNIX_EPOCH);
testing.advance(Duration::from_secs(10));
assert_eq!(time_source.now(), UNIX_EPOCH + Duration::from_secs(10));
}
}