From e2023b95f24d61b77782ada8c825c03af16e35d4 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Mon, 6 Feb 2023 10:12:17 -0500 Subject: [PATCH] Prevent test dependencies from leaking into production (#2264) * Prevent test dependencies from leaking into production * refactor & fix tests * fix tests take two * fix more tests * Fix missed called to mergeDependencyFeatures * Add test * fix glacier compilation * fix more tests * fix one more test --- .../rustsdk/AwsFluentClientDecorator.kt | 3 +- .../amazon/smithy/rustsdk/AwsRuntimeType.kt | 3 +- .../rustsdk/IntegrationTestDependencies.kt | 10 +-- .../customize/glacier/TreeHashHeader.kt | 12 ++-- .../endpoints/OperationInputTestGenerator.kt | 5 +- .../rustsdk/EndpointsCredentialsTest.kt | 8 +-- .../generators/EndpointParamsGenerator.kt | 3 +- .../generators/EndpointTestGenerator.kt | 4 +- .../protocol/ProtocolTestGenerator.kt | 12 +--- .../codegen/core/rustlang/CargoDependency.kt | 13 +++- .../rust/codegen/core/rustlang/RustModule.kt | 41 ++++++++++-- .../rust/codegen/core/rustlang/RustWriter.kt | 54 +++++++++++++--- .../codegen/core/smithy/CodegenDelegator.kt | 43 ++++++++++--- .../rust/codegen/core/smithy/RuntimeType.kt | 59 ++++++++++++----- .../smithy/rust/codegen/core/testutil/Rust.kt | 58 ++++++++++++++++- .../core/rustlang/InlineDependencyTest.kt | 11 ++-- .../core/smithy/CodegenDelegatorTest.kt | 21 ++++++- .../smithy/generators/InstantiatorTest.kt | 3 +- .../ServerHttpSensitivityGeneratorTest.kt | 20 +++--- .../generators/ServerInstantiatorTest.kt | 63 ++++++++++--------- 20 files changed, 323 insertions(+), 123 deletions(-) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt index c06b58994b..f8a50a7723 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt @@ -16,7 +16,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.client.Fluen import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerics import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientSection import software.amazon.smithy.rust.codegen.core.rustlang.Attribute -import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope import software.amazon.smithy.rust.codegen.core.rustlang.Feature import software.amazon.smithy.rust.codegen.core.rustlang.GenericTypeArg import software.amazon.smithy.rust.codegen.core.rustlang.RustGenerics @@ -228,7 +227,7 @@ private class AwsFluentClientDocs(private val codegenContext: CodegenContext) : private val serviceShape = codegenContext.serviceShape private val crateName = codegenContext.moduleUseName() private val codegenScope = - arrayOf("aws_config" to AwsCargoDependency.awsConfig(codegenContext.runtimeConfig).copy(scope = DependencyScope.Dev).toType()) + arrayOf("aws_config" to AwsCargoDependency.awsConfig(codegenContext.runtimeConfig).toDevDependency().toType()) // If no `aws-config` version is provided, assume that docs referencing `aws-config` cannot be given. // Also, STS and SSO must NOT reference `aws-config` since that would create a circular dependency. diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt index f4b05b7baf..9fdbc93eda 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsRuntimeType.kt @@ -7,7 +7,6 @@ package software.amazon.smithy.rustsdk import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency -import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope import software.amazon.smithy.rust.codegen.core.rustlang.Visibility import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeCrateLocation @@ -63,7 +62,7 @@ object AwsRuntimeType { fun awsCredentialTypes(runtimeConfig: RuntimeConfig) = AwsCargoDependency.awsCredentialTypes(runtimeConfig).toType() fun awsCredentialTypesTestUtil(runtimeConfig: RuntimeConfig) = - AwsCargoDependency.awsCredentialTypes(runtimeConfig).copy(scope = DependencyScope.Dev).withFeature("test-util").toType() + AwsCargoDependency.awsCredentialTypes(runtimeConfig).toDevDependency().withFeature("test-util").toType() fun awsEndpoint(runtimeConfig: RuntimeConfig) = AwsCargoDependency.awsEndpoint(runtimeConfig).toType() fun awsHttp(runtimeConfig: RuntimeConfig) = AwsCargoDependency.awsHttp(runtimeConfig).toType() diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt index 0eafec8fcd..9cbddde250 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt @@ -30,6 +30,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection +import software.amazon.smithy.rust.codegen.core.testutil.testDependenciesOnly import java.nio.file.Files import java.nio.file.Paths import kotlin.io.path.absolute @@ -72,7 +73,7 @@ class IntegrationTestDependencies( private val hasBenches: Boolean, ) : LibRsCustomization() { override fun section(section: LibRsSection) = when (section) { - is LibRsSection.Body -> writable { + is LibRsSection.Body -> testDependenciesOnly { if (hasTests) { val smithyClient = CargoDependency.smithyClient(runtimeConfig) .copy(features = setOf("test-util"), scope = DependencyScope.Dev) @@ -81,7 +82,7 @@ class IntegrationTestDependencies( addDependency(SerdeJson) addDependency(Tokio) addDependency(FuturesUtil) - addDependency(Tracing) + addDependency(Tracing.toDevDependency()) addDependency(TracingSubscriber) } if (hasBenches) { @@ -91,6 +92,7 @@ class IntegrationTestDependencies( serviceSpecific.section(section)(this) } } + else -> emptySection } @@ -114,8 +116,8 @@ class S3TestDependencies : LibRsCustomization() { override fun section(section: LibRsSection): Writable = writable { addDependency(AsyncStd) - addDependency(BytesUtils) - addDependency(FastRand) + addDependency(BytesUtils.toDevDependency()) + addDependency(FastRand.toDevDependency()) addDependency(HdrHistogram) addDependency(Smol) addDependency(TempFile) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt index 023a663c42..c97357a2fd 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt @@ -33,13 +33,16 @@ private val UploadMultipartPart: ShapeId = ShapeId.from("com.amazonaws.glacier#U private val Applies = setOf(UploadArchive, UploadMultipartPart) class TreeHashHeader(private val runtimeConfig: RuntimeConfig) : OperationCustomization() { - private val glacierChecksums = RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("glacier_checksums")) + private val glacierChecksums = RuntimeType.forInlineDependency( + InlineAwsDependency.forRustFile( + "glacier_checksums", + additionalDependency = TreeHashDependencies.toTypedArray(), + ), + ) + override fun section(section: OperationSection): Writable { return when (section) { is OperationSection.MutateRequest -> writable { - TreeHashDependencies.forEach { dep -> - addDependency(dep) - } rustTemplate( """ #{glacier_checksums}::add_checksum_treehash( @@ -49,6 +52,7 @@ class TreeHashHeader(private val runtimeConfig: RuntimeConfig) : OperationCustom "glacier_checksums" to glacierChecksums, "BuildError" to runtimeConfig.operationBuildError(), ) } + else -> emptySection } } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt index 37ecbad73c..9d620e11cb 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt @@ -17,7 +17,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointTypesG import software.amazon.smithy.rust.codegen.client.smithy.generators.clientInstantiator import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.AttributeKind -import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.escape import software.amazon.smithy.rust.codegen.core.rustlang.join import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -25,6 +24,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.PublicImportSymbolProvider +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.generators.setterName import software.amazon.smithy.rust.codegen.core.testutil.integrationTest @@ -146,8 +146,7 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test: let _result = dbg!(#{invoke_operation}); #{assertion} """, - "capture_request" to CargoDependency.smithyClient(runtimeConfig) - .withFeature("test-util").toType().resolve("test_connection::capture_request"), + "capture_request" to RuntimeType.captureRequest(runtimeConfig), "conf" to config(testOperationInput), "invoke_operation" to operationInvocation(testOperationInput), "assertion" to writable { diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt index 1df1a0c0e0..ea2bfe026f 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt @@ -6,8 +6,8 @@ package software.amazon.smithy.rustsdk import org.junit.jupiter.api.Test -import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest import software.amazon.smithy.rust.codegen.core.testutil.tokioTest @@ -96,8 +96,7 @@ class EndpointsCredentialsTest { let auth_header = req.headers().get("AUTHORIZATION").unwrap().to_str().unwrap(); assert!(auth_header.contains("/us-west-2/foobaz/aws4_request"), "{}", auth_header); """, - "capture_request" to CargoDependency.smithyClient(context.runtimeConfig) - .withFeature("test-util").toType().resolve("test_connection::capture_request"), + "capture_request" to RuntimeType.captureRequest(context.runtimeConfig), "Credentials" to AwsCargoDependency.awsCredentialTypes(context.runtimeConfig) .withFeature("test-util").toType().resolve("Credentials"), "Region" to AwsRuntimeType.awsTypes(context.runtimeConfig).resolve("region::Region"), @@ -120,8 +119,7 @@ class EndpointsCredentialsTest { let auth_header = req.headers().get("AUTHORIZATION").unwrap().to_str().unwrap(); assert!(auth_header.contains("/region-custom-auth/name-custom-auth/aws4_request"), "{}", auth_header); """, - "capture_request" to CargoDependency.smithyClient(context.runtimeConfig) - .withFeature("test-util").toType().resolve("test_connection::capture_request"), + "capture_request" to RuntimeType.captureRequest(context.runtimeConfig), "Credentials" to AwsCargoDependency.awsCredentialTypes(context.runtimeConfig) .withFeature("test-util").toType().resolve("Credentials"), "Region" to AwsRuntimeType.awsTypes(context.runtimeConfig).resolve("region::Region"), diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt index 1e5059830d..b2a7bde292 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt @@ -13,7 +13,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName import software.amazon.smithy.rust.codegen.client.smithy.endpoint.symbol import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive -import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter @@ -59,7 +58,7 @@ val EndpointTests = RustModule.new( documentation = "Generated endpoint tests", parent = EndpointsModule, inline = true, -).copy(rustMetadata = RustMetadata.TestModule) +).cfgTest() // stdlib is isolated because it contains code generated names of stdlib functions–we want to ensure we avoid clashing val EndpointsStdLib = RustModule.private("endpoint_lib", "Endpoints standard library functions") diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt index 183e25d33e..c4d6efc327 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointTestGenerator.kt @@ -14,7 +14,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustom import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Types import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName import software.amazon.smithy.rust.codegen.client.smithy.generators.clientInstantiator -import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.escape @@ -48,8 +47,7 @@ internal class EndpointTestGenerator( "Error" to types.resolveEndpointError, "Document" to RuntimeType.document(runtimeConfig), "HashMap" to RuntimeType.HashMap, - "capture_request" to CargoDependency.smithyClient(runtimeConfig) - .withFeature("test-util").toType().resolve("test_connection::capture_request"), + "capture_request" to RuntimeType.captureRequest(runtimeConfig), ) private val instantiator = clientInstantiator(codegenContext) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt index 476e67ef5a..b5a24be36c 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt @@ -22,10 +22,8 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.generators.clientInstantiator import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.allow -import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.rustlang.Visibility import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.escape import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -91,14 +89,10 @@ class ProtocolTestGenerator( if (allTests.isNotEmpty()) { val operationName = operationSymbol.name val testModuleName = "${operationName.toSnakeCase()}_request_test" - val moduleMeta = RustMetadata( - visibility = Visibility.PRIVATE, - additionalAttributes = listOf( - Attribute.CfgTest, - Attribute(allow("unreachable_code", "unused_variables")), - ), + val additionalAttributes = listOf( + Attribute(allow("unreachable_code", "unused_variables")), ) - writer.withInlineModule(RustModule.LeafModule(testModuleName, moduleMeta, inline = true)) { + writer.withInlineModule(RustModule.inlineTests(testModuleName, additionalAttributes = additionalAttributes)) { renderAllTestCases(allTests) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt index 6d355da68a..fea09c8fd0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt @@ -133,6 +133,8 @@ data class CargoDependency( return copy(features = features.toMutableSet().apply { add(feature) }) } + fun toDevDependency() = copy(scope = DependencyScope.Dev) + override fun version(): String = when (location) { is CratesIo -> location.version is Local -> "local" @@ -220,7 +222,12 @@ data class CargoDependency( val Smol: CargoDependency = CargoDependency("smol", CratesIo("1.2.0"), DependencyScope.Dev) val TempFile: CargoDependency = CargoDependency("tempfile", CratesIo("3.2.0"), DependencyScope.Dev) val Tokio: CargoDependency = - CargoDependency("tokio", CratesIo("1.8.4"), DependencyScope.Dev, features = setOf("macros", "test-util", "rt-multi-thread")) + CargoDependency( + "tokio", + CratesIo("1.8.4"), + DependencyScope.Dev, + features = setOf("macros", "test-util", "rt-multi-thread"), + ) val TracingAppender: CargoDependency = CargoDependency( "tracing-appender", CratesIo("0.2.2"), @@ -236,12 +243,16 @@ data class CargoDependency( fun smithyAsync(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-async") fun smithyChecksums(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-checksums") fun smithyClient(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-client") + fun smithyClientTestUtil(runtimeConfig: RuntimeConfig) = + smithyClient(runtimeConfig).toDevDependency().withFeature("test-util") + fun smithyEventStream(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-eventstream") fun smithyHttp(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-http") fun smithyHttpTower(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-http-tower") fun smithyJson(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-json") fun smithyProtocolTestHelpers(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-protocol-test", scope = DependencyScope.Dev) + fun smithyQuery(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-query") fun smithyTypes(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-types") fun smithyXml(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-xml") diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt index 6745e3b2ee..573fa242f0 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustModule.kt @@ -32,7 +32,10 @@ sealed class RustModule { val documentation: String? = null, val parent: RustModule = LibRs, val inline: Boolean = false, + /* module is a cfg(test) module */ + val tests: Boolean = false, ) : RustModule() { + init { check(!name.contains("::")) { "Module names CANNOT contain `::`—modules must be nested with parent (name was: `$name`)" @@ -45,6 +48,12 @@ sealed class RustModule { "Module `$name` cannot be a module name—it is a reserved word." } } + + /** Convert a module into a module gated with `#[cfg(test)]` */ + fun cfgTest(): LeafModule = this.copy( + rustMetadata = rustMetadata.copy(additionalAttributes = rustMetadata.additionalAttributes + Attribute.CfgTest), + tests = true, + ) } companion object { @@ -78,12 +87,36 @@ sealed class RustModule { fun pubCrate(name: String, documentation: String? = null, parent: RustModule): LeafModule = new(name, visibility = Visibility.PUBCRATE, documentation = documentation, inline = false, parent = parent) + fun inlineTests( + name: String = "test", + parent: RustModule = LibRs, + additionalAttributes: List = listOf(), + ) = new( + name, + Visibility.PRIVATE, + inline = true, + additionalAttributes = additionalAttributes, + parent = parent, + ).cfgTest() + /* Common modules used across client, server and tests */ val Config = public("config", documentation = "Configuration for the service.") - val Error = public("error", documentation = "All error types that operations can return. Documentation on these types is copied from the model.") - val Model = public("model", documentation = "Data structures used by operation inputs/outputs. Documentation on these types is copied from the model.") - val Input = public("input", documentation = "Input structures for operations. Documentation on these types is copied from the model.") - val Output = public("output", documentation = "Output structures for operations. Documentation on these types is copied from the model.") + val Error = public( + "error", + documentation = "All error types that operations can return. Documentation on these types is copied from the model.", + ) + val Model = public( + "model", + documentation = "Data structures used by operation inputs/outputs. Documentation on these types is copied from the model.", + ) + val Input = public( + "input", + documentation = "Input structures for operations. Documentation on these types is copied from the model.", + ) + val Output = public( + "output", + documentation = "Output structures for operations. Documentation on these types is copied from the model.", + ) val Types = public("types", documentation = "Data primitives referenced by other data types.") /** diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt index 2b4a17c193..9cd7d00bad 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt @@ -10,6 +10,7 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Element import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.codegen.core.SymbolDependencyContainer import software.amazon.smithy.codegen.core.SymbolWriter import software.amazon.smithy.codegen.core.SymbolWriter.Factory import software.amazon.smithy.model.Model @@ -267,7 +268,10 @@ fun > T.docsOrFallback( note: String? = null, ): T { val htmlDocs: (T.() -> Unit)? = when (docString?.isNotBlank()) { - true -> { { docs(normalizeHtml(escape(docString))) } } + true -> { + { docs(normalizeHtml(escape(docString))) } + } + else -> null } return docsOrFallback(htmlDocs, autoSuppressMissingDocs, note) @@ -394,6 +398,8 @@ class RustWriter private constructor( private val printWarning: Boolean = true, /** Insert comments indicating where code was generated */ private val debugMode: Boolean = false, + /** When true, automatically change all dependencies to be in the test scope */ + val devDependenciesOnly: Boolean = false, ) : SymbolWriter(UseDeclarations(namespace)) { companion object { @@ -409,6 +415,13 @@ class RustWriter private constructor( fileName.endsWith(".toml") -> RustWriter(fileName, namespace, "#", debugMode = debugMode) fileName.endsWith(".md") -> rawWriter(fileName, debugMode = debugMode) fileName == "LICENSE" -> rawWriter(fileName, debugMode = debugMode) + fileName.startsWith("tests/") -> RustWriter( + fileName, + namespace, + debugMode = debugMode, + devDependenciesOnly = true, + ) + else -> RustWriter(fileName, namespace, debugMode = debugMode) } } @@ -474,6 +487,22 @@ class RustWriter private constructor( preamble.add(preWriter) } + private fun addDependencyTestAware(dependencyContainer: SymbolDependencyContainer): RustWriter { + if (!devDependenciesOnly) { + super.addDependency(dependencyContainer) + } else { + dependencyContainer.dependencies.forEach { dependency -> + super.addDependency( + when (val dep = RustDependency.fromSymbolDependency(dependency)) { + is CargoDependency -> dep.toDevDependency() + else -> dependencyContainer + }, + ) + } + } + return this + } + /** * Create an inline module. Instead of being in a new file, inline modules are written as a `mod { ... }` block * directly into the parent. @@ -499,14 +528,19 @@ class RustWriter private constructor( // In Rust, modules must specify their own imports—they don't have access to the parent scope. // To easily handle this, create a new inner writer to collect imports, then dump it // into an inline module. - val innerWriter = RustWriter(this.filename, "${this.namespace}::${module.name}", printWarning = false) + val innerWriter = RustWriter( + this.filename, + "${this.namespace}::${module.name}", + printWarning = false, + devDependenciesOnly = devDependenciesOnly || module.tests, + ) moduleWriter(innerWriter) module.documentation?.let { docs -> docs(docs) } module.rustMetadata.render(this) rustBlock("mod ${module.name}") { writeWithNoFormatting(innerWriter.toString()) } - innerWriter.dependencies.forEach { addDependency(it) } + innerWriter.dependencies.forEach { addDependencyTestAware(it) } return this } @@ -605,7 +639,7 @@ class RustWriter private constructor( override fun toString(): String { val contents = super.toString() val preheader = if (preamble.isNotEmpty()) { - val prewriter = RustWriter(filename, namespace, printWarning = false) + val prewriter = RustWriter(filename, namespace, printWarning = false, devDependenciesOnly = devDependenciesOnly) preamble.forEach { it(prewriter) } prewriter.toString() } else null @@ -623,7 +657,7 @@ class RustWriter private constructor( fun format(r: Any) = formatter.apply(r, "") fun addDepsRecursively(symbol: Symbol) { - addDependency(symbol) + addDependencyTestAware(symbol) symbol.references.forEach { addDepsRecursively(it.symbol) } } @@ -647,9 +681,9 @@ class RustWriter private constructor( @Suppress("UNCHECKED_CAST") val func = t as? Writable ?: throw CodegenException("RustWriteableInjector.apply choked on non-function t ($t)") - val innerWriter = RustWriter(filename, namespace, printWarning = false) + val innerWriter = RustWriter(filename, namespace, printWarning = false, devDependenciesOnly = devDependenciesOnly) func(innerWriter) - innerWriter.dependencies.forEach { addDependency(it) } + innerWriter.dependencies.forEach { addDependencyTestAware(it) } return innerWriter.toString().trimEnd() } } @@ -658,7 +692,7 @@ class RustWriter private constructor( override fun apply(t: Any, u: String): String { return when (t) { is RuntimeType -> { - t.dependency?.also { addDependency(it) } + t.dependency?.also { addDependencyTestAware(it) } // for now, use the fully qualified type name t.fullyQualifiedName() } @@ -676,9 +710,9 @@ class RustWriter private constructor( @Suppress("UNCHECKED_CAST") val func = t as? Writable ?: throw CodegenException("Invalid function type (expected writable) ($t)") - val innerWriter = RustWriter(filename, namespace, printWarning = false) + val innerWriter = RustWriter(filename, namespace, printWarning = false, devDependenciesOnly = devDependenciesOnly) func(innerWriter) - innerWriter.dependencies.forEach { addDependency(it) } + innerWriter.dependencies.forEach { addDependencyTestAware(it) } return innerWriter.toString().trimEnd() } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegator.kt index 768a24073b..ccb3005d69 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegator.kt @@ -11,6 +11,7 @@ import software.amazon.smithy.codegen.core.WriterDelegator import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope import software.amazon.smithy.rust.codegen.core.rustlang.Feature import software.amazon.smithy.rust.codegen.core.rustlang.InlineDependency import software.amazon.smithy.rust.codegen.core.rustlang.RustDependency @@ -171,11 +172,23 @@ open class RustCrate( } } -val ErrorsModule = RustModule.public("error", documentation = "All error types that operations can return. Documentation on these types is copied from the model.") +val ErrorsModule = RustModule.public( + "error", + documentation = "All error types that operations can return. Documentation on these types is copied from the model.", +) val OperationsModule = RustModule.public("operation", documentation = "All operations that this crate can perform.") -val ModelsModule = RustModule.public("model", documentation = "Data structures used by operation inputs/outputs. Documentation on these types is copied from the model.") -val InputsModule = RustModule.public("input", documentation = "Input structures for operations. Documentation on these types is copied from the model.") -val OutputsModule = RustModule.public("output", documentation = "Output structures for operations. Documentation on these types is copied from the model.") +val ModelsModule = RustModule.public( + "model", + documentation = "Data structures used by operation inputs/outputs. Documentation on these types is copied from the model.", +) +val InputsModule = RustModule.public( + "input", + documentation = "Input structures for operations. Documentation on these types is copied from the model.", +) +val OutputsModule = RustModule.public( + "output", + documentation = "Output structures for operations. Documentation on these types is copied from the model.", +) val UnconstrainedModule = RustModule.private("unconstrained", "Unconstrained types for constrained shapes.") @@ -198,10 +211,12 @@ fun WriterDelegator.finalize( this.useFileWriter("src/lib.rs", "crate::lib") { LibRsGenerator(settings, model, libRsCustomizations, requireDocs).render(it) } - val cargoDependencies = mergeDependencyFeatures( + val cargoDependencies = + this.dependencies.map { RustDependency.fromSymbolDependency(it) } - .filterIsInstance().distinct(), - ) + .filterIsInstance().distinct() + .mergeDependencyFeatures() + .mergeIdenticalTestDependencies() this.useFileWriter("Cargo.toml") { val cargoToml = CargoTomlGenerator( settings, @@ -223,9 +238,19 @@ private fun CargoDependency.mergeWith(other: CargoDependency): CargoDependency { ) } -fun mergeDependencyFeatures(cargoDependencies: List): List = - cargoDependencies.groupBy { it.key } +internal fun List.mergeDependencyFeatures(): List = + this.groupBy { it.key } .mapValues { group -> group.value.reduce { acc, next -> acc.mergeWith(next) } } .values .toList() .sortedBy { it.name } + +/** + * If the same dependency exists both in prod and test scope, remove it from the test scope. + */ +internal fun List.mergeIdenticalTestDependencies(): List { + val compileDeps = + this.filter { it.scope == DependencyScope.Compile }.toSet() + + return this.filterNot { it.scope == DependencyScope.Dev && compileDeps.contains(it.copy(scope = DependencyScope.Compile)) } +} diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt index 1c489f188f..b0752430c2 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt @@ -80,10 +80,11 @@ data class RuntimeConfig( */ fun fromNode(maybeNode: Optional): RuntimeConfig { val node = maybeNode.orElse(Node.objectNode()) - val crateVersionMap = node.getObjectMember("versions").orElse(Node.objectNode()).members.entries.let { members -> - val map = members.associate { it.key.toString() to it.value.expectStringNode().value } - CrateVersionMap(map) - } + val crateVersionMap = + node.getObjectMember("versions").orElse(Node.objectNode()).members.entries.let { members -> + val map = members.associate { it.key.toString() to it.value.expectStringNode().value } + CrateVersionMap(map) + } val path = node.getStringMember("relativePath").orNull()?.value val runtimeCrateLocation = RuntimeCrateLocation(path = path, versions = crateVersionMap) return RuntimeConfig( @@ -95,7 +96,11 @@ data class RuntimeConfig( val crateSrcPrefix: String = cratePrefix.replace("-", "_") - fun smithyRuntimeCrate(runtimeCrateName: String, optional: Boolean = false, scope: DependencyScope = DependencyScope.Compile): CargoDependency { + fun smithyRuntimeCrate( + runtimeCrateName: String, + optional: Boolean = false, + scope: DependencyScope = DependencyScope.Compile, + ): CargoDependency { val crateName = "$cratePrefix-$runtimeCrateName" return CargoDependency( crateName, @@ -250,31 +255,48 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) fun smithyQuery(runtimeConfig: RuntimeConfig) = CargoDependency.smithyQuery(runtimeConfig).toType() fun smithyTypes(runtimeConfig: RuntimeConfig) = CargoDependency.smithyTypes(runtimeConfig).toType() fun smithyXml(runtimeConfig: RuntimeConfig) = CargoDependency.smithyXml(runtimeConfig).toType() - private fun smithyProtocolTest(runtimeConfig: RuntimeConfig) = CargoDependency.smithyProtocolTestHelpers(runtimeConfig).toType() + private fun smithyProtocolTest(runtimeConfig: RuntimeConfig) = + CargoDependency.smithyProtocolTestHelpers(runtimeConfig).toType() // smithy runtime type members - fun base64Decode(runtimeConfig: RuntimeConfig): RuntimeType = smithyTypes(runtimeConfig).resolve("base64::decode") - fun base64Encode(runtimeConfig: RuntimeConfig): RuntimeType = smithyTypes(runtimeConfig).resolve("base64::encode") + fun base64Decode(runtimeConfig: RuntimeConfig): RuntimeType = + smithyTypes(runtimeConfig).resolve("base64::decode") + + fun base64Encode(runtimeConfig: RuntimeConfig): RuntimeType = + smithyTypes(runtimeConfig).resolve("base64::encode") + fun blob(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("Blob") fun byteStream(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("byte_stream::ByteStream") fun classifyRetry(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("retry::ClassifyRetry") fun dateTime(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("DateTime") fun document(runtimeConfig: RuntimeConfig): RuntimeType = smithyTypes(runtimeConfig).resolve("Document") fun errorKind(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("retry::ErrorKind") - fun eventStreamReceiver(runtimeConfig: RuntimeConfig): RuntimeType = smithyHttp(runtimeConfig).resolve("event_stream::Receiver") + fun eventStreamReceiver(runtimeConfig: RuntimeConfig): RuntimeType = + smithyHttp(runtimeConfig).resolve("event_stream::Receiver") + fun genericError(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("Error") fun jsonErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.jsonErrors(runtimeConfig)) fun labelFormat(runtimeConfig: RuntimeConfig, func: String) = smithyHttp(runtimeConfig).resolve("label::$func") fun operation(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("operation::Operation") fun operationModule(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("operation") - fun parseHttpResponse(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("response::ParseHttpResponse") - fun parseStrictResponse(runtimeConfig: RuntimeConfig) = smithyHttp(runtimeConfig).resolve("response::ParseStrictResponse") - fun protocolTest(runtimeConfig: RuntimeConfig, func: String): RuntimeType = smithyProtocolTest(runtimeConfig).resolve(func) - fun provideErrorKind(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("retry::ProvideErrorKind") + fun parseHttpResponse(runtimeConfig: RuntimeConfig) = + smithyHttp(runtimeConfig).resolve("response::ParseHttpResponse") + + fun parseStrictResponse(runtimeConfig: RuntimeConfig) = + smithyHttp(runtimeConfig).resolve("response::ParseStrictResponse") + + fun protocolTest(runtimeConfig: RuntimeConfig, func: String): RuntimeType = + smithyProtocolTest(runtimeConfig).resolve(func) + + fun provideErrorKind(runtimeConfig: RuntimeConfig) = + smithyTypes(runtimeConfig).resolve("retry::ProvideErrorKind") + fun queryFormat(runtimeConfig: RuntimeConfig, func: String) = smithyHttp(runtimeConfig).resolve("query::$func") fun sdkBody(runtimeConfig: RuntimeConfig): RuntimeType = smithyHttp(runtimeConfig).resolve("body::SdkBody") fun sdkError(runtimeConfig: RuntimeConfig): RuntimeType = smithyHttp(runtimeConfig).resolve("result::SdkError") - fun sdkSuccess(runtimeConfig: RuntimeConfig): RuntimeType = smithyHttp(runtimeConfig).resolve("result::SdkSuccess") + fun sdkSuccess(runtimeConfig: RuntimeConfig): RuntimeType = + smithyHttp(runtimeConfig).resolve("result::SdkSuccess") + fun timestampFormat(runtimeConfig: RuntimeConfig, format: TimestampFormatTrait.Format): RuntimeType { val timestampFormat = when (format) { TimestampFormatTrait.Format.EPOCH_SECONDS -> "EpochSeconds" @@ -286,7 +308,11 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) return smithyTypes(runtimeConfig).resolve("date_time::Format::$timestampFormat") } - fun forInlineDependency(inlineDependency: InlineDependency) = RuntimeType("crate::${inlineDependency.name}", inlineDependency) + fun captureRequest(runtimeConfig: RuntimeConfig) = + CargoDependency.smithyClientTestUtil(runtimeConfig).toType().resolve("test_connection::capture_request") + + fun forInlineDependency(inlineDependency: InlineDependency) = + RuntimeType("crate::${inlineDependency.name}", inlineDependency) fun forInlineFun(name: String, module: RustModule, func: Writable) = RuntimeType( "${module.fullyQualifiedPath()}::$name", @@ -296,10 +322,13 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) // inlinable types fun ec2QueryErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.ec2QueryErrors(runtimeConfig)) + fun wrappedXmlErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.wrappedXmlErrors(runtimeConfig)) + fun unwrappedXmlErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.unwrappedXmlErrors(runtimeConfig)) + val IdempotencyToken by lazy { forInlineDependency(InlineDependency.idempotencyToken()) } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt index a8567c4db4..c91581c052 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt @@ -9,6 +9,7 @@ import com.moandjiezana.toml.TomlWriter import org.intellij.lang.annotations.Language import software.amazon.smithy.build.FileManifest import software.amazon.smithy.build.PluginContext +import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.Model import software.amazon.smithy.model.node.Node @@ -18,7 +19,9 @@ import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.traits.EnumDefinition import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope import software.amazon.smithy.rust.codegen.core.rustlang.RustDependency +import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.raw @@ -34,6 +37,7 @@ import software.amazon.smithy.rust.codegen.core.util.CommandFailed import software.amazon.smithy.rust.codegen.core.util.PANIC import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.letIf +import software.amazon.smithy.rust.codegen.core.util.orNullIfEmpty import software.amazon.smithy.rust.codegen.core.util.runCommand import java.io.File import java.nio.file.Files.createTempDirectory @@ -221,7 +225,45 @@ fun RustWriter.unitTest( if (async) { rust("async") } - return rustBlock("fn $name()", *args, block = block) + return testDependenciesOnly { rustBlock("fn $name()", *args, block = block) } +} + +fun RustWriter.cargoDependencies() = dependencies.map { RustDependency.fromSymbolDependency(it) } + .filterIsInstance().distinct() + +fun RustWriter.assertNoNewDependencies(block: Writable, dependencyFilter: (CargoDependency) -> String?): RustWriter { + val startingDependencies = cargoDependencies().toSet() + block(this) + val endingDependencies = cargoDependencies().toSet() + val newDeps = (endingDependencies - startingDependencies) + val invalidDeps = + newDeps.mapNotNull { dep -> dependencyFilter(dep)?.let { message -> message to dep } }.orNullIfEmpty() + if (invalidDeps != null) { + val badDeps = invalidDeps.map { it.second.rustName } + val writtenOut = this.toString() + val badLines = writtenOut.lines().filter { line -> badDeps.any { line.contains(it) } } + throw CodegenException( + "found invalid dependencies. ${invalidDeps.map { it.first }}\nHint: the following lines may be the problem.\n${ + badLines.joinToString( + separator = "\n", + prefix = " ", + ) + }", + ) + } + return this +} + +fun RustWriter.testDependenciesOnly(block: Writable) = assertNoNewDependencies(block) { dep -> + if (dep.scope != DependencyScope.Dev) { + "Cannot add $dep — this writer should only add test dependencies." + } else { + null + } +} + +fun testDependenciesOnly(block: Writable): Writable = { + testDependenciesOnly(block) } fun RustWriter.tokioTest(name: String, vararg args: Any, block: Writable) { @@ -252,6 +294,13 @@ class TestWriterDelegator( fun generatedFiles() = fileManifest.files.map { baseDir.relativize(it) } } +/** + * Generate a newtest module + * + * This should only be used in test code—the generated module name will be something like `tests_123` + */ +fun RustCrate.testModule(block: Writable) = lib { withInlineModule(RustModule.inlineTests(safeName("tests")), block) } + fun FileManifest.printGeneratedFiles() { this.files.forEach { path -> println("file:///$path") @@ -424,8 +473,11 @@ fun RustCrate.integrationTest(name: String, writable: Writable) = this.withFile( fun TestWriterDelegator.unitTest(test: Writable): TestWriterDelegator { lib { - unitTest(safeName("test")) { - test(this) + val name = safeName("test") + withInlineModule(RustModule.inlineTests(name)) { + unitTest(name) { + test(this) + } } } return this diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/InlineDependencyTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/InlineDependencyTest.kt index 2f1d170e5e..9664ce1dac 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/InlineDependencyTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/InlineDependencyTest.kt @@ -34,12 +34,15 @@ internal class InlineDependencyTest { fun `locate dependencies from the inlineable module`() { val dep = InlineDependency.idempotencyToken() val testProject = TestWorkspace.testProject() - testProject.unitTest { + testProject.lib { rustTemplate( """ - use #{idempotency}::uuid_v4; - let res = uuid_v4(0); - assert_eq!(res, "00000000-0000-4000-8000-000000000000"); + ##[test] + fn idempotency_works() { + use #{idempotency}::uuid_v4; + let res = uuid_v4(0); + assert_eq!(res, "00000000-0000-4000-8000-000000000000"); + } """, "idempotency" to dep.toType(), diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegatorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegatorTest.kt index 5be3d0a898..c9407372d7 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegatorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenDelegatorTest.kt @@ -10,11 +10,12 @@ import org.junit.jupiter.api.Test import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.CratesIo import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope.Compile +import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope.Dev class CodegenDelegatorTest { @Test fun testMergeDependencyFeatures() { - val merged = mergeDependencyFeatures( + val merged = listOf( CargoDependency("A", CratesIo("1"), Compile, optional = false, features = setOf()), CargoDependency("A", CratesIo("1"), Compile, optional = false, features = setOf("f1")), @@ -26,8 +27,7 @@ class CodegenDelegatorTest { CargoDependency("C", CratesIo("3"), Compile, optional = true, features = setOf()), CargoDependency("C", CratesIo("3"), Compile, optional = true, features = setOf()), - ).shuffled(), - ) + ).shuffled().mergeDependencyFeatures() merged shouldBe setOf( CargoDependency("A", CratesIo("1"), Compile, optional = false, features = setOf("f1", "f2")), @@ -35,4 +35,19 @@ class CodegenDelegatorTest { CargoDependency("C", CratesIo("3"), Compile, optional = true, features = setOf()), ) } + + @Test + fun testMergeIdenticalFeatures() { + val merged = listOf( + CargoDependency("A", CratesIo("1"), Compile), + CargoDependency("A", CratesIo("1"), Dev), + CargoDependency("B", CratesIo("1"), Compile), + CargoDependency("B", CratesIo("1"), Dev, features = setOf("a", "b")), + ).mergeIdenticalTestDependencies() + merged shouldBe setOf( + CargoDependency("A", CratesIo("1"), Compile), + CargoDependency("B", CratesIo("1"), Compile), + CargoDependency("B", CratesIo("1"), Dev, features = setOf("a", "b")), + ) + } } diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt index a0f8200c50..c55ec2ea2a 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt @@ -25,6 +25,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.renderWithModelBuilder import software.amazon.smithy.rust.codegen.core.testutil.testCodegenContext +import software.amazon.smithy.rust.codegen.core.testutil.testModule import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.lookup @@ -278,7 +279,7 @@ class InstantiatorTest { ) val project = TestWorkspace.testProject() - project.withModule(RustModule.Model) { + project.testModule { unitTest("blob_inputs_are_binary_data") { withBlock("let blob = ", ";") { sut.render( diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerHttpSensitivityGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerHttpSensitivityGeneratorTest.kt index 9a8ae01cab..a90acec6e4 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerHttpSensitivityGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerHttpSensitivityGeneratorTest.kt @@ -14,6 +14,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest +import software.amazon.smithy.rust.codegen.core.testutil.testModule import software.amazon.smithy.rust.codegen.core.testutil.unitTest import software.amazon.smithy.rust.codegen.core.util.getTrait import software.amazon.smithy.rust.codegen.core.util.inputShape @@ -57,7 +58,7 @@ class ServerHttpSensitivityGeneratorTest { assertEquals(listOf("query_b"), (querySensitivity as QuerySensitivity.NotSensitiveMapValue).queryKeys) val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("query_closure") { rustTemplate( """ @@ -70,6 +71,7 @@ class ServerHttpSensitivityGeneratorTest { ) } } + testProject.compileAndTest() } @@ -104,7 +106,7 @@ class ServerHttpSensitivityGeneratorTest { querySensitivity as QuerySensitivity.SensitiveMapValue val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("query_params_closure") { rustTemplate( """ @@ -152,7 +154,7 @@ class ServerHttpSensitivityGeneratorTest { assert((querySensitivity as QuerySensitivity.NotSensitiveMapValue).queryKeys.isEmpty()) val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("query_params_special_closure") { rustTemplate( """ @@ -200,7 +202,7 @@ class ServerHttpSensitivityGeneratorTest { querySensitivity as QuerySensitivity.SensitiveMapValue val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("query_params_special_closure") { rustTemplate( """ @@ -277,7 +279,7 @@ class ServerHttpSensitivityGeneratorTest { assertEquals(null, (headerData as HeaderSensitivity.NotSensitiveMapValue).prefixHeader) val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("header_closure") { rustTemplate( """ @@ -325,7 +327,7 @@ class ServerHttpSensitivityGeneratorTest { assertEquals("prefix-", (headerData as HeaderSensitivity.SensitiveMapValue).prefixHeader) val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("prefix_headers_closure") { rustTemplate( """ @@ -408,7 +410,7 @@ class ServerHttpSensitivityGeneratorTest { assertEquals("prefix-", asMapValue.prefixHeader) val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("prefix_headers_special_closure") { rustTemplate( """ @@ -462,7 +464,7 @@ class ServerHttpSensitivityGeneratorTest { assert(!asSensitiveMapValue.keySensitive) val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("prefix_headers_special_closure") { rustTemplate( """ @@ -514,7 +516,7 @@ class ServerHttpSensitivityGeneratorTest { assertEquals(listOf(1, 2), labelData.labelIndexes) val testProject = TestWorkspace.testProject(serverTestSymbolProvider(model)) - testProject.lib { + testProject.testModule { unitTest("uri_closure") { rustTemplate( """ diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt index 1bfbd26e2f..96ea000cdb 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiatorTest.kt @@ -144,37 +144,40 @@ class ServerInstantiatorTest { nestedStruct.serverRenderWithModelBuilder(model, symbolProvider, this) UnionGenerator(model, symbolProvider, this, union).render() - unitTest("server_instantiator_test") { - withBlock("let result = ", ";") { - sut.render(this, structure, data) - } - - rust( - """ - use std::collections::HashMap; - use aws_smithy_types::{DateTime, Document}; - - let expected = MyStructRequired { - str: "".to_owned(), - primitive_int: 0, - int: 0, - ts: DateTime::from_secs(0), - byte: 0, - union: NestedUnion::Struct(NestedStruct { + withInlineModule(RustModule.inlineTests()) { + unitTest("server_instantiator_test") { + withBlock("let result = ", ";") { + sut.render(this, structure, data) + } + + rust( + """ + use std::collections::HashMap; + use aws_smithy_types::{DateTime, Document}; + use super::*; + + let expected = MyStructRequired { str: "".to_owned(), - num: 0, - }), - structure: NestedStruct { - str: "".to_owned(), - num: 0, - }, - list: Vec::new(), - map: HashMap::new(), - doc: Document::Object(HashMap::new()), - }; - assert_eq!(result, expected); - """, - ) + primitive_int: 0, + int: 0, + ts: DateTime::from_secs(0), + byte: 0, + union: NestedUnion::Struct(NestedStruct { + str: "".to_owned(), + num: 0, + }), + structure: NestedStruct { + str: "".to_owned(), + num: 0, + }, + list: Vec::new(), + map: HashMap::new(), + doc: Document::Object(HashMap::new()), + }; + assert_eq!(result, expected); + """, + ) + } } } project.compileAndTest()