Skip to content

Commit 72a417c

Browse files
authored
chore: new shared testing features (#1514)
1 parent 4ae289c commit 72a417c

File tree

5 files changed

+125
-2
lines changed

5 files changed

+125
-2
lines changed

build.gradle.kts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ allprojects {
6161
}
6262
}
6363

64-
if (testJavaVersion != null) {
65-
tasks.withType<Test> {
64+
tasks.withType<Test>().configureEach {
65+
if (testJavaVersion != null) {
6666
// JDK8 tests fail with out of memory sometimes, not sure why...
6767
maxHeapSize = "2g"
6868
val toolchains = project.extensions.getByType<JavaToolchainService>()
@@ -72,6 +72,13 @@ allprojects {
7272
},
7373
)
7474
}
75+
76+
if (testJavaVersion == null || testJavaVersion.asInt() >= 9) {
77+
// Required to enable reflective access in testing.
78+
// See smithy-kotlin/runtime/testing/jvm/src/aws/smithy/kotlin/runtime/testing/SystemOverrides.kt for more
79+
// info.
80+
jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED")
81+
}
7582
}
7683

7784
// Enables running `./gradlew allDeps` to get a comprehensive list of dependencies for every subproject

runtime/testing/common/src/aws/smithy/kotlin/runtime/testing/TestAnnotations.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ public expect annotation class AfterAll()
2727
@Target(AnnotationTarget.FUNCTION)
2828
@Retention(AnnotationRetention.RUNTIME)
2929
public expect annotation class IgnoreNative()
30+
31+
@Target(AnnotationTarget.CLASS)
32+
@Retention(AnnotationRetention.RUNTIME)
33+
public expect annotation class TestInstance(val value: TestLifecycle)
34+
35+
public expect enum class TestLifecycle {
36+
PER_CLASS,
37+
PER_METHOD,
38+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.testing
6+
7+
import java.util.*
8+
9+
@PublishedApi
10+
internal val editableEnvVars: MutableMap<String, String> by lazy {
11+
val systemEnv = System.getenv()
12+
val classOfMap = systemEnv::class.java
13+
14+
@Suppress("UNCHECKED_CAST")
15+
classOfMap
16+
.getDeclaredField("m")
17+
.apply { isAccessible = true }
18+
.get(systemEnv) as MutableMap<String, String>
19+
}
20+
21+
@PublishedApi
22+
internal fun <K, V> MutableMap<K, V>.setAll(newEntries: Map<K, V>) {
23+
clear()
24+
putAll(newEntries)
25+
}
26+
27+
/**
28+
* Runs the given [block] with modified environment variables. The variables given in [newVars] are merged onto the
29+
* existing environment variables, adding values that did not exist previously and updating values which did exist.
30+
* After the block completes, environment variables will be restored to their previous values. This method may be nested
31+
* multiple times to apply/restore hierarchical overrides.
32+
*
33+
* **Caution**: This method is not thread-safe as environment variables are global for a JVM instance.
34+
*
35+
* # How it works
36+
*
37+
* Normally environment variables cannot be modified within a JVM instance so this function makes use of private
38+
* reflection to gain access to the underlying mutable map used by [System.getenv]. At runtime, this requires
39+
* `--add-opens=java.base/java.util=ALL-UNNAMED` to be passed to the `java` executable on JDK 9+. For instance:
40+
*
41+
* ```sh
42+
* java \
43+
* -jar my-testing-jar \
44+
* -classpath testing-jvm.X.Y.Z.jar,... \
45+
* --add-opens=java.base/java.util=ALL-UNNAMED
46+
* ```
47+
*
48+
* In **smithy-kotlin** and **aws-sdk-kotlin** this is handled in the root scripts by adding a JVM arg to all JVM test
49+
* JARs (whether they rely on this module or not).
50+
*
51+
* @param newVars The new environment variables to merge onto the existing environment variables
52+
* @param block The block to execute with updated environment variables
53+
*/
54+
public suspend inline fun <T> withEnvVars(
55+
newVars: Map<String, String>,
56+
crossinline block: suspend () -> T,
57+
): T {
58+
val originalVars = System.getenv()
59+
editableEnvVars.setAll(originalVars + newVars)
60+
return try {
61+
block()
62+
} finally {
63+
editableEnvVars.setAll(originalVars)
64+
}
65+
}
66+
67+
/**
68+
* Runs the given [block] with modified JVM system properties. The properties given in [newProps] are merged onto the
69+
* existing system properties, adding values that did not exist previously and updating values which did exist. After
70+
* the block completes, system properties will be restored to their previous values. This method may be nested multiple
71+
* times to apply/restore hierarchical overrides.
72+
*
73+
* **Caution**: This method is not thread-safe as system properties are global for a JVM instance.
74+
*
75+
* @param newProps The new system properties to merge onto the existing system properties.
76+
* @param block The block to execute with updated system properties
77+
*/
78+
public suspend inline fun <T> withSystemProperties(
79+
newProps: Map<String, String>,
80+
crossinline block: suspend () -> T,
81+
): T {
82+
val originalProps = System.getProperties()
83+
val replacementProps = Properties().apply {
84+
putAll(originalProps)
85+
putAll(newProps)
86+
}
87+
System.setProperties(replacementProps)
88+
return try {
89+
block()
90+
} finally {
91+
System.setProperties(originalProps)
92+
}
93+
}

runtime/testing/jvm/src/aws/smithy/kotlin/runtime/testing/TestAnnotationsJVM.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
package aws.smithy.kotlin.runtime.testing
7+
import org.junit.jupiter.api.TestInstance
78
import org.junit.jupiter.api.condition.DisabledOnOs
89
import org.junit.jupiter.api.condition.OS
910

@@ -17,3 +18,7 @@ public actual typealias AfterAll = org.junit.jupiter.api.AfterAll
1718
@Retention(AnnotationRetention.RUNTIME)
1819
@Target(AnnotationTarget.FUNCTION)
1920
public actual annotation class IgnoreNative { /* no-op on JVM */ }
21+
22+
public actual typealias TestInstance = TestInstance
23+
24+
public actual typealias TestLifecycle = TestInstance.Lifecycle

runtime/testing/native/src/aws/smithy/kotlin/runtime/testing/TestAnnotationsNative.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,12 @@ public actual typealias BeforeAll = kotlin.test.BeforeClass
1010
public actual typealias AfterAll = kotlin.test.AfterClass
1111

1212
public actual typealias IgnoreNative = kotlin.test.Ignore
13+
14+
// Effectively ignored on K/N. This is fine because we only use this to enable non-static @BeforeAll/@AfterAll methods
15+
// on JVM.
16+
public actual annotation class TestInstance(actual val value: TestLifecycle)
17+
18+
public actual enum class TestLifecycle {
19+
PER_CLASS,
20+
PER_METHOD,
21+
}

0 commit comments

Comments
 (0)