diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a5482109..627cc8ee 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -55,7 +55,7 @@ jobs: --health-retries 5 strategy: matrix: - java: ['21', '17', '11', '8'] + java: ['25', '21', '17', '11', '8'] runs-on: ubuntu-latest name: Run test suite with Java ${{ matrix.java }} needs: build-and-check diff --git a/agent/bin/test_projects b/agent/bin/test_projects index 541d20e1..c1bce35f 100755 --- a/agent/bin/test_projects +++ b/agent/bin/test_projects @@ -87,7 +87,23 @@ else install_petclinic "land-of-apps/spring-petclinic" old-java-support fi -patch -N -p1 -d build/fixtures/spring-petclinic < test/petclinic/pom.patch +# Select the appropriate patch file based on Java version +if is_java 25; then + PATCH_FILE="test/petclinic/pom-java25.patch" +else + PATCH_FILE="test/petclinic/pom.patch" +fi + +# Apply patch, but only ignore if already applied (not other failures) +if ! patch -N -p1 -d build/fixtures/spring-petclinic < "$PATCH_FILE" 2>&1 | tee /tmp/patch_output.txt; then + if grep -q "Reversed (or previously applied) patch detected" /tmp/patch_output.txt; then + echo "Patch already applied, continuing..." + else + echo "ERROR: Patch failed to apply!" + cat /tmp/patch_output.txt + exit 1 + fi +fi install_scala_test_app diff --git a/agent/build.gradle b/agent/build.gradle index b479bbb7..c7dceb37 100644 --- a/agent/build.gradle +++ b/agent/build.gradle @@ -52,7 +52,7 @@ dependencies { implementation 'com.alibaba:fastjson:1.2.83' implementation "org.javassist:javassist:${javassistVersion}" implementation 'org.reflections:reflections:0.10.2' - implementation 'net.bytebuddy:byte-buddy:1.14.10' + implementation 'net.bytebuddy:byte-buddy:1.18.4' implementation 'org.apache.commons:commons-lang3:3.20.0' implementation 'commons-io:commons-io:2.15.1' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.1' diff --git a/agent/src/main/java/com/appland/appmap/reflect/HttpHeaders.java b/agent/src/main/java/com/appland/appmap/reflect/HttpHeaders.java index e7af8558..5695bb75 100644 --- a/agent/src/main/java/com/appland/appmap/reflect/HttpHeaders.java +++ b/agent/src/main/java/com/appland/appmap/reflect/HttpHeaders.java @@ -32,13 +32,14 @@ default Enumeration getHeaderNames() { /** * Non-standard utility method. Retrieves all headers. + * Header names are normalized to lowercase since HTTP headers are case-insensitive. */ default Map getHeaders() { HashMap headers = new HashMap<>(); for (Enumeration e = getHeaderNames(); e.hasMoreElements();) { String headerName = (String) e.nextElement(); - headers.put(headerName, this.getHeader(headerName)); + headers.put(headerName.toLowerCase(), this.getHeader(headerName)); } return headers; diff --git a/agent/test/classloading/app/build.gradle b/agent/test/classloading/app/build.gradle index b29e9257..8e783789 100644 --- a/agent/test/classloading/app/build.gradle +++ b/agent/test/classloading/app/build.gradle @@ -35,14 +35,6 @@ dependencies { implementation files(appmapJar) } -// Apply a specific Java toolchain to ease working on different environments. -java { - toolchain { - languageVersion = JavaLanguageVersion.of(8) - } -} - - application { // Define the main class for the application. mainClass = 'com.appland.appmap.test.fixture.Runner' diff --git a/agent/test/classloading/lib/build.gradle b/agent/test/classloading/lib/build.gradle index e0b2609f..1949f0d3 100644 --- a/agent/test/classloading/lib/build.gradle +++ b/agent/test/classloading/lib/build.gradle @@ -18,10 +18,3 @@ repositories { dependencies { } - -// Apply a specific Java toolchain to ease working on different environments. -java { - toolchain { - languageVersion = JavaLanguageVersion.of(8) - } -} diff --git a/agent/test/classloading/settings.gradle b/agent/test/classloading/settings.gradle index a18cde8a..82be1b4a 100644 --- a/agent/test/classloading/settings.gradle +++ b/agent/test/classloading/settings.gradle @@ -1,16 +1,2 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/8.1.1/userguide/multi_project_builds.html - */ - -plugins { - // Apply the foojay-resolver plugin to allow automatic download of JDKs - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' -} - rootProject.name = 'classloading' include 'app', 'lib' diff --git a/agent/test/gretty-tomcat/build.gradle b/agent/test/gretty-tomcat/build.gradle index 4d8da04f..fe6000d5 100644 --- a/agent/test/gretty-tomcat/build.gradle +++ b/agent/test/gretty-tomcat/build.gradle @@ -1,8 +1,25 @@ +buildscript { + def grettyVersion = '4.1.6' + + // Gretty 4 does not support Gradle 9 and above + if (GradleVersion.current() >= GradleVersion.version('9.0')) { + grettyVersion = '5.0.1' + } + + repositories { + mavenCentral() + } + dependencies { + classpath "org.gretty:gretty:$grettyVersion" + } +} + plugins { id 'war' - id 'org.gretty' version '4.1.6' } +apply plugin: 'org.gretty' + repositories { mavenCentral() } diff --git a/agent/test/helper.bash b/agent/test/helper.bash index edc2b985..95a0206a 100644 --- a/agent/test/helper.bash +++ b/agent/test/helper.bash @@ -151,7 +151,13 @@ find_annotation_jar() { export ANNOTATION_JAR="$(find_annotation_jar)" # absolute gradle wrapper path (in the same directory as this file) -GRADLE_WRAPPER="$TOP_LEVEL/agent/test/gradlew" +if is_java 25; then + # use 9.1 for newer java + GRADLE_WRAPPER="$TOP_LEVEL/gradlew" +else + # this is 8.5 for older javas + GRADLE_WRAPPER="$TOP_LEVEL/agent/test/gradlew" +fi # Shared gradle wrapper function gradlew() { diff --git a/agent/test/intellij/intellij.bats b/agent/test/intellij/intellij.bats old mode 100644 new mode 100755 index 2fa1841b..35f497f5 --- a/agent/test/intellij/intellij.bats +++ b/agent/test/intellij/intellij.bats @@ -6,6 +6,7 @@ init_plugin() { setup_file() { is_java 17 || skip "needs Java 17" + is_java 25 && skip "incompatible with Java 25" export AGENT_JAR="$(find_agent_jar)" diff --git a/agent/test/jdbc/build.gradle b/agent/test/jdbc/build.gradle index 162848ca..fb3c9c76 100644 --- a/agent/test/jdbc/build.gradle +++ b/agent/test/jdbc/build.gradle @@ -1,16 +1,32 @@ +buildscript { + def isModern = JavaVersion.current() >= JavaVersion.VERSION_25 + project.ext { + useJakarta = isModern + springBootVersion = isModern ? '3.1.4' : '2.7.0' + javaVersion = isModern ? JavaVersion.VERSION_17 : JavaVersion.VERSION_1_8 + } + + repositories { + mavenCentral() + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.springBootVersion}" + } +} + plugins { - id 'org.springframework.boot' version '2.7.0' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'io.spring.dependency-management' version '1.1.7' id 'java' } +apply plugin: 'org.springframework.boot' + group = 'com.example' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '1.8' -// suppress warnings about source compatibility -tasks.withType(JavaCompile) { - options.compilerArgs << '-Xlint:-options' +java { + sourceCompatibility = javaVersion + targetCompatibility = javaVersion } repositories { @@ -22,10 +38,26 @@ dependencies { runtimeOnly 'com.h2database:h2' runtimeOnly 'com.oracle.database.jdbc:ojdbc8:21.9.0.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } def appmapJar = "$System.env.AGENT_JAR" +// Spring boot 3.x uses Jakarta EE namespaces +task transformSources(type: Copy) { + from 'src/main/java' + into "$buildDir/generated-sources/transformed" + + if (useJakarta) { + filter { line -> + line.replaceAll('javax.persistence', 'jakarta.persistence') + } + } +} + +sourceSets.main.java.srcDirs = [transformSources.destinationDir] +compileJava.dependsOn transformSources + test { useJUnitPlatform() if (System.env.ORACLE_URL) { diff --git a/agent/test/petclinic/petclinic-tests.bats b/agent/test/petclinic/petclinic-tests.bats index bfa11557..c7c6f7ab 100644 --- a/agent/test/petclinic/petclinic-tests.bats +++ b/agent/test/petclinic/petclinic-tests.bats @@ -43,9 +43,11 @@ run_petclinic_test() { run cat ./tmp/appmap/junit/org_springframework_samples_petclinic_${TEST_NAME}_testOwnerDetails.appmap.json assert_success - # Thread 1 is the test runner's main thread. Check to make sure that parent_id - # of the "return" event matches the id of the "call" event. - assert_json_eq '.events | map(select(.thread_id == 1)) | ((.[0].event == "call" and .[1].event == "return") and (.[1].parent_id == .[0].id))' "true" + # Find the test runner's main thread (the one with testOwnerDetails method). + # Check to make sure that parent_id of the "return" event matches the id of the "call" event. + # Note: In Java 21 the main thread is typically thread 1, but in Java 25 it can be thread 3. + local main_thread_id=$(jq -r '[.events[] | select(.method_id == "testOwnerDetails")][0].thread_id' ./tmp/appmap/junit/org_springframework_samples_petclinic_${TEST_NAME}_testOwnerDetails.appmap.json) + assert_json_eq ".events | map(select(.thread_id == ${main_thread_id})) | ((.[0].event == \"call\" and .[1].event == \"return\") and (.[1].parent_id == .[0].id))" "true" } @test "extra properties in appmap.yml are ignored when config is loaded" { diff --git a/agent/test/petclinic/petclinic.bats b/agent/test/petclinic/petclinic.bats index 31b956b9..9288d718 100644 --- a/agent/test/petclinic/petclinic.bats +++ b/agent/test/petclinic/petclinic.bats @@ -211,7 +211,8 @@ setup() { assert_success stop_recording - assert_json_eq '.events[] | .http_server_response | .headers["Content-Type"]' "text/html;charset=UTF-8" + # HTTP headers are case-insensitive and are normalized to lowercase + assert_json_eq '.events[] | .http_server_response | .headers["content-type"]' "text/html;charset=UTF-8" } @test "recordings capture elapsed time" { diff --git a/agent/test/petclinic/pom-java25.patch b/agent/test/petclinic/pom-java25.patch new file mode 100644 index 00000000..45d16060 --- /dev/null +++ b/agent/test/petclinic/pom-java25.patch @@ -0,0 +1,55 @@ +diff --git a/pom.xml b/pom.xml +index fb38cc3..6cb8530 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -65,6 +65,23 @@ + + org.springframework.boot + spring-boot-starter-webmvc ++ ++ ++ org.springframework.boot ++ spring-boot-starter-tomcat ++ ++ ++ ++ ++ com.appland ++ annotation ++ LATEST ++ system ++ ${env.ANNOTATION_JAR} ++ ++ ++ com.fasterxml.jackson.core ++ jackson-databind + + + +@@ -407,5 +424,26 @@ + + + ++ ++ tomcat ++ ++ true ++ ++ ++ ++ org.springframework.boot ++ spring-boot-starter-tomcat ++ ++ ++ ++ ++ jetty ++ ++ ++ org.springframework.boot ++ spring-boot-starter-jetty ++ ++ ++ + + diff --git a/agent/test/test-frameworks/build.gradle b/agent/test/test-frameworks/build.gradle index 0097825b..629a1be8 100644 --- a/agent/test/test-frameworks/build.gradle +++ b/agent/test/test-frameworks/build.gradle @@ -36,6 +36,9 @@ def addAgentArgs = [ ] task test_junit(type: Test) { + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath + testLogging { events TestLogEvent.STANDARD_OUT } @@ -44,6 +47,9 @@ task test_junit(type: Test) { } task test_testng(type: Test) { + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath + useTestNG() testLogging { showStandardStreams = true @@ -57,21 +63,14 @@ task test_testng(type: Test) { def jdk8Dependency = 'org.testng:testng:7.5.1' def jdk11Dependency = 'org.testng:testng:7.9.0' -tasks.register('setTestDependencies') { - doLast { - def jdkVersion = javaToolchains.launcherFor(java.toolchain).get().metadata.languageVersion.asInt() - if (jdkVersion <= 8) { - dependencies { - testImplementation jdk8Dependency - } - } else { - dependencies { - testImplementation jdk11Dependency - } - } +// Add TestNG dependency during configuration phase (not execution phase) +def jdkVersion = System.getProperty('java.version').split('\\.')[0] as Integer +if (jdkVersion <= 8) { + dependencies { + testImplementation jdk8Dependency + } +} else { + dependencies { + testImplementation jdk11Dependency } -} - -tasks.named("compileTestJava") { - dependsOn 'setTestDependencies' }