diff --git a/.github/workflows/local_tests.yml b/.github/workflows/local_tests.yml index 88e1da8b..ec22ea48 100644 --- a/.github/workflows/local_tests.yml +++ b/.github/workflows/local_tests.yml @@ -139,7 +139,7 @@ jobs: ) pushd ts copy .github/mvn-settings.xml ~/.m2/settings.xml - mvn clean verify -Ptestsuite -Dquarkus.version=${{ matrix.quarkus-version }} + mvn clean verify -Ptestsuite -DincludeTags=testing-testsuite,runtimes,reproducers -Dquarkus.version=${{ matrix.quarkus-version }} shell: cmd - name: Linux test if: startsWith(matrix.os, 'ubuntu') @@ -159,7 +159,7 @@ jobs: native-image --version pushd ts cp .github/mvn-settings.xml ~/.m2/settings.xml - mvn clean verify -Ptestsuite -Dquarkus.version=${{ matrix.quarkus-version }} + mvn clean verify -Ptestsuite -DincludeTags=testing-testsuite,runtimes,reproducers -Dquarkus.version=${{ matrix.quarkus-version }} shell: bash - name: Prepare failure archive (if maven failed) if: failure() diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 46538976..f0b0bb9e 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -56,7 +56,7 @@ org.graalvm.sdk graal-sdk - 21.2.0 + 21.3.1 diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java b/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java index 390bd8b1..71a493c6 100644 --- a/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.condition.OS; import java.io.File; @@ -572,8 +570,7 @@ public void timezonesBakedIn(TestInfo testInfo) throws IOException, InterruptedE @Test @Tag("jdk-17") @Tag("recordannotations") - @DisabledOnJre(JRE.JAVA_11) - @IfMandrelVersion(min = "21.3.1.1") + @IfMandrelVersion(min = "21.3.1.1", minJDK = "17") public void recordAnnotationsWork(TestInfo testInfo) throws IOException, InterruptedException { final Apps app = Apps.RECORDANNOTATIONS; LOGGER.info("Testing app: " + app); diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/IfMandrelVersion.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/IfMandrelVersion.java index 71c9c0a1..f85b5af5 100644 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/IfMandrelVersion.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/IfMandrelVersion.java @@ -40,6 +40,13 @@ * * IfMandrelVersion(min = "21.1", inContainer = true) * i.e. [21.1, +∞) + * + * IfMandrelVersion(min = "21.3.1", minJDK = "17") + * i.e. [21.3.1, +∞) and [17, +∞) + * + * IfMandrelVersion(min = "22", minJDK = "17", maxJDK = "17.0.2") + * i.e. [22, +∞) and [17, 17.0.2] + * * //@formatter:on * Note that versions 21.1.0.0-final and 21.1.0.0-snapshot and 21.1.0.0 are all considered equal. * @@ -47,6 +54,11 @@ * * inContainer: Whether the version should be pulled from a builder image container. * + * JDK versions comparator uses feature.interim.update, the fourth, patch number or + * any other qualifiers following that are not used, i.e. + * 17.0.3-beta+5-202203292328 and 17.0.3-beta+6 are the same and + * 11.0.14.1+1-LTS and 11.0.14 are the same. + * * @author Michal Karm Babacek */ @Target(ElementType.METHOD) @@ -58,5 +70,9 @@ String max() default ""; + String minJDK() default ""; + + String maxJDK() default ""; + boolean inContainer() default false; } diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/MandrelVersionCondition.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/MandrelVersionCondition.java index 4c4bf485..a3b4e9cb 100644 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/MandrelVersionCondition.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/MandrelVersionCondition.java @@ -25,8 +25,11 @@ import org.junit.jupiter.api.extension.ExtensionContext; import java.lang.reflect.AnnotatedElement; +import java.util.regex.Pattern; import static java.lang.String.format; +import static org.graalvm.tests.integration.utils.versions.UsedVersion.compareJDKVersion; +import static org.graalvm.tests.integration.utils.versions.UsedVersion.featureInterimUpdate; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; @@ -52,15 +55,28 @@ public ConditionEvaluationResult evaluateExecutionCondition( private ConditionEvaluationResult disableIfVersionMismatch(IfMandrelVersion annotation, AnnotatedElement element) { final Version usedVersion = UsedVersion.getVersion(annotation.inContainer()); - if (annotation.min().isBlank() || usedVersion.compareTo(Version.parse(annotation.min())) >= 0) { - if (annotation.max().isBlank() || usedVersion.compareTo(Version.parse(annotation.max())) <= 0) { - return enabled(format( - "%s is enabled as Mandrel version %s does satisfy constraints: minVersion: %s, maxVersion: %s", - element, usedVersion.toString(), annotation.min(), annotation.max())); - } + boolean jdkConstraintSatisfied = true; + if (!annotation.minJDK().isBlank() || !annotation.maxJDK().isBlank()) { + final int[] jdkVersion = new int[]{ + UsedVersion.jdkFeature(annotation.inContainer()), + UsedVersion.jdkInterim(annotation.inContainer()), + UsedVersion.jdkUpdate(annotation.inContainer()) + }; + final Pattern p = Pattern.compile("(?[0-9]+)(\\.(?[0-9]*)\\.(?[0-9]*))?"); + final int[] min = featureInterimUpdate(p, annotation.minJDK(), Integer.MIN_VALUE); + final int[] max = featureInterimUpdate(p, annotation.maxJDK(), Integer.MAX_VALUE); + jdkConstraintSatisfied = compareJDKVersion(jdkVersion, min) >= 0 && compareJDKVersion(jdkVersion, max) <= 0; + } + final boolean mandrelConstraintSatisfied = + (annotation.min().isBlank() || usedVersion.compareTo(Version.parse(annotation.min())) >= 0) && + (annotation.max().isBlank() || usedVersion.compareTo(Version.parse(annotation.max())) <= 0); + if (mandrelConstraintSatisfied && jdkConstraintSatisfied) { + return enabled(format( + "%s is enabled as Mandrel version %s does satisfy constraints: min: %s, max: %s, minJDK: %s, maxJDK: %s", + element, usedVersion.toString(), annotation.min(), annotation.max(), annotation.minJDK(), annotation.maxJDK())); } return disabled(format( - "%s is disabled as Mandrel version %s does not satisfy constraints: minVersion: %s, maxVersion: %s", - element, usedVersion, annotation.min(), annotation.max())); + "%s is disabled as Mandrel version %s does not satisfy constraints: min: %s, max: %s, minJDK: %s, maxJDK: %s", + element, usedVersion, annotation.min(), annotation.max(), annotation.minJDK(), annotation.maxJDK())); } } diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/QuarkusVersion.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/QuarkusVersion.java index 4921a517..124677bc 100644 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/QuarkusVersion.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/QuarkusVersion.java @@ -25,7 +25,7 @@ public class QuarkusVersion implements Comparable { private final int major; private final int minor; private final int patch; - private boolean snapshot; + private final boolean snapshot; public QuarkusVersion(String version) { this.version = version; diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/UsedVersion.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/UsedVersion.java index 2dc1f3be..a3d5e1b0 100644 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/UsedVersion.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/UsedVersion.java @@ -49,6 +49,7 @@ * @author Michal Karm Babacek */ public class UsedVersion { + private static final int UNDEFINED = -1; public static Version getVersion(boolean inContainer) { return inContainer ? InContainer.mVersion.version : Locally.mVersion.version; @@ -75,8 +76,8 @@ public static int jdkUpdate(boolean inContainer) { */ private static final class MVersion { private static final Logger LOGGER = Logger.getLogger(MVersion.class.getName()); - private static final Pattern VERSION_PATTERN = - Pattern.compile("(?:GraalVM|native-image)(?: Version)? ([^ ]*).*Java Version ([\\d]+)\\.([\\d]+)\\.([\\d]+).*"); + private static final Pattern VERSION_PATTERN = Pattern.compile( + "(?:GraalVM|native-image)(?: Version)? (?[^ ]*).*Java Version (?[\\d]+)\\.(?[\\d]+)\\.(?[\\d]+).*"); private final Version version; private final boolean jdkUsesSysLibs; @@ -98,7 +99,8 @@ public MVersion(boolean inContainer) { final String[] lines = out.split(System.lineSeparator()); lastLine = lines[lines.length - 1].trim(); } else { - final List cmd = getRunCommand("native-image", "--version"); + final String TEST_TESTSUITE_ABSOLUTE_PATH = System.getProperty("FAKE_NATIVE_IMAGE_DIR", ""); + final List cmd = getRunCommand(TEST_TESTSUITE_ABSOLUTE_PATH + "native-image", "--version"); LOGGER.info("Running command " + cmd + " to determine Mandrel version used."); try { lastLine = Commands.runCommand(cmd).trim(); @@ -124,10 +126,17 @@ public MVersion(boolean inContainer) { "Output: '" + lastLine + "'"); } } - version = versionParse(m.group(1)); - jdkFeature = Integer.parseInt(m.group(2)); - jdkInterim = Integer.parseInt(m.group(3)); - jdkUpdate = Integer.parseInt(m.group(4)); + version = versionParse(m.group("version")); + final String jFeature = m.group("jfeature"); + final String jInterim = m.group("jinterim"); + final String jUpdate = m.group("jupdate"); + jdkFeature = jFeature == null ? UNDEFINED : Integer.parseInt(jFeature); + jdkInterim = jInterim == null ? UNDEFINED : Integer.parseInt(jInterim); + jdkUpdate = jUpdate == null ? UNDEFINED : Integer.parseInt(jUpdate); + if (jdkFeature == UNDEFINED) { + LOGGER.warn("Failed to correctly parse Java feature (major) version from native-image version command output. " + + "JDK version constraints in tests won't work reliably."); + } LOGGER.infof("The test suite runs with Mandrel version %s %s, JDK %d.%d.%d.", version.toString(), inContainer ? "in container" : " installed locally on PATH", jdkFeature, jdkInterim, jdkUpdate); } @@ -149,4 +158,31 @@ private static class Locally { private static final MVersion mVersion = new MVersion(false); } + public static int[] featureInterimUpdate(Pattern pattern, String version, int defaultValue) { + final Matcher m = pattern.matcher(version); + if (!m.matches()) { + return new int[]{defaultValue, defaultValue, defaultValue}; + } + final String jFeature = m.group("jfeature"); + final String jInterim = m.group("jinterim"); + final String jUpdate = m.group("jupdate"); + return new int[]{ + jFeature == null ? defaultValue : Integer.parseInt(jFeature), + jInterim == null ? defaultValue : Integer.parseInt(jInterim), + jUpdate == null ? defaultValue : Integer.parseInt(jUpdate) + }; + } + + public static int compareJDKVersion(int[] a, int[] b) { + if (a.length != 3 || b.length != 3) { + throw new IllegalArgumentException("3 version elements expected: feature, interim, update"); + } + for (int i = 0; i < 3; i++) { + int compare = Integer.compare(a[i], b[i]); + if (compare != 0) { + return compare; + } + } + return 0; + } } diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/VersionsTest.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/VersionsTest.java new file mode 100644 index 00000000..863c27fa --- /dev/null +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/versions/VersionsTest.java @@ -0,0 +1,154 @@ +package org.graalvm.tests.integration.utils.versions;/* + * Copyright (c) 2022, Red Hat Inc. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 + * + * http://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. + * + */ + +import org.graalvm.tests.integration.utils.Logs; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.PosixFilePermissions; + +import static org.graalvm.tests.integration.utils.Commands.IS_THIS_WINDOWS; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Testing test suite... + * + * A fake native-image executable script is created, it contains a single version string. + * Using the property FAKE_NATIVE_IMAGE_DIR, the test suite is tricked into running it + * during native-image version resolution. + * A series of test methods is executed by JUnit, business as usual. Those test methods log + * their executing into a file. + * After all tests are done, the file is examined. If it contains any superfluous entries + * or if it's missing anything, the test lass fails. + */ +@Tag("testing-testsuite") +public class VersionsTest { + static final Path NATIVE_IMAGE = Path.of(System.getProperty("java.io.tmpdir"), IS_THIS_WINDOWS ? "native-image.cmd" : "native-image"); + static final String VERSION = "native-image 22.2.0-devdb26f5c4fbe Mandrel Distribution (Java Version 17.0.3-beta+5-202203292328)"; + static final Path LOG = Path.of(System.getProperty("java.io.tmpdir"), "versions-log"); + static final StandardOpenOption[] LOG_FILE_OPS = new StandardOpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.WRITE}; + + @BeforeAll + public static void setup() throws IOException { + System.setProperty("FAKE_NATIVE_IMAGE_DIR", System.getProperty("java.io.tmpdir") + File.separator); + Files.writeString(NATIVE_IMAGE, IS_THIS_WINDOWS ? + "@echo off" + System.lineSeparator() + + "echo " + VERSION + System.lineSeparator() : + "#!/bin/sh" + System.lineSeparator() + + "echo '" + VERSION + "'" + System.lineSeparator(), + StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); + if(!IS_THIS_WINDOWS) { + Files.setPosixFilePermissions(NATIVE_IMAGE, PosixFilePermissions.fromString("rwxr-xr-x")); + } + Files.deleteIfExists(LOG); + } + + @AfterAll + public static void teardown() throws IOException { + System.clearProperty("FAKE_NATIVE_IMAGE_DIR"); + Files.deleteIfExists(NATIVE_IMAGE); + final String testlog = Files.readString(LOG, StandardCharsets.UTF_8); + try { + assertEquals( + "Running test jdkVersionCheckA\n" + + "Running test jdkVersionCheckB\n" + + "Running test jdkVersionCheckC\n" + + "Running test jdkVersionCheckD\n" + + "Running test jdkVersionCheckI\n", testlog); + } finally { + Logs.archiveLog(VersionsTest.class.getCanonicalName(), "versionTest", LOG.toFile()); + Files.deleteIfExists(LOG); + } + } + + @Test + @Order(1) + @IfMandrelVersion(min = "21.3.1") + public void jdkVersionCheckA(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(2) + @IfMandrelVersion(min = "21.3.1", minJDK = "17") + public void jdkVersionCheckB(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(3) + @IfMandrelVersion(min = "22", minJDK = "17") + public void jdkVersionCheckC(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(4) + @IfMandrelVersion(min = "22", minJDK = "17.0.2") + public void jdkVersionCheckD(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(5) + @IfMandrelVersion(min = "22", minJDK = "17", maxJDK = "17.0.2") + public void jdkVersionCheckE(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(6) + @IfMandrelVersion(min = "21", minJDK = "11.0.12", maxJDK = "17.0.1") + public void jdkVersionCheckF(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(7) + @IfMandrelVersion(min = "21", maxJDK = "11") + public void jdkVersionCheckG(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(8) + @IfMandrelVersion(min = "21.2", minJDK = "18.0.0") + public void jdkVersionCheckH(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + + @Test + @Order(9) + @IfMandrelVersion(min = "22", max = "22.2", minJDK = "17.0.1", maxJDK = "17.0.3") + public void jdkVersionCheckI(TestInfo testInfo) throws IOException { + Files.writeString(LOG, "Running test " + testInfo.getTestMethod().get().getName() + "\n", StandardCharsets.UTF_8, LOG_FILE_OPS); + } + +}