From 36074f0e96799befa5a9984bf6d48f27b9b5b74f Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 23 Oct 2024 19:31:16 +0000 Subject: [PATCH 01/20] refactor: Kill your Zombie code Use this link to re-run the recipe: https://app.moderne.io/builder/mybR2aHWC?organizationId=T3BlblJld3JpdGU%3D Co-authored-by: Moderne --- .../java/testing/junit5/TemporaryFolderToTempDir.java | 6 +++--- .../openrewrite/java/testing/junit5/UseTestMethodOrder.java | 2 +- .../java/testing/junit5/JUnit5BestPracticesTest.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java b/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java index 7b0bb2d08..ca412b8df 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java @@ -49,7 +49,7 @@ public TreeVisitor getVisitor() { final AnnotationMatcher classRule = new AnnotationMatcher("@org.junit.ClassRule"); final AnnotationMatcher rule = new AnnotationMatcher("@org.junit.Rule"); - + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { @@ -157,7 +157,7 @@ private J convertToNewFile(J.MethodInvocation mi, ExecutionContext ctx) { private static class AddNewFolderMethod extends JavaIsoVisitor { private final J.MethodInvocation methodInvocation; - + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { @@ -236,7 +236,7 @@ private static class TranslateNewFolderMethodInvocation extends JavaVisitor javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { diff --git a/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java b/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java index 5fa7ef266..4525701a8 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java @@ -45,7 +45,7 @@ public String getDescription() { public TreeVisitor getVisitor() { return Preconditions.check(new UsesType<>("org.junit.FixMethodOrder", false), new JavaIsoVisitor() { - + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { diff --git a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java index 3d1dccc53..412e39e39 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java @@ -189,7 +189,7 @@ void somethingElse() { ) ); } - + @Test void changeThrowingRunnableToExecutable() { //language=java From 386f3d45d00e9a062b6ff2199b84d4ec993977e8 Mon Sep 17 00:00:00 2001 From: Niels de Bruin Date: Mon, 28 Oct 2024 11:47:05 +0100 Subject: [PATCH 02/20] refactor: add @Nullable to methods who may return null (#621) Use this link to re-run the recipe: https://app.moderne.io/builder/ji8mLIdUI?organizationId=T3BlblJld3JpdGU%3D Co-authored-by: Moderne --- .../openrewrite/java/testing/cleanup/RemoveEmptyTests.java | 4 +++- .../openrewrite/java/testing/junit5/MigrateJUnitTestCase.java | 2 +- .../java/testing/junit5/TemporaryFolderToTempDir.java | 2 +- .../openrewrite/java/testing/junit5/TestRuleToTestInfo.java | 2 +- .../java/testing/mockito/PowerMockitoMockStaticToMockito.java | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java b/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java index cbc914457..d2701d963 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.testing.cleanup; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; @@ -53,8 +54,9 @@ public Set getTags() { @Override public TreeVisitor getVisitor() { return Preconditions.check(new FindEmptyMethods(false), new JavaVisitor() { + @Override - public J visitMethodDeclaration(MethodDeclaration method, ExecutionContext ctx) { + public @Nullable J visitMethodDeclaration(MethodDeclaration method, ExecutionContext ctx) { if (hasTestAnnotation(method) && isEmptyMethod(method)) { //noinspection ConstantConditions return null; diff --git a/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java b/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java index e9ce733b0..1bb528abd 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java @@ -83,7 +83,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon @SuppressWarnings("ConstantConditions") @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + public J.@Nullable MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); if ((mi.getSelect() != null && TypeUtils.isOfClassType(mi.getSelect().getType(), "junit.framework.TestCase")) || (mi.getMethodType() != null && TypeUtils.isOfClassType(mi.getMethodType().getDeclaringType(), "junit.framework.TestCase"))) { diff --git a/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java b/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java index ca412b8df..d6b3bf130 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java @@ -108,7 +108,7 @@ private boolean isRuleAnnotatedTemporaryFolder(J.VariableDeclarations vd) { } @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + public @Nullable J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); updateCursor(mi); if (mi.getSelect() != null && mi.getMethodType() != null && diff --git a/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java b/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java index 53e3c4fb0..5d97b74dc 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java @@ -101,7 +101,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m } @Override - public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) { + public J.@Nullable NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) { J.NewClass nc = super.visitNewClass(newClass, ctx); if (TypeUtils.isOfClassType(nc.getType(), "org.junit.rules.TestName")) { //noinspection ConstantConditions diff --git a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java index 019425f50..aa7cb417d 100644 --- a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java +++ b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java @@ -168,7 +168,7 @@ public J visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx } @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + public @Nullable J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); Map mockStaticInvocationsByClassName = getCursor().getNearestMessage(MOCK_STATIC_INVOCATIONS); From 6ab39a25bc43b7960c99d4f0de48a47c9e785545 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 29 Oct 2024 22:16:44 +0100 Subject: [PATCH 03/20] Place binary operator on the same line As seen on #599 --- .../java/testing/jmockit/JMockitBlockRewriter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockRewriter.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockRewriter.java index db8b9631f..3856b476e 100644 --- a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockRewriter.java +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockRewriter.java @@ -109,8 +109,9 @@ J.Block rewriteMethodBody() { methodInvocationIdx++; methodInvocationsToRewrite.add(new ArrayList<>()); } - if (isFullVerifications() && uniqueMocks.stream().noneMatch(mock -> mock.getType().equals(mockObj.getType()) - && mock.getSimpleName().equals(mockObj.getSimpleName()))) { + if (isFullVerifications() && + uniqueMocks.stream().noneMatch(mock -> mock.getType().equals(mockObj.getType()) && + mock.getSimpleName().equals(mockObj.getSimpleName()))) { uniqueMocks.add(mockObj); } } From 4a8792ef7accec8ec5ce6b3db0842b8dcf7c0ca0 Mon Sep 17 00:00:00 2001 From: Jente Sondervorst Date: Wed, 30 Oct 2024 14:22:01 +0100 Subject: [PATCH 04/20] Add `@MockitoSettings(strictness = ...)` based on how Mockito is used, not based on version alone (#623) * The default strictness of Mockito did not change until v4 fixes #622 * Revert "The default strictness of Mockito did not change until v4" This reverts commit ffdcd85e9653b92e06730185729dd989fd22fc14. * Added the MockitoSettings for MockitoRule strictness or silent method call. * Repositioned stuff a bit * Apply formatter to tests * Demonstrate failure when using static import * Poll strictness message before updateCursor * Workaround left over prefix on modifier after adding annotation * Remove unused import --------- Co-authored-by: Jente Sondervorst Co-authored-by: Tim te Beek --- .../MockitoJUnitToMockitoExtension.java | 41 +- .../MockitoJUnitToMockitoExtensionTest.java | 461 +++++++++++++++--- 2 files changed, 428 insertions(+), 74 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java index e82911cb0..0c957f25f 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java @@ -19,6 +19,7 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; @@ -74,6 +75,7 @@ public TreeVisitor getVisitor() { public static class MockitoRuleToMockitoExtensionVisitor extends JavaIsoVisitor { private static final String MOCKITO_RULE_INVOCATION_KEY = "mockitoRuleInvocation"; private static final String MOCKITO_TEST_RULE_INVOCATION_KEY = "mockitoTestRuleInvocation"; + private static final String STRICTNESS_KEY = "strictness"; private static final String EXTEND_WITH_MOCKITO_EXTENSION = "@org.junit.jupiter.api.extension.ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)"; private static final String RUN_WITH_MOCKITO_JUNIT_RUNNER = "@org.junit.runner.RunWith(org.mockito.runners.MockitoJUnitRunner.class)"; @@ -100,6 +102,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex if (classDecl.getBody().getStatements().size() != cd.getBody().getStatements().size() && (FindAnnotations.find(classDecl.withBody(null), RUN_WITH_MOCKITO_JUNIT_RUNNER).isEmpty() && FindAnnotations.find(classDecl.withBody(null), EXTEND_WITH_MOCKITO_EXTENSION).isEmpty())) { + String strictness = getCursor().pollMessage(STRICTNESS_KEY); cd = JavaTemplate.builder("@ExtendWith(MockitoExtension.class)") .javaParser(JavaParser.fromJavaVersion() @@ -110,6 +113,23 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex maybeAddImport("org.junit.jupiter.api.extension.ExtendWith"); maybeAddImport("org.mockito.junit.jupiter.MockitoExtension"); + + if (strictness != null) { + cd = JavaTemplate.builder("@MockitoSettings(strictness = " + strictness + ")") + .doBeforeParseTemplate(System.out::println) + .javaParser(JavaParser.fromJavaVersion() + .classpathFromResources(ctx, "junit-jupiter-api-5.9", "mockito-junit-jupiter-3.12")) + .imports("org.mockito.junit.jupiter.MockitoSettings", "org.mockito.quality.Strictness") + .build() + .apply(updateCursor(cd), cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + maybeAddImport("org.mockito.junit.jupiter.MockitoSettings", false); + maybeAddImport("org.mockito.quality.Strictness", false); + } + + // Workaround first modifier incorrectly getting a trailing space as part of the prefix + cd = cd.withModifiers(ListUtils.mapFirst(cd.getModifiers(), + modifier -> modifier.withPrefix(modifier.getPrefix().withWhitespace( + modifier.getPrefix().getLastWhitespace().replaceAll(" $", ""))))); } } @@ -119,10 +139,27 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { if (method.getMethodType() != null) { + String key = null; if (TypeUtils.isOfClassType(method.getMethodType().getDeclaringType(), "org.mockito.junit.MockitoRule")) { - getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, MOCKITO_RULE_INVOCATION_KEY, method); + key = MOCKITO_RULE_INVOCATION_KEY; } else if (TypeUtils.isOfClassType(method.getMethodType().getDeclaringType(), "org.mockito.junit.MockitoTestRule")) { - getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, MOCKITO_TEST_RULE_INVOCATION_KEY, method); + key = MOCKITO_TEST_RULE_INVOCATION_KEY; + } + if (key != null) { + getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, key, method); + String strictness = null; + switch (method.getSimpleName()) { + case "strictness": + strictness = method.getArguments().get(0).toString(); + break; + case "silent": + strictness = "Strictness.LENIENT"; + break; + } + if (strictness != null && !strictness.contains("STRICT_STUBS")) { + strictness = strictness.startsWith("Strictness.") ? strictness : "Strictness." + strictness; + getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, STRICTNESS_KEY, strictness); + } } } diff --git a/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java b/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java index 0b8d71722..c42b35e40 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java @@ -48,12 +48,12 @@ void leavesOtherRulesAlone() { import org.mockito.Mock; import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoJUnit; - + class MyTest { - + @Rule TemporaryFolder tempDir = new TemporaryFolder(); - + @Rule MockitoRule mockitoRule = MockitoJUnit.rule(); } @@ -64,10 +64,10 @@ class MyTest { import org.junit.rules.TemporaryFolder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + @ExtendWith(MockitoExtension.class) class MyTest { - + @Rule TemporaryFolder tempDir = new TemporaryFolder(); } @@ -89,10 +89,10 @@ void leavesOtherAnnotationsAlone() { import org.mockito.Mock; import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoJUnit; - + @FixMethodOrder(MethodSorters.NAME_ASCENDING) class MyTest { - + @Rule MockitoRule mockitoRule = MockitoJUnit.rule(); } @@ -103,7 +103,7 @@ class MyTest { import org.junit.runners.MethodSorters; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + @ExtendWith(MockitoExtension.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class MyTest { @@ -121,21 +121,21 @@ void refactorMockitoRule() { java( """ import java.util.List; - + import org.junit.Rule; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; - + class MyTest { - + @Rule MockitoRule mockitoRule = MockitoJUnit.rule(); - + @Mock private List list; - + public void exampleTest() { mockitoRule.strictness(Strictness.LENIENT); list.add(100); @@ -144,17 +144,20 @@ public void exampleTest() { """, """ import java.util.List; - + import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) class MyTest { - + @Mock private List list; - + public void exampleTest() { list.add(100); } @@ -172,21 +175,21 @@ void refactorMockitoTestRule() { java( """ import java.util.List; - + import org.junit.Rule; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoTestRule; import org.mockito.quality.Strictness; - + class MyTest { - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + public void exampleTest() { mockitoTestRule.strictness(Strictness.LENIENT); list.add(100); @@ -195,17 +198,20 @@ public void exampleTest() { """, """ import java.util.List; - + import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) class MyTest { - + @Mock private List list; - + public void exampleTest() { list.add(100); } @@ -229,22 +235,22 @@ void onlyRefactorMockitoRule() { import org.mockito.junit.MockitoTestRule; import org.mockito.junit.VerificationCollector; import org.mockito.quality.Strictness; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + class MyTest { - + @Rule VerificationCollector verificationCollectorRule = MockitoJUnit.collector(); - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -260,20 +266,20 @@ public void exampleTest() { import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; import org.mockito.junit.jupiter.MockitoExtension; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @ExtendWith(MockitoExtension.class) class MyTest { - + @Rule VerificationCollector verificationCollectorRule = MockitoJUnit.collector(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -293,20 +299,20 @@ void unchangedMockitoCollectorRule() { java( """ import java.util.List; - + import org.junit.Rule; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; - + class MyTest { - + @Rule VerificationCollector verificationCollectorRule = MockitoJUnit.collector(); - + @Mock private List list; - + public void exampleTest() { list.add(100); verificationCollectorRule.collectAndReport(); @@ -325,25 +331,25 @@ void unchangedMockitoCollectorDeclaredInMethod() { java( """ import java.util.List; - + import org.mockito.Mock; import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; - + import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; - + class MyTest { - + public void unsupported() { VerificationCollector collector = MockitoJUnit.collector().assertLazily(); - + List mockList = mock(List.class); verify(mockList).add("one"); verify(mockList).clear(); - + try { collector.collectAndReport(); } catch (MockitoAssertionError error) { @@ -374,20 +380,20 @@ void leaveMockitoJUnitRunnerAlone() { import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoTestRule; import org.mockito.runners.MockitoJUnitRunner; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @RunWith(MockitoJUnitRunner.class) class MyTest { - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -399,17 +405,17 @@ public void exampleTest() { import org.junit.Test; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @RunWith(MockitoJUnitRunner.class) class MyTest { - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -434,20 +440,20 @@ void leaveExtendWithAlone() { import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoTestRule; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @ExtendWith(MockitoExtension.class) class MyTest { - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -459,17 +465,17 @@ public void exampleTest() { import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @ExtendWith(MockitoExtension.class) class MyTest { - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -479,4 +485,315 @@ public void exampleTest() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void silentRuleAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().silent(); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void warnStrictnessRuleAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.WARN); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void warnStrictnessRuleAddMockitoSettingsWithStaticImport() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + + import java.util.List; + + import static org.mockito.Mockito.when; + import static org.mockito.quality.Strictness.WARN; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(WARN); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void lenientStrictnessRuleAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.LENIENT); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void strictRuleDoNotAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } } From 6820b1b3f5daaf35f6abb8bd27d63ec5177c7ac5 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 30 Oct 2024 14:48:01 +0100 Subject: [PATCH 05/20] Drop RetainStrictnessWarn; replaced with #623 (#625) --- .../testing/mockito/RetainStrictnessWarn.java | 100 ------------- .../resources/META-INF/rewrite/mockito.yml | 1 - .../mockito/RetainStrictnessWarnTest.java | 139 ------------------ 3 files changed, 240 deletions(-) delete mode 100644 src/main/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarn.java delete mode 100644 src/test/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarnTest.java diff --git a/src/main/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarn.java b/src/main/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarn.java deleted file mode 100644 index cdda4962a..000000000 --- a/src/main/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarn.java +++ /dev/null @@ -1,100 +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.openrewrite.java.testing.mockito; - -import org.jspecify.annotations.Nullable; -import org.openrewrite.*; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.RemoveAnnotation; -import org.openrewrite.java.dependencies.DependencyInsight; -import org.openrewrite.java.search.FindAnnotations; -import org.openrewrite.java.search.UsesType; -import org.openrewrite.java.tree.J; - -import java.util.Comparator; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -public class RetainStrictnessWarn extends ScanningRecipe { - - private static final String EXTEND_WITH_FQ = "org.junit.jupiter.api.extension.ExtendWith"; - private static final String MOCKITO_EXTENSION_FQ = "org.mockito.junit.jupiter.MockitoExtension"; - private static final String MOCKITO_SETTINGS_FQ = "org.mockito.junit.jupiter.MockitoSettings"; - private static final String MOCKITO_STRICTNESS_FQ = "org.mockito.quality.Strictness"; - - private static final String EXTEND_WITH_MOCKITO_EXTENSION = "@" + EXTEND_WITH_FQ + "(" + MOCKITO_EXTENSION_FQ + ".class)"; - - @Override - public String getDisplayName() { - return "Retain Mockito strictness `WARN` when switching to JUnit 5"; - } - - @Override - public String getDescription() { - return "Migrating from JUnit 4 to 5 [changes the default strictness](https://stackoverflow.com/a/53234137/53444) of the mocks from `WARN` to `STRICT_STUBS`. " + - "To prevent tests from failing we restore the original behavior by adding `@MockitoSettings(strictness = Strictness.WARN)`."; - } - - @Override - public AtomicBoolean getInitialValue(ExecutionContext ctx) { - return new AtomicBoolean(false); - } - - @Override - public TreeVisitor getScanner(AtomicBoolean usingOlderMockito) { - TreeVisitor div = new DependencyInsight("org.mockito", "mockito-*", "[1.1,2.17)", null).getVisitor(); - return new TreeVisitor() { - @Override - public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { - if (!usingOlderMockito.get() && div.visit(tree, ctx) != tree) { - usingOlderMockito.set(true); - } - return tree; - } - }; - } - - @Override - public TreeVisitor getVisitor(AtomicBoolean usingOlderMockito) { - return Preconditions.check(usingOlderMockito.get(), - Preconditions.check( - Preconditions.and( - new UsesType<>(MOCKITO_EXTENSION_FQ, true), - Preconditions.not(new UsesType<>(MOCKITO_SETTINGS_FQ, false)) - ), new JavaIsoVisitor() { - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - Set annotations = FindAnnotations.find(classDecl, EXTEND_WITH_MOCKITO_EXTENSION); - if (!annotations.isEmpty()) { - maybeAddImport(MOCKITO_SETTINGS_FQ); - maybeAddImport(MOCKITO_STRICTNESS_FQ); - classDecl = JavaTemplate.builder("@MockitoSettings(strictness = Strictness.WARN)") - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-junit-jupiter", "mockito-core")) - .imports(MOCKITO_SETTINGS_FQ, MOCKITO_STRICTNESS_FQ) - .build() - .apply(getCursor(), classDecl.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); - doAfterVisit(new RemoveAnnotation(EXTEND_WITH_MOCKITO_EXTENSION).getVisitor()); - maybeRemoveImport(EXTEND_WITH_FQ); - maybeRemoveImport(MOCKITO_EXTENSION_FQ); - } - return super.visitClassDeclaration(classDecl, ctx); - } - }) - ); - } -} diff --git a/src/main/resources/META-INF/rewrite/mockito.yml b/src/main/resources/META-INF/rewrite/mockito.yml index 5e5c61f6f..d1b62eaf2 100644 --- a/src/main/resources/META-INF/rewrite/mockito.yml +++ b/src/main/resources/META-INF/rewrite/mockito.yml @@ -154,7 +154,6 @@ recipeList: - org.openrewrite.java.testing.mockito.MockUtilsToStatic - org.openrewrite.java.testing.junit5.MockitoJUnitToMockitoExtension - org.openrewrite.java.testing.mockito.ReplacePowerMockito - - org.openrewrite.java.testing.mockito.RetainStrictnessWarn - org.openrewrite.java.dependencies.AddDependency: groupId: org.mockito artifactId: mockito-junit-jupiter diff --git a/src/test/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarnTest.java b/src/test/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarnTest.java deleted file mode 100644 index 1f9fbae59..000000000 --- a/src/test/java/org/openrewrite/java/testing/mockito/RetainStrictnessWarnTest.java +++ /dev/null @@ -1,139 +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.openrewrite.java.testing.mockito; - -import org.intellij.lang.annotations.Language; -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.maven.Assertions.pomXml; - -class RetainStrictnessWarnTest implements RewriteTest { - - @Language("xml") - private static final String POM_XML_WITH_OLDER_MOCKITO = """ - - 4.0.0 - bla.bla - bla-bla - 1.0.0 - - - org.mockito - mockito-all - 1.1 - test - - - - """; - - @Language("java") - private static final String JAVA_BEFORE = """ - import org.junit.jupiter.api.extension.ExtendWith; - import org.mockito.junit.jupiter.MockitoExtension; - - @ExtendWith(MockitoExtension.class) - class MyTest { - } - """; - - @Language("java") - private static final String JAVA_AFTER = """ - import org.mockito.junit.jupiter.MockitoSettings; - import org.mockito.quality.Strictness; - - @MockitoSettings(strictness = Strictness.WARN) - class MyTest { - } - """; - - @Override - public void defaults(RecipeSpec spec) { - spec - .parser(JavaParser.fromJavaVersion() - .classpathFromResources(new InMemoryExecutionContext(), - "mockito-core", "mockito-junit-jupiter", "junit-jupiter-api")) - .recipe(new RetainStrictnessWarn()); - } - - @Test - @DocumentExample - void shouldAddMockitoSettingsWithLenientStubbing() { - //language=java - rewriteRun( - pomXml(POM_XML_WITH_OLDER_MOCKITO), - java(JAVA_BEFORE, JAVA_AFTER) - ); - } - - @Test - void shouldLeaveExisting() { - //language=java - rewriteRun( - pomXml(POM_XML_WITH_OLDER_MOCKITO), - java( - """ - import org.mockito.junit.jupiter.MockitoSettings; - import org.mockito.quality.Strictness; - - @MockitoSettings(strictness = Strictness.STRICT_STUBS) - class MyTest { - } - """ - ) - ); - } - - @Test - void shouldRunBeforeMockitoCore2_17() { - rewriteRun( - pomXml(POM_XML_WITH_OLDER_MOCKITO), - java(JAVA_BEFORE, JAVA_AFTER) - ); - } - - @Test - void shouldNotRunOnNewerMockito() { - rewriteRun( - //language=xml - pomXml( - """ - - 4.0.0 - bla.bla - bla-bla - 1.0.0 - - - org.mockito - mockito-core - 2.17.0 - test - - - - """ - ), - java(JAVA_BEFORE) - ); - } -} From d8565962067addf10c77c6ac6644507815480006 Mon Sep 17 00:00:00 2001 From: Jente Sondervorst Date: Wed, 30 Oct 2024 23:02:34 +0100 Subject: [PATCH 06/20] Added default warn strictness if no strictness modifiers are used. (#626) --- .../testing/junit5/MockitoJUnitToMockitoExtension.java | 9 +++++++-- .../mockito/MockitoJUnitToMockitoExtensionTest.java | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java index 0c957f25f..177b361bf 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java @@ -114,7 +114,12 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex maybeAddImport("org.junit.jupiter.api.extension.ExtendWith"); maybeAddImport("org.mockito.junit.jupiter.MockitoExtension"); - if (strictness != null) { + if (strictness == null) { + // As we are in a Rule, and rules where always warn by default, + // we cannot use junit5 Strictness.STRICT_STUBS during migration + strictness = "Strictness.WARN"; + } + if (!strictness.contains("STRICT_STUBS")) { cd = JavaTemplate.builder("@MockitoSettings(strictness = " + strictness + ")") .doBeforeParseTemplate(System.out::println) .javaParser(JavaParser.fromJavaVersion() @@ -156,7 +161,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu strictness = "Strictness.LENIENT"; break; } - if (strictness != null && !strictness.contains("STRICT_STUBS")) { + if (strictness != null) { strictness = strictness.startsWith("Strictness.") ? strictness : "Strictness." + strictness; getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, STRICTNESS_KEY, strictness); } diff --git a/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java b/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java index c42b35e40..03f3017f2 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java @@ -64,8 +64,11 @@ class MyTest { import org.junit.rules.TemporaryFolder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) class MyTest { @Rule @@ -103,9 +106,12 @@ class MyTest { import org.junit.runners.MethodSorters; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; @ExtendWith(MockitoExtension.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) + @MockitoSettings(strictness = Strictness.WARN) class MyTest { } """ @@ -266,12 +272,15 @@ public void exampleTest() { import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; import java.util.List; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) class MyTest { @Rule From 05a917eafd98d6fe5adcf0aac7eb9c5c4708440c Mon Sep 17 00:00:00 2001 From: Jeremy Tymes Date: Thu, 31 Oct 2024 14:49:12 -0400 Subject: [PATCH 07/20] SimplifyChainedAssertJAssertion drops method calls in replacement (#629) * Add test for maintaining method call after replacement * Only get select when method name matches chained assertion * Reuse CHAINED_ASSERT_MATCHER to check argument unpack --------- Co-authored-by: Tim te Beek --- .../SimplifyChainedAssertJAssertion.java | 24 ++-- .../SimplifyChainedAssertJAssertionTest.java | 135 ++++++++++++------ 2 files changed, 98 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java index ca514103f..ba431a43c 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java @@ -30,10 +30,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; @AllArgsConstructor @NoArgsConstructor @@ -163,19 +160,16 @@ private String getStringTemplateAndAppendArguments(J.MethodInvocation assertThat return "assertThat(#{any()}).%s(#{any()})"; } - } - private static Expression extractEitherArgument(boolean assertThatArgumentIsEmpty, Expression assertThatArgument, Expression methodToReplaceArgument) { - if (assertThatArgumentIsEmpty) { - return methodToReplaceArgument; - } - // Only on the assertThat argument do we possibly replace the argument with the select; such as list.size() -> list - if (assertThatArgument instanceof J.MethodInvocation) { - Expression select = ((J.MethodInvocation) assertThatArgument).getSelect(); - if (select != null) { - return select; + private Expression extractEitherArgument(boolean assertThatArgumentIsEmpty, Expression assertThatArgument, Expression methodToReplaceArgument) { + if (assertThatArgumentIsEmpty) { + return methodToReplaceArgument; + } + // Only on the assertThat argument do we possibly replace the argument with the select; such as list.size() -> list + if (CHAINED_ASSERT_MATCHER.matches(assertThatArgument)) { + return Objects.requireNonNull(((J.MethodInvocation) assertThatArgument).getSelect()); } + return assertThatArgument; } - return assertThatArgument; } } diff --git a/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java index 8a2b7f42e..b88b7d315 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java @@ -42,7 +42,7 @@ void stringIsEmpty() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat("hello world".isEmpty()).isTrue(); @@ -51,7 +51,7 @@ void testMethod() { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat("hello world").isEmpty(); @@ -71,7 +71,7 @@ void stringIsEmptyDescribedAs() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod(String actual) { assertThat(actual.isEmpty()).as("Reason").isTrue(); @@ -80,7 +80,7 @@ void testMethod(String actual) { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod(String actual) { assertThat(actual).as("Reason").isEmpty(); @@ -102,12 +102,12 @@ void chainedRecipes() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat(getString().isEmpty()).isTrue(); } - + String getString() { return "hello world"; } @@ -115,12 +115,12 @@ String getString() { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat(getString()).isEmpty(); } - + String getString() { return "hello world"; } @@ -141,14 +141,14 @@ void chainedRecipesOfDifferingTypes() { java( """ import java.nio.file.Path; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void string(String actual) { assertThat(actual.startsWith("prefix")).isTrue(); } - + void path(Path actual) { assertThat(actual.startsWith("prefix")).isTrue(); } @@ -156,14 +156,14 @@ void path(Path actual) { """, """ import java.nio.file.Path; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void string(String actual) { assertThat(actual).startsWith("prefix"); } - + void path(Path actual) { assertThat(actual).startsWithRaw(Path.of("prefix")); } @@ -181,13 +181,13 @@ void assertThatArgHasArgument() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { String expected = "hello world"; assertThat(getString().equalsIgnoreCase(expected)).isTrue(); } - + String getString() { return "hello world"; } @@ -195,13 +195,13 @@ String getString() { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { String expected = "hello world"; assertThat(getString()).isEqualToIgnoringCase(expected); } - + String getString() { return "hello world"; } @@ -219,13 +219,13 @@ void replacementHasArgument() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { int length = 5; assertThat(getString().length()).isEqualTo(length); } - + String getString() { return "hello world"; } @@ -233,13 +233,13 @@ String getString() { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { int length = 5; assertThat(getString()).hasSize(length); } - + String getString() { return "hello world"; } @@ -258,12 +258,12 @@ void normalCase() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat(getString().trim()).isEmpty(); } - + String getString() { return "hello world"; } @@ -271,12 +271,12 @@ String getString() { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat(getString()).isBlank(); } - + String getString() { return "hello world"; } @@ -297,7 +297,7 @@ void stringContains() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat("hello world".contains("lo wo")).isTrue(); @@ -307,7 +307,7 @@ void testMethod() { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat("hello world").contains("lo wo"); @@ -319,6 +319,49 @@ void testMethod() { ); } + @Test + void stringContainsObjectMethod() { + rewriteRun( + spec -> spec.recipes( + new SimplifyChainedAssertJAssertion("contains", "isTrue", "contains", "java.lang.String")), + //language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Pojo { + public String getString() { + return "lo wo"; + } + } + + class MyTest { + void testMethod() { + var pojo = new Pojo(); + assertThat("hello world".contains(pojo.getString())).isTrue(); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Pojo { + public String getString() { + return "lo wo"; + } + } + + class MyTest { + void testMethod() { + var pojo = new Pojo(); + assertThat("hello world").contains(pojo.getString()); + } + } + """ + ) + ); + } + @Test void mapMethodDealsWithTwoArguments() { rewriteRun( @@ -328,16 +371,16 @@ void mapMethodDealsWithTwoArguments() { """ import java.util.Collections; import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { String key = "key"; String value = "value"; assertThat(getMap().get(key)).isEqualTo(value); } - + Map getMap() { return Collections.emptyMap(); } @@ -346,16 +389,16 @@ Map getMap() { """ import java.util.Collections; import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { String key = "key"; String value = "value"; assertThat(getMap()).containsEntry(key, value); } - + Map getMap() { return Collections.emptyMap(); } @@ -373,9 +416,9 @@ void keySetContainsWithMultipleArguments() { java( """ import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod(Map map) { // we don't yet support `containsKeys` @@ -395,12 +438,12 @@ void isNotEmptyTest() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat(getString().isEmpty()).isFalse(); } - + String getString() { return "hello world"; } @@ -408,12 +451,12 @@ String getString() { """, """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat(getString()).isNotEmpty(); } - + String getString() { return "hello world"; } @@ -431,12 +474,12 @@ void doesNoRunOnWrongCombination() { java( """ import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { void testMethod() { assertThat(getString().isBlank()).isFalse(); } - + String getString() { return "hello world"; } @@ -463,7 +506,7 @@ void simplifyPresenceAssertion() { """ import static org.assertj.core.api.Assertions.assertThat; import java.util.Optional; - + class Test { void simpleTest(Optional o) { assertThat(o.isPresent()).isTrue(); @@ -476,7 +519,7 @@ void simpleTest(Optional o) { """ import static org.assertj.core.api.Assertions.assertThat; import java.util.Optional; - + class Test { void simpleTest(Optional o) { assertThat(o).isPresent(); @@ -502,7 +545,7 @@ void simplifiyEqualityAssertion() { """ import static org.assertj.core.api.Assertions.assertThat; import java.util.Optional; - + class Test { void simpleTest(Optional o) { assertThat(o.get()).isEqualTo("foo"); @@ -513,7 +556,7 @@ void simpleTest(Optional o) { """ import static org.assertj.core.api.Assertions.assertThat; import java.util.Optional; - + class Test { void simpleTest(Optional o) { assertThat(o).contains("foo"); From 5f16e85a4a78a4e884e1ac07b55d391e21f91b46 Mon Sep 17 00:00:00 2001 From: Nate Danner Date: Fri, 1 Nov 2024 21:08:01 +0000 Subject: [PATCH 08/20] refactor: Standardize `.editorconfig` Use this link to re-run the recipe: https://app.moderne.io/builder/N3MJaFjMF?organizationId=T3BlblJld3JpdGU%3D Co-authored-by: Moderne --- .editorconfig | 9 +++++++++ src/test/java/org/openrewrite/java/testing/.editorconfig | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 .editorconfig delete mode 100644 src/test/java/org/openrewrite/java/testing/.editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..7a7ad4c70 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +insert_final_newline = true +trim_trailing_whitespace = true + +[src/test*/java/**.java] +indent_size = 4 +ij_continuation_indent_size = 2 diff --git a/src/test/java/org/openrewrite/java/testing/.editorconfig b/src/test/java/org/openrewrite/java/testing/.editorconfig deleted file mode 100644 index a4824935e..000000000 --- a/src/test/java/org/openrewrite/java/testing/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -root = true - -[*.java] -indent_size = 4 -ij_continuation_indent_size = 2 From 56fffc0323b0074c3353a5089f00a87db84421bd Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sat, 2 Nov 2024 13:07:00 +0100 Subject: [PATCH 09/20] Do not print template before use --- .../java/testing/junit5/MockitoJUnitToMockitoExtension.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java index 177b361bf..16939a286 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java @@ -121,7 +121,6 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex } if (!strictness.contains("STRICT_STUBS")) { cd = JavaTemplate.builder("@MockitoSettings(strictness = " + strictness + ")") - .doBeforeParseTemplate(System.out::println) .javaParser(JavaParser.fromJavaVersion() .classpathFromResources(ctx, "junit-jupiter-api-5.9", "mockito-junit-jupiter-3.12")) .imports("org.mockito.junit.jupiter.MockitoSettings", "org.mockito.quality.Strictness") From 72232f7e2e277da377c6d04b8c475f7459079f31 Mon Sep 17 00:00:00 2001 From: SiBorea <108953913+SiBorea@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:08:59 +0800 Subject: [PATCH 10/20] Add JMockit's MockUp to Mockito migration (#599) * Add JMockit's MockUp to Mockito migration * Adopt mockito-core:5.+ through parserClasspath * Apply bot code suggestions * Add type check for new class statement * Fix statement construction * Remove debug statement * Fix code format * Use CallRealMethod for non mock methods * Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add description about private methods * Update JMockitMockUpToMockitoTest.java * Fix UT to match gralde build order * Add setUp/tearDown support * Add MockUp params support * Remove useless * Fix JavaTemplate type inference * Remove unused local variables * MockUp to try-with-resource * Add import class test * Add multiple mockUp test * Apply bot suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Remove useless type cast * Fix variable naming * Remove fqn from naming * Remove unneeded method call * Shorter var naming * Update JMockitMockUpToMockitoTest.java * Refactor mockup migration to resuse existing code and cleanup * Resolve deprecated print() * Utilize LambdaBlockToExpression * Make constants * Apply bot suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Apply formatter and fix compilation * Place binary operators on the same line * Add missing language hints * Adopt AtomicReference instead of array element zero * Fix workflow build * Update src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Use mockito-core-3 instead of mockito-core-5 * Inline constants used once; Explicitly name reference var * Use now public VariableReferences.findRhsReferences --------- Co-authored-by: Tim te Beek Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek Co-authored-by: Shivani Sharma --- .../jmockit/JMockitMockUpToMockito.java | 532 ++++++++++++++ .../java/testing/mockito/MockitoUtils.java | 93 +++ .../PowerMockitoMockStaticToMockito.java | 58 +- .../resources/META-INF/rewrite/jmockit.yml | 1 + .../jmockit/JMockitMockUpToMockitoTest.java | 653 ++++++++++++++++++ ...kitNonStrictExpectationsToMockitoTest.java | 255 ++++--- .../testing/jmockit/JMockitTestUtils.java | 1 + 7 files changed, 1410 insertions(+), 183 deletions(-) create mode 100644 src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java create mode 100644 src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java create mode 100644 src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java new file mode 100644 index 000000000..fb942c504 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java @@ -0,0 +1,532 @@ +/* + * 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.openrewrite.java.testing.jmockit; + +import org.openrewrite.*; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.staticanalysis.LambdaBlockToExpression; +import org.openrewrite.staticanalysis.VariableReferences; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static org.openrewrite.java.testing.mockito.MockitoUtils.maybeAddMethodWithAnnotation; +import static org.openrewrite.java.tree.Flag.Private; +import static org.openrewrite.java.tree.Flag.Static; + +public class JMockitMockUpToMockito extends Recipe { + + private static final String JMOCKIT_MOCKUP_IMPORT = "mockit.MockUp"; + private static final String JMOCKIT_MOCK_IMPORT = "mockit.Mock"; + + private static final String MOCKITO_CLASSPATH = "mockito-core-3"; + private static final String MOCKITO_ALL_IMPORT = "org.mockito.Mockito.*"; + private static final String MOCKITO_MATCHER_IMPORT = "org.mockito.ArgumentMatchers.*"; + private static final String MOCKITO_DELEGATEANSWER_IMPORT = "org.mockito.AdditionalAnswers.delegatesTo"; + private static final String MOCKITO_STATIC_PREFIX = "mockStatic"; + private static final String MOCKITO_STATIC_IMPORT = "org.mockito.MockedStatic"; + private static final String MOCKITO_MOCK_PREFIX = "mock"; + private static final String MOCKITO_CONSTRUCTION_PREFIX = "mockCons"; + private static final String MOCKITO_CONSTRUCTION_IMPORT = "org.mockito.MockedConstruction"; + + @Override + public String getDisplayName() { + return "Rewrite JMockit MockUp to Mockito statements"; + } + + @Override + public String getDescription() { + return "Rewrites JMockit `MockUp` blocks to Mockito statements. This recipe will not rewrite private methods in MockUp."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(JMOCKIT_MOCKUP_IMPORT, false), new JMockitMockUpToMockitoVisitor()); + } + + private static class JMockitMockUpToMockitoVisitor extends JavaIsoVisitor { + private final Map tearDownMocks = new HashMap<>(); + + /** + * Handle at class level because need to handle the case where when there is a MockUp in a setup method, and we + * need to close the migrated mockCons in the teardown, yet the teardown method comes before the setup method + */ + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + // Handle @Before/@BeforeEach mockUp + Set mds = TreeVisitor.collect( + new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration md, ExecutionContext ctx) { + if (isSetUpMethod(md)) { + return SearchResult.found(md); + } + return super.visitMethodDeclaration(md, ctx); + } + }, + classDecl, + new HashSet<>() + ) + .stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .collect(Collectors.toSet()); + if (mds.isEmpty()) { + return super.visitClassDeclaration(classDecl, ctx); + } + + AtomicReference cdRef = new AtomicReference<>(classDecl); + mds.forEach(md -> md.getBody() + .getStatements() + .stream() + .filter(this::isMockUpStatement) + .map(J.NewClass.class::cast) + .forEach(newClass -> { + String className = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).toString(); + + Map mockedMethods = getMockUpMethods(newClass); + + // Add mockStatic field + if (mockedMethods.values().stream().anyMatch(m -> m.getFlags().contains(Static))) { + cdRef.set(JavaTemplate.builder("private MockedStatic #{};") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + new Cursor(getCursor().getParentOrThrow(), cdRef.get()), + cdRef.get().getBody().getCoordinates().firstStatement(), + MOCKITO_STATIC_PREFIX + className + )); + J.VariableDeclarations mockField = (J.VariableDeclarations) cdRef.get().getBody().getStatements().get(0); + J.Identifier mockFieldId = mockField.getVariables().get(0).getName(); + tearDownMocks.put(MOCKITO_STATIC_PREFIX + className, mockFieldId); + } + // Add mockConstruction field + if (mockedMethods.values().stream().anyMatch(m -> !m.getFlags().contains(Static))) { + cdRef.set(JavaTemplate.builder("private MockedConstruction #{};") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_CONSTRUCTION_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + updateCursor(cdRef.get()), + cdRef.get().getBody().getCoordinates().firstStatement(), + MOCKITO_CONSTRUCTION_PREFIX + className + )); + J.VariableDeclarations mockField = (J.VariableDeclarations) cdRef.get().getBody().getStatements().get(0); + J.Identifier mockFieldId = mockField.getVariables().get(0).getName(); + tearDownMocks.put(MOCKITO_CONSTRUCTION_PREFIX + className, mockFieldId); + } + })); + + J.ClassDeclaration cd = maybeAddMethodWithAnnotation(this, cdRef.get(), ctx, true, "tearDown", + "@org.junit.After", + "@After", + "junit-4.13", + "org.junit.After", + ""); + + return super.visitClassDeclaration(cd, ctx); + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl, ExecutionContext ctx) { + J.MethodDeclaration md = methodDecl; + if (md.getBody() == null) { + return md; + } + if (isTearDownMethod(md)) { + for (J.Identifier id : tearDownMocks.values()) { + String type = TypeUtils.asFullyQualified(id.getFieldType().getType()).getFullyQualifiedName(); + md = JavaTemplate.builder("#{any(" + type + ")}.closeOnDemand();") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT, MOCKITO_CONSTRUCTION_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + updateCursor(md), + md.getBody().getCoordinates().lastStatement(), + id + ); + } + return md; + } + + boolean isBeforeTest = isSetUpMethod(md); + List varDeclarationInTry = new ArrayList<>(); + List mockStaticMethodInTry = new ArrayList<>(); + List mockConstructionMethodInTry = new ArrayList<>(); + List encloseStatements = new ArrayList<>(); + List residualStatements = new ArrayList<>(); + for (Statement statement : md.getBody().getStatements()) { + if (!isMockUpStatement(statement)) { + encloseStatements.add(statement); + continue; + } + + J.NewClass newClass = (J.NewClass) statement; + + // Only discard @Mock method declarations + residualStatements.addAll(newClass + .getBody() + .getStatements() + .stream() + .filter(s -> { + if (s instanceof J.MethodDeclaration) { + return ((J.MethodDeclaration) s).getLeadingAnnotations().stream() + .noneMatch(o -> TypeUtils.isOfClassType(o.getType(), JMOCKIT_MOCK_IMPORT)); + } + return true; + }) + .collect(toList()) + ); + + JavaType mockType = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).getType(); + String className = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).toString(); + + Map mockedMethods = getMockUpMethods(newClass); + + // Add MockStatic + Map mockedPublicStaticMethods = mockedMethods + .entrySet() + .stream() + .filter(m -> m.getValue().getFlags().contains(Static)) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (!mockedPublicStaticMethods.isEmpty()) { + if (isBeforeTest) { + String tpl = getMockStaticDeclarationInBefore(className) + + getMockStaticMethods((JavaType.Class) mockType, className, mockedPublicStaticMethods); + + md = JavaTemplate.builder(tpl) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + updateCursor(md), + statement.getCoordinates().after(), + tearDownMocks.get(MOCKITO_STATIC_PREFIX + className) + ); + } else { + varDeclarationInTry.add(getMockStaticDeclarationInTry(className)); + mockStaticMethodInTry.add(getMockStaticMethods((JavaType.Class) mockType, className, mockedPublicStaticMethods)); + } + + maybeAddImport(MOCKITO_STATIC_IMPORT); + } + + // Add MockConstruction + Map mockedPublicMethods = mockedMethods + .entrySet() + .stream() + .filter(m -> !m.getValue().getFlags().contains(Static)) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (!mockedPublicMethods.isEmpty()) { + if (isBeforeTest) { + String tpl = getMockConstructionMethods(className, mockedPublicMethods) + + getMockConstructionDeclarationInBefore(className); + + md = JavaTemplate.builder(tpl) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT, MOCKITO_DELEGATEANSWER_IMPORT) + .build() + .apply( + updateCursor(md), + statement.getCoordinates().after(), + tearDownMocks.get(MOCKITO_CONSTRUCTION_PREFIX + className) + ); + } else { + varDeclarationInTry.add(getMockConstructionDeclarationInTry(className)); + mockConstructionMethodInTry.add(getMockConstructionMethods(className, mockedPublicMethods)); + } + + maybeAddImport(MOCKITO_CONSTRUCTION_IMPORT); + maybeAddImport("org.mockito.Answers", "CALLS_REAL_METHODS", false); + maybeAddImport("org.mockito.AdditionalAnswers", "delegatesTo", false); + } + + List statements = md.getBody().getStatements(); + statements.remove(statement); + md = md.withBody(md.getBody().withStatements(statements)); + } + + if (!varDeclarationInTry.isEmpty()) { + String tpl = String.join("", mockConstructionMethodInTry) + + "try (" + + String.join(";", varDeclarationInTry) + + ") {" + + String.join(";", mockStaticMethodInTry) + + "}"; + + J.MethodDeclaration residualMd = md.withBody(md.getBody().withStatements(residualStatements)); + residualMd = JavaTemplate.builder(tpl) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT, MOCKITO_CONSTRUCTION_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT, MOCKITO_MATCHER_IMPORT, MOCKITO_MATCHER_IMPORT, MOCKITO_DELEGATEANSWER_IMPORT) + .build() + .apply(updateCursor(residualMd), residualMd.getBody().getCoordinates().lastStatement()); + + List mdStatements = residualMd.getBody().getStatements(); + J.Try try_ = (J.Try) mdStatements.get(mdStatements.size() - 1); + + List tryStatements = try_.getBody().getStatements(); + tryStatements.addAll(encloseStatements); + try_ = try_.withBody(try_.getBody().withStatements(tryStatements)); + + mdStatements.set(mdStatements.size() - 1, try_); + md = md.withBody(residualMd.getBody().withStatements(mdStatements)); + } + + maybeAddImport(MOCKITO_ALL_IMPORT.replace(".*", ""), "*", false); + maybeRemoveImport(JMOCKIT_MOCK_IMPORT); + maybeRemoveImport(JMOCKIT_MOCKUP_IMPORT); + + doAfterVisit(new LambdaBlockToExpression().getVisitor()); + return maybeAutoFormat(methodDecl, md, ctx); + } + + private String getMatcher(JavaType s) { + maybeAddImport(MOCKITO_MATCHER_IMPORT.replace(".*", ""), "*", false); + if (s instanceof JavaType.Primitive) { + switch (s.toString()) { + case "int": + return "anyInt()"; + case "long": + return "anyLong()"; + case "double": + return "anyDouble()"; + case "float": + return "anyFloat()"; + case "short": + return "anyShort()"; + case "byte": + return "anyByte()"; + case "char": + return "anyChar()"; + case "boolean": + return "anyBoolean()"; + } + } else if (s instanceof JavaType.Array) { + String elem = TypeUtils.asArray(s).getElemType().toString(); + return "nullable(" + elem + "[].class)"; + } + return "nullable(" + TypeUtils.asFullyQualified(s).getClassName() + ".class)"; + } + + private String getAnswerBody(J.MethodDeclaration md) { + Set usedVariables = new HashSet<>(); + new JavaIsoVisitor>() { + @Override + public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, Set ctx) { + Cursor scope = getCursor().dropParentUntil((is) -> is instanceof J.ClassDeclaration || is instanceof J.Block || is instanceof J.MethodDeclaration || is instanceof J.ForLoop || is instanceof J.ForEachLoop || is instanceof J.ForLoop.Control || is instanceof J.ForEachLoop.Control || is instanceof J.Case || is instanceof J.Try || is instanceof J.Try.Resource || is instanceof J.Try.Catch || is instanceof J.MultiCatch || is instanceof J.Lambda || is instanceof JavaSourceFile); + if (!VariableReferences.findRhsReferences(scope.getValue(), variable.getName()).isEmpty()) { + ctx.add(variable.getSimpleName()); + } + return super.visitVariable(variable, ctx); + } + }.visit(md, usedVariables); + + StringBuilder sb = new StringBuilder(); + List parameters = md.getParameters(); + for (int i = 0; i < parameters.size(); i++) { + if (!(parameters.get(i) instanceof J.VariableDeclarations)) { + continue; + } + J.VariableDeclarations vd = (J.VariableDeclarations) parameters.get(i); + String className; + if (vd.getType() instanceof JavaType.Primitive) { + className = vd.getType().toString(); + } else { + className = vd.getTypeAsFullyQualified().getClassName(); + } + String varName = vd.getVariables().get(0).getName().getSimpleName(); + if (usedVariables.contains(varName)) { + sb.append(className).append(" ").append(varName) + .append(" = invocation.getArgument(").append(i).append(");"); + } + } + + boolean hasReturn = false; + for (Statement s : md.getBody().getStatements()) { + hasReturn |= s instanceof J.Return; + sb.append(s.print(getCursor())).append(";"); + } + // Avoid syntax error + if (!hasReturn) { + sb.append("return null;"); + } + return sb.toString(); + } + + private String getCallRealMethod(JavaType.Method m) { + return "(" + + m.getParameterTypes() + .stream() + .map(this::getMatcher) + .collect(Collectors.joining(", ")) + + ")).thenCallRealMethod();"; + } + + private String getMockStaticDeclarationInBefore(String className) { + return "#{any(" + MOCKITO_STATIC_IMPORT + ")}" + + " = mockStatic(" + className + ".class);"; + } + + private String getMockStaticDeclarationInTry(String className) { + return "MockedStatic " + MOCKITO_STATIC_PREFIX + className + + " = mockStatic(" + className + ".class)"; + } + + private String getMockStaticMethods(JavaType.Class clazz, String className, Map mockedMethods) { + StringBuilder tpl = new StringBuilder(); + + // To generate predictable method order + List keys = mockedMethods.keySet().stream() + .sorted(Comparator.comparing(o -> o.print(getCursor()))) + .collect(toList()); + for (J.MethodDeclaration m : keys) { + tpl.append("mockStatic").append(className) + .append(".when(() -> ").append(className).append(".").append(m.getSimpleName()).append("(") + .append(m.getParameters() + .stream() + .filter(J.VariableDeclarations.class::isInstance) + .map(J.VariableDeclarations.class::cast) + .map(J.VariableDeclarations::getType) + .map(this::getMatcher) + .collect(Collectors.joining(", ")) + ) + .append(")).thenAnswer(invocation -> {") + .append(getAnswerBody(m)) + .append("});"); + } + + // Call real method for non private, static methods + clazz.getMethods() + .stream() + .filter(m -> !m.isConstructor()) + .filter(m -> !m.getFlags().contains(Private)) + .filter(m -> m.getFlags().contains(Static)) + .filter(m -> !mockedMethods.containsValue(m)) + .forEach(m -> tpl.append("mockStatic").append(className).append(".when(() -> ") + .append(className).append(".").append(m.getName()) + .append(getCallRealMethod(m)) + .append(");") + ); + + return tpl.toString(); + } + + private String getMockConstructionDeclarationInBefore(String className) { + return "#{any(" + MOCKITO_CONSTRUCTION_IMPORT + ")}" + + " = mockConstructionWithAnswer(" + className + ".class, delegatesTo(" + MOCKITO_MOCK_PREFIX + className + "));"; + } + + private String getMockConstructionDeclarationInTry(String className) { + return "MockedConstruction " + MOCKITO_CONSTRUCTION_PREFIX + className + + " = mockConstructionWithAnswer(" + className + ".class, delegatesTo(" + MOCKITO_MOCK_PREFIX + className + "))"; + } + + private String getMockConstructionMethods(String className, Map mockedMethods) { + StringBuilder tpl = new StringBuilder() + .append(className) + .append(" ") + .append(MOCKITO_MOCK_PREFIX).append(className) + .append(" = mock(").append(className).append(".class, CALLS_REAL_METHODS);"); + + mockedMethods + .keySet() + .stream() + .sorted(Comparator.comparing(o -> o.print(getCursor()))) + .forEach(m -> tpl.append("doAnswer(invocation -> {") + .append(getAnswerBody(m)) + .append("}).when(").append(MOCKITO_MOCK_PREFIX).append(className).append(").").append(m.getSimpleName()).append("(") + .append(m.getParameters() + .stream() + .filter(J.VariableDeclarations.class::isInstance) + .map(J.VariableDeclarations.class::cast) + .map(J.VariableDeclarations::getType) + .map(this::getMatcher) + .collect(Collectors.joining(", ")) + ) + .append(");")); + + return tpl.toString(); + } + + private boolean isMockUpStatement(Tree tree) { + return tree instanceof J.NewClass && + ((J.NewClass) tree).getClazz() != null && + TypeUtils.isOfClassType(((J.NewClass) tree).getClazz().getType(), JMOCKIT_MOCKUP_IMPORT); + } + + private boolean isSetUpMethod(J.MethodDeclaration md) { + return md + .getLeadingAnnotations() + .stream() + .anyMatch(o -> TypeUtils.isOfClassType(o.getType(), "org.junit.Before")); + } + + private boolean isTearDownMethod(J.MethodDeclaration md) { + return md + .getLeadingAnnotations() + .stream() + .anyMatch(o -> TypeUtils.isOfClassType(o.getType(), "org.junit.After")); + } + + private Map getMockUpMethods(J.NewClass newClass) { + JavaType mockType = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).getType(); + return newClass.getBody() + .getStatements() + .stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .filter(s -> s.getLeadingAnnotations().stream() + .anyMatch(o -> TypeUtils.isOfClassType(o.getType(), JMOCKIT_MOCK_IMPORT))) + .map(method -> { + Optional found = TypeUtils.findDeclaredMethod( + TypeUtils.asFullyQualified(mockType), + method.getSimpleName(), + method.getMethodType().getParameterTypes() + ); + if (found.isPresent()) { + JavaType.Method m = found.get(); + if (!m.getFlags().contains(Private)) { + return new AbstractMap.SimpleEntry<>(method, found.get()); + } + } + return null; + }) + .filter(Objects::nonNull) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java b/src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java new file mode 100644 index 000000000..ad143e008 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java @@ -0,0 +1,93 @@ +/* + * 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.openrewrite.java.testing.mockito; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Statement; + +import java.util.List; +import java.util.stream.Collectors; + +public class MockitoUtils { + public static J.ClassDeclaration maybeAddMethodWithAnnotation( + JavaVisitor visitor, + J.ClassDeclaration classDecl, + ExecutionContext ctx, + boolean isPublic, + String methodName, + String methodAnnotationSignature, + String methodAnnotationToAdd, + String additionalClasspathResource, + String importToAdd, + String methodAnnotationParameters + ) { + if (hasMethodWithAnnotation(classDecl, new AnnotationMatcher(methodAnnotationSignature))) { + return classDecl; + } + + J.MethodDeclaration firstTestMethod = getFirstTestMethod( + classDecl.getBody().getStatements().stream().filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast).collect(Collectors.toList())); + + visitor.maybeAddImport(importToAdd); + String tplStr = methodAnnotationToAdd + methodAnnotationParameters + + (isPublic ? " public" : "") + " void " + methodName + "() {}"; + return JavaTemplate.builder(tplStr) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, additionalClasspathResource)) + .imports(importToAdd) + .build() + .apply( + new Cursor(visitor.getCursor().getParentOrThrow(), classDecl), + (firstTestMethod != null) ? + firstTestMethod.getCoordinates().before() : + classDecl.getBody().getCoordinates().lastStatement() + ); + } + + private static boolean hasMethodWithAnnotation(J.ClassDeclaration classDecl, AnnotationMatcher annotationMatcher) { + for (Statement statement : classDecl.getBody().getStatements()) { + if (statement instanceof J.MethodDeclaration) { + J.MethodDeclaration methodDeclaration = (J.MethodDeclaration) statement; + List allAnnotations = methodDeclaration.getAllAnnotations(); + for (J.Annotation annotation : allAnnotations) { + if (annotationMatcher.matches(annotation)) { + return true; + } + } + } + } + return false; + } + + private static J.@Nullable MethodDeclaration getFirstTestMethod(List methods) { + for (J.MethodDeclaration methodDeclaration : methods) { + for (J.Annotation annotation : methodDeclaration.getLeadingAnnotations()) { + if ("Test".equals(annotation.getSimpleName())) { + return methodDeclaration; + } + } + } + return null; + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java index aa7cb417d..dbc259a8e 100644 --- a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java +++ b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java @@ -26,6 +26,8 @@ import java.util.*; import java.util.stream.Collectors; +import static org.openrewrite.java.testing.mockito.MockitoUtils.maybeAddMethodWithAnnotation; + public class PowerMockitoMockStaticToMockito extends Recipe { @Override @@ -213,32 +215,6 @@ private static boolean isFieldAlreadyDefined(J.Block classBody, String fieldName return false; } - private static J.@Nullable MethodDeclaration getFirstTestMethod(List methods) { - for (J.MethodDeclaration methodDeclaration : methods) { - for (J.Annotation annotation : methodDeclaration.getLeadingAnnotations()) { - if ("Test".equals(annotation.getSimpleName())) { - return methodDeclaration; - } - } - } - return null; - } - - private static boolean hasMethodWithAnnotation(J.ClassDeclaration classDecl, AnnotationMatcher annotationMatcher) { - for (Statement statement : classDecl.getBody().getStatements()) { - if (statement instanceof J.MethodDeclaration) { - J.MethodDeclaration methodDeclaration = (J.MethodDeclaration) statement; - List allAnnotations = methodDeclaration.getAllAnnotations(); - for (J.Annotation annotation : allAnnotations) { - if (annotationMatcher.matches(annotation)) { - return true; - } - } - } - } - return false; - } - private static boolean isStaticMockAlreadyClosed(J.Identifier staticMock, J.Block methodBody) { for (Statement statement : methodBody.getStatements()) { if (statement instanceof J.MethodInvocation) { @@ -465,7 +441,7 @@ private J.ClassDeclaration addFieldDeclarationForMockedTypes(J.ClassDeclaration private J.ClassDeclaration maybeAddSetUpMethodBody(J.ClassDeclaration classDecl, ExecutionContext ctx) { String testGroupsAsString = getTestGroupsAsString(); - return maybeAddMethodWithAnnotation(classDecl, ctx, "setUpStaticMocks", + return maybeAddMethodWithAnnotation(this, classDecl, ctx, false, "setUpStaticMocks", setUpMethodAnnotationSignature, setUpMethodAnnotation, additionalClasspathResource, setUpImportToAdd, testGroupsAsString); } @@ -483,38 +459,12 @@ private String getTestGroupsAsString() { private J.ClassDeclaration maybeAddTearDownMethodBody(J.ClassDeclaration classDecl, ExecutionContext ctx) { String testGroupsAsString = (getTestGroupsAsString().isEmpty()) ? tearDownMethodAnnotationParameters : getTestGroupsAsString(); - return maybeAddMethodWithAnnotation(classDecl, ctx, "tearDownStaticMocks", + return maybeAddMethodWithAnnotation(this, classDecl, ctx, false, "tearDownStaticMocks", tearDownMethodAnnotationSignature, tearDownMethodAnnotation, additionalClasspathResource, tearDownImportToAdd, testGroupsAsString); } - private J.ClassDeclaration maybeAddMethodWithAnnotation(J.ClassDeclaration classDecl, ExecutionContext ctx, - String methodName, String methodAnnotationSignature, - String methodAnnotationToAdd, - String additionalClasspathResource, String importToAdd, - String methodAnnotationParameters) { - if (hasMethodWithAnnotation(classDecl, new AnnotationMatcher(methodAnnotationSignature))) { - return classDecl; - } - - J.MethodDeclaration firstTestMethod = getFirstTestMethod( - classDecl.getBody().getStatements().stream().filter(J.MethodDeclaration.class::isInstance) - .map(J.MethodDeclaration.class::cast).collect(Collectors.toList())); - - maybeAddImport(importToAdd); - return JavaTemplate.builder(methodAnnotationToAdd + methodAnnotationParameters + " void " + methodName + "() {}") - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, additionalClasspathResource)) - .imports(importToAdd) - .build() - .apply( - new Cursor(getCursor().getParentOrThrow(), classDecl), - (firstTestMethod != null) ? - firstTestMethod.getCoordinates().before() : - classDecl.getBody().getCoordinates().lastStatement() - ); - } private J.MethodInvocation modifyWhenMethodInvocation(J.MethodInvocation whenMethod) { List methodArguments = whenMethod.getArguments(); diff --git a/src/main/resources/META-INF/rewrite/jmockit.yml b/src/main/resources/META-INF/rewrite/jmockit.yml index a15136715..d1ee335d7 100644 --- a/src/main/resources/META-INF/rewrite/jmockit.yml +++ b/src/main/resources/META-INF/rewrite/jmockit.yml @@ -23,6 +23,7 @@ tags: - jmockit recipeList: - org.openrewrite.java.testing.jmockit.JMockitBlockToMockito + - org.openrewrite.java.testing.jmockit.JMockitMockUpToMockito - org.openrewrite.java.testing.jmockit.JMockitAnnotatedArgumentToMockito - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: mockit.Mocked diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java new file mode 100644 index 000000000..49c9ed48b --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java @@ -0,0 +1,653 @@ +/* + * 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.openrewrite.java.testing.jmockit; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.TypeValidation; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.*; + +class JMockitMockUpToMockitoTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + setParserSettings(spec, JMOCKIT_DEPENDENCY, JUNIT_4_DEPENDENCY); + } + + @DocumentExample + @Test + void mockUpStaticMethodTest() { + //language=java + rewriteRun( + java( + """ + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + import org.junit.Test; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + + @Mock + public int staticMethod() { + return 1024; + } + + @Mock + public int staticMethod(int v) { + return 128; + } + }; + assertEquals(1024, MyClazz.staticMethod()); + assertEquals(128, MyClazz.staticMethod(0)); + } + + public static class MyClazz { + public static int staticMethod() { + return 0; + } + + public static int staticMethod(int v) { + return 1; + } + } + } + """, """ + import static org.junit.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + import org.junit.Test; + import org.mockito.MockedStatic; + + public class MockUpTest { + @Test + public void test() { + try (MockedStatic mockStaticMyClazz = mockStatic(MyClazz.class)) { + mockStaticMyClazz.when(() -> MyClazz.staticMethod()).thenAnswer(invocation -> 1024); + mockStaticMyClazz.when(() -> MyClazz.staticMethod(anyInt())).thenAnswer(invocation -> 128); + assertEquals(1024, MyClazz.staticMethod()); + assertEquals(128, MyClazz.staticMethod(0)); + } + } + + public static class MyClazz { + public static int staticMethod() { + return 0; + } + + public static int staticMethod(int v) { + return 1; + } + } + } + """)); + } + + @Test + void mockUpMultipleTest() { + //language=java + rewriteRun( + spec -> spec.afterTypeValidationOptions(TypeValidation.builder().identifiers(false).build()), + java( + """ + package com.openrewrite; + public static class Foo { + public String getMsg() { + return "foo"; + } + + public String getMsg(String echo) { + return "foo" + echo; + } + } + """, + SourceSpec::skip + ), + java( + """ + package com.openrewrite; + public static class Bar { + public String getMsg() { + return "bar"; + } + + public String getMsg(String echo) { + return "bar" + echo; + } + } + """, + SourceSpec::skip + ), + java( + """ + import com.openrewrite.Foo; + import com.openrewrite.Bar; + import org.junit.Test; + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + @Mock + public String getMsg() { + return "FOO"; + } + @Mock + public String getMsg(String echo) { + return "FOO" + echo; + } + }; + new MockUp() { + @Mock + public String getMsg() { + return "BAR"; + } + @Mock + public String getMsg(String echo) { + return "BAR" + echo; + } + }; + assertEquals("FOO", new Foo().getMsg()); + assertEquals("FOOecho", new Foo().getMsg("echo")); + assertEquals("BAR", new Bar().getMsg()); + assertEquals("BARecho", new Bar().getMsg("echo")); + } + } + """, """ + import com.openrewrite.Foo; + import com.openrewrite.Bar; + import org.junit.Test; + import org.mockito.MockedConstruction; + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + public class MockUpTest { + @Test + public void test() { + Foo mockFoo = mock(Foo.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "FOO").when(mockFoo).getMsg(); + doAnswer(invocation -> { + String echo = invocation.getArgument(0); + return "FOO" + echo; + }).when(mockFoo).getMsg(nullable(String.class)); + Bar mockBar = mock(Bar.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "BAR").when(mockBar).getMsg(); + doAnswer(invocation -> { + String echo = invocation.getArgument(0); + return "BAR" + echo; + }).when(mockBar).getMsg(nullable(String.class)); + try (MockedConstruction mockConsFoo = mockConstructionWithAnswer(Foo.class, delegatesTo(mockFoo));MockedConstruction mockConsBar = mockConstructionWithAnswer(Bar.class, delegatesTo(mockBar))) { + assertEquals("FOO", new Foo().getMsg()); + assertEquals("FOOecho", new Foo().getMsg("echo")); + assertEquals("BAR", new Bar().getMsg()); + assertEquals("BARecho", new Bar().getMsg("echo")); + } + } + } + """) + ); + } + + @Test + void mockUpInnerStatementTest() { + //language=java + rewriteRun( + java( + """ + import mockit.Mock; + import mockit.MockUp; + + import org.junit.Test; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + final String msg = "newMsg"; + + @Mock + public String getMsg() { + return msg; + } + }; + + // Should ignore the newClass statement + new Runnable() { + @Override + public void run() { + System.out.println("run"); + } + }; + assertEquals("newMsg", new MyClazz().getMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """, """ + import org.junit.Test; + import org.mockito.MockedConstruction; + + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.Mockito.*; + + public class MockUpTest { + @Test + public void test() { + final String msg = "newMsg"; + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> msg).when(mockMyClazz).getMsg(); + try (MockedConstruction mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz))) { + + // Should ignore the newClass statement + new Runnable() { + @Override + public void run() { + System.out.println("run"); + } + }; + assertEquals("newMsg", new MyClazz().getMsg()); + } + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """)); + } + + @Test + void mockUpVoidTest() { + //language=java + rewriteRun( + java( + """ + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + import org.junit.Test; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + @Mock + public void changeMsg() { + MockUpClass.Save.msg = "mockMsg"; + } + + @Mock + public void changeText(String text) { + MockUpClass.Save.text = "mockText"; + } + }; + + assertEquals("mockMsg", new MockUpClass().getMsg()); + assertEquals("mockText", new MockUpClass().getText()); + } + + public static class MockUpClass { + public static class Save { + public static String msg = "msg"; + public static String text = "text"; + } + + public final String getMsg() { + changeMsg(); + return Save.msg; + } + + public void changeMsg() { + Save.msg = "newMsg"; + } + + public String getText() { + changeText("newText"); + return Save.text; + } + + public static void changeText(String text) { + Save.text = text; + } + } + } + """, + """ + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + import org.junit.Test; + import org.mockito.MockedConstruction; + import org.mockito.MockedStatic; + + public class MockUpTest { + @Test + public void test() { + MockUpClass mockMockUpClass = mock(MockUpClass.class, CALLS_REAL_METHODS); + doAnswer(invocation -> { + MockUpClass.Save.msg = "mockMsg"; + return null; + }).when(mockMockUpClass).changeMsg(); + try (MockedStatic mockStaticMockUpClass = mockStatic(MockUpClass.class);MockedConstruction mockConsMockUpClass = mockConstructionWithAnswer(MockUpClass.class, delegatesTo(mockMockUpClass))) { + mockStaticMockUpClass.when(() -> MockUpClass.changeText(nullable(String.class))).thenAnswer(invocation -> { + String text = invocation.getArgument(0); + MockUpClass.Save.text = "mockText"; + return null; + }); + + assertEquals("mockMsg", new MockUpClass().getMsg()); + assertEquals("mockText", new MockUpClass().getText()); + } + } + + public static class MockUpClass { + public static class Save { + public static String msg = "msg"; + public static String text = "text"; + } + + public final String getMsg() { + changeMsg(); + return Save.msg; + } + + public void changeMsg() { + Save.msg = "newMsg"; + } + + public String getText() { + changeText("newText"); + return Save.text; + } + + public static void changeText(String text) { + Save.text = text; + } + } + } + """)); + } + + @Test + void mockUpAtSetUpWithoutTearDownTest() { + rewriteRun( + //language=java + java( + """ + import org.junit.Before; + import org.junit.Test; + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Before + public void setUp() { + new MockUp() { + @Mock + public String getMsg() { + return "mockMsg"; + } + }; + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """, + """ + import org.junit.After; + import org.junit.Before; + import org.junit.Test; + import org.mockito.MockedConstruction; + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.Mockito.*; + + public class MockUpTest { + private MockedConstruction mockConsMyClazz; + + @Before + public void setUp() { + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "mockMsg").when(mockMyClazz).getMsg(); + mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz)); + } + + @After + public void tearDown() { + mockConsMyClazz.closeOnDemand(); + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """ + ) + ); + } + + @Test + void mockUpAtSetUpWithTearDownTest() { + rewriteRun( + //language=java + java( + """ + import org.junit.Before; + import org.junit.After; + import org.junit.Test; + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Before + public void setUp() { + new MockUp() { + @Mock + public String getMsg() { + return "mockMsg"; + } + + @Mock + public String getStaticMsg() { + return "mockStaticMsg"; + } + }; + } + + @After + public void tearDown() { + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + assertEquals("mockStaticMsg", MyClazz.getStaticMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + + public static String getStaticMsg() { + return "staticMsg"; + } + } + } + """, + """ + import org.junit.Before; + import org.junit.After; + import org.junit.Test; + import org.mockito.MockedConstruction; + import org.mockito.MockedStatic; + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.Mockito.*; + + public class MockUpTest { + private MockedConstruction mockConsMyClazz; + private MockedStatic mockStaticMyClazz; + + @Before + public void setUp() { + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "mockMsg").when(mockMyClazz).getMsg(); + mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz)); + mockStaticMyClazz = mockStatic(MyClazz.class); + mockStaticMyClazz.when(() -> MyClazz.getStaticMsg()).thenAnswer(invocation -> "mockStaticMsg"); + } + + @After + public void tearDown() { + mockConsMyClazz.closeOnDemand(); + mockStaticMyClazz.closeOnDemand(); + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + assertEquals("mockStaticMsg", MyClazz.getStaticMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + + public static String getStaticMsg() { + return "staticMsg"; + } + } + } + """ + ) + ); + } + + @Test + void mockUpWithParamsTest() { + rewriteRun( + //language=java + java( + """ + import mockit.Mock; + import mockit.MockUp; + import org.junit.Test; + + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Test + public void init() { + new MockUp() { + @Mock + public String getMsg(String foo, String bar, String unused) { + return foo + bar; + } + }; + assertEquals("foobar", new MyClazz().getMsg("foo", "bar", "unused")); + } + + public static class MyClazz { + public String getMsg(String foo, String bar, String unused) { + return "msg"; + } + } + } + """, + """ + import org.junit.Test; + import org.mockito.MockedConstruction; + + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + public class MockUpTest { + @Test + public void init() { + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> { + String foo = invocation.getArgument(0); + String bar = invocation.getArgument(1); + return foo + bar; + }).when(mockMyClazz).getMsg(nullable(String.class), nullable(String.class), nullable(String.class)); + try (MockedConstruction mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz))) { + assertEquals("foobar", new MyClazz().getMsg("foo", "bar", "unused")); + } + } + + public static class MyClazz { + public String getMsg(String foo, String bar, String unused) { + return "msg"; + } + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitNonStrictExpectationsToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitNonStrictExpectationsToMockitoTest.java index bc186bb48..463746a26 100644 --- a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitNonStrictExpectationsToMockitoTest.java +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitNonStrictExpectationsToMockitoTest.java @@ -21,12 +21,9 @@ import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.MOCKITO_CORE_DEPENDENCY; -import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.setParserSettings; +import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.*; class JMockitNonStrictExpectationsToMockitoTest implements RewriteTest { - - private static final String JUNIT_4_DEPENDENCY = "junit-4.13.2"; private static final String LEGACY_JMOCKIT_DEPENDENCY = "jmockit-1.22"; @Override @@ -53,14 +50,14 @@ public String getSomeField() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertNull; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { new NonStrictExpectations() {{ myObject.getSomeField(); @@ -74,16 +71,16 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertNull; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { lenient().when(myObject.getSomeField()).thenReturn(null); assertNull(myObject.getSomeField()); @@ -113,14 +110,14 @@ public int getSomeField() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { new NonStrictExpectations() {{ myObject.getSomeField(); @@ -139,16 +136,16 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { lenient().when(myObject.getSomeField()).thenReturn(10); assertEquals(10, myObject.getSomeField()); @@ -180,14 +177,14 @@ public String getSomeField(String s) { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { new NonStrictExpectations() {{ myObject.getSomeField(anyString); @@ -201,15 +198,15 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { lenient().when(myObject.getSomeField(anyString())).thenReturn("foo"); assertEquals("foo", myObject.getSomeField("bar")); @@ -239,16 +236,16 @@ public String getSomeField() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + String expected = "expected"; - + void test() { new NonStrictExpectations() {{ myObject.getSomeField(); @@ -262,18 +259,18 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + String expected = "expected"; - + void test() { lenient().when(myObject.getSomeField()).thenReturn(expected); assertEquals(expected, myObject.getSomeField()); @@ -303,14 +300,14 @@ public Object getSomeField() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertNotNull; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { new NonStrictExpectations() {{ myObject.getSomeField(); @@ -324,16 +321,16 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { lenient().when(myObject.getSomeField()).thenReturn(new Object()); assertNotNull(myObject.getSomeField()); @@ -363,12 +360,12 @@ public String getSomeField() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() throws RuntimeException { new NonStrictExpectations() {{ myObject.getSomeField(); @@ -382,15 +379,15 @@ void test() throws RuntimeException { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() throws RuntimeException { lenient().when(myObject.getSomeField()).thenThrow(new RuntimeException()); myObject.getSomeField(); @@ -420,14 +417,14 @@ public String getSomeField() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() throws RuntimeException { new NonStrictExpectations() {{ myObject.getSomeField(); @@ -442,16 +439,16 @@ void test() throws RuntimeException { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() throws RuntimeException { lenient().when(myObject.getSomeField()).thenReturn("foo", "bar"); assertEquals("foo", myObject.getSomeField()); @@ -470,7 +467,7 @@ void whenClassArgumentMatcher() { java( """ import java.util.List; - + class MyObject { public String getSomeField(List input) { return "X"; @@ -485,19 +482,19 @@ public String getSomeOtherField(Object input) { """ import java.util.ArrayList; import java.util.List; - + import mockit.NonStrictExpectations; import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertNull; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { new NonStrictExpectations() {{ myObject.getSomeField((List) any); @@ -513,19 +510,19 @@ void test() { """ import java.util.ArrayList; import java.util.List; - + import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertNull; import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { lenient().when(myObject.getSomeField(anyList())).thenReturn(null); lenient().when(myObject.getSomeOtherField(any(Object.class))).thenReturn(null); @@ -545,7 +542,7 @@ void whenNoArguments() { java( """ import java.util.List; - + class MyObject { public String getSomeField() { return "X"; @@ -557,19 +554,19 @@ public String getSomeField() { """ import java.util.ArrayList; import java.util.List; - + import mockit.NonStrictExpectations; import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertNull; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { new NonStrictExpectations() {{ myObject.getSomeField(); @@ -582,20 +579,20 @@ void test() { """ import java.util.ArrayList; import java.util.List; - + import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertNull; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { lenient().when(myObject.getSomeField()).thenReturn(null); assertNull(myObject.getSomeField()); @@ -614,7 +611,7 @@ void whenMixedArgumentMatcher() { java( """ import java.util.List; - + class MyObject { public String getSomeField(String s, String s2, String s3, long l1) { return "X"; @@ -626,19 +623,19 @@ public String getSomeField(String s, String s2, String s3, long l1) { """ import java.util.ArrayList; import java.util.List; - + import mockit.NonStrictExpectations; import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertNull; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { String bazz = "bazz"; new NonStrictExpectations() {{ @@ -652,19 +649,19 @@ void test() { """ import java.util.ArrayList; import java.util.List; - + import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertNull; import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { String bazz = "bazz"; lenient().when(myObject.getSomeField(eq("foo"), anyString(), eq(bazz), eq(10L))).thenReturn(null); @@ -683,7 +680,7 @@ void whenSetupStatements() { java( """ class MyObject { - + public String getSomeField(String s) { return "X"; } @@ -699,26 +696,26 @@ public String getString() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { String a = "a"; String s = "s"; - + new NonStrictExpectations() {{ myObject.getSomeField(anyString); result = s; - + myObject.getString(); result = a; }}; - + assertEquals("s", myObject.getSomeField("foo")); assertEquals("a", myObject.getString()); } @@ -728,21 +725,21 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { String a = "a"; String s = "s"; lenient().when(myObject.getSomeField(anyString())).thenReturn(s); lenient().when(myObject.getString()).thenReturn(a); - + assertEquals("s", myObject.getSomeField("foo")); assertEquals("a", myObject.getString()); } @@ -771,14 +768,14 @@ public String getSomeField(String s) { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { String a = "a"; new NonStrictExpectations() {{ @@ -787,7 +784,7 @@ void test() { String b = "b"; result = s; }}; - + assertEquals("s", myObject.getSomeField("foo")); } } @@ -796,21 +793,21 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { String a = "a"; String s = "s"; String b = "b"; lenient().when(myObject.getSomeField(anyString())).thenReturn(s); - + assertEquals("s", myObject.getSomeField("foo")); } } @@ -838,14 +835,14 @@ public String getSomeField() { import mockit.Tested; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; - + @RunWith(JMockit.class) class MyTest { @Tested MyObject myObject; - + void test() { new NonStrictExpectations(myObject) {{ myObject.getSomeField(); @@ -859,16 +856,16 @@ void test() { import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @InjectMocks MyObject myObject; - + void test() { lenient().when(myObject.getSomeField()).thenReturn("foo"); assertEquals("foo", myObject.getSomeField()); @@ -905,18 +902,18 @@ public void doSomething() {} import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; - + @RunWith(JMockit.class) class MyTest { @Mocked Object myObject; - + @Mocked MyObject myOtherObject; - + void test() { new NonStrictExpectations() {{ myObject.hashCode(); @@ -938,19 +935,19 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock Object myObject; - + @Mock MyObject myOtherObject; - + void test() { lenient().when(myObject.hashCode()).thenReturn(10); lenient().when(myOtherObject.getSomeObjectField()).thenReturn(null); @@ -985,15 +982,15 @@ public String getSomeStringField() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; - + @RunWith(JMockit.class) class MyTest { @Mocked MyObject myObject; - + void test() { new NonStrictExpectations() {{ myObject.getSomeStringField(); @@ -1012,17 +1009,17 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock MyObject myObject; - + void test() { lenient().when(myObject.getSomeStringField()).thenReturn("a"); assertEquals("a", myObject.getSomeStringField()); @@ -1045,15 +1042,15 @@ void whenNoResultsNoTimes() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; - + @RunWith(JMockit.class) class MyTest { @Mocked Object myObject; - + void test() { new NonStrictExpectations() {{ myObject.wait(anyLong); @@ -1066,15 +1063,15 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock Object myObject; - + void test() { myObject.wait(1L); } @@ -1094,12 +1091,12 @@ void whenTimes() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + @RunWith(JMockit.class) class MyTest { @Mocked Object myObject; - + void test() { new NonStrictExpectations() {{ myObject.wait(anyLong, anyInt); @@ -1115,14 +1112,14 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock Object myObject; - + void test() { myObject.wait(10L, 10); myObject.wait(10L, 10); @@ -1145,12 +1142,12 @@ void whenTimesAndResult() { import mockit.Mocked; import mockit.integration.junit4.JMockit; import org.junit.runner.RunWith; - + @RunWith(JMockit.class) class MyTest { @Mocked Object myObject; - + void test() { new NonStrictExpectations() {{ myObject.toString(); @@ -1166,14 +1163,14 @@ void test() { import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - + import static org.mockito.Mockito.*; - + @RunWith(MockitoJUnitRunner.class) class MyTest { @Mock Object myObject; - + void test() { when(myObject.toString()).thenReturn("foo"); myObject.toString(); diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitTestUtils.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitTestUtils.java index c66d3fbb1..9c2ac5f31 100644 --- a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitTestUtils.java +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitTestUtils.java @@ -24,6 +24,7 @@ public class JMockitTestUtils { static final String MOCKITO_CORE_DEPENDENCY = "mockito-core-3.12"; static final String JUNIT_5_JUPITER_DEPENDENCY = "junit-jupiter-api-5.9"; + static final String JUNIT_4_DEPENDENCY = "junit-4.13.2"; static final String JMOCKIT_DEPENDENCY = "jmockit-1.49"; static final String MOCKITO_JUPITER_DEPENDENCY = "mockito-junit-jupiter-3.12"; From c4ab9a5c5cd0c0e8a992a02bf9a5177d68638a1d Mon Sep 17 00:00:00 2001 From: Jente Sondervorst Date: Sun, 3 Nov 2024 13:40:39 +0100 Subject: [PATCH 11/20] Initial support for DbRider migration from junit4 to junit5 (#627) * Initial draft of the ExecutionListenerToDbRiderAnnotation ScanningRecipe fixes #624 * Added missing import * Ran best practices * Finishing up on the declarative recipe * Finishing up on the declarative recipe * Minimize tests * Apply formatter * Review comment refactoring * Polish to static constructor & reduce visibility * Final polish: fix header year & only use ridr-junit5 in src/main --------- Co-authored-by: Jente Sondervorst Co-authored-by: Tim te Beek --- build.gradle.kts | 4 +- .../ExecutionListenerToDbRiderAnnotation.java | 265 ++++++++++++++++++ .../java/testing/dbrider/package-info.java | 21 ++ .../rewrite/classpath/rider-junit5-1.44.0.jar | Bin 0 -> 25791 bytes .../resources/META-INF/rewrite/dbrider.yml | 32 +++ .../resources/META-INF/rewrite/junit5.yml | 1 + ...onListenerToDbRiderAnnotationUnitTest.java | 233 +++++++++++++++ .../rewrite/classpath/rider-spring-1.18.0.jar | Bin 0 -> 7276 bytes 8 files changed, 553 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java create mode 100644 src/main/java/org/openrewrite/java/testing/dbrider/package-info.java create mode 100644 src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar create mode 100644 src/main/resources/META-INF/rewrite/dbrider.yml create mode 100644 src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java create mode 100644 src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar diff --git a/build.gradle.kts b/build.gradle.kts index 71a58d2a0..d29963509 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,7 @@ recipeDependencies { parserClasspath("org.powermock:powermock-core:1.7.+") parserClasspath("com.squareup.okhttp3:mockwebserver:4.10.0") parserClasspath("org.springframework:spring-test:6.1.12") + parserClasspath("com.github.database-rider:rider-junit5:1.44.0") } val rewriteVersion = rewriteRecipe.rewriteVersion.get() @@ -69,7 +70,4 @@ dependencies { testRuntimeOnly("org.mockito.kotlin:mockito-kotlin:latest.release") testRuntimeOnly("org.testcontainers:testcontainers:latest.release") testRuntimeOnly("org.testcontainers:nginx:latest.release") - -// testImplementation("org.hamcrest:hamcrest:latest.release") -// testImplementation("org.assertj:assertj-core:latest.release") } diff --git a/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java b/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java new file mode 100644 index 000000000..93c1ac817 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java @@ -0,0 +1,265 @@ +/* + * 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.openrewrite.java.testing.dbrider; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +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.Space; + +import java.util.Comparator; +import java.util.List; + +public class ExecutionListenerToDbRiderAnnotation extends Recipe { + + private static final AnnotationMatcher EXECUTION_LISTENER_ANNOTATION_MATCHER = new AnnotationMatcher("@org.springframework.test.context.TestExecutionListeners"); + private static final AnnotationMatcher DBRIDER_ANNOTATION_MATCHER = new AnnotationMatcher("@com.github.database.rider.junit5.api.DBRider"); + private static final String DBRIDER_TEST_EXECUTION_LISTENER = "com.github.database.rider.spring.DBRiderTestExecutionListener"; + + @Override + public String getDisplayName() { + return "Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation"; + } + + @Override + public String getDescription() { + return "Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation. " + + "This recipe is useful when migrating from JUnit 4 `dbrider-spring` to JUnit 5 `dbrider-junit5`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(DBRIDER_TEST_EXECUTION_LISTENER, true), new JavaIsoVisitor() { + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDeclaration, ExecutionContext ctx) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDeclaration, ctx); + DbRiderExecutionListenerContext context = DbRiderExecutionListenerContext.ofClass(cd); + if (!context.shouldMigrate()) { + return cd; + } + if (context.shouldAddDbRiderAnnotation()) { + cd = JavaTemplate.builder("@DBRider") + .imports("com.github.database.rider.junit5.api.DBRider") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "rider-junit5-1.44")) + .build() + .apply(getCursor(), cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + maybeAddImport("com.github.database.rider.junit5.api.DBRider"); + } + Space prefix = cd.getLeadingAnnotations().get(cd.getLeadingAnnotations().size() - 1).getPrefix(); + return cd.withLeadingAnnotations(ListUtils.map(cd.getLeadingAnnotations(), annotation -> { + if (annotation != null && EXECUTION_LISTENER_ANNOTATION_MATCHER.matches(annotation)) { + J.Annotation executionListenerAnnotation = context.getExecutionListenerAnnotation(); + maybeRemoveImport(DBRIDER_TEST_EXECUTION_LISTENER); + maybeRemoveImport("org.springframework.test.context.TestExecutionListeners.MergeMode"); + maybeRemoveImport("org.springframework.test.context.TestExecutionListeners"); + if (executionListenerAnnotation != null) { + return executionListenerAnnotation + .withArguments(firstItemPrefixWorkaround(executionListenerAnnotation.getArguments())) + .withPrefix(prefix); + } + return null; + } + return annotation; + })); + } + }); + } + + private static class DbRiderExecutionListenerContext { + private J.@Nullable Annotation testExecutionListenerAnnotation; + private boolean dbriderFound = false; + private J.@Nullable NewArray listeners; + private J.@Nullable FieldAccess listener; + private @Nullable Expression inheritListeners; + private @Nullable Expression mergeMode; + + static DbRiderExecutionListenerContext ofClass(J.ClassDeclaration clazz) { + DbRiderExecutionListenerContext context = new DbRiderExecutionListenerContext(); + clazz.getLeadingAnnotations().forEach(annotation -> { + if (EXECUTION_LISTENER_ANNOTATION_MATCHER.matches(annotation)) { + context.testExecutionListenersFound(annotation); + } else if (DBRIDER_ANNOTATION_MATCHER.matches(annotation)) { + context.dbriderFound = true; + } + }); + return context; + } + + private void testExecutionListenersFound(final J.Annotation annotation) { + testExecutionListenerAnnotation = annotation; + if (annotation.getArguments() != null) { + annotation.getArguments().forEach(arg -> { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + switch (((J.Identifier) assignment.getVariable()).getSimpleName()) { + case "value": + case "listeners": + if (assignment.getAssignment() instanceof J.NewArray) { + listeners = (J.NewArray) assignment.getAssignment(); + } + break; + case "inheritListeners": + inheritListeners = assignment.getAssignment(); + break; + case "mergeMode": + mergeMode = assignment.getAssignment(); + break; + } + } else if (arg instanceof J.NewArray) { + listeners = (J.NewArray) arg; + } else if (arg instanceof J.FieldAccess) { + listener = (J.FieldAccess) arg; + } + }); + } + } + + public boolean shouldMigrate() { + return isTestExecutionListenerForDbRider() && !dbriderFound; + } + + public boolean shouldAddDbRiderAnnotation() { + if (dbriderFound) { + return false; + } + + return isTestExecutionListenerForDbRider(); + } + + public J.@Nullable Annotation getExecutionListenerAnnotation() { + if (isTestExecutionListenerForDbRider()) { + if (canTestExecutionListenerBeRemoved()) { + return null; + } + if (testExecutionListenerAnnotation != null && testExecutionListenerAnnotation.getArguments() != null) { + return testExecutionListenerAnnotation.withArguments(ListUtils.map(testExecutionListenerAnnotation.getArguments(), arg -> { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + Expression newValue = assignment.getAssignment(); + switch (((J.Identifier) assignment.getVariable()).getSimpleName()) { + case "value": + case "listeners": + if (assignment.getAssignment() instanceof J.NewArray) { + newValue = getMigratedListeners(); + } + break; + case "inheritListeners": + newValue = getMigratedInheritListeners(); + break; + case "mergeMode": + newValue = getMigratedMergeMode(); + break; + } + if (newValue == null) { + return null; + } + return assignment.withAssignment(newValue); + } else if (arg instanceof J.NewArray) { + return getMigratedListeners(); + } + if (arg instanceof J.FieldAccess && isTypeReference(arg, DBRIDER_TEST_EXECUTION_LISTENER)) { + return null; + } + return arg; + })); + } + } + + return testExecutionListenerAnnotation; + } + + // We can only remove an execution listener annotation if: + // - InheritListeners was null or true + // - MergeMode was TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS + // By default, the TestExecutionListeners.MergeMode is REPLACE_DEFAULTS so if we remove the annotation, other defaults would kick in. + private boolean canTestExecutionListenerBeRemoved() { + if (listener == null && listeners != null && listeners.getInitializer() != null && + listeners.getInitializer().stream().allMatch(listener -> isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER))) { + return (getMigratedInheritListeners() == null && getMigratedMergeMode() != null); + } + return false; + } + + private @Nullable Expression getMigratedMergeMode() { + if (mergeMode != null && mergeMode instanceof J.FieldAccess && "REPLACE_DEFAULTS".equals(((J.FieldAccess) mergeMode).getName().getSimpleName())) { + return null; + } + return mergeMode; + } + + private @Nullable Expression getMigratedInheritListeners() { + if (inheritListeners != null && (inheritListeners instanceof J.Literal && Boolean.TRUE.equals(((J.Literal) inheritListeners).getValue()))) { + return null; + } + return inheritListeners; + } + + // Remove the DBRiderTestExecutionListener from the listeners array + // If the listeners array is empty after removing the DBRiderTestExecutionListener, return null so that the array itself can be removed + private J.@Nullable NewArray getMigratedListeners() { + if (listeners != null && listeners.getInitializer() != null) { + List newListeners = ListUtils.map(listeners.getInitializer(), listener -> { + if (listener instanceof J.FieldAccess && isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER)) { + return null; + } + return listener; + }); + if (newListeners.isEmpty()) { + return null; + } + return listeners.withInitializer(firstItemPrefixWorkaround(newListeners)); + } + return listeners; + } + + private boolean isTestExecutionListenerForDbRider() { + if (listener != null) { + return isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER); + } + if (listeners != null && listeners.getInitializer() != null) { + return listeners.getInitializer().stream().anyMatch(listener -> isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER)); + } + return false; + } + + private static boolean isTypeReference(Expression expression, String type) { + return expression.getType() instanceof JavaType.Parameterized && + ((JavaType.Parameterized) expression.getType()).getFullyQualifiedName().equals("java.lang.Class") && + ((JavaType.Parameterized) expression.getType()).getTypeParameters().size() == 1 && + ((JavaType.Parameterized) expression.getType()).getTypeParameters().get(0) instanceof JavaType.Class && + ((JavaType.Class) ((JavaType.Parameterized) expression.getType()).getTypeParameters().get(0)).getFullyQualifiedName().equals(type); + } + } + + private static @Nullable List firstItemPrefixWorkaround(@Nullable List list) { + if (list == null || list.isEmpty()) { + return list; + } + return ListUtils.mapFirst(list, t -> t.withPrefix(t.getPrefix().withWhitespace(t.getPrefix().getLastWhitespace().replaceAll(" $", "")))); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java b/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java new file mode 100644 index 000000000..8a8f30054 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java.testing.dbrider; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar b/src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c4bbfc62831e7c154d4f42fc7884d29f12c852d GIT binary patch literal 25791 zcmbrl1#o0xjwWbkW@ct)W@ct)cABZoY?qnsGBYzXLz$VGnagn1J+n7EyL)|oaT$>j z880P;UP}3X>H8!lSx_(-ARs6pAckLWT0sBP3;OS8d2uyidT9j-My0=EXn)0^{wenH zMdhXR_iN?9@4r|7OH5u^L0UpwRgFPj;z53DQcjkhVGdrFo@Qq1ccTi^BJ1vv3xkY2 zowWR%TSb$~tvsFVwCb%3oC?$!wfyfXRiCCzeEWgbTD=UFqqmIySg@Oo@t_LqJ7JQ zz+pp#QpwUUw}sOm1kolJ6`bn?T8Zy|5aIp9fGWC(3t~H=5!DgMZqY- zUa?^hZ6mj4C>VucV335MJSeIxB0ZuF3?8~gF^Wqy)ZrA)NNY%&qm-=3xGsg7L^V_= zJIScuf>u&YW5^47l36KIa*c1`2SbFX+yD*V43(mk)j$$SE{amUEGNLS709v9OG!vO z4SNNQ4&frqoh&QPX88w>Jn}FhhpCN?QZu0CH|k_IYX`lN50^zrO3`)pxI{|Zd71Lk zGMa3>Z7NGP`73>t1DDRc3jP*bas)x6oai)94v25ZmMK$R+YkEwI%{%QJT?nJ`0G+k zjTLE}#cE8mrOlE#%5KEMny-pbEJrYn(ZWXpnA!`k&83~#^kKDb#vk8aMNd|4W9CX+0_xR$@sTPmY{)%VYv{R((j`KPTexjHCp&$K?4*@G@!) z8ulb-=d8hw@}koFDDI%SlrS2zem zHD{vQPc<7YE*_TG`{)H)@B2ADqL_|xUF9(ykrX2(W(@TBzu(V_??fyr z_^#jLbCK@g%w@M>c|`RNc}RwSnO8|FjWAHLJR8-99UGx(-W9e|j)B?pED?T2 z;My(@!P*{*wWj)M?9lEw<_YUK=?QrQqZN=flXOS0opuK-P}HHR6loaGu;i{cZLT*` z77=ge8xtLKfePbVp8fNVS^)K~J%;g?e8ZRANE`S~YuV|s>3AQ;Dm}yQPa{vICCIwI zDX14LNC!TX9HkJRchr#rk+BZ&aYYU=w&4LT_Kr#N`aPVgnQu5POONHC_Xm;D#Q+X7 zGeec0QeaNbepe-7Gn+0e-dD{pg6^x-DEQIB`htBzWq9`Su4a;zP{;Xp1iCIM`J11z zCF~!1e~_8*vJ0oDX>dJ$ErmJ+_Tf*P24rifomRSauIz@yoM-2Msf*}h{D6OOLq->H z8D(B5QkSgrB1Wvpc~GFiP+|#b1{ltDkXTq(h(`D9mfudnEN};G!MJFZQ?fpc`}%)n zsDU*D@t1hlqydoX7_=tZw8?-8Sc!?aUd}S`EA(V7vtbv@gIw{-GBQ}v{Jgo3+EO88 zY5M1b^+kYfquKY`wREnIt(SB;v9!6<)K$GjR_JukT+`6*PeV7auBlRHi?hlm6&t)Q zE&#M8i9DLKrt++(bE^g_@!U~-%oB&Xz!kfC?lZdF!6lsw^a3ey+(++;9-c_FXL%zw zb%e+u7awdK3kvz#zuLQwa6|%(X+LJHE@nLft&JkEC@xiBbmb=rr_yOZ(|Bl zDQ>tXB#2Y>g)?4g{O&RO>8XQk`ylL!yimiR7q5zCW#i-G`0kNS9vcS1vJlky7}Y%f}YLsvXq8NtFKVYvLm;|Xc$jc z>DwC{>(*=_M{9oVlRaln$YK{{5Wju&Kuc*39>!+l(ki9AOpw$((-c%uh_5&~ zKMj%1?{u$D|Nay8A!fiwgwi^+Z3_sCe2YZFo`_489(z=Lsx2e9f5&`MG`b_R;e%@3 z7E-P3bw}|>{1UP+x`Ln?za}?D%O3+fACZzng+A+>@6xGMk~U?1YYFXXQm$xPz#?Ji zm@kJn`?grQ5LR&_e0G)fI8?VUGP@Y?3zDwx2LD~w-#P(~;YdW9opA8rrlp)4=l9oNf=7xB0oZAgTNeDfzI)tu{D5s66o znXsckylZlyn6tb-V}YX(870yk7H10~WE&G=$C;;MC?d|Z!2>$)I;_ORV@{`Ni#5o)#6GDUvjS?77 z#i4ubexHgfF~JO~cwzJl;?yS`li(Cqo@YTJ&-2vLgonRV1&z|#=raFKjHi>wr{^Y{ z|D@{icW=N4_%4~hTEfr?b<932x@p@l6H11ty|y4(7z#DLku(!ualW2R5yl=BXLzrC zF6ts`beIx#y-|2@PF1eppth-|gVZQ(-|F3#AYQofhgAEwLb$uBJ~~m8O={<`YOl#- z)#gwV56c*nP>Xr5Q8t>!FxyCHu8i9gy7)b{Z2-n&!_n`hDDUyx*n&Oht!A0cr4w7T zuVjZc0mjZSNA4DbvW`@J0^`%VE@)jXnuJ)nZRg2A9{tKx#pu|I;s^1(ja)6xdXiXP zGy6G0?6%3e*xD>}uI(=Juw+VYbl=eLwwZYRXTGu>)okLWeo_kIx5laH8U@KD6N7vozENz(nGHg%GL!XcPD4E540ruUj zCbhmZO_A7?de7K2PP=n68H+I}*q{N+Z6D2woz`gRB7K|Pl+`|UNF2M}&-kXmtf9(oEM?^>^r=D=ma$_<27cfD zHpHjfdjEW#YORv0L3APT7c6&egFgO!RCNl{2Y$CI3JiGzbO>Sq7`HfrTJSRb9mP} zwyA^sHZ?RltGYcat~gYj3eTec(9*sYrQrVAEvgP2f`iC5I*2Zp3(q64TD>t^O0Odk zgLe3?T$@+m-&if2P$Ywq7@3rVXUpqIqBZT0zd~#sFub%~O^Qb9)QcQD@F2u0CKh94{%J1>Ca;*N#3|5sTO5~OI0{o7 zh@I;Gc!wjf&fTDiqq1~i-7FfN=8WS}rN7nUF5Djqlrb?MJnuF=on*Uj58Tf{EP3=T zw_1K$lOE)l*fd`y%#OU_mSJeUFPJ?YTkko3h3DZP^tDJrH5dKklkdvw4(hQF_XQ5T`7daVioZYbfGedzuz`=}&1#6jO1BYX8EJUQ}+;+HRt! ztOyg7473B{cfy1`v*&`oO9MltMA3hll`7>e^x4?GG3re~8l=oenT_lQEP;UgM)?75 z`uy>rFu!FQnL7+|^(12U;P-of(SO-EK}QN{N#J(ld`BMUc-$wuA!1g(bu5q#(IR7IAQMHHFZtuI-Tndg6R<$Uul(2M*BQ==van#fj5@ zHeit?*@Lmmsl>1)jUmUjh4A7GY$5D5g-KNa_K>Ri^ui0osdOSpicC?+vGdFJkr0)0 z=d_%zDN9=vEOv}GU;*&Oov8_Ndyataq2>H4EQk2$y$h-l;6Q&DQc`F`U_WS|9GGYes?(8>Amu`9y? z=@u?%(){a}!0+F1Cq@1oMd=CH*f+SQrcr7*3H|)>0v)B&=f#YJq~i$hQh4B!qXGZD z&j$woE~x?r0{Q>}0%HGn9>xD`6Ga^zTmi-o0N4L`3>T}&D{cs(Ws_THhX~EjB8tfB zS|RQYxKO19{p@3>QimdRM~VQ|;jILaTH>!=R0LF>7gwnH3k9I#3)}mxaylMz z@-}w%*7_TOc*eJ4UB_%+Si53?($6P6q zMap4uY$$J@1gp`CUPxhMyE3cj`LnA~(#&tRi@O)W&)ra%;KD5_ZYfOa_BtuxcV6J* zu`X_a-ihBToeFr!R{`?;v+~!5pzIaI(kDm^5)duFXL&oFx(_rgYa>?RhNp?t4o#zp zSM<1z$2OGX@iaGNCKY%d%`c$^$x1FItntS5Q`j3@*T-#ISVgbm13?3bKnLqE#6-6> zk6q6na+wA&BM5feyv;fb2YtKInfB{|UXs zjm2N=Bmfr(Nci6lzqkXy8sH^w>|ktZ?xN)4=>88Remk7;UCuXtK$&cYK(uwL zqmm%E#+E=l4m5iQg~d)zjd8k%N2U#3su~U{YYh0UOkH)dJWck6icg|eLWh$^ZShtzIIyC;lVJ_T?y~u9EzcN_Gh}R+)%(1lY8l! z62>;5#-YRO4tnWK*P%wgB=uW9*_82MbR+R>uubuh?7g>P%PUpjM<=#1;YCZZ%enB& z>0wyg?FFT*Gf#~v9PsJL$aZDA*z;>mo(J` zAu9piVl&mlv3~Zf9-HbUOoG6F+Bx$QGZUdvvz&l9XD?0CQlX2W7CJLU08nf`+)}r6 zg8!bpib1QOtky|^D&UejROrj4@##n1bxiPQ#!N%Q!@(k^JetWO$25$QJe-oUZu2}^ z*Lb16gH6vzj!dHb#Pcq*i(@>;z&1Fi&iZ)XitRW(C@qMrkja!UV`QY-z@(i8|03xg z9F??@&BlIO30-EO*|@!7^2d>$Bv*8vnOd*|^WreIw=z`4+4iG7pNhxP{+VyZsulz)M6_T&{N3 z4xAxH@NcMC32Eilqbj3{T7C{U2|&Gxi7+*cBpqwbUe)ZK#K*_?w zhHm0`a@RI{ggpd9p9DOmS@MM1dcK%ci$E4m}=Eh}H8@t!jf^}r14tt+1;QJ4Tc z>iwY}D6W$LCP+~$2k*k7ISJQMv_ZEzWF)kW!5QKlAAl-f97fd8b`qkpmDhyn=hw)`M zG}TtXcxUyqLtIJ58JXINo*y%hUuCn3=A#kB$ z(TQ7C;B;L~(>*-3`tR)c+%-&l@Wf&6X8EGHN>C^yE8t`4?AS@uq_)!l^Tqb7+f?9a zS$=*R$y>6Y39##9x(t5#sIq|^%xsB2(GFZY^(=g!ZR*<=qYG2-pZM=qHO}hP=o#I4 zQC&Q=bH*LUR|~DdLY@wL_V|fGhgwi7w^HX!c+ucuPt{#}n@eK(8mknNtT(#--Uj!x z=K!OHHQA{5WhN&hKd~{){TZ6lt0Zs~CUiF!k3|?GPu2>cY@_krQzc%X)MTtD>D%Aq zQhb&(m}DecM~()jCfv1B#y*@i7p>hknwrx+IyHHudULEIhfwCFIAe}`XxChZy`oGn zhw8kuTiwh<87S}zbK*|IEZ+NMPVH%Q9^P5@7vR-D1`L7w;(wr-SaD@J3RE5Dxyemy z+boXV7f4{>#Q{?r+nDXCliJdbj#DKEDN1ABUjgE9Mhn)1XRkFbF{w=m^Onfq_pY+B z8IZU0#g?S7@4&JHL7FWW5&B#2h+rU;+9>HuwXT-T$C;d%8Sc!|lNniZ3u8dyFfWVo zgQ=ePfKUJI?!bN%%V7s8w3DBAm!;YUL!s^ZD%e~g6aYAbu6&p+piHINYwSLNvy~>l zfs?1PheAMpIv}$D7Dk;ZdKNKXu}i*f2Rf)jN}7$4p(7bqnWv*%VwnFs1QEYxZ&C;P7eFo2Umve)N`_IjI?= za#Kz}9eittY=}DG);3Bl={GYKg+YG!0ckO*8>|anpOHIqWfn#nA@{>sso$xhXyWYA z8v^v44kvZMRszF--nvUiVs|MqG!4P9G>8^F+0!kVDJvP*Lf>m(igLOnI))>j1*RRN zx*PH++zGjxv9b(zD02VukQE-escRsdPKBmKnT_vLQ6Uy=gm=ub)%6v0345s~?QT2eaZ;qk2y` z#NAj$U)u_a-6acK+wH28cy?w^NmJvddZpEMxo?BoQu`;m$;RttDdvV3{f_)$&5()S z@T6C>NKs^YS=i&}6adAf+FDFUonukz>c!m+qQq-qQ3Hocq(sg8jp0Hho2QX+8nfUdYU;sKZk zON1)>}nD-&UvIHhX&`}m4%5L?2E&}b+y&6skb(M zdt*>maY|>j&NR;!_sU0AF7r(m{L&Pc365>nSGHQJSKE|}l=hmP1h+Z8aB;M(?~jk8 z_79Kaqm$NQFsiA>Qq4;exNxR$rI(ab3*x^jhwKjMS_w}7g3Hl}X%dl*x;uHSR3$Je1*j%w> z9~3V`dHdK}dM!pkus0LT*GF^*QQd??oCs;7_f6z_@3EQEhU{`VrjY3@%z z%ikUj`Y%n&_3sq%Vj}8)$wxJFSHRy!4q)#2pPW*$%7zT7FcN>xq!?uqyMg9b^lPeE zow4%@B>8}Mf4DFq;9;B9+GI36LHhT;C3PF!>n`N0vWc5zL|TNH*{_NVxutSe%lGTA zV~jqWRK3G`rZ_wd`B#;`6_6h0m~mKa3Z5uzj!!0@1Nvgc#2=iY7=fqK1eGN*URcA4 z(XQrqxr@Hx_X&%gXO9rU8wvXW!803aJ=;R34B%_Tl8C`IHT~iXINg-^h;j}rmkniA z7P;37TExUgp^;INQzVun$^26`u`0QoAa1_`Z^uaoO0p};owv{M`})hXs|Ynit3-V} zeg?}j%?d)-2C=&Dp{b{YDbKSK=KMwPnMYeT>p{U0{-{K*WS@z2cFx0o8{H3d0zKcC z-Cw)La0hUL+l~~XYf5)v#G?wYckD&<{iym9TV7c8R*Yqx>P4Z?@C3d%FU9V zVGX)}5H)n)!lzQM=#@W0=!Qw;J=wGiaMCY4OhL^1a^ZLjEfYDOxZA4(eXnpbCV_KI z`x}by-r=d?2iYv<_q=Jwm`xhuP4uE?W~VMuiD!f&Oa*__THy%($HO8UtVs}p?&NrYFQpx z4+B!SZeG>#S^LvcYHwdTOt36+_B}24Eai?`bNn)y#p&4dlJkAzv$J9O<#Xx`6g{j1 ze&b|HeeHgbIA@cVA|EO0&B0%84MRa+6K7pQv-{4LTKFwVsV z{5Z2BST?R+me5IhJ{E_}-q4MN;SaSKV-n zO-q;ddcV#V*@y5ZsrcT780>`Hkxb<#7%|h1VdkG`StVQRR#aBhnl*^1q;;o{>Znc^ zolimoF(b?1#gvj|G)n8%Hi7u~I<*ID(tt@EdbIO>n-1G+LCTu9Uam?Jhl>~r6X0&` z6_Sf)rmJ-7s|bmX*jzC=#1^xBD_+oOn}+sTdlU)$j&^z(O2sB>loI57$I}#V8{&C2 zi-NXV52$h2hS^%FB$S0t(WVv02S>fNvRzJ)l{|g6Fy?6+b>r1~E1B!qCjDr!f)WeU zBJxUl(~`2q&SI}Zy#hZF)XQ1(5N)GQPRMNcKL@;`Uuq%0!HXv7q|PiVlLSBt*AmQV zRt`F>X_MCF!-Dc_8sLAfyBln8ckuqw6-YFYBi0fjuT+QeuiW*3lMM4%H<_zb9~RNc z2CrY0n;tEFo75>FN@LUgb#}?m=FIhiE)Y4r11opMLDHX%v+eIb)(p=LiOg`y@rX31 zp98*Se~)Udb~f#n&{i%E;%)J{Ioq6*+@Jy}L9}FF?+{Hva%gB$n()9u9V^ZQ^iiq> zZZ}<2XlPW&8=D(p(a;0E>4mUKL+Z&eQIBj`2;Oqm|9D`U@?v*iYEdbakUKTZO-#Ru zzIYr1(!>o5Ug9H3?R7{sP2*wR# zL4H2f78Ttu@qkG46(LyeiHwW(p*?I^CoC?8z-ZFwdvvSWRkUyZAwSIAMZ(0Lc12S& z;0AIDbD>bmM*G?s6|kE}^A)NY`RCYbF(!+-dgU00k#?we@=iya`AaqKfJR}rmqvjl zGNjM8h|r)5J)tzpcTc4rvK6`t50s$L$U(o2vnUEgxeQ7iUA#aG*+D%SDrE+>SgxbW zJS~N-cz#4|h;6`oeKeDOPK@ewZ|~T+|EXxmrvn;6v4ZJ$n#xGIlu;VZgjhStNM(VD z92F+&B+PJ;OP4x&N`VY=lDaZ|Ntp?a!M^rSP=$*05vZs$lQ0?!n0OM4a+4M=MReEO zD7Z^oQy-fcIP_b1!-lHlt+xEo7~ChK>eyi^q;u6_J?40eY{NKC%c0qtfQs8F9D-Ht zD4noK_n|D*uxcUu636hEF(((cNCsa{+pNJ7tQ#w@!r{Rv?yN&hCgdDHo+6MMfGA zW?TMD=e%xG_e)86+VEo=0dw^v1^Vw34Txdgq${zPQr|h;c{vhhx=QzS@->TYp0#^} zeoYCZY(9RCQucu~3tk315dKx1`9U=bUEL)0@5n{>!X8%RTau(@Y56gwo#S1MNpO#; zhd4I6=xTB*Oj@u^N#3l+$5ZWESa2Oj$FlgBniwhoC5bz_jVndb6rydnSha^*16~bP z%!rPtv)`rNR6^u^nGwQMkw)g9!=={-y0mC!-F#)UF%BsSd6ZJh5Akj0_L7uqAi`HmT72L1tbIa#(e%@Hz4q zf5eX%d&AE;x)sXnkp*O!gu!?=lmcHU%|g{du_5HdlaebnJCK@91eX!pCKjc~l$h7k z$=FNC*%ad3Ed~A{h{s)aL?{-sfA5^^RDV}5Hsmc34qi#q+N4RjV96{p!#$g=qvYJr zX*dQai+5{i(15R0w{9(MD&wXvw3}-To3@@WmericmoU==gy*_Qb{x2lGBj3THVB;E z!)WBOFS$O_#15V|N?|D&nn_$0??K8nsJ**7{DJFbY~goZM!m94fCZs_ zLg7s{_M7^b`^Gp#jWRMO>=Zw_&+2%rLU^dvpvqV;$~LYS`B7V(qrZSxidf(c&^^H( z8Dg3fT@yy76Sd?Lg%B^#E8U2R^_LF;?^ zo|HpSxxk=us$n*{AwbV`)1lgWmlHNaE83G0DG#+Z>IuVPN=33CI0tWoBiX30faONR zR?m8eFdFy3RqOuc{O`g?ZmjV3^boJbep~66)y>3tOWygTOPQJbutzLS8UC6949WN` zd_uVw{1Dw=-R?l-KFY@js{BFo!Z8=MR2Qu+GUzm~VCQ8b{Oak72be|Jf?LS7w|=xW zB@rv!z;&86&YQ|$&UmV5dbZ^*IfWB`hs3dEmDN;)L$7f>6%Fwfcw*p{t=#}9)E~fd zRaCfrMWkhTHESp`Ix^F#%8g-cql@JDs^J&;+BnnVm~K-`j5^l`Ej!BoD%*h-zqI7%ig>g8qyMz@aV!`FJ_%a^n=4U{z81>T zumt{z)-Z28U2ZtIz ze-pn4f!BXQU5%(%Kf$kfj;UpCFhZ~K(TSboCH4xxI!#qmOyO7LqO1Y#F&q)x8G)Qp zz;KVJQ#lZPNSgFSVKFqFeU$Wv&7u#Njy9hB4lp$kr|)TYzQOA#ecd%8vtx=8b;+k4 znTV=2B&MmPx;}C545G?SqjgJjuV7UFRQ$+g;d)e5m5QFNoxX~sJ22nxzoIL z@75uoOB@NrRf%-yVL0JVvIFhWKJjDmB(vJWXP`mNOK@=qX@PeWQ; z#kTkt8bYAKT1KCm@K2ypn&JZ53~4@U!UL&)Nu8x19cER3Hc2Vsb2YaXCZqZye0H>8QU%+hIVj_*v)5IT<$_UES%I2FB8IhO8!i6m_GD&aI^tt0Dz^X=2wo z*9GdFdXGc(&s-Cm%+^m+qogIQ1KugZ8&n+bwLg~|t*+v67>2DiWxjPv4r{5QrVzG0 z*Uo>Al5SwVU-nx?1%+qsr5yE&$2iF*9M~l{+QEhAGb6BKje_O*z$vn1_lkxwg`NSj z`t7{M(?x+F{M0Bj)8XyfueECp^ULRe8|cq%9utpq18}Ev z1#&6#npow@K>A4V#~loGE!=^+XLHEPtv$P3NI3=5)csXE(~RgzJZx-w_*?{>+*e&& zpB*W~0K#A(t^@*XtS3F)m)0R!+Uo&Jp22Zft*r=Dyi>m8&)X4-#a(*&vSDs$$Fy67 zu(Fm@o03A^;|TK(Hsdx{>owPj(CqTg$Eoug3tAfuO%NILsu0S4CBY)pPvWrXdb9Nj zD%TDlfix8hHBI^QxRTX(xJYT;77DXFH%ZqNWp72B>(zqPnfBBitGKkIk~C@S=j2Qlt72;FV)cV6n+xA-#^sI^x9~k7^lXs+t%*C<54TK4dF!~WR`=jz zPz$UMbXRPS8SCq|*eU^?o$$>(a?by*Vzi|3VH$B~HZ|rli^SfLsg80R~Zsjh;kw169(1gi%#C*gD zgPz%1GOBt5l4g97@hq1*{S4TIgQhG9Modxhl4QOlf3cERGy(bp*4L_#-{Y@*Jwyl5 zo-zYzX1>^l3AgHhCt#yr_64v#ei;s|nEQh6B;Pj0(7vXI{GG#v_$|{$bAVGbskNUh z`E~$t9lg(MHsw+Hnj3R+>xcdO01}S3f;;8sCsP!m^Fe@Imtf?6Dcnm{TlZs7w+Xw@8jo7(1w%MjshqF!7AjFQAO z%O-l|gzg;Qgxn+nE8YY<`ORQeen(?!j2CD)~dDf67x$TT-8I4B#9GS3;<)|a_4->7> zkb%HFL*tW;#hBWXvK1&@OjfN0#-d-w6Tie6CS%+?!jSk3tk!FE*0&crMJ7unu#maiaS z>=#p|0YmeTSVPIU19O(IxB#Zh8Q1gUAof~nY3I5)*|R zvZt=Y@(MPY4BgEjv&z1Y49*tz%Ij#I;Er6Asl|F79?PPkZJat!QvmCwb_(d3jeZUS zKH75*P#Ms@E^91~_2%Eo1G{jIIz(wu^Pqd>Z#nmy1Q1$6{5b2Hw7MR|V<(QafmEv9 zDH&(*2y7zuqKz^lsV5vU0t4ZDhiZag7b_S%i(-SNZ)qD?S!SxK{LNQX3TpmgiEKnk zy(NC&uF|PWmCO&^BVtq9mc-R|3wefL;9*PD^}@s98HC%mi$mOI0JTaQ((DBX{I0PX z>em$D@hhVCvUH9gWL4H0K&0duM&|M*wYh*r5xX+0RY7C$iw31ED!Du=xeVs67RGQ1 z#4DJ$qqsHRF6!lZi-_WWV}xX(3-o+c!o`cV`vGzh<V^r6g zx6DTuZWY_3?n|fm>QB%eC$CuWz(XP%!SXJ)Hg%d+&MoL@;t6wgm~~9v#3Zuejcz}_ zO};4+SD`(Sic|x(+M3WR^u9iJ@6h1W+s7S(Jl}KK`)Rhuga8{-*dtE*Xc(>Zf}pcU z8pixCi1N#COmc&97US2(#GE~?sAg1U>;QgIV=h~B!lgr^A7mVOdIvFh`g=5Ub2GXz z4~~&{vIX8ke+pk)tm&psfAEGj_7GY3DD(_i`xzPcH6U=~95ez_c5_(=><&x#k>!c2 zTU-eyA{JymPNdkNDu;XyarRYD*g*nDZSjYFRG4Sh-O7qM1M zwV<$hnYuJKYO@(2@rmuAv1=%jPSS`=1jYtw=4Lj-J5Vuw+Qbw#4=8y*|AsJ_L^q*` zIG-Z$@eVqkeM)#j^6?$^M-P#)^ghBfe2R*lgS=PJlrrkuyxa@194=WP0|p7}DIwIt z@LMK>o0}eJ$7e~$;f=5Vg|E4xRwOX^J8;FI3Iz0z-J<`~I@*8iB>m?a(SPtY;+_C= z2iLzC{eOV--Maq(=Rdzc4LCB)SjmX9fkVrF5ku~wK}Y;VB1!}U1M7E9k{kHPkd*_{ zqD{4CHD|8UaHY(}TvRo8osRd~WXp41>9a$%B;rPZZnMt!&Q8|a+O$PuyPBZI^K{mW z|BZ(s>)#qWJdd~l{M;3S@S<4SH#^PpR*v!N=%8C|fYS3!ongG#A(uQ&#Y-twmS69> z*r4Y2-mWHjgZmy(lV;^=QGpmDLjRI5&zKGLaJxP45deqb$D(0KnkLz-F(WQ*Ead@=g z)tI*K8EwZixLa+IIX#NqH7z^&D%YN!GHLn19SXFcq^wFz?U16ZOKg6NfDPWYIU~$X zLbm8JW?tHF98BsW!)!F@rJ;-GyccLOE5r%!2{d3)#>m@|BKl}cl+VVAF$0&7cU_PLGnUK z^T(fh2opyOE^jf}uXTC^$WZZ7-h39fmZ4;2lLtX}#O-_ugLx;6W2e%L67gQ`(VgJ= z1j5ytLt7cs;f-(QBKLKQOaUc7S>7gD-eri*?^a;PcOA#CTH(JGI0Pv#KWoYsUchEv z;Qb9kIDbzH?h@jBkOl|X-NHhA5AEjRe6W_pyt`Fqz+8y#wMrCr7%?Oo6pMf3!Lt$GrZ1`Rs?hk4zShs za4#CH9mhM|*j3owiTla-M5tpww`%Q8Jn#bh8y`)aFWl*N^4frtekmbA#@jaNh#K1_ zq4zf}ZDJ3zEexQ<7Hx;xh6}=A^&kWkqi^V(fs{jo7p5DKc=~{*I*w*4$c1EJ6!n*4 zTiX0+$Zy!*0*}E$ldllmX`R%>Uc)Zi^wjaYy0)k!wK>o=BF~`(AmVD4)YHokz~pa$ zCnM|$z{#zGE4?)U3Xg6X!6woWLR~WAwdN*L{5(K^Ss#R<>U`SE$wCA#S7Ew``3;al ziu*J&$uVAvo^195IY_O8Ftxwmh0Pc#iRlWEK|R~p5i~NN+iuV8Ud+PS-u{OF3hnO= z>1iv7#rhyw+*rYBR&-C9+f9S}LRU4&TGG_nkSr)9O{$y2<7K0yK?Lc6y0ovVE&?{J zt5#7oHn#vH0SoONaVmr(p61yLGB7e;ksciJ4ub&OMg4>Gog0@9ij@~LK0j}+C0NdF z_g0`G7}e)7IoHyGaYuc(gZ=aN?%5s_JHH9&F6Gk{t+) z)?O?XYSM+{eIuyHsuap$>vj$YtYgK!rMc($O6l}iz}F~~<<VcI;=m0yz$qa5Fa`Ax-$~FpW3CCoCw{04_6O$G)Tv&ks z?|h7+(Js#wv@dWy*JvpMa}?Q|3J_Jy9qnCRJ^d##7dtoz>D>-eU4QRj|E(%*66gSH zp{Xm1reu&l&u`9Ou9%F86-$Il+O>i0?fqe)FUSSd;{Y7ZE-Pzlk)o*Y|ZnUY&4~5R#Z=4eR^0ad? z7k;Eq-{5xKO8Q?*0rY&Us|h2KQS&Pe(LzbqELkl}Fa{%nR{ubq{msYpmbRpyQ_lkl zTw}=KD}UEHRxsf@3My#MddX5+ibL3zqtHd&=y7>QqNnh2kxlpMd{i3wyIQS{Gh#4ax*O7&Q^#PgmJr=-nCJPuk zOnduH(K}F^`H`t-aMGLp$nm*y*n7Vt_r=?5d@CsTMfsgK{=FZN^+D*raM=24pY%=p zdz<-9|0}QmCL;wS=Pd6&GSh2w#1WDzG#>He1cJ>`5N%XQpd3Jeu z%S=vRac1>;o{>Yl8Ro_M5*~$VrX@MIE+hcRrt@ag@+-+kb3mT=ao@601xkeOv`TRp z4Xb*4#n|tHH*lL4A}#9*ZX1WdOvp{<);=}ZrN|PUA_W?yXl70IUqD9XJZox`i_~E{ z4i)GX>uT&uq|GSr1vX1GYV6X_{c73kBNbZ+A=1AW5apV6jH;B~N_f;IY{AzL1Fe|P zS|53nz7(hcrKJK!;$4KyeNpG0X(l>2Sl+@od+>)H@am2g5KB;)$(Q_ohiT5SlSZGogmvWi-_7D%@b*@BOUF&ug^yhF%Uoru#OB&77YL@$Cr{q(+bZ#xuj`O-tP_o^3;<}9FcRRJub6~;Vpi3!sbS)de-KaAWI+aT6!(wo zs1VY(uZXX-HM-8DQd)_EZBx+edrEK1|1lbw*H$l7J#ZxtE}~T3v8Lv;MDOR@TD@Ne zV^?cYp}WMVx>e%9dBzI>13z%uLBKvoP~9qjW~RrcGthhqh(xQMsD#+)E-+jW?D5*3 zjl&2>-C^^PqC{|wzi8R_IfgsS-m{lh@C&p2@yQb(EvXm692-rWmSs>RAFk5FcMfW~ z@a%L7@~3z>lGFz=h^AK2Cfb|Z-w={YK;Fano2M@_l3xCODh1)ZWukMD9$TkD6F26) z&21LAdO}A9|^;db#&q6Zh&xH37k8#xZ z?C35;-_(_U(?9Yiwn=}NS6~4;@3HS3o%FTruEiT;7Cc@~Efih9W!4$col%lZrg$q5 zTRyjsuvRTRW=GSC7+yaUaJWiCmU$KigpuUE6M80A;KoJw37|{lG~vXFK$X`CN9{%D zN52?f!V`a!UzzZfY~73V3_@Qyl zCHG=+bF#D3`i3qaqVw$a60L+cerP5 zMBzP+60Z4g5uskRQK$UDJpv9kNiSK{C!yXbGq7}M(VrceL7?5VJCS*p1%l-qL)diG zk7LM4DN#qe(6)Mt6dO#f)n5%Jq(HYx;kfLpFa0hd--lelOLI7QOze*tu!4UrfbWU( zu* zVTW#PhZX8R(fBp9_&lO@dCS62=CHVXbFBt`Tu85(*KU|P!6R+r|0?aQ!=h;0HomlU zcXy|NG}5t@bc4XsC5=dzq##JQONpd_gmft&u{0`OOM`UC2Zzt|>cji!=XZSf9=pfv zAJ=)_GqW>u&%M|68*bbMR9nfw0Nhcmc;VpdVUB;vwZ&mXV1{Txt+|nvxF*DtrW^ zI3Q6Ut(ty+ZdYp?cf;bn-ylO^mQSSY&3OFESxNc*Fs|7L>woW`AHjx9>NH6bXC{o$j3aX z8_zrEBWiVkHY+XHbSc6 z5&p@h*VCS7#z%3lex2#r*Xto6HFIOv%asuJlNn1^vSH(I{eIzXPT}nVhD~z*qMv61 z-&q-j<9m=fpC##v>u4mv1LmU0a`-amxiV^kw+LFz&)SO&35ggi0g^K$;e2Tn=cPp=-X!9xLS)e2$R?jx8PMUHj!bXvxA z>dE8s$bjvF6)gBh9rfQh(rbLVQv^hIjW9Ah5VqMGVsm&Z*G~zrxqe}%?jxCn?H|dA zB%Ao?iEg$?@SGOy)h5$$w5v^NgN{Ghp`Wi>(lAHO_(kr7ewniOnD6>G7@qeI&rdM* zIoOG0U*L%ogbu+eEbGmZc>`!H77N2Fv9&zqTFVns$P#4(pVmw(7NH{PW>l$v*sZCa zWR^8MtT1gw(HQJBF=EsW!u$H+(&Q~`Htm*TA$`V~T@~Jyyz>xYG2Af>`e9Bng?cvk zDU;ar60^wIl7h$-c=g0VO4R<9+-MEMvsGjU!u>iir-sWmqg--rP}8>z)YI~EK3Qo; zpNj#tXKg)W;gZ5&y9+}a1q^_&yYV7wE&S{EXg;8`(b_@T(^VB(A8X1~zn$0LROx+Y z*CwV-_;oLB7Pn8t%Eci>k=k(M7*f7THY;(uZM^K4LPB9j5W3#HPir0x!P{Wp>g<$) z_s7Q4y!eZaP(a!7T$#EUN2`;i&AnnNBBlny2{uu|^zDwZYQmw*1Hgy3ce3-4DpAr; zWnbUrI+n)-*)y|mFsMp-6N31Yj?}dEIEhW=IYD>bHB?>{=Di;Q8q3IJarAD{xKd_n z4+G5)(nJb4dXpIuydYBMB|K3)KE-i1K<@{ZgfYVMcVF>j=OBmTimHx)ONqC$VnliM zk_bRU8oZzqGE9*IZIGCZoWoYZ%aW?dD)@HZbE`9Itcs^kMkN=Sr4kw&k@`$TbdW%s!fi|?6N>&h_O}#*)LN!+QLe}Ci$J`flR_rNNkrV}EXd7%S3+^TydH8x4aHMf^p2u3?(hpkv;Qa{u zP@P-DoHEwij0|o=2B$|;1gODcR>jiVU>=&<-I^r}D3Tfk7=x#Dq_tXauMT4fu*=o`IYEr>pL*3^c*dW`;f8em>L zkYnY7>s7Rrpwy#M)}4XWa@Qfok;x~>@(8jQNy6F-?88{ZV_}2l>rrend4HxC!EBLU z1zfu3TCUmzDLUNeAifLjOGAT`_YZB3H1ClVY>}PqoN*F7vJyY%&tg86?~zzHdyTc6 zrmf%oMRQtBdqxTrvy3mQ$+5sTrN0C%i0m;c;TUCn3rIr=p()@|SC5#AJSj5q4TAkzbB0DMs(Fn~arL-O$k(V5LAeF&T&8o;PdNLr`V1!yD zySL(aGE`KL)8}a4eSWwWo|ZpVu|PTe3|H57@LfP^wl(4-csJ2rKTqnr$m|^;5mpb5yi0pT3iQZ?lg<4jov&nO@vlu z+j{WMHoqt+(FI~w(OWZk&?=hTzmlT^&F9-u}SNR|2{2PINL zahk{C4Ws3K#!z*~@@pP60bd|CSq}l7<};){_e$pDnk|XImTpCbE-8U(9NJgoZz5ugAaTu zVWBO=7|=qD-~$bFaJCQ@)DvzD?*1V~jvJ@^J52B_CWI0I#q^y6v!33cig+e>-wPv4 zBvqarC#k(gRFiWatAA2;B;HR%27T8tCUm30T2yk;#;7_snV&@nj7#M;tGsKH9ZTPytI@@v5zG(+HqR_1^b|To~FKbESSDA zeIV{bp*3sljJAc_@b&RRKtTvf_8ggdRGWpOjSYzr;j*BCUEmGr?AjgEx)afdg-42w z@sw!g8|Qx$)?=`2HHPpVZ39VAE3{(u%lLiM>`+Y z;WFQO(uF*ez_e)eipM%tqP$s(k;bh>N)T~BGR3gR{Ok2y6E4LL04FSVDz0Bpt&aw? z)79}_rp^mJ1{)yeDOEv#9vgiB64NQj{t_hy?wcGfK@U!R^CMx|b0y_U2yHI2RL@2W zmCIv5u3FEKQtO9tZeCQ@D@A|T&26y@#aJ~16dV36#=%^k%#5kxUB6dsBML5re4Y=0 zoO)(H8s`HwG~r^B4o20NaioHUYlhHsfqn0_Bh2`Z-|p;f_q^Bh&x>HL#Z-QpLIW#o zHXXDH5z!LV@~u1foUp~1gRe}Ru_)QYwBTE1G>Ccm?c1x@bf2(Y=Ti8H%J_(UUrY2` zgyRvyEM;%G5aRJl@<_g*6BpjfuDA?-UXq-rcgbFetxofv3$x6ers>X96MWw(KJURj z)5#qhT(@I;`C9xc^k7j!NycMI*5l!l$t#kB3-`%SeM1{9YM2(JtO#_45!`hXV@bvn z>W__p-kmjxNNea0%&f%;-z2W!6YfzP;HGm2bm$=`(QFSGU-`%SF5xsD<>oWII^tx1 z>p$X8R#Hca6zSMs`m}fwS^w~8_<)CbScsT6Q6k`|jtSRnN^6;Uzwyq@FG)o6XSVRX=4-TiuQ5|k_!XtlVPevlNrLo5WPPm4y& zi{hA6We7q+Jb%ASMyJ~Sl0)BP_$)~ll-JZFV_GGabcn5&_#mY+qjJIFto-H27z~{< zX3o!Fq^ zUC@b5$(b(`&T)BT_hwKuoIKJI2F5?ay~I@ae}-~@-ag`u1ZJar3WZ6ND4<=pKXG8PK+t+cz}CDh48WRms9ktgIKQaTYif3(hS zzcD>+E^T}aHcE2sx$-Zx*kyn>Q8qwEjopw8OKhbv{w6?V+cjC&# zqT$I~CURWPL$X@u^ob2UL&~0w2q{Z4BvWO546;|W;ZGi>;Xra?wZ>mraM9mVZ_pp1 zNh}*bU{-qqo1PLi;dt~W#f=KvHCSRo7?vcsMP$I!`aDhQ6Ko^(nCZrhlEL*g&qmKY zw07W0W3^`i#ZK;P3&|DOb`dT&G38|hH#Saqi70CfP7mZ%MO?#qc4r0sx$zNv!yJW3 ze$N{3IDW8al4g2@VS1mD_{2+7GcMx}XO()`U6xQw@I(Im*rY(>q>*MuIuSbut z_afh2-w&J4vt4Q7cw-Ro%nQZNX=4xnBZC4*ku^BTrNT$EoD%T6)K|8$}XTO>-mZ~|J^a$7Q3Jg+84X9w6hCw_YUjndpq2>_0sl%y%`T z?9;+qKF2Qv6fROo@RwE)ZFV zhw9rNAV7(C25vVk z1u?F(x6W2IS-lP_;#K$9p38o$Dpj3+?@XT^)x`V_2yL$atKhr(@U1F(l9&lfVrQo+ z8e>?Dtg|=y?9T*)OmUdfXrc)i34rZ7_3^DKWHe^Pxk`i@_m_p6nD9*f%AN#;+;zr2 zi9`r;$GOwY7nBU0dZlDQQQ49MG26UXMxy@lO$XuyVDS9J!C+w6k7e0uZ;E0PMv0ov zDkk%pc|Z|LijqgFReH{Z_-+kTc+`0lKvz2jX(WLZP82`03o3%)5jqeEepi6oF3I*Z z5G+^e&Okv?Xr$(22FGBeA#E7?NoFotCo|8X<9Qt%;8 z`DH|)U)rb}YXH|9gN1&zRLIi?#Z~-JX0lIxIvZ}*i$-`V1SbSbp+Mm;iM3@M59Ct` z;3A{;>2d=(d*-OkHiUanxmH6a@G$nZ_9fe5L7H4kc3MDG1TN)v{zQ`6!ii4_kvUFK zYLgsuWSZ3tjmRRl9IlrdinB%Wz zQ5Fz*S2|#Oig_Y(f(0A2`>r-lk-JviBDiHXHk!S^Oo#f98RDm0QBUO(* z!VN*3DpAJ`&$G)uEp*$)?4QcwUpR3YUg7&UikIL=Du1ohd^ghuNIqiDq*roh)_*<5 zC$|zE+h$;@GYzvxJ72CZBr!CINPAvAhIkIqcdRppCuMNn^Fc5rALM?Pi%1`h#G zb0yTZ&>Ak7?b_LAl5w{SG^%;W;KKb1fFviMmUtT zHIrtp)MtdC+>2|odk4TWP>HalleTS zSV(=IbGDY5P9pQpBEaXkf4!r7T_JQ{P0#4Lr{X*oMOlAReY&g~E#XN18v+5o(QQGnlP7ACFs zL`Gs_4a!E}Pdf()VqT7SNwv62I=?SvQ!ZPPV1%*oZvQM`-ZOfn%GwCJn=Z;=_3{IF zx>Y7!*uS52G5z3dEq)^DGhqwF_|Qlj=DhBUy1LOu3H!k7yS|0#qv`MnK2klFa;w0G z6smnyZ;0jn??W8m{W>_^@4ZC?0aaR6{2 zU0?8%^?ww;Cv^R;C)9t-0B&@Qf2;E!zVxqm-VHzed%Ka#{1dsK zdDFk^=?zc(hxFQ?NdH9_|8;l&smXR-q4Ddj_}AM1MH~OM<_&NBM?anaMDsg+{MV8< z{PDjfww;diiaq+d$YKP9fK4rvH=E&v@9|Hn(AiKWs3u z{=nwn(1*7zZUgmxSYY7%sl{*bdbjOvqd$JwjgbF=-7i2Ow_R>SC4RWfQvQLAlF!0KOoy#kWpPe|Q@BYntL`tD6Uh+#bXHuyQx}|5*JP(Wn6tuAhzo01#ilqOOa)C=74D F{SWgfZLt6V literal 0 HcmV?d00001 diff --git a/src/main/resources/META-INF/rewrite/dbrider.yml b/src/main/resources/META-INF/rewrite/dbrider.yml new file mode 100644 index 000000000..a6ecc2980 --- /dev/null +++ b/src/main/resources/META-INF/rewrite/dbrider.yml @@ -0,0 +1,32 @@ +# +# 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.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5 +displayName: Migrate rider-spring (JUnit4) to rider-junit5 (JUnit5) +description: This recipe will migrate the necessary dependencies and annotations from DbRider with JUnit4 to JUnit5 in a Spring application. +tags: + - testing + - dbrider + - spring +recipeList: + - org.openrewrite.java.testing.dbrider.ExecutionListenerToDbRiderAnnotation + - org.openrewrite.java.dependencies.ChangeDependency: + oldGroupId: com.github.database-rider + oldArtifactId: rider-spring + newArtifactId: rider-junit5 + newVersion: 1.x +--- \ No newline at end of file diff --git a/src/main/resources/META-INF/rewrite/junit5.yml b/src/main/resources/META-INF/rewrite/junit5.yml index a8a38c9cf..4cbe3fe26 100755 --- a/src/main/resources/META-INF/rewrite/junit5.yml +++ b/src/main/resources/META-INF/rewrite/junit5.yml @@ -142,6 +142,7 @@ recipeList: - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: org.jbehave.core.junit.JUnitStories newFullyQualifiedTypeName: org.jbehave.core.junit.JupiterStories + - org.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5 --- type: specs.openrewrite.org/v1beta/recipe diff --git a/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java b/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java new file mode 100644 index 000000000..b1820b6fc --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java @@ -0,0 +1,233 @@ +/* + * 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.openrewrite.java.testing.dbrider; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ExecutionListenerToDbRiderAnnotationUnitTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "spring-test-6.1", "rider-spring-1.18", "rider-junit5-1.44")) + .recipe(new ExecutionListenerToDbRiderAnnotation()); + } + + @Test + @DocumentExample + void replaceAnnotationIfOnlyDbRiderListenerMergedWithDefaults() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS, listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerReplacedDefaults() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.REPLACE_DEFAULTS, listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerAndNoMergeModeSpecified() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerThroughValue() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(DBRiderTestExecutionListener.class) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerThroughValueArray() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners({DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void keepAnnotationIfOnlyDbRiderListenerSetAndNonDefaultSetting() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(value = {DBRiderTestExecutionListener.class}, inheritListeners = false) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners(inheritListeners = false) + public class Sample {} + """ + ) + ); + } + + @Test + void removeListenerFromOtherListeners() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.REPLACE_DEFAULTS, listeners = {DBRiderTestExecutionListener.class, DirtiesContextTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners(listeners = {DirtiesContextTestExecutionListener.class}) + public class Sample {} + """ + ) + ); + } + + @Test + void doNotTouchIfNoListenerPresent() { + rewriteRun( + //language=java + java( + """ + @Deprecated + public class Sample {} + """ + ) + ); + } + + @Test + void doNotTouchIfDbRiderAlreadyPresent() { + rewriteRun( + //language=java + java( + """ + import com.github.database.rider.junit5.api.DBRider; + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @DBRider + @TestExecutionListeners(listeners = {DBRiderTestExecutionListener.class}, inheritListeners = false) + public class Sample {} + """ + ) + ); + } +} diff --git a/src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar b/src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..6809b154b4374fff36fdb86b4bcc027caf6d82f5 GIT binary patch literal 7276 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 Bm0 Date: Tue, 5 Nov 2024 10:20:37 +0100 Subject: [PATCH 12/20] `assertInstanceOf` not converted from JUnit5 to assertJ (#606) * add test to confirm issue * Add `AssertInstanceOf` support for JUnitToAssertj recipe * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add `AssertInstanceOf` support for JUnitToAssertj recipe * Apply suggestions from code review Co-authored-by: Tim te Beek * Apply suggestions from code review * Apply suggestions from code review * Apply suggestions from code review * Demonstrate that all method invocations are visited, as preconditions apply to file * Call super.visitMethodInvocation first, as is customary unless explicitly needed in a different order * Reduce & fix tests * Update src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Extract constant for ASSERT_INSTANCE_OF_MATCHER * Add multiple returns --------- Co-authored-by: Tim te Beek Co-authored-by: lingenj Co-authored-by: Tim te Beek Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Jacob van Lingen --- .../JUnitAssertInstanceOfToAssertThat.java | 76 ++++++ .../resources/META-INF/rewrite/assertj.yml | 1 + .../assertj/AssertJBestPracticesTest.java | 1 + ...JUnitAssertInstanceOfToAssertThatTest.java | 232 ++++++++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java create mode 100644 src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThatTest.java diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java new file mode 100644 index 000000000..6a727a403 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java @@ -0,0 +1,76 @@ +/* + * 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.openrewrite.java.testing.assertj; + +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.UsesMethod; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; + +public class JUnitAssertInstanceOfToAssertThat extends Recipe { + + private static final MethodMatcher ASSERT_INSTANCE_OF_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertInstanceOf(..)", true); + + @Override + public String getDisplayName() { + return "JUnit `assertInstanceOf` to AssertJ"; + } + + @Override + public String getDescription() { + return "Convert JUnit-style `assertInstanceOf()` to AssertJ's `assertThat().isInstanceOf()`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesMethod<>(ASSERT_INSTANCE_OF_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation md = super.visitMethodInvocation(method, ctx); + if (!ASSERT_INSTANCE_OF_MATCHER.matches(md)) { + return md; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + Expression expectedType = md.getArguments().get(0); + Expression actualValue = md.getArguments().get(1); + if (md.getArguments().size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isInstanceOf(#{any()});") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), method.getCoordinates().replace(), actualValue, expectedType); + } + + Expression messageOrSupplier = md.getArguments().get(2); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isInstanceOf(#{any()});") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), method.getCoordinates().replace(), actualValue, messageOrSupplier, expectedType); + } + }); + } +} diff --git a/src/main/resources/META-INF/rewrite/assertj.yml b/src/main/resources/META-INF/rewrite/assertj.yml index bd979458c..173f603bb 100644 --- a/src/main/resources/META-INF/rewrite/assertj.yml +++ b/src/main/resources/META-INF/rewrite/assertj.yml @@ -466,6 +466,7 @@ recipeList: - org.openrewrite.java.testing.assertj.JUnitAssertTrueToAssertThat - org.openrewrite.java.testing.assertj.JUnitFailToAssertJFail - org.openrewrite.java.testing.assertj.JUnitAssertThrowsToAssertExceptionType + - org.openrewrite.java.testing.assertj.JUnitAssertInstanceOfToAssertThat - org.openrewrite.java.dependencies.AddDependency: groupId: org.assertj artifactId: assertj-core diff --git a/src/test/java/org/openrewrite/java/testing/assertj/AssertJBestPracticesTest.java b/src/test/java/org/openrewrite/java/testing/assertj/AssertJBestPracticesTest.java index f96060d54..b6c37a621 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/AssertJBestPracticesTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/AssertJBestPracticesTest.java @@ -319,4 +319,5 @@ void test(%2$s x, %2$s y, Object value) { template.formatted(imprt, argumentsType, dedicatedAssertion))); } } + } diff --git a/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThatTest.java b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThatTest.java new file mode 100644 index 000000000..10579e3c2 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThatTest.java @@ -0,0 +1,232 @@ +/* + * 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.openrewrite.java.testing.assertj; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class JUnitAssertInstanceOfToAssertThatTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9")) + .recipe(new JUnitAssertInstanceOfToAssertThat()); + } + + @Test + @DocumentExample + void convertsIsInstanceOf() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void convertsIsInstanceOfWithMessage() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4, "error message"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).as("error message").isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void convertsIsInstanceOfWithMessageLambda() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4, () -> "error message"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).as(() -> "error message").isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void convertsIsInstanceOfWithMessageMethodReference() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4, this::getErrorMessage); + } + + String getErrorMessage() { + return "error message"; + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).as(this::getErrorMessage).isInstanceOf(Integer.class); + } + + String getErrorMessage() { + return "error message"; + } + } + """ + ) + ); + } + + @Test + void canBeRerun() { + rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3-*")), + // language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void doesNotConvertAnyOtherMethods() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import static org.junit.jupiter.api.Assertions.assertTrue; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4); + assertTrue(1 == 1, "Message"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + import static org.junit.jupiter.api.Assertions.assertTrue; + + class Test { + void test() { + assertThat(4).isInstanceOf(Integer.class); + assertTrue(1 == 1, "Message"); + } + } + """ + ) + ); + } + + @Test + void doesConvertNestedMethodInvocations() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import static org.junit.jupiter.api.Assertions.assertAll; + + class Test { + void test() { + assertAll(() -> assertInstanceOf(Integer.class, 4)); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + import static org.junit.jupiter.api.Assertions.assertAll; + + class Test { + void test() { + assertAll(() -> assertThat(4).isInstanceOf(Integer.class)); + } + } + """ + ) + ); + } +} From 9118a3c6ea88cf483d35b10107a313a364e646e9 Mon Sep 17 00:00:00 2001 From: Jacob van Lingen Date: Tue, 5 Nov 2024 12:54:36 +0100 Subject: [PATCH 13/20] Add `VerifyZeroToNoMoreInteractions` for Mockito 2 (#631) * Add `VerifyZeroToNoMoreInteractions` for Mockito 2 * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Apply suggestions from code review * Apply formatter * Reuse ChangeMethodName, as that also changes the type and additional cases * Fix casing issue in mockito.yml --------- Co-authored-by: lingenj Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek --- .../VerifyZeroToNoMoreInteractions.java | 84 +++++++ .../resources/META-INF/rewrite/mockito.yml | 1 + .../VerifyZeroToNoMoreInteractionsTest.java | 214 ++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractions.java create mode 100644 src/test/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractionsTest.java diff --git a/src/main/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractions.java b/src/main/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractions.java new file mode 100644 index 000000000..2315e88ba --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractions.java @@ -0,0 +1,84 @@ +/* + * 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.openrewrite.java.testing.mockito; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.java.ChangeMethodName; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.dependencies.DependencyInsight; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.J; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class VerifyZeroToNoMoreInteractions extends ScanningRecipe { + + private static final String VERIFY_ZERO_INTERACTIONS = "org.mockito.Mockito verifyZeroInteractions(..)"; + private static final MethodMatcher ASSERT_INSTANCE_OF_MATCHER = new MethodMatcher(VERIFY_ZERO_INTERACTIONS, true); + + @Override + public String getDisplayName() { + return "Replace `verifyZeroInteractions() to `verifyNoMoreInteractions()"; + } + + @Override + public String getDescription() { + return "Replaces `verifyZeroInteractions()` with `verifyNoMoreInteractions()` in Mockito tests when migration when using a Mockito version < 3.x."; + } + + @Override + public AtomicBoolean getInitialValue(final ExecutionContext ctx) { + return new AtomicBoolean(false); + } + + @Override + public TreeVisitor getScanner(AtomicBoolean usingOlderMockito) { + TreeVisitor div = new DependencyInsight("org.mockito", "mockito-*", "[1.0,3.0)", null).getVisitor(); + return new TreeVisitor() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!usingOlderMockito.get() && div.visit(tree, ctx) != tree) { + usingOlderMockito.set(true); + } + return tree; + } + }; + } + + @Override + public TreeVisitor getVisitor(AtomicBoolean usingOlderMockito) { + return Preconditions.check(usingOlderMockito.get(), + Preconditions.check(new UsesMethod<>(ASSERT_INSTANCE_OF_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation md = super.visitMethodInvocation(method, ctx); + + if (!ASSERT_INSTANCE_OF_MATCHER.matches(md)) { + return md; + } + + maybeAddImport("org.mockito.Mockito", "verifyNoMoreInteractions"); + maybeRemoveImport("org.mockito.Mockito.verifyZeroInteractions"); + + ChangeMethodName changeMethodName = new ChangeMethodName(VERIFY_ZERO_INTERACTIONS, "verifyNoMoreInteractions", false, false); + return (J.MethodInvocation) changeMethodName.getVisitor().visitNonNull(md, ctx); + } + }) + ); + } +} diff --git a/src/main/resources/META-INF/rewrite/mockito.yml b/src/main/resources/META-INF/rewrite/mockito.yml index d1b62eaf2..dc569f5e0 100644 --- a/src/main/resources/META-INF/rewrite/mockito.yml +++ b/src/main/resources/META-INF/rewrite/mockito.yml @@ -132,6 +132,7 @@ recipeList: oldParameterNames: - mode - verification + - org.openrewrite.java.testing.mockito.VerifyZeroToNoMoreInteractions - org.openrewrite.java.ChangeMethodName: methodPattern: org.mockito.Mockito verifyZeroInteractions(..) newMethodName: verifyNoInteractions diff --git a/src/test/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractionsTest.java b/src/test/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractionsTest.java new file mode 100644 index 000000000..6b342323f --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractionsTest.java @@ -0,0 +1,214 @@ +/* + * 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.openrewrite.java.testing.mockito; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.maven.Assertions.pomXml; + +class VerifyZeroToNoMoreInteractionsTest implements RewriteTest { + + @Language("xml") + private static final String POM_XML_WITH_MOCKITO_2 = """ + + 4.0.0 + bla.bla + bla-bla + 1.0.0 + + + org.mockito + mockito-core + 2.17.0 + test + + + + """; + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "mockito-core-3", "mockito-junit-jupiter-3")) + .recipe(new VerifyZeroToNoMoreInteractions()); + } + + @Test + @DocumentExample + void shouldReplaceToNoMoreInteractions() { + //language=java + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + java( + """ + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest { + void test() { + verifyZeroInteractions(System.out); + } + } + """, + """ + import static org.mockito.Mockito.verifyNoMoreInteractions; + + class MyTest { + void test() { + verifyNoMoreInteractions(System.out); + } + } + """ + ) + ); + } + + @Test + void shouldNotReplaceToNoMoreInteractionsForImportOnly() { + //language=java + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + java( + """ + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest {} + """ + ) + ); + } + + @Test + void doesNotConvertAnyOtherMethods() { + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + // language=java + java( + """ + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.Mock; + import static org.mockito.Mockito.verifyZeroInteractions; + import static org.mockito.Mockito.verify; + + class MyTest { + @Mock + Object myObject; + + void test() { + verifyZeroInteractions(System.out); + verify(myObject); + } + } + """, + """ + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.Mock; + import static org.mockito.Mockito.verifyNoMoreInteractions; + import static org.mockito.Mockito.verify; + + class MyTest { + @Mock + Object myObject; + + void test() { + verifyNoMoreInteractions(System.out); + verify(myObject); + } + } + """ + ) + ); + } + + @Test + void doesConvertNestedMethodInvocations() { + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + // language=java + java( + """ + import java.util.function.Consumer; + + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest { + void test() { + Runnable f = () -> verifyZeroInteractions(System.out); + f.run(); + } + } + """, + """ + import java.util.function.Consumer; + + import static org.mockito.Mockito.verifyNoMoreInteractions; + + class MyTest { + void test() { + Runnable f = () -> verifyNoMoreInteractions(System.out); + f.run(); + } + } + """ + ) + ); + } + + @Test + void shouldNotRunOnNewerMockito3OrHigher() { + rewriteRun( + //language=xml + pomXml( + """ + + 4.0.0 + bla.bla + bla-bla + 1.0.0 + + + org.mockito + mockito-core + 3.0.0 + test + + + + """), + //language=java + java( + """ + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest { + void test() { + verifyZeroInteractions(System.out); + } + } + """ + ) + ); + } +} From 6d665a0f3e8c10c515fd31716dd189603c49f2b4 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 5 Nov 2024 14:00:35 +0100 Subject: [PATCH 14/20] Do not execute Mockito1to4Migration twice in JUnit4to5Migration --- src/main/resources/META-INF/rewrite/junit5.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/META-INF/rewrite/junit5.yml b/src/main/resources/META-INF/rewrite/junit5.yml index 4cbe3fe26..b4a71a983 100755 --- a/src/main/resources/META-INF/rewrite/junit5.yml +++ b/src/main/resources/META-INF/rewrite/junit5.yml @@ -129,7 +129,6 @@ recipeList: onlyIfUsing: org.junit.jupiter.params.ParameterizedTest acceptTransitive: true scope: test - - org.openrewrite.java.testing.mockito.Mockito1to4Migration - org.openrewrite.maven.UpgradePluginVersion: groupId: org.apache.maven.plugins artifactId: maven-surefire-plugin From 671addcda3325aeeca3335f736bbce701178212a Mon Sep 17 00:00:00 2001 From: Adam Birem Date: Tue, 5 Nov 2024 17:26:57 +0100 Subject: [PATCH 15/20] Added handling of org.mockito.Mockito.eq(..) when simplifying mockito matchers (#635) * Add test case for issue https://github.com/openrewrite/rewrite-testing-frameworks/issues/634 * Add handling of org.mockito.Mockito.eq * Apply formatter to minimize diff * Add issue reference to document the change --------- Co-authored-by: Tim te Beek --- .../SimplifyMockitoVerifyWhenGiven.java | 50 +++++++------- .../SimplifyMockitoVerifyWhenGivenTest.java | 67 ++++++++++++++----- 2 files changed, 77 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGiven.java b/src/main/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGiven.java index 76260a777..e837aa9be 100644 --- a/src/main/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGiven.java +++ b/src/main/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGiven.java @@ -39,6 +39,7 @@ public class SimplifyMockitoVerifyWhenGiven extends Recipe { private static final MethodMatcher VERIFY_MATCHER = new MethodMatcher("org.mockito.Mockito verify(..)"); private static final MethodMatcher STUBBER_MATCHER = new MethodMatcher("org.mockito.stubbing.Stubber when(..)"); private static final MethodMatcher EQ_MATCHER = new MethodMatcher("org.mockito.ArgumentMatchers eq(..)"); + private static final MethodMatcher MOCKITO_EQ_MATCHER = new MethodMatcher("org.mockito.Mockito eq(..)"); @Override public String getDisplayName() { @@ -57,32 +58,35 @@ public Set getTags() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesMethod<>(EQ_MATCHER), new JavaIsoVisitor() { - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); + return Preconditions.check(Preconditions.or(new UsesMethod<>(EQ_MATCHER), new UsesMethod<>(MOCKITO_EQ_MATCHER)), + new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); - if ((WHEN_MATCHER.matches(mi) || GIVEN_MATCHER.matches(mi)) && mi.getArguments().get(0) instanceof J.MethodInvocation) { - List updatedArguments = new ArrayList<>(mi.getArguments()); - updatedArguments.set(0, checkAndUpdateEq((J.MethodInvocation) mi.getArguments().get(0))); - mi = mi.withArguments(updatedArguments); - } else if (VERIFY_MATCHER.matches(mi.getSelect()) || - STUBBER_MATCHER.matches(mi.getSelect())) { - mi = checkAndUpdateEq(mi); - } + if ((WHEN_MATCHER.matches(mi) || GIVEN_MATCHER.matches(mi)) && mi.getArguments().get(0) instanceof J.MethodInvocation) { + List updatedArguments = new ArrayList<>(mi.getArguments()); + updatedArguments.set(0, checkAndUpdateEq((J.MethodInvocation) mi.getArguments().get(0))); + mi = mi.withArguments(updatedArguments); + } else if (VERIFY_MATCHER.matches(mi.getSelect()) || + STUBBER_MATCHER.matches(mi.getSelect())) { + mi = checkAndUpdateEq(mi); + } - maybeRemoveImport("org.mockito.ArgumentMatchers.eq"); - return mi; - } + maybeRemoveImport("org.mockito.ArgumentMatchers.eq"); + maybeRemoveImport("org.mockito.Mockito.eq"); + return mi; + } - private J.MethodInvocation checkAndUpdateEq(J.MethodInvocation methodInvocation) { - if (methodInvocation.getArguments().stream().allMatch(EQ_MATCHER::matches)) { - return methodInvocation.withArguments(ListUtils.map(methodInvocation.getArguments(), invocation -> - ((MethodCall) invocation).getArguments().get(0).withPrefix(invocation.getPrefix()))); - } - return methodInvocation; - } - }); + private J.MethodInvocation checkAndUpdateEq(J.MethodInvocation methodInvocation) { + if (methodInvocation.getArguments().stream().allMatch(arg -> EQ_MATCHER.matches(arg) || + MOCKITO_EQ_MATCHER.matches(arg))) { + return methodInvocation.withArguments(ListUtils.map(methodInvocation.getArguments(), invocation -> + ((MethodCall) invocation).getArguments().get(0).withPrefix(invocation.getPrefix()))); + } + return methodInvocation; + } + }); } } diff --git a/src/test/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGivenTest.java b/src/test/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGivenTest.java index 69815db02..baa688cdf 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGivenTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGivenTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -42,7 +43,7 @@ void shouldRemoveUnneccesaryEqFromVerify() { import static org.mockito.Mockito.verify; import static org.mockito.Mockito.mock; import static org.mockito.ArgumentMatchers.eq; - + class Test { void test() { var mockString = mock(String.class); @@ -52,7 +53,39 @@ void test() { """, """ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.mock; - + + class Test { + void test() { + var mockString = mock(String.class); + verify(mockString).replace("foo", "bar"); + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/634") + void shouldRemoveUnneccesaryEqFromVerify_withMockitoStarImport() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.eq; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.verify; + + class Test { + void test() { + var mockString = mock(String.class); + verify(mockString).replace(eq("foo"), eq("bar")); + } + } + """, """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.verify; + class Test { void test() { var mockString = mock(String.class); @@ -73,7 +106,7 @@ void shouldRemoveUnneccesaryEqFromWhen() { import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.eq; - + class Test { void test() { var mockString = mock(String.class); @@ -83,7 +116,7 @@ void test() { """, """ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; - + class Test { void test() { var mockString = mock(String.class); @@ -105,7 +138,7 @@ void shouldNotRemoveEqWhenMatchersAreMixed() { import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.anyString; - + class Test { void test() { var mockString = mock(String.class); @@ -125,7 +158,7 @@ void shouldRemoveUnneccesaryEqFromStubber() { """ import static org.mockito.Mockito.doThrow; import static org.mockito.ArgumentMatchers.eq; - + class Test { void test() { doThrow(new RuntimeException()).when("foo").substring(eq(1)); @@ -133,7 +166,7 @@ void test() { } """, """ import static org.mockito.Mockito.doThrow; - + class Test { void test() { doThrow(new RuntimeException()).when("foo").substring(1); @@ -152,7 +185,7 @@ void shouldRemoveUnneccesaryEqFromBDDGiven() { """ import static org.mockito.BDDMockito.given; import static org.mockito.ArgumentMatchers.eq; - + class Test { void test() { given("foo".substring(eq(1))); @@ -160,7 +193,7 @@ void test() { } """, """ import static org.mockito.BDDMockito.given; - + class Test { void test() { given("foo".substring(1)); @@ -181,13 +214,13 @@ void shouldNotRemoveEqImportWhenStillNeeded() { import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.anyString; - + class Test { void testRemoveEq() { var mockString = mock(String.class); when(mockString.replace(eq("foo"), eq("bar"))).thenReturn("bar"); } - + void testKeepEq() { var mockString = mock(String.class); when(mockString.replace(eq("foo"), anyString())).thenReturn("bar"); @@ -198,13 +231,13 @@ void testKeepEq() { import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.anyString; - + class Test { void testRemoveEq() { var mockString = mock(String.class); when(mockString.replace("foo", "bar")).thenReturn("bar"); } - + void testKeepEq() { var mockString = mock(String.class); when(mockString.replace(eq("foo"), anyString())).thenReturn("bar"); @@ -227,7 +260,7 @@ void shouldFixSonarExamples() { import static org.mockito.Mockito.doThrow; import static org.mockito.BDDMockito.given; import static org.mockito.ArgumentMatchers.eq; - + class Test { void test(Object v1, Object v2, Object v3, Object v4, Object v5, Foo foo) { given(foo.bar(eq(v1), eq(v2), eq(v3))).willReturn(null); @@ -236,7 +269,7 @@ void test(Object v1, Object v2, Object v3, Object v4, Object v5, Foo foo) { verify(foo).bar(eq(v1), eq(v2), eq(v3)); } } - + class Foo { Object bar(Object v1, Object v2, Object v3) { return null; } String baz(Object v4, Object v5) { return ""; } @@ -248,7 +281,7 @@ void quux(int x) {} import static org.mockito.Mockito.verify; import static org.mockito.Mockito.doThrow; import static org.mockito.BDDMockito.given; - + class Test { void test(Object v1, Object v2, Object v3, Object v4, Object v5, Foo foo) { given(foo.bar(v1, v2, v3)).willReturn(null); @@ -257,7 +290,7 @@ void test(Object v1, Object v2, Object v3, Object v4, Object v5, Foo foo) { verify(foo).bar(v1, v2, v3); } } - + class Foo { Object bar(Object v1, Object v2, Object v3) { return null; } String baz(Object v4, Object v5) { return ""; } From 909f0ebbc5f5ad3f599476306efcd3af0476ff09 Mon Sep 17 00:00:00 2001 From: Jacob van Lingen Date: Tue, 5 Nov 2024 17:34:10 +0100 Subject: [PATCH 16/20] Update AssertJ recipes to current recipe code style (#633) * Update AssertJ recipes to current recipe code style * Apply formatter and remove unused imports * Apply suggestions from code review --------- Co-authored-by: Tim te Beek --- .../AdoptAssertJDurationAssertions.java | 369 +++++++++--------- .../JUnitAssertArrayEqualsToAssertThat.java | 156 ++++---- .../JUnitAssertEqualsToAssertThat.java | 156 +++----- .../assertj/JUnitAssertFalseToAssertThat.java | 85 ++-- .../JUnitAssertInstanceOfToAssertThat.java | 18 +- .../JUnitAssertNotEqualsToAssertThat.java | 163 +++----- .../JUnitAssertNotNullToAssertThat.java | 88 ++--- .../assertj/JUnitAssertNullToAssertThat.java | 86 ++-- .../assertj/JUnitAssertSameToAssertThat.java | 90 ++--- ...UnitAssertThrowsToAssertExceptionType.java | 70 ++-- .../assertj/JUnitAssertTrueToAssertThat.java | 87 ++--- .../assertj/JUnitFailToAssertJFail.java | 162 +++----- .../assertj/SimplifyAssertJAssertion.java | 54 ++- .../SimplifyChainedAssertJAssertion.java | 165 ++++---- ...AssertThrowsToAssertExceptionTypeTest.java | 11 +- 15 files changed, 719 insertions(+), 1041 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java b/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java index ef26a84bd..b84ca6133 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java @@ -32,17 +32,48 @@ import java.util.*; -public class AdoptAssertJDurationAssertions extends Recipe { - - static final String DURATION_ASSERT_HAS_LONG = "org.assertj.core.api.AbstractDurationAssert has*(long)"; +import static org.openrewrite.Preconditions.or; - static final String INTEGER_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractIntegerAssert isEqualTo(..)"; - static final String INTEGER_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractIntegerAssert isGreaterThan(..)"; - static final String INTEGER_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractIntegerAssert isLessThan(..)"; +public class AdoptAssertJDurationAssertions extends Recipe { - static final String LONG_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractLongAssert isLessThan(..)"; - static final String LONG_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractLongAssert isGreaterThan(..)"; - static final String LONG_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractLongAssert isEqualTo(..)"; + private static final String DURATION_ASSERT_HAS_LONG = "org.assertj.core.api.AbstractDurationAssert has*(long)"; + private static final String INTEGER_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractIntegerAssert isEqualTo(..)"; + private static final String INTEGER_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractIntegerAssert isGreaterThan(..)"; + private static final String INTEGER_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractIntegerAssert isLessThan(..)"; + private static final String LONG_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractLongAssert isLessThan(..)"; + private static final String LONG_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractLongAssert isGreaterThan(..)"; + private static final String LONG_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractLongAssert isEqualTo(..)"; + + private static final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); + private static final MethodMatcher GET_NANO_MATCHER = new MethodMatcher("java.time.Duration getNano()"); + private static final MethodMatcher GET_SECONDS_MATCHER = new MethodMatcher("java.time.Duration getSeconds()"); + private static final MethodMatcher AS_MATCHER = new MethodMatcher("org.assertj.core.api.AbstractObjectAssert as(..)"); + private static final MethodMatcher TIME_UNIT_MATCHERS = new MethodMatcher(DURATION_ASSERT_HAS_LONG, true); + + private static final List IS_MATCHERS = Arrays.asList( + new MethodMatcher(INTEGER_ASSERT_IS_EQUAL_TO, true), + new MethodMatcher(INTEGER_ASSERT_IS_GREATER_THAN, true), + new MethodMatcher(INTEGER_ASSERT_IS_LESS_THAN, true), + + new MethodMatcher(LONG_ASSERT_IS_EQUAL_TO, true), + new MethodMatcher(LONG_ASSERT_IS_GREATER_THAN, true), + new MethodMatcher(LONG_ASSERT_IS_LESS_THAN, true) + ); + + private static final Map METHOD_MAP = new HashMap() {{ + put("getSeconds", "hasSeconds"); + put("getNano", "hasNanos"); + + put("hasNanos", "hasMillis"); + put("hasMillis", "hasSeconds"); + put("hasSeconds", "hasMinutes"); + put("hasMinutes", "hasHours"); + put("hasHours", "hasDays"); + + put("isGreaterThan", "isPositive"); + put("isLessThan", "isNegative"); + put("isEqualTo", "isZero"); + }}; @Override public String getDisplayName() { @@ -56,190 +87,156 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(Preconditions.or( + return Preconditions.check( + or( new UsesMethod<>(DURATION_ASSERT_HAS_LONG, true), - new UsesMethod<>(INTEGER_ASSERT_IS_EQUAL_TO, true), new UsesMethod<>(INTEGER_ASSERT_IS_GREATER_THAN, true), new UsesMethod<>(INTEGER_ASSERT_IS_LESS_THAN, true), - new UsesMethod<>(LONG_ASSERT_IS_EQUAL_TO, true), new UsesMethod<>(LONG_ASSERT_IS_GREATER_THAN, true), new UsesMethod<>(LONG_ASSERT_IS_LESS_THAN, true) - ), new AdoptAssertJDurationAssertionsVisitor() - ); - } - - @SuppressWarnings("DataFlowIssue") - private static class AdoptAssertJDurationAssertionsVisitor extends JavaIsoVisitor { - private static final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); - private static final MethodMatcher GET_NANO_MATCHER = new MethodMatcher("java.time.Duration getNano()"); - private static final MethodMatcher GET_SECONDS_MATCHER = new MethodMatcher("java.time.Duration getSeconds()"); - private static final MethodMatcher AS_MATCHER = new MethodMatcher("org.assertj.core.api.AbstractObjectAssert as(..)"); - private static final MethodMatcher TIME_UNIT_MATCHERS = new MethodMatcher(DURATION_ASSERT_HAS_LONG, true); - private static final List IS_MATCHERS = Arrays.asList( - new MethodMatcher(INTEGER_ASSERT_IS_EQUAL_TO, true), - new MethodMatcher(INTEGER_ASSERT_IS_GREATER_THAN, true), - new MethodMatcher(INTEGER_ASSERT_IS_LESS_THAN, true), - - new MethodMatcher(LONG_ASSERT_IS_EQUAL_TO, true), - new MethodMatcher(LONG_ASSERT_IS_GREATER_THAN, true), - new MethodMatcher(LONG_ASSERT_IS_LESS_THAN, true) - ); - private static final Map METHOD_MAP = new HashMap() {{ - put("getSeconds", "hasSeconds"); - put("getNano", "hasNanos"); - - put("hasNanos", "hasMillis"); - put("hasMillis", "hasSeconds"); - put("hasSeconds", "hasMinutes"); - put("hasMinutes", "hasHours"); - put("hasHours", "hasDays"); - - put("isGreaterThan", "isPositive"); - put("isLessThan", "isNegative"); - put("isEqualTo", "isZero"); - }}; - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); - if (TIME_UNIT_MATCHERS.matches(mi)) { - return simplifyTimeUnits(mi, ctx); - } else if (IS_MATCHERS.stream().anyMatch(matcher -> matcher.matches(mi))) { - return simplifyMultipleAssertions(mi, ctx); - } - return mi; - } - - private J.MethodInvocation simplifyMultipleAssertions(J.MethodInvocation m, ExecutionContext ctx) { - Expression isEqualToArg = m.getArguments().get(0); - Expression select = m.getSelect(); - List templateParameters = new ArrayList<>(); - templateParameters.add(null); - Expression asDescription = null; - - if (AS_MATCHER.matches(select)) { - asDescription = ((J.MethodInvocation) select).getArguments().get(0); - select = ((J.MethodInvocation) select).getSelect(); - templateParameters.add(asDescription); - } - - if (!ASSERT_THAT_MATCHER.matches(select)) { - return m; - } - - Expression assertThatArgumentExpr = ((J.MethodInvocation) select).getArguments().get(0); - if (!(assertThatArgumentExpr instanceof J.MethodInvocation)) { - return m; - } - J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThatArgumentExpr; - - if (isZero(isEqualToArg) && checkIfRelatedToDuration(assertThatArg)) { - String formatted_template = formatTemplate("assertThat(#{any()}).%s();", m.getSimpleName(), asDescription); - templateParameters.set(0, assertThatArg); - return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); - } - - if (GET_NANO_MATCHER.matches(assertThatArg) || GET_SECONDS_MATCHER.matches(assertThatArg)) { - Expression assertThatArgSelect = assertThatArg.getSelect(); - String methodName = assertThatArg.getSimpleName(); - String formatted_template = formatTemplate("assertThat(#{any()}).%s(#{any()});", methodName, asDescription); - templateParameters.set(0, assertThatArgSelect); - templateParameters.add(isEqualToArg); - - return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); - } - - return m; - } - - private boolean isZero(Expression isEqualToArg) { - if (isEqualToArg instanceof J.Literal) { - J.Literal literal = (J.Literal) isEqualToArg; - return literal.getValue() instanceof Number && ((Number) literal.getValue()).longValue() == 0; - } - return false; - } - - private J.MethodInvocation simplifyTimeUnits(J.MethodInvocation m, ExecutionContext ctx) { - Expression arg = m.getArguments().get(0); - Long argValue = SimplifyDurationCreationUnits.getConstantIntegralValue(arg); - if (argValue == null) { - return m; - } - - List unitInfo = getUnitInfo(m.getSimpleName(), Math.toIntExact(argValue)); - String methodName = (String) unitInfo.get(0); - int methodArg = (int) unitInfo.get(1); - if (!(m.getSimpleName().equals(methodName))) { - // update method invocation with new name and arg - String template = String.format("#{any()}.%s(%d)", methodName, methodArg); - return applyTemplate(ctx, m, template, m.getSelect()); - } - - return m; - } - - private static List getUnitInfo(String name, int argValue) { - final int timeLength; - if (name.equals("hasSeconds") || name.equals("hasMinutes")) { - timeLength = 60; - } else if (name.equals("hasNanos") || name.equals("hasMillis")) { - timeLength = 1000; - } else if (name.equals("hasHours")) { - timeLength = 24; - } else { - return Arrays.asList(name, argValue); - } - - if (argValue % timeLength == 0) { - String newName = METHOD_MAP.get(name); - return getUnitInfo(newName, argValue / timeLength); - } else { - // returning name, newArg - return Arrays.asList(name, argValue); - } - } - - private J.MethodInvocation applyTemplate(ExecutionContext ctx, J.MethodInvocation m, String template, Object... parameters) { - J.MethodInvocation invocation = JavaTemplate.builder(template) - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) - .build() - .apply(getCursor(), m.getCoordinates().replace(), parameters); - - // retain whitespace formatting - if (invocation.getPadding().getSelect() != null && m.getPadding().getSelect() != null) { - return invocation.getPadding() - .withSelect( - invocation.getPadding().getSelect() - .withAfter(m.getPadding().getSelect().getAfter()) - ); - } - return invocation; - } - - private boolean checkIfRelatedToDuration(J.MethodInvocation argument) { - // assertThat(.).isEqual(0) - if (argument.getSelect() != null) { - if (argument.getSelect() instanceof J.MethodInvocation) { - J.MethodInvocation selectMethod = (J.MethodInvocation) argument.getSelect(); - return TypeUtils.isOfType(selectMethod.getType(), JavaType.buildType("java.time.Duration")); + ), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (TIME_UNIT_MATCHERS.matches(mi)) { + return simplifyTimeUnits(mi, ctx); + } else if (IS_MATCHERS.stream().anyMatch(matcher -> matcher.matches(mi))) { + return simplifyMultipleAssertions(mi, ctx); + } + return mi; + } + + private J.MethodInvocation simplifyMultipleAssertions(J.MethodInvocation m, ExecutionContext ctx) { + Expression isEqualToArg = m.getArguments().get(0); + Expression select = m.getSelect(); + List templateParameters = new ArrayList<>(); + templateParameters.add(null); + Expression asDescription = null; + + if (AS_MATCHER.matches(select)) { + asDescription = ((J.MethodInvocation) select).getArguments().get(0); + select = ((J.MethodInvocation) select).getSelect(); + templateParameters.add(asDescription); + } + + if (!ASSERT_THAT_MATCHER.matches(select)) { + return m; + } + + Expression assertThatArgumentExpr = ((J.MethodInvocation) select).getArguments().get(0); + if (!(assertThatArgumentExpr instanceof J.MethodInvocation)) { + return m; + } + J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThatArgumentExpr; + + if (isZero(isEqualToArg) && checkIfRelatedToDuration(assertThatArg)) { + String formatted_template = formatTemplate("assertThat(#{any()}).%s();", m.getSimpleName(), asDescription); + templateParameters.set(0, assertThatArg); + return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); + } + + if (GET_NANO_MATCHER.matches(assertThatArg) || GET_SECONDS_MATCHER.matches(assertThatArg)) { + Expression assertThatArgSelect = assertThatArg.getSelect(); + String methodName = assertThatArg.getSimpleName(); + String formatted_template = formatTemplate("assertThat(#{any()}).%s(#{any()});", methodName, asDescription); + templateParameters.set(0, assertThatArgSelect); + templateParameters.add(isEqualToArg); + + return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); + } + + return m; + } + + private boolean isZero(Expression isEqualToArg) { + if (isEqualToArg instanceof J.Literal) { + J.Literal literal = (J.Literal) isEqualToArg; + return literal.getValue() instanceof Number && ((Number) literal.getValue()).longValue() == 0; + } + return false; + } + + private J.MethodInvocation simplifyTimeUnits(J.MethodInvocation m, ExecutionContext ctx) { + Expression arg = m.getArguments().get(0); + Long argValue = SimplifyDurationCreationUnits.getConstantIntegralValue(arg); + if (argValue == null) { + return m; + } + + List unitInfo = getUnitInfo(m.getSimpleName(), Math.toIntExact(argValue)); + String methodName = (String) unitInfo.get(0); + int methodArg = (int) unitInfo.get(1); + if (!(m.getSimpleName().equals(methodName))) { + // update method invocation with new name and arg + String template = String.format("#{any()}.%s(%d)", methodName, methodArg); + return applyTemplate(ctx, m, template, m.getSelect()); + } + + return m; + } + + private List getUnitInfo(String name, int argValue) { + final int timeLength; + if (name.equals("hasSeconds") || name.equals("hasMinutes")) { + timeLength = 60; + } else if (name.equals("hasNanos") || name.equals("hasMillis")) { + timeLength = 1000; + } else if (name.equals("hasHours")) { + timeLength = 24; + } else { + return Arrays.asList(name, argValue); + } + + if (argValue % timeLength == 0) { + String newName = METHOD_MAP.get(name); + return getUnitInfo(newName, argValue / timeLength); + } else { + // returning name, newArg + return Arrays.asList(name, argValue); + } + } + + private J.MethodInvocation applyTemplate(ExecutionContext ctx, J.MethodInvocation m, String template, Object... parameters) { + J.MethodInvocation invocation = JavaTemplate.builder(template) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), m.getCoordinates().replace(), parameters); + + // retain whitespace formatting + if (invocation.getPadding().getSelect() != null && m.getPadding().getSelect() != null) { + return invocation.getPadding() + .withSelect( + invocation.getPadding().getSelect() + .withAfter(m.getPadding().getSelect().getAfter()) + ); + } + return invocation; + } + + private boolean checkIfRelatedToDuration(J.MethodInvocation argument) { + if (argument.getSelect() != null) { + if (argument.getSelect() instanceof J.MethodInvocation) { + J.MethodInvocation selectMethod = (J.MethodInvocation) argument.getSelect(); + return TypeUtils.isOfType(selectMethod.getType(), JavaType.buildType("java.time.Duration")); + } + } + return false; + } + + @SuppressWarnings("ConstantValue") + private String formatTemplate(String template, String methodName, Object asDescriptionArg) { + String replacementMethod = METHOD_MAP.get(methodName); + if (asDescriptionArg == null) { + return String.format(template, replacementMethod); + } + StringBuilder newTemplate = new StringBuilder(template); + newTemplate.insert(newTemplate.indexOf(").") + 1, ".as(#{any()})"); + return String.format(newTemplate.toString(), replacementMethod); + } } - } - return false; - } - - @SuppressWarnings("ConstantValue") - private String formatTemplate(String template, String methodName, Object asDescriptionArg) { - String replacementMethod = METHOD_MAP.get(methodName); - if (asDescriptionArg == null) { - return String.format(template, replacementMethod); - } - StringBuilder newTemplate = new StringBuilder(template); - newTemplate.insert(newTemplate.indexOf(").") + 1, ".as(#{any()})"); - return String.format(newTemplate.toString(), replacementMethod); - } + ); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java index ecdde2479..2c636e98f 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java @@ -23,7 +23,7 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -32,11 +32,14 @@ import java.util.List; public class JUnitAssertArrayEqualsToAssertThat extends Recipe { - private static final String JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME = "org.junit.jupiter.api.Assertions"; + + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher ASSERT_ARRAY_EQUALS_MATCHER = new MethodMatcher(JUNIT + " assertArrayEquals(..)", true); @Override public String getDisplayName() { - return "JUnit `assertArrayEquals` To AssertJ"; + return "JUnit `assertArrayEquals` to assertJ"; } @Override @@ -46,93 +49,72 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>(JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME, false), new AssertArrayEqualsToAssertThatVisitor()); - } - - public static class AssertArrayEqualsToAssertThatVisitor extends JavaIsoVisitor { - private static final MethodMatcher JUNIT_ASSERT_EQUALS = new MethodMatcher(JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME + " assertArrayEquals(..)"); - - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_EQUALS.matches(method)) { - return method; - } - - List args = method.getArguments(); - Expression expected = args.get(0); - Expression actual = args.get(1); - - // Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - maybeRemoveImport(JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME); - - if (args.size() == 2) { - return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected); - } else if (args.size() == 3 && !isFloatingPointType(args.get(2))) { - Expression message = args.get(2); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(String)}).containsExactly(#{anyArray()});") : - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(java.util.function.Supplier)}).containsExactly(#{anyArray()});"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, message, expected); - } else if (args.size() == 3) { - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - // assert is using floating points with a delta and no message. - return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()}, within(#{any()}));") - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) + return Preconditions.check(new UsesMethod<>(ASSERT_ARRAY_EQUALS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation md = super.visitMethodInvocation(method, ctx); + if (!ASSERT_ARRAY_EQUALS_MATCHER.matches(md)) { + return md; + } + + maybeAddImport(ASSERTJ, "assertThat", false); + maybeRemoveImport(JUNIT); + + List args = md.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), md.getCoordinates().replace(), actual, expected); + } + if (args.size() == 3 && isFloatingPointType(args.get(2))) { + maybeAddImport(ASSERTJ, "within", false); + // assert is using floating points with a delta and no message. + return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), md.getCoordinates().replace(), actual, expected, args.get(2)); + } + if (args.size() == 3) { + Expression message = args.get(2); + return JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any()}).containsExactly(#{anyArray()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), md.getCoordinates().replace(), actual, message, expected); + } + + maybeAddImport(ASSERTJ, "within", false); + + // The assertEquals is using a floating point with a delta argument and a message. + Expression message = args.get(3); + return JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any()}).containsExactly(#{anyArray()}, within(#{}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected, args.get(2)); + .apply(getCursor(), md.getCoordinates().replace(), actual, message, expected, args.get(2)); } - // The assertEquals is using a floating point with a delta argument and a message. - Expression message = args.get(3); - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(String)}).containsExactly(#{anyArray()}, within(#{any()}));") : - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(java.util.function.Supplier)}).containsExactly(#{anyArray()}, within(#{}));"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, message, expected, args.get(2)); - } - - /** - * Returns true if the expression's type is either a primitive float/double or their object forms Float/Double - * - * @param expression The expression parsed from the original AST. - * @return true if the type is a floating point number. - */ - private static boolean isFloatingPointType(Expression expression) { - - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); - if (fullyQualified != null) { - String typeName = fullyQualified.getFullyQualifiedName(); - return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + /** + * Returns true if the expression's type is either a primitive float/double or their object forms Float/Double + * + * @param expression The expression parsed from the original AST. + * @return true if the type is a floating point number. + */ + private boolean isFloatingPointType(Expression expression) { + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); + if (fullyQualified != null) { + String typeName = fullyQualified.getFullyQualifiedName(); + return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + } + + JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); + return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; } - - JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); - return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java index 276c3870d..cbfad48c2 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java @@ -23,7 +23,7 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -33,6 +33,10 @@ public class JUnitAssertEqualsToAssertThat extends Recipe { + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher ASSERT_EQUALS_MATCHER = new MethodMatcher(JUNIT + " assertEquals(..)", true); + @Override public String getDisplayName() { return "JUnit `assertEquals` to AssertJ"; @@ -45,105 +49,67 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertEqualsToAssertThatVisitor()); - } - - public static class AssertEqualsToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_EQUALS = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertEquals(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_EQUALS.matches(method)) { - return method; - } - - List args = method.getArguments(); - Expression expected = args.get(0); - Expression actual = args.get(1); - - //always add the import (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - if (args.size() == 2) { - return JavaTemplate.builder("assertThat(#{any()}).isEqualTo(#{any()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected); - } else if (args.size() == 3 && !isFloatingPointType(args.get(2))) { - Expression message = args.get(2); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isEqualTo(#{any()});") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isEqualTo(#{any()});"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat") + return Preconditions.check(new UsesMethod<>(ASSERT_EQUALS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_EQUALS_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport(ASSERTJ, "assertThat", false); + maybeRemoveImport(JUNIT); + + List args = mi.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected); + } + if (args.size() == 3 && !isFloatingPointType(args.get(2))) { + Expression message = args.get(2); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .imports("java.util.function.Supplier") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected); + } + if (args.size() == 3) { + maybeAddImport(ASSERTJ, "within", false); + return JavaTemplate.builder("assertThat(#{any()}).isCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected, args.get(2)); + } + + maybeAddImport(ASSERTJ, "within", false); + + // The assertEquals is using a floating point with a delta argument and a message. + Expression message = args.get(3); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") .imports("java.util.function.Supplier") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected - ); - } else if (args.size() == 3) { - //always add the import (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - return JavaTemplate.builder("assertThat(#{any()}).isCloseTo(#{any()}, within(#{any()}));") - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected, args.get(2)); - + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected, args.get(2)); } - // The assertEquals is using a floating point with a delta argument and a message. - Expression message = args.get(3); - - //always add the import (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isCloseTo(#{any()}, within(#{any()}));") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isCloseTo(#{any()}, within(#{any()}));"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .imports("java.util.function.Supplier") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected, - args.get(2) - ); - } + private boolean isFloatingPointType(Expression expression) { + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); + if (fullyQualified != null) { + String typeName = fullyQualified.getFullyQualifiedName(); + return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + } - private static boolean isFloatingPointType(Expression expression) { - - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); - if (fullyQualified != null) { - String typeName = fullyQualified.getFullyQualifiedName(); - return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); + return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; } - - JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); - return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java index 149e5b3df..84d1462a1 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java @@ -23,15 +23,16 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertFalseToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_FALSE_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertFalse(boolean, ..)", true); + @Override public String getDisplayName() { return "JUnit `assertFalse` to AssertJ"; @@ -44,66 +45,34 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertFalseToAssertThatVisitor()); - } - - public static class AssertFalseToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_FALSE = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertFalse(boolean, ..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_FALSE.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_FALSE_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_FALSE_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any(boolean)}).isFalse();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + } - List args = method.getArguments(); - Expression actual = args.get(0); - - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any(boolean)}).isFalse();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - } else { Expression message = args.get(1); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(String)}).isFalse();") : - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(java.util.function.Supplier)}).isFalse();"); - - method = template + return JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any()}).isFalse();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java index 6a727a403..f5921f80c 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java @@ -46,30 +46,30 @@ public TreeVisitor getVisitor() { return Preconditions.check(new UsesMethod<>(ASSERT_INSTANCE_OF_MATCHER), new JavaIsoVisitor() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation md = super.visitMethodInvocation(method, ctx); - if (!ASSERT_INSTANCE_OF_MATCHER.matches(md)) { - return md; + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_INSTANCE_OF_MATCHER.matches(mi)) { + return mi; } maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); maybeRemoveImport("org.junit.jupiter.api.Assertions"); - Expression expectedType = md.getArguments().get(0); - Expression actualValue = md.getArguments().get(1); - if (md.getArguments().size() == 2) { + Expression expected = mi.getArguments().get(0); + Expression actual = mi.getArguments().get(1); + if (mi.getArguments().size() == 2) { return JavaTemplate.builder("assertThat(#{any()}).isInstanceOf(#{any()});") .staticImports("org.assertj.core.api.Assertions.assertThat") .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply(getCursor(), method.getCoordinates().replace(), actualValue, expectedType); + .apply(getCursor(), method.getCoordinates().replace(), actual, expected); } - Expression messageOrSupplier = md.getArguments().get(2); + Expression messageOrSupplier = mi.getArguments().get(2); return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isInstanceOf(#{any()});") .staticImports("org.assertj.core.api.Assertions.assertThat") .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply(getCursor(), method.getCoordinates().replace(), actualValue, messageOrSupplier, expectedType); + .apply(getCursor(), method.getCoordinates().replace(), actual, messageOrSupplier, expected); } }); } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java index d78506810..f8dfce9e7 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java @@ -23,7 +23,7 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -33,6 +33,10 @@ public class JUnitAssertNotEqualsToAssertThat extends Recipe { + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher ASSERT_NOT_EQUALS_MATCHER = new MethodMatcher(JUNIT + " assertNotEquals(..)", true); + @Override public String getDisplayName() { return "JUnit `assertNotEquals` to AssertJ"; @@ -45,117 +49,64 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertNotEqualsToAssertThatVisitor()); - } - - public static class AssertNotEqualsToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_EQUALS = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertNotEquals(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_EQUALS.matches(method)) { - return method; - } - - List args = method.getArguments(); - - Expression expected = args.get(0); - Expression actual = args.get(1); - - if (args.size() == 2) { - method = JavaTemplate.builder("assertThat(#{any()}).isNotEqualTo(#{any()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - expected - ); - } else if (args.size() == 3 && !isFloatingPointType(args.get(2))) { - Expression message = args.get(2); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotEqualTo(#{any()});") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNotEqualTo(#{any()});"); - + return Preconditions.check(new UsesMethod<>(ASSERT_NOT_EQUALS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_NOT_EQUALS_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport(ASSERTJ, "assertThat", false); + maybeRemoveImport(JUNIT); + + List args = mi.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isNotEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected); + } + if (args.size() == 3 && isFloatingPointType(args.get(2))) { + maybeAddImport(ASSERTJ, "within", false); + return JavaTemplate.builder("assertThat(#{any()}).isNotCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected, args.get(2)); + } + if (args.size() == 3) { + Expression message = args.get(2); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNotEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected); + } + + maybeAddImport(ASSERTJ, "within", false); - method = template - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected - ); - } else if (args.size() == 3) { - method = JavaTemplate.builder("assertThat(#{any()}).isNotCloseTo(#{any()}, within(#{any()}));") - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - expected, - args.get(2) - ); - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - } else { Expression message = args.get(3); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotCloseTo(#{any()}, within(#{any()}));") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNotCloseTo(#{any()}, within(#{any()}));"); - - method = template - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNotCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected, - args.get(2) - ); - - maybeAddImport("org.assertj.core.api.Assertions", "within", false); + .apply(getCursor(), method.getCoordinates().replace(), actual, message, expected, args.get(2)); } - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); + private boolean isFloatingPointType(Expression expression) { + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); + if (fullyQualified != null) { + String typeName = fullyQualified.getFullyQualifiedName(); + return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + } - return method; - } - - private static boolean isFloatingPointType(Expression expression) { - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); - if (fullyQualified != null) { - String typeName = fullyQualified.getFullyQualifiedName(); - return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); + return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; } - - JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); - return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java index dea4c6ffe..f6d4696ad 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java @@ -23,15 +23,16 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertNotNullToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_NOT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertNotNull(..)", true); + @Override public String getDisplayName() { return "JUnit `assertNotNull` to AssertJ"; @@ -44,68 +45,35 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertNotNullToAssertThatVisitor()); - } - - public static class AssertNotNullToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_NOT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertNotNull(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_NOT_NULL_MATCHER.matches(method)) { - return method; - } - - List args = method.getArguments(); - Expression actual = args.get(0); + return Preconditions.check(new UsesMethod<>(ASSERT_NOT_NULL_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_NOT_NULL_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any()}).isNotNull();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + + } - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any()}).isNotNull();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - - } else { Expression message = args.get(1); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotNull();") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNotNull();"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNotNull();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - //And if there are no longer references to the JUnit assertions class, we can remove the import. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java index c2c916f20..7dc9586e8 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java @@ -23,15 +23,16 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertNullToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertNull(..)", true); + @Override public String getDisplayName() { return "JUnit `assertNull` to AssertJ"; @@ -44,67 +45,34 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertNullToAssertThatVisitor()); - } - - public static class AssertNullToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertNull(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_NULL_MATCHER.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_NULL_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_NULL_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any()}).isNull();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + } - List args = method.getArguments(); - Expression actual = args.get(0); - - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any()}).isNull();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - } else { Expression message = args.get(1); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNull();") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNull();"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNull();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - // Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java index 1241d70e0..88a3a584e 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java @@ -23,15 +23,16 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertSameToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_SAME_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertSame(..)", true); + @Override public String getDisplayName() { return "JUnit `assertSame` to AssertJ"; @@ -44,70 +45,35 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertSameToAssertThatVisitor()); - } - - public static class AssertSameToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_SAME_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertSame(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_SAME_MATCHER.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_SAME_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_SAME_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isSameAs(#{any()});") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected); + } - List args = method.getArguments(); - Expression expected = args.get(0); - Expression actual = args.get(1); - - if (args.size() == 2) { - method = JavaTemplate.builder("assertThat(#{any()}).isSameAs(#{any()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - expected - ); - } else { Expression message = args.get(2); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isSameAs(#{any()});") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isSameAs(#{any()});"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isSameAs(#{any()});") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected); } - - // Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java index 6a880f816..0960100d0 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java @@ -30,6 +30,9 @@ public class JUnitAssertThrowsToAssertExceptionType extends Recipe { + private static final MethodMatcher ASSERT_THROWS_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertThrows(..)"); + private static final JavaType THROWING_CALLABLE_TYPE = JavaType.buildType("org.assertj.core.api.ThrowableAssert.ThrowingCallable"); + @Override public String getDisplayName() { return "JUnit AssertThrows to AssertJ exceptionType"; @@ -42,47 +45,38 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesMethod<>("org.junit.jupiter.api.Assertions assertThrows(..)"), new AssertExceptionTypeVisitor()); - } - - private static class AssertExceptionTypeVisitor extends JavaIsoVisitor { - private static final MethodMatcher ASSERT_THROWS_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertThrows(..)"); - private static final JavaType THROWING_CALLABLE_TYPE = JavaType.buildType("org.assertj.core.api.ThrowableAssert.ThrowingCallable"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); - if (ASSERT_THROWS_MATCHER.matches(mi) && - mi.getArguments().size() == 2 && - getCursor().getParentTreeCursor().getValue() instanceof J.Block) { - J executable = mi.getArguments().get(1); - if (executable instanceof J.Lambda) { - executable = ((J.Lambda) executable).withType(THROWING_CALLABLE_TYPE); - } else if (executable instanceof J.MemberReference) { - executable = ((J.MemberReference) executable).withType(THROWING_CALLABLE_TYPE); - } else { - executable = null; - } + return Preconditions.check(new UsesMethod<>(ASSERT_THROWS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (ASSERT_THROWS_MATCHER.matches(mi) && + mi.getArguments().size() == 2 && + getCursor().getParentTreeCursor().getValue() instanceof J.Block) { + J executable = mi.getArguments().get(1); + if (executable instanceof J.Lambda) { + executable = ((J.Lambda) executable).withType(THROWING_CALLABLE_TYPE); + } else if (executable instanceof J.MemberReference) { + executable = ((J.MemberReference) executable).withType(THROWING_CALLABLE_TYPE); + } else { + executable = null; + } - if (executable != null) { - mi = JavaTemplate - .builder("assertThatExceptionOfType(#{any(java.lang.Class)}).isThrownBy(#{any(org.assertj.core.api.ThrowableAssert.ThrowingCallable)})") - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) - .staticImports("org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType") - .build() - .apply( - getCursor(), - mi.getCoordinates().replace(), - mi.getArguments().get(0), executable - ); - maybeAddImport("org.assertj.core.api.AssertionsForClassTypes", "assertThatExceptionOfType", false); - maybeRemoveImport("org.junit.jupiter.api.Assertions.assertThrows"); - maybeRemoveImport("org.junit.jupiter.api.Assertions"); + if (executable != null) { + mi = JavaTemplate + .builder("assertThatExceptionOfType(#{any(java.lang.Class)}).isThrownBy(#{any(org.assertj.core.api.ThrowableAssert.ThrowingCallable)})") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .staticImports("org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType") + .build() + .apply(getCursor(), mi.getCoordinates().replace(), mi.getArguments().get(0), executable); + maybeAddImport("org.assertj.core.api.AssertionsForClassTypes", "assertThatExceptionOfType", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions.assertThrows"); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); - doAfterVisit(new LambdaBlockToExpression().getVisitor()); + doAfterVisit(new LambdaBlockToExpression().getVisitor()); + } } + return mi; } - return mi; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java index b478716dc..8541a4f2c 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java @@ -23,14 +23,16 @@ 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.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertTrueToAssertThat extends Recipe { + + private static final MethodMatcher ASSERT_TRUE_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertTrue(boolean, ..)"); + @Override public String getDisplayName() { return "JUnit `assertTrue` to AssertJ"; @@ -43,67 +45,34 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertTrueToAssertThatVisitor()); - } - - public static class AssertTrueToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_TRUE = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertTrue(boolean, ..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_TRUE.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_TRUE_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_TRUE_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any(boolean)}).isTrue();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + } - List args = method.getArguments(); - Expression actual = args.get(0); - - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any(boolean)}).isTrue();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - } else { Expression message = args.get(1); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(String)}).isTrue();") : - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(java.util.function.Supplier)}).isTrue();"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isTrue();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java index b93ee3c9e..aef7ddfde 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java @@ -20,14 +20,20 @@ import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.*; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; +import java.util.Collections; import java.util.List; public class JUnitFailToAssertJFail extends Recipe { + + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher FAIL_MATCHER = new MethodMatcher(JUNIT + " fail(..)"); + @Override public String getDisplayName() { return "JUnit fail to AssertJ"; @@ -40,118 +46,70 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new JUnitFailToAssertJFailVisitor()); - } - - public static class JUnitFailToAssertJFailVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_FAIL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " fail(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = method; - - if (!JUNIT_FAIL_MATCHER.matches(m)) { - return m; - } - - List args = m.getArguments(); + return Preconditions.check(new UsesMethod<>(FAIL_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = method; + if (!FAIL_MATCHER.matches(mi)) { + return mi; + } - if (args.size() == 1) { - // fail(), fail(String), fail(Supplier), fail(Throwable) - if (args.get(0) instanceof J.Empty) { - m = JavaTemplate.builder("org.assertj.core.api.Assertions.fail(\"\");") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), m.getCoordinates().replace()); - } else if (args.get(0) instanceof J.Literal || - TypeUtils.isAssignableTo("java.lang.String", args.get(0).getType())) { - m = JavaTemplate.builder("org.assertj.core.api.Assertions.fail(#{any()});") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - m.getCoordinates().replace(), - args.get(0) - ); + List args = mi.getArguments(); + if (args.size() == 1) { + // fail(), fail(String), fail(Supplier), fail(Throwable) + if (args.get(0) instanceof J.Empty) { + mi = JavaTemplate.builder(ASSERTJ + ".fail(\"\");") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace()); + } else if (args.get(0) instanceof J.Literal || + TypeUtils.isAssignableTo("java.lang.String", args.get(0).getType())) { + mi = JavaTemplate.builder(ASSERTJ + ".fail(#{any()});") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), args.get(0)); + } else { + mi = JavaTemplate.builder(ASSERTJ + ".fail(\"\", #{any()});") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), args.get(0)); + } } else { - m = JavaTemplate.builder("org.assertj.core.api.Assertions.fail(\"\", #{any()});") - .javaParser(assertionsParser(ctx)) + // fail(String, Throwable) + String anyArgs = String.join(",", Collections.nCopies(args.size(), "#{any()}")); + mi = JavaTemplate.builder(ASSERTJ + ".fail(" + anyArgs + ");") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - m.getCoordinates().replace(), - args.get(0) - ); + .apply(getCursor(), mi.getCoordinates().replace(), args.toArray()); } - } else { - // fail(String, Throwable) - StringBuilder templateBuilder = new StringBuilder("org.assertj.core.api.Assertions.fail("); - for (int i = 0; i < args.size(); i++) { - templateBuilder.append("#{any()}"); - if (i < args.size() - 1) { - templateBuilder.append(", "); - } - } - templateBuilder.append(");"); - m = JavaTemplate.builder(templateBuilder.toString()) - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - m.getCoordinates().replace(), - args.toArray() - ); + doAfterVisit(new RemoveUnusedImports().getVisitor()); + doAfterVisit(new UnqualifiedMethodInvocations()); + return mi; } - doAfterVisit(new RemoveUnusedImports().getVisitor()); - doAfterVisit(new UnqualifiedMethodInvocations()); - return m; - } + class UnqualifiedMethodInvocations extends JavaIsoVisitor { + private final MethodMatcher INTERNAL_FAIL_MATCHER = new MethodMatcher(ASSERTJ + " fail(..)"); - private static class UnqualifiedMethodInvocations extends JavaIsoVisitor { - private static final MethodMatcher ASSERTJ_FAIL_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions" + " fail(..)"); + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!INTERNAL_FAIL_MATCHER.matches(mi)) { + return mi; + } - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!ASSERTJ_FAIL_MATCHER.matches(method)) { - return method; - } + maybeAddImport(ASSERTJ, "fail", false); + maybeRemoveImport(JUNIT + ".fail"); - StringBuilder templateBuilder = new StringBuilder("fail("); - List arguments = method.getArguments(); - for (int i = 0; i < arguments.size(); i++) { - templateBuilder.append("#{any()}"); - if (i < arguments.size() - 1) { - templateBuilder.append(", "); - } + List arguments = mi.getArguments(); + String anyArgs = String.join(",", Collections.nCopies(arguments.size(), "#{any()}")); + return JavaTemplate.builder("fail(" + anyArgs + ");") + .staticImports(ASSERTJ + ".fail") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), arguments.toArray()); } - templateBuilder.append(");"); - - method = JavaTemplate.builder(templateBuilder.toString()) - .staticImports("org.assertj.core.api.Assertions" + ".fail") - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - arguments.toArray() - ); - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "fail", false); - maybeRemoveImport("org.junit.jupiter.api.Assertions.fail"); - return super.visitMethodInvocation(method, ctx); } - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertion.java b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertion.java index 1d3685955..de1d6be55 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertion.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertion.java @@ -33,6 +33,8 @@ @NoArgsConstructor public class SimplifyAssertJAssertion extends Recipe { + private static final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); + @Option(displayName = "AssertJ assertion", description = "The assertion method that should be replaced.", example = "hasSize", @@ -67,38 +69,34 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return new ShorthenChainedAssertJAssertionsVisitor(); - } - - private class ShorthenChainedAssertJAssertionsVisitor extends JavaIsoVisitor { - private final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); - private final MethodMatcher ASSERT_TO_REPLACE = new MethodMatcher("org.assertj.core.api.* " + assertToReplace + "(..)"); + final MethodMatcher assertToReplace = new MethodMatcher("org.assertj.core.api.* " + this.assertToReplace + "(..)"); + return new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); + // Match the end of the chain first, then the select to avoid matching the wrong method chain + if (!assertToReplace.matches(mi) || !ASSERT_THAT_MATCHER.matches(mi.getSelect())) { + return mi; + } - // Match the end of the chain first, then the select to avoid matching the wrong method chain - if (!ASSERT_TO_REPLACE.matches(mi) || !ASSERT_THAT_MATCHER.matches(mi.getSelect())) { - return mi; - } + // Compare argument with passed in literal + if (!(mi.getArguments().get(0) instanceof J.Literal) || + !literalArgument.equals(((J.Literal) mi.getArguments().get(0)).getValueSource())) { // Implies "null" is `null` + return mi; + } - // Compare argument with passed in literal - if (!(mi.getArguments().get(0) instanceof J.Literal) || - !literalArgument.equals(((J.Literal) mi.getArguments().get(0)).getValueSource())) { // Implies "null" is `null` - return mi; - } + // Check argument type of assertThat + if (!TypeUtils.isAssignableTo(requiredType, ((J.MethodInvocation) mi.getSelect()).getArguments().get(0).getType())) { + return mi; + } - // Check argument type of assertThat - if (!TypeUtils.isAssignableTo(requiredType, ((J.MethodInvocation) mi.getSelect()).getArguments().get(0).getType())) { - return mi; + // Assume zero argument replacement method + return JavaTemplate.builder("#{any()}." + dedicatedAssertion + "()") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), mi.getSelect()); } - - // Assume zero argument replacement method - return JavaTemplate.builder("#{any()}." + dedicatedAssertion + "()") - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) - .build() - .apply(getCursor(), mi.getCoordinates().replace(), mi.getSelect()); - } + }; } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java index ba431a43c..e6d37912e 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java @@ -35,6 +35,7 @@ @AllArgsConstructor @NoArgsConstructor public class SimplifyChainedAssertJAssertion extends Recipe { + @Option(displayName = "AssertJ chained assertion", description = "The chained AssertJ assertion to move to dedicated assertion.", example = "equals", @@ -81,95 +82,89 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return new SimplifyChainedAssertJAssertionsVisitor(); - } - - private class SimplifyChainedAssertJAssertionsVisitor extends JavaIsoVisitor { - private final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); - private final MethodMatcher CHAINED_ASSERT_MATCHER = new MethodMatcher("java..* " + chainedAssertion + "(..)"); - private final MethodMatcher ASSERT_TO_REPLACE = new MethodMatcher("org.assertj.core.api.* " + assertToReplace + "(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); - - // assert has correct assertion - if (!ASSERT_TO_REPLACE.matches(mi) || mi.getArguments().size() != 1) { - return mi; + MethodMatcher assertThatMatcher = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); + MethodMatcher chainedAssertMatcher = new MethodMatcher("java..* " + chainedAssertion + "(..)"); + MethodMatcher assertToReplace = new MethodMatcher("org.assertj.core.api.* " + this.assertToReplace + "(..)"); + + return new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); + + // assert has correct assertion + if (!assertToReplace.matches(mi) || mi.getArguments().size() != 1) { + return mi; + } + + // assertThat has method call + J.MethodInvocation assertThat = (J.MethodInvocation) mi.getSelect(); + if (!assertThatMatcher.matches(assertThat) || !(assertThat.getArguments().get(0) instanceof J.MethodInvocation)) { + return mi; + } + + J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThat.getArguments().get(0); + if (!chainedAssertMatcher.matches(assertThatArg)) { + return mi; + } + + // Extract the actual argument for the new assertThat call + Expression actual = assertThatArg.getSelect() != null ? assertThatArg.getSelect() : assertThatArg; + if (!TypeUtils.isAssignableTo(requiredType, actual.getType())) { + return mi; + } + List arguments = new ArrayList<>(); + arguments.add(actual); + + String template = getStringTemplateAndAppendArguments(assertThatArg, mi, arguments); + return JavaTemplate.builder(String.format(template, dedicatedAssertion)) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5.9", "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), arguments.toArray()); } - // assertThat has method call - J.MethodInvocation assertThat = (J.MethodInvocation) mi.getSelect(); - if (!ASSERT_THAT_MATCHER.matches(assertThat) || !(assertThat.getArguments().get(0) instanceof J.MethodInvocation)) { - return mi; + private String getStringTemplateAndAppendArguments(J.MethodInvocation assertThatArg, J.MethodInvocation methodToReplace, List arguments) { + Expression assertThatArgument = assertThatArg.getArguments().get(0); + Expression methodToReplaceArgument = methodToReplace.getArguments().get(0); + boolean assertThatArgumentIsEmpty = assertThatArgument instanceof J.Empty; + boolean methodToReplaceArgumentIsEmpty = methodToReplaceArgument instanceof J.Empty; + + // If both arguments are empty, then the select is already added to the arguments list, and we use a minimal template + if (assertThatArgumentIsEmpty && methodToReplaceArgumentIsEmpty) { + return "assertThat(#{any()}).%s()"; + } + + // If both arguments are not empty, then we add both to the arguments to the arguments list, and return a template with two arguments + if (!assertThatArgumentIsEmpty && !methodToReplaceArgumentIsEmpty) { + // This should only happen for map assertions using a key and value + arguments.add(assertThatArgument); + arguments.add(methodToReplaceArgument); + return "assertThat(#{any()}).%s(#{any()}, #{any()})"; + } + + // If either argument is empty, we choose which one to add to the arguments list, and optionally extract the select + arguments.add(extractEitherArgument(assertThatArgumentIsEmpty, assertThatArgument, methodToReplaceArgument)); + + // Special case for Path.of() assertions + if ("java.nio.file.Path".equals(requiredType) && dedicatedAssertion.contains("Raw") && + TypeUtils.isAssignableTo("java.lang.String", assertThatArgument.getType())) { + maybeAddImport("java.nio.file.Path"); + return "assertThat(#{any()}).%s(Path.of(#{any()}))"; + } + + return "assertThat(#{any()}).%s(#{any()})"; } - J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThat.getArguments().get(0); - if (!CHAINED_ASSERT_MATCHER.matches(assertThatArg)) { - return mi; - } - - // Extract the actual argument for the new assertThat call - Expression actual = assertThatArg.getSelect() != null ? assertThatArg.getSelect() : assertThatArg; - if (!TypeUtils.isAssignableTo(requiredType, actual.getType())) { - return mi; - } - List arguments = new ArrayList<>(); - arguments.add(actual); - - String template = getStringTemplateAndAppendArguments(assertThatArg, mi, arguments); - return applyTemplate(String.format(template, dedicatedAssertion), arguments, mi, ctx); - } - - private J.MethodInvocation applyTemplate(String formattedTemplate, List arguments, J.MethodInvocation mi, ExecutionContext ctx) { - return JavaTemplate.builder(formattedTemplate) - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5.9", "assertj-core-3.24")) - .build() - .apply(getCursor(), mi.getCoordinates().replace(), arguments.toArray()); - } - - private String getStringTemplateAndAppendArguments(J.MethodInvocation assertThatArg, J.MethodInvocation methodToReplace, List arguments) { - Expression assertThatArgument = assertThatArg.getArguments().get(0); - Expression methodToReplaceArgument = methodToReplace.getArguments().get(0); - boolean assertThatArgumentIsEmpty = assertThatArgument instanceof J.Empty; - boolean methodToReplaceArgumentIsEmpty = methodToReplaceArgument instanceof J.Empty; - - // If both arguments are empty, then the select is already added to the arguments list, and we use a minimal template - if (assertThatArgumentIsEmpty && methodToReplaceArgumentIsEmpty) { - return "assertThat(#{any()}).%s()"; - } - - // If both arguments are not empty, then we add both to the arguments to the arguments list, and return a template with two arguments - if (!assertThatArgumentIsEmpty && !methodToReplaceArgumentIsEmpty) { - // This should only happen for map assertions using a key and value - arguments.add(assertThatArgument); - arguments.add(methodToReplaceArgument); - return "assertThat(#{any()}).%s(#{any()}, #{any()})"; - } - - // If either argument is empty, we choose which one to add to the arguments list, and optionally extract the select - arguments.add(extractEitherArgument(assertThatArgumentIsEmpty, assertThatArgument, methodToReplaceArgument)); - - // Special case for Path.of() assertions - if ("java.nio.file.Path".equals(requiredType) && dedicatedAssertion.contains("Raw") && - TypeUtils.isAssignableTo("java.lang.String", assertThatArgument.getType())) { - maybeAddImport("java.nio.file.Path"); - return "assertThat(#{any()}).%s(Path.of(#{any()}))"; - } - - return "assertThat(#{any()}).%s(#{any()})"; - } - - private Expression extractEitherArgument(boolean assertThatArgumentIsEmpty, Expression assertThatArgument, Expression methodToReplaceArgument) { - if (assertThatArgumentIsEmpty) { - return methodToReplaceArgument; - } - // Only on the assertThat argument do we possibly replace the argument with the select; such as list.size() -> list - if (CHAINED_ASSERT_MATCHER.matches(assertThatArgument)) { - return Objects.requireNonNull(((J.MethodInvocation) assertThatArgument).getSelect()); + private Expression extractEitherArgument(boolean assertThatArgumentIsEmpty, Expression assertThatArgument, Expression methodToReplaceArgument) { + if (assertThatArgumentIsEmpty) { + return methodToReplaceArgument; + } + // Only on the assertThat argument do we possibly replace the argument with the select; such as list.size() -> list + if (chainedAssertMatcher.matches(assertThatArgument)) { + return Objects.requireNonNull(((J.MethodInvocation) assertThatArgument).getSelect()); + } + return assertThatArgument; } - return assertThatArgument; - } + }; } } diff --git a/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java index 1b5e7fd24..85b9f6a01 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java @@ -44,12 +44,10 @@ void toAssertExceptionOfType() { java( """ import static org.junit.jupiter.api.Assertions.assertThrows; - + public class SimpleExpectedExceptionTest { public void throwsExceptionWithSpecificType() { - assertThrows(NullPointerException.class, () -> { - foo(); - }); + assertThrows(NullPointerException.class, () -> foo()); } void foo() { throw new NullPointerException(); @@ -58,11 +56,10 @@ void foo() { """, """ import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; - + public class SimpleExpectedExceptionTest { public void throwsExceptionWithSpecificType() { - assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> - foo()); + assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> foo()); } void foo() { throw new NullPointerException(); From eb1e066ff0eab4d1766eb8e6787e4197d389eb41 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl Date: Mon, 11 Nov 2024 16:20:49 +0000 Subject: [PATCH 17/20] refactor: Update Gradle wrapper Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.gradle.UpdateGradleWrapper?organizationId=T3BlblJld3JpdGU%3D#defaults=W3sibmFtZSI6ImFkZElmTWlzc2luZyIsInZhbHVlIjoiRmFsc2UifV0= Co-authored-by: Moderne --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7c553f646..571bbea59 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26 +distributionSha256Sum=57dafb5c2622c6cc08b993c85b7c06956a2f53536432a30ead46166dbca0f1e9 From 9a5b4448ac33f470743bf428d11e9023a062eb76 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 12 Nov 2024 12:19:21 +0100 Subject: [PATCH 18/20] Document that the JUnit to AssertJ migration includes Jupiter --- src/main/resources/META-INF/rewrite/assertj.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/resources/META-INF/rewrite/assertj.yml b/src/main/resources/META-INF/rewrite/assertj.yml index 173f603bb..302b76692 100644 --- a/src/main/resources/META-INF/rewrite/assertj.yml +++ b/src/main/resources/META-INF/rewrite/assertj.yml @@ -16,7 +16,7 @@ --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.assertj.Assertj -displayName: Assertj +displayName: AssertJ best practices description: Migrates JUnit asserts to AssertJ and applies best practices to assertions. tags: - testing @@ -449,7 +449,10 @@ recipeList: type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.assertj.JUnitToAssertj displayName: Migrate JUnit asserts to AssertJ -description: AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability. Converts assertions from `org.junit.jupiter.api.Assertions` to `org.assertj.core.api.Assertions`. +description: >- + AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability. + Converts assertions from `org.junit.jupiter.api.Assertions` to `org.assertj.core.api.Assertions`. + Will convert JUnit 4 to JUnit Jupiter if necessary to match and modify assertions. tags: - testing - assertj From ef62c319fd5b4b77b030fcb409b11402debdf121 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 12 Nov 2024 14:09:53 -0800 Subject: [PATCH 19/20] Create a mockito upgrade recipe that just does 4 -> 5, so it can be included in Spring 3.1 upgrade. Both "Migrate to Spring Boot 2.4" and "Mockito 5.x upgrade" include "Mockito 4.x upgrade" So if we add "Mockito 5.x upgrade" into "Migrate to Spring Boot 3.1" then "Mockito 4.x upgrade", and all its constituent recipes, will be run twice. The performance cost of this is not dire, but the spring boot migration is already a very large recipe with ~2k steps. --- src/main/resources/META-INF/rewrite/mockito.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/rewrite/mockito.yml b/src/main/resources/META-INF/rewrite/mockito.yml index dc569f5e0..e75ef742d 100644 --- a/src/main/resources/META-INF/rewrite/mockito.yml +++ b/src/main/resources/META-INF/rewrite/mockito.yml @@ -31,6 +31,17 @@ type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.mockito.Mockito1to5Migration displayName: Mockito 5.x upgrade description: Upgrade Mockito from 1.x to 5.x. +tags: + - testing + - mockito +recipeList: + - org.openrewrite.java.testing.mockito.Mockito1to4Migration + - org.openrewrite.java.testing.mockito.Mockito4to5Only +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.mockito.Mockito4to5Only +displayName: Mockito 4 to 5.x upgrade only +description: Upgrade Mockito from 4.x to 5.x. Does not include 1.x to 4.x migration. tags: - testing - mockito @@ -49,7 +60,6 @@ recipeList: artifactId: byte-buddy* newVersion: 1.15.x - org.openrewrite.maven.RemoveDuplicateDependencies - --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.mockito.Mockito1to4Migration From a04dd14271fdd7f1fc88a183b3bd5d1e67d92164 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 13 Nov 2024 16:59:14 +0100 Subject: [PATCH 20/20] Drop workaround following upstream fix https://github.com/openrewrite/rewrite/commit/f50e53baafaeaee463d434be3fa1353c594d1c44 --- .../java/testing/junit5/MockitoJUnitToMockitoExtension.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java index 16939a286..6836be849 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java @@ -129,11 +129,6 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex maybeAddImport("org.mockito.junit.jupiter.MockitoSettings", false); maybeAddImport("org.mockito.quality.Strictness", false); } - - // Workaround first modifier incorrectly getting a trailing space as part of the prefix - cd = cd.withModifiers(ListUtils.mapFirst(cd.getModifiers(), - modifier -> modifier.withPrefix(modifier.getPrefix().withWhitespace( - modifier.getPrefix().getLastWhitespace().replaceAll(" $", ""))))); } }