diff --git a/.changes/c376a9ee-568d-4638-8f4f-2e9a54f2869c.json b/.changes/c376a9ee-568d-4638-8f4f-2e9a54f2869c.json new file mode 100644 index 0000000000..6571cee336 --- /dev/null +++ b/.changes/c376a9ee-568d-4638-8f4f-2e9a54f2869c.json @@ -0,0 +1,5 @@ +{ + "id": "c376a9ee-568d-4638-8f4f-2e9a54f2869c", + "type": "misc", + "description": "Better smoke tests runner logging - Failed smoke tests now print exception stack trace" +} \ No newline at end of file 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 524e1924a6..17faf0ad48 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 @@ -129,6 +129,7 @@ data class KotlinDependency( val IDENTITY_API = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS", RUNTIME_GROUP, "identity-api", RUNTIME_VERSION) val SMITHY_RPCV2_PROTOCOLS = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.awsprotocol.rpcv2", RUNTIME_GROUP, "smithy-rpcv2-protocols", RUNTIME_VERSION) val SMITHY_RPCV2_PROTOCOLS_CBOR = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.awsprotocol.rpcv2.cbor", RUNTIME_GROUP, "smithy-rpcv2-protocols", RUNTIME_VERSION) + val AWS_SIGNING_CRT = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.auth.awssigning.crt", RUNTIME_GROUP, "aws-signing-crt", RUNTIME_VERSION) // External third-party dependencies val KOTLIN_STDLIB = KotlinDependency(GradleConfiguration.Implementation, "kotlin", "org.jetbrains.kotlin", "kotlin-stdlib", KOTLIN_COMPILER_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 d37e47737a..3616934407 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,6 +116,7 @@ object RuntimeTypes { object SmokeTests : RuntimeTypePackage(KotlinDependency.CORE, "smoketests") { val exitProcess = symbol("exitProcess") + val printExceptionStackTrace = symbol("printExceptionStackTrace") } object Collections : RuntimeTypePackage(KotlinDependency.CORE, "collections") { @@ -378,6 +379,10 @@ object RuntimeTypes { val sigV4 = symbol("sigV4") val sigV4A = symbol("sigV4A") } + + object AwsSigningCrt : RuntimeTypePackage(KotlinDependency.AWS_SIGNING_CRT) { + val CrtAwsSigner = symbol("CrtAwsSigner") + } } object Observability { 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 edcf8b7b2d..f2651da97d 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,8 +3,13 @@ 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.integration.SectionKey import software.amazon.smithy.kotlin.codegen.model.getTrait import software.amazon.smithy.kotlin.codegen.model.hasTrait +import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointParametersGenerator +import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointProviderGenerator +import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestUriValue.EndpointParameters +import software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestUriValue.EndpointProvider import software.amazon.smithy.kotlin.codegen.rendering.util.format import software.amazon.smithy.kotlin.codegen.utils.dq import software.amazon.smithy.kotlin.codegen.utils.toCamelCase @@ -18,7 +23,21 @@ object SmokeTestsRunner : SectionId object SmokeTestAdditionalEnvVars : SectionId object SmokeTestDefaultConfig : SectionId object SmokeTestRegionDefault : SectionId +object SmokeTestUseDualStackKey : SectionId +object SmokeTestSigv4aRegionSetKey : SectionId +object SmokeTestSigv4aRegionSetValue : SectionId +object SmokeTestAccountIdBasedRoutingKey : SectionId +object SmokeTestAccountIdBasedRoutingValue : SectionId object SmokeTestHttpEngineOverride : SectionId +object SmokeTestUseAccelerateKey : SectionId +object SmokeTestUseMultiRegionAccessPointsKey : SectionId +object SmokeTestUseMultiRegionAccessPointsValue : SectionId +object SmokeTestUseGlobalEndpoint : SectionId +object SmokeTestUriKey : SectionId +object SmokeTestUriValue : SectionId { + val EndpointProvider: SectionKey = SectionKey("EndpointProvider") + val EndpointParameters: SectionKey = SectionKey("EndpointParameters") +} const val SKIP_TAGS = "AWS_SMOKE_TEST_SKIP_TAGS" const val SERVICE_FILTER = "AWS_SMOKE_TEST_SERVICE_IDS" @@ -31,10 +50,11 @@ class SmokeTestsRunnerGenerator( ctx: CodegenContext, ) { private val model = ctx.model - private val sdkId = ctx.settings.sdkId + private val settings = ctx.settings + private val sdkId = settings.sdkId private val symbolProvider = ctx.symbolProvider - private val service = symbolProvider.toSymbol(model.expectShape(ctx.settings.service)) - private val operations = ctx.model.topDownOperations(ctx.settings.service).filter { it.hasTrait() } + private val service = symbolProvider.toSymbol(model.expectShape(settings.service)) + private val operations = model.topDownOperations(settings.service).filter { it.hasTrait() } internal fun render() { writer.declareSection(SmokeTestsRunner) { @@ -108,12 +128,72 @@ class SmokeTestsRunnerGenerator( writer.withInlineBlock("#L {", "}", service) { if (testCase.vendorParams.isPresent) { testCase.vendorParams.get().members.forEach { vendorParam -> - if (vendorParam.key.value == "region") { - writeInline("#L = ", vendorParam.key.value.toCamelCase()) - declareSection(SmokeTestRegionDefault) - write("#L", vendorParam.value.format()) - } else { - write("#L = #L", vendorParam.key.value.toCamelCase(), vendorParam.value.format()) + when (vendorParam.key.value) { + "region" -> { + writeInline("#L = ", vendorParam.key.value.toCamelCase()) + declareSection(SmokeTestRegionDefault) + write("#L", vendorParam.value.format()) + } + "sigv4aRegionSet" -> { + declareSection(SmokeTestSigv4aRegionSetKey) { + writeInline("#L", vendorParam.key.value.toCamelCase()) + } + writeInline(" = ") + declareSection(SmokeTestSigv4aRegionSetValue) { + write("#L", vendorParam.value.format()) + } + } + "uri" -> { + declareSection(SmokeTestUriKey) { + writeInline("#L", vendorParam.key.value.toCamelCase()) + } + writeInline(" = ") + declareSection( + SmokeTestUriValue, + mapOf( + EndpointProvider to EndpointProviderGenerator.getSymbol(settings), + EndpointParameters to EndpointParametersGenerator.getSymbol(settings), + ), + ) { + write("#L", vendorParam.value.format()) + } + } + "useDualstack" -> { + declareSection(SmokeTestUseDualStackKey) { + writeInline("#L", vendorParam.key.value.toCamelCase()) + } + write(" = #L", vendorParam.value.format()) + } + "useAccountIdRouting" -> { + declareSection(SmokeTestAccountIdBasedRoutingKey) { + writeInline("#L", vendorParam.key.value.toCamelCase()) + } + writeInline(" = ") + declareSection(SmokeTestAccountIdBasedRoutingValue) { + write("#L", vendorParam.value.format()) + } + } + "useAccelerate" -> { + declareSection(SmokeTestUseAccelerateKey) { + writeInline("#L", vendorParam.key.value.toCamelCase()) + } + write(" = #L", vendorParam.value.format()) + } + "useMultiRegionAccessPoints" -> { + declareSection(SmokeTestUseMultiRegionAccessPointsKey) { + writeInline("#L", vendorParam.key.value.toCamelCase()) + } + writeInline(" = ") + declareSection(SmokeTestUseMultiRegionAccessPointsValue) { + write("#L", vendorParam.value.format()) + } + } + "useGlobalEndpoint" -> { + declareSection(SmokeTestUseGlobalEndpoint) { + write("#L = #L", vendorParam.key.value.toCamelCase(), vendorParam.value.format()) + } + } + else -> write("#L = #L", vendorParam.key.value.toCamelCase(), vendorParam.value.format()) } } } else { @@ -156,6 +236,7 @@ class SmokeTestsRunnerGenerator( testCase.expectation.isFailure, writer, ) + writer.write("if (!success) #T(e)", RuntimeTypes.Core.SmokeTests.printExceptionStackTrace) writer.write("if (!success) exitCode = 1") } 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 ddb5b3c1db..540a1d3fec 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 @@ -122,6 +122,7 @@ class SmokeTestsRunnerGeneratorTest { val success = e is SmokeTestsSuccessException val status = if (success) "ok" else "not ok" println("${'$'}status Test SuccessTest - no error expected from service ") + if (!success) printExceptionStackTrace(e) if (!success) exitCode = 1 } } @@ -155,6 +156,7 @@ class SmokeTestsRunnerGeneratorTest { val success = e is InvalidMessageError val status = if (success) "ok" else "not ok" println("${'$'}status Test InvalidMessageErrorTest - error expected from service ") + if (!success) printExceptionStackTrace(e) if (!success) exitCode = 1 } } @@ -189,6 +191,7 @@ class SmokeTestsRunnerGeneratorTest { val success = e is SmokeTestsFailureException val status = if (success) "ok" else "not ok" println("${'$'}status Test FailureTest - error expected from service ") + if (!success) printExceptionStackTrace(e) if (!success) exitCode = 1 } } diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index c15a33b0b1..7e22ab9c6b 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -425,7 +425,7 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/ResponseLengthVal } public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsFailureException : java/lang/Exception { - public fun ()V + public fun (Ljava/lang/String;)V } public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { @@ -452,7 +452,7 @@ public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterce } public final class aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsSuccessException : java/lang/Exception { - public fun ()V + public fun (Ljava/lang/String;)V } public final class aws/smithy/kotlin/runtime/http/middleware/DefaultValidateResponse : aws/smithy/kotlin/runtime/http/operation/ReceiveMiddleware { 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 63848ffe24..8c53468de3 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 @@ -16,14 +16,14 @@ public class SmokeTestsInterceptor : HttpInterceptor { override fun readBeforeDeserialization(context: ProtocolResponseInterceptorContext) { val status = context.protocolResponse.status.value when (status) { - in 400..599 -> throw SmokeTestsFailureException() - in 200..299 -> throw SmokeTestsSuccessException() - else -> throw SmokeTestsUnexpectedException() + in 400..599 -> throw SmokeTestsFailureException("Smoke test failed with HTTP status code: $status") + in 200..299 -> throw SmokeTestsSuccessException("Smoke test succeeded with HTTP status code: $status") + else -> throw SmokeTestsUnexpectedException("Smoke test returned HTTP status code: $status") } } } -@InternalApi public class SmokeTestsFailureException : Exception() +@InternalApi public class SmokeTestsFailureException(message: String) : Exception(message) -@InternalApi public class SmokeTestsSuccessException : Exception() -private class SmokeTestsUnexpectedException : Exception() +@InternalApi public class SmokeTestsSuccessException(message: String) : Exception(message) +private class SmokeTestsUnexpectedException(message: String) : Exception(message) diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index d1ba553de5..45f5ffdbe8 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -2048,6 +2048,10 @@ 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/smoketests/SmokeTestsFunctionsKt { + public static final fun printExceptionStackTrace (Ljava/lang/Exception;)V +} + 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/SmokeTestsFunctions.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt index f996632e99..6e2e51b2d1 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,3 +1,19 @@ package aws.smithy.kotlin.runtime.smoketests public expect fun exitProcess(status: Int): Nothing + +/** + * Prints an exceptions stack trace using test anything protocol (TAP) format e.g. + * + * #java.lang.ArithmeticException: / by zero + * # at FileKt.main(File.kt:3) + * # at FileKt.main(File.kt) + * # at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + * # at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) + * # at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) + * # at java.base/java.lang.reflect.Method.invoke(Unknown Source) + * # at executors.JavaRunnerExecutor$Companion.main(JavaRunnerExecutor.kt:27) + * # at executors.JavaRunnerExecutor.main(JavaRunnerExecutor.kt) + */ +public fun printExceptionStackTrace(exception: Exception): Unit = + println(exception.stackTraceToString().split("\n").joinToString("\n") { "#$it" })