Repository: rusoto/rusoto (language: rust)

Issue:
Refactor our S3 integration tests
Our [S3 integration tests](https://github.com/rusoto/rusoto/blob/master/integration_tests/tests/s3.rs) consists of one massive test that tries to test most of our S3 functionality. This is difficult to read and failing tests can be hard to track down.

We should refactor it so it's easier to read: a test per behavior is a good goal.

The `Drop` pattern from our [Elastictranscoder tests](https://github.com/rusoto/rusoto/blob/master/integration_tests/tests/elastictranscoder.rs) can be used to ensure if a test fails, it cleans up after itself by deleting the test bucket.


Discussion / hints:
Starting to work on these :man_dancing: 

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45c2d012c0a..db3b21382a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
 - Fix Profile Config Loading should ignore comments with '=' chars
 - Add App Mesh service
 - Fix service_crategen to parse operations with multiple static params
+- Refactor S3 integration tests - about a `#[test]` per behavior
 - Add support for non signing clients
 
 ## [0.40.0] - 2019-06-28


Test patch:
diff --git a/integration_tests/tests/s3.rs b/integration_tests/tests/s3.rs
index 474c64d2c8d..f1465fb2334 100644
--- a/integration_tests/tests/s3.rs
+++ b/integration_tests/tests/s3.rs
@@ -30,120 +30,267 @@ use rusoto_s3::{
     StreamingBody, UploadPartCopyRequest, UploadPartRequest, S3,
 };
 
-type TestClient = S3Client;
+struct TestS3Client {
+    region: Region,
+    s3: S3Client,
+    bucket_name: String,
+    // This flag signifies whether this bucket was already deleted as part of a test
+    bucket_deleted: bool,
+}
 
-// Rust is in bad need of an integration test harness
-// This creates the S3 resources needed for a suite of tests,
-// executes those tests, and then destroys the resources
-#[test]
-fn test_all_the_things() {
-    let _ = env_logger::try_init();
+impl TestS3Client {
+    // construct S3 testing client
+    fn new(bucket_name: String) -> TestS3Client {
+        let region = if let Ok(endpoint) = env::var("S3_ENDPOINT") {
+            let region = Region::Custom {
+                name: "us-east-1".to_owned(),
+                endpoint: endpoint.to_owned(),
+            };
+            println!(
+                "picked up non-standard endpoint {:?} from S3_ENDPOINT env. variable",
+                region
+            );
+            region
+        } else {
+            Region::UsEast1
+        };
 
-    let region = if let Ok(endpoint) = env::var("S3_ENDPOINT") {
-        let region = Region::Custom {
-            name: "us-east-1".to_owned(),
-            endpoint: endpoint.to_owned(),
+        TestS3Client {
+            region: region.to_owned(),
+            s3: S3Client::new(region),
+            bucket_name: bucket_name.to_owned(),
+            bucket_deleted: false,
+        }
+    }
+
+    fn create_test_bucket(&self, name: String) {
+        let create_bucket_req = CreateBucketRequest {
+            bucket: name.clone(),
+            ..Default::default()
         };
-        println!(
-            "picked up non-standard endpoint {:?} from S3_ENDPOINT env. variable",
-            region
-        );
-        region
-    } else {
-        Region::UsEast1
-    };
+        self.s3
+            .create_bucket(create_bucket_req)
+            .sync()
+            .expect("Failed to create test bucket");
+    }
 
-    let client = S3Client::new(region.clone());
-    let credentials = DefaultCredentialsProvider::new()
-        .unwrap()
-        .credentials()
-        .wait()
-        .unwrap();
+    fn delete_object(&self, key: String) {
+        let delete_object_req = DeleteObjectRequest {
+            bucket: self.bucket_name.to_owned(),
+            key: key.to_owned(),
+            ..Default::default()
+        };
 
-    let test_bucket = format!("rusoto-test-bucket-{}", get_time().sec);
-    let filename = format!("test_file_{}", get_time().sec);
-    let utf8_filename = format!("test[über]file@{}", get_time().sec);
-    let binary_filename = format!("test_file_b{}", get_time().sec);
-    let multipart_filename = format!("test_multipart_file_{}", get_time().sec);
-    let metadata_filename = format!("test_metadata_file_{}", get_time().sec);
+        self.s3
+            .delete_object(delete_object_req)
+            .sync()
+            .expect("Couldn't delete object");
+    }
 
-    // get a list of list_buckets
-    test_list_buckets(&client);
+    fn put_test_object(&self, filename: String) {
+        let contents: Vec<u8> = Vec::new();
+        let put_request = PutObjectRequest {
+            bucket: self.bucket_name.to_owned(),
+            key: filename.to_owned(),
+            body: Some(contents.into()),
+            ..Default::default()
+        };
 
-    // create a bucket for these tests
-    test_create_bucket(&client, &test_bucket);
+        self.s3
+            .put_object(put_request)
+            .sync()
+            .expect("Failed to put test object");
+    }
+}
 
-    // list items v2
-    list_items_in_bucket(&client, &test_bucket);
+impl Drop for TestS3Client {
+    fn drop(&mut self) {
+        if self.bucket_deleted {
+            return;
+        }
+        let delete_bucket_req = DeleteBucketRequest {
+            bucket: self.bucket_name.clone(),
+            ..Default::default()
+        };
 
-    // do a multipart upload
-    test_multipart_upload(
-        &client,
-        &region,
-        &credentials,
-        &test_bucket,
-        &multipart_filename,
+        match self.s3.delete_bucket(delete_bucket_req).sync() {
+            Ok(_) => println!("Deleted S3 bucket: {}", self.bucket_name),
+            Err(e) => println!("Failed to delete S3 bucket: {}", e),
+        }
+    }
+}
+
+// NOTE: Do we want to use logging calls instead of printlns??
+// inititializes logging
+fn init_logging() {
+    let _ = env_logger::try_init();
+}
+
+#[test]
+// creates a bucket and test listing buckets and items in bucket
+fn test_bucket_creation_deletion() {
+    init_logging();
+
+    let bucket_name = format!("s3-test-bucket-{}", get_time().sec);
+    let mut test_client = TestS3Client::new(bucket_name.clone());
+
+    let create_bucket_req = CreateBucketRequest {
+        bucket: bucket_name.clone(),
+        ..Default::default()
+    };
+
+    // first create a bucket
+    let create_bucket_resp = test_client.s3.create_bucket(create_bucket_req).sync();
+    assert!(create_bucket_resp.is_ok());
+    println!(
+        "Bucket {} created, resp: {:#?}",
+        bucket_name.clone(),
+        create_bucket_resp.unwrap()
     );
 
+    // now lets check for our bucket and list items in the one we created
+    let resp = test_client.s3.list_buckets().sync();
+    assert!(resp.is_ok());
+
+    let resp = resp.unwrap();
+    let mut bucket_found = false;
+    for bucket in resp.buckets.unwrap().iter() {
+        if bucket.name == Some(bucket_name.clone()) {
+            bucket_found = true;
+            break;
+        }
+    }
+    assert!(bucket_found);
+
+    let list_obj_req = ListObjectsV2Request {
+        bucket: bucket_name.to_owned(),
+        start_after: Some("foo".to_owned()),
+        ..Default::default()
+    };
+    let result = test_client.s3.list_objects_v2(list_obj_req).sync();
+    assert!(result.is_ok());
+
+    test_delete_bucket(&test_client.s3, &bucket_name);
+    test_client.bucket_deleted = true;
+}
+
+#[test]
+// test against normal files
+fn test_puts_gets_deletes() {
+    init_logging();
+
+    let bucket_name = format!("test-bucket-{}-{}", "default".to_owned(), get_time().sec);
+    let test_client = TestS3Client::new(bucket_name.clone());
+    test_client.create_test_bucket(bucket_name.clone());
+
     // modify the bucket's CORS properties
     if cfg!(not(feature = "disable_minio_unsupported")) {
         // Minio support: CORS is not implemented by Minio
-        test_put_bucket_cors(&client, &test_bucket);
+        test_put_bucket_cors(&test_client.s3, &test_client.bucket_name);
     }
 
+    // file used for testing puts/gets
+    let filename = format!("test_file_{}", get_time().sec);
+
+    // test failure responses on empty bucket
+    test_get_object_no_such_object(&test_client.s3, &test_client.bucket_name, &filename);
+
     // PUT an object via buffer (no_credentials is an arbitrary choice)
     test_put_object_with_filename(
-        &client,
-        &test_bucket,
+        &test_client.s3,
+        &test_client.bucket_name,
         &filename,
         &"tests/sample-data/no_credentials",
     );
 
     // HEAD the object that was PUT
-    test_head_object(&client, &test_bucket, &filename);
+    test_head_object(&test_client.s3, &test_client.bucket_name, &filename);
 
     // GET the object
-    test_get_object(&client, &test_bucket, &filename);
-    test_get_object_range(&client, &test_bucket, &filename);
+    test_get_object(&test_client.s3, &test_client.bucket_name, &filename);
+    test_get_object_range(&test_client.s3, &test_client.bucket_name, &filename);
+
+    // add two objects to test the listing by paging
+    for i in 1..3 {
+        test_client.put_test_object(format!("test_object_{}", i));
+    }
+
+    // list items with paging using list object API v1
+    list_items_in_bucket_paged_v1(&test_client.s3, &test_client.bucket_name);
+
+    // list items with paging using list object API v2
+    if cfg!(not(feature = "disable_ceph_unsupported")) {
+        // Ceph support: this test depends on the list object v2 API which is not implemented by Ceph
+        list_items_in_bucket_paged_v2(&test_client.s3, &test_client.bucket_name);
+    }
 
     // copy the object to change its settings
-    test_copy_object(&client, &test_bucket, &filename);
+    test_copy_object(&test_client.s3, &test_client.bucket_name, &filename);
+
+    // delete object, will also allow drop() to remove the bucket
+    test_delete_object(&test_client.s3, &test_client.bucket_name, &filename);
+
+    // remove test objects used for pagination tests
+    for i in 1..3 {
+        test_client.delete_object(format!("test_object_{}", i));
+    }
+}
+
+#[test]
+// test against utf8 files
+fn test_puts_gets_deletes_utf8() {
+    init_logging();
+
+    let bucket_name = format!("test-bucket-{}-{}", "utf-8".to_owned(), get_time().sec);
+    let test_client = TestS3Client::new(bucket_name.clone());
+    test_client.create_test_bucket(bucket_name.clone());
 
+    let utf8_filename = format!("test[über]file@{}", get_time().sec);
     // UTF8 filenames
     test_put_object_with_filename(
-        &client,
-        &test_bucket,
+        &test_client.s3,
+        &test_client.bucket_name,
         &utf8_filename,
         &"tests/sample-data/no_credentials",
     );
 
-    test_copy_object_utf8(&client, &test_bucket, &utf8_filename);
+    test_copy_object_utf8(&test_client.s3, &test_client.bucket_name, &utf8_filename);
+    test_delete_object(&test_client.s3, &test_client.bucket_name, &utf8_filename);
+}
+
+#[test]
+// test against binary files
+fn test_puts_gets_deletes_binary() {
+    init_logging();
 
-    test_delete_object(&client, &test_bucket, &utf8_filename);
+    let bucket_name = format!("test-bucket-{}-{}", "binary".to_owned(), get_time().sec);
+    let test_client = TestS3Client::new(bucket_name.clone());
+    test_client.create_test_bucket(bucket_name.clone());
 
-    // test failure responses
-    test_get_object_no_such_object(&client, &test_bucket, &binary_filename);
+    let binary_filename = format!("test_file_b{}", get_time().sec);
 
     // Binary objects:
     test_put_object_with_filename(
-        &client,
-        &test_bucket,
+        &test_client.s3,
+        &test_client.bucket_name,
         &binary_filename,
         &"tests/sample-data/binary-file",
     );
-    test_get_object(&client, &test_bucket, &binary_filename);
-    test_get_object_blocking_read(&client, &test_bucket, &binary_filename);
+    test_get_object(&test_client.s3, &test_client.bucket_name, &binary_filename);
+    test_get_object_blocking_read(&test_client.s3, &test_client.bucket_name, &binary_filename);
+    test_delete_object(&test_client.s3, &test_client.bucket_name, &binary_filename);
+}
 
-    // PUT an object via stream
-    let another_filename = format!("streaming{}", filename);
-    test_put_object_stream_with_filename(
-        &client,
-        &test_bucket,
-        &another_filename,
-        &"tests/sample-data/binary-file",
-    );
+#[test]
+// test metadata ops
+fn test_puts_gets_deletes_metadata() {
+    init_logging();
+
+    let bucket_name = format!("test-bucket-{}-{}", "metadata".to_owned(), get_time().sec);
+    let test_client = TestS3Client::new(bucket_name.clone());
+    test_client.create_test_bucket(bucket_name.clone());
 
-    // metadata tests
+    let metadata_filename = format!("test_metadata_file_{}", get_time().sec);
     let mut metadata = HashMap::<String, String>::new();
     metadata.insert(
         "rusoto-metadata-some".to_string(),
@@ -152,66 +299,165 @@ fn test_all_the_things() {
     metadata.insert("rusoto-metadata-none".to_string(), "".to_string());
 
     test_put_object_with_metadata(
-        &client,
-        &test_bucket,
+        &test_client.s3,
+        &test_client.bucket_name,
         &metadata_filename,
         &"tests/sample-data/no_credentials",
         &metadata,
     );
 
-    test_head_object_with_metadata(&client, &test_bucket, &metadata_filename, &metadata);
-    test_get_object_with_metadata(&client, &test_bucket, &metadata_filename, &metadata);
-
-    // list items with paging using list object API v1
-    list_items_in_bucket_paged_v1(&client, &test_bucket);
-
-    // list items with paging using list object API v2
-    if cfg!(not(feature = "disable_ceph_unsupported")) {
-        // Ceph support: this test depends on the list object v2 API which is not implemented by Ceph
-        list_items_in_bucket_paged_v2(&client, &test_bucket);
-    }
+    test_head_object_with_metadata(
+        &test_client.s3,
+        &test_client.bucket_name,
+        &metadata_filename,
+        &metadata,
+    );
+    test_get_object_with_metadata(
+        &test_client.s3,
+        &test_client.bucket_name,
+        &metadata_filename,
+        &metadata,
+    );
+    test_delete_object(
+        &test_client.s3,
+        &test_client.bucket_name,
+        &metadata_filename,
+    );
+}
 
-    test_delete_object(&client, &test_bucket, &metadata_filename);
-    test_delete_object(&client, &test_bucket, &binary_filename);
-    test_delete_object(&client, &test_bucket, &another_filename);
+#[test]
+// test object ops using presigned urls
+fn test_puts_gets_deletes_presigned_url() {
+    init_logging();
 
-    // DELETE the object
-    test_delete_object(&client, &test_bucket, &filename);
+    let bucket_name = format!("test-bucket-{}-{}", "presigned".to_owned(), get_time().sec);
+    let test_client = TestS3Client::new(bucket_name.clone());
+    test_client.create_test_bucket(bucket_name.clone());
 
-    let filename = format!("{}_for_presigned", filename);
+    let filename = format!("test_file_{}_for_presigned", get_time().sec);
     // PUT an object for presigned url
     test_put_object_with_filename(
-        &client,
-        &test_bucket,
+        &test_client.s3,
+        &test_client.bucket_name,
         &filename,
         &"tests/sample-data/no_credentials",
     );
+
+    let credentials = DefaultCredentialsProvider::new()
+        .unwrap()
+        .credentials()
+        .wait()
+        .unwrap();
+
     // generate a presigned url
-    test_get_object_with_presigned_url(&region, &credentials, &test_bucket, &filename);
-    test_get_object_with_expired_presigned_url(&region, &credentials, &test_bucket, &filename);
-    test_put_object_with_presigned_url(&region, &credentials, &test_bucket, &filename);
-    test_delete_object_with_presigned_url(&region, &credentials, &test_bucket, &filename);
+    test_get_object_with_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &filename,
+    );
+    test_get_object_with_expired_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &filename,
+    );
+    test_put_object_with_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &filename,
+    );
+    test_delete_object_with_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &filename,
+    );
 
-    let utf8_filename = format!("{}_for_presigned", utf8_filename);
+    let utf8_filename = format!("test[über]file@{}_for_presigned", get_time().sec);
     // UTF8 filenames for presigned url
     test_put_object_with_filename(
-        &client,
-        &test_bucket,
+        &test_client.s3,
+        &test_client.bucket_name,
         &utf8_filename,
         &"tests/sample-data/no_credentials",
     );
     // generate a presigned url
-    test_get_object_with_presigned_url(&region, &credentials, &test_bucket, &utf8_filename);
-    test_get_object_with_expired_presigned_url(&region, &credentials, &test_bucket, &utf8_filename);
-    test_put_object_with_presigned_url(&region, &credentials, &test_bucket, &utf8_filename);
-    test_delete_object_with_presigned_url(&region, &credentials, &test_bucket, &utf8_filename);
+    test_get_object_with_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &utf8_filename,
+    );
+    test_get_object_with_expired_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &utf8_filename,
+    );
+    test_put_object_with_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &utf8_filename,
+    );
+    test_delete_object_with_presigned_url(
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &utf8_filename,
+    );
+}
 
-    // delete the test bucket
-    test_delete_bucket(&client, &test_bucket);
+#[test]
+fn test_multipart_stream_uploads() {
+    init_logging();
+
+    let bucket_name = format!("test-bucket-{}-{}", "multipart".to_owned(), get_time().sec);
+    let test_client = TestS3Client::new(bucket_name.clone());
+    test_client.create_test_bucket(bucket_name.clone());
+
+    let multipart_filename = format!("test_multipart_file_{}", get_time().sec);
+    let credentials = DefaultCredentialsProvider::new()
+        .unwrap()
+        .credentials()
+        .wait()
+        .unwrap();
+
+    // test put via multipart upload
+    test_multipart_upload(
+        &test_client.s3,
+        &test_client.region,
+        &credentials,
+        &test_client.bucket_name,
+        &multipart_filename,
+    );
+
+    // PUT an object via stream
+    let streaming_filename = format!("streaming_test_file_{}", get_time().sec);
+    test_put_object_stream_with_filename(
+        &test_client.s3,
+        &test_client.bucket_name,
+        &streaming_filename,
+        &"tests/sample-data/binary-file",
+    );
+
+    test_delete_object(
+        &test_client.s3,
+        &test_client.bucket_name,
+        &multipart_filename,
+    );
+
+    test_delete_object(
+        &test_client.s3,
+        &test_client.bucket_name,
+        &streaming_filename,
+    )
 }
 
 fn test_multipart_upload(
-    client: &TestClient,
+    client: &S3Client,
     region: &Region,
     credentials: &AwsCredentials,
     bucket: &str,
@@ -353,11 +599,14 @@ fn test_multipart_upload(
             part_number: Some(2),
         },
     ];
+
     let completed_upload2 = CompletedMultipartUpload {
         parts: Some(completed_parts_2),
     };
+
     let complete_req2 = CompleteMultipartUploadRequest {
         bucket: bucket.to_owned(),
+
         key: filename.to_owned(),
         upload_id: upload_id2.to_owned(),
         multipart_upload: Some(completed_upload2),
@@ -369,25 +618,9 @@ fn test_multipart_upload(
         .sync()
         .expect("Couldn't complete multipart upload2");
     println!("{:#?}", response2);
-
-    // delete the completed file
-    test_delete_object(client, bucket, filename);
-}
-
-fn test_create_bucket(client: &TestClient, bucket: &str) {
-    let create_bucket_req = CreateBucketRequest {
-        bucket: bucket.to_owned(),
-        ..Default::default()
-    };
-
-    let result = client
-        .create_bucket(create_bucket_req)
-        .sync()
-        .expect("Couldn't create bucket");
-    println!("{:#?}", result);
 }
 
-fn test_delete_bucket(client: &TestClient, bucket: &str) {
+fn test_delete_bucket(client: &S3Client, bucket: &str) {
     let delete_bucket_req = DeleteBucketRequest {
         bucket: bucket.to_owned(),
         ..Default::default()
@@ -408,7 +641,7 @@ fn test_delete_bucket(client: &TestClient, bucket: &str) {
 }
 
 fn test_put_object_with_filename(
-    client: &TestClient,
+    client: &S3Client,
     bucket: &str,
     dest_filename: &str,
     local_filename: &str,
@@ -431,7 +664,7 @@ fn test_put_object_with_filename(
 }
 
 fn test_put_object_stream_with_filename(
-    client: &TestClient,
+    client: &S3Client,
     bucket: &str,
     dest_filename: &str,
     local_filename: &str,
@@ -450,7 +683,7 @@ fn test_put_object_stream_with_filename(
     println!("{:#?}", result);
 }
 
-fn test_head_object(client: &TestClient, bucket: &str, filename: &str) {
+fn test_head_object(client: &S3Client, bucket: &str, filename: &str) {
     let head_req = HeadObjectRequest {
         bucket: bucket.to_owned(),
         key: filename.to_owned(),
@@ -464,7 +697,7 @@ fn test_head_object(client: &TestClient, bucket: &str, filename: &str) {
     println!("{:#?}", result);
 }
 
-fn test_get_object(client: &TestClient, bucket: &str, filename: &str) {
+fn test_get_object(client: &S3Client, bucket: &str, filename: &str) {
     let get_req = GetObjectRequest {
         bucket: bucket.to_owned(),
         key: filename.to_owned(),
@@ -483,7 +716,7 @@ fn test_get_object(client: &TestClient, bucket: &str, filename: &str) {
     assert!(body.len() > 0);
 }
 
-fn test_get_object_blocking_read(client: &TestClient, bucket: &str, filename: &str) {
+fn test_get_object_blocking_read(client: &S3Client, bucket: &str, filename: &str) {
     let get_req = GetObjectRequest {
         bucket: bucket.to_owned(),
         key: filename.to_owned(),
@@ -503,7 +736,7 @@ fn test_get_object_blocking_read(client: &TestClient, bucket: &str, filename: &s
     assert!(body.len() > 0);
 }
 
-fn test_get_object_no_such_object(client: &TestClient, bucket: &str, filename: &str) {
+fn test_get_object_no_such_object(client: &S3Client, bucket: &str, filename: &str) {
     let get_req = GetObjectRequest {
         bucket: bucket.to_owned(),
         key: filename.to_owned(),
@@ -516,7 +749,7 @@ fn test_get_object_no_such_object(client: &TestClient, bucket: &str, filename: &
     };
 }
 
-fn test_get_object_range(client: &TestClient, bucket: &str, filename: &str) {
+fn test_get_object_range(client: &S3Client, bucket: &str, filename: &str) {
     let get_req = GetObjectRequest {
         bucket: bucket.to_owned(),
         key: filename.to_owned(),
@@ -532,7 +765,7 @@ fn test_get_object_range(client: &TestClient, bucket: &str, filename: &str) {
     assert_eq!(result.content_length.unwrap(), 2);
 }
 
-fn test_copy_object(client: &TestClient, bucket: &str, filename: &str) {
+fn test_copy_object(client: &S3Client, bucket: &str, filename: &str) {
     let req = CopyObjectRequest {
         bucket: bucket.to_owned(),
         key: filename.to_owned(),
@@ -550,7 +783,7 @@ fn test_copy_object(client: &TestClient, bucket: &str, filename: &str) {
     println!("{:#?}", result);
 }
 
-fn test_copy_object_utf8(client: &TestClient, bucket: &str, filename: &str) {
+fn test_copy_object_utf8(client: &S3Client, bucket: &str, filename: &str) {
     let req = CopyObjectRequest {
         bucket: bucket.to_owned(),
         key: format!("{}", filename.to_owned()),
@@ -568,7 +801,7 @@ fn test_copy_object_utf8(client: &TestClient, bucket: &str, filename: &str) {
     println!("{:#?}", result);
 }
 
-fn test_delete_object(client: &TestClient, bucket: &str, filename: &str) {
+fn test_delete_object(client: &S3Client, bucket: &str, filename: &str) {
     let del_req = DeleteObjectRequest {
         bucket: bucket.to_owned(),
         key: filename.to_owned(),
@@ -582,25 +815,7 @@ fn test_delete_object(client: &TestClient, bucket: &str, filename: &str) {
     println!("{:#?}", result);
 }
 
-fn test_list_buckets(client: &TestClient) {
-    let result = client.list_buckets().sync().expect("Couldn't list buckets");
-    println!("\nbuckets available: {:#?}", result);
-}
-
-fn list_items_in_bucket(client: &TestClient, bucket: &str) {
-    let list_obj_req = ListObjectsV2Request {
-        bucket: bucket.to_owned(),
-        start_after: Some("foo".to_owned()),
-        ..Default::default()
-    };
-    let result = client
-        .list_objects_v2(list_obj_req)
-        .sync()
-        .expect("Couldn't list items in bucket (v2)");
-    println!("Items in bucket: {:#?}", result);
-}
-
-fn list_items_in_bucket_paged_v1(client: &TestClient, bucket: &str) {
+fn list_items_in_bucket_paged_v1(client: &S3Client, bucket: &str) {
     let mut list_request = ListObjectsRequest {
         delimiter: Some("/".to_owned()),
         bucket: bucket.to_owned(),
@@ -630,7 +845,7 @@ fn list_items_in_bucket_paged_v1(client: &TestClient, bucket: &str) {
 }
 
 // Assuming there's already more than three item in our test bucket:
-fn list_items_in_bucket_paged_v2(client: &TestClient, bucket: &str) {
+fn list_items_in_bucket_paged_v2(client: &S3Client, bucket: &str) {
     let mut list_obj_req = ListObjectsV2Request {
         bucket: bucket.to_owned(),
         max_keys: Some(1),
@@ -657,7 +872,7 @@ fn list_items_in_bucket_paged_v2(client: &TestClient, bucket: &str) {
     );
 }
 
-fn test_put_bucket_cors(client: &TestClient, bucket: &str) {
+fn test_put_bucket_cors(client: &S3Client, bucket: &str) {
     let cors_rules = vec![CORSRule {
         allowed_methods: vec!["PUT".to_owned(), "POST".to_owned(), "DELETE".to_owned()],
         allowed_origins: vec!["http://www.example.com".to_owned()],
@@ -685,7 +900,7 @@ fn test_put_bucket_cors(client: &TestClient, bucket: &str) {
 }
 
 fn test_put_object_with_metadata(
-    client: &TestClient,
+    client: &S3Client,
     bucket: &str,
     dest_filename: &str,
     local_filename: &str,
@@ -710,7 +925,7 @@ fn test_put_object_with_metadata(
 }
 
 fn test_head_object_with_metadata(
-    client: &TestClient,
+    client: &S3Client,
     bucket: &str,
     filename: &str,
     metadata: &HashMap<String, String>,
@@ -732,7 +947,7 @@ fn test_head_object_with_metadata(
 }
 
 fn test_get_object_with_metadata(
-    client: &TestClient,
+    client: &S3Client,
     bucket: &str,
     filename: &str,
     metadata: &HashMap<String, String>,