Skip to content

Commit 5715cbc

Browse files
authored
feat: smoke tests (#1388)
1 parent 918865c commit 5715cbc

File tree

15 files changed

+633
-1
lines changed

15 files changed

+633
-1
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ build/
1212
.idea/
1313
__pycache__/
1414
local.properties
15+
1516
# ignore generated files
1617
services/*/generated-src
1718
services/*/build.gradle.kts
1819
.kotest/
19-
*.klib
20+
*.klib
21+
tests/codegen/smoke-tests/services/*/generated-src
22+
tests/codegen/smoke-tests/services/*/build.gradle.kts

codegen/aws-sdk-codegen/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
api(libs.smithy.aws.cloudformation.traits)
3030
api(libs.smithy.protocol.test.traits)
3131
implementation(libs.smithy.aws.endpoints)
32+
implementation(libs.smithy.smoke.test.traits)
3233

3334
testImplementation(libs.junit.jupiter)
3435
testImplementation(libs.junit.jupiter.params)

codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/GradleGenerator.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@
44
*/
55
package aws.sdk.kotlin.codegen
66

7+
import aws.sdk.kotlin.codegen.model.traits.testing.TestFailedResponseTrait
8+
import aws.sdk.kotlin.codegen.model.traits.testing.TestSuccessResponseTrait
9+
import aws.sdk.kotlin.codegen.smoketests.smokeTestDenyList
710
import software.amazon.smithy.kotlin.codegen.core.*
811
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
12+
import software.amazon.smithy.kotlin.codegen.model.expectShape
13+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
914
import software.amazon.smithy.kotlin.codegen.rendering.GradleWriter
15+
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
16+
import software.amazon.smithy.model.shapes.ServiceShape
17+
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait
1018

1119
// TODO - would be nice to allow integrations to define custom settings in the plugin
1220
// e.g. we could then more consistently apply this integration if we could define a property like: `build.isAwsSdk: true`
@@ -64,9 +72,72 @@ class GradleGenerator : KotlinIntegration {
6472
}
6573
}
6674
}
75+
if (ctx.model.topDownOperations(ctx.settings.service).any { it.hasTrait<SmokeTestsTrait>() } && ctx.settings.sdkId !in smokeTestDenyList) {
76+
write("")
77+
generateSmokeTestConfig(writer, ctx)
78+
}
6779
}
6880

6981
val contents = writer.toString()
7082
delegator.fileManifest.writeFile("build.gradle.kts", contents)
7183
}
84+
85+
private fun generateSmokeTestConfig(writer: GradleWriter, ctx: CodegenContext) {
86+
generateSmokeTestJarTask(writer, ctx)
87+
writer.write("")
88+
generateSmokeTestTask(writer, ctx)
89+
}
90+
91+
/**
92+
* Generates a gradle task to create smoke test runner JARs
93+
*/
94+
private fun generateSmokeTestJarTask(writer: GradleWriter, ctx: CodegenContext) {
95+
writer.withBlock("jvm {", "}") {
96+
withBlock("compilations {", "}") {
97+
write("val mainPath = getByName(#S).output.classesDirs", "main")
98+
write("val testPath = getByName(#S).output.classesDirs", "test")
99+
withBlock("tasks {", "}") {
100+
withBlock("register<Jar>(#S) {", "}", "smokeTestJar") {
101+
write("description = #S", "Creates smoke tests jar")
102+
write("group = #S", "application")
103+
write("dependsOn(build)")
104+
write("mustRunAfter(build)")
105+
withBlock("manifest {", "}") {
106+
write("attributes[#S] = #S", "Main-Class", "${ctx.settings.pkg.name}.smoketests.SmokeTestsKt")
107+
}
108+
write("val runtimePath = configurations.getByName(#S).map { if (it.isDirectory) it else zipTree(it) }", "jvmRuntimeClasspath")
109+
write("duplicatesStrategy = DuplicatesStrategy.EXCLUDE")
110+
write("from(runtimePath, mainPath, testPath)")
111+
write("archiveBaseName.set(#S)", "\${project.name}-smoketests")
112+
}
113+
}
114+
}
115+
}
116+
}
117+
118+
/**
119+
* Generates a gradle task to run smoke tests
120+
*/
121+
private fun generateSmokeTestTask(writer: GradleWriter, ctx: CodegenContext) {
122+
val hasSuccessResponseTrait = ctx.model.expectShape<ServiceShape>(ctx.settings.service).hasTrait(TestSuccessResponseTrait.ID)
123+
val hasFailedResponseTrait = ctx.model.expectShape<ServiceShape>(ctx.settings.service).hasTrait(TestFailedResponseTrait.ID)
124+
val inTestingEnvironment = hasFailedResponseTrait || hasSuccessResponseTrait
125+
126+
/**
127+
* E2E tests don't have sdkVersion in jar names. They're added later for publishing.
128+
* @see SmokeTestE2ETest
129+
*/
130+
val jarName = if (inTestingEnvironment) "\${project.name}-smoketests.jar" else "\${project.name}-smoketests-\$sdkVersion.jar"
131+
132+
writer.withBlock("tasks.register<JavaExec>(#S) {", "}", "smokeTest") {
133+
write("description = #S", "Runs smoke tests jar")
134+
write("group = #S", "verification")
135+
write("dependsOn(tasks.getByName(#S))", "smokeTestJar")
136+
write("mustRunAfter(tasks.getByName(#S))", "smokeTestJar")
137+
write("")
138+
write("val sdkVersion: String by project")
139+
write("val jarFile = file(#S)", "build/libs/$jarName")
140+
write("classpath = files(jarFile)")
141+
}
142+
}
72143
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package aws.sdk.kotlin.codegen.model.traits.testing
2+
3+
import software.amazon.smithy.model.node.ObjectNode
4+
import software.amazon.smithy.model.shapes.ShapeId
5+
import software.amazon.smithy.model.traits.AnnotationTrait
6+
7+
/**
8+
* Indicates the annotated service should always return a failed response.
9+
* IMPORTANT: This trait is intended for use in integration or E2E tests only, not in real-life smoke tests that run
10+
* against a service endpoint.
11+
*/
12+
class TestFailedResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) {
13+
companion object {
14+
val ID: ShapeId = ShapeId.from("smithy.kotlin.traits#failedResponseTrait")
15+
}
16+
}
17+
18+
/**
19+
* Indicates the annotated service should always return a success response.
20+
* IMPORTANT: This trait is intended for use in integration or E2E tests only, not in real-life smoke tests that run
21+
* against a service endpoint.
22+
*/
23+
class TestSuccessResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) {
24+
companion object {
25+
val ID: ShapeId = ShapeId.from("smithy.kotlin.traits#successResponseTrait")
26+
}
27+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package aws.sdk.kotlin.codegen.smoketests
2+
3+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
4+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
5+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
6+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriter
7+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
8+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
9+
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestAdditionalEnvVars
10+
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestDefaultConfig
11+
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestRegionDefault
12+
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
13+
import software.amazon.smithy.model.Model
14+
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait
15+
16+
/**
17+
* Adds AWS region support to smoke tests
18+
*/
19+
class SmokeTestsCodegenRegionIntegration : KotlinIntegration {
20+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
21+
model.topDownOperations(settings.service).any { it.hasTrait<SmokeTestsTrait>() }
22+
23+
override val sectionWriters: List<SectionWriterBinding>
24+
get() = listOf(
25+
SectionWriterBinding(SmokeTestAdditionalEnvVars, envVars),
26+
SectionWriterBinding(SmokeTestDefaultConfig, region),
27+
SectionWriterBinding(SmokeTestRegionDefault, regionDefault),
28+
)
29+
30+
private val envVars = SectionWriter { writer, _ ->
31+
writer.write(
32+
"private val regionOverride = #T.System.getenv(#S)",
33+
RuntimeTypes.Core.Utils.PlatformProvider,
34+
"AWS_SMOKE_TEST_REGION",
35+
)
36+
}
37+
38+
private val region = SectionWriter { writer, _ ->
39+
writer.write("region = regionOverride")
40+
}
41+
42+
private val regionDefault = SectionWriter { writer, _ ->
43+
writer.write("regionOverride ?:")
44+
}
45+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package aws.sdk.kotlin.codegen.smoketests
2+
3+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
4+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
5+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriter
6+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
7+
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestsRunner
8+
import software.amazon.smithy.model.Model
9+
10+
/**
11+
* Will wipe the smoke test runner file for services that are deny listed.
12+
*
13+
* Some services model smoke tests incorrectly and the code generated file will not compile.
14+
*/
15+
class SmokeTestsDenyListIntegration : KotlinIntegration {
16+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
17+
settings.sdkId in smokeTestDenyList
18+
19+
override val sectionWriters: List<SectionWriterBinding>
20+
get() = listOf(
21+
SectionWriterBinding(SmokeTestsRunner, smokeTestDenyListSectionWriter),
22+
)
23+
24+
private val smokeTestDenyListSectionWriter = SectionWriter { writer, _ ->
25+
writer.write("// Smoke tests for service deny listed until model is fixed")
26+
}
27+
}
28+
29+
/**
30+
* SDK ID's of services that model smoke tests incorrectly
31+
*/
32+
val smokeTestDenyList = setOf(
33+
"Application Auto Scaling",
34+
"SWF",
35+
"WAFV2",
36+
)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package aws.sdk.kotlin.codegen.smoketests.testing
2+
3+
import aws.sdk.kotlin.codegen.model.traits.testing.TestFailedResponseTrait
4+
import aws.sdk.kotlin.codegen.model.traits.testing.TestSuccessResponseTrait
5+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
6+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
7+
import software.amazon.smithy.kotlin.codegen.core.withBlock
8+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
9+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriter
10+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
11+
import software.amazon.smithy.kotlin.codegen.model.expectShape
12+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
13+
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestHttpEngineOverride
14+
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
15+
import software.amazon.smithy.model.Model
16+
import software.amazon.smithy.model.shapes.ServiceShape
17+
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait
18+
19+
/**
20+
* Adds [TestFailedResponseTrait] support to smoke tests
21+
* IMPORTANT: This integration is intended for use in integration or E2E tests only, not in real-life smoke tests that run
22+
* against a service endpoint.
23+
*/
24+
class SmokeTestFailHttpEngineIntegration : KotlinIntegration {
25+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
26+
model.topDownOperations(settings.service).any { it.hasTrait<SmokeTestsTrait>() } &&
27+
!model.expectShape<ServiceShape>(settings.service).hasTrait(TestSuccessResponseTrait.ID) &&
28+
model.expectShape<ServiceShape>(settings.service).hasTrait(TestFailedResponseTrait.ID)
29+
30+
override val sectionWriters: List<SectionWriterBinding>
31+
get() = listOf(
32+
SectionWriterBinding(SmokeTestHttpEngineOverride, httpClientOverride),
33+
)
34+
35+
private val httpClientOverride = SectionWriter { writer, _ ->
36+
writer.withBlock("httpClient = #T(", ")", RuntimeTypes.HttpTest.TestEngine) {
37+
withBlock("roundTripImpl = { _, request ->", "}") {
38+
write(
39+
"val resp = #T(#T.BadRequest, #T.Empty, #T.Empty)",
40+
RuntimeTypes.Http.Response.HttpResponse,
41+
RuntimeTypes.Http.StatusCode,
42+
RuntimeTypes.Http.Headers,
43+
RuntimeTypes.Http.HttpBody,
44+
)
45+
write("val now = #T.now()", RuntimeTypes.Core.Instant)
46+
write("#T(request, resp, now, now)", RuntimeTypes.Http.HttpCall)
47+
}
48+
}
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package aws.sdk.kotlin.codegen.smoketests.testing
2+
3+
import aws.sdk.kotlin.codegen.model.traits.testing.TestFailedResponseTrait
4+
import aws.sdk.kotlin.codegen.model.traits.testing.TestSuccessResponseTrait
5+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
6+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
7+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
8+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriter
9+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
10+
import software.amazon.smithy.kotlin.codegen.model.expectShape
11+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
12+
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestHttpEngineOverride
13+
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
14+
import software.amazon.smithy.model.Model
15+
import software.amazon.smithy.model.shapes.ServiceShape
16+
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait
17+
18+
/**
19+
* Adds [TestSuccessResponseTrait] support to smoke tests
20+
* IMPORTANT: This integration is intended for use in integration or E2E tests only, not in real-life smoke tests that run
21+
* against a service endpoint.
22+
*/
23+
class SmokeTestSuccessHttpEngineIntegration : KotlinIntegration {
24+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
25+
model.topDownOperations(settings.service).any { it.hasTrait<SmokeTestsTrait>() } &&
26+
model.expectShape<ServiceShape>(settings.service).hasTrait(TestSuccessResponseTrait.ID) &&
27+
!model.expectShape<ServiceShape>(settings.service).hasTrait(TestFailedResponseTrait.ID)
28+
29+
override val sectionWriters: List<SectionWriterBinding>
30+
get() = listOf(
31+
SectionWriterBinding(SmokeTestHttpEngineOverride, httpClientOverride),
32+
)
33+
34+
private val httpClientOverride = SectionWriter { writer, _ ->
35+
writer.write("httpClient = #T()", RuntimeTypes.HttpTest.TestEngine)
36+
}
37+
}

codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,7 @@ aws.sdk.kotlin.codegen.customization.s3.express.SigV4S3ExpressAuthSchemeIntegrat
4343
aws.sdk.kotlin.codegen.customization.s3.express.S3ExpressIntegration
4444
aws.sdk.kotlin.codegen.customization.s3.S3ExpiresIntegration
4545
aws.sdk.kotlin.codegen.BusinessMetricsIntegration
46+
aws.sdk.kotlin.codegen.smoketests.SmokeTestsDenyListIntegration
47+
aws.sdk.kotlin.codegen.smoketests.SmokeTestsCodegenRegionIntegration
48+
aws.sdk.kotlin.codegen.smoketests.testing.SmokeTestSuccessHttpEngineIntegration
49+
aws.sdk.kotlin.codegen.smoketests.testing.SmokeTestFailHttpEngineIntegration

settings.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ include(":services")
4343
include(":tests")
4444
include(":tests:codegen:event-stream")
4545
include(":tests:e2e-test-util")
46+
include(":tests:codegen:smoke-tests")
47+
include(":tests:codegen:smoke-tests:services")
4648

4749
// generated services
4850
val File.isServiceDir: Boolean
@@ -57,6 +59,13 @@ file("services").listFiles().forEach {
5759
}
5860
}
5961

62+
// generated services by smoke tests test suite
63+
file("tests/codegen/smoke-tests/services").listFiles().forEach {
64+
if (it.isServiceDir) {
65+
include(":tests:codegen:smoke-tests:services:${it.name}")
66+
}
67+
}
68+
6069
// Service benchmarks project
6170
val benchmarkServices = listOf(
6271
// keep this list in sync with tests/benchmarks/service-benchmarks/build.gradle.kts

0 commit comments

Comments
 (0)