Skip to content

Commit

Permalink
Feat find unit tests (#669)
Browse files Browse the repository at this point in the history
* find unit tests

* Add to datatable method invocations used in unit tests

* Change accumulator unit test and its method invocations to a hasmap

* polish

* polish

* polish

* Apply formatter

* Light polish

* Use preconditions to tell tests and non tests apart

* Apply correct license headers

* Further polish

* Loop over entrySet

* Update src/main/java/org/openrewrite/java/testing/search/FindUnitTests.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: Tim te Beek <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 27, 2025
1 parent fcacbdd commit 6aa80c9
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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<FindUnitTestTable.Row> {
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;
}
}

117 changes: 117 additions & 0 deletions src/main/java/org/openrewrite/java/testing/search/FindUnitTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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<FindUnitTests.Accumulator> {

@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<UnitTest, Set<J.MethodInvocation>> unitTestAndTheirMethods = new HashMap<>();
}

@Override
public Accumulator getInitialValue(ExecutionContext ctx) {
return new Accumulator();
}

@Override
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
JavaVisitor<ExecutionContext> scanningVisitor = new JavaVisitor<ExecutionContext>() {
@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<?, ExecutionContext> getVisitor(Accumulator acc) {
JavaVisitor<ExecutionContext> tableRowVisitor = new JavaVisitor<ExecutionContext>() {
@Override
public J visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) {
for (Map.Entry<UnitTest, Set<J.MethodInvocation>> 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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* 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
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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() {
}
}
"""
)
);
}
}
}

0 comments on commit 6aa80c9

Please sign in to comment.