diff --git a/src/main/java/org/openrewrite/java/testing/search/FindUnitTestTable.java b/src/main/java/org/openrewrite/java/testing/search/FindUnitTestTable.java new file mode 100644 index 000000000..1253681ac --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/search/FindUnitTestTable.java @@ -0,0 +1,53 @@ +/* + * Copyright 2025 the original author or authors. + *

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

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

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.testing.search; + +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + + public class FindUnitTestTable extends DataTable { + public FindUnitTestTable(Recipe recipe) { + super(recipe, + recipe.getName(), + recipe.getDescription()); + } + + @Value + public static class Row { + @Column(displayName = "Full method name", + description = "The fully qualified name of the method declaration") + String fullyQualifiedMethodName; + + @Column(displayName = "Method name", + description = "The name of the method declaration") + String methodName; + + @Column(displayName = "Method invocation", + description = "How the method declaration is used as method invocation in a unit test.") + String methodInvocationExample; + + @Column(displayName = "Name of test", + description = "The name of the unit test where the method declaration is used.") + String nameOfTest; + + @Column(displayName = "Location of test", + description = "The location of the unit test where the method declaration is used.") + String locationOfTest; + } + } + diff --git a/src/main/java/org/openrewrite/java/testing/search/FindUnitTests.java b/src/main/java/org/openrewrite/java/testing/search/FindUnitTests.java new file mode 100644 index 000000000..2ca95a20a --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/search/FindUnitTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2025 the original author or authors. + *

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

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

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.testing.search; + +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.ScanningRecipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.search.IsLikelyNotTest; +import org.openrewrite.java.search.IsLikelyTest; +import org.openrewrite.java.tree.J; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.singletonList; + +public class FindUnitTests extends ScanningRecipe { + + @Override + public String getDisplayName() { + return "Find unit tests"; + } + + @Override + public String getDescription() { + return "Produces a data table showing examples of how methods declared get used in unit tests."; + } + + transient FindUnitTestTable unitTestTable = new FindUnitTestTable(this); + + public static class Accumulator { + Map> unitTestAndTheirMethods = new HashMap<>(); + } + + @Override + public Accumulator getInitialValue(ExecutionContext ctx) { + return new Accumulator(); + } + + @Override + public TreeVisitor getScanner(Accumulator acc) { + JavaVisitor scanningVisitor = new JavaVisitor() { + @Override + public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + // get the method declaration the method invocation is in + J.MethodDeclaration methodDeclaration = getCursor().firstEnclosing(J.MethodDeclaration.class); + if (methodDeclaration != null && + methodDeclaration.getLeadingAnnotations().stream() + .filter(o -> o.getAnnotationType() instanceof J.Identifier) + .anyMatch(o -> "Test".equals(o.getSimpleName()))) { + UnitTest unitTest = new UnitTest( + getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class).getType().getFullyQualifiedName(), + methodDeclaration.getSimpleName(), + methodDeclaration.printTrimmed(getCursor())); + acc.unitTestAndTheirMethods.merge(unitTest, + new HashSet<>(singletonList(method)), + (a, b) -> { + a.addAll(b); + return a; + }); + } + return super.visitMethodInvocation(method, ctx); + } + }; + return Preconditions.check(new IsLikelyTest().getVisitor(), scanningVisitor); + } + + @Override + public TreeVisitor getVisitor(Accumulator acc) { + JavaVisitor tableRowVisitor = new JavaVisitor() { + @Override + public J visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) { + for (Map.Entry> entry : acc.unitTestAndTheirMethods.entrySet()) { + for (J.MethodInvocation method : entry.getValue()) { + if (method.getSimpleName().equals(methodDeclaration.getSimpleName())) { + unitTestTable.insertRow(ctx, new FindUnitTestTable.Row( + methodDeclaration.getName().toString(), + methodDeclaration.getSimpleName(), + method.printTrimmed(getCursor()), + entry.getKey().getClazz(), + entry.getKey().getUnitTestName() + )); + } + } + } + return super.visitMethodDeclaration(methodDeclaration, ctx); + } + }; + return Preconditions.check(new IsLikelyNotTest().getVisitor(), tableRowVisitor); + } + +} + +@Value +class UnitTest { + String clazz; + String unitTestName; + String unitTest; +} diff --git a/src/main/java/org/openrewrite/java/testing/search/package-info.java b/src/main/java/org/openrewrite/java/testing/search/package-info.java new file mode 100644 index 000000000..72687ca5a --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/search/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2024 the original author or authors. + *

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

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

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +package org.openrewrite.java.testing.search; + +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/org/openrewrite/java/testing/search/FindUnitTestsTest.java b/src/test/java/org/openrewrite/java/testing/search/FindUnitTestsTest.java new file mode 100644 index 000000000..d4008b124 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/search/FindUnitTestsTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2025 the original author or authors. + *

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

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

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.testing.search; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Nested; +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.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.java; + +class FindUnitTestsTest implements RewriteTest { + + @Language("java") + private static final String CLASS_FOO = """ + package foo; + + public class Foo { + public void bar() { + } + public void baz() { + } + } + """; + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindUnitTests()) + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), + "junit-jupiter-api-5.9")); + } + + @DocumentExample + @Test + void dataTable() { + rewriteRun( + spec -> spec.dataTable(FindUnitTestTable.Row.class, rows -> assertThat(rows) + .extracting(FindUnitTestTable.Row::getFullyQualifiedMethodName) + .containsExactly("bar", "baz")), + java(CLASS_FOO), + //language=java + java( + """ + import foo.Foo; + import org.junit.jupiter.api.Test; + + public class FooTest { + @Test + public void test() { + Foo foo = new Foo(); + foo.bar(); + foo.baz(); + } + } + """ + ) + ); + } + + @Nested + class NotFound { + + @Test + void notATest() { + //language=java + rewriteRun( + spec -> spec.afterRecipe(run -> assertThat(run.getDataTables()).hasSize(1)), // stats table + java(CLASS_FOO), + java( + """ + import foo.Foo; + + public class FooTest { + public void test() { + new Foo().bar(); + } + } + """ + ) + ); + } + + @Test + void methodFromTest() { + //language=java + rewriteRun( + spec -> spec.afterRecipe(run -> assertThat(run.getDataTables()).hasSize(1)), // stats table + java(CLASS_FOO), + java( + """ + import org.junit.jupiter.api.Test; + + public class FooTest { + @Test + public void test() { + beep(); + } + + public void beep() { + } + } + """ + ) + ); + } + } +}