Remove vestiges of the old TimeSource implementation & refactor (#2877)

## Motivation and Context
When `TimeSource` was created, we didn't carefully track down all the
places and left a bit of existing implementation to limit the scope of
the change.
## Description
This removes the public (doc hidden) Testing code from
aws-credential-types and our other test time sources instead.


## Testing
- IT/UT

## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the
smithy-rs codegen or runtime crates
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS
SDK, generated SDK code, or SDK runtime crates

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._

---------

Co-authored-by: John DiSanti <jdisanti@amazon.com>
This commit is contained in:
Russell Cohen 2023-07-26 19:51:36 -04:00 committed by GitHub
parent 5d0887034e
commit 9a4156de04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 131 additions and 238 deletions

View File

@ -686,3 +686,15 @@ x-amzn-errortype: InvalidRequestException
references = ["smithy-rs#2866"]
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "server" }
author = "david-perez"
[[aws-sdk-rust]]
message = """The `doc(hidden)` `time_source` in `aws-credential-types` was removed. Use `aws_smithy_async::time` instead."""
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "rcoh"
references = ["smithy-rs#2877"]
[[aws-sdk-rust]]
message = """The `doc(hidden)` `with_env` in `ProviderConfig` was removed."""
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "rcoh"
references = ["smithy-rs#2877"]

View File

@ -66,6 +66,7 @@ aws-smithy-client = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-client", fe
# used for a usage example
hyper-rustls = { version = "0.24", features = ["webpki-tokio", "http2", "http1"] }
aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] }
[package.metadata.docs.rs]
all-features = true

View File

@ -199,6 +199,8 @@ impl Builder {
#[cfg(test)]
mod test {
use aws_credential_types::provider::ProvideCredentials;
use aws_smithy_async::time::StaticTimeSource;
use std::time::UNIX_EPOCH;
use crate::default_provider::credentials::DefaultCredentialsChain;
@ -279,21 +281,15 @@ mod test {
make_test!(imds_no_iam_role);
make_test!(imds_default_chain_error);
make_test!(imds_default_chain_success, builder: |config| {
config.with_time_source(aws_credential_types::time_source::TimeSource::testing(
&aws_credential_types::time_source::TestingTimeSource::new(std::time::UNIX_EPOCH),
))
config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
});
make_test!(imds_assume_role);
make_test!(imds_config_with_no_creds, builder: |config| {
config.with_time_source(aws_credential_types::time_source::TimeSource::testing(
&aws_credential_types::time_source::TestingTimeSource::new(std::time::UNIX_EPOCH),
))
config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
});
make_test!(imds_disabled);
make_test!(imds_default_chain_retries, builder: |config| {
config.with_time_source(aws_credential_types::time_source::TimeSource::testing(
&aws_credential_types::time_source::TestingTimeSource::new(std::time::UNIX_EPOCH),
))
config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
});
make_test!(ecs_assume_role);
make_test!(ecs_credentials);
@ -335,7 +331,6 @@ mod test {
async fn no_providers_configured_err() {
use crate::provider_config::ProviderConfig;
use aws_credential_types::provider::error::CredentialsError;
use aws_credential_types::time_source::TimeSource;
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_client::erase::boxclone::BoxCloneService;
use aws_smithy_client::never::NeverConnected;
@ -343,7 +338,7 @@ mod test {
tokio::time::pause();
let conf = ProviderConfig::no_configuration()
.with_tcp_connector(BoxCloneService::new(NeverConnected::new()))
.with_time_source(TimeSource::default())
.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
.with_sleep(TokioSleep::new());
let provider = DefaultCredentialsChain::builder()
.configure(conf)

View File

@ -571,8 +571,8 @@ impl<T, E> ClassifyRetry<SdkSuccess<T>, SdkError<E>> for ImdsResponseRetryClassi
pub(crate) mod test {
use crate::imds::client::{Client, EndpointMode, ImdsResponseRetryClassifier};
use crate::provider_config::ProviderConfig;
use aws_credential_types::time_source::{TestingTimeSource, TimeSource};
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_async::test_util::instant_time_and_sleep;
use aws_smithy_client::erase::DynConnector;
use aws_smithy_client::test_connection::{capture_request, TestConnection};
use aws_smithy_client::{SdkError, SdkSuccess};
@ -700,14 +700,13 @@ pub(crate) mod test {
imds_response(r#"test-imds-output2"#),
),
]);
let mut time_source = TestingTimeSource::new(UNIX_EPOCH);
tokio::time::pause();
let (time_source, sleep) = instant_time_and_sleep(UNIX_EPOCH);
let client = super::Client::builder()
.configure(
&ProviderConfig::no_configuration()
.with_http_connector(DynConnector::new(connection.clone()))
.with_time_source(TimeSource::testing(&time_source))
.with_sleep(TokioSleep::new()),
.with_time_source(time_source.clone())
.with_sleep(sleep),
)
.endpoint_mode(EndpointMode::IpV6)
.token_ttl(Duration::from_secs(600))
@ -752,14 +751,13 @@ pub(crate) mod test {
imds_response(r#"test-imds-output3"#),
),
]);
tokio::time::pause();
let mut time_source = TestingTimeSource::new(UNIX_EPOCH);
let (time_source, sleep) = instant_time_and_sleep(UNIX_EPOCH);
let client = super::Client::builder()
.configure(
&ProviderConfig::no_configuration()
.with_sleep(TokioSleep::new())
.with_sleep(sleep)
.with_http_connector(DynConnector::new(connection.clone()))
.with_time_source(TimeSource::testing(&time_source)),
.with_time_source(time_source.clone()),
)
.endpoint_mode(EndpointMode::IpV6)
.token_ttl(Duration::from_secs(600))

View File

@ -306,8 +306,7 @@ mod test {
};
use crate::provider_config::ProviderConfig;
use aws_credential_types::provider::ProvideCredentials;
use aws_credential_types::time_source::{TestingTimeSource, TimeSource};
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_async::test_util::instant_time_and_sleep;
use aws_smithy_client::erase::DynConnector;
use aws_smithy_client::test_connection::TestConnection;
use tracing_test::traced_test;
@ -369,16 +368,12 @@ mod test {
// set to 2021-09-21T04:16:50Z that makes returned credentials' expiry (2021-09-21T04:16:53Z)
// not stale
let time_of_request_to_fetch_credentials = UNIX_EPOCH + Duration::from_secs(1632197810);
let time_source = TimeSource::testing(&TestingTimeSource::new(
time_of_request_to_fetch_credentials,
));
tokio::time::pause();
let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials);
let provider_config = ProviderConfig::no_configuration()
.with_http_connector(DynConnector::new(connection.clone()))
.with_time_source(time_source)
.with_sleep(TokioSleep::new());
.with_sleep(sleep)
.with_time_source(time_source);
let client = crate::imds::Client::builder()
.configure(&provider_config)
.build()
@ -419,16 +414,12 @@ mod test {
// set to 2021-09-21T17:41:25Z that renders fetched credentials already expired (2021-09-21T04:16:53Z)
let time_of_request_to_fetch_credentials = UNIX_EPOCH + Duration::from_secs(1632246085);
let time_source = TimeSource::testing(&TestingTimeSource::new(
time_of_request_to_fetch_credentials,
));
tokio::time::pause();
let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials);
let provider_config = ProviderConfig::no_configuration()
.with_http_connector(DynConnector::new(connection.clone()))
.with_time_source(time_source)
.with_sleep(TokioSleep::new());
.with_sleep(sleep)
.with_time_source(time_source);
let client = crate::imds::Client::builder()
.configure(&provider_config)
.build()

View File

@ -620,9 +620,8 @@ mod loader {
let credentials_cache = if credentials_provider.is_some() {
Some(self.credentials_cache.unwrap_or_else(|| {
let mut builder = CredentialsCache::lazy_builder().time_source(
aws_credential_types::time_source::TimeSource::shared(conf.time_source()),
);
let mut builder =
CredentialsCache::lazy_builder().time_source(conf.time_source());
builder.set_sleep(conf.sleep());
builder.into_credentials_cache()
}))

View File

@ -5,7 +5,6 @@
//! Configuration Options for Credential Providers
use aws_credential_types::time_source::TimeSource;
use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
use aws_smithy_async::time::SharedTimeSource;
use aws_smithy_client::erase::DynConnector;
@ -282,7 +281,7 @@ impl ProviderConfig {
}
}
#[doc(hidden)]
#[cfg(test)]
pub fn with_env(self, env: Env) -> Self {
ProviderConfig {
parsed_profile: Default::default(),
@ -291,8 +290,11 @@ impl ProviderConfig {
}
}
#[doc(hidden)]
pub fn with_time_source(self, time_source: TimeSource) -> Self {
/// Override the time source for this configuration
pub fn with_time_source(
self,
time_source: impl aws_smithy_async::time::TimeSource + 'static,
) -> Self {
ProviderConfig {
time_source: SharedTimeSource::new(time_source),
..self

View File

@ -291,9 +291,10 @@ mod test {
use crate::sts::AssumeRoleProvider;
use aws_credential_types::credential_fn::provide_credentials_fn;
use aws_credential_types::provider::ProvideCredentials;
use aws_credential_types::time_source::{TestingTimeSource, TimeSource};
use aws_credential_types::Credentials;
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_async::test_util::instant_time_and_sleep;
use aws_smithy_async::time::StaticTimeSource;
use aws_smithy_client::erase::DynConnector;
use aws_smithy_client::test_connection::{capture_request, TestConnection};
use aws_smithy_http::body::SdkBody;
@ -305,9 +306,9 @@ mod test {
let (server, request) = capture_request(None);
let provider_conf = ProviderConfig::empty()
.with_sleep(TokioSleep::new())
.with_time_source(TimeSource::testing(&TestingTimeSource::new(
.with_time_source(StaticTimeSource::new(
UNIX_EPOCH + Duration::from_secs(1234567890 - 120),
)))
))
.with_http_connector(DynConnector::new(server));
let provider = AssumeRoleProvider::builder("myrole")
.configure(&provider_conf)
@ -335,13 +336,13 @@ mod test {
)).unwrap()),
]);
let mut testing_time_source = TestingTimeSource::new(
let (testing_time_source, sleep) = instant_time_and_sleep(
UNIX_EPOCH + Duration::from_secs(1234567890 - 120), // 1234567890 since UNIX_EPOCH is 2009-02-13T23:31:30Z
);
let provider_conf = ProviderConfig::empty()
.with_sleep(TokioSleep::new())
.with_time_source(TimeSource::testing(&testing_time_source))
.with_sleep(sleep)
.with_time_source(testing_time_source.clone())
.with_http_connector(DynConnector::new(conn));
let credentials_list = std::sync::Arc::new(std::sync::Mutex::new(vec![
Credentials::new(
@ -371,8 +372,6 @@ mod test {
}
}));
tokio::time::pause();
let creds_first = provider
.provide_credentials()
.await

View File

@ -20,7 +20,7 @@ tracing = "0.1"
zeroize = "1"
[dev-dependencies]
aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async", features = ["rt-tokio"] }
aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async", features = ["rt-tokio", "test-util"] }
# used to test compatibility
async-trait = "0.1.51"

View File

@ -5,16 +5,16 @@
//! Lazy, credentials cache implementation
use std::time::{Duration, Instant};
use std::time::Duration;
use aws_smithy_async::future::timeout::Timeout;
use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
use aws_smithy_async::time::SharedTimeSource;
use tracing::{debug, info, info_span, Instrument};
use crate::cache::{ExpiringCache, ProvideCachedCredentials};
use crate::provider::SharedCredentialsProvider;
use crate::provider::{error::CredentialsError, future, ProvideCredentials};
use crate::time_source::TimeSource;
const DEFAULT_LOAD_TIMEOUT: Duration = Duration::from_secs(5);
const DEFAULT_CREDENTIAL_EXPIRATION: Duration = Duration::from_secs(15 * 60);
@ -23,7 +23,7 @@ const DEFAULT_BUFFER_TIME_JITTER_FRACTION: fn() -> f64 = fastrand::f64;
#[derive(Debug)]
pub(crate) struct LazyCredentialsCache {
time: TimeSource,
time: SharedTimeSource,
sleeper: SharedAsyncSleep,
cache: ExpiringCache<Credentials, CredentialsError>,
provider: SharedCredentialsProvider,
@ -35,7 +35,7 @@ pub(crate) struct LazyCredentialsCache {
impl LazyCredentialsCache {
fn new(
time: TimeSource,
time: SharedTimeSource,
sleeper: SharedAsyncSleep,
provider: SharedCredentialsProvider,
load_timeout: Duration,
@ -79,7 +79,7 @@ impl ProvideCachedCredentials for LazyCredentialsCache {
// since the futures are not eagerly executed, and the cache will only run one
// of them.
let future = Timeout::new(provider.provide_credentials(), timeout_future);
let start_time = Instant::now();
let start_time = self.time.now();
let result = cache
.get_or_load(|| {
let span = info_span!("lazy_load_credentials");
@ -111,14 +111,14 @@ impl ProvideCachedCredentials for LazyCredentialsCache {
// only once for the first thread that succeeds in populating a cache value.
info!(
"credentials cache miss occurred; added new AWS credentials (took {:?})",
start_time.elapsed()
self.time.now().duration_since(start_time)
);
Ok((credentials, expiry + jitter))
}
// Only instrument the the actual load future so that no span
// is opened if the cache decides not to execute it.
.instrument(span)
// Only instrument the the actual load future so that no span
// is opened if the cache decides not to execute it.
.instrument(span)
})
.await;
debug!("loaded credentials");
@ -137,8 +137,8 @@ mod builder {
use crate::cache::{CredentialsCache, Inner};
use crate::provider::SharedCredentialsProvider;
use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep};
use aws_smithy_async::time::SharedTimeSource;
use super::TimeSource;
use super::{
LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_BUFFER_TIME_JITTER_FRACTION,
DEFAULT_CREDENTIAL_EXPIRATION, DEFAULT_LOAD_TIMEOUT,
@ -159,7 +159,7 @@ mod builder {
#[derive(Clone, Debug, Default)]
pub struct Builder {
sleep: Option<SharedAsyncSleep>,
time_source: Option<TimeSource>,
time_source: Option<SharedTimeSource>,
load_timeout: Option<Duration>,
buffer_time: Option<Duration>,
buffer_time_jitter_fraction: Option<fn() -> f64>,
@ -193,13 +193,13 @@ mod builder {
}
#[doc(hidden)] // because they only exist for tests
pub fn time_source(mut self, time_source: TimeSource) -> Self {
pub fn time_source(mut self, time_source: SharedTimeSource) -> Self {
self.set_time_source(Some(time_source));
self
}
#[doc(hidden)] // because they only exist for tests
pub fn set_time_source(&mut self, time_source: Option<TimeSource>) -> &mut Self {
pub fn set_time_source(&mut self, time_source: Option<SharedTimeSource>) -> &mut Self {
self.time_source = time_source;
self
}
@ -346,37 +346,45 @@ mod tests {
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep};
use aws_smithy_async::test_util::{instant_time_and_sleep, ManualTimeSource};
use aws_smithy_async::time::{SharedTimeSource, TimeSource};
use tracing::info;
use tracing_test::traced_test;
use crate::provider::SharedCredentialsProvider;
use crate::{
cache::ProvideCachedCredentials, credential_fn::provide_credentials_fn,
provider::error::CredentialsError, time_source::TestingTimeSource, Credentials,
provider::error::CredentialsError, Credentials,
};
use super::{
LazyCredentialsCache, TimeSource, DEFAULT_BUFFER_TIME, DEFAULT_CREDENTIAL_EXPIRATION,
LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_CREDENTIAL_EXPIRATION,
DEFAULT_LOAD_TIMEOUT,
};
const BUFFER_TIME_NO_JITTER: fn() -> f64 = || 0_f64;
fn test_provider(
time: TimeSource,
time: impl TimeSource + 'static,
buffer_time_jitter_fraction: fn() -> f64,
load_list: Vec<crate::provider::Result>,
) -> LazyCredentialsCache {
let load_list = Arc::new(Mutex::new(load_list));
LazyCredentialsCache::new(
time,
SharedTimeSource::new(time),
SharedAsyncSleep::new(TokioSleep::new()),
SharedCredentialsProvider::new(provide_credentials_fn(move || {
let list = load_list.clone();
async move {
let next = list.lock().unwrap().remove(0);
info!("refreshing the credentials to {:?}", next);
next
let mut list = list.lock().unwrap();
if list.len() > 0 {
let next = list.remove(0);
info!("refreshing the credentials to {:?}", next);
next
} else {
drop(list);
panic!("no more credentials")
}
}
})),
DEFAULT_LOAD_TIMEOUT,
@ -405,13 +413,13 @@ mod tests {
#[traced_test]
#[tokio::test]
async fn initial_populate_credentials() {
let time = TestingTimeSource::new(UNIX_EPOCH);
let time = ManualTimeSource::new(UNIX_EPOCH);
let provider = SharedCredentialsProvider::new(provide_credentials_fn(|| async {
info!("refreshing the credentials");
Ok(credentials(1000))
}));
let credentials_cache = LazyCredentialsCache::new(
TimeSource::testing(&time),
SharedTimeSource::new(time),
SharedAsyncSleep::new(TokioSleep::new()),
provider,
DEFAULT_LOAD_TIMEOUT,
@ -433,9 +441,9 @@ mod tests {
#[traced_test]
#[tokio::test]
async fn reload_expired_credentials() {
let mut time = TestingTimeSource::new(epoch_secs(100));
let time = ManualTimeSource::new(epoch_secs(100));
let credentials_cache = test_provider(
TimeSource::testing(&time),
time.clone(),
BUFFER_TIME_NO_JITTER,
vec![
Ok(credentials(1000)),
@ -457,9 +465,9 @@ mod tests {
#[traced_test]
#[tokio::test]
async fn load_failed_error() {
let mut time = TestingTimeSource::new(epoch_secs(100));
let time = ManualTimeSource::new(epoch_secs(100));
let credentials_cache = test_provider(
TimeSource::testing(&time),
time.clone(),
BUFFER_TIME_NO_JITTER,
vec![
Ok(credentials(1000)),
@ -484,9 +492,9 @@ mod tests {
.build()
.unwrap();
let time = TestingTimeSource::new(epoch_secs(0));
let time = ManualTimeSource::new(epoch_secs(0));
let credentials_cache = Arc::new(test_provider(
TimeSource::testing(&time),
time.clone(),
BUFFER_TIME_NO_JITTER,
vec![
Ok(credentials(500)),
@ -497,16 +505,15 @@ mod tests {
],
));
let locked_time = Arc::new(Mutex::new(time));
for i in 0..4 {
// credentials are available up until 4500 seconds after the unix epoch
// 4*50 = 200 tasks are launched => we can advance time 4500/20 => 225 seconds per advance
for _ in 0..4 {
let mut tasks = Vec::new();
for j in 0..50 {
for _ in 0..50 {
let credentials_cache = credentials_cache.clone();
let time = locked_time.clone();
let time = time.clone();
tasks.push(rt.spawn(async move {
let now = epoch_secs(i * 1000 + (4 * j));
time.lock().unwrap().set_time(now);
let now = time.advance(Duration::from_secs(22));
let creds = credentials_cache
.provide_cached_credentials()
@ -529,15 +536,15 @@ mod tests {
#[tokio::test]
#[traced_test]
async fn load_timeout() {
let time = TestingTimeSource::new(epoch_secs(100));
let (time, sleep) = instant_time_and_sleep(epoch_secs(100));
let credentials_cache = LazyCredentialsCache::new(
TimeSource::testing(&time),
SharedAsyncSleep::new(TokioSleep::new()),
SharedTimeSource::new(time.clone()),
SharedAsyncSleep::new(sleep),
SharedCredentialsProvider::new(provide_credentials_fn(|| async {
aws_smithy_async::future::never::Never::new().await;
Ok(credentials(1000))
})),
Duration::from_millis(5),
Duration::from_secs(5),
DEFAULT_BUFFER_TIME,
BUFFER_TIME_NO_JITTER,
DEFAULT_CREDENTIAL_EXPIRATION,
@ -547,14 +554,15 @@ mod tests {
credentials_cache.provide_cached_credentials().await,
Err(CredentialsError::ProviderTimedOut { .. })
));
assert_eq!(time.now(), epoch_secs(105));
}
#[tokio::test]
async fn buffer_time_jitter() {
let mut time = TestingTimeSource::new(epoch_secs(100));
let time = ManualTimeSource::new(epoch_secs(100));
let buffer_time_jitter_fraction = || 0.5_f64;
let credentials_cache = test_provider(
TimeSource::testing(&time),
time.clone(),
buffer_time_jitter_fraction,
vec![Ok(credentials(1000)), Ok(credentials(2000))],
);

View File

@ -21,7 +21,5 @@ pub mod cache;
pub mod credential_fn;
mod credentials_impl;
pub mod provider;
#[doc(hidden)]
pub mod time_source;
pub use credentials_impl::Credentials;

View File

@ -1,142 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use aws_smithy_async::time::{SharedTimeSource, TimeSource as TimeSourceTrait};
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
impl TimeSourceTrait for TimeSource {
fn now(&self) -> SystemTime {
self.now()
}
}
/// Time source abstraction
///
/// Simple abstraction representing time either real-time or manually-specified for testing
#[derive(Debug, Clone)]
// TODO(breakingChangeWindow): Delete this struct
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()))
}
/// Creates `TimeSource` from a shared time source
pub fn shared(time_source: SharedTimeSource) -> Self {
TimeSource(Inner::Shared(time_source))
}
/// 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(),
Inner::Shared(ts) => ts.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
/// > This has been superseded by [`aws_smithy_async::time::TimeSource`] and will be removed in a
/// > future release.
///
/// # 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
}
}
#[derive(Debug, Clone)]
enum Inner {
Default,
Testing(TestingTimeSource),
Shared(SharedTimeSource),
}
#[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));
}
}

View File

@ -35,11 +35,43 @@ impl ManualTimeSource {
.unwrap()
.as_secs_f64()
}
/// Creates a new [`ManualTimeSource`]
pub fn new(start_time: SystemTime) -> ManualTimeSource {
Self {
start_time,
log: Default::default(),
}
}
/// Advances the time of this time source by `duration`.
pub fn advance(&self, duration: Duration) -> SystemTime {
let mut log = self.log.lock().unwrap();
log.push(duration);
self._now(&log)
}
fn _now(&self, log: &[Duration]) -> SystemTime {
self.start_time + log.iter().sum::<Duration>()
}
/// Sets the `time` of this manual time source.
///
/// # Panics
/// This function panics if `time` < `now()`
pub fn set_time(&self, time: SystemTime) {
let mut log = self.log.lock().unwrap();
let now = self._now(&log);
if time < now {
panic!("Cannot move time backwards!");
}
log.push(time.duration_since(now).unwrap());
}
}
impl TimeSource for ManualTimeSource {
fn now(&self) -> SystemTime {
self.start_time + self.log.lock().unwrap().iter().sum::<Duration>()
self._now(&self.log.lock().unwrap())
}
}