Clarifications about various test tasks (#358)

In addition this commit replaces eager tasks generation for running tests using different java versions with rule based task created on-demand.
This commit is contained in:
Nikita Salnikov-Tarnovski 2020-04-29 04:11:17 +03:00 committed by GitHub
parent fbbccd31a6
commit 6e1c0436da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 130 deletions

View File

@ -3,6 +3,38 @@
Pull requests for bug fixes are welcome, but before submitting new features or changes to current functionality [open an issue](https://github.com/open-telemetry/opentelemetry-auto-instr-java/issues/new)
and discuss your ideas or propose the changes you wish to make. After a resolution is reached a PR can be submitted for review.
### Testing
#### Java versions
Open Telemetry Auto Instrumentation's minimal supported version is java 7.
All jar files that we produce, unless noted otherwise, have bytecode compatible with java 7 runtime.
In addition to that we test our code with all later java versions as well: from 8 to 14.
Some libraries that we auto-instrument may have higher minimal requirements.
In this case we compile and test corresponding auto-instrumentation with higher java version as required by library.
The resulting classes will have higher bytecode level,
but as it matches library's java version, no runtime problem arise.
#### Instrumentation tests
Executing `./gradlew instrumentation:test` will run tests for all supported auto-instrumentations
using that java version which runs the Gradle build itself.
These tests usually use the minimal supported version of the instrumented library.
In addition to that each instrumentation has a separate test set called `latestDepTest`.
It was created by [Gradle test sets plugin](https://github.com/unbroken-dome/gradle-testsets-plugin).
It uses the very same tests as before, but declares a dynamic dependency on the latest available version of this library.
You can run them all by executing `./gradlew latestDepTest`.
#### Executing tests with specific java version
In order to run tests on a specific java version, just execute `./gradlew testJava7` (or `testJava11` or `latestDepTestJava14` etc).
Then Gradle task rule will kick in and do the following:
* check, if Gradle already runs on a java with required version
* if not, look for an environment variable named `JAVA_N_HOME`, where `N` is the requested java version
* if Gradle could not found requested java version, then build will fail
* Gradle will now find all corresponding test tasks and configure them to use java executable of the requested version.
This works both for tasks named `test` and `latestDepTest`.
But currently does not work for other custom test tasks, such as those created by test sets plugin.
### Code Style
This project includes a `.editorconfig` file for basic editor settings. This file is supported by most common text editors.

View File

@ -10,6 +10,7 @@ apply from: "$rootDir/gradle/spotbugs.gradle"
def applyCodeCoverage = !(
project.path.startsWith(":smoke-tests") ||
//TODO why some tests fail on java 11 if jacoco is present?
project.path == ":opentelemetry-auto" ||
project.path == ":load-generator" ||
project.path.startsWith(":benchmark") ||
@ -234,21 +235,22 @@ if (project.hasProperty("removeJarVersionNumbers") && removeJarVersionNumbers) {
}
}
if (project.parent && project.parent.ext.has("javaExecutableVersionCache")) {
project.ext.javaExecutableVersionCache = project.parent.ext.javaExecutableVersionCache
} else {
project.ext.javaExecutableVersionCache = [:]
if (!rootProject.ext.has("javaExecutableVersionCache")) {
rootProject.ext.javaExecutableVersionCache = [:]
}
JavaVersion getJavaExecutableVersion(String path) {
def cache = project.ext.javaExecutableVersionCache
if (cache.containsKey(path)) {
return cache.get(path)
/**
* Returns version of java from a given java home.
*/
JavaVersion getJavaHomeVersion(String javaHome) {
def cache = rootProject.ext.javaExecutableVersionCache
if (cache.containsKey(javaHome)) {
return cache.get(javaHome)
}
new ByteArrayOutputStream().withStream { stream ->
exec {
commandLine = [path, "-version"]
commandLine = [toExecutable(javaHome), "-version"]
errorOutput = stream
}
def output = stream.toString()
@ -257,20 +259,16 @@ JavaVersion getJavaExecutableVersion(String path) {
def matcher = line =~ /^(?:java|openjdk) version "([^"]+)"/
if (matcher) {
def version = JavaVersion.toVersion(matcher.group(1))
cache.put(path, version)
cache.put(javaHome, version)
return version
}
}
// Getting here means we didn't find a line matching the version pattern.
throw new GradleScriptException("Cannot determine java version. Executable: ${path}, output: ${output}", null)
throw new GradleScriptException("Cannot determine java version. Executable: ${javaHome}, output: ${output}", null)
}
}
ext {
getJavaExecutableVersion = this.&getJavaExecutableVersion
}
def isJavaVersionAllowed(JavaVersion version) {
if (project.hasProperty('minJavaVersionForTests') && project.getProperty('minJavaVersionForTests').compareTo(version) > 0) {
return false
@ -281,78 +279,68 @@ def isJavaVersionAllowed(JavaVersion version) {
return true
}
def isJdkForced(String javaName) {
return (project.hasProperty('forceJdk') && project.getProperty('forceJdk').contains(javaName))
/**
* For a given java home return the location of java executable
*/
static String toExecutable(String javaHome) {
return Objects.requireNonNull(javaHome) + "/bin/java"
}
// Disable default test tasks if current JVM doesn't match version requirements
tasks.withType(Test).configureEach {
if (name.endsWith("Generated")) {
return
/**
* Returns java home for a given version or {@code null} if not found
*/
String findJavaHome(JavaVersion version) {
def javaHome = System.getenv("JAVA_${version.majorVersion}_HOME")
if (javaHome == null) {
return null
}
// Always run all tests that are runnable on JVM used for compilation
onlyIf { isJavaVersionAllowed(JavaVersion.current()) }
def foundVersion = getJavaHomeVersion(javaHome)
return version == foundVersion ? javaHome : null
}
// Generate tests tasks for all provided JVMs
for (def env : System.getenv().entrySet()) {
def matcher = env.key =~ /JAVA_([^_]+)_HOME/
if (!matcher) {
continue
}
def javaName = matcher.group(1)
def javaHome = env.value
def javaPath = "$javaHome/bin/java"
def javaVersion = getJavaExecutableVersion(javaPath)
ext {
findJavaHome = this.&findJavaHome
}
// This is slightly complicated because we need to dereference symlinks to make sure
// we are considering same JVM implementation
def currentJavaHome = new File(System.getProperty("java.home")).toPath().toRealPath()
if (currentJavaHome.endsWith("jre")) {
currentJavaHome = currentJavaHome.parent
}
if (currentJavaHome == new File(javaHome).toPath().toRealPath()) {
// Skip JVM implementation we are running gradle on
continue
}
def addTestRule(String testTaskName) {
def prefix = testTaskName + "Java"
tasks.addRule("Pattern: $prefix<Version>: Runs tests using given java version") { String taskName ->
if (taskName.startsWith(prefix)) {
def requestedJavaVersion = JavaVersion.toVersion(taskName - prefix)
def gradleJavaVersion = JavaVersion.current()
def parentTask = task "testJava${javaName}"() {
group = 'Verification'
description = "Run tests for Java ${javaName}"
}
tasks.check.dependsOn parentTask
if (gradleJavaVersion != requestedJavaVersion) {
def javaHomeForTests = findJavaHome(requestedJavaVersion)
if (javaHomeForTests != null) {
tasks.withType(Test).all {
//if (name.endsWith("Generated")) {
if (!name.equals("test")) {
// The way we're copying the test doesn't currently work with "test-sets" generated tests.
return
}
def clonedTask = it
def newTask = task "${clonedTask.name}Java${javaName}Generated"(type: clonedTask.class) {
description "Runs $clonedTask.name under java ${javaName}"
executable = javaPath
if (javaName == "7") {
// Disable JIT for this method. Sometimes Java7 JVM crashes trying to compile it.
jvmArgs '-XX:CompileCommand=exclude,net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor::onParameterizedType'
}
onlyIf { isJavaVersionAllowed(javaVersion) || isJdkForced(javaName) }
if (applyCodeCoverage) {
jacoco {
// Disable jacoco for additional JVM tests to speed things up a bit
enabled = false
tasks.withType(Test).all {
// minJavaVersionForTests property, which specifies java version requirements for tests,
// currently does not work with custom source sets created by org.unbroken-dome.test-sets plugin.
// Thus we are forced to ignore all of them
// TODO solve this problem. We want to run custom test sets with all java versions as well
if (name != testTaskName) {
return
}
executable = toExecutable(javaHomeForTests)
enabled = isJavaVersionAllowed(requestedJavaVersion)
if (requestedJavaVersion.isJava7()) {
// Disable JIT for this method. Sometimes Java7 JVM crashes trying to compile it.
jvmArgs '-XX:CompileCommand=exclude,net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Substitutor::onParameterizedType'
}
}
} else {
throw new BuildCancelledException("Requested java version $requestedJavaVersion not found")
}
}
}
parentTask.dependsOn newTask
task(taskName, dependsOn: testTaskName)
}
}
}
addTestRule("test")
addTestRule("latestDepTest")
tasks.withType(Test).configureEach {
if (project.findProperty("enableJunitPlatform") == true) {
useJUnitPlatform()
@ -362,8 +350,9 @@ tasks.withType(Test).configureEach {
// This value is quite big because with lower values (3 mins) we were experiencing large number of false positives
timeout = Duration.ofMinutes(15)
// Disable all tests if current JVM doesn't match version requirements
// Disable all tests if skipTests property was specified
onlyIf { !project.rootProject.hasProperty("skipTests") }
enabled = isJavaVersionAllowed(JavaVersion.current()) && !project.rootProject.hasProperty("skipTests")
}
plugins.withType(BasePlugin) {

View File

@ -12,60 +12,22 @@ jar {
}
}
// If the current JDK version (the one running gradle) is < 9, we need to find a version >= 9
// to compile this project. java.gradle creates a map of java executables
// called "javaExecutableVersionCache" pulled from the environment.
// This loops over the cache to find a usable jdk.
// Since this project is the only one that requires a version above Java 8
// it's special cased here instead of putting a generic version matcher in java.gradle
if (JavaVersion.VERSION_1_9.compareTo(JavaVersion.current()) > 0) {
def targetJavaHome
compileMain_java9Java {
options.fork = true
options.forkOptions.javaHome = file(findJavaHome(JavaVersion.VERSION_1_9))
// Find a compatible version in the cache
ext.javaExecutableVersionCache.find { key, value ->
if (JavaVersion.VERSION_1_9.compareTo(value) <= 0) {
// JAVA_HOME/bin/java -> JAVA_HOME
targetJavaHome = file(key).parentFile.parentFile
return true
}
return false
}
if (targetJavaHome != null) {
// if we found a compatible jdk, compile the src/main/java9 folder with it
compileMain_java9Java {
options.fork = true
options.forkOptions.javaHome = targetJavaHome
options.compilerArgs = ['--module-path', classpath.asPath]
options.sourcepath = files(sourceSets.main_java9.java.srcDirs)
}
} else {
compileMain_java9Java {
enabled = false
}
}
options.compilerArgs = ['--module-path', classpath.asPath]
options.sourcepath = files(sourceSets.main_java9.java.srcDirs)
}
// java.gradle generates a test task per jdk and assigns the test task its own java executable
// For each Test task, this loop creates a jlink image using the test's executable
// At the end, we have 1 jlink image per JVM: each one used by a testXXXGenerated task
tasks.withType(Test).each {
def javaExecutable = it.executable
def javaVersion = getJavaExecutableVersion(javaExecutable)
//This test will be automatically disabled by `java.gradle` if tried to run on java earlier than 9
test {
doFirst {
def specificJDKHome = file(executable).parentFile.parent
def jlinkExecutable = specificJDKHome + "/bin/jlink"
def jdkModulesPath = specificJDKHome + "/jmods"
def generatedImageDir = "${buildDir}/${it.name}image"
// Only Java 9 and above have jlink
if (JavaVersion.VERSION_1_9.compareTo(javaVersion) > 0) {
return
}
// JAVA_HOME/bin/java -> JAVA_HOME
def specificJDKHome = file(javaExecutable).parentFile.parent
def jlinkExecutable = specificJDKHome + "/bin/jlink"
def jdkModulesPath = specificJDKHome + "/jmods"
def generatedImageDir = "${buildDir}/${it.name}image"
it.doFirst {
delete generatedImageDir
// Run the jlink command to create the image
@ -74,10 +36,10 @@ tasks.withType(Test).each {
'--add-modules', 'java.instrument,io.opentelemetry.smoketest.moduleapp',
"--module-path", "${jdkModulesPath}:" + jar.archiveFile.get().toString(), "--output", generatedImageDir
}
}
it.jvmArgs "-Dio.opentelemetry.smoketest.module.image=${generatedImageDir}"
it.dependsOn jar
jvmArgs "-Dio.opentelemetry.smoketest.module.image=${generatedImageDir}"
}
dependsOn jar
}
dependencies {

View File

@ -39,8 +39,6 @@ repositories {
description = 'trace-java'
tasks.register("latestDepTest")
wrapper {
distributionType = Wrapper.DistributionType.ALL
}