Skip to content

Commit

Permalink
Rust: Added replay and wrapper testing (#5566)
Browse files Browse the repository at this point in the history
* Updates for Rust changes Oct 30 2023
* Added replay and wrapper testing
  • Loading branch information
DavidSouther authored and ford-at-aws committed Dec 15, 2023
1 parent 8370fc9 commit 30362e8
Show file tree
Hide file tree
Showing 9 changed files with 504 additions and 136 deletions.
18 changes: 10 additions & 8 deletions rust_dev_preview/examples/testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ edition = "2021"
# snippet-start:[testing.rust.Cargo.toml]
[dependencies]
async-trait = "0.1.51"
# snippet-end:[testing.rust.Cargo.toml]
aws-config = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" }
aws-credential-types = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next", features = [ "hardcoded-credentials", ] }
aws-sdk-s3 = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" }
aws-smithy-http = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" }
aws-smithy-runtime = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next", features = ["test-util"] }
aws-smithy-runtime-api = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next", features = ["test-util"] }
aws-types = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" }
aws-credential-types = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next", features = [
"hardcoded-credentials",
] }
tokio = { version = "1.20.1", features = ["full"] }
serde_json = "1"
clap = { version = "~4.4", features = ["derive"] }
http = "0.2.9"
mockall = "0.11.4"
serde_json = "1"
tokio = { version = "1.20.1", features = ["full"] }
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
# snippet-end:[testing.rust.Cargo.toml]

[[bin]]
name = "intro"
path = "src/intro.rs"
name = "main"
path = "src/main.rs"
126 changes: 67 additions & 59 deletions rust_dev_preview/examples/testing/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
* SPDX-License-Identifier: Apache-2.0.
*/

#![allow(dead_code)]

use aws_sdk_s3 as s3;
use std::error::Error;

#[allow(dead_code)]
// snippet-start:[testing.rust.enums-struct]
pub struct ListObjectsResult {
pub objects: Vec<s3::types::Object>,
pub continuation_token: Option<String>,
pub next_continuation_token: Option<String>,
pub has_more: bool,
}
// snippet-end:[testing.rust.enums-struct]

#[allow(dead_code)]
// snippet-start:[testing.rust.enums-enum]
pub enum ListObjects {
Real(s3::Client),
Expand All @@ -30,12 +29,13 @@ pub enum ListObjects {

// snippet-start:[testing.rust.enums-list_objects]
impl ListObjects {
#[allow(dead_code)]
pub async fn list_objects(
&self,
bucket: &str,
prefix: &str,
continuation_token: Option<String>,
) -> Result<ListObjectsResult, Box<dyn Error + Send + Sync + 'static>> {
) -> Result<ListObjectsResult, s3::Error> {
match self {
Self::Real(s3) => {
Self::real_list_objects(s3.clone(), bucket, prefix, continuation_token).await
Expand All @@ -54,13 +54,14 @@ impl ListObjects {
}
// snippet-end:[testing.rust.enums-list_objects]

#[allow(dead_code)]
// snippet-start:[testing.rust.enums-real-list-objects]
async fn real_list_objects(
s3: s3::Client,
bucket: &str,
prefix: &str,
continuation_token: Option<String>,
) -> Result<ListObjectsResult, Box<dyn Error + Send + Sync + 'static>> {
) -> Result<ListObjectsResult, s3::Error> {
let response = s3
.list_objects_v2()
.bucket(bucket)
Expand All @@ -70,46 +71,48 @@ impl ListObjects {
.await?;
Ok(ListObjectsResult {
objects: response.contents().to_vec(),
continuation_token: response.continuation_token().map(|t| t.to_string()),
next_continuation_token: response.next_continuation_token.clone(),
has_more: response.is_truncated(),
})
}
// snippet-end:[testing.rust.enums-real-list-objects]

#[allow(clippy::result_large_err)]
// snippet-start:[testing.rust.enums-test]
#[cfg(test)]
fn test_list_objects(
pages: &[Vec<s3::types::Object>],
continuation_token: Option<String>,
) -> Result<ListObjectsResult, Box<dyn Error + Send + Sync + 'static>> {
) -> Result<ListObjectsResult, s3::Error> {
use std::str::FromStr;
let index = continuation_token
.map(|t| usize::from_str(&t).expect("valid token"))
.unwrap_or_default();
if pages.is_empty() {
Ok(ListObjectsResult {
objects: Vec::new(),
continuation_token: None,
next_continuation_token: None,
has_more: false,
})
} else {
Ok(ListObjectsResult {
objects: pages[index].clone(),
continuation_token: Some(format!("{}", index + 1)),
objects: pages[index].to_vec(),
next_continuation_token: Some(format!("{}", index + 1)),
has_more: index + 1 < pages.len(),
})
}
}
}
// snippet-end:[testing.rust.enums-test]

#[allow(dead_code)]
// snippet-start:[testing.rust.enums-function]
async fn determine_prefix_file_size(
// Now we take an instance of our enum rather than the S3 client
list_objects_impl: ListObjects,
bucket: &str,
prefix: &str,
) -> Result<usize, Box<dyn Error + Send + Sync + 'static>> {
) -> Result<usize, s3::Error> {
let mut next_token: Option<String> = None;
let mut total_size_bytes = 0;
loop {
Expand All @@ -123,7 +126,7 @@ async fn determine_prefix_file_size(
}

// Handle pagination, and break the loop if there are no more pages
next_token = result.continuation_token;
next_token = result.next_continuation_token;
if !result.has_more {
break;
}
Expand All @@ -132,53 +135,58 @@ async fn determine_prefix_file_size(
}
// snippet-end:[testing.rust.enums-function]

// snippet-start:[testing.rust.enums-tests]
#[tokio::test]
async fn test_single_page() {
use s3::types::Object;

// Create a TestListObjects instance with just one page of two objects in it
let fake = ListObjects::Test {
expected_bucket: "some-bucket".into(),
expected_prefix: "some-prefix".into(),
pages: vec![[5, 2i64]
.iter()
.map(|size| Object::builder().size(*size).build())
.collect()],
};

// Run the code we want to test with it
let size = determine_prefix_file_size(fake, "some-bucket", "some-prefix")
.await
.unwrap();

// Verify we got the correct total size back
assert_eq!(7, size);
}
#[cfg(test)]
mod test {
use super::*;
use aws_sdk_s3 as s3;
// snippet-start:[testing.rust.enums-tests]
#[tokio::test]
async fn test_single_page() {
use s3::types::Object;

// Create a TestListObjects instance with just one page of two objects in it
let fake = ListObjects::Test {
expected_bucket: "test-bucket".into(),
expected_prefix: "test-prefix".into(),
pages: vec![[5, 2i64]
.iter()
.map(|size| Object::builder().size(*size).build())
.collect()],
};

// Run the code we want to test with it
let size = determine_prefix_file_size(fake, "test-bucket", "test-prefix")
.await
.unwrap();

// Verify we got the correct total size back
assert_eq!(7, size);
}

#[tokio::test]
async fn test_multiple_pages() {
use s3::types::Object;
#[tokio::test]
async fn test_multiple_pages() {
use s3::types::Object;

// This time, we add a helper function for making pages
fn make_page(sizes: &[i64]) -> Vec<Object> {
sizes
.iter()
.map(|size| Object::builder().size(*size).build())
.collect()
}
// This time, we add a helper function for making pages
fn make_page(sizes: &[i64]) -> Vec<Object> {
sizes
.iter()
.map(|size| Object::builder().size(*size).build())
.collect()
}

// Create the TestListObjects instance with two pages of objects now
let fake = ListObjects::Test {
expected_bucket: "some-bucket".into(),
expected_prefix: "some-prefix".into(),
pages: vec![make_page(&[5, 2]), make_page(&[3, 9])],
};

// And now test and verify
let size = determine_prefix_file_size(fake, "some-bucket", "some-prefix")
.await
.unwrap();
assert_eq!(19, size);
// Create the TestListObjects instance with two pages of objects now
let fake = ListObjects::Test {
expected_bucket: "test-bucket".into(),
expected_prefix: "test-prefix".into(),
pages: vec![make_page(&[5, 2]), make_page(&[3, 9])],
};

// And now test and verify
let size = determine_prefix_file_size(fake, "test-bucket", "test-prefix")
.await
.unwrap();
assert_eq!(19, size);
}
// snippet-end:[testing.rust.enums-tests]
}
// snippet-end:[testing.rust.enums-tests]
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ use aws_config::meta::region::RegionProviderChain;
use aws_sdk_s3 as s3;
// snippet-end:[testing.rust.intro-import]
use clap::Parser;
use std::error::Error;

// The testing approaches imported as modules below
mod enums;
mod replay;
mod traits;
mod wrapper;

#[derive(Debug, Parser)]
struct Opt {
Expand All @@ -30,17 +35,13 @@ struct Opt {
verbose: bool,
}

// The two testing approaches imported as modules below
mod enums;
mod traits;

// snippet-start:[testing.rust.intro-function]
// Lists all objects in an S3 bucket with the given prefix, and adds up their size.
async fn determine_prefix_file_size(
s3: s3::Client,
bucket: &str,
prefix: &str,
) -> Result<usize, Box<dyn Error + Send + Sync + 'static>> {
) -> Result<usize, s3::Error> {
let mut next_token: Option<String> = None;
let mut total_size_bytes = 0;
loop {
Expand All @@ -59,7 +60,7 @@ async fn determine_prefix_file_size(
}

// Handle pagination, and break the loop if there are no more pages
next_token = response.continuation_token().map(|t| t.to_string());
next_token = response.next_continuation_token.clone();
if !response.is_truncated() {
break;
}
Expand All @@ -68,8 +69,9 @@ async fn determine_prefix_file_size(
}
// snippet-end:[testing.rust.intro-function]

#[allow(clippy::result_large_err)]
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
async fn main() -> Result<(), s3::Error> {
tracing_subscriber::fmt::init();

let Opt {
Expand Down
Loading

0 comments on commit 30362e8

Please sign in to comment.