Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: aws smoke tests support #1437

Merged
merged 48 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
602bac1
feat: smoke tests
0marperez Aug 23, 2024
989d8ec
Merge branch 'main' of https://github.com/awslabs/aws-sdk-kotlin into…
0marperez Aug 23, 2024
9ca8f2e
Sending verified commit to trigger CI
0marperez Aug 23, 2024
b00ec1f
Actually sign commit message[B
0marperez Aug 23, 2024
35708df
PR feedback
0marperez Aug 28, 2024
2399355
Remove error log commited by accident
0marperez Aug 28, 2024
2d98a99
Add TODO
0marperez Aug 28, 2024
d7a4877
Added E2E tests
0marperez Sep 13, 2024
f390f6a
Remove dangerous gradle task, move sdk denylist check to codegen inst…
0marperez Sep 17, 2024
90a93ac
Fix kotlin native builds failing
0marperez Sep 18, 2024
6b288ed
Temp print statement to debug
0marperez Sep 18, 2024
c6701cb
Change debugging print to exception
0marperez Sep 18, 2024
95b553d
chmod gradlew before tests
0marperez Sep 18, 2024
5ac3db2
Use gradlew.bat
0marperez Sep 19, 2024
ca30211
ls sdk root dir
0marperez Sep 19, 2024
08356db
Use gradle connector instead of process builder
0marperez Sep 20, 2024
e7fdeff
build before testing
0marperez Sep 20, 2024
8f0dec1
increase gradle connection timeout
0marperez Sep 20, 2024
431e63e
run from sdk root dir, better logging, use explicit gradle distributi…
0marperez Sep 20, 2024
1706764
Enable gradle daemon with an idle timeout, increment internal timeout
0marperez Sep 21, 2024
bb53c3d
use gradle runner instead of connector
0marperez Sep 22, 2024
e13e9fc
Specify smokeTests to run & better test failure logging
0marperez Sep 22, 2024
afc5168
fix parallel task execution issues
0marperez Sep 22, 2024
c39f3fe
move task dependency from ci tests to code
0marperez Sep 23, 2024
5dfd004
easy to fix PR feedback and decoupling SmokeTestRunnerGenerator from SDK
0marperez Sep 23, 2024
87fa6bb
Merge branch 'main' of https://github.com/awslabs/aws-sdk-kotlin into…
0marperez Sep 23, 2024
c1e0edf
Remove accidental changes to AWS model
0marperez Sep 24, 2024
dfcffbb
Run smoke tests in common
0marperez Sep 24, 2024
25b8e8e
Self nits
0marperez Sep 24, 2024
8aae6e4
Use new runtime function for env vars
0marperez Sep 24, 2024
5687f7e
Cleanup, Clarity changes to test code in src
0marperez Sep 25, 2024
1e35fc7
Merge from main
0marperez Oct 2, 2024
d465c50
Disable native for smoke test e2e tests
0marperez Oct 3, 2024
73f694a
Use vararg instead of list & add FIXME to enable native builds later
0marperez Oct 4, 2024
774a856
Merge branch 'main' of https://github.com/awslabs/aws-sdk-kotlin into…
0marperez Oct 7, 2024
578350b
Remove iot data plane from smoke test deny list
0marperez Oct 8, 2024
e2107d7
AWS & S3 vendor params support
0marperez Oct 8, 2024
408b3cf
Add test for exception logging
0marperez Oct 9, 2024
ecec7e3
Merge branch 'main' of https://github.com/awslabs/aws-sdk-kotlin into…
0marperez Oct 24, 2024
ca2fa02
Checkpoint for smoke tests code generation bug fixes
0marperez Oct 30, 2024
f7c17d6
Merge from main
0marperez Nov 1, 2024
ea40b9b
Checkpoint
0marperez Nov 1, 2024
c9d266f
PR feedback
0marperez Nov 5, 2024
d427f65
Merge branch 'main' of https://github.com/awslabs/aws-sdk-kotlin into…
0marperez Nov 5, 2024
dc5c114
File rename
0marperez Nov 5, 2024
c6ab629
Track upstream PR feedback
0marperez Nov 6, 2024
93c850b
PR feedback
0marperez Nov 6, 2024
e784343
bump smithy kotlin versions
0marperez Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ build/
.idea/
__pycache__/
local.properties

# ignore generated files
services/*/generated-src
services/*/build.gradle.kts
.kotest/
*.klib
*.klib
tests/codegen/smoke-tests/services/*/generated-src
tests/codegen/smoke-tests/services/*/build.gradle.kts
1 change: 1 addition & 0 deletions codegen/aws-sdk-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
api(libs.smithy.aws.cloudformation.traits)
api(libs.smithy.protocol.test.traits)
implementation(libs.smithy.aws.endpoints)
implementation(libs.smithy.smoke.test.traits)

testImplementation(libs.junit.jupiter)
testImplementation(libs.junit.jupiter.params)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@
*/
package aws.sdk.kotlin.codegen

import aws.sdk.kotlin.codegen.model.traits.testing.TestFailedResponseTrait
import aws.sdk.kotlin.codegen.model.traits.testing.TestSuccessResponseTrait
import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.model.expectShape
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.kotlin.codegen.rendering.GradleWriter
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait

// TODO - would be nice to allow integrations to define custom settings in the plugin
// e.g. we could then more consistently apply this integration if we could define a property like: `build.isAwsSdk: true`
Expand Down Expand Up @@ -64,9 +71,72 @@ class GradleGenerator : KotlinIntegration {
}
}
}
if (ctx.model.topDownOperations(ctx.settings.service).any { it.hasTrait<SmokeTestsTrait>() }) {
write("")
generateSmokeTestConfig(writer, ctx)
}
}

val contents = writer.toString()
delegator.fileManifest.writeFile("build.gradle.kts", contents)
}

private fun generateSmokeTestConfig(writer: GradleWriter, ctx: CodegenContext) {
generateSmokeTestJarTask(writer, ctx)
writer.write("")
generateSmokeTestTask(writer, ctx)
}

/**
* Generates a gradle task to create smoke test runner JARs
*/
private fun generateSmokeTestJarTask(writer: GradleWriter, ctx: CodegenContext) {
writer.withBlock("jvm {", "}") {
withBlock("compilations {", "}") {
write("val mainPath = getByName(#S).output.classesDirs", "main")
write("val testPath = getByName(#S).output.classesDirs", "test")
withBlock("tasks {", "}") {
withBlock("register<Jar>(#S) {", "}", "smokeTestJar") {
write("description = #S", "Creates smoke tests jar")
write("group = #S", "application")
write("dependsOn(build)")
write("mustRunAfter(build)")
withBlock("manifest {", "}") {
write("attributes[#S] = #S", "Main-Class", "${ctx.settings.pkg.name}.smoketests.SmokeTestsKt")
}
write("val runtimePath = configurations.getByName(#S).map { if (it.isDirectory) it else zipTree(it) }", "jvmRuntimeClasspath")
write("duplicatesStrategy = DuplicatesStrategy.EXCLUDE")
write("from(runtimePath, mainPath, testPath)")
write("archiveBaseName.set(#S)", "\${project.name}-smoketests")
}
}
}
}
}

/**
* Generates a gradle task to run smoke tests
*/
private fun generateSmokeTestTask(writer: GradleWriter, ctx: CodegenContext) {
val hasSuccessResponseTrait = ctx.model.expectShape<ServiceShape>(ctx.settings.service).hasTrait(TestSuccessResponseTrait.ID)
val hasFailedResponseTrait = ctx.model.expectShape<ServiceShape>(ctx.settings.service).hasTrait(TestFailedResponseTrait.ID)
val inTestingEnvironment = hasFailedResponseTrait || hasSuccessResponseTrait

/**
* E2E tests don't have sdkVersion in jar names. They're added later for publishing.
* @see SmokeTestE2ETest
*/
val jarName = if (inTestingEnvironment) "\${project.name}-smoketests.jar" else "\${project.name}-smoketests-\$sdkVersion.jar"

writer.withBlock("tasks.register<JavaExec>(#S) {", "}", "smokeTest") {
write("description = #S", "Runs smoke tests jar")
write("group = #S", "verification")
write("dependsOn(tasks.getByName(#S))", "smokeTestJar")
write("mustRunAfter(tasks.getByName(#S))", "smokeTestJar")
write("")
write("val sdkVersion: String by project")
write("val jarFile = file(#S)", "build/libs/$jarName")
write("classpath = files(jarFile)")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package aws.sdk.kotlin.codegen.model.traits.testing

import software.amazon.smithy.model.node.ObjectNode
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.AnnotationTrait

/**
* Indicates the annotated service should always return a failed response.
* IMPORTANT: This trait is intended for use in integration or E2E tests only, not in real-life smoke tests that run
* against a service endpoint.
*/
class TestFailedResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) {
companion object {
val ID: ShapeId = ShapeId.from("smithy.kotlin.traits#failedResponseTrait")
}
}

/**
* Indicates the annotated service should always return a success response.
* IMPORTANT: This trait is intended for use in integration or E2E tests only, not in real-life smoke tests that run
* against a service endpoint.
*/
class TestSuccessResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) {
companion object {
val ID: ShapeId = ShapeId.from("smithy.kotlin.traits#successResponseTrait")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package aws.sdk.kotlin.codegen.smoketests

import aws.sdk.kotlin.codegen.AwsRuntimeTypes
import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
import software.amazon.smithy.kotlin.codegen.core.getContextValue
import software.amazon.smithy.kotlin.codegen.core.withBlock
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.*
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
import software.amazon.smithy.model.Model
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait

/**
* Generates AWS specific code for smoke test runners
*/
class AwsSmokeTestsRunnerGeneratorIntegration : KotlinIntegration {
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
model.topDownOperations(settings.service).any { it.hasTrait<SmokeTestsTrait>() }

override val sectionWriters: List<SectionWriterBinding>
get() = listOf(
AwsSmokeTestsRunnerGenerator.regionEnvironmentVariable,
AwsSmokeTestsRunnerGenerator.clientConfig,
AwsSmokeTestsRunnerGenerator.defaultClientConfig,
AwsSmokeTestsRunnerGenerator.skipTagsEnvironmentVariable,
AwsSmokeTestsRunnerGenerator.serviceFilterEnvironmentVariable,
)
}

/**
* The section writer bindings used by [AwsSmokeTestsRunnerGeneratorIntegration]
*/
private object AwsSmokeTestsRunnerGenerator {
/**
* Adds region environment variable support to AWS smoke test runners.
* Preserves other environment variables added via section writer binding, if any.
*/
val regionEnvironmentVariable =
SectionWriterBinding(SmokeTestSectionIds.AdditionalEnvironmentVariables) { writer, previous ->
writer.write("#L", previous)
writer.write(
"private val regionOverride = #T.System.getenv(#S)",
RuntimeTypes.Core.Utils.PlatformProvider,
AWS_REGION,
)
}

/**
* Add AWS specific client config support to AWS smoke test runners
*/
val clientConfig =
SectionWriterBinding(SmokeTestSectionIds.ClientConfig) { writer, _ ->
val name = writer.getContextValue(SmokeTestSectionIds.ClientConfig.Name)
val value = writer.getContextValue(SmokeTestSectionIds.ClientConfig.Value)

// Normalize client config names
val newName = when (name) {
"uri" -> "endpointProvider"
"useDualstack" -> "useDualStack"
"sigv4aRegionSet" -> "sigV4aSigningRegionSet"
"useAccountIdRouting" -> "accountIdEndpointMode"
"useAccelerate" -> "enableAccelerate"
"useMultiRegionAccessPoints" -> "disableMrap"
"useGlobalEndpoint" -> {
writer.write("throw #T(#S)", RuntimeTypes.Core.SmokeTests.SmokeTestsException, "'useGlobalEndpoint' is not supported by the SDK")
return@SectionWriterBinding
}
else -> name
}
writer.writeInline("#L = ", newName)

// Normalize client values
when (newName) {
"endpointProvider" -> {
val endpointProvider = writer.getContextValue(SmokeTestSectionIds.ClientConfig.EndpointProvider)
val endpointParameters = writer.getContextValue(SmokeTestSectionIds.ClientConfig.EndpointParams)

writer.withBlock("object : #T {", "}", endpointProvider) {
write(
"override suspend fun resolveEndpoint(params: #1T): #2T = #2T(#3L)",
endpointParameters,
RuntimeTypes.SmithyClient.Endpoints.Endpoint,
value,
)
}
}
"sigV4aSigningRegionSet" -> {
// Render new value
writer.write("#L.toSet()", value)
// Also configure sigV4a - TODO: Remove once sigV4a is supported for default signer.
writer.write(
"authSchemes = listOf(#T(#T))",
RuntimeTypes.Auth.HttpAuthAws.SigV4AsymmetricAuthScheme,
RuntimeTypes.Auth.AwsSigningCrt.CrtAwsSigner,
)
}
"accountIdEndpointMode" -> {
when (value) {
"true" -> writer.write("#T.REQUIRED", AwsRuntimeTypes.Config.Endpoints.AccountIdEndpointMode)
"false" -> writer.write("#T.DISABLED", AwsRuntimeTypes.Config.Endpoints.AccountIdEndpointMode)
}
}
"disableMrap" -> {
when (value) {
"true" -> writer.write("false")
"false" -> writer.write("true")
}
}
"region" -> {
writer.write("regionOverride ?: #L", value)
}
else -> writer.write("#L", value)
}
}

/**
* Add default client config to AWS smoke test runners.
* Preserves previous default config if any.
*/
val defaultClientConfig =
SectionWriterBinding(SmokeTestSectionIds.DefaultClientConfig) { writer, previous ->
writer.write("#L", previous)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is writing previous necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added previous just in case we add more default client config in the future via another SectionWriterBinding

writer.write("region = regionOverride")
}

/**
* Replaces environment variable with one specific to AWS smoke test runners
*/
val skipTagsEnvironmentVariable =
SectionWriterBinding(SmokeTestSectionIds.SkipTags) { writer, _ -> writer.writeInline("#S", AWS_SKIP_TAGS) }

/**
* Replaces environment variable with one specific to AWS smoke test runners
*/
val serviceFilterEnvironmentVariable =
SectionWriterBinding(SmokeTestSectionIds.ServiceFilter) { writer, _ -> writer.writeInline("#S", AWS_SERVICE_FILTER) }
}

/**
* Env var for AWS smoke test runners.
* Should be a string that corresponds to an AWS region.
* The region to use when executing smoke tests. This value MUST override any value supplied in the smoke tests themselves.
*/
private const val AWS_REGION = "AWS_SMOKE_TEST_REGION"

/**
* Env var for AWS smoke test runners.
* Should be a comma-delimited list of strings that correspond to tags on the test cases.
* If a test case is tagged with one of the tags indicated by AWS_SMOKE_TEST_SKIP_TAGS, it MUST be skipped by the smoke test runner.
*/
const val AWS_SKIP_TAGS = "AWS_SMOKE_TEST_SKIP_TAGS"

/**
* Env var for AWS smoke test runners.
* Should be a comma-separated list of service identifiers to test.
*/
const val AWS_SERVICE_FILTER = "AWS_SMOKE_TEST_SERVICE_IDS"

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package aws.sdk.kotlin.codegen.smoketests.testing

import aws.sdk.kotlin.codegen.model.traits.testing.TestFailedResponseTrait
import aws.sdk.kotlin.codegen.model.traits.testing.TestSuccessResponseTrait
import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
import software.amazon.smithy.kotlin.codegen.core.withBlock
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.integration.SectionWriter
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
import software.amazon.smithy.kotlin.codegen.model.expectShape
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestSectionIds
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait

/**
* Adds [TestFailedResponseTrait] support to smoke tests
* IMPORTANT: This integration is intended for use in integration or E2E tests only, not in real-life smoke tests that run
* against a service endpoint.
*/
class SmokeTestFailHttpEngineIntegration : KotlinIntegration {
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
model.topDownOperations(settings.service).any { it.hasTrait<SmokeTestsTrait>() } &&
!model.expectShape<ServiceShape>(settings.service).hasTrait(TestSuccessResponseTrait.ID) &&
model.expectShape<ServiceShape>(settings.service).hasTrait(TestFailedResponseTrait.ID)

override val sectionWriters: List<SectionWriterBinding>
get() = listOf(
SectionWriterBinding(SmokeTestSectionIds.HttpEngineOverride, httpClientOverride),
)

private val httpClientOverride = SectionWriter { writer, _ ->
writer.withBlock("httpClient = #T(", ")", RuntimeTypes.HttpTest.TestEngine) {
withBlock("roundTripImpl = { _, request ->", "}") {
write(
"val resp = #T(#T.BadRequest, #T.Empty, #T.Empty)",
RuntimeTypes.Http.Response.HttpResponse,
RuntimeTypes.Http.StatusCode,
RuntimeTypes.Http.Headers,
RuntimeTypes.Http.HttpBody,
)
write("val now = #T.now()", RuntimeTypes.Core.Instant)
write("#T(request, resp, now, now)", RuntimeTypes.Http.HttpCall)
}
}
}
}
Loading
Loading