Skip to content

Commit 3858a5d

Browse files
authored
test: add multipart upload e2e test coverage (#624)
1 parent 3ebea3b commit 3858a5d

File tree

2 files changed

+74
-4
lines changed

2 files changed

+74
-4
lines changed

services/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ plugins {
1111
val platforms = listOf("common", "jvm")
1212

1313
val sdkVersion: String by project
14+
val smithyKotlinVersion: String by project
1415
val kotlinVersion: String by project
1516
val coroutinesVersion: String by project
1617
val kotestVersion: String by project
@@ -81,11 +82,12 @@ subprojects {
8182

8283
dependencies {
8384
// Compile against the main compilation's compile classpath and outputs:
84-
implementation(main.compileDependencyFiles + main.output.classesDirs)
85+
implementation(main.compileDependencyFiles + main.runtimeDependencyFiles + main.output.classesDirs)
8586

8687
implementation(kotlin("test"))
8788
implementation(kotlin("test-junit5"))
8889
implementation(project(":aws-runtime:testing"))
90+
implementation("aws.smithy.kotlin:hashing:$smithyKotlinVersion")
8991
implementation(project(":tests:e2e-test-util"))
9092
}
9193
}

services/s3/e2eTest/S3IntegrationTest.kt

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@
55
package aws.sdk.kotlin.e2etest
66

77
import aws.sdk.kotlin.services.s3.S3Client
8+
import aws.sdk.kotlin.services.s3.model.CompletedPart
89
import aws.sdk.kotlin.services.s3.model.GetObjectRequest
910
import aws.sdk.kotlin.testing.PRINTABLE_CHARS
1011
import aws.sdk.kotlin.testing.withAllEngines
1112
import aws.smithy.kotlin.runtime.content.ByteStream
13+
import aws.smithy.kotlin.runtime.content.asByteStream
1214
import aws.smithy.kotlin.runtime.content.decodeToString
1315
import aws.smithy.kotlin.runtime.content.fromFile
16+
import aws.smithy.kotlin.runtime.content.toByteArray
17+
import aws.smithy.kotlin.runtime.hashing.sha256
1418
import aws.smithy.kotlin.runtime.testing.RandomTempFile
15-
import kotlinx.coroutines.ExperimentalCoroutinesApi
16-
import kotlinx.coroutines.runBlocking
17-
import kotlinx.coroutines.withTimeout
19+
import aws.smithy.kotlin.runtime.util.encodeToHex
20+
import kotlinx.coroutines.*
1821
import org.junit.jupiter.api.AfterAll
1922
import org.junit.jupiter.api.BeforeAll
2023
import org.junit.jupiter.api.TestInstance
24+
import java.io.File
25+
import java.util.UUID
2126
import kotlin.test.Test
2227
import kotlin.test.assertEquals
2328
import kotlin.time.Duration.Companion.seconds
@@ -150,8 +155,71 @@ class S3BucketOpsIntegrationTest {
150155
}
151156
}
152157
}
158+
159+
@Test
160+
fun testMultipartUpload(): Unit = runBlocking {
161+
s3WithAllEngines { s3 ->
162+
val objKey = "test-multipart-${UUID.randomUUID()}"
163+
val contentSize: Long = 8 * 1024 * 1024 // 2 parts
164+
val file = RandomTempFile(sizeInBytes = contentSize)
165+
val partSize = 5 * 1024 * 1024 // 5 MB - min part size
166+
167+
val expectedSha256 = file.readBytes().sha256().encodeToHex()
168+
169+
val resp = s3.createMultipartUpload {
170+
bucket = testBucket
171+
key = objKey
172+
}
173+
174+
val completedParts = file.chunk(partSize)
175+
.mapIndexed { idx, chunk ->
176+
async {
177+
val uploadResp = s3.uploadPart {
178+
bucket = testBucket
179+
key = objKey
180+
uploadId = resp.uploadId
181+
body = file.asByteStream(chunk)
182+
partNumber = idx + 1
183+
}
184+
185+
CompletedPart {
186+
partNumber = idx + 1
187+
eTag = uploadResp.eTag
188+
}
189+
}
190+
}
191+
.toList()
192+
.awaitAll()
193+
194+
s3.completeMultipartUpload {
195+
bucket = testBucket
196+
key = objKey
197+
uploadId = resp.uploadId
198+
multipartUpload {
199+
parts = completedParts
200+
}
201+
}
202+
203+
// TOOD - eventually make use of s3 checksums
204+
val getRequest = GetObjectRequest {
205+
bucket = testBucket
206+
key = objKey
207+
}
208+
val actualSha256 = s3.getObject(getRequest) { resp ->
209+
resp.body!!.toByteArray().sha256().encodeToHex()
210+
}
211+
212+
assertEquals(expectedSha256, actualSha256)
213+
}
214+
}
153215
}
154216

217+
// generate sequence of "chunks" where each range defines the inclusive start and end bytes
218+
private fun File.chunk(partSize: Int): Sequence<LongRange> =
219+
(0 until length() step partSize.toLong()).asSequence().map {
220+
it until minOf(it + partSize, length())
221+
}
222+
155223
internal suspend fun s3WithAllEngines(block: suspend (S3Client) -> Unit) {
156224
withAllEngines { engine ->
157225
S3Client {

0 commit comments

Comments
 (0)