From c4846ab59b19f612c7be57b9560e8e313742c609 Mon Sep 17 00:00:00 2001 From: Philzen Date: Thu, 6 Jun 2024 22:46:51 +0200 Subject: [PATCH 01/51] Add TestNG dependency, so it is available on the classpath for tests --- build.gradle.kts | 3 +++ pom.xml | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 4ca33620..a3a6171e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,6 +55,9 @@ dependencies { // Contains the OpenRewriteBestPractices recipe, which you can apply to your recipes rewrite("org.openrewrite.recipe:rewrite-recommendations:latest.release") + + // ↓ Classpath resource for MigrateTestNg* recipes + testRuntimeOnly("org.testng:testng:7.5.1") // 7.5.x is the last Java 8 compatible version: https://github.com/testng-team/testng/issues/2775 } signing { diff --git a/pom.xml b/pom.xml index 5c0fc776..211bade3 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,16 @@ 6.1.8 test + + + + org.testng + testng + + 7.5.1 + test + + From 09d1ee0bacf25baf0cf50180ba97f6649e67f2a7 Mon Sep 17 00:00:00 2001 From: Philzen Date: Thu, 6 Jun 2024 21:29:24 +0200 Subject: [PATCH 02/51] =?UTF-8?q?Implement=20TestNG=20=E2=86=92=20JUnit5?= =?UTF-8?q?=20@Test=20annotation=20migration=20recipe=20with=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves #17, #19 and #26 --- .../testng/UpdateTestAnnotationToJunit5.java | 202 ++++++ .../UpdateTestAnnotationToJunit5Test.java | 686 ++++++++++++++++++ 2 files changed, 888 insertions(+) create mode 100644 src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java create mode 100644 src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java new file mode 100644 index 00000000..bd576a5f --- /dev/null +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -0,0 +1,202 @@ +package org.philzen.oss.testng; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.*; +import org.openrewrite.java.search.FindImports; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.marker.Markup; + +import java.util.Comparator; +import java.util.Objects; + +@Value +@NonNullApi +@EqualsAndHashCode(callSuper = true) +public class UpdateTestAnnotationToJunit5 extends Recipe { + + @Override + public String getDisplayName() { + return "Migrate TestNG `@Test` annotations to JUnit 5"; + } + + @Override + public String getDescription() { + return "Update usages of TestNG's `@org.testng.annotations.Test` annotation to JUnit 5's `@org.junit.jupiter.api.Test` annotation."; + } + + @Nullable + static private JavaParser.Builder javaParser; + static private JavaParser.Builder javaParser() { + if (javaParser == null) { + javaParser = JavaParser.fromJavaVersion().classpath("junit-jupiter-api"); + } + return javaParser; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(Preconditions.or( + new UsesType<>("org.testng.annotations.Test", false), + new FindImports("org.testng.annotations.Test", null).getVisitor() + ), new UpdateTestAnnotationToJunit5Visitor()); + } + + // inspired by https://github.com/openrewrite/rewrite-testing-frameworks/blob/4e8ba68b2a28a180f84de7bab9eb12b4643e342e/src/main/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotation.java# + private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor { + + private static final AnnotationMatcher TESTNG_TEST = new AnnotationMatcher("@org.testng.annotations.Test"); + + private final JavaTemplate junitExecutable = JavaTemplate + .builder("org.junit.jupiter.api.function.Executable o = () -> #{};") + .javaParser(javaParser()).build(); + + private final JavaTemplate timeoutAnnotation = JavaTemplate + .builder("@Timeout(value = #{any(long)}, unit = TimeUnit.MILLISECONDS)") + .imports("org.junit.jupiter.api.Timeout", "java.util.concurrent.TimeUnit") + .javaParser(javaParser()).build(); + + @Override + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { + J.CompilationUnit c = super.visitCompilationUnit(cu, ctx); + if (!c.findType("org.testng.annotations.Test").isEmpty()) { + // Update other references like `Test.class`. + c = (J.CompilationUnit) new ChangeType("org.testng.annotations.Test", "org.junit.jupiter.api.Test", true) + .getVisitor().visitNonNull(c, ctx); + } + + maybeRemoveImport("org.testng.annotations.Test"); + doAfterVisit(new JavaIsoVisitor() { + @Override + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { + return cu.withClasses(ListUtils.map(cu.getClasses(), clazz -> (J.ClassDeclaration) visit(clazz, ctx))) + // take one more pass over the imports now that we've had a chance to add warnings to all + // uses of @Test through the rest of the source file + .withImports(ListUtils.map(cu.getImports(), anImport -> (J.Import) visit(anImport, ctx))); + } + + @Override + public J.Import visitImport(J.Import anImport, ExecutionContext ctx) { + if ("org.testng.annotations.Test".equals(anImport.getTypeName())) { + return Markup.error(anImport, new IllegalStateException("This import should have been removed by this recipe.")); + } + return anImport; + } + + @Override + public JavaType visitType(@Nullable JavaType javaType, ExecutionContext ctx) { + if (TypeUtils.isOfClassType(javaType, "org.testng.annotations.Test")) { + getCursor().putMessageOnFirstEnclosing(J.class, "danglingTestRef", true); + } + return javaType; + } + + @Override + public J postVisit(J tree, ExecutionContext ctx) { + if (getCursor().getMessage("danglingTestRef", false)) { + return Markup.warn(tree, new IllegalStateException("This still has a type of `org.testng.annotations.Test`")); + } + return tree; + } + }); + + return c; + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + final ChangeTestAnnotation cta = new ChangeTestAnnotation(); + J.MethodDeclaration m = (J.MethodDeclaration) cta.visitNonNull(method, ctx, getCursor().getParentOrThrow()); + if (m == method) { + return super.visitMethodDeclaration(method, ctx); + } + + if (cta.expectedException instanceof J.FieldAccess + && TypeUtils.isAssignableTo("java.lang.Throwable", ((J.FieldAccess) cta.expectedException).getTarget().getType())) + { + m = junitExecutable.apply(updateCursor(m), m.getCoordinates().replaceBody(), m.getBody()); + final J.Block body = m.getBody(); + assert body != null; + final J.Lambda lambda = (J.Lambda) + ((J.VariableDeclarations) body.getStatements().get(0)) + .getVariables().get(0).getInitializer(); + + m = JavaTemplate.builder("Assertions.assertThrows(#{any(java.lang.Class)}, #{any(org.junit.jupiter.api.function.Executable)});") + .javaParser(javaParser()) + .imports("org.junit.jupiter.api.Assertions").build() + .apply(updateCursor(m), m.getCoordinates().replaceBody(), cta.expectedException, lambda); + maybeAddImport("org.junit.jupiter.api.Assertions"); + } + + if (cta.timeout != null) { + maybeAddImport("java.util.concurrent.TimeUnit"); + maybeAddImport("org.junit.jupiter.api.Timeout"); + m = timeoutAnnotation.apply( + updateCursor(m), + m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)), + cta.timeout + ); + } + maybeAddImport("org.junit.jupiter.api.Test"); + + return super.visitMethodDeclaration(m, ctx); + } + + private static class ChangeTestAnnotation extends JavaIsoVisitor { + + private boolean found; + + @Nullable + Expression expectedException, timeout; + + @Override + public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { + if (found || !TESTNG_TEST.matches(a)) { + return a; + } + + // While unlikely, it's possible that a method has an inner class/lambda/etc. with methods that have test annotations + // Avoid considering any but the first test annotation found + found = true; + if (a.getArguments() != null) { + for (Expression arg : a.getArguments()) { + if (!(arg instanceof J.Assignment)) { + continue; + } + final J.Assignment assign = (J.Assignment) arg; + final String assignParamName = ((J.Identifier) assign.getVariable()).getSimpleName(); + final Expression e = assign.getAssignment(); + if ("expectedExceptions".equals(assignParamName)) { + // if attribute was given in { array form }, pick the first element (null is not allowed) + expectedException = !(e instanceof J.NewArray) + ? e : Objects.requireNonNull(((J.NewArray) e).getInitializer()).get(0); + } else if ("timeOut".equals(assignParamName)) { + timeout = e; + } + } + } + + if (a.getAnnotationType() instanceof J.FieldAccess) { + return JavaTemplate.builder("@org.junit.jupiter.api.Test") + .javaParser(javaParser()) + .build() + .apply(getCursor(), a.getCoordinates().replace()); + } else { + return a.withArguments(null) + .withType(JavaType.ShallowClass.build("org.junit.jupiter.api.Test")); + } + } + } + } +} diff --git a/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java b/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java new file mode 100644 index 00000000..4e8db8a4 --- /dev/null +++ b/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java @@ -0,0 +1,686 @@ +package org.philzen.oss.testng; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UpdateTestAnnotationToJunit5Test implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UpdateTestAnnotationToJunit5()); + } + + @Nested class NoAttributes { + + @Test void isMigratedToJunitTestAnnotationWithoutParameters() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + import org.testng.annotations.Test; + + public class BazTest { + + @Test + public void shouldDoStuff() { + // + } + } + """, + """ + package de.foo.bar; + + import org.junit.jupiter.api.Test; + + public class BazTest { + + @Test + public void shouldDoStuff() { + // + } + } + """ + )); + } + + @Test void isMigratedPreservingOtherAnnotationsAndComments() { + // language=java + rewriteRun(java( + """ + package org.openrewrite; + public @interface Issue { + String value(); + } + """ + ), java( + """ + import org.testng.annotations.Test; + import org.openrewrite.Issue; + + public class MyTest { + + // some comments + @Issue("some issue") + @Test + public void test() { + } + + // some more comments + @Test + public void test1() { + } + + @Test + // even more comments + public void test2() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.openrewrite.Issue; + + public class MyTest { + + // some comments + @Issue("some issue") + @Test + public void test() { + } + + // some more comments + @Test + public void test1() { + } + + @Test + // even more comments + public void test2() { + } + } + """ + )); + } + + @Test void isMigratedWhenReferencedAsVariable() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + public class MyTest { + Object o = Test.class; + } + """, + """ + import org.junit.jupiter.api.Test; + + public class MyTest { + Object o = Test.class; + } + """ + )); + } + + @Test void isMigrated_whenUsedInJavadoc() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + /** @see org.testng.annotations.Test */ + public class MyTest { + @Test + public void test() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + + /** @see org.junit.jupiter.api.Test */ + public class MyTest { + @Test + public void test() { + } + } + """ + )); + } + + @Test void noTestAnnotationValues_sameLine_multipleImports() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + import java.util.List; + import org.testng.annotations.Test; + import static org.assertj.core.api.Assertions.assertThat; + + public class Baz { + + @Test public void shouldDoStuff() { + // + } + } + """, + """ + package de.foo.bar; + + import org.junit.jupiter.api.Test; + + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + public class Baz { + + @Test public void shouldDoStuff() { + // + } + } + """ + )); + } + + @Test void fullyQualified() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + class Baz { + + @org.testng.annotations.Test + public void shouldDoStuff() { + // + } + } + """, + """ + package de.foo.bar; + + class Baz { + + @org.junit.jupiter.api.Test + public void shouldDoStuff() { + // + } + } + """ + )); + } + + @Test void fullyQualified_sameLineAsMethodDeclaration() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + class Baz { + + @org.testng.annotations.Test public void shouldDoStuff() { + // + } + } + """, + """ + package de.foo.bar; + + class Baz { + + @org.junit.jupiter.api.Test public void shouldDoStuff() { + // + } + } + """ + )); + } + + @Test void mixedFullyQualifiedAndNot() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + public class MyTest { + @org.testng.annotations.Test + public void feature1() { + } + + @Test + public void feature2() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + + public class MyTest { + @org.junit.jupiter.api.Test + public void feature1() { + } + + @Test + public void feature2() { + } + } + """ + )); + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @Test void nestedClass() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + class Baz { + + @Test public void shouldDoStuff() { + // + } + + public class NestedGroupedTests { + + @Test public void shouldDoStuff() { + // + } + } + } + """, + """ + import org.junit.jupiter.api.Test; + + class Baz { + + @Test public void shouldDoStuff() { + // + } + + public class NestedGroupedTests { + + @Test public void shouldDoStuff() { + // + } + } + } + """ + )); + } + + @Test void noChangeNecessary() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + import org.junit.jupiter.api.Test; + + class Baz { + + @Test public void shouldDoStuff() { + // + } + } + """ + )); + } + + @Test void noChangeNecessary_fullyQualified() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + class Baz { + + @org.junit.jupiter.api.Test public void shouldDoStuff() { + // + } + } + """ + )); + } + + @Test void noChangeNecessary_nestedClass() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + import org.junit.jupiter.api.Nested; + import org.junit.jupiter.api.Test; + + class Baz { + + @Test public void shouldDoStuff() { + // + } + + @Nested class NestedGroupedTests { + + @Test public void shouldDoStuff() { + // + } + } + } + """ + )); + } + } + + @Nested class Attribute_expectedExceptions { + + @Test void isMigratedToBodyWrappedInAssertThrows_forLiteralJavaExceptionThrown() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test() { + throw new IllegalArgumentException("boom"); + } + } + """, + """ + import org.junit.jupiter.api.Assertions; + import org.junit.jupiter.api.Test; + + public class MyTest { + + @Test + public void test() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + throw new IllegalArgumentException("boom"); + }); + } + } + """ + )); + } + + @Test void isMigratedToBodyWrappedInAssertThrows_forLiteralCustomExceptionThrown() { + // language=java + rewriteRun(java( + """ + package com.abc; + public class MyException extends Exception { + public MyException(String message) { + super(message); + } + } + """ + ), java( + """ + import com.abc.MyException; + import org.testng.annotations.Test; + + public class MyTest { + + @Test(expectedExceptions = MyException.class) + public void test() { + throw new MyException("my exception"); + } + } + """, + """ + import com.abc.MyException; + import org.junit.jupiter.api.Assertions; + import org.junit.jupiter.api.Test; + + public class MyTest { + + @Test + public void test() { + Assertions.assertThrows(MyException.class, () -> { + throw new MyException("my exception"); + }); + } + } + """ + )); + } + + @SuppressWarnings("ConstantConditions") + @Test void isMigratedToBodyWrappedInAssertThrows_forSingleStatement() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(expectedExceptions = IndexOutOfBoundsException.class) + public void test() { + int arr = new int[]{}[0]; + } + } + """, + """ + import org.junit.jupiter.api.Assertions; + import org.junit.jupiter.api.Test; + + public class MyTest { + + @Test + public void test() { + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> { + int arr = new int[]{}[0]; + }); + } + } + """ + )); + } + + @Test void isMigratedToBodyWrappedInAssertThrows() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test() { + String foo = "foo"; + throw new IllegalArgumentException("boom"); + } + } + """, + """ + import org.junit.jupiter.api.Assertions; + import org.junit.jupiter.api.Test; + + public class MyTest { + + @Test + public void test() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String foo = "foo"; + throw new IllegalArgumentException("boom"); + }); + } + } + """ + )); + } + + @Nested class Array { + + @Test void extractsFirstElementOfMultiple() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(expectedExceptions = { RuntimeException.class, IllegalAccessError.class, UnknownError.class } ) + public void test() { + throw new RuntimeException("Whooopsie!"); + } + } + """, + """ + import org.junit.jupiter.api.Assertions; + import org.junit.jupiter.api.Test; + + public class MyTest { + + @Test + public void test() { + Assertions.assertThrows(RuntimeException.class, () -> { + throw new RuntimeException("Whooopsie!"); + }); + } + } + """ + ) + ); + } + @SuppressWarnings("DefaultAnnotationParam") + @Test void doesNotAddAssert_ifEmpty() { + // language=java + rewriteRun( + java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(expectedExceptions = { } ) + public void test() { + throw new RuntimeException("Not really caught nor tested"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + public class MyTest { + + @Test + public void test() { + throw new RuntimeException("Not really caught nor tested"); + } + } + """ + ) + ); + } + } + } + + @Nested class Attribute_timeOut { + + @Test void isMigratedToTimeoutAnnotation() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(timeOut = 500) + public void test() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.Timeout; + + import java.util.concurrent.TimeUnit; + + public class MyTest { + + @Test + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) + public void test() { + } + } + """ + )); + } + + /** + * Unfortunately doesn't keep annotation on same line + * TODO investigate how this could be achieved + */ + @Test void isMigratedToTimeoutAnnotation_butNotPreservingSameLine() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(timeOut = 500) public void test() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.Timeout; + + import java.util.concurrent.TimeUnit; + + public class MyTest { + + @Test + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) + public void test() { + } + } + """ + )); + } + } + + @Nested class MultipleAttributes { + + @Test void expectedExceptions_and_timeOut() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + public class MyTest { + + @Test(expectedExceptions = IllegalArgumentException.class, timeOut = 500) + public void test() { + throw new IllegalArgumentException("boom"); + } + } + """, + """ + import org.junit.jupiter.api.Assertions; + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.Timeout; + + import java.util.concurrent.TimeUnit; + + public class MyTest { + + @Test + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) + public void test() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + throw new IllegalArgumentException("boom"); + }); + } + } + """ + )); + } + } +} From a94d4f3da03044008a3114be6e1259e7ff4cd429 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 8 Jun 2024 01:26:21 +0200 Subject: [PATCH 03/51] Fix extraneous import added when all usages are fully qualified --- .../org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index bd576a5f..0d8db391 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -148,7 +148,6 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex cta.timeout ); } - maybeAddImport("org.junit.jupiter.api.Test"); return super.visitMethodDeclaration(m, ctx); } From 19b3099f34b7ce6460d2f3240120e42d1a3b82de Mon Sep 17 00:00:00 2001 From: Philzen Date: Fri, 7 Jun 2024 01:41:56 +0200 Subject: [PATCH 04/51] Implement migration of expectedExceptionsMessageRegExp annotation attribute Resolves #22 --- .../testng/UpdateTestAnnotationToJunit5.java | 27 +++++++++++++--- .../UpdateTestAnnotationToJunit5Test.java | 32 +++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 0d8db391..358f2b69 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -18,7 +18,9 @@ import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.Markup; +import java.util.Arrays; import java.util.Comparator; +import java.util.List; import java.util.Objects; @Value @@ -132,11 +134,24 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex ((J.VariableDeclarations) body.getStatements().get(0)) .getVariables().get(0).getInitializer(); - m = JavaTemplate.builder("Assertions.assertThrows(#{any(java.lang.Class)}, #{any(org.junit.jupiter.api.function.Executable)});") - .javaParser(javaParser()) - .imports("org.junit.jupiter.api.Assertions").build() - .apply(updateCursor(m), m.getCoordinates().replaceBody(), cta.expectedException, lambda); maybeAddImport("org.junit.jupiter.api.Assertions"); + final List parameters = Arrays.asList(cta.expectedException, lambda); + final String code = "Assertions.assertThrows(#{any(java.lang.Class)}, #{any(org.junit.jupiter.api.function.Executable)});"; + if (!(cta.expectedExceptionMessageRegExp instanceof J.Literal)) { + m = JavaTemplate.builder(code).javaParser(javaParser()) + .imports("org.junit.jupiter.api.Assertions").build() + .apply(updateCursor(m), m.getCoordinates().replaceBody(), parameters.toArray()); + } else { + m = JavaTemplate.builder( + "final Throwable thrown = " + code + System.lineSeparator() + + "Assertions.assertTrue(thrown.getMessage().matches(#{any(java.lang.String)}));" + ).javaParser(javaParser()).imports("org.junit.jupiter.api.Assertions").build() + .apply( + updateCursor(m), + m.getCoordinates().replaceBody(), + ListUtils.concat(parameters, cta.expectedExceptionMessageRegExp).toArray() + ); + } } if (cta.timeout != null) { @@ -157,7 +172,7 @@ private static class ChangeTestAnnotation extends JavaIsoVisitor { + throw new IllegalArgumentException("boom !"); + }); + Assertions.assertTrue(thrown.getMessage().matches("boom.*!")); + } + } + """ + )); + } + @Test void isMigratedToBodyWrappedInAssertThrows_forLiteralCustomExceptionThrown() { // language=java rewriteRun(java( From 2f74be1960486cf05ea80b64b1270af954f5db6a Mon Sep 17 00:00:00 2001 From: Philzen Date: Fri, 7 Jun 2024 05:14:11 +0200 Subject: [PATCH 05/51] Implement migration of TestNG enabled = false to Junit5 @Disabled Resolves #18 --- .../testng/UpdateTestAnnotationToJunit5.java | 20 +++++++++-- .../UpdateTestAnnotationToJunit5Test.java | 35 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 358f2b69..9bb9dc46 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -60,6 +60,11 @@ private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor< private static final AnnotationMatcher TESTNG_TEST = new AnnotationMatcher("@org.testng.annotations.Test"); + private final JavaTemplate disabledAnnotation = JavaTemplate + .builder("@Disabled") + .imports("org.junit.jupiter.api.Disabled") + .javaParser(javaParser()).build(); + private final JavaTemplate junitExecutable = JavaTemplate .builder("org.junit.jupiter.api.function.Executable o = () -> #{};") .javaParser(javaParser()).build(); @@ -124,7 +129,16 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex return super.visitMethodDeclaration(method, ctx); } + if (J.Literal.isLiteralValue(cta.enabled, Boolean.FALSE)) { + maybeAddImport("org.junit.jupiter.api.Disabled"); + m = disabledAnnotation.apply( + updateCursor(m), + m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)) + ); + } + if (cta.expectedException instanceof J.FieldAccess + // TestNG actually allows any type of Class here, however anything but a Throwable doesn't make sense && TypeUtils.isAssignableTo("java.lang.Throwable", ((J.FieldAccess) cta.expectedException).getTarget().getType())) { m = junitExecutable.apply(updateCursor(m), m.getCoordinates().replaceBody(), m.getBody()); @@ -172,7 +186,7 @@ private static class ChangeTestAnnotation extends JavaIsoVisitor Date: Fri, 7 Jun 2024 05:31:13 +0200 Subject: [PATCH 06/51] Add general introductory text and Java 8 compatiblity badge --- .github/workflows/ci.yml | 2 +- README.md | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42aa4d4e..f5e32d45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ --- -name: ci +name: Build on: push: diff --git a/README.md b/README.md index 35e42253..2390d589 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +# TestNG to Junit5 recipe   [![Compatible with Java 8](https://img.shields.io/badge/Works%20on%20Java-8-seagreen?logo=openjdk&labelColor=snow&logoColor=black)](#) + +[![Build](https://github.com/Philzen/rewrite-recipe-testng-to-junit-jupiter/actions/workflows/ci.yml/badge.svg)](https://github.com/Philzen/rewrite-recipe-testng-to-junit-jupiter/actions/workflows/ci.yml) + +Converts [TestNG](https://testng.org/) test annotations and assertions to +[Junit 5](https://junit.org/junit5/docs/current/user-guide/). + +Inspired by the [Migrate JUnit 4 @Test annotations to JUnit 5](https://docs.openrewrite.org/recipes/java/testing/junit5/updatetestannotation) recipe + ## Rewrite recipe starter This repository serves as a template for building your own recipe JARs and publishing them to a repository where they can be applied on [app.moderne.io](https://app.moderne.io) against all the public OSS code that is included there. @@ -140,4 +149,4 @@ You can apply these recommendations to your recipes by running the following com or ```bash mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-recommendations:RELEASE -Drewrite.activeRecipes=org.openrewrite.recipes.OpenRewriteBestPractices -``` \ No newline at end of file +``` From 0b63586ede02a86e81222d0545a9fb2349d3d5c6 Mon Sep 17 00:00:00 2001 From: Philzen Date: Fri, 7 Jun 2024 08:20:08 +0200 Subject: [PATCH 07/51] Implement migration of TestNG @Test(description) to Junit5 @DisplayName Resolves #24 --- .../testng/UpdateTestAnnotationToJunit5.java | 22 +++++- .../UpdateTestAnnotationToJunit5Test.java | 70 +++++++++++++++++-- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 9bb9dc46..e968a0b1 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -60,6 +60,11 @@ private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor< private static final AnnotationMatcher TESTNG_TEST = new AnnotationMatcher("@org.testng.annotations.Test"); + private final JavaTemplate displayNameAnnotation = JavaTemplate + .builder("@DisplayName(#{any(java.lang.String)})") + .imports("org.junit.jupiter.api.DisplayName") + .javaParser(javaParser()).build(); + private final JavaTemplate disabledAnnotation = JavaTemplate .builder("@Disabled") .imports("org.junit.jupiter.api.Disabled") @@ -129,11 +134,20 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex return super.visitMethodDeclaration(method, ctx); } + if (cta.description != null && !J.Literal.isLiteralValue(cta.description, "")) { + maybeAddImport("org.junit.jupiter.api.DisplayName"); + m = displayNameAnnotation.apply( + updateCursor(m), + m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()), + cta.description + ); + } + if (J.Literal.isLiteralValue(cta.enabled, Boolean.FALSE)) { maybeAddImport("org.junit.jupiter.api.Disabled"); m = disabledAnnotation.apply( updateCursor(m), - m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)) + m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()) ); } @@ -186,7 +200,7 @@ private static class ChangeTestAnnotation extends JavaIsoVisitor Date: Sat, 8 Jun 2024 01:13:27 +0200 Subject: [PATCH 08/51] Implement migration of TestNG @Test(groups) to Junit5 @Tag Resolves #28 --- .../testng/UpdateTestAnnotationToJunit5.java | 30 +++- .../UpdateTestAnnotationToJunit5Test.java | 143 ++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index e968a0b1..51b4f61b 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -74,6 +74,11 @@ private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor< .builder("org.junit.jupiter.api.function.Executable o = () -> #{};") .javaParser(javaParser()).build(); + private final JavaTemplate tagAnnotation = JavaTemplate + .builder("@Tag(#{any(java.lang.String)})") + .imports("org.junit.jupiter.api.Tag") + .javaParser(javaParser()).build(); + private final JavaTemplate timeoutAnnotation = JavaTemplate .builder("@Timeout(value = #{any(long)}, unit = TimeUnit.MILLISECONDS)") .imports("org.junit.jupiter.api.Timeout", "java.util.concurrent.TimeUnit") @@ -182,6 +187,27 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex } } + if (cta.groups != null) { + maybeAddImport("org.junit.jupiter.api.Tag"); + if (cta.groups instanceof J.Literal && !J.Literal.isLiteralValue(cta.groups, "")) { + m = tagAnnotation.apply( + updateCursor(m), + m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()), + cta.groups + ); + } else if (cta.groups instanceof J.NewArray && ((J.NewArray) cta.groups).getInitializer() != null) { + final List groups = ((J.NewArray) cta.groups).getInitializer(); + for (Expression group : groups) { + if (group instanceof J.Empty) continue; + m = tagAnnotation.apply( + updateCursor(m), + m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()), + group + ); + } + } + } + if (cta.timeout != null) { maybeAddImport("java.util.concurrent.TimeUnit"); maybeAddImport("org.junit.jupiter.api.Timeout"); @@ -200,7 +226,7 @@ private static class ChangeTestAnnotation extends JavaIsoVisitor Date: Sun, 9 Jun 2024 05:57:38 +0200 Subject: [PATCH 09/51] Remove samples content --- .../org/philzen/oss/AppendToReleaseNotes.java | 108 ----------- .../philzen/oss/AssertEqualsToAssertThat.java | 89 --------- .../java/org/philzen/oss/ClassHierarchy.java | 73 ------- .../philzen/oss/NoGuavaListsNewArrayList.java | 111 ----------- .../java/org/philzen/oss/SimplifyTernary.java | 63 ------ .../java/org/philzen/oss/StringIsEmpty.java | 22 --- .../philzen/oss/UpdateConcoursePipeline.java | 92 --------- .../oss/table/ClassHierarchyReport.java | 50 ----- .../resources/META-INF/rewrite/rewrite.yml | 32 ---- .../META-INF/rewrite/stringutils.yml | 31 --- .../philzen/oss/AppendToReleaseNotesTest.java | 67 ------- .../oss/AssertEqualsToAssertThatTest.java | 89 --------- .../org/philzen/oss/ClassHierarchyTest.java | 88 --------- .../oss/NoGuavaListsNewArrayListTest.java | 179 ------------------ .../org/philzen/oss/SimplifyTernaryTest.java | 116 ------------ .../org/philzen/oss/StringIsEmptyTest.java | 170 ----------------- .../oss/UpdateConcoursePipelineTest.java | 57 ------ .../philzen/oss/UseApacheStringUtilsTest.java | 60 ------ .../oss/UseOpenRewriteNullableTest.java | 65 ------- 19 files changed, 1562 deletions(-) delete mode 100644 src/main/java/org/philzen/oss/AppendToReleaseNotes.java delete mode 100644 src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java delete mode 100644 src/main/java/org/philzen/oss/ClassHierarchy.java delete mode 100644 src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java delete mode 100644 src/main/java/org/philzen/oss/SimplifyTernary.java delete mode 100644 src/main/java/org/philzen/oss/StringIsEmpty.java delete mode 100644 src/main/java/org/philzen/oss/UpdateConcoursePipeline.java delete mode 100644 src/main/java/org/philzen/oss/table/ClassHierarchyReport.java delete mode 100644 src/main/resources/META-INF/rewrite/rewrite.yml delete mode 100644 src/main/resources/META-INF/rewrite/stringutils.yml delete mode 100644 src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java delete mode 100644 src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java delete mode 100644 src/test/java/org/philzen/oss/ClassHierarchyTest.java delete mode 100644 src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java delete mode 100644 src/test/java/org/philzen/oss/SimplifyTernaryTest.java delete mode 100644 src/test/java/org/philzen/oss/StringIsEmptyTest.java delete mode 100644 src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java delete mode 100644 src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java delete mode 100644 src/test/java/org/philzen/oss/UseOpenRewriteNullableTest.java diff --git a/src/main/java/org/philzen/oss/AppendToReleaseNotes.java b/src/main/java/org/philzen/oss/AppendToReleaseNotes.java deleted file mode 100644 index 623d4e1a..00000000 --- a/src/main/java/org/philzen/oss/AppendToReleaseNotes.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.text.PlainText; -import org.openrewrite.text.PlainTextParser; -import org.openrewrite.text.PlainTextVisitor; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.Collections; -import java.util.stream.Collectors; - -@Value -@EqualsAndHashCode(callSuper = false) -public class AppendToReleaseNotes extends ScanningRecipe { - - @Override - public String getDisplayName() { - return "Append to release notes"; - } - - @Override - public String getDescription() { - return "Adds the specified line to RELEASE.md."; - } - - @Option(displayName = "Message", - description = "Message to append to the bottom of RELEASE.md.", - example = "## 1.0.0\n\n- New feature") - String message; - - // The shared state between the scanner and the visitor. The custom class ensures we can easily extend the recipe. - public static class Accumulator { - boolean found; - } - - @Override - public Accumulator getInitialValue(ExecutionContext ctx) { - return new Accumulator(); - } - - @Override - public TreeVisitor getScanner(Accumulator acc) { - return new TreeVisitor() { - @Override - public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { - if (tree instanceof SourceFile) { - Path sourcePath = ((SourceFile) tree).getSourcePath(); - acc.found |= "RELEASE.md".equals(sourcePath.toString()); - } - return tree; - } - }; - } - - @Override - public Collection generate(Accumulator acc, ExecutionContext ctx) { - if (acc.found) { - return Collections.emptyList(); - } - // If the file was not found, create it - return PlainTextParser.builder().build() - // We start with an empty string that we then append to in the visitor - .parse("") - // Be sure to set the source path for any generated file, so that the visitor can find it - .map(it -> (SourceFile) it.withSourcePath(Paths.get("RELEASE.md"))) - .collect(Collectors.toList()); - } - - @Override - public TreeVisitor getVisitor(Accumulator acc) { - return new PlainTextVisitor() { - @Override - public PlainText visitText(PlainText text, ExecutionContext ctx) { - PlainText t = super.visitText(text, ctx); - // If the file is not RELEASE.md, don't modify it - if (!"RELEASE.md".equals(t.getSourcePath().toString())) { - return t; - } - // If the file already contains the message, don't append it again - if (t.getText().contains(message)) { - return t; - } - // Append the message to the end of the file - return t.withText(t.getText() + "\n" + message); - } - }; - } -} diff --git a/src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java b/src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java deleted file mode 100644 index bae3df57..00000000 --- a/src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Preconditions; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; - -import java.util.List; - -@Value -@EqualsAndHashCode(callSuper = false) -public class AssertEqualsToAssertThat extends Recipe { - @Override - public String getDisplayName() { - // language=markdown - return "JUnit `assertEquals()` to Assertj `assertThat()`"; - } - - @Override - public String getDescription() { - return "Use AssertJ assertThat instead of JUnit assertEquals()."; - } - - private static MethodMatcher MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertEquals(..)"); - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", null), - new JavaIsoVisitor() { - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - if (!MATCHER.matches(m)) { - return m; - } - List arguments = m.getArguments(); - maybeAddImport("org.assertj.core.api.Assertions"); - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - if (arguments.size() == 2) { - Expression expected = arguments.get(0); - Expression actual = arguments.get(1); - - m = JavaTemplate.builder("Assertions.assertThat(#{any()}).isEqualTo(#{any()})") - .imports("org.assertj.core.api.Assertions") - .javaParser(JavaParser.fromJavaVersion() - .classpath("assertj-core")) - .build() - .apply(getCursor(), m.getCoordinates().replace(), actual, expected); - } else if (arguments.size() == 3) { - Expression expected = arguments.get(0); - Expression actual = arguments.get(1); - Expression description = arguments.get(2); - - m = JavaTemplate.builder("Assertions.assertThat(#{any()}).as(#{any()}).isEqualTo(#{any()})") - .imports("org.assertj.core.api.Assertions") - .javaParser(JavaParser.fromJavaVersion() - .classpath("assertj-core")) - .build() - .apply(getCursor(), m.getCoordinates().replace(), actual, description, expected); - } - return m; - } - }); - } -} diff --git a/src/main/java/org/philzen/oss/ClassHierarchy.java b/src/main/java/org/philzen/oss/ClassHierarchy.java deleted file mode 100644 index bcb540e3..00000000 --- a/src/main/java/org/philzen/oss/ClassHierarchy.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.philzen.oss.table.ClassHierarchyReport; -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; - -@Value -@EqualsAndHashCode(callSuper = false) -public class ClassHierarchy extends Recipe { - - transient ClassHierarchyReport report = new ClassHierarchyReport(this); - - @Override - public String getDisplayName() { - return "Class hierarchy"; - } - - @Override - public String getDescription() { - return "Produces a data table showing inheritance relationships between classes."; - } - - @Override - public TreeVisitor getVisitor() { - return new JavaIsoVisitor() { - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - JavaType.FullyQualified type = classDecl.getType(); - // Capture all classes, which all extend java.lang.Object - if (type instanceof JavaType.Class && type.getSupertype() != null) { - JavaType.FullyQualified supertype = type.getSupertype(); - // Capture the direct superclass - report.insertRow(ctx, new ClassHierarchyReport.Row( - type.getFullyQualifiedName(), - ClassHierarchyReport.Relationship.EXTENDS, - supertype.getFullyQualifiedName())); - - // Capture all interfaces - for (JavaType.FullyQualified anInterface : type.getInterfaces()) { - report.insertRow(ctx, new ClassHierarchyReport.Row( - type.getFullyQualifiedName(), - ClassHierarchyReport.Relationship.IMPLEMENTS, - anInterface.getFullyQualifiedName() - )); - } - } - return super.visitClassDeclaration(classDecl, ctx); - } - }; - } -} diff --git a/src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java b/src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java deleted file mode 100644 index d243c6e9..00000000 --- a/src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - *

- * 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 org.philzen.oss; - -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Preconditions; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.TreeVisitingPrinter; -import org.openrewrite.java.search.UsesMethod; -import org.openrewrite.java.tree.J; - -@Value -@EqualsAndHashCode(callSuper = false) -public class NoGuavaListsNewArrayList extends Recipe { - // These matchers use a syntax described on https://docs.openrewrite.org/reference/method-patterns - private static final MethodMatcher NEW_ARRAY_LIST = new MethodMatcher("com.google.common.collect.Lists newArrayList()"); - private static final MethodMatcher NEW_ARRAY_LIST_ITERABLE = new MethodMatcher("com.google.common.collect.Lists newArrayList(java.lang.Iterable)"); - private static final MethodMatcher NEW_ARRAY_LIST_CAPACITY = new MethodMatcher("com.google.common.collect.Lists newArrayListWithCapacity(int)"); - - @Override - public String getDisplayName() { - //language=markdown - return "Use `new ArrayList<>()` instead of Guava"; - } - - @Override - public String getDescription() { - //language=markdown - return "Prefer the Java standard library over third-party usage of Guava in simple cases like this."; - } - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check( - // Any change to the AST made by the preconditions check will lead to the visitor returned by Recipe - // .getVisitor() being applied - // No changes made by the preconditions check will be kept - Preconditions.or(new UsesMethod<>(NEW_ARRAY_LIST), - new UsesMethod<>(NEW_ARRAY_LIST_ITERABLE), - new UsesMethod<>(NEW_ARRAY_LIST_CAPACITY)), - // To avoid stale state persisting between cycles, getVisitor() should always return a new instance of - // its visitor - new JavaVisitor() { - private final JavaTemplate newArrayList = JavaTemplate.builder("new ArrayList<>()") - .imports("java.util.ArrayList") - .build(); - - private final JavaTemplate newArrayListIterable = - JavaTemplate.builder("new ArrayList<>(#{any(java.util.Collection)})") - .imports("java.util.ArrayList") - .build(); - - private final JavaTemplate newArrayListCapacity = - JavaTemplate.builder("new ArrayList<>(#{any(int)})") - .imports("java.util.ArrayList") - .build(); - - // This method override is only here to show how to print the AST for debugging purposes. - // You can remove this method if you don't need it. - @Override - public J visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { - // This is a useful debugging tool if you're ever unsure what the visitor is visiting - String printed = TreeVisitingPrinter.printTree(cu); - System.out.printf(printed); - // You must always delegate to the super method to ensure the visitor continues to visit deeper - return super.visitCompilationUnit(cu, ctx); - } - - // Visit any method invocation, and replace matches with the new ArrayList instantiation. - @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (NEW_ARRAY_LIST.matches(method)) { - maybeRemoveImport("com.google.common.collect.Lists"); - maybeAddImport("java.util.ArrayList"); - return newArrayList.apply(getCursor(), method.getCoordinates().replace()); - } else if (NEW_ARRAY_LIST_ITERABLE.matches(method)) { - maybeRemoveImport("com.google.common.collect.Lists"); - maybeAddImport("java.util.ArrayList"); - return newArrayListIterable.apply(getCursor(), method.getCoordinates().replace(), - method.getArguments().get(0)); - } else if (NEW_ARRAY_LIST_CAPACITY.matches(method)) { - maybeRemoveImport("com.google.common.collect.Lists"); - maybeAddImport("java.util.ArrayList"); - return newArrayListCapacity.apply(getCursor(), method.getCoordinates().replace(), - method.getArguments().get(0)); - } - return super.visitMethodInvocation(method, ctx); - } - } - ); - } -} diff --git a/src/main/java/org/philzen/oss/SimplifyTernary.java b/src/main/java/org/philzen/oss/SimplifyTernary.java deleted file mode 100644 index d609b305..00000000 --- a/src/main/java/org/philzen/oss/SimplifyTernary.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import com.google.errorprone.refaster.annotation.AfterTemplate; -import com.google.errorprone.refaster.annotation.BeforeTemplate; -import org.openrewrite.java.template.RecipeDescriptor; - -@SuppressWarnings({"SimplifiableConditionalExpression", "unused"}) -@RecipeDescriptor( - name = "Simplify ternary expressions", - description = "Simplifies various types of ternary expressions to improve code readability." -) -public class SimplifyTernary { - - @RecipeDescriptor( - name = "Replace `booleanExpression ? true : false` with `booleanExpression`", - description = "Replace ternary expressions like `booleanExpression ? true : false` with `booleanExpression`." - ) - public static class SimplifyTernaryTrueFalse { - - @BeforeTemplate - boolean before(boolean expr) { - return expr ? true : false; - } - - @AfterTemplate - boolean after(boolean expr) { - return expr; - } - } - - @RecipeDescriptor( - name = "Replace `booleanExpression ? false : true` with `!booleanExpression`", - description = "Replace ternary expressions like `booleanExpression ? false : true` with `!booleanExpression`." - ) - public static class SimplifyTernaryFalseTrue { - - @BeforeTemplate - boolean before(boolean expr) { - return expr ? false : true; - } - - @AfterTemplate - boolean after(boolean expr) { - // We wrap the expression in parentheses as the input expression might be a complex expression - return !(expr); - } - } -} diff --git a/src/main/java/org/philzen/oss/StringIsEmpty.java b/src/main/java/org/philzen/oss/StringIsEmpty.java deleted file mode 100644 index a5659acd..00000000 --- a/src/main/java/org/philzen/oss/StringIsEmpty.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -// TODO - This is a placeholder for a Refaster recipe. Implement the recipe by adding before and after annotated methods. -// The rule should replace calls to `String.length() == 0` with `String.isEmpty()`, as well as similar variants. -// You're done when all the tests in `StringIsEmptyTest` passes. -public class StringIsEmpty { -} diff --git a/src/main/java/org/philzen/oss/UpdateConcoursePipeline.java b/src/main/java/org/philzen/oss/UpdateConcoursePipeline.java deleted file mode 100644 index 5290f84f..00000000 --- a/src/main/java/org/philzen/oss/UpdateConcoursePipeline.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.openrewrite.*; -import org.openrewrite.yaml.ChangePropertyValue; -import org.openrewrite.yaml.YamlIsoVisitor; -import org.openrewrite.yaml.tree.Yaml; - -@Value -@EqualsAndHashCode(callSuper = false) -public class UpdateConcoursePipeline extends Recipe { - @Override - public String getDisplayName() { - return "Update concourse pipeline"; - } - - @Override - public String getDescription() { - return "Update the tag filter on concourse pipelines."; - } - - @Option(displayName = "New tag filter version", - description = "tag filter version.", - example = "8.2.0") - String version; - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check( - Preconditions.or( - new FindSourceFiles("ci/pipeline*.yml").getVisitor(), - new FindSourceFiles("ci/pipeline*.yaml").getVisitor()), - new YamlIsoVisitor() { - - @Override - public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) { - Yaml.Mapping.Entry e = super.visitMappingEntry(entry, ctx); - if ("source".equals(e.getKey().getValue())) { - Yaml.Block value = e.getValue(); - if (!(value instanceof Yaml.Mapping)) { - return e; - } - Yaml.Mapping mapping = (Yaml.Mapping) value; - Yaml.Mapping.Entry uriEntry = null; - Yaml.Mapping.Entry tagFilter = null; - for (Yaml.Mapping.Entry mappingEntry : mapping.getEntries()) { - if ("uri".equals(mappingEntry.getKey().getValue())) { - uriEntry = mappingEntry; - } else if ("tag_filter".equals(mappingEntry.getKey().getValue())) { - tagFilter = mappingEntry; - } - } - if (uriEntry == null || tagFilter == null) { - return e; - } - if (!(uriEntry.getValue() instanceof Yaml.Scalar) || !(tagFilter.getValue() instanceof Yaml.Scalar)) { - return e; - } - Yaml.Scalar uriValue = (Yaml.Scalar) uriEntry.getValue(); - if (!uriValue.getValue().contains(".git")) { - return e; - } - Yaml.Scalar tagFilterValue = (Yaml.Scalar) tagFilter.getValue(); - if (version.equals(tagFilterValue.getValue())) { - return e; - } - return (Yaml.Mapping.Entry) new ChangePropertyValue("source.tag_filter", version, null, null, null) - .getVisitor() - .visitNonNull(e, ctx); - } - return e; - } - } - ); - } -} diff --git a/src/main/java/org/philzen/oss/table/ClassHierarchyReport.java b/src/main/java/org/philzen/oss/table/ClassHierarchyReport.java deleted file mode 100644 index 55bef165..00000000 --- a/src/main/java/org/philzen/oss/table/ClassHierarchyReport.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss.table; - -import lombok.Value; -import org.openrewrite.Column; -import org.openrewrite.DataTable; -import org.openrewrite.Recipe; - -public class ClassHierarchyReport extends DataTable { - - public ClassHierarchyReport(Recipe recipe) { - super(recipe, - "Class hierarchy report", - "Records inheritance relationships between classes."); - } - - @Value - public static class Row { - @Column(displayName = "Class name", - description = "Fully qualified name of the class.") - String className; - - @Column(displayName = "Relationship", - description = "Whether the class implements a super interface or extends a superclass.") - Relationship relationship; - - @Column(displayName = "Super class name", - description = "Fully qualified name of the superclass.") - String superClassName; - } - - public enum Relationship { - EXTENDS, - IMPLEMENTS - } -} diff --git a/src/main/resources/META-INF/rewrite/rewrite.yml b/src/main/resources/META-INF/rewrite/rewrite.yml deleted file mode 100644 index 9bf81b0c..00000000 --- a/src/main/resources/META-INF/rewrite/rewrite.yml +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright 2021 the original author or authors. -#

-# 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. -# - -# Include any Declarative YAML format recipes here, as per: -# https://docs.openrewrite.org/reference/yaml-format-reference -# These are most easily composed through the Yaml recipe builder at: -# https://app.moderne.io/recipes/builder - -# Notice how we can have multiple recipes in the same file, separated by `---` -# You can also have multiple files in `src/main/resources/META-INF/rewrite`, each containing one or more recipes. ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.philzen.oss.UseOpenRewriteNullable -displayName: Prefer OpenRewrite Nullable -description: Replaces JetBrains Nullable with OpenRewrite Nullable. -recipeList: - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: org.jetbrains.annotations.Nullable - newFullyQualifiedTypeName: org.openrewrite.internal.lang.Nullable diff --git a/src/main/resources/META-INF/rewrite/stringutils.yml b/src/main/resources/META-INF/rewrite/stringutils.yml deleted file mode 100644 index 4b98316c..00000000 --- a/src/main/resources/META-INF/rewrite/stringutils.yml +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright 2024 the original author or authors. -#

-# 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. -# - ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.philzen.oss.UseApacheStringUtils -displayName: Use Apache `StringUtils` -description: Replace Spring string utilities with Apache string utilities -recipeList: - - org.openrewrite.java.dependencies.AddDependency: - groupId: org.apache.commons - artifactId: commons-lang3 - version: latest.release - onlyIfUsing: org.springframework.util.StringUtils - configuration: implementation - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: org.springframework.util.StringUtils - newFullyQualifiedTypeName: org.apache.commons.lang3.StringUtils diff --git a/src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java b/src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java deleted file mode 100644 index e66ade6b..00000000 --- a/src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import java.nio.file.Paths; - -import static org.openrewrite.test.SourceSpecs.text; - -class AppendToReleaseNotesTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new AppendToReleaseNotes("Hello world")); - } - - @Test - void createNewReleaseNotes() { - // Notice how the before text is null, indicating that the file does not exist yet. - // The after text is the content of the file after the recipe is applied. - rewriteRun( - text( - null, - """ - Hello world - """, - spec -> spec.path(Paths.get("RELEASE.md") - ) - ) - ); - } - - @DocumentExample - @Test - void editExistingReleaseNotes() { - // When the file does already exist, we assert the content is modified as expected. - rewriteRun( - text( - """ - You say goodbye, I say - """, - """ - You say goodbye, I say - Hello world - """, - spec -> spec.path(Paths.get("RELEASE.md") - ) - ) - ); - } -} diff --git a/src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java b/src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java deleted file mode 100644 index 53fb16e3..00000000 --- a/src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -class AssertEqualsToAssertThatTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new AssertEqualsToAssertThat()) - .parser(JavaParser.fromJavaVersion() - .classpath("junit-jupiter-api")); - } - - @DocumentExample - @Test - void twoArgument() { - rewriteRun( - //language=java - java( - """ - import org.junit.jupiter.api.Assertions; - - class A { - void foo() { - Assertions.assertEquals(1, 2); - } - } - """, - """ - import org.assertj.core.api.Assertions; - - class A { - void foo() { - Assertions.assertThat(2).isEqualTo(1); - } - } - """ - ) - ); - } - - @Test - void withDescription() { - rewriteRun( - //language=java - java( - """ - import org.junit.jupiter.api.Assertions; - - class A { - void foo() { - Assertions.assertEquals(1, 2, "one equals two, everyone knows that"); - } - } - """, - """ - import org.assertj.core.api.Assertions; - - class A { - void foo() { - Assertions.assertThat(2).as("one equals two, everyone knows that").isEqualTo(1); - } - } - """ - ) - ); - } -} diff --git a/src/test/java/org/philzen/oss/ClassHierarchyTest.java b/src/test/java/org/philzen/oss/ClassHierarchyTest.java deleted file mode 100644 index 0c3b124e..00000000 --- a/src/test/java/org/philzen/oss/ClassHierarchyTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.philzen.oss.table.ClassHierarchyReport; -import org.junit.jupiter.api.Test; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.openrewrite.java.Assertions.java; - -class ClassHierarchyTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new ClassHierarchy()); - } - - @Test - void basic() { - rewriteRun( - spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> { - assertThat(rows).containsExactly(new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object")); - }), - //language=java - java( - """ - class A {} - """ - ) - ); - } - - @Test - void bExtendsA() { - rewriteRun( - spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> { - assertThat(rows).containsExactly( - new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"), - new ClassHierarchyReport.Row("B", ClassHierarchyReport.Relationship.EXTENDS, "A")); - }), - //language=java - java( - """ - class A {} - """ - ), - //language=java - java( - """ - class B extends A {} - """ - ) - ); - } - - @Test - void interfaceRelationship() { - rewriteRun( - spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> { - assertThat(rows).containsExactly( - new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"), - new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.IMPLEMENTS, "java.io.Serializable")); - }), - // language=java - java( - """ - import java.io.Serializable; - class A implements Serializable {} - """ - ) - ); - } -} diff --git a/src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java b/src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java deleted file mode 100644 index 71d34b3b..00000000 --- a/src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -// This is a test for the NoGuavaListsNewArrayList recipe, as an example of how to write a test for an imperative recipe. -class NoGuavaListsNewArrayListTest implements RewriteTest { - - // Note, you can define defaults for the RecipeSpec and these defaults will be used for all tests. - // In this case, the recipe and the parser are common. See below, on how the defaults can be overridden - // per test. - @Override - public void defaults(RecipeSpec spec) { - // Note how we directly instantiate the recipe class here - spec.recipe(new NoGuavaListsNewArrayList()) - .parser(JavaParser.fromJavaVersion() - .logCompilationWarningsAndErrors(true) - // The before/after examples are using Guava classes, so we need to add the Guava library to the classpath - .classpath("guava")); - } - - @DocumentExample - @Test - void replaceWithNewArrayList() { - rewriteRun( - // There is an overloaded version or rewriteRun that allows the RecipeSpec to be customized specifically - // for a given test. In this case, the parser for this test is configured to not log compilation warnings. - spec -> spec - .parser(JavaParser.fromJavaVersion() - .logCompilationWarningsAndErrors(false) - .classpath("guava")), - // language=java - java( - """ - import com.google.common.collect.*; - - import java.util.List; - - class Test { - List cardinalsWorldSeries = Lists.newArrayList(); - } - """, - """ - import java.util.ArrayList; - import java.util.List; - - class Test { - List cardinalsWorldSeries = new ArrayList<>(); - } - """ - ) - ); - } - - @Test - void replaceWithNewArrayListIterable() { - rewriteRun( - // language=java - java( - """ - import com.google.common.collect.*; - - import java.util.Collections; - import java.util.List; - - class Test { - List l = Collections.emptyList(); - List cardinalsWorldSeries = Lists.newArrayList(l); - } - """, - """ - import java.util.ArrayList; - import java.util.Collections; - import java.util.List; - - class Test { - List l = Collections.emptyList(); - List cardinalsWorldSeries = new ArrayList<>(l); - } - """ - ) - ); - } - - @Test - void replaceWithNewArrayListWithCapacity() { - rewriteRun( - // language=java - java( - """ - import com.google.common.collect.*; - - import java.util.ArrayList; - import java.util.List; - - class Test { - List cardinalsWorldSeries = Lists.newArrayListWithCapacity(2); - } - """, - """ - import java.util.ArrayList; - import java.util.List; - - class Test { - List cardinalsWorldSeries = new ArrayList<>(2); - } - """) - ); - } - - // This test is to show that the `super.visitMethodInvocation` is needed to ensure that nested method invocations are visited. - @Test - void showNeedForSuperVisitMethodInvocation() { - rewriteRun( - //language=java - java( - """ - import com.google.common.collect.*; - - import java.util.Collections; - import java.util.List; - - class Test { - List cardinalsWorldSeries = Collections.unmodifiableList(Lists.newArrayList()); - } - """, - """ - import java.util.ArrayList; - import java.util.Collections; - import java.util.List; - - class Test { - List cardinalsWorldSeries = Collections.unmodifiableList(new ArrayList<>()); - } - """ - ) - ); - } - - // Often you want to make sure no changes are made when the target state is already achieved. - // To do so only passs in a before state and no after state to the rewriteRun method SourceSpecs. - @Test - void noChangeNecessary() { - rewriteRun( - //language=java - java( - """ - import java.util.ArrayList; - import java.util.Collections; - import java.util.List; - - class Test { - List cardinalsWorldSeries = Collections.unmodifiableList(new ArrayList<>()); - } - """ - ) - ); - } -} diff --git a/src/test/java/org/philzen/oss/SimplifyTernaryTest.java b/src/test/java/org/philzen/oss/SimplifyTernaryTest.java deleted file mode 100644 index 024cd88f..00000000 --- a/src/test/java/org/philzen/oss/SimplifyTernaryTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -// This is a test for the SimplifyTernary recipe, as an example of how to write a test for a Refaster style recipe. -class SimplifyTernaryTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - // Note that we instantiate a generated class here, with `Recipes` appended to the Refaster class name - spec.recipe(new SimplifyTernaryRecipes()); - } - - @Test - @DocumentExample - void simplified() { - rewriteRun( - //language=java - java( - """ - class Test { - boolean trueCondition1 = true ? true : false; - boolean trueCondition2 = false ? false : true; - boolean trueCondition3 = booleanExpression() ? true : false; - boolean trueCondition4 = trueCondition1 && trueCondition2 ? true : false; - boolean trueCondition5 = !true ? false : true; - boolean trueCondition6 = !false ? true : false; - - boolean falseCondition1 = true ? false : true; - boolean falseCondition2 = !false ? false : true; - boolean falseCondition3 = booleanExpression() ? false : true; - boolean falseCondition4 = trueCondition1 && trueCondition2 ? false : true; - boolean falseCondition5 = !false ? false : true; - boolean falseCondition6 = !true ? true : false; - - boolean binary1 = booleanExpression() && booleanExpression() ? true : false; - boolean binary2 = booleanExpression() && booleanExpression() ? false : true; - boolean binary3 = booleanExpression() || booleanExpression() ? true : false; - boolean binary4 = booleanExpression() || booleanExpression() ? false : true; - - boolean booleanExpression() { - return true; - } - } - """, - """ - class Test { - boolean trueCondition1 = true; - boolean trueCondition2 = true; - boolean trueCondition3 = booleanExpression(); - boolean trueCondition4 = trueCondition1 && trueCondition2; - boolean trueCondition5 = true; - boolean trueCondition6 = true; - - boolean falseCondition1 = false; - boolean falseCondition2 = false; - boolean falseCondition3 = !booleanExpression(); - boolean falseCondition4 = !(trueCondition1 && trueCondition2); - boolean falseCondition5 = false; - boolean falseCondition6 = false; - - boolean binary1 = booleanExpression() && booleanExpression(); - boolean binary2 = !(booleanExpression() && booleanExpression()); - boolean binary3 = booleanExpression() || booleanExpression(); - boolean binary4 = !(booleanExpression() || booleanExpression()); - - boolean booleanExpression() { - return true; - } - } - """ - ) - ); - } - - // It's good practice to also include a test that verifies that the recipe doesn't change anything when it shouldn't. - @Test - void unchanged() { - rewriteRun( - //language=java - java( - """ - class Test { - boolean unchanged1 = booleanExpression() ? booleanExpression() : !booleanExpression(); - boolean unchanged2 = booleanExpression() ? true : !booleanExpression(); - boolean unchanged3 = booleanExpression() ? booleanExpression() : false; - - boolean booleanExpression() { - return true; - } - } - """ - ) - ); - } -} diff --git a/src/test/java/org/philzen/oss/StringIsEmptyTest.java b/src/test/java/org/philzen/oss/StringIsEmptyTest.java deleted file mode 100644 index 823bda6e..00000000 --- a/src/test/java/org/philzen/oss/StringIsEmptyTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.Recipe; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -@Disabled("Remove this annotation to run the tests once you implement the recipe") -class StringIsEmptyTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - // Note that we instantiate a generated class here, with `Recipes` appended to the Refaster class name - // You might need to trigger an explicit build of your project to generate this class with Ctrl + F9 - - // TODO: Uncomment the line below once you have implemented the recipe - //spec.recipe(new StringIsEmptyRecipe()); - } - - @DocumentExample - @Test - void standardizeStringIsEmpty() { - // Notice how we pass in both the "before" and "after" code snippets - // This indicates that we expect the recipe to transform the "before" code snippet into the "after" code snippet - // If the recipe does not do this, the test will fail, and a diff will be shown - rewriteRun( - //language=java - java( - """ - class A { - void test(String s, boolean b) { - b = s.length() == 0; - b = 0 == s.length(); - b = s.length() < 1; - b = 1 > s.length(); - b = s.equals(""); - b = "".equals(s); - b = s.isEmpty(); - } - } - """, - """ - class A { - void test(String s, boolean b) { - b = s.isEmpty(); - b = s.isEmpty(); - b = s.isEmpty(); - b = s.isEmpty(); - b = s.isEmpty(); - b = s.isEmpty(); - b = s.isEmpty(); - } - } - """ - ) - ); - } - - @Test - void showStringTypeMatchAndSimplification() { - // Notice how the recipe will match anything that is of type String, not just local variables - // Take a closer look at the last two replacements to `true` and `false`. - // Open up the generated recipe and see if you can work out why those are replaced with booleans! - rewriteRun( - //language=java - java( - """ - class A { - String field; - - String methodCall() { - return "Hello World"; - } - - void test(String argument) { - boolean bool1 = field.length() == 0; - boolean bool2 = methodCall().length() == 0; - boolean bool3 = argument.length() == 0; - boolean bool4 = "".length() == 0; - boolean bool5 = "literal".length() == 0; - } - } - """, - """ - class A { - String field; - - String methodCall() { - return "Hello World"; - } - - void test(String argument) { - boolean bool1 = field.isEmpty(); - boolean bool2 = methodCall().isEmpty(); - boolean bool3 = argument.isEmpty(); - boolean bool4 = true; - boolean bool5 = false; - } - } - """ - ) - ); - } - - @Test - void doNothingForStringIsEmpty() { - // Notice how we only pass in the "before" code snippet, and not the "after" code snippet - // That indicates that we expect the recipe to do nothing in this case, and will fail if it does anything - rewriteRun( - //language=java - java( - """ - class A { - void test(String s, boolean b) { - b = s.isEmpty(); - } - } - """ - ) - ); - } - - @Test - void doNothingForCharSequence() { - // When a different type is used, the recipe should do nothing - // See if you can modify the recipe to handle CharSequence as well, or create a separate recipe for it - rewriteRun( - //language=java - java( - """ - class A { - void test(CharSequence s, boolean b) { - b = s.length() == 0; - } - } - """ - ) - ); - } - - @Test - void recipeDocumentation() { - // This is a test to validate the correctness of the documentation in the recipe - // By default you get generated documentation, but you can customize it through the RecipeDescriptor annotation - Recipe recipe = null; // TODO: = new StringIsEmptyRecipe(); - String displayName = recipe.getDisplayName(); - String description = recipe.getDescription(); - assert "Standardize empty String checks".equals(displayName) : displayName; - assert "Replace calls to `String.length() == 0` with `String.isEmpty()`.".equals(description) : description; - } -} diff --git a/src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java b/src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java deleted file mode 100644 index 7e07d4ce..00000000 --- a/src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RewriteTest; - -import java.nio.file.Paths; - -import static org.openrewrite.yaml.Assertions.yaml; - -class UpdateConcoursePipelineTest implements RewriteTest { - - @DocumentExample - @Test - void updateTagFilter() { - rewriteRun( - spec -> spec.recipe(new UpdateConcoursePipeline("8.2.0")), - //language=yaml - yaml( - """ - --- - resources: - - name: tasks - type: git - source: - uri: git@github.com:Example/concourse-tasks.git - tag_filter: 8.1.0 - """, - """ - --- - resources: - - name: tasks - type: git - source: - uri: git@github.com:Example/concourse-tasks.git - tag_filter: 8.2.0 - """, - spec -> spec.path(Paths.get("ci/pipeline.yml")) - ) - ); - } -} diff --git a/src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java b/src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java deleted file mode 100644 index d144163b..00000000 --- a/src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -class UseApacheStringUtilsTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipeFromResources("org.philzen.oss.UseApacheStringUtils") - .parser(JavaParser.fromJavaVersion().classpath("commons-lang3", "spring-core")); - } - - @DocumentExample - @Test - void replacesStringEquals() { - rewriteRun( - //language=java - java( - """ - import org.springframework.util.StringUtils; - - class A { - boolean test(String s) { - return StringUtils.containsWhitespace(s); - } - } - """, - """ - import org.apache.commons.lang3.StringUtils; - - class A { - boolean test(String s) { - return StringUtils.containsWhitespace(s); - } - } - """ - ) - ); - } -} diff --git a/src/test/java/org/philzen/oss/UseOpenRewriteNullableTest.java b/src/test/java/org/philzen/oss/UseOpenRewriteNullableTest.java deleted file mode 100644 index 154beada..00000000 --- a/src/test/java/org/philzen/oss/UseOpenRewriteNullableTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 org.philzen.oss; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -// This is a test for the UseOpenRewriteNullable recipe, as an example of how to write a test for a declarative recipe. -class UseOpenRewriteNullableTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec - // Use the fully qualified class name of the recipe defined in src/main/resources/META-INF/rewrite/rewrite.yml - .recipeFromResources("org.philzen.oss.UseOpenRewriteNullable") - // The before and after text blocks contain references to annotations from these two classpath entries - .parser(JavaParser.fromJavaVersion().classpath("annotations", "rewrite-core")); - } - - @DocumentExample - @Test - void replacesNullableAnnotation() { - rewriteRun( - // Composite recipes are a hierarchy of recipes that can be applied in a single pass. - // To view what the composite recipe does, you can use the RecipePrinter to print the recipe to the console. - spec -> spec.printRecipe(() -> System.out::println), - //language=java - java( - """ - import org.jetbrains.annotations.Nullable; - - class A { - @Nullable - String s; - } - """, - """ - import org.openrewrite.internal.lang.Nullable; - - class A { - @Nullable - String s; - } - """ - ) - ); - } -} From 48532bf09d3330a50448c430728c517542877a83 Mon Sep 17 00:00:00 2001 From: Philzen Date: Mon, 17 Jun 2024 16:27:01 +0200 Subject: [PATCH 10/51] Add build & usage writeup and remove unrelated content from template --- README.md | 158 ++++++++++++++++++++---------------------------------- 1 file changed, 59 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 2390d589..5a53e93a 100644 --- a/README.md +++ b/README.md @@ -7,86 +7,86 @@ Converts [TestNG](https://testng.org/) test annotations and assertions to Inspired by the [Migrate JUnit 4 @Test annotations to JUnit 5](https://docs.openrewrite.org/recipes/java/testing/junit5/updatetestannotation) recipe -## Rewrite recipe starter +## Usage -This repository serves as a template for building your own recipe JARs and publishing them to a repository where they can be applied on [app.moderne.io](https://app.moderne.io) against all the public OSS code that is included there. +### Prerequisites -We've provided a sample recipe (NoGuavaListsNewArray) and a sample test class. Both of these exist as placeholders, and they should be replaced by whatever recipe you are interested in writing. +- cloned the project to your local machine +- if you're an SDKMAN!-Andy, simply run `sdk env` from the project directory + otherwise ensure: + - JDK 17+ + - Maven 3.9.x *or* Gradle 8.x -To begin, fork this repository and customize it by: + are provided on your system. -1. Changing the root project name in `settings.gradle.kts`. -2. Changing the `group` in `build.gradle.kts`. -3. Changing the package structure from `com.yourorg` to whatever you want. +### Build & install this recipe to your local repository -## Getting started +From the project directory, run one of the following: -Familiarize yourself with the [OpenRewrite documentation](https://docs.openrewrite.org/), in particular the [concepts & explanations](https://docs.openrewrite.org/concepts-explanations) op topics like the [lossless semantic trees](https://docs.openrewrite.org/concepts-explanations/lossless-semantic-trees), [recipes](https://docs.openrewrite.org/concepts-explanations/recipes) and [visitors](https://docs.openrewrite.org/concepts-explanations/visitors). +

Maven +

-You might be interested to watch some of the [videos available on OpenRewrite and Moderne](https://www.youtube.com/@moderne-auto-remediation). - -Once you want to dive into the code there is a [comprehensive getting started guide](https://docs.openrewrite.org/authoring-recipes/recipe-development-environment) -available in the OpenRewrite docs that provides more details than the below README. - -## Reference recipes - -* [META-INF/rewrite/stringutils.yml](./src/main/resources/META-INF/rewrite/stringutils.yml) - A declarative YAML recipe that replaces usages of `org.springframework.util.StringUtils` with `org.apache.commons.lang3.StringUtils`. - - [UseApacheStringUtilsTest](src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java) - A test class for the `com.yourorg.UseApacheStringUtils` recipe. -* [NoGuavaListsNewArrayList.java](src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java) - An imperative Java recipe that replaces usages of `com.google.common.collect.Lists` with `new ArrayList<>()`. - - [NoGuavaListsNewArrayListTest.java](src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java) - A test class for the `NoGuavaListsNewArrayList` recipe. -* [SimplifyTernary](src/main/java/org/philzen/oss/SimplifyTernary.java) - An Refaster style recipe that simplifies ternary expressions. - - [SimplifyTernaryTest](src/test/java/org/philzen/oss/SimplifyTernaryTest.java) - A test class for the `SimplifyTernary` recipe. -* [AssertEqualsToAssertThat](src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java) - An imperative Java recipe that replaces JUnit's `assertEquals` with AssertJ's `assertThat`, to show how to handle classpath dependencies. - - [AssertEqualsToAssertThatTest](src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java) - A test class for the `AssertEqualsToAssertThat` recipe. -* [AppendToReleaseNotes](src/main/java/org/philzen/oss/AppendToReleaseNotes.java) - A ScanningRecipe that appends a message to the release notes of a project. - - [AppendToReleaseNotesTest](src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java) - A test class for the `AppendToReleaseNotes` recipe. -* [ClassHierarchy](src/main/java/org/philzen/oss/ClassHierarchy.java) - A recipe that demonstrates how to produce a data table on the class hierarchy of a project. - - [ClassHierarchyTest](src/test/java/org/philzen/oss/ClassHierarchyTest.java) - A test class for the `ClassHierarchy` recipe. -* [UpdateConcoursePipeline](src/main/java/org/philzen/oss/UpdateConcoursePipeline.java) - A recipe that demonstrates how to update a Concourse pipeline, as an example of operating on Yaml files. - - [UpdateConcoursePipelineTest](src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java) - A test class for the `UpdateConcoursePipeline` recipe. +```bash +mvn install -DskipTests +``` +

+
-## Local Publishing for Testing +
Gradle +

-Before you publish your recipe module to an artifact repository, you may want to try it out locally. -To do this on the command line, run: ```bash ./gradlew publishToMavenLocal # or ./gradlew pTML # or mvn install ``` +

+
+ This will publish to your local maven repository, typically under `~/.m2/repository`. -Replace the groupId, artifactId, recipe name, and version in the below snippets with the ones that correspond to your recipe. +### Migrate a project + +
Maven +

+ +In the `pom.xml` of a different project you wish to run the recipe on, +make it a plugin dependency of rewrite-maven-plugin: -In the pom.xml of a different project you wish to test your recipe out in, make your recipe module a plugin dependency of rewrite-maven-plugin: ```xml - - - - org.openrewrite.maven - rewrite-maven-plugin - RELEASE - - - com.yourorg.NoGuavaListsNewArrayList - - - - - com.yourorg - rewrite-recipe-starter - 0.1.0-SNAPSHOT - - - - - + + + + org.openrewrite.maven + rewrite-maven-plugin + 5.33.0 + + + org.philzen.oss.testng.MigrateToJunit5 + + + + + org.philzen.oss + rewrite-testng-to-junit5 + 1.0.1-SNAPSHOT + + + + + ``` +Now run the recipe via `mvn rewrite:run`. +

+ +
Gradle +

Unlike Maven, Gradle must be explicitly configured to resolve dependencies from Maven local. -The root project of your Gradle build, make your recipe module a dependency of the `rewrite` configuration: +In the root project of a gradle build that you wish to run this recipe on, +make it a dependency of the `rewrite` configuration: ```groovy plugins { @@ -108,45 +108,5 @@ rewrite { } ``` -Now you can run `mvn rewrite:run` or `gradlew rewriteRun` to run your recipe. - -## Publishing to Artifact Repositories - -This project is configured to publish to Moderne's open artifact repository (via the `publishing` task at the bottom of -the `build.gradle.kts` file). If you want to publish elsewhere, you'll want to update that task. -[app.moderne.io](https://app.moderne.io) can draw recipes from the provided repository, as well as from [Maven Central](https://search.maven.org). - -Note: -Running the publish task _will not_ update [app.moderne.io](https://app.moderne.io), as only Moderne employees can -add new recipes. If you want to add your recipe to [app.moderne.io](https://app.moderne.io), please ask the -team in [Slack](https://join.slack.com/t/rewriteoss/shared_invite/zt-nj42n3ea-b~62rIHzb3Vo0E1APKCXEA) or in [Discord](https://discord.gg/xk3ZKrhWAb). - -These other docs might also be useful for you depending on where you want to publish the recipe: - -* Sonatype's instructions for [publishing to Maven Central](https://maven.apache.org/repository/guide-central-repository-upload.html) -* Gradle's instructions on the [Gradle Publishing Plugin](https://docs.gradle.org/current/userguide/publishing\_maven.html). - -### From Github Actions - -The `.github` directory contains a Github action that will push a snapshot on every successful build. - -Run the release action to publish a release version of a recipe. - -### From the command line - -To build a snapshot, run `./gradlew snapshot publish` to build a snapshot and publish it to Moderne's open artifact repository for inclusion at [app.moderne.io](https://app.moderne.io). - -To build a release, run `./gradlew final publish` to tag a release and publish it to Moderne's open artifact repository for inclusion at [app.moderne.io](https://app.moderne.io). - - -## Applying OpenRewrite recipe development best practices - -We maintain a collection of [best practices for writing OpenRewrite recipes](https://docs.openrewrite.org/recipes/recipes/openrewritebestpractices). -You can apply these recommendations to your recipes by running the following command: -```bash -./gradlew rewriteRun -Drewrite.activeRecipe=org.openrewrite.recipes.OpenRewriteBestPractices -``` -or -```bash -mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-recommendations:RELEASE -Drewrite.activeRecipes=org.openrewrite.recipes.OpenRewriteBestPractices -``` +Now run the recipe via `gradlew rewriteRun`. +

From f78a1a8d89ebe84bd944c6ab26c6c5a540bc9039 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sun, 9 Jun 2024 04:21:44 +0200 Subject: [PATCH 11/51] Add rewrite-testing-frameworks dependency (enables official recipes) Makes official org.openrewrite.java.testing.* recipes available for usage in our own master recipe. --- build.gradle.kts | 5 +++++ pom.xml | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index a3a6171e..32b18335 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,6 +58,11 @@ dependencies { // ↓ Classpath resource for MigrateTestNg* recipes testRuntimeOnly("org.testng:testng:7.5.1") // 7.5.x is the last Java 8 compatible version: https://github.com/testng-team/testng/issues/2775 + + // ↓ to allow using testing recipes in our recipe list + testRuntimeOnly("org.openrewrite.recipe:rewrite-testing-frameworks:latest.release") { + exclude("org.testcontainers", "testcontainers") + } } signing { diff --git a/pom.xml b/pom.xml index 211bade3..49ea4929 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,18 @@ test + + org.openrewrite.recipe + rewrite-testing-frameworks + + + org.testcontainers + testcontainers + + + runtime + + org.junit.jupiter junit-jupiter From e57948c8f06eaf4e59db43e87093b588b2e062e5 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 8 Jun 2024 02:35:41 +0200 Subject: [PATCH 12/51] Cook up basic recipe list including testing.junit5.AddMissingNested --- src/main/resources/META-INF/rewrite/rewrite.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/resources/META-INF/rewrite/rewrite.yml diff --git a/src/main/resources/META-INF/rewrite/rewrite.yml b/src/main/resources/META-INF/rewrite/rewrite.yml new file mode 100644 index 00000000..bed3b20e --- /dev/null +++ b/src/main/resources/META-INF/rewrite/rewrite.yml @@ -0,0 +1,12 @@ +type: specs.openrewrite.org/v1beta/recipe +name: org.philzen.oss.testng.MigrateToJunit5 +tags: [TestNG, JUnit5, Jupiter, JUnit] +displayName: JUnit Jupiter migration from TestNG +description: Migrates TestNG tests to JUnit Jupiter. +estimatedEffortPerOccurrence: PT20S +preconditions: +- org.openrewrite.FindSourceFiles: + filePattern: "**/*.java" +recipeList: +- org.philzen.oss.testng.UpdateTestAnnotationToJunit5 +- org.openrewrite.java.testing.junit5.AddMissingNested From 374cf3d2da6a3ea0e48cf633a6ad8bd9d2933f53 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sun, 9 Jun 2024 06:49:36 +0200 Subject: [PATCH 13/51] Set up code coverage statistics --- pom.xml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pom.xml b/pom.xml index 49ea4929..fc883d54 100644 --- a/pom.xml +++ b/pom.xml @@ -175,4 +175,38 @@ + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + prepare-agent + + prepare-agent + + + + report + + report + + + + XML + + + + + + + + + From dcb19b1f7f453fc66d52302856f544e176c5d7f9 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sun, 9 Jun 2024 19:38:23 +0200 Subject: [PATCH 14/51] [CI] Run Jacoco during `mvn verify` and upload XML output as artifact --- .github/workflows/ci.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5e32d45..aec0b323 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,8 +36,24 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: arguments: ${{ env.GRADLE_SWITCHES }} build test - - name: verify - run: mvn --show-version --no-transfer-progress --update-snapshots --fail-at-end --batch-mode -Dstyle.color=always verify + - name: "[Maven] Verify & generate JaCoCo XML" + env: + MAVEN_OPTS: '-Dstyle.color=always' + MAVEN_SWITCHES: >- + --show-version + --no-transfer-progress + --update-snapshots + --fail-at-end + --batch-mode + run: mvn ${{ env.MAVEN_SWITCHES }} -P coverage jacoco:prepare-agent verify jacoco:report + + # Upload JaCoCo XML report as artifact + # https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts#uploading-build-and-test-artifacts + - name: Upload code coverage results + uses: actions/upload-artifact@v4 + with: + name: code-coverage-jacoco-xml + path: target/site/jacoco/jacoco.xml publish-snapshots: needs: [build] From 855a52dd078c373c1be806c4a4f617a2ad8f94d3 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sun, 9 Jun 2024 21:35:18 +0200 Subject: [PATCH 15/51] [CI] Activate dedicated Sonar GitHub action --- .github/workflows/analyse.yml | 45 +++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 15 +++++++----- 2 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/analyse.yml diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml new file mode 100644 index 00000000..88deb264 --- /dev/null +++ b/.github/workflows/analyse.yml @@ -0,0 +1,45 @@ +name: SonarCloud +on: + workflow_call: + inputs: {} + pull_request: + types: [opened, synchronize, reopened] +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: 'zulu' # Alternative distribution options are available. + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Download code coverage results + uses: actions/download-artifact@v4 + with: + name: maven-build-target-folder + path: target + - name: Analyze project + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + MAVEN_OPTS: >- + -Dsonar.projectKey=Philzen_rewrite-recipe-testng-to-junit-jupiter + -Dsonar.organization=philzen + -Dsonar.host.url=https://sonarcloud.io + run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar ${{ env.MAVEN_OPTS }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aec0b323..58d1a9f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,15 +45,18 @@ jobs: --update-snapshots --fail-at-end --batch-mode - run: mvn ${{ env.MAVEN_SWITCHES }} -P coverage jacoco:prepare-agent verify jacoco:report + run: mvn ${{ env.MAVEN_SWITCHES }} -P coverage verify - # Upload JaCoCo XML report as artifact - # https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts#uploading-build-and-test-artifacts - - name: Upload code coverage results + - name: Upload maven build result uses: actions/upload-artifact@v4 with: - name: code-coverage-jacoco-xml - path: target/site/jacoco/jacoco.xml + name: maven-build-target-folder + path: target + + call: + needs: [build] + uses: './.github/workflows/analyse.yml' + secrets: inherit publish-snapshots: needs: [build] From 3ec4c6b15f92eb9c4f810b30e3ea966293744b81 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sun, 9 Jun 2024 23:52:57 +0200 Subject: [PATCH 16/51] Include code coverage badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a53e93a..807733c9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # TestNG to Junit5 recipe   [![Compatible with Java 8](https://img.shields.io/badge/Works%20on%20Java-8-seagreen?logo=openjdk&labelColor=snow&logoColor=black)](#) [![Build](https://github.com/Philzen/rewrite-recipe-testng-to-junit-jupiter/actions/workflows/ci.yml/badge.svg)](https://github.com/Philzen/rewrite-recipe-testng-to-junit-jupiter/actions/workflows/ci.yml) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Philzen_rewrite-recipe-testng-to-junit-jupiter&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Philzen_rewrite-recipe-testng-to-junit-jupiter) Converts [TestNG](https://testng.org/) test annotations and assertions to [Junit 5](https://junit.org/junit5/docs/current/user-guide/). From 474c2924c0738ca5f1ece3ef7b278a107e29da11 Mon Sep 17 00:00:00 2001 From: Philzen Date: Mon, 10 Jun 2024 00:19:03 +0200 Subject: [PATCH 17/51] Make java parser instantiation thread safe and move to utils class --- .../testng/UpdateTestAnnotationToJunit5.java | 31 ++++++++----------- .../java/org/philzen/oss/utils/Parser.java | 28 +++++++++++++++++ 2 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/philzen/oss/utils/Parser.java diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 51b4f61b..4a05ba94 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -9,7 +9,10 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.*; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.search.FindImports; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.Expression; @@ -17,6 +20,7 @@ import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.Markup; +import org.philzen.oss.utils.Parser; import java.util.Arrays; import java.util.Comparator; @@ -38,15 +42,6 @@ public String getDescription() { return "Update usages of TestNG's `@org.testng.annotations.Test` annotation to JUnit 5's `@org.junit.jupiter.api.Test` annotation."; } - @Nullable - static private JavaParser.Builder javaParser; - static private JavaParser.Builder javaParser() { - if (javaParser == null) { - javaParser = JavaParser.fromJavaVersion().classpath("junit-jupiter-api"); - } - return javaParser; - } - @Override public TreeVisitor getVisitor() { return Preconditions.check(Preconditions.or( @@ -63,26 +58,26 @@ private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor< private final JavaTemplate displayNameAnnotation = JavaTemplate .builder("@DisplayName(#{any(java.lang.String)})") .imports("org.junit.jupiter.api.DisplayName") - .javaParser(javaParser()).build(); + .javaParser(Parser.jupiter()).build(); private final JavaTemplate disabledAnnotation = JavaTemplate .builder("@Disabled") .imports("org.junit.jupiter.api.Disabled") - .javaParser(javaParser()).build(); + .javaParser(Parser.jupiter()).build(); private final JavaTemplate junitExecutable = JavaTemplate .builder("org.junit.jupiter.api.function.Executable o = () -> #{};") - .javaParser(javaParser()).build(); + .javaParser(Parser.jupiter()).build(); private final JavaTemplate tagAnnotation = JavaTemplate .builder("@Tag(#{any(java.lang.String)})") .imports("org.junit.jupiter.api.Tag") - .javaParser(javaParser()).build(); + .javaParser(Parser.jupiter()).build(); private final JavaTemplate timeoutAnnotation = JavaTemplate .builder("@Timeout(value = #{any(long)}, unit = TimeUnit.MILLISECONDS)") .imports("org.junit.jupiter.api.Timeout", "java.util.concurrent.TimeUnit") - .javaParser(javaParser()).build(); + .javaParser(Parser.jupiter()).build(); @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { @@ -171,14 +166,14 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex final List parameters = Arrays.asList(cta.expectedException, lambda); final String code = "Assertions.assertThrows(#{any(java.lang.Class)}, #{any(org.junit.jupiter.api.function.Executable)});"; if (!(cta.expectedExceptionMessageRegExp instanceof J.Literal)) { - m = JavaTemplate.builder(code).javaParser(javaParser()) + m = JavaTemplate.builder(code).javaParser(Parser.jupiter()) .imports("org.junit.jupiter.api.Assertions").build() .apply(updateCursor(m), m.getCoordinates().replaceBody(), parameters.toArray()); } else { m = JavaTemplate.builder( "final Throwable thrown = " + code + System.lineSeparator() + "Assertions.assertTrue(thrown.getMessage().matches(#{any(java.lang.String)}));" - ).javaParser(javaParser()).imports("org.junit.jupiter.api.Assertions").build() + ).javaParser(Parser.jupiter()).imports("org.junit.jupiter.api.Assertions").build() .apply( updateCursor(m), m.getCoordinates().replaceBody(), @@ -265,7 +260,7 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (a.getAnnotationType() instanceof J.FieldAccess) { return JavaTemplate.builder("@org.junit.jupiter.api.Test") - .javaParser(javaParser()) + .javaParser(Parser.jupiter()) .build() .apply(getCursor(), a.getCoordinates().replace()); } else { diff --git a/src/main/java/org/philzen/oss/utils/Parser.java b/src/main/java/org/philzen/oss/utils/Parser.java new file mode 100644 index 00000000..5c78336e --- /dev/null +++ b/src/main/java/org/philzen/oss/utils/Parser.java @@ -0,0 +1,28 @@ +package org.philzen.oss.utils; + +import org.openrewrite.java.JavaParser; + +public enum Parser {; + + private static final class JavaParserHolder { + static final JavaParser.Builder jupiter = + JavaParser.fromJavaVersion().classpath("junit-jupiter-api"); + + static final JavaParser.Builder runtimeClasspath = + JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()); + } + + /** + * Get a {@link JavaParser.Builder} with junit-jupiter-api added to the classpath + */ + public static JavaParser.Builder jupiter() { + return JavaParserHolder.jupiter; + } + + /** + * Get a {@link JavaParser.Builder} for the full runtime classpath + */ + public static JavaParser.Builder runtime() { + return JavaParserHolder.runtimeClasspath; + } +} From 470db0f57ebff0dae32428c29c8616336aa9518b Mon Sep 17 00:00:00 2001 From: Philzen Date: Mon, 10 Jun 2024 00:31:01 +0200 Subject: [PATCH 18/51] Put common constant strings into static final variables --- .../testng/UpdateTestAnnotationToJunit5.java | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 4a05ba94..de820384 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -39,17 +39,24 @@ public String getDisplayName() { @Override public String getDescription() { - return "Update usages of TestNG's `@org.testng.annotations.Test` annotation to JUnit 5's `@org.junit.jupiter.api.Test` annotation."; + return String.format( + "Update usages of TestNG's `@%s` annotation to JUnit 5's `@%s` annotation.", TESTNG_TYPE, JUPITER_TYPE + ); } @Override public TreeVisitor getVisitor() { return Preconditions.check(Preconditions.or( - new UsesType<>("org.testng.annotations.Test", false), - new FindImports("org.testng.annotations.Test", null).getVisitor() + new UsesType<>(TESTNG_TYPE, false), + new FindImports(TESTNG_TYPE, null).getVisitor() ), new UpdateTestAnnotationToJunit5Visitor()); } + public static final String TESTNG_TYPE = "org.testng.annotations.Test"; + public static final String JUPITER_API_NAMESPACE = "org.junit.jupiter.api"; + public static final String JUPITER_TYPE = JUPITER_API_NAMESPACE + ".Test"; + public static final String JUPITER_ASSERTIONS_TYPE = JUPITER_API_NAMESPACE + ".Assertions"; + // inspired by https://github.com/openrewrite/rewrite-testing-frameworks/blob/4e8ba68b2a28a180f84de7bab9eb12b4643e342e/src/main/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotation.java# private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor { @@ -57,38 +64,38 @@ private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor< private final JavaTemplate displayNameAnnotation = JavaTemplate .builder("@DisplayName(#{any(java.lang.String)})") - .imports("org.junit.jupiter.api.DisplayName") + .imports(JUPITER_API_NAMESPACE + ".DisplayName") .javaParser(Parser.jupiter()).build(); private final JavaTemplate disabledAnnotation = JavaTemplate .builder("@Disabled") - .imports("org.junit.jupiter.api.Disabled") + .imports(JUPITER_API_NAMESPACE + ".Disabled") .javaParser(Parser.jupiter()).build(); private final JavaTemplate junitExecutable = JavaTemplate - .builder("org.junit.jupiter.api.function.Executable o = () -> #{};") + .builder(JUPITER_API_NAMESPACE + ".function.Executable o = () -> #{};") .javaParser(Parser.jupiter()).build(); private final JavaTemplate tagAnnotation = JavaTemplate .builder("@Tag(#{any(java.lang.String)})") - .imports("org.junit.jupiter.api.Tag") + .imports(JUPITER_API_NAMESPACE + ".Tag") .javaParser(Parser.jupiter()).build(); private final JavaTemplate timeoutAnnotation = JavaTemplate .builder("@Timeout(value = #{any(long)}, unit = TimeUnit.MILLISECONDS)") - .imports("org.junit.jupiter.api.Timeout", "java.util.concurrent.TimeUnit") + .imports(JUPITER_API_NAMESPACE + ".Timeout", "java.util.concurrent.TimeUnit") .javaParser(Parser.jupiter()).build(); @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { J.CompilationUnit c = super.visitCompilationUnit(cu, ctx); - if (!c.findType("org.testng.annotations.Test").isEmpty()) { + if (!c.findType(TESTNG_TYPE).isEmpty()) { // Update other references like `Test.class`. - c = (J.CompilationUnit) new ChangeType("org.testng.annotations.Test", "org.junit.jupiter.api.Test", true) + c = (J.CompilationUnit) new ChangeType(TESTNG_TYPE, JUPITER_TYPE, true) .getVisitor().visitNonNull(c, ctx); } - maybeRemoveImport("org.testng.annotations.Test"); + maybeRemoveImport(TESTNG_TYPE); doAfterVisit(new JavaIsoVisitor() { @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { @@ -100,7 +107,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon @Override public J.Import visitImport(J.Import anImport, ExecutionContext ctx) { - if ("org.testng.annotations.Test".equals(anImport.getTypeName())) { + if (TESTNG_TYPE.equals(anImport.getTypeName())) { return Markup.error(anImport, new IllegalStateException("This import should have been removed by this recipe.")); } return anImport; @@ -108,7 +115,7 @@ public J.Import visitImport(J.Import anImport, ExecutionContext ctx) { @Override public JavaType visitType(@Nullable JavaType javaType, ExecutionContext ctx) { - if (TypeUtils.isOfClassType(javaType, "org.testng.annotations.Test")) { + if (TypeUtils.isOfClassType(javaType, TESTNG_TYPE)) { getCursor().putMessageOnFirstEnclosing(J.class, "danglingTestRef", true); } return javaType; @@ -117,7 +124,9 @@ public JavaType visitType(@Nullable JavaType javaType, ExecutionContext ctx) { @Override public J postVisit(J tree, ExecutionContext ctx) { if (getCursor().getMessage("danglingTestRef", false)) { - return Markup.warn(tree, new IllegalStateException("This still has a type of `org.testng.annotations.Test`")); + return Markup.warn(tree, new IllegalStateException( + String.format("This still has a type of `%s`", TESTNG_TYPE) + )); } return tree; } @@ -135,7 +144,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex } if (cta.description != null && !J.Literal.isLiteralValue(cta.description, "")) { - maybeAddImport("org.junit.jupiter.api.DisplayName"); + maybeAddImport(JUPITER_API_NAMESPACE + ".DisplayName"); m = displayNameAnnotation.apply( updateCursor(m), m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()), @@ -144,7 +153,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex } if (J.Literal.isLiteralValue(cta.enabled, Boolean.FALSE)) { - maybeAddImport("org.junit.jupiter.api.Disabled"); + maybeAddImport(JUPITER_API_NAMESPACE + ".Disabled"); m = disabledAnnotation.apply( updateCursor(m), m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()) @@ -162,18 +171,18 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex ((J.VariableDeclarations) body.getStatements().get(0)) .getVariables().get(0).getInitializer(); - maybeAddImport("org.junit.jupiter.api.Assertions"); + maybeAddImport(JUPITER_ASSERTIONS_TYPE); final List parameters = Arrays.asList(cta.expectedException, lambda); final String code = "Assertions.assertThrows(#{any(java.lang.Class)}, #{any(org.junit.jupiter.api.function.Executable)});"; if (!(cta.expectedExceptionMessageRegExp instanceof J.Literal)) { m = JavaTemplate.builder(code).javaParser(Parser.jupiter()) - .imports("org.junit.jupiter.api.Assertions").build() + .imports(JUPITER_ASSERTIONS_TYPE).build() .apply(updateCursor(m), m.getCoordinates().replaceBody(), parameters.toArray()); } else { m = JavaTemplate.builder( "final Throwable thrown = " + code + System.lineSeparator() + "Assertions.assertTrue(thrown.getMessage().matches(#{any(java.lang.String)}));" - ).javaParser(Parser.jupiter()).imports("org.junit.jupiter.api.Assertions").build() + ).javaParser(Parser.jupiter()).imports(JUPITER_ASSERTIONS_TYPE).build() .apply( updateCursor(m), m.getCoordinates().replaceBody(), @@ -183,7 +192,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex } if (cta.groups != null) { - maybeAddImport("org.junit.jupiter.api.Tag"); + maybeAddImport(JUPITER_API_NAMESPACE + ".Tag"); if (cta.groups instanceof J.Literal && !J.Literal.isLiteralValue(cta.groups, "")) { m = tagAnnotation.apply( updateCursor(m), @@ -205,7 +214,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex if (cta.timeout != null) { maybeAddImport("java.util.concurrent.TimeUnit"); - maybeAddImport("org.junit.jupiter.api.Timeout"); + maybeAddImport(JUPITER_API_NAMESPACE + ".Timeout"); m = timeoutAnnotation.apply( updateCursor(m), m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)), @@ -259,13 +268,13 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { } if (a.getAnnotationType() instanceof J.FieldAccess) { - return JavaTemplate.builder("@org.junit.jupiter.api.Test") + return JavaTemplate.builder("@" + JUPITER_TYPE) .javaParser(Parser.jupiter()) .build() .apply(getCursor(), a.getCoordinates().replace()); } else { return a.withArguments(null) - .withType(JavaType.ShallowClass.build("org.junit.jupiter.api.Test")); + .withType(JavaType.ShallowClass.build(JUPITER_TYPE)); } } } From 3059a8802f9118cc31350dfbc2dc12e3232f5090 Mon Sep 17 00:00:00 2001 From: Philzen Date: Mon, 10 Jun 2024 05:11:16 +0200 Subject: [PATCH 19/51] Factor out post visitCompilationUnit visitor --- .../testng/UpdateTestAnnotationToJunit5.java | 39 +------------ .../org/philzen/oss/utils/AfterVisitor.java | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+), 37 deletions(-) create mode 100644 src/main/java/org/philzen/oss/utils/AfterVisitor.java diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index de820384..f0dbf940 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -6,7 +6,6 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.AnnotationMatcher; @@ -19,7 +18,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; -import org.openrewrite.marker.Markup; +import org.philzen.oss.utils.AfterVisitor; import org.philzen.oss.utils.Parser; import java.util.Arrays; @@ -96,41 +95,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } maybeRemoveImport(TESTNG_TYPE); - doAfterVisit(new JavaIsoVisitor() { - @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { - return cu.withClasses(ListUtils.map(cu.getClasses(), clazz -> (J.ClassDeclaration) visit(clazz, ctx))) - // take one more pass over the imports now that we've had a chance to add warnings to all - // uses of @Test through the rest of the source file - .withImports(ListUtils.map(cu.getImports(), anImport -> (J.Import) visit(anImport, ctx))); - } - - @Override - public J.Import visitImport(J.Import anImport, ExecutionContext ctx) { - if (TESTNG_TYPE.equals(anImport.getTypeName())) { - return Markup.error(anImport, new IllegalStateException("This import should have been removed by this recipe.")); - } - return anImport; - } - - @Override - public JavaType visitType(@Nullable JavaType javaType, ExecutionContext ctx) { - if (TypeUtils.isOfClassType(javaType, TESTNG_TYPE)) { - getCursor().putMessageOnFirstEnclosing(J.class, "danglingTestRef", true); - } - return javaType; - } - - @Override - public J postVisit(J tree, ExecutionContext ctx) { - if (getCursor().getMessage("danglingTestRef", false)) { - return Markup.warn(tree, new IllegalStateException( - String.format("This still has a type of `%s`", TESTNG_TYPE) - )); - } - return tree; - } - }); + doAfterVisit(new AfterVisitor(TESTNG_TYPE)); return c; } diff --git a/src/main/java/org/philzen/oss/utils/AfterVisitor.java b/src/main/java/org/philzen/oss/utils/AfterVisitor.java new file mode 100644 index 00000000..c756cd7d --- /dev/null +++ b/src/main/java/org/philzen/oss/utils/AfterVisitor.java @@ -0,0 +1,56 @@ +package org.philzen.oss.utils; + +import lombok.RequiredArgsConstructor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.marker.Markup; + +@NonNullApi +@RequiredArgsConstructor +public class AfterVisitor extends JavaIsoVisitor { + + /** + * The fully qualified type of the annotation that has to be gone now + */ + private final String typeBeGone; + + @Override + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { + return cu.withClasses(ListUtils.map(cu.getClasses(), clazz -> (J.ClassDeclaration) visit(clazz, ctx))) + // take one more pass over the imports now that we've had a chance to add warnings to all + // uses of the type (that should have been removed) through the rest of the source file + .withImports(ListUtils.map(cu.getImports(), anImport -> (J.Import) visit(anImport, ctx))); + } + + @Override + public J.Import visitImport(J.Import anImport, ExecutionContext ctx) { + if (typeBeGone.equals(anImport.getTypeName())) { + return Markup.error(anImport, new IllegalStateException("This import should have been removed by this recipe.")); + } + return anImport; + } + + @Override + public JavaType visitType(@Nullable JavaType javaType, ExecutionContext ctx) { + if (TypeUtils.isOfClassType(javaType, typeBeGone)) { + getCursor().putMessageOnFirstEnclosing(J.class, "danglingTestRef", true); + } + return javaType; + } + + @Override + public J postVisit(J tree, ExecutionContext ctx) { + if (getCursor().getMessage("danglingTestRef") != null) { + return Markup.warn(tree, new IllegalStateException( + String.format("This still has a type of `%s`", typeBeGone) + )); + } + return tree; + } +} From 4eaf2954e2194915e24f5d91ae4ad4ea1862bfc2 Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 11 Jun 2024 00:09:39 +0200 Subject: [PATCH 20/51] Always modify instead of creating new when annotation type J.FieldAccess Annotation type is J.FieldAccess anytime the annotation is fully qualified. The code in org.openrewrite.java.testing.junit5.UpdateTestAnnotation (which this is based on) would build a new annotation instance instead of modifying the type of the existing annotation. It turns out the latter also works fine for this recipe's scope. --- .../oss/testng/UpdateTestAnnotationToJunit5.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index f0dbf940..bb22319b 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -232,15 +232,8 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { } } - if (a.getAnnotationType() instanceof J.FieldAccess) { - return JavaTemplate.builder("@" + JUPITER_TYPE) - .javaParser(Parser.jupiter()) - .build() - .apply(getCursor(), a.getCoordinates().replace()); - } else { - return a.withArguments(null) - .withType(JavaType.ShallowClass.build(JUPITER_TYPE)); - } + // change @Test annotation type to JUnit 5 and remove all attribute arguments + return a.withArguments(null).withType(JavaType.ShallowClass.build(JUPITER_TYPE)); } } } From 79bb143e129efd75d59050ea0f5661c23e611ede Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 11 Jun 2024 05:03:25 +0200 Subject: [PATCH 21/51] Implement migration of class-level `@Test` annotation to methods Resolves #16 --- .../testng/UpdateTestAnnotationToJunit5.java | 44 ++- .../java/org/philzen/oss/utils/Cleanup.java | 44 +++ .../UpdateTestAnnotationToJunit5Test.java | 312 ++++++++++++++++++ .../org/philzen/oss/utils/CleanupTest.java | 155 +++++++++ 4 files changed, 550 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/philzen/oss/utils/Cleanup.java create mode 100644 src/test/java/org/philzen/oss/utils/CleanupTest.java diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index bb22319b..5fec5d34 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -6,6 +6,7 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.AnnotationMatcher; @@ -19,12 +20,10 @@ import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import org.philzen.oss.utils.AfterVisitor; +import org.philzen.oss.utils.Cleanup; import org.philzen.oss.utils.Parser; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; +import java.util.*; @Value @NonNullApi @@ -100,12 +99,47 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon return c; } + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, + ExecutionContext executionContext) { + + final Optional maybeAnnotation = classDecl.getLeadingAnnotations() + .stream().filter(TESTNG_TEST::matches).findFirst(); + + if (maybeAnnotation.isPresent()) { + classDecl = Cleanup.removeAnnotation(classDecl, maybeAnnotation.get()); + + getCursor().putMessage( + // don't know a good way to determine if annotation is fully qualified, therefore determining + // it from the toString() method and passing on a code template for the JavaTemplate.Builder + "ADD_TO_ALL_METHODS", "@" + (maybeAnnotation.get().toString().contains(".") ? JUPITER_TYPE : "Test") + ); + } + + return super.visitClassDeclaration(classDecl, executionContext); + } + @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { final ChangeTestAnnotation cta = new ChangeTestAnnotation(); J.MethodDeclaration m = (J.MethodDeclaration) cta.visitNonNull(method, ctx, getCursor().getParentOrThrow()); + + // method identity changes when `@Test` annotation was found and migrated by ChangeTestAnnotation if (m == method) { - return super.visitMethodDeclaration(method, ctx); + final boolean isContainedInInnerClass = + method.getMethodType() == null // ← (=true) not really, but in this fringe case it's better not to make a replacement + || method.getMethodType().getDeclaringType().getOwningClass() != null; + final boolean isPublic = method.getModifiers().stream().anyMatch(mod -> mod.toString().equals("public")); + final String neededOnAllMethods = getCursor().getNearestMessage("ADD_TO_ALL_METHODS"); + if (neededOnAllMethods == null || !isPublic || isContainedInInnerClass) { + return super.visitMethodDeclaration(method, ctx); + } + + return JavaTemplate.builder(neededOnAllMethods).imports(JUPITER_TYPE) + // for unknown reasons recipe:run on other projects will fail with "Unable to construct JavaParser" + // ↓ if using Jupiter-only classpath Parser (strangely, all tests pass) + .javaParser(Parser.runtime()).build() + .apply(getCursor(), method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed())); } if (cta.description != null && !J.Literal.isLiteralValue(cta.description, "")) { diff --git a/src/main/java/org/philzen/oss/utils/Cleanup.java b/src/main/java/org/philzen/oss/utils/Cleanup.java new file mode 100644 index 00000000..1d3d19d3 --- /dev/null +++ b/src/main/java/org/philzen/oss/utils/Cleanup.java @@ -0,0 +1,44 @@ +package org.philzen.oss.utils; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JContainer; +import org.openrewrite.java.tree.Space; + +import java.util.ArrayList; +import java.util.List; + +public enum Cleanup {; + + /** + * Removes an annotation and cleans the space that it occupied. + * Same could be achieved with {@link org.openrewrite.java.RemoveAnnotationVisitor}, however + * that would also traverse the whole LST underneath the class, yielding suboptimal performance.

+ * + * Space cleaning algorithm borrowed from {@link org.openrewrite.java.RemoveAnnotationVisitor#visitClassDeclaration(J.ClassDeclaration, ExecutionContext)} + */ + public static J.ClassDeclaration removeAnnotation(J.ClassDeclaration classDeclaration, J.Annotation a) { + + classDeclaration.getLeadingAnnotations().remove(a); + if (!classDeclaration.getLeadingAnnotations().isEmpty()) { + final List newLeadingAnnotations = new ArrayList<>(); + for (final J.Annotation other : classDeclaration.getLeadingAnnotations()) { + newLeadingAnnotations.add(other.withPrefix(other.getPrefix().withWhitespace(""))); + } + return classDeclaration.withLeadingAnnotations(newLeadingAnnotations); + } + + final List modifiers = classDeclaration.getModifiers(); + if (!modifiers.isEmpty()) { + return classDeclaration.withModifiers(Space.formatFirstPrefix(modifiers, Space.firstPrefix(modifiers).withWhitespace(""))); + } + + final JContainer typeParameters = classDeclaration.getPadding().getTypeParameters(); + if (typeParameters != null) { + return classDeclaration.getPadding().withTypeParameters(typeParameters.withBefore(typeParameters.getBefore().withWhitespace(""))); + } + + final J.ClassDeclaration.Padding padding = classDeclaration.getPadding(); + return padding.withKind(padding.getKind().withPrefix(padding.getKind().getPrefix().withWhitespace(""))); + } +} diff --git a/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java b/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java index 3ad43830..e3d203a3 100644 --- a/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java +++ b/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java @@ -17,6 +17,318 @@ public void defaults(RecipeSpec spec) { @Nested class NoAttributes { + @Nested class onClass { + + @Test void isMigratedToMethods() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Test + public class BazTest { + + public void shouldDoStuff() { + // + } + + public void shouldDoMoreStuff() { + // + } + } + """, + """ + import org.junit.jupiter.api.Test; + + public class BazTest { + + @Test + public void shouldDoStuff() { + // + } + + @Test + public void shouldDoMoreStuff() { + // + } + } + """ + )); + } + + @Test void isMigratedToMethods_preservingOtherAnnotations() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Deprecated @Test + public class BazTest { + + public void shouldDoStuff() { + // + } + } + """, + """ + import org.junit.jupiter.api.Test; + + @Deprecated + public class BazTest { + + @Test + public void shouldDoStuff() { + // + } + } + """ + )); + } + + /** + * Non-public method are executed only if they are explicitly annotated with `@Test` + */ + @Test void isMigratedOnlyToPublicMethods() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Test + public class BazTest { + + public void shouldDoStuff() { + // + } + + void thisAintNoTest() { + // + } + } + """, + """ + import org.junit.jupiter.api.Test; + + public class BazTest { + + @Test + public void shouldDoStuff() { + // + } + + void thisAintNoTest() { + // + } + } + """ + )); + } + + @Test void isMigratedToMethods_whenClassIsTyped() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Test + class BazTest { + + public void shouldDoStuff() { + // + } + + public void shouldDoMoreStuff() { + // + } + } + """, + """ + import org.junit.jupiter.api.Test; + + class BazTest { + + @Test + public void shouldDoStuff() { + // + } + + @Test + public void shouldDoMoreStuff() { + // + } + } + """ + )); + } + + @Test void isMigratedToMethods_whenFullyQualified() { + // language=java + rewriteRun(java( + """ + package de.foo.bar; + + @org.testng.annotations.Test + public class BazTest { + + public void shouldDoStuff() { + // + } + + public void shouldDoMoreStuff() { + // + } + } + """, + """ + package de.foo.bar; + + public class BazTest { + + @org.junit.jupiter.api.Test + public void shouldDoStuff() { + // + } + + @org.junit.jupiter.api.Test + public void shouldDoMoreStuff() { + // + } + } + """ + )); + } + + @Test void migrationPreservesOtherAnnotations() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Test + @Deprecated + class BazTest { + + public void shouldDoStuff() { + // + } + } + """, + """ + import org.junit.jupiter.api.Test; + + @Deprecated + class BazTest { + + @Test + public void shouldDoStuff() { + // + } + } + """ + )); + } + + @Test void doesNotOverwriteMethodLevelTestAnnotations() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Test + class BazTest { + + public void shouldDoStuff() { } + + @Test(enabled = false) + public void shouldDoMoreStuff() { } + } + """, + """ + import org.junit.jupiter.api.Disabled; + import org.junit.jupiter.api.Test; + + class BazTest { + + @Test + public void shouldDoStuff() { } + + @Test + @Disabled + public void shouldDoMoreStuff() { } + } + """ + )); + } + + /** + * Inner class methods are executed by the TestNG runner only when they are explicitly annotated with @Test, + * class-level annotation will not make them execute. + */ + @Test void doesNotAnnotateInnerClassMethods() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Test + public class BazTest { + public void test() { } + + public static class Inner { + public void noTest() { } + } + } + """, + """ + import org.junit.jupiter.api.Test; + + public class BazTest { + @Test + public void test() { } + + public static class Inner { + public void noTest() { } + } + } + """ + )); + } + + @Test void isRemoved_WhenOnlyInnerClassMethods() { + // language=java + rewriteRun(java( + """ + import org.testng.annotations.Test; + + @Test + public class BazTest { + public static class Inner { + public void noTest() { } + } + } + """, + """ + public class BazTest { + public static class Inner { + public void noTest() { } + } + } + """ + )); + } + + @Test void noChangeOnOtherAnnotations() { + // language=java + rewriteRun(java( + """ + @Deprecated + public class BazTest { + } + """ + )); + } + } + @Test void isMigratedToJunitTestAnnotationWithoutParameters() { // language=java rewriteRun(java( diff --git a/src/test/java/org/philzen/oss/utils/CleanupTest.java b/src/test/java/org/philzen/oss/utils/CleanupTest.java new file mode 100644 index 00000000..c3c559b8 --- /dev/null +++ b/src/test/java/org/philzen/oss/utils/CleanupTest.java @@ -0,0 +1,155 @@ +package org.philzen.oss.utils; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +@NonNullApi +class CleanupTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(RewriteTest.toRecipe(() -> new JavaIsoVisitor<>() { + + static final String TYPE_TO_REMOVE = "java.lang.Deprecated"; + private static final AnnotationMatcher MATCHER = new AnnotationMatcher("@" + TYPE_TO_REMOVE); + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { + if (classDecl.getLeadingAnnotations().stream().noneMatch(MATCHER::matches)) { + return classDecl; + } + + for (final J.Annotation annotation : classDecl.getLeadingAnnotations().stream().filter(MATCHER::matches).toList()) { + maybeRemoveImport(TYPE_TO_REMOVE); + classDecl = Cleanup.removeAnnotation(classDecl, annotation); + } + + return classDecl; + } + })); + } + + @Nested class removeAnnotation { + + @Test void onClassWithModifier() { + rewriteRun( + // language=java + java( + """ + @Deprecated + public class BazTest { + + } + """, + """ + public class BazTest { + + } + """ + ) + ); + } + + @Test void onClassWithoutModifier() { + rewriteRun( + // language=java + java( + """ + @Deprecated + class BazTest { + + } + """, + """ + class BazTest { + + } + """ + ) + ); + } + + /** + * This test is a bit convoluted as it will fail with "Recipe was expected to make a change but made no changes" + * when not also including removal of an import (only happens on typed classes without modifiers) + */ + @Test void onTypedClass() { + rewriteRun( + // language=java + java( + """ + import java.lang.Deprecated; + + @Deprecated + class BazTest { + + } + """, + """ + class BazTest { + + } + """ + ) + ); + } + + @Test void preservingExistingAnnotationsAfterIt() { + rewriteRun( + // language=java + java( + """ + import javax.annotation.concurrent.ThreadSafe; + + @Deprecated + @ThreadSafe + public class BazTest { + + } + """, + """ + import javax.annotation.concurrent.ThreadSafe; + + @ThreadSafe + public class BazTest { + + } + """ + ) + ); + } + + @Test void preservingExistingAnnotationsBeforeIt() { + rewriteRun( + // language=java + java( + """ + import javax.annotation.concurrent.ThreadSafe; + + @ThreadSafe @Deprecated + public class BazTest { + + } + """, + """ + import javax.annotation.concurrent.ThreadSafe; + + @ThreadSafe + public class BazTest { + + } + """ + ) + ); + } + } +} From ff1d555235ef0706d73d44ad265786c126487aaa Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 11 Jun 2024 10:21:38 +0200 Subject: [PATCH 22/51] Replace GitHub and SonarCloud badges with shields.io --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 807733c9..c39d0071 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # TestNG to Junit5 recipe   [![Compatible with Java 8](https://img.shields.io/badge/Works%20on%20Java-8-seagreen?logo=openjdk&labelColor=snow&logoColor=black)](#) -[![Build](https://github.com/Philzen/rewrite-recipe-testng-to-junit-jupiter/actions/workflows/ci.yml/badge.svg)](https://github.com/Philzen/rewrite-recipe-testng-to-junit-jupiter/actions/workflows/ci.yml) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Philzen_rewrite-recipe-testng-to-junit-jupiter&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Philzen_rewrite-recipe-testng-to-junit-jupiter) +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/philzen/rewrite-recipe-testng-to-junit-jupiter/ci.yml?logo=github)](https://github.com/Philzen/rewrite-recipe-testng-to-junit-jupiter/actions/workflows/ci.yml)   +[![Sonar Coverage](https://img.shields.io/sonar/coverage/Philzen_rewrite-recipe-testng-to-junit-jupiter?server=https%3A%2F%2Fsonarcloud.io&logo=sonarcloud&label=Coverage)](https://sonarcloud.io/summary/new_code?id=Philzen_rewrite-recipe-testng-to-junit-jupiter) Converts [TestNG](https://testng.org/) test annotations and assertions to [Junit 5](https://junit.org/junit5/docs/current/user-guide/). From 305e89b8c3e7c57a2457b3876cc5124a52cc42ac Mon Sep 17 00:00:00 2001 From: Philzen Date: Wed, 12 Jun 2024 14:45:43 +0200 Subject: [PATCH 23/51] Move maven build action into Analyze workflow SonarCloud depends on the jacoco XML to be produced, but will be started before it is uploaded on PRs, thus failing. Moving the maven build action into that workflow reduces the overall build time as maven and gradle build can run in parallel. --- .github/workflows/analyse.yml | 25 +++++++++++++++++++++---- .github/workflows/ci.yml | 21 --------------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml index 88deb264..5a199243 100644 --- a/.github/workflows/analyse.yml +++ b/.github/workflows/analyse.yml @@ -1,9 +1,10 @@ name: SonarCloud on: - workflow_call: - inputs: {} + push: + branches: [main] pull_request: types: [opened, synchronize, reopened] + jobs: analyze: name: Analyze @@ -29,11 +30,27 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - - name: Download code coverage results - uses: actions/download-artifact@v4 + + - name: "[Maven] Verify & generate JaCoCo XML" + env: + MAVEN_OPTS: '-Dstyle.color=always' + MAVEN_SWITCHES: >- + --show-version + --no-transfer-progress + --update-snapshots + --fail-at-end + --batch-mode + run: mvn ${{ env.MAVEN_SWITCHES }} -P coverage verify + + - name: Upload maven build result + uses: actions/upload-artifact@v4 with: name: maven-build-target-folder path: target + + - name: List current location + run: find . + - name: Analyze project env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58d1a9f9..bd294e60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,27 +36,6 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: arguments: ${{ env.GRADLE_SWITCHES }} build test - - name: "[Maven] Verify & generate JaCoCo XML" - env: - MAVEN_OPTS: '-Dstyle.color=always' - MAVEN_SWITCHES: >- - --show-version - --no-transfer-progress - --update-snapshots - --fail-at-end - --batch-mode - run: mvn ${{ env.MAVEN_SWITCHES }} -P coverage verify - - - name: Upload maven build result - uses: actions/upload-artifact@v4 - with: - name: maven-build-target-folder - path: target - - call: - needs: [build] - uses: './.github/workflows/analyse.yml' - secrets: inherit publish-snapshots: needs: [build] From c75166997e7a8e5bcd52e55955915de1465c0ed1 Mon Sep 17 00:00:00 2001 From: Philzen Date: Fri, 14 Jun 2024 21:04:57 +0200 Subject: [PATCH 24/51] Add list of annotation attributes that cannot be migrated (yet) --- docs/NOT-IMPLEMENTED.md | 157 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 docs/NOT-IMPLEMENTED.md diff --git a/docs/NOT-IMPLEMENTED.md b/docs/NOT-IMPLEMENTED.md new file mode 100644 index 00000000..feaf91de --- /dev/null +++ b/docs/NOT-IMPLEMENTED.md @@ -0,0 +1,157 @@ +The following is a list of `@org.testng.annotations.Test` annotation attributes for which +there is no clear plan on how to migrate them yet. + +Any contributions or suggestions for equivalents in a JUnit5 test setup are welcome. + +- ```java + /** + * The list of groups this method depends on. Every method member of one of these groups is + * guaranteed to have been invoked before this method. Furthermore, if any of these methods was + * not a SUCCESS, this test method will not be run and will be flagged as a SKIP. + * + * @return the value + */ + String[] dependsOnGroups() default {}; + ``` + +- ```java + /** + * The list of methods this method depends on. There is no guarantee on the order on which the + * methods depended upon will be run, but you are guaranteed that all these methods will be run + * before the test method that contains this annotation is run. Furthermore, if any of these + * methods was not a SUCCESS, this test method will not be run and will be flagged as a SKIP. + * + *

If some of these methods have been overloaded, all the overloaded versions will be run. + * + * @return the value + */ + String[] dependsOnMethods() default {}; + ``` + +- ```java + /** + * The maximum number of milliseconds that the total number of invocations on this test method + * should take. This annotation will be ignored if the attribute invocationCount is not specified + * on this method. If it hasn't returned after this time, it will be marked as a FAIL. + * + * @return the value (default 0) + */ + long invocationTimeOut() default 0; + ``` + +- ```java + /** + * The number of times this method should be invoked. + * + * @return the value (default 1) + */ + int invocationCount() default 1; + ``` + +- ```java + /** + * The size of the thread pool for this method. The method will be invoked from multiple threads + * as specified by invocationCount. Note: this attribute is ignored if invocationCount is not + * specified + * + * @return the value (default 0) + */ + int threadPoolSize() default 0; + ``` + +- ```java + /** + * The percentage of success expected from this method. + * + * @return the value (default 100) + */ + int successPercentage() default 100; + ``` + +- ```java + /** + * If set to true, this test method will always be run even if it depends on a method that failed. + * This attribute will be ignored if this test doesn't depend on any method or group. + * + * @return the value (default false) + */ + boolean alwaysRun() default false; + ``` + +- ```java + /** + * The name of the suite this test class should be placed in. This attribute is ignore if @Test is + * not at the class level. + * + * @return the value (default empty) + */ + String suiteName() default ""; + ``` + +- ```java + /** + * The name of the test this test class should be placed in. This attribute is ignore if @Test is + * not at the class level. + * + * @return the value (default empty) + */ + String testName() default ""; + ``` + +- ```java + /** + * If set to true, all the methods on this test class are guaranteed to run in the same thread, + * even if the tests are currently being run with parallel="true". + * + *

This attribute can only be used at the class level and will be ignored if used at the method + * level. + * + * @return true if single threaded (default false) + */ + boolean singleThreaded() default false; + ``` + +- ```java + /** + * The name of the class that should be called to test if the test should be retried. + * + * @return String The name of the class that will test if a test method should be retried. + */ + Class retryAnalyzer() default DisabledRetryAnalyzer.class; + ``` + +- ```java + /** + * If true and invocationCount is specified with a value > 1, then all invocations after a + * failure will be marked as a SKIP instead of a FAIL. + * + * @return the value (default false) + */ + boolean skipFailedInvocations() default false; + ``` + +- ```java + /** + * If set to true, this test will run even if the methods it depends on are missing or excluded. + * + * @return the value (default false) + */ + boolean ignoreMissingDependencies() default false; + ``` + +- ```java + /** + * The scheduling priority. Lower priorities will be scheduled first. + * + * @return the value (default 0) + */ + int priority() default 0; + ``` + +- ```java + /** + * @return - An array of {@link CustomAttribute} that represents a set of custom attributes for a + * test method. + */ + CustomAttribute[] attributes() default {}; + ``` From ed8c4f67c12ddb3dffbac053a9268e31ff6e2f01 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 15 Jun 2024 17:56:37 +0200 Subject: [PATCH 25/51] Avoid 'File encoding has not been set' maven warning, defining UTF-8 --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index fc883d54..ed6795ff 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,10 @@ rewrite-testng-to-junit5 0.1-SNAPSHOT + + UTF-8 + + org.projectlombok From d89aaf1ba5e861994c1e0414187b990682be400f Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 15 Jun 2024 21:27:29 +0200 Subject: [PATCH 26/51] Factor out complex J.* operations into Util classes --- .../testng/UpdateTestAnnotationToJunit5.java | 36 ++++++------- .../java/org/philzen/oss/utils/Class.java | 20 ++++++++ .../java/org/philzen/oss/utils/Method.java | 50 +++++++++++++++++++ 3 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/philzen/oss/utils/Class.java create mode 100644 src/main/java/org/philzen/oss/utils/Method.java diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 5fec5d34..5b2cce53 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -19,11 +19,13 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; -import org.philzen.oss.utils.AfterVisitor; -import org.philzen.oss.utils.Cleanup; -import org.philzen.oss.utils.Parser; +import org.philzen.oss.utils.Class; +import org.philzen.oss.utils.*; -import java.util.*; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; @Value @NonNullApi @@ -103,16 +105,14 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { - final Optional maybeAnnotation = classDecl.getLeadingAnnotations() - .stream().filter(TESTNG_TEST::matches).findFirst(); - - if (maybeAnnotation.isPresent()) { - classDecl = Cleanup.removeAnnotation(classDecl, maybeAnnotation.get()); + final J.Annotation testAnnotation = Class.getAnnotation(classDecl, TESTNG_TEST); + if (testAnnotation != null) { + classDecl = Cleanup.removeAnnotation(classDecl, testAnnotation); getCursor().putMessage( // don't know a good way to determine if annotation is fully qualified, therefore determining // it from the toString() method and passing on a code template for the JavaTemplate.Builder - "ADD_TO_ALL_METHODS", "@" + (maybeAnnotation.get().toString().contains(".") ? JUPITER_TYPE : "Test") + "ADD_TO_ALL_METHODS", "@" + (testAnnotation.toString().contains(".") ? JUPITER_TYPE : "Test") ); } @@ -126,13 +126,10 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex // method identity changes when `@Test` annotation was found and migrated by ChangeTestAnnotation if (m == method) { - final boolean isContainedInInnerClass = - method.getMethodType() == null // ← (=true) not really, but in this fringe case it's better not to make a replacement - || method.getMethodType().getDeclaringType().getOwningClass() != null; - final boolean isPublic = method.getModifiers().stream().anyMatch(mod -> mod.toString().equals("public")); final String neededOnAllMethods = getCursor().getNearestMessage("ADD_TO_ALL_METHODS"); - if (neededOnAllMethods == null || !isPublic || isContainedInInnerClass) { - return super.visitMethodDeclaration(method, ctx); + final boolean isContainedInInnerClass = Boolean.TRUE.equals(Method.isContainedInInnerClass(m)); + if (neededOnAllMethods == null || !Method.isPublic(m) || isContainedInInnerClass) { + return super.visitMethodDeclaration(m, ctx); } return JavaTemplate.builder(neededOnAllMethods).imports(JUPITER_TYPE) @@ -164,14 +161,9 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex && TypeUtils.isAssignableTo("java.lang.Throwable", ((J.FieldAccess) cta.expectedException).getTarget().getType())) { m = junitExecutable.apply(updateCursor(m), m.getCoordinates().replaceBody(), m.getBody()); - final J.Block body = m.getBody(); - assert body != null; - final J.Lambda lambda = (J.Lambda) - ((J.VariableDeclarations) body.getStatements().get(0)) - .getVariables().get(0).getInitializer(); maybeAddImport(JUPITER_ASSERTIONS_TYPE); - final List parameters = Arrays.asList(cta.expectedException, lambda); + final List parameters = Arrays.asList(cta.expectedException, Method.getFirstStatementLambdaAssignment(m)); final String code = "Assertions.assertThrows(#{any(java.lang.Class)}, #{any(org.junit.jupiter.api.function.Executable)});"; if (!(cta.expectedExceptionMessageRegExp instanceof J.Literal)) { m = JavaTemplate.builder(code).javaParser(Parser.jupiter()) diff --git a/src/main/java/org/philzen/oss/utils/Class.java b/src/main/java/org/philzen/oss/utils/Class.java new file mode 100644 index 00000000..2601a057 --- /dev/null +++ b/src/main/java/org/philzen/oss/utils/Class.java @@ -0,0 +1,20 @@ +package org.philzen.oss.utils; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.tree.J; + +import java.util.Optional; + +@NonNullApi +public enum Class {; + + @Nullable + public static J.Annotation getAnnotation(J.ClassDeclaration classDeclaration, AnnotationMatcher annotation) { + final Optional maybeAnnotation = classDeclaration.getLeadingAnnotations() + .stream().filter(annotation::matches).findFirst(); + + return maybeAnnotation.orElse(null); + } +} diff --git a/src/main/java/org/philzen/oss/utils/Method.java b/src/main/java/org/philzen/oss/utils/Method.java new file mode 100644 index 00000000..42f0c5d3 --- /dev/null +++ b/src/main/java/org/philzen/oss/utils/Method.java @@ -0,0 +1,50 @@ +package org.philzen.oss.utils; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +@NonNullApi +public enum Method {; + + /** + * Whether this method is declared in a nested class and not on the main class scope + * @return Returns null in the fringe case that {@link J.MethodDeclaration#getMethodType()} is + * null as it's not possible to query the parent scope information then (respectively + * it's not clear what {@link J.MethodDeclaration#getMethodType()} == null means) + */ + @Nullable + public static Boolean isContainedInInnerClass(J.MethodDeclaration method) { + final JavaType.Method methodType = method.getMethodType(); + if (methodType == null) { + return null; + } + + return methodType.getDeclaringType().getOwningClass() != null; + } + + public static boolean isPublic(J.MethodDeclaration method) { + return method.getModifiers().stream().anyMatch(mod -> mod.toString().equals("public")); + } + + /** + * Suppose you have a method that looks like this: + *

+     *     void method() {
+     *         Supplier x = () -> { return "x" };
+     *     }
+     * 

+ * Then this method will return the {@link J.Lambda} that represents () -> { return "x" }. + * @return The lambda or null, if no such expression exists on the first statement of the method body + */ + @Nullable + public static J.Lambda getFirstStatementLambdaAssignment(J.MethodDeclaration method) { + final J.Block body = method.getBody(); + if (body == null) { + return null; + } + + return (J.Lambda) ((J.VariableDeclarations) body.getStatements().get(0)).getVariables().get(0).getInitializer(); + } +} From bc3ebcf6de246adfc2a51cbf2b221d127db47c80 Mon Sep 17 00:00:00 2001 From: Philzen Date: Mon, 17 Jun 2024 22:30:15 +0200 Subject: [PATCH 27/51] Remove redundant checks and return early wherever feasible --- .../testng/UpdateTestAnnotationToJunit5.java | 73 +++++++++---------- .../org/philzen/oss/utils/AfterVisitor.java | 56 -------------- 2 files changed, 33 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/org/philzen/oss/utils/AfterVisitor.java diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 5b2cce53..943f138a 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -17,10 +17,11 @@ import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import org.philzen.oss.utils.Class; -import org.philzen.oss.utils.*; +import org.philzen.oss.utils.Cleanup; +import org.philzen.oss.utils.Method; +import org.philzen.oss.utils.Parser; import java.util.Arrays; import java.util.Comparator; @@ -93,11 +94,9 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon // Update other references like `Test.class`. c = (J.CompilationUnit) new ChangeType(TESTNG_TYPE, JUPITER_TYPE, true) .getVisitor().visitNonNull(c, ctx); + maybeRemoveImport(TESTNG_TYPE); } - maybeRemoveImport(TESTNG_TYPE); - doAfterVisit(new AfterVisitor(TESTNG_TYPE)); - return c; } @@ -121,7 +120,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - final ChangeTestAnnotation cta = new ChangeTestAnnotation(); + final ProcessAnnotationAttributes cta = new ProcessAnnotationAttributes(); J.MethodDeclaration m = (J.MethodDeclaration) cta.visitNonNull(method, ctx, getCursor().getParentOrThrow()); // method identity changes when `@Test` annotation was found and migrated by ChangeTestAnnotation @@ -129,7 +128,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex final String neededOnAllMethods = getCursor().getNearestMessage("ADD_TO_ALL_METHODS"); final boolean isContainedInInnerClass = Boolean.TRUE.equals(Method.isContainedInInnerClass(m)); if (neededOnAllMethods == null || !Method.isPublic(m) || isContainedInInnerClass) { - return super.visitMethodDeclaration(m, ctx); + return m; } return JavaTemplate.builder(neededOnAllMethods).imports(JUPITER_TYPE) @@ -213,53 +212,47 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex ); } - return super.visitMethodDeclaration(m, ctx); + return m; } - private static class ChangeTestAnnotation extends JavaIsoVisitor { - - private boolean found; + /** + * Parses all annotation arguments, retains all that are migratable + * and removes them from the visited @Test-annotation + */ + private static class ProcessAnnotationAttributes extends JavaIsoVisitor { @Nullable Expression description, enabled, expectedException, expectedExceptionMessageRegExp, groups, timeout; @Override public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { - if (found || !TESTNG_TEST.matches(a)) { + if (a.getArguments() == null || !TESTNG_TEST.matches(a)) { return a; } - // While unlikely, it's possible that a method has an inner class/lambda/etc. with methods that have test annotations - // Avoid considering any but the first test annotation found - found = true; - if (a.getArguments() != null) { - for (Expression arg : a.getArguments()) { - if (!(arg instanceof J.Assignment)) { - continue; - } - final J.Assignment assign = (J.Assignment) arg; - final String assignParamName = ((J.Identifier) assign.getVariable()).getSimpleName(); - final Expression e = assign.getAssignment(); - if ("description".equals(assignParamName)) { - description = e; - } else if ("enabled".equals(assignParamName)) { - enabled = e; - } else if ("expectedExceptions".equals(assignParamName)) { - // if attribute was given in { array form }, pick the first element (null is not allowed) - expectedException = !(e instanceof J.NewArray) - ? e : Objects.requireNonNull(((J.NewArray) e).getInitializer()).get(0); - } else if ("expectedExceptionsMessageRegExp".equals(assignParamName)) { - expectedExceptionMessageRegExp = e; - } else if ("groups".equals(assignParamName)) { - groups = e; - } else if ("timeOut".equals(assignParamName)) { - timeout = e; - } + for (Expression arg : a.getArguments()) { + final J.Assignment assign = (J.Assignment) arg; + final String assignParamName = ((J.Identifier) assign.getVariable()).getSimpleName(); + final Expression e = assign.getAssignment(); + if ("description".equals(assignParamName)) { + description = e; + } else if ("enabled".equals(assignParamName)) { + enabled = e; + } else if ("expectedExceptions".equals(assignParamName)) { + // if attribute was given in { array form }, pick the first element (null is not allowed) + expectedException = !(e instanceof J.NewArray) + ? e : Objects.requireNonNull(((J.NewArray) e).getInitializer()).get(0); + } else if ("expectedExceptionsMessageRegExp".equals(assignParamName)) { + expectedExceptionMessageRegExp = e; + } else if ("groups".equals(assignParamName)) { + groups = e; + } else if ("timeOut".equals(assignParamName)) { + timeout = e; } } - // change @Test annotation type to JUnit 5 and remove all attribute arguments - return a.withArguments(null).withType(JavaType.ShallowClass.build(JUPITER_TYPE)); + // remove all attribute arguments (JUnit 5 @Test annotation doesn't allow any) + return a.withArguments(null); } } } diff --git a/src/main/java/org/philzen/oss/utils/AfterVisitor.java b/src/main/java/org/philzen/oss/utils/AfterVisitor.java deleted file mode 100644 index c756cd7d..00000000 --- a/src/main/java/org/philzen/oss/utils/AfterVisitor.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.philzen.oss.utils; - -import lombok.RequiredArgsConstructor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.NonNullApi; -import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeUtils; -import org.openrewrite.marker.Markup; - -@NonNullApi -@RequiredArgsConstructor -public class AfterVisitor extends JavaIsoVisitor { - - /** - * The fully qualified type of the annotation that has to be gone now - */ - private final String typeBeGone; - - @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { - return cu.withClasses(ListUtils.map(cu.getClasses(), clazz -> (J.ClassDeclaration) visit(clazz, ctx))) - // take one more pass over the imports now that we've had a chance to add warnings to all - // uses of the type (that should have been removed) through the rest of the source file - .withImports(ListUtils.map(cu.getImports(), anImport -> (J.Import) visit(anImport, ctx))); - } - - @Override - public J.Import visitImport(J.Import anImport, ExecutionContext ctx) { - if (typeBeGone.equals(anImport.getTypeName())) { - return Markup.error(anImport, new IllegalStateException("This import should have been removed by this recipe.")); - } - return anImport; - } - - @Override - public JavaType visitType(@Nullable JavaType javaType, ExecutionContext ctx) { - if (TypeUtils.isOfClassType(javaType, typeBeGone)) { - getCursor().putMessageOnFirstEnclosing(J.class, "danglingTestRef", true); - } - return javaType; - } - - @Override - public J postVisit(J tree, ExecutionContext ctx) { - if (getCursor().getMessage("danglingTestRef") != null) { - return Markup.warn(tree, new IllegalStateException( - String.format("This still has a type of `%s`", typeBeGone) - )); - } - return tree; - } -} From c583f94be852c5745101ac2919b761469fc692f7 Mon Sep 17 00:00:00 2001 From: Philzen Date: Tue, 18 Jun 2024 01:03:55 +0200 Subject: [PATCH 28/51] Move repetitive Comparator declarations into Util class --- .../testng/UpdateTestAnnotationToJunit5.java | 34 ++++--------------- src/main/java/org/philzen/oss/utils/Sort.java | 10 ++++++ 2 files changed, 17 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/philzen/oss/utils/Sort.java diff --git a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java index 943f138a..daa8c4d6 100644 --- a/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java +++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java @@ -19,12 +19,9 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; import org.philzen.oss.utils.Class; -import org.philzen.oss.utils.Cleanup; -import org.philzen.oss.utils.Method; -import org.philzen.oss.utils.Parser; +import org.philzen.oss.utils.*; import java.util.Arrays; -import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -135,24 +132,19 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex // for unknown reasons recipe:run on other projects will fail with "Unable to construct JavaParser" // ↓ if using Jupiter-only classpath Parser (strangely, all tests pass) .javaParser(Parser.runtime()).build() - .apply(getCursor(), method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed())); + .apply(getCursor(), m.getCoordinates().addAnnotation(Sort.BELOW)); } if (cta.description != null && !J.Literal.isLiteralValue(cta.description, "")) { maybeAddImport(JUPITER_API_NAMESPACE + ".DisplayName"); m = displayNameAnnotation.apply( - updateCursor(m), - m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()), - cta.description + updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW), cta.description ); } if (J.Literal.isLiteralValue(cta.enabled, Boolean.FALSE)) { maybeAddImport(JUPITER_API_NAMESPACE + ".Disabled"); - m = disabledAnnotation.apply( - updateCursor(m), - m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()) - ); + m = disabledAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW)); } if (cta.expectedException instanceof J.FieldAccess @@ -184,20 +176,12 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex if (cta.groups != null) { maybeAddImport(JUPITER_API_NAMESPACE + ".Tag"); if (cta.groups instanceof J.Literal && !J.Literal.isLiteralValue(cta.groups, "")) { - m = tagAnnotation.apply( - updateCursor(m), - m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()), - cta.groups - ); + m = tagAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW), cta.groups); } else if (cta.groups instanceof J.NewArray && ((J.NewArray) cta.groups).getInitializer() != null) { final List groups = ((J.NewArray) cta.groups).getInitializer(); for (Expression group : groups) { if (group instanceof J.Empty) continue; - m = tagAnnotation.apply( - updateCursor(m), - m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName).reversed()), - group - ); + m = tagAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW), group); } } } @@ -205,11 +189,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex if (cta.timeout != null) { maybeAddImport("java.util.concurrent.TimeUnit"); maybeAddImport(JUPITER_API_NAMESPACE + ".Timeout"); - m = timeoutAnnotation.apply( - updateCursor(m), - m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)), - cta.timeout - ); + m = timeoutAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.ABOVE), cta.timeout); } return m; diff --git a/src/main/java/org/philzen/oss/utils/Sort.java b/src/main/java/org/philzen/oss/utils/Sort.java new file mode 100644 index 00000000..be28ae4d --- /dev/null +++ b/src/main/java/org/philzen/oss/utils/Sort.java @@ -0,0 +1,10 @@ +package org.philzen.oss.utils; + +import org.openrewrite.java.tree.J; + +import java.util.Comparator; + +public enum Sort {; + public static final Comparator ABOVE = java.util.Comparator.comparing(J.Annotation::getSimpleName); + public static final Comparator BELOW = java.util.Comparator.comparing(J.Annotation::getSimpleName).reversed(); +} From 68f70fe771f9f2a6ba68c7a19219e8fe842e39a8 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Thu, 20 Jun 2024 05:59:28 -0400 Subject: [PATCH 29/51] Improvements on the template for maven users (#56) * Improvements on the template for maven users Signed-off-by: Adriano Machado <60320+ammachado@users.noreply.github.com> * Minor touch ups --------- Signed-off-by: Adriano Machado <60320+ammachado@users.noreply.github.com> Co-authored-by: Tim te Beek --- .gitignore | 2 + gradle/licenseHeader.txt => licenseHeader.txt | 0 pom.xml | 68 +++++++++++++++++-- 3 files changed, 66 insertions(+), 4 deletions(-) rename gradle/licenseHeader.txt => licenseHeader.txt (100%) diff --git a/.gitignore b/.gitignore index 808a1516..bdc0db8d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ target/ .idea/ out/ src/main/generated/ +.vscode/ +*.iml diff --git a/gradle/licenseHeader.txt b/licenseHeader.txt similarity index 100% rename from gradle/licenseHeader.txt rename to licenseHeader.txt diff --git a/pom.xml b/pom.xml index ed6795ff..dea33516 100644 --- a/pom.xml +++ b/pom.xml @@ -9,6 +9,7 @@ UTF-8 + UTF-8 @@ -17,6 +18,7 @@ lombok 1.18.32 provided + true @@ -30,6 +32,7 @@ error_prone_core 2.28.0 provided + true com.google.auto.service @@ -127,7 +130,7 @@ org.openrewrite.recipe rewrite-recipe-bom - 2.11.1 + 2.13.2 pom import @@ -164,18 +167,75 @@ org.openrewrite rewrite-templating - 1.8.1 + 1.11.1 + org.apache.maven.plugins maven-surefire-plugin - 3.1.2 + 3.3.0 + org.apache.maven.plugins maven-failsafe-plugin - 3.1.2 + 3.3.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + jar + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.7.0 + + + copy + process-sources + + copy + + + + + + junit + junit + 3.8.1 + + + ${project.basedir}/src/main/resources/META-INF/rewrite/classpath + true + + + + + + + org.openrewrite.maven + rewrite-maven-plugin + 5.34.1 + + true + + + + org.openrewrite.recipe + rewrite-recommendations + 1.6.1 + + From 3585aaf32e9ab38348dccee1bf4f11824c49ecf8 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 16:16:17 +0200 Subject: [PATCH 30/51] Disable process-sources:copy goal --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index dea33516..a6500929 100644 --- a/pom.xml +++ b/pom.xml @@ -214,6 +214,7 @@ ${project.basedir}/src/main/resources/META-INF/rewrite/classpath true + true From ac7b188894b1b2f63d5dc35a279ff6e24ba2d3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Wed, 17 Apr 2024 16:47:34 +0200 Subject: [PATCH 31/51] Add tests and dependency for Data Provider migration --- pom.xml | 6 ++ .../MigrateDataProviderTest.java | 80 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java diff --git a/pom.xml b/pom.xml index a6500929..c5af48fc 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,12 @@ test + + org.junit.jupiter + junit-jupiter-params + test + + diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java new file mode 100644 index 00000000..81162f7a --- /dev/null +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java @@ -0,0 +1,80 @@ +package io.github.mboegers.openrewrite.testngtojupiter.parameterized; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class MigrateDataProviderTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpath("junit-jupiter-api", "junit-jupiter-params", "testng")) + .recipe(new MigrateDataProvider()); + } + + @Nested + class Wrap { + /* + * annotation parameter other than name are ignored + */ + @Test + void withName() { + @Language("java") String is = """ + import org.testng.annotations.DataProvider; + + public class BoxPrimitiveDataProvider { + @DataProvider(name = "anotherBoxPrimitiveDataProvider") + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + } + """; + @Language("java") String should = """ + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream anotherBoxPrimitiveDataProviderSource() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """; + rewriteRun(java(is, should)); + } + + @Test + void withDefaultName() { + @Language("java") String is = """ + import org.testng.annotations.DataProvider; + + public class BoxPrimitiveDataProvider { + @DataProvider + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + } + """; + @Language("java") String should = """ + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream boxPrimitiveDataProviderSource() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """; + rewriteRun(java(is, should)); + } + } +} From 98177a0892ec022b378d63613110984ba1221f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Wed, 17 Apr 2024 16:47:47 +0200 Subject: [PATCH 32/51] Implement data provider migrations --- .../parameterized/MigrateDataProvider.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java new file mode 100644 index 00000000..23f3e3f4 --- /dev/null +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java @@ -0,0 +1,89 @@ +package io.github.mboegers.openrewrite.testngtojupiter.parameterized; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.*; +import org.openrewrite.java.tree.J; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class MigrateDataProvider extends Recipe { + @Override + public String getDisplayName() { + return "Migrate @DataProvider utilities"; + } + + @Override + public String getDescription() { + return "Wrap `@DataProvider` methods into a Jupiter parameterized test MethodSource."; + } + + @Override + public TreeVisitor getVisitor() { + return new MigrateDataProviderVisitor(); + } + + private class MigrateDataProviderVisitor extends JavaIsoVisitor { + private static final AnnotationMatcher DATAPROVIDER_MATCHER = new AnnotationMatcher("@org.testng.annotations.DataProvider"); + + private static final JavaTemplate methodeSourceTemplate = JavaTemplate.builder(""" + public static Stream #{}Source() { + return Arrays.stream(#{}()).map(Arguments::of); + } + """) + .imports("org.junit.jupiter.params.provider.Arguments", "java.util.Arrays", "java.util.stream.Stream") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpath("junit-jupiter-api", "junit-jupiter-params", "testng")) + .build(); + private static final RemoveAnnotationVisitor removeAnnotationVisitor = new RemoveAnnotationVisitor(DATAPROVIDER_MATCHER); + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, org.openrewrite.ExecutionContext executionContext) { + classDecl = super.visitClassDeclaration(classDecl, executionContext); + + Set dataProviders = classDecl.getBody().getStatements().stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .filter(m -> m.getLeadingAnnotations().stream().anyMatch(DATAPROVIDER_MATCHER::matches)) + .collect(Collectors.toSet()); + + for (J.MethodDeclaration provider : dataProviders) { + String providerMethodName = provider.getSimpleName(); + String providerName = provider.getLeadingAnnotations().stream() + .filter(DATAPROVIDER_MATCHER::matches) + .map(J.Annotation::getArguments) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(J.Assignment.class::isInstance) + .map(J.Assignment.class::cast) + .filter(a -> "name".equals(((J.Identifier) a.getVariable()).getSimpleName())) + .map(J.Assignment::getAssignment) + .filter(J.Literal.class::isInstance) + .map(J.Literal.class::cast) + .map(J.Literal::getValue) + .map(Objects::toString) + .findAny() + .orElse(providerMethodName); + + classDecl = classDecl.withBody(methodeSourceTemplate.apply( + new Cursor(getCursor(), classDecl.getBody()), classDecl.getBody().getCoordinates().lastStatement(), + providerName, providerMethodName)); + } + + doAfterVisit(new RemoveAnnotationVisitor(DATAPROVIDER_MATCHER)); + maybeRemoveImport("org.testng.annotations.DataProvider"); + maybeAddImport("org.junit.jupiter.params.provider.Arguments"); + maybeAddImport("java.util.Arrays"); + maybeAddImport("java.util.stream.Stream"); + + return classDecl; + } + } +} From 12f63729caaee068db79fb7b96c41a988473b132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Wed, 17 Apr 2024 17:08:36 +0200 Subject: [PATCH 33/51] Fix licence headers --- .../parameterized/MigrateDataProvider.java | 52 +++++++++++++------ .../MigrateDataProviderTest.java | 10 ++++ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java index 23f3e3f4..0ecae4d6 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java @@ -1,5 +1,16 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + package io.github.mboegers.openrewrite.testngtojupiter.parameterized; +import org.jetbrains.annotations.NotNull; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; @@ -9,6 +20,7 @@ import java.util.Collection; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -45,38 +57,27 @@ private class MigrateDataProviderVisitor extends JavaIsoVisitor dataProviders = classDecl.getBody().getStatements().stream() .filter(J.MethodDeclaration.class::isInstance) .map(J.MethodDeclaration.class::cast) .filter(m -> m.getLeadingAnnotations().stream().anyMatch(DATAPROVIDER_MATCHER::matches)) .collect(Collectors.toSet()); + // for each add a Wrapper that translates to Jupiter method source for (J.MethodDeclaration provider : dataProviders) { String providerMethodName = provider.getSimpleName(); - String providerName = provider.getLeadingAnnotations().stream() - .filter(DATAPROVIDER_MATCHER::matches) - .map(J.Annotation::getArguments) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .filter(J.Assignment.class::isInstance) - .map(J.Assignment.class::cast) - .filter(a -> "name".equals(((J.Identifier) a.getVariable()).getSimpleName())) - .map(J.Assignment::getAssignment) - .filter(J.Literal.class::isInstance) - .map(J.Literal.class::cast) - .map(J.Literal::getValue) - .map(Objects::toString) - .findAny() - .orElse(providerMethodName); + String providerName = findProvidernameFromParameter(provider).orElse(providerMethodName); classDecl = classDecl.withBody(methodeSourceTemplate.apply( new Cursor(getCursor(), classDecl.getBody()), classDecl.getBody().getCoordinates().lastStatement(), providerName, providerMethodName)); } + // remove annotation and straighten imports doAfterVisit(new RemoveAnnotationVisitor(DATAPROVIDER_MATCHER)); maybeRemoveImport("org.testng.annotations.DataProvider"); maybeAddImport("org.junit.jupiter.params.provider.Arguments"); @@ -85,5 +86,22 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or return classDecl; } + + private static @NotNull Optional findProvidernameFromParameter(J.MethodDeclaration provider) { + return provider.getLeadingAnnotations().stream() + .filter(DATAPROVIDER_MATCHER::matches) + .map(J.Annotation::getArguments) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(J.Assignment.class::isInstance) + .map(J.Assignment.class::cast) + .filter(a -> "name".equals(((J.Identifier) a.getVariable()).getSimpleName())) + .map(J.Assignment::getAssignment) + .filter(J.Literal.class::isInstance) + .map(J.Literal.class::cast) + .map(J.Literal::getValue) + .map(Objects::toString) + .findAny(); + } } } diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java index 81162f7a..9ab2169f 100644 --- a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java @@ -1,3 +1,13 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + package io.github.mboegers.openrewrite.testngtojupiter.parameterized; import org.intellij.lang.annotations.Language; From 58c0050d740641c3373b4308e59c14c510d384c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Thu, 18 Apr 2024 14:47:06 +0200 Subject: [PATCH 34/51] Add Search Visitors for annotation usage --- .../helper/FindAnnotatedMethods.java | 44 +++++++++++++++++++ .../helper/UsesAnnotation.java | 28 ++++++++++++ .../testngtojupiter/helper/package-info.java | 22 ++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java create mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java create mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/package-info.java diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java new file mode 100644 index 00000000..cf7eeea3 --- /dev/null +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java @@ -0,0 +1,44 @@ +package io.github.mboegers.openrewrite.testngtojupiter.helper; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.marker.SearchResult; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Finds a Method that is annotated with an Annotation matching the given Annotation Matcher + */ +public class FindAnnotatedMethods extends JavaIsoVisitor { + + private final AnnotationMatcher annotationMatcher; + + public FindAnnotatedMethods(AnnotationMatcher annotationMatcher) { + this.annotationMatcher = annotationMatcher; + } + + public static Set find(J subtree, AnnotationMatcher annotationMatcher) { + return TreeVisitor.collect(new FindAnnotatedMethods(annotationMatcher), subtree, new HashSet<>()) + .stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .collect(Collectors.toSet()); + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); + + boolean isAnnotatedWithTargetAnnotation = m.getLeadingAnnotations().stream().anyMatch(annotationMatcher::matches); + if (isAnnotatedWithTargetAnnotation) { + m = SearchResult.found(m); + } + + return m; + } +} diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java new file mode 100644 index 00000000..7057dd8f --- /dev/null +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java @@ -0,0 +1,28 @@ +package io.github.mboegers.openrewrite.testngtojupiter.helper; + +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.marker.SearchResult; + +/** + * Checks whether a given annotation is used + */ +public class UsesAnnotation

extends JavaIsoVisitor

{ + private final AnnotationMatcher annotationMatcher; + + public UsesAnnotation(AnnotationMatcher annotationMatcher) { + this.annotationMatcher = annotationMatcher; + } + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, P ctx) { + annotation = super.visitAnnotation(annotation, ctx); + + if (annotationMatcher.matches(annotation)) { + return SearchResult.found(annotation); + } else { + return annotation; + } + } +} diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/package-info.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/package-info.java new file mode 100644 index 00000000..b568e5cd --- /dev/null +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * 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. + */ +@NonNullApi +@NonNullFields +package io.github.mboegers.openrewrite.testngtojupiter.helper; + +// We annotate the package to indicate that fields and methods in this package are non-null by default. +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; From acb321d4776249809c36196dcbb16a46a1cd0555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Thu, 18 Apr 2024 14:47:32 +0200 Subject: [PATCH 35/51] Use preconditions and annotation search --- .../parameterized/MigrateDataProvider.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java index 0ecae4d6..9f10b09d 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java @@ -10,11 +10,10 @@ package io.github.mboegers.openrewrite.testngtojupiter.parameterized; +import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotatedMethods; +import io.github.mboegers.openrewrite.testngtojupiter.helper.UsesAnnotation; import org.jetbrains.annotations.NotNull; -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; import org.openrewrite.java.*; import org.openrewrite.java.tree.J; @@ -22,9 +21,11 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; public class MigrateDataProvider extends Recipe { + + private static final AnnotationMatcher DATAPROVIDER_MATCHER = new AnnotationMatcher("@org.testng.annotations.DataProvider"); + @Override public String getDisplayName() { return "Migrate @DataProvider utilities"; @@ -37,11 +38,10 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return new MigrateDataProviderVisitor(); + return Preconditions.check(new UsesAnnotation<>(DATAPROVIDER_MATCHER), new MigrateDataProviderVisitor()); } private class MigrateDataProviderVisitor extends JavaIsoVisitor { - private static final AnnotationMatcher DATAPROVIDER_MATCHER = new AnnotationMatcher("@org.testng.annotations.DataProvider"); private static final JavaTemplate methodeSourceTemplate = JavaTemplate.builder(""" public static Stream #{}Source() { @@ -60,12 +60,7 @@ private class MigrateDataProviderVisitor extends JavaIsoVisitor dataProviders = classDecl.getBody().getStatements().stream() - .filter(J.MethodDeclaration.class::isInstance) - .map(J.MethodDeclaration.class::cast) - .filter(m -> m.getLeadingAnnotations().stream().anyMatch(DATAPROVIDER_MATCHER::matches)) - .collect(Collectors.toSet()); + Set dataProviders = FindAnnotatedMethods.find(classDecl, DATAPROVIDER_MATCHER); // for each add a Wrapper that translates to Jupiter method source for (J.MethodDeclaration provider : dataProviders) { From f5e4fb2bea196931a0f788fb24dae33edad692ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Thu, 18 Apr 2024 15:45:41 +0200 Subject: [PATCH 36/51] Introduce search visitor for Annotation and helper to extract annotation parameter value --- .../helper/AnnotationParameterValue.java | 33 +++++++++++++++++ .../helper/FindAnnotation.java | 36 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java create mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java new file mode 100644 index 00000000..25bf8921 --- /dev/null +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java @@ -0,0 +1,33 @@ +package io.github.mboegers.openrewrite.testngtojupiter.helper; + +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; + +import java.util.List; +import java.util.Optional; + +public final class AnnotationParameterValue { + + public static Optional extract(Optional annotation, String parameterName, Class valueClass) { + return annotation.flatMap(j -> extract(j, parameterName, valueClass)); + } + + public static Optional extract(J.Annotation annotation, String parameterName, Class valueClass) { + List arguments = annotation.getArguments(); + + if (arguments == null) { + return Optional.empty(); + } + + return arguments.stream() + .filter(J.Assignment.class::isInstance) + .map(J.Assignment.class::cast) + .filter(a -> parameterName.equals(((J.Identifier) a.getVariable()).getSimpleName())) + .map(J.Assignment::getAssignment) + .filter(J.Literal.class::isInstance) + .map(J.Literal.class::cast) + .findAny() + .map(J.Literal::getValue) + .map(valueClass::cast); + } +} diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java new file mode 100644 index 00000000..5026263e --- /dev/null +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java @@ -0,0 +1,36 @@ +package io.github.mboegers.openrewrite.testngtojupiter.helper; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.marker.SearchResult; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; + +public class FindAnnotation extends JavaIsoVisitor { + + private final AnnotationMatcher annotationMatcher; + + public FindAnnotation(AnnotationMatcher annotationMatcher) { + this.annotationMatcher = annotationMatcher; + } + + public static Set find(J tree, AnnotationMatcher annotationMatcher) { + return TreeVisitor.collect(new FindAnnotation(annotationMatcher), tree, new HashSet<>(), J.Annotation.class, Function.identity()); + } + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext executionContext) { + annotation = super.visitAnnotation(annotation, executionContext); + + if (annotationMatcher.matches(annotation)) { + annotation = SearchResult.found(annotation); + } + + return annotation; + } +} From 58a0641602378d91d0182b637b54ddbf229d7f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Thu, 18 Apr 2024 16:13:50 +0200 Subject: [PATCH 37/51] Refactor to use helpers --- .../helper/AnnotationParameterValue.java | 11 +++++++++-- .../testngtojupiter/helper/FindAnnotation.java | 5 +++++ .../parameterized/MigrateDataProvider.java | 8 ++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java index 25bf8921..92d04e78 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java @@ -8,8 +8,15 @@ public final class AnnotationParameterValue { - public static Optional extract(Optional annotation, String parameterName, Class valueClass) { - return annotation.flatMap(j -> extract(j, parameterName, valueClass)); + public static boolean hasAny(J.Annotation annotation) { + List arguments = annotation.getArguments(); + + if (arguments == null || arguments.isEmpty()) { + return false; + } + + boolean containsNoEmpty = arguments.stream().noneMatch(J.Empty.class::isInstance); + return containsNoEmpty; } public static Optional extract(J.Annotation annotation, String parameterName, Class valueClass) { diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java index 5026263e..149d900a 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java @@ -8,6 +8,7 @@ import org.openrewrite.marker.SearchResult; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -19,6 +20,10 @@ public FindAnnotation(AnnotationMatcher annotationMatcher) { this.annotationMatcher = annotationMatcher; } + public static Optional findFirst(J tree, AnnotationMatcher annotationMatcher) { + return TreeVisitor.collect(new FindAnnotation(annotationMatcher), tree, new HashSet<>(), J.Annotation.class, Function.identity()).stream().findFirst(); + } + public static Set find(J tree, AnnotationMatcher annotationMatcher) { return TreeVisitor.collect(new FindAnnotation(annotationMatcher), tree, new HashSet<>(), J.Annotation.class, Function.identity()); } diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java index 9f10b09d..1f8aec2f 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java @@ -10,7 +10,9 @@ package io.github.mboegers.openrewrite.testngtojupiter.parameterized; +import io.github.mboegers.openrewrite.testngtojupiter.helper.AnnotationParameterValue; import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotatedMethods; +import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation; import io.github.mboegers.openrewrite.testngtojupiter.helper.UsesAnnotation; import org.jetbrains.annotations.NotNull; import org.openrewrite.*; @@ -65,7 +67,9 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or // for each add a Wrapper that translates to Jupiter method source for (J.MethodDeclaration provider : dataProviders) { String providerMethodName = provider.getSimpleName(); - String providerName = findProvidernameFromParameter(provider).orElse(providerMethodName); + String providerName = FindAnnotation.find(provider, DATAPROVIDER_MATCHER).stream().findAny() + .flatMap(j -> AnnotationParameterValue.extract(j, "name", String.class)) + .orElse(providerMethodName); classDecl = classDecl.withBody(methodeSourceTemplate.apply( new Cursor(getCursor(), classDecl.getBody()), classDecl.getBody().getCoordinates().lastStatement(), @@ -82,7 +86,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or return classDecl; } - private static @NotNull Optional findProvidernameFromParameter(J.MethodDeclaration provider) { + private static @NotNull Optional findProviderNameFromParameter(J.MethodDeclaration provider) { return provider.getLeadingAnnotations().stream() .filter(DATAPROVIDER_MATCHER::matches) .map(J.Annotation::getArguments) From ae36444c8d043091f5849b2ee2c1f6dd871ea3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Thu, 18 Apr 2024 16:24:23 +0200 Subject: [PATCH 38/51] remove unused method --- .../parameterized/MigrateDataProvider.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java index 1f8aec2f..02f9eb28 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java @@ -14,14 +14,10 @@ import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotatedMethods; import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation; import io.github.mboegers.openrewrite.testngtojupiter.helper.UsesAnnotation; -import org.jetbrains.annotations.NotNull; import org.openrewrite.*; import org.openrewrite.java.*; import org.openrewrite.java.tree.J; -import java.util.Collection; -import java.util.Objects; -import java.util.Optional; import java.util.Set; public class MigrateDataProvider extends Recipe { @@ -85,22 +81,5 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or return classDecl; } - - private static @NotNull Optional findProviderNameFromParameter(J.MethodDeclaration provider) { - return provider.getLeadingAnnotations().stream() - .filter(DATAPROVIDER_MATCHER::matches) - .map(J.Annotation::getArguments) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .filter(J.Assignment.class::isInstance) - .map(J.Assignment.class::cast) - .filter(a -> "name".equals(((J.Identifier) a.getVariable()).getSimpleName())) - .map(J.Assignment::getAssignment) - .filter(J.Literal.class::isInstance) - .map(J.Literal.class::cast) - .map(J.Literal::getValue) - .map(Objects::toString) - .findAny(); - } } } From 423b1e26b1aeb0655d30c50a92cffbb199c6b10f Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 02:30:03 +0200 Subject: [PATCH 39/51] Move DataProvider recipe one package-level and rename helper Retaining git commit history by doing this in a separate step --- .../{parameterized => }/MigrateDataProvider.java | 6 +++--- ...notationParameterValue.java => AnnotationArguments.java} | 2 +- .../{parameterized => }/MigrateDataProviderTest.java | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) rename src/main/java/io/github/mboegers/openrewrite/testngtojupiter/{parameterized => }/MigrateDataProvider.java (95%) rename src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/{AnnotationParameterValue.java => AnnotationArguments.java} (96%) rename src/test/java/io/github/mboegers/openrewrite/testngtojupiter/{parameterized => }/MigrateDataProviderTest.java (96%) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java similarity index 95% rename from src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java rename to src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 02f9eb28..9767fb24 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -8,9 +8,9 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package io.github.mboegers.openrewrite.testngtojupiter.parameterized; +package io.github.mboegers.openrewrite.testngtojupiter; -import io.github.mboegers.openrewrite.testngtojupiter.helper.AnnotationParameterValue; +import io.github.mboegers.openrewrite.testngtojupiter.helper.AnnotationArguments; import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotatedMethods; import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation; import io.github.mboegers.openrewrite.testngtojupiter.helper.UsesAnnotation; @@ -64,7 +64,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or for (J.MethodDeclaration provider : dataProviders) { String providerMethodName = provider.getSimpleName(); String providerName = FindAnnotation.find(provider, DATAPROVIDER_MATCHER).stream().findAny() - .flatMap(j -> AnnotationParameterValue.extract(j, "name", String.class)) + .flatMap(j -> AnnotationArguments.extract(j, "name", String.class)) .orElse(providerMethodName); classDecl = classDecl.withBody(methodeSourceTemplate.apply( diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java similarity index 96% rename from src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java rename to src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java index 92d04e78..9f8a0c32 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationParameterValue.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Optional; -public final class AnnotationParameterValue { +public final class AnnotationArguments { public static boolean hasAny(J.Annotation annotation) { List arguments = annotation.getArguments(); diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java similarity index 96% rename from src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java rename to src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java index 9ab2169f..1222aa1e 100644 --- a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/parameterized/MigrateDataProviderTest.java +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java @@ -8,8 +8,9 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package io.github.mboegers.openrewrite.testngtojupiter.parameterized; +package io.github.mboegers.openrewrite.testngtojupiter; +import io.github.mboegers.openrewrite.testngtojupiter.MigrateDataProvider; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; From e70bbbbc0792f5857a67b39bc6fd0563129edeac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Mon, 22 Apr 2024 20:41:16 +0200 Subject: [PATCH 40/51] Refactor MigrateDataProvider recipe, add javadoc to utilities --- .../testngtojupiter/MigrateDataProvider.java | 95 ++++++++++++++++--- .../helper/AnnotationArguments.java | 44 ++++++++- .../helper/FindAnnotatedMethods.java | 5 +- .../helper/FindAnnotation.java | 6 ++ .../helper/UsesAnnotation.java | 4 +- .../MigrateDataProviderTest.java | 54 +++++++++-- 6 files changed, 183 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 9767fb24..2570c2ee 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -15,14 +15,22 @@ import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation; import io.github.mboegers.openrewrite.testngtojupiter.helper.UsesAnnotation; import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaCoordinates; +import org.openrewrite.java.tree.JavaType; +import java.util.Comparator; +import java.util.Optional; import java.util.Set; +import static java.util.Objects.requireNonNull; + public class MigrateDataProvider extends Recipe { - private static final AnnotationMatcher DATAPROVIDER_MATCHER = new AnnotationMatcher("@org.testng.annotations.DataProvider"); + private static final String DATA_PROVIDER = "org.testng.annotations.DataProvider"; + private static final AnnotationMatcher DATA_PROVIDER_MATCHER = new AnnotationMatcher("@" + DATA_PROVIDER); @Override public String getDisplayName() { @@ -31,19 +39,36 @@ public String getDisplayName() { @Override public String getDescription() { - return "Wrap `@DataProvider` methods into a Jupiter parameterized test MethodSource."; + return "Wrap `@DataProvider` methods into a Jupiter parameterized test with MethodSource."; } @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesAnnotation<>(DATAPROVIDER_MATCHER), new MigrateDataProviderVisitor()); + return Preconditions.check(new UsesAnnotation<>(DATA_PROVIDER_MATCHER), new TreeVisitor<>() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext, Cursor parent) { + tree = super.visit(tree, executionContext, parent); + // wrap methods + tree = new WrapDataProviderMethod().visit(tree, executionContext, parent); + // remove @DataProvider + tree = new RemoveAnnotationVisitor(DATA_PROVIDER_MATCHER).visit(tree, executionContext, parent); + // use @MethodeSource and @ParameterizedTest + tree = new UseParameterizedTest().visit(tree, executionContext, parent); + // remove dataProviderName and dataProviderClass arguments + tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProvider") + .getVisitor().visit(tree, executionContext); + tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProviderName") + .getVisitor().visit(tree, executionContext); + return tree; + } + }); } - private class MigrateDataProviderVisitor extends JavaIsoVisitor { + private class WrapDataProviderMethod extends JavaIsoVisitor { private static final JavaTemplate methodeSourceTemplate = JavaTemplate.builder(""" - public static Stream #{}Source() { - return Arrays.stream(#{}()).map(Arguments::of); + public static Stream #{}() { + return Arrays.stream(#{}()).map(Arguments::of); } """) .imports("org.junit.jupiter.params.provider.Arguments", "java.util.Arrays", "java.util.stream.Stream") @@ -52,19 +77,18 @@ private class MigrateDataProviderVisitor extends JavaIsoVisitor dataProviders = FindAnnotatedMethods.find(classDecl, DATAPROVIDER_MATCHER); + Set dataProviders = FindAnnotatedMethods.find(classDecl, DATA_PROVIDER_MATCHER); // for each add a Wrapper that translates to Jupiter method source for (J.MethodDeclaration provider : dataProviders) { String providerMethodName = provider.getSimpleName(); - String providerName = FindAnnotation.find(provider, DATAPROVIDER_MATCHER).stream().findAny() - .flatMap(j -> AnnotationArguments.extract(j, "name", String.class)) + String providerName = FindAnnotation.find(provider, DATA_PROVIDER_MATCHER).stream().findAny() + .flatMap(j -> AnnotationArguments.extractLiteral(j, "name", String.class)) .orElse(providerMethodName); classDecl = classDecl.withBody(methodeSourceTemplate.apply( @@ -72,9 +96,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or providerName, providerMethodName)); } - // remove annotation and straighten imports - doAfterVisit(new RemoveAnnotationVisitor(DATAPROVIDER_MATCHER)); - maybeRemoveImport("org.testng.annotations.DataProvider"); + // add new imports maybeAddImport("org.junit.jupiter.params.provider.Arguments"); maybeAddImport("java.util.Arrays"); maybeAddImport("java.util.stream.Stream"); @@ -82,4 +104,51 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or return classDecl; } } + + private class UseParameterizedTest extends JavaIsoVisitor { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { + method = super.visitMethodDeclaration(method, executionContext); + + Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); + if (testNgAnnotation.isEmpty()) { + return method; + } + + // determine Provider name and class + String dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class) + .orElse(method.getSimpleName()); + String dataProviderClass = AnnotationArguments.extractAssignments(testNgAnnotation.get(), "dataProviderClass").stream() + .findAny() + .map(J.FieldAccess.class::cast) + .map(J.FieldAccess::getTarget) + .map(e -> e.unwrap().getType()) + .filter(JavaType.Class.class::isInstance) + .map(JavaType.Class.class::cast) + .map(JavaType.Class::getFullyQualifiedName) + .orElse(requireNonNull(getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class).getType()).getFullyQualifiedName()); + + // add parameterized test annotation + JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)); + method = JavaTemplate + .builder("@ParameterizedTest") + .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) + .imports("org.junit.jupiter.params.ParameterizedTest") + .build() + .apply(getCursor(), addAnnotationCoordinate); + maybeAddImport("org.junit.jupiter.params.ParameterizedTest", false); + + // add MethodSource annotation + addAnnotationCoordinate = method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)); + method = JavaTemplate + .builder("@MethodSource(\"#{}##{}Source\")") + .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) + .imports("org.junit.jupiter.params.provider.MethodSource") + .build() + .apply(getCursor(), addAnnotationCoordinate, dataProviderClass, dataProviderMethodName); + maybeAddImport("org.junit.jupiter.params.provider.MethodSource", false); + + return method; + } + } } diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java index 9f8a0c32..e39a39c9 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java @@ -6,8 +6,19 @@ import java.util.List; import java.util.Optional; +/** + * Answer questions regarding annotation arguments and there values + * + * @see J.Annotation + */ public final class AnnotationArguments { + /** + * Determins if the annotation has any arguments + * + * @param annotation + * @return + */ public static boolean hasAny(J.Annotation annotation) { List arguments = annotation.getArguments(); @@ -19,18 +30,45 @@ public static boolean hasAny(J.Annotation annotation) { return containsNoEmpty; } - public static Optional extract(J.Annotation annotation, String parameterName, Class valueClass) { + /** + * Extracts all assignments with the given argument name from the annotation + * + * @param annotation to extract the assignments from + * @param argumentName to extract + * @return + */ + public static List extractAssignments(J.Annotation annotation, String argumentName) { List arguments = annotation.getArguments(); if (arguments == null) { - return Optional.empty(); + return List.of(); } return arguments.stream() .filter(J.Assignment.class::isInstance) .map(J.Assignment.class::cast) - .filter(a -> parameterName.equals(((J.Identifier) a.getVariable()).getSimpleName())) + .filter(a -> argumentName.equals(((J.Identifier) a.getVariable()).getSimpleName())) .map(J.Assignment::getAssignment) + .toList(); + } + + /** + * Extract an annotation argument as literal + * + * @param annotation to extract literal from + * @param argumentName to extract + * @param valueClass expected type of the value + * @param Type of the value + * @return the value or Optional#empty + */ + public static Optional extractLiteral(J.Annotation annotation, String argumentName, Class valueClass) { + List arguments = annotation.getArguments(); + + if (arguments == null) { + return Optional.empty(); + } + + return extractAssignments(annotation, argumentName).stream() .filter(J.Literal.class::isInstance) .map(J.Literal.class::cast) .findAny() diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java index cf7eeea3..296dbdce 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java @@ -12,7 +12,10 @@ import java.util.stream.Collectors; /** - * Finds a Method that is annotated with an Annotation matching the given Annotation Matcher + * Finds a {@linkplain J.MethodDeclaration} that is annotated with an {@linkplain J.Annotation} matching the given Annotation Matcher + * + * @see AnnotationMatcher + * @see org.openrewrite.java.tree.J.MethodDeclaration */ public class FindAnnotatedMethods extends JavaIsoVisitor { diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java index 149d900a..b9d1aaad 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java @@ -12,6 +12,12 @@ import java.util.Set; import java.util.function.Function; +/** + * Finds {@linkplain J.Annotation}s in the source set + * + * @see AnnotationMatcher + * @see J.Annotation + */ public class FindAnnotation extends JavaIsoVisitor { private final AnnotationMatcher annotationMatcher; diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java index 7057dd8f..39a026e3 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java @@ -6,7 +6,7 @@ import org.openrewrite.marker.SearchResult; /** - * Checks whether a given annotation is used + * Checks whether a given {@linkplain J.Annotation} is used */ public class UsesAnnotation

extends JavaIsoVisitor

{ private final AnnotationMatcher annotationMatcher; @@ -18,7 +18,7 @@ public UsesAnnotation(AnnotationMatcher annotationMatcher) { @Override public J.Annotation visitAnnotation(J.Annotation annotation, P ctx) { annotation = super.visitAnnotation(annotation, ctx); - + if (annotationMatcher.matches(annotation)) { return SearchResult.found(annotation); } else { diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java index 1222aa1e..7ef801b9 100644 --- a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java @@ -21,6 +21,7 @@ import static org.openrewrite.java.Assertions.java; class MigrateDataProviderTest implements RewriteTest { + @Override public void defaults(RecipeSpec spec) { spec.parser(JavaParser.fromJavaVersion() @@ -29,11 +30,9 @@ public void defaults(RecipeSpec spec) { .recipe(new MigrateDataProvider()); } + @Nested - class Wrap { - /* - * annotation parameter other than name are ignored - */ + class WrapDataProvider { @Test void withName() { @Language("java") String is = """ @@ -53,7 +52,7 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - public static Stream anotherBoxPrimitiveDataProviderSource() { + public static Stream anotherBoxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } } @@ -80,12 +79,55 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - public static Stream boxPrimitiveDataProviderSource() { + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } } """; rewriteRun(java(is, should)); } + + @Test + void doNothingWithoutAnnotation() { + rewriteRun(java(""" + import org.testng.annotations.DataProvider; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + } + """)); + } + + } + + @Nested + class MigrateParameterToAnnotation { + + @Test + void useWrappedMethod() { + rewriteRun( + java( + """ + import org.testng.annotations.Test; + import de.boeg.tst.BoxPrimitiveDataProvider; + + public class HotSpotConstantReflectionProviderTest { + @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """, """ + import org.junit.jupiter.params.ParameterizedTest; + import org.junit.jupiter.params.provider.MethodSource; + + class BoxPrimitiveDataProvider {} + + public class HotSpotConstantReflectionProviderTest { + @ParameterizedTest + @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProviderSource") + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """ + )); + } } } From d2e4e0bdbe58cb82f73662e66e9e742c1a6a552f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Wed, 24 Apr 2024 16:22:54 +0200 Subject: [PATCH 41/51] Apply best practices --- .../testngtojupiter/MigrateDataProvider.java | 91 ++++-- .../MigrateDataProviderTest.java | 276 +++++++++++++++++- 2 files changed, 326 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 2570c2ee..ed7f2338 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -13,7 +13,6 @@ import io.github.mboegers.openrewrite.testngtojupiter.helper.AnnotationArguments; import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotatedMethods; import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation; -import io.github.mboegers.openrewrite.testngtojupiter.helper.UsesAnnotation; import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; @@ -21,7 +20,6 @@ import org.openrewrite.java.tree.JavaCoordinates; import org.openrewrite.java.tree.JavaType; -import java.util.Comparator; import java.util.Optional; import java.util.Set; @@ -44,7 +42,7 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesAnnotation<>(DATA_PROVIDER_MATCHER), new TreeVisitor<>() { + return new TreeVisitor<>() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext, Cursor parent) { tree = super.visit(tree, executionContext, parent); @@ -54,14 +52,15 @@ public TreeVisitor getVisitor() { tree = new RemoveAnnotationVisitor(DATA_PROVIDER_MATCHER).visit(tree, executionContext, parent); // use @MethodeSource and @ParameterizedTest tree = new UseParameterizedTest().visit(tree, executionContext, parent); + tree = new UseMethodSource().visit(tree, executionContext, parent); // remove dataProviderName and dataProviderClass arguments tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProvider") .getVisitor().visit(tree, executionContext); - tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProviderName") + tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProviderClass") .getVisitor().visit(tree, executionContext); return tree; } - }); + }; } private class WrapDataProviderMethod extends JavaIsoVisitor { @@ -73,9 +72,7 @@ private class WrapDataProviderMethod extends JavaIsoVisitor { """) .imports("org.junit.jupiter.params.provider.Arguments", "java.util.Arrays", "java.util.stream.Stream") .contextSensitive() - .javaParser(JavaParser.fromJavaVersion() - .logCompilationWarningsAndErrors(true) - .classpath("junit-jupiter-api", "junit-jupiter-params", "testng")) + .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) .build(); @Override @@ -107,17 +104,66 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or private class UseParameterizedTest extends JavaIsoVisitor { @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { - method = super.visitMethodDeclaration(method, executionContext); + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + method = super.visitMethodDeclaration(method, ctx); + // if @ParameterizedTest is used, skip + Optional paraeterizedTestAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.ParameterizedTest")); + if (paraeterizedTestAnnotation.isPresent()) { + return method; + } + + // if no TestNG @Test present, skip + Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); + if (testNgAnnotation.isEmpty()) { + return method; + } + + // determine if a parameterized test is applicable + Optional dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); + if (dataProviderMethodName.isEmpty()) { + return method; + } + + JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation((a, b) -> 1); + + method = JavaTemplate + .builder("@ParameterizedTest") + .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) + .imports("org.junit.jupiter.params.ParameterizedTest") + .build() + .apply(getCursor(), addAnnotationCoordinate); + + maybeAddImport("org.junit.jupiter.params.ParameterizedTest"); + + return method; + } + } + + class UseMethodSource extends JavaIsoVisitor { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + method = super.visitMethodDeclaration(method, ctx); + + // if @MethodSource is used, skip + Optional methodSourceAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.provider.MethodSource")); + if (methodSourceAnnotation.isPresent()) { + return method; + } + + // if no testng annotation is present, skip Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); if (testNgAnnotation.isEmpty()) { return method; } - // determine Provider name and class - String dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class) - .orElse(method.getSimpleName()); + // determine Provider name, if not present skip! + Optional dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); + if (dataProviderMethodName.isEmpty()) { + return method; + } + + // determin provider class or use current class as default String dataProviderClass = AnnotationArguments.extractAssignments(testNgAnnotation.get(), "dataProviderClass").stream() .findAny() .map(J.FieldAccess.class::cast) @@ -128,25 +174,16 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex .map(JavaType.Class::getFullyQualifiedName) .orElse(requireNonNull(getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class).getType()).getFullyQualifiedName()); - // add parameterized test annotation - JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)); - method = JavaTemplate - .builder("@ParameterizedTest") - .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) - .imports("org.junit.jupiter.params.ParameterizedTest") - .build() - .apply(getCursor(), addAnnotationCoordinate); - maybeAddImport("org.junit.jupiter.params.ParameterizedTest", false); - // add MethodSource annotation - addAnnotationCoordinate = method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)); + JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation((a, b) -> 1); method = JavaTemplate - .builder("@MethodSource(\"#{}##{}Source\")") + .builder("@MethodSource(\"#{}##{}\")") .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) .imports("org.junit.jupiter.params.provider.MethodSource") .build() - .apply(getCursor(), addAnnotationCoordinate, dataProviderClass, dataProviderMethodName); - maybeAddImport("org.junit.jupiter.params.provider.MethodSource", false); + .apply(getCursor(), addAnnotationCoordinate, dataProviderClass, dataProviderMethodName.get()); + + maybeAddImport("org.junit.jupiter.params.provider.MethodSource"); return method; } diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java index 7ef801b9..d6f3d6e1 100644 --- a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java @@ -87,43 +87,291 @@ public static Stream boxPrimitiveDataProvider() { rewriteRun(java(is, should)); } + } + + @Nested + class NewTests { @Test - void doNothingWithoutAnnotation() { - rewriteRun(java(""" - import org.testng.annotations.DataProvider; + void fullMigrate() { + rewriteRun( + java( + """ + package de.boeg.tst; + + import org.testng.annotations.DataProvider; - public class BoxPrimitiveDataProvider { - public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - } - """)); + public class BoxPrimitiveDataProvider { + @DataProvider + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + } + """, + """ + package de.boeg.tst; + + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream boxPrimitiveDataProvider() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """ + ), + java( + """ + import org.testng.annotations.Test; + import de.boeg.tst.BoxPrimitiveDataProvider; + + public class HotSpotConstantReflectionProviderTest { + @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """, """ + import org.junit.jupiter.params.ParameterizedTest; + import org.junit.jupiter.params.provider.MethodSource; + import org.testng.annotations.Test; + import de.boeg.tst.BoxPrimitiveDataProvider; + + public class HotSpotConstantReflectionProviderTest { + @Test + @ParameterizedTest + @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """ + )); } - } + @Test + void WrapOnlyDataprovider() { + rewriteRun( + java( + """ + package de.boeg.tst; + + import org.testng.annotations.DataProvider; + + public class BoxPrimitiveDataProvider { + @DataProvider + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + } + """, + """ + package de.boeg.tst; + + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream boxPrimitiveDataProvider() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """ + ), + java( + """ + import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; + import org.junit.jupiter.params.provider.MethodSource; + import de.boeg.tst.BoxPrimitiveDataProvider; + + class BoxPrimitiveDataProvider {} + + public class HotSpotConstantReflectionProviderTest { + @Test + @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @ParameterizedTest + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """ + )); + } - @Nested - class MigrateParameterToAnnotation { + @Test + void addsParameterizedTest() { + rewriteRun( + java( + """ + package de.boeg.tst; + + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream boxPrimitiveDataProvider() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """ + ), + java( + """ + import org.testng.annotations.Test; + + import org.junit.jupiter.params.provider.MethodSource; + + import de.boeg.tst.BoxPrimitiveDataProvider; + + public class HotSpotConstantReflectionProviderTest { + @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) + @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """, """ + import org.testng.annotations.Test; + + import org.junit.jupiter.params.ParameterizedTest; + import org.junit.jupiter.params.provider.MethodSource; + + import de.boeg.tst.BoxPrimitiveDataProvider; + + public class HotSpotConstantReflectionProviderTest { + @Test + @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @ParameterizedTest + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """ + )); + } @Test - void useWrappedMethod() { + void addsMethodSource() { rewriteRun( + java( + """ + package de.boeg.tst; + + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream boxPrimitiveDataProvider() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """ + ), java( """ import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; import de.boeg.tst.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) + @ParameterizedTest public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """, """ + import org.junit.jupiter.params.provider.MethodSource; + import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; + import de.boeg.tst.BoxPrimitiveDataProvider; + + public class HotSpotConstantReflectionProviderTest { + @Test + @ParameterizedTest + @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """ + )); + } + + @Test + void removesTestNgAnnotationArguments() { + rewriteRun( + java( + """ + package de.boeg.tst; + + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream boxPrimitiveDataProvider() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """ + ), + java( + """ + import org.testng.annotations.Test; import org.junit.jupiter.params.ParameterizedTest; + import de.boeg.tst.BoxPrimitiveDataProvider; + + public class HotSpotConstantReflectionProviderTest { + @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) + @ParameterizedTest + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """, """ import org.junit.jupiter.params.provider.MethodSource; + import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; + import de.boeg.tst.BoxPrimitiveDataProvider; - class BoxPrimitiveDataProvider {} - public class HotSpotConstantReflectionProviderTest { + @Test @ParameterizedTest - @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProviderSource") + @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} + } + """ + )); + } + + @Test + void doNothingIfNameMissing() { + rewriteRun( + java( + """ + package de.boeg.tst; + + import org.junit.jupiter.params.provider.Arguments; + + import java.util.Arrays; + import java.util.stream.Stream; + + public class BoxPrimitiveDataProvider { + public static Object[][] boxPrimitiveDataProvider() { /*...*/ } + + public static Stream boxPrimitiveDataProvider() { + return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); + } + } + """ + ), + java( + """ + import org.testng.annotations.Test; + + public class HotSpotConstantReflectionProviderTest { + @Test(enabled = false) public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """ From 13f1a8d9f8430f074aff76484ab11663f5d4cb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Fri, 26 Apr 2024 21:06:23 +0200 Subject: [PATCH 42/51] add packages to test files to fix import order --- .../testngtojupiter/MigrateDataProvider.java | 1 + .../MigrateDataProviderTest.java | 96 +++++++++++-------- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index ed7f2338..6674aca0 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -184,6 +184,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex .apply(getCursor(), addAnnotationCoordinate, dataProviderClass, dataProviderMethodName.get()); maybeAddImport("org.junit.jupiter.params.provider.MethodSource"); + maybeRemoveImport(dataProviderClass); return method; } diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java index d6f3d6e1..078f2fa1 100644 --- a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java @@ -36,6 +36,8 @@ class WrapDataProvider { @Test void withName() { @Language("java") String is = """ + package de.boeg.tst.provider; + import org.testng.annotations.DataProvider; public class BoxPrimitiveDataProvider { @@ -44,6 +46,8 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } } """; @Language("java") String should = """ + package de.boeg.tst.provider; + import org.junit.jupiter.params.provider.Arguments; import java.util.Arrays; @@ -63,6 +67,7 @@ public static Stream anotherBoxPrimitiveDataProvider() { @Test void withDefaultName() { @Language("java") String is = """ + package de.boeg.tst.provider; import org.testng.annotations.DataProvider; public class BoxPrimitiveDataProvider { @@ -71,6 +76,7 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } } """; @Language("java") String should = """ + package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; import java.util.Arrays; @@ -96,8 +102,7 @@ void fullMigrate() { rewriteRun( java( """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.testng.annotations.DataProvider; public class BoxPrimitiveDataProvider { @@ -106,10 +111,9 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } } """, """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; @@ -124,23 +128,27 @@ public static Stream boxPrimitiveDataProvider() { ), java( """ + package de.boeg.tst.real; import org.testng.annotations.Test; - import de.boeg.tst.BoxPrimitiveDataProvider; + + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """, """ + package de.boeg.tst.real; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.testng.annotations.Test; - import de.boeg.tst.BoxPrimitiveDataProvider; + + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test @ParameterizedTest - @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """ @@ -152,8 +160,7 @@ void WrapOnlyDataprovider() { rewriteRun( java( """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.testng.annotations.DataProvider; public class BoxPrimitiveDataProvider { @@ -162,10 +169,9 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } } """, """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; @@ -180,16 +186,19 @@ public static Stream boxPrimitiveDataProvider() { ), java( """ + package de.boeg.tst.real; import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; - import de.boeg.tst.BoxPrimitiveDataProvider; + + import de.boeg.tst.provider.BoxPrimitiveDataProvider; class BoxPrimitiveDataProvider {} public class HotSpotConstantReflectionProviderTest { @Test - @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") @ParameterizedTest public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } @@ -202,10 +211,9 @@ void addsParameterizedTest() { rewriteRun( java( """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; @@ -220,28 +228,28 @@ public static Stream boxPrimitiveDataProvider() { ), java( """ + package de.boeg.tst.real; import org.testng.annotations.Test; - import org.junit.jupiter.params.provider.MethodSource; - import de.boeg.tst.BoxPrimitiveDataProvider; + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) - @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """, """ + package de.boeg.tst.real; import org.testng.annotations.Test; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; - import de.boeg.tst.BoxPrimitiveDataProvider; + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test - @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") @ParameterizedTest public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } @@ -254,10 +262,9 @@ void addsMethodSource() { rewriteRun( java( """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; @@ -272,9 +279,12 @@ public static Stream boxPrimitiveDataProvider() { ), java( """ + package de.boeg.tst.real; import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; - import de.boeg.tst.BoxPrimitiveDataProvider; + + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) @@ -282,15 +292,18 @@ public class HotSpotConstantReflectionProviderTest { public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """, """ + package de.boeg.tst.real; import org.junit.jupiter.params.provider.MethodSource; import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; - import de.boeg.tst.BoxPrimitiveDataProvider; + + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test @ParameterizedTest - @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """ @@ -302,10 +315,9 @@ void removesTestNgAnnotationArguments() { rewriteRun( java( """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; @@ -320,9 +332,12 @@ public static Stream boxPrimitiveDataProvider() { ), java( """ + package de.boeg.tst.real; import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; - import de.boeg.tst.BoxPrimitiveDataProvider; + + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) @@ -330,15 +345,18 @@ public class HotSpotConstantReflectionProviderTest { public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """, """ + package de.boeg.tst.real; import org.junit.jupiter.params.provider.MethodSource; import org.testng.annotations.Test; + import org.junit.jupiter.params.ParameterizedTest; - import de.boeg.tst.BoxPrimitiveDataProvider; + + import de.boeg.tst.provider.BoxPrimitiveDataProvider; public class HotSpotConstantReflectionProviderTest { @Test @ParameterizedTest - @MethodSource("de.boeg.tst.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") + @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } """ @@ -350,10 +368,9 @@ void doNothingIfNameMissing() { rewriteRun( java( """ - package de.boeg.tst; - + package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; @@ -368,6 +385,7 @@ public static Stream boxPrimitiveDataProvider() { ), java( """ + package de.boeg.tst.real; import org.testng.annotations.Test; public class HotSpotConstantReflectionProviderTest { From bf07f9ca67d5e48f87b53dae117d13bd01bfc793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Fri, 26 Apr 2024 21:12:09 +0200 Subject: [PATCH 43/51] apply best practices --- .../testngtojupiter/MigrateDataProvider.java | 16 +++++++------- .../testngtojupiter/package-info.java | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/package-info.java diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 6674aca0..9514cbac 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -44,20 +44,20 @@ public String getDescription() { public TreeVisitor getVisitor() { return new TreeVisitor<>() { @Override - public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext, Cursor parent) { - tree = super.visit(tree, executionContext, parent); + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx, Cursor parent) { + tree = super.visit(tree, ctx, parent); // wrap methods - tree = new WrapDataProviderMethod().visit(tree, executionContext, parent); + tree = new WrapDataProviderMethod().visit(tree, ctx, parent); // remove @DataProvider - tree = new RemoveAnnotationVisitor(DATA_PROVIDER_MATCHER).visit(tree, executionContext, parent); + tree = new RemoveAnnotationVisitor(DATA_PROVIDER_MATCHER).visit(tree, ctx, parent); // use @MethodeSource and @ParameterizedTest - tree = new UseParameterizedTest().visit(tree, executionContext, parent); - tree = new UseMethodSource().visit(tree, executionContext, parent); + tree = new UseParameterizedTest().visit(tree, ctx, parent); + tree = new UseMethodSource().visit(tree, ctx, parent); // remove dataProviderName and dataProviderClass arguments tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProvider") - .getVisitor().visit(tree, executionContext); + .getVisitor().visit(tree, ctx); tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProviderClass") - .getVisitor().visit(tree, executionContext); + .getVisitor().visit(tree, ctx); return tree; } }; diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/package-info.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/package-info.java new file mode 100644 index 00000000..28274ce3 --- /dev/null +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * 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. + */ +@NonNullApi +@NonNullFields +package io.github.mboegers.openrewrite.testngtojupiter; + +// We annotate the package to indicate that fields and methods in this package are non-null by default. +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; From 7994e9e8e25da0bb90d941941f1b5ea2f7037937 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 02:48:26 +0200 Subject: [PATCH 44/51] Introduce JDK 8 compatibility --- .../testngtojupiter/MigrateDataProvider.java | 29 ++++++++++--------- .../helper/AnnotationArguments.java | 6 ++-- .../MigrateDataProviderTest.java | 1 - 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 9514cbac..86119d7a 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -42,7 +42,8 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return new TreeVisitor<>() { + return new TreeVisitor() { + @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx, Cursor parent) { tree = super.visit(tree, ctx, parent); @@ -65,15 +66,15 @@ public TreeVisitor getVisitor() { private class WrapDataProviderMethod extends JavaIsoVisitor { - private static final JavaTemplate methodeSourceTemplate = JavaTemplate.builder(""" - public static Stream #{}() { - return Arrays.stream(#{}()).map(Arguments::of); - } - """) - .imports("org.junit.jupiter.params.provider.Arguments", "java.util.Arrays", "java.util.stream.Stream") - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) - .build(); + private final JavaTemplate methodeSourceTemplate = JavaTemplate.builder( + "public static Stream #{}() {\n" + + " return Arrays.stream(#{}()).map(Arguments::of);\n" + + "}" + ) + .imports("org.junit.jupiter.params.provider.Arguments", "java.util.Arrays", "java.util.stream.Stream") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) + .build(); @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, org.openrewrite.ExecutionContext ctx) { @@ -115,13 +116,13 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex // if no TestNG @Test present, skip Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); - if (testNgAnnotation.isEmpty()) { + if (!testNgAnnotation.isPresent()) { return method; } // determine if a parameterized test is applicable Optional dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); - if (dataProviderMethodName.isEmpty()) { + if (!dataProviderMethodName.isPresent()) { return method; } @@ -153,13 +154,13 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex // if no testng annotation is present, skip Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); - if (testNgAnnotation.isEmpty()) { + if (!testNgAnnotation.isPresent()) { return method; } // determine Provider name, if not present skip! Optional dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); - if (dataProviderMethodName.isEmpty()) { + if (!dataProviderMethodName.isPresent()) { return method; } diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java index e39a39c9..5776d874 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java @@ -3,8 +3,10 @@ import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; /** * Answer questions regarding annotation arguments and there values @@ -41,7 +43,7 @@ public static List extractAssignments(J.Annotation annotation, Strin List arguments = annotation.getArguments(); if (arguments == null) { - return List.of(); + return Collections.emptyList(); } return arguments.stream() @@ -49,7 +51,7 @@ public static List extractAssignments(J.Annotation annotation, Strin .map(J.Assignment.class::cast) .filter(a -> argumentName.equals(((J.Identifier) a.getVariable()).getSimpleName())) .map(J.Assignment::getAssignment) - .toList(); + .collect(Collectors.toList()); } /** diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java index 078f2fa1..ccf04758 100644 --- a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java @@ -10,7 +10,6 @@ package io.github.mboegers.openrewrite.testngtojupiter; -import io.github.mboegers.openrewrite.testngtojupiter.MigrateDataProvider; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; From 942895d3cafe1cf7dff16f37b1142c0c522cbb3a Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 02:49:23 +0200 Subject: [PATCH 45/51] Make inner classes static --- .../openrewrite/testngtojupiter/MigrateDataProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 86119d7a..7da865cd 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -64,7 +64,7 @@ public TreeVisitor getVisitor() { }; } - private class WrapDataProviderMethod extends JavaIsoVisitor { + private static class WrapDataProviderMethod extends JavaIsoVisitor { private final JavaTemplate methodeSourceTemplate = JavaTemplate.builder( "public static Stream #{}() {\n" @@ -103,7 +103,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, or } } - private class UseParameterizedTest extends JavaIsoVisitor { + private static class UseParameterizedTest extends JavaIsoVisitor { @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { method = super.visitMethodDeclaration(method, ctx); @@ -141,7 +141,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex } } - class UseMethodSource extends JavaIsoVisitor { + static class UseMethodSource extends JavaIsoVisitor { @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { method = super.visitMethodDeclaration(method, ctx); From 4c5fc0f45760b9cc18bc68355ca14a0597d4af14 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 02:55:20 +0200 Subject: [PATCH 46/51] Fix trailing whitespace in text blocks --- .../MigrateDataProviderTest.java | 155 ++++++++---------- 1 file changed, 72 insertions(+), 83 deletions(-) diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java index ccf04758..f77ec5f3 100644 --- a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java +++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProviderTest.java @@ -29,16 +29,14 @@ public void defaults(RecipeSpec spec) { .recipe(new MigrateDataProvider()); } + @Nested class WrapDataProvider { - @Nested - class WrapDataProvider { - @Test - void withName() { + @Test void withName() { @Language("java") String is = """ package de.boeg.tst.provider; - + import org.testng.annotations.DataProvider; - + public class BoxPrimitiveDataProvider { @DataProvider(name = "anotherBoxPrimitiveDataProvider") public static Object[][] boxPrimitiveDataProvider() { /*...*/ } @@ -46,15 +44,15 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } """; @Language("java") String should = """ package de.boeg.tst.provider; - + import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream anotherBoxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -63,12 +61,11 @@ public static Stream anotherBoxPrimitiveDataProvider() { rewriteRun(java(is, should)); } - @Test - void withDefaultName() { + @Test void withDefaultName() { @Language("java") String is = """ package de.boeg.tst.provider; import org.testng.annotations.DataProvider; - + public class BoxPrimitiveDataProvider { @DataProvider public static Object[][] boxPrimitiveDataProvider() { /*...*/ } @@ -77,13 +74,13 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } @Language("java") String should = """ package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -91,20 +88,18 @@ public static Stream boxPrimitiveDataProvider() { """; rewriteRun(java(is, should)); } - } - @Nested - class NewTests { - @Test - void fullMigrate() { + @Nested class NewTests { + + @Test void fullMigrate() { rewriteRun( java( """ package de.boeg.tst.provider; import org.testng.annotations.DataProvider; - - public class BoxPrimitiveDataProvider { + + public class BoxPrimitiveDataProvider { @DataProvider public static Object[][] boxPrimitiveDataProvider() { /*...*/ } } @@ -112,13 +107,13 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } """ package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -129,9 +124,9 @@ public static Stream boxPrimitiveDataProvider() { """ package de.boeg.tst.real; import org.testng.annotations.Test; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} @@ -141,9 +136,9 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.testng.annotations.Test; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test @ParameterizedTest @@ -154,15 +149,14 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} )); } - @Test - void WrapOnlyDataprovider() { + @Test void WrapOnlyDataprovider() { rewriteRun( java( """ package de.boeg.tst.provider; import org.testng.annotations.DataProvider; - - public class BoxPrimitiveDataProvider { + + public class BoxPrimitiveDataProvider { @DataProvider public static Object[][] boxPrimitiveDataProvider() { /*...*/ } } @@ -170,13 +164,13 @@ public static Object[][] boxPrimitiveDataProvider() { /*...*/ } """ package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -187,14 +181,14 @@ public static Stream boxPrimitiveDataProvider() { """ package de.boeg.tst.real; import org.testng.annotations.Test; - + import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + class BoxPrimitiveDataProvider {} - + public class HotSpotConstantReflectionProviderTest { @Test @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") @@ -205,20 +199,18 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} )); } - @Test - void addsParameterizedTest() { - rewriteRun( - java( + @Test void addsParameterizedTest() { + rewriteRun(java( """ package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -230,9 +222,9 @@ public static Stream boxPrimitiveDataProvider() { package de.boeg.tst.real; import org.testng.annotations.Test; import org.junit.jupiter.params.provider.MethodSource; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") @@ -243,9 +235,9 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} import org.testng.annotations.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test @MethodSource("de.boeg.tst.provider.BoxPrimitiveDataProvider#boxPrimitiveDataProvider") @@ -256,20 +248,19 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} )); } - @Test - void addsMethodSource() { + @Test void addsMethodSource() { rewriteRun( java( """ package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -280,25 +271,26 @@ public static Stream boxPrimitiveDataProvider() { """ package de.boeg.tst.real; import org.testng.annotations.Test; - + import org.junit.jupiter.params.ParameterizedTest; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) @ParameterizedTest public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} } - """, """ + """, + """ package de.boeg.tst.real; import org.junit.jupiter.params.provider.MethodSource; import org.testng.annotations.Test; - + import org.junit.jupiter.params.ParameterizedTest; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test @ParameterizedTest @@ -309,20 +301,19 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} )); } - @Test - void removesTestNgAnnotationArguments() { + @Test void removesTestNgAnnotationArguments() { rewriteRun( java( """ package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -333,11 +324,11 @@ public static Stream boxPrimitiveDataProvider() { """ package de.boeg.tst.real; import org.testng.annotations.Test; - + import org.junit.jupiter.params.ParameterizedTest; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test(dataProvider = "boxPrimitiveDataProvider", dataProviderClass = BoxPrimitiveDataProvider.class) @ParameterizedTest @@ -347,11 +338,11 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} package de.boeg.tst.real; import org.junit.jupiter.params.provider.MethodSource; import org.testng.annotations.Test; - + import org.junit.jupiter.params.ParameterizedTest; - + import de.boeg.tst.provider.BoxPrimitiveDataProvider; - + public class HotSpotConstantReflectionProviderTest { @Test @ParameterizedTest @@ -362,20 +353,18 @@ public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} )); } - @Test - void doNothingIfNameMissing() { - rewriteRun( - java( + @Test void doNothingIfNameMissing() { + rewriteRun(java( """ package de.boeg.tst.provider; import org.junit.jupiter.params.provider.Arguments; - + import java.util.Arrays; import java.util.stream.Stream; - + public class BoxPrimitiveDataProvider { public static Object[][] boxPrimitiveDataProvider() { /*...*/ } - + public static Stream boxPrimitiveDataProvider() { return Arrays.stream(boxPrimitiveDataProvider()).map(Arguments::of); } @@ -386,7 +375,7 @@ public static Stream boxPrimitiveDataProvider() { """ package de.boeg.tst.real; import org.testng.annotations.Test; - + public class HotSpotConstantReflectionProviderTest { @Test(enabled = false) public void testUnboxPrimitive(Object constant, Object expected) {/*...*/} From 34390f1342294160e495eb047ec4e9e9144a6897 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 03:18:24 +0200 Subject: [PATCH 47/51] Fix typos and remove undocumented JavaDoc tags --- .../openrewrite/testngtojupiter/MigrateDataProvider.java | 4 ++-- .../testngtojupiter/helper/AnnotationArguments.java | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 7da865cd..5a8e9654 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -109,8 +109,8 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex method = super.visitMethodDeclaration(method, ctx); // if @ParameterizedTest is used, skip - Optional paraeterizedTestAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.ParameterizedTest")); - if (paraeterizedTestAnnotation.isPresent()) { + Optional parameterizedTestAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.ParameterizedTest")); + if (parameterizedTestAnnotation.isPresent()) { return method; } diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java index 5776d874..420d0f7d 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java @@ -9,17 +9,14 @@ import java.util.stream.Collectors; /** - * Answer questions regarding annotation arguments and there values + * Answer questions regarding annotation arguments and their values * * @see J.Annotation */ public final class AnnotationArguments { /** - * Determins if the annotation has any arguments - * - * @param annotation - * @return + * Determines if the annotation has any arguments */ public static boolean hasAny(J.Annotation annotation) { List arguments = annotation.getArguments(); @@ -37,7 +34,6 @@ public static boolean hasAny(J.Annotation annotation) { * * @param annotation to extract the assignments from * @param argumentName to extract - * @return */ public static List extractAssignments(J.Annotation annotation, String argumentName) { List arguments = annotation.getArguments(); From c098fa7332b515222487e7ab646a11cbe9294c64 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 03:20:07 +0200 Subject: [PATCH 48/51] Remove unused methods --- .../helper/AnnotationArguments.java | 14 ---------- .../helper/UsesAnnotation.java | 28 ------------------- 2 files changed, 42 deletions(-) delete mode 100644 src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java index 420d0f7d..be5fd969 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java @@ -15,20 +15,6 @@ */ public final class AnnotationArguments { - /** - * Determines if the annotation has any arguments - */ - public static boolean hasAny(J.Annotation annotation) { - List arguments = annotation.getArguments(); - - if (arguments == null || arguments.isEmpty()) { - return false; - } - - boolean containsNoEmpty = arguments.stream().noneMatch(J.Empty.class::isInstance); - return containsNoEmpty; - } - /** * Extracts all assignments with the given argument name from the annotation * diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java deleted file mode 100644 index 39a026e3..00000000 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/UsesAnnotation.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.mboegers.openrewrite.testngtojupiter.helper; - -import org.openrewrite.java.AnnotationMatcher; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.marker.SearchResult; - -/** - * Checks whether a given {@linkplain J.Annotation} is used - */ -public class UsesAnnotation

extends JavaIsoVisitor

{ - private final AnnotationMatcher annotationMatcher; - - public UsesAnnotation(AnnotationMatcher annotationMatcher) { - this.annotationMatcher = annotationMatcher; - } - - @Override - public J.Annotation visitAnnotation(J.Annotation annotation, P ctx) { - annotation = super.visitAnnotation(annotation, ctx); - - if (annotationMatcher.matches(annotation)) { - return SearchResult.found(annotation); - } else { - return annotation; - } - } -} From 27d4d09838d2b0d4fb01c0ad22e2f5b679ecb615 Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 03:31:05 +0200 Subject: [PATCH 49/51] Convert helper class to non-instantiable enum utility class --- .../openrewrite/testngtojupiter/helper/AnnotationArguments.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java index be5fd969..633e0d8e 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java @@ -13,7 +13,7 @@ * * @see J.Annotation */ -public final class AnnotationArguments { +public enum AnnotationArguments {; /** * Extracts all assignments with the given argument name from the annotation From a72cc391e47cfa90cdb93dcd714401a85571157e Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 03:41:01 +0200 Subject: [PATCH 50/51] Apply finality to variables and return early where appropriate --- .../testngtojupiter/MigrateDataProvider.java | 51 +++++++++---------- .../helper/AnnotationArguments.java | 6 +-- .../helper/FindAnnotatedMethods.java | 9 ++-- .../helper/FindAnnotation.java | 4 +- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java index 5a8e9654..e4e1408c 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateDataProvider.java @@ -80,12 +80,12 @@ private static class WrapDataProviderMethod extends JavaIsoVisitor dataProviders = FindAnnotatedMethods.find(classDecl, DATA_PROVIDER_MATCHER); + final Set dataProviders = FindAnnotatedMethods.find(classDecl, DATA_PROVIDER_MATCHER); // for each add a Wrapper that translates to Jupiter method source for (J.MethodDeclaration provider : dataProviders) { - String providerMethodName = provider.getSimpleName(); - String providerName = FindAnnotation.find(provider, DATA_PROVIDER_MATCHER).stream().findAny() + final String providerMethodName = provider.getSimpleName(); + final String providerName = FindAnnotation.find(provider, DATA_PROVIDER_MATCHER).stream().findAny() .flatMap(j -> AnnotationArguments.extractLiteral(j, "name", String.class)) .orElse(providerMethodName); @@ -109,35 +109,33 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex method = super.visitMethodDeclaration(method, ctx); // if @ParameterizedTest is used, skip - Optional parameterizedTestAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.ParameterizedTest")); + final Optional parameterizedTestAnnotation = + FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.ParameterizedTest")); if (parameterizedTestAnnotation.isPresent()) { return method; } // if no TestNG @Test present, skip - Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); + final Optional testNgAnnotation = + FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); if (!testNgAnnotation.isPresent()) { return method; } // determine if a parameterized test is applicable - Optional dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); + final Optional dataProviderMethodName = + AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); if (!dataProviderMethodName.isPresent()) { return method; } - JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation((a, b) -> 1); - - method = JavaTemplate + maybeAddImport("org.junit.jupiter.params.ParameterizedTest"); + return JavaTemplate .builder("@ParameterizedTest") .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) .imports("org.junit.jupiter.params.ParameterizedTest") .build() - .apply(getCursor(), addAnnotationCoordinate); - - maybeAddImport("org.junit.jupiter.params.ParameterizedTest"); - - return method; + .apply(getCursor(), method.getCoordinates().addAnnotation((a, b) -> 1)); } } @@ -147,25 +145,25 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex method = super.visitMethodDeclaration(method, ctx); // if @MethodSource is used, skip - Optional methodSourceAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.provider.MethodSource")); + final Optional methodSourceAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.provider.MethodSource")); if (methodSourceAnnotation.isPresent()) { return method; } // if no testng annotation is present, skip - Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); + final Optional testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test")); if (!testNgAnnotation.isPresent()) { return method; } // determine Provider name, if not present skip! - Optional dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); + final Optional dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class); if (!dataProviderMethodName.isPresent()) { return method; } // determin provider class or use current class as default - String dataProviderClass = AnnotationArguments.extractAssignments(testNgAnnotation.get(), "dataProviderClass").stream() + final String dataProviderClass = AnnotationArguments.extractAssignments(testNgAnnotation.get(), "dataProviderClass").stream() .findAny() .map(J.FieldAccess.class::cast) .map(J.FieldAccess::getTarget) @@ -176,18 +174,19 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex .orElse(requireNonNull(getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class).getType()).getFullyQualifiedName()); // add MethodSource annotation - JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation((a, b) -> 1); - method = JavaTemplate + maybeAddImport("org.junit.jupiter.params.provider.MethodSource"); + maybeRemoveImport(dataProviderClass); + return JavaTemplate .builder("@MethodSource(\"#{}##{}\")") .javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")) .imports("org.junit.jupiter.params.provider.MethodSource") .build() - .apply(getCursor(), addAnnotationCoordinate, dataProviderClass, dataProviderMethodName.get()); - - maybeAddImport("org.junit.jupiter.params.provider.MethodSource"); - maybeRemoveImport(dataProviderClass); - - return method; + .apply( + getCursor(), + method.getCoordinates().addAnnotation((a, b) -> 1), + dataProviderClass, + dataProviderMethodName.get() + ); } } } diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java index 633e0d8e..3805c4cd 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/AnnotationArguments.java @@ -22,8 +22,8 @@ public enum AnnotationArguments {; * @param argumentName to extract */ public static List extractAssignments(J.Annotation annotation, String argumentName) { - List arguments = annotation.getArguments(); - + + final List arguments = annotation.getArguments(); if (arguments == null) { return Collections.emptyList(); } @@ -46,8 +46,8 @@ public static List extractAssignments(J.Annotation annotation, Strin * @return the value or Optional#empty */ public static Optional extractLiteral(J.Annotation annotation, String argumentName, Class valueClass) { - List arguments = annotation.getArguments(); + final List arguments = annotation.getArguments(); if (arguments == null) { return Optional.empty(); } diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java index 296dbdce..23f1fcd4 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotatedMethods.java @@ -35,11 +35,10 @@ public static Set find(J subtree, AnnotationMatcher annotat @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); - - boolean isAnnotatedWithTargetAnnotation = m.getLeadingAnnotations().stream().anyMatch(annotationMatcher::matches); - if (isAnnotatedWithTargetAnnotation) { - m = SearchResult.found(m); + + final J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); + if (m.getLeadingAnnotations().stream().anyMatch(annotationMatcher::matches)) { + return SearchResult.found(m); } return m; diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java index b9d1aaad..c234734b 100644 --- a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java +++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/helper/FindAnnotation.java @@ -36,10 +36,10 @@ public static Set find(J tree, AnnotationMatcher annotationMatcher @Override public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext executionContext) { + annotation = super.visitAnnotation(annotation, executionContext); - if (annotationMatcher.matches(annotation)) { - annotation = SearchResult.found(annotation); + return SearchResult.found(annotation); } return annotation; From 20f672eca7be2b7d1283825661a2ceda93be1b6f Mon Sep 17 00:00:00 2001 From: Philzen Date: Sat, 22 Jun 2024 03:47:24 +0200 Subject: [PATCH 51/51] Add MigrateDataProvider to recipe list --- src/main/resources/META-INF/rewrite/rewrite.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/META-INF/rewrite/rewrite.yml b/src/main/resources/META-INF/rewrite/rewrite.yml index bed3b20e..af3c16e2 100644 --- a/src/main/resources/META-INF/rewrite/rewrite.yml +++ b/src/main/resources/META-INF/rewrite/rewrite.yml @@ -8,5 +8,6 @@ preconditions: - org.openrewrite.FindSourceFiles: filePattern: "**/*.java" recipeList: +- io.github.mboegers.openrewrite.testngtojupiter.MigrateDataProvider - org.philzen.oss.testng.UpdateTestAnnotationToJunit5 - org.openrewrite.java.testing.junit5.AddMissingNested