diff --git a/src/main/java/org/openrewrite/java/testing/junit6/MinimumJreConditions.java b/src/main/java/org/openrewrite/java/testing/junit6/MinimumJreConditions.java new file mode 100644 index 000000000..64feea7dd --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/junit6/MinimumJreConditions.java @@ -0,0 +1,387 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 org.openrewrite.java.testing.junit6; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.*; +import org.openrewrite.java.trait.Annotated; +import org.openrewrite.java.trait.Literal; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Space; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +@Value +@EqualsAndHashCode(callSuper = false) +public class MinimumJreConditions extends Recipe { + + private static final String JRE_IMPORT = "org.junit.jupiter.api.condition.JRE"; + private static final String ENABLED_ON_JRE = "org.junit.jupiter.api.condition.EnabledOnJre"; + private static final String DISABLED_ON_JRE = "org.junit.jupiter.api.condition.DisabledOnJre"; + private static final String ENABLED_FOR_JRE_RANGE = "org.junit.jupiter.api.condition.EnabledForJreRange"; + private static final String DISABLED_FOR_JRE_RANGE = "org.junit.jupiter.api.condition.DisabledForJreRange"; + private static final Annotated.Matcher ENABLED_JRE_MATCHER = new Annotated.Matcher("@" + ENABLED_ON_JRE); + private static final Annotated.Matcher DISABLED_JRE_MATCHER = new Annotated.Matcher("@" + DISABLED_ON_JRE); + private static final Annotated.Matcher ENABLED_JRE_RANGE_MATCHER = new Annotated.Matcher("@" + ENABLED_FOR_JRE_RANGE); + private static final Annotated.Matcher DISABLED_JRE_RANGE_MATCHER = new Annotated.Matcher("@" + DISABLED_FOR_JRE_RANGE); + private static final List TEST_ANNOTATION_MATCHERS = Arrays.asList( + new Annotated.Matcher("@org.junit.jupiter.api.Test"), + new Annotated.Matcher("@org.junit.jupiter.api.TestFactory"), + new Annotated.Matcher("@org.junit.jupiter.api.TestTemplate"), + new Annotated.Matcher("@org.junit.jupiter.api.RepeatedTest"), + new Annotated.Matcher("@org.junit.jupiter.params.ParameterizedTest") + ); + + @Option(displayName = "JRE version", description = "The minimum JRE version to use for test conditions.", example = "17") + String javaVersion; + + @Override + public String getDisplayName() { + return "Migrate JUnit JRE conditions"; + } + + @Override + public String getDescription() { + return "This recipe will:\n" + " - Remove tests that are only active on JREs that are below the specified version.\n" + " - Adjust ranges to use minimum the specified version."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + + @Override + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { + J.CompilationUnit c = super.visitCompilationUnit(cu, ctx); + if (cu != c) { + maybeRemoveImport("org.junit.jupiter.api.Test"); + maybeRemoveImport("org.junit.jupiter.api.TestFactory"); + maybeRemoveImport("org.junit.jupiter.api.TestTemplate"); + maybeRemoveImport("org.junit.jupiter.api.RepeatedTest"); + maybeRemoveImport("org.junit.jupiter.params.ParameterizedTest"); + maybeRemoveImport(ENABLED_ON_JRE); + maybeRemoveImport(DISABLED_ON_JRE); + maybeRemoveImport(ENABLED_FOR_JRE_RANGE); + maybeRemoveImport(DISABLED_FOR_JRE_RANGE); + } + return c; + } + + @Override + public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); + boolean isUnitTest = false; + List enabledOnJre = null; + List disabledOnJre = null; + Range enabledOnJreRange = null; + Range disabledOnJreRange = null; + Space prefix = Space.EMPTY; + + // First assemble all annotations to see if this is a unit test impacted with JRE conditional annotations + for (J.Annotation ann : method.getLeadingAnnotations()) { + Cursor annotationCursor = new Cursor(getCursor(), ann); + if (TEST_ANNOTATION_MATCHERS.stream().anyMatch(matcher -> matcher.get(annotationCursor).isPresent())) { + isUnitTest = true; + } + Optional annotated = ENABLED_JRE_MATCHER.get(annotationCursor); + if (annotated.isPresent()) { + enabledOnJre = annotated.map(a -> getDefaultAttribute(a).map(this::extractVersionsFromAnnotationArgument) + .orElseGet(() -> a.getAttribute("versions").map(Literal::getStrings).orElse(null))) + .orElse(null); + prefix = ann.getPrefix(); + } + annotated = DISABLED_JRE_MATCHER.get(annotationCursor); + if (annotated.isPresent()) { + disabledOnJre = annotated.map(a -> getDefaultAttribute(a).map(this::extractVersionsFromAnnotationArgument) + .orElseGet(() -> a.getAttribute("versions").map(Literal::getStrings).orElse(null))) + .orElse(null); + prefix = ann.getPrefix(); + } + annotated = ENABLED_JRE_RANGE_MATCHER.get(annotationCursor); + if (annotated.isPresent()) { + enabledOnJreRange = annotated.map(Range::new).orElse(null); + prefix = ann.getPrefix(); + } + annotated = DISABLED_JRE_RANGE_MATCHER.get(annotationCursor); + if (annotated.isPresent()) { + disabledOnJreRange = annotated.map(Range::new).orElse(null); + prefix = ann.getPrefix(); + } + } + + // Only act upon unit tests that have JRE conditions + if (!isUnitTest || (enabledOnJre == null && disabledOnJre == null && enabledOnJreRange == null && disabledOnJreRange == null)) { + return m; + } + + // Now transform the annotations to the correct results + if (enabledOnJre != null) { + if (enabledOnJre.stream().allMatch(v -> compareVersions(v, javaVersion) < 0)) { + // Remove the test method if it is enabled on a JRE version lower than the specified version + maybeRemoveImport(JRE_IMPORT); + return null; + } + m = updateAnnotationVersions(m, enabledOnJre, ENABLED_ON_JRE, ctx); + } + if (disabledOnJre != null) { + if (disabledOnJre.stream().allMatch(v -> compareVersions(v, javaVersion) < 0)) { + m = removeAnnotation(m, DISABLED_ON_JRE, ctx); + } else { + m = updateAnnotationVersions(m, disabledOnJre, DISABLED_ON_JRE, ctx); + } + } + if (enabledOnJreRange != null) { + if (compareVersions(enabledOnJreRange.getMax(), javaVersion) < 0) { + // Remove the test method if it is enabled on a JRE range that ends before the specified version + maybeRemoveImport(JRE_IMPORT); + return null; + } + if (compareVersions(enabledOnJreRange.getMin(), javaVersion) <= 0 && enabledOnJreRange.getMaxNotation() == null) { + // The test is enabled for all versions from `javaVersion till end -> remove annotation + m = removeAnnotation(m, ENABLED_FOR_JRE_RANGE, ctx); + } else { + m = updateRangeStart(m, ENABLED_FOR_JRE_RANGE, enabledOnJreRange, ctx); + } + } + if (disabledOnJreRange != null) { + if (compareVersions(disabledOnJreRange.getMax(), javaVersion) < 0) { + m = removeAnnotation(m, DISABLED_FOR_JRE_RANGE, ctx); + } else if (compareVersions(disabledOnJreRange.getMin(), javaVersion) <= 0 && disabledOnJreRange.getMaxNotation() == null) { + // The test is disabled for all versions from `javaVersion` till end -> remove test + maybeRemoveImport(JRE_IMPORT); + return null; + } else { + m = updateRangeStart(m, DISABLED_FOR_JRE_RANGE, disabledOnJreRange, ctx); + } + } + + replaceSingleVersionRangeWithEquivalentAnnotation(enabledOnJreRange, ENABLED_FOR_JRE_RANGE, ENABLED_ON_JRE); + replaceSingleVersionRangeWithEquivalentAnnotation(disabledOnJreRange, DISABLED_FOR_JRE_RANGE, DISABLED_ON_JRE); + + if (m != method) { + return simplifySingleValueAnnotationAttributeArrays(m, prefix); + } + return method; + } + + private J.MethodDeclaration removeAnnotation(J.MethodDeclaration m, String annotationType, ExecutionContext ctx) { + RemoveAnnotation removeAnnotation = new RemoveAnnotation("@" + annotationType); + maybeRemoveImport(JRE_IMPORT); + return removeAnnotation.getVisitor().visitMethodDeclaration(m, ctx); + } + + private J.MethodDeclaration updateAnnotationVersions(J.MethodDeclaration m, List versions, String annotationType, ExecutionContext ctx) { + if (versions.stream().anyMatch(v -> compareVersions(v, javaVersion) < 0)) { + // Update the annotation to only include JRE versions that are greater than or equal to the specified version + AddOrUpdateAnnotationAttribute updatedVersions = new AddOrUpdateAnnotationAttribute(annotationType, "versions", versions.stream().filter(v -> compareVersions(v, javaVersion) >= 0).collect(joining(", ")), null, false, false); + m = (J.MethodDeclaration) updatedVersions.getVisitor().visitNonNull(m, ctx, getCursor().getParent()); + } + return m; + } + + // Return a method declaration with a new range only if the start is below requested java version. + // If the start version would be equal to the end version, then no updated method is returned as this is handled in a later phase + private J.MethodDeclaration updateRangeStart(J.MethodDeclaration m, String annotationtype, Range range, ExecutionContext ctx) { + if (compareVersions(range.getMin(), javaVersion) <= 0 && compareVersions(range.getMax(), javaVersion) != 0) { + // Update the range to start at the specified version + String attributeName = range.getMinNotation() == RangeNotation.VERSION ? "minVersion" : "min"; + RemoveAnnotationAttribute updatedRange = new RemoveAnnotationAttribute(annotationtype, attributeName); + m = (J.MethodDeclaration) updatedRange.getVisitor().visitNonNull(m, ctx, getCursor().getParent()); + } + return m; + } + + // If the range is resulting in a single version after updating (current max is the new min), then we can replace the range annotation with a single version annotation. + private void replaceSingleVersionRangeWithEquivalentAnnotation(@Nullable Range range, String rangeAnnotationType, String singleVersionAnnotationType) { + if (range != null && compareVersions(range.getMax(), javaVersion) == 0) { + doAfterVisit(new ChangeType(rangeAnnotationType, singleVersionAnnotationType, false).getVisitor()); + doAfterVisit(new RemoveAnnotationAttribute(singleVersionAnnotationType, "min").getVisitor()); + doAfterVisit(new RemoveAnnotationAttribute(singleVersionAnnotationType, "minVersion").getVisitor()); + doAfterVisit(new RemoveAnnotationAttribute(singleVersionAnnotationType, "max").getVisitor()); + doAfterVisit(new RemoveAnnotationAttribute(singleVersionAnnotationType, "maxVersion").getVisitor()); + String attributeName = range.getNotation() == RangeNotation.VERSION ? "versions" : "value"; + String newValue = formatJreValue(javaVersion, range.getNotation()); + doAfterVisit(new AddOrUpdateAnnotationAttribute(singleVersionAnnotationType, attributeName, newValue, null, false, false).getVisitor()); + } + } + + private J.MethodDeclaration simplifySingleValueAnnotationAttributeArrays(J.MethodDeclaration m, Space prefix) { + return m.withLeadingAnnotations(ListUtils.map(m.getLeadingAnnotations(), ann -> { + Cursor parentCursor = getCursor().getParent(); + if (ENABLED_JRE_MATCHER.get(ann, parentCursor).isPresent() || + DISABLED_JRE_MATCHER.get(ann, parentCursor).isPresent() || + ENABLED_JRE_RANGE_MATCHER.get(ann, parentCursor).isPresent() || + DISABLED_JRE_RANGE_MATCHER.get(ann, parentCursor).isPresent()) { + ann = ann + .withArguments(ListUtils.map(ann.getArguments(), arg -> { + if (arg instanceof J.Assignment) { + J.Assignment ass = (J.Assignment) arg; + if (ass.getAssignment() instanceof J.NewArray) { + List initializer = ((J.NewArray) ass.getAssignment()).getInitializer(); + if (initializer != null && initializer.size() == 1 && !(initializer.get(0) instanceof J.Empty)) { + return ass.withAssignment(initializer.get(0).withPrefix(ass.getAssignment().getPrefix())); + } + } + } + return arg; + })) + .withPrefix(prefix); + } + return ann; + })); + } + + private List extractVersionsFromAnnotationArgument(Expression expression) { + if (expression instanceof J.NewArray) { + List initializer = ((J.NewArray) expression).getInitializer(); + if (initializer == null) { + return emptyList(); + } + return initializer.stream().map(Objects::toString).collect(toList()); + } + return singletonList(expression.toString()); + } + }; + } + + private static Optional getDefaultAttribute(Annotated annotated) { + if (annotated.getTree().getArguments() == null) { + return Optional.empty(); + } + for (Expression argument : annotated.getTree().getArguments()) { + if (!(argument instanceof J.Assignment)) { + return Optional.of(argument); + } + } + return getAttribute(annotated, "value"); + } + + private static Optional getAttribute(Annotated annotated, String attribute) { + if (annotated.getTree().getArguments() == null) { + return Optional.empty(); + } + for (Expression argument : annotated.getTree().getArguments()) { + if (argument instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) argument; + if (assignment.getVariable() instanceof J.Identifier && + ((J.Identifier) assignment.getVariable()).getSimpleName().equals(attribute)) { + return Optional.of(assignment.getAssignment()); + } + } + } + return Optional.empty(); + } + + private static int compareVersions(String version1, String version2) { + String numericVersion1 = fromJREVersion(version1); + String numericVersion2 = fromJREVersion(version2); + int v1 = Integer.parseInt(numericVersion1.replaceAll("\\D", "")); + int v2 = Integer.parseInt(numericVersion2.replaceAll("\\D", "")); + + int comparison = Integer.compare(v1, v2); + if (comparison == 0) { + return comparison; + } + return comparison / Math.abs(comparison); + } + + private static String fromJREVersion(String version) { + if (version.startsWith("JRE.")) { + version = version.substring(4); + } + if (version.startsWith("JAVA_")) { + return version.substring(5); + } + if ("OTHER".equals(version)) { + return String.valueOf(Integer.MAX_VALUE); + } + return version; + } + + private static String formatJreValue(String version, @Nullable RangeNotation notation) { + if (notation == null) { + return version; + } + switch (notation) { + case JRE: + return "JRE.JAVA_" + version; + case STATIC_JRE: + return "JAVA_" + version; + case VERSION: + default: + return version; + } + } + + @Getter + @ToString + private static final class Range { + private final String min; + + @Nullable + private final RangeNotation minNotation; + + private final String max; + + @Nullable + private final RangeNotation maxNotation; + + public Range(Annotated annotated) { + Optional minAttribute = getAttribute(annotated, "min").map(Objects::toString); + Optional minVersionAttribute = getAttribute(annotated, "minVersion").map(Objects::toString); + Optional maxAttribute = getAttribute(annotated, "max").map(Objects::toString); + Optional maxVersionAttribute = getAttribute(annotated, "maxVersion").map(Objects::toString); + String min = minAttribute.orElse(minVersionAttribute.orElse(null)); + String max = maxAttribute.orElse(maxVersionAttribute.orElse(null)); + this.min = min == null ? "0" : min; + this.max = max == null ? String.valueOf(Integer.MAX_VALUE) : max; + this.minNotation = minAttribute + .map(it -> it.startsWith("JRE.") ? RangeNotation.JRE : RangeNotation.STATIC_JRE) + .orElse(minVersionAttribute.map(__ -> RangeNotation.VERSION).orElse(null)); + this.maxNotation = maxAttribute + .map(it -> it.startsWith("JRE.") ? RangeNotation.JRE : RangeNotation.STATIC_JRE) + .orElse(maxVersionAttribute.map(__ -> RangeNotation.VERSION).orElse(null)); + } + + private RangeNotation getNotation() { + if (minNotation == null && maxNotation == null) { + return RangeNotation.VERSION; + } + if (maxNotation == null) { + return minNotation; + } + return maxNotation; + } + } + + enum RangeNotation { + JRE, STATIC_JRE, VERSION + } +} diff --git a/src/main/resources/META-INF/rewrite/examples.yml b/src/main/resources/META-INF/rewrite/examples.yml index b49736dec..988c789e7 100644 --- a/src/main/resources/META-INF/rewrite/examples.yml +++ b/src/main/resources/META-INF/rewrite/examples.yml @@ -3247,6 +3247,90 @@ examples: language: java --- type: specs.openrewrite.org/v1beta/example +recipeName: org.openrewrite.java.testing.junit6.MinimumJreConditions +examples: +- description: '' + parameters: + - '17' + sources: + - before: | + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.DisabledOnJre; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledOnJre(JRE.JAVA_8) + void testOnJava8() { + System.out.println("Java 8"); + } + + @Test + @EnabledOnJre(JRE.JAVA_21) + void testOnJava21() { + System.out.println("Java 21"); + } + + @Test + @DisabledOnJre(JRE.JAVA_11) + void testNotOnJava11() { + System.out.println("Not Java 11"); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_11) + void testRange8To11() { + System.out.println("Java 8-11"); + } + + @Test + @DisabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_11) + void testNotRange8To11() { + System.out.println("Not Java 8-11"); + } + + @Test + void testAlways() { + System.out.println("Always"); + } + } + after: | + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.DisabledOnJre; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + + @Test + @EnabledOnJre(JRE.JAVA_21) + void testOnJava21() { + System.out.println("Java 21"); + } + + @Test + void testNotOnJava11() { + System.out.println("Not Java 11"); + } + + @Test + void testNotRange8To11() { + System.out.println("Not Java 8-11"); + } + + @Test + void testAlways() { + System.out.println("Always"); + } + } + language: java +--- +type: specs.openrewrite.org/v1beta/example recipeName: org.openrewrite.java.testing.mockito.CleanupMockitoImports examples: - description: '' diff --git a/src/main/resources/META-INF/rewrite/junit6.yml b/src/main/resources/META-INF/rewrite/junit6.yml index 6bf58f8c3..7da92a2ca 100755 --- a/src/main/resources/META-INF/rewrite/junit6.yml +++ b/src/main/resources/META-INF/rewrite/junit6.yml @@ -54,3 +54,10 @@ recipeList: - org.openrewrite.java.RemoveAnnotationAttribute: annotationType: org.junit.jupiter.params.provider.CsvFileSource attributeName: lineSeparator + - org.openrewrite.java.testing.junit6.MinimumJreConditions: + javaVersion: 17 + - org.openrewrite.java.ChangeMethodName: + methodPattern: org.junit.jupiter.api.extension.ExtensionContext.Store getOrComputeIfAbsent((..)) + newMethodName: computeIfAbsent + matchOverrides: true + ignoreDefinition: true diff --git a/src/test/java/org/openrewrite/java/testing/junit6/MinimumJreConditionsTest.java b/src/test/java/org/openrewrite/java/testing/junit6/MinimumJreConditionsTest.java new file mode 100644 index 000000000..163f0673f --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/junit6/MinimumJreConditionsTest.java @@ -0,0 +1,1152 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 org.openrewrite.java.testing.junit6; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class MinimumJreConditionsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api")) + .recipe(new MinimumJreConditions("17")); + } + + + @DocumentExample + @Test + void handleTestClassWithMixedConditions() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.DisabledOnJre; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledOnJre(JRE.JAVA_8) + void testOnJava8() { + System.out.println("Java 8"); + } + + @Test + @EnabledOnJre(JRE.JAVA_21) + void testOnJava21() { + System.out.println("Java 21"); + } + + @Test + @DisabledOnJre(JRE.JAVA_11) + void testNotOnJava11() { + System.out.println("Not Java 11"); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_11) + void testRange8To11() { + System.out.println("Java 8-11"); + } + + @Test + @DisabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_11) + void testNotRange8To11() { + System.out.println("Not Java 8-11"); + } + + @Test + void testAlways() { + System.out.println("Always"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + + @Test + @EnabledOnJre(JRE.JAVA_21) + void testOnJava21() { + System.out.println("Java 21"); + } + + @Test + void testNotOnJava11() { + System.out.println("Not Java 11"); + } + + @Test + void testNotRange8To11() { + System.out.println("Not Java 8-11"); + } + + @Test + void testAlways() { + System.out.println("Always"); + } + } + """ + ) + ); + } + + @Nested + class EnabledOnJreTests { + @ParameterizedTest + @ValueSource(strings = { + "JAVA_11", + "JAVA_8", + "JRE.JAVA_11", + "JRE.JAVA_8", + "value = JRE.JAVA_11", + "value = JRE.JAVA_8", + "versions = { 11 }", + "versions = { 8, 11 }", + "versions = { 8 }" + }) + void removeTestEnabledOnOlderJre(String jre) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + import static org.junit.jupiter.api.condition.JRE.*; + + class MyTest { + @Test + @EnabledOnJre(%s) + void testOnlyOnJava8() { + System.out.println("Java 8"); + } + + @Test + void normalTest() { + System.out.println("Any version"); + } + } + """.formatted(jre), + """ + import org.junit.jupiter.api.Test; + + class MyTest { + + @Test + void normalTest() { + System.out.println("Any version"); + } + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "JAVA_17", + "JRE.JAVA_17", + "value = JRE.JAVA_17", + "versions = 17", + "JRE.JAVA_21", + "value = JRE.JAVA_21", + "versions = 21", + "JAVA_21" + }) + void keepAnnotationEnabledOnCurrentOrNewerJre(String jre) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + import static org.junit.jupiter.api.condition.JRE.*; + + class MyTest { + @Test + @EnabledOnJre(%s) + void testOnJava17() { + System.out.println("Java 17"); + } + } + """.formatted(jre) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "versions = { 17 }", + "versions = { 21 }", + }) + void doNotUnwrapSingleValueArrayIfNotTouched(String jre) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + + class MyTest { + @Test + @EnabledOnJre(%s) + void notOnThisJava() { + System.out.println("Not this Java"); + } + } + """.formatted(jre) + ) + ); + } + + @Test + void keepAnnotationValuesForCurrentOrNewerJREs() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + + class MyTest { + @Test + @EnabledOnJre(versions = { 11, 17, 21 }) + void testOnJavaSomeJaveVersions() { + System.out.println("Java 17 and 21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + + class MyTest { + @Test + @EnabledOnJre(versions = {17, 21}) + void testOnJavaSomeJaveVersions() { + System.out.println("Java 17 and 21"); + } + } + """ + ) + ); + } + + @Test + void filterAnnotationVersionsToBeCurrentOrHigher() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + + class MyTest { + @Test + @EnabledOnJre(versions = { 11, 17 }) + void testOnJavaSomeJaveVersions() { + System.out.println("Java 17"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + + class MyTest { + @Test + @EnabledOnJre(versions = 17) + void testOnJavaSomeJaveVersions() { + System.out.println("Java 17"); + } + } + """ + ) + ); + } + + @Test + void withMinimumJava21() { + rewriteRun( + spec -> spec.recipe(new MinimumJreConditions("21")), + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledOnJre(JRE.JAVA_17) + void testOnJava17() { + System.out.println("Java 17"); + } + + @Test + @EnabledOnJre(JRE.JAVA_21) + void testOnJava21() { + System.out.println("Java 21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + + @Test + @EnabledOnJre(JRE.JAVA_21) + void testOnJava21() { + System.out.println("Java 21"); + } + } + """ + ) + ); + } + } + + @Nested + class DisabledOnJreTests { + @ParameterizedTest + @ValueSource(strings = { + "JAVA_11", + "JAVA_8", + "JRE.JAVA_11", + "JRE.JAVA_8", + "value = JRE.JAVA_11", + "value = JRE.JAVA_8", + "versions = { 11 }", + "versions = { 8, 11 }", + "versions = { 8 }" + }) + void removeAnnotationDisabledOnOlderJre(String jre) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + import static org.junit.jupiter.api.condition.JRE.*; + + class MyTest { + @Test + @DisabledOnJre(%s) + void testNotOnJava8() { + System.out.println("Not Java 8"); + } + } + """.formatted(jre), + """ + import org.junit.jupiter.api.Test; + + class MyTest { + @Test + void testNotOnJava8() { + System.out.println("Not Java 8"); + } + } + """ + ) + ); + } + + @Test + void keepAnnotationValuesForCurrentOrNewerJREs() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledOnJre; + + class MyTest { + @Test + @DisabledOnJre(versions = { 8, 17, 21 }) + void testNotOnJava17() { + System.out.println("Not Java 17 or 21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledOnJre; + + class MyTest { + @Test + @DisabledOnJre(versions = {17, 21}) + void testNotOnJava17() { + System.out.println("Not Java 17 or 21"); + } + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "JRE.JAVA_17", + "value = JRE.JAVA_17", + "versions = 17", + "JAVA_17", + "JRE.JAVA_21", + "value = JRE.JAVA_21", + "versions = 21", + "JAVA_21" + }) + void keepDisabledOnCurrentOrNewerJRE(String jre) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + import static org.junit.jupiter.api.condition.JRE.*; + + class MyTest { + @Test + @DisabledOnJre(%s) + void testNotOnJava17() { + System.out.println("Not Java 17"); + } + } + """.formatted(jre) + ) + ); + } + + @Test + void doNotUnwrapIfNotTouched() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledOnJre; + + class MyTest { + @Test + @DisabledOnJre(versions = { 17 }) + void notOnThisJava() { + System.out.println("Not this Java"); + } + } + """ + ) + ); + } + } + + @Nested + class EnabledForJreRange { + @ParameterizedTest + @ValueSource(strings = { + "min = JRE.JAVA_8, max = JRE.JAVA_11", + "minVersion = 8, maxVersion = 11" + }) + void removeTestEnabledForRangeEndingBeforeMinimum(String range) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(%s) + void testOnJava8To11() { + System.out.println("Java 8-11"); + } + } + """.formatted(range), + """ + class MyTest { + } + """ + ) + ); + } + + @Test + void keepEnabledForRangeStartingAtMinimum() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(minVersion = 17, maxVersion = 21) + void testOnJava17To21() { + System.out.println("Java 17-21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(maxVersion = 21) + void testOnJava17To21() { + System.out.println("Java 17-21"); + } + } + """ + ) + ); + } + + @Test + void keepEnabledForRangeStartingAtMinimumWithJREImport() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_21) + void testOnJava17To21() { + System.out.println("Java 17-21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(max = JRE.JAVA_21) + void testOnJava17To21() { + System.out.println("Java 17-21"); + } + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "min = JRE.JAVA_21", + "minVersion = 21" + }) + void keepEnabledForRangeAfterMinimum(String range) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(%s) + void testOnJava21AndLater() { + System.out.println("Java 21+"); + } + } + """.formatted(range) + ) + ); + } + + @Test + void handleRangeWithOnlyMinWithJREImport() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(min = JRE.JAVA_11) + void testOnJava11AndLater() { + System.out.println("Java 11+"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + class MyTest { + @Test + void testOnJava11AndLater() { + System.out.println("Java 11+"); + } + } + """ + ) + ); + } + + @Test + void handleRangeWithOnlyMin() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + + class MyTest { + @Test + @EnabledForJreRange(minVersion = 11) + void testOnJava11AndLater() { + System.out.println("Java 11+"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + class MyTest { + @Test + void testOnJava11AndLater() { + System.out.println("Java 11+"); + } + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "max = JRE.JAVA_11", + "maxVersion = 11" + }) + void handleRangeWithOnlyMax(String range) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(%s) + void testUpToJava11() { + System.out.println("Up to Java 11"); + } + } + """.formatted(range), + """ + class MyTest { + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "min = JRE.JAVA_8, max = JRE.JAVA_16", + "minVersion = 8, maxVersion = 16" + }) + void handleEnabledForRangeEndingAt16(String range) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(%s) + void testOnJava8To16() { + System.out.println("Java 8-16"); + } + } + """.formatted(range), + """ + class MyTest { + } + """ + ) + ); + } + + @Test + void handleEnabledForRangeStartingBefore17EndingAfter() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + + class MyTest { + @Test + @EnabledForJreRange(minVersion = 11, maxVersion = 21) + void testOnJava11To21() { + System.out.println("Java 11-21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + + class MyTest { + @Test + @EnabledForJreRange(maxVersion = 21) + void testOnJava11To21() { + System.out.println("Java 11-21"); + } + } + """ + ) + ); + } + + @Test + void handleEnabledForRangeStartingBefore17EndingAfterWithJREImport() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_21) + void testOnJava11To21() { + System.out.println("Java 11-21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(max = JRE.JAVA_21) + void testOnJava11To21() { + System.out.println("Java 11-21"); + } + } + """ + ) + ); + } + + @Test + void handleEnabledForRangeStartingBefore17EndingOn17() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + + class MyTest { + @Test + @EnabledForJreRange(minVersion = 11, maxVersion = 17) + void testOnJava11To21() { + System.out.println("Java 11-17"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + + class MyTest { + @Test + @EnabledOnJre(versions = 17) + void testOnJava11To21() { + System.out.println("Java 11-17"); + } + } + """ + ) + ); + } + + @Test + void handleEnabledForRangeStartingBefore17EndingOn17WithJREImport() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_17) + void testOnJava11To21() { + System.out.println("Java 11-17"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledOnJre(JRE.JAVA_17) + void testOnJava11To21() { + System.out.println("Java 11-17"); + } + } + """ + ) + ); + } + } + + @Nested + class DisabledForJreRange { + @ParameterizedTest + @ValueSource(strings = { + "min = JRE.JAVA_8, max = JRE.JAVA_11", + "minVersion = 8, maxVersion = 11" + }) + void removeAnnotationDisabledForRangeEndingBeforeMinimum(String range) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledForJreRange(%s) + void testNotOnJava8To11() { + System.out.println("Not Java 8-11"); + } + } + """.formatted(range), + """ + import org.junit.jupiter.api.Test; + + class MyTest { + @Test + void testNotOnJava8To11() { + System.out.println("Not Java 8-11"); + } + } + """ + ) + ); + } + + @CsvSource(value = { + "min = JRE.JAVA_17, max = JRE.JAVA_21;max = JRE.JAVA_21", + "minVersion = 17, maxVersion = 21;maxVersion = 21" + }, delimiter = ';') + @ParameterizedTest + void keepDisabledForRangeStartingAtMinimum(String range, String afterRange) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledForJreRange(%s) + void testNotOnJava17To21() { + System.out.println("Not Java 17-21"); + } + } + """.formatted(range), + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledForJreRange(%s) + void testNotOnJava17To21() { + System.out.println("Not Java 17-21"); + } + } + """.formatted(afterRange) + ) + ); + } + + @Test + void handleDisabledForRangeStartingBefore17() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + + class MyTest { + @Test + @DisabledForJreRange(minVersion = 11, maxVersion = 21) + void testNotOnJava11To21() { + System.out.println("Not Java 11-21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + + class MyTest { + @Test + @DisabledForJreRange(maxVersion = 21) + void testNotOnJava11To21() { + System.out.println("Not Java 11-21"); + } + } + """ + ) + ); + } + + @Test + void handleDisabledForRangeStartingBefore17WithJREImport() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_21) + void testNotOnJava11To21() { + System.out.println("Not Java 11-21"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledForJreRange(max = JRE.JAVA_21) + void testNotOnJava11To21() { + System.out.println("Not Java 11-21"); + } + } + """ + ) + ); + } + + @Test + void handleRangeWithOnlyMinWithJREImport() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledForJreRange(min = JRE.JAVA_11) + void testOnJava11AndLater() { + System.out.println("Java 11+"); + } + } + """, + """ + + class MyTest { + } + """ + ) + ); + } + + @Test + void handleRangeWithOnlyMin() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledForJreRange; + + class MyTest { + @Test + @EnabledForJreRange(minVersion = 11) + void testOnJava11AndLater() { + System.out.println("Java 11+"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + class MyTest { + @Test + void testOnJava11AndLater() { + System.out.println("Java 11+"); + } + } + """ + ) + ); + } + } + + @Test + void convertRangeEndingOnCurrentVersionToDisabledAnnotation() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + + class MyTest { + @Test + @DisabledForJreRange(minVersion = 11, maxVersion = 17) + void testNotOnJava11To17() { + System.out.println("Not Java 11-17"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledOnJre; + + class MyTest { + @Test + @DisabledOnJre(versions = 17) + void testNotOnJava11To17() { + System.out.println("Not Java 11-17"); + } + } + """ + ) + ); + } + + @Test + void convertRangeEndingOnCurrentVersionToDisabledAnnotationWithJREImport() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledForJreRange; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_17) + void testNotOnJava11To17() { + System.out.println("Not Java 11-17"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.DisabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @DisabledOnJre(JRE.JAVA_17) + void testNotOnJava11To17() { + System.out.println("Not Java 11-17"); + } + } + """ + ) + ); + } + + @Test + void doNotRemoveNonTestMethods() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @EnabledOnJre(JRE.JAVA_8) + void notATest() { + System.out.println("Not a test"); + } + } + """ + ) + ); + } + + @Test + void tolerateOtherJreValue() { + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledOnJre; + import org.junit.jupiter.api.condition.JRE; + + class MyTest { + @Test + @EnabledOnJre(JRE.OTHER) + void testOnOther() { + System.out.println("Other JRE"); + } + } + """ + ) + ); + } +}