Skip to content

Commit 7d8ddb9

Browse files
authored
chore: move codegen test utilities to new smithy-kotlin-codegen-testutils package (#765)
1 parent 8629920 commit 7d8ddb9

File tree

9 files changed

+165
-86
lines changed

9 files changed

+165
-86
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "9ab15fa5-76b7-4bf0-9931-eaa50fd57a19",
3+
"type": "misc",
4+
"description": "Move test utilities out of **smithy-kotlin-codegen** package into new **smithy-kotlin-codegen-testutils** package. This eliminates the need for the codegen package to declare runtime dependencies on JUnit and other test packages."
5+
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ include(":runtime:tracing:tracing-core")
5959
include(":runtime:utils")
6060

6161
include(":smithy-kotlin-codegen")
62+
include(":smithy-kotlin-codegen-testutils")
6263

6364
include(":tests")
6465
include(":tests:benchmarks:aws-signing-benchmarks")
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
plugins {
6+
kotlin("jvm")
7+
jacoco
8+
`maven-publish`
9+
}
10+
11+
description = "Provides common test utilities for Smithy-Kotlin code generation"
12+
extra["displayName"] = "Smithy :: Kotlin :: Codegen Utils"
13+
extra["moduleName"] = "software.amazon.smithy.kotlin.codegen.test"
14+
15+
val sdkVersion: String by project
16+
group = "software.amazon.smithy.kotlin"
17+
version = sdkVersion
18+
19+
val smithyVersion: String by project
20+
val kotlinVersion: String by project
21+
val junitVersion: String by project
22+
val kotestVersion: String by project
23+
val jsoupVersion: String by project
24+
25+
dependencies {
26+
implementation(kotlin("stdlib-jdk8"))
27+
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
28+
api(project(":smithy-kotlin-codegen"))
29+
30+
// Test dependencies
31+
implementation("org.junit.jupiter:junit-jupiter:$junitVersion")
32+
implementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
33+
implementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
34+
implementation("org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion")
35+
}
36+
37+
// unlike the runtime, smithy-kotlin codegen package is not expected to run on Android...we can target 1.8
38+
tasks.compileKotlin {
39+
kotlinOptions.jvmTarget = "1.8"
40+
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
41+
}
42+
43+
tasks.compileTestKotlin {
44+
kotlinOptions.jvmTarget = "1.8"
45+
}
46+
47+
// Reusable license copySpec
48+
val licenseSpec = copySpec {
49+
from("${project.rootDir}/LICENSE")
50+
from("${project.rootDir}/NOTICE")
51+
}
52+
53+
// Configure jars to include license related info
54+
tasks.jar {
55+
metaInf.with(licenseSpec)
56+
inputs.property("moduleName", project.name)
57+
manifest {
58+
attributes["Automatic-Module-Name"] = project.name
59+
}
60+
}
61+
62+
val sourcesJar by tasks.creating(Jar::class) {
63+
group = "publishing"
64+
description = "Assembles Kotlin sources jar"
65+
classifier = "sources"
66+
from(sourceSets.getByName("main").allSource)
67+
}
68+
69+
tasks.test {
70+
useJUnitPlatform()
71+
testLogging {
72+
events("passed", "skipped", "failed")
73+
showStandardStreams = true
74+
showStackTraces = true
75+
showExceptions = true
76+
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
77+
}
78+
}
79+
80+
publishing {
81+
publications {
82+
create<MavenPublication>("codegen-testutils") {
83+
from(components["java"])
84+
artifact(sourcesJar)
85+
}
86+
}
87+
}
88+
89+
apply(from = rootProject.file("gradle/publish.gradle"))
Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,9 @@ import software.amazon.smithy.model.shapes.*
2121
import software.amazon.smithy.model.traits.TimestampFormatTrait
2222
import software.amazon.smithy.utils.StringUtils
2323

24-
/**
25-
* This file houses test classes and functions relating to the code generator (protocols, serializers, etc)
26-
*
27-
* Items contained here should be relatively high-level, utilizing all members of codegen classes, Smithy, and
28-
* anything else necessary for test functionality.
29-
*/
24+
// This file houses test classes and functions relating to the code generator (protocols, serializers, etc)
25+
// Items contained here should be relatively high-level, utilizing all members of codegen classes, Smithy, and
26+
// anything else necessary for test functionality.
3027

3128
/**
3229
* Container for type instances necessary for tests
@@ -37,8 +34,8 @@ data class TestContext(
3734
val generator: ProtocolGenerator,
3835
)
3936

40-
// Execute the codegen and return the generated output
41-
internal fun testRender(
37+
/** Execute the codegen and return the generated output */
38+
fun testRender(
4239
members: List<MemberShape>,
4340
renderFn: (List<MemberShape>, KotlinWriter) -> Unit,
4441
): String {
@@ -47,8 +44,8 @@ internal fun testRender(
4744
return writer.toString()
4845
}
4946

50-
// Drive codegen for serialization of a given shape
51-
internal fun codegenSerializerForShape(model: Model, shapeId: String, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): String {
47+
/** Drive codegen for serialization of a given shape */
48+
fun codegenSerializerForShape(model: Model, shapeId: String, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): String {
5249
val ctx = model.newTestContext()
5350

5451
val op = ctx.generationCtx.model.expectShape(ShapeId.from(shapeId))
@@ -62,8 +59,8 @@ internal fun codegenSerializerForShape(model: Model, shapeId: String, location:
6259
}
6360
}
6461

65-
// Drive codegen for deserialization of a given shape
66-
internal fun codegenDeserializerForShape(model: Model, shapeId: String, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): String {
62+
/** Drive codegen for deserialization of a given shape */
63+
fun codegenDeserializerForShape(model: Model, shapeId: String, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): String {
6764
val ctx = model.newTestContext()
6865
val op = ctx.generationCtx.model.expectShape(ShapeId.from(shapeId))
6966

@@ -77,8 +74,8 @@ internal fun codegenDeserializerForShape(model: Model, shapeId: String, location
7774
}
7875
}
7976

80-
// Drive codegen for serializer of a union of a given shape
81-
internal fun codegenUnionSerializerForShape(model: Model, shapeId: String): String {
77+
/** Drive codegen for serializer of a union of a given shape */
78+
fun codegenUnionSerializerForShape(model: Model, shapeId: String): String {
8279
val ctx = model.newTestContext()
8380

8481
val bindingIndex = HttpBindingIndex.of(ctx.generationCtx.model)
@@ -98,8 +95,8 @@ internal fun codegenUnionSerializerForShape(model: Model, shapeId: String): Stri
9895
}
9996
}
10097

101-
// Retrieves Response Document members for HttpTrait-enabled protocols
102-
internal fun TestContext.responseMembers(shape: Shape, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): List<MemberShape> {
98+
/** Retrieves response document members for HttpTrait-enabled protocols */
99+
fun TestContext.responseMembers(shape: Shape, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): List<MemberShape> {
103100
val bindingIndex = HttpBindingIndex.of(this.generationCtx.model)
104101
val responseBindings = bindingIndex.getResponseBindings(shape)
105102

@@ -109,8 +106,8 @@ internal fun TestContext.responseMembers(shape: Shape, location: HttpBinding.Loc
109106
.map { it.member }
110107
}
111108

112-
// Retrieves Request Document members for HttpTrait-enabled protocols
113-
internal fun TestContext.requestMembers(shape: Shape, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): List<MemberShape> {
109+
/** Retrieves Request Document members for HttpTrait-enabled protocols */
110+
fun TestContext.requestMembers(shape: Shape, location: HttpBinding.Location = HttpBinding.Location.DOCUMENT): List<MemberShape> {
114111
val bindingIndex = HttpBindingIndex.of(this.generationCtx.model)
115112
val responseBindings = bindingIndex.getRequestBindings(shape)
116113

@@ -120,21 +117,21 @@ internal fun TestContext.requestMembers(shape: Shape, location: HttpBinding.Loca
120117
.map { it.member }
121118
}
122119

123-
internal fun TestContext.toGenerationContext(): GenerationContext =
120+
fun TestContext.toGenerationContext(): GenerationContext =
124121
GenerationContext(generationCtx.model, generationCtx.symbolProvider, generationCtx.settings, generator)
125122

126123
fun <T : Shape> TestContext.toRenderingContext(writer: KotlinWriter, forShape: T? = null): RenderingContext<T> =
127124
toGenerationContext().toRenderingContext(writer, forShape)
128125

129-
// A HttpProtocolClientGenerator for testing
130-
internal class TestProtocolClientGenerator(
126+
/** An HttpProtocolClientGenerator for testing */
127+
class TestProtocolClientGenerator(
131128
ctx: ProtocolGenerator.GenerationContext,
132129
features: List<ProtocolMiddleware>,
133130
httpBindingResolver: HttpBindingResolver,
134131
) : HttpProtocolClientGenerator(ctx, features, httpBindingResolver)
135132

136-
// A HttpBindingProtocolGenerator for testing (nothing is rendered for serializing/deserializing payload bodies)
137-
internal class MockHttpProtocolGenerator : HttpBindingProtocolGenerator() {
133+
/** An HttpBindingProtocolGenerator for testing (nothing is rendered for serializing/deserializing payload bodies) */
134+
class MockHttpProtocolGenerator : HttpBindingProtocolGenerator() {
138135
override val defaultTimestampFormat: TimestampFormatTrait.Format = TimestampFormatTrait.Format.EPOCH_SECONDS
139136
override fun getProtocolHttpBindingResolver(model: Model, serviceShape: ServiceShape): HttpBindingResolver =
140137
HttpTraitResolver(model, serviceShape, ProtocolContentTypes.consistent("application/json"))
@@ -192,7 +189,7 @@ internal class MockHttpProtocolGenerator : HttpBindingProtocolGenerator() {
192189
}
193190
}
194191

195-
// Create a test harness with all necessary codegen types
192+
/** Create a test harness with all necessary codegen types */
196193
fun codegenTestHarnessForModelSnippet(
197194
generator: ProtocolGenerator,
198195
namespace: String = TestModelDefault.NAMESPACE,
@@ -219,8 +216,10 @@ data class CodegenTestHarness(
219216
val protocol: String,
220217
)
221218

222-
// Create and use a writer to drive codegen from a function taking a writer.
223-
// Strip off comment and package preamble.
219+
/**
220+
* Create and use a writer to drive codegen from a function taking a writer.
221+
* Strip off comment and package preamble.
222+
*/
224223
fun generateCode(generator: (KotlinWriter) -> Unit): String {
225224
val packageDeclaration = "some-unique-thing-that-will-never-be-codegened"
226225
val writer = KotlinWriter(packageDeclaration)

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/test/LangTestUtils.kt renamed to smithy-kotlin-codegen-testutils/src/main/kotlin/software/amazon/smithy/kotlin/codegen/test/LangTestUtils.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ package software.amazon.smithy.kotlin.codegen.test
66

77
import kotlin.test.assertEquals
88

9-
/**
10-
* This file houses test functions specific to Kotlin language particulars.
11-
*/
9+
// This file houses test functions specific to Kotlin language particulars.
1210

13-
internal fun String.assertBalancedBracesAndParens() {
11+
fun String.assertBalancedBracesAndParens() {
1412
// sanity check since we are testing fragments
1513
var openBraces = 0
1614
var closedBraces = 0

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/test/MiscTestUtils.kt renamed to smithy-kotlin-codegen-testutils/src/main/kotlin/software/amazon/smithy/kotlin/codegen/test/MiscTestUtils.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ import io.kotest.matchers.string.shouldContain
88
import io.kotest.matchers.string.shouldContainOnlyOnce
99
import kotlin.test.assertNotNull
1010

11-
/**
12-
* This file houses miscellaneous test functions that do not fall under other
13-
* test categories specified in this package.
14-
*/
11+
// This file houses miscellaneous test functions that do not fall under other test categories specified in this package.
1512

16-
// Will generate an IDE diff in the case of a test assertion failure.
13+
/** Generate an IDE diff in the case of a test assertion failure. */
1714
fun String?.shouldContainOnlyOnceWithDiff(expected: String) {
1815
try {
1916
this.shouldContainOnlyOnce(expected)
@@ -22,8 +19,8 @@ fun String?.shouldContainOnlyOnceWithDiff(expected: String) {
2219
}
2320
}
2421

25-
// Will generate an IDE diff in the case of a test assertion failure.
26-
internal fun String?.shouldContainWithDiff(expected: String) {
22+
/** Generate an IDE diff in the case of a test assertion failure. */
23+
fun String?.shouldContainWithDiff(expected: String) {
2724
try {
2825
this.shouldContain(expected)
2926
} catch (originalException: AssertionError) {
@@ -46,15 +43,15 @@ fun String.shouldContain(expectedStart: String, expectedEnd: String) {
4643
fun <T> List<T>.indexOfSublistOrNull(sublist: List<T>, startFrom: Int = 0): Int? =
4744
drop(startFrom).windowed(sublist.size).indexOf(sublist)
4845

49-
// Format a multi-line string suitable for comparison with codegen, defaults to one level of indention.
46+
/** Format a multi-line string suitable for comparison with codegen, defaults to one level of indention. */
5047
fun String.formatForTest(indent: String = " ") =
5148
trimIndent()
5249
.prependIndent(indent)
5350
.split('\n')
5451
.map { if (it.isBlank()) "" else it }
5552
.joinToString(separator = "\n") { it }
5653

57-
internal fun String.stripCodegenPrefix(packageName: String = "test"): String {
54+
fun String.stripCodegenPrefix(packageName: String = "test"): String {
5855
val packageDirective = "package $packageName"
5956
return this.substring(this.indexOf(packageDirective) + packageDirective.length).trim()
6057
}
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import software.amazon.smithy.codegen.core.SymbolProvider
99
import software.amazon.smithy.kotlin.codegen.KotlinCodegenPlugin
1010
import software.amazon.smithy.kotlin.codegen.KotlinSettings
1111
import software.amazon.smithy.kotlin.codegen.core.*
12+
import software.amazon.smithy.kotlin.codegen.inferService
1213
import software.amazon.smithy.kotlin.codegen.model.OperationNormalizer
1314
import software.amazon.smithy.kotlin.codegen.model.shapes
1415
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
@@ -21,12 +22,8 @@ import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer
2122
import software.amazon.smithy.model.validation.ValidatedResultException
2223
import java.net.URL
2324

24-
/**
25-
* This file houses classes and functions to help with testing with Smithy models.
26-
*
27-
* These functions should be relatively low-level and deal directly with types provided
28-
* by smithy-codegen.
29-
*/
25+
// This file houses classes and functions to help with testing with Smithy models.
26+
// These functions should be relatively low-level and deal directly with types provided by smithy-codegen.
3027

3128
/**
3229
* Unless necessary to deviate for test reasons, the following literals should be used in test models:
@@ -35,7 +32,7 @@ import java.net.URL
3532
* namespace: TestDefault.NAMESPACE
3633
* service name: "Test"
3734
*/
38-
internal object TestModelDefault {
35+
object TestModelDefault {
3936
const val SMITHY_IDL_VERSION = "1"
4037
const val MODEL_VERSION = "1.0.0"
4138
const val NAMESPACE = "com.test"
@@ -64,7 +61,7 @@ private fun Model.applyKotlinCodegenTransforms(serviceShapeId: String?): Model {
6461
/**
6562
* Load and initialize a model from a Java resource URL
6663
*/
67-
internal fun URL.toSmithyModel(serviceShapeId: String? = null): Model {
64+
fun URL.toSmithyModel(serviceShapeId: String? = null): Model {
6865
val model = Model.assembler()
6966
.addImport(this)
7067
.discoverModels()
@@ -98,7 +95,7 @@ fun String.toSmithyModel(sourceLocation: String? = null, serviceShapeId: String?
9895
*
9996
* NOTE: this is used for debugging / unit test generation, please don't remove.
10097
*/
101-
internal fun Model.toSmithyIDL(): String {
98+
fun Model.toSmithyIDL(): String {
10299
val builtInModelIds = setOf("smithy.test.smithy", "aws.auth.smithy", "aws.protocols.smithy", "aws.api.smithy")
103100
val ms: SmithyIdlModelSerializer = SmithyIdlModelSerializer.builder().build()
104101
val node = ms.serialize(this)
@@ -167,7 +164,7 @@ fun Model.defaultSettings(
167164
generateDefaultBuildFiles: Boolean = false,
168165
): KotlinSettings {
169166
val serviceId = if (serviceName == null) {
170-
KotlinSettings.inferService(this)
167+
this.inferService()
171168
} else {
172169
this.getShape(ShapeId.from("$packageName#$serviceName")).getOrNull()?.id
173170
?: error("Unable to find service '$serviceName' in model.")
@@ -193,8 +190,8 @@ fun Model.defaultSettings(
193190
)
194191
}
195192

196-
// Generate a Smithy IDL model based on input parameters and source string
197-
internal fun String.generateTestModel(
193+
/** Generate a Smithy IDL model based on input parameters and source string */
194+
fun String.generateTestModel(
198195
protocol: String,
199196
namespace: String = TestModelDefault.NAMESPACE,
200197
serviceName: String = TestModelDefault.SERVICE_NAME,
@@ -256,5 +253,5 @@ fun String.prependNamespaceAndService(
256253
257254
258255
""".trimIndent() + this.trimIndent()
259-
)
256+
)
260257
}

smithy-kotlin-codegen/build.gradle.kts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ dependencies {
3232
implementation("org.jsoup:jsoup:$jsoupVersion")
3333

3434
// Test dependencies
35-
// These are not set as test dependencies so they can be shared with other modules
36-
implementation("org.junit.jupiter:junit-jupiter:$junitVersion")
37-
implementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
38-
implementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
39-
implementation("org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion")
35+
testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion")
36+
testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
37+
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
38+
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion")
39+
testImplementation(project(":smithy-kotlin-codegen-testutils"))
4040
}
4141

4242
val generateSdkRuntimeVersion by tasks.registering {

0 commit comments

Comments
 (0)