Skip to content

Commit af5585b

Browse files
authored
test: allow e2e service tests to be checked in (#283)
1 parent cfab41d commit af5585b

File tree

6 files changed

+375
-1
lines changed

6 files changed

+375
-1
lines changed

aws-runtime/crt-util/common/src/aws/sdk/kotlin/runtime/crt/ReadChannelBodyStream.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal expect fun transferRequestBody(outgoing: SdkBuffer, dest: MutableBuffer
2828
public class ReadChannelBodyStream(
2929
// the request body channel
3030
private val bodyChan: SdkByteReadChannel,
31-
callContext: CoroutineContext
31+
private val callContext: CoroutineContext
3232
) : HttpRequestBodyStream, CoroutineScope {
3333

3434
private val producerJob = Job(callContext.job)
@@ -60,6 +60,8 @@ public class ReadChannelBodyStream(
6060
if (bufferChan.isClosedForReceive) {
6161
return true
6262
}
63+
// ensure the request context hasn't been cancelled
64+
callContext.ensureActive()
6365
outgoing = bufferChan.tryReceive().getOrNull() ?: return false
6466
}
6567

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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.runtime.testing
6+
7+
import java.io.IOException
8+
import java.io.InputStream
9+
import kotlin.random.Random
10+
11+
/**
12+
* Test utility InputStream implementation that generates random ASCII data when
13+
* read, up to the size specified when constructed.
14+
*/
15+
public class RandomInputStream constructor(
16+
/** The requested amount of data contained in this random stream. */
17+
private val lengthInBytes: Long,
18+
19+
/** Flag controlling whether binary or character data is used. */
20+
private val binaryData: Boolean = false
21+
) : InputStream() {
22+
23+
/** The number of bytes of data remaining in this random stream. */
24+
protected var remainingBytes: Long = lengthInBytes
25+
26+
public val bytesRead: Long
27+
get() = lengthInBytes - remainingBytes
28+
29+
@Throws(IOException::class)
30+
override fun read(b: ByteArray, off: Int, len: Int): Int {
31+
// Signal that we're out of data if we've hit our limit
32+
if (remainingBytes <= 0) {
33+
return -1
34+
}
35+
var bytesToRead = len
36+
if (bytesToRead > remainingBytes) {
37+
bytesToRead = remainingBytes.toInt()
38+
}
39+
remainingBytes -= bytesToRead.toLong()
40+
if (binaryData) {
41+
val endExclusive = off + bytesToRead
42+
Random.nextBytes(b, off, endExclusive)
43+
} else {
44+
for (i in 0 until bytesToRead) {
45+
b[off + i] = Random.nextInt(MIN_CHAR_CODE, MAX_CHAR_CODE + 1).toByte()
46+
}
47+
}
48+
return bytesToRead
49+
}
50+
51+
@Throws(IOException::class)
52+
override fun read(): Int {
53+
// Signal that we're out of data if we've hit our limit
54+
if (remainingBytes <= 0) {
55+
return -1
56+
}
57+
remainingBytes--
58+
return if (binaryData) {
59+
Random.nextInt()
60+
} else {
61+
Random.nextInt(MIN_CHAR_CODE, MAX_CHAR_CODE + 1)
62+
}
63+
}
64+
65+
public companion object {
66+
/** The minimum ASCII code contained in the data in this stream. */
67+
private const val MIN_CHAR_CODE = 32
68+
69+
/** The maximum ASCII code contained in the data in this stream. */
70+
private const val MAX_CHAR_CODE = 125
71+
}
72+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.runtime.testing
6+
7+
import java.io.BufferedOutputStream
8+
import java.io.File
9+
import java.io.FileOutputStream
10+
import java.io.IOException
11+
import java.util.*
12+
13+
/**
14+
* Extension of File that creates a temporary file with a specified name in
15+
* Java's temporary directory, as declared in the JRE's system properties. The
16+
* file is immediately filled with a specified amount of random ASCII data.
17+
*
18+
* @see RandomInputStream
19+
*/
20+
public class RandomTempFile : File {
21+
/** Flag controlling whether binary or character data is used. */
22+
private val binaryData: Boolean
23+
24+
/**
25+
* Creates, and fills, a temp file with a randomly generated name and specified size of random ASCII data.
26+
*
27+
* @param sizeInBytes The amount of random ASCII data, in bytes, for the new temp
28+
* file.
29+
* @throws IOException If any problems were encountered creating the new temp file.
30+
*/
31+
public constructor(sizeInBytes: Long) : this(UUID.randomUUID().toString(), sizeInBytes, false)
32+
33+
/**
34+
* Creates, and fills, a temp file with the specified name and specified
35+
* size of random data.
36+
*
37+
* @param filename The name for the new temporary file, within the Java temp
38+
* directory as declared in the JRE's system properties.
39+
* @param sizeInBytes The amount of random ASCII data, in bytes, for the new temp
40+
* file.
41+
* @param binaryData Whether to fill the file with binary or character data.
42+
*
43+
* @throws IOException
44+
* If any problems were encountered creating the new temp file.
45+
*/
46+
public constructor(filename: String, sizeInBytes: Long, binaryData: Boolean = false) : super(
47+
TEMP_DIR + separator + System.currentTimeMillis().toString() + "-" + filename
48+
) {
49+
this.binaryData = binaryData
50+
createFile(sizeInBytes)
51+
}
52+
53+
@Throws(IOException::class)
54+
public fun createFile(sizeInBytes: Long) {
55+
deleteOnExit()
56+
FileOutputStream(this).use { outputStream ->
57+
BufferedOutputStream(outputStream).use { bufferedOutputStream ->
58+
RandomInputStream(sizeInBytes, binaryData).use { inputStream ->
59+
inputStream.copyTo(bufferedOutputStream)
60+
}
61+
}
62+
}
63+
}
64+
65+
override fun delete(): Boolean {
66+
if (!super.delete()) {
67+
throw RuntimeException("Could not delete: $absolutePath")
68+
}
69+
return true
70+
}
71+
72+
public companion object {
73+
private val TEMP_DIR: String = System.getProperty("java.io.tmpdir")
74+
}
75+
}

services/build.gradle.kts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,33 @@ subprojects {
8181
}
8282

8383
apply(from = rootProject.file("gradle/publish.gradle"))
84+
85+
if (project.file("e2eTest").exists()) {
86+
87+
kotlin.target.compilations {
88+
val main by getting
89+
val e2eTest by creating {
90+
defaultSourceSet {
91+
kotlin.srcDir("e2eTest")
92+
dependencies {
93+
implementation(main.compileDependencyFiles + main.runtimeDependencyFiles + main.output.classesDirs)
94+
95+
implementation(kotlin("test"))
96+
implementation(kotlin("test-junit5"))
97+
implementation(project(":aws-runtime:testing"))
98+
}
99+
}
100+
101+
tasks.register<Test>("e2eTest") {
102+
description = "Run e2e service tests"
103+
group = "verification"
104+
classpath = compileDependencyFiles + runtimeDependencyFiles
105+
testClassesDirs = output.classesDirs
106+
useJUnitPlatform()
107+
}
108+
}
109+
}
110+
}
84111
}
85112

86113

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.e2etest
6+
7+
import aws.sdk.kotlin.runtime.testing.RandomTempFile
8+
import aws.sdk.kotlin.runtime.testing.runSuspendTest
9+
import aws.sdk.kotlin.services.s3.S3Client
10+
import aws.sdk.kotlin.services.s3.model.*
11+
import aws.smithy.kotlin.runtime.content.ByteStream
12+
import aws.smithy.kotlin.runtime.content.decodeToString
13+
import aws.smithy.kotlin.runtime.content.fromFile
14+
import kotlinx.coroutines.runBlocking
15+
import kotlinx.coroutines.withTimeout
16+
import org.junit.jupiter.api.AfterAll
17+
import org.junit.jupiter.api.BeforeAll
18+
import org.junit.jupiter.api.TestInstance
19+
import kotlin.test.Test
20+
import kotlin.test.assertEquals
21+
import kotlin.time.Duration
22+
import kotlin.time.ExperimentalTime
23+
24+
/**
25+
* Tests for bucket operations
26+
*/
27+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
28+
class S3BucketOpsIntegrationTest {
29+
companion object {
30+
const val DEFAULT_REGION = "us-east-2"
31+
}
32+
33+
val client = S3Client {
34+
region = DEFAULT_REGION
35+
}
36+
37+
lateinit var testBucket: String
38+
39+
@BeforeAll
40+
private fun createResources(): Unit = runBlocking {
41+
testBucket = S3TestUtils.getTestBucket(client)
42+
}
43+
44+
@AfterAll
45+
private fun cleanup() = runBlocking {
46+
S3TestUtils.deleteBucketAndAllContents(client, testBucket)
47+
}
48+
49+
@Test
50+
fun testPutObjectFromMemory() = runSuspendTest {
51+
val contents = """
52+
A lep is a ball.
53+
A tay is a hammer.
54+
A korf is a tiger.
55+
A flix is a comb.
56+
A wogsin is a gift.
57+
""".trimIndent()
58+
59+
val keyName = "put-obj-from-memory.txt"
60+
61+
client.putObject {
62+
bucket = testBucket
63+
key = keyName
64+
body = ByteStream.fromString(contents)
65+
}
66+
67+
val req = GetObjectRequest {
68+
bucket = testBucket
69+
key = keyName
70+
}
71+
val roundTrippedContents = client.getObject(req) { it.body?.decodeToString() }
72+
73+
assertEquals(contents, roundTrippedContents)
74+
}
75+
76+
@OptIn(ExperimentalTime::class)
77+
@Test
78+
fun testPutObjectFromFile() = runSuspendTest {
79+
val tempFile = RandomTempFile(1024)
80+
val keyName = "put-obj-from-file.txt"
81+
82+
// This test fails sporadically (by never completing)
83+
// see https://github.com/awslabs/aws-sdk-kotlin/issues/282
84+
withTimeout(Duration.seconds(5)) {
85+
client.putObject {
86+
bucket = testBucket
87+
key = keyName
88+
body = ByteStream.fromFile(tempFile)
89+
}
90+
}
91+
92+
val req = GetObjectRequest {
93+
bucket = testBucket
94+
key = keyName
95+
}
96+
val roundTrippedContents = client.getObject(req) { it.body?.decodeToString() }
97+
98+
val contents = tempFile.readText()
99+
assertEquals(contents, roundTrippedContents)
100+
}
101+
}

0 commit comments

Comments
 (0)