diff --git a/test/functional/buildAndPackage/build.xml b/test/functional/buildAndPackage/build.xml
new file mode 100644
index 000000000..8559411b0
--- /dev/null
+++ b/test/functional/buildAndPackage/build.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+ AdoptOpenJDK Functional tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ant version is ${ant.version}
+ ============COMPILER SETTINGS============
+ ===fork: yes
+ ===executable: ${compiler.javac}
+ ===debug: on
+ ===destdir: ${DEST}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/playlist.xml b/test/functional/buildAndPackage/playlist.xml
new file mode 100644
index 000000000..32ec8b7c7
--- /dev/null
+++ b/test/functional/buildAndPackage/playlist.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+ Adopt_HS_FeatureTests
+
+ $(JAVA_COMMAND) $(JVM_OPTIONS) -cp \
+ $(Q)$(RESOURCES_DIR)$(P)$(TESTNG)$(P)$(TEST_RESROOT)$(D)BuildAndPackagingTests.jar$(Q) org.testng.TestNG \
+ $(Q)$(TEST_RESROOT)$(D)testng.xml$(Q) -d $(REPORTDIR) -testnames CommonFeatureTests,HotspotFeatureTests -groups $(TEST_GROUP) \
+ -excludegroups $(DEFAULT_EXCLUDE); $(TEST_STATUS)
+
+
+ extended
+
+
+ functional
+
+
+ hotspot
+
+
+ adoptopenjdk
+
+
+
+ Adopt_J9_FeatureTests
+
+ $(JAVA_COMMAND) $(JVM_OPTIONS) -cp \
+ $(Q)$(RESOURCES_DIR)$(P)$(TESTNG)$(P)$(TEST_RESROOT)$(D)BuildAndPackagingTests.jar$(Q) org.testng.TestNG \
+ $(Q)$(TEST_RESROOT)$(D)testng.xml$(Q) -d $(REPORTDIR) -testnames CommonFeatureTests -groups $(TEST_GROUP) \
+ -excludegroups $(DEFAULT_EXCLUDE); $(TEST_STATUS)
+
+
+ extended
+
+
+ functional
+
+
+ openj9
+
+
+
+ CudaEnabledTest
+ $(JAVA_COMMAND) $(JVM_OPTIONS) -cp $(Q)$(RESOURCES_DIR)$(P)$(TESTNG)$(P)$(TEST_RESROOT)$(D)BuildAndPackagingTests.jar$(Q) org.testng.TestNG $(Q)$(TEST_RESROOT)$(D)testng.xml$(Q) -d $(REPORTDIR) -testnames CudaEnabledTest -groups $(TEST_GROUP) -excludegroups $(DEFAULT_EXCLUDE); \
+ $(TEST_STATUS)
+
+ extended
+
+
+ functional
+
+ os.linux
+
+ openj9
+
+
+ adoptopenjdk
+
+
+
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/src/net/adoptopenjdk/test/BundledFreetypeTest.java b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/BundledFreetypeTest.java
new file mode 100644
index 000000000..1da72c267
--- /dev/null
+++ b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/BundledFreetypeTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.adoptopenjdk.test;
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static net.adoptopenjdk.test.JdkPlatform.OperatingSystem;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Freetype needs to be bundled on Windows and macOS but should not be present on Linux or AIX.
+ *
+ * @see AdoptOpenJDK enhancement request
+ */
+@Test(groups = {"level.extended"})
+public class BundledFreetypeTest {
+
+ private static final Logger LOGGER = Logger.getLogger(BundledFreetypeTest.class.getName());
+
+ private final JdkPlatform jdkPlatform = new JdkPlatform();
+
+ @Test
+ public void freetypeOnlyBundledOnWindowsAndMacOS() throws IOException {
+ String testJdkHome = System.getenv("TEST_JDK_HOME");
+ if (testJdkHome == null) {
+ throw new AssertionError("TEST_JDK_HOME is not set");
+ }
+
+ Pattern freetypePattern = Pattern.compile("(.*)?libfreetype\\.(dll|dylib|so)$");
+ Set freetypeFiles = Files.walk(Paths.get(testJdkHome))
+ .map(Path::toString)
+ .filter(name -> freetypePattern.matcher(name).matches())
+ .collect(Collectors.toSet());
+
+ if (jdkPlatform.runsOn(OperatingSystem.MACOS) || jdkPlatform.runsOn(OperatingSystem.WINDOWS)) {
+ assertTrue(freetypeFiles.size() > 0, "Expected libfreetype to be bundled but is not.");
+ } else {
+ LOGGER.info("Found freetype-related files: " + freetypeFiles.toString());
+ assertEquals(freetypeFiles.size(), 0, "Expected libfreetype not to be bundled but it is.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/src/net/adoptopenjdk/test/CudaEnabledTest.java b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/CudaEnabledTest.java
new file mode 100644
index 000000000..9ca79bf79
--- /dev/null
+++ b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/CudaEnabledTest.java
@@ -0,0 +1,103 @@
+package net.adoptopenjdk.test;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.util.Scanner;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.log4testng.Logger;
+
+/*
+ * Tests if the Cuda functionality is enabled in this build.
+ * Fit for OpenJ9 builds on Windows, xLinux and pLinux.
+ */
+@Test(groups={ "level.extended" })
+public class CudaEnabledTest {
+
+ private static Logger logger = Logger.getLogger(CudaEnabledTest.class);
+
+ public int getJDKVersion() {
+ String javaVersion = System.getProperty("java.version");
+ if (javaVersion.startsWith("1.")) {
+ javaVersion = javaVersion.substring(2);
+ }
+ int dotIndex = javaVersion.indexOf('.');
+ int dashIndex = javaVersion.indexOf('-');
+ try {
+ return Integer.parseInt(javaVersion.substring(0, dotIndex > -1 ? dotIndex : dashIndex > -1 ? dashIndex : javaVersion.length()));
+ } catch (NumberFormatException e) {
+ System.out.println("Cannot determine System.getProperty('java.version')=" + javaVersion + "\n");
+ return -1;
+ }
+ }
+
+ @Test
+ public void testIfCudaIsEnabled() {
+
+ logger.info("Starting test to see if CUDA functionality is enabled in this build.");
+
+ //Stage 1: Find the location of the j9prt lib file.
+ String prtLibDirectory = System.getProperty("java.home");
+ String jreSubdir = "";
+ if((new File(prtLibDirectory + "/jre")).exists()) {
+ jreSubdir = "/jre";
+ }
+ if("Linux".contains(System.getProperty("os.name").split(" ")[0])) {
+ if(getJDKVersion() == 8) {
+ prtLibDirectory += jreSubdir + "/lib/amd64/compressedrefs";
+ } else {
+ prtLibDirectory += "/lib/compressedrefs";
+ }
+ }
+ //windows
+ if("Windows".contains(System.getProperty("os.name").split(" ")[0])) {
+ if(getJDKVersion() == 8) {
+ //jdk8 32:
+ prtLibDirectory += jreSubdir + "/bin/compressedrefs";
+ if(!(new File(prtLibDirectory)).exists()) {
+ //In case of a 32-bit build, or a non-cr build.
+ prtLibDirectory = System.getProperty("java.home") + jreSubdir + "/bin/default";
+ }
+ } else {
+ prtLibDirectory += "/bin/compressedrefs";
+ }
+ }
+
+ File prtDirObject = new File(prtLibDirectory);
+ Assert.assertTrue(prtDirObject.exists(), "Can't find the predicted location of the j9prt lib file. Expected location: " + prtLibDirectory);
+
+ String[] prtLibDirectoryFiles = prtDirObject.list();
+ String prtFile = null;
+ for(int x = 0 ; x < prtLibDirectoryFiles.length ; x++) {
+ if(prtLibDirectoryFiles[x].contains("j9prt")) {
+ prtFile = prtLibDirectory + "/" + prtLibDirectoryFiles[x];
+ break;
+ }
+ }
+ Assert.assertNotNull(prtFile,"Can't find the j9prt lib file in " + prtLibDirectory);
+ Assert.assertTrue((new File (prtFile)).exists(), "Found the prt file, but it doesn't exist. Tautology bug.");
+ Assert.assertTrue((new File (prtFile)).canRead(), "Found the prt file, but it can't be read. Likely a permissions bug.");
+
+ //Stage 2: Iterate through the j9prt lib file to find "cudart".
+ //If we find it, then cuda functionality is enabled on this build.
+ try {
+ BufferedReader prtFileReader = new BufferedReader(new FileReader(prtFile));
+ String oneLine = "";
+ while ((oneLine = prtFileReader.readLine()) != null) {
+ if(oneLine.contains("cudart")) {
+ logger.info("Test completed successfully.");
+ return; //Success!
+ }
+ }
+ prtFileReader.close();
+ } catch (FileNotFoundException e) {
+ Assert.fail("A file that exists could not be found. This should never happen.");
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+ Assert.fail("Cuda should be enabled on this build, but we found no evidence that this was the case.");
+ }
+
+}
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/src/net/adoptopenjdk/test/FeatureTests.java b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/FeatureTests.java
new file mode 100644
index 000000000..f7070a20d
--- /dev/null
+++ b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/FeatureTests.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.adoptopenjdk.test;
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static net.adoptopenjdk.test.JdkPlatform.Architecture;
+import static net.adoptopenjdk.test.JdkPlatform.OperatingSystem;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Tests the availability of various features like garbage collectors, flight recorder, that need to be enabled via
+ * command line flags.
+ */
+@Test(groups = {"level.extended"})
+public class FeatureTests {
+
+ private static final Logger LOGGER = Logger.getLogger(FeatureTests.class.getName());
+
+ private final JdkVersion jdkVersion = new JdkVersion();
+
+ private final JdkPlatform jdkPlatform = new JdkPlatform();
+
+ /**
+ * Tests whether Shenandoah GC is available.
+ *
+ * Shenandoah GC was enabled by default with JDK 15 (JEP 379) and backported to 11.0.9.
+ *
+ * @see JEP 379: Shenandoah: A Low-Pause-Time Garbage
+ * Collector (Production)
+ * @see JDK-8250784 (Backport)
+ * @see Shenandoah Support
+ * Overview
+ */
+ @Test
+ public void testShenandoahAvailable() {
+ String testJdkHome = System.getenv("TEST_JDK_HOME");
+ if (testJdkHome == null) {
+ throw new AssertionError("TEST_JDK_HOME is not set");
+ }
+
+ boolean shouldBePresent = false;
+ if ((jdkVersion.isNewerOrEqual(15) || jdkVersion.isNewerOrEqualSameFeature(11, 0, 9))) {
+ if (jdkPlatform.runsOn(OperatingSystem.LINUX, Architecture.AARCH64)
+ || jdkPlatform.runsOn(OperatingSystem.LINUX, Architecture.X86)
+ || jdkPlatform.runsOn(OperatingSystem.LINUX, Architecture.X64)
+ || jdkPlatform.runsOn(OperatingSystem.MACOS, Architecture.X64)
+ || jdkPlatform.runsOn(OperatingSystem.MACOS, Architecture.AARCH64)
+ || jdkPlatform.runsOn(OperatingSystem.WINDOWS, Architecture.X64)
+ || jdkPlatform.runsOn(OperatingSystem.WINDOWS, Architecture.AARCH64)
+ ) {
+ shouldBePresent = true;
+ }
+ }
+
+ LOGGER.info(String.format("Detected %s on %s, expect Shenandoah to be present: %s",
+ jdkVersion, jdkPlatform, shouldBePresent));
+
+ List command = new ArrayList<>();
+ command.add(String.format("%s/bin/java", testJdkHome));
+ command.add("-XX:+UseShenandoahGC");
+ command.add("-version");
+
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ processBuilder.inheritIO();
+
+ int retCode = processBuilder.start().waitFor();
+ if (shouldBePresent) {
+ assertEquals(retCode, 0, "Expected Shenandoah to be present but it is absent.");
+ } else {
+ assertTrue(retCode > 0, "Expected Shenandoah to be absent but it is present.");
+ }
+ } catch (InterruptedException | IOException e) {
+ throw new RuntimeException("Failed to launch JVM", e);
+ }
+ }
+
+ /**
+ * Tests whether Z Garbage Collector is available.
+ *
+ * Z Garbage Collector was enabled by default with JDK 15 (JEP 377).
+ *
+ * @see JEP 377: ZGC: A Scalable Low-Latency Garbage Collector
+ * (Production)
+ */
+ @Test
+ public void testZGCAvailable() {
+ String testJdkHome = System.getenv("TEST_JDK_HOME");
+ if (testJdkHome == null) {
+ throw new AssertionError("TEST_JDK_HOME is not set");
+ }
+
+ boolean shouldBePresent = false;
+ if (jdkVersion.isNewerOrEqual(15)) {
+ if (jdkPlatform.runsOn(OperatingSystem.LINUX, Architecture.AARCH64)
+ || jdkPlatform.runsOn(OperatingSystem.LINUX, Architecture.X64)
+ || jdkPlatform.runsOn(OperatingSystem.MACOS, Architecture.X64)
+ || jdkPlatform.runsOn(OperatingSystem.WINDOWS, Architecture.X64)
+ ) {
+ shouldBePresent = true;
+ }
+ }
+
+ LOGGER.info(String.format("Detected %s on %s, expect ZGC to be present: %s",
+ jdkVersion, jdkPlatform, shouldBePresent));
+
+ List command = new ArrayList<>();
+ command.add(String.format("%s/bin/java", testJdkHome));
+ command.add("-XX:+UseZGC");
+ command.add("-version");
+
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ processBuilder.inheritIO();
+
+ int retCode = processBuilder.start().waitFor();
+ if (shouldBePresent) {
+ assertEquals(retCode, 0, "Expected ZGC to be present but it is absent.");
+ } else {
+ assertTrue(retCode > 0, "Expected ZGC to be absent but it is present.");
+ }
+ } catch (InterruptedException | IOException e) {
+ throw new RuntimeException("Failed to launch JVM", e);
+ }
+ }
+
+ /**
+ * Tests whether JDK Flight Recorder is available.
+ *
+ * JDK Flight recorder was added to JDK 11 (JEP 328) and backported to JDK 8u262.
+ *
+ * @see JEP 328: Flight Recorder
+ * @see command = new ArrayList<>();
+ command.add(String.format("%s/bin/java", testJdkHome));
+ command.add("-XX:StartFlightRecording");
+ command.add("-version");
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ processBuilder.inheritIO();
+ int retCode = processBuilder.start().waitFor();
+ if (shouldBePresent) {
+ assertEquals(retCode, 0, "Expected JFR to be present but it is absent.");
+ } else {
+ assertTrue(retCode > 0, "Expected JFR to be absent but it is present.");
+ }
+ } catch (InterruptedException | IOException e) {
+ throw new RuntimeException("Failed to launch JVM", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/src/net/adoptopenjdk/test/JdkPlatform.java b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/JdkPlatform.java
new file mode 100644
index 000000000..dd9261637
--- /dev/null
+++ b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/JdkPlatform.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.adoptopenjdk.test;
+
+import java.util.Collection;
+import java.util.Locale;
+
+public class JdkPlatform {
+
+ private final Architecture architecture;
+
+ private final OperatingSystem operatingSystem;
+
+ public JdkPlatform() {
+ this.architecture = detectArchitecture();
+ this.operatingSystem = detectOperatingSystem();
+ }
+
+ public boolean runsOn(Architecture architecture) {
+ return architecture == this.architecture;
+ }
+
+ public boolean runsOnAnyArchitecture(Collection architectures) {
+ return architectures.contains(this.architecture);
+ }
+
+ public boolean runsOn(OperatingSystem operatingSystem) {
+ return operatingSystem == this.operatingSystem;
+ }
+
+ public boolean runsOn(OperatingSystem operatingSystem, Architecture architecture) {
+ return this.runsOn(operatingSystem) && this.runsOn(architecture);
+ }
+
+ public boolean runsOnAnyOperatingSystem(Collection operatingSystems) {
+ return operatingSystems.contains(this.operatingSystem);
+ }
+
+ @Override
+ public String toString() {
+ return this.operatingSystem.name() + "/" + this.architecture.name();
+ }
+
+ private static Architecture detectArchitecture() {
+ String arch = normalize(System.getProperty("os.arch"));
+
+ if (arch.matches("^(arm|arm32)$")) {
+ return Architecture.ARM;
+ }
+ if (arch.equals("aarch64")) {
+ return Architecture.AARCH64;
+ }
+ if (arch.equals("ppc64le")) {
+ return Architecture.PPC64LE;
+ }
+ if (arch.equals("riscv")) {
+ return Architecture.RISCV;
+ }
+ if (arch.equals("riscv64")) {
+ return Architecture.RISCV64;
+ }
+ if (arch.matches("^(sparc|sparc32)$")) {
+ return Architecture.SPARC32;
+ }
+ if (arch.matches("^(sparcv9|sparc64)$")) {
+ return Architecture.SPARC64;
+ }
+ if (arch.equals("s390x")) {
+ return Architecture.S390X;
+ }
+ if (arch.matches("^(amd64|em64t|x64|x86_64)$")) {
+ return Architecture.X64;
+ }
+ if (arch.matches("^(x86|i[3-6]86|ia32|x32)$")) {
+ return Architecture.X86;
+ }
+
+ throw new AssertionError("Unrecognized architecture: " + arch);
+ }
+
+ private static OperatingSystem detectOperatingSystem() {
+ String osName = normalize(System.getProperty("os.name"));
+
+ if (osName.contains("aix")) {
+ return OperatingSystem.AIX;
+ }
+ if (osName.contains("bsd")) {
+ return OperatingSystem.BSD;
+ }
+ if (osName.contains("linux")) {
+ return OperatingSystem.LINUX;
+ }
+ if (osName.contains("mac")) {
+ return OperatingSystem.MACOS;
+ }
+ if (osName.contains("solaris") || osName.contains("sunos")) {
+ return OperatingSystem.SOLARIS;
+ }
+ if (osName.contains("win")) {
+ return OperatingSystem.WINDOWS;
+ }
+
+ throw new AssertionError("Unrecognized operating system: " + osName);
+ }
+
+ private static String normalize(String str) {
+ if (str == null) {
+ return "";
+ }
+ return str.trim().toLowerCase(Locale.US);
+ }
+
+ enum Architecture {
+ ARM, AARCH64, PPC64LE, RISCV, RISCV64, SPARC32, SPARC64, S390X, X64, X86
+ }
+
+ enum OperatingSystem {
+ AIX, BSD, LINUX, MACOS, SOLARIS, WINDOWS
+ }
+}
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/src/net/adoptopenjdk/test/JdkVersion.java b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/JdkVersion.java
new file mode 100644
index 000000000..7aba40b67
--- /dev/null
+++ b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/JdkVersion.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.adoptopenjdk.test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JdkVersion {
+
+ /**
+ * Matches JDK version numbers that were used before JEP 223 came into effect (JDK 8 and earlier).
+ */
+ private static final Pattern PRE_223_PATTERN = Pattern.compile(
+ "^(?1\\.(?[0-8]+)\\.0(_(?[0-9]+)))(-(?.*)?)?$"
+ );
+
+ private final int feature;
+
+ private final int interim;
+
+ private final int update;
+
+ private final int patch;
+
+ public JdkVersion() {
+ String versionString = System.getProperty("java.version");
+ if (versionString.isEmpty()) {
+ throw new AssertionError("Property java.version is empty");
+ }
+
+ Matcher pre223Matcher = PRE_223_PATTERN.matcher(versionString);
+ if (pre223Matcher.matches()) {
+ // Handle 8 or earlier.
+ this.feature = Integer.parseInt(pre223Matcher.group("major"));
+ this.interim = 0;
+ this.update = Integer.parseInt(pre223Matcher.group("update"));
+ this.patch = 0;
+ return;
+ }
+
+ // Handle 9 or newer.
+ Class runtimeClass = Runtime.class;
+ try {
+ Method versionMethod = runtimeClass.getDeclaredMethod("version", (Class>[]) null);
+ Object versionObject = versionMethod.invoke(null, (Object[]) null);
+ Class> versionClass = versionObject.getClass();
+
+ Method featureMethod;
+ Method interimMethod;
+ Method updateMethod;
+ Method patchMethod;
+ try {
+ // Java 10 or newer (https://openjdk.java.net/jeps/322)
+ featureMethod = versionClass.getDeclaredMethod("feature", (Class>[]) null);
+ interimMethod = versionClass.getDeclaredMethod("interim", (Class>[]) null);
+ updateMethod = versionClass.getDeclaredMethod("update", (Class>[]) null);
+ patchMethod = versionClass.getDeclaredMethod("patch", (Class>[]) null);
+ } catch (NoSuchMethodException e) {
+ // Java 9 (https://openjdk.java.net/jeps/223)
+ featureMethod = versionClass.getDeclaredMethod("major", (Class>[]) null);
+ interimMethod = versionClass.getDeclaredMethod("minor", (Class>[]) null);
+ updateMethod = versionClass.getDeclaredMethod("security", (Class>[]) null);
+ patchMethod = null;
+ }
+
+ feature = (int) featureMethod.invoke(versionObject, (Object[]) null);
+ interim = (int) interimMethod.invoke(versionObject, (Object[]) null);
+ update = (int) updateMethod.invoke(versionObject, (Object[]) null);
+ if (patchMethod != null) {
+ patch = (int) patchMethod.invoke(versionObject, (Object[]) null);
+ } else {
+ patch = 0;
+ }
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ throw new AssertionError("Cannot determine JDK version", e);
+ }
+ }
+
+ public int getFeature() {
+ return feature;
+ }
+
+ public int getInterim() {
+ return interim;
+ }
+
+ public int getUpdate() {
+ return update;
+ }
+
+ public int getPatch() {
+ return patch;
+ }
+
+ public boolean isNewerOrEqual(int feature) {
+ return this.feature >= feature;
+ }
+
+ public boolean isNewerOrEqualSameFeature(int feature, int interim, int update) {
+ if (this.feature != feature) {
+ return false;
+ }
+ if (this.interim >= interim) {
+ return true;
+ }
+ return this.update >= update;
+ }
+
+ @Override
+ public String toString() {
+ return feature + "." + interim + "." + update + "." + patch;
+ }
+}
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/src/net/adoptopenjdk/test/StreamUtils.java b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/StreamUtils.java
new file mode 100644
index 000000000..85599746c
--- /dev/null
+++ b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/StreamUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.adoptopenjdk.test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public final class StreamUtils {
+
+ private StreamUtils() {
+ // no instances
+ }
+
+ /**
+ * Reads the entire {@link InputStream} into a string.
+ *
+ * @throws IOException If an I/O error occurs
+ */
+ public static String consumeStream(InputStream inputStream) throws IOException {
+ String lineSeparator = System.getProperty("line.separator");
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line);
+ builder.append(lineSeparator);
+ }
+ return builder.toString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/src/net/adoptopenjdk/test/VendorPropertiesTest.java b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/VendorPropertiesTest.java
new file mode 100644
index 000000000..78356f698
--- /dev/null
+++ b/test/functional/buildAndPackage/src/net/adoptopenjdk/test/VendorPropertiesTest.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.adoptopenjdk.test;
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Tests whether vendor names and correct URLs appear in all the places they are supposed to.
+ */
+@Test(groups = {"level.extended"})
+public class VendorPropertiesTest {
+
+ private final VmPropertiesChecks vendorChecks;
+
+ public VendorPropertiesTest() {
+ Set allPropertiesChecks = new LinkedHashSet<>();
+ allPropertiesChecks.add(new AdoptOpenJDKPropertiesChecks());
+ allPropertiesChecks.add(new CorrettoPropertiesChecks());
+
+ // TODO: Somehow obtain the vendor name from the outside. Using any JVM properties is not a solution
+ // because that's what we want to test here.
+ String vendor = "AdoptOpenJDK";
+ this.vendorChecks = allPropertiesChecks.stream()
+ .filter(checks -> checks.supports(vendor))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("No checks found for vendor: " + vendor));
+ }
+
+ @Test
+ public void javaVersionPrintsVendor() {
+ String testJdkHome = System.getenv("TEST_JDK_HOME");
+ if (testJdkHome == null) {
+ throw new AssertionError("TEST_JDK_HOME is not set");
+ }
+
+ List command = new ArrayList<>();
+ command.add(String.format("%s/bin/java", testJdkHome));
+ command.add("-version");
+
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ Process process = processBuilder.start();
+
+ String stderr = StreamUtils.consumeStream(process.getErrorStream());
+
+ if (process.waitFor() != 0) {
+ throw new AssertionError("Could not run java -version");
+ }
+
+ this.vendorChecks.javaVersion(stderr);
+ } catch (InterruptedException | IOException e) {
+ throw new RuntimeException("Failed to launch JVM", e);
+ }
+ }
+
+ @Test
+ public void vmPropertiesPointToVendor() {
+ this.vendorChecks.javaVendor(System.getProperty("java.vendor"));
+ this.vendorChecks.javaVendorUrl(System.getProperty("java.vendor.url"));
+ this.vendorChecks.javaVendorUrlBug(System.getProperty("java.vendor.url.bug"));
+ this.vendorChecks.javaVendorVersion(System.getProperty("java.vendor.version"));
+ this.vendorChecks.javaVmVendor(System.getProperty("java.vm.vendor"));
+ this.vendorChecks.javaVmVersion(System.getProperty("java.vm.version"));
+ }
+
+ private interface VmPropertiesChecks {
+ /**
+ * Tests whether the implementation of {@linkplain VmPropertiesChecks} is suitable to verify a JDK.
+ *
+ * @param vendor Name identifying the vendor.
+ */
+ boolean supports(String vendor);
+
+ /**
+ * Checks whether the output of {@code java -version} is acceptable.
+ */
+ void javaVersion(String value);
+
+ /**
+ * Checks the value of {@code java.vendor}.
+ */
+ void javaVendor(String value);
+
+ /**
+ * Checks the value of {@code java.vendor.url}.
+ */
+ void javaVendorUrl(String value);
+
+ /**
+ * Checks the value of {@code java.vendor.url.bug}.
+ */
+ void javaVendorUrlBug(String value);
+
+ /**
+ * Checks the value of {@code java.vendor.version}.
+ */
+ void javaVendorVersion(String value);
+
+ /**
+ * Checks the value of {@code java.vm.vendor}.
+ */
+ void javaVmVendor(String value);
+
+ /**
+ * Checks the value of {@code java.vm.version}.
+ */
+ void javaVmVersion(String value);
+ }
+
+ private static class AdoptOpenJDKPropertiesChecks implements VmPropertiesChecks {
+
+ @Override
+ public boolean supports(String vendor) {
+ return vendor.toLowerCase(Locale.US).equals("adoptopenjdk");
+ }
+
+ @Override
+ public void javaVersion(String value) {
+ assertTrue(value.contains("AdoptOpenJDK"));
+ }
+
+ @Override
+ public void javaVendor(String value) {
+ assertEquals(value, "AdoptOpenJDK");
+ }
+
+ @Override
+ public void javaVendorUrl(String value) {
+ assertEquals(value, "https://adoptopenjdk.net/");
+ }
+
+ @Override
+ public void javaVendorUrlBug(String value) {
+ assertEquals(value, "https://github.com/AdoptOpenJDK/openjdk-support/issues");
+ }
+
+ @Override
+ public void javaVendorVersion(String value) {
+ assertNotEquals(value.replaceAll("[^0-9]", "").length(), 0,
+ "java.vendor.version contains no numbers: " + value);
+ }
+
+ @Override
+ public void javaVmVendor(String value) {
+ assertEquals(value, "AdoptOpenJDK");
+ }
+
+ @Override
+ public void javaVmVersion(String value) {
+ assertNotEquals(value.replaceAll("[^0-9]", "").length(), 0,
+ "java.vm.version contains no numbers: " + value);
+ }
+ }
+
+ private static class CorrettoPropertiesChecks implements VmPropertiesChecks {
+
+ @Override
+ public boolean supports(String vendor) {
+ return vendor.toLowerCase(Locale.US).startsWith("amazon");
+ }
+
+ @Override
+ public void javaVersion(String value) {
+ assertTrue(value.contains("Corretto"));
+ }
+
+ @Override
+ public void javaVendor(String value) {
+ assertEquals(value, "Amazon.com Inc.");
+ }
+
+ @Override
+ public void javaVendorUrl(String value) {
+ assertEquals(value, "https://aws.amazon.com/corretto/");
+ }
+
+ @Override
+ public void javaVendorUrlBug(String value) {
+ assertTrue(value.startsWith("https://github.com/corretto/corretto"));
+ }
+
+ @Override
+ public void javaVendorVersion(String value) {
+ assertTrue(value.startsWith("Corretto"));
+ assertNotEquals(value.replaceAll("[^0-9]", "").length(), 0,
+ "java.vendor.version contains no numbers: " + value);
+ }
+
+ @Override
+ public void javaVmVendor(String value) {
+ assertEquals(value, "Amazon.com Inc.");
+ }
+
+ @Override
+ public void javaVmVersion(String value) {
+ assertNotEquals(value.replaceAll("[^0-9]", "").length(), 0,
+ "java.vm.version contains no numbers: " + value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/functional/buildAndPackage/testng.xml b/test/functional/buildAndPackage/testng.xml
new file mode 100644
index 000000000..35b24fdbf
--- /dev/null
+++ b/test/functional/buildAndPackage/testng.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file