From 0ffb5a530dcfefe38dd7b2d2716228c44f0680a3 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Fri, 23 Aug 2024 10:08:07 -0400 Subject: [PATCH 01/15] feat: smoke tests --- .../smithy-kotlin-codegen/build.gradle.kts | 1 + .../kotlin/codegen/core/KotlinDelegator.kt | 7 +- .../kotlin/codegen/core/KotlinWriter.kt | 2 + .../kotlin/codegen/core/RuntimeTypes.kt | 5 + .../codegen/rendering/GradleGenerator.kt | 2 + .../smoketests/SmokeTestsIntegration.kt | 39 ++++ .../smoketests/SmokeTestsRunnerGenerator.kt | 172 +++++++++++++++ .../kotlin/codegen/rendering/util/Node.kt | 31 +++ .../smithy/kotlin/codegen/utils/Model.kt | 16 ++ ...tlin.codegen.integration.KotlinIntegration | 1 + .../SmokeTestsRunnerGeneratorTest.kt | 200 ++++++++++++++++++ .../protocol/http-client/api/http-client.api | 39 ++++ .../interceptors/SmokeTestsInterceptor.kt | 39 ++++ 13 files changed, 551 insertions(+), 3 deletions(-) create mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt create mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt create mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt create mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt create mode 100644 codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt create mode 100644 runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt diff --git a/codegen/smithy-kotlin-codegen/build.gradle.kts b/codegen/smithy-kotlin-codegen/build.gradle.kts index 8c8a662f7..956418641 100644 --- a/codegen/smithy-kotlin-codegen/build.gradle.kts +++ b/codegen/smithy-kotlin-codegen/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(libs.smithy.aws.traits) implementation(libs.smithy.protocol.traits) implementation(libs.smithy.protocol.test.traits) + implementation(libs.smithy.smoke.test.traits) implementation(libs.jsoup) // Test dependencies diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt index 7b5b871e6..ffe268c75 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt @@ -121,9 +121,10 @@ class KotlinDelegator( * * @param filename Name of the file to create. * @param block Lambda that accepts and works with the file. + * @param sourceSetRoot Root directory for source set */ - fun useFileWriter(filename: String, namespace: String, block: (KotlinWriter) -> Unit) { - val writer: KotlinWriter = checkoutWriter(filename, namespace) + fun useFileWriter(filename: String, namespace: String, sourceSetRoot: String = DEFAULT_SOURCE_SET_ROOT, block: (KotlinWriter) -> Unit) { + val writer: KotlinWriter = checkoutWriter(filename, namespace, sourceSetRoot) block(writer) } @@ -205,6 +206,6 @@ internal data class GeneratedDependency( } fun KotlinDelegator.useFileWriter(symbol: Symbol, block: (KotlinWriter) -> Unit) = - useFileWriter("${symbol.name}.kt", symbol.namespace, block) + useFileWriter("${symbol.name}.kt", symbol.namespace, DEFAULT_SOURCE_SET_ROOT, block) fun KotlinDelegator.applyFileWriter(symbol: Symbol, block: KotlinWriter.() -> Unit) = useFileWriter(symbol, block) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt index 6fb4c81cf..b0a4e46bc 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt @@ -198,6 +198,8 @@ class KotlinWriter( ) } + fun emptyLine(): KotlinWriter = this.write("") + /** * Clean/escape any content from the doc that would invalidate the Kotlin output. */ diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index d25e1c01c..4f1a3812f 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -86,6 +86,11 @@ object RuntimeTypes { val FlexibleChecksumsResponseInterceptor = symbol("FlexibleChecksumsResponseInterceptor") val ResponseLengthValidationInterceptor = symbol("ResponseLengthValidationInterceptor") val RequestCompressionInterceptor = symbol("RequestCompressionInterceptor") + val SmokeTestsInterceptor = symbol("SmokeTestsInterceptor") + val SmokeTestsFailureException = symbol("SmokeTestsFailureException") + val SmokeTestsSuccessException = symbol("SmokeTestsSuccessException") + val SmokeTestsUnexpectedException = symbol("SmokeTestsUnexpectedException") + val exitProcess = symbol("exitProcess") } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt index dcaacf2f3..9be396903 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt @@ -250,4 +250,6 @@ class GradleWriter(parent: GradleWriter? = null) : AbstractCodeWriter() } && !smokeTestDenyList.contains(settings.sdkId) + + override fun writeAdditionalFiles(ctx: CodegenContext, delegator: KotlinDelegator) = + delegator.useFileWriter("SmokeTests.kt", "smoketests", "./jvm-src/main/java/") { writer -> + SmokeTestsRunnerGenerator( + writer, + ctx.symbolProvider.toSymbol(ctx.model.expectShape(ctx.settings.service)), + ctx.model.operations(ctx.settings.service).filter { it.hasTrait() }, + ctx.model, + ctx.symbolProvider, + ctx.settings.sdkId, + ).render() + } +} + +/** + * SDK ID's of services that model smoke tests incorrectly + */ +val smokeTestDenyList = setOf( + "Application Auto Scaling", + "SWF", + "WAFV2", +) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt new file mode 100644 index 000000000..62bdf0141 --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -0,0 +1,172 @@ +package software.amazon.smithy.kotlin.codegen.rendering.smoketests + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.codegen.core.SymbolProvider +import software.amazon.smithy.kotlin.codegen.core.* +import software.amazon.smithy.kotlin.codegen.model.getTrait +import software.amazon.smithy.kotlin.codegen.rendering.util.render +import software.amazon.smithy.kotlin.codegen.utils.dq +import software.amazon.smithy.kotlin.codegen.utils.getOrNull +import software.amazon.smithy.kotlin.codegen.utils.toCamelCase +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.smoketests.traits.SmokeTestCase +import software.amazon.smithy.smoketests.traits.SmokeTestsTrait +import kotlin.jvm.optionals.getOrNull + +/** + * Renders smoke tests runner for a service + */ +class SmokeTestsRunnerGenerator( + private val writer: KotlinWriter, + private val service: Symbol, + private val operations: List, + private val model: Model, + private val symbolProvider: SymbolProvider, + private val sdkId: String, +) { + internal fun render() { + writer.write("private var exitCode = 0") + writer.write("private val regionOverride = System.getenv(\"AWS_SMOKE_TEST_REGION\")") + writer.write("private val skipTags = System.getenv(\"AWS_SMOKE_TEST_SKIP_TAGS\")?.let { it.split(\",\").map { it.trim() }.toSet() }") + writer.emptyLine() + writer.withBlock("public suspend fun main() {", "}") { + renderFunctionCalls() + write("#L(exitCode)", RuntimeTypes.HttpClient.Interceptors.exitProcess) + } + writer.emptyLine() + renderFunctions() + } + + private fun renderFunctionCalls() { + operations.forEach { operation -> + operation.getTrait()?.testCases?.forEach { testCase -> + writer.write("${testCase.id.toCamelCase()}()") + } + } + } + + private fun renderFunctions() { + operations.forEach { operation -> + operation.getTrait()?.testCases?.forEach { testCase -> + renderFunction(operation, testCase) + writer.emptyLine() + } + } + } + + private fun renderFunction(operation: OperationShape, testCase: SmokeTestCase) { + writer.withBlock("private suspend fun ${testCase.id.toCamelCase()}() {", "}") { + write("val tags = setOf(${testCase.tags.joinToString(",") { it.dq()} })") + write("if (skipTags != null && tags.any { it in skipTags }) return") + emptyLine() + withInlineBlock("try {", "} ") { + renderClient(testCase) + renderOperation(operation, testCase) + } + withBlock("catch (e: Exception) {", "}") { + renderCatchBlock(testCase) + } + } + } + + private fun renderClient(testCase: SmokeTestCase) { + writer.withInlineBlock("#L {", "}", service) { + if (testCase.vendorParams.isPresent) { + testCase.vendorParams.get().members.forEach { vendorParam -> + if (vendorParam.key.value == "region") { + write("region = regionOverride ?: #L", vendorParam.value.render()) + } else { + write("#L = #L", vendorParam.key.value.toCamelCase(), vendorParam.value.render()) + } + } + } else { + write("region = regionOverride") + } + val expectingSpecificError = testCase.expectation.failure.getOrNull()?.errorId?.getOrNull() != null + write("interceptors.add(#T($expectingSpecificError))", RuntimeTypes.HttpClient.Interceptors.SmokeTestsInterceptor) + } + checkVendorParamsAreCorrect(testCase) + } + + /** + * Smithy IDL doesn't check that vendor params are found in vendor params shape so we have to check here. + */ + private fun checkVendorParamsAreCorrect(testCase: SmokeTestCase) { + if (testCase.vendorParamsShape.isPresent && testCase.vendorParams.isPresent) { + val vendorParamsShape = model.getShape(testCase.vendorParamsShape.get()).get() + val validVendorParams = vendorParamsShape.members().map { it.memberName } + val vendorParams = testCase.vendorParams.get().members.map { it.key.value } + + vendorParams.forEach { vendorParam -> + check(validVendorParams.contains(vendorParam)) { + "Smithy smoke test \"${testCase.id}\" contains invalid vendor param \"$vendorParam\", it was not found in vendor params shape \"${testCase.vendorParamsShape}\"" + } + } + } + } + + private fun renderOperation(operation: OperationShape, testCase: SmokeTestCase) { + val operationSymbol = symbolProvider.toSymbol(model.getShape(operation.input.get()).get()) + + writer.addImport(operationSymbol) + writer.withBlock(".use { client ->", "}") { + withBlock("client.#L(", ")", operation.defaultName()) { + withBlock("#L {", "}", operationSymbol.name) { + testCase.params.get().members.forEach { member -> + write("#L = #L", member.key.value.toCamelCase(), member.value.render()) + } + } + } + } + } + + private fun renderCatchBlock(testCase: SmokeTestCase) { + val successCriterion = RuntimeTypes.HttpClient.Interceptors.SmokeTestsSuccessException + val failureCriterion = getFailureCriterion(testCase) + val expected = if (testCase.expectation.isFailure) { + failureCriterion + } else { + successCriterion + } + + writer.write("val success = e is #L", expected) + writer.write("val status = if (success) \"ok\" else \"not ok\"") + printTestResult( + sdkId.filter { !it.isWhitespace() }, + testCase.id, + testCase.expectation.isFailure, + writer, + ) + writer.write("if (!success) exitCode = 1") + } + + /** + * Tries to get the specific exception required in the failure criterion of a test. + * If no specific exception is required we default to the generic smoke tests failure exception. + * + * Some smoke tests model exceptions not found in the model, in that case we default to the generic smoke tests + * failure exception. + */ + private fun getFailureCriterion(testCase: SmokeTestCase): Symbol = testCase.expectation.failure.getOrNull()?.errorId?.let { + try { + symbolProvider.toSymbol(model.getShape(it.get()).get()) + } catch (e: Exception) { + RuntimeTypes.HttpClient.Interceptors.SmokeTestsFailureException + } + } ?: RuntimeTypes.HttpClient.Interceptors.SmokeTestsFailureException + + /** + * Renders print statement for smoke test result in accordance to design doc & test anything protocol (TAP) + */ + private fun printTestResult( + service: String, + testCase: String, + errorExpected: Boolean, + writer: KotlinWriter, + ) { + val expectation = if (errorExpected) "error expected from service" else "no error expected from service" + val testResult = "\$status $service $testCase - $expectation" + writer.write("println(#S)", testResult) + } +} diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt new file mode 100644 index 000000000..71a75af92 --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt @@ -0,0 +1,31 @@ +package software.amazon.smithy.kotlin.codegen.rendering.util + +import software.amazon.smithy.kotlin.codegen.utils.dq +import software.amazon.smithy.model.node.ArrayNode +import software.amazon.smithy.model.node.BooleanNode +import software.amazon.smithy.model.node.Node +import software.amazon.smithy.model.node.NullNode +import software.amazon.smithy.model.node.NumberNode +import software.amazon.smithy.model.node.ObjectNode +import software.amazon.smithy.model.node.StringNode + +/** + * Renders a [Node] into String format for codegen. + */ +fun Node.render(): String = when (this) { + is NullNode -> "null" + is StringNode -> value.dq() + is BooleanNode -> value.toString() + is NumberNode -> value.toString() + is ArrayNode -> elements.joinToString(",", "listOf(", ")") { element -> + element.render() + } + is ObjectNode -> buildString { + append("mapOf(") + stringMap.forEach { (key, value) -> + append("\t${key.dq()} to ${value.render()}") + } + append(")") + } + else -> throw Exception("Unexpected node type: $this") +} diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt new file mode 100644 index 000000000..613b9aebb --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt @@ -0,0 +1,16 @@ +package software.amazon.smithy.kotlin.codegen.utils + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.knowledge.TopDownIndex +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.utils.SmithyInternalApi + +/** + * Syntactic sugar for getting a services operations + */ +@SmithyInternalApi +fun Model.operations(service: ShapeId): Set { + val topDownIndex = TopDownIndex.of(this) + return topDownIndex.getContainedOperations(service) +} diff --git a/codegen/smithy-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration b/codegen/smithy-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration index a230428ee..0d02b5118 100644 --- a/codegen/smithy-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +++ b/codegen/smithy-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration @@ -12,3 +12,4 @@ software.amazon.smithy.kotlin.codegen.rendering.endpoints.discovery.EndpointDisc software.amazon.smithy.kotlin.codegen.rendering.endpoints.SdkEndpointBuiltinIntegration software.amazon.smithy.kotlin.codegen.rendering.compression.RequestCompressionIntegration software.amazon.smithy.kotlin.codegen.rendering.auth.SigV4AsymmetricAuthSchemeIntegration +software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestsIntegration diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt new file mode 100644 index 000000000..eb6d6fc19 --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt @@ -0,0 +1,200 @@ +package software.amazon.smithy.kotlin.codegen.rendering.smoketests + +import software.amazon.smithy.kotlin.codegen.model.hasTrait +import software.amazon.smithy.kotlin.codegen.test.* +import software.amazon.smithy.kotlin.codegen.utils.operations +import software.amazon.smithy.model.Model +import software.amazon.smithy.smoketests.traits.SmokeTestsTrait +import kotlin.test.Test + +class SmokeTestsRunnerGeneratorTest { + private val moneySign = "$" + + private fun codegen(model: Model): String { + val testCtx = model.newTestContext() + val codegenCtx = testCtx.toCodegenContext() + val writer = testCtx.newWriter() + SmokeTestsRunnerGenerator( + writer, + codegenCtx.symbolProvider.toSymbol(codegenCtx.model.expectShape(codegenCtx.settings.service)), + codegenCtx.model.operations(codegenCtx.settings.service).filter { it.hasTrait() }, + codegenCtx.model, + codegenCtx.symbolProvider, + codegenCtx.settings.sdkId, + ).render() + return writer.toString() + } + + @Test + fun codegenTest() { + val model = + """ + ${moneySign}version: "2" + namespace com.test + + use smithy.test#smokeTests + + service Test { + version: "1.0.0", + operations: [ TestOperation ], + } + + @smokeTests( + [ + { + id: "SuccessTest" + params: {bar: "2"} + tags: [ + "success" + ] + expect: { + success: {} + } + vendorParamsShape: AwsVendorParams, + vendorParams: { + region: "eu-central-1" + } + } + { + id: "InvalidMessageErrorTest" + params: {bar: "föö"} + expect: { + failure: {errorId: InvalidMessageError} + } + } + { + id: "FailureTest" + params: {bar: "föö"} + expect: { + failure: {} + } + } + ] + ) + + operation TestOperation { + input := { + bar: String + } + errors: [ + InvalidMessageError + ] + } + + @error("client") + structure InvalidMessageError {} + + structure AwsVendorParams { + region: String + } + """.toSmithyModel() + + val generatedCode = codegen(model) + + generatedCode.shouldContainOnlyOnceWithDiff( + """ + private var exitCode = 0 + private val regionOverride = System.getenv("AWS_SMOKE_TEST_REGION") + private val skipTags = System.getenv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } + """.trimIndent(), + ) + + generatedCode.shouldContainOnlyOnceWithDiff( + """ + public suspend fun main() { + successTest() + invalidMessageErrorTest() + failureTest() + aws.smithy.kotlin.runtime.http.interceptors.exitProcess(exitCode) + } + """.trimIndent(), + ) + + generatedCode.shouldContainOnlyOnceWithDiff( + """ + private suspend fun successTest() { + val tags = setOf("success") + if (skipTags != null && tags.any { it in skipTags }) return + + try { + com.test.TestClient { + region = regionOverride ?: "eu-central-1" + interceptors.add(SmokeTestsInterceptor(false)) + + }.use { client -> + client.testOperation( + TestOperationRequest { + bar = "2" + } + ) + } + + } catch (e: Exception) { + val success = e is aws.smithy.kotlin.runtime.http.interceptors.SmokeTestsSuccessException + val status = if (success) "ok" else "not ok" + println("${moneySign}status Test SuccessTest - no error expected from service") + if (!success) exitCode = 1 + } + } + """.trimIndent(), + ) + + generatedCode.shouldContainOnlyOnceWithDiff( + """ + private suspend fun invalidMessageErrorTest() { + val tags = setOf() + if (skipTags != null && tags.any { it in skipTags }) return + + try { + com.test.TestClient { + region = regionOverride + interceptors.add(SmokeTestsInterceptor(true)) + + }.use { client -> + client.testOperation( + TestOperationRequest { + bar = "föö" + } + ) + } + + } catch (e: Exception) { + val success = e is com.test.model.InvalidMessageError + val status = if (success) "ok" else "not ok" + println("${moneySign}status Test InvalidMessageErrorTest - error expected from service") + if (!success) exitCode = 1 + } + } + """.trimIndent(), + ) + + generatedCode.shouldContainOnlyOnceWithDiff( + """ + private suspend fun failureTest() { + val tags = setOf() + if (skipTags != null && tags.any { it in skipTags }) return + + try { + com.test.TestClient { + region = regionOverride + interceptors.add(SmokeTestsInterceptor(false)) + + }.use { client -> + client.testOperation( + TestOperationRequest { + bar = "föö" + } + ) + } + + } catch (e: Exception) { + val success = e is aws.smithy.kotlin.runtime.http.interceptors.SmokeTestsFailureException + val status = if (success) "ok" else "not ok" + println("${moneySign}status Test FailureTest - error expected from service") + if (!success) exitCode = 1 + } + } + """.trimIndent(), + ) + } +} diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 9927ae030..288d1b13f 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -424,6 +424,45 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/ResponseLengthVal public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V } +public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsFailureException : java/lang/Exception { + public fun ()V +} + +public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { + public fun (Z)V + public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun modifyBeforeCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun modifyBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun modifyBeforeRetryLoop (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun modifyBeforeSerialization (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun modifyBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun modifyBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun readAfterAttempt (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V + public fun readAfterDeserialization (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V + public fun readAfterExecution (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V + public fun readAfterSerialization (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V + public fun readAfterSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V + public fun readAfterTransmit (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;)V + public fun readBeforeAttempt (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V + public fun readBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;)V + public fun readBeforeExecution (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;)V + public fun readBeforeSerialization (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;)V + public fun readBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V + public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V +} + +public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptorKt { + public static final fun exitProcess (I)Ljava/lang/Void; +} + +public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsSuccessException : java/lang/Exception { + public fun ()V +} + +public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsUnexpectedException : java/lang/Exception { + public fun ()V +} + public final class aws/smithy/kotlin/runtime/http/middleware/DefaultValidateResponse : aws/smithy/kotlin/runtime/http/operation/ReceiveMiddleware { public fun ()V public fun handle (Laws/smithy/kotlin/runtime/http/operation/OperationRequest;Laws/smithy/kotlin/runtime/io/Handler;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt new file mode 100644 index 000000000..3828fc649 --- /dev/null +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt @@ -0,0 +1,39 @@ +package aws.smithy.kotlin.runtime.http.interceptors + +import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.client.ProtocolResponseInterceptorContext +import aws.smithy.kotlin.runtime.http.request.HttpRequest +import aws.smithy.kotlin.runtime.http.response.HttpResponse +import kotlin.system.exitProcess + +/** + * Interceptor for smoke test runner clients. + * + * A passing test is not predicated on an SDK being able to parse the server response received, it’s based on the + * response’s HTTP status code UNLESS we're expecting a specific error. + */ +@InternalApi +public class SmokeTestsInterceptor( + private val expectingSpecificError: Boolean, +) : HttpInterceptor { + override fun readBeforeDeserialization(context: ProtocolResponseInterceptorContext) { + if (expectingSpecificError) return + val status = context.protocolResponse.status.value + when (status) { + in 400..599 -> throw SmokeTestsFailureException() + in 200..299 -> throw SmokeTestsSuccessException() + else -> throw SmokeTestsUnexpectedException() + } + } +} + +@InternalApi public class SmokeTestsFailureException : Exception() + +@InternalApi public class SmokeTestsSuccessException : Exception() + +@InternalApi public class SmokeTestsUnexpectedException : Exception() + +/** + * Runtime function to exit smoke test runners with a specific status code. + */ +@InternalApi public fun exitProcess(status: Int): Nothing = exitProcess(status) From aef03ac0b3015576f8b38b5f71bebb4c09db6025 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Fri, 23 Aug 2024 10:12:43 -0400 Subject: [PATCH 02/15] misc: changelog --- .changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json diff --git a/.changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json b/.changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json new file mode 100644 index 000000000..4ac68a2c1 --- /dev/null +++ b/.changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json @@ -0,0 +1,5 @@ +{ + "id": "756754c3-f6e1-4ff2-ae31-08b3b67b6750", + "type": "feature", + "description": "Add support for smoke tests" +} \ No newline at end of file From fc97ab9a650e2fa10f12cd763b7846cd0b068855 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Fri, 23 Aug 2024 11:26:34 -0400 Subject: [PATCH 03/15] Move exit process function to runtime only --- .../amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt | 1 - .../rendering/smoketests/SmokeTestsRunnerGenerator.kt | 4 +++- .../rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt | 2 +- .../runtime/http/interceptors/SmokeTestsInterceptor.kt | 6 ------ 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 4f1a3812f..42be17ec3 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -90,7 +90,6 @@ object RuntimeTypes { val SmokeTestsFailureException = symbol("SmokeTestsFailureException") val SmokeTestsSuccessException = symbol("SmokeTestsSuccessException") val SmokeTestsUnexpectedException = symbol("SmokeTestsUnexpectedException") - val exitProcess = symbol("exitProcess") } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index 62bdf0141..a369593f8 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -26,13 +26,15 @@ class SmokeTestsRunnerGenerator( private val sdkId: String, ) { internal fun render() { + writer.write("import kotlin.system.exitProcess") + writer.emptyLine() writer.write("private var exitCode = 0") writer.write("private val regionOverride = System.getenv(\"AWS_SMOKE_TEST_REGION\")") writer.write("private val skipTags = System.getenv(\"AWS_SMOKE_TEST_SKIP_TAGS\")?.let { it.split(\",\").map { it.trim() }.toSet() }") writer.emptyLine() writer.withBlock("public suspend fun main() {", "}") { renderFunctionCalls() - write("#L(exitCode)", RuntimeTypes.HttpClient.Interceptors.exitProcess) + write("exitProcess(exitCode)") } writer.emptyLine() renderFunctions() diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt index eb6d6fc19..066f1b764 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt @@ -105,7 +105,7 @@ class SmokeTestsRunnerGeneratorTest { successTest() invalidMessageErrorTest() failureTest() - aws.smithy.kotlin.runtime.http.interceptors.exitProcess(exitCode) + exitProcess(exitCode) } """.trimIndent(), ) diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt index 3828fc649..d6c898e1d 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt @@ -4,7 +4,6 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.client.ProtocolResponseInterceptorContext import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.response.HttpResponse -import kotlin.system.exitProcess /** * Interceptor for smoke test runner clients. @@ -32,8 +31,3 @@ public class SmokeTestsInterceptor( @InternalApi public class SmokeTestsSuccessException : Exception() @InternalApi public class SmokeTestsUnexpectedException : Exception() - -/** - * Runtime function to exit smoke test runners with a specific status code. - */ -@InternalApi public fun exitProcess(status: Int): Nothing = exitProcess(status) From f152351f6c276d50f59897cbc64217f98e38cbd7 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Fri, 23 Aug 2024 11:42:03 -0400 Subject: [PATCH 04/15] Api dump --- runtime/protocol/http-client/api/http-client.api | 4 ---- 1 file changed, 4 deletions(-) diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 288d1b13f..7b033f26c 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -451,10 +451,6 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterce public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V } -public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptorKt { - public static final fun exitProcess (I)Ljava/lang/Void; -} - public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsSuccessException : java/lang/Exception { public fun ()V } From f051f673dee58f7f9b2ac378bc14caa502137679 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Wed, 28 Aug 2024 11:07:34 -0400 Subject: [PATCH 05/15] PR feedback --- .../756754c3-f6e1-4ff2-ae31-08b3b67b6750.json | 2 +- .../smoketests/SmokeTestsIntegration.kt | 25 ++-- .../smoketests/SmokeTestsRunnerGenerator.kt | 117 +++++++++--------- .../kotlin/codegen/rendering/util/Node.kt | 8 +- .../SmokeTestsRunnerGeneratorTest.kt | 102 ++++++++------- .../protocol/http-client/api/http-client.api | 2 +- .../interceptors/SmokeTestsInterceptor.kt | 5 +- 7 files changed, 133 insertions(+), 128 deletions(-) diff --git a/.changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json b/.changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json index 4ac68a2c1..84d27c4cc 100644 --- a/.changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json +++ b/.changes/756754c3-f6e1-4ff2-ae31-08b3b67b6750.json @@ -1,5 +1,5 @@ { "id": "756754c3-f6e1-4ff2-ae31-08b3b67b6750", "type": "feature", - "description": "Add support for smoke tests" + "description": "Add support for [smoke tests](https://smithy.io/2.0/additional-specs/smoke-tests.html)" } \ No newline at end of file diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt index e1ce2a857..4c76499e1 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt @@ -10,30 +10,21 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.smoketests.traits.SmokeTestsTrait /** - * Renders smoke test runner for a service if any of the operations has the smoke test trait. + * Renders smoke test runner for a service if any of the operations have the [SmokeTestsTrait]. */ class SmokeTestsIntegration : KotlinIntegration { override fun enabledForService(model: Model, settings: KotlinSettings): Boolean = - model.operations(settings.service).any { it.hasTrait() } && !smokeTestDenyList.contains(settings.sdkId) + model.operations(settings.service).any { it.hasTrait() } override fun writeAdditionalFiles(ctx: CodegenContext, delegator: KotlinDelegator) = - delegator.useFileWriter("SmokeTests.kt", "smoketests", "./jvm-src/main/java/") { writer -> + delegator.useFileWriter( + "SmokeTests.kt", + "${ctx.settings.pkg.name}.smoketests", + "./jvm-src/test/java/", + ) { writer -> SmokeTestsRunnerGenerator( writer, - ctx.symbolProvider.toSymbol(ctx.model.expectShape(ctx.settings.service)), - ctx.model.operations(ctx.settings.service).filter { it.hasTrait() }, - ctx.model, - ctx.symbolProvider, - ctx.settings.sdkId, + ctx, ).render() } } - -/** - * SDK ID's of services that model smoke tests incorrectly - */ -val smokeTestDenyList = setOf( - "Application Auto Scaling", - "SWF", - "WAFV2", -) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index a369593f8..980326eb2 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -1,49 +1,58 @@ package software.amazon.smithy.kotlin.codegen.rendering.smoketests import software.amazon.smithy.codegen.core.Symbol -import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.kotlin.codegen.core.* +import software.amazon.smithy.kotlin.codegen.integration.SectionId import software.amazon.smithy.kotlin.codegen.model.getTrait -import software.amazon.smithy.kotlin.codegen.rendering.util.render +import software.amazon.smithy.kotlin.codegen.model.hasTrait +import software.amazon.smithy.kotlin.codegen.rendering.util.format import software.amazon.smithy.kotlin.codegen.utils.dq import software.amazon.smithy.kotlin.codegen.utils.getOrNull +import software.amazon.smithy.kotlin.codegen.utils.operations import software.amazon.smithy.kotlin.codegen.utils.toCamelCase -import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.smoketests.traits.SmokeTestCase import software.amazon.smithy.smoketests.traits.SmokeTestsTrait import kotlin.jvm.optionals.getOrNull +object SmokeTestsRunner : SectionId + /** * Renders smoke tests runner for a service */ class SmokeTestsRunnerGenerator( private val writer: KotlinWriter, - private val service: Symbol, - private val operations: List, - private val model: Model, - private val symbolProvider: SymbolProvider, - private val sdkId: String, + ctx: CodegenContext, +// private val testing: Boolean = false, TODO: Implement tests ) { + private val model = ctx.model + private val sdkId = ctx.settings.sdkId + private val symbolProvider = ctx.symbolProvider + private val service = symbolProvider.toSymbol(model.expectShape(ctx.settings.service)) + private val operations = ctx.model.operations(ctx.settings.service).filter { it.hasTrait() } + internal fun render() { - writer.write("import kotlin.system.exitProcess") - writer.emptyLine() - writer.write("private var exitCode = 0") - writer.write("private val regionOverride = System.getenv(\"AWS_SMOKE_TEST_REGION\")") - writer.write("private val skipTags = System.getenv(\"AWS_SMOKE_TEST_SKIP_TAGS\")?.let { it.split(\",\").map { it.trim() }.toSet() }") - writer.emptyLine() - writer.withBlock("public suspend fun main() {", "}") { - renderFunctionCalls() - write("exitProcess(exitCode)") + writer.declareSection(SmokeTestsRunner) { + writer.write("import kotlin.system.exitProcess") + writer.emptyLine() + writer.write("private var exitCode = 0") + writer.write("private val regionOverride = System.getenv(#S)", "AWS_SMOKE_TEST_REGION") + writer.write("private val skipTags = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() } ?: emptySet()", "AWS_SMOKE_TEST_SKIP_TAGS") + writer.write("private val serviceFilter = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() }", "AWS_SMOKE_TEST_SERVICE_IDS") + writer.emptyLine() + writer.withBlock("public suspend fun main() {", "}") { + renderFunctionCalls() + write("exitProcess(exitCode)") + } + writer.emptyLine() + renderFunctions() } - writer.emptyLine() - renderFunctions() } private fun renderFunctionCalls() { operations.forEach { operation -> operation.getTrait()?.testCases?.forEach { testCase -> - writer.write("${testCase.id.toCamelCase()}()") + writer.write("${testCase.functionName}()") } } } @@ -58,9 +67,19 @@ class SmokeTestsRunnerGenerator( } private fun renderFunction(operation: OperationShape, testCase: SmokeTestCase) { - writer.withBlock("private suspend fun ${testCase.id.toCamelCase()}() {", "}") { + writer.withBlock("private suspend fun ${testCase.functionName}() {", "}") { write("val tags = setOf(${testCase.tags.joinToString(",") { it.dq()} })") - write("if (skipTags != null && tags.any { it in skipTags }) return") + writer.withBlock("if ((serviceFilter != null && #S !in serviceFilter) || tags.any { it in skipTags }) {", "}", sdkId) { + printTestResult( + sdkId.filter { !it.isWhitespace() }, + testCase.id, + testCase.expectation.isFailure, + writer, + "ok", + "# skip", + ) + writer.write("return") + } emptyLine() withInlineBlock("try {", "} ") { renderClient(testCase) @@ -77,33 +96,17 @@ class SmokeTestsRunnerGenerator( if (testCase.vendorParams.isPresent) { testCase.vendorParams.get().members.forEach { vendorParam -> if (vendorParam.key.value == "region") { - write("region = regionOverride ?: #L", vendorParam.value.render()) + write("region = regionOverride ?: #L", vendorParam.value.format()) } else { - write("#L = #L", vendorParam.key.value.toCamelCase(), vendorParam.value.render()) + write("#L = #L", vendorParam.key.value.toCamelCase(), vendorParam.value.format()) } } } else { write("region = regionOverride") } val expectingSpecificError = testCase.expectation.failure.getOrNull()?.errorId?.getOrNull() != null - write("interceptors.add(#T($expectingSpecificError))", RuntimeTypes.HttpClient.Interceptors.SmokeTestsInterceptor) - } - checkVendorParamsAreCorrect(testCase) - } - - /** - * Smithy IDL doesn't check that vendor params are found in vendor params shape so we have to check here. - */ - private fun checkVendorParamsAreCorrect(testCase: SmokeTestCase) { - if (testCase.vendorParamsShape.isPresent && testCase.vendorParams.isPresent) { - val vendorParamsShape = model.getShape(testCase.vendorParamsShape.get()).get() - val validVendorParams = vendorParamsShape.members().map { it.memberName } - val vendorParams = testCase.vendorParams.get().members.map { it.key.value } - - vendorParams.forEach { vendorParam -> - check(validVendorParams.contains(vendorParam)) { - "Smithy smoke test \"${testCase.id}\" contains invalid vendor param \"$vendorParam\", it was not found in vendor params shape \"${testCase.vendorParamsShape}\"" - } + if (!expectingSpecificError) { + write("interceptors.add(#T())", RuntimeTypes.HttpClient.Interceptors.SmokeTestsInterceptor) } } } @@ -111,12 +114,11 @@ class SmokeTestsRunnerGenerator( private fun renderOperation(operation: OperationShape, testCase: SmokeTestCase) { val operationSymbol = symbolProvider.toSymbol(model.getShape(operation.input.get()).get()) - writer.addImport(operationSymbol) writer.withBlock(".use { client ->", "}") { withBlock("client.#L(", ")", operation.defaultName()) { - withBlock("#L {", "}", operationSymbol.name) { + withBlock("#L {", "}", operationSymbol) { testCase.params.get().members.forEach { member -> - write("#L = #L", member.key.value.toCamelCase(), member.value.render()) + write("#L = #L", member.key.value.toCamelCase(), member.value.format()) } } } @@ -146,17 +148,11 @@ class SmokeTestsRunnerGenerator( /** * Tries to get the specific exception required in the failure criterion of a test. * If no specific exception is required we default to the generic smoke tests failure exception. - * - * Some smoke tests model exceptions not found in the model, in that case we default to the generic smoke tests - * failure exception. */ - private fun getFailureCriterion(testCase: SmokeTestCase): Symbol = testCase.expectation.failure.getOrNull()?.errorId?.let { - try { - symbolProvider.toSymbol(model.getShape(it.get()).get()) - } catch (e: Exception) { - RuntimeTypes.HttpClient.Interceptors.SmokeTestsFailureException - } - } ?: RuntimeTypes.HttpClient.Interceptors.SmokeTestsFailureException + private fun getFailureCriterion(testCase: SmokeTestCase): Symbol = + testCase.expectation.failure.getOrNull()?.errorId?.getOrNull()?.let { + symbolProvider.toSymbol(model.getShape(it).get()) + } ?: RuntimeTypes.HttpClient.Interceptors.SmokeTestsFailureException /** * Renders print statement for smoke test result in accordance to design doc & test anything protocol (TAP) @@ -166,9 +162,18 @@ class SmokeTestsRunnerGenerator( testCase: String, errorExpected: Boolean, writer: KotlinWriter, + statusOverride: String? = null, + directive: String? = "", ) { val expectation = if (errorExpected) "error expected from service" else "no error expected from service" - val testResult = "\$status $service $testCase - $expectation" + val status = statusOverride ?: "\$status" + val testResult = "$status $service $testCase - $expectation $directive" writer.write("println(#S)", testResult) } } + +/** + * Derives a function name for a [SmokeTestCase] + */ +private val SmokeTestCase.functionName: String + get() = this.id.toCamelCase() diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt index 71a75af92..692de8b65 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt @@ -10,20 +10,20 @@ import software.amazon.smithy.model.node.ObjectNode import software.amazon.smithy.model.node.StringNode /** - * Renders a [Node] into String format for codegen. + * Formats a [Node] into a String for codegen. */ -fun Node.render(): String = when (this) { +fun Node.format(): String = when (this) { is NullNode -> "null" is StringNode -> value.dq() is BooleanNode -> value.toString() is NumberNode -> value.toString() is ArrayNode -> elements.joinToString(",", "listOf(", ")") { element -> - element.render() + element.format() } is ObjectNode -> buildString { append("mapOf(") stringMap.forEach { (key, value) -> - append("\t${key.dq()} to ${value.render()}") + append("\t${key.dq()} to ${value.format()}") } append(")") } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt index 066f1b764..13b69a9d8 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt @@ -1,35 +1,13 @@ package software.amazon.smithy.kotlin.codegen.rendering.smoketests -import software.amazon.smithy.kotlin.codegen.model.hasTrait import software.amazon.smithy.kotlin.codegen.test.* -import software.amazon.smithy.kotlin.codegen.utils.operations import software.amazon.smithy.model.Model -import software.amazon.smithy.smoketests.traits.SmokeTestsTrait import kotlin.test.Test class SmokeTestsRunnerGeneratorTest { - private val moneySign = "$" - - private fun codegen(model: Model): String { - val testCtx = model.newTestContext() - val codegenCtx = testCtx.toCodegenContext() - val writer = testCtx.newWriter() - SmokeTestsRunnerGenerator( - writer, - codegenCtx.symbolProvider.toSymbol(codegenCtx.model.expectShape(codegenCtx.settings.service)), - codegenCtx.model.operations(codegenCtx.settings.service).filter { it.hasTrait() }, - codegenCtx.model, - codegenCtx.symbolProvider, - codegenCtx.settings.sdkId, - ).render() - return writer.toString() - } - - @Test - fun codegenTest() { - val model = - """ - ${moneySign}version: "2" + val model = + """ + ${'$'}version: "2" namespace com.test use smithy.test#smokeTests @@ -89,16 +67,22 @@ class SmokeTestsRunnerGeneratorTest { } """.toSmithyModel() - val generatedCode = codegen(model) + private val generatedCode = generateSmokeTests(model) + @Test + fun variablesTest() { generatedCode.shouldContainOnlyOnceWithDiff( """ private var exitCode = 0 private val regionOverride = System.getenv("AWS_SMOKE_TEST_REGION") - private val skipTags = System.getenv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } + private val skipTags = System.getenv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } ?: emptySet() + private val serviceFilter = System.getenv("AWS_SMOKE_TEST_SERVICE_IDS")?.let { it.split(",").map { it.trim() }.toSet() } """.trimIndent(), ) + } + @Test + fun mainTest() { generatedCode.shouldContainOnlyOnceWithDiff( """ public suspend fun main() { @@ -109,21 +93,27 @@ class SmokeTestsRunnerGeneratorTest { } """.trimIndent(), ) + } + @Test + fun successTest() { generatedCode.shouldContainOnlyOnceWithDiff( """ private suspend fun successTest() { val tags = setOf("success") - if (skipTags != null && tags.any { it in skipTags }) return + if ((serviceFilter != null && "Test" !in serviceFilter) || tags.any { it in skipTags }) { + println("ok Test SuccessTest - no error expected from service # skip") + return + } try { com.test.TestClient { region = regionOverride ?: "eu-central-1" - interceptors.add(SmokeTestsInterceptor(false)) + interceptors.add(SmokeTestsInterceptor()) }.use { client -> client.testOperation( - TestOperationRequest { + com.test.model.TestOperationRequest { bar = "2" } ) @@ -132,69 +122,91 @@ class SmokeTestsRunnerGeneratorTest { } catch (e: Exception) { val success = e is aws.smithy.kotlin.runtime.http.interceptors.SmokeTestsSuccessException val status = if (success) "ok" else "not ok" - println("${moneySign}status Test SuccessTest - no error expected from service") + println("${'$'}status Test SuccessTest - no error expected from service ") if (!success) exitCode = 1 } } """.trimIndent(), ) + } + @Test + fun invalidMessageErrorTest() { generatedCode.shouldContainOnlyOnceWithDiff( """ private suspend fun invalidMessageErrorTest() { val tags = setOf() - if (skipTags != null && tags.any { it in skipTags }) return - + if ((serviceFilter != null && "Test" !in serviceFilter) || tags.any { it in skipTags }) { + println("ok Test InvalidMessageErrorTest - error expected from service # skip") + return + } + try { com.test.TestClient { region = regionOverride - interceptors.add(SmokeTestsInterceptor(true)) - + }.use { client -> client.testOperation( - TestOperationRequest { + com.test.model.TestOperationRequest { bar = "föö" } ) } - + } catch (e: Exception) { val success = e is com.test.model.InvalidMessageError val status = if (success) "ok" else "not ok" - println("${moneySign}status Test InvalidMessageErrorTest - error expected from service") + println("${'$'}status Test InvalidMessageErrorTest - error expected from service ") if (!success) exitCode = 1 } } """.trimIndent(), ) + } + @Test + fun failureTest() { generatedCode.shouldContainOnlyOnceWithDiff( """ private suspend fun failureTest() { val tags = setOf() - if (skipTags != null && tags.any { it in skipTags }) return - + if ((serviceFilter != null && "Test" !in serviceFilter) || tags.any { it in skipTags }) { + println("ok Test FailureTest - error expected from service # skip") + return + } + try { com.test.TestClient { region = regionOverride - interceptors.add(SmokeTestsInterceptor(false)) - + interceptors.add(SmokeTestsInterceptor()) + }.use { client -> client.testOperation( - TestOperationRequest { + com.test.model.TestOperationRequest { bar = "föö" } ) } - + } catch (e: Exception) { val success = e is aws.smithy.kotlin.runtime.http.interceptors.SmokeTestsFailureException val status = if (success) "ok" else "not ok" - println("${moneySign}status Test FailureTest - error expected from service") + println("${'$'}status Test FailureTest - error expected from service ") if (!success) exitCode = 1 } } """.trimIndent(), ) } + + private fun generateSmokeTests(model: Model): String { + val testCtx = model.newTestContext() + val codegenCtx = testCtx.toCodegenContext() + val writer = testCtx.newWriter() + SmokeTestsRunnerGenerator( + writer, + codegenCtx, + ).render() + return writer.toString() + } } diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 7b033f26c..0de85deed 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -429,7 +429,7 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsFailure } public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { - public fun (Z)V + public fun ()V public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun modifyBeforeCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun modifyBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt index d6c898e1d..46c503d8f 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt @@ -12,11 +12,8 @@ import aws.smithy.kotlin.runtime.http.response.HttpResponse * response’s HTTP status code UNLESS we're expecting a specific error. */ @InternalApi -public class SmokeTestsInterceptor( - private val expectingSpecificError: Boolean, -) : HttpInterceptor { +public class SmokeTestsInterceptor : HttpInterceptor { override fun readBeforeDeserialization(context: ProtocolResponseInterceptorContext) { - if (expectingSpecificError) return val status = context.protocolResponse.status.value when (status) { in 400..599 -> throw SmokeTestsFailureException() From 4e58bbd672dff777e19b31fe9d0559182c180d03 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Fri, 13 Sep 2024 14:05:36 -0400 Subject: [PATCH 06/15] Added E2E tests --- .../kotlin/codegen/core/KotlinDependency.kt | 1 + .../kotlin/codegen/core/RuntimeTypes.kt | 5 ++- .../model/traits/FailedResponseTrait.kt | 14 ++++++ .../model/traits/SuccessResponseTrait.kt | 14 ++++++ .../smoketests/SmokeTestsRunnerGenerator.kt | 45 +++++++++++++++++-- .../protocol/http-client/api/http-client.api | 4 -- .../interceptors/SmokeTestsInterceptor.kt | 5 +-- 7 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt create mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SuccessResponseTrait.kt diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDependency.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDependency.kt index ec4c117aa..524e1924a 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDependency.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDependency.kt @@ -104,6 +104,7 @@ data class KotlinDependency( val CORE = KotlinDependency(GradleConfiguration.Api, RUNTIME_ROOT_NS, RUNTIME_GROUP, "runtime-core", RUNTIME_VERSION) val HTTP = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.http", RUNTIME_GROUP, "http", RUNTIME_VERSION) val HTTP_CLIENT = KotlinDependency(GradleConfiguration.Api, "$RUNTIME_ROOT_NS.http", RUNTIME_GROUP, "http-client", RUNTIME_VERSION) + val HTTP_TEST = KotlinDependency(GradleConfiguration.Api, "$RUNTIME_ROOT_NS.httptest", RUNTIME_GROUP, "http-test", RUNTIME_VERSION) val SERDE = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.serde", RUNTIME_GROUP, "serde", RUNTIME_VERSION) val SERDE_JSON = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.serde.json", RUNTIME_GROUP, "serde-json", RUNTIME_VERSION) val SERDE_XML = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.serde.xml", RUNTIME_GROUP, "serde-xml", RUNTIME_VERSION) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 42be17ec3..12492e581 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -89,10 +89,13 @@ object RuntimeTypes { val SmokeTestsInterceptor = symbol("SmokeTestsInterceptor") val SmokeTestsFailureException = symbol("SmokeTestsFailureException") val SmokeTestsSuccessException = symbol("SmokeTestsSuccessException") - val SmokeTestsUnexpectedException = symbol("SmokeTestsUnexpectedException") } } + object HttpTest : RuntimeTypePackage(KotlinDependency.HTTP_TEST) { + val TestEngine = symbol("TestEngine") + } + object Core : RuntimeTypePackage(KotlinDependency.CORE) { val Clock = symbol("Clock", "time") val ExecutionContext = symbol("ExecutionContext", "operation") diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt new file mode 100644 index 000000000..d75e673f1 --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt @@ -0,0 +1,14 @@ +package software.amazon.smithy.kotlin.codegen.model.traits + +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. + */ +class FailedResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) { + companion object { + val ID: ShapeId = ShapeId.from("com.test#failedResponseTrait") + } +} diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SuccessResponseTrait.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SuccessResponseTrait.kt new file mode 100644 index 000000000..059c1b01e --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SuccessResponseTrait.kt @@ -0,0 +1,14 @@ +package software.amazon.smithy.kotlin.codegen.model.traits + +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 success response. + */ +class SuccessResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) { + companion object { + val ID: ShapeId = ShapeId.from("com.test#successResponseTrait") + } +} diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index 980326eb2..4883febc8 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -3,42 +3,58 @@ package software.amazon.smithy.kotlin.codegen.rendering.smoketests import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.kotlin.codegen.core.* import software.amazon.smithy.kotlin.codegen.integration.SectionId +import software.amazon.smithy.kotlin.codegen.model.expectShape import software.amazon.smithy.kotlin.codegen.model.getTrait import software.amazon.smithy.kotlin.codegen.model.hasTrait +import software.amazon.smithy.kotlin.codegen.model.traits.FailedResponseTrait +import software.amazon.smithy.kotlin.codegen.model.traits.SuccessResponseTrait import software.amazon.smithy.kotlin.codegen.rendering.util.format import software.amazon.smithy.kotlin.codegen.utils.dq -import software.amazon.smithy.kotlin.codegen.utils.getOrNull import software.amazon.smithy.kotlin.codegen.utils.operations import software.amazon.smithy.kotlin.codegen.utils.toCamelCase import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.smoketests.traits.SmokeTestCase import software.amazon.smithy.smoketests.traits.SmokeTestsTrait import kotlin.jvm.optionals.getOrNull object SmokeTestsRunner : SectionId +// Env var constants +const val SKIP_TAGS = "AWS_SMOKE_TEST_SKIP_TAGS" +const val SERVICE_FILTER = "AWS_SMOKE_TEST_SERVICE_IDS" + /** * Renders smoke tests runner for a service */ class SmokeTestsRunnerGenerator( private val writer: KotlinWriter, ctx: CodegenContext, -// private val testing: Boolean = false, TODO: Implement tests ) { + // Generator config private val model = ctx.model private val sdkId = ctx.settings.sdkId private val symbolProvider = ctx.symbolProvider private val service = symbolProvider.toSymbol(model.expectShape(ctx.settings.service)) private val operations = ctx.model.operations(ctx.settings.service).filter { it.hasTrait() } + // Test config + private val hasSuccessResponseTrait = ctx.model.expectShape(ctx.settings.service).hasTrait(SuccessResponseTrait.ID) + private val hasFailedResponseTrait = ctx.model.expectShape(ctx.settings.service).hasTrait(FailedResponseTrait.ID) + init { + check(!(hasSuccessResponseTrait && hasFailedResponseTrait)) { + "A service can't have both the success response trait and the failed response trait." + } + } + internal fun render() { writer.declareSection(SmokeTestsRunner) { writer.write("import kotlin.system.exitProcess") writer.emptyLine() writer.write("private var exitCode = 0") writer.write("private val regionOverride = System.getenv(#S)", "AWS_SMOKE_TEST_REGION") - writer.write("private val skipTags = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() } ?: emptySet()", "AWS_SMOKE_TEST_SKIP_TAGS") - writer.write("private val serviceFilter = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() }", "AWS_SMOKE_TEST_SERVICE_IDS") + writer.write("private val skipTags = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() } ?: emptySet()", SKIP_TAGS) + writer.write("private val serviceFilter = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() }", SERVICE_FILTER) writer.emptyLine() writer.withBlock("public suspend fun main() {", "}") { renderFunctionCalls() @@ -93,6 +109,7 @@ class SmokeTestsRunnerGenerator( private fun renderClient(testCase: SmokeTestCase) { writer.withInlineBlock("#L {", "}", service) { + // Client config if (testCase.vendorParams.isPresent) { testCase.vendorParams.get().members.forEach { vendorParam -> if (vendorParam.key.value == "region") { @@ -108,6 +125,26 @@ class SmokeTestsRunnerGenerator( if (!expectingSpecificError) { write("interceptors.add(#T())", RuntimeTypes.HttpClient.Interceptors.SmokeTestsInterceptor) } + + // Test config + if (hasSuccessResponseTrait) { + write("httpClient = #T()", RuntimeTypes.HttpTest.TestEngine) + } + if (hasFailedResponseTrait) { + 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) + } + } + } } } diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 0de85deed..c15a33b0b 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -455,10 +455,6 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsSuccess public fun ()V } -public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsUnexpectedException : java/lang/Exception { - public fun ()V -} - public final class aws/smithy/kotlin/runtime/http/middleware/DefaultValidateResponse : aws/smithy/kotlin/runtime/http/operation/ReceiveMiddleware { public fun ()V public fun handle (Laws/smithy/kotlin/runtime/http/operation/OperationRequest;Laws/smithy/kotlin/runtime/io/Handler;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt index 46c503d8f..63848ffe2 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt @@ -9,7 +9,7 @@ import aws.smithy.kotlin.runtime.http.response.HttpResponse * Interceptor for smoke test runner clients. * * A passing test is not predicated on an SDK being able to parse the server response received, it’s based on the - * response’s HTTP status code UNLESS we're expecting a specific error. + * response’s HTTP status code. */ @InternalApi public class SmokeTestsInterceptor : HttpInterceptor { @@ -26,5 +26,4 @@ public class SmokeTestsInterceptor : HttpInterceptor { @InternalApi public class SmokeTestsFailureException : Exception() @InternalApi public class SmokeTestsSuccessException : Exception() - -@InternalApi public class SmokeTestsUnexpectedException : Exception() +private class SmokeTestsUnexpectedException : Exception() From 39f66de308b982ed55fc57b8384156d5e2fc13f0 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Tue, 17 Sep 2024 12:05:57 -0400 Subject: [PATCH 07/15] Move custom traits to shared file, changed traits namespace, misc pr feedback --- .../codegen/model/traits/FailedResponseTrait.kt | 14 -------------- ...{SuccessResponseTrait.kt => SmokeTestTraits.kt} | 11 ++++++++++- .../smoketests/SmokeTestsRunnerGenerator.kt | 4 ++-- 3 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt rename codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/{SuccessResponseTrait.kt => SmokeTestTraits.kt} (53%) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt deleted file mode 100644 index d75e673f1..000000000 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/FailedResponseTrait.kt +++ /dev/null @@ -1,14 +0,0 @@ -package software.amazon.smithy.kotlin.codegen.model.traits - -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. - */ -class FailedResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) { - companion object { - val ID: ShapeId = ShapeId.from("com.test#failedResponseTrait") - } -} diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SuccessResponseTrait.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SmokeTestTraits.kt similarity index 53% rename from codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SuccessResponseTrait.kt rename to codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SmokeTestTraits.kt index 059c1b01e..0186362da 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SuccessResponseTrait.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SmokeTestTraits.kt @@ -4,11 +4,20 @@ 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. + */ +class FailedResponseTrait(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. */ class SuccessResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) { companion object { - val ID: ShapeId = ShapeId.from("com.test#successResponseTrait") + val ID: ShapeId = ShapeId.from("smithy.kotlin.traits#successResponseTrait") } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index 4883febc8..6acceb8b2 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -39,8 +39,8 @@ class SmokeTestsRunnerGenerator( private val operations = ctx.model.operations(ctx.settings.service).filter { it.hasTrait() } // Test config - private val hasSuccessResponseTrait = ctx.model.expectShape(ctx.settings.service).hasTrait(SuccessResponseTrait.ID) - private val hasFailedResponseTrait = ctx.model.expectShape(ctx.settings.service).hasTrait(FailedResponseTrait.ID) + private val hasSuccessResponseTrait = model.expectShape(ctx.settings.service).hasTrait(SuccessResponseTrait.ID) + private val hasFailedResponseTrait = model.expectShape(ctx.settings.service).hasTrait(FailedResponseTrait.ID) init { check(!(hasSuccessResponseTrait && hasFailedResponseTrait)) { "A service can't have both the success response trait and the failed response trait." From faa8652314a6f9dfd581ee569f19250e5e3db33e Mon Sep 17 00:00:00 2001 From: 0marperez Date: Fri, 20 Sep 2024 12:43:44 -0400 Subject: [PATCH 08/15] downstream testing: build before running jvm tests --- .github/workflows/continuous-integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 9b0dc1c88..86e22fb8c 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -131,5 +131,6 @@ jobs: sed -i "s/smithy-kotlin-runtime-version = .*$/smithy-kotlin-runtime-version = \"$SMITHY_KOTLIN_RUNTIME_VERSION\"/" gradle/libs.versions.toml sed -i "s/smithy-kotlin-codegen-version = .*$/smithy-kotlin-codegen-version = \"$SMITHY_KOTLIN_CODEGEN_VERSION\"/" gradle/libs.versions.toml ./gradlew --parallel publishToMavenLocal + ./gradlew build ./gradlew test jvmTest ./gradlew testAllProtocols \ No newline at end of file From 524c8cae019627fceabd4b16ff8229b406e73aa7 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Mon, 23 Sep 2024 10:34:35 -0400 Subject: [PATCH 09/15] remove redundant build from downstream ci tests --- .github/workflows/continuous-integration.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 86e22fb8c..9b0dc1c88 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -131,6 +131,5 @@ jobs: sed -i "s/smithy-kotlin-runtime-version = .*$/smithy-kotlin-runtime-version = \"$SMITHY_KOTLIN_RUNTIME_VERSION\"/" gradle/libs.versions.toml sed -i "s/smithy-kotlin-codegen-version = .*$/smithy-kotlin-codegen-version = \"$SMITHY_KOTLIN_CODEGEN_VERSION\"/" gradle/libs.versions.toml ./gradlew --parallel publishToMavenLocal - ./gradlew build ./gradlew test jvmTest ./gradlew testAllProtocols \ No newline at end of file From bcafd534be746cc846a34a66a2f726a870099083 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Mon, 23 Sep 2024 17:36:31 -0400 Subject: [PATCH 10/15] easy to fix PR feedback and decoupling SmokeTestRunnerGenerator from SDK --- .../kotlin/codegen/core/KotlinWriter.kt | 2 - .../codegen/rendering/GradleGenerator.kt | 2 - .../smoketests/SmokeTestsIntegration.kt | 4 +- .../smoketests/SmokeTestsRunnerGenerator.kt | 85 ++++++------------- .../kotlin/codegen/rendering/util/Node.kt | 8 +- .../smithy/kotlin/codegen/utils/Model.kt | 4 +- .../SmokeTestsRunnerGeneratorTest.kt | 17 ++-- 7 files changed, 39 insertions(+), 83 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt index b0a4e46bc..6fb4c81cf 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt @@ -198,8 +198,6 @@ class KotlinWriter( ) } - fun emptyLine(): KotlinWriter = this.write("") - /** * Clean/escape any content from the doc that would invalidate the Kotlin output. */ diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt index 9be396903..dcaacf2f3 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/GradleGenerator.kt @@ -250,6 +250,4 @@ class GradleWriter(parent: GradleWriter? = null) : AbstractCodeWriter() } + model.topDownOperations(settings.service).any { it.hasTrait() } override fun writeAdditionalFiles(ctx: CodegenContext, delegator: KotlinDelegator) = delegator.useFileWriter( diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index 6acceb8b2..67d557e81 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -3,24 +3,23 @@ package software.amazon.smithy.kotlin.codegen.rendering.smoketests import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.kotlin.codegen.core.* import software.amazon.smithy.kotlin.codegen.integration.SectionId -import software.amazon.smithy.kotlin.codegen.model.expectShape import software.amazon.smithy.kotlin.codegen.model.getTrait import software.amazon.smithy.kotlin.codegen.model.hasTrait -import software.amazon.smithy.kotlin.codegen.model.traits.FailedResponseTrait -import software.amazon.smithy.kotlin.codegen.model.traits.SuccessResponseTrait import software.amazon.smithy.kotlin.codegen.rendering.util.format import software.amazon.smithy.kotlin.codegen.utils.dq -import software.amazon.smithy.kotlin.codegen.utils.operations +import software.amazon.smithy.kotlin.codegen.utils.topDownOperations import software.amazon.smithy.kotlin.codegen.utils.toCamelCase import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.smoketests.traits.SmokeTestCase import software.amazon.smithy.smoketests.traits.SmokeTestsTrait import kotlin.jvm.optionals.getOrNull object SmokeTestsRunner : SectionId +object SmokeTestAdditionalEnvVars : SectionId +object SmokeTestDefaultConfig: SectionId +object SmokeTestRegionDefault: SectionId +object SmokeTestHttpEngineOverride: SectionId -// Env var constants const val SKIP_TAGS = "AWS_SMOKE_TEST_SKIP_TAGS" const val SERVICE_FILTER = "AWS_SMOKE_TEST_SERVICE_IDS" @@ -31,36 +30,26 @@ class SmokeTestsRunnerGenerator( private val writer: KotlinWriter, ctx: CodegenContext, ) { - // Generator config private val model = ctx.model private val sdkId = ctx.settings.sdkId private val symbolProvider = ctx.symbolProvider private val service = symbolProvider.toSymbol(model.expectShape(ctx.settings.service)) - private val operations = ctx.model.operations(ctx.settings.service).filter { it.hasTrait() } - - // Test config - private val hasSuccessResponseTrait = model.expectShape(ctx.settings.service).hasTrait(SuccessResponseTrait.ID) - private val hasFailedResponseTrait = model.expectShape(ctx.settings.service).hasTrait(FailedResponseTrait.ID) - init { - check(!(hasSuccessResponseTrait && hasFailedResponseTrait)) { - "A service can't have both the success response trait and the failed response trait." - } - } + private val operations = ctx.model.topDownOperations(ctx.settings.service).filter { it.hasTrait() } internal fun render() { writer.declareSection(SmokeTestsRunner) { - writer.write("import kotlin.system.exitProcess") - writer.emptyLine() - writer.write("private var exitCode = 0") - writer.write("private val regionOverride = System.getenv(#S)", "AWS_SMOKE_TEST_REGION") - writer.write("private val skipTags = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() } ?: emptySet()", SKIP_TAGS) - writer.write("private val serviceFilter = System.getenv(#S)?.let { it.split(\",\").map { it.trim() }.toSet() }", SERVICE_FILTER) - writer.emptyLine() - writer.withBlock("public suspend fun main() {", "}") { + write("import kotlin.system.exitProcess") + write("") + write("private var exitCode = 0") + write("private val skipTags = System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", SKIP_TAGS, ",") + write("private val serviceFilter = System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", SERVICE_FILTER, ",") + declareSection(SmokeTestAdditionalEnvVars) + write("") + withBlock("public suspend fun main() {", "}") { renderFunctionCalls() write("exitProcess(exitCode)") } - writer.emptyLine() + write("") renderFunctions() } } @@ -77,7 +66,7 @@ class SmokeTestsRunnerGenerator( operations.forEach { operation -> operation.getTrait()?.testCases?.forEach { testCase -> renderFunction(operation, testCase) - writer.emptyLine() + writer.write("") } } } @@ -85,7 +74,7 @@ class SmokeTestsRunnerGenerator( private fun renderFunction(operation: OperationShape, testCase: SmokeTestCase) { writer.withBlock("private suspend fun ${testCase.functionName}() {", "}") { write("val tags = setOf(${testCase.tags.joinToString(",") { it.dq()} })") - writer.withBlock("if ((serviceFilter != null && #S !in serviceFilter) || tags.any { it in skipTags }) {", "}", sdkId) { + writer.withBlock("if ((serviceFilter.isNotEmpty() && #S !in serviceFilter) || tags.any { it in skipTags }) {", "}", sdkId) { printTestResult( sdkId.filter { !it.isWhitespace() }, testCase.id, @@ -96,7 +85,7 @@ class SmokeTestsRunnerGenerator( ) writer.write("return") } - emptyLine() + write("") withInlineBlock("try {", "} ") { renderClient(testCase) renderOperation(operation, testCase) @@ -109,42 +98,24 @@ class SmokeTestsRunnerGenerator( private fun renderClient(testCase: SmokeTestCase) { writer.withInlineBlock("#L {", "}", service) { - // Client config if (testCase.vendorParams.isPresent) { testCase.vendorParams.get().members.forEach { vendorParam -> if (vendorParam.key.value == "region") { - write("region = regionOverride ?: #L", vendorParam.value.format()) + writeInline("#L = ", vendorParam.key.value.toCamelCase()) + declareSection(SmokeTestRegionDefault) + write("#L", vendorParam.value.format()) } else { write("#L = #L", vendorParam.key.value.toCamelCase(), vendorParam.value.format()) } } } else { - write("region = regionOverride") + declareSection(SmokeTestDefaultConfig) } val expectingSpecificError = testCase.expectation.failure.getOrNull()?.errorId?.getOrNull() != null if (!expectingSpecificError) { write("interceptors.add(#T())", RuntimeTypes.HttpClient.Interceptors.SmokeTestsInterceptor) } - - // Test config - if (hasSuccessResponseTrait) { - write("httpClient = #T()", RuntimeTypes.HttpTest.TestEngine) - } - if (hasFailedResponseTrait) { - 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) - } - } - } + declareSection(SmokeTestHttpEngineOverride) } } @@ -163,16 +134,14 @@ class SmokeTestsRunnerGenerator( } private fun renderCatchBlock(testCase: SmokeTestCase) { - val successCriterion = RuntimeTypes.HttpClient.Interceptors.SmokeTestsSuccessException - val failureCriterion = getFailureCriterion(testCase) val expected = if (testCase.expectation.isFailure) { - failureCriterion + getFailureCriterion(testCase) } else { - successCriterion + RuntimeTypes.HttpClient.Interceptors.SmokeTestsSuccessException } - writer.write("val success = e is #L", expected) - writer.write("val status = if (success) \"ok\" else \"not ok\"") + writer.write("val success = e is #T", expected) + writer.write("val status = if (success) #S else #S", "ok", "not ok") printTestResult( sdkId.filter { !it.isWhitespace() }, testCase.id, diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt index 692de8b65..1e7d3103c 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt @@ -20,12 +20,8 @@ fun Node.format(): String = when (this) { is ArrayNode -> elements.joinToString(",", "listOf(", ")") { element -> element.format() } - is ObjectNode -> buildString { - append("mapOf(") - stringMap.forEach { (key, value) -> - append("\t${key.dq()} to ${value.format()}") - } - append(")") + is ObjectNode -> stringMap.entries.joinToString(", ", "mapOf(", ")") { (key, value) -> + "${key.dq()} to ${value.format()}" } else -> throw Exception("Unexpected node type: $this") } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt index 613b9aebb..27b1ced80 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt @@ -4,13 +4,11 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.TopDownIndex import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ShapeId -import software.amazon.smithy.utils.SmithyInternalApi /** * Syntactic sugar for getting a services operations */ -@SmithyInternalApi -fun Model.operations(service: ShapeId): Set { +fun Model.topDownOperations(service: ShapeId): Set { val topDownIndex = TopDownIndex.of(this) return topDownIndex.getContainedOperations(service) } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt index 13b69a9d8..62e1f8b0a 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt @@ -74,7 +74,6 @@ class SmokeTestsRunnerGeneratorTest { generatedCode.shouldContainOnlyOnceWithDiff( """ private var exitCode = 0 - private val regionOverride = System.getenv("AWS_SMOKE_TEST_REGION") private val skipTags = System.getenv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } ?: emptySet() private val serviceFilter = System.getenv("AWS_SMOKE_TEST_SERVICE_IDS")?.let { it.split(",").map { it.trim() }.toSet() } """.trimIndent(), @@ -101,14 +100,14 @@ class SmokeTestsRunnerGeneratorTest { """ private suspend fun successTest() { val tags = setOf("success") - if ((serviceFilter != null && "Test" !in serviceFilter) || tags.any { it in skipTags }) { + if ((serviceFilter.isNotEmpty() && "Test" !in serviceFilter) || tags.any { it in skipTags }) { println("ok Test SuccessTest - no error expected from service # skip") return } try { com.test.TestClient { - region = regionOverride ?: "eu-central-1" + region = "eu-central-1" interceptors.add(SmokeTestsInterceptor()) }.use { client -> @@ -120,7 +119,7 @@ class SmokeTestsRunnerGeneratorTest { } } catch (e: Exception) { - val success = e is aws.smithy.kotlin.runtime.http.interceptors.SmokeTestsSuccessException + val success = e is SmokeTestsSuccessException val status = if (success) "ok" else "not ok" println("${'$'}status Test SuccessTest - no error expected from service ") if (!success) exitCode = 1 @@ -136,14 +135,13 @@ class SmokeTestsRunnerGeneratorTest { """ private suspend fun invalidMessageErrorTest() { val tags = setOf() - if ((serviceFilter != null && "Test" !in serviceFilter) || tags.any { it in skipTags }) { + if ((serviceFilter.isNotEmpty() && "Test" !in serviceFilter) || tags.any { it in skipTags }) { println("ok Test InvalidMessageErrorTest - error expected from service # skip") return } try { com.test.TestClient { - region = regionOverride }.use { client -> client.testOperation( @@ -154,7 +152,7 @@ class SmokeTestsRunnerGeneratorTest { } } catch (e: Exception) { - val success = e is com.test.model.InvalidMessageError + val success = e is InvalidMessageError val status = if (success) "ok" else "not ok" println("${'$'}status Test InvalidMessageErrorTest - error expected from service ") if (!success) exitCode = 1 @@ -170,14 +168,13 @@ class SmokeTestsRunnerGeneratorTest { """ private suspend fun failureTest() { val tags = setOf() - if ((serviceFilter != null && "Test" !in serviceFilter) || tags.any { it in skipTags }) { + if ((serviceFilter.isNotEmpty() && "Test" !in serviceFilter) || tags.any { it in skipTags }) { println("ok Test FailureTest - error expected from service # skip") return } try { com.test.TestClient { - region = regionOverride interceptors.add(SmokeTestsInterceptor()) }.use { client -> @@ -189,7 +186,7 @@ class SmokeTestsRunnerGeneratorTest { } } catch (e: Exception) { - val success = e is aws.smithy.kotlin.runtime.http.interceptors.SmokeTestsFailureException + val success = e is SmokeTestsFailureException val status = if (success) "ok" else "not ok" println("${'$'}status Test FailureTest - error expected from service ") if (!success) exitCode = 1 From 915e8454eb484788bb8f2a72b01f6eeb8b751c62 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Mon, 23 Sep 2024 21:12:50 -0400 Subject: [PATCH 11/15] ktlint --- .../rendering/smoketests/SmokeTestsRunnerGenerator.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index 67d557e81..1c80f1f9e 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -7,8 +7,8 @@ import software.amazon.smithy.kotlin.codegen.model.getTrait import software.amazon.smithy.kotlin.codegen.model.hasTrait import software.amazon.smithy.kotlin.codegen.rendering.util.format import software.amazon.smithy.kotlin.codegen.utils.dq -import software.amazon.smithy.kotlin.codegen.utils.topDownOperations import software.amazon.smithy.kotlin.codegen.utils.toCamelCase +import software.amazon.smithy.kotlin.codegen.utils.topDownOperations import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.smoketests.traits.SmokeTestCase import software.amazon.smithy.smoketests.traits.SmokeTestsTrait @@ -16,9 +16,9 @@ import kotlin.jvm.optionals.getOrNull object SmokeTestsRunner : SectionId object SmokeTestAdditionalEnvVars : SectionId -object SmokeTestDefaultConfig: SectionId -object SmokeTestRegionDefault: SectionId -object SmokeTestHttpEngineOverride: SectionId +object SmokeTestDefaultConfig : SectionId +object SmokeTestRegionDefault : SectionId +object SmokeTestHttpEngineOverride : SectionId const val SKIP_TAGS = "AWS_SMOKE_TEST_SKIP_TAGS" const val SERVICE_FILTER = "AWS_SMOKE_TEST_SERVICE_IDS" From 1055f1b58ad0e517f19ee4bc1469edb2a354b787 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Mon, 23 Sep 2024 22:30:26 -0400 Subject: [PATCH 12/15] Run in common --- .../kotlin/codegen/core/KotlinDelegator.kt | 2 +- .../kotlin/codegen/core/RuntimeTypes.kt | 4 ++++ .../codegen/model/traits/SmokeTestTraits.kt | 23 ------------------- .../smoketests/SmokeTestsIntegration.kt | 3 ++- .../smoketests/SmokeTestsRunnerGenerator.kt | 4 +--- runtime/runtime-core/api/runtime-core.api | 4 ++++ .../smoketests/SmokeTestsRuntimeFunctions.kt | 3 +++ .../smoketests/SmokeTestsFunctionsJVM.kt | 5 ++++ .../smoketests/SmokeTestsFunctionsNative.kt | 5 ++++ 9 files changed, 25 insertions(+), 28 deletions(-) delete mode 100644 codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SmokeTestTraits.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsRuntimeFunctions.kt create mode 100644 runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt create mode 100644 runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt index ffe268c75..9ae10da76 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDelegator.kt @@ -15,7 +15,7 @@ import software.amazon.smithy.model.shapes.Shape import java.nio.file.Paths const val DEFAULT_SOURCE_SET_ROOT = "./src/main/kotlin/" -private const val DEFAULT_TEST_SOURCE_SET_ROOT = "./src/test/kotlin/" +const val DEFAULT_TEST_SOURCE_SET_ROOT = "./src/test/kotlin/" /** * Manages writers for Kotlin files. diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 12492e581..172348d4e 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -114,6 +114,10 @@ object RuntimeTypes { val SmithyBusinessMetric = symbol("SmithyBusinessMetric") } + object SmokeTests : RuntimeTypePackage(KotlinDependency.CORE, "smoketests") { + val ExitProcess = symbol("exitProcess") + } + object Collections : RuntimeTypePackage(KotlinDependency.CORE, "collections") { val Attributes = symbol("Attributes") val attributesOf = symbol("attributesOf") diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SmokeTestTraits.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SmokeTestTraits.kt deleted file mode 100644 index 0186362da..000000000 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/model/traits/SmokeTestTraits.kt +++ /dev/null @@ -1,23 +0,0 @@ -package software.amazon.smithy.kotlin.codegen.model.traits - -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. - */ -class FailedResponseTrait(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. - */ -class SuccessResponseTrait(node: ObjectNode) : AnnotationTrait(ID, node) { - companion object { - val ID: ShapeId = ShapeId.from("smithy.kotlin.traits#successResponseTrait") - } -} diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt index b32b56f18..192494e9e 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt @@ -2,6 +2,7 @@ package software.amazon.smithy.kotlin.codegen.rendering.smoketests import software.amazon.smithy.kotlin.codegen.KotlinSettings import software.amazon.smithy.kotlin.codegen.core.CodegenContext +import software.amazon.smithy.kotlin.codegen.core.DEFAULT_TEST_SOURCE_SET_ROOT import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration import software.amazon.smithy.kotlin.codegen.model.hasTrait @@ -20,7 +21,7 @@ class SmokeTestsIntegration : KotlinIntegration { delegator.useFileWriter( "SmokeTests.kt", "${ctx.settings.pkg.name}.smoketests", - "./jvm-src/test/java/", + DEFAULT_TEST_SOURCE_SET_ROOT, ) { writer -> SmokeTestsRunnerGenerator( writer, diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index 1c80f1f9e..fa2122ade 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -38,8 +38,6 @@ class SmokeTestsRunnerGenerator( internal fun render() { writer.declareSection(SmokeTestsRunner) { - write("import kotlin.system.exitProcess") - write("") write("private var exitCode = 0") write("private val skipTags = System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", SKIP_TAGS, ",") write("private val serviceFilter = System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", SERVICE_FILTER, ",") @@ -47,7 +45,7 @@ class SmokeTestsRunnerGenerator( write("") withBlock("public suspend fun main() {", "}") { renderFunctionCalls() - write("exitProcess(exitCode)") + write("#T(exitCode)", RuntimeTypes.Core.SmokeTests.ExitProcess) } write("") renderFunctions() diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index add320979..d1ba553de 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -2044,6 +2044,10 @@ public final class aws/smithy/kotlin/runtime/retries/policy/SuccessAcceptor : aw public final fun getSuccess ()Z } +public final class aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVMKt { + public static final fun exitProcess (I)Ljava/lang/Void; +} + public final class aws/smithy/kotlin/runtime/text/Scanner { public fun (Ljava/lang/String;)V public final fun getText ()Ljava/lang/String; diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsRuntimeFunctions.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsRuntimeFunctions.kt new file mode 100644 index 000000000..f996632e9 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsRuntimeFunctions.kt @@ -0,0 +1,3 @@ +package aws.smithy.kotlin.runtime.smoketests + +public expect fun exitProcess(status: Int): Nothing diff --git a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt new file mode 100644 index 000000000..b11912df7 --- /dev/null +++ b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt @@ -0,0 +1,5 @@ +package aws.smithy.kotlin.runtime.smoketests + +import kotlin.system.exitProcess + +public actual fun exitProcess(status: Int): Nothing = exitProcess(status) diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt new file mode 100644 index 000000000..b11912df7 --- /dev/null +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt @@ -0,0 +1,5 @@ +package aws.smithy.kotlin.runtime.smoketests + +import kotlin.system.exitProcess + +public actual fun exitProcess(status: Int): Nothing = exitProcess(status) From bd0072c21c4b7aacef95f9123108fa8aee0a2abc Mon Sep 17 00:00:00 2001 From: 0marperez Date: Tue, 24 Sep 2024 10:12:16 -0400 Subject: [PATCH 13/15] Move some functions to common KMP code --- .../smithy/kotlin/codegen/core/RuntimeTypes.kt | 4 +++- .../smoketests/SmokeTestsRunnerGenerator.kt | 18 ++++++++++++++---- ...timeFunctions.kt => SmokeTestsFunctions.kt} | 1 + .../smoketests/SmokeTestsFunctionsJVM.kt | 1 + .../smoketests/SmokeTestsFunctionsNative.kt | 1 + 5 files changed, 20 insertions(+), 5 deletions(-) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/{SmokeTestsRuntimeFunctions.kt => SmokeTestsFunctions.kt} (67%) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 172348d4e..1d4d31120 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -115,7 +115,8 @@ object RuntimeTypes { } object SmokeTests : RuntimeTypePackage(KotlinDependency.CORE, "smoketests") { - val ExitProcess = symbol("exitProcess") + val exitProcess = symbol("exitProcess") + val getEnv = symbol("getEnv") } object Collections : RuntimeTypePackage(KotlinDependency.CORE, "collections") { @@ -174,6 +175,7 @@ object RuntimeTypes { val Closeable = symbol("Closeable") val SdkManagedGroup = symbol("SdkManagedGroup") val addIfManaged = symbol("addIfManaged", isExtension = true) + val use = symbol("use") } object Text : RuntimeTypePackage(KotlinDependency.CORE, "text") { diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index fa2122ade..b83d754b4 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -39,13 +39,23 @@ class SmokeTestsRunnerGenerator( internal fun render() { writer.declareSection(SmokeTestsRunner) { write("private var exitCode = 0") - write("private val skipTags = System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", SKIP_TAGS, ",") - write("private val serviceFilter = System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", SERVICE_FILTER, ",") + write( + "private val skipTags = #T(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", + RuntimeTypes.Core.SmokeTests.getEnv, + SKIP_TAGS, + ",", + ) + write( + "private val serviceFilter = #T(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", + RuntimeTypes.Core.SmokeTests.getEnv, + SERVICE_FILTER, + ",", + ) declareSection(SmokeTestAdditionalEnvVars) write("") withBlock("public suspend fun main() {", "}") { renderFunctionCalls() - write("#T(exitCode)", RuntimeTypes.Core.SmokeTests.ExitProcess) + write("#T(exitCode)", RuntimeTypes.Core.SmokeTests.exitProcess) } write("") renderFunctions() @@ -120,7 +130,7 @@ class SmokeTestsRunnerGenerator( private fun renderOperation(operation: OperationShape, testCase: SmokeTestCase) { val operationSymbol = symbolProvider.toSymbol(model.getShape(operation.input.get()).get()) - writer.withBlock(".use { client ->", "}") { + writer.withBlock(".#T { client ->", "}", RuntimeTypes.Core.IO.use) { withBlock("client.#L(", ")", operation.defaultName()) { withBlock("#L {", "}", operationSymbol) { testCase.params.get().members.forEach { member -> diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsRuntimeFunctions.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt similarity index 67% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsRuntimeFunctions.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt index f996632e9..d1b29a607 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsRuntimeFunctions.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt @@ -1,3 +1,4 @@ package aws.smithy.kotlin.runtime.smoketests public expect fun exitProcess(status: Int): Nothing +public expect fun getEnv(name: String): String? diff --git a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt index b11912df7..5ac7d675b 100644 --- a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt +++ b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt @@ -3,3 +3,4 @@ package aws.smithy.kotlin.runtime.smoketests import kotlin.system.exitProcess public actual fun exitProcess(status: Int): Nothing = exitProcess(status) +public actual fun getEnv(name: String): String? = System.getenv(name) diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt index b11912df7..a2b633b79 100644 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt @@ -3,3 +3,4 @@ package aws.smithy.kotlin.runtime.smoketests import kotlin.system.exitProcess public actual fun exitProcess(status: Int): Nothing = exitProcess(status) +public actual fun getEnv(name: String): String? = TODO("Not yet implemented") From c93d8a6839f5c58d90ad170efb7bd7b3fb9bd021 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Tue, 24 Sep 2024 10:59:26 -0400 Subject: [PATCH 14/15] api dump --- .../rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt | 4 ++-- runtime/runtime-core/api/runtime-core.api | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt index 62e1f8b0a..7f9c54a0f 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt @@ -74,8 +74,8 @@ class SmokeTestsRunnerGeneratorTest { generatedCode.shouldContainOnlyOnceWithDiff( """ private var exitCode = 0 - private val skipTags = System.getenv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } ?: emptySet() - private val serviceFilter = System.getenv("AWS_SMOKE_TEST_SERVICE_IDS")?.let { it.split(",").map { it.trim() }.toSet() } + private val skipTags = getEnv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } ?: emptySet() + private val serviceFilter = getEnv("AWS_SMOKE_TEST_SERVICE_IDS")?.let { it.split(",").map { it.trim() }.toSet() } """.trimIndent(), ) } diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index d1ba553de..ed3c8d6b4 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -2046,6 +2046,7 @@ public final class aws/smithy/kotlin/runtime/retries/policy/SuccessAcceptor : aw public final class aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVMKt { public static final fun exitProcess (I)Ljava/lang/Void; + public static final fun getEnv (Ljava/lang/String;)Ljava/lang/String; } public final class aws/smithy/kotlin/runtime/text/Scanner { From 4a10ec75578f5a3b546d9700c06163fc14000178 Mon Sep 17 00:00:00 2001 From: 0marperez Date: Tue, 24 Sep 2024 23:54:58 -0400 Subject: [PATCH 15/15] Use already existing KMP way of getting env vars --- .../amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt | 2 +- .../rendering/smoketests/SmokeTestsRunnerGenerator.kt | 8 ++++---- .../rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt | 4 ++-- runtime/runtime-core/api/runtime-core.api | 1 - .../kotlin/runtime/smoketests/SmokeTestsFunctions.kt | 1 - .../kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt | 1 - .../runtime/smoketests/SmokeTestsFunctionsNative.kt | 1 - 7 files changed, 7 insertions(+), 11 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 1d4d31120..d37e47737 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -116,7 +116,6 @@ object RuntimeTypes { object SmokeTests : RuntimeTypePackage(KotlinDependency.CORE, "smoketests") { val exitProcess = symbol("exitProcess") - val getEnv = symbol("getEnv") } object Collections : RuntimeTypePackage(KotlinDependency.CORE, "collections") { @@ -197,6 +196,7 @@ object RuntimeTypes { val truthiness = symbol("truthiness") val toNumber = symbol("toNumber") val type = symbol("type") + val PlatformProvider = symbol("PlatformProvider") } object Net : RuntimeTypePackage(KotlinDependency.CORE, "net") { diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index b83d754b4..edcf8b7b2 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -40,14 +40,14 @@ class SmokeTestsRunnerGenerator( writer.declareSection(SmokeTestsRunner) { write("private var exitCode = 0") write( - "private val skipTags = #T(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", - RuntimeTypes.Core.SmokeTests.getEnv, + "private val skipTags = #T.System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", + RuntimeTypes.Core.Utils.PlatformProvider, SKIP_TAGS, ",", ) write( - "private val serviceFilter = #T(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", - RuntimeTypes.Core.SmokeTests.getEnv, + "private val serviceFilter = #T.System.getenv(#S)?.let { it.split(#S).map { it.trim() }.toSet() } ?: emptySet()", + RuntimeTypes.Core.Utils.PlatformProvider, SERVICE_FILTER, ",", ) diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt index 7f9c54a0f..ddb5b3c1d 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt @@ -74,8 +74,8 @@ class SmokeTestsRunnerGeneratorTest { generatedCode.shouldContainOnlyOnceWithDiff( """ private var exitCode = 0 - private val skipTags = getEnv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } ?: emptySet() - private val serviceFilter = getEnv("AWS_SMOKE_TEST_SERVICE_IDS")?.let { it.split(",").map { it.trim() }.toSet() } + private val skipTags = PlatformProvider.System.getenv("AWS_SMOKE_TEST_SKIP_TAGS")?.let { it.split(",").map { it.trim() }.toSet() } ?: emptySet() + private val serviceFilter = PlatformProvider.System.getenv("AWS_SMOKE_TEST_SERVICE_IDS")?.let { it.split(",").map { it.trim() }.toSet() } """.trimIndent(), ) } diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index ed3c8d6b4..d1ba553de 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -2046,7 +2046,6 @@ public final class aws/smithy/kotlin/runtime/retries/policy/SuccessAcceptor : aw public final class aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVMKt { public static final fun exitProcess (I)Ljava/lang/Void; - public static final fun getEnv (Ljava/lang/String;)Ljava/lang/String; } public final class aws/smithy/kotlin/runtime/text/Scanner { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt index d1b29a607..f996632e9 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt @@ -1,4 +1,3 @@ package aws.smithy.kotlin.runtime.smoketests public expect fun exitProcess(status: Int): Nothing -public expect fun getEnv(name: String): String? diff --git a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt index 5ac7d675b..b11912df7 100644 --- a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt +++ b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt @@ -3,4 +3,3 @@ package aws.smithy.kotlin.runtime.smoketests import kotlin.system.exitProcess public actual fun exitProcess(status: Int): Nothing = exitProcess(status) -public actual fun getEnv(name: String): String? = System.getenv(name) diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt index a2b633b79..b11912df7 100644 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt @@ -3,4 +3,3 @@ package aws.smithy.kotlin.runtime.smoketests import kotlin.system.exitProcess public actual fun exitProcess(status: Int): Nothing = exitProcess(status) -public actual fun getEnv(name: String): String? = TODO("Not yet implemented")