AWS SDK

AWS SDK

rev. beaf1e7be46b779a1dedd92bc48143dba23aff97..a483b08a5c5215e3404cafb73a660d0a9cd360fc

Files changed:

tmp-codegen-diff/services/build.gradle.kts

@@ -1,1 +156,203 @@
    9      9   
import java.time.LocalDateTime
   10     10   
   11     11   
plugins {
   12     12   
    `maven-publish`
   13     13   
    `dokka-convention`
   14     14   
    alias(libs.plugins.aws.kotlin.repo.tools.kmp) apply false
   15     15   
}
   16     16   
   17     17   
val sdkVersion: String by project
   18     18   
          19  +
configureIosSimulatorTasks()
          20  +
   19     21   
val optinAnnotations = listOf(
   20     22   
    "aws.smithy.kotlin.runtime.InternalApi",
   21     23   
    "aws.sdk.kotlin.runtime.InternalSdkApi",
   22     24   
    "kotlin.RequiresOptIn",
   23     25   
)
   24     26   
   25     27   
// capture locally - scope issue with custom KMP plugin
   26     28   
val libraries = libs
   27     29   
   28     30   
subprojects {
   29     31   
    group = "aws.sdk.kotlin"
   30     32   
    version = sdkVersion
   31     33   
   32     34   
    apply {
   33     35   
        plugin("org.jetbrains.kotlin.multiplatform")
   34     36   
        plugin(libraries.plugins.aws.kotlin.repo.tools.kmp.get().pluginId)
   35     37   
    }
   36     38   
   37     39   
    logger.info("configuring: $project")
   38     40   
   39     41   
    kotlin {
   40     42   
        explicitApi()
   41     43   
   42     44   
        sourceSets {
   43     45   
            all {
   44     46   
                // have generated sdk's opt-in to internal runtime features
   45     47   
                optinAnnotations.forEach { languageSettings.optIn(it) }
   46     48   
            }
   47     49   
   48     50   
            getByName("commonMain") {
   49     51   
                kotlin.srcDir("generated-src/main/kotlin")
   50     52   
            }
   51     53   
   52     54   
            getByName("commonTest") {
   53     55   
                kotlin.srcDir("generated-src/test")
   54     56   
   55     57   
                dependencies {
   56     58   
                    implementation(libraries.kotlinx.coroutines.test)
   57     59   
                    implementation(libraries.smithy.kotlin.http.test)
   58     60   
                }
   59     61   
            }
   60     62   
        }
   61     63   
   62     64   
        if (project.file("e2eTest").exists()) {
   63         -
            jvm().compilations {
          65  +
            sourceSets {
   64     66   
                val e2eTest by creating {
   65         -
                    defaultSourceSet {
   66         -
                        kotlin.srcDir("e2eTest/src")
   67         -
                        resources.srcDir("e2eTest/test-resources")
   68         -
                        dependsOn(this@kotlin.sourceSets.getByName("commonMain"))
   69         -
                        dependsOn(this@kotlin.sourceSets.getByName("jvmMain"))
          67  +
                    kotlin.srcDir("e2eTest/src/commonMain")
          68  +
                    resources.srcDir("e2eTest/test-resources")
          69  +
                    dependsOn(this@kotlin.sourceSets.getByName("commonMain"))
          70  +
          71  +
                    dependencies {
          72  +
                        api(libraries.smithy.kotlin.testing)
          73  +
                        implementation(libraries.kotlin.test)
          74  +
                        implementation(libraries.kotlinx.coroutines.test)
          75  +
                        implementation(libraries.smithy.kotlin.http.test)
          76  +
                        implementation(project(":tests:e2e-test-util"))
          77  +
                    }
   70     78   
          79  +
                    if (project.name == "s3") {
   71     80   
                        dependencies {
   72         -
                            api(libraries.smithy.kotlin.testing)
   73         -
                            implementation(libraries.kotlin.test)
   74         -
                            implementation(libraries.kotlin.test.junit5)
   75         -
                            implementation(project(":tests:e2e-test-util"))
   76         -
                            implementation(libraries.slf4j.simple)
          81  +
                            rootProject.findProject(":services:s3control")?.let { implementation(it) }
          82  +
                            rootProject.findProject(":services:sts")?.let { implementation(it) }
   77     83   
                        }
   78     84   
                    }
          85  +
                }
          86  +
            }
   79     87   
   80         -
                    tasks.register<Test>("e2eTest") {
   81         -
                        description = "Run e2e service tests"
   82         -
                        group = "verification"
   83         -
   84         -
                        if (project.name == "s3") {
   85         -
                            dependencies {
   86         -
                                implementation(project(":services:s3control"))
   87         -
                                implementation(project(":services:sts"))
   88         -
                                implementation(libs.smithy.kotlin.aws.signing.crt)
   89         -
                            }
          88  +
            jvm().compilations {
          89  +
                val e2eTest by creating {
          90  +
                    defaultSourceSet {
          91  +
                        kotlin.srcDir("e2eTest/src/jvmMain")
          92  +
                        dependsOn(this@kotlin.sourceSets["e2eTest"])
          93  +
                        dependsOn(this@kotlin.sourceSets.getByName("jvmMain"))
          94  +
                        dependencies {
          95  +
                            implementation(libraries.slf4j.simple)
          96  +
                            implementation(libraries.kotlin.test.junit5)
          97  +
                            implementation(libraries.smithy.kotlin.aws.signing.crt)
   90     98   
                        }
   91     99   
   92    100   
                        if (project.name == "sesv2") {
   93    101   
                            dependencies {
   94         -
                                implementation(libs.smithy.kotlin.aws.signing.crt) // needed for E2E test of SigV4a
         102  +
                                implementation(libraries.smithy.kotlin.aws.signing.crt)
   95    103   
                            }
   96    104   
                        }
   97    105   
   98    106   
                        if (project.name == "route53") {
   99    107   
                            dependencies {
  100         -
                                implementation(libraries.smithy.kotlin.http.test) // needed for URI E2E tests
         108  +
                                implementation(libraries.smithy.kotlin.http.test)
  101    109   
                            }
  102    110   
                        }
         111  +
                    }
  103    112   
  104         -
                        // Run the tests with the classpath containing the compile dependencies (including 'main'),
  105         -
                        // runtime dependencies, and the outputs of this compilation:
         113  +
                    tasks.register<Test>("jvmE2eTest") {
         114  +
                        description = "Run JVM E2E service tests"
         115  +
                        group = "verification"
  106    116   
                        classpath = compileDependencyFiles + runtimeDependencyFiles + output.allOutputs
  107         -
  108         -
                        // Run only the tests from this compilation's outputs:
  109    117   
                        testClassesDirs = output.classesDirs
  110         -
  111    118   
                        useJUnitPlatform()
  112    119   
                        testLogging {
  113    120   
                            events("passed", "skipped", "failed")
  114    121   
                            showStandardStreams = true
  115    122   
                            showStackTraces = true
  116    123   
                            showExceptions = true
  117    124   
                            exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
  118    125   
                        }
  119         -
  120         -
                        // model a random input to enable re-running e2e tests back to back without
  121         -
                        // up-to-date checks or cache getting in the way
  122         -
                        inputs.property("integration.datetime", LocalDateTime.now())
  123    126   
                        systemProperty("org.slf4j.simpleLogger.defaultLogLevel", System.getProperty("org.slf4j.simpleLogger.defaultLogLevel", "WARN"))
  124    127   
                    }
  125    128   
                }
  126    129   
            }
         130  +
         131  +
            targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().configureEach {
         132  +
                val target = this
         133  +
                target.compilations {
         134  +
                    val e2eTest by creating {
         135  +
                        defaultSourceSet {
         136  +
                            kotlin.srcDir("e2eTest/src/nativeMain")
         137  +
                            dependsOn(this@kotlin.sourceSets["e2eTest"])
         138  +
                            dependsOn(this@kotlin.sourceSets.getByName("nativeMain"))
         139  +
                            dependencies {
         140  +
                                implementation(project(":tests:e2e-test-util"))
         141  +
                            }
         142  +
                        }
         143  +
                    }
         144  +
                }
         145  +
         146  +
                binaries.test("e2eTest", listOf(DEBUG)) {
         147  +
                    compilation = target.compilations.getByName("e2eTest")
         148  +
                }
         149  +
         150  +
                tasks.register<Exec>("${target.targetName}E2eTest") {
         151  +
                    description = "Run ${target.targetName} E2E service tests"
         152  +
                    group = "verification"
         153  +
                    val linkTaskName = "linkE2eTestDebugTest${target.targetName.replaceFirstChar { it.uppercase() }}"
         154  +
                    dependsOn(linkTaskName)
         155  +
                    executable = "build/bin/${target.targetName}/e2eTestDebugTest/e2eTest.kexe"
         156  +
         157  +
                    // TODO More consideration needed for running E2E tests on iOS... booting simulator, configuring credentials inside the sim, etc.
         158  +
                    onlyIf { !target.targetName.startsWith("ios") }
         159  +
                }
         160  +
            }
         161  +
         162  +
            tasks.register("nativeE2eTest") {
         163  +
                description = "Run Native E2E service tests"
         164  +
                group = "verification"
         165  +
                targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().forEach {
         166  +
                    dependsOn("${it.targetName}E2eTest")
         167  +
                }
         168  +
            }
         169  +
         170  +
            tasks.register("e2eTest") {
         171  +
                dependsOn("jvmE2eTest")
         172  +
                dependsOn("nativeE2eTest")
         173  +
            }
  127    174   
        }
  128    175   
    }
  129    176   
  130    177   
    tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
  131    178   
        compilerOptions {
  132    179   
            allWarningsAsErrors.set(false) // FIXME Tons of errors occur in generated code
  133    180   
            jvmTarget.set(JvmTarget.JVM_1_8) // fixes outgoing variant metadata: https://github.com/smithy-lang/smithy-kotlin/issues/258
  134    181   
            freeCompilerArgs.add("-Xjdk-release=1.8")
  135    182   
        }
  136    183   
    }

tmp-codegen-diff/services/cloudwatchlogs/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/cloudwatchlogs/GetLogEventsPaginatorTest.kt

Renamed from tmp-codegen-diff/services/cloudwatchlogs/e2eTest/src/GetLogEventsPaginatorTest.kt

@@ -1,1 +80,84 @@
    6      6   
import aws.sdk.kotlin.services.cloudwatchlogs.*
    7      7   
import aws.sdk.kotlin.services.cloudwatchlogs.model.GetLogEventsResponse
    8      8   
import aws.sdk.kotlin.services.cloudwatchlogs.model.InputLogEvent
    9      9   
import aws.sdk.kotlin.services.cloudwatchlogs.model.OutputLogEvent
   10     10   
import aws.sdk.kotlin.services.cloudwatchlogs.paginators.getLogEventsPaginated
   11     11   
import aws.smithy.kotlin.runtime.time.Instant
   12     12   
import aws.smithy.kotlin.runtime.time.epochMilliseconds
   13     13   
import aws.smithy.kotlin.runtime.util.Uuid
   14     14   
import kotlinx.coroutines.*
   15     15   
import kotlinx.coroutines.flow.Flow
          16  +
import kotlinx.coroutines.test.runTest
   16     17   
import kotlin.test.Test
   17     18   
import kotlin.test.assertTrue
   18     19   
import kotlin.time.Duration.Companion.milliseconds
   19     20   
import kotlin.time.Duration.Companion.seconds
   20     21   
   21     22   
private const val MESSAGES_PER_BATCH = 100
   22     23   
private const val BATCHES = 10
   23     24   
private const val TOTAL_MESSAGES = MESSAGES_PER_BATCH * BATCHES
   24     25   
private val TIMESTAMP_DELTA_PER_MESSAGE = (-1).milliseconds
   25     26   
private val POLLING_DELAY = 5.seconds
   26     27   
   27     28   
// Uncertain what a reliable value for this is...technically, _any_ amount of empty pages is possible given how
   28     29   
// token-based pagination works. 🤷
   29     30   
private const val MAX_SEQUENTIAL_EMPTY_PAGES = 5
   30     31   
   31     32   
class GetLogEventsPaginatorTest {
   32     33   
    @Test
   33         -
    fun testGetLogEventsPagination() = runBlocking {
   34         -
        CloudWatchLogsClient.fromEnvironment().use { cwl ->
          34  +
    fun testGetLogEventsPagination() = runTest {
          35  +
        val cwl = CloudWatchLogsClient.fromEnvironment()
          36  +
        try {
   35     37   
            val (group, stream) = cwl.createLogGroupStream()
   36     38   
   37     39   
            try {
   38     40   
                cwl.publishMessageBatches(group, stream)
   39     41   
   40     42   
                val eventFlow = cwl.getLogEventsPaginated {
   41     43   
                    logGroupName = group
   42     44   
                    logStreamName = stream
   43     45   
                    limit = MESSAGES_PER_BATCH
   44     46   
                    startFromHead = true
   45     47   
                }
   46     48   
   47     49   
                assertFlowTerminates(eventFlow)
   48     50   
            } finally {
   49     51   
                cwl.deleteLogGroupStream(group, stream)
   50     52   
            }
          53  +
        } finally {
          54  +
            cwl.close()
   51     55   
        }
   52     56   
    }
   53     57   
}
   54     58   
   55     59   
private suspend fun assertFlowTerminates(eventFlow: Flow<GetLogEventsResponse>) {
   56     60   
    var maxSeen = 0
   57     61   
    var sequentialEmptyPages = 0
   58     62   
    var totalPages = 0
   59     63   
   60     64   
    eventFlow.collect { page ->
@@ -84,88 +152,148 @@
  104    108   
        """.trimIndent(),
  105    109   
    ) { maxSeen == TOTAL_MESSAGES }
  106    110   
}
  107    111   
  108    112   
private fun createMessageBatches(anchorTime: Instant) = (0 until BATCHES).map { batchIndex ->
  109    113   
    (0 until MESSAGES_PER_BATCH).map { batchMessageIndex ->
  110    114   
        val overallMessageIndex = batchIndex * MESSAGES_PER_BATCH + batchMessageIndex
  111    115   
        val timestampDelta = TIMESTAMP_DELTA_PER_MESSAGE * (TOTAL_MESSAGES - overallMessageIndex)
  112    116   
  113    117   
        InputLogEvent {
  114         -
            message = String.format(
  115         -
                "Message %d/%d (%d/%d in batch %d/%d)",
  116         -
                overallMessageIndex + 1,
  117         -
                TOTAL_MESSAGES,
  118         -
                batchMessageIndex + 1,
  119         -
                MESSAGES_PER_BATCH,
  120         -
                batchIndex + 1,
  121         -
                BATCHES,
  122         -
            )
         118  +
            message = "Message ${overallMessageIndex + 1}/$TOTAL_MESSAGES (${batchMessageIndex + 1}/$MESSAGES_PER_BATCH in batch ${batchIndex + 1}/$BATCHES)"
  123    119   
  124    120   
            timestamp = (anchorTime + timestampDelta).epochMilliseconds
  125    121   
        }
  126    122   
    }
  127    123   
}
  128    124   
  129    125   
private suspend fun CloudWatchLogsClient.createLogGroupStream(): Pair<String, String> {
  130    126   
    val group = "paginator-test-group_${Uuid.random()}"
  131    127   
    val stream = "paginator-test-stream_${Uuid.random()}"
  132    128   

tmp-codegen-diff/services/dynamodb/e2eTest/src/DynamoDbTestUtils.kt

@@ -1,0 +10,0 @@
    1         -
/*
    2         -
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3         -
 * SPDX-License-Identifier: Apache-2.0
    4         -
 */
    5         -
package aws.sdk.kotlin.services.dynamodb
    6         -
    7         -
/**
    8         -
 * Checks if a Dynamo DB table exists based on its name
    9         -
 */
   10         -
internal suspend fun DynamoDbClient.tableExists(name: String): Boolean = this.listTables {}.tableNames?.contains(name) ?: false

tmp-codegen-diff/services/dynamodb/e2eTest/src/PaginatorTest.kt

@@ -1,0 +121,0 @@
    1         -
/*
    2         -
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3         -
 * SPDX-License-Identifier: Apache-2.0
    4         -
 */
    5         -
package aws.sdk.kotlin.services.dynamodb
    6         -
    7         -
import aws.sdk.kotlin.services.dynamodb.model.*
    8         -
import aws.sdk.kotlin.services.dynamodb.paginators.scanPaginated
    9         -
import aws.sdk.kotlin.services.dynamodb.waiters.waitUntilTableExists
   10         -
import kotlinx.coroutines.runBlocking
   11         -
import kotlinx.coroutines.test.runTest
   12         -
import org.junit.jupiter.api.AfterAll
   13         -
import org.junit.jupiter.api.BeforeAll
   14         -
import org.junit.jupiter.api.TestInstance
   15         -
import kotlin.random.Random
   16         -
import kotlin.test.Test
   17         -
import kotlin.test.assertEquals
   18         -
import kotlin.time.Duration.Companion.seconds
   19         -
   20         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   21         -
class PaginatorTest {
   22         -
    private val client = DynamoDbClient { region = "us-west-2" }
   23         -
    private val table = "testTable${Random.nextInt()}"
   24         -
   25         -
    @BeforeAll
   26         -
    private fun setUp(): Unit = runBlocking {
   27         -
        if (!client.tableExists(table)) {
   28         -
            client.createTable {
   29         -
                tableName = table
   30         -
                attributeDefinitions = listOf(
   31         -
                    AttributeDefinition {
   32         -
                        attributeName = "Artist"
   33         -
                        attributeType = ScalarAttributeType.S
   34         -
                    },
   35         -
                    AttributeDefinition {
   36         -
                        attributeName = "SongTitle"
   37         -
                        attributeType = ScalarAttributeType.S
   38         -
                    },
   39         -
                )
   40         -
                keySchema = listOf(
   41         -
                    KeySchemaElement {
   42         -
                        attributeName = "Artist"
   43         -
                        keyType = KeyType.Hash
   44         -
                    },
   45         -
                    KeySchemaElement {
   46         -
                        attributeName = "SongTitle"
   47         -
                        keyType = KeyType.Range
   48         -
                    },
   49         -
                )
   50         -
                provisionedThroughput = ProvisionedThroughput {
   51         -
                    readCapacityUnits = 5
   52         -
                    writeCapacityUnits = 5
   53         -
                }
   54         -
                tableClass = TableClass.Standard
   55         -
            }
   56         -
   57         -
            client.waitUntilTableExists {
   58         -
                tableName = table
   59         -
            }
   60         -
        }
   61         -
    }
   62         -
   63         -
    @AfterAll
   64         -
    private fun cleanUp(): Unit = runBlocking {
   65         -
        if (client.tableExists(table)) {
   66         -
            client.deleteTable {
   67         -
                tableName = table
   68         -
            }
   69         -
        }
   70         -
        client.close()
   71         -
    }
   72         -
   73         -
    @Test
   74         -
    fun scanPaginatedRespectsExclusiveStartKey() = runTest(
   75         -
        timeout = 20.seconds,
   76         -
    ) {
   77         -
        client.putItem {
   78         -
            tableName = table
   79         -
            item = mapOf(
   80         -
                "Artist" to AttributeValue.S("Foo"),
   81         -
                "SongTitle" to AttributeValue.S("Bar"),
   82         -
            )
   83         -
        }
   84         -
   85         -
        client.putItem {
   86         -
            tableName = table
   87         -
            item = mapOf(
   88         -
                "Artist" to AttributeValue.S("Foo"),
   89         -
                "SongTitle" to AttributeValue.S("Baz"),
   90         -
            )
   91         -
        }
   92         -
   93         -
        client.putItem {
   94         -
            tableName = table
   95         -
            item = mapOf(
   96         -
                "Artist" to AttributeValue.S("Foo"),
   97         -
                "SongTitle" to AttributeValue.S("Qux"),
   98         -
            )
   99         -
        }
  100         -
  101         -
        val results = mutableListOf<Map<String, AttributeValue>?>()
  102         -
  103         -
        client.scanPaginated {
  104         -
            tableName = table
  105         -
            exclusiveStartKey = mapOf(
  106         -
                "Artist" to AttributeValue.S("Foo"),
  107         -
                "SongTitle" to AttributeValue.S("Bar"),
  108         -
            )
  109         -
            limit = 1
  110         -
        }.collect { scan ->
  111         -
            if (scan.items?.isNotEmpty() == true) {
  112         -
                results.add(scan.items.single())
  113         -
            }
  114         -
        }
  115         -
  116         -
        assertEquals(2, results.size)
  117         -
        // NOTE: Items are returned in alphabetical order
  118         -
        assertEquals((AttributeValue.S("Baz")), results[0]?.get("SongTitle"))
  119         -
        assertEquals((AttributeValue.S("Qux")), results[1]?.get("SongTitle"))
  120         -
    }
  121         -
}

tmp-codegen-diff/services/dynamodb/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/dynamodb/PaginatorTest.kt

@@ -0,1 +0,126 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
package aws.sdk.kotlin.services.dynamodb
           6  +
           7  +
import aws.sdk.kotlin.services.dynamodb.model.*
           8  +
import aws.sdk.kotlin.services.dynamodb.paginators.scanPaginated
           9  +
import aws.sdk.kotlin.services.dynamodb.waiters.waitUntilTableExists
          10  +
import aws.smithy.kotlin.runtime.util.Uuid
          11  +
import kotlinx.coroutines.runBlocking
          12  +
import kotlinx.coroutines.withTimeout
          13  +
import kotlin.test.AfterTest
          14  +
import kotlin.test.BeforeTest
          15  +
import kotlin.test.Test
          16  +
import kotlin.test.assertEquals
          17  +
import kotlin.time.Duration.Companion.seconds
          18  +
          19  +
class PaginatorTest {
          20  +
    private lateinit var client: DynamoDbClient
          21  +
    private val table = "testTable-${Uuid.random()}"
          22  +
          23  +
    @BeforeTest
          24  +
    fun setUp() = runBlocking {
          25  +
        client = DynamoDbClient { region = "us-west-2" }
          26  +
          27  +
        if (!client.tableExists(table)) {
          28  +
            client.createTable {
          29  +
                tableName = table
          30  +
                attributeDefinitions = listOf(
          31  +
                    AttributeDefinition {
          32  +
                        attributeName = "Artist"
          33  +
                        attributeType = ScalarAttributeType.S
          34  +
                    },
          35  +
                    AttributeDefinition {
          36  +
                        attributeName = "SongTitle"
          37  +
                        attributeType = ScalarAttributeType.S
          38  +
                    },
          39  +
                )
          40  +
                keySchema = listOf(
          41  +
                    KeySchemaElement {
          42  +
                        attributeName = "Artist"
          43  +
                        keyType = KeyType.Hash
          44  +
                    },
          45  +
                    KeySchemaElement {
          46  +
                        attributeName = "SongTitle"
          47  +
                        keyType = KeyType.Range
          48  +
                    },
          49  +
                )
          50  +
                provisionedThroughput = ProvisionedThroughput {
          51  +
                    readCapacityUnits = 5
          52  +
                    writeCapacityUnits = 5
          53  +
                }
          54  +
                tableClass = TableClass.Standard
          55  +
            }
          56  +
          57  +
            client.waitUntilTableExists {
          58  +
                tableName = table
          59  +
            }
          60  +
        }
          61  +
    }
          62  +
          63  +
    @AfterTest
          64  +
    fun cleanUp() = runBlocking {
          65  +
        if (client.tableExists(table)) {
          66  +
            client.deleteTable {
          67  +
                tableName = table
          68  +
            }
          69  +
        }
          70  +
        client.close()
          71  +
    }
          72  +
          73  +
    @Test
          74  +
    fun scanPaginatedRespectsExclusiveStartKey() = runBlocking {
          75  +
        withTimeout(20.seconds) {
          76  +
            client.putItem {
          77  +
                tableName = table
          78  +
                item = mapOf(
          79  +
                    "Artist" to AttributeValue.S("Foo"),
          80  +
                    "SongTitle" to AttributeValue.S("Bar"),
          81  +
                )
          82  +
            }
          83  +
          84  +
            client.putItem {
          85  +
                tableName = table
          86  +
                item = mapOf(
          87  +
                    "Artist" to AttributeValue.S("Foo"),
          88  +
                    "SongTitle" to AttributeValue.S("Baz"),
          89  +
                )
          90  +
            }
          91  +
          92  +
            client.putItem {
          93  +
                tableName = table
          94  +
                item = mapOf(
          95  +
                    "Artist" to AttributeValue.S("Foo"),
          96  +
                    "SongTitle" to AttributeValue.S("Qux"),
          97  +
                )
          98  +
            }
          99  +
         100  +
            val results = mutableListOf<Map<String, AttributeValue>?>()
         101  +
         102  +
            client.scanPaginated {
         103  +
                tableName = table
         104  +
                exclusiveStartKey = mapOf(
         105  +
                    "Artist" to AttributeValue.S("Foo"),
         106  +
                    "SongTitle" to AttributeValue.S("Bar"),
         107  +
                )
         108  +
                limit = 1
         109  +
            }.collect { scan ->
         110  +
                if (scan.items?.isNotEmpty() == true) {
         111  +
                    results.add(scan.items.single())
         112  +
                }
         113  +
            }
         114  +
         115  +
            assertEquals(2, results.size)
         116  +
            // NOTE: Items are returned in alphabetical order
         117  +
            assertEquals((AttributeValue.S("Baz")), results[0]?.get("SongTitle"))
         118  +
            assertEquals((AttributeValue.S("Qux")), results[1]?.get("SongTitle"))
         119  +
        }
         120  +
    }
         121  +
}
         122  +
         123  +
/**
         124  +
 * Checks if a Dynamo DB table exists based on its name
         125  +
 */
         126  +
private suspend fun DynamoDbClient.tableExists(name: String): Boolean = this.listTables {}.tableNames?.contains(name) ?: false

tmp-codegen-diff/services/kinesis/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/kinesis/KinesisSubscribeToShardTest.kt

Renamed from tmp-codegen-diff/services/kinesis/e2eTest/src/KinesisSubscribeToShardTest.kt

@@ -1,1 +191,195 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
package aws.sdk.kotlin.services.kinesis
    6      6   
    7      7   
import aws.sdk.kotlin.services.kinesis.model.*
    8      8   
import aws.sdk.kotlin.services.kinesis.waiters.waitUntilStreamExists
    9      9   
import aws.sdk.kotlin.testing.withAllEngines
          10  +
import aws.smithy.kotlin.runtime.io.use
   10     11   
import aws.smithy.kotlin.runtime.retries.getOrThrow
   11     12   
import kotlinx.coroutines.*
   12     13   
import kotlinx.coroutines.flow.first
   13         -
import org.junit.jupiter.api.AfterAll
   14         -
import org.junit.jupiter.api.BeforeAll
   15         -
import org.junit.jupiter.api.TestInstance
   16         -
import java.util.*
   17         -
import kotlin.test.Test
   18         -
import kotlin.test.assertEquals
          14  +
import kotlin.test.*
   19     15   
import kotlin.time.Duration.Companion.seconds
   20     16   
   21     17   
private val WAIT_TIMEOUT = 30.seconds
   22     18   
private val POLLING_RATE = 3.seconds
   23     19   
   24     20   
private val STREAM_NAME_PREFIX = "aws-sdk-kotlin-e2e-test-stream-"
   25     21   
private val STREAM_CONSUMER_NAME_PREFIX = "aws-sdk-kotlin-e2e-test-"
   26     22   
   27     23   
private val TEST_DATA = "Bees, bees, bees, bees!"
   28     24   
   29     25   
/**
   30     26   
 * Tests for Kinesis SubscribeToShard (an RPC-bound protocol)
   31     27   
 */
   32         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   33     28   
class KinesisSubscribeToShardTest {
   34     29   
    private val client = KinesisClient { region = "us-east-1" }
   35     30   
   36     31   
    private lateinit var dataStreamArn: String
   37     32   
    private lateinit var dataStreamConsumerArn: String
   38     33   
   39         -
    /**
   40         -
     * Create infrastructure required for the test, if it doesn't exist already.
   41         -
     */
   42         -
    @BeforeAll
          34  +
    @BeforeTest
   43     35   
    fun setup(): Unit = runBlocking {
          36  +
        println("Setting up...")
   44     37   
        dataStreamArn = client.getOrCreateStream()
   45     38   
        dataStreamConsumerArn = client.getOrRegisterStreamConsumer()
   46     39   
    }
   47     40   
   48         -
    /**
   49         -
     * Delete infrastructure used for the test.
   50         -
     */
   51         -
    @AfterAll
          41  +
    @AfterTest
   52     42   
    fun cleanUp(): Unit = runBlocking {
          43  +
        println("Cleaning up...")
   53     44   
        client.deregisterStreamConsumer {
   54     45   
            streamArn = dataStreamArn
   55     46   
            consumerArn = dataStreamConsumerArn
   56     47   
        }
   57     48   
   58     49   
        client.deleteStream {
   59     50   
            streamArn = dataStreamArn
   60     51   
        }
   61     52   
    }
   62     53   
   63     54   
    /**
   64     55   
     * Select the single shard ID associated with the data stream, and subscribe to it.
   65     56   
     * Read one event and make sure the data matches what's expected.
   66     57   
     */
   67     58   
    @Test
   68     59   
    fun testSubscribeToShard(): Unit = runBlocking {
          60  +
        println("testSubscribeToShard starting...")
   69     61   
        val dataStreamShardId = client.listShards {
   70     62   
            streamArn = dataStreamArn
   71     63   
        }.shards?.single()!!.shardId
          64  +
        println("Found stream shard ID: $dataStreamShardId")
   72     65   
   73     66   
        withAllEngines { engine ->
   74     67   
            client.withConfig {
   75     68   
                httpClient = engine
   76     69   
            }.use { clientWithTestEngine ->
          70  +
                println("Subscribing to shard...")
   77     71   
                clientWithTestEngine.subscribeToShard(
   78     72   
                    SubscribeToShardRequest {
   79     73   
                        consumerArn = dataStreamConsumerArn
   80     74   
                        shardId = dataStreamShardId
   81     75   
                        startingPosition = StartingPosition {
   82     76   
                            type = ShardIteratorType.TrimHorizon
   83     77   
                        }
   84     78   
                    },
   85     79   
                ) {
   86     80   
                    val event = it.eventStream?.first()
          81  +
                    println("Got an event! $event")
   87     82   
                    val record = event?.asSubscribeToShardEvent()?.records?.single()
   88     83   
                    assertEquals(TEST_DATA, record?.data?.decodeToString())
   89     84   
                }
   90     85   
   91     86   
                // Wait 5 seconds, otherwise a ResourceInUseException gets thrown. Source:
   92     87   
                // https://docs.aws.amazon.com/kinesis/latest/APIReference/API_SubscribeToShard.html
   93     88   
                // > If you call SubscribeToShard 5 seconds or more after a successful call, the second call takes over the subscription
   94     89   
                delay(5.seconds)
   95     90   
            }
   96     91   
        }
          92  +
        println("testSubscribeToShard ending...")
   97     93   
    }
   98     94   
   99     95   
    /**
  100     96   
     * Get a Kinesis data stream with the [STREAM_NAME_PREFIX], or if one does not exist,
  101     97   
     * create one and populate it with one test record.
  102     98   
     * @return the ARN of the data stream
  103     99   
     */
  104    100   
    private suspend fun KinesisClient.getOrCreateStream(): String = listStreams { }
  105    101   
        .streamSummaries
  106    102   
        ?.find { it.streamName?.startsWith(STREAM_NAME_PREFIX) ?: false }
  107    103   
        ?.streamArn ?: run {
  108    104   
        // Create a new data stream, then wait for it to be active
  109         -
        val randomStreamName = STREAM_NAME_PREFIX + UUID.randomUUID()
         105  +
        val randomStreamName = STREAM_NAME_PREFIX + (0..Int.MAX_VALUE).random()
         106  +
        println("Creating a new stream: $randomStreamName")
  110    107   
        createStream {
  111    108   
            streamName = randomStreamName
  112    109   
            shardCount = 1
  113    110   
        }
  114    111   
         112  +
        println("Waiting until stream exists...")
  115    113   
        val newStreamArn = waitUntilStreamExists({ streamName = randomStreamName })
  116    114   
            .getOrThrow()
  117    115   
            .streamDescription!!
  118    116   
            .streamArn!!
         117  +
        println("New stream now exists: $newStreamArn ")
  119    118   
  120    119   
        // Put a record, then wait for it to appear on the stream
         120  +
        println("Putting a record on the stream...")
  121    121   
        putRecord {
  122    122   
            data = TEST_DATA.encodeToByteArray()
  123    123   
            streamArn = newStreamArn
  124    124   
            partitionKey = "Goodbye"
  125    125   
        }
  126    126   
  127    127   
        val newStreamShardId = client.listShards {
  128    128   
            streamArn = newStreamArn
  129    129   
        }.shards?.single()!!.shardId
         130  +
        println("New stream's shard ID: $newStreamShardId")
  130    131   
  131    132   
        val currentShardIterator = getShardIterator {
  132    133   
            shardId = newStreamShardId
  133    134   
            shardIteratorType = ShardIteratorType.TrimHorizon
  134    135   
            streamArn = newStreamArn
  135    136   
        }.shardIterator!!
         137  +
        println("New stream's shard iterator: $currentShardIterator")
  136    138   
  137    139   
        waitForResource {
  138    140   
            getRecords {
  139    141   
                shardIterator = currentShardIterator
  140    142   
                streamArn = newStreamArn
  141    143   
            }.records
  142    144   
                ?.firstOrNull { it.data?.decodeToString() == TEST_DATA }
  143    145   
        }
  144    146   
  145    147   
        newStreamArn
  146    148   
    }
  147    149   
  148    150   
    /**
  149    151   
     * Get a Kinesis data stream consumer, or if it doesn't exist, register a new one.
  150    152   
     * @return the ARN of the stream consumer
  151    153   
     */
  152    154   
    private suspend fun KinesisClient.getOrRegisterStreamConsumer(): String = listStreamConsumers { streamArn = dataStreamArn }
  153    155   
        .consumers
  154    156   
        ?.firstOrNull { it.consumerName?.startsWith(STREAM_CONSUMER_NAME_PREFIX) ?: false }
  155    157   
        ?.consumerArn ?: run {
  156    158   
        // Register a new consumer and wait for it to be active
  157    159   
  158         -
        val randomConsumerName = STREAM_CONSUMER_NAME_PREFIX + UUID.randomUUID()
         160  +
        val randomConsumerName = STREAM_CONSUMER_NAME_PREFIX + (0..Int.MAX_VALUE).random()
         161  +
        println("Registering a new stream consumer: $randomConsumerName")
  159    162   
        registerStreamConsumer {
  160    163   
            consumerName = randomConsumerName
  161    164   
            streamArn = dataStreamArn
  162    165   
        }
  163    166   
         167  +
        println("Waiting for consumer to become active")
  164    168   
        waitForResource {
  165    169   
            listStreamConsumers { streamArn = dataStreamArn }
  166    170   
                ?.consumers
  167    171   
                ?.firstOrNull { it.consumerName == randomConsumerName }
  168    172   
                ?.takeIf { it.consumerStatus == ConsumerStatus.Active }
  169    173   
                ?.consumerArn
  170    174   
        }
  171    175   
    }
  172    176   
  173    177   
    /**

tmp-codegen-diff/services/polly/common/test/aws/sdk/kotlin/services/polly/PollyTest.kt

@@ -20,20 +61,61 @@
   40     40   
        }
   41     41   
   42     42   
        try {
   43     43   
            val presignedRequest = pollyClient.presignSynthesizeSpeech(request, 10.seconds)
   44     44   
   45     45   
            assertEquals(HttpMethod.GET, presignedRequest.method)
   46     46   
            assertTrue("Host".equals(presignedRequest.headers.entries().single().key, ignoreCase = true))
   47     47   
            assertEquals("polly.us-east-2.amazonaws.com", presignedRequest.headers["Host"])
   48     48   
            assertEquals("/v1/speech", presignedRequest.url.path.toString())
   49     49   
            val expectedQueryParameters = setOf("OutputFormat", "Text", "VoiceId", "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date", "X-Amz-SignedHeaders", "X-Amz-Expires", "X-Amz-Signature")
   50         -
            assertEquals(expectedQueryParameters, presignedRequest.url.parameters.encodedParameters.keys)
          50  +
            assertEquals(expectedQueryParameters, presignedRequest.url.parameters.encodedParameters.keys.toSet())
   51     51   
        } finally {
   52     52   
            pollyClient.close()
   53     53   
        }
   54     54   
    }
   55     55   
}
   56     56   
   57     57   
object NoHttpEngine : HttpClientEngineBase("no-http") {
   58     58   
    override val config: HttpClientEngineConfig = HttpClientEngineConfig.Default
   59     59   
   60     60   
    override suspend fun roundTrip(context: ExecutionContext, request: HttpRequest): HttpCall = error("Should not need HTTP round trip")

tmp-codegen-diff/services/polly/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/polly/PollyPresignerTest.kt

Renamed from tmp-codegen-diff/services/polly/e2eTest/src/PollyPresignerTest.kt

@@ -1,1 +45,43 @@
    4      4   
 */
    5      5   
package aws.sdk.kotlin.services.polly
    6      6   
    7      7   
import aws.sdk.kotlin.services.polly.model.OutputFormat
    8      8   
import aws.sdk.kotlin.services.polly.model.SynthesizeSpeechRequest
    9      9   
import aws.sdk.kotlin.services.polly.model.VoiceId
   10     10   
import aws.sdk.kotlin.services.polly.presigners.presignSynthesizeSpeech
   11     11   
import aws.sdk.kotlin.testing.withAllEngines
   12     12   
import aws.smithy.kotlin.runtime.http.SdkHttpClient
   13     13   
import aws.smithy.kotlin.runtime.http.complete
   14         -
import kotlinx.coroutines.runBlocking
   15         -
import org.junit.jupiter.api.TestInstance
          14  +
import kotlinx.coroutines.test.runTest
   16     15   
import kotlin.test.Test
   17     16   
import kotlin.test.assertEquals
   18     17   
import kotlin.time.Duration.Companion.seconds
   19     18   
   20     19   
/**
   21     20   
 * Tests for presigner
   22     21   
 */
   23         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   24     22   
class PollyPresignerTest {
   25     23   
    @Test
   26         -
    fun clientBasedPresign() = runBlocking {
          24  +
    fun clientBasedPresign() = runTest {
   27     25   
        val unsignedRequest = SynthesizeSpeechRequest {
   28     26   
            voiceId = VoiceId.Salli
   29     27   
            outputFormat = OutputFormat.Pcm
   30     28   
            text = "hello world"
   31     29   
        }
   32     30   
   33     31   
        val client = PollyClient { region = "us-east-1" }
   34     32   
        val presignedRequest = client.presignSynthesizeSpeech(unsignedRequest, 10.seconds)
   35     33   
   36     34   
        withAllEngines { engine ->

tmp-codegen-diff/services/route53/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/route53/InvalidChangeBatchTest.kt

Renamed from tmp-codegen-diff/services/route53/e2eTest/src/InvalidChangeBatchTest.kt

@@ -1,1 +52,52 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
package aws.sdk.kotlin.services.route53
    6      6   
    7      7   
import aws.sdk.kotlin.services.route53.model.*
    8         -
import aws.smithy.kotlin.runtime.util.Uuid
           8  +
import aws.smithy.kotlin.runtime.io.use
    9      9   
import kotlinx.coroutines.test.runTest
   10     10   
import kotlin.test.Test
   11     11   
import kotlin.test.assertFailsWith
   12     12   
import kotlin.test.assertNotNull
   13     13   
   14     14   
// https://github.com/awslabs/aws-sdk-kotlin/issues/1433
   15     15   
class InvalidChangeBatchTest {
   16     16   
    @Test
   17     17   
    fun testMessageIsPopulated() = runTest {
   18     18   
        Route53Client {
   19     19   
            region = "us-east-1"
   20     20   
        }.use { client ->
   21     21   
            val createHostedZoneResp = client.createHostedZone {
   22         -
                this.callerReference = Uuid.random().toString()
          22  +
                this.callerReference = (0..Int.MAX_VALUE).random().toString()
   23     23   
                this.name = "this-is-a-test-hosted-zone-for-aws-sdk-kotlin.com"
   24     24   
            }
   25     25   
   26     26   
            val hostedZoneId = checkNotNull(createHostedZoneResp.hostedZone?.id) { "Hosted zone is unexpectedly null" }
   27     27   
   28     28   
            try {
   29     29   
                val exception = assertFailsWith<InvalidChangeBatch> {
   30     30   
                    client.changeResourceRecordSets {
   31     31   
                        this.hostedZoneId = hostedZoneId
   32     32   
                        this.changeBatch = ChangeBatch {

tmp-codegen-diff/services/route53/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/route53/Route53UriTest.kt

Renamed from tmp-codegen-diff/services/route53/e2eTest/src/Route53UriTest.kt

@@ -1,1 +43,44 @@
    4      4   
 */
    5      5   
    6      6   
package aws.sdk.kotlin.services.route53
    7      7   
    8      8   
import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
    9      9   
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
   10     10   
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
   11     11   
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor
   12     12   
import aws.smithy.kotlin.runtime.http.request.HttpRequest
   13     13   
import aws.smithy.kotlin.runtime.httptest.TestEngine
          14  +
import aws.smithy.kotlin.runtime.io.use
   14     15   
import kotlinx.coroutines.test.runTest
   15     16   
import kotlin.test.Test
   16     17   
import kotlin.test.assertEquals
   17     18   
import kotlin.test.fail
   18     19   
   19     20   
class Route53UriTest {
   20     21   
    /**
   21     22   
     * Validates that HostedZoneId isn't trimmed when not prefixed
   22     23   
     */
   23     24   
    @Test

tmp-codegen-diff/services/s3/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/s3/MultiRegionAccessPointTest.kt

Renamed from tmp-codegen-diff/services/s3/e2eTest/src/MultiRegionAccessPointTest.kt

@@ -1,1 +195,179 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
package aws.sdk.kotlin.e2etest
    6      6   
    7         -
import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketAndAllContents
    8      7   
import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId
    9      8   
import aws.sdk.kotlin.e2etest.S3TestUtils.getBucketWithPrefix
   10      9   
import aws.sdk.kotlin.services.s3.S3Client
   11     10   
import aws.sdk.kotlin.services.s3.deleteObject
   12     11   
import aws.sdk.kotlin.services.s3.putObject
   13     12   
import aws.sdk.kotlin.services.s3.withConfig
   14     13   
import aws.sdk.kotlin.services.s3control.S3ControlClient
   15     14   
import aws.sdk.kotlin.services.s3control.createMultiRegionAccessPoint
   16     15   
import aws.sdk.kotlin.services.s3control.deleteMultiRegionAccessPoint
   17     16   
import aws.sdk.kotlin.services.s3control.describeMultiRegionAccessPointOperation
   18     17   
import aws.sdk.kotlin.services.s3control.getMultiRegionAccessPoint
   19     18   
import aws.sdk.kotlin.services.s3control.model.Region
   20     19   
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
   21     20   
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
   22     21   
import aws.smithy.kotlin.runtime.auth.awssigning.crt.CrtAwsSigner
   23     22   
import aws.smithy.kotlin.runtime.http.auth.SigV4AsymmetricAuthScheme
          23  +
import aws.smithy.kotlin.runtime.testing.AfterAll
          24  +
import aws.smithy.kotlin.runtime.testing.BeforeAll
   24     25   
import kotlinx.coroutines.delay
   25     26   
import kotlinx.coroutines.runBlocking
   26     27   
import kotlinx.coroutines.withTimeout
   27         -
import org.junit.jupiter.api.AfterAll
   28         -
import org.junit.jupiter.api.BeforeAll
   29         -
import org.junit.jupiter.api.TestInstance
   30         -
import org.junit.jupiter.params.ParameterizedTest
   31         -
import org.junit.jupiter.params.provider.Arguments
   32         -
import org.junit.jupiter.params.provider.MethodSource
   33         -
import java.util.stream.Stream
          28  +
import kotlin.jvm.JvmStatic
          29  +
import kotlin.test.Test
   34     30   
import kotlin.time.Duration
   35     31   
import kotlin.time.Duration.Companion.minutes
   36     32   
import kotlin.time.Duration.Companion.seconds
   37     33   
   38     34   
private const val MRAP_BUCKET_PREFIX = "s3-mrap-test-bucket-"
   39     35   
private const val MULTI_REGION_ACCESS_POINT_NAME = "aws-sdk-for-kotlin-test-multi-region-access-point"
   40     36   
private const val TEST_OBJECT_KEY = "test.txt"
   41     37   
   42         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   43     38   
class MutliRegionAccessPointTest {
   44         -
    private lateinit var s3West: S3Client
   45         -
    private lateinit var s3East: S3Client
   46         -
    private lateinit var s3Control: S3ControlClient
   47         -
   48         -
    private lateinit var accountId: String
   49         -
    private lateinit var multiRegionAccessPointArn: String
   50         -
    private lateinit var usWestBucket: String
   51         -
    private lateinit var usEastBucket: String
   52         -
   53         -
    @BeforeAll
   54         -
    fun setup(): Unit = runBlocking {
   55         -
        s3West = S3Client { region = "us-west-2" }
   56         -
        s3East = S3Client { region = "us-east-2" }
   57         -
        s3Control = S3ControlClient { region = "us-west-2" }
   58         -
   59         -
        accountId = getAccountId()
   60         -
        usWestBucket = getBucketWithPrefix(s3West, MRAP_BUCKET_PREFIX, "us-west-2", accountId)
   61         -
        usEastBucket = getBucketWithPrefix(s3East, MRAP_BUCKET_PREFIX, "us-east-2", accountId)
   62         -
   63         -
        multiRegionAccessPointArn = s3Control.createMultiRegionAccessPoint(
   64         -
            MULTI_REGION_ACCESS_POINT_NAME,
   65         -
            accountId,
   66         -
            listOf(usWestBucket, usEastBucket),
   67         -
        )
   68         -
    }
          39  +
    companion object {
          40  +
        private lateinit var s3West: S3Client
          41  +
        private lateinit var s3East: S3Client
          42  +
        private lateinit var s3Control: S3ControlClient
          43  +
        private lateinit var accountId: String
          44  +
        private lateinit var multiRegionAccessPointArn: String
          45  +
        private lateinit var usWestBucket: String
          46  +
        private lateinit var usEastBucket: String
          47  +
          48  +
        @BeforeAll
          49  +
        @JvmStatic
          50  +
        fun setup() = runBlocking {
          51  +
            s3West = S3Client { region = "us-west-2" }
          52  +
            s3East = S3Client { region = "us-east-2" }
          53  +
            s3Control = S3ControlClient { region = "us-west-2" }
          54  +
          55  +
            accountId = getAccountId()
          56  +
            usWestBucket = getBucketWithPrefix(s3West, MRAP_BUCKET_PREFIX, "us-west-2", accountId)
          57  +
            usEastBucket = getBucketWithPrefix(s3East, MRAP_BUCKET_PREFIX, "us-east-2", accountId)
          58  +
          59  +
            multiRegionAccessPointArn = s3Control.createMultiRegionAccessPoint(
          60  +
                MULTI_REGION_ACCESS_POINT_NAME,
          61  +
                accountId,
          62  +
                listOf(usWestBucket, usEastBucket),
          63  +
            )
          64  +
        }
   69     65   
   70         -
    @AfterAll
   71         -
    fun cleanup(): Unit = runBlocking {
   72         -
        s3Control.deleteMultiRegionAccessPoint(MULTI_REGION_ACCESS_POINT_NAME, accountId)
          66  +
        @AfterAll
          67  +
        @JvmStatic
          68  +
        fun cleanup() = runBlocking {
          69  +
            s3Control.deleteMultiRegionAccessPoint(MULTI_REGION_ACCESS_POINT_NAME, accountId)
   73     70   
   74         -
        deleteBucketAndAllContents(s3West, usWestBucket)
   75         -
        deleteBucketAndAllContents(s3East, usEastBucket)
          71  +
            s3West.close()
          72  +
            s3East.close()
          73  +
            s3Control.close()
          74  +
        }
          75  +
    }
   76     76   
   77         -
        s3West.close()
   78         -
        s3East.close()
   79         -
        s3Control.close()
          77  +
    @Test
          78  +
    fun testMultiRegionAccessPointOperation() = runBlocking {
          79  +
        // use .distinct() to deduplicate DefaultAwsSigner and CrtAwsSigner, which are the same on Native
          80  +
        listOf(DefaultAwsSigner, CrtAwsSigner).distinct().forEach { signer ->
          81  +
            testMultiRegionAccessPointOperation(signer)
          82  +
        }
   80     83   
    }
   81     84   
   82         -
    @ParameterizedTest
   83         -
    @MethodSource("signerProvider")
   84         -
    fun testMultiRegionAccessPointOperation(signer: AwsSigner): Unit = runBlocking {
          85  +
    private suspend fun testMultiRegionAccessPointOperation(signer: AwsSigner) {
   85     86   
        println("Testing multi-region access point operations with $signer")
   86     87   
   87     88   
        val s3SigV4a = s3West.withConfig {
   88     89   
            authSchemes = listOf(SigV4AsymmetricAuthScheme(signer))
   89     90   
        }
   90     91   
   91     92   
        s3SigV4a.putObject {
   92     93   
            bucket = multiRegionAccessPointArn
   93     94   
            key = TEST_OBJECT_KEY
   94     95   
        }
   95     96   
   96     97   
        s3SigV4a.deleteObject {
   97     98   
            bucket = multiRegionAccessPointArn
   98     99   
            key = TEST_OBJECT_KEY
   99    100   
        }
  100    101   
    }
  101         -
  102         -
    fun signerProvider(): Stream<Arguments> = Stream.of(
  103         -
        Arguments.of(DefaultAwsSigner),
  104         -
        Arguments.of(CrtAwsSigner),
  105         -
    )
  106    102   
}
  107    103   
  108         -
/**
  109         -
 * Create a multi-region access point named [name] in account [accountId] with [buckets] buckets.
  110         -
 * @return the ARN of the multi-region access point that was created
  111         -
 */
  112    104   
private suspend fun S3ControlClient.createMultiRegionAccessPoint(
  113    105   
    name: String,
  114    106   
    accountId: String,
  115    107   
    buckets: List<String>,
  116    108   
): String {
  117    109   
    println("Creating multi-region access point: $name")
  118    110   
  119    111   
    val requestTokenArn = checkNotNull(
  120    112   
        createMultiRegionAccessPoint {
  121    113   
            this.accountId = accountId
  122    114   
            details {
  123    115   
                this.name = name
  124    116   
                this.regions = buckets.map { Region { bucket = it } }
  125    117   
            }
  126    118   
        }.requestTokenArn,
  127    119   
    ) { "createMultiRegionAccessPoint requestTokenArn was unexpectedly null" }
  128    120   
  129    121   
    waitUntilOperationCompletes("createMultiRegionAccessPoint", accountId, requestTokenArn, 10.minutes)
  130    122   
  131         -
    return getMultiRegionAccessPointArn(name, accountId)
         123  +
    return getMultiRegionAccessPoint {
         124  +
        this.name = name
         125  +
        this.accountId = accountId
         126  +
    }.accessPoint?.alias?.let {
         127  +
        "arn:aws:s3::$accountId:accesspoint/$it"
         128  +
    } ?: throw IllegalStateException("Failed to get ARN for multi-region access point $name")
  132    129   
}
  133    130   
  134         -
private suspend fun S3ControlClient.getMultiRegionAccessPointArn(
  135         -
    name: String,
  136         -
    accountId: String,
  137         -
): String = getMultiRegionAccessPoint {
  138         -
    this.name = name
  139         -
    this.accountId = accountId
  140         -
}.accessPoint?.alias?.let {
  141         -
    "arn:aws:s3::$accountId:accesspoint/$it"
  142         -
} ?: throw IllegalStateException("Failed to get ARN for multi-region access point $name")
  143         -
  144    131   
private suspend fun S3ControlClient.deleteMultiRegionAccessPoint(
  145    132   
    name: String,
  146    133   
    accountId: String,
  147    134   
) {
  148    135   
    println("Deleting multi-region access point $name")
  149    136   
  150    137   
    val requestTokenArn = checkNotNull(
  151    138   
        deleteMultiRegionAccessPoint {
  152    139   
            this.accountId = accountId
  153    140   
            details {
  154    141   
                this.name = name
  155    142   
            }
  156    143   
        }.requestTokenArn,
  157    144   
    ) { "deleteMultiRegionAccessPoint requestTokenArn was unexpectedly null" }
  158    145   
  159    146   
    waitUntilOperationCompletes("deleteMultiRegionAccessPoint", accountId, requestTokenArn, 5.minutes)
  160    147   
}
  161    148   
  162         -
/**
  163         -
 * Continuously poll the status of [requestTokenArn] until its status is "SUCCEEDED" or [timeout] duration has passed.
  164         -
 */
  165    149   
private suspend fun S3ControlClient.waitUntilOperationCompletes(
  166    150   
    operation: String,
  167    151   
    accountId: String,
  168    152   
    requestTokenArn: String,
  169    153   
    timeout: Duration,
  170    154   
) = withTimeout(timeout) {
  171    155   
    var status: String? = null
  172    156   
  173    157   
    while (true) {
  174    158   
        val latestStatus = describeMultiRegionAccessPointOperation {
  175    159   
            this.accountId = accountId
  176    160   
            this.requestTokenArn = requestTokenArn
  177    161   
        }.asyncOperation?.requestStatus
  178    162   
  179    163   
        when (latestStatus) {
  180    164   
            "SUCCEEDED" -> {
  181    165   
                println("$operation operation succeeded.")
  182    166   
                return@withTimeout
  183    167   
            }
  184    168   
            "FAILED" -> throw IllegalStateException("$operation operation failed")
  185    169   
            else -> {
  186    170   
                if (status == null || latestStatus != status) {
  187    171   
                    println("Waiting for $operation to complete. Status: $latestStatus ")
  188    172   
                    status = latestStatus
  189    173   
                }
  190    174   
            }
  191    175   
        }
  192    176   
  193         -
        delay(10.seconds) // Avoid constant status checks
         177  +
        delay(10.seconds)
  194    178   
    }
  195    179   
}

tmp-codegen-diff/services/s3/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/s3/PaginatorTest.kt

Renamed from tmp-codegen-diff/services/s3/e2eTest/src/PaginatorTest.kt

@@ -1,1 +71,74 @@
    4      4   
 */
    5      5   
package aws.sdk.kotlin.e2etest
    6      6   
    7      7   
import aws.sdk.kotlin.services.s3.S3Client
    8      8   
import aws.sdk.kotlin.services.s3.abortMultipartUpload
    9      9   
import aws.sdk.kotlin.services.s3.createMultipartUpload
   10     10   
import aws.sdk.kotlin.services.s3.model.CompletedPart
   11     11   
import aws.sdk.kotlin.services.s3.paginators.listPartsPaginated
   12     12   
import aws.sdk.kotlin.services.s3.uploadPart
   13     13   
import aws.smithy.kotlin.runtime.content.ByteStream
          14  +
import aws.smithy.kotlin.runtime.testing.AfterAll
          15  +
import aws.smithy.kotlin.runtime.testing.BeforeAll
   14     16   
import kotlinx.coroutines.flow.toList
   15     17   
import kotlinx.coroutines.flow.transform
   16     18   
import kotlinx.coroutines.runBlocking
   17     19   
import kotlinx.coroutines.withTimeout
   18         -
import org.junit.jupiter.api.AfterAll
   19         -
import org.junit.jupiter.api.BeforeAll
   20         -
import org.junit.jupiter.api.Test
   21         -
import org.junit.jupiter.api.TestInstance
          20  +
import kotlin.jvm.JvmStatic
          21  +
import kotlin.test.Test
   22     22   
import kotlin.test.assertContentEquals
   23     23   
import kotlin.time.Duration.Companion.seconds
   24     24   
   25         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   26     25   
class PaginatorTest {
   27         -
    private val client = S3Client {
   28         -
        region = S3TestUtils.DEFAULT_REGION
   29         -
    }
   30         -
   31         -
    private lateinit var testBucket: String
          26  +
    companion object {
          27  +
        private lateinit var client: S3Client
          28  +
        private lateinit var testBucket: String
   32     29   
   33         -
    @BeforeAll
   34         -
    fun createResources(): Unit = runBlocking {
   35         -
        testBucket = S3TestUtils.getTestBucket(client)
   36         -
    }
          30  +
        @BeforeAll
          31  +
        @JvmStatic
          32  +
        fun setups() = runBlocking {
          33  +
            client = S3Client {
          34  +
                region = S3TestUtils.DEFAULT_REGION
          35  +
            }
          36  +
            testBucket = S3TestUtils.getOrCreateSharedBucket(client)
          37  +
        }
   37     38   
   38         -
    @AfterAll
   39         -
    fun cleanup() = runBlocking {
   40         -
        S3TestUtils.deleteBucketAndAllContents(client, testBucket)
   41         -
        client.close()
          39  +
        @AfterAll
          40  +
        @JvmStatic
          41  +
        fun cleanup() = runBlocking {
          42  +
            S3TestUtils.cleanupSharedBucket(client)
          43  +
            client.close()
          44  +
        }
   42     45   
    }
   43     46   
   44     47   
    // ListParts has a strange pagination termination condition via [IsTerminated]. Verify it actually works correctly.
   45     48   
    @Test
   46     49   
    fun testListPartsPagination() = runBlocking {
   47     50   
        val chunk = "!".repeat(5 * 1024 * 1024).encodeToByteArray() // Parts must be at least 5MB
   48     51   
        val expectedParts = (1..10).toList()
   49     52   
   50     53   
        val id = client.createMultipartUpload {
   51     54   
            bucket = testBucket

tmp-codegen-diff/services/s3/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/s3/S3ChecksumTest.kt

Renamed from tmp-codegen-diff/services/s3/e2eTest/src/S3ChecksumTest.kt

@@ -1,1 +198,189 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5         -
    6      5   
package aws.sdk.kotlin.e2etest
    7      6   
    8         -
import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketContents
    9         -
import aws.sdk.kotlin.e2etest.S3TestUtils.deleteMultiPartUploads
   10         -
import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId
   11         -
import aws.sdk.kotlin.e2etest.S3TestUtils.getTestBucket
   12      7   
import aws.sdk.kotlin.e2etest.S3TestUtils.responseCodeFromPut
   13      8   
import aws.sdk.kotlin.services.s3.*
   14      9   
import aws.sdk.kotlin.services.s3.model.*
   15     10   
import aws.sdk.kotlin.services.s3.presigners.presignPutObject
   16     11   
import aws.smithy.kotlin.runtime.content.*
   17     12   
import aws.smithy.kotlin.runtime.hashing.crc32
          13  +
import aws.smithy.kotlin.runtime.testing.AfterAll
          14  +
import aws.smithy.kotlin.runtime.testing.BeforeAll
   18     15   
import aws.smithy.kotlin.runtime.testing.RandomTempFile
   19     16   
import kotlinx.coroutines.runBlocking
   20         -
import org.junit.jupiter.api.*
   21         -
import java.io.File
   22         -
import java.io.FileInputStream
   23         -
import java.util.*
   24         -
import kotlin.test.assertEquals
   25         -
import kotlin.test.assertFalse
   26         -
import kotlin.test.assertTrue
          17  +
import kotlin.jvm.JvmStatic
          18  +
import kotlin.random.Random
          19  +
import kotlin.test.*
   27     20   
import kotlin.time.Duration.Companion.seconds
   28     21   
   29         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   30     22   
class S3ChecksumTest {
   31         -
    private val client = S3Client { region = "us-west-2" }
   32         -
    private lateinit var testBucket: String
   33         -
    private fun testKey(): String = "test-object" + UUID.randomUUID()
   34         -
   35         -
    @BeforeAll
   36         -
    private fun setUp(): Unit = runBlocking {
   37         -
        val accountId = getAccountId()
   38         -
        testBucket = getTestBucket(client, "us-west-2", accountId)
   39         -
    }
          23  +
    companion object {
          24  +
        private lateinit var client: S3Client
          25  +
        private lateinit var testBucket: String
          26  +
          27  +
        @BeforeAll
          28  +
        @JvmStatic
          29  +
        fun setup() = runBlocking {
          30  +
            client = S3Client { region = S3TestUtils.DEFAULT_REGION }
          31  +
            testBucket = S3TestUtils.getOrCreateSharedBucket(client, "us-west-2")
          32  +
        }
   40     33   
   41         -
    @AfterAll
   42         -
    private fun cleanUp(): Unit = runBlocking {
   43         -
        deleteMultiPartUploads(client, testBucket)
   44         -
        deleteBucketContents(client, testBucket)
   45         -
        client.close()
          34  +
        @AfterAll
          35  +
        @JvmStatic
          36  +
        fun cleanup() = runBlocking {
          37  +
            S3TestUtils.cleanupSharedBucket(client)
          38  +
            client.close()
          39  +
        }
   46     40   
    }
   47     41   
          42  +
    private fun testKey(): String = "test-object${Random.nextInt()}"
          43  +
   48     44   
    @Test
   49         -
    fun testPutObject(): Unit = runBlocking {
          45  +
    fun testPutObject() = runBlocking {
   50     46   
        val testBody = "Hello World"
   51     47   
        val testKey = testKey()
   52     48   
   53     49   
        client.putObject {
   54     50   
            bucket = testBucket
   55     51   
            key = testKey
   56     52   
            body = ByteStream.fromString(testBody)
   57     53   
        }
   58     54   
   59     55   
        client.getObject(
   60     56   
            GetObjectRequest {
   61     57   
                bucket = testBucket
   62     58   
                key = testKey
   63     59   
            },
   64     60   
        ) { actual ->
   65     61   
            assertEquals(testBody, actual.body?.decodeToString() ?: "")
   66     62   
        }
   67     63   
    }
   68     64   
   69     65   
    @Test
   70         -
    fun testPutObjectWithEmptyBody(): Unit = runBlocking {
          66  +
    fun testPutObjectWithEmptyBody() = runBlocking {
   71     67   
        val testKey = testKey()
   72     68   
        val testBody = ""
   73     69   
   74     70   
        client.putObject {
   75     71   
            bucket = testBucket
   76     72   
            key = testKey
   77     73   
        }
   78     74   
   79     75   
        client.getObject(
   80     76   
            GetObjectRequest {
   81     77   
                bucket = testBucket
   82     78   
                key = testKey
   83     79   
            },
   84     80   
        ) { actual ->
   85     81   
            assertEquals(testBody, actual.body?.decodeToString() ?: "")
   86     82   
        }
   87     83   
    }
   88     84   
   89     85   
    @Test
   90         -
    fun testPutObjectAwsChunkedEncoded(): Unit = runBlocking {
          86  +
    fun testPutObjectAwsChunkedEncoded() = runBlocking {
   91     87   
        val testKey = testKey()
   92     88   
        val testBody = "Hello World"
   93     89   
   94         -
        val tempFile = File.createTempFile("test", ".txt").also {
   95         -
            it.writeText(testBody)
   96         -
            it.deleteOnExit()
   97         -
        }
   98         -
        val inputStream = FileInputStream(tempFile)
   99         -
  100     90   
        client.putObject {
  101     91   
            bucket = testBucket
  102     92   
            key = testKey
  103         -
            body = ByteStream.fromInputStream(inputStream, testBody.length.toLong())
          93  +
            body = ByteStream.fromString(testBody)
  104     94   
        }
  105     95   
  106     96   
        client.getObject(
  107     97   
            GetObjectRequest {
  108     98   
                bucket = testBucket
  109     99   
                key = testKey
  110    100   
            },
  111    101   
        ) { actual ->
  112    102   
            assertEquals(testBody, actual.body?.decodeToString() ?: "")
  113    103   
        }
  114    104   
    }
  115    105   
  116    106   
    @Test
  117         -
    fun testMultiPartUpload(): Unit = runBlocking {
         107  +
    fun testMultiPartUpload() = runBlocking<Unit> {
  118    108   
        val testKey = testKey()
  119         -
  120         -
        val partSize = 5 * 1024 * 1024 // 5 MB - min part size
  121         -
        val contentSize: Long = 8 * 1024 * 1024 // 2 parts
         109  +
        val partSize = 5 * 1024 * 1024
         110  +
        val contentSize: Long = 8 * 1024 * 1024
  122    111   
        val file = RandomTempFile(sizeInBytes = contentSize)
  123    112   
  124    113   
        val expectedChecksum = file.readBytes().crc32()
  125    114   
  126    115   
        val testUploadId = client.createMultipartUpload {
  127    116   
            bucket = testBucket
  128    117   
            key = testKey
  129    118   
        }.uploadId
  130    119   
  131         -
        val uploadedParts = file.chunk(partSize).mapIndexed { index, chunk ->
  132         -
            val adjustedIndex = index + 1 // index starts from 0 but partNumber needs to start from 1
         120  +
        val fileBytes = file.readBytes()
         121  +
        val chunks = fileBytes.chunk(partSize).toList()
         122  +
        val uploadedParts = chunks.mapIndexed { index, chunk ->
         123  +
            val adjustedIndex = index + 1
  133    124   
  134         -
            runBlocking {
  135         -
                client.uploadPart {
  136         -
                    bucket = testBucket
  137         -
                    key = testKey
         125  +
            client.uploadPart {
         126  +
                bucket = testBucket
         127  +
                key = testKey
         128  +
                partNumber = adjustedIndex
         129  +
                uploadId = testUploadId
         130  +
                body = ByteStream.fromBytes(chunk)
         131  +
            }.let {
         132  +
                CompletedPart {
  138    133   
                    partNumber = adjustedIndex
  139         -
                    uploadId = testUploadId
  140         -
                    body = file.asByteStream(chunk)
  141         -
                }.let {
  142         -
                    CompletedPart {
  143         -
                        partNumber = adjustedIndex
  144         -
                        eTag = it.eTag
  145         -
                    }
         134  +
                    eTag = it.eTag
  146    135   
                }
  147    136   
            }
  148         -
        }.toList()
         137  +
        }
  149    138   
  150    139   
        client.completeMultipartUpload {
  151    140   
            bucket = testBucket
  152    141   
            key = testKey
  153    142   
            uploadId = testUploadId
  154    143   
            multipartUpload = CompletedMultipartUpload {
  155    144   
                parts = uploadedParts
  156    145   
            }
  157    146   
        }
  158    147   
  159    148   
        client.getObject(
  160    149   
            GetObjectRequest {
  161    150   
                bucket = testBucket
  162    151   
                key = testKey
  163    152   
            },
  164    153   
        ) { actual ->
  165    154   
            val actualChecksum = actual.body!!.toByteArray().crc32()
  166    155   
            assertEquals(actualChecksum, expectedChecksum)
  167    156   
        }
         157  +
         158  +
        file.delete()
  168    159   
    }
  169    160   
  170    161   
    @Test
  171    162   
    fun testPresignedUrlNoDefault() = runBlocking {
  172    163   
        val contents = "presign-test"
  173    164   
  174    165   
        val unsignedPutRequest = PutObjectRequest {
  175    166   
            bucket = testBucket
  176    167   
            key = testKey()
  177    168   
        }
  178    169   
        val presignedPutRequest = client.presignPutObject(unsignedPutRequest, 60.seconds)
  179    170   
  180    171   
        assertFalse(presignedPutRequest.url.toString().contains("x-amz-checksum-crc32"))
  181         -
        assertTrue(responseCodeFromPut(presignedPutRequest, contents) in 200..299)
         172  +
        assertTrue(responseCodeFromPut(client.config.httpClient, presignedPutRequest, contents) in 200..299)
  182    173   
    }
  183    174   
  184    175   
    @Test
  185    176   
    fun testPresignedUrlChecksumValue() = runBlocking {
  186    177   
        val contents = "presign-test"
  187    178   
  188    179   
        val unsignedPutRequest = PutObjectRequest {
  189    180   
            bucket = testBucket
  190    181   
            key = testKey()
  191    182   
            checksumCrc32 = "dBBx+Q=="
  192    183   
        }
  193    184   
        val presignedPutRequest = client.presignPutObject(unsignedPutRequest, 60.seconds)
  194    185   
  195    186   
        assertTrue(presignedPutRequest.url.toString().contains("x-amz-checksum-crc32"))
  196         -
        assertTrue(responseCodeFromPut(presignedPutRequest, contents) in 200..299)
         187  +
        assertTrue(responseCodeFromPut(client.config.httpClient, presignedPutRequest, contents) in 200..299)
  197    188   
    }
  198    189   
}

tmp-codegen-diff/services/s3/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/s3/S3ExpressTest.kt

Renamed from tmp-codegen-diff/services/s3/e2eTest/src/S3ExpressTest.kt

@@ -1,1 +183,173 @@
    6      6   
    7      7   
import aws.sdk.kotlin.services.s3.*
    8      8   
import aws.sdk.kotlin.services.s3.express.S3_EXPRESS_SESSION_TOKEN_HEADER
    9      9   
import aws.sdk.kotlin.services.s3.model.*
   10     10   
import aws.sdk.kotlin.services.s3.presigners.presignPutObject
   11     11   
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
   12     12   
import aws.smithy.kotlin.runtime.content.ByteStream
   13     13   
import aws.smithy.kotlin.runtime.content.decodeToString
   14     14   
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor
   15     15   
import aws.smithy.kotlin.runtime.http.request.HttpRequest
          16  +
import aws.smithy.kotlin.runtime.io.use
          17  +
import aws.smithy.kotlin.runtime.testing.AfterAll
          18  +
import aws.smithy.kotlin.runtime.testing.BeforeAll
   16     19   
import kotlinx.coroutines.runBlocking
   17         -
import kotlinx.coroutines.test.runTest
   18         -
import org.junit.jupiter.api.AfterAll
   19         -
import org.junit.jupiter.api.BeforeAll
   20         -
import org.junit.jupiter.api.TestInstance
          20  +
import kotlin.jvm.JvmStatic
   21     21   
import kotlin.test.*
   22     22   
import kotlin.time.Duration.Companion.minutes
   23     23   
   24         -
/**
   25         -
 * Tests for S3 Express operations
   26         -
 */
   27         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   28     24   
class S3ExpressTest {
   29         -
    private val client = S3Client {
   30         -
        region = S3TestUtils.DEFAULT_REGION
   31         -
    }
   32         -
   33         -
    private val testBuckets: MutableList<String> = mutableListOf()
   34         -
   35         -
    @BeforeAll
   36         -
    fun setup(): Unit = runBlocking {
   37         -
        val suffix = "--usw2-az1--x-s3" // us-west-2 availability zone 1
   38         -
   39         -
        // create a few test buckets to test the credentials cache
   40         -
        testBuckets.add(S3TestUtils.getTestDirectoryBucket(client, suffix))
   41         -
        testBuckets.add(S3TestUtils.getTestDirectoryBucket(client, suffix))
   42         -
        testBuckets.add(S3TestUtils.getTestDirectoryBucket(client, suffix))
   43         -
    }
          25  +
    companion object {
          26  +
        private lateinit var client: S3Client
          27  +
        private const val S3_EXPRESS_BUCKET_SUFFIX = "--usw2-az1--x-s3"
          28  +
        private lateinit var testBuckets: List<String>
          29  +
          30  +
        @BeforeAll
          31  +
        @JvmStatic
          32  +
        fun setup() = runBlocking {
          33  +
            client = S3Client { region = S3TestUtils.DEFAULT_REGION }
          34  +
            testBuckets = S3TestUtils.getOrCreateSharedDirectoryBuckets(client, S3_EXPRESS_BUCKET_SUFFIX)
          35  +
        }
   44     36   
   45         -
    @AfterAll
   46         -
    fun cleanup(): Unit = runBlocking {
   47         -
        testBuckets.forEach { bucket ->
   48         -
            S3TestUtils.deleteMultiPartUploads(client, bucket)
   49         -
            S3TestUtils.deleteBucketAndAllContents(client, bucket)
          37  +
        @AfterAll
          38  +
        @JvmStatic
          39  +
        fun cleanup() = runBlocking {
          40  +
            S3TestUtils.cleanupSharedDirectoryBuckets(client, S3_EXPRESS_BUCKET_SUFFIX)
          41  +
            client.close()
   50     42   
        }
   51         -
        client.close()
   52     43   
    }
   53     44   
   54     45   
    @Test
   55         -
    fun testPutObject() = runTest {
          46  +
    fun testPutObject(): Unit = runBlocking {
   56     47   
        val content = "30 minutes, or it's free!"
   57     48   
        val keyName = "express.txt"
   58     49   
   59     50   
        testBuckets.forEach { bucketName ->
   60     51   
            val trackingInterceptor = S3ExpressInvocationTrackingInterceptor()
   61     52   
            client.withConfig {
   62     53   
                interceptors += trackingInterceptor
   63     54   
            }.use { trackingClient ->
   64     55   
                trackingClient.putObject {
   65     56   
                    bucket = bucketName
   66     57   
                    key = keyName
   67     58   
                    body = ByteStream.fromString(content)
   68     59   
                }
   69     60   
   70     61   
                val req = GetObjectRequest {
   71     62   
                    bucket = bucketName
   72     63   
                    key = keyName
   73     64   
                }
   74     65   
   75     66   
                val respContent = client.getObject(req) {
   76     67   
                    it.body?.decodeToString()
   77     68   
                }
   78     69   
   79     70   
                assertEquals(content, respContent)
   80     71   
                assertEquals(1, trackingInterceptor.s3ExpressInvocations)
   81     72   
            }
   82     73   
        }
   83     74   
    }
   84     75   
   85     76   
    @Ignore
   86     77   
    @Test
   87         -
    fun testPresignedPutObject() = runTest {
          78  +
    fun testPresignedPutObject(): Unit = runBlocking {
   88     79   
        val content = "Presign this!"
   89     80   
        val keyName = "express-presigned.txt"
   90     81   
   91     82   
        testBuckets.forEach { bucketName ->
   92     83   
            val presigned = client.presignPutObject(
   93     84   
                PutObjectRequest {
   94     85   
                    bucket = bucketName
   95     86   
                    key = keyName
   96     87   
                    body = ByteStream.fromString(content)
   97     88   
                },
   98     89   
                5.minutes,
   99     90   
            )
  100     91   
  101     92   
            // FIXME Presigned requests should use S3 Express Auth Scheme resulting in `X-Amz-S3session-Token`
  102     93   
            // https://github.com/awslabs/aws-sdk-kotlin/issues/1236
  103     94   
            assertTrue(presigned.url.parameters.decodedParameters.contains(S3_EXPRESS_SESSION_TOKEN_HEADER))
  104     95   
        }
  105     96   
    }
  106     97   
  107     98   
    @Test
  108         -
    fun testChecksums() = runTest {
          99  +
    fun testChecksums(): Unit = runBlocking {
  109    100   
        val bucketName = testBuckets.first() // only need one bucket for this test
  110    101   
  111    102   
        val keysToDelete = listOf("checksums.txt", "delete-me.txt", "dont-forget-about-me.txt")
  112    103   
        keysToDelete.forEach {
  113    104   
            client.putObject {
  114    105   
                bucket = bucketName
  115    106   
                key = it
  116    107   
                body = ByteStream.fromString("Check out these sums!")
  117    108   
            }
  118    109   
        }
  119    110   
  120    111   
        client.withConfig {
  121    112   
            interceptors += CRC32ChecksumValidatingInterceptor()
  122    113   
        }.use { validatingClient ->
  123    114   
            // s3:DeleteObjects requires a checksum, even if the user doesn't specify one.
  124    115   
            // normally the SDK would default to MD5, but S3 Express must default to CRC32 instead.
  125    116   
            val req = DeleteObjectsRequest {
  126    117   
                bucket = bucketName
  127    118   
                delete = Delete {
  128    119   
                    objects = keysToDelete.map {
  129    120   
                        ObjectIdentifier { key = it }
  130    121   
                    }
  131    122   
                }
  132    123   
            }
  133    124   
  134    125   
            validatingClient.deleteObjects(req)
  135    126   
        }
  136    127   
    }
  137    128   
  138    129   
    @Test
  139         -
    fun testUploadPartContainsCRC32Checksum() = runTest {
         130  +
    fun testUploadPartContainsCRC32Checksum(): Unit = runBlocking {
  140    131   
        val testBucket = testBuckets.first()
  141    132   
        val testObject = "I-will-be-uploaded-in-parts-!"
  142    133   
  143         -
        // Parts need to be at least 5 MB
  144    134   
        val partOne = "Hello".repeat(1_048_576)
  145    135   
        val partTwo = "World".repeat(1_048_576)
  146    136   
  147    137   
        val testUploadId = client.createMultipartUpload {
  148    138   
            bucket = testBucket
  149    139   
            key = testObject
  150    140   
        }.uploadId
  151    141   
  152         -
        var eTagPartOne: String?
  153         -
        var eTagPartTwo: String?
         142  +
        var eTagPartOne: String? = null
         143  +
        var eTagPartTwo: String? = null
  154    144   
  155    145   
        client.withConfig {
  156    146   
            interceptors += CRC32ChecksumValidatingInterceptor()
  157    147   
        }.use { validatingClient ->
  158    148   
            eTagPartOne = validatingClient.uploadPart {
  159    149   
                bucket = testBucket
  160    150   
                key = testObject
  161    151   
                partNumber = 1
  162    152   
                uploadId = testUploadId
  163    153   
                body = ByteStream.fromString(partOne)

tmp-codegen-diff/services/s3/e2eTest/src/commonMain/kotlin/aws/sdk/kotlin/services/s3/S3IntegrationTest.kt

Renamed from tmp-codegen-diff/services/s3/e2eTest/src/S3IntegrationTest.kt

@@ -1,1 +352,325 @@
    7      7   
import aws.sdk.kotlin.services.s3.*
    8      8   
import aws.sdk.kotlin.services.s3.model.*
    9      9   
import aws.sdk.kotlin.testing.PRINTABLE_CHARS
   10     10   
import aws.sdk.kotlin.testing.withAllEngines
   11     11   
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
   12     12   
import aws.smithy.kotlin.runtime.content.*
   13     13   
import aws.smithy.kotlin.runtime.hashing.sha256
   14     14   
import aws.smithy.kotlin.runtime.http.HttpException
   15     15   
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor
   16     16   
import aws.smithy.kotlin.runtime.http.request.HttpRequest
          17  +
import aws.smithy.kotlin.runtime.io.use
          18  +
import aws.smithy.kotlin.runtime.testing.AfterAll
          19  +
import aws.smithy.kotlin.runtime.testing.BeforeAll
   17     20   
import aws.smithy.kotlin.runtime.testing.RandomTempFile
   18     21   
import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
   19     22   
import kotlinx.coroutines.async
   20     23   
import kotlinx.coroutines.awaitAll
   21     24   
import kotlinx.coroutines.flow.flow
   22         -
import kotlinx.coroutines.flow.toList
   23     25   
import kotlinx.coroutines.runBlocking
   24         -
import kotlinx.coroutines.withTimeout
   25         -
import org.junit.jupiter.api.AfterAll
   26         -
import org.junit.jupiter.api.BeforeAll
   27         -
import org.junit.jupiter.api.TestInstance
   28         -
import java.io.File
   29         -
import java.util.*
          26  +
import kotlin.jvm.JvmStatic
          27  +
import kotlin.random.Random
   30     28   
import kotlin.test.*
   31         -
import kotlin.time.Duration.Companion.seconds
   32     29   
   33         -
/**
   34         -
 * Tests for bucket operations and presigner
   35         -
 */
   36         -
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
   37     30   
class S3BucketOpsIntegrationTest {
   38         -
    private val client = S3Client {
   39         -
        region = S3TestUtils.DEFAULT_REGION
   40         -
    }
   41         -
   42         -
    private lateinit var testBucket: String
   43         -
   44         -
    @BeforeAll
   45         -
    fun createResources(): Unit = runBlocking {
   46         -
        testBucket = S3TestUtils.getTestBucket(client)
   47         -
    }
          31  +
    companion object {
          32  +
        private lateinit var client: S3Client
          33  +
        private lateinit var testBucket: String
          34  +
          35  +
        @BeforeAll
          36  +
        @JvmStatic
          37  +
        fun setup() = runBlocking {
          38  +
            client = S3Client {
          39  +
                region = S3TestUtils.DEFAULT_REGION
          40  +
            }
          41  +
            testBucket = S3TestUtils.getOrCreateSharedBucket(client)
          42  +
        }
   48     43   
   49         -
    @AfterAll
   50         -
    fun cleanup() = runBlocking {
   51         -
        S3TestUtils.deleteBucketAndAllContents(client, testBucket)
   52         -
        client.close()
          44  +
        @AfterAll
          45  +
        @JvmStatic
          46  +
        fun cleanup(): Unit = runBlocking {
          47  +
            S3TestUtils.cleanupSharedBucket(client)
          48  +
            client.close()
          49  +
        }
   53     50   
    }
   54     51   
   55     52   
    @Test
   56         -
    fun testPutObjectFromMemory(): Unit = runBlocking {
          53  +
    fun testPutObjectFromMemory() = runBlocking {
   57     54   
        val contents = """
   58     55   
            A lep is a ball.
   59     56   
            A tay is a hammer.
   60     57   
            A korf is a tiger.
   61     58   
            A flix is a comb.
   62     59   
            A wogsin is a gift.
   63     60   
        """.trimIndent()
   64     61   
   65     62   
        val keyName = "put-obj-from-memory.txt"
   66     63   
   67     64   
        client.putObject {
   68     65   
            bucket = testBucket
   69     66   
            key = keyName
   70     67   
            body = ByteStream.fromString(contents)
   71     68   
        }
   72     69   
   73     70   
        val req = GetObjectRequest {
   74     71   
            bucket = testBucket
   75     72   
            key = keyName
   76     73   
        }
   77     74   
        val roundTrippedContents = client.getObject(req) { it.body?.decodeToString() }
   78     75   
   79     76   
        assertEquals(contents, roundTrippedContents)
   80     77   
    }
   81     78   
   82     79   
    @Test
   83         -
    fun testPutObjectFromFile(): Unit = runBlocking {
   84         -
        val tempFile = RandomTempFile(1024)
   85         -
        val keyName = "put-obj-from-file.txt"
   86         -
   87         -
        // This test fails sporadically (by never completing)
   88         -
        // see https://github.com/awslabs/aws-sdk-kotlin/issues/282
   89         -
        withTimeout(5.seconds) {
   90         -
            client.putObject {
   91         -
                bucket = testBucket
   92         -
                key = keyName
   93         -
                body = ByteStream.fromFile(tempFile)
   94         -
            }
   95         -
        }
   96         -
   97         -
        val req = GetObjectRequest {
   98         -
            bucket = testBucket
   99         -
            key = keyName
  100         -
        }
  101         -
        val roundTrippedContents = client.getObject(req) { it.body?.decodeToString() }
  102         -
  103         -
        val contents = tempFile.readText()
  104         -
        assertEquals(contents, roundTrippedContents)
  105         -
    }
  106         -
  107         -
    @Test
  108         -
    fun testPutObjectWithToByteStreamAndContentLength(): Unit = runBlocking {
          80  +
    fun testPutObjectWithToByteStreamAndContentLength() = runBlocking<Unit> {
  109     81   
        // See https://github.com/awslabs/aws-sdk-kotlin/issues/1249
  110     82   
        val keyName = "toByteStream-contentLength.txt"
  111     83   
        val arr = "Hello!".encodeToByteArray()
  112     84   
  113     85   
        client.putObject {
  114     86   
            bucket = testBucket
  115     87   
            key = keyName
  116     88   
            body = flow { emit(arr) }.toByteStream(this@runBlocking, arr.size.toLong())
  117     89   
            contentLength = arr.size.toLong()
  118     90   
        }
  119     91   
    }
  120     92   
  121     93   
    @Test
  122         -
    fun testGetEmptyObject(): Unit = runBlocking {
          94  +
    fun testGetEmptyObject() = runBlocking {
  123     95   
        // See https://github.com/awslabs/aws-sdk-kotlin/issues/1014
  124     96   
        val keyName = "get-empty-obj.txt"
  125     97   
  126     98   
        client.putObject {
  127     99   
            bucket = testBucket
  128    100   
            key = keyName
  129    101   
            body = ByteStream.fromBytes(byteArrayOf())
  130    102   
        }
  131    103   
  132    104   
        val req = GetObjectRequest {
  133    105   
            bucket = testBucket
  134    106   
            key = keyName
  135    107   
        }
  136    108   
        val actualLength = client.getObject(req) { it.contentLength }
  137    109   
        assertEquals(0, actualLength)
  138    110   
    }
  139    111   
  140    112   
    @Test
  141         -
    fun testQueryParameterEncoding(): Unit = runBlocking {
         113  +
    fun testQueryParameterEncoding() = runBlocking {
  142    114   
        // see: https://github.com/awslabs/aws-sdk-kotlin/issues/448
  143    115   
  144    116   
        // this is mostly a stress test of signing w.r.t query parameter encoding (since
  145    117   
        // delimiter is bound via @httpQuery) and the ability of an HTTP engine to keep
  146    118   
        // the same encoding going out on the wire (e.g. not double percent encoding)
  147    119   
  148    120   
        s3WithAllEngines { s3 ->
  149    121   
            s3.listObjects {
  150    122   
                bucket = testBucket
  151    123   
                delimiter = PRINTABLE_CHARS
  152    124   
                prefix = null
  153    125   
            }
  154         -
            // only care that request is accepted, not the results
  155    126   
        }
  156    127   
    }
  157    128   
  158    129   
    @Test
  159         -
    fun testPathEncoding(): Unit = runBlocking {
         130  +
    fun testPathEncoding() = runBlocking {
  160    131   
        // this is mostly a stress test of signing w.r.t path encoding (since key is bound
  161    132   
        // via @httpLabel) and the ability of an HTTP engine to keep the same encoding going
  162    133   
        // out on the wire (e.g. not double percent encoding)
  163    134   
  164    135   
        // NOTE: S3 provides guidance on choosing object key names: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
  165    136   
        // This test includes all printable chars (including ones S3 recommends avoiding). Users should
  166    137   
        // strive to fall within the guidelines given by S3 though
  167    138   
  168    139   
        s3WithAllEngines { s3 ->
  169    140   
            val objKey = "foo$PRINTABLE_CHARS"
  170    141   
            val content = "hello rfc3986"
  171    142   
  172    143   
            s3.putObject {
  173    144   
                bucket = testBucket
  174    145   
                key = objKey
  175    146   
                body = ByteStream.fromString(content)
  176    147   
            }
  177    148   
  178    149   
            val req = GetObjectRequest {
  179    150   
                bucket = testBucket
  180    151   
                key = objKey
  181    152   
            }
  182    153   
  183    154   
            s3.getObject(req) { resp ->
  184    155   
                val actual = resp.body!!.decodeToString()
  185    156   
                assertEquals(content, actual)
  186    157   
            }
  187    158   
        }
  188    159   
    }
  189    160   
  190    161   
    @Test
  191         -
    fun testMultipartUpload(): Unit = runBlocking {
         162  +
    fun testMultipartUpload() = runBlocking {
  192    163   
        s3WithAllEngines { s3 ->
  193         -
            val objKey = "test-multipart-${UUID.randomUUID()}"
  194         -
            val contentSize: Long = 8 * 1024 * 1024 // 2 parts
         164  +
            val objKey = "test-multipart-${Random.nextInt()}"
         165  +
            val contentSize: Long = 8 * 1024 * 1024
  195    166   
            val file = RandomTempFile(sizeInBytes = contentSize)
  196         -
            val partSize = 5 * 1024 * 1024 // 5 MB - min part size
         167  +
            val partSize = 5 * 1024 * 1024
  197    168   
  198    169   
            val expectedSha256 = file.readBytes().sha256().encodeToHex()
  199    170   
  200    171   
            val resp = s3.createMultipartUpload {
  201    172   
                bucket = testBucket
  202    173   
                key = objKey
  203    174   
            }
  204    175   
  205         -
            val completedParts = file.chunk(partSize)
         176  +
            val fileBytes = file.readBytes()
         177  +
            val completedParts = fileBytes.chunk(partSize)
  206    178   
                .mapIndexed { idx, chunk ->
  207    179   
                    async {
  208    180   
                        val uploadResp = s3.uploadPart {
  209    181   
                            bucket = testBucket
  210    182   
                            key = objKey
  211    183   
                            uploadId = resp.uploadId
  212         -
                            body = file.asByteStream(chunk)
         184  +
                            body = ByteStream.fromBytes(chunk)
  213    185   
                            partNumber = idx + 1
  214    186   
                        }
  215    187   
  216    188   
                        CompletedPart {
  217    189   
                            partNumber = idx + 1
  218    190   
                            eTag = uploadResp.eTag
  219    191   
                        }
  220    192   
                    }
  221    193   
                }
  222    194   
                .toList()
  223    195   
                .awaitAll()
  224    196   
  225    197   
            s3.completeMultipartUpload {
  226    198   
                bucket = testBucket
  227    199   
                key = objKey
  228    200   
                uploadId = resp.uploadId
  229    201   
                multipartUpload {
  230    202   
                    parts = completedParts
  231    203   
                }
  232    204   
            }
  233    205   
  234         -
            // TOOD - eventually make use of s3 checksums
  235    206   
            val getRequest = GetObjectRequest {
  236    207   
                bucket = testBucket
  237    208   
                key = objKey
  238    209   
            }
  239    210   
            val actualSha256 = s3.getObject(getRequest) { resp ->
  240    211   
                resp.body!!.toByteArray().sha256().encodeToHex()
  241    212   
            }
  242    213   
  243    214   
            assertEquals(expectedSha256, actualSha256)
         215  +
            file.delete()
  244    216   
        }
  245    217   
    }
  246    218   
  247    219   
    @Test
  248         -
    fun testPutObjectWithChecksum(): Unit = runBlocking {
         220  +
    fun testPutObjectWithChecksum() = runBlocking {
  249    221   
        val contents = "AAAAAAAAAA"
  250    222   
        val keyName = "put-obj-with-checksum.txt"
  251    223   
  252    224   
        val resp = client.putObject {
  253    225   
            bucket = testBucket
  254    226   
            key = keyName
  255    227   
            body = ByteStream.fromString(contents)
  256    228   
            checksumAlgorithm = ChecksumAlgorithm.Sha256
  257    229   
        }
  258    230   
  259    231   
        val req = GetObjectRequest {
  260    232   
            bucket = testBucket
  261    233   
            key = keyName
  262    234   
            checksumMode = ChecksumMode.Enabled
  263    235   
        }
  264    236   
  265    237   
        val roundTrippedContents = client.getObject(req) {
  266    238   
            assertEquals(resp.checksumSha256, it.checksumSha256)
  267    239   
            it.body?.decodeToString()
  268    240   
        }
  269    241   
  270    242   
        assertEquals(contents, roundTrippedContents)
  271    243   
    }
  272    244   
  273    245   
    @Test
  274    246   
    fun testPutObjectWithIncorrectChecksum(): Unit = runBlocking {
  275    247   
        val contents = "AAAAAAAAAA"
  276         -
  277    248   
        val keyName = "put-obj-with-checksum.txt"
  278    249   
  279    250   
        val ex = assertFails {
  280    251   
            client.putObject {
  281    252   
                bucket = testBucket
  282    253   
                key = keyName
  283    254   
                body = ByteStream.fromString(contents)
  284    255   
                checksumAlgorithm = ChecksumAlgorithm.Sha256
  285    256   
                checksumSha256 = "blerg"
  286    257   
            }
  287    258   
        }
  288    259   
        ex.message?.let {
  289         -
            assert(it.contains("Value for x-amz-checksum-sha256 header is invalid."))
         260  +
            assertTrue(it.contains("Value for x-amz-checksum-sha256 header is invalid."))
  290    261   
        }
  291    262   
    }
  292    263   
  293    264   
    @Test
  294    265   
    fun testWriteGetObjectResponse(): Unit = runBlocking {
  295         -
        // Interceptor which validates the `Host` header against an `expectedHost`
  296    266   
        class WriteGetObjectResponseHostInterceptor(val expectedHost: String) : HttpInterceptor {
  297    267   
            override fun readAfterSigning(context: ProtocolRequestInterceptorContext<Any, HttpRequest>) {
  298    268   
                val req = context.protocolRequest
  299    269   
                assertEquals(expectedHost, req.headers["Host"])
  300    270   
            }
  301    271   
        }
  302    272   
  303    273   
        val expectedHost = "s3-object-lambda.${client.config.region}.amazonaws.com"
  304    274   
  305    275   
        client.withConfig {
  306    276   
            interceptors = mutableListOf(WriteGetObjectResponseHostInterceptor(expectedHost))
  307    277   
        }.use {
  308    278   
            // The request is expected to fail because we don't have the proper infrastructure set up for the request
  309    279   
            // (S3 Access Point, Lambda Function, etc.)
  310    280   
            val ex = assertFailsWith<HttpException> {
  311    281   
                it.writeGetObjectResponse {}
  312    282   
            }
  313         -
            assertContains(ex.message!!, "$expectedHost")
         283  +
            // JVM error message contains the host, Native error message is generic DNS error
         284  +
            assertTrue(
         285  +
                ex.message!!.contains(expectedHost) || ex.message!!.contains("Host name was invalid for dns resolution"),
         286  +
                "Expected error message to contain either '$expectedHost' or 'Host name was invalid for dns resolution', but got: ${ex.message}",
         287  +
            )
  314    288   
        }
  315    289   
    }
  316    290   
  317    291   
    @Test
  318         -
    fun testHeadObjectForbidden(): Unit = runBlocking {
         292  +
    fun testHeadObjectForbidden() = runBlocking {
  319    293   
        val ex = assertFailsWith<S3Exception> {
  320    294   
            client.withConfig {
  321    295   
                region = "us-east-1"
  322    296   
            }.headObject {
  323    297   
                bucket = "bucket"
  324    298   
                key = "any-key.txt"
  325    299   
            }
  326    300   
        }
  327    301   
  328    302   
        assertContains(ex.message, "Service returned error code 403: Forbidden")
  329    303   
        assertEquals("403: Forbidden", ex.sdkErrorMetadata.errorCode!!)
  330    304   
    }
  331    305   
}
  332    306   
  333         -
// generate sequence of "chunks" where each range defines the inclusive start and end bytes
  334         -
internal fun File.chunk(partSize: Int): Sequence<LongRange> = (0 until length() step partSize.toLong()).asSequence().map {
  335         -
    it until minOf(it + partSize, length())
         307  +
internal fun ByteArray.chunk(partSize: Int): Sequence<ByteArray> = (0 until size step partSize).asSequence().map { start ->
         308  +
    copyOfRange(start, minOf(start + partSize, size))
  336    309   
}
  337    310   
  338    311   
internal suspend fun s3WithAllEngines(block: suspend (S3Client) -> Unit) {
  339    312   
    withAllEngines { engine ->
  340    313   
        S3Client {
  341    314   
            region = S3TestUtils.DEFAULT_REGION
  342    315   
            httpClient = engine
  343    316   
        }.use {
  344    317   
            try {
  345    318   
                block(it)