Skip to content

Commit 4255dc0

Browse files
authored
Introduces testJvmConstraint Gradle extension to replace extra properties (#9892)
* chore: Starts extracting testJvm configuration to another script plugin * chore: Moved test jvm constraint logic to convention plugin * chore: Moved additional test suite constraints to new test task extension # Conflicts: # dd-java-agent/instrumentation/aerospike-4.0/build.gradle # dd-java-agent/instrumentation/armeria/armeria-jetty-1.24/build.gradle * chore: Configure test jvm constraint on test task, added project wide test jvm constraints * chore: configure source set via extension method * chore: Use source set configuration * chore: Renames jvmConstraints to testJvmConstraints * fix: profiling-controller-oracle tests could never run, introduce an includeJdk directive * refactor: Simplify test jvm enablement code * chore: Handle test sources compilation override * chore: testJvm is optional * refactor: small rename job * refactor: Convert remaining usage of min/max jdk properties to our extension * refactor: Renames min|maxJavaVersionForTests to min|maxJavaVersion * chore: Make spotless happy * fix: Apply test sources compiler settings via common compilerConfiguration * fix: Make vertx compile test with Java 11 * fix: PR comments
1 parent 0ab0769 commit 4255dc0

File tree

119 files changed

+1167
-735
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+1167
-735
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import datadog.gradle.plugin.testJvmConstraints.ProvideJvmArgsOnJvmLauncherVersion
2+
import datadog.gradle.plugin.testJvmConstraints.TestJvmConstraintsExtension
3+
import datadog.gradle.plugin.testJvmConstraints.TestJvmConstraintsExtension.Companion.TEST_JVM_CONSTRAINTS
4+
import datadog.gradle.plugin.testJvmConstraints.TestJvmSpec
5+
import datadog.gradle.plugin.testJvmConstraints.isJavaVersionAllowed
6+
import datadog.gradle.plugin.testJvmConstraints.isTestJvmAllowed
7+
8+
plugins {
9+
java
10+
}
11+
12+
val projectExtension = extensions.create<TestJvmConstraintsExtension>(TEST_JVM_CONSTRAINTS)
13+
14+
val testJvmSpec = TestJvmSpec(project)
15+
16+
tasks.withType<Test>().configureEach {
17+
if (extensions.findByName(TEST_JVM_CONSTRAINTS) != null) {
18+
return@configureEach
19+
}
20+
21+
inputs.property("testJvm", testJvmSpec.testJvmProperty).optional(true)
22+
23+
val taskExtension = project.objects.newInstance<TestJvmConstraintsExtension>().also {
24+
configureConventions(it, projectExtension)
25+
}
26+
27+
inputs.property("$TEST_JVM_CONSTRAINTS.allowReflectiveAccessToJdk", taskExtension.allowReflectiveAccessToJdk).optional(true)
28+
inputs.property("$TEST_JVM_CONSTRAINTS.excludeJdk", taskExtension.excludeJdk)
29+
inputs.property("$TEST_JVM_CONSTRAINTS.includeJdk", taskExtension.includeJdk)
30+
inputs.property("$TEST_JVM_CONSTRAINTS.forceJdk", taskExtension.forceJdk)
31+
inputs.property("$TEST_JVM_CONSTRAINTS.minJavaVersion", taskExtension.minJavaVersion).optional(true)
32+
inputs.property("$TEST_JVM_CONSTRAINTS.maxJavaVersion", taskExtension.maxJavaVersion).optional(true)
33+
34+
extensions.add(TEST_JVM_CONSTRAINTS, taskExtension)
35+
36+
configureTestJvm(taskExtension)
37+
}
38+
39+
/**
40+
* Provide arguments if condition is met.
41+
*/
42+
fun Test.conditionalJvmArgs(
43+
applyFromVersion: JavaVersion,
44+
jvmArgsToApply: List<String>,
45+
additionalCondition: Provider<Boolean> = project.providers.provider { true }
46+
) {
47+
jvmArgumentProviders.add(
48+
ProvideJvmArgsOnJvmLauncherVersion(
49+
this,
50+
applyFromVersion,
51+
jvmArgsToApply,
52+
additionalCondition
53+
)
54+
)
55+
}
56+
57+
/**
58+
* Configure the jvm launcher of the test task and ensure the test task
59+
* can be run with the test task launcher.
60+
*/
61+
private fun Test.configureTestJvm(extension: TestJvmConstraintsExtension) {
62+
if (testJvmSpec.javaTestLauncher.isPresent) {
63+
javaLauncher = testJvmSpec.javaTestLauncher
64+
onlyIf("Allowed or forced JDK") {
65+
extension.isTestJvmAllowed(testJvmSpec)
66+
}
67+
} else {
68+
onlyIf("Is current Daemon JVM allowed") {
69+
extension.isJavaVersionAllowed(JavaVersion.current())
70+
}
71+
}
72+
73+
// temporary workaround when using Java16+: some tests require reflective access to java.lang/java.util
74+
conditionalJvmArgs(
75+
JavaVersion.VERSION_16,
76+
listOf(
77+
"--add-opens=java.base/java.lang=ALL-UNNAMED",
78+
"--add-opens=java.base/java.util=ALL-UNNAMED"
79+
),
80+
extension.allowReflectiveAccessToJdk
81+
)
82+
}
83+
84+
// Jacoco plugin is not applied on every project
85+
pluginManager.withPlugin("org.gradle.jacoco") {
86+
tasks.withType<Test>().configureEach {
87+
// Disable jacoco for additional 'testJvm' tests to speed things up a bit
88+
if (testJvmSpec.javaTestLauncher.isPresent) {
89+
extensions.configure<JacocoTaskExtension> {
90+
isEnabled = false
91+
}
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Configures the convention, this tells Gradle where to look for values.
98+
*
99+
* Currently, the extension is still configured to look at project's _extra_ properties.
100+
*/
101+
private fun Test.configureConventions(
102+
taskExtension: TestJvmConstraintsExtension,
103+
projectExtension: TestJvmConstraintsExtension
104+
) {
105+
taskExtension.minJavaVersion.convention(projectExtension.minJavaVersion
106+
.orElse(providers.provider { project.findProperty("${name}MinJavaVersionForTests") as? JavaVersion })
107+
.orElse(providers.provider { project.findProperty("minJavaVersion") as? JavaVersion })
108+
)
109+
taskExtension.maxJavaVersion.convention(projectExtension.maxJavaVersion
110+
.orElse(providers.provider { project.findProperty("${name}MaxJavaVersionForTests") as? JavaVersion })
111+
.orElse(providers.provider { project.findProperty("maxJavaVersion") as? JavaVersion })
112+
)
113+
taskExtension.forceJdk.convention(projectExtension.forceJdk
114+
.orElse(providers.provider {
115+
@Suppress("UNCHECKED_CAST")
116+
project.findProperty("forceJdk") as? List<String> ?: emptyList()
117+
})
118+
)
119+
taskExtension.excludeJdk.convention(projectExtension.excludeJdk
120+
.orElse(providers.provider {
121+
@Suppress("UNCHECKED_CAST")
122+
project.findProperty("excludeJdk") as? List<String> ?: emptyList()
123+
})
124+
)
125+
taskExtension.allowReflectiveAccessToJdk.convention(projectExtension.allowReflectiveAccessToJdk
126+
.orElse(providers.provider { project.findProperty("allowReflectiveAccessToJdk") as? Boolean })
127+
)
128+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.api.JavaVersion
4+
import org.gradle.api.provider.Provider
5+
import org.gradle.api.tasks.Input
6+
import org.gradle.api.tasks.Internal
7+
import org.gradle.api.tasks.Optional
8+
import org.gradle.api.tasks.testing.Test
9+
import org.gradle.process.CommandLineArgumentProvider
10+
11+
class ProvideJvmArgsOnJvmLauncherVersion(
12+
@get:Internal
13+
val test: Test,
14+
15+
@get:Input
16+
val applyFromVersion: JavaVersion,
17+
18+
@get:Input
19+
val jvmArgsToApply: List<String>,
20+
21+
@get:Input
22+
@get:Optional
23+
val additionalCondition: Provider<Boolean>
24+
) : CommandLineArgumentProvider {
25+
26+
override fun asArguments(): Iterable<String> {
27+
val launcherVersion = test.javaLauncher
28+
.map { JavaVersion.toVersion(it.metadata.languageVersion.asInt()) }
29+
.orElse(JavaVersion.current())
30+
.get()
31+
32+
return if (launcherVersion.isCompatibleWith(applyFromVersion) && additionalCondition.getOrElse(true)) {
33+
jvmArgsToApply
34+
} else {
35+
emptyList()
36+
}
37+
}
38+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.api.JavaVersion
4+
import org.gradle.api.provider.ListProperty
5+
import org.gradle.api.provider.Property
6+
7+
interface TestJvmConstraintsExtension {
8+
/**
9+
* Sets an explicit minimum bound to allowed JDK version
10+
*/
11+
val minJavaVersion: Property<JavaVersion>
12+
13+
/**
14+
* Sets an explicit maximum bound to allowed JDK version
15+
*/
16+
val maxJavaVersion: Property<JavaVersion>
17+
18+
/**
19+
* List of allowed JDK names (passed through the `testJvm` property).
20+
*/
21+
val forceJdk: ListProperty<String>
22+
23+
/**
24+
* List of included JDK names (passed through the `testJvm` property).
25+
*/
26+
val includeJdk: ListProperty<String>
27+
28+
/**
29+
* List of excluded JDK names (passed through the `testJvm` property).
30+
*/
31+
val excludeJdk: ListProperty<String>
32+
33+
/**
34+
* Indicate if the test JVM allows reflective access to JDK
35+
* `java.base/java.lang` and `java.base/java.util` modules by
36+
* openning them.
37+
*/
38+
val allowReflectiveAccessToJdk: Property<Boolean>
39+
40+
companion object {
41+
const val TEST_JVM_CONSTRAINTS = "testJvmConstraints"
42+
}
43+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.api.JavaVersion
4+
import org.gradle.api.logging.Logging
5+
6+
private val logger = Logging.getLogger("TestJvmConstraintsUtils")
7+
8+
internal fun TestJvmConstraintsExtension.isJavaVersionAllowed(version: JavaVersion): Boolean {
9+
return withinAllowedRange(version)
10+
}
11+
12+
internal fun TestJvmConstraintsExtension.isTestJvmAllowed(testJvmSpec: TestJvmSpec): Boolean {
13+
val testJvmName = testJvmSpec.normalizedTestJvm.get()
14+
15+
val included = includeJdk.get()
16+
if (included.isNotEmpty() && included.none { it.equals(testJvmName, ignoreCase = true) }) {
17+
return false
18+
}
19+
20+
val excluded = excludeJdk.get()
21+
if (excluded.isNotEmpty() && excluded.any { it.equals(testJvmName, ignoreCase = true) }) {
22+
return false
23+
}
24+
25+
val launcherVersion = JavaVersion.toVersion(testJvmSpec.javaTestLauncher.get().metadata.languageVersion.asInt())
26+
if (!withinAllowedRange(launcherVersion) && forceJdk.get().none { it.equals(testJvmName, ignoreCase = true) }) {
27+
return false
28+
}
29+
30+
return true
31+
}
32+
33+
private fun TestJvmConstraintsExtension.withinAllowedRange(currentJvmVersion: JavaVersion): Boolean {
34+
val definedMin = minJavaVersion.isPresent
35+
val definedMax = maxJavaVersion.isPresent
36+
37+
if (definedMin && (minJavaVersion.get()) > currentJvmVersion) {
38+
logger.info("isWithinAllowedRange returns false b/o minProp=${minJavaVersion.get()} is defined and greater than version=$currentJvmVersion")
39+
return false
40+
}
41+
42+
if (definedMax && (maxJavaVersion.get()) < currentJvmVersion) {
43+
logger.info("isWithinAllowedRange returns false b/o maxProp=${maxJavaVersion.get()} is defined and lower than version=$currentJvmVersion")
44+
return false
45+
}
46+
47+
return true
48+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.kotlin.dsl.support.serviceOf
4+
import org.gradle.api.GradleException
5+
import org.gradle.api.Project
6+
import org.gradle.jvm.toolchain.JavaLauncher
7+
import org.gradle.jvm.toolchain.JavaToolchainService
8+
import java.nio.file.Files
9+
import java.nio.file.Path
10+
import java.nio.file.Paths
11+
12+
13+
class TestJvmSpec(val project: Project) {
14+
companion object {
15+
const val TEST_JVM = "testJvm"
16+
}
17+
18+
private val currentJavaHomePath = project.providers.systemProperty("java.home").map { it.normalizeToJDKJavaHome() }
19+
20+
val testJvmProperty = project.providers.gradleProperty(TEST_JVM)
21+
22+
val normalizedTestJvm = testJvmProperty.map { testJvm ->
23+
if (testJvm.isBlank()) {
24+
throw GradleException("testJvm property is blank")
25+
}
26+
27+
// "stable" is calculated as the largest X found in JAVA_X_HOME
28+
if (testJvm == "stable") {
29+
val javaVersions = project.providers.environmentVariablesPrefixedBy("JAVA_").map { javaHomes ->
30+
javaHomes
31+
.filter { it.key.matches(Regex("^JAVA_[0-9]+_HOME$")) }
32+
.map { Regex("^JAVA_(\\d+)_HOME$").find(it.key)!!.groupValues[1].toInt() }
33+
}.get()
34+
35+
if (javaVersions.isEmpty()) {
36+
throw GradleException("No valid JAVA_X_HOME environment variables found.")
37+
}
38+
39+
javaVersions.max().toString()
40+
} else {
41+
testJvm
42+
}
43+
}.map { project.logger.info("normalized testJvm: $it"); it }
44+
45+
val testJvmHomePath = normalizedTestJvm.map {
46+
if (Files.exists(Paths.get(it))) {
47+
it.normalizeToJDKJavaHome()
48+
} else {
49+
val matcher = Regex("([a-zA-Z]*)([0-9]+)").find(it)
50+
if (matcher == null) {
51+
throw GradleException("Unable to find launcher for Java '$it'. It needs to match '([a-zA-Z]*)([0-9]+)'.")
52+
}
53+
val testJvmEnv = "JAVA_${it}_HOME"
54+
val testJvmHome = project.providers.environmentVariable(testJvmEnv).orNull
55+
if (testJvmHome == null) {
56+
throw GradleException("Unable to find launcher for Java '$it'. Have you set '$testJvmEnv'?")
57+
}
58+
59+
testJvmHome.normalizeToJDKJavaHome()
60+
}
61+
}.map { project.logger.info("testJvm home path: $it"); it }
62+
63+
val javaTestLauncher = project.providers.zip(testJvmHomePath, normalizedTestJvm) { testJvmHome, testJvm ->
64+
// Only change test JVM if it's not the one we are running the gradle build with
65+
if (currentJavaHomePath.get() == testJvmHome) {
66+
project.providers.provider<JavaLauncher?> { null }
67+
} else {
68+
// This is using internal APIs
69+
val jvmSpec = org.gradle.jvm.toolchain.internal.SpecificInstallationToolchainSpec(
70+
project.serviceOf<org.gradle.api.internal.provider.PropertyFactory>(),
71+
project.file(testJvmHome)
72+
)
73+
74+
// The provider always says that a value is present so we need to wrap it for proper error messages
75+
project.javaToolchains.launcherFor(jvmSpec).orElse(project.providers.provider {
76+
throw GradleException("Unable to find launcher for Java $testJvm. Does '$testJvmHome' point to a JDK?")
77+
})
78+
}
79+
}.flatMap { it }.map { project.logger.info("testJvm launcher: ${it.executablePath}"); it }
80+
81+
private fun String.normalizeToJDKJavaHome(): Path {
82+
val javaHome = project.file(this).toPath().toRealPath()
83+
return if (javaHome.endsWith("jre")) javaHome.parent else javaHome
84+
}
85+
86+
private val Project.javaToolchains: JavaToolchainService get() =
87+
extensions.getByName("javaToolchains") as JavaToolchainService
88+
}

dd-java-agent/agent-bootstrap/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jmh {
7070
}
7171

7272
tasks.withType(Test).configureEach {
73-
configureJvmArgs(
73+
conditionalJvmArgs(
7474
it,
7575
JavaVersion.VERSION_16,
7676
['--add-opens', 'java.base/java.net=ALL-UNNAMED'] // for HostNameResolverForkedTest

dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
// Set properties before any plugins get loaded
2-
ext {
3-
minJavaVersionForTests = JavaVersion.VERSION_11
4-
// By default tests with be compiled for `minJavaVersionForTests` version,
5-
// but in this case we would like to avoid this since we would like to run with ZULU8
6-
skipSettingTestJavaVersion = true
7-
excludeJdk = ['SEMERU11', 'SEMERU17']
8-
}
9-
101
apply from: "$rootDir/gradle/java.gradle"
112
apply plugin: 'idea'
123

4+
tracerJava {
5+
addSourceSetFor(JavaVersion.VERSION_11) {
6+
// By default tests with be compiled for `minJavaVersion` version,
7+
// but in this case we would like to avoid this since we would like to run with ZULU8
8+
applyForTestSources = false
9+
}
10+
}
11+
12+
testJvmConstraints {
13+
minJavaVersion = JavaVersion.VERSION_11
14+
excludeJdk = ['SEMERU11', 'SEMERU17']
15+
}
16+
1317
minimumBranchCoverage = 0.5
1418
minimumInstructionCoverage = 0.7
1519

0 commit comments

Comments
 (0)