From c7949be8573b2b6499e65ab5618469ecb0b3f703 Mon Sep 17 00:00:00 2001 From: Zelda Hessler Date: Wed, 27 Jul 2022 10:34:01 -0500 Subject: [PATCH] Add/alternative runtime tests (#1575) * add: alternative runtime tests --- .../rustsdk/IntegrationTestDependencies.kt | 16 +- aws/sdk/integration-tests/s3/Cargo.toml | 3 + .../s3/tests/alternative-async-runtime.rs | 159 ++++++++++++++++++ .../integration-tests/s3/tests/timeouts.rs | 3 +- .../rust/codegen/rustlang/CargoDependency.kt | 2 +- .../rust/codegen/smithy/RuntimeTypes.kt | 5 +- rust-runtime/aws-smithy-async/src/rt/sleep.rs | 2 +- 7 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt index abc90e3f5..512ef6e2b 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt @@ -82,7 +82,7 @@ class IntegrationTestDependencies( private fun serviceSpecificCustomizations(): List = when (moduleName) { "transcribestreaming" -> listOf(TranscribeTestDependencies()) - "s3" -> listOf(S3TestDependencies()) + "s3" -> listOf(S3TestDependencies(runtimeConfig)) else -> emptyList() } } @@ -95,19 +95,29 @@ class TranscribeTestDependencies : LibRsCustomization() { } } -class S3TestDependencies : LibRsCustomization() { +class S3TestDependencies( + private val runtimeConfig: RuntimeConfig +) : LibRsCustomization() { override fun section(section: LibRsSection): Writable = writable { + addDependency(AsyncStd) addDependency(BytesUtils) + addDependency(Smol) addDependency(TempFile) + runtimeConfig.runtimeCrate("async", scope = DependencyScope.Dev) + runtimeConfig.runtimeCrate("client", scope = DependencyScope.Dev) + runtimeConfig.runtimeCrate("http", scope = DependencyScope.Dev) + runtimeConfig.runtimeCrate("types", scope = DependencyScope.Dev) } } +private val AsyncStd = CargoDependency("async-std", CratesIo("1.12"), scope = DependencyScope.Dev) private val AsyncStream = CargoDependency("async-stream", CratesIo("0.3"), DependencyScope.Dev) private val Criterion = CargoDependency("criterion", CratesIo("0.3"), scope = DependencyScope.Dev) private val FuturesCore = CargoDependency("futures-core", CratesIo("0.3"), DependencyScope.Dev) +private val FuturesUtil = CargoDependency("futures-util", CratesIo("0.3"), scope = DependencyScope.Dev) private val Hound = CargoDependency("hound", CratesIo("3.4"), DependencyScope.Dev) private val SerdeJson = CargoDependency("serde_json", CratesIo("1"), features = emptySet(), scope = DependencyScope.Dev) +private val Smol = CargoDependency("smol", CratesIo("1.2"), scope = DependencyScope.Dev) private val Tokio = CargoDependency("tokio", CratesIo("1"), features = setOf("macros", "test-util"), scope = DependencyScope.Dev) -private val FuturesUtil = CargoDependency("futures-util", CratesIo("0.3"), scope = DependencyScope.Dev) private val Tracing = CargoDependency("tracing", CratesIo("0.1"), scope = DependencyScope.Dev) private val TracingSubscriber = CargoDependency("tracing-subscriber", CratesIo("0.2"), scope = DependencyScope.Dev) diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index 35ee810ad..12306b946 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -8,8 +8,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dev-dependencies] +async-std = "1.12" aws-config = { path = "../../build/aws-sdk/sdk/aws-config" } aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } +aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3" } aws-sdk-sts = { path = "../../build/aws-sdk/sdk/sts" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["rt-tokio"] } @@ -24,6 +26,7 @@ http-body = "0.4.5" hyper = "0.14" serde_json = "1" tempfile = "3" +smol = "1.2" tokio = { version = "1", features = ["full", "test-util"] } tracing-subscriber = { version = "0.3.5", features = ["env-filter"] } tracing = "0.1" diff --git a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs new file mode 100644 index 000000000..95e53dfe4 --- /dev/null +++ b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs @@ -0,0 +1,159 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_sdk_s3::model::{ + CompressionType, CsvInput, CsvOutput, ExpressionType, FileHeaderInfo, InputSerialization, + OutputSerialization, +}; +use aws_sdk_s3::{Client, Config, Credentials, Region}; +use aws_smithy_async::assert_elapsed; +use aws_smithy_async::rt::sleep::{AsyncSleep, Sleep}; +use aws_smithy_client::never::NeverConnector; +use aws_smithy_http::result::SdkError; +use aws_smithy_types::timeout; +use aws_smithy_types::tristate::TriState; + +use std::fmt::Debug; +use std::sync::Arc; +use std::time::Duration; + +#[derive(Debug)] +struct SmolSleep; + +impl AsyncSleep for SmolSleep { + fn sleep(&self, duration: Duration) -> Sleep { + Sleep::new(async move { + smol::Timer::after(duration).await; + }) + } +} + +#[test] +fn test_smol_runtime_timeouts() { + if let Err(err) = smol::block_on(async { timeout_test(Arc::new(SmolSleep)).await }) { + println!("{err}"); + panic!(); + } +} + +#[test] +fn test_smol_runtime_retry() { + if let Err(err) = smol::block_on(async { retry_test(Arc::new(SmolSleep)).await }) { + println!("{err}"); + panic!(); + } +} + +#[derive(Debug)] +struct AsyncStdSleep; + +impl AsyncSleep for AsyncStdSleep { + fn sleep(&self, duration: Duration) -> Sleep { + Sleep::new(async move { async_std::task::sleep(duration).await }) + } +} + +#[test] +fn test_async_std_runtime_timeouts() { + if let Err(err) = + async_std::task::block_on(async { timeout_test(Arc::new(AsyncStdSleep)).await }) + { + println!("{err}"); + panic!(); + } +} + +#[test] +fn test_async_std_runtime_retry() { + if let Err(err) = async_std::task::block_on(async { retry_test(Arc::new(AsyncStdSleep)).await }) + { + println!("{err}"); + panic!(); + } +} + +async fn timeout_test(sleep_impl: Arc) -> Result<(), Box> { + let conn = NeverConnector::new(); + let region = Region::from_static("us-east-2"); + let credentials = Credentials::new("test", "test", None, None, "test"); + let api_timeouts = + timeout::Api::new().with_call_timeout(TriState::Set(Duration::from_secs_f32(0.5))); + let timeout_config = timeout::Config::new().with_api_timeouts(api_timeouts); + let config = Config::builder() + .region(region) + .credentials_provider(credentials) + .timeout_config(timeout_config) + .sleep_impl(sleep_impl) + .build(); + let client = Client::from_conf_conn(config, conn.clone()); + + let now = std::time::Instant::now(); + + let err = client + .select_object_content() + .bucket("aws-rust-sdk") + .key("sample_data.csv") + .expression_type(ExpressionType::Sql) + .expression("SELECT * FROM s3object s WHERE s.\"Name\" = 'Jane'") + .input_serialization( + InputSerialization::builder() + .csv( + CsvInput::builder() + .file_header_info(FileHeaderInfo::Use) + .build(), + ) + .compression_type(CompressionType::None) + .build(), + ) + .output_serialization( + OutputSerialization::builder() + .csv(CsvOutput::builder().build()) + .build(), + ) + .send() + .await + .unwrap_err(); + + assert_eq!(format!("{:?}", err), "TimeoutError(RequestTimeoutError { kind: \"API call (all attempts including retries)\", duration: 500ms })"); + assert_elapsed!(now, std::time::Duration::from_secs_f32(0.5)); + + Ok(()) +} + +async fn retry_test(sleep_impl: Arc) -> Result<(), Box> { + let conn = NeverConnector::new(); + let credentials = Credentials::new("test", "test", None, None, "test"); + let conf = aws_types::SdkConfig::builder() + .region(Region::new("us-east-2")) + .credentials_provider(aws_types::credentials::SharedCredentialsProvider::new( + credentials, + )) + .timeout_config( + timeout::Config::new().with_api_timeouts( + timeout::Api::new() + .with_call_attempt_timeout(TriState::Set(Duration::from_secs_f64(0.1))), + ), + ) + .sleep_impl(sleep_impl) + .build(); + let client = Client::from_conf_conn(Config::new(&conf), conn.clone()); + let resp = client + .list_buckets() + .send() + .await + .expect_err("call should fail"); + assert_eq!( + conn.num_calls(), + 3, + "client level timeouts should be retried" + ); + assert!( + matches!(resp, SdkError::TimeoutError { .. }), + "expected a timeout error, got: {}", + resp + ); + + Ok(()) +} diff --git a/aws/sdk/integration-tests/s3/tests/timeouts.rs b/aws/sdk/integration-tests/s3/tests/timeouts.rs index 64300c108..473ebbab8 100644 --- a/aws/sdk/integration-tests/s3/tests/timeouts.rs +++ b/aws/sdk/integration-tests/s3/tests/timeouts.rs @@ -19,7 +19,7 @@ use aws_smithy_types::tristate::TriState; use std::sync::Arc; use std::time::Duration; -#[tokio::test] +#[tokio::test(start_paused = true)] async fn test_timeout_service_ends_request_that_never_completes() { let conn: NeverService, http::Response, ConnectorError> = NeverService::new(); @@ -38,7 +38,6 @@ async fn test_timeout_service_ends_request_that_never_completes() { let client = Client::from_conf_conn(config, conn.clone()); let now = tokio::time::Instant::now(); - tokio::time::pause(); let err = client .select_object_content() diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt index 1f4a13a32..4d1a5b643 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/CargoDependency.kt @@ -217,7 +217,7 @@ data class CargoDependency( fun SmithyHttp(runtimeConfig: RuntimeConfig) = runtimeConfig.runtimeCrate("http") fun SmithyHttpTower(runtimeConfig: RuntimeConfig) = runtimeConfig.runtimeCrate("http-tower") fun SmithyProtocolTestHelpers(runtimeConfig: RuntimeConfig) = - runtimeConfig.runtimeCrate("protocol-test").copy(scope = DependencyScope.Dev) + runtimeConfig.runtimeCrate("protocol-test", scope = DependencyScope.Dev) fun smithyJson(runtimeConfig: RuntimeConfig): CargoDependency = runtimeConfig.runtimeCrate("json") fun smithyQuery(runtimeConfig: RuntimeConfig): CargoDependency = runtimeConfig.runtimeCrate("query") fun smithyXml(runtimeConfig: RuntimeConfig): CargoDependency = runtimeConfig.runtimeCrate("xml") diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt index f9e56f4bd..7674d30c2 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt @@ -12,6 +12,7 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.rust.codegen.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.rustlang.CratesIo import software.amazon.smithy.rust.codegen.rustlang.DependencyLocation +import software.amazon.smithy.rust.codegen.rustlang.DependencyScope import software.amazon.smithy.rust.codegen.rustlang.InlineDependency import software.amazon.smithy.rust.codegen.rustlang.Local import software.amazon.smithy.rust.codegen.rustlang.RustDependency @@ -87,8 +88,8 @@ data class RuntimeConfig( val crateSrcPrefix: String = cratePrefix.replace("-", "_") - fun runtimeCrate(runtimeCrateName: String, optional: Boolean = false): CargoDependency = - CargoDependency("$cratePrefix-$runtimeCrateName", runtimeCrateLocation.crateLocation(), optional = optional) + fun runtimeCrate(runtimeCrateName: String, optional: Boolean = false, scope: DependencyScope = DependencyScope.Compile): CargoDependency = + CargoDependency("$cratePrefix-$runtimeCrateName", runtimeCrateLocation.crateLocation(), optional = optional, scope = scope) } /** diff --git a/rust-runtime/aws-smithy-async/src/rt/sleep.rs b/rust-runtime/aws-smithy-async/src/rt/sleep.rs index a851671a1..9921fd9d0 100644 --- a/rust-runtime/aws-smithy-async/src/rt/sleep.rs +++ b/rust-runtime/aws-smithy-async/src/rt/sleep.rs @@ -14,7 +14,7 @@ use std::task::{Context, Poll}; use std::time::Duration; /// Async trait with a `sleep` function. -pub trait AsyncSleep: std::fmt::Debug + Send + Sync { +pub trait AsyncSleep: Debug + Send + Sync { /// Returns a future that sleeps for the given `duration` of time. fn sleep(&self, duration: Duration) -> Sleep; } -- GitLab